diff --git a/.github/CLA.md b/.github/CLA.md index fb829b3434191..840ddc9fdb05c 100644 --- a/.github/CLA.md +++ b/.github/CLA.md @@ -2,7 +2,7 @@ This license is for your protection as a Contributor as well as the protection of the maintainers of the LocalStack software; it does not change your rights to use your own Contributions for any other purpose. In the following, the maintainers of LocalStack are referred to as "LocalStack". -You accept and agree to the following terms and conditions for Your present and future Contributions submitted to "LocalStack". Except for the license granted herein to LocalSack and recipients of software distributed by "LocalStack", You reserve all right, title, and interest in and to Your Contributions. +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to "LocalStack". Except for the license granted herein to LocalStack and recipients of software distributed by "LocalStack", You reserve all right, title, and interest in and to Your Contributions. 1. Definitions. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 47868d7b130ac..5691d5c485625 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,11 @@ blank_issues_enabled: true contact_links: - - name: 📖 LocalStack Documentation + - name: 📖 Contributing Guidelines + url: https://github.com/localstack/localstack/blob/main/docs/CONTRIBUTING.md + about: Please read before contributing + - name: 📖 LocalStack Documentation url: https://localstack.cloud/docs/getting-started/overview/ about: The LocalStack documentation may answer your questions! - - name: 💬 LocalStack Community Support (Slack) + - name: 💬 LocalStack Community Support (Slack) url: https://localstack.cloud/slack about: Please ask and answer questions here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ee3b8eecf5459..f7f407252989f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,24 +1,28 @@ - + - ## Motivation + - ## Changes - - +## Tests + -What's left to do: +## Related -- [ ] ... -- [ ] ... + diff --git a/.github/actions/build-image/action.yml b/.github/actions/build-image/action.yml index eeb8832cb4494..ec17d18e80a25 100644 --- a/.github/actions/build-image/action.yml +++ b/.github/actions/build-image/action.yml @@ -19,7 +19,7 @@ runs: # This GH Action requires localstack repo in 'localstack' dir + full git history (fetch-depth: 0) steps: - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version-file: 'localstack/.python-version' diff --git a/.github/actions/load-localstack-docker-from-artifacts/action.yml b/.github/actions/load-localstack-docker-from-artifacts/action.yml index cb22c52682734..a86352b786a43 100644 --- a/.github/actions/load-localstack-docker-from-artifacts/action.yml +++ b/.github/actions/load-localstack-docker-from-artifacts/action.yml @@ -9,12 +9,12 @@ runs: using: "composite" steps: - name: Download Docker Image - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: localstack-docker-image-${{ inputs.platform }} - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version-file: '.python-version' cache: 'pip' diff --git a/.github/actions/setup-tests-env/action.yml b/.github/actions/setup-tests-env/action.yml index 95cd7fe359787..f33279972c9f2 100644 --- a/.github/actions/setup-tests-env/action.yml +++ b/.github/actions/setup-tests-env/action.yml @@ -4,7 +4,7 @@ runs: using: "composite" steps: - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version-file: '.python-version' cache: 'pip' diff --git a/.github/bot_templates/ASF_UPGRADE_PR.md b/.github/bot_templates/ASF_UPGRADE_PR.md index 567edf963fdd4..6eca11dca9e1f 100644 --- a/.github/bot_templates/ASF_UPGRADE_PR.md +++ b/.github/bot_templates/ASF_UPGRADE_PR.md @@ -1,6 +1,6 @@ # 🚀 ASF Update Report 🚀 This PR has been automatically generated to update the generated API stubs for our ASF services. -It uses the latest code-generator from the _master_ branch ([scaffold.py](https://github.com/localstack/localstack/blob/master/localstack/aws/scaffold.py)) and the latest _published_ version of [botocore](https://github.com/boto/botocore) to re-generate all API stubs which are already present in the `localstack.aws.api` module of the _master_ branch. +It uses the latest code-generator from the _main_ branch ([scaffold.py](https://github.com/localstack/localstack/blob/main/localstack/aws/scaffold.py)) and the latest _published_ version of [botocore](https://github.com/boto/botocore) to re-generate all API stubs which are already present in the `localstack.aws.api` module of the _main_ branch. ## 🔄 Updated Services This PR updates the following services: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3fd7b9f6a75e2..21c4e338fd3ce 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,11 +7,11 @@ updates: ignore: - dependency-name: "python" update-types: ["version-update:semver-major", "version-update:semver-minor"] - - dependency-name: "eclipse-temurin" - update-types: ["version-update:semver-major"] labels: - "area: dependencies" - "semver: patch" + - "docs: skip" + - "notes: skip" groups: docker-base-images: patterns: @@ -23,6 +23,8 @@ updates: labels: - "area: dependencies" - "semver: patch" + - "docs: skip" + - "notes: skip" groups: github-actions: patterns: diff --git a/.github/workflows/asf-updates.yml b/.github/workflows/asf-updates.yml index 69bf11a17e754..b378158091348 100644 --- a/.github/workflows/asf-updates.yml +++ b/.github/workflows/asf-updates.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Open Source - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -19,17 +19,17 @@ jobs: sudo apt-get update sudo apt-get install jq - - name: Set up Python 3.11 + - name: Set up Python id: setup-python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version-file: '.python-version' - name: Install release helper dependencies - run: pip install --upgrade setuptools setuptools_scm + run: python3 -m pip install --upgrade setuptools setuptools_scm uv - name: Cache LocalStack community dependencies (venv) - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .venv key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-venv-${{ hashFiles('requirements-dev.txt') }} @@ -44,29 +44,21 @@ jobs: - name: Update ASF APIs run: | - source .venv/bin/activate - python3 -m localstack.aws.scaffold upgrade - - - name: Format code - run: | - source .venv/bin/activate - # explicitly perform an unsafe fix to remove unused imports in the generated ASF APIs - ruff check --select F401 --unsafe-fixes --fix . --config "lint.preview = true" - make format-modified + make asf-regenerate - name: Check for changes id: check-for-changes run: | # Check if there are changed files and store the result in target/diff-check.log - # Check against the PR branch if it exists, otherwise against the master + # Check against the PR branch if it exists, otherwise against the main # Store the result in target/diff-check.log and store the diff count in the GitHub Action output "diff-count" mkdir -p target - (git diff --name-only origin/asf-auto-updates localstack-core/localstack/aws/api/ 2>/dev/null || git diff --name-only origin/master localstack-core/localstack/aws/api/ 2>/dev/null) | tee target/diff-check.log + (git diff --name-only origin/asf-auto-updates localstack-core/localstack/aws/api/ 2>/dev/null || git diff --name-only origin/main localstack-core/localstack/aws/api/ 2>/dev/null) | tee target/diff-check.log echo "diff-count=$(cat target/diff-check.log | wc -l)" >> $GITHUB_OUTPUT - # Store a (multiline-sanitized) list of changed services (compared to the master) in the GitHub Action output "changed-services" + # Store a (multiline-sanitized) list of changed services (compared to the main) in the GitHub Action output "changed-services" echo "changed-services<> $GITHUB_OUTPUT - echo "$(git diff --name-only origin/master localstack-core/localstack/aws/api/ | sed 's#localstack-core/localstack/aws/api/#- #g' | sed 's#/__init__.py##g' | sed 's/_/-/g')" >> $GITHUB_OUTPUT + echo "$(git diff --name-only origin/main localstack-core/localstack/aws/api/ | sed 's#localstack-core/localstack/aws/api/#- #g' | sed 's#/__init__.py##g' | sed 's/_/-/g')" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Update botocore and transitive pins @@ -76,17 +68,40 @@ jobs: source .venv/bin/activate # determine botocore version in venv BOTOCORE_VERSION=$(python -c "import botocore; print(botocore.__version__)"); - echo "Pinning botocore, boto3, and boto3-stubs to version $BOTOCORE_VERSION" + echo "Pinning botocore to version $BOTOCORE_VERSION" bin/release-helper.sh set-dep-ver botocore "==$BOTOCORE_VERSION" - bin/release-helper.sh set-dep-ver boto3 "==$BOTOCORE_VERSION" + + # determine boto3 version that works with $BOTOCORE_VERSION + uv venv /tmp/boto3-ver-venv + source /tmp/boto3-ver-venv/bin/activate + uv pip install "botocore==$BOTOCORE_VERSION" boto3 + export BOTO3_VERSION=$(uv pip list --format=json | jq -r '.[] | select(.name=="boto3") | .version') + deactivate + + # pin boto3 to that predetermined version + source .venv/bin/activate + echo "Pinning boto3 to version $BOTO3_VERSION" + bin/release-helper.sh set-dep-ver boto3 "==$BOTO3_VERSION" + + # determine awscli version that works with $BOTOCORE_VERSION + uv venv /tmp/awscli-ver-venv + source /tmp/awscli-ver-venv/bin/activate + uv pip install "botocore==$BOTOCORE_VERSION" awscli + export AWSCLI_VERSION=$(uv pip list --format=json | jq -r '.[] | select(.name=="awscli") | .version') + deactivate + + # pin awscli to that predetermined version + source .venv/bin/activate + echo "Pinning awscli to version $AWSCLI_VERSION" + bin/release-helper.sh set-dep-ver awscli "==$AWSCLI_VERSION" # upgrade the requirements files only for the botocore package - pip install pip-tools - pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --extra base-runtime -o requirements-base-runtime.txt pyproject.toml - pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --upgrade-package "awscli" --extra runtime -o requirements-runtime.txt pyproject.toml - pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --upgrade-package "awscli" --extra test -o requirements-test.txt pyproject.toml - pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --upgrade-package "awscli" --extra dev -o requirements-dev.txt pyproject.toml - pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --upgrade-package "awscli" --extra typehint -o requirements-typehint.txt pyproject.toml + python3 -m pip install --upgrade "pip<26.0" pip-tools + pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTO3_VERSION" --extra base-runtime -o requirements-base-runtime.txt pyproject.toml + pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTO3_VERSION" --upgrade-package "awscli==$AWSCLI_VERSION" --extra runtime -o requirements-runtime.txt pyproject.toml + pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTO3_VERSION" --upgrade-package "awscli==$AWSCLI_VERSION" --extra test -o requirements-test.txt pyproject.toml + pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTO3_VERSION" --upgrade-package "awscli==$AWSCLI_VERSION" --extra dev -o requirements-dev.txt pyproject.toml + pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTO3_VERSION" --upgrade-package "awscli==$AWSCLI_VERSION" --extra typehint -o requirements-typehint.txt pyproject.toml - name: Read PR markdown template if: ${{ success() && steps.check-for-changes.outputs.diff-count != '0' && steps.check-for-changes.outputs.diff-count != '' }} @@ -105,7 +120,7 @@ jobs: replace: ${{ steps.check-for-changes.outputs.changed-services }} - name: Create PR - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 if: ${{ success() && steps.check-for-changes.outputs.diff-count != '0' && steps.check-for-changes.outputs.diff-count != '' }} with: title: "Update ASF APIs" @@ -114,6 +129,6 @@ jobs: author: "LocalStack Bot " committer: "LocalStack Bot " commit-message: "update generated ASF APIs to latest version" - labels: "area: asf, area: dependencies, semver: patch" + labels: "area: asf, area: dependencies, semver: patch, docs: skip, notes: skip" token: ${{ secrets.PRO_ACCESS_TOKEN }} reviewers: silv-io,alexrashed diff --git a/.github/workflows/aws-main.yml b/.github/workflows/aws-main.yml index e3daf1a6a5fc8..9b65946d4332a 100644 --- a/.github/workflows/aws-main.yml +++ b/.github/workflows/aws-main.yml @@ -16,7 +16,7 @@ on: - '!.git-blame-ignore-revs' - '!docs/**' branches: - - master + - main tags: - 'v[0-9]+.[0-9]+.[0-9]+' pull_request: @@ -83,7 +83,7 @@ jobs: # default "disableCaching" to `false` if it's a push or schedule event disableCaching: ${{ inputs.disableCaching == true }} # default "disableTestSelection" to `true` if it's a push or schedule event - disableTestSelection: ${{ (inputs.enableTestSelection != '' && inputs.enableTestSelection) || github.event_name == 'push' }} + disableTestSelection: ${{ (inputs.enableTestSelection != '' && !inputs.enableTestSelection) || github.event_name == 'push' }} PYTEST_LOGLEVEL: ${{ inputs.PYTEST_LOGLEVEL }} forceARMTests: ${{ inputs.forceARMTests == true }} secrets: @@ -96,12 +96,14 @@ jobs: runs-on: ubuntu-latest needs: - test + # Do not push coverage data if only parts of the tests were executed + if: ${{ !(inputs.onlyAcceptanceTests == true || inputs.enableTestSelection == true || github.event_name == 'push') && !failure() && !cancelled() && github.repository == 'localstack/localstack' }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version-file: '.python-version' cache: 'pip' @@ -112,7 +114,7 @@ jobs: run: make install-dev - name: Load all test results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: test-results-* path: target/coverage/ @@ -138,6 +140,7 @@ jobs: - name: Report coverage statistics env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | source .venv/bin/activate coverage report || true @@ -172,7 +175,7 @@ jobs: python -m scripts.metrics_coverage.diff_metrics_coverage - name: Archive coverage and parity metrics - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-and-parity-metrics path: | @@ -189,14 +192,14 @@ jobs: push: name: "Push images" runs-on: ubuntu-latest - # push image on master, target branch not set, and the dependent steps were either successful or skipped - if: ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') ) && !failure() && !cancelled() && github.repository == 'localstack/localstack' + # push image on main, target branch not set, and the dependent steps were either successful or skipped + if: ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && !failure() && !cancelled() && github.repository == 'localstack/localstack' needs: # all tests need to be successful for the image to be pushed - test steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -207,7 +210,7 @@ jobs: platform: ${{ env.PLATFORM_NAME_AMD64 }} - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -269,7 +272,7 @@ jobs: push-to-tinybird: name: Push Workflow Status to Tinybird - if: always() && ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') ) && github.repository == 'localstack/localstack' + if: always() && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && github.repository == 'localstack/localstack' runs-on: ubuntu-latest needs: - test diff --git a/.github/workflows/aws-tests-mamr.yml b/.github/workflows/aws-tests-mamr.yml index 63872a81ec488..c5a4b7a2e6bfa 100644 --- a/.github/workflows/aws-tests-mamr.yml +++ b/.github/workflows/aws-tests-mamr.yml @@ -67,7 +67,7 @@ jobs: push-to-tinybird: name: Push Workflow Status to Tinybird - if: always() && ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') ) && github.repository == 'localstack/localstack' + if: always() && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && github.repository == 'localstack/localstack' runs-on: ubuntu-latest needs: - test-ma-mr diff --git a/.github/workflows/aws-tests-s3-image.yml b/.github/workflows/aws-tests-s3-image.yml index f57d89930f1c2..8af6dcc13d65b 100644 --- a/.github/workflows/aws-tests-s3-image.yml +++ b/.github/workflows/aws-tests-s3-image.yml @@ -3,7 +3,7 @@ name: AWS / S3 Image Integration Tests on: push: paths: - - .github/workflows/tests-s3-image.yml + - .github/workflows/aws-tests-s3-image.yml - localstack-core/localstack/aws/*.py - localstack-core/localstack/aws/handlers/*¨ - localstack-core/localstack/aws/protocol/** @@ -19,10 +19,10 @@ on: - setup.cfg - Makefile branches: - - master + - main pull_request: paths: - - .github/workflows/tests-s3-image.yml + - .github/workflows/aws-tests-s3-image.yml - localstack-core/localstack/aws/*.py - localstack-core/localstack/aws/handlers/*¨ - localstack-core/localstack/aws/protocol/** @@ -71,8 +71,8 @@ env: CI_COMMIT_BRANCH: ${{ github.head_ref || github.ref_name }} CI_COMMIT_SHA: ${{ github.sha }} CI_JOB_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }} - # report to tinybird if executed on master - TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && github.ref == 'refs/heads/master' && '--report-to-tinybird ' || '' }}" + # report to tinybird if executed on main + TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && github.ref == 'refs/heads/main' && '--report-to-tinybird ' || '' }}" jobs: @@ -83,7 +83,7 @@ jobs: - arch: amd64 runner: ubuntu-latest - arch: arm64 - runner: buildjet-2vcpu-ubuntu-2204-arm + runner: ubuntu-24.04-arm name: "Build and Test S3 image" env: PLATFORM: ${{ matrix.arch }} @@ -95,7 +95,7 @@ jobs: runs-on: ${{ matrix.runner }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -114,9 +114,9 @@ jobs: password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version-file: '.python-version' - name: Install docker build dependencies run: pip install --upgrade setuptools setuptools_scm @@ -137,7 +137,7 @@ jobs: make docker-run-tests-s3-only - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-s3-image-${{ matrix.arch }} @@ -149,7 +149,7 @@ jobs: run: ./bin/docker-helper.sh save - name: Store Docker image as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: localstack-s3-image-${{ matrix.arch }} path: ${{ steps.save-image.outputs.IMAGE_FILENAME }} @@ -173,12 +173,12 @@ jobs: ) steps: - name: Download AMD64 Results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-results-s3-image-amd64 - name: Download ARM64 Results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-results-s3-image-arm64 @@ -206,23 +206,23 @@ jobs: password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version-file: '.python-version' - name: Install docker build dependencies run: pip install --upgrade setuptools setuptools_scm - name: Download AMD64 image - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: localstack-s3-image-amd64 - name: Download ARM64 image - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: localstack-s3-image-arm64 @@ -268,7 +268,7 @@ jobs: failOnError: false push-to-tinybird: - if: always() && github.ref == 'refs/heads/master' && github.repository == 'localstack/localstack' + if: always() && github.ref == 'refs/heads/main' && github.repository == 'localstack/localstack' runs-on: ubuntu-latest needs: push-s3-image steps: diff --git a/.github/workflows/aws-tests.yml b/.github/workflows/aws-tests.yml index 9951389d18a3c..c47daa1c79e23 100644 --- a/.github/workflows/aws-tests.yml +++ b/.github/workflows/aws-tests.yml @@ -131,11 +131,15 @@ env: CI_COMMIT_BRANCH: ${{ github.head_ref || github.ref_name }} CI_COMMIT_SHA: ${{ github.sha }} CI_JOB_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }} - # report to tinybird if executed on master - TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') ) && '--report-to-tinybird ' || '' }}" + # report to tinybird if executed on main + TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') ) && '--report-to-tinybird ' || '' }}" DOCKER_PULL_SECRET_AVAILABLE: ${{ secrets.DOCKERHUB_PULL_USERNAME != '' && secrets.DOCKERHUB_PULL_TOKEN != '' && 'true' || 'false' }} - +# Only one pull-request triggered run should be executed at a time +# (head_ref is only set for PR events, otherwise fallback to run_id which differs for every run). +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true jobs: build: @@ -148,9 +152,9 @@ jobs: - ubuntu-latest - ubuntu-24.04-arm exclude: - # skip the ARM integration tests in forks, and also if not on master/upgrade-dependencies and forceARMTests is not set to true + # skip the ARM integration tests in forks, and also if not on main/upgrade-dependencies and forceARMTests is not set to true # TODO ARM runners are not yet available for private repositories; skip them for potential private forks - - runner: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'ubuntu-24.04-arm' || ''}} + - runner: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'ubuntu-24.04-arm' || ''}} fail-fast: false runs-on: ${{ matrix.runner }} steps: @@ -159,14 +163,14 @@ jobs: run: echo "PLATFORM=${{ (runner.arch == 'X64' && 'amd64') || (runner.arch == 'ARM64' && 'arm64') || '' }}" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: localstack # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 - name: Build Image - uses: localstack/localstack/.github/actions/build-image@master + uses: localstack/localstack/.github/actions/build-image@main with: disableCaching: ${{ inputs.disableCaching == true && 'true' || 'false' }} dockerhubPullUsername: ${{ secrets.DOCKERHUB_PULL_USERNAME }} @@ -175,7 +179,7 @@ jobs: - name: Restore Lambda common runtime packages id: cached-lambda-common-restore if: inputs.disableCaching != true - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: localstack/tests/aws/services/lambda_/functions/common key: common-it-${{ runner.os }}-${{ runner.arch }}-lambda-common-${{ hashFiles('localstack/tests/aws/services/lambda_/functions/common/**/src/*', 'localstack/tests/aws/services/lambda_/functions/common/**/Makefile') }} @@ -185,13 +189,13 @@ jobs: - name: Save Lambda common runtime packages if: inputs.disableCaching != true - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: localstack/tests/aws/services/lambda_/functions/common key: ${{ steps.cached-lambda-common-restore.outputs.cache-primary-key }} - name: Archive Lambda common packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: lambda-common-${{ env.PLATFORM }} path: | @@ -206,10 +210,10 @@ jobs: cloudwatch-v1: ${{ steps.changes.outputs.cloudwatch-v1 }} dynamodb-v2: ${{ steps.changes.outputs.dynamodb-v2 }} events-v1: ${{ steps.changes.outputs.events-v1 }} - cloudformation-v2: ${{ steps.changes.outputs.cloudformation-v2 }} + cloudformation-legacy: ${{ steps.changes.outputs.cloudformation-legacy }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -220,6 +224,9 @@ jobs: - name: Linting run: make lint + - name: Verify Plux Entrypoints + run: make entrypoints && git diff --exit-code plux.ini + - name: Check AWS compatibility markers run: make check-aws-markers @@ -240,7 +247,7 @@ jobs: - name: Archive Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-selection path: | @@ -259,18 +266,22 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} filters: | cloudwatch-v1: + - 'localstack-core/localstack/services/cloudwatch/**' - 'tests/aws/services/cloudwatch/**' dynamodb-v2: + - 'localstack-core/localstack/services/dynamodb/**' - 'tests/aws/services/dynamodb/**' - 'tests/aws/services/dynamodbstreams/**' - 'tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py' events-v1: + - 'localstack-core/localstack/services/events/**' - 'tests/aws/services/events/**' - cloudformation-v2: - - 'tests/aws/services/cloudformation/v2/**' + cloudformation-legacy: + - 'localstack-core/localstack/services/cloudformation/**' + - 'tests/aws/services/cloudformation/**' - name: Run Unit Tests - timeout-minutes: 8 + timeout-minutes: 20 env: # add the GitHub API token to avoid rate limit issues GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -285,7 +296,7 @@ jobs: run: make test-coverage - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-preflight @@ -308,7 +319,7 @@ jobs: if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - name: Download Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: test-results-preflight @@ -317,7 +328,7 @@ jobs: if: success() || failure() with: files: | - test-results-preflight/*.xml + **/pytest-junit-*.xml check_name: "Test Results ${{ inputs.testAWSAccountId != '000000000000' && '(MA/MR) ' || ''}}- Preflight, Unit" test_file_prefix: "-/opt/code/localstack/" action_fail_on_inconclusive: true @@ -336,9 +347,9 @@ jobs: - ubuntu-latest - ubuntu-24.04-arm exclude: - # skip the ARM integration tests in forks, and also if not on master/upgrade-dependencies and forceARMTests is not set to true + # skip the ARM integration tests in forks, and also if not on main/upgrade-dependencies and forceARMTests is not set to true # TODO ARM runners are not yet available for private repositories; skip them for potential private forks - - runner: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'ubuntu-24.04-arm' || ''}} + - runner: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'ubuntu-24.04-arm' || ''}} fail-fast: false runs-on: ${{ matrix.runner }} env: @@ -365,13 +376,13 @@ jobs: echo "${{ inputs.testEnvironmentVariables }}" | sed "s/;/\n/" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 - name: Download Lambda Common packages - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: lambda-common-${{ env.PLATFORM }} path: | @@ -384,7 +395,7 @@ jobs: - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-selection path: dist/testselection/ @@ -410,7 +421,7 @@ jobs: mv .test_durations .test_durations-${{ env.PLATFORM }}-${{ matrix.group }} - name: Archive Test Durations - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: pytest-split-durations-${{ env.PLATFORM }}-${{ matrix.group }} @@ -419,7 +430,7 @@ jobs: retention-days: 5 - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-integration-${{ env.PLATFORM }}-${{ matrix.group }} @@ -431,7 +442,7 @@ jobs: - name: Archive Parity Metric Results if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: parity-metric-raw-${{ env.PLATFORM }}-${{ matrix.group }} path: target/metric_reports @@ -452,7 +463,7 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -477,7 +488,7 @@ jobs: run: make test-coverage - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-bootstrap @@ -495,9 +506,9 @@ jobs: - amd64 - arm64 exclude: - # skip the ARM integration tests in forks, and also if not on master/upgrade-dependencies and forceARMTests is not set to true + # skip the ARM integration tests in forks, and also if not on main/upgrade-dependencies and forceARMTests is not set to true # TODO ARM runners are not yet available for private repositories; skip them for potential private forks - - arch: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'arm64' || ''}} + - arch: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'arm64' || ''}} needs: - test-integration - test-bootstrap @@ -511,13 +522,13 @@ jobs: if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - name: Download Bootstrap Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 if: ${{ matrix.arch == 'amd64' }} with: pattern: test-results-bootstrap - name: Download Integration Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: test-results-integration-${{ matrix.arch }}-* @@ -541,9 +552,9 @@ jobs: - ubuntu-latest - ubuntu-24.04-arm exclude: - # skip the ARM integration tests in forks, and also if not on master/upgrade-dependencies and forceARMTests is not set to true + # skip the ARM integration tests in forks, and also if not on main/upgrade-dependencies and forceARMTests is not set to true # TODO ARM runners are not yet available for private repositories; skip them for potential private forks - - runner: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'ubuntu-24.04-arm' || ''}} + - runner: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'ubuntu-24.04-arm' || ''}} fail-fast: false runs-on: ${{ matrix.runner }} env: @@ -572,7 +583,7 @@ jobs: echo "${{ inputs.testEnvironmentVariables }}" | sed "s/;/\n/" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -598,7 +609,7 @@ jobs: run: make docker-run-tests - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-acceptance-${{ env.PLATFORM }} @@ -616,9 +627,9 @@ jobs: - amd64 - arm64 exclude: - # skip the ARM integration tests in forks, and also if not on master/upgrade-dependencies and forceARMTests is not set to true + # skip the ARM integration tests in forks, and also if not on main/upgrade-dependencies and forceARMTests is not set to true # TODO ARM runners are not yet available for private repositories; skip them for potential private forks - - arch: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'arm64' || ''}} + - arch: ${{ ((github.repository != 'localstack/localstack') || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'upgrade-dependencies' && inputs.forceARMTests == false)) && 'arm64' || ''}} needs: - test-acceptance runs-on: ubuntu-latest @@ -631,7 +642,7 @@ jobs: if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - name: Download Acceptance Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: test-results-acceptance-${{ matrix.arch }} @@ -647,7 +658,7 @@ jobs: test-cloudwatch-v1: name: Test CloudWatch V1 - if: ${{ !inputs.onlyAcceptanceTests && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || needs.test-preflight.outputs.cloudwatch-v1 == 'true') }} + if: ${{ !inputs.onlyAcceptanceTests && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.disableTestSelection || needs.test-preflight.outputs.cloudwatch-v1 == 'true') }} runs-on: ubuntu-latest needs: - test-preflight @@ -657,16 +668,19 @@ jobs: # Set job-specific environment variables for pytest-tinybird CI_JOB_NAME: ${{ github.job }} CI_JOB_ID: ${{ github.job }} + outputs: + # we need this output to conditionally execute the Publishing step + job_status: "executed" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-selection path: dist/testselection/ @@ -685,7 +699,7 @@ jobs: run: make test-coverage - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-cloudwatch-v1 @@ -697,7 +711,7 @@ jobs: test-ddb-v2: name: Test DynamoDB(Streams) v2 - if: ${{ !inputs.onlyAcceptanceTests && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || needs.test-preflight.outputs.dynamodb-v2 == 'true') }} + if: ${{ !inputs.onlyAcceptanceTests && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.disableTestSelection || needs.test-preflight.outputs.dynamodb-v2 == 'true') }} runs-on: ubuntu-latest needs: - test-preflight @@ -707,16 +721,19 @@ jobs: # Set job-specific environment variables for pytest-tinybird CI_JOB_NAME: ${{ github.job }} CI_JOB_ID: ${{ github.job }} + outputs: + # we need this output to conditionally execute the Publishing step + job_status: "executed" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-selection path: dist/testselection/ @@ -734,7 +751,7 @@ jobs: run: make test-coverage - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-dynamodb-v2 @@ -746,7 +763,7 @@ jobs: test-events-v1: name: Test EventBridge v1 - if: ${{ !inputs.onlyAcceptanceTests && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || needs.test-preflight.outputs.events-v1 == 'true') }} + if: ${{ !inputs.onlyAcceptanceTests && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.disableTestSelection || needs.test-preflight.outputs.events-v1 == 'true') }} runs-on: ubuntu-latest needs: - test-preflight @@ -756,16 +773,19 @@ jobs: # Set job-specific environment variables for pytest-tinybird CI_JOB_NAME: ${{ github.job }} CI_JOB_ID: ${{ github.job }} + outputs: + # we need this output to conditionally execute the Publishing step + job_status: "executed" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-selection path: dist/testselection/ @@ -784,7 +804,7 @@ jobs: run: make test-coverage - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-events-v1 @@ -793,49 +813,52 @@ jobs: .coverage.events_v1 retention-days: 30 - test-cfn-v2-engine: - name: Test CloudFormation Engine v2 - if: ${{ !inputs.onlyAcceptanceTests && ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || needs.test-preflight.outputs.cloudformation-v2 == 'true' )}} + test-cfn-legacy-engine: + name: Test CloudFormation Engine legacy + if: ${{ !inputs.onlyAcceptanceTests && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.disableTestSelection || needs.test-preflight.outputs.cloudformation-legacy == 'true' )}} runs-on: ubuntu-latest needs: - test-preflight - build timeout-minutes: 60 env: - COVERAGE_FILE: ".coverage.cloudformation_v2" - JUNIT_REPORTS_FILE: "pytest-junit-cloudformation-v2.xml" + COVERAGE_FILE: ".coverage.cloudformation_legacy" + JUNIT_REPORTS_FILE: "pytest-junit-cloudformation-legacy.xml" # Set job-specific environment variables for pytest-tinybird CI_JOB_NAME: ${{ github.job }} CI_JOB_ID: ${{ github.job }} + outputs: + # we need this output to conditionally execute the Publishing step + job_status: "executed" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-selection path: dist/testselection/ - - name: Run CloudFormation Engine v2 Tests - timeout-minutes: 30 + - name: Run CloudFormation Engine legacy Tests + timeout-minutes: 60 env: # add the GitHub API token to avoid rate limit issues GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TEST_PATH: "tests/aws/services/cloudformation/v2" - PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }} --reruns 3 -o junit_suite_name='cloudformation_v2'" - PROVIDER_OVERRIDE_CLOUDFORMATION: "engine-v2" + TEST_PATH: "tests/aws/services/cloudformation" + PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }} --reruns 3 -o junit_suite_name='cloudformation_legacy'" + PROVIDER_OVERRIDE_CLOUDFORMATION: "engine-legacy" run: make test-coverage - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: - name: test-results-cloudformation-v2 + name: test-results-cloudformation-legacy include-hidden-files: true path: | ${{ env.COVERAGE_FILE }} @@ -844,8 +867,10 @@ jobs: publish-alternative-provider-test-results: name: Publish Alternative Provider Test Results + # execute on success or failure, but not if the workflow is cancelled or all of the dependencies has been skipped + if: ${{ !cancelled() && (contains(needs.*.outputs.job_status, 'executed')) }} needs: - - test-cfn-v2-engine + - test-cfn-legacy-engine - test-events-v1 - test-ddb-v2 - test-cloudwatch-v1 @@ -855,26 +880,24 @@ jobs: pull-requests: write contents: read issues: read - # execute on success or failure, but not if the workflow is cancelled or any of the dependencies has been skipped - if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - - name: Download Cloudformation v2 Artifacts - uses: actions/download-artifact@v4 + - name: Download Cloudformation legacy Artifacts + uses: actions/download-artifact@v8 with: - pattern: test-results-cloudformation-v2 + pattern: test-results-cloudformation-legacy - name: Download EventBridge v1 Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: test-results-events-v1 - name: Download DynamoDB v2 Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: test-results-dynamodb-v2 - name: Download CloudWatch v1 Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: pattern: test-results-cloudwatch-v1 @@ -890,7 +913,7 @@ jobs: capture-not-implemented: name: "Capture Not Implemented" - if: ${{ !inputs.onlyAcceptanceTests && ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') ) }} + if: ${{ (!inputs.onlyAcceptanceTests && github.ref == 'refs/heads/main') || startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest needs: build env: @@ -905,7 +928,7 @@ jobs: password: ${{ secrets.DOCKERHUB_PULL_TOKEN }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -927,6 +950,7 @@ jobs: IMAGE_NAME: "localstack/localstack:latest" run: | source .venv/bin/activate + pip install --pre localstack localstack start -d localstack wait -t 120 || (localstack logs && false) @@ -948,7 +972,7 @@ jobs: localstack stop - name: Archive Capture-Not-Implemented Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: capture-notimplemented path: results/ diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index 68f30c7ef9c4a..81c002d21daac 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -3,7 +3,7 @@ name: Update Docker Hub Description on: push: branches: - - master + - main paths: - DOCKER.md - .github/workflows/dockerhub-description.yml @@ -13,10 +13,10 @@ jobs: name: Sync DockerHub Description runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v4 + uses: peter-evans/dockerhub-description@v5 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} diff --git a/.github/workflows/marker-report.yml b/.github/workflows/marker-report.yml index 6992be9827954..0b89c8dabe800 100644 --- a/.github/workflows/marker-report.yml +++ b/.github/workflows/marker-report.yml @@ -26,7 +26,7 @@ on: paths: - "tests/**" branches: - - master + - main concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -38,18 +38,18 @@ jobs: timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Python id: setup-python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: '.python-version' - name: Cache LocalStack community dependencies (venv) - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .venv key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-venv-${{ hashFiles('requirements-dev.txt') }} @@ -101,7 +101,7 @@ jobs: - name: Upload generated markdown if: ${{ inputs.createIssue }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: path: ./target/MARKER_REPORT_ISSUE.md diff --git a/.github/workflows/pr-cla.yml b/.github/workflows/pr-cla.yml index b81b0786207e5..63d722b5d99c0 100644 --- a/.github/workflows/pr-cla.yml +++ b/.github/workflows/pr-cla.yml @@ -24,7 +24,7 @@ jobs: remote-organization-name: "localstack" remote-repository-name: "localstack" path-to-signatures: "etc/cla-signatures/signatures.json" - path-to-document: "https://github.com/localstack/localstack/blob/master/.github/CLA.md" + path-to-document: "https://github.com/localstack/localstack/blob/main/.github/CLA.md" branch: "cla-signatures" allowlist: "localstack-bot,*[bot]" lock-pullrequest-aftermerge: false diff --git a/.github/workflows/pr-enforce-no-major-master.yml b/.github/workflows/pr-enforce-no-major-main.yml similarity index 76% rename from .github/workflows/pr-enforce-no-major-master.yml rename to .github/workflows/pr-enforce-no-major-main.yml index d2efcf8f93191..3a037de0506ec 100644 --- a/.github/workflows/pr-enforce-no-major-master.yml +++ b/.github/workflows/pr-enforce-no-major-main.yml @@ -1,11 +1,11 @@ -name: Enforce no major on master +name: Enforce no major on main on: pull_request_target: types: [labeled, unlabeled, opened, edited, synchronize] - # only enforce for PRs targeting the master branch + # only enforce for PRs targeting the main branch branches: - - master + - main jobs: enforce-no-major: diff --git a/.github/workflows/pr-enforce-no-major-minor-master.yml b/.github/workflows/pr-enforce-no-major-minor-main.yml similarity index 75% rename from .github/workflows/pr-enforce-no-major-minor-master.yml rename to .github/workflows/pr-enforce-no-major-minor-main.yml index 60fbd79b4108e..ef0adc4a98fa0 100644 --- a/.github/workflows/pr-enforce-no-major-minor-master.yml +++ b/.github/workflows/pr-enforce-no-major-minor-main.yml @@ -1,11 +1,11 @@ -name: Enforce no major or minor on master +name: Enforce no major or minor on main on: pull_request_target: types: [labeled, unlabeled, opened, edited, synchronize] - # only enforce for PRs targeting the master branch + # only enforce for PRs targeting the main branch branches: - - master + - main jobs: enforce-no-major-minor: diff --git a/.github/workflows/pr-enforce-pr-labels.yml b/.github/workflows/pr-enforce-pr-labels.yml index b316c45845360..81f3eb8a73359 100644 --- a/.github/workflows/pr-enforce-pr-labels.yml +++ b/.github/workflows/pr-enforce-pr-labels.yml @@ -1,11 +1,10 @@ -name: Enforce PR Labels - +name: Enforce Labels on: pull_request_target: - types: [labeled, unlabeled, opened, edited, synchronize] + types: [labeled, unlabeled, opened, synchronize] jobs: - enforce-semver-labels: - uses: localstack/meta/.github/workflows/pr-enforce-semver-labels.yml@main + labels: + uses: localstack/meta/.github/workflows/pr-enforce-labels.yml@main secrets: github-token: ${{ secrets.PRO_ACCESS_TOKEN }} diff --git a/.github/workflows/pr-validate-features-files.yml b/.github/workflows/pr-validate-features-files.yml index d62d2b5ffaa77..5e842e7f2500a 100644 --- a/.github/workflows/pr-validate-features-files.yml +++ b/.github/workflows/pr-validate-features-files.yml @@ -5,7 +5,7 @@ on: paths: - localstack-core/localstack/services/** branches: - - master + - main jobs: validate-features-files: diff --git a/.github/workflows/pr-welcome-first-time-contributors.yml b/.github/workflows/pr-welcome-first-time-contributors.yml index c01b376ececde..ea606a412bcef 100644 --- a/.github/workflows/pr-welcome-first-time-contributors.yml +++ b/.github/workflows/pr-welcome-first-time-contributors.yml @@ -12,12 +12,12 @@ jobs: welcome: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v8 with: github-token: ${{ secrets.PRO_ACCESS_TOKEN }} script: | - const issueMessage = `Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/slack/) for real-time help and drop a message to [LocalStack Support](https://docs.localstack.cloud/getting-started/help-and-support/) if you are a licensed user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md).`; - const prMessage = `Welcome to LocalStack! Thanks for raising your first Pull Request and landing in your contributions. Our team will reach out with any reviews or feedbacks that we have shortly. We recommend joining our [Slack Community](https://localstack.cloud/slack/) and share your PR on the **#community** channel to share your contributions with us. Please make sure you are following our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [Code of Conduct](https://github.com/localstack/.github/blob/main/CODE_OF_CONDUCT.md).`; + const issueMessage = `Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/slack/) for real-time help and drop a message to [LocalStack Support](https://docs.localstack.cloud/getting-started/help-and-support/) if you are a licensed user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/localstack/blob/main/docs/CONTRIBUTING.md).`; + const prMessage = `Welcome to LocalStack! Thanks for raising your first Pull Request and landing in your contributions. Our team will reach out with any reviews or feedbacks that we have shortly. We recommend joining our [Slack Community](https://localstack.cloud/slack/) and share your PR on the **#community** channel to share your contributions with us. Please make sure you are following our [contributing guidelines](https://github.com/localstack/localstack/blob/main/docs/CONTRIBUTING.md) and our [Code of Conduct](https://github.com/localstack/.github/blob/main/CODE_OF_CONDUCT.md).`; if (!issueMessage && !prMessage) { throw new Error('Action should have either issueMessage or prMessage set'); diff --git a/.github/workflows/rebase-release-prs.yml b/.github/workflows/rebase-release-prs.yml index 44f9ba9397c06..81f1f50333f76 100644 --- a/.github/workflows/rebase-release-prs.yml +++ b/.github/workflows/rebase-release-prs.yml @@ -10,7 +10,7 @@ jobs: steps: - name: find release branches id: find-release-branches - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | // find all refs in the repo starting with "release/" @@ -28,8 +28,8 @@ jobs: matrix: head: ${{ fromJson(needs.find-release-branches.outputs.matrix) }} steps: - - uses: peter-evans/rebase@v3 + - uses: peter-evans/rebase@v4 with: token: ${{ secrets.PRO_ACCESS_TOKEN }} head: ${{ matrix.head }} - base: master + base: main diff --git a/.github/workflows/rebase-release-targeting-prs.yml b/.github/workflows/rebase-release-targeting-prs.yml index d92480c9a5ef6..37387dbaba1ad 100644 --- a/.github/workflows/rebase-release-targeting-prs.yml +++ b/.github/workflows/rebase-release-targeting-prs.yml @@ -9,13 +9,13 @@ jobs: steps: - name: Determine base ref id: determine-base-ref - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: result-encoding: string script: | // remove the ref prefx "refs/heads/" return context.payload.ref.substr(11) - - uses: peter-evans/rebase@v3 + - uses: peter-evans/rebase@v4 with: token: ${{ secrets.PRO_ACCESS_TOKEN }} base: ${{steps.determine-base-ref.outputs.result}} diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index f9835bf171c39..ab2562ad0adbd 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -10,6 +10,6 @@ jobs: sync-labels: uses: localstack/meta/.github/workflows/sync-labels.yml@main with: - categories: status,aws,semver + categories: status,aws,semver,docs,notes secrets: github-token: ${{ secrets.PRO_ACCESS_TOKEN }} diff --git a/.github/workflows/tests-bin.yml b/.github/workflows/tests-bin.yml index 4da8063a78600..1c9886019933c 100644 --- a/.github/workflows/tests-bin.yml +++ b/.github/workflows/tests-bin.yml @@ -12,7 +12,7 @@ on: - '.github/workflows/tests-bin.yml' - 'tests/bin/*.bats' branches: - - master + - main - release/* jobs: @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup BATS run: | @@ -29,9 +29,10 @@ jobs: sudo ./install.sh /usr/local - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version-file: '.python-version' + - name: Install helper script dependencies run: pip install --upgrade setuptools setuptools_scm @@ -42,7 +43,7 @@ jobs: mv report.xml tests-junit-base.xml - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-tests-bin diff --git a/.github/workflows/tests-cli.yml b/.github/workflows/tests-cli.yml deleted file mode 100644 index 68eca09252eaf..0000000000000 --- a/.github/workflows/tests-cli.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: CLI Tests -on: - workflow_dispatch: - inputs: - PYTEST_LOGLEVEL: - type: choice - description: Loglevel for PyTest - options: - - DEBUG - - INFO - - WARNING - - ERROR - - CRITICAL - default: WARNING - pull_request: - paths: - - '**' - - '!.github/**' - - '.github/workflows/tests-cli.yml' - - '!docs/**' - - '!scripts/**' - - '!.dockerignore' - - '!.git-blame-ignore-revs' - - '!CODE_OF_CONDUCT.md' - - '!CODEOWNERS' - - '!CONTRIBUTING.md' - - '!docker-compose.yml' - - '!docker-compose-pro.yml' - - '!Dockerfile*' - - '!LICENSE.txt' - - '!README.md' - push: - paths: - - '**' - - '!.github/**' - - '.github/workflows/tests-cli.yml' - - '!docs/**' - - '!scripts/**' - - '!.dockerignore' - - '!.git-blame-ignore-revs' - - '!CODE_OF_CONDUCT.md' - - '!CODEOWNERS' - - '!CONTRIBUTING.md' - - '!docker-compose.yml' - - '!docker-compose-pro.yml' - - '!Dockerfile*' - - '!LICENSE.txt' - - '!README.md' - branches: - - master - - release/* - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - # Configure PyTest log level - PYTEST_LOGLEVEL: "${{ inputs.PYTEST_LOGLEVEL || 'WARNING' }}" - # Set non-job-specific environment variables for pytest-tinybird - TINYBIRD_URL: https://api.tinybird.co - TINYBIRD_DATASOURCE: community_tests_cli - TINYBIRD_TOKEN: ${{ secrets.TINYBIRD_CI_TOKEN }} - CI_COMMIT_BRANCH: ${{ github.head_ref || github.ref_name }} - CI_COMMIT_SHA: ${{ github.sha }} - CI_JOB_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }} - # report to tinybird if executed on master - TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && github.ref == 'refs/heads/master' && '--report-to-tinybird ' || '' }}" - -permissions: - contents: read # checkout the repository - -jobs: - cli-tests: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] - timeout-minutes: 10 - env: - # Set job-specific environment variables for pytest-tinybird - CI_JOB_NAME: ${{ github.job }}-${{ matrix.python-version }} - CI_JOB_ID: ${{ github.job }}-${{ matrix.python-version }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python - id: setup-python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install CLI test dependencies - run: | - make venv - source .venv/bin/activate - pip install -e . - pip install pytest pytest-tinybird - - name: Run CLI tests - env: - PYTEST_ADDOPTS: "${{ env.TINYBIRD_PYTEST_ARGS }}-p no:localstack.testing.pytest.fixtures -p no:localstack_snapshot.pytest.snapshot -p no:localstack.testing.pytest.filters -p no:localstack.testing.pytest.fixture_conflicts -p no:localstack.testing.pytest.validation_tracking -p no:localstack.testing.pytest.path_filter -p no:tests.fixtures -p no:localstack.testing.pytest.stepfunctions.fixtures -p no:localstack.testing.pytest.cloudformation.fixtures -s" - TEST_PATH: "tests/cli/" - run: make test - - push-to-tinybird: - if: always() && github.ref == 'refs/heads/master' && github.repository == 'localstack/localstack' - runs-on: ubuntu-latest - needs: cli-tests - permissions: - actions: read - steps: - - name: Push to Tinybird - uses: localstack/tinybird-workflow-push@v3 - with: - workflow_id: "tests_cli" - tinybird_token: ${{ secrets.TINYBIRD_CI_TOKEN }} - github_token: ${{ secrets.GITHUB_TOKEN }} - tinybird_datasource: "ci_workflows" diff --git a/.github/workflows/tests-podman.yml b/.github/workflows/tests-podman.yml index 664de59630750..c384fcc4b486e 100644 --- a/.github/workflows/tests-podman.yml +++ b/.github/workflows/tests-podman.yml @@ -24,8 +24,8 @@ env: CI_COMMIT_BRANCH: ${{ github.head_ref || github.ref_name }} CI_COMMIT_SHA: ${{ github.sha }} CI_JOB_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }} - # report to tinybird if executed on master - TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && github.ref == 'refs/heads/master' && '--report-to-tinybird ' || '' }}" + # report to tinybird if executed on main + TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && github.ref == 'refs/heads/main' && '--report-to-tinybird ' || '' }}" jobs: podman-tests: @@ -37,12 +37,12 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version-file: '.python-version' - name: Install podman and test dependencies run: | @@ -77,7 +77,7 @@ jobs: DOCKER_HOST=$podmanSocket make test push-to-tinybird: - if: always() && github.ref == 'refs/heads/master' && github.repository == 'localstack/localstack' + if: always() && github.ref == 'refs/heads/main' && github.repository == 'localstack/localstack' runs-on: ubuntu-latest needs: podman-tests steps: diff --git a/.github/workflows/tests-pro-integration.yml b/.github/workflows/tests-pro-integration.yml index 22fe1a82b138e..ea21c9101de22 100644 --- a/.github/workflows/tests-pro-integration.yml +++ b/.github/workflows/tests-pro-integration.yml @@ -85,7 +85,7 @@ on: - '!LICENSE.txt' - '!README.md' branches: - - master + - main concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -101,9 +101,9 @@ env: CI_COMMIT_BRANCH: ${{ github.head_ref || github.ref_name }} CI_COMMIT_SHA: ${{ github.sha }} CI_JOB_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }} - # report to tinybird if executed on master on community AND pro (targetRef not set) - TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && github.ref == 'refs/heads/master' && inputs.targetRef == '' && '--report-to-tinybird ' || '' }}" - # enable test selection if not running on master and test selection is not explicitly disabled + # report to tinybird if executed on main on community AND pro (targetRef not set) + TINYBIRD_PYTEST_ARGS: "${{ github.repository == 'localstack/localstack' && github.ref == 'refs/heads/main' && inputs.targetRef == '' && '--report-to-tinybird ' || '' }}" + # enable test selection if not running on main and test selection is not explicitly disabled TESTSELECTION_PYTEST_ARGS: "${{ !inputs.disableTestSelection && '--path-filter=../../localstack/target/testselection/test-selection.txt ' || '' }}" jobs: @@ -135,14 +135,14 @@ jobs: docker-images: false - name: Checkout Community - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: localstack fetch-depth: 0 # we need the additional commits to figure out the merge base for test selection - name: "Determine Companion Ref" id: determine-companion-ref - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{ secrets.PRO_ACCESS_TOKEN }} result-encoding: string @@ -152,14 +152,14 @@ jobs: return context.payload.inputs.targetRef } - const DEFAULT_REF = "refs/heads/master" + const DEFAULT_REF = "refs/heads/main" async function isCompanionRefExisting(refName) { try { // strip the leading "refs/" for the API call const apiRef = refName.substr(5) console.log("Checking if companion repo has ref: ", apiRef) - await github.rest.git.getRef({owner: "localstack", repo: "localstack-ext", ref: apiRef}) + await github.rest.git.getRef({owner: "localstack", repo: "localstack-pro", ref: apiRef}) return true } catch (error) { if (error.status == 404) { @@ -181,15 +181,15 @@ jobs: } if (ref == DEFAULT_REF) { - console.log("Current ref is default ref. Using the same for ext repo: ", DEFAULT_REF) + console.log("Current ref is default ref. Using the same for pro repo: ", DEFAULT_REF) return DEFAULT_REF } if (await isCompanionRefExisting(ref)) { - console.log("Using companion ref in ext repo: ", ref) + console.log("Using companion ref in pro repo: ", ref) return ref } else if (baseRef && baseRef != DEFAULT_REF && (await isCompanionRefExisting(baseRef))) { - console.log("Using PR base companion ref in ext repo: ", baseRef) + console.log("Using PR base companion ref in pro repo: ", baseRef) return baseRef } @@ -198,66 +198,44 @@ jobs: return DEFAULT_REF - name: Checkout Pro - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: - repository: localstack/localstack-ext + repository: localstack/localstack-pro ref: ${{steps.determine-companion-ref.outputs.result}} token: ${{ secrets.PRO_ACCESS_TOKEN }} - path: localstack-ext + path: localstack-pro - - name: Set up Python 3.11 - id: setup-python - uses: actions/setup-python@v5 + - name: Set up Node 22.x + uses: actions/setup-node@v6 with: - python-version: '3.11' + node-version: 22.x - - name: Set up Node 18.x - uses: actions/setup-node@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v5 with: - node-version: 18.x - - - name: Set up JDK 11 - uses: actions/setup-java@v4 - with: - java-version: '11' + java-version: '21' distribution: 'temurin' - - name: Set up Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 0.13.7 - - name: Install OS packages run: | sudo apt-get update sudo apt-get install -y --allow-downgrades libsnappy-dev jq libvirt-dev - - name: Cache Ext Dependencies (venv) - if: inputs.disableCaching != true - uses: actions/cache@v4 - with: - path: | - localstack-ext/.venv - # include the matrix group (to re-use the venv) - key: community-it-${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-venv-${{ hashFiles('localstack-ext/localstack-pro-core/requirements-test.txt') }}-${{steps.determine-companion-ref.outputs.result}} - restore-keys: | - community-it-${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-venv-${{ hashFiles('localstack-ext/localstack-pro-core/requirements-test.txt') }}-refs/heads/master - - - name: Cache Ext Dependencies (libs) + - name: Cache Pro Dependencies (libs) if: inputs.disableCaching != true - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | localstack/localstack-core/.filesystem/var/lib/localstack # include the matrix group (to re-use the var-libs used in the specific test group) key: community-it-${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-libs-${{ hashFiles('**/packages.py', '**/packages/*') }}-${{steps.determine-companion-ref.outputs.result}}-group-${{ matrix.group }} restore-keys: | - community-it-${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-libs-${{ hashFiles('**/packages.py', '**/packages/*') }}-refs/heads/master-group-${{ matrix.group }} + community-it-${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-libs-${{ hashFiles('**/packages.py', '**/packages/*') }}-refs/heads/main-group-${{ matrix.group }} - name: Restore Lambda common runtime packages id: cached-lambda-common-restore if: inputs.disableCaching != true - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: | localstack/tests/aws/services/lambda_/functions/common @@ -269,45 +247,29 @@ jobs: - name: Save Lambda common runtime packages if: inputs.disableCaching != true - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 with: path: | localstack/tests/aws/services/lambda_/functions/common key: ${{ steps.cached-lambda-common-restore.outputs.cache-primary-key }} + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: ${{ inputs.disableCaching != true }} + - name: Install Python Dependencies for Pro - working-directory: localstack-ext + working-directory: localstack-pro run: make install-ci - - name: Link Community into Pro venv - working-directory: localstack-ext - run: | - source .venv/bin/activate - pip install -e ../localstack[runtime,test] - - - name: Create Community Entrypoints - working-directory: localstack - # Entrypoints need to be generated _after_ the community edition has been linked into the venv - run: | - VENV_DIR="../localstack-ext/.venv" make entrypoints - ../localstack-ext/.venv/bin/python -m plux show - - - name: Create Pro Entrypoints - working-directory: localstack-ext - # Entrypoints need to be generated _after_ the community edition has been linked into the venv - run: | - make entrypoints - cd localstack-pro-core - ../.venv/bin/python -m plux show - - name: Test Pro Startup env: DEBUG: 1 DNS_ADDRESS: 0 LOCALSTACK_AUTH_TOKEN: "test" - working-directory: localstack-ext + working-directory: localstack-pro run: | - source .venv/bin/activate + source localstack-pro-core/.venv/bin/activate bin/test_localstack_pro.sh - name: Determine Test Selection @@ -320,7 +282,7 @@ jobs: echo "Do test selection based on Pull Request event" SCRIPT_OPTS="--base-commit-sha ${{ github.event.pull_request.base.sha }} --head-commit-sha ${{ github.event.pull_request.head.sha }}" fi - . ../localstack-ext/.venv/bin/activate + . ../localstack-pro/localstack-pro-core/.venv/bin/activate python -m localstack.testing.testselection.scripts.generate_test_selection $(pwd) target/testselection/test-selection.txt $SCRIPT_OPTS || (mkdir -p target/testselection && echo "SENTINEL_ALL_TESTS" >> target/testselection/test-selection.txt) echo "Resulting Test Selection file:" cat target/testselection/test-selection.txt @@ -340,14 +302,14 @@ jobs: JUNIT_REPORTS_FILE: "pytest-junit-community-${{ matrix.group }}.xml" TEST_PATH: "../../localstack/tests/aws/" # TODO: run tests in tests/integration PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }}--splits ${{ strategy.job-total }} --group ${{ matrix.group }} --durations-path ../../localstack/.test_durations --store-durations" - working-directory: localstack-ext + working-directory: localstack-pro run: | # Remove the host tmp folder (might contain remnant files with different permissions) sudo rm -rf ../localstack/localstack-core/.filesystem/var/lib/localstack/tmp make test - name: Archive Test Durations - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: pytest-split-durations-community-${{ matrix.group }} @@ -356,12 +318,12 @@ jobs: retention-days: 5 - name: Archive Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: success() || failure() with: name: test-results-community-${{ matrix.group }} path: | - localstack-ext/localstack-pro-core/pytest-junit-community-${{ matrix.group }}.xml + localstack-pro/localstack-pro-core/pytest-junit-community-${{ matrix.group }}.xml retention-days: 30 publish-pro-test-results: @@ -382,12 +344,12 @@ jobs: ) steps: - name: Download Artifacts 1 - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-results-community-1 - name: Download Artifacts 2 - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: test-results-community-2 @@ -399,7 +361,7 @@ jobs: action_fail_on_inconclusive: true push-to-tinybird: - if: always() && github.ref == 'refs/heads/master' && github.repository == 'localstack/localstack' + if: always() && github.ref == 'refs/heads/main' && github.repository == 'localstack/localstack' runs-on: ubuntu-latest needs: publish-pro-test-results steps: diff --git a/.github/workflows/update-cfn-resources.yml b/.github/workflows/update-cfn-resources.yml new file mode 100644 index 0000000000000..67ed3fbfb1560 --- /dev/null +++ b/.github/workflows/update-cfn-resources.yml @@ -0,0 +1,65 @@ +name: Update CloudFormation Resources + +on: + schedule: + - cron: '0 6 * * TUE' + pull_request: + paths: + - '.github/workflows/update-cfn-resources.yml' + - 'scripts/update_cfn_resources.py' + workflow_dispatch: + +jobs: + update-cfn-resources: + name: Update CloudFormation resources metadata + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version-file: '.python-version' + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Determine boto dependency pins + id: boto_versions + run: | + BOTO3_VERSION=$(grep '^boto3==' requirements-base-runtime.txt | head -n1 | sed 's/^boto3==//') + BOTOCORE_VERSION=$(grep '^botocore==' requirements-base-runtime.txt | head -n1 | sed 's/^botocore==//') + echo "boto3_version=$BOTO3_VERSION" >> "$GITHUB_OUTPUT" + echo "botocore_version=$BOTOCORE_VERSION" >> "$GITHUB_OUTPUT" + + - name: Run CloudFormation resource updater + id: run-script + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-1 + run: | + uv run --no-project --no-config \ + --with boto3==${{ steps.boto_versions.outputs.boto3_version }} \ + --with botocore==${{ steps.boto_versions.outputs.botocore_version }} \ + python scripts/update_cfn_resources.py --resource-file localstack-core/localstack/services/cloudformation/resources.py + + - name: Create Pull Request + if: ${{ github.event_name != 'pull_request' }} + uses: peter-evans/create-pull-request@v8 + with: + title: "Update CloudFormation resource availability" + body: "Automated update of CloudFormation resource metadata." + branch: "bot/update-cfn-resources" + commit-message: "chore: update CloudFormation resource metadata" + author: "LocalStack Bot " + committer: "LocalStack Bot " + labels: "area: cloudformation, semver: patch, docs: skip, notes: skip" + token: ${{ secrets.PRO_ACCESS_TOKEN }} + signoff: true diff --git a/.github/workflows/update-openapi-spec.yml b/.github/workflows/update-openapi-spec.yml index 07d28dee8eccf..781fd02cace2d 100644 --- a/.github/workflows/update-openapi-spec.yml +++ b/.github/workflows/update-openapi-spec.yml @@ -3,7 +3,7 @@ name: Update OpenAPI Spec on: push: branches: - - master + - main paths: - '**/*openapi.yaml' - '**/*openapi.yml' @@ -16,7 +16,7 @@ jobs: steps: # This step dispatches a workflow in the OpenAPI repo, updating the spec and opening a PR. - name: Dispatch update spec workflow - uses: peter-evans/repository-dispatch@v3 + uses: peter-evans/repository-dispatch@v4 with: token: ${{ secrets.PRO_ACCESS_TOKEN }} repository: localstack/openapi diff --git a/.github/workflows/update-test-durations.yml b/.github/workflows/update-test-durations.yml index 12c33df527337..8f349dd64e3d6 100644 --- a/.github/workflows/update-test-durations.yml +++ b/.github/workflows/update-test-durations.yml @@ -2,74 +2,25 @@ name: Update test durations on: schedule: - - cron: 0 4 * 1-12 MON + # trigger on the 5th of each month at 4 AM UTC + - cron: 0 4 5 * * workflow_dispatch: inputs: - publishMethod: - description: 'Select how to publish the workflow result' + publish-method: + description: "Method to publish the test duration update results" type: choice options: - UPLOAD_ARTIFACT - CREATE_PR default: UPLOAD_ARTIFACT -env: - # Take test durations only for this platform - PLATFORM: "amd64" - jobs: - report: - name: "Download, merge and create PR with test durations" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - path: localstack - - - name: Latest run-id from community repository - run: | - latest_workflow_id=$(curl -s https://api.github.com/repos/localstack/localstack/actions/workflows \ - | jq '.workflows[] | select(.path==".github/workflows/aws-main.yml").id') - latest_run_id=$(curl -s \ - "https://api.github.com/repos/localstack/localstack/actions/workflows/${latest_workflow_id}/runs?branch=master&status=success&per_page=30" \ - | jq '[.workflow_runs[] | select(.event == "schedule")][0].id') - echo "Latest run: https://github.com/localstack/localstack/actions/runs/${latest_run_id}" - echo "AWS_MAIN_LATEST_SCHEDULED_RUN_ID=${latest_run_id}" >> $GITHUB_ENV - - - name: Load test durations - uses: actions/download-artifact@v4 - with: - pattern: pytest-split-durations-${{ env.PLATFORM }}-* - path: artifacts-test-durations - merge-multiple: true - run-id: ${{ env.AWS_MAIN_LATEST_SCHEDULED_RUN_ID }} - github-token: ${{ secrets.GITHUB_TOKEN }} # PAT with access to artifacts from GH Actions - - - name: Merge test durations files - shell: bash - run: | - jq -s 'add | to_entries | sort_by(.key) | from_entries' artifacts-test-durations/.test_durations-${{ env.PLATFORM }}* > localstack/.test_durations || echo "::warning::Test durations were not merged" - - - name: Upload artifact with merged test durations - uses: actions/upload-artifact@v4 - if: ${{ success() && inputs.publishMethod == 'UPLOAD_ARTIFACT' }} - with: - name: merged-test-durations - path: localstack/.test_durations - include-hidden-files: true - if-no-files-found: error - - - name: Create PR - uses: peter-evans/create-pull-request@v7 - if: ${{ success() && inputs.publishMethod != 'UPLOAD_ARTIFACT' }} - with: - title: "[Testing] Update test durations" - body: "This PR includes an updated `.test_durations` file, generated based on latest test durations from master" - branch: "test-durations-auto-updates" - author: "LocalStack Bot " - committer: "LocalStack Bot " - commit-message: "CI: update .test_durations to latest version" - path: localstack - add-paths: .test_durations - labels: "semver: patch, area: testing, area: ci" - token: ${{ secrets.PRO_ACCESS_TOKEN }} + update-test-durations: + uses: localstack/meta/.github/workflows/update-test-durations.yml@main + with: + repository-name: localstack + test-durations-file-path: .test_durations + publish-method: ${{ inputs.publish-method || 'CREATE_PR' }} + secrets: + gh-token: ${{ secrets.GITHUB_TOKEN }} + pr-token: ${{ secrets.PRO_ACCESS_TOKEN }} diff --git a/.github/workflows/upgrade-python-dependencies.yml b/.github/workflows/upgrade-python-dependencies.yml index 83b26043bd8c7..7d2a1ba8b5c87 100644 --- a/.github/workflows/upgrade-python-dependencies.yml +++ b/.github/workflows/upgrade-python-dependencies.yml @@ -11,3 +11,5 @@ jobs: uses: localstack/meta/.github/workflows/upgrade-python-dependencies.yml@main secrets: github-token: ${{ secrets.PRO_ACCESS_TOKEN }} + with: + labels: "area: dependencies, semver: patch, docs: skip, notes: skip" diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml index 2f0b19cfd7ca0..5209f23e9376a 100644 --- a/.github/workflows/validate-codeowners.yml +++ b/.github/workflows/validate-codeowners.yml @@ -3,17 +3,17 @@ name: LocalStack - Validate Codeowners on: push: branches: - - master + - main pull_request: branches: - - master + - main jobs: validate-codeowners: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Validate codeowners uses: mszostok/codeowners-validator@v0.7.4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75487ebeb308d..ef4ba8830d397 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,10 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks +# TODO add pre-commit hook for plux entrypoints reconciliation on changed files repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.12.3 + rev: v0.15.4 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -11,17 +12,21 @@ repos: - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.16.1 + rev: v1.19.1 hooks: - id: mypy entry: bash -c 'cd localstack-core && mypy --install-types --non-interactive' - additional_dependencies: ['botocore-stubs', 'rolo'] + additional_dependencies: ['botocore-stubs', 'rolo', 'moto-ext'] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: + - id: no-commit-to-branch - id: end-of-file-fixer + exclude: "plux.ini" - id: trailing-whitespace + - id: check-json + files: .*\.(snapshot|validation)\.json - repo: https://github.com/localstack/pre-commit-hooks rev: v1.2.1 @@ -29,7 +34,7 @@ repos: - id: check-pinned-deps-for-needed-upgrade - repo: https://github.com/python-openapi/openapi-spec-validator - rev: 0.8.0b1 + rev: 0.8.4 hooks: - id: openapi-spec-validator files: .*openapi.*\.(json|yaml|yml) diff --git a/.python-version b/.python-version index 2c0733315e415..24ee5b1be9961 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11 +3.13 diff --git a/.test_durations b/.test_durations index fc4927a66ecd1..084ea1f360468 100644 --- a/.test_durations +++ b/.test_durations @@ -1,5259 +1,5145 @@ { - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_lambda_dynamodb": 1.8591925400000093, - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_opensearch_crud": 3.4542978110000035, - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_search_books": 63.59329259399999, - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_setup": 89.16224498400001, - "tests/aws/scenario/kinesis_firehose/test_kinesis_firehose.py::TestKinesisFirehoseScenario::test_kinesis_firehose_s3": 0.002819474000034461, - "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_destination_sns": 5.6126325460001, - "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_infra": 12.26068547899996, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_prefill_dynamodb_table": 25.062954546999947, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]": 4.753118397000037, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]": 2.7944347069999935, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input2-FAILED]": 0.9179088109999611, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input3-FAILED]": 0.6808095409999737, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input4-FAILED]": 0.5240530809999768, - "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_deployed_infra_state": 0.0027725980000354866, - "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_populate_data": 0.0017560499999831336, - "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_user_clicks_are_stored": 0.001746832000037557, - "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_notes_rest_api": 4.639125250000006, - "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_validate_infra_setup": 32.74242746199997, - "tests/aws/services/acm/test_acm.py::TestACM::test_boto_wait_for_certificate_validation": 1.118890973999953, - "tests/aws/services/acm/test_acm.py::TestACM::test_certificate_for_subdomain_wildcard": 2.217093190000014, - "tests/aws/services/acm/test_acm.py::TestACM::test_create_certificate_for_multiple_alternative_domains": 11.206475674999979, - "tests/aws/services/acm/test_acm.py::TestACM::test_domain_validation": 0.2747168080000506, - "tests/aws/services/acm/test_acm.py::TestACM::test_import_certificate": 1.0384068200000343, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiAuthorizer::test_authorizer_crud_no_api": 0.03355816800001321, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_doc_parts_crud_no_api": 0.03499321999998983, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_documentation_part_lifecycle": 0.07383347799998319, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts": 0.13196213499998066, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_create_documentation_part_operations": 0.04432463400002007, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_delete_documentation_part": 0.05508120899997948, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_part": 0.04705650800002559, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_parts": 0.016791373999978987, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_update_documentation_part": 0.058419485000001714, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_lifecycle": 0.07490121500001123, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_request_parameters": 0.050075023999966106, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_model": 0.28907013999997844, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_validation": 0.07500743499997498, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method": 0.08137474700004077, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method_validation": 0.15210924999996678, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_lifecycle": 0.07780886799997688, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_validation": 0.10623816899999383, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_update_model": 0.07650709399996458, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_create_request_validator_invalid_api_id": 0.01676081699997667, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_delete_request_validator": 0.04610745300004737, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validator": 0.04737211000002617, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validators": 0.01598174600007951, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_update_request_validator_operations": 0.0658933759999627, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_request_validator_lifecycle": 0.0967978199999493, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_validators_crud_no_api": 0.034875632999956, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource": 0.8957564810000349, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource_validation": 0.07905507799995348, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_resource_parent_invalid": 0.03482252899999594, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_delete_resource": 0.07523433899996235, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_resource_lifecycle": 0.12031327400001146, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_behaviour": 0.1741109189999861, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_binary_media_types": 0.027287844999989375, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_optional_params": 0.07789825200001133, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_tags": 0.04767004299998234, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_get_api_case_insensitive": 0.0019003470000029665, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_list_and_delete_apis": 0.09343369599997686, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_behaviour": 0.05412815500000079, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_compression": 0.09286251099996434, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_invalid_api_id": 0.015286289000016495, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_operation_add_remove": 0.0521871969999097, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_crud": 0.11371110400000362, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_put": 0.10616874300006884, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_validation": 0.11559211499996991, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_update_gateway_response": 0.13145873799999208, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_integration": 0.04566580500005557, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_responsetemplates": 0.0019817740000576123, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_statuscode": 0.04377819199987698, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_api": 0.024984995000011168, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_method": 0.04507710900003303, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_resource": 0.04138816200003248, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_status_code": 0.05497632100008332, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_integration_response": 0.10275084199997764, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_method_response": 0.09687917200000129, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_request_parameter_bool_type": 0.0017891400000280555, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_validation": 0.08119039999991173, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_wrong_type": 0.045587436000005255, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_lack_response_parameters_and_models": 0.08356579499996997, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response": 0.07124489599999606, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_negative_tests": 0.09211698100000376, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_wrong_operations": 0.09276022000005923, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_wrong_param_names": 0.09258312299999716, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_invoke_test_method": 0.1999600709999072, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_account": 0.04572928500004991, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_authorizer_crud": 0.0019280499999467793, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_handle_domain_name": 0.24527348200001597, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_http_integration_with_path_request_parameter": 0.0021185520000130964, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_asynchronous_invocation": 1.3223967149999112, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_integration_aws_type": 7.77135099599991, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/foo1]": 0.0017183639999984734, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/{test_param1}]": 0.0016879060000292156, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method": 0.0016394659999150463, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method_with_path_param": 0.001756387000000359, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_with_is_base_64_encoded": 0.0016327339999406831, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_mock_integration": 0.06332164799999873, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_mock_integration_response_params": 0.0017545439999935297, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigateway_with_custom_authorization_method": 15.379194112999983, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[dev]": 1.6465004900000508, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[local]": 1.6253507880000484, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_test_invoke_method_api": 2.1734129350000444, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping": 0.18929234099999803, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping_root": 0.16708001899996816, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[host_based_url]": 0.06823920899995528, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[localstack_path_based_url]": 0.06749188899999581, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[path_based_url]": 0.06842219500003921, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_delete_rest_api_with_invalid_id": 0.012952146000031917, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.HOST_BASED]": 0.07642984999995406, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.LS_PATH_BASED]": 0.07887058799997249, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.PATH_BASED]": 0.07558965200007606, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.HOST_BASED]": 0.09626078599995935, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.LS_PATH_BASED]": 0.07413806400006706, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.PATH_BASED]": 0.07322217200004388, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.HOST_BASED]": 0.07702566299997216, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.LS_PATH_BASED]": 0.07619589600005838, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.PATH_BASED]": 0.0759787090000259, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.HOST_BASED]": 0.07350749300002235, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.LS_PATH_BASED]": 0.07567922399999816, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.PATH_BASED]": 0.07501279400003114, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_multiple_api_keys_validate": 0.5071212729999957, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_with_request_template": 0.0017567589999885058, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_without_request_template": 0.0019377499999677639, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_response_headers_invocation_with_apigw": 1.8057933929999876, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_update_rest_api_deployment": 0.07676386299999649, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[custom]": 0.0018434160000424527, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[proxy]": 0.0017542780000212588, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-GET]": 0.1139157070000465, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-POST]": 0.10478480900002296, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-GET]": 0.10333242800004427, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-POST]": 0.1045801750000237, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-GET]": 0.10142848099997082, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-POST]": 0.09949420200001668, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-GET]": 0.09949811899997485, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-POST]": 0.09679538299997148, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-GET]": 0.09742141499998525, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-POST]": 0.09756221799995046, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-GET]": 0.09938227000003508, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-POST]": 0.10375858100002233, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestTagging::test_tag_api": 0.06936384800008, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_apigw_call_api_with_aws_endpoint_url": 0.01400064099999554, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-ANY]": 3.399788921000038, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-GET]": 3.378014028999985, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-ANY]": 3.3944754569999986, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-GET]": 9.588368939999953, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestCanaryDeployments::test_invoking_canary_deployment": 0.1304895579999652, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment": 0.1331955169999901, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_by_stage_update": 0.13333336500005544, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_validation": 0.10110085700006266, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_with_stage": 0.10907110799996644, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_update_stages": 0.14577936400002045, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_canary_deployment_validation": 0.16637644599990153, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_with_copy_ops": 0.13467170900003111, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator": 2.4615997920000154, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_models": 0.17534835300000395, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_one_ofmodels": 0.18558459099995162, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_body_formatting": 3.4996529319999468, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_path_template_formatting": 0.628897190000032, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_integration_request_parameters_mapping": 0.11012785400004077, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_invocation_trace_id": 2.3368035429999736, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_api_not_existing": 0.032551157999989755, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_proxy_routing_with_hardcoded_resource_sibling": 0.2121103460000313, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_not_found": 0.1219497260000253, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_custom_api_id": 0.13833483900003785, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_hardcoded_resource_sibling_order": 0.20469306899997264, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[False]": 0.40519716100004644, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[True]": 0.45230475300002126, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_update_deployments": 0.33244588300004807, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDocumentations::test_documentation_parts_and_versions": 0.11479638700006944, - "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_create_update_stages": 0.3202888729999813, - "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_update_stage_remove_wildcard": 0.31526051399993094, - "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_api_key_required_for_methods": 0.1953746879999585, - "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_usage_plan_crud": 0.19260923499996352, - "tests/aws/services/apigateway/test_apigateway_custom_ids.py::test_apigateway_custom_ids": 0.09079105299991852, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_error_aws_proxy_not_supported": 0.14670340999998643, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[PutItem]": 0.44720512300000337, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Query]": 0.4590416809999738, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Scan]": 0.3601540660000069, - "tests/aws/services/apigateway/test_apigateway_eventbridge.py::test_apigateway_to_eventbridge": 0.2087996730000441, - "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_api_keys": 0.16784295099989777, - "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_usage_plan_api_keys": 0.16944145500002605, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_create_domain_names": 0.07329601600008573, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.4037520669999708, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETS]": 0.3130234049999103, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.40965416099999175, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETS]": 0.3155040400000644, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_name": 0.07397052900000745, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_names": 0.0719271370000456, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP]": 1.8164248749999956, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP_PROXY]": 1.7498827080000297, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP]": 2.0298281169999655, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP_PROXY]": 2.024674245999961, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP]": 2.9520059260000266, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP_PROXY]": 2.202295377999974, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_proxy_integration_request_data_mappings": 1.981778596999959, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[openapi.spec.tf.json]": 0.3748473929999818, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[swagger-mock-cors.json]": 0.443353590000072, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api": 0.06481902100000525, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[ignore]": 0.8797029519999455, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[prepend]": 0.9219029829999954, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[split]": 0.8851523529999668, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[ignore]": 0.6085407979999786, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[prepend]": 0.630305169000053, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[split]": 0.6079022139999779, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_swagger_api": 0.7859107119999749, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models": 0.28531187400000135, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models_and_request_validation": 0.3994953889999806, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_cognito_auth_identity_source": 0.3857450619999554, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_global_api_key_authorizer": 0.28112125799998466, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_http_method_integration": 0.28810908600001994, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_integer_http_status_code": 0.17882546300000968, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_stage_variables": 1.6661230509999996, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[merge]": 0.34298273899997866, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[overwrite]": 0.3410583930000257, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS]": 2.4024414019999085, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS_PROXY]": 2.408681938999962, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP]": 0.8181987309998249, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP_PROXY]": 0.8030356849999407, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": 6.454628359000026, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_http_integration_status_code_selection": 0.12344339599997056, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_path_param": 0.09995677200004138, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_request_overrides_in_response_template": 0.12366609900004732, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[False]": 0.08879398500005209, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[True]": 0.09053800800006684, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_vtl_map_assignation": 0.09530660299992633, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_response_with_response_template": 1.2166933430000881, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_responses": 0.17058324700002458, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_validation": 0.21587373899990325, - "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecord]": 1.135655194999913, - "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecords]": 1.0992614450000247, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_binary_response": 3.7942881140000964, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_response_payload_format_validation": 4.063599011000065, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration": 1.6757779069998833, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_response_with_mapping_templates": 1.8655714450000005, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_with_request_template": 1.8079630610000095, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration": 4.062134031000028, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_non_post_method": 1.2897300920000134, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_request_data_mapping": 2.8216764090000197, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_response_format": 2.0445097860001624, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_rust_proxy_integration": 1.7827746500000785, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_selection_patterns": 1.9364196969999057, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_put_integration_aws_proxy_uri": 1.3109805709999591, - "tests/aws/services/apigateway/test_apigateway_lambda_cfn.py::TestApigatewayLambdaIntegration::test_scenario_validate_infra": 7.638353240999891, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[CONVERT_TO_TEXT]": 0.6176476680000178, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[None]": 0.5394738730000199, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary": 0.4803462309999986, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary_with_request_template": 0.3061233189999939, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary": 0.5531388009999318, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary_with_request_template": 0.32468523000000005, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_text": 0.577642001000072, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_no_content_handling": 0.5665712100001201, - "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_any": 0.4161948939998865, - "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_method_mapping": 0.459655709000117, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_amz_json_protocol": 0.9801772509999864, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration": 1.1851523539999107, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttribute]": 0.25785856799996054, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttributes]": 0.2524745690000145, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_request_and_response_xml_templates_integration": 0.34808442500013825, - "tests/aws/services/apigateway/test_apigateway_ssm.py::test_get_parameter_query_protocol": 0.0019834490000221194, - "tests/aws/services/apigateway/test_apigateway_ssm.py::test_ssm_aws_integration": 0.21950245400012136, - "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[DeleteStateMachine]": 2.312177140999893, - "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[StartExecution]": 1.4536808509999446, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_api_exceptions": 0.0022912079999741763, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_exceptions": 0.0016714309999770194, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_invalid_desiredstate": 0.0017175680000036664, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_double_create_with_client_token": 0.002082115000007434, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_lifecycle": 0.002318820999903437, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources": 0.0020213000000239845, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources_with_resource_model": 0.0018823979999069707, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_update": 0.0017229379999434968, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[FAIL]": 0.0017355400000269583, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[SUCCESS]": 0.0017351310001458842, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_request": 0.0017461219999859168, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_get_request_status": 0.0018566989999726502, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_invalid_request_token_exc": 0.002325904000144874, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_list_request_status": 0.0018138389999649007, - "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_deleting_resource": 0.0017233779999514809, - "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_single_resource": 4.203915298999959, - "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": 0.001805542999932186, - "tests/aws/services/cloudformation/api/test_changesets.py::test_autoexpand_capability_requirement": 0.08932291500013889, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_non_supported_resource_change_set": 27.22724400800007, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_supported_resource_change_set": 17.806701735000047, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_update_refreshes_template_metadata": 2.155039932999898, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_create_existing": 0.001633058999914283, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_invalid_params": 0.015966558999934932, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_missing_stackname": 0.004827821000048971, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_nonexisting": 0.016184872000053474, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_without_parameters": 0.0017474940000283823, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_with_ssm_parameter": 1.1573612189998812, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_without_parameters": 0.09023810299993329, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_changeset_with_stack_id": 0.24376118700001825, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_delete_create": 2.1556554349999715, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_while_in_review": 0.0018482550000271658, - "tests/aws/services/cloudformation/api/test_changesets.py::test_delete_change_set_exception": 0.021744644000023072, - "tests/aws/services/cloudformation/api/test_changesets.py::test_deleted_changeset": 0.05105870799991408, - "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_nonexisting": 0.013830210000037368, - "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": 0.04928400800008603, - "tests/aws/services/cloudformation/api/test_changesets.py::test_empty_changeset": 1.328948627999921, - "tests/aws/services/cloudformation/api/test_changesets.py::test_execute_change_set": 0.0017321649999075817, - "tests/aws/services/cloudformation/api/test_changesets.py::test_multiple_create_changeset": 0.3614531260000149, - "tests/aws/services/cloudformation/api/test_changesets.py::test_name_conflicts": 1.9197973079998292, - "tests/aws/services/cloudformation/api/test_drift_detection.py::test_drift_detection_on_lambda": 0.001801456000066537, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": 0.001718579000112186, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": 0.0018584750000627537, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": 0.0017523839999284974, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": 0.0017677729999832081, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": 0.001807909000035579, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": 0.0017189720000487796, - "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": 0.0017422749999695952, - "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": 0.0017155049999928451, - "tests/aws/services/cloudformation/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": 0.001733549000050516, - "tests/aws/services/cloudformation/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": 0.0017273859999704655, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": 6.262886840999954, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_lifecycle_nested_stack": 0.0023648509999247835, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_output_in_params": 12.656267582000055, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack": 6.22095801699993, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack_output_refs": 6.23890205400005, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stacks_conditions": 6.232598200999973, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_with_nested_stack": 12.365712428999927, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": 2.1101331890000665, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": 2.1052438460000076, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_reference_unsupported_resource": 2.1164659569999458, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_sub_resolving": 2.120728740000004, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": 0.0016880599998785328, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": 0.001961254999969242, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": 0.0018179660000896547, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": 0.001745798999991166, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": 0.0016605590000153825, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": 0.0017011349999620506, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": 0.0019151379999584606, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource0]": 0.00182810600006178, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource1]": 0.0017859260000250288, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_modifying_with_policy_specifying_resource_id": 0.001772800000026109, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_replacement": 0.0018073649999905683, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": 0.0017013760000281763, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": 0.0017273060000206897, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::S3::Bucket]": 0.0017663989999618934, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::SNS::Topic]": 0.0017604880000590128, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": 0.0017843129999164375, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": 0.0017649059999484962, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": 0.001774002999923141, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": 0.0016868789999762157, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": 0.0017028290000098423, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_empty_policy": 0.001797386999896844, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[False]": 0.001658975000054852, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[True]": 0.0017804059999662059, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_policy": 0.0017856859999483277, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_create_stack_with_custom_id": 1.0596463009999297, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[False-0]": 0.0017657970000755085, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[True-1]": 0.001804891000006137, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": 0.0016743649999853005, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[True-1]": 0.001749214999904325, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": 2.1074563150001495, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": 2.108339416000149, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": 1.0547990000000027, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": 1.0567504480000025, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": 2.1817230719999543, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_stack_resources_for_removed_resource": 18.45106128100008, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": 2.2991229010000325, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": 4.368161413000053, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_name_creation": 0.08920185199997377, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_update_resources": 4.441264477000004, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_actual_update": 4.179479352000044, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": 2.090991225000039, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": 2.256792214999905, - "tests/aws/services/cloudformation/api/test_stacks.py::test_blocked_stack_deletion": 0.0019525179999391185, - "tests/aws/services/cloudformation/api/test_stacks.py::test_describe_stack_events_errors": 0.023749629999883837, - "tests/aws/services/cloudformation/api/test_stacks.py::test_events_resource_types": 2.1503585100000464, - "tests/aws/services/cloudformation/api/test_stacks.py::test_linting_error_during_creation": 0.001829676999932417, - "tests/aws/services/cloudformation/api/test_stacks.py::test_list_parameter_type": 2.1265436880000834, - "tests/aws/services/cloudformation/api/test_stacks.py::test_name_conflicts": 2.402198083999906, - "tests/aws/services/cloudformation/api/test_stacks.py::test_no_echo_parameter": 3.9161584590000302, - "tests/aws/services/cloudformation/api/test_stacks.py::test_notifications": 0.0018007629998919583, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-B-C]": 2.3849829010000576, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-C-B]": 2.386323845999982, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-A-C]": 2.389029623000056, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-C-A]": 2.3938188020000553, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-A-B]": 2.4041329739999355, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-B-A]": 2.389554589000113, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_resource_not_found": 2.116385367000021, - "tests/aws/services/cloudformation/api/test_stacks.py::test_update_termination_protection": 2.132597996999948, - "tests/aws/services/cloudformation/api/test_stacks.py::test_updating_an_updated_stack_sets_status": 6.42036151800005, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": 1.140919112000006, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": 0.09525446200007082, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": 1.1429320170001347, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": 0.09972416499999781, - "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary": 2.2563617829998748, - "tests/aws/services/cloudformation/api/test_templates.py::test_validate_invalid_json_template_should_fail": 0.09040959099991142, - "tests/aws/services/cloudformation/api/test_templates.py::test_validate_template": 0.09211332400002448, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach": 1.2868793879999885, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_multiple_resources": 1.386565983999958, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_use_case": 1.5463610600000948, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_length": 1.269616398999915, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_to_json_string": 1.2850613649999332, - "tests/aws/services/cloudformation/api/test_transformers.py::test_duplicate_resources": 2.3573717790000046, - "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_individual_resource_level": 2.2449173310001242, - "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_property_level": 2.2853386879999107, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_basic_update": 3.1261854889999086, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_diff_after_update": 3.1500545210000155, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_parameters_update": 3.1239089100000683, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_template_error": 0.0016695120000349561, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_set_notification_arn_with_update": 0.0016462579999370064, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_tags": 0.0016588820000151827, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_using_template_url": 3.2000377109999363, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability0]": 0.0017795789999581757, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability1]": 0.0018383289999519548, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": 0.0016748120000329436, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": 3.1228443820000393, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_template": 0.0019446889999699124, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_resource_types": 0.0017927249998592742, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_role_without_permissions": 0.0017733480000288182, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_rollback_configuration": 0.001759071000037693, - "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[missing-def]": 0.0016907060000903584, - "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[multiple-nones]": 0.0016583559998935016, - "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[none-value]": 0.0018622899999627407, - "tests/aws/services/cloudformation/api/test_validations.py::test_missing_resources_block": 0.0016586369999913586, - "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[invalid-key]": 0.0017820590001065284, - "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[missing-type]": 0.0016485470000588975, - "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": 2.117958759999965, - "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": 0.0017751659999021285, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_condition_on_outputs": 2.1252235939999764, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[create]": 2.13701943500007, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[no-create]": 2.1287500720001162, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[dev-us-west-2]": 2.1021212040000137, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[production-us-east-1]": 2.10239591200002, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_with_select": 2.1070476199998893, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[None-FallbackParamValue]": 2.1347859960001188, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[false-DefaultParamValue]": 2.138201448000018, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[true-FallbackParamValue]": 2.1320724930000097, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": 0.0019821429999637985, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_intrinsic_fn_condition": 0.0016866759999629721, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_with_macro": 0.0016739630000301986, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": 0.0017256490000363556, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": 0.0017262409999148076, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": 0.0017245770001181882, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": 0.0017897810001841208, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": 0.0016727309999851059, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_deploys_resource": 2.1094780890000493, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_doesnt_deploy_resource": 0.08724021700004414, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[nope]": 2.096514058000025, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[yep]": 2.097483908000072, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_sub_in_conditions": 2.1233398790000138, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": 4.233549260000132, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level": 2.074252192000017, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level": 2.0730904560000454, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": 2.0966487100000677, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": 0.0017004610000412868, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": 0.0016706059999478384, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": 2.1139441360001, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": 2.094758117999959, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": 0.0017483839999385964, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": 0.0018001660000663833, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_simple_mapping_working": 2.109327890999907, - "tests/aws/services/cloudformation/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": 0.0018667840000716751, - "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_fn_sub_cases": 2.12412515099993, - "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": 2.1114215530000138, - "tests/aws/services/cloudformation/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": 2.1255051870000443, - "tests/aws/services/cloudformation/engine/test_references.py::test_useful_error_when_invalid_ref": 0.01766548300008708, - "tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/test_basic.py::TestBasicCRD::test_black_box": 2.5566669820000243, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_instance_with_key_pair": 2.4513768500002016, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": 7.205947429000048, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_security_group_with_tags": 2.109163421000062, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": 2.5199879269999883, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_autogenerated_values": 2.1006752270000106, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_black_box": 2.138026726000021, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_getatt": 2.1383570699999837, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestUpdates::test_update_without_replacement": 0.0018247059999794146, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Arn]": 0.0017127450000771205, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Id]": 0.0017090480000661046, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Path]": 0.0016925959999980478, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[PermissionsBoundary]": 0.0016998700000385725, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[UserName]": 0.0016497860001436493, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": 2.2857147590000295, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_cfn_handle_iam_role_resource_no_role_name": 2.1389533749999146, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_delete_role_detaches_role_policy": 4.223758951999912, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_user_access_key": 4.20742734800001, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_username_defaultname": 2.1817282070001056, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_managed_policy_with_empty_resource": 2.47778097399987, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_policy_attachments": 2.3026068830000668, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_server_certificate": 2.245159842000021, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_update_inline_policy": 4.28998763200002, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_updating_stack_with_iam_role": 12.262457225000048, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Arn]": 0.001770010000086586, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainArn]": 0.001718093000022236, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainEndpoint]": 0.0018502519999401557, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainName]": 0.00203874699991502, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[EngineVersion]": 0.001752617999954964, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Id]": 0.0018496100000220395, - "tests/aws/services/cloudformation/resource_providers/scheduler/test_scheduler.py::test_schedule_and_group": 2.498942882000051, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestBasicCRD::test_black_box": 0.0020350910000388467, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestUpdates::test_update_without_replacement": 0.0017724959999441126, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[AllowedPattern]": 0.001951621999864983, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[DataType]": 0.0019140310000693717, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Description]": 0.0017333719998759989, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Id]": 0.0017605429999321132, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Name]": 0.0017548219999525827, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Policies]": 0.0017184529999667575, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Tier]": 0.0017294639999363426, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Type]": 0.001711288999899807, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Value]": 0.0017169110000168075, - "tests/aws/services/cloudformation/resources/test_acm.py::test_cfn_acm_certificate": 2.103202859000021, - "tests/aws/services/cloudformation/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": 11.481987719000017, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_account": 2.1575564920001398, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": 2.116327663999982, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_aws_integration": 2.3330158210000036, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_rest_api": 2.3321271110000907, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_swagger_import": 2.3414432530000795, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": 2.6908957749999445, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": 2.2344279829999323, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": 3.316799441999933, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_with_apigateway_resources": 2.3481310099999746, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": 9.962780654000085, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_apigateway_stage": 4.554352121999955, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_usage_plan": 4.492471101000092, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_url_output": 2.19083423699999, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[10]": 8.634859523000046, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[11]": 8.674219913000115, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": 8.682995712999968, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy": 5.5978490960000045, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": 2.437001627999962, - "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_create_macro": 3.201351855000212, - "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_waitcondition": 2.2023980000000165, - "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_creation": 2.1142722259996845, - "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_ext_statistic": 2.1270276670002204, - "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_composite_alarm_creation": 2.4173014210000474, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": 2.4921620569998595, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": 2.4762469000002056, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_default_name_for_table": 2.483544122000012, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_deploy_stack_with_dynamodb_table": 2.232402340000135, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table": 2.492649267999923, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": 2.1494175759999052, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_globalindex_read_write_provisioned_throughput_dynamodb_table": 2.1877572200003215, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_table_with_ttl_and_sse": 2.142040158000327, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_ttl_cdk": 1.270172974000161, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_update_ec2_instance_type": 0.0018225399996936176, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": 2.4730414709999877, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_tables": 2.2050810190003176, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_dhcp_options": 2.325468927000202, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": 2.158153948000063, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_internet_gateway_ref_and_attr": 2.300158639000074, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_keypair_create_import": 2.2343917840000813, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation": 2.241844635999996, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": 2.2321249990002343, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_transit_gateway_attachment": 2.7933389080001234, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_creates_default_sg": 2.474828806000005, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_with_route_table": 2.3085319510000772, - "tests/aws/services/cloudformation/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": 4.214922818000105, - "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_api_destination_resource": 16.395980405000046, - "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_bus_resource": 2.1544752039999366, - "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_creation_without_target": 2.117284753000149, - "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_to_logs": 2.232324323999819, - "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policies": 13.332169992000217, - "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policy_statement": 2.1195276010003, - "tests/aws/services/cloudformation/resources/test_events.py::test_rule_pattern_transformation": 2.134911890000012, - "tests/aws/services/cloudformation/resources/test_events.py::test_rule_properties": 2.1462256440001966, - "tests/aws/services/cloudformation/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": 29.547423629999912, - "tests/aws/services/cloudformation/resources/test_integration.py::test_events_sqs_sns_lambda": 14.78055541499998, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_cfn_handle_kinesis_firehose_resources": 11.387066733999973, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_default_parameters_kinesis": 11.324862966000182, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_describe_template": 0.13614104099997348, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": 11.357302712999854, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_kinesis_stream_consumer_creations": 17.301159116000008, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_stream_creation": 11.35855876100004, - "tests/aws/services/cloudformation/resources/test_kms.py::test_cfn_with_kms_resources": 2.135656648000122, - "tests/aws/services/cloudformation/resources/test_kms.py::test_deploy_stack_with_kms": 2.123311767999894, - "tests/aws/services/cloudformation/resources/test_kms.py::test_kms_key_disabled": 2.1212718999997833, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaDestinations::test_generic_destination_routing[sqs-sqs]": 15.61256068800003, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": 10.724501675000056, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": 21.34937487000002, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": 7.884791771999744, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": 8.15349554799991, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": 9.456715711000015, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_cfn_function_url": 7.571186258000125, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_event_invoke_config": 6.289769599999772, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_alias": 12.531488443999933, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": 11.066651045000071, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run": 6.602450584000053, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_empty_string_replacement_deny_list": 6.182805175000112, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_non_empty_string_replacement_deny_list": 6.178905628999928, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_code_signing_config": 2.2026724590000413, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_function_tags": 6.557388057000026, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_layer_crud": 6.2773250780001035, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_logging_config": 6.2155377429999135, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version": 6.799997513000108, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": 12.581515278000097, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_vpc": 0.0020820070001263957, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter": 11.466839559999926, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": 12.712890368000217, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": 6.218357078000054, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_python_lambda_code_deployed_via_s3": 6.700574433000156, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function": 8.295991722999815, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function_name": 12.322580002999985, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_permissions": 8.302687592999746, - "tests/aws/services/cloudformation/resources/test_logs.py::test_cfn_handle_log_group_resource": 2.40735709899991, - "tests/aws/services/cloudformation/resources/test_logs.py::test_logstream": 2.120762069999955, - "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain": 0.0018734879997737153, - "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain_with_alternative_types": 17.31709127499994, - "tests/aws/services/cloudformation/resources/test_redshift.py::test_redshift_cluster": 2.1340201469997737, - "tests/aws/services/cloudformation/resources/test_resource_groups.py::test_group_defaults": 2.2786965110001347, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_health_check": 2.2646535770002174, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_id": 2.1899505000001227, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_name": 2.18740697200019, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_without_resource_record": 2.1770236969996404, - "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_autoname": 2.1310846019998735, - "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_versioning": 2.1164292809999097, - "tests/aws/services/cloudformation/resources/test_s3.py::test_bucketpolicy": 10.215162469000006, - "tests/aws/services/cloudformation/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": 2.172483757000009, - "tests/aws/services/cloudformation/resources/test_s3.py::test_cors_configuration": 2.5275519839997287, - "tests/aws/services/cloudformation/resources/test_s3.py::test_object_lock_configuration": 2.5219108829999186, - "tests/aws/services/cloudformation/resources/test_s3.py::test_website_configuration": 2.4870599389998915, - "tests/aws/services/cloudformation/resources/test_sam.py::test_cfn_handle_serverless_api_resource": 6.629851936999785, - "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_policies": 6.32645338400016, - "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_sqs_event": 13.490114923999954, - "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_template": 6.634891635000258, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": 1.270135848999871, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": 2.2852553329998955, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": 2.1230692959998123, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": 2.1228311260001647, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": 2.273966974999894, - "tests/aws/services/cloudformation/resources/test_sns.py::test_deploy_stack_with_sns_topic": 2.14185912500011, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription": 2.1272246389999054, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": 2.1660405849997915, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": 2.3463721789996725, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_without_suffix_fails": 2.093717362000234, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_policy_resets_to_default": 1.3770199089997277, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_attributes": 4.47499999799993, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_name": 4.524773090000053, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_with_attributes": 1.2405141169997478, - "tests/aws/services/cloudformation/resources/test_sns.py::test_update_subscription": 4.247274863000257, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_cfn_handle_sqs_resource": 2.144439621000174, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": 2.1098485859999982, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": 2.1151642349998383, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_queue_policy": 2.1237477040001522, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": 4.2113350760000685, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": 4.210529220000126, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_deploy_patch_baseline": 2.2699770309998257, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_maintenance_window": 2.176962357000093, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_parameter_defaults": 2.306172483999717, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameter_tag": 4.202411019999772, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameters": 4.183204357999557, - "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": 1.143669811000109, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke": 9.550658830999964, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost": 9.59580293199997, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost_with_path": 15.700124639999785, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_with_path": 15.636654505000024, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": 4.789177542999823, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_with_dependencies": 2.1900348299998313, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_nested_statemachine_with_sync2": 15.495529817999795, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_retry_and_catch": 0.0027130080000006274, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": 2.6778726249999636, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_definitionsubstitution": 7.314838959000099, - "tests/aws/services/cloudformation/test_cloudformation_ui.py::TestCloudFormationUi::test_get_cloudformation_ui": 0.07332294999991973, - "tests/aws/services/cloudformation/test_cloudtrail_trace.py::test_cloudtrail_trace_example": 0.0017383449999215372, - "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_cfn_with_exports": 2.1333055009999953, - "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_import_values_across_stacks": 4.213759987000003, - "tests/aws/services/cloudformation/test_template_engine.py::TestImports::test_stack_imports": 4.220892208000123, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-0-False]": 0.08964925200007201, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-1-False]": 0.08422666199999185, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-0-False]": 0.08488471499981642, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-1-True]": 2.1260705879999477, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-0-False]": 0.08623441800023102, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-1-True]": 2.1192471740000656, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-0-True]": 2.1387521929998456, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-1-True]": 2.140584247000106, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_base64_sub_and_getatt_functions": 2.1050183369998194, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": 2.102920144999871, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cidr_function": 0.0019710050003141077, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_find_map_function": 2.106992183000102, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-northeast-1]": 2.1157430159998967, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-southeast-2]": 2.118299900999773, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-central-1]": 2.1192461379998804, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-west-1]": 2.115412787999958, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-1]": 2.102858225000091, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-2]": 2.117316669999809, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-1]": 2.116469378000147, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-2]": 2.113965917000087, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": 2.1053546840000763, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_split_length_and_join_functions": 2.1763561250002113, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_not_ready": 2.1185990649998985, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_number_type": 2.1050137680001626, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_to_json_functions": 0.0020020740003019455, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_attribute_uses_macro": 5.71837533799976, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_capabilities_requirements": 5.290424990000247, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": 0.02768524099997194, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": 3.6952866749998066, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": 3.7014039419998426, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": 3.671679276000077, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": 3.6547270949999984, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": 4.69161074800013, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_global_scope": 5.095732750999787, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_macro_deployment": 3.231526179999946, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_pyplate_param_type_list": 8.741712994999943, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_scope_order_and_parameters": 0.002084426000010353, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": 5.746132134999925, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": 5.749774039000158, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": 3.761602764000145, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_validate_lambda_internals": 5.246526786000004, - "tests/aws/services/cloudformation/test_template_engine.py::TestPreviousValues::test_parameter_usepreviousvalue_behavior": 0.0019251359999543638, - "tests/aws/services/cloudformation/test_template_engine.py::TestPseudoParameters::test_stack_id": 2.104047305999984, - "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager.yaml]": 2.1310968800000865, - "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_full.yaml]": 2.11986049799998, - "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_partial.yaml]": 2.1146658840000327, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": 2.1632730360001915, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": 2.167986142000018, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm": 2.127064709000024, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_secure": 2.12845302300002, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_with_version": 2.1652705839996997, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": 7.458595521000007, - "tests/aws/services/cloudformation/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": 2.3791878570000335, - "tests/aws/services/cloudformation/test_template_engine.py::TestTypes::test_implicit_type_conversion": 2.161635395000303, - "tests/aws/services/cloudformation/test_unsupported.py::test_unsupported": 2.1033945589999803, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_deleting_resource": 0.0017235239999990881, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_simple_update_single_resource": 0.0019867199998770957, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": 0.001741058000106932, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_autoexpand_capability_requirement": 0.0017797410000639502, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_and_then_remove_non_supported_resource_change_set": 0.0016536529999484628, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_and_then_remove_supported_resource_change_set": 0.00181669099993087, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_and_then_update_refreshes_template_metadata": 0.0016712470001039037, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_create_existing": 0.0016903119999369665, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_invalid_params": 0.001792554999838103, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_missing_stackname": 0.0022553450000941666, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_update_nonexisting": 0.0016758350000145583, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_update_without_parameters": 0.001668050000034782, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_with_ssm_parameter": 0.0016511690000697854, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_without_parameters": 0.0017023749999225402, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_changeset_with_stack_id": 0.0018163299998832372, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_delete_create": 0.0016352380000626, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_while_in_review": 0.0017116320000241103, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_delete_change_set_exception": 0.001698206999890317, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_deleted_changeset": 0.0018160699999043572, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_nonexisting": 0.001783457999863458, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": 0.0016695619999609335, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_empty_changeset": 0.0018521370000144088, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_execute_change_set": 0.0018994260001363727, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_multiple_create_changeset": 0.001713917000188303, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_name_conflicts": 0.0016986770001494733, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py::test_drift_detection_on_lambda": 0.0017151990000456863, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": 0.0017232350000995211, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": 0.001835976999700506, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": 0.0017163709999294952, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": 0.0016968439999800466, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": 0.0016727689996969275, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": 0.0019036030000734172, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": 0.0019336509999448026, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": 0.0016879680001693487, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": 0.001720890000115105, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": 0.0016984680000859953, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": 0.0016937380000854318, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_lifecycle_nested_stack": 0.0017744109998147906, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_output_in_params": 0.0017189360000884335, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_stack": 0.0018285020000803343, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_stack_output_refs": 0.0016813849999834929, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_stacks_conditions": 0.0016877870000371331, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_with_nested_stack": 0.0018146469999464898, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": 0.001734095000074376, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": 0.0017131850001987914, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_reference_unsupported_resource": 0.0016889600001377403, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_sub_resolving": 0.0017052300001978438, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": 0.001709838000124364, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": 0.0018338520001179859, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": 0.001799057999960496, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": 0.001690101000122013, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": 0.0018431500000133383, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": 0.0017103700001825928, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": 0.001713484999982029, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource0]": 0.0017426999997951498, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource1]": 0.001716681999823777, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_modifying_with_policy_specifying_resource_id": 0.0016971759998796188, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_replacement": 0.0016691230002834345, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": 0.0016989689997899404, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": 0.0017215709999618412, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::S3::Bucket]": 0.0017201279997607344, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::SNS::Topic]": 0.0016897609998522967, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": 0.0016901819999475265, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": 0.0018265100002281542, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": 0.0017044900000655616, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": 0.0017103499999393534, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": 0.0017869250000330794, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_update_with_empty_policy": 0.0016639630000554462, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[False]": 0.0016498869999850285, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[True]": 0.0016779990000941325, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_update_with_policy": 0.0016693529998974554, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_create_stack_with_custom_id": 0.001750455999854239, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[False-0]": 0.0017780569999104046, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[True-1]": 0.0016620989999864832, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": 0.001804886999707378, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[True-1]": 0.0017882259999169037, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": 0.0017871649999960937, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": 0.0017563960000188672, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": 0.0026928499999030464, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": 0.0016842299999098032, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": 0.0018253969999477704, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_list_stack_resources_for_removed_resource": 0.001634677999845735, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": 0.003515056999958688, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": 0.0018813419999332837, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_name_creation": 0.0016461790003177157, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_update_resources": 0.0016776480001681193, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_actual_update": 0.0018086339998717449, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": 0.0016744120002840646, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": 0.0016165629999704834, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_blocked_stack_deletion": 0.001795380000203295, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_describe_stack_events_errors": 0.0018149069999253697, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_events_resource_types": 0.001670483999987482, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_linting_error_during_creation": 0.001801600999897346, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_list_parameter_type": 0.0016658370002460288, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_name_conflicts": 0.0017978459998175822, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_no_echo_parameter": 0.0016944299998158385, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_notifications": 0.001774180999973396, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-B-C]": 0.0016742519999297656, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-C-B]": 0.001706192999790801, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-A-C]": 0.0016916250001486333, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-C-A]": 0.0016806840001208911, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-A-B]": 0.0017207400001097994, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-B-A]": 0.001758431000098426, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_resource_not_found": 0.0017059420001714898, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_update_termination_protection": 0.0017720159999043972, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_updating_an_updated_stack_sets_status": 0.0019514640000579675, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": 0.001728274000242891, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": 0.0016871759999048663, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": 0.0017169630000353209, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": 0.0016617779999705817, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_get_template_summary": 0.0017513170000711398, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_invalid_json_template_should_fail": 0.0016949410000961507, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_template": 0.0016985380000278383, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_duplicate_resources": 0.0017502350001450395, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_individual_resource_level": 0.0017053300000497984, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_property_level": 0.001998632000095313, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_basic_update": 0.001691623999931835, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_diff_after_update": 0.0016937190000589908, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_parameters_update": 0.001678520000041317, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_template_error": 0.0016723400001410482, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_set_notification_arn_with_update": 0.0016622790001292742, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_tags": 0.0016795820001789252, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_using_template_url": 0.0016387240002586623, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_capabilities[capability0]": 0.001814044999946418, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_capabilities[capability1]": 0.0018373789998804568, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": 0.0018282920000274316, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_previous_parameter_value": 0.0017151189999822236, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_previous_template": 0.0018118509999567323, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_resource_types": 0.001732491999746344, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_role_without_permissions": 0.0018157390002215834, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_rollback_configuration": 0.0017220919999090256, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[missing-def]": 0.0018446529998072947, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[multiple-nones]": 0.0017364999998790154, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[none-value]": 0.0016850419999627775, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_missing_resources_block": 0.0016888499999367923, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[invalid-key]": 0.0018640090002008947, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[missing-type]": 0.0016791509999620757, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": 0.0018324199998005497, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": 0.0018111399999725109, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_condition_on_outputs": 0.00179946900016148, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[create]": 0.0016814849998354475, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[no-create]": 0.0018406650001452363, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[dev-us-west-2]": 0.0016677700000400364, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[production-us-east-1]": 0.0016551660000914126, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_with_select": 0.0016477029998895887, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[None-FallbackParamValue]": 0.0016784000001734967, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[false-DefaultParamValue]": 0.001681885999687438, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": 0.0018248149999635643, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_intrinsic_fn_condition": 0.0016250690000561008, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_with_macro": 0.0019515550000051007, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": 0.0017046889997800463, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": 0.001822922000201288, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": 0.0018188850001479295, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": 0.0018030539999926987, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": 0.0017044180001448694, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_deploys_resource": 0.0018210190000900184, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_doesnt_deploy_resource": 0.0018338129998483055, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[nope]": 0.0017868929999167449, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[yep]": 0.0016552360000332556, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_sub_in_conditions": 0.0017058920000181388, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": 0.0017761039998731576, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level": 0.0017006709999805025, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level": 0.0017273630001000129, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": 0.0018210890000318614, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": 0.0018563349999567436, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": 0.0018329399999856832, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": 0.0019993739999790705, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": 0.0018118810000942176, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": 0.0018055489999824204, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": 0.0016977770001176395, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_simple_mapping_working": 0.0017975740001929807, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": 0.0018111100000623992, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_fn_sub_cases": 0.0017092380001031415, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": 0.0018495399999665096, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": 0.0017825160000484175, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_useful_error_when_invalid_ref": 0.0018190049997883762, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_acm.py::test_cfn_acm_certificate": 0.0018359570001393877, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": 0.0016857139999046922, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_account": 0.0016862449999734963, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": 0.001695611999821267, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_apigateway_aws_integration": 0.0017970940000395785, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_apigateway_rest_api": 0.0016967550000117626, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_apigateway_swagger_import": 0.0016738110000460438, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": 0.001699358999985634, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": 0.0018615239998780453, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": 0.001790731000028245, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_with_apigateway_resources": 0.0017010620001656207, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": 0.0016958630001226993, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_apigateway_stage": 0.0016983880000225327, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_usage_plan": 0.0016916960000799008, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_url_output": 0.0017248869999093586, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[10]": 0.001978144000077009, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[11]": 0.0016880480002328113, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": 0.0016510780001226522, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy": 0.0016527709999536455, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": 0.0016752940000515082, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_create_macro": 0.0016905629997836513, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_waitcondition": 0.0017012840000916185, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_creation": 0.0016896509998787224, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_ext_statistic": 0.0017029069999807689, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_composite_alarm_creation": 0.0017780390001007618, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": 0.0016942810000273312, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": 0.0016387860000577348, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_default_name_for_table": 0.0016486050001276453, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_deploy_stack_with_dynamodb_table": 0.0016618889999335806, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table": 0.0016626609999548236, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": 0.0018572460000996216, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_globalindex_read_write_provisioned_throughput_dynamodb_table": 0.0016321319999406114, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_table_with_ttl_and_sse": 0.0017833370000062132, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_ttl_cdk": 0.0016485029998420941, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_update_ec2_instance_type": 0.0017888570000650361, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": 0.0016953520000697608, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_with_multiple_route_tables": 0.0016884590002064215, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_dhcp_options": 0.0016788609998457105, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": 0.0017795799999476003, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_internet_gateway_ref_and_attr": 0.001700270999890563, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_keypair_create_import": 0.0023714049998488917, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation": 0.001690673999974024, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": 0.0017877069999485684, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_transit_gateway_attachment": 0.0016975059998003417, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_creates_default_sg": 0.001676576999898316, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_with_route_table": 0.0017451260000598268, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": 0.0016977569998744002, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_cfn_event_api_destination_resource": 0.0016754549999404844, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_cfn_event_bus_resource": 0.001675765000300089, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_event_rule_creation_without_target": 0.0016924359997574356, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_event_rule_to_logs": 0.0016655960000662162, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_eventbus_policies": 0.0016750639997553662, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_eventbus_policy_statement": 0.0016676499999448424, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_pattern_transformation": 0.0018699899999319314, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_properties": 0.0017137759998604452, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": 0.0016666980002355558, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py::test_events_sqs_sns_lambda": 0.00329278900017016, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_cfn_handle_kinesis_firehose_resources": 0.001773138000316976, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_default_parameters_kinesis": 0.0017935170001237566, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_describe_template": 0.0016922160000376607, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": 0.0017784589997518196, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_kinesis_stream_consumer_creations": 0.0021793629998683173, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_stream_creation": 0.001796732999764572, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_cfn_with_kms_resources": 0.0017107199998918077, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_deploy_stack_with_kms": 0.0016710360000615765, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_kms_key_disabled": 0.0018011309998655634, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaDestinations::test_generic_destination_routing[sqs-sqs]": 0.0018442919999870355, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": 0.0018948469999031659, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": 0.0016773479999301344, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": 0.0017946689999916998, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": 0.0018134039999040397, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": 0.0016799620000256255, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_cfn_function_url": 0.0017666270002791862, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_event_invoke_config": 0.0016828980001264426, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_alias": 0.0016984070000489737, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": 0.001676395999993474, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_run": 0.001647542999990037, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_run_with_empty_string_replacement_deny_list": 0.0016486940000959294, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_run_with_non_empty_string_replacement_deny_list": 0.0018721149999691988, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_code_signing_config": 0.001695931999847744, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_function_tags": 0.0017808220000006258, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_layer_crud": 0.0016838910000842588, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_logging_config": 0.0018428199998652417, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version": 0.0016892000001007546, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": 0.0016805029999886756, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_vpc": 0.0017119930000717432, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter": 0.001684631000216541, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": 0.0017208500000833737, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": 0.0017974540000977868, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_python_lambda_code_deployed_via_s3": 0.0016693420000137849, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_function": 0.001705941999944116, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_function_name": 0.0017860420000488375, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_permissions": 0.0016682309999396239, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py::test_cfn_handle_log_group_resource": 0.0018377200001395977, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py::test_logstream": 0.0016848320001372485, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain": 0.0017543020001085097, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain_with_alternative_types": 0.0016806340001949138, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py::test_redshift_cluster": 0.0016836899999361776, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py::test_group_defaults": 0.0016862449999734963, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_health_check": 0.0018637789999047527, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_record_set_via_id": 0.001683809999803998, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_record_set_via_name": 0.0016877260000001115, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_record_set_without_resource_record": 0.0018404049999389827, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucket_autoname": 0.0017966519999390584, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucket_versioning": 0.0016780289997768705, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucketpolicy": 0.0018148569999993924, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": 0.0018787060000704514, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cors_configuration": 0.0016968150000593596, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_object_lock_configuration": 0.0016885390000425105, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_website_configuration": 0.0017139660001248558, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_cfn_handle_serverless_api_resource": 0.001695763000043371, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_policies": 0.0016953919998741185, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_sqs_event": 0.0016917850000481849, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_template": 0.0017366990000482474, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": 0.001769240000157879, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": 0.0018164089999572752, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": 0.0018601209999360435, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": 0.0017155399998500798, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": 0.0016824570000153471, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_deploy_stack_with_sns_topic": 0.0016731300001993077, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_subscription": 0.0018112089999249292, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": 0.0016994900001918722, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_fifo_without_suffix_fails": 0.0017242360002001078, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_with_attributes": 0.0021096929997383995, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_update_subscription": 0.0017188760000408365, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_cfn_handle_sqs_resource": 0.001718196000183525, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": 0.0017217520000940567, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": 0.0017578199999661592, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_queue_policy": 0.0017452659999435127, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_queue_no_change": 0.0017346249999263819, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_sqs_queuepolicy": 0.0017400060000909434, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_deploy_patch_baseline": 0.0018159779999677994, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_maintenance_window": 0.001825676999942516, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_parameter_defaults": 0.0016709559999981138, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_update_ssm_parameter_tag": 0.0017308280000634113, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_update_ssm_parameters": 0.002274052000075244, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": 0.0018148370002109004, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_apigateway_invoke": 9.55670019199988, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_apigateway_invoke_localhost": 9.584605539999984, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_apigateway_invoke_localhost_with_path": 15.692652052000085, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_apigateway_invoke_with_path": 15.646685982000008, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": 4.851693664999857, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_cfn_statemachine_with_dependencies": 2.1767626269995617, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_nested_statemachine_with_sync2": 0.0021158739998554665, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_retry_and_catch": 0.002625191000106497, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": 2.679367164000041, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_statemachine_definitionsubstitution": 7.326691123999581, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImportValues::test_cfn_with_exports": 0.0018481789998077147, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImportValues::test_import_values_across_stacks": 0.0017253270000310295, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImports::test_stack_imports": 0.0017042289998698834, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-0-False]": 0.001812481999877491, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-1-False]": 0.0018454229998496885, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-0-False]": 0.001703666000139492, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-1-True]": 0.0018242640001062682, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-0-False]": 0.001811089000057109, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-1-True]": 0.0018392220001715032, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-0-True]": 0.0016692019999027252, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-1-True]": 0.001964799999996103, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_base64_sub_and_getatt_functions": 0.0017246960001102707, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": 0.001845413999944867, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_cidr_function": 0.0016858829999364389, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_find_map_function": 0.001707173999875522, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-northeast-1]": 0.0017478900001606235, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-southeast-2]": 0.0016470099999423837, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-central-1]": 0.0016771180000887398, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-west-1]": 0.0018514749999667401, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-1]": 0.001696795000043494, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-2]": 0.001673230000278636, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-1]": 0.0016384739999466547, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-2]": 0.0017933750000338478, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": 0.0017050199999175675, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_split_length_and_join_functions": 0.0017103499999393534, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_sub_not_ready": 0.0016859340000792145, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_sub_number_type": 0.0016717969997444015, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_to_json_functions": 0.0017297660001531767, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_attribute_uses_macro": 0.0017378120001012576, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_capabilities_requirements": 0.001692386000058832, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": 0.0017056810002031852, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": 0.0016890389999844047, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": 0.001696092999964094, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": 0.0017201979997025774, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": 0.0017487510001501505, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": 0.001725908000025811, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_global_scope": 0.0017288839999309857, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_macro_deployment": 0.001692316000116989, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_pyplate_param_type_list": 0.0018219290000160981, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_scope_order_and_parameters": 0.001714959000082672, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": 0.0017161719997602631, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": 0.0016997699997318705, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": 0.001674000999855707, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_validate_lambda_internals": 0.0016987270000754506, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestPreviousValues::test_parameter_usepreviousvalue_behavior": 0.0018217800002275908, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestPseudoParameters::test_stack_id": 0.0017287039997881948, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager.yaml]": 0.0017080749998967804, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_full.yaml]": 0.0016871469997568056, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_partial.yaml]": 0.0018325589999221847, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": 0.001732661999994889, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": 0.0018173420000948681, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_resolve_ssm": 0.0017745410000316042, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_resolve_ssm_secure": 0.0016943190000802133, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_resolve_ssm_with_version": 0.0018198770001163211, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": 0.0016926470000271365, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": 0.0017125839999607706, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestTypes::test_implicit_type_conversion": 0.0019701090000125987, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": 0.0016892300000108662, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": 0.0016801029996713623, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": 0.0017243259999304428, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": 0.0017388329999903362, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": 0.0016767370000252413, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": 0.0019475070000680716, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": 0.00167573499993523, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": 0.001654263999853356, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": 0.0017023049999806972, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": 0.001713946999871041, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": 0.00170007099995928, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": 0.0017363389999900392, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": 0.0016983069999696454, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": 0.0019328380001297774, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": 0.0018102590001944918, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": 0.0017719749998832413, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": 0.0018616449999626639, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": 0.001899123999692165, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": 0.0016359300000203802, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": 0.0018022530000507686, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": 0.0020442969998839544, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": 0.0018097470001521287, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": 0.0018465579996700399, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": 0.002085636000174418, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": 0.0018310569998902793, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": 0.0024419459998625825, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": 0.0017792999999528547, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": 0.0016353779999462859, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": 0.0017669059998297598, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": 0.0017667370000253868, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": 0.0016881470000953414, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": 0.0016976259998955356, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": 0.0016824959998302802, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": 0.0016775280000729254, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": 0.0016920450000270648, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": 0.0018032859998129425, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": 0.0018512150002152339, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": 0.001958166000122219, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": 0.002630271000043649, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": 0.0018072519997076597, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": 0.0017117819998020423, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": 0.001813523000009809, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": 0.0018322190001072158, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": 0.0016858030001003499, - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": 0.001711601999886625, - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": 0.001663472000245747, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": 0.0018693700001222169, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": 0.0017495929998858628, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": 0.0018855289999919478, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": 0.002151330999822676, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": 0.001835615000118196, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": 0.001983103000156916, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": 0.0018663130001641548, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": 0.0016807940000944654, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": 0.0017692399999305053, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": 0.00170671199998651, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": 0.0017727369997828646, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": 0.0019083720001162874, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": 0.0017145480001090618, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": 0.0017811529999107734, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": 0.0018243150000216701, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": 0.0018056389997127553, - "tests/aws/services/cloudformation/v2/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": 0.0017073240001082013, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": 0.0016461689997413487, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": 0.0018166610000207584, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": 0.0016723999999612715, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": 0.0016454180001801433, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": 0.0017560850001245853, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": 0.0016841009999097878, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": 0.0017815749999954278, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": 0.001802894000093147, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": 0.001684521000015593, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": 0.0016738509998504014, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": 0.00165469600005963, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": 0.0018175010000049951, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": 0.0016636110001400084, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": 0.0016616679997696338, - "tests/aws/services/cloudformation/v2/test_change_sets.py::test_single_resource_static_update": 0.001830304999884902, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_alarm_lambda_target": 1.6588327070001014, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle": 0.0017814530001487583, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created": 2.4053226489997996, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions": 5.327894351999703, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream": 0.0017906409998431627, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle": 0.13635574800014183, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering": 0.1217799759999707, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm": 0.08614855199994054, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly": 0.07565382800021325, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm": 0.0789208129999679, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions": 10.267964980000215, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data": 2.0696550639997895, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data0]": 0.0017495630002031248, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data1]": 0.0017082350000237057, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data2]": 0.0016820660000576027, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics": 1.054883954999923, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination": 2.193059437999864, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Average]": 0.03721415499990144, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Maximum]": 0.03557060400021328, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Minimum]": 0.03759001799994621, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[SampleCount]": 0.034644050999986575, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Sum]": 0.03631544700010636, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units": 0.027011082999933933, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions": 0.0456323319999683, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels": 0.041791563000060705, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics": 0.1820999319998009, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results": 0.04700870300007409, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions": 0.035240652999846134, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units": 0.03014209200023288, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule": 0.0016768159998719057, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints": 0.53788765399986, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name": 0.01694008100002975, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs0]": 0.04137902499996926, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs1]": 0.03300057700016623, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs2]": 0.03621734200009996, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs3]": 0.03134011600013764, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs4]": 0.032011985999815806, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs5]": 0.03184679200012397, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs6]": 0.03489718600008018, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination": 5.487225811999679, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness": 2.0642293529999733, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters": 4.089517793999903, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget": 0.001764189999903465, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions": 2.113000705999866, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics": 0.05675538199966468, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_parallel_put_metric_data_list_metrics": 0.26865911299978507, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms": 0.08756290399992395, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm": 10.626126554999928, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character": 0.07427573499990103, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_gzip": 0.025508685999966474, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation": 0.042883724000148504, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list": 0.03484400100023777, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc": 0.031368503999829045, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_raw_metric_data": 0.02475959900016278, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm": 2.3241947650001293, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input": 0.08598116500002106, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags": 0.130997010000101, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm": 4.622326079999766, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_error": 2.565200761999904, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_successful": 2.505402973000173, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSQSMetrics::test_alarm_number_of_messages_sent": 61.344707223999876, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSqsApproximateMetrics::test_sqs_approximate_metrics": 4.301799806999952, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_binary": 0.11349163599999201, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items": 0.14627707300002157, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items_streaming": 1.2025184160000322, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_existing_table": 0.3768779879999897, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_matching_schema": 0.156668475999993, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_binary_data_with_stream": 2.41027739499998, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_continuous_backup_update": 0.6152465350000398, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_create_duplicate_table": 0.12191865000005464, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_data_encoding_consistency": 2.0314438209999253, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_delete_table": 0.11897333099994967, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_batch_execute_statement": 0.15081189100004622, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_class": 0.19210136700002067, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_partial_sse_specification": 0.6410952319999978, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_sse_specification": 0.11019953400000304, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_statement_empy_parameter": 0.12211886099998992, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_transaction": 0.2074975699999868, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_get_batch_items": 0.14252459000005047, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_idempotent_writing": 0.1841898709999441, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_partiql_missing": 0.1308254310000052, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_pay_per_request": 0.05984891499997502, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_records_with_update_item": 0.0036306599999988975, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_shard_iterator": 0.9187986900000169, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_stream_view_type": 1.4272737469999583, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_describe_with_exclusive_start_shard_id": 0.8492215069999247, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_shard_iterator_format": 3.151018262999969, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_update_table_without_sse_specification_change": 0.1575417570000468, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_with_kinesis_stream": 1.5270516769999745, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_empty_and_binary_values": 0.09601939199995968, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables": 0.10391515699996035, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables_version_2019": 0.48177860900005953, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PAY_PER_REQUEST]": 0.6263888520000478, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PROVISIONED]": 1.0917168589999733, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_invalid_query_index": 0.08313355399997135, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_large_data_download": 2.196408341999984, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_list_tags_of_resource": 0.09998308000001543, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_more_than_20_global_secondary_indexes": 0.3060519480000039, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_multiple_update_expressions": 0.193885236999904, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_non_ascii_chars": 3.675071284000012, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_nosql_workbench_localhost_region": 0.1730570020000073, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_query_on_deleted_resource": 0.6398077360000798, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_in_put_item": 0.1343439750000357, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_on_conditions_check_failure": 0.3338704630000393, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_stream_destination_records": 12.188755568999966, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_streams_on_global_tables": 1.3810885899999903, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live": 0.2813494790000277, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live_deletion": 0.5211896499999398, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_get_items": 0.11801381499992658, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming": 1.6436349730000188, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming_for_different_tables": 1.516481970999962, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_binary_data": 0.10573595300002125, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_canceled": 0.13781535000003942, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_items": 0.11073084000003064, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_local_secondary_index": 0.13313932499994507, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_query_index": 0.11534692899999754, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_enable_kinesis_streaming_destination": 0.0018199290000211477, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_non_existent_stream": 0.033307666000041536, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_stream_spec_and_region_replacement": 2.3782015960000535, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_table_v2_stream": 3.436000407999927, - "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3": 0.8893998539999757, - "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3_validation": 0.26893967099999827, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_route_table_association": 1.6482777530000021, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-id_manager]": 0.0872756140000206, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-tag]": 0.06628052000002072, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-id_manager]": 0.05318838499999856, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-tag]": 0.350141973999996, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id": 0.07275645400005715, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id_and_vpc_id": 0.08844031700004962, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_tags": 0.15842780500003073, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_endpoint": 0.2315456210000093, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_with_custom_id": 0.14685428699999648, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpc_endpoints_with_filter": 0.6905055550000156, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpn_gateways_filter_by_vpc": 0.4941545809999752, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_get_security_groups_for_vpc": 0.617765808999934, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[id]": 0.11494995800006791, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[name]": 0.0912102619999473, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_reserved_instance_api": 0.04226078100009545, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vcp_peering_difference_regions": 1.635318161999919, - "tests/aws/services/ec2/test_ec2.py::test_create_specific_vpc_id": 0.02708632300004865, - "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_ids": 0.5261020180000173, - "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_names": 0.3469415580000259, - "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filters": 0.47863561100007246, - "tests/aws/services/ec2/test_ec2.py::test_pickle_ec2_backend": 2.3284769100000062, - "tests/aws/services/ec2/test_ec2.py::test_raise_create_volume_without_size": 0.025804088999962005, - "tests/aws/services/ec2/test_ec2.py::test_raise_duplicate_launch_template_name": 0.04926594399995565, - "tests/aws/services/ec2/test_ec2.py::test_raise_invalid_launch_template_name": 0.019498324999972283, - "tests/aws/services/ec2/test_ec2.py::test_raise_modify_to_invalid_default_version": 0.05252567400003727, - "tests/aws/services/ec2/test_ec2.py::test_raise_when_launch_template_data_missing": 0.016165004999947996, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_domain": 0.003018074000010529, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_existing_domain_causes_exception": 0.003031398999951307, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_describe_domains": 0.002428226999995786, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_domain_version": 0.0030371299999956136, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_version_for_domain": 0.0019651680000265515, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_versions": 0.0021843499999363303, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_list_versions": 0.002715414999954646, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_path_endpoint_strategy": 0.0030334530000573068, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_update_domain_config": 0.0026147569999466214, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth0]": 0.23469952200008493, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth1]": 0.15394945100001678, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth2]": 0.1223246079999285, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_invalid_parameters": 0.02115666700001384, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_name_validation": 0.05575944999998228, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[api-key]": 0.0540015199999857, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[basic]": 0.05361437499999511, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[oauth]": 0.05495488499991552, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection": 0.06798480000003337, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_invalid_parameters": 0.017069528000035916, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_name_validation": 0.014286934999915957, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params0]": 0.07059437300006266, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params1]": 0.056218457999932525, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params2]": 0.06160210199999483, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_delete_connection": 0.13109994299992422, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_list_connections": 0.05958683800008657, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_update_connection": 0.0980421550000301, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[custom]": 0.08734138699998084, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[default]": 0.05860850100009429, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_unknown_event_bus": 0.014170994999972208, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[custom]": 0.11181014900000719, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[default]": 0.09229064799995967, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_delete_archive_error_unknown_archive": 0.013437099000100261, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_describe_archive_error_unknown_archive": 0.013217936999978974, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_error_unknown_source_arn": 0.013384460000054332, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[custom]": 0.1051071169999318, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[default]": 0.07749451299997645, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-custom]": 0.5411336529999744, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-default]": 0.515105186000028, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-custom]": 0.6220621250000136, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-default]": 0.7255060229999231, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[custom]": 0.09801773099997035, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[default]": 0.07185793400003604, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[custom]": 0.09465817900002094, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[default]": 0.05763519100003123, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_update_archive_error_unknown_archive": 0.001707845999987967, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_describe_replay_error_unknown_replay": 0.013978424000015366, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replay_with_limit": 0.20878605499996183, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_event_source_arn": 0.09801699600001257, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_prefix": 0.15152677599996878, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[custom]": 0.001698627999985547, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[default]": 0.0017006219999871064, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_different_archive": 0.11871430000002192, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_name_same_archive": 0.06989189299997633, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[0]": 0.06381381500000316, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[10]": 0.06427945100000443, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_archive": 0.013826757000003909, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_event_bus": 0.09415415000000849, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::tests_concurrency_error_too_many_active_replays": 0.0017979939999577255, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions0]": 0.04175286000003098, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions1]": 0.11111188900002844, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions0]": 0.04467408600004319, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions1]": 0.12745141699997475, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_multiple_event_buses_same_name": 0.041425725000010516, - "tests/aws/services/events/test_events.py::TestEventBus::test_delete_default_event_bus": 0.01302329199995711, - "tests/aws/services/events/test_events.py::TestEventBus::test_describe_delete_not_existing_event_bus": 0.024849921999987146, - "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_limit": 0.2282282749999922, - "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_prefix": 0.07724721299990733, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[domain]": 0.2837156449999725, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[path]": 0.28376381499998615, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[standard]": 0.42943199400008325, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_nonexistent_event_bus": 0.1535605530000339, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_to_default_eventbus_for_custom_eventbus": 1.031943348000027, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[custom]": 0.29073789900007796, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[default]": 0.09472468299998127, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission_non_existing_event_bus": 0.01379484000000275, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[custom]": 0.0834056490000421, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[default]": 0.06415180200002624, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-custom]": 0.04245576499994286, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-default]": 0.02076899000002186, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-custom]": 0.04953463399994007, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-default]": 0.027508268000019598, - "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_nested": 10.228749643999947, - "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_with_values_in_array": 5.283841778999999, - "tests/aws/services/events/test_events.py::TestEventRule::test_delete_rule_with_targets": 0.09435497599997689, - "tests/aws/services/events/test_events.py::TestEventRule::test_describe_nonexistent_rule": 0.01850615400002198, - "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[custom]": 0.11928338600000643, - "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[default]": 0.07781191099996931, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[custom]": 0.20858407599996553, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[default]": 0.1563032209999733, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[custom]": 0.13232287200003157, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[default]": 0.0914042509999149, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[custom]": 0.28537931100004243, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[default]": 0.26561828599994897, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_with_limit": 0.258920729999943, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_pattern_to_single_matching_rules_single_target": 7.348347513999954, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_different_targets": 0.5252856949999796, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_single_target": 18.615692163999995, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_single_matching_rules_single_target": 10.426828766000028, - "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[custom]": 0.08185192199999847, - "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[default]": 0.055609611000022596, - "tests/aws/services/events/test_events.py::TestEventRule::test_put_multiple_rules_with_same_name": 0.07952248800000916, - "tests/aws/services/events/test_events.py::TestEventRule::test_update_rule_with_targets": 0.10432465199994567, - "tests/aws/services/events/test_events.py::TestEventTarget::test_add_exceed_fife_targets_per_rule": 0.09373364599997558, - "tests/aws/services/events/test_events.py::TestEventTarget::test_list_target_by_rule_limit": 0.14459214499999007, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[custom]": 0.10854775699993979, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[default]": 0.07789943799997445, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_across_different_rules": 0.10777059900004815, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_single_rule": 0.07667117599999074, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_across_different_rules": 0.1110918530000049, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_single_rule": 0.07648567600000433, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_target_id_validation": 0.09121507799994788, - "tests/aws/services/events/test_events.py::TestEvents::test_create_connection_validations": 0.01308466200003977, - "tests/aws/services/events/test_events.py::TestEvents::test_events_written_to_disk_are_timestamp_prefixed_for_chronological_ordering": 0.0017903000000387692, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[ARRAY]": 0.013749877000009292, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[MALFORMED_JSON]": 0.013566947999947843, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[SERIALIZED_STRING]": 0.013508372999979201, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[STRING]": 0.013660237000067355, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_with_too_big_detail": 0.017984473000069556, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail": 0.013129133000006732, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail_type": 0.013097560000005615, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[custom]": 0.0436409479999611, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[default]": 0.015689125000051263, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_response_entries_order": 0.2882998000000043, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_time": 0.977946060000022, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_target_delivery_failure": 1.1554350709998857, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_time_field": 0.18852878599994938, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_without_source": 0.013379289999988941, - "tests/aws/services/events/test_events_cross_account_region.py::TestEventsCrossAccountRegion::test_put_events[custom-account]": 0.15055006500000445, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-account]": 0.4320094120000135, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region]": 0.42844577800002526, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region_account]": 0.43192022499999894, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-account]": 0.46716208999993114, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region]": 0.4716454199999589, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region_account]": 0.4605463089999944, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path": 0.1830151029999456, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_max_level_depth": 0.18371442299996943, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_multiple_targets": 0.2892783020000138, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail0]": 0.18150700799992592, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail1]": 0.18669072900001993, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" multiple list items\"]": 0.23574862099997063, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item multiple list items system account id payload user id\"]": 0.2505186330000697, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item\"]": 0.22521228300001894, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Payload of with path users-service/users/ and \"]": 0.2210204590000444, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : \"\"}]": 0.22071483799999214, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : }]": 0.223227755000039, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"nested\": {\"level1\": {\"level2\": {\"level3\": \"users-service/users/\"} } }, \"bod\": \"\"}]": 0.22565098699993769, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": }]": 0.2196051179999472, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": [, \"hardcoded\"]}]": 0.22595661100001507, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"id\": , \"body\": }]": 0.2242483599999332, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"multi_replacement\": \"users//second/\"}]": 0.22825741599996263, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"singlelistitem\": }]": 0.2635568649999982, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"not_valid\": \"users-service/users/\", \"bod\": }]": 5.147318427000016, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"payload\": \"\"}]": 5.147621232000006, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"singlelistitem\": \"\"}]": 5.1489003419999335, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[\"Message containing all pre defined variables \"]": 0.21727245199991785, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[{\"originalEvent\": , \"originalEventJson\": }]": 0.2275967389999778, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_json": 0.39502966300000253, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"Event of type, at time , info extracted from detail \"]": 0.3987984340000139, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"{[/Check with special starting characters for event of type\"]": 0.3936877289999643, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_missing_keys": 0.10821629500003382, - "tests/aws/services/events/test_events_inputs.py::test_put_event_input_path_and_input_transformer": 0.10013962100003937, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_array_event_payload": 0.013773596000078214, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays]": 0.014040058999967187, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_NEG]": 0.015562696999950276, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_EXC]": 0.08843525900005034, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_null_NEG]": 0.013602265000031366, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean]": 0.014405872999986968, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean_NEG]": 0.013858427999991818, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_many_rules]": 0.015654740000059064, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match]": 0.013726020999968114, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match_NEG]": 0.014024318999986463, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or]": 0.013989125999955832, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or_NEG]": 0.013855427000009968, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase]": 0.013965616999939812, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_EXC]": 0.087636539000016, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_NEG]": 0.013942755000016405, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list]": 0.014905666000004203, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_EXC]": 0.0870418189999782, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_NEG]": 0.013550779000013335, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number]": 0.013796421999984432, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_NEG]": 0.013867847000028632, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list]": 0.013652535999995052, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list_NEG]": 0.01993404000006649, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_zero]": 0.01369203000001562, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string]": 0.013876404000029652, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_NEG]": 0.014050567999959185, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list]": 0.01392395199997054, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_NEG]": 0.017232824000018354, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null]": 0.013848819999964235, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix]": 0.014241361000017605, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_NEG]": 0.01461464099998011, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_empty_EXC]": 0.0911445909999884, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_ignorecase_EXC]": 0.08758095500002128, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_EXC]": 0.09594218699999146, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list]": 0.01404647199996134, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_NEG]": 0.013891630000046007, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_type_EXC]": 0.08727440600000591, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix]": 0.0137232250000352, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_NEG]": 0.014114688999995906, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_empty_EXC]": 0.09220430799996393, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_ignorecase_EXC]": 0.08718205799993939, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_int_EXC]": 0.08683477899995751, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list]": 0.013841734999971322, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_NEG]": 0.01361837700005708, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_type_EXC]": 0.08767270199996346, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard]": 0.01391706799995518, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_NEG]": 0.014058657999953539, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_empty]": 0.014069723999909911, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list]": 0.013921857000013915, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_NEG]": 0.014829747000021598, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_type_EXC]": 0.08730382099997769, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_type_EXC]": 0.08700658000009298, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists]": 0.013848348999999871, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_NEG]": 0.013587809999989986, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false]": 0.013813382999956048, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false_NEG]": 0.013968915000020843, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase]": 0.013946584000052553, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_EXC]": 0.0989963449999891, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_NEG]": 0.013983473000052982, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty]": 0.014182284999947115, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty_NEG]": 0.01472548299994969, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_list_EXC]": 0.08705670699998791, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address]": 0.01376603400001386, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_EXC]": 0.08761551899999631, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_NEG]": 0.014279410999961328, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_ip_EXC]": 0.08630129899995609, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_mask_EXC]": 0.08683350100000098, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_type_EXC]": 0.08684269600001926, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6]": 0.014033853000000818, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_NEG]": 0.0140171250000094, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_bad_ip_EXC]": 0.0869462569999655, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_EXC]": 0.08727682899996125, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and]": 0.013542794999978014, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and_NEG]": 0.013737861999970846, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_number_EXC]": 0.08614337300002717, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_operatorcasing_EXC]": 0.09880673900005377, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_syntax_EXC]": 0.08737821999994821, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix]": 0.013585403000035967, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_NEG]": 0.013656999999966501, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_empty]": 0.01393199099999265, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_ignorecase]": 0.013522798999986207, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_int_EXC]": 0.08657317699993428, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_list_EXC]": 0.08644767200007664, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix]": 0.01367644599997675, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_NEG]": 0.014039999000033276, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_empty]": 0.013767712000003485, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase]": 0.014643884999998136, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase_NEG]": 0.013586600999985876, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_int_EXC]": 0.08728891299995212, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_list_EXC]": 0.08597028900004489, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_complex_EXC]": 0.0877361409999935, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_empty_NEG]": 0.014157860000068467, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_int_EXC]": 0.08731635800000959, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_list_EXC]": 0.086449765999987, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating]": 0.01423243000004959, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating_NEG]": 0.014291718000038145, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating]": 0.014023174999977073, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_NEG]": 0.014559693000023799, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_star_EXC]": 0.08697795699993094, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_simplified]": 0.01385032199999614, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event]": 0.013866277999966314, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event_NEG]": 0.01401473199996417, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern]": 0.017195278999963648, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern_NEG]": 0.013633105999986128, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dynamodb]": 0.013941133999935573, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb]": 0.013991518000011638, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb_NEG]": 0.013824928000019554, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_list_empty_NEG]": 0.0173190119999731, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[int_nolist_EXC]": 0.08675671000008833, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[key_case_sensitive_NEG]": 0.01405114100003857, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[list_within_dict]": 0.014002227999981187, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[minimal]": 0.014479203000007601, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[nested_json_NEG]": 0.0179087590000222, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value]": 0.013696995000032075, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value_NEG]": 0.013822751000020617, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[number_comparison_float]": 0.014144864999991569, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-int-float]": 0.013695142000017313, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-null_NEG]": 0.013890828000000965, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-string_NEG]": 0.013516184999957659, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_case_sensitive_EXC]": 0.8294943249999278, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_multiple_list]": 0.014124957999968046, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-anything-but]": 0.0139261749999946, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists-parent]": 0.014139785999930155, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists]": 0.013657061000003523, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but]": 0.013953606999962176, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but_NEG]": 0.013552523999976529, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[prefix]": 0.01898723199997221, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[sample1]": 0.017245091000006596, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string]": 0.01347310500000276, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_empty]": 0.01394448000007742, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_nolist_EXC]": 0.08822467699991421, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_source": 0.023055875000011383, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_escape_characters": 0.012032885999985865, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_multi_key": 0.012080663999995522, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_with_large_and_complex_payload": 0.025368117000084567, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_event_payload": 0.013811659999987569, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[[\"not\", \"a\", \"dict\", \"but valid json\"]]": 0.0857283979999579, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[this is valid json but not a dict]": 0.08595454499999278, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{\"not\": closed mark\"]": 0.0876374609999857, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{'bad': 'quotation'}]": 0.08669788200000994, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_plain_string_payload": 0.013962031000005481, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_event_with_content_base_rule_in_pattern": 0.19360697100000834, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_anything_but": 5.307954657999915, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_false": 5.238481857000011, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_true": 5.234440705999987, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::test_schedule_cron_target_sqs": 0.001737500999979602, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 1 * * * *)]": 0.013733963999925436, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 dummy ? * MON-FRI *)]": 0.013225159999933567, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(7 20 * * NOT *)]": 0.013126385999953527, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(71 8 1 * ? *)]": 0.01372266300006686, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(INVALID)]": 0.013064934999988509, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(* * ? * SAT#3 *)]": 0.038826570999958676, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 10 * * ? *)]": 0.03709637500003282, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 12 * * ? *)]": 0.03669567899987669, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 18 ? * MON-FRI *)]": 0.03676752400008354, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT *)]": 0.03594952800006013, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT#3 *)]": 0.036397962000023654, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 8 1 * ? *)]": 0.03570408699999916, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/10 * ? * MON-FRI *)]": 0.03540154099994197, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/15 * * * ? *)]": 0.035606159000053594, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 0-2 ? * MON-FRI *)]": 0.035662940000065646, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 20-23 ? * MON-FRI *)]": 0.03531066499999724, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 5 ? JAN 1-5 2022)]": 0.03637777100004769, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 8-17 ? * MON-FRI *)]": 0.03596931400011272, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 10 ? * 6L 2002-2005)]": 0.03603244700002506, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 12 * * ? *)]": 0.03643399500003852, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(5,35 14 * * ? *)]": 0.036786365000011756, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_scheduled_rule_does_not_trigger_on_put_events": 3.094000660000006, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[ rate(10 minutes)]": 0.011253780999993523, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate( 10 minutes )]": 0.011276268000017353, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate()]": 0.011101514000017687, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(-10 minutes)]": 0.01081757099996139, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(0 minutes)]": 0.011134967000032248, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 days)]": 0.011167419000003065, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 hours)]": 0.01106499999997368, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 minutes)]": 0.01116553499997508, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 MINUTES)]": 0.011139827999954832, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 day)]": 0.010950137000008908, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 hour)]": 0.011457675000031031, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minute)]": 0.010989634999930331, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minutess)]": 0.01123357700004135, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 seconds)]": 0.01199954899993827, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 years)]": 0.011193364000007477, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10)]": 0.011089900999991187, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(foo minutes)]": 0.011157720000028348, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_schedule_rate": 0.03725044399999433, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_scheduled_rule_logs": 0.0019174680001015076, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_put_rule_with_schedule_custom_event_bus": 0.04064513899999156, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_custom_input_target_sqs": 60.11011747900005, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_target_sqs": 0.0017124849999845537, - "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_create_event_bus_with_tags": 0.041447564999998576, - "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_list_tags_for_deleted_event_bus": 0.0341245990000516, - "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_list_tags_for_deleted_rule": 0.06179310899983648, - "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_put_rule_with_tags": 0.061931929000024866, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_custom]": 0.06673670500015305, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_default]": 0.01950081899997258, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_custom]": 0.08961624400001256, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_default]": 0.06215435599983721, - "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_event_bus]": 0.02803000400001565, - "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_rule]": 0.029277464999950098, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_custom]": 0.06772289600007753, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_default]": 0.04664786500006812, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_custom]": 0.08818240199991578, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_default]": 0.06167665899988606, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth0]": 0.11412957299990012, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth1]": 0.1063869629998635, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth2]": 0.11224377400003505, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiGateway::test_put_events_with_target_api_gateway": 8.156932009999991, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetCloudWatchLogs::test_put_events_with_target_cloudwatch_logs": 0.2092246670000577, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination0]": 0.28902916300012294, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination1]": 0.31755686799999694, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination2]": 0.2868856390000474, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetFirehose::test_put_events_with_target_firehose": 0.99379687499993, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetKinesis::test_put_events_with_target_kinesis": 0.8892552610001303, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda": 4.247750239000084, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entries_partial_match": 4.267272430000048, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entry": 4.2584446840000965, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[domain]": 0.21977460400000837, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[path]": 0.2163728280000896, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[standard]": 0.5083796410000332, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs": 0.1774397429999226, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs_event_detail_match": 5.211128032999909, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetStepFunctions::test_put_events_with_target_statefunction_machine": 2.9524454170000354, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_api_gateway": 5.693505470000105, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination0]": 4.401832877000061, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination1]": 4.376761090999935, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination2]": 4.359639313999992, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_lambda": 4.246128535000025, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_elasticsearch_s3_backup": 0.0018654810000953148, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source": 31.22429723999994, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source_multiple_delivery_streams": 40.96366342400006, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[domain]": 0.0018198449999999866, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[path]": 0.001755044999981692, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[port]": 0.0018549310000253172, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_s3_as_destination_with_file_extension": 1.1729861930000425, - "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[False]": 0.07258426900000359, - "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[True]": 1.5689024840000911, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_role_with_malformed_assume_role_policy_document": 0.018865736999941873, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_add_permission_boundary_afterwards": 0.10586921500009794, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_with_permission_boundary": 0.089678197000012, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_role": 0.11553348100017047, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_root": 0.038981957999908445, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_user": 0.1460948719998214, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_role_with_path_lifecycle": 0.1168416680000064, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_detach_role_policy": 0.08421882499999356, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_iam_role_to_new_iam_user": 0.09604501699993762, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_describe_role": 0.14556146800009628, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_role_with_assume_role_policy": 0.13123533000009502, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_user_with_tags": 0.031119363000016165, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_delete_non_existent_policy_returns_no_such_entity": 0.015018507000036152, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_instance_profile_tags": 0.14267651400007253, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_list_roles_with_permission_boundary": 0.1661541449999504, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_recreate_iam_role": 0.05844579100005376, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_role_attach_policy": 0.3997728690000031, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[ecs.amazonaws.com-AWSServiceRoleForECS]": 0.0018847480000658834, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[eks.amazonaws.com-AWSServiceRoleForAmazonEKS]": 0.0017429409999749623, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[group]": 0.18756371200004196, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[role]": 0.21019440700001724, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[user]": 0.22487879000004796, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_update_assume_role_policy": 0.1117942150002591, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_user_attach_policy": 0.41098029999989194, - "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_group_policy_encoding": 0.05440843300004872, - "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_role_policy_encoding": 0.17527150600005825, - "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_user_policy_encoding": 0.08587161700006618, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_already_exists": 0.03270288199996685, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_deletion": 3.880975657000022, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[accountdiscovery.ssm.amazonaws.com]": 0.27654325400010293, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[acm.amazonaws.com]": 0.2750345010000501, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[appmesh.amazonaws.com]": 0.2793880969999236, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling-plans.amazonaws.com]": 0.2732665129999532, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling.amazonaws.com]": 0.2751391469998907, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[backup.amazonaws.com]": 0.28093682899998385, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[batch.amazonaws.com]": 0.2987161080000078, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cassandra.application-autoscaling.amazonaws.com]": 0.28010696200010443, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cks.kms.amazonaws.com]": 0.2769673779999948, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cloudtrail.amazonaws.com]": 0.27491705499994623, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[codestar-notifications.amazonaws.com]": 0.2738159899998891, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[config.amazonaws.com]": 0.27364827000008063, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[connect.amazonaws.com]": 0.2736134460001267, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms-fleet-advisor.amazonaws.com]": 0.2746804170000132, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms.amazonaws.com]": 0.2725408940000307, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[docdb-elastic.amazonaws.com]": 0.2739201319999438, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2-instance-connect.amazonaws.com]": 0.2709721389999231, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2.application-autoscaling.amazonaws.com]": 0.2755708099999765, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecr.amazonaws.com]": 0.2750042569997504, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecs.amazonaws.com]": 0.2728810629999998, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-connector.amazonaws.com]": 0.2751310169999215, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-fargate.amazonaws.com]": 0.27249375600001713, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-nodegroup.amazonaws.com]": 0.2719201500000281, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks.amazonaws.com]": 0.2704133899999306, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticache.amazonaws.com]": 0.2763020240001879, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticbeanstalk.amazonaws.com]": 0.27332827500015355, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticfilesystem.amazonaws.com]": 0.2715916519999837, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticloadbalancing.amazonaws.com]": 0.27636849600014557, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[email.cognito-idp.amazonaws.com]": 0.27807051199988564, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emr-containers.amazonaws.com]": 0.27575367400015693, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emrwal.amazonaws.com]": 0.28013338399989607, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[fis.amazonaws.com]": 0.27435615099989263, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[grafana.amazonaws.com]": 0.2769268970000667, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[imagebuilder.amazonaws.com]": 0.2770457899998746, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[iotmanagedintegrations.amazonaws.com]": 0.346458245000008, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafka.amazonaws.com]": 0.27361881100000573, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafkaconnect.amazonaws.com]": 0.2760644240000829, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lakeformation.amazonaws.com]": 0.27113695299988194, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lex.amazonaws.com]": 0.3455643260000443, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lexv2.amazonaws.com]": 1.1703846189999467, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lightsail.amazonaws.com]": 0.29297139200002675, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[m2.amazonaws.com]": 0.28311628300002667, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[memorydb.amazonaws.com]": 0.27104241299991827, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mq.amazonaws.com]": 0.2802386009999509, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mrk.kms.amazonaws.com]": 0.27355616400006966, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[notifications.amazonaws.com]": 0.2750217759999032, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[observability.aoss.amazonaws.com]": 0.27250392300015847, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opensearchservice.amazonaws.com]": 0.26988900999992893, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.apigateway.amazonaws.com]": 0.2692581550001023, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.emr-serverless.amazonaws.com]": 0.2729070909999791, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsdatasync.ssm.amazonaws.com]": 0.2735837970000148, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsinsights.ssm.amazonaws.com]": 0.2741830200000095, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[pullthroughcache.ecr.amazonaws.com]": 0.2724824179999814, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ram.amazonaws.com]": 0.27586270899985266, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rds.amazonaws.com]": 0.28175316900001235, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[redshift.amazonaws.com]": 0.2739707079999789, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.cassandra.amazonaws.com]": 0.2758735529999967, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.ecr.amazonaws.com]": 0.273337993000041, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[repository.sync.codeconnections.amazonaws.com]": 0.2732975970000098, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[resource-explorer-2.amazonaws.com]": 0.2727198950000229, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rolesanywhere.amazonaws.com]": 0.2752373880000505, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[s3-outposts.amazonaws.com]": 0.2774595779999345, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ses.amazonaws.com]": 0.27736040199988565, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[shield.amazonaws.com]": 0.2791164980000076, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-incidents.amazonaws.com]": 0.27360992199987777, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-quicksetup.amazonaws.com]": 0.2746261009999671, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm.amazonaws.com]": 0.27520300399999087, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[sso.amazonaws.com]": 0.2750662770000645, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[vpcorigin.cloudfront.amazonaws.com]": 0.27600891700001284, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[waf.amazonaws.com]": 0.27462486800004626, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[wafv2.amazonaws.com]": 0.2747683989999814, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[autoscaling.amazonaws.com]": 0.12866534100010085, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[connect.amazonaws.com]": 0.12731474600002457, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[lexv2.amazonaws.com]": 0.12675599700003204, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[accountdiscovery.ssm.amazonaws.com]": 0.015008775000069363, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[acm.amazonaws.com]": 0.015101142999924377, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[appmesh.amazonaws.com]": 0.014972994000117978, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[autoscaling-plans.amazonaws.com]": 0.015302595999969526, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[backup.amazonaws.com]": 0.015539133999936894, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[batch.amazonaws.com]": 0.014786135999884209, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cassandra.application-autoscaling.amazonaws.com]": 0.015324632999977439, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cks.kms.amazonaws.com]": 0.014890811000100257, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cloudtrail.amazonaws.com]": 0.01638002400000005, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[codestar-notifications.amazonaws.com]": 0.014907828999980666, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[config.amazonaws.com]": 0.01490401200010183, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms-fleet-advisor.amazonaws.com]": 0.015192269000067427, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms.amazonaws.com]": 0.015451729000119485, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[docdb-elastic.amazonaws.com]": 0.01544304000003649, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2-instance-connect.amazonaws.com]": 0.01489394199995786, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2.application-autoscaling.amazonaws.com]": 0.015489530999957424, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecr.amazonaws.com]": 0.015028806000032091, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecs.amazonaws.com]": 0.015088601999991624, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-connector.amazonaws.com]": 0.015484601999901315, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-fargate.amazonaws.com]": 0.015096322999966105, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-nodegroup.amazonaws.com]": 0.01493789900007414, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks.amazonaws.com]": 0.015514628999881097, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticache.amazonaws.com]": 0.014896337999971365, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticbeanstalk.amazonaws.com]": 0.015030989999900157, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticfilesystem.amazonaws.com]": 0.015137280999965697, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticloadbalancing.amazonaws.com]": 0.014849800999968465, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[email.cognito-idp.amazonaws.com]": 0.015457290999961515, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emr-containers.amazonaws.com]": 0.015155442999912339, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emrwal.amazonaws.com]": 0.017907458000081533, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[fis.amazonaws.com]": 0.015226617000166698, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[grafana.amazonaws.com]": 0.014904071999922053, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[imagebuilder.amazonaws.com]": 0.0152319679999664, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[iotmanagedintegrations.amazonaws.com]": 0.015132921000031274, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafka.amazonaws.com]": 0.014931924000165964, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafkaconnect.amazonaws.com]": 0.015321054000082768, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lakeformation.amazonaws.com]": 0.015461557999969955, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lex.amazonaws.com]": 0.015088816000002225, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lightsail.amazonaws.com]": 0.015012570000067171, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[m2.amazonaws.com]": 0.015426644000058332, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[memorydb.amazonaws.com]": 0.015414660000033109, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mq.amazonaws.com]": 0.015021111999999448, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mrk.kms.amazonaws.com]": 0.01491172699991239, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[notifications.amazonaws.com]": 0.01601394400006484, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[observability.aoss.amazonaws.com]": 0.01501275899988741, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opensearchservice.amazonaws.com]": 0.015112174000023515, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.apigateway.amazonaws.com]": 0.015522201999942808, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.emr-serverless.amazonaws.com]": 0.016088323999838394, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsdatasync.ssm.amazonaws.com]": 0.015216238000107296, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsinsights.ssm.amazonaws.com]": 0.015034387000014249, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[pullthroughcache.ecr.amazonaws.com]": 0.015051701999823308, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ram.amazonaws.com]": 0.015461076999940815, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rds.amazonaws.com]": 0.015285187999893424, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[redshift.amazonaws.com]": 0.015173035999964668, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.cassandra.amazonaws.com]": 0.015621908999946754, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.ecr.amazonaws.com]": 0.015025550999894222, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[repository.sync.codeconnections.amazonaws.com]": 0.015146869999966839, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[resource-explorer-2.amazonaws.com]": 0.015084589000025517, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rolesanywhere.amazonaws.com]": 0.014947003000088444, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[s3-outposts.amazonaws.com]": 0.01496205600005851, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ses.amazonaws.com]": 0.015130417000136731, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[shield.amazonaws.com]": 0.015220353999893632, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-incidents.amazonaws.com]": 0.014909291000094527, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-quicksetup.amazonaws.com]": 0.015137168000023848, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm.amazonaws.com]": 0.015164626999990105, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[sso.amazonaws.com]": 0.015343555999947966, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[vpcorigin.cloudfront.amazonaws.com]": 0.015081605999966996, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[waf.amazonaws.com]": 0.01497851100009484, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[wafv2.amazonaws.com]": 0.0161602949999633, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_service": 0.07391881000000922, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_user": 0.024205239000025358, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_delete_user_after_service_credential_created": 0.07588603800002147, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_id_match_user_mismatch": 0.09093537699993703, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_invalid_update_parameters": 0.0752078730000676, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_list_service_specific_credential_different_service": 0.07629627200003597, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[cassandra.amazonaws.com]": 0.10273542599998109, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[codecommit.amazonaws.com]": 0.10563920300000973, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[satisfiesregexbutstillinvalid]": 0.09140832399998544, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[totally-wrong-credential-id-with-hyphens]": 0.09034530899987203, - "tests/aws/services/iam/test_iam.py::TestRoles::test_role_with_tags": 0.07079992199999197, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_add_tags_to_stream": 0.6656089489999886, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_blob_handling": 0.6499804300000278, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_shard_count": 0.6625004589999435, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_stream_name_raises": 0.037835629999904086, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records": 0.720968950999918, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_empty_stream": 0.6532199759999457, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_next_shard_iterator": 0.6557734530001653, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_shard_iterator_with_surrounding_quotes": 0.6549024720001171, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_record_lifecycle_data_integrity": 0.8495777449999196, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_stream_consumers": 1.311129015000006, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard": 4.504598001999966, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_cbor_at_timestamp": 4.348904934000075, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_timeout": 6.299241492000078, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp": 4.492809118999958, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp_cbor": 0.6369343710000521, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_sequence_number_as_iterator": 4.524160890999951, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisJavaSDK::test_subscribe_to_shard_with_java_sdk_v2_lambda": 9.55920469800003, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_add_tags_to_stream": 0.6744998809999743, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_cbor_blob_handling": 0.6526024050000387, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_shard_count": 0.6476333750000549, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_stream_name_raises": 0.04097701499983941, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records": 0.7072758749998229, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_empty_stream": 0.6546011099998168, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_next_shard_iterator": 0.6554258190001292, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_shard_iterator_with_surrounding_quotes": 0.658289616999923, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_record_lifecycle_data_integrity": 0.8496299580000368, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_stream_consumers": 1.267917539999985, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard": 4.468063014999984, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_cbor_at_timestamp": 4.339432351000028, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_timeout": 6.311445835000086, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp": 4.5011585800000375, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp_cbor": 0.6400576260000435, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_sequence_number_as_iterator": 4.486823338999898, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisPythonClient::test_run_kcl": 26.93776612499994, - "tests/aws/services/kms/test_kms.py::TestKMS::test_all_types_of_key_id_can_be_used_for_encryption": 0.06494192699983614, - "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_delete_deleted_key": 0.03335391999996773, - "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_use_disabled_or_deleted_keys": 0.052931303000150365, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_alias": 0.11019844700001613, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_custom_key_asymmetric": 0.037706835999870236, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_invalid_key": 0.023597825000024386, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_same_name_two_keys": 0.05924451999987923, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_valid_key": 0.04095820799989269, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key": 0.11579136999989714, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_id": 0.02934977799986882, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_hmac": 0.03689107500008504, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_symmetric_decrypt": 0.029594279999855644, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[lowercase_prefix]": 0.09164164300000266, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[too_long_key]": 0.08905004500013547, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[uppercase_prefix]": 0.08814092000000073, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_tag_and_untag": 0.11232046399993578, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_too_many_tags_raises_error": 0.08897360599996773, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_list_delete_alias": 0.058487191000040184, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_multi_region_key": 0.16881458399996063, - "tests/aws/services/kms/test_kms.py::TestKMS::test_derive_shared_secret": 0.19611615799999527, - "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_and_list_sign_key": 0.03476213400006145, - "tests/aws/services/kms/test_kms.py::TestKMS::test_disable_and_enable_key": 0.053302724999980455, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.05132915799993043, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.03342328899987024, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt_encryption_context": 0.18521451800006616, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_1]": 0.13456630199993924, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_256]": 0.13794123399986802, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_1]": 0.3471852719999333, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_256]": 0.22891654900001868, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_1]": 0.7416380699997944, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_256]": 1.4525493990000768, - "tests/aws/services/kms/test_kms.py::TestKMS::test_error_messaging_for_invalid_keys": 0.22137786799999049, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_224-HMAC_SHA_224]": 0.12181289099999049, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_256-HMAC_SHA_256]": 0.12165450500003772, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_384-HMAC_SHA_384]": 0.12276580199988985, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_512-HMAC_SHA_512]": 0.12456876399994599, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1024]": 0.0851899689999982, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[12]": 0.08676812200008044, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1]": 0.08481757099991682, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[44]": 0.08455359399999907, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[91]": 0.08433361199990941, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[0]": 0.08769573900008254, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[1025]": 0.08952539500000967, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[None]": 0.09406993900006455, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_does_not_exist": 0.1160767130000977, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_in_different_region": 0.13495136299980004, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_invalid_uuid": 0.10313127000006261, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_parameters_for_import": 0.7752716300000202, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_public_key": 0.057767248999880394, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_put_list_key_policies": 0.04831409700000222, - "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key": 0.11943663200008814, - "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key_invalid_operations": 0.10336854399997719, - "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_asymmetric": 0.21531683999978668, - "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_symmetric": 0.2921221430000287, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_224-HMAC_SHA_256]": 0.10146143299994037, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_256-INVALID]": 0.10149931599994488, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_key_usage": 0.7619058449998874, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_256-some different important message]": 0.18493324300004588, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_512-some important message]": 0.18357465799999773, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-INVALID-some important message]": 0.1809678710001208, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[180]": 0.10894483899994611, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[90]": 0.10686972500002412, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_status": 0.05503462999990916, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_encryption_decryption": 0.12556557400000656, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_limits": 0.22562786900004994, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_with_long_tag_value_raises_error": 0.10730461800005742, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_aliases_of_key": 0.06176765700001852, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_grants_with_invalid_key": 0.013170928000022286, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_keys": 0.026431174999970608, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_retirable_grants": 0.0683796620000976, - "tests/aws/services/kms/test_kms.py::TestKMS::test_non_multi_region_keys_should_not_have_multi_region_properties": 0.1670322990000841, - "tests/aws/services/kms/test_kms.py::TestKMS::test_plaintext_size_for_encrypt": 0.10000337899998613, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.18053645399993457, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.13641436000011709, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_incorrect_source_key": 0.1203014320000193, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_invalid_destination_key": 0.04753469199999927, - "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_key": 0.5240556229999811, - "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_id_and_key_id": 0.05587179700000888, - "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_token": 0.05690569399996548, - "tests/aws/services/kms/test_kms.py::TestKMS::test_revoke_grant": 0.05681567900001028, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_modifies_key_material": 0.11486728599993512, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_is_disabled": 0.7929894569997487, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_that_does_not_exist": 0.08718738500010659, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_with_imported_key_material": 0.100093988000026, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_non_symmetric_key": 1.4577738630000567, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_disabled": 0.11549465200005216, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_enabled": 0.13673397199988813, - "tests/aws/services/kms/test_kms.py::TestKMS::test_schedule_and_cancel_key_deletion": 0.04728603499995643, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P256-ECDSA_SHA_256]": 0.2991813239998464, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P384-ECDSA_SHA_384]": 0.30465004900020176, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.30739025399998354, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_256]": 0.7255258870000034, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_384]": 0.6955293329999677, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_512]": 0.8146632570001202, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 3.2752407160000985, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 4.6014803660000325, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_1]": 0.1169449619999341, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_256]": 0.12183301399988977, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_1]": 0.3825505659998498, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_256]": 0.4281850120000854, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_1]": 0.6988828559999547, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_256]": 0.9752125920001617, - "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_and_untag": 0.12554772500004674, - "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_with_invalid_tag_key": 0.1053680989998611, - "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_key_with_duplicate_tag_keys_raises_error": 0.10309362800001054, - "tests/aws/services/kms/test_kms.py::TestKMS::test_untag_key_partially": 0.11488069100005305, - "tests/aws/services/kms/test_kms.py::TestKMS::test_update_alias": 0.06715193499996985, - "tests/aws/services/kms/test_kms.py::TestKMS::test_update_and_add_tags_on_tagged_key": 0.11512721300005069, - "tests/aws/services/kms/test_kms.py::TestKMS::test_update_key_description": 0.04216177000000698, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P256-ECDSA_SHA_256]": 0.040377650999857906, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P384-ECDSA_SHA_384]": 0.04202404000000115, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.043791787000031945, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_256]": 0.21464519300002394, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_384]": 0.16541899200001353, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_512]": 0.168733700999951, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 1.1178364539999848, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 1.2230685570000333, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key": 0.1816854940000212, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair": 0.1395070600000281, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair_without_plaintext": 0.1570776459999479, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_without_plaintext": 0.18148438599996553, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key": 0.035309089000065796, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair": 0.12095041800000672, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_dry_run": 0.02949523700010559, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext": 0.051217230000133895, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext_dry_run": 0.07268918600004781, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_without_plaintext": 0.03035679300000993, - "tests/aws/services/kms/test_kms.py::TestKMSMultiAccounts::test_cross_accounts_access": 1.729220373999965, - "tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.py::test_adding_tags": 17.631117195999877, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_deletion_event_source_mapping_with_dynamodb": 6.106967671999996, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_disabled_dynamodb_event_source_mapping": 12.290464992000011, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_duplicate_event_source_mappings": 5.569817903000171, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_filter_type]": 12.759028485000044, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_multiple_filters]": 0.01949063600000045, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_or_filter]": 12.80061878999993, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[date_time_conversion]": 12.787256531999901, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_false_filter]": 12.787223159000064, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_filter_type]": 12.861079945000029, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[insert_same_entry_twice]": 12.76723725699992, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[numeric_filter]": 12.801853258999927, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[prefix_filter]": 12.793306839999786, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping": 14.773536923000165, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_on_failure_destination_config": 12.326346782999963, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_s3_on_failure_destination": 11.467876665000063, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_sns_on_failure_destination_config": 11.326636291, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[[{\"eventName\": [\"INSERT\"=123}]]": 4.541035339000018, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[single-string]": 4.551128230000131, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 15.787028628999906, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 14.864239499999712, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 14.833716632999995, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 14.811849359000007, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[null_item_identifier_failure]": 14.793406796, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 14.832705266999938, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failures": 15.152531349000128, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 9.792657134000137, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_dict_success]": 9.727105500000107, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_list_success]": 9.721277063999878, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_batch_item_failure_success]": 9.741211215000021, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_success]": 9.731033525000157, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_esm_with_not_existing_dynamodb_stream": 1.847918232999973, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisEventFiltering::test_kinesis_event_filtering_json_pattern": 9.221229530000073, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping": 12.124542015000088, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping_multiple_lambdas_single_kinesis_event_stream": 19.4569824780001, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_disable_kinesis_event_source_mapping": 29.237951503999966, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_duplicate_event_source_mappings": 3.4308385409999573, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_esm_with_not_existing_kinesis_stream": 1.4248643280000124, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_empty_provided": 9.21406395799977, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_async_invocation": 20.183046332999993, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_on_failure_destination_config": 9.177713167999855, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_s3_on_failure_destination": 9.234696785999859, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_sns_on_failure_destination_config": 9.218450892999954, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_trim_horizon": 26.2689719949999, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-before-ingestion]": 14.291592448000074, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-while-retrying]": 9.304034706000039, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded_discard_records": 19.332092689999854, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 12.151112499999954, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 12.158886597999981, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 12.194354185000293, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 12.153801438999835, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[null_item_identifier_failure]": 12.175606449999805, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 12.163257663999957, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failures": 12.278017024000064, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 7.11028017700005, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_dict_success]": 7.115026274999764, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_list_success]": 7.125336644999834, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_string_success]": 7.098304268999982, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_batch_item_failure_success]": 7.12129396399996, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_success]": 7.106951506000087, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_duplicate_event_source_mappings": 2.598862421999911, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_event_source_mapping_default_batch_size": 3.436823104000041, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[and]": 6.455917790000058, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[exists]": 6.452980779999734, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-bigger]": 6.4280974519999745, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-range]": 6.427309272000002, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-smaller]": 6.4399540580002395, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[or]": 6.430508362000182, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-filter]": 0.002295086000003721, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-matching]": 0.002510781999944811, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[prefix]": 6.407622073999846, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[single]": 6.4176224929999535, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[valid-json-filter]": 6.447590876000049, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping": 6.360414357999844, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[10000]": 9.588201448999826, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[1000]": 9.54244215000017, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[100]": 4.591935854999974, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[15]": 9.590653606999922, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[10000]": 0.02089085499983412, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[1000]": 8.740668241000094, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[100]": 6.6316958139998405, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[20]": 6.422534341999835, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_reserved_concurrency": 8.680707425999799, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_window_size_override": 26.836252384999852, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_update": 12.663267043999895, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[None]": 1.2579787570000462, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter2]": 1.2263874390000638, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter3]": 1.2247902080000586, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[simple string]": 1.2218056500000785, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_esm_with_not_existing_sqs_queue": 1.1958207269999548, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_failing_lambda_retries_after_visibility_timeout": 18.671009881999908, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_fifo_message_group_parallelism": 63.49843166200003, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_message_body_and_attributes_passed_correctly": 4.152928347999932, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_redrive_policy_with_failing_lambda": 16.86877668599982, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures": 0.003110908000053314, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_empty_json_batch_succeeds": 9.602823443999796, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_invalid_result_json_batch_fails": 16.96466153900019, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_on_lambda_error": 10.372471217000111, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_sqs_queue_as_lambda_dead_letter_queue": 6.252290095000035, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_alias_routingconfig": 3.1730388249998214, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_lambda_alias_moving": 3.389555078000285, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[1]": 1.6808617039998808, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[2]": 1.723265531999914, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_function_state": 1.2316579450000518, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_different_iam_keys_environment": 3.7469249169998875, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_large_response": 1.6334867709999799, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response": 1.8499307699999008, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response_but_with_custom_limit": 1.5865839640000559, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_large_payloads": 1.8303828909999993, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_ignore_architecture": 1.5328259970001454, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[nodejs]": 7.684209659000089, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[python]": 1.6480342039999414, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_host_prefix_api_operation": 9.84963017999985, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_init_environment": 3.676184160000048, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_no_timeout": 3.6246289089999664, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_timed_out_environment_reuse": 0.0028428949999579345, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_with_timeout": 3.594745670999828, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_mixed_architecture": 0.0027579460002016276, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_arm": 0.0028347669999675418, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_x86": 1.8280395420001696, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_ulimits": 1.6121395759998904, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_delete_lambda_during_sync_invoke": 0.0017551639994053403, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_recreate_function": 3.3897109480003564, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_block": 12.510622922000039, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_crud": 1.2318670630006636, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_update": 1.3869260860005852, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_moves_with_alias": 0.002844558000560937, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_scheduling": 8.510932137999589, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency": 2.8972144579997803, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_on_alias": 2.936844003999795, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency": 14.937752569000168, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency_async_queue": 3.919685405000564, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_provisioned_overlap": 12.257354295999903, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_error": 1.5851359799999045, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_exit": 0.0024207700000715704, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[body-n\\x87r\\x9e\\xe9\\xb5\\xd7I\\xee\\x9bmt]": 1.3601066559999708, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[message-\\x99\\xeb,j\\x07\\xa1zYh]": 1.3560118329992292, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_error": 7.680137858999842, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit": 0.0017815039998367865, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit_segfault": 0.001689740999836431, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_error": 2.078147134000119, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_timeout": 42.21676927300041, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_wrapper_not_found": 0.0022428079998917383, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[nodejs16.x]": 0.0025751830000899645, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[python3.10]": 0.0023063769999680517, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[nodejs16.x]": 2.2744897260001835, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[python3.10]": 2.279039306000186, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event_error": 0.0023121399999581627, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": 2.2860843379999096, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": 8.68022176699992, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": 2.280357951999804, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": 2.5905306349995953, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[nodejs16.x]": 1.5968785639997805, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[python3.10]": 1.5871303079998142, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[nodejs16.x]": 15.741419938000035, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[python3.10]": 7.77611514299997, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_qualifier": 1.8154841760001545, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invoke_exceptions": 0.11245390100043551, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_lambda_with_context": 0.0033356610001646914, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_upload_lambda_from_s3": 2.1800418549998994, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_delete_function": 1.1517036850004843, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_alias": 1.1828112369998962, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_concurrency": 1.1366513269999814, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_invocation": 1.521739911000168, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_tags": 1.167680384999585, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function": 1.1475878969995392, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function_configuration": 1.134314247000475, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_lambda_layer": 0.10747023400017497, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_list_versions_by_function": 1.136862062000091, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_publish_version": 1.181476793999991, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaPermissions::test_lambda_permission_url_invocation": 0.002786019000268425, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_update_function_url_config": 1.4784457299999758, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_default": 2.094357168999977, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_trim_x_headers": 1.966418984999791, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[BUFFERED]": 1.9120143480001843, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[None]": 1.930916618999845, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[RESPONSE_STREAM]": 0.012038217000053919, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_form_payload": 1.9208320339998863, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_headers_and_status": 1.5827251089999663, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invalid_invoke_mode": 1.473137238999925, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[boolean]": 1.8244553830002133, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[dict]": 1.8263942199998837, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[float]": 1.816632462999678, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response-json]": 1.8255985230002807, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response]": 1.8341550269999516, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[integer]": 1.8118135639999764, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[list-mixed]": 1.8275532070001645, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[string]": 1.8134462679997796, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id": 1.547590350000064, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id_aliased": 1.5429372549999698, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_exception": 1.8369036990002314, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_non_existing_url": 1.057647807999956, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_persists_after_alias_delete": 3.8815891470003407, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_async_invoke_queue_upon_function_update": 93.75921184800018, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_function_update_during_invoke": 0.0036731340001097124, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_handler_update": 2.2249111759997504, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_versions_with_code_changes": 5.514880239000377, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_async_invoke_with_retry": 11.26577465299988, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_format": 0.025870402999771613, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke": 3.659220598000047, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke_url": 3.6154512880002585, - "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_code_signing_not_found_excs": 1.3248440430000414, - "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_function_code_signing_config": 1.2859016789999487, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings": 0.0910165049999705, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size": 1.4427104820000523, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size_config_update": 7.346102621000057, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_lifecycle": 1.531063056999983, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_naming": 1.702489392000075, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_deletion": 1.1994329019999554, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_update": 1.2087753720000478, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_notfound_and_invalid_routingconfigs": 2.425216807999959, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_exceptions": 2.8103612960000532, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_lifecycle": 1.3523335519999478, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_filter_criteria_validation": 3.543260241999974, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_self_managed": 0.001998851000053037, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": 3.4403314090000094, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": 1.9260186640000256, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_exceptions": 0.15615853399998514, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle": 8.076686644999995, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle_delete_function": 6.068899886999986, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_function_name_variations": 16.055669911000052, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_create_lambda_exceptions": 0.17116884100002494, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_delete_on_nonexisting_version": 1.2380888339999956, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_arns": 2.592323059999984, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_lifecycle": 17.07096592800002, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-create_function]": 0.10607619399999635, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-delete_function]": 0.09170757399996887, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-get_function]": 0.6635176260000435, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-invoke]": 0.09174749099994983, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-create_function]": 0.10467427399998996, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-delete_function]": 0.09164685200002509, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-get_function]": 0.09082329699995739, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-invoke]": 0.09355379199993763, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-create_function]": 0.1052476010000305, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-delete_function]": 0.09066651700001671, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-get_function]": 0.09013190400003168, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-invoke]": 0.09163795700001742, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-create_function]": 0.10284151299998712, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-delete_function]": 0.09030901200003427, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-get_function]": 0.09189935099999502, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-invoke]": 0.008838929000006601, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-create_function]": 0.1057286190000184, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-delete_function]": 0.09438250299996298, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-get_function]": 0.09094487799998774, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-invoke]": 0.09209692999999675, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-create_function]": 0.008108448000001545, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-delete_function]": 0.08941914600003997, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-get_function]": 0.09045368400001053, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-invoke]": 0.008877950999988116, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-create_function]": 0.10341382999999382, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-delete_function]": 0.09283747900002481, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-get_function]": 0.09005106600002932, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-invoke]": 0.08967896000001474, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-create_function]": 0.10639932400002294, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-delete_function]": 0.09407763999999474, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-get_function]": 0.09480182799995873, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-invoke]": 0.09320125499999676, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-create_function]": 0.10550488100000166, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-delete_function]": 0.09174484299998653, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-get_function]": 0.09240018400001304, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-invoke]": 0.09217551599999751, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-create_function]": 0.10418250800000806, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-delete_function]": 0.09240945199999828, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-get_function]": 0.09086244200000237, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-invoke]": 0.09106459999998151, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-create_function]": 0.10340884800001504, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-delete_function]": 0.09056178299999829, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-get_function]": 0.09090549199999032, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-invoke]": 0.08798102899999094, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-create_function]": 0.10416694799999959, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-delete_function]": 0.009126463000058038, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-get_function]": 0.09042750399993338, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-invoke]": 0.0899811300000124, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-create_function]": 0.1079632160000017, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-delete_function]": 0.093636575000005, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-get_function]": 0.09056257600002482, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-invoke]": 0.09602341400000114, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-create_function]": 0.10478527300000451, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-delete_function]": 0.08995763800001555, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-get_function]": 0.09097450099997673, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-invoke]": 0.09144750100000465, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-create_function]": 0.10607784899997341, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-delete_function]": 0.09822603599997137, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-get_function]": 0.0929182080000146, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-invoke]": 0.10925572600001487, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-create_function]": 0.10475273199998014, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-delete_function]": 0.09132657499995389, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-get_function]": 0.09114438800000357, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-invoke]": 0.09240598499997077, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-create_function]": 0.1050961019999761, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-delete_function]": 0.08926702299999079, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-get_function]": 0.0904325039999776, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-invoke]": 0.09163165300000742, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-create_function]": 0.10561663799998655, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-delete_function]": 0.09154435199999966, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-get_function]": 0.09274387399997863, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-invoke]": 0.09260056800002303, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[delete_function]": 1.2143496360000086, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function]": 1.2299110940000162, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_code_signing_config]": 1.2165081950000172, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_concurrency]": 1.2192828569999676, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_configuration]": 1.2357642310000188, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_event_invoke_config]": 1.2334706489999974, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_url_config]": 1.2263347210000006, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[invoke]": 1.2182909210000332, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_invoke": 0.09083194000001527, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": 0.0017375329999822497, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": 0.4179838289999509, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3": 1.5160923459999935, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_zipfile": 1.4127229810000017, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_code_updates": 2.3035886030000086, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_config_updates": 2.2844414750000226, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_list_functions": 2.5023929350000174, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[delete_function]": 0.09268410699999663, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function]": 0.09218001800002185, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_code_signing_config]": 0.09373805000001312, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_concurrency]": 0.09258512100004168, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_configuration]": 0.0919927639999969, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_event_invoke_config]": 0.09304169899999692, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_url_config]": 0.0947174580000194, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function]": 1.7942339789999835, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_configuration]": 1.2113523499999985, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_event_invoke_config]": 1.2166737310000144, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[delete_function]": 0.10309209899997995, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function]": 0.10230458300000578, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function_configuration]": 0.10183342499999526, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_redundant_updates": 1.3633443020000016, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_update_lambda_exceptions": 1.2209352170000045, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_vpc_config": 2.1232066660000157, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_and_image_config_crud": 0.8664933720000079, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_crud": 5.75704882200003, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_versions": 1.9504911709999817, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_zip_file_to_image": 1.5499271729999577, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes0]": 0.13422012399996675, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes1]": 0.1315451780000103, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_deterministic_version": 0.06222224999999071, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_exceptions": 0.30969639399995685, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_exceptions": 17.504394522999974, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_quota_exception": 16.393886264999935, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_lifecycle": 1.4697190080000269, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_exceptions": 0.23918489999999792, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_lifecycle": 0.17744573599998148, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_s3_content": 0.21223691000000144, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_aws": 1.2281434420000323, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_fields": 1.2896680900000206, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_create_multiple_lambda_permissions": 1.224903295000047, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_lambda_permission_fn_versioning": 1.3838460799999552, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_permission_exceptions": 1.3612876249999886, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_remove_multi_permissions": 1.2765855080000392, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_lambda_provisioned_lifecycle": 2.4525274289999857, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_exceptions": 1.385112499999991, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_limits": 1.2698044900000127, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_allow": 1.233173767999972, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_default_terminate": 1.2069979709999927, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_invalid_value": 1.2065661640000087, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency": 1.8892534199999886, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_exceptions": 1.2358771770000772, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_limits": 1.220562405999999, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_basic": 13.708655550999993, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_permissions": 1.2643269049999617, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_version_and_alias": 1.3708985120000534, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_lambda_envvars_near_limit_succeeds": 1.2934690420000265, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_fails_multiple_keys": 16.211619877999965, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_variables_fails": 16.215384775999894, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_lambda": 12.77011238099999, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_request_create_lambda": 1.996707391999962, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_unzipped_lambda": 4.724087802000042, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_zipped_create_lambda": 1.621360535000008, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_exceptions": 0.10609610999995311, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet8]": 4.301395871999944, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java11]": 3.3049281660000247, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java17]": 3.2946198550000076, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java21]": 3.2985338720000072, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.12]": 1.2522969709999643, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.13]": 7.347160301000088, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet8]": 1.2289751259999662, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java11]": 1.2535956019999617, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java17]": 1.239825854000003, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java21]": 1.232484688999989, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.12]": 1.2206994539999982, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.13]": 1.219443704000014, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_esm_create": 1.5140729230000147, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_fn_create": 1.228791855000054, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[event_source_mapping]": 0.12381458199996587, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[lambda_function]": 0.12499760600002219, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[event_source_mapping]": 1.422096483999951, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[lambda_function]": 1.298512072000051, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_nonexisting_resource": 1.2993060119999882, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_exceptions": 1.3051099389999763, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_lifecycle": 1.3788510119999842, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_limits": 1.3886170369999604, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_versions": 1.265109343000006, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag": 1.1355670440000267, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_alias": 3.4029506680000736, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_invalid_id": 1.1297659330000442, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_deletion_without_qualifier": 1.37914562200001, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_exceptions": 7.611816517999955, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_lifecycle": 1.3384153280000533, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_list_paging": 1.3850888060001125, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_version_on_create": 1.2833536359999869, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_update": 1.4115198009999688, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_wrong_sha256": 1.2545855300000142, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_version_lifecycle": 2.484034909999991, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_advanced_logging_configuration_format_switch": 1.6071665009999379, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_advanced_logging_configuration": 1.278103082999678, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config0]": 33.94296191899997, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config1]": 1.4748748920000025, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config2]": 1.4453500559999668, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config3]": 2.492105589999994, - "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_cross_region_arn_function_access": 1.139468200000465, - "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_update_function_configuration_full_arn": 1.2428232330003084, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_disabled": 15.204814091999651, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[dotnetcore3.1]": 0.10922257999982321, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[go1.x]": 0.10509972799991374, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[java8]": 0.10707773800004361, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs12.x]": 0.10794945199995709, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs14.x]": 0.10670257100036906, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[provided]": 0.10294355799987898, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[python3.7]": 1.1508106580004096, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[ruby2.7]": 0.10469957299983434, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet6]": 1.9592016710000735, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet8]": 1.899118367999904, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java11]": 4.946052971999961, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java17]": 4.363307842999916, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java21]": 4.176003104000074, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java8.al2]": 5.874369872999864, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs16.x]": 1.7829422719999002, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs18.x]": 1.7394704750000756, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs20.x]": 1.72124726800007, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs22.x]": 1.682612797000047, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.10]": 1.7387179920000335, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.11]": 1.7491155709998338, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.12]": 1.8116745430000947, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.13]": 1.7402287500000284, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.8]": 1.7426490690000946, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.9]": 1.7346543499999143, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.2]": 2.4150127039999916, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.3]": 2.302569482000081, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.4]": 2.09285273099988, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet6]": 3.556518664999885, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet8]": 2.569433885999956, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java11]": 2.4770941459998994, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java17]": 2.4379554160000225, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java21]": 2.4606446770000048, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java8.al2]": 4.498446123000008, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs16.x]": 9.58646904300008, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs18.x]": 2.5105010989999528, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs20.x]": 2.415828357000123, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs22.x]": 7.45901530000009, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2023]": 3.261089681000044, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2]": 5.124950828999999, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.10]": 7.628996817000029, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.11]": 2.5231310189999476, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.12]": 3.3646108000000368, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.13]": 2.605426027999897, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.8]": 2.7307419060000484, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.9]": 7.652198057999954, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.2]": 8.558754177999958, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.3]": 9.610121607999986, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.4]": 9.623663167000018, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet6]": 3.718319668000049, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet8]": 3.7301175530001274, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java11]": 4.8510681330000125, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java17]": 3.66357911099999, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java21]": 3.691383351000127, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java8.al2]": 3.9038316710000345, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs16.x]": 3.555306981000058, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs18.x]": 3.5876227229999813, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs20.x]": 3.5694588220000014, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs22.x]": 3.548932771000068, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2023]": 4.690739939999958, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2]": 3.608797967999976, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.10]": 3.526540546000092, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.11]": 3.5657367549998753, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.12]": 4.58616891500003, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.13]": 3.5678040840001586, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.8]": 3.542580509000004, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.9]": 3.563971250999998, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.2]": 3.6153081290000273, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.3]": 3.6907235470000614, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.4]": 3.646961569000041, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet6]": 1.812769954999908, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet8]": 1.8198864690000391, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java11]": 1.9406517560000793, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java17]": 1.833452467999905, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java21]": 1.8572878669999682, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java8.al2]": 2.104230029000064, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs16.x]": 1.7113875879999796, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs18.x]": 1.7553012099998568, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs20.x]": 1.6992024609999135, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs22.x]": 1.7060004229999777, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.10]": 1.701958970000078, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.11]": 1.7037847970000257, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.12]": 1.6940829820000545, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.13]": 1.6966000300000132, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.8]": 2.7384168849999924, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.9]": 1.6776964240001462, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.2]": 1.7668514010000536, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.3]": 1.7738371529999313, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.4]": 1.769729454999947, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet6]": 1.8527065499998798, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet8]": 1.839407309000194, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java11]": 1.9961731849998614, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java17]": 1.8712743390000242, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java21]": 1.8808367500000713, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java8.al2]": 2.1239435859999958, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs16.x]": 1.7239446369999314, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs18.x]": 1.7487895199999457, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs20.x]": 1.7156638459998703, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs22.x]": 1.7452296549998891, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2023]": 1.7391481500001191, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2]": 1.7503012290000015, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.10]": 1.7146287699997629, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.11]": 1.7497371279998788, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.12]": 1.7230264360000547, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.13]": 1.723328993000223, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.8]": 1.7193251679999548, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.9]": 1.7220050959998616, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.2]": 1.7799142559999837, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.3]": 1.7659715199999937, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.4]": 1.7802337749999424, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDLQ::test_dead_letter_queue": 20.243131066999922, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationEventbridge::test_invoke_lambda_eventbridge": 16.10188266399996, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload0]": 1.8613699219999944, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload1]": 1.8772197720001031, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_lambda_destination_default_retries": 21.398218818000032, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_maxeventage": 63.85512571300001, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_retries": 22.50119047499993, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_additional_docker_flags": 1.5492804409999508, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_lambda_docker_networks": 4.936304337000024, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[nodejs20.x]": 3.41296803299997, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[python3.12]": 4.372210234000022, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_environment_placeholder": 0.45191760300008355, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_error_path_not_absolute": 0.02646276100006162, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_publish_version": 1.113749372000143, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestLambdaDNS::test_lambda_localhost_localstack_cloud_connectivity": 1.5720982120000144, - "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[Active]": 2.5744458500000746, - "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[PassThrough]": 2.5764993139999888, - "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_xray_trace_propagation": 1.5462961090000817, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestCloudwatchLogs::test_multi_line_prints": 3.624001548999786, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2023]": 1.8818634790000033, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2]": 1.8787955039997541, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2023]": 1.9628375559998403, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2]": 1.9695461410001371, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom-INTERFACE]": 3.0325628439998127, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequest-INTERFACE]": 3.027488008000091, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequestCustom-CUSTOM]": 3.0723002060000226, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_lambda_subscribe_sns_topic": 8.85726094599977, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_runtime_with_lib": 5.623548458999949, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java11]": 2.67506008700002, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java17]": 2.574775374000069, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java21]": 2.7595085299999482, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java8.al2]": 2.800040777999925, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java11]": 1.7221893089999867, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java17]": 1.698258729000031, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java21]": 1.7587888389999762, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java8.al2]": 1.7417926940000825, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs16.x]": 4.712040349999938, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs18.x]": 4.694500220999998, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs20.x]": 4.676033183999948, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs22.x]": 4.686106505999987, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.10]": 1.6478566229995977, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.11]": 1.7794072119997963, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.12]": 1.6553480690001834, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.13]": 1.6518568010001218, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.8]": 1.6778594499999144, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.9]": 1.6703607810002268, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.10]": 1.5447454499997093, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.11]": 1.5190538190001917, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.12]": 1.5483294129999194, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.13]": 1.5491482299999007, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.8]": 1.579595849999805, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.9]": 1.5444160470001407, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_group": 0.2093402220000371, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_stream": 0.49619339500031856, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_delivery_logs_for_sns": 1.0934741949999989, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_filter_log_events_response_header": 0.05597701700003199, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_list_tags_log_group": 0.22845650900012515, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_metric_filters": 0.0019960860001901892, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_events_multi_bytes_msg": 0.058269946999871536, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_firehose": 0.5033557619999556, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_kinesis": 2.390128225000126, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_lambda": 2.9595373019999442, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_resource_does_not_exist": 0.043008572999951866, - "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend": 0.14299478299994917, - "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend_with_custom_endpoint": 0.16543726200006859, - "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint": 10.964950407999822, - "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint_disabled": 10.431276466999861, - "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_route_through_edge": 10.351477474000149, - "tests/aws/services/opensearch/test_opensearch.py::TestMultiClusterManager::test_multi_cluster": 17.41935173500019, - "tests/aws/services/opensearch/test_opensearch.py::TestMultiplexingClusterManager::test_multiplexing_cluster": 10.712634626000181, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_cloudformation_deployment": 12.245869679999942, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_custom_endpoint": 0.020952545000227474, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_name": 0.027585891999933665, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_existing_domain_causes_exception": 10.95183583700009, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_indices": 12.142682875999753, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_describe_domains": 10.50056709599994, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_lifecycle": 13.691449409000143, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_version": 10.52693980700019, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_path": 10.461875981000276, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_port": 9.890676918000054, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_exception_header_field": 0.012844930000255772, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_version_for_domain": 9.417730744999972, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_versions": 0.02411261999986891, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_document": 10.956192454000075, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_gzip_responses": 11.11908126000003, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_list_versions": 0.10235233499997776, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_search": 11.141928919999827, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_security_plugin": 16.037830759999906, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_sql_plugin": 15.726944203999892, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_update_domain_config": 10.524298520999764, - "tests/aws/services/opensearch/test_opensearch.py::TestSingletonClusterManager::test_endpoint_strategy_port_singleton_cluster": 9.791047596999988, - "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_cluster_security_groups": 0.03551398500007963, - "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_create_clusters": 0.16633352300027582, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_cloudformation_query": 0.0016760129999511264, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_create_group": 0.42543509899996934, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_different_region": 0.0017822209999849292, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_tag_query": 0.0018122259998563095, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_type_filters": 0.0018035399998552748, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_search_resources": 0.0016781360000095447, - "tests/aws/services/resourcegroupstaggingapi/test_rgsa.py::TestRGSAIntegrations::test_get_resources": 0.5062781630001609, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_associate_vpc_with_hosted_zone": 0.47818312600020363, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone": 0.6217465839999932, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone_in_non_existent_vpc": 0.18971460699981435, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_private_hosted_zone": 1.7543981000001168, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_crud_health_check": 0.15641552700003558, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_reusable_delegation_sets": 0.15347252900005515, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_associate_and_disassociate_resolver_rule": 0.5017922620002082, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[INBOUND-5]": 0.3507684099997732, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[OUTBOUND-10]": 0.2979596430000129, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_query_log_config": 0.3172143340000275, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule": 0.40107656600002883, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule_with_invalid_direction": 0.3049212399998851, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_endpoint": 0.09031297299998187, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_query_log_config": 0.16013734800003476, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_rule": 0.09132384299982732, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_resolver_endpoint": 0.3063655109999672, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_disassociate_non_existent_association": 0.09003192300019691, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_domain_lists": 0.19216798200000085, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules": 0.3582587490000151, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_empty_rule_group": 0.10574194199989506, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_missing_rule_group": 0.1586205730002348, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multipe_create_resolver_rule": 0.4272995470000751, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multiple_create_resolver_endpoint_with_same_req_id": 0.3024692070000583, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_route53resolver_bad_create_endpoint_security_groups": 0.20258284600004117, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_update_resolver_endpoint": 0.32076443400001153, - "tests/aws/services/s3/test_s3.py::TestS3::test_access_bucket_different_region": 0.00197315500008699, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_availability": 0.03372516799981895, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_does_not_exist": 0.4623202800000854, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_exists": 0.25396079799998006, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_name_with_dots": 0.5805706840001221, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_operation_between_regions": 0.47893485800000235, - "tests/aws/services/s3/test_s3.py::TestS3::test_complete_multipart_parts_order": 0.49265821099993445, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_in_place_with_bucket_encryption": 0.1425520850000339, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_kms": 0.6988784260001921, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character": 0.663166062999835, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character_plus_for_space": 0.09724246000018866, - "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_head_bucket": 0.6611636489999455, - "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_via_host_name": 0.040557594000119934, - "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_existing_name": 0.44061686200029726, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_no_such_bucket": 0.01961162600014177, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy": 0.10069347600028777, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy_expected_bucket_owner": 0.1094853189999867, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_with_content": 0.7423675210000056, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_keys_in_versioned_bucket": 0.5462430949999089, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys": 0.086179201999812, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_in_non_existing_bucket": 0.024269766999850617, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_quiet": 0.08039799000016501, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_object_tagging": 0.11380159099985576, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_objects_encoding": 0.12062863000005564, - "tests/aws/services/s3/test_s3.py::TestS3::test_different_location_constraint": 0.6148220570000831, - "tests/aws/services/s3/test_s3.py::TestS3::test_download_fileobj_multiple_range_requests": 1.1260778729997583, - "tests/aws/services/s3/test_s3.py::TestS3::test_empty_bucket_fixture": 0.16037406600003123, - "tests/aws/services/s3/test_s3.py::TestS3::test_etag_on_get_object_call": 0.47629589899975144, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_notification_configuration_no_such_bucket": 0.019710392999968462, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy": 0.12410200700014684, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000000000020]": 0.06696469300004537, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000]": 0.0676245930001187, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[aa000000000$]": 0.06933318099959251, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[abcd]": 0.06929101999980958, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_versioning_order": 0.5435158000000229, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_after_deleted_in_versioned_bucket": 0.12014189200021974, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes": 0.3204719489999661, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_versioned": 0.5527548090001346, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_with_space": 0.0997401950000949, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[False]": 0.10040963499977806, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[True]": 0.10121348900042904, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_no_such_bucket": 0.0215344539999478, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part": 0.24432591999993747, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[COMPOSITE]": 0.1283157430002575, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[FULL_OBJECT]": 0.13171113299972603, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_with_anon_credentials": 0.5088541049999549, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_range_object_headers": 0.09785267100005512, - "tests/aws/services/s3/test_s3.py::TestS3::test_head_object_fields": 0.10052647200018328, - "tests/aws/services/s3/test_s3.py::TestS3::test_invalid_range_error": 0.09207504699998026, - "tests/aws/services/s3/test_s3.py::TestS3::test_metadata_header_character_decoding": 0.45979596900019715, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_and_list_parts": 0.18372949900003732, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_too_small": 0.105998050999915, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_wrong_part": 0.09781825100003516, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_copy_object_etag": 0.13706869999987248, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_no_such_upload": 0.08683782000002793, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_overwrite_key": 0.12511915000004592, - "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[False]": 0.18616027999996732, - "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[True]": 0.19385757999998532, - "tests/aws/services/s3/test_s3.py::TestS3::test_precondition_failed_error": 0.10293714499994167, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_content_language_disposition": 0.9427751069999886, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_hash_prefix": 0.4541095059998952, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_utf8_key": 0.46126005400014947, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_inventory_config_order": 0.16159274699975867, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy": 0.092179490000035, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_expected_bucket_owner": 1.3950950420000936, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000000000020]": 0.0684062149998681, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000]": 0.06977988200014806, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[aa000000000$]": 0.06607288000009248, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[abcd]": 0.06742357799998899, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_single_character_trailing_slash": 0.1532584480000878, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[a/%F0%9F%98%80/]": 0.4744005269999434, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[file%2Fname]": 0.4674334710000494, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key//]": 0.4632082770001489, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key/]": 0.4743285330000617, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123/]": 0.46959542699983103, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123]": 0.4782150339999589, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%percent]": 0.4759737120002683, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test@key/]": 0.47201639499985504, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_acl_on_delete_marker": 0.5632814609998604, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_checksum": 0.10131459999979597, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_content_encoding": 0.10397253600012846, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines": 0.09010460799981956, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig": 0.08548940700006824, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig_empty_body": 0.08991244899993944, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_with_trailing_checksum": 0.10859601499987548, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[DEEP_ARCHIVE-False]": 0.10350070200024675, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER-False]": 0.10688397300009456, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER_IR-True]": 0.10397368400026608, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[INTELLIGENT_TIERING-True]": 0.10497393600007854, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[ONEZONE_IA-True]": 0.10440879899988431, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[REDUCED_REDUNDANCY-True]": 0.10434775400017315, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD-True]": 0.10438086200019825, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD_IA-True]": 0.10487764499998775, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class_outposts": 0.08708290399999896, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_tagging_empty_list": 0.12534269499997208, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_with_md5_and_chunk_signature": 0.0875544689997696, - "tests/aws/services/s3/test_s3.py::TestS3::test_putobject_with_multiple_keys": 0.46337015699987205, - "tests/aws/services/s3/test_s3.py::TestS3::test_range_header_body_length": 0.11301083399985146, - "tests/aws/services/s3/test_s3.py::TestS3::test_range_key_not_exists": 0.06778927400000612, - "tests/aws/services/s3/test_s3.py::TestS3::test_region_header_exists_outside_us_east_1": 0.5587312520001433, - "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure": 0.16935184000021763, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_analytics_configurations": 0.2299701679996815, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects": 0.5159335739999733, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects_using_requests_with_acl": 0.0019586980001804477, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_public_objects_using_requests": 0.4941589100001238, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl": 1.3570944759999293, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl_exceptions": 0.26492087900032857, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_content_type_and_metadata": 0.5177701119998801, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_directive_copy": 0.48290225899995676, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_replace": 0.48337952499991843, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place": 0.5470693109998592, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_metadata_directive": 0.5677437270003338, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_storage_class": 0.4938991559999977, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_suspended_only": 0.6019478410003103, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_versioned": 0.6336351559998548, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_website_redirect_location": 0.4808714890000374, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_with_encryption": 0.7918998130001, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_preconditions": 3.5386904110000614, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_storage_class": 0.5078367099999923, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32C]": 0.4979822300001615, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32]": 0.4912876190001043, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC64NVME]": 0.4952810469999349, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA1]": 0.49826639499997327, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA256]": 0.49278280899989113, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32C]": 0.5029639230001521, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32]": 0.5056044279997423, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC64NVME]": 0.5136505590000979, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA1]": 0.5004996220004614, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA256]": 0.5039495170003647, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_wrong_format": 0.4283393179998711, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[COPY]": 0.5040150139998332, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[None]": 0.5067537710001488, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[REPLACE]": 0.5023609609997948, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[COPY]": 0.5988136040000427, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[None]": 0.603453060999982, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[REPLACE]": 0.599321836999934, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_object_with_version_id": 0.5217531939999844, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_objects_trailing_slash": 0.07734378200007086, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_download_object_with_lambda": 4.256885804999683, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_header_overrides": 0.13046132199997373, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_headers": 0.16092970299996523, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[get_object]": 3.559920361000195, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[head_object]": 3.5600981710001633, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_hostname_with_subdomain": 0.02023860899976171, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_intelligent_tier_config": 0.16747719499994673, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_invalid_content_md5": 24.373805396999842, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_inventory_report_crud": 0.17200360399988313, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_lambda_integration": 11.63584078000008, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_acls": 0.20544137900014903, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_sse": 0.20587831300008474, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl": 0.18171623100010947, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl_exceptions": 0.2409344909997344, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expiry": 3.5639310170001863, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_inventory_report_exceptions": 0.1615676229998826, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_more_than_1000_items": 14.565878652000265, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_object_versioned": 0.6609418120001465, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_raw_request_routing": 0.11487258400006795, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer": 0.08942348599998695, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer_exceptions": 0.08705117699992115, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_bucket_key_default": 0.23803369300026134, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_default_kms_key": 0.0019469159999516705, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key": 0.2839329689998067, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key_state": 0.3018501680001009, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_timestamp_precision": 0.11186059200008458, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_upload_download_gzip": 0.09927624499982812, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_bucket_name": 0.3946347980001974, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_key_names": 0.10620404300016162, - "tests/aws/services/s3/test_s3.py::TestS3::test_set_external_hostname": 0.1404979160001858, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_big_file": 0.614674966000166, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_multipart": 0.4839288320001742, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_with_xml_preamble": 0.4592400470000939, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_cancelled_valid_etag": 0.11369754499992268, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_newlines_valid_etag": 0.10058801399986805, - "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[False]": 0.1419779989998915, - "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[True]": 0.14505885999983548, - "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxy_does_not_decode_gzip": 0.08702040799994393, - "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxying_headers": 0.09950163400003476, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_date": 0.08062298699996973, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry": 0.12360423899986017, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry_versioned": 0.16846179700019093, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_multiple_rules": 0.13078046699979495, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_object_size_rules": 0.12982043600004545, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_tag_rules": 0.20158644499997536, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_bucket_lifecycle_configuration": 0.11799245800011704, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_lifecycle_configuration_on_bucket_deletion": 0.12202781700011656, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_lifecycle_expired_object_delete_marker": 0.11648194200029138, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_object_expiry_after_bucket_lifecycle_configuration": 0.13417481299961764, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_put_bucket_lifecycle_conf_exc": 0.13694396000005327, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_s3_transition_default_minimum_object_size": 0.1299478010000712, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging": 0.161322557999938, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_accept_wrong_grants": 0.14077334499984318, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_cross_locations": 0.17989501100009875, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_wrong_target": 0.12823111799980325, - "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config": 1.8359033250001175, - "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config_without_filter": 0.6505051759997968, - "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_s3_get_deep_archive_object_restore": 0.5508051580000028, - "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_storage_class_deep_archive": 0.17338402100017447, - "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_access": 0.1323644389999572, - "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_copy_object": 0.0968086959996981, - "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_shared_bucket_namespace": 0.0724576600000546, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32C]": 0.5026149279997298, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32]": 0.5010776960000385, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA1]": 0.5283802360002028, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA256]": 0.5499513750000915, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_default": 0.23165879600014705, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32C]": 0.5652639659997476, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32]": 0.58090115899995, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC64NVME]": 0.61629050800002, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object_default": 0.14199672399968222, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32C]": 0.09737393699970198, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32]": 0.09684394199985036, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC64NVME]": 0.08106761500016546, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA1]": 0.09791653399997813, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA256]": 0.10076278599990474, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32C]": 0.07366583499992885, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32]": 0.07458639599963135, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC64NVME]": 0.07208600299986756, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA1]": 0.07353632400008792, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA256]": 0.071970599999986, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32C]": 0.07240395500002705, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32]": 0.07303740699944683, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC64NVME]": 0.07220480699993459, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA1]": 0.07851158700009364, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA256]": 0.0767022900001848, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_composite": 12.80642985999998, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_full_object": 33.148257720000174, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_size_validation": 0.13118641400001252, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32C]": 6.620719754999982, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32]": 6.388613734000046, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC64NVME]": 6.345888356999922, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA1]": 4.007398880999972, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA256]": 6.687101071000143, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[COMPOSITE]": 0.18046534199970665, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[FULL_OBJECT]": 0.17284081900015735, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_delete_locked_object": 0.13317263899989484, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_get_object_legal_hold": 0.14256791200023144, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_legal_hold_exc": 0.17897700100002112, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_with_legal_hold": 0.1158486830001948, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_copy_object_legal_hold": 0.5244989570001053, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_legal_hold_lock_versioned": 0.5520742510002492, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_bucket_config_default_retention": 0.14835029800019583, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_delete_markers": 0.13188643000012235, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_extend_duration": 0.135053811999569, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_copy_object_retention_lock": 0.5054117750000842, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_lock_mode_validation": 0.107428581999784, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention": 6.175300314999959, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_compliance_mode": 6.1472041960003025, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_exc": 0.26930742400031704, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_default_checksum": 0.10709031700002924, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3]": 0.10529939399998511, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3v4]": 0.10577803699993638, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": 0.34373631999983445, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": 0.2979261619998397, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": 0.24154480300012438, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_file_as_string": 0.3515007290000085, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_files": 0.13909012799967968, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_metadata": 0.11692746099970464, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_storage_class": 0.36012375300015265, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[invalid]": 0.18654262899985952, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[list]": 0.17272819900017566, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[notxml]": 0.18366617699985, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[single]": 0.17299679200004903, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_wrong_content_type": 0.15278917399996317, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_expires": 3.153930395999623, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3]": 0.16289918699976624, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3v4]": 0.16386224200027755, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3]": 0.1760144830000172, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3v4]": 0.17413464399987788, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3]": 0.1622722900001463, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3v4]": 0.16275654499986558, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_presigned_post_with_different_user_credentials": 0.1964797800001179, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_redirect": 0.09812805800015667, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_status_201_response": 0.08644123299973216, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_delete_has_empty_content_length_header": 0.10167214500006594, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_object_ignores_request_body": 0.09191854100004093, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_request_expires_ignored_if_validation_disabled": 3.1161971999997604, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_head_has_correct_content_length_header": 0.08780549300013263, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_forward_slash_bucket": 0.10604770899999494, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_match": 0.105679173000226, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_none_match": 0.10367928900018342, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_check_signature_validation_for_port_permutation": 0.10720932100025493, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_with_additional_query_params": 0.1145536000001357, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_double_encoded_credentials": 0.1775440209999033, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-False]": 0.22498785400011911, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-True]": 0.2261991319999197, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-False]": 0.23872102300015285, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-True]": 0.25754940899992107, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-False]": 2.184290624000141, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-True]": 2.191994462000139, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-False]": 2.1791915280000467, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-True]": 2.182097841000086, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-False]": 0.13042441499987945, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-True]": 0.15132369799994194, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-False]": 0.12425882600018667, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-True]": 0.12749365900026532, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_signed_headers_in_qs": 1.983413371000097, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_x_amz_in_qs": 8.48407282900007, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_different_user_credentials": 0.21027165200007403, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_session_token": 0.15356245299994953, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object": 0.4607080420003058, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-False]": 0.09722037299980002, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-True]": 0.17112357400014844, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-False]": 0.0954565699998966, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-True]": 0.1720241810000971, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[False]": 0.5738141779997932, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[True]": 0.6047878110000511, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[False]": 0.5804183309999189, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[True]": 0.5753122500000245, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_copy_md5": 0.11649680199980139, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_case_sensitive_headers": 0.09057117399993331, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_content_type_same_as_upload_and_range": 0.11288940099984757, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_default_content_type": 0.08887177299948235, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3]": 0.10305149099986011, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3v4]": 0.10333485599994674, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_ignored_special_headers": 0.13232632900007957, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3]": 0.10285235300011664, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3v4]": 0.10090957199963668, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3]": 3.201685520000183, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3v4]": 3.204823587999954, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3]": 0.183133386000236, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3v4]": 0.1955025620000015, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_same_header_and_qs_parameter": 0.19760925999980827, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3]": 1.3245765589999792, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3v4]": 0.22717458900001475, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32C]": 9.109814508999989, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32]": 10.052975495999817, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC64NVME]": 12.02609666599983, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA1]": 8.692350379999652, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA256]": 4.503924855999912, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_algorithm": 0.11403651099976742, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_automatic_sdk_calculation": 0.2547866529998828, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_with_content_encoding": 0.1162493109998195, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32C]": 0.12463894999996228, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32]": 0.12991229500016743, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC64NVME]": 0.1231433659997947, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[None]": 0.12419337000005726, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA1]": 0.12668078900014734, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA256]": 0.1262730730002204, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-False]": 0.10006109999994806, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-True]": 0.09790990500005137, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-False]": 0.10607122200008234, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-True]": 0.10388245300009658, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_copy_object_with_sse_c": 0.23267155000007733, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c": 0.4733700520000639, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c_validation": 0.1975247729997136, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_object_retrieval_sse_c": 0.2584269160001895, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_default_checksum_with_sse_c": 0.1894062630001372, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_lifecycle_with_sse_c": 0.188436652000064, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_validation_sse_c": 0.22591462299965315, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_sse_c_with_versioning": 0.23512968300019566, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_crud_website_configuration": 0.11060472899998786, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_object_website_redirect_location": 0.276459290000048, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_conditions": 0.5619045869998445, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_empty_replace_prefix": 0.44247989000018606, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_order": 0.2546975669999938, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_redirects": 0.15884029899984853, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_hosting": 0.5631324440000753, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_index": 0.14533859099992696, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_validate_website_configuration": 0.20917430299982698, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_404": 0.23761776600008488, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_http_methods": 0.14725610900018182, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_index_lookup": 0.2945937140004844, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_no_such_website": 0.13676470700011123, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_redirect_all": 0.32232925099992826, - "tests/aws/services/s3/test_s3.py::TestS3TerraformRawRequests::test_terraform_request_sequence": 0.05900464700016528, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_crud": 0.10540322399992874, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_exc": 0.13851688300019305, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_bucket_with_objects": 0.4535801210001864, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_versioned_bucket_with_objects": 0.4932700060001025, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms": 0.2388688280000224, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms_aws_managed_key": 0.2852026420000584, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_s3": 0.10983556899986979, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption": 0.09529618700003084, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption_exc": 0.49727485400012483, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_crud": 0.1460439669999687, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_exc": 0.09237591300006898, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_crud": 0.16730051600006846, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_exc": 0.22144096100009847, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": 0.2186370959998385, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tags_delete_or_overwrite_object": 0.14671714800033442, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_with_tags": 0.21395852599971477, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_tagging_validation": 0.18930809700032114, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_bucket_ownership_controls_exc": 0.12224592200004736, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_crud_bucket_ownership_controls": 0.17803875799995694, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_crud": 0.1270301729998664, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_exc": 0.10410168999987945, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_bucket_versioning_crud": 0.16540369900008045, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_object_version_id_format": 0.09930176700004267, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": 0.09405574400011574, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": 0.09369080900023619, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": 0.09177899899987096, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": 0.09439786999973876, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration": 0.09033451999994213, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration_twice": 0.08557629100005215, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration": 0.08174477600005048, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration_not_exist": 0.0731129029998101, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations": 0.08643900700008089, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations_paginated": 0.8569166489999134, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_overwrite_bucket_metrics_configuration": 0.16004871900008766, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_put_bucket_metrics_configuration": 0.15436997200004043, - "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_no_copy_source_range": 0.1962781349998295, - "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_range": 0.34236564600018937, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object": 0.09988595000004352, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_on_suspended_bucket": 0.6108128949999809, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_versioned": 0.5940755259998696, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects": 0.09846562500024447, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects_versioned": 0.5083191290000286, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_range": 0.32879147899984673, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_with_version_unversioned_bucket": 0.4745985639999617, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_list_object_versions_order_unversioned": 0.533842204000166, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_put_object_on_suspended_bucket": 0.6439261220002663, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_delete_object_with_no_locking": 0.11706702700007554, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_disable_versioning_on_locked_bucket": 0.07679679600005329, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_object_lock_configuration_exc": 0.07985410700007378, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_put_object_lock_configuration": 0.1086205780002274, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_exc": 0.12956235400019978, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_on_existing_bucket": 0.1253470589999779, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_etag": 0.15538120600012917, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_delete": 0.15204420999998547, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put": 0.16793554499986385, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put_identical": 0.1593157020001854, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_delete": 0.1609870260003845, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_put": 0.1158939949998512, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match": 0.13742292599999928, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_and_if_none_match_validation": 0.07533402000012757, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_validation": 0.09496577899994918, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_versioned_bucket": 0.18487065999988772, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match": 0.11959712800012312, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_validation": 0.09374148400024751, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_versioned_bucket": 0.16477816199994777, - "tests/aws/services/s3/test_s3_api.py::TestS3PublicAccessBlock::test_crud_public_access_block": 0.11627519800003938, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_bucket_creation": 0.4474063399998158, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_listing": 0.3960190000000239, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_read": 1.670120024000198, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_read_range": 2.711662180999838, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_expose_headers": 0.2759559010000885, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_get_no_config": 0.11957721000021593, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_no_config": 0.21138417699989986, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket": 0.17481359900034477, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket_ls_allowed": 0.08358171299983042, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_list_buckets": 0.09279736300027253, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_headers": 0.8091445500001555, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_methods": 0.7545037549998597, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_origins": 0.6713996000003135, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_no_config_localstack_allowed": 0.12117535599963958, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_fails_partial_origin": 0.46337537699992026, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_match_partial_origin": 0.17344863099992835, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_delete_cors": 0.20390555400013, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_get_cors": 0.1831076309999844, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors": 0.17309904399985498, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_default_values": 0.5044573299999229, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_empty_origin": 0.16918629799988594, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_invalid_rules": 0.17182613899967691, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_s3_cors_disabled": 0.10894259599990619, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_bucket_region": 0.5934922499998265, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_prefix_with_case_sensitivity": 0.5263748169998053, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_when_continuation_token_is_empty": 0.4874251250000725, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_continuation_token": 0.5459260639997865, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_max_buckets": 0.51605313999994, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multipart_uploads_marker_common_prefixes": 0.5179120589998547, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_next_marker": 0.6594019139997727, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_with_prefix_and_delimiter": 0.5231705650001004, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_s3_list_multiparts_timestamp_precision": 0.08256486099980975, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_object_versions_pagination_common_prefixes": 0.6021719129996654, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_markers": 0.7060049270000945, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix": 0.6096113930000229, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination": 0.629096724999954, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination_many_versions": 1.2326595360000283, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_s3_list_object_versions_timestamp_precision": 0.11446008900020388, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_marker_common_prefixes": 0.5709854959998211, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_next_marker": 0.5387953989998095, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[%2F]": 0.48179683899979864, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[/]": 0.4681397829999696, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[]": 0.4925782140001047, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_empty_marker": 0.44353926600001614, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjectsV2]": 0.09622208499990847, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjects]": 0.09562790399991172, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_common_prefixes": 0.5468975119999868, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_start_after": 0.6675455349998174, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix": 0.5389973770002143, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix_and_delimiter": 0.52619140600018, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_empty_part_number_marker": 0.12096679100022811, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_pagination": 0.15753977300005317, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_via_object_attrs_pagination": 0.27373330099999293, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_s3_list_parts_timestamp_precision": 0.09555631299963352, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put": 1.873343327000157, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_in_different_region": 1.868576179999991, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_versioned": 6.58268254599966, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_put_acl": 1.294172462999768, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_restore_object": 1.1957186710001224, - "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_by_presigned_request_via_dynamodb": 6.120672615999865, - "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_put_via_dynamodb": 2.961846759000082, - "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_invalid_lambda_arn": 0.4548882860001413, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_not_exist": 0.3890254170000844, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_notifications_with_filter": 1.6482997969997086, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_invalid_topic_arn": 0.26342117700005474, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_object_created_put": 1.7558761759999015, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_bucket_notification_with_invalid_filter_rules": 0.28131020299997544, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_delete_objects": 0.8454481849998956, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_case_insensitive": 0.1065665880000779, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_invalid_sqs_arn": 0.42254666399981033, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_key_encoding": 0.6476338730001316, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_multiple_invalid_sqs_arns": 0.6256722480000008, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_notifications_with_filter": 0.7662331340000037, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_and_object_removed": 0.9403812260002269, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_complete_multipart_upload": 0.6938460090000262, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_copy": 0.7065687189999608, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put": 0.7356424399999923, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_versioned": 1.1276264819998687, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_with_presigned_url_upload": 0.9520425279999927, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_put_acl": 0.8580521609999323, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_delete_event": 0.6910409119998349, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_put_event": 0.6938666420001027, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_restore_object": 0.8563706419997743, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_xray_header": 1.6470353830002296, - "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_lifecycle_public_access_block": 0.29192462700029864, - "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_public_access_block_validations": 0.03225669899984496, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_already_exists": 0.0016606019999017008, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_bucket_not_exists": 0.0017097729996748967, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_lifecycle": 0.0017145429999345652, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_name_validation": 0.0018094780000410537, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_pagination": 0.0016869920000317506, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_public_access_block_configuration": 0.0016833139998198021, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_crud_public_access_block": 0.0017165360000035434, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_empty_public_access_block": 0.0017025199999807228, - "tests/aws/services/scheduler/test_scheduler.py::test_list_schedules": 0.06595184399975551, - "tests/aws/services/scheduler/test_scheduler.py::test_tag_resource": 0.03569380599992655, - "tests/aws/services/scheduler/test_scheduler.py::test_untag_resource": 0.031017261999977563, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[ rate(10 minutes)]": 0.01506955700006074, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31)]": 0.014849436000076821, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31T23:59:59Z)]": 0.014749059999985548, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron()]": 0.015635273000043526, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 1 * * * *)]": 0.017838727000025756, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 dummy ? * MON-FRI *)]": 0.016321272999903158, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(7 20 * * NOT *)]": 0.01576132900004268, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(71 8 1 * ? *)]": 0.015490920000047481, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(INVALID)]": 0.018163261000154307, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate( 10 minutes )]": 0.015455353999868748, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate()]": 0.015951258000086455, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(-10 minutes)]": 0.016002262000029077, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 minutess)]": 0.01627392799991867, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 seconds)]": 0.015326893000064956, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 years)]": 0.014951536000125998, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10)]": 0.016240656000036324, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(foo minutes)]": 0.016535918000045058, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_valid_schedule_expression": 0.11654861100009839, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times": 0.05780057299989494, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times_snapshots": 0.001749435999954585, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_can_recreate_delete_secret": 0.05750505899982272, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2]": 0.0879677850000462, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2c3-]": 0.08986523200019292, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name]": 0.08724958100015101, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[s-c64bdc03]": 0.11293259700028102, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets": 0.10444908099998429, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets_snapshot": 0.0018658340000001772, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_version_from_empty_secret": 0.043871468999896024, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_with_custom_id": 0.025167964000047505, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_delete_non_existent_secret_returns_as_if_secret_exists": 0.022006639999744948, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version": 0.9338546039998619, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version_stage": 0.19720595700005106, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_exp_raised_on_creation_of_secret_scheduled_for_deletion": 0.04384953499993571, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_first_rotate_secret_with_missing_lambda_arn": 0.0387822600000618, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_force_delete_deleted_secret": 0.060503461000052994, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_random_exclude_characters_and_symbols": 0.01641634299994621, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value": 0.07993451700008336, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value_errors": 0.045177548000083334, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_custom_client_request_token_new_version_stages": 0.06019618800019089, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_duplicate_req": 0.04847852500006411, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_null_client_request_token_new_version_stages": 0.05797301100005825, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_duplicate_client_request_token": 0.049712256000020716, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_non_provided_client_request_token": 0.050505537999924854, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv *?!]Name\\\\-]": 0.10239482799988764, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv Name]": 0.09064332899993133, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv*Name? ]": 0.09293515400031538, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[Inv Name]": 0.09800008599995635, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_accessed_date": 0.06454952400031289, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_updated_date": 0.08975562500017986, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_list_secrets_filtering": 0.19309195499999987, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[CreateSecret]": 0.02916261200016379, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[PutSecretValue]": 0.02612051000005522, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[RotateSecret]": 0.02564147599991884, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[UpdateSecret]": 0.02649162099987734, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_no_replacement": 1.5417170400000941, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_replacement": 0.2265479120001146, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_new_custom_client_request_token": 0.052243447000137166, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_version_stages": 0.108671897000022, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_resource_policy": 0.05148330600013651, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_invalid_lambda_arn": 0.2262543760000426, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_multiple_times_with_lambda_success": 2.9134931899998264, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[None]": 2.3650102779997724, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[True]": 2.383824018000041, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists": 0.05125931100019443, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists_snapshots": 0.06292859500035775, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_not_found": 0.026290863000212994, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_restore": 0.04855026999985057, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_tags": 0.1297430100000838, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_version_not_found": 0.04458845600015593, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_description": 0.10627695200014387, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending": 0.2360657359997731, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle": 0.2846910329997172, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_1": 0.28796212799989007, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_2": 0.322898695000049, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_3": 0.2773040709998895, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_previous": 0.21628996899971753, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_return_type": 0.0500496419999763, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_with_non_provided_client_request_token": 0.04900064100002055, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access": 0.14401837500008696, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access_non_default_key": 0.11852805099988473, - "tests/aws/services/ses/test_ses.py::TestSES::test_cannot_create_event_for_no_topic": 0.04366634999996677, - "tests/aws/services/ses/test_ses.py::TestSES::test_clone_receipt_rule_set": 0.8827203099999679, - "tests/aws/services/ses/test_ses.py::TestSES::test_creating_event_destination_without_configuration_set": 0.06723909900028957, - "tests/aws/services/ses/test_ses.py::TestSES::test_delete_template": 0.06744242499962638, - "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set": 0.0166297680000298, - "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set_event_destination": 0.03544346399985443, - "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_domain": 0.014465330000348331, - "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_email": 0.029242627000030552, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-]": 0.01664231699987795, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-test]": 0.017959887999950297, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-]": 0.017698113000051308, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-test_invalid_value:123]": 0.01887810400012313, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test]": 0.017543815999943035, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test_invalid_value:123]": 0.01738436100004037, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name_len]": 0.017171424999787632, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_value_len]": 0.018015015999935713, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_priority_name_value]": 0.01655714500020622, - "tests/aws/services/ses/test_ses.py::TestSES::test_list_templates": 0.16027558500013583, - "tests/aws/services/ses/test_ses.py::TestSES::test_sending_to_deleted_topic": 0.47302001500020197, - "tests/aws/services/ses/test_ses.py::TestSES::test_sent_message_counter": 0.13389550400006556, - "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_email": 1.577122723999537, - "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_raw_email": 1.5435215759998755, - "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_templated_email": 1.6819929349996983, - "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-a-this-marketing-campaign]": 0.019557791999659457, - "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-b-that-campaign]": 0.02005948799978796, - "tests/aws/services/ses/test_ses.py::TestSES::test_trying_to_delete_event_destination_from_non_existent_configuration_set": 0.09985097499975382, - "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_email_can_retrospect": 1.5249821879997398, - "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_templated_email_can_retrospect": 0.07850659400014592, - "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[]": 0.2094221119998565, - "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[sns.us-east-1.amazonaws.com]": 0.15048183800013248, - "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_access": 0.13347943999974632, - "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_publish_to_sqs": 0.5912939269999242, - "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_access": 0.10083079599985467, - "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_delivery_sqs": 0.20867633800003205, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_create_platform_endpoint_check_idempotency": 0.002061959000002389, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_disabled_endpoint": 0.13475104599979204, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_gcm": 0.001910187000021324, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_platform_endpoint_is_dispatched": 0.17146861200012609, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_subscribe_platform_endpoint": 0.1879886719998467, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_empty_sns_message": 0.10173204599982455, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_message_structure_json_exc": 0.06150560100013536, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_batch_too_long_message": 0.08085113000015554, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_by_path_parameters": 0.1422315580000486, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_before_subscribe_topic": 0.15103066699998635, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_by_target_arn": 0.21622816099988995, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_non_existent_target": 0.036729945000161024, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_too_long_message": 0.08286049400021511, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_with_empty_subject": 0.04519835700011754, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_wrong_arn_format": 0.037021448999894346, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_topic_publish_another_region": 0.06448831600005178, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_unknown_topic_publish": 0.04628687700005685, - "tests/aws/services/sns/test_sns.py::TestSNSPublishDelivery::test_delivery_lambda": 2.174588754000297, - "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_sms_can_retrospect": 0.2707566549997864, - "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_to_platform_endpoint_can_retrospect": 0.21777770699986831, - "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_subscription_tokens_can_retrospect": 1.1070162589996926, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms": 0.01965063799980271, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms_endpoint": 0.1932339239999692, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_wrong_phone_format": 0.06734938300019166, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_subscribe_sms_endpoint": 0.05685347299981913, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_create_subscriptions_with_attributes": 0.09498173899987705, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions": 0.3607071060000635, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions_by_topic_pagination": 1.6172531190002246, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_not_found_error_on_set_subscription_attributes": 0.3112330290002774, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_sns_confirm_subscription_wrong_token": 0.1305780960001357, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_idempotency": 0.127111806999892, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_protocol": 0.03617736700016394, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_topic": 0.0675346940001873, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_from_non_existing_subscription": 0.09394025200003853, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_idempotency": 0.11907734399983383, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_wrong_arn_format": 0.0503471410002021, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_validate_set_sub_attributes": 0.2790145450003365, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionFirehose::test_publish_to_firehose_with_s3": 1.3150663779999832, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[False]": 2.6836687929999243, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[True]": 2.6859250099998917, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_http_subscription_response": 0.08937643899980685, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_multiple_subscriptions_http_endpoint": 1.7281893479996597, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_redrive_policy_http_subscription": 0.6651373640002021, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[False]": 2.135803071000055, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[True]": 1.640622330000042, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[False]": 1.621023389000129, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[True]": 1.6229521409998142, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_lambda_url_sig_validation": 2.0731141179999213, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[1]": 4.238022110000202, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[2]": 4.25448828399999, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_python_lambda_subscribe_sns_topic": 4.211322753000104, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_redrive_policy_lambda_subscription": 1.3338678729999174, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_sns_topic_as_lambda_dead_letter_queue": 2.3732942519998232, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_email_sender": 2.1266051649997735, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_topic_email_subscription_confirmation": 0.06319854999992458, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_attribute_raw_subscribe": 0.16274477099977958, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_empty_or_wrong_message_attributes": 0.3991618609998113, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_not_missing": 0.23755770699972345, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_prefixes": 0.1974293079997551, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_structure_json_to_sqs": 0.2291508650000651, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_exceptions": 0.0729826280000907, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_from_sns_to_sqs": 0.7068346380001458, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_without_topic": 0.036589964000086184, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns": 0.24746136899989324, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns_with_xray_propagation": 0.15770290100022066, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[1]": 0.16735678399982135, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[2]": 0.16339168099989365, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_unicode_chars": 0.1518844510001145, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[False]": 0.20318071899964707, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[True]": 0.2230318360000183, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_sqs_topic_subscription_confirmation": 0.08207524600015859, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_sqs_queue": 0.19571102300005805, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_to_sqs_with_queue_url": 0.05330634599977202, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscription_after_failure_to_deliver": 1.5452918719997797, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[False]": 0.2888521459999538, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[True]": 0.29185546200005774, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[False]": 1.1999326170000586, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[True]": 1.1974212950001402, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs_ordering": 2.7802646520001417, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[False]": 3.6392769450001197, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[True]": 3.6750010330001714, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[False]": 1.6147210609999547, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[True]": 1.6015147880002587, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_deduplication_on_topic_level": 1.695315785000048, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[False]": 0.30163222300006964, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[True]": 0.30990992199986067, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_with_target_arn": 0.035956387999931394, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_validations_for_fifo": 0.24760621199970956, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_check_idempotency": 0.09449936500004696, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_with_more_tags": 0.03747917400005463, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_after_delete_with_new_tags": 0.058019091000005574, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_test_arn": 0.3237592730001779, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_with_attributes": 0.2850619740002003, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_delete_topic_idempotency": 0.058431228999779705, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags": 0.09438304900004368, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_topic_delivery_policy_crud": 0.0018642310001268925, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy": 0.3746523679999427, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy_attributes_array": 4.352528548999999, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_filter_policy": 5.342556068999784, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_empty_array_payload": 0.19072873899972365, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_for_batch": 3.40477781699974, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_ip_address_condition": 0.3762319180000304, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_large_complex_payload": 0.21614578200001233, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[False]": 5.362140765999811, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[True]": 5.361197647000154, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_attributes": 0.6364353639999081, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_of_object_attributes": 0.36628857299979245, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_dot_attribute": 6.976484288999927, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_or_attribute": 0.843804930000033, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity": 0.06214441499992063, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity_with_or": 0.06746930900021653, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy": 0.13294175999999425, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_exists_operator": 0.1219205439997495, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_nested_anything_but_operator": 0.17863118100012798, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_numeric_operator": 0.23636517900013132, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_string_operators": 0.24219511700016483, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_set_subscription_filter_policy_scope": 0.13158637100013948, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property": 0.11965933099986614, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property_constraints": 0.19429368800001612, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[domain]": 0.10752379699988523, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[path]": 0.10544912199975442, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[standard]": 0.10423173499998484, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[domain]": 0.03475557199999457, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[path]": 0.0348200830001133, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[standard]": 0.03623972199989112, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs]": 0.10454385399975763, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs_query]": 0.10239039499992941, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs]": 3.145401195000204, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs_query]": 3.148666141999911, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs]": 0.13738820699995813, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs_query]": 0.13174159899995175, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs]": 0.10681960999954754, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs_query]": 0.2592347089998839, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs]": 2.1067708160001075, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs_query]": 2.111746906999997, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs]": 0.6510865280001781, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs_query]": 0.6724273390000235, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs]": 0.11603891000004296, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs_query]": 0.11010402200008684, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs]": 0.09707912399994711, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs_query]": 0.1039994969999043, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs]": 0.07050939199962158, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs_query]": 0.07079123699986667, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs]": 0.09038001099997928, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs_query]": 0.09358487700023943, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs]": 0.1482310450003297, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs_query]": 0.14826946399989538, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_same_attributes_is_idempotent": 0.043409686000359216, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs]": 0.09654605099990476, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs_query]": 0.09302374099979716, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs]": 0.001718439000114813, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs_query]": 0.0017113269998390024, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs]": 0.1279264779998357, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs_query]": 0.12824502899979962, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs]": 0.03473943800008783, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs_query]": 0.03750203100003091, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs]": 0.040735833000098864, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs_query]": 0.040174810000053185, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs]": 1.5617854769998303, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs_query]": 1.5655761660002554, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs]": 0.04883473900008539, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs_query]": 0.051089651999973285, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs]": 0.0018045790000087436, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs_query]": 0.0017138610000984045, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_attributes_is_idempotent": 0.04293179500018596, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs]": 0.2143847980000828, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs_query]": 0.21307093099994745, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_same_attributes_is_idempotent": 0.044327179999982036, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs]": 0.034677759000260266, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs_query]": 0.033690141000079166, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_without_attributes_is_idempotent": 0.042326238999748966, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs]": 0.08396395599993411, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs_query]": 0.08376116699992053, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs]": 0.0018328619999010698, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs_query]": 0.001697619999958988, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_config": 0.04248411000003216, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs]": 0.0034503839999615593, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs_query]": 0.0017856139998002618, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs]": 0.06519370700016225, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs_query]": 0.06442652600003385, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs]": 0.14152619700007563, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs_query]": 0.145868844000006, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_message_attributes": 0.7880914509999002, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs]": 0.18546046000005845, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs_query]": 0.19117494100009935, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs]": 0.0018380019998858188, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs_query]": 0.0017066459997749917, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs]": 1.137682100999882, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs_query]": 1.138916465999955, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs]": 0.0018055800001093303, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs_query]": 0.0017697320001843764, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-]": 0.19547669299981862, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-invalid:id]": 0.07452020500022627, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.07201551799994377, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-]": 0.07547444499982703, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-invalid:id]": 0.07336394300000393, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.07479634299988902, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs]": 0.6734129700000722, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs_query]": 0.6641241560000708, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs]": 0.1479326650000985, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs_query]": 0.15015552200020466, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs]": 0.11765419299968016, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs_query]": 0.1204620309999882, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs]": 0.033526314999789975, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs_query]": 0.03353892500012989, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_disallow_queue_name_with_slashes": 0.0017790219997095846, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs]": 7.113487185000167, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs_query]": 6.999521292000281, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs]": 0.1458080309998877, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs_query]": 0.06851366699993378, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_host_via_header_complete_message_lifecycle": 0.09760431699987748, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_hostname_via_host_header": 0.03677318900008686, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs]": 0.2616445420001128, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs_query]": 0.2665408689999822, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs]": 0.34677693000003273, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs_query]": 0.3620400509996671, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs]": 0.24807855600010953, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs_query]": 0.25108450399966387, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs]": 1.1119839509999565, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs_query]": 1.137555243999941, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-False]": 1.159753192999915, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-True]": 1.1587123429999338, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-False]": 1.1658219659998394, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-True]": 1.1722177530000408, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-False]": 1.1406274129999474, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-True]": 1.1481800210001438, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-False]": 1.1473605260000568, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-True]": 1.150387224000042, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs]": 1.187417882000318, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs_query]": 1.1913687579999532, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs]": 0.0019062679998569365, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs_query]": 0.0017382050000378513, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs]": 0.18099170999994385, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs_query]": 0.18955985000025066, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs]": 0.1724835659999826, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs_query]": 0.17187849300012203, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs]": 0.1668164180000531, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs_query]": 0.16858229400008895, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility": 2.1271436969998376, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs]": 2.1331166939999093, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs_query]": 2.1352081219999945, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs]": 0.29302098199991633, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs_query]": 0.3093348029999561, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs]": 0.28830223499994645, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs_query]": 0.284946778000176, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs]": 0.14624972299975525, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs_query]": 0.15135191199988185, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs]": 2.118956553000089, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs_query]": 2.120084540000107, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_requires_suffix": 0.017153455999959988, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs]": 4.108532061999995, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs_query]": 4.1291995909998604, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs]": 0.1641569599999002, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs_query]": 0.1655647800000679, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs]": 0.26543260299990834, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs_query]": 0.2654057050001484, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs]": 0.15305341599992062, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs_query]": 0.15108570500001406, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs]": 2.1739506179999353, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs_query]": 2.1985084300001745, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs]": 0.19059476500001438, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs_query]": 0.20182209400013562, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs]": 0.10873189800008731, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs_query]": 0.11343428300006053, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs]": 0.09141701400017155, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs_query]": 0.09346205100018778, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_list_queues_with_query_auth": 0.023389039999983652, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs]": 0.03441413899986401, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs_query]": 0.043016021999847, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[domain]": 0.05448491900006047, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[path]": 0.05592134499988788, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[standard]": 0.059369954999738184, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs]": 0.06397124000000076, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs_query]": 0.0660786719997759, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_inflight_message_requeue": 4.604776447999939, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs]": 0.15665863899994292, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs_query]": 0.15387543699989692, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_dead_letter_arn_rejected_before_lookup": 0.001818336000042109, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs]": 0.033555155999920316, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs_query]": 0.03342973099984192, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs]": 0.03570585400007076, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs_query]": 0.037233435999951325, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs]": 0.038085110000110944, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs_query]": 0.043317520999835324, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues": 0.11012235600014719, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_domain": 0.06901149400005124, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_standard": 0.06885107600032825, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_without_endpoint_strategy": 0.07646024099994975, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_pagination": 0.29593796399990424, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[\"{\\\\\"foo\\\\\": \\\\\"ba\\\\rr\\\\\"}\"]": 0.0884703809997518, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[{\"foo\": \"ba\\rr\", \"foo2\": \"ba"r"\"}]": 0.08731576400009544, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_too_long": 0.17177202699986083, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_group_id_too_long": 0.17450663900012842, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention": 3.1045608689996698, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_fifo": 3.083965822999744, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_with_inflight": 5.620424720999836, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs]": 0.12747570100009398, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs_query]": 0.13217381699996622, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs]": 0.06487595299995519, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs_query]": 0.06970577799984312, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs]": 0.07318649699982416, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs_query]": 0.07302193299960891, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_non_existent_queue": 0.17747946299982686, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs]": 0.2643771289999677, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs_query]": 0.25984335999987707, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs]": 0.055575712999825555, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs_query]": 0.05585215499991136, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs]": 0.10435367500008397, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs_query]": 0.10580533899997135, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs]": 0.18128249099959248, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs_query]": 0.2748848099997758, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs]": 1.2515650919999644, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs_query]": 1.2575099419998423, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs]": 0.10844683400023314, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs_query]": 0.10840762400016501, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs]": 3.1661334680002255, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs_query]": 3.156630189000225, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs]": 4.25626735600008, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs_query]": 4.294738597000105, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs]": 0.034184860000095796, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs_query]": 0.03318413700003475, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs]": 1.6992580140001792, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs_query]": 1.9988293859998976, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs]": 1.0976392610002677, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs_query]": 1.1001510930000222, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs]": 0.2485836040000322, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs_query]": 0.25222833499969965, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs]": 0.07162777800022013, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs_query]": 0.07297605300027499, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs]": 0.3463207379995765, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs_query]": 0.31689583499996843, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs]": 0.16922910099992805, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs_query]": 0.16967031099989072, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs]": 0.10661419899997782, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs_query]": 0.10664911000026223, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs]": 0.1053867180000907, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs_query]": 0.11304337899991879, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs]": 0.11087818900023194, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs_query]": 0.10616224300042632, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs]": 0.001733337000132451, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs_query]": 0.001793809999981022, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs]": 2.0897462479997557, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs_query]": 2.0868110979999983, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_message_size": 0.2547429329999886, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs]": 0.1463161740000487, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs_query]": 0.15387652100002924, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs]": 0.16675059699991834, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs_query]": 0.16321063999998842, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs]": 0.11992886500024724, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs_query]": 0.11729322700034572, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs]": 2.0280393080001886, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs_query]": 1.9986103029998503, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs]": 0.14649228600001152, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs_query]": 0.15205280400027732, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs]": 0.12333786599992891, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs_query]": 0.1281049060000896, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs]": 0.03400034599985702, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs_query]": 0.033624128000155906, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs]": 0.15147296499981167, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs_query]": 0.1587568139996165, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs]": 0.12213781499985998, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs_query]": 0.12322557599986794, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_empty_message_group_id": 0.09241603600003145, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs]": 0.06860787000005075, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs_query]": 0.0703884360000302, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs]": 0.10976956899980905, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs_query]": 0.11230360000013206, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs]": 0.0708486459998312, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs_query]": 0.06905346600001394, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs]": 0.15133838899987495, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs_query]": 0.1494742600000336, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs]": 0.0019508220002535381, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs_query]": 0.0018372909999015974, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs]": 0.03470251799990365, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs_query]": 0.033766475999982504, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs]": 0.14802395499987142, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs_query]": 0.15189857499990467, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs]": 0.18062490399961462, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs_query]": 0.18720810200011329, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs]": 0.15475551500026086, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs_query]": 0.156282358999988, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs]": 0.17268306799996935, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs_query]": 0.17473566800026674, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs]": 0.06981231999998272, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs_query]": 0.07361489100003382, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs]": 0.07361996599979648, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs_query]": 0.07084435399997346, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_multiple_queues": 0.09980903499990745, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs]": 0.24809545799985244, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs_query]": 0.24808775599967703, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs]": 0.08631835699998192, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs_query]": 0.08829990099980023, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs]": 0.09819445800007998, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs_query]": 0.09715929800017875, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs]": 0.06786696999984088, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs_query]": 0.07095895299971744, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs]": 0.07369445700010147, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs_query]": 0.07962261000011495, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs]": 0.04832844500015199, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs_query]": 0.05207483599997431, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs]": 0.25009891800027617, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs_query]": 0.2538656209999317, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs]": 0.22862591600005544, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs_query]": 0.2296396100000493, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs]": 0.16849181600014163, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs_query]": 0.17888240299976133, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs]": 0.16688097800010837, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs_query]": 0.169827818000158, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs]": 0.25291564400004063, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs_query]": 0.2611969100000806, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs]": 0.0019182309999905556, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs_query]": 0.0019104269999843382, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs]": 0.11183937199962202, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs_query]": 0.13503917200000615, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_standard_queue_cannot_have_fifo_suffix": 0.015540052000005744, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs]": 0.15980262700009007, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs_query]": 0.1541566400001102, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs]": 0.0988305590001346, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs_query]": 0.1031639009997889, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs]": 0.048020590000305674, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs_query]": 0.05069036099985169, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs]": 0.11389594299976125, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs_query]": 0.11447156900021582, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs]": 0.039492491000373775, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs_query]": 0.041711072999987664, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs]": 0.13545690500018281, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs_query]": 0.1473616860000675, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs]": 0.15009467799995946, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs_query]": 0.15596685299988167, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs]": 0.04993029800016302, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs_query]": 0.04960832400001891, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs]": 1.068008306000138, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs_query]": 1.0694903500000237, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs]": 1.0693614430001617, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs_query]": 1.0790416099998765, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[domain]": 0.13308852899990598, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[off]": 0.12985905699997602, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[path]": 0.13166133400000035, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[standard]": 0.19578174399998716, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_create_queue_fails": 0.03523274000008314, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[domain]": 0.05263482599980307, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[path]": 0.05283828500000709, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[standard]": 0.0552116430001206, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails": 0.03660737000018344, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails_json_format": 0.0020953309999640624, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs]": 0.05693651600040539, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs_query]": 0.06019795600013822, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_all": 0.057296069000130956, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_json_format": 0.0018420989999867743, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_of_fifo_queue": 0.04729289399983827, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_invalid_arg_returns_error": 0.04465686999992613, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_query_args": 0.04524931899982221, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[domain]": 0.047666362000200024, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[path]": 0.04622982400019282, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[standard]": 0.04553283000018382, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[domain]": 0.06004502400014644, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[path]": 0.061547522999944704, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[standard]": 0.05873433200008549, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[domain]": 0.04622646499979055, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[path]": 0.044323269999949844, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[standard]": 0.044290297000088685, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_send_and_receive_messages": 0.13151893600047515, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_json_format_returns_returns_xml": 0.03652240099995652, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_returns_unknown_operation": 0.033648229000164065, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_invalid_action_raises_exception": 0.03826458099979391, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_overwrite_queue_url_in_params": 0.06420296300029804, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_queue_url_format_path_strategy": 0.02895888900002319, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_send_message_via_queue_url_with_json_protocol": 1.0978493459999754, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_valid_action_with_missing_parameter_raises_exception": 0.03638321799962796, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-domain]": 0.112489504999985, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-path]": 0.10932953699989412, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-standard]": 0.11194339099984063, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-domain]": 0.11523653100016418, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-path]": 0.11383334700008163, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-standard]": 0.11134208800035594, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-domain]": 0.08522040000002562, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-path]": 0.08396175000007133, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-standard]": 0.08988810800019564, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-domain]": 0.08566115100006755, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-path]": 0.08728481999992255, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-standard]": 0.0954757540000628, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[domain]": 0.08271128700016561, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[path]": 0.08218180499989103, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[standard]": 0.08071549300029801, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[domain]": 0.11263048900013928, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[path]": 0.10979123000015534, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[standard]": 0.1132800049999787, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[domain]": 0.11487801200019021, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[path]": 0.12267478399985521, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[standard]": 0.1169061169998713, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-domain]": 0.03133796900010566, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-path]": 0.033945033999998486, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-standard]": 0.034815323999964676, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-domain]": 0.03424945799974921, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-path]": 0.033452457000066715, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-standard]": 0.03485122499978388, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[domain]": 0.02132417899997563, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[path]": 0.02129169600016212, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[standard]": 0.023901496000007683, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[domain]": 0.13510917500002506, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[path]": 0.13671734900026422, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[standard]": 0.13242709000019204, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[domain]": 0.027754940000249917, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[path]": 0.026966301999891584, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[standard]": 0.028718152000010377, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[domain]": 0.09328148699978556, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[path]": 0.08878544600020177, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[standard]": 0.09320026200020948, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[domain]": 0.022032154999806153, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[path]": 0.021176046999698883, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[standard]": 0.02238916999999674, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsOverrideHeaders::test_receive_message_override_max_number_of_messages": 0.5866712409999764, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsOverrideHeaders::test_receive_message_override_message_wait_time_seconds": 25.30734893299973, - "tests/aws/services/sqs/test_sqs_move_task.py::test_basic_move_task_workflow": 1.8504090120004548, - "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_source_arn_in_task_handle": 0.05581147700058864, - "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_handle": 0.055586209000466624, - "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_id_in_task_handle": 0.0789477330004047, - "tests/aws/services/sqs/test_sqs_move_task.py::test_destination_needs_to_exist": 0.11856383100030143, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_cancel": 1.9038878649998878, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_delete_destination_queue_while_running": 1.9773679940003603, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_with_throughput_limit": 3.4274532429994906, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_default_destination": 1.827939088000221, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_multiple_sources_as_default_destination": 3.890405383000598, - "tests/aws/services/sqs/test_sqs_move_task.py::test_source_needs_redrive_policy": 0.10197077599968907, - "tests/aws/services/sqs/test_sqs_move_task.py::test_start_multiple_move_tasks": 0.7627350459997615, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_describe_parameters": 0.01810817599971415, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_maintenance_window": 0.023817740000595222, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_secret": 0.03787198200006969, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameter_by_arn": 0.08231778299978032, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_and_secrets": 0.14842445400017823, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_by_path_and_filter_by_labels": 0.08196294599974863, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_secret_parameter": 0.07568512800071403, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[///b//c]": 0.07566867600007754, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[/b/c]": 0.07476626700008637, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_parameters_with_path": 0.17018202700000984, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_put_parameters": 0.0893688510004722, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[domain]": 0.15690301000040563, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[path]": 0.1290321239998775, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[standard]": 0.1642492669998319, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task": 2.242031850999865, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_failure": 2.020435523999822, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_no_worker_name": 1.9649174989995117, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_on_deleted": 0.4500415950005845, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_start_timeout": 5.732111526000153, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_with_heartbeat": 6.045177750000221, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY]": 2.329653178999706, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY_GLOBAL_QL_JSONATA]": 2.37017564900043, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EXPRESSION]": 6.779571311999916, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_LITERALS]": 2.4363465440001164, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_FALSE]": 2.1746429400000125, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_TRUE]": 0.9968447280002692, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_CONSTANT_LITERALS]": 1.155137544000354, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_EMPTY]": 0.7360776300001817, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_PATHS]": 1.011504891000186, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_SCOPE_MAP]": 1.0812960429998384, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_VAR]": 1.3519847150000714, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_parallel_cases[BASE_SCOPE_PARALLEL]": 1.1427289280004516, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 1.9891838349999489, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_PARAMETERS]": 1.0018021460000455, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_RESULT]": 0.9999479560001419, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_catch_state": 2.4012238420004905, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[CORRECT]": 1.0208247360001224, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[INCORRECT]": 1.0028377329999785, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_wait_state": 0.7542664139996305, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_CHOICE]": 1.0591938850002407, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_FAIL]": 0.9515942590005579, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INPUTPATH]": 0.9882390630000373, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.2406991290004044, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 1.9965731669999514, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_OUTPUTPATH]": 1.007706987000347, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_PARAMETERS]": 0.9767649740001616, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_WAIT]": 0.9948958230002063, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.3146945670005152, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.305960007000067, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 1.30932739400032, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 1.020460675999857, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 1.1418136660004166, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 1.1738197749996289, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_PER_BATCH_PATH]": 0.0020755529999405553, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_state_assign_evaluation_order[BASE_EVALUATION_ORDER_PASS_STATE]": 0.0019113699995614297, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS]": 0.0019522949996826355, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS_FIELD]": 0.001715875000172673, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ASSIGN]": 1.2490582929999619, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT]": 1.2790427709996948, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_FIELD]": 1.2577873539999018, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_MULTIPLE_STATES]": 1.2923457249999046, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_variables_in_lambda_task[BASE_ASSIGN_FROM_LAMBDA_TASK_RESULT]": 2.7275797179995607, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_decl_version_1_0": 0.6958222570001453, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_base": 2.6480675540001357, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_failure": 0.001965859999472741, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_execution_dateformat": 0.41480535900018367, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[0]]": 0.7389285600006588, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[10]]": 0.7164162270005363, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[*]]": 0.6628290879998531, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5].itemValue]": 0.7336257259999002, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5]]": 0.7090061819994844, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:]]": 0.7024853229995642, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[:1]]": 0.6933978339998248, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*].itemValue]": 0.7153138220000983, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*]]": 0.7159685199994783, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:].itemValue]": 0.6784299910004847, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:]]": 0.7141097290000289, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1].itemValue]": 0.6934069059998365, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1]]": 0.7097749800004749, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$[*]]": 0.71615936399985, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_query_context_object_values": 1.635277502000008, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail": 2.0985364250000202, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_empty": 0.6383006179994481, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_intrinsic": 0.7259708199999295, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_path": 0.705225165999309, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path": 0.0020772070001839893, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path_base": 0.6974344999994173, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result": 0.6891566399999647, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_jsonpaths": 0.6880147540000507, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_null_input_output_paths": 0.7843766139999389, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1.5]": 0.7031375800002024, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1]": 0.7188891609998791, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[0]": 0.6999193100000412, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1.5]": 0.6973953849997088, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1]": 1.5692182150005465, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24855]": 0.002017295000314334, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24856]": 0.001901509999697737, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000Z]": 0.7089863589999368, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000]": 0.6924165779996656, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.00Z]": 0.5085419379997802, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[Z]": 0.7197762620003232, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[]": 0.7080046660003063, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_executions_and_heartbeat_notifications": 0.0021513960000447696, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_heartbeat_notifications": 0.003356468999754725, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sns_publish_wait_for_task_token": 2.679377270000259, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_PARALLEL_WAIT_FOR_TASK_TOKEN]": 0.010154371000226092, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_WAIT_FOR_TASK_TOKEN_CATCH]": 1.9305591559991626, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_token": 2.5050398080006744, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_tok_with_heartbeat": 7.73147349699957, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token": 2.6198129390004397, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_call_chain": 4.455658238999604, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_no_token_parameter": 5.7943492770000375, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_timeout": 5.824181580999721, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync": 1.3633710930002962, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync2": 1.1816195270007483, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_failure": 1.1522702939996634, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_timeout": 7.597652157000539, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sync_with_task_token": 3.1107491289999416, - "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals": 14.514289044999714, - "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals_path": 16.099977430999843, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_boolean": 16.30216810899998, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_null": 15.001139835000004, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_numeric": 14.001467439999999, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_present": 13.966535318000012, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_string": 13.39825145499998, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_timestamp": 0.00360081199997353, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals": 21.387754504999975, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals_path": 21.37731186800002, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than": 2.5167962280000324, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals": 2.4989763789999984, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals_path": 2.4573440810000307, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_path": 2.568412529999989, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than": 2.2600690750000467, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals": 2.4290509850000603, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals_path": 3.074075902000004, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_path": 2.516358124999954, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals": 6.226543449000019, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals_path": 1.4046097709999685, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than": 1.7633436250000045, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals": 1.3591214109999896, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals_path": 1.4134702739999625, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_path": 1.7416662459999657, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than": 1.415889107000055, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals": 1.4140059269999483, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals_path": 1.4245928910000316, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_path": 1.392402632000028, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals": 7.271110790999899, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals_path": 1.4025970529999654, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than": 1.3869821399999864, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals": 1.4126027149999345, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals_path": 0.6629442820000122, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_path": 0.6542495320000512, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than": 1.4236030490000076, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals": 1.4605613190000213, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals_path": 0.6199327100000005, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_path": 0.6922918869999535, - "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comment_in_parameters": 0.44134421400002566, - "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comments_as_per_docs": 22.157156340000085, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_error_cause_path": 0.9382617709999863, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$.Execution.Input]": 0.9514327339999227, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$]": 0.7324065549999546, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$.Execution.Input]": 0.7468561869999348, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$]": 0.9226442729999462, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_result_selector": 2.399989683000001, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_variable": 0.9819469849999223, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_lambda_task": 2.4867980180000586, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke": 2.5020032009999795, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke_retry": 5.885236023999994, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_INTRINSIC]": 1.61057839099999, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_JSONATA]": 5.173212783000054, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH]": 1.6032758730000296, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH_CONTEXT]": 1.6520411930000023, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_VARIABLE]": 1.603894294999975, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[EMPTY_CREDENTIALS]": 0.8242704360000062, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[INVALID_CREDENTIALS_FIELD]": 0.8054714610000246, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_invalid_param": 0.0019231250000188993, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_put_item_no_such_table": 3.322759299999973, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_invalid_secret_name": 0.7822934949999762, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_no_such_bucket": 0.7145450499999697, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_s3_no_such_key": 0.7783031249999226, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.3597563090000335, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_data_limit_exceeded_on_large_utf8_response": 2.3607912890000193, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_start_large_input": 4.796172262000084, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.2836558480000235, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_data_limit_exceeded_on_large_utf8_response": 2.3897195730000362, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function": 2.4308436389999883, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function_catch": 2.4252569010000116, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_custom_exception": 2.292089906000001, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception": 2.460956431999989, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception_catch": 2.5426769500000432, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_invalid_param": 0.7670283300000165, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_invalid_table_name": 0.8333409889999643, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_no_such_table": 0.7583451810000383, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_invoke_timeout": 6.816047728000001, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function": 1.840961856999968, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function_catch": 1.8704461300000048, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_custom_exception": 2.4264822150000214, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception": 2.2047895949999656, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch": 2.4215069370000037, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.Payload]": 2.3673283059999903, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.no.such.path]": 2.3282058679999977, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[None]": 3.1659377110000264, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sfn.py::TestTaskServiceSfn::test_start_execution_no_such_arn": 1.037894940000001, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_empty_body": 0.0018017769999687516, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue": 1.377715324999997, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue_no_catch": 1.0765385149999815, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_sqs_failure_in_wait_for_task_tok": 2.733356070999946, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS]": 1.3465718539999898, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS_DOUBLE_QUOTES]": 1.107220018000021, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[MAX_CONCURRENCY]": 1.1005247840000152, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_COUNT]": 1.1027006970000457, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_PERCENTAGE]": 1.0990770319999683, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[ITEMS]": 2.2318607270000257, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[MAX_CONCURRENCY]": 2.2284459820000393, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_COUNT]": 2.1952385499999423, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_PERCENTAGE]": 2.2095875189999674, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[HEARTBEAT_SECONDS]": 2.4980992810000657, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[TIMEOUT_SECONDS]": 0.0019382850000511098, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[HEARTBEAT_SECONDS]": 3.283979819000024, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[TIMEOUT_SECONDS]": 0.0017817220000324596, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_PASS_RESULT]": 1.3144713360000537, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_RAISE_FAILURE]": 1.2751362060000133, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_catch": 2.98343294, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_query_runtime_memory": 2.3308225460000926, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_retry": 10.162601367999969, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_PASS_RESULT]": 0.6018137350000075, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_RAISE_FAILURE]": 0.5176827190001063, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_catch": 2.235349595999992, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_query_runtime_memory": 1.392398294999964, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_retry": 9.518081786999971, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_0": 0.47417495700000245, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_2": 3.6662597139999207, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_contains": 3.207171326999969, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_get_item": 0.6790916459999607, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_length": 0.6848478709998744, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_partition": 8.240546040000027, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_range": 1.6391085660000044, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_unique": 0.675856643999964, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_partition": 6.077333998000086, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_range": 1.999990698999909, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_decode": 0.9667441670000017, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_encode": 0.9847756110000319, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_context_json_path": 0.7224820760000057, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_escape_sequence": 0.4457683000000543, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_1": 2.5116429599999037, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_2": 2.9935324090000677, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_1": 0.6803017839999939, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_2": 0.6942053579998628, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_hash_calculations.py::TestHashCalculations::test_hash": 1.9456632200000286, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge": 0.6850081620001447, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge_escaped_argument": 0.6971334689999367, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_to_string": 2.8634183789998815, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_string_to_json": 3.4719998020000276, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation_jsonata.py::TestJsonManipulationJSONata::test_parse": 2.087498473999858, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_add": 6.807506750000016, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random": 1.3580643889998782, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random_seeded": 0.7297351829998888, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations_jsonata.py::TestMathOperationsJSONata::test_math_random_seeded": 0.0021580640000138374, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split": 2.4791937839999036, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split_context_object": 0.6677804850000939, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_unique_id_generation.py::TestUniqueIdGeneration::test_uuid": 1.4820307870000988, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_False]": 0.998255337000046, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_True]": 1.0063819260000173, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_False]": 1.0014054359999136, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_True]": 1.0379777800001193, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_False]": 1.010286930999996, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_True]": 1.0057918129999734, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_deleted_log_group": 0.9970833500000253, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_log_group_with_multiple_runs": 1.6317852160000257, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_False]": 0.7197714320000159, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_True]": 0.7217546900000116, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_False]": 0.7253661020000663, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_True]": 0.7198694840000144, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_False]": 0.7109153319998995, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_True]": 0.7155022570000256, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_False]": 1.0010752700000012, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_True]": 1.0119501069999615, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_False]": 0.7954570580000109, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_True]": 0.8113759809999692, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_False]": 0.7552789170000551, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_True]": 0.7013259110000263, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_False]": 1.0107652009999128, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_True]": 1.0084796310001138, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_False]": 1.0135865869999634, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_True]": 0.9770494079999708, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_False]": 1.7912125009999045, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_True]": 0.9438085760000376, - "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_happy_path": 0.42989456499992684, - "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_hybrid_path": 0.3669824530001051, - "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_retry_path": 7.22663837999994, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": 1.7220257889999857, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": 1.7232101889999285, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": 1.5624631519999639, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": 1.7049762190000592, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": 0.9977716460000465, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": 0.9562400290001278, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": 0.9150270070000488, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_retries": 3.340779224999892, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": 0.949390562000076, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": 0.8293090280001252, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": 2.359212247999949, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": 1.2038061430000653, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": 0.9425786960000551, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": 0.9691656630000125, - "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_set": 0.004709639999987303, - "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_unset": 0.006456018999983826, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_DIRECT_EXPR]": 0.9549841680001236, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EMPTY]": 0.889614396999832, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EXPR]": 1.0169817800000374, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_LITERALS]": 1.0376705190000166, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_lambda[BASE_LAMBDA]": 2.6001813139999967, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[BOOL]": 0.8827991209999482, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[FLOAT]": 0.688300326999979, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[INT]": 0.6892659250000861, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[JSONATA_EXPR]": 0.895088018000024, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_EMPY]": 0.7206845710001062, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_RICH]": 0.9035954920000222, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[NULL]": 0.6960282400000324, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[STR_LIT]": 0.6778374269999858, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_task_lambda[BASE_TASK_LAMBDA]": 2.3199001380000936, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_FALSE]": 0.6950512359999266, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_TRUE]": 0.9404303080000318, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSONATA]": 0.45010343000012654, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSON_PATH]": 0.44744589000004, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_jsonata_query_language_field_downgrade_exception": 0.001987330000019938, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE]": 0.4402096359999632, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE_DEFAULT]": 0.6387934280000991, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONATA_TO_JSONPATH]": 2.222615226999892, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONPATH_TO_JSONATA]": 2.222495937999952, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONATA_TO_JSONPATH]": 2.220916958999851, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONPATH_TO_JSONATA]": 2.2386456769999086, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONATA_OUTPUT_TO_JSONPATH]": 0.8416305189999775, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONPATH_OUTPUT_TO_JSONATA]": 1.7210791940000263, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_task_dataflow_to_state": 2.300100521000104, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONATA_ASSIGN_JSONPATH_REF]": 0.8524179450000702, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONPATH_ASSIGN_JSONATA_REF]": 0.838684760000092, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_empty": 2.0757423480000625, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_states_runtime": 2.3876029600000948, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO]": 0.779847683999833, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO_JSONATA]": 0.7469914060002338, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_condition_constant_jsonata": 0.49586971799999446, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE]": 0.7197179480001523, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_JSONATA]": 0.7026216879999083, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_LITERAL_JSONATA]": 0.7172579140000153, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.715119679000054, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.6774791009999035, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.9027840589999414, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.7634367919999931, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_ASSIGN]": 0.7148710230000006, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_OUTPUT]": 0.7190550219999068, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONPATH]": 0.7124750469999981, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_STRING_LITERALS]": 0.7668642950000049, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_cause_jsonata": 0.6619611830000167, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_error_jsonata": 0.6612164130000338, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION]": 0.0017835149999427813, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION_2]": 0.0017719539999916378, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_ERRORPATH]": 0.7003735649999498, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_CONTEXTPATH]": 0.6571601889999101, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_JSONPATH]": 0.6971713200000522, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_CAUSEPATH]": 0.6899200109999128, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_HEARTBEATSECONDSPATH]": 0.0015948340000022654, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_INPUTPATH]": 0.6830984910000097, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_OUTPUTPATH]": 0.6692485119999674, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_TIMEOUTSECONDSPATH]": 0.0017567569999528132, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_empty_retry": 2.13356346199987, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_base": 9.549384801999963, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_extended_input": 9.639404133000085, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_with_retry_extended_input": 9.948011956000073, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_batching_base_json_max_per_batch_jsonata": 0.001854380999930072, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_decl": 0.8279420279999385, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_first_line": 0.8216388949999782, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json": 0.8230287430000089, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items": 0.7898285349999696, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items_jsonata": 1.757611799000074, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[INVALID_ITEMS_PATH]": 1.1107659000001604, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_ITEM_READER]": 1.1251319250000051, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_PREVIOUS]": 1.1458473320000166, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_list_objects_v2": 0.7980860540000094, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_first_row_extra_fields": 0.8099386909999566, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_duplicate_headers": 0.7922998169999573, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_extra_fields": 0.8055924210000285, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_first_row_typed_headers": 0.7770112780000318, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[0]": 0.7849523349999572, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[100000000]": 0.7992133989999957, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[2]": 0.8102652139999691, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[-1]": 0.7922323219999043, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[0]": 0.7997625250000056, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[1.5]": 0.021506852000015897, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000000]": 0.8075668440000072, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000001]": 1.0245488549999209, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[2]": 0.8080124699999942, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_json_no_json_list_object": 0.803940085000022, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state": 0.819581085999971, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition": 0.8460003739999138, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition_legacy": 0.8408662329999288, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch": 0.7535390860000462, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_empty_fail": 0.7672907280000345, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_legacy": 0.7760022920000438, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector": 0.7913092329999927, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector_parameters": 1.0733225429999038, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_items_path_from_previous": 0.8346928060001346, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_parameters": 0.7989063950000173, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant": 1.6442169060001106, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant_lambda": 2.9174723020000783, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_item_selector": 0.7845633699998871, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_parameters": 0.852207351000061, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR]": 1.9092864090000603, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR_JSONATA]": 0.753142304999983, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_parameters": 1.0286497059997828, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_singleton": 1.3126578070000505, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[empty]": 0.6987842519998821, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[mixed]": 1.6113327610000852, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[singleton]": 0.6821663010001657, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[boolean]": 0.7487476300000253, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[function]": 0.0019548070000610096, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[null]": 0.7645098100000496, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[number]": 0.7629414209999368, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[object]": 0.7535066549999101, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[string]": 0.7449569469999915, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[boolean]": 0.771803008999882, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[null]": 0.7750460279999061, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[number]": 0.788661425999976, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[object]": 0.7726528880000387, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[string]": 1.5009154410000747, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[empty]": 0.686478971000156, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[mixed]": 0.7287054009999565, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[singleton]": 0.7008249870001464, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[boolean]": 0.9667607129999851, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[null]": 0.925189299000067, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[number]": 0.9430383090000305, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[object]": 0.9621818390000954, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[string]": 0.930252745000189, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[boolean]": 0.7733566610000935, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[null]": 0.769822764999958, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[number]": 0.776680987000077, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[object]": 0.7789946899999904, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[string]": 0.7788582509999742, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_label": 0.6886462960000017, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy": 0.805254517000094, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed": 0.7968573199998445, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_item_selector": 0.790882341999918, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_parameters": 0.8340644240000756, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline": 1.652162575000034, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_item_selector": 0.8323863050001137, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_parameters": 0.8565904749999618, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_reentrant": 1.6678457490000937, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested": 0.8785099399999581, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed": 0.8581223020001971, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed_no_max_max_concurrency": 10.860483625000029, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_no_processor_config": 0.7567306510001117, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_legacy": 1.980591358999959, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_singleton_legacy": 1.308044371999813, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_result_writer": 1.012002290000055, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry": 4.52254801100014, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_legacy": 3.717991963999907, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_multiple_retriers": 7.698763803999896, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[-1]": 0.7049893669999392, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[0]": 0.7109394060000795, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[1]": 0.709907009999938, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[NoNumber]": 0.7165135210001381, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[tolerated_failure_count_value0]": 0.6835508369998706, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1.1]": 0.7095147379998252, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1]": 0.6826312800001233, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[0]": 0.7294024690000924, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1.1]": 0.691483135999988, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100.1]": 0.7112506999999368, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100]": 0.7034129740000026, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1]": 0.7425394960000631, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[NoNumber]": 0.7140869689999363, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[tolerated_failure_percentage_value0]": 0.7125602840001193, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[count_literal]": 0.700274484999909, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[percentage_literal]": 0.7035602409998774, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[0]": 0.6857358580000437, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[1]": 0.6892366759999504, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[NoNumber]": 0.6632936199998767, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[max_concurrency_value0]": 0.7168398289999232, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path_negative": 0.7763080400000035, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE]": 0.8052484840000034, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE_PARAMETERS]": 0.7583163770000283, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_catch": 0.7078042470000128, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_fail": 0.5324258179999788, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_nested": 0.9977190710000059, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_order": 0.8165190849999817, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_retry": 3.621605466000119, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features": 4.240869398999962, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_jitter_none": 4.441330146000041, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_max_attempts_zero": 2.335554794000018, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_seconds_jsonata": 0.4613009570000486, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[NANOSECONDS]": 0.4512630569998919, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[SECONDS]": 1.35777501400014, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_DATE]": 0.40580544500016913, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_ISO]": 0.41160655400017276, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_TIME]": 0.4061650510000163, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[JSONATA]": 0.41421622999985175, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_T]": 0.4227252950000775, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_Z]": 0.401756176000049, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_DATE]": 0.0017534609999074746, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_ISO]": 0.0017542309998361816, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_TIME]": 0.0017845879999640601, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NANOSECONDS]": 0.6537537839999459, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_T]": 0.001763689000085833, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_Z]": 0.001747098000009828, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[SECONDS]": 0.6605004030000146, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_DATE]": 0.6935427880000589, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_ISO]": 0.64727527499997, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_TIME]": 0.7373158099999273, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NANOSECONDS]": 0.6940160560000095, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_T]": 0.6849085180000429, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_Z]": 0.48893706999990627, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[SECONDS]": 0.6999829699998372, - "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_path_based_on_data": 6.397225202999948, - "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_step_functions_calling_api_gateway": 11.397811037999986, - "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_wait_for_callback": 19.590867753999873, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_base": 2.9955853489999527, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_error": 2.7195412360000546, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[HelloWorld]": 3.03086947099996, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[None]": 3.0400981070000626, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[]": 2.9885273759998654, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[request_body3]": 3.0524723719998974, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header1]": 4.085523456000033, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header2]": 3.048816011000099, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[singleStringHeader]": 0.0030492549998371032, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_query_parameters": 3.4116910290000533, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_delete_item": 0.9864020889999665, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_get_item": 1.1284898199999134, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_update_get_item": 1.3404560799999672, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_list_secrets": 0.9294702930000085, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[binary]": 1.0854392920000464, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[bytearray]": 1.0974341249999497, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_binary]": 1.1380771200000481, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_str]": 1.159153058999891, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[str]": 1.066789176000043, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[bool]": 1.14153302800014, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[dict]": 1.1551140980000127, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[list]": 2.3945919840000442, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[num]": 1.188813573999937, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[str]": 1.1513815199999726, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template0]": 0.9334489699999722, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template1]": 0.9372429140000804, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution": 0.9985067530000151, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution_implicit_json_serialisation": 1.0079264870001907, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_DELETE_ITEM]": 1.2789913940000588, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_GET_ITEM]": 1.2289150139999947, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_QUERY]": 1.2570946210000784, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_UPDATE_GET_ITEM]": 1.6176261929999782, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_invalid_integration": 0.577723138000124, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task": 0.0019267240000999664, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_raise_failure": 0.0017757210000581836, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync": 0.0018563520000043354, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync_raise_failure": 0.0018338500000254498, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_base": 2.028847136999957, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_malformed_detail": 0.9016878250000673, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_mixed_malformed_detail": 0.9900200229999427, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_no_source": 31.008931994999898, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_bytes_payload": 2.063031224999918, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0.0]": 2.1149553510000487, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_0]": 2.0556827930000736, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_1]": 2.0568516579998004, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[HelloWorld]": 2.0247143689999803, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[True]": 2.0435847239999703, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value5]": 2.0474390850000646, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value6]": 2.0937964239999474, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_pipe": 3.671816361000083, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_string_payload": 2.0251886030000605, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_lambda_task_filter_parameters_input": 2.12621185699993, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke": 3.5816846650000116, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_bytes_payload": 2.50494870600005, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0.0]": 2.5228463550000697, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_0]": 2.509149897000043, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_1]": 2.5284124640000982, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[HelloWorld]": 2.48911896900006, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[True]": 2.492393434000178, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value5]": 2.515985006000051, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value6]": 2.5625799570000254, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_unsupported_param": 2.5292429790000597, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_list_functions": 0.002096830000027694, - "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution": 1.0109025780000138, - "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution_input_json": 1.040150487999881, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params0-True]": 1.2359824780003237, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params1-False]": 0.9524391729999024, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[1]": 0.8906811589997687, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[HelloWorld]": 0.9345146069999828, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[None]": 0.9149203189999753, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[True]": 0.9078963760000534, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[]": 0.9619701499998428, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[message1]": 0.9312344529998882, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base_error_topic_arn": 0.9428704630001903, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[\"HelloWorld\"]": 2.234957452999879, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[HelloWorld]": 1.1247440929998902, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[message_value3]": 1.0742752799997106, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[{}]": 1.072550260999833, - "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message": 1.1171851199997036, - "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_attributes": 1.1468574259997695, - "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_unsupported_parameters": 1.0712639749999653, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING]": 2.284389454999882, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING_TO_JSONPATH]": 3.495701100999895, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT]": 0.0027904560001843493, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.001899331999993592, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_VARIABLE_SAMPLING]": 0.001907587000005151, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT]": 0.0017096770000080141, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.001751124000065829, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_VARIABLE_SAMPLING]": 0.0018400999999812484, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT]": 2.2794532020000133, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT_TO_JSONPATH]": 2.2775014440001087, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY]": 3.56064378200017, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY_TO_JSONPATH]": 3.52841641100008, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dump]": 1.4815840229998685, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dumps]": 1.4895683499998995, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dump]": 1.4801428460000352, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dumps]": 1.4839042299995526, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_invalid_sm": 0.5587203119998776, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_valid_sm": 1.5607782820000011, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_definition_format_sm": 0.4438962619999529, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_sm_name": 0.4404979649998495, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_exact_duplicate_sm": 0.5331606180002382, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition": 0.5041620080000939, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition_and_role": 0.6225898540001253, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_role_arn": 0.6128592519999074, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_update_none": 0.4590780179999001, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_same_parameters": 0.5608002579997446, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_delete_nonexistent_sm": 0.4229455600000165, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution": 0.7340801870000178, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_arn_containing_punctuation": 1.895308127999897, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_invalid_arn": 0.4211252790000799, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_no_such_state_machine": 0.7278816259999985, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_invalid_arn_sm": 0.4249557079999704, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_nonexistent_sm": 0.429683298999862, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_sm_arn_containing_punctuation": 0.43426738900006967, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_state_machine_for_execution": 0.7131116110001585, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_invalid_arn": 0.41550556699985464, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_no_such_execution": 0.4718798310002512, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_reversed": 0.5399221570000918, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_arn": 0.47758628700012196, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_input": 0.7723481109999284, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_invalid_arn": 0.41682884799979547, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_no_such_state_machine": 0.43787986999996065, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_pagination": 1.7233896389998336, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_versions_pagination": 2.845546381000304, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms": 0.5413942569998653, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms_pagination": 0.8840717989999121, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution": 0.5934464520000802, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution_idempotent": 1.1258412639999733, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_sync_execution": 0.4536565440000686, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_state_machine_status_filter": 0.8166807700001755, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_stop_execution": 0.5280075769999257, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[\\x00activity]": 0.3389150379998682, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity name]": 0.35876297999993767, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\"name]": 0.3370844840001155, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity#name]": 0.33648348799988526, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity$name]": 0.33924911100007193, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity%name]": 0.3443260410003859, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity&name]": 0.3410857369997302, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity*name]": 0.3452924919997713, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity,name]": 0.3380723699997361, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity/name]": 0.3416569350001737, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity:name]": 0.3437918379997882, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity;name]": 0.34107409399985045, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activityname]": 0.3359390230000372, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity?name]": 0.33733432199983326, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity[name]": 0.3390754890001517, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\\\name]": 0.3429689030001555, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x1f]": 0.3413359619999028, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x7f]": 0.3384880399996746, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity]name]": 0.3383667810001043, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity^name]": 0.34289657499994064, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity`name]": 0.33841489299993555, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity{name]": 0.33395739799971125, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity|name]": 0.3404211739998573, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity}name]": 0.3358119669999269, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity~name]": 0.3358754739997494, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[ACTIVITY_NAME_ABC]": 0.4114520599998741, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[Activity1]": 0.4063136790000499, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]": 0.4237922399997842, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name.1]": 0.41539400100009516, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name_123]": 0.4070558680000431, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name.v2]": 0.42165062499998385, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name]": 0.41060388599998987, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activityName.with.dots]": 0.41991113200015207, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity_123.name]": 0.4107109789997594, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_activity_invalid_arn": 0.42191968400015867, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_deleted_activity": 0.36266206400000556, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_deleted": 0.35691779799981305, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_invalid_arn": 0.43205298299994865, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_list_activities": 0.3894902080000975, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_create_alias_single_router_config": 0.6911751879999883, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_delete_list": 0.8351259619998928, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_invoke_describe_list": 0.8496771829998124, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_update_describe": 0.7559092070000588, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_no_such_alias_arn": 0.7458462149998013, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_revision_with_alias": 0.694284596999978, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_version_with_alias": 0.7267470739998316, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_name": 0.7000303140000597, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_router_configs": 0.7310111619999589, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_not_idempotent": 0.7152012529998046, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_with_state_machine_arn": 0.683119433000229, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_idempotent_create_alias": 1.9530064230000335, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_invalid_next_token": 0.707369227000072, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[0]": 0.7910726399995838, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[1]": 0.8064809599998171, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_update_no_such_alias_arn": 0.7063752999999906, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_create_describe_delete": 0.7647490140002446, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_activity_task": 0.914557552999895, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[SYNC]": 0.8911468290000357, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[WAIT_FOR_TASK_TOKEN]": 0.9154476649998742, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_async_describe_history_execution": 1.3852232379999805, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_sync_execution": 0.7955915319998894, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_deleted_log_group": 0.5098920640000415, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration0]": 0.4485659439997107, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration1]": 0.4430130040000222, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration0]": 0.398403684999721, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration1]": 0.399038998999913, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration2]": 0.39976912799988895, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-False]": 0.4701010420001239, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-True]": 0.4864735240000755, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-False]": 0.5354216949999682, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-True]": 0.47868891100006294, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-False]": 0.4806218470000658, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-True]": 0.4887028920004468, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-False]": 0.47605741400002444, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-True]": 0.475419378999959, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_multiple_destinations": 0.4521561159999692, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_update_logging_configuration": 1.7943260749998444, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_list_map_runs_and_describe_map_run": 0.8486460459998852, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_empty_fail": 0.34684389499989265, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[ ]": 0.3413571050000428, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\"]": 0.32088011800010463, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[#]": 0.3250530410002739, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[$]": 0.3239804429999822, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[%]": 0.32518490999996175, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[&]": 0.3222675139998046, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[*]": 0.3296964239996214, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[,]": 0.3299801690000095, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[:]": 0.33626083500007553, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[;]": 0.3235918559998936, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[<]": 0.32847937999986243, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[>]": 0.3265827579998586, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[?]": 0.3279778310002257, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[[]": 0.3410170460001609, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\\\]": 0.3271184880002238, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\n]": 0.3321672040001431, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\r]": 0.32894806400008747, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\t]": 0.3282310399999915, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x00]": 0.3258154899999681, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x01]": 0.32531574000017827, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x02]": 0.33082467900021584, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x03]": 0.3281101350000881, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x04]": 0.32742342700021254, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x05]": 0.3269184780001524, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x06]": 0.32453608800005895, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x07]": 0.32597672699989744, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x08]": 0.32968034300006366, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0b]": 0.3271136609998848, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0c]": 0.3240375730001688, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0e]": 0.32885056100008114, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0f]": 0.32835943200007023, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x10]": 0.33190535800008547, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x11]": 0.3371905930000594, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x12]": 0.3544236179998279, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x13]": 0.33132125699989956, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x14]": 0.3320923490000496, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x15]": 0.3246423040002355, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x16]": 0.3275199650001923, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x17]": 0.3394696319999184, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x18]": 0.3265617240001575, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x19]": 0.32626942999991115, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1a]": 0.32973674100003336, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1b]": 0.33027967300017735, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1c]": 0.33031535500003883, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1d]": 0.33244451900009153, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1e]": 0.33309187400004703, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1f]": 0.3303883969999788, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x7f]": 0.333045213999867, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x80]": 0.32697609199999533, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x81]": 0.33379805799995665, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x82]": 0.3498903610002344, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x83]": 0.3351786089997404, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x84]": 0.3263143230001333, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x85]": 0.3327412659998572, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x86]": 0.3334438929998669, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x87]": 1.5312017169999308, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x88]": 0.3210110069999246, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x89]": 0.32561847800002397, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8a]": 0.32332312199991975, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8b]": 0.3246128939999835, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8c]": 0.33803340899999057, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8d]": 0.37181417599958877, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8e]": 0.32271031599975686, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8f]": 0.3221501860002718, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x90]": 0.3285164240000995, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x91]": 0.33644636199983324, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x92]": 0.3234084169998823, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x93]": 0.3290148810001483, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x94]": 0.3297526320000088, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x95]": 0.3221491619999597, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x96]": 0.3257365539998318, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x97]": 0.32633882499999345, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x98]": 0.32342142800007423, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x99]": 0.322541339000054, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9a]": 0.32604189300013786, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9b]": 0.32799611600012213, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9c]": 0.32561064200035617, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9d]": 0.32712014499998077, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9e]": 0.3242601060001107, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9f]": 0.32478709300016817, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[]]": 0.3549811279997357, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[^]": 0.3344453119998434, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[`]": 0.32844328199985284, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[{]": 0.3286596970001483, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[|]": 0.33020460500029003, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[}]": 0.3274862549999398, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[~]": 0.32512895699983346, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_too_long_fail": 0.34042027100008454, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_create_state_machine": 0.35719300799996745, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[None]": 0.3433994580000217, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list1]": 0.3460811429997648, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list2]": 0.34608605299990813, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list3]": 0.34997660699991684, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list0]": 0.3686806250000245, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list1]": 0.3594021689998499, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list2]": 0.36508042099990234, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list3]": 0.3598952189997817, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list4]": 0.36457554299977346, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine_version": 0.37595672199995533, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys0]": 0.4050373049999507, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys1]": 0.36674198800028535, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys2]": 0.3694483499998569, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys3]": 0.37154390799992143, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_DICT]": 0.34352941099996315, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_STRING]": 0.3399476570000388, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[NOT_A_DEF]": 0.34436999500007914, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[ILLEGAL_WFTT]": 0.35396685399996386, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_BASE_NO_STARTAT]": 0.34037100799992004, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_BASE_PASS]": 0.33357084399972337, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_BASE_NO_STARTAT]": 0.340517797000075, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_BASE_PASS]": 0.3426629060002142, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 2.066036771999734, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_PARAMETERS]": 0.9011115660000542, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_RESULT]": 0.8421264399999018, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_EVALUATION_ORDER_PASS_STATE]": 0.9268872759996611, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_CHOICE]": 0.9766749249997702, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_FAIL]": 0.8671612899997854, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INPUTPATH]": 0.8809260700002142, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 2.4498498919999747, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 1.5962050880000334, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_OUTPUTPATH]": 0.9305147039999611, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_PARAMETERS]": 0.9043679999997494, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_WAIT]": 0.9090189990001818, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.2055034330001035, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.2588669660001415, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 1.1418684259999736, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 0.8962754719998429, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 0.9674165119997724, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 0.9213006160000532, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_CONDITION_CONSTANT_JSONATA]": 0.5330749569998261, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.5529085300001952, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_express_with_publish": 0.40781699199988, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_no_version_description": 0.45628577799993764, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_with_version_description": 0.4541499859999476, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_publish": 0.4267770520002614, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_version_description_no_publish": 0.403634534000048, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version": 0.5088450189998639, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version_with_revision": 0.5200427799998124, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_no_publish_on_creation": 0.4476839070000551, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_publish_on_creation": 0.4601777590000893, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_idempotent_publish": 0.45513325900014934, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_delete_version": 0.48298622600032104, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_state_machine_versions_pagination": 0.8877154889999019, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version": 0.5511433350000061, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_invalid_arn": 0.4149735830001191, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_no_such_machine": 0.4331624040000861, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_start_version_execution": 0.8321363099998962, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_update_state_machine": 0.4953909719999956, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_version_ids_between_deletions": 0.4729205829996772, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_CHOICE_STATE]": 0.9758676430003561, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_FAIL_STATE]": 0.8161885699998948, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_PASS_STATE]": 0.8274042749999353, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_RESULT_PASS_STATE]": 0.8595386260001305, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_SUCCEED_STATE]": 0.8081798659998185, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_PASS_STATE]": 0.9085055300001841, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_RESULT_PASS_STATE]": 0.920266830999708, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_CHOICE_STATE]": 0.6679897489998439, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_FAIL_STATE]": 0.4884387440001774, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_PASS_STATE]": 0.5210119239998221, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_RESULT_PASS_STATE]": 0.5144390389998534, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_SUCCEED_STATE]": 1.6637656010000228, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_PASS_STATE]": 0.6044787109999561, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_RESULT_PASS_STATE]": 0.5974511680001342, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_CHOICE_STATE]": 1.0205980109999473, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_FAIL_STATE]": 0.8107255429999896, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_PASS_STATE]": 0.8068977529997028, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_RESULT_PASS_STATE]": 0.8136454610000783, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_SUCCEED_STATE]": 0.8235590780000166, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_PASS_STATE]": 0.9077844600001299, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_RESULT_PASS_STATE]": 0.9122443769997517, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[DEBUG]": 2.40678171400009, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[INFO]": 2.39312528299979, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[TRACE]": 2.401771804999953, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[DEBUG]": 2.379632098999764, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[INFO]": 2.378426283999943, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[TRACE]": 2.3799031010000817, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_choice_state_machine": 3.988727494000159, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_map_state_machine": 1.160958612000286, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_state_machine": 1.5510584529995413, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_state_machines_in_parallel": 1.8738574419999168, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_events_state_machine": 0.0018517629998768825, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_intrinsic_functions": 1.235324023000203, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_try_catch_state_machine": 10.150347214000021, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_aws_sdk_task": 1.2106949270000769, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_default_logging_configuration": 0.06757723100008661, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-central-1]": 0.0017627960000936582, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-west-1]": 0.0016403459997036407, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-1]": 0.0019984649998150417, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-2]": 0.0016599130001395679, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_run_aws_sdk_secrets_manager": 3.3417508019999786, - "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_no_timeout": 5.994449399000132, - "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_path_timeout": 6.198517898999853, - "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_timeout": 6.109951656000021, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_lambda": 6.894155072000103, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda": 6.852017531000001, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda_with_path": 7.0650439430000915, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_global_timeout": 5.5959590180000305, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_service_lambda_map_timeout": 0.0039377110001623805, - "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_assume_role_tag_validation": 0.16559362299994973, - "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_iam_role_chaining_override_transitive_tags": 0.22754587200006426, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_non_existent_role": 0.0186742299999878, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role": 0.24685753800031307, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_saml": 0.05329433599990807, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_web_identity": 0.04907957099999294, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_expiration_date_format": 0.0207340029999159, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[False]": 0.09128128200018182, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[True]": 0.09230722000006608, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_root": 0.016829451999910816, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[False]": 0.06986664199985171, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[True]": 0.21712195300005988, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_federation_token": 0.13912097199977325, - "tests/aws/services/support/test_support.py::TestConfigService::test_support_case_lifecycle": 0.06849215300007927, - "tests/aws/services/swf/test_swf.py::TestSwf::test_run_workflow": 0.18720082400022875, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_deletion": 0.1371174730002167, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_start_transcription_job": 0.4754534879998573, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_get_transcription_job": 0.4818586520000281, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_list_transcription_jobs": 4.422854875999974, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_invalid_length": 33.24437079199993, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_speaker_labels": 0.0017114890001721506, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_happy_path": 3.1467155769998953, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_speaker_diarization": 0.0018758070000330918, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[None-None]": 2.382481632000008, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-2-None]": 5.0111458260000745, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-3-test-output]": 4.981071361000204, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-4-test-output.json]": 3.0274327150000317, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-5-test-files/test-output.json]": 2.969176392999998, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-6-test-files/test-output]": 4.951191982999944, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job_same_name": 2.285088588000008, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.amr-hello my name is]": 2.1622178519999125, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.flac-hello my name is]": 2.161767103999864, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp3-hello my name is]": 4.665412912999955, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp4-hello my name is]": 2.166329664000159, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.ogg-hello my name is]": 2.6754738390000057, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.webm-hello my name is]": 2.161084437999989, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mkv-one of the most vital]": 2.1618564929999593, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mp4-one of the most vital]": 2.1905418379999446, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_unsupported_media_format_failure": 3.1827230619999227, - "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_error_injection": 25.733884051000132, - "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_read_error_injection": 25.721735740999748, - "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_write_error_injection": 51.349487702999795, - "tests/aws/test_error_injection.py::TestErrorInjection::test_kinesis_error_injection": 2.0667036649999773, - "tests/aws/test_integration.py::TestIntegration::test_firehose_extended_s3": 0.19402966199982075, - "tests/aws/test_integration.py::TestIntegration::test_firehose_kinesis_to_s3": 39.55232836599998, - "tests/aws/test_integration.py::TestIntegration::test_firehose_s3": 0.3504830370000036, - "tests/aws/test_integration.py::TestIntegration::test_lambda_streams_batch_and_transactions": 41.55390902299996, - "tests/aws/test_integration.py::TestIntegration::test_scheduled_lambda": 6.204941354000084, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.10]": 1.8779431529999329, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.11]": 1.861379849000059, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.12]": 1.8903420469998764, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.13]": 1.8803573299999243, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.8]": 1.9424699109999892, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.9]": 1.8734911540000212, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.10]": 7.829105158000175, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.11]": 7.77104674799989, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.12]": 1.7672955929999716, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.13]": 15.851045397000235, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.8]": 15.813660433999985, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.9]": 1.809717424999917, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.10]": 3.9275238970001283, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.11]": 3.931963920999806, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.12]": 3.9199773269999696, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.13]": 3.907474767999929, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.8]": 3.9402414050000516, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.9]": 3.9124237250000533, - "tests/aws/test_integration.py::test_kinesis_lambda_forward_chain": 0.003315785000040705, - "tests/aws/test_moto.py::test_call_include_response_metadata": 0.007413605999772699, - "tests/aws/test_moto.py::test_call_multi_region_backends": 0.017886380999925677, - "tests/aws/test_moto.py::test_call_non_implemented_operation": 0.04351320999990094, - "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[IO[bytes]]": 0.025206423000099676, - "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[bytes]": 0.021419778999870687, - "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[str]": 0.05104993500026467, - "tests/aws/test_moto.py::test_call_sqs_invalid_call_raises_http_exception": 0.008053254000060406, - "tests/aws/test_moto.py::test_call_with_es_creates_state_correctly": 0.06393011299996942, - "tests/aws/test_moto.py::test_call_with_modified_request": 0.01102967799988619, - "tests/aws/test_moto.py::test_call_with_sns_with_full_uri": 0.005301119000023391, - "tests/aws/test_moto.py::test_call_with_sqs_creates_state_correctly": 3.5696953580002173, - "tests/aws/test_moto.py::test_call_with_sqs_invalid_call_raises_exception": 0.007393988000103491, - "tests/aws/test_moto.py::test_call_with_sqs_modifies_state_in_moto_backend": 0.009535218999872086, - "tests/aws/test_moto.py::test_call_with_sqs_returns_service_response": 0.006871298999840292, - "tests/aws/test_moto.py::test_moto_fallback_dispatcher": 0.011215466999829005, - "tests/aws/test_moto.py::test_moto_fallback_dispatcher_error_handling": 0.03453630500007421, - "tests/aws/test_moto.py::test_request_with_response_header_location_fields": 0.10320277400001032, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_localstack_backends": 0.1588861079999333, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_moto_backends": 1.686497204000034, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_dynamodb": 0.27847857299980205, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_kinesis": 1.4830951399999321, - "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_api_gateway": 0.4300387180001053, - "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_sns": 0.07013101300003655, - "tests/aws/test_network_configuration.py::TestLambda::test_function_url": 1.159327340000118, - "tests/aws/test_network_configuration.py::TestLambda::test_http_api_for_function_url": 0.0019428480002261495, - "tests/aws/test_network_configuration.py::TestOpenSearch::test_default_strategy": 10.25090428700014, - "tests/aws/test_network_configuration.py::TestOpenSearch::test_path_strategy": 10.68757030200004, - "tests/aws/test_network_configuration.py::TestOpenSearch::test_port_strategy": 10.467708148999918, - "tests/aws/test_network_configuration.py::TestS3::test_201_response": 0.09010279499989338, - "tests/aws/test_network_configuration.py::TestS3::test_multipart_upload": 0.12009285099998124, - "tests/aws/test_network_configuration.py::TestS3::test_non_us_east_1_location": 0.07768254500001603, - "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[domain]": 0.021290008000050875, - "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[standard]": 0.024970439999833616, - "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_with_external_port": 0.020647534000090673, - "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_without_external_port": 0.02268692699976782, - "tests/aws/test_network_configuration.py::TestSQS::test_path_strategy": 0.02072189399996205, - "tests/aws/test_notifications.py::TestNotifications::test_sns_to_sqs": 0.15411809499983065, - "tests/aws/test_notifications.py::TestNotifications::test_sqs_queue_names": 0.021744305999845892, - "tests/aws/test_serverless.py::TestServerless::test_apigateway_deployed": 0.03336402199988697, - "tests/aws/test_serverless.py::TestServerless::test_dynamodb_stream_handler_deployed": 0.0394248370000696, - "tests/aws/test_serverless.py::TestServerless::test_event_rules_deployed": 99.53009074600004, - "tests/aws/test_serverless.py::TestServerless::test_kinesis_stream_handler_deployed": 0.0018530179997924279, - "tests/aws/test_serverless.py::TestServerless::test_lambda_with_configs_deployed": 0.01954193899996426, - "tests/aws/test_serverless.py::TestServerless::test_queue_handler_deployed": 0.0352729079997971, - "tests/aws/test_serverless.py::TestServerless::test_s3_bucket_deployed": 22.648420071000146, - "tests/aws/test_terraform.py::TestTerraform::test_acm": 0.0018343319998166407, - "tests/aws/test_terraform.py::TestTerraform::test_apigateway": 0.0018153869998513983, - "tests/aws/test_terraform.py::TestTerraform::test_apigateway_escaped_policy": 0.0017592210001566855, - "tests/aws/test_terraform.py::TestTerraform::test_bucket_exists": 0.004861798000092676, - "tests/aws/test_terraform.py::TestTerraform::test_dynamodb": 0.0017788090001431556, - "tests/aws/test_terraform.py::TestTerraform::test_event_source_mapping": 0.0018187639998359373, - "tests/aws/test_terraform.py::TestTerraform::test_lambda": 0.001744885000107388, - "tests/aws/test_terraform.py::TestTerraform::test_route53": 0.0017445150001549337, - "tests/aws/test_terraform.py::TestTerraform::test_security_groups": 0.0017493430000286025, - "tests/aws/test_terraform.py::TestTerraform::test_sqs": 0.0018370569998751307, - "tests/aws/test_validate.py::TestMissingParameter::test_elasticache": 0.0017946890002349392, - "tests/aws/test_validate.py::TestMissingParameter::test_opensearch": 0.0017385730000114563, - "tests/aws/test_validate.py::TestMissingParameter::test_sns": 0.0017343559998153069, - "tests/aws/test_validate.py::TestMissingParameter::test_sqs_create_queue": 0.0018129719999251392, - "tests/aws/test_validate.py::TestMissingParameter::test_sqs_send_message": 0.0018621550000261777, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_container_starts_non_root": 0.0017813040001328773, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_custom_docker_flags": 0.0017338650000056077, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_logs": 0.0017396559999269812, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_pulling_image_message": 0.0017686600001525221, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_restart": 0.001760785999977088, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_already_running": 0.0018234939998365007, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_cli_within_container": 0.0018741469998531102, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_wait_stop": 0.0018491500002255634, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_status_services": 0.00177234599982512, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_volume_dir_mounted_correctly": 0.0017771249997622363, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_wait_timeout_raises_exception": 0.001886471000034362, - "tests/cli/test_cli.py::TestDNSServer::test_dns_port_not_published_by_default": 0.0018739780000487372, - "tests/cli/test_cli.py::TestDNSServer::test_dns_port_published_with_flag": 0.0018557219998456276, - "tests/cli/test_cli.py::TestHooks::test_prepare_host_hook_called_with_correct_dirs": 0.5663967779998984, - "tests/cli/test_cli.py::TestImports::test_import_venv": 0.006796589999794378, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_404_unfortunately_detected_as_s3_request": 0.0371446389999619, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_internal_failure_handler_http_errors": 0.02024471599975186, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_http_errors": 0.0018143350000627834, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_unexpected_errors": 0.001855641000020114, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_patch_http_errors": 0.11661765700000615, - "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_http": 0.11512412299975949, - "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https": 0.10054100100001051, - "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https_localhost": 0.06373853900004178, - "tests/integration/aws/test_app.py::TestHttps::test_default_cert_works": 0.06979465700010223, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_return_response": 0.0029861989999062644, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_ssl_websockets": 0.0032003690000692586, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websocket_reject_through_edge_router": 0.003164523000123154, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websockets_served_through_edge_router": 0.00314615699994647, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_request_streaming": 0.11618684200016105, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_response_streaming": 0.12722986900007527, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_raw_header_handling": 0.10808157400037999, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_response_close_handlers_called_with_router": 0.11196197499998561, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-False]": 0.0018279910000273958, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-True]": 0.0017667959998561855, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-False]": 0.0017781480000849115, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-True]": 0.0018504639999719075, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-False]": 2.993281944000273, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-True]": 3.0005114819998653, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-False]": 2.9977195240001038, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-True]": 2.8067074260000027, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[CmdDockerClient]": 0.0018186329998570727, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[SdkDockerClient]": 21.109316934000162, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[CmdDockerClient]": 0.0017999489998601348, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[SdkDockerClient]": 0.2808834279999246, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[CmdDockerClient]": 0.002006374999837135, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[SdkDockerClient]": 0.20336770599988085, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[CmdDockerClient]": 0.0038232349997997517, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[SdkDockerClient]": 0.24341681299983975, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[CmdDockerClient]": 0.0019576330000745656, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[SdkDockerClient]": 0.2369550779999372, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[CmdDockerClient]": 0.0019394000000829692, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[SdkDockerClient]": 0.23823619599988888, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[CmdDockerClient]": 0.0019820589998289506, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[SdkDockerClient]": 0.24137264699993466, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[CmdDockerClient]": 0.001921847000176058, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[SdkDockerClient]": 0.008290619999570481, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[CmdDockerClient]": 0.004033467999761342, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[SdkDockerClient]": 0.1899938500000644, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[CmdDockerClient]": 0.0019309049998810224, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[SdkDockerClient]": 0.3339840560001903, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[CmdDockerClient]": 0.0018105870001363655, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[SdkDockerClient]": 0.1952319469999111, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[CmdDockerClient]": 0.0018592400001580245, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[SdkDockerClient]": 0.007935213999871849, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[CmdDockerClient]": 0.001781242999868482, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[SdkDockerClient]": 0.2979444059999423, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[CmdDockerClient]": 0.0018179020000843593, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[SdkDockerClient]": 1.1872944670001289, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[CmdDockerClient]": 0.001816017999772157, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[SdkDockerClient]": 0.026060707000169714, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[CmdDockerClient]": 0.0018290529999376304, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[SdkDockerClient]": 0.21483664199990926, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[CmdDockerClient]": 0.0019134599999688362, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[SdkDockerClient]": 0.193792146000078, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-False]": 0.0018575269998564181, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-True]": 0.0018645200000264595, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-False]": 0.18440558999964196, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-True]": 0.19530575099997805, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-False]": 0.0019827009998607537, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-True]": 0.0018338320001021202, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-False]": 0.19494387199983976, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-True]": 0.2020680020002601, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[CmdDockerClient]": 0.0018320290002975526, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[SdkDockerClient]": 0.004421719999982088, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[CmdDockerClient]": 0.0018368769999597134, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[SdkDockerClient]": 0.02752769599965177, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[CmdDockerClient]": 0.0017802099998789345, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[SdkDockerClient]": 0.022833673000150156, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[CmdDockerClient]": 0.001705982999965272, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[SdkDockerClient]": 0.0017637290000038774, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[CmdDockerClient]": 0.0018106179998085281, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[SdkDockerClient]": 0.9137878090000413, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[CmdDockerClient]": 0.006371895000029326, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[SdkDockerClient]": 0.005901615000311722, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[CmdDockerClient]": 0.0017871430000013788, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[SdkDockerClient]": 0.2440898830000151, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[CmdDockerClient]": 0.0017250789999252447, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[SdkDockerClient]": 0.2333486850002373, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[CmdDockerClient]": 0.0018875219998335524, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[SdkDockerClient]": 0.03366921000019829, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[CmdDockerClient]": 0.0017591609998817148, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[SdkDockerClient]": 0.23776943600000777, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[CmdDockerClient]": 0.0018234020001273166, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[SdkDockerClient]": 0.26536420999991606, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[CmdDockerClient]": 0.0018476180000561726, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[SdkDockerClient]": 0.24206064300005892, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[CmdDockerClient]": 0.0019263750000391155, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[SdkDockerClient]": 0.23012958599997546, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[CmdDockerClient]": 0.0018907979999767122, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[SdkDockerClient]": 0.23264229999995223, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[CmdDockerClient]": 0.0018340210001497326, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[SdkDockerClient]": 0.005919421999806218, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[CmdDockerClient]": 0.0018231610001748777, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[SdkDockerClient]": 0.28212738200022613, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[CmdDockerClient]": 0.003832000000102198, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[SdkDockerClient]": 0.7521369390001382, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[CmdDockerClient]": 0.0018195349998677557, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[SdkDockerClient]": 0.007529335000072024, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[CmdDockerClient]": 0.0018649600001481303, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[SdkDockerClient]": 0.280608644999802, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[CmdDockerClient]": 0.0018378389997906197, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[SdkDockerClient]": 0.7297077759999411, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[CmdDockerClient]": 0.0019359729999450792, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[SdkDockerClient]": 0.18600953500003925, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[CmdDockerClient]": 0.0017877249999855849, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[SdkDockerClient]": 0.007020174999979645, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[CmdDockerClient]": 0.0019323960000292573, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[SdkDockerClient]": 0.19265641600009076, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[CmdDockerClient]": 0.0018036359999769047, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[SdkDockerClient]": 0.03866516400012188, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[CmdDockerClient]": 0.0018083239999668876, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[SdkDockerClient]": 0.427574906000018, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[CmdDockerClient]": 0.001843040000039764, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[SdkDockerClient]": 0.1874245050000809, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[CmdDockerClient]": 0.001788898000086192, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[SdkDockerClient]": 0.3614849369996591, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[CmdDockerClient]": 0.0020182479997856717, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[SdkDockerClient]": 0.005898363999676803, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[CmdDockerClient]": 0.0019386280000617262, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[SdkDockerClient]": 0.19567936599992208, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[CmdDockerClient]": 0.0019162370001595264, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[SdkDockerClient]": 0.007451228999798332, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[CmdDockerClient]": 0.0018103279999195365, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[SdkDockerClient]": 0.17593369199994413, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[CmdDockerClient]": 0.0017914319998908468, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[SdkDockerClient]": 0.007077435999917725, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[CmdDockerClient]": 0.0017911910001657816, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[SdkDockerClient]": 0.026405472000305963, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[CmdDockerClient]": 0.0017855300000064744, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[SdkDockerClient]": 0.4062381970002207, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[CmdDockerClient]": 0.0017760840000846656, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[SdkDockerClient]": 0.006417986000087694, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[CmdDockerClient]": 0.0017904310000176338, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[SdkDockerClient]": 0.020978276000050755, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[CmdDockerClient]": 0.003590529000348397, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[SdkDockerClient]": 0.024861886000053346, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[CmdDockerClient]": 0.0018019020001247554, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[SdkDockerClient]": 0.18822473599993828, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[CmdDockerClient]": 0.001833671999975195, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[SdkDockerClient]": 0.00172626899984607, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[CmdDockerClient]": 0.0017924940002558287, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[SdkDockerClient]": 0.19090764500015212, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[CmdDockerClient]": 0.0018557020000571356, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[SdkDockerClient]": 0.028438552000125128, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[CmdDockerClient]": 0.0018745380000382283, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[SdkDockerClient]": 0.15242643400006273, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[CmdDockerClient]": 0.0018388420000974293, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[SdkDockerClient]": 0.007792616999950042, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[CmdDockerClient]": 0.0018087949999880948, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[SdkDockerClient]": 22.395865744000048, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[CmdDockerClient]": 0.0017964720000236412, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[SdkDockerClient]": 0.07842941399985648, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[CmdDockerClient]": 0.0018239639998682833, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[SdkDockerClient]": 0.07836071599990646, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[CmdDockerClient]": 0.0018112799998561968, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[SdkDockerClient]": 0.006042651999905502, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[CmdDockerClient]": 0.0018221700001959107, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[SdkDockerClient]": 0.006365897000023324, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[CmdDockerClient]": 0.0018216590001429722, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[SdkDockerClient]": 0.22259903300005135, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[CmdDockerClient]": 0.0018007190001299023, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[SdkDockerClient]": 0.005689947999599099, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[CmdDockerClient]": 0.001766414999792687, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[SdkDockerClient]": 0.7465849210000215, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[CmdDockerClient]": 0.0018374680003034882, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[SdkDockerClient]": 0.5688395139998192, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[CmdDockerClient]": 0.0018443320000187668, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[SdkDockerClient]": 0.7086373600002389, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[CmdDockerClient]": 0.0018484390000139683, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[SdkDockerClient]": 0.7328827269998328, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[CmdDockerClient]": 0.0018078129999139492, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[SdkDockerClient]": 0.27862614700006816, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[CmdDockerClient]": 0.001804085999765448, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[SdkDockerClient]": 1.05901116799987, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[CmdDockerClient]": 0.0018175109998992411, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[SdkDockerClient]": 0.015406587999905241, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[CmdDockerClient]": 0.0017761550000159332, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[SdkDockerClient]": 0.008014397000124518, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[CmdDockerClient]": 0.0017947779999758495, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[SdkDockerClient]": 0.005579230999956053, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[CmdDockerClient]": 0.0017820040000060544, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[SdkDockerClient]": 0.005961905000276602, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[CmdDockerClient]": 0.0018130330001895345, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[SdkDockerClient]": 0.1702978579999126, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[CmdDockerClient]": 0.0018242139999529172, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[SdkDockerClient]": 0.9444557199997234, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[CmdDockerClient]": 0.0018474679998234933, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[SdkDockerClient]": 0.12634902299987516, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[CmdDockerClient]": 0.0019429160001891432, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[SdkDockerClient]": 0.30333460499991816, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[CmdDockerClient]": 0.003885360999902332, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[SdkDockerClient]": 0.18452799499982575, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[CmdDockerClient]": 0.0018384400002560142, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[SdkDockerClient]": 0.1911943339998743, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[CmdDockerClient]": 0.0019333079999341862, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[SdkDockerClient]": 0.18416577200014217, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[CmdDockerClient]": 0.001781302999916079, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[SdkDockerClient]": 10.973466285000313, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-echo]": 0.0018253760001698538, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-entrypoint1]": 0.0017549839999446704, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-echo]": 0.18603482000025906, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-entrypoint1]": 0.17842079499996544, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[CmdDockerClient]": 0.001754272999960449, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[SdkDockerClient]": 0.0055246189999706985, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[CmdDockerClient]": 0.0018115000002580928, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[SdkDockerClient]": 0.006359410999948523, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[CmdDockerClient]": 0.0019261240001924307, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[SdkDockerClient]": 0.1739173290002327, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[CmdDockerClient]": 0.0018314980000013747, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[SdkDockerClient]": 0.005794296999965809, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[CmdDockerClient]": 0.0017898400001286063, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[SdkDockerClient]": 0.14789147300007244, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[CmdDockerClient]": 0.0019260750000285043, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[SdkDockerClient]": 0.00637922800024171, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[CmdDockerClient]": 0.0018047670000669314, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[SdkDockerClient]": 0.005626045999861162, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[CmdDockerClient]": 0.0034125350000522303, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[SdkDockerClient]": 0.7523694960002558, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[CmdDockerClient]": 0.0019969660002061573, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[SdkDockerClient]": 0.0060299699998722645, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[CmdDockerClient]": 0.0018857469999602472, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[SdkDockerClient]": 0.0063817870000093535, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[CmdDockerClient]": 0.0034511750000092434, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[SdkDockerClient]": 0.04260960600004182, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[CmdDockerClient]": 0.0019208830001389288, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[SdkDockerClient]": 1.2036195279999902, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[CmdDockerClient]": 0.001868655999942348, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[SdkDockerClient]": 0.19184098899972923, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[CmdDockerClient]": 0.0018886829998336907, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[SdkDockerClient]": 0.1940702940000847, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[CmdDockerClient]": 0.001814155000147366, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[SdkDockerClient]": 3.2792525320001005, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[CmdDockerClient]": 0.003254308000123274, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[SdkDockerClient]": 0.20709205499997552, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[CmdDockerClient]": 0.0069121800001994416, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[SdkDockerClient]": 0.4432827999999063, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[CmdDockerClient]": 0.0020904499999687687, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[SdkDockerClient]": 0.7677727949997006, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[CmdDockerClient]": 0.0023048719999678724, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[SdkDockerClient]": 0.19581444599998576, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[CmdDockerClient]": 0.0020353570002953347, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[SdkDockerClient]": 0.19118021299982502, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[CmdDockerClient]": 0.001973692000092342, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[SdkDockerClient]": 0.14664760900018337, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[CmdDockerClient]": 0.001984722999850419, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[SdkDockerClient]": 0.1865279260000534, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[CmdDockerClient]": 0.0019371050000245305, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[SdkDockerClient]": 0.16360530699989795, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_no_retries": 0.027791447999788943, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_after_init": 1.0891511770000761, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_on_init": 1.070130115000211, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_timeout_seconds": 0.01904424200029098, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[CmdDockerClient]": 0.0019399090001570585, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[SdkDockerClient]": 0.3685316299997794, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[CmdDockerClient]": 0.003377438000143229, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[SdkDockerClient]": 0.17139327299992146, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[CmdDockerClient]": 0.0020075449999694683, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[SdkDockerClient]": 0.18690938999975515, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[CmdDockerClient]": 0.003387957000086317, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[SdkDockerClient]": 0.4063843779999843, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[CmdDockerClient]": 0.0019780800000717136, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[SdkDockerClient]": 0.36656208399972456, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[CmdDockerClient]": 0.001833459999943443, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[SdkDockerClient]": 0.02720381499989344, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-None]": 0.0019707470000867033, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-tcp]": 0.001967380999985835, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-udp]": 0.001932635000002847, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-None]": 1.4595667970002069, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-tcp]": 1.4548421970000618, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-udp]": 1.479573094999978, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-None]": 0.003389441000081206, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-tcp]": 0.0019720289999440865, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-udp]": 0.0019828790000246954, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-None]": 2.6437320379998255, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-tcp]": 2.5562603310002032, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-udp]": 2.7824807570002577, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[CmdDockerClient]": 0.0035280400002193346, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[SdkDockerClient]": 0.3674299270001029, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-False]": 0.0018578050000996882, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-True]": 0.0018486980000034237, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-False]": 0.11840362899988577, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-True]": 0.11947073299984368, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[CmdDockerClient]": 0.0018177810002271144, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[SdkDockerClient]": 0.1848618220001299, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[CmdDockerClient]": 0.001893242000051032, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[SdkDockerClient]": 0.7353357959998448, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[CmdDockerClient]": 0.0018639669999629405, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[SdkDockerClient]": 0.24491471800024556, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[CmdDockerClient]": 0.0018848059999072575, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[SdkDockerClient]": 0.18334250900011284, - "tests/integration/services/test_internal.py::TestHealthResource::test_get": 0.017968850999977803, - "tests/integration/services/test_internal.py::TestHealthResource::test_head": 0.018190129999993587, - "tests/integration/services/test_internal.py::TestInfoEndpoint::test_get": 0.04994243600003756, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[boot-True]": 0.017859725000107574, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[ready-True]": 0.01841755100008413, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[shutdown-False]": 0.018065029999888793, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[start-True]": 0.02321548800000528, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_nonexisting_stage": 0.018814966000036293, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_stages_have_completed": 1.5346031930000663, - "tests/integration/test_config_endpoint.py::test_config_endpoint": 0.06227641799955563, - "tests/integration/test_config_service.py::TestConfigService::test_put_configuration_recorder": 0.21785928999975113, - "tests/integration/test_config_service.py::TestConfigService::test_put_delivery_channel": 0.21353211399991778, - "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher": 0.006584207000059905, - "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher_avoid_fallback": 0.004360786000006556, - "tests/integration/test_security.py::TestCSRF::test_CSRF": 0.10043611099968075, - "tests/integration/test_security.py::TestCSRF::test_additional_allowed_origins": 0.01657879400022466, - "tests/integration/test_security.py::TestCSRF::test_cors_apigw_not_applied": 0.04249741100011306, - "tests/integration/test_security.py::TestCSRF::test_cors_s3_override": 0.08404852300009225, - "tests/integration/test_security.py::TestCSRF::test_default_cors_headers": 0.014409634999992704, - "tests/integration/test_security.py::TestCSRF::test_disable_cors_checks": 0.01787133399989216, - "tests/integration/test_security.py::TestCSRF::test_disable_cors_headers": 0.0210201509999024, - "tests/integration/test_security.py::TestCSRF::test_internal_route_cors_headers[/_localstack/health]": 0.0112286150001637, - "tests/integration/test_security.py::TestCSRF::test_no_cors_without_origin_header": 0.010233193999738432, - "tests/integration/test_stores.py::test_nonstandard_regions": 0.16057162700030858, - "tests/integration/utils/test_diagnose.py::test_diagnose_resource": 0.2482701450001059 + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_lambda_dynamodb": 2.6456269580000367, + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_opensearch_crud": 3.8196625279999807, + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_search_books": 63.172157873, + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_setup": 100.94292506599996, + "tests/aws/scenario/kinesis_firehose/test_kinesis_firehose.py::TestKinesisFirehoseScenario::test_kinesis_firehose_s3": 0.0024516159999734555, + "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_destination_sns": 6.336545428000022, + "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_infra": 12.118331920999992, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_prefill_dynamodb_table": 23.143875615000013, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]": 3.7690518139999654, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]": 2.5610185580000575, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input2-FAILED]": 0.8786274600000183, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input3-FAILED]": 0.6337569899999949, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input4-FAILED]": 1.2380335299999956, + "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_deployed_infra_state": 0.002696966000030443, + "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_populate_data": 0.0013323470000159432, + "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_user_clicks_are_stored": 0.0013318549999894458, + "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_notes_rest_api": 5.305738380999969, + "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_validate_infra_setup": 29.924309257999994, + "tests/aws/services/acm/test_acm.py::TestACM::test_boto_wait_for_certificate_validation": 1.1489508750000255, + "tests/aws/services/acm/test_acm.py::TestACM::test_certificate_for_subdomain_wildcard": 2.2340195770000264, + "tests/aws/services/acm/test_acm.py::TestACM::test_create_certificate_for_multiple_alternative_domains": 11.005133203000014, + "tests/aws/services/acm/test_acm.py::TestACM::test_domain_validation": 0.2472397850000334, + "tests/aws/services/acm/test_acm.py::TestACM::test_import_certificate": 0.817190614000026, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiAuthorizer::test_authorizer_crud_no_api": 0.04637266700001419, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_doc_parts_crud_no_api": 0.03568605899999966, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_documentation_part_lifecycle": 0.07663063499995815, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts": 0.1284634840000649, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_create_documentation_part_operations": 0.045668518000070435, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_delete_documentation_part": 0.05593114100003049, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_part": 0.048879558999942674, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_parts": 0.01651635399997531, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_update_documentation_part": 0.05875432800002045, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_lifecycle": 0.10627967199997101, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_request_parameters": 0.06528431500004217, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_model": 0.3300249609999355, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_validation": 0.08674511299994947, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method": 0.08591561100001854, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method_validation": 0.15129677699991362, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_lifecycle": 0.07697661400004563, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_validation": 0.1075453300000504, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_update_model": 0.07755871699998806, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_create_request_validator_invalid_api_id": 0.017583082999976796, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_delete_request_validator": 0.04679665200001182, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validator": 0.047971775000064554, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validators": 0.0162609330000123, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_update_request_validator_operations": 0.06894953899995926, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_request_validator_lifecycle": 0.09952419999996209, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_validators_crud_no_api": 0.034721441999920444, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource": 0.14982810300000438, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource_validation": 0.11104134399994336, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_resource_parent_invalid": 0.03867284199992582, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_delete_resource": 0.0820284310000261, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_resource_lifecycle": 0.12337009500004115, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_behaviour": 0.18124320999993415, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_on_root": 0.046250127000007524, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_private_type": 0.03360670300003221, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_verify_defaults": 0.09124261400006617, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_binary_media_types": 0.026391756999942118, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_endpoint_configuration": 0.11478790700010677, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_optional_params": 0.08147438099996407, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_tags": 0.04802782199999456, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_get_api_case_insensitive": 0.0013809680000917979, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_list_and_delete_apis": 0.09562653300008606, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_behaviour": 0.061753654000028746, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_compression": 0.10118536500004893, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_concatenation_of_errors": 0.0012802589999978409, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_invalid_api_id": 0.016668700000025183, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_ip_address_type": 0.09125376000002916, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_operation_add_remove": 0.05485495099998161, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_crud": 0.10914063399997076, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_put": 0.10674725900003068, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_validation": 0.10924249300001065, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_update_gateway_response": 0.13462732799996502, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_delete_integration_response_errors": 0.0973584589999632, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_integration": 0.0410652640000535, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_responsetemplates": 0.0013634360000196466, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_statuscode": 0.042483876999938275, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_api": 0.025697712999999567, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_method": 0.040934195000033924, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_resource": 0.04118440400003465, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_status_code": 0.05235487900000635, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_integration_response": 0.10124393299992107, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_method_response": 0.09978650199997219, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_request_parameter_bool_type": 0.001287863000015932, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_validation": 0.079560125999933, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_wrong_type": 0.04337553199997046, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_lack_response_parameters_and_models": 0.07873504900004491, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response": 0.06936705200001825, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_negative_tests": 0.0943478489999734, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_wrong_operations": 0.09541652200005046, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_wrong_param_names": 0.08857474900003126, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_failed_invoke_test_method": 0.1586982680000233, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_invoke_test_method": 0.37606985500002565, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_account": 0.04710929199995917, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_authorizer_crud": 0.001686710999990737, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_handle_domain_name": 0.2803978350000307, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_http_integration_with_path_request_parameter": 0.0015659879999248005, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_asynchronous_invocation": 1.361566460000006, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_integration_aws_type": 7.860705881000001, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/foo1]": 0.0012725959999784209, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/{test_param1}]": 0.0013139220000084606, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method": 0.0013322269999775926, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method_with_path_param": 0.001333619999968505, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_with_is_base_64_encoded": 0.0013637549999998555, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_mock_integration": 0.07010537399997929, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_mock_integration_response_params": 0.0013196450000236837, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigateway_with_custom_authorization_method": 15.471587273000011, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[dev]": 1.7265479289999917, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[local]": 1.6964592960000004, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_test_invoke_method_api": 2.193996351999999, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping": 0.2133842019999861, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping_root": 0.18810665699999163, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[host_based_url]": 0.07115619400002515, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[localstack_path_based_url]": 0.06978841199997987, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[path_based_url]": 0.07084975799995163, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_delete_rest_api_with_invalid_id": 0.012866712999993979, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.HOST_BASED]": 0.0809882980000225, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.LS_PATH_BASED]": 0.07979475600001251, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.PATH_BASED]": 0.08190060800006904, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.HOST_BASED]": 0.10462854999997262, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.LS_PATH_BASED]": 0.07954306400000632, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.PATH_BASED]": 0.07887761400002091, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.HOST_BASED]": 0.08149311500005751, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.LS_PATH_BASED]": 0.07939165600004117, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.PATH_BASED]": 0.08065985100000717, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.HOST_BASED]": 0.07685778200004734, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.LS_PATH_BASED]": 0.07367952599997807, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.PATH_BASED]": 0.0745811909999361, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_multiple_api_keys_validate": 0.48899641799999927, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_with_request_template": 0.0013646300000118572, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_without_request_template": 0.0014124499999752516, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_response_headers_invocation_with_apigw": 1.8284203879999836, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_update_rest_api_deployment": 0.08179651199998261, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[custom]": 0.0015519380000341698, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[proxy]": 0.0013731010000128663, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-GET]": 0.1083645309999497, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-POST]": 0.10870774400001437, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-GET]": 0.10920055999991973, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-POST]": 0.1107898310000337, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-GET]": 0.10944720500009453, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-POST]": 0.11896505100003196, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-GET]": 0.11953732599999967, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-POST]": 0.11522686000000704, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-GET]": 0.1121500239999591, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-POST]": 0.11266086099999484, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-GET]": 0.10965637800001105, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-POST]": 0.11511425099996586, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestTagging::test_tag_api": 0.07799319600002264, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_apigw_call_api_with_aws_endpoint_url": 0.013762637999946037, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-ANY]": 3.5463964600000395, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-GET]": 3.541395850000015, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-ANY]": 3.5451315570000475, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-GET]": 9.751090780999903, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestCanaryDeployments::test_invoking_canary_deployment": 0.13880184499998904, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment": 0.13779946799996878, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_by_stage_update": 0.14376558899999736, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_validation": 0.10003202500007546, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_with_stage": 0.11599074100001872, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_update_stages": 0.16610740099997656, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_canary_deployment_validation": 0.16833170599994673, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_with_copy_ops": 0.14386649600004375, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator": 2.5303406539999855, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_models": 0.19166108199999599, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_one_ofmodels": 0.20112380299997312, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_body_formatting": 3.5986269530000072, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_path_template_formatting": 0.6584828619999712, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_integration_request_parameters_mapping": 0.11663483400002406, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_invocation_trace_id": 2.3271853149999515, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_api_not_existing": 0.027123352999979033, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_proxy_routing_with_hardcoded_resource_sibling": 0.278068807000011, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_not_found": 0.11751300899999251, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_custom_api_id": 0.1108844639999802, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_hardcoded_resource_sibling_order": 0.23728950700001405, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[False]": 0.39955931599996575, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[True]": 0.4354391549999832, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_update_deployments": 0.32936303499997166, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDocumentations::test_documentation_parts_and_versions": 0.1091472539999927, + "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_create_update_stages": 0.29803537299994787, + "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_update_stage_remove_wildcard": 0.28483423400001584, + "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_api_key_required_for_methods": 0.21461681999994653, + "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_usage_plan_crud": 0.2111040710000225, + "tests/aws/services/apigateway/test_apigateway_custom_ids.py::test_apigateway_custom_ids": 0.06435841899997286, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_error_aws_proxy_not_supported": 0.1581544699999995, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[PutItem]": 0.3884141519999389, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Query]": 0.4693211480000059, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Scan]": 0.3616664450000826, + "tests/aws/services/apigateway/test_apigateway_eventbridge.py::test_apigateway_to_eventbridge": 0.21977327600001217, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_api_keys": 0.18066219599995748, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_usage_plan_api_keys": 0.17354237999995803, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_create_domain_names": 0.08563463299998375, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.37870676500006084, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETS]": 0.2890934869999455, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.3853350519999026, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETS]": 0.28761935200003563, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_name": 0.08182579400005352, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_names": 0.08371523999994679, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP]": 1.8550186850000046, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP_PROXY]": 1.8178027780001003, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP]": 2.075905109999894, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP_PROXY]": 2.072051518999956, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP]": 2.131987229999993, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP_PROXY]": 2.154788033999921, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_proxy_integration_request_data_mappings": 1.972163066000121, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[openapi.spec.tf.json]": 0.3222264710000218, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[swagger-mock-cors.json]": 0.4126889309998205, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api": 0.06846491800001786, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[ignore]": 0.7914613269999791, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[prepend]": 0.7977636560000292, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[split]": 0.80010070000003, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[ignore]": 0.5907906319999938, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[prepend]": 0.5621962060000669, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[split]": 0.5516925409999658, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_swagger_api": 1.615986711000005, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models": 0.26273103200003334, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models_and_request_validation": 0.3703296430001046, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_cognito_auth_identity_source": 0.37319118899995374, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_global_api_key_authorizer": 0.26718424400007734, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_http_method_integration": 0.2859772630001771, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_integer_http_status_code": 0.177729103999809, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_stage_variables": 1.7214130680000608, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[merge]": 0.28449716099999023, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[overwrite]": 0.2869662570000173, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS]": 2.464199117000021, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS_PROXY]": 2.5354477359999237, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP]": 0.744673705000082, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP_PROXY]": 0.7362165459999233, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": 5.7030561540000235, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_http_integration_status_code_selection": 0.13366499699998258, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_path_param": 0.10666246499999943, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_request_overrides_in_response_template": 0.130015960000037, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[False]": 0.09563170399985665, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[True]": 0.09545003699997778, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_vtl_map_assignation": 0.10569509699996615, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_response_with_response_template": 1.2647721769999407, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_responses": 0.1892761960000371, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_validation": 0.2247657509999499, + "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecord]": 1.104556083000034, + "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecords]": 1.1078049389999478, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_binary_response": 3.8748110579999775, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_response_payload_format_validation": 3.9616266670000186, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration": 1.7649456100000407, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_response_with_mapping_templates": 1.9408369819999507, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_with_request_template": 1.9131105740000294, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration": 4.015105978000065, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_non_post_method": 1.3791511099999525, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_request_data_mapping": 2.697379294999905, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_response_format": 2.0455601890000707, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_rust_proxy_integration": 3.8343832280000925, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_selection_patterns": 2.0280876619999617, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_put_integration_aws_proxy_uri": 1.3393577350000214, + "tests/aws/services/apigateway/test_apigateway_lambda_cfn.py::TestApigatewayLambdaIntegration::test_scenario_validate_infra": 7.600949304999972, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[CONVERT_TO_TEXT]": 0.5568799640000179, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[None]": 0.5543614330000537, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary": 0.5010715759999584, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary_with_request_template": 0.31180505199995423, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary": 0.5640049149999413, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary_with_request_template": 0.3463934539998945, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_text": 0.5812749249998888, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_no_content_handling": 0.5713210280000567, + "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_any": 0.43049775500003307, + "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_method_mapping": 0.48674832099993637, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_amz_json_protocol": 0.8626181380000162, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration": 1.3032076849999612, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttribute]": 0.26291680799999995, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttributes]": 0.26487768399988454, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_request_and_response_xml_templates_integration": 0.3606767649998801, + "tests/aws/services/apigateway/test_apigateway_ssm.py::test_get_parameter_query_protocol": 0.0015783369999553543, + "tests/aws/services/apigateway/test_apigateway_ssm.py::test_ssm_aws_integration": 0.23722017299996878, + "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[DeleteStateMachine]": 2.433100842000158, + "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[StartExecution]": 1.4970347130000619, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_api_exceptions": 0.0012828729999228017, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_exceptions": 0.0012778220000200236, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_invalid_desiredstate": 0.001262093000036657, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_double_create_with_client_token": 0.001291487999878882, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_lifecycle": 0.0015637899998637295, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources": 0.0013646459998426508, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources_with_resource_model": 0.0012826419999782956, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_update": 0.0012996839999459553, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[FAIL]": 0.0013915470000256391, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[SUCCESS]": 0.0014010439999765367, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_request": 0.0013400499998397208, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_get_request_status": 0.0013697960000627063, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_invalid_request_token_exc": 0.001311826000005567, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_list_request_status": 0.001352633000124115, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_deleting_resource": 8.404162154000005, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_single_resource": 10.339746874999946, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": 10.33777058599992, + "tests/aws/services/cloudformation/api/test_changesets.py::test_autoexpand_capability_requirement": 0.42268806100003076, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_non_supported_resource_change_set": 6.51865665899993, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_supported_resource_change_set": 8.31361275900008, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_update_refreshes_template_metadata": 2.144253931000094, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_create_existing": 2.14049316299986, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_invalid_params": 0.01632428599987179, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_missing_stackname": 0.024681855000039832, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_no_changes": 2.1330406629999743, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_nonexisting": 0.016597931000092103, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_without_parameters": 0.0018949560000010024, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_with_ssm_parameter": 1.1957698999999593, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_without_parameters": 0.094190215000026, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_changeset_with_stack_id": 0.2775503120000167, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_delete_create": 3.2038420780000934, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_while_in_review": 0.0013845860000856192, + "tests/aws/services/cloudformation/api/test_changesets.py::test_delete_change_set_exception": 0.024341292999906727, + "tests/aws/services/cloudformation/api/test_changesets.py::test_deleted_changeset": 0.05408272800002578, + "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_nonexisting": 0.013788048999913372, + "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": 0.059652244999938375, + "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_changeset_after_delete": 1.3491826900000206, + "tests/aws/services/cloudformation/api/test_changesets.py::test_empty_changeset": 0.4026904029999514, + "tests/aws/services/cloudformation/api/test_changesets.py::test_execute_change_set": 0.001401807999968696, + "tests/aws/services/cloudformation/api/test_changesets.py::test_multiple_create_changeset": 0.42415508400017643, + "tests/aws/services/cloudformation/api/test_changesets.py::test_name_conflicts": 2.9426208600000336, + "tests/aws/services/cloudformation/api/test_changesets.py::test_update_change_set_with_aws_novalue_repro": 1.1511685609999631, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[condition]": 0.035923608999951284, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[parameter]": 0.026244636000001265, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[resource]": 0.026627414999893517, + "tests/aws/services/cloudformation/api/test_drift_detection.py::test_drift_detection_on_lambda": 0.0014843859999018605, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": 0.0012884269999631215, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": 0.0013140459999476661, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": 0.001317321999977139, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": 0.0012937479999663992, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": 0.0012758150000991009, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": 0.0012699610000481698, + "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": 0.001265823999915483, + "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": 0.0013062100001661747, + "tests/aws/services/cloudformation/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": 0.001257027999940874, + "tests/aws/services/cloudformation/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": 0.001323012999932871, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": 7.330610597999907, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_lifecycle_nested_stack": 0.0020546960000729086, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_output_in_params": 14.60362346099987, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack": 8.262399467000137, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack_output_refs": 8.281446308999989, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stacks_conditions": 8.316307896000012, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_with_nested_stack": 14.293170295999971, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": 2.1506889739999906, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": 2.1516910290000624, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_redeploy_cdk_with_reference": 24.494509819999962, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_reference_unsupported_resource": 4.167485872999919, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_sub_resolving": 0.1437111919998415, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_resource": 4.164431039999954, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_stack": 0.020557286999974167, + "tests/aws/services/cloudformation/api/test_resources.py::test_invalid_logical_resource_id": 0.019758518999879016, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": 0.0012493589999849064, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": 0.0012993219999088979, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": 0.001286689000039587, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": 0.0012922900000376103, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": 0.001327837999951953, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": 0.0013621420000617945, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": 0.0013370649999160378, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource0]": 0.0012736160000486052, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource1]": 0.0012961170000380662, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_modifying_with_policy_specifying_resource_id": 0.0012676930000452558, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_replacement": 0.0013636340000857672, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": 0.0012412739999945188, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": 0.0012607610000259228, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::S3::Bucket]": 0.001317547999974522, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::SNS::Topic]": 0.0013644050000038987, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": 0.00131176599995797, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": 0.0014152809999359306, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": 0.0013307830000712784, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": 0.001373472000068432, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": 0.0013158750000457076, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_empty_policy": 0.0012688460000163104, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[False]": 0.0012807390000943997, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[True]": 0.0012595690000125614, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_policy": 0.0012570939999250186, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_create_stack_with_custom_id": 1.081139306999944, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[False-0]": 0.001523873999872194, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[True-1]": 0.0012987500000463115, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": 0.0012799439999753304, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[True-1]": 0.0012856550000606148, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": 2.133162459999994, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": 2.1307114629998978, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": 0.05945624800017413, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": 1.0784446300000354, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": 0.18756459200005793, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle[no-tags]": 1.350698270999942, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle[with-tags]": 1.3260699039999508, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": 4.1352001839999275, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": 3.492257999000117, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_name_creation": 0.06792954400009421, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_update_resources": 8.515684383000007, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_actual_update": 6.269330416999992, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": 0.11370097400003942, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": 2.3034133049999355, + "tests/aws/services/cloudformation/api/test_stacks.py::test_blank_parameter_value": 1.071889287999852, + "tests/aws/services/cloudformation/api/test_stacks.py::test_blocked_stack_deletion": 0.0014894900000399502, + "tests/aws/services/cloudformation/api/test_stacks.py::test_describe_stack_events_errors": 0.025344392000079097, + "tests/aws/services/cloudformation/api/test_stacks.py::test_events_resource_types": 4.182726062000029, + "tests/aws/services/cloudformation/api/test_stacks.py::test_linting_error_during_creation": 0.0014967969999588604, + "tests/aws/services/cloudformation/api/test_stacks.py::test_list_parameter_type": 11.034818866000023, + "tests/aws/services/cloudformation/api/test_stacks.py::test_name_conflicts": 4.507989442000053, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_echo_parameter": 5.060418283999979, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_parameters_given": 0.019058277999988604, + "tests/aws/services/cloudformation/api/test_stacks.py::test_non_existing_stack_message": 0.017700245000014547, + "tests/aws/services/cloudformation/api/test_stacks.py::test_notifications": 0.0013656060000357684, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[A-B-C]": 4.303141054999969, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[B-C]": 4.357745637999983, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[C]": 4.36363145200005, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-B-C]": 6.3941780439998865, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-C-B]": 6.417384671999912, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-A-C]": 6.406346469000027, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-C-A]": 6.387385734999839, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-A-B]": 6.401446863999922, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-B-A]": 6.40824198700011, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_resource_not_found": 2.1229699609998534, + "tests/aws/services/cloudformation/api/test_stacks.py::test_update_termination_protection": 2.1878578499998866, + "tests/aws/services/cloudformation/api/test_stacks.py::test_updating_an_updated_stack_sets_status": 10.54534160999981, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": 0.16516910999996526, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": 0.10127567199981513, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": 1.1671113730000116, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": 0.10172859800002243, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_change_set": 0.014019324999935634, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_change_set_id": 0.014410829000098602, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_stack": 0.014762304000100812, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary": 4.25621017200001, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary_non_executed_change_set": 0.04406099700008781, + "tests/aws/services/cloudformation/api/test_templates.py::test_validate_invalid_json_template_should_fail": 0.0811384389999148, + "tests/aws/services/cloudformation/api/test_templates.py::test_validate_template": 0.07756446200005485, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach": 1.2765373019998378, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_multiple_resources": 1.287062479000042, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_use_case": 0.7187638440000228, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_length": 1.25983037900005, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_to_json_string": 1.2771336899999142, + "tests/aws/services/cloudformation/api/test_transformers.py::test_duplicate_resources": 2.337123980000001, + "tests/aws/services/cloudformation/api/test_transformers.py::test_redeployment_with_fn_include": 4.404611287999842, + "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_individual_resource_level": 0.22337059300002693, + "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_property_level": 4.3048543359999485, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_basic_update": 3.1980818490001184, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_diff_after_update": 5.236281359000145, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_parameters_update": 3.1766320180000776, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_template_error": 0.0012510779998819999, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_set_notification_arn_with_update": 0.0013314389999550258, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_tags": 0.0012567379999381956, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_using_template_url": 3.253050607999967, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability0]": 0.0012752530000170736, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability1]": 0.0013630089999878692, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": 0.0013736480000261508, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": 0.14219253900000695, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_template": 0.0015657689999670765, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_resource_types": 0.0013220510001019647, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_role_without_permissions": 0.0014732760000697454, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_rollback_configuration": 0.0013093370000660798, + "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[missing-def]": 0.0012777979999327727, + "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[multiple-nones]": 0.001243515000055595, + "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[none-value]": 0.0014904480001405318, + "tests/aws/services/cloudformation/api/test_validations.py::test_missing_resources_block": 0.0012547750000067026, + "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[invalid-key]": 0.0013734779998912927, + "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[missing-type]": 0.0013267199999518198, + "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": 4.137238700000012, + "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": 0.001252722999879552, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_condition_on_outputs": 4.165156610000054, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[create]": 4.1715636259999656, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[no-create]": 4.159586366999974, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[dev-us-west-2]": 4.15645468699995, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[production-us-east-1]": 4.1565657690000535, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_with_select": 4.146686298999953, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[None-FallbackParamValue]": 4.175571267999999, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[false-DefaultParamValue]": 4.164219216999982, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": 0.0014985720000595393, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_intrinsic_fn_condition": 0.0012824169999703372, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_with_macro": 0.0012421309999126606, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": 0.001226452000082645, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": 0.00126094600000215, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": 0.0013150480000376774, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": 0.001276625999935277, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": 0.0012213420000080077, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_deploys_resource": 0.1266208590000133, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_doesnt_deploy_resource": 0.10141634600006455, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[nope]": 4.136201971999981, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[yep]": 2.1237331290000157, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_sub_in_conditions": 4.189682816999948, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": 8.354058769999938, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level_v2": 0.025360976000001756, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level_v2": 0.023679225000023507, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": 2.133420092999927, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": 0.0014187719998517423, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": 0.001293928999871241, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": 4.161171374999867, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": 4.144934368000008, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": 0.001371194000057585, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": 0.0014664020000054734, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_simple_mapping_working": 0.1355105000001231, + "tests/aws/services/cloudformation/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": 0.001410466999914206, + "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_fn_sub_cases": 4.1788247969999475, + "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": 4.152975798000057, + "tests/aws/services/cloudformation/engine/test_references.py::TestPseudoParameters::test_stack_id": 4.152600853999957, + "tests/aws/services/cloudformation/engine/test_references.py::test_aws_novalue[no]": 4.1535521920000065, + "tests/aws/services/cloudformation/engine/test_references.py::test_aws_novalue[yes]": 4.157548119999888, + "tests/aws/services/cloudformation/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": 4.161505732000023, + "tests/aws/services/cloudformation/engine/test_references.py::test_useful_error_when_invalid_ref": 0.019521310000072845, + "tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/test_basic.py::TestBasicCRD::test_black_box": 6.6140516240000125, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_instance_with_key_pair": 6.479124407000199, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_prefix_list": 8.20703760299989, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_security_group_with_tags": 2.1464800029998514, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_vpc_endpoint": 4.625849566999932, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_autogenerated_values": 4.151636282000027, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_black_box": 2.197229077999964, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_getatt": 4.200119262000044, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestUpdates::test_update_without_replacement": 0.001651728000069852, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Arn]": 0.0013811910000640637, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Id]": 0.001353188000166483, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Path]": 0.0012658949997330637, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[PermissionsBoundary]": 0.0012701129999186378, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[UserName]": 0.00126517400008197, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": 2.3437178450003557, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_cfn_handle_iam_role_resource_no_role_name": 4.180303478999804, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_delete_role_detaches_role_policy": 8.319186326999898, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_user_access_key": 8.340515252999694, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_username_defaultname": 2.180155017000061, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_managed_policy_with_empty_resource": 4.4238964660000875, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_policy_attachments": 4.30654615599974, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_server_certificate": 2.2540401740000107, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_update_inline_policy": 8.421094074999928, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_updating_stack_with_iam_role": 16.363797130999956, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Arn]": 0.001231880999966961, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainArn]": 0.0013240129999303463, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainEndpoint]": 0.001354239999955098, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainName]": 0.0016851209998094419, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[EngineVersion]": 0.001278217999924891, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Id]": 0.001330916000142679, + "tests/aws/services/cloudformation/resource_providers/scheduler/test_scheduler.py::test_schedule_and_group": 4.45412737200013, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestBasicCRD::test_black_box": 0.00172058700013622, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestUpdates::test_update_without_replacement": 0.0012627999999494932, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[AllowedPattern]": 0.0012639199999284756, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[DataType]": 0.0012954500000432745, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Description]": 0.0012519689998953254, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Id]": 0.0012483120001434145, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Name]": 0.0012446750001799955, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Policies]": 0.0012754919998769765, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Tier]": 0.001262076999864803, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Type]": 0.0012562859999434295, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Value]": 0.001272166000035213, + "tests/aws/services/cloudformation/resources/test_acm.py::test_cfn_acm_certificate": 2.1173333429999275, + "tests/aws/services/cloudformation/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": 9.4983547610002, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_account": 4.222376223000083, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": 2.1449203789998137, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_apigateway_deployment_canary_settings": 4.274206154000012, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_aws_integration": 2.311353435999763, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_rest_api": 4.41301292199978, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_swagger_import": 4.494508205999864, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": 4.532676222999953, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": 4.238668605999692, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": 4.3272567079998225, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_with_apigateway_resources": 6.418256917999997, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": 9.90875629900006, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_apigateway_stage": 8.606673580999995, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_usage_plan": 14.600022588999764, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_url_output": 4.18214868899986, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[10]": 4.70298974100001, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[11]": 4.705063898999924, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": 4.719372566000175, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[28]": 4.825019957999984, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v20]": 3.13943703599989, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v28]": 3.20759565000003, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": 4.433135898000046, + "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_create_macro": 1.2475991829999202, + "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_waitcondition": 4.247003953000103, + "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_creation": 4.11753443900011, + "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_ext_statistic": 4.192617946000155, + "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_composite_alarm_creation": 6.470381188999909, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": 4.432689299000231, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": 4.425877864999848, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_default_name_for_table": 4.416706745000056, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_deploy_stack_with_dynamodb_table": 6.2831660369997735, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table": 6.477947327000038, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": 4.190461296999956, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_globalindex_read_write_provisioned_throughput_dynamodb_table": 4.22975604699991, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_table_with_ttl_and_sse": 4.16537280700004, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_ttl_cdk": 2.299598031999949, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_update_ec2_instance_type": 0.0014331259999380563, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": 4.588824819000138, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_tables": 4.245242613000073, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_dhcp_options": 2.330296950000047, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": 4.1783281779999015, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_internet_gateway_ref_and_attr": 4.31606573199997, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_keypair_create_import": 4.250140030000239, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation": 6.313525779999964, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": 6.293967880000309, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_transit_gateway_attachment": 12.99227860699989, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_creates_default_sg": 4.650551889999861, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_gateway_attachment": 4.155121082000051, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_with_route_table": 6.362862209000241, + "tests/aws/services/cloudformation/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": 5.869054202999905, + "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_api_destination_resource": 20.4463428680001, + "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_bus_resource": 6.226091407000013, + "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_creation_without_target": 2.1473210199997084, + "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_to_logs": 4.307591508000087, + "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policies": 8.31663774899971, + "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policy_statement": 4.158356432999881, + "tests/aws/services/cloudformation/resources/test_events.py::test_rule_pattern_transformation": 4.174901840000075, + "tests/aws/services/cloudformation/resources/test_events.py::test_rule_properties": 4.184917601000052, + "tests/aws/services/cloudformation/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": 32.506783354999925, + "tests/aws/services/cloudformation/resources/test_integration.py::test_events_sqs_sns_lambda": 15.593235349999986, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_cfn_handle_kinesis_firehose_resources": 14.345025411000051, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_default_parameters_kinesis": 12.262631130999807, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_describe_template": 0.14888456999983646, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": 12.273976467999773, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_kinesis_stream_consumer_creations": 18.282819819999986, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_stream_creation": 12.289271137999776, + "tests/aws/services/cloudformation/resources/test_kms.py::test_cfn_with_kms_resources": 6.232263468999918, + "tests/aws/services/cloudformation/resources/test_kms.py::test_deploy_stack_with_kms": 6.204588087999809, + "tests/aws/services/cloudformation/resources/test_kms.py::test_kms_key_disabled": 2.14332899899955, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaDestinations::test_generic_destination_routing[sqs-sqs]": 21.494629775999783, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": 12.452103505999958, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": 23.42588423799998, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": 9.782929393999893, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": 12.05539659200008, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": 9.370127350000303, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_cfn_function_url": 9.367169862000083, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_event_invoke_config": 8.262103927999988, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_alias": 14.521502121999902, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": 11.027221931000213, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run": 8.579829952999944, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_empty_string_replacement_deny_list": 14.39437914899986, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_non_empty_string_replacement_deny_list": 12.37610093599983, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_code_signing_config": 2.2121260529997926, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_function_tags": 8.535121148999906, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_layer_crud": 8.304940178000379, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_logging_config": 8.238738352000155, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version": 8.774578849999898, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": 14.545320326000137, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_vpc": 0.0017805810002755607, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter": 11.386835117999908, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": 12.709736131000227, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": 8.21350188700012, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_python_lambda_code_deployed_via_s3": 8.683106293000037, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function": 12.408234104999792, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function_name": 16.41772111099999, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_permissions": 12.409125654999798, + "tests/aws/services/cloudformation/resources/test_logs.py::test_cfn_handle_log_group_resource": 4.481038537000131, + "tests/aws/services/cloudformation/resources/test_logs.py::test_logstream": 4.1918819790000725, + "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain": 0.0016306630002418387, + "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain_with_alternative_types": 19.285289068999873, + "tests/aws/services/cloudformation/resources/test_redshift.py::test_redshift_cluster": 4.143793640999775, + "tests/aws/services/cloudformation/resources/test_resource_groups.py::test_group_defaults": 2.2599653190000026, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_health_check": 2.2765954470000906, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_id": 2.2500844759999836, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_name": 4.2690334790002, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_without_resource_record": 4.2310036540000056, + "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_autoname": 4.16007450300026, + "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_versioning": 4.160953516000063, + "tests/aws/services/cloudformation/resources/test_s3.py::test_bucketpolicy": 4.30778638400011, + "tests/aws/services/cloudformation/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": 6.235421530000167, + "tests/aws/services/cloudformation/resources/test_s3.py::test_cors_configuration": 4.4985221819997605, + "tests/aws/services/cloudformation/resources/test_s3.py::test_object_lock_configuration": 4.482574827999997, + "tests/aws/services/cloudformation/resources/test_s3.py::test_website_configuration": 2.4667444419999356, + "tests/aws/services/cloudformation/resources/test_sam.py::test_cfn_handle_serverless_api_resource": 8.555735732999892, + "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_policies": 8.303682415999901, + "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_sqs_event": 13.358245395999802, + "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_template": 8.656946364000078, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": 2.333561540000119, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": 4.308154777999789, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": 4.186568650000254, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": 4.17600065900001, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": 4.281105772000046, + "tests/aws/services/cloudformation/resources/test_sns.py::test_deploy_stack_with_sns_topic": 4.241079905000106, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription": 4.161976669999831, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": 4.251499906999925, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": 4.334394874000054, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_without_suffix_fails": 1.107051457000125, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_policy_resets_to_default": 1.4153283230000397, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_attributes": 8.565429712000196, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_name": 8.584190715999966, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_with_attributes": 2.2986846329999935, + "tests/aws/services/cloudformation/resources/test_sns.py::test_update_subscription": 8.336254971999779, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_cfn_handle_sqs_resource": 4.1892736419995344, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": 4.1552736280004865, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": 4.150328796999929, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_queue_policy": 4.158265520999748, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": 8.29441399300049, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": 8.313355954999679, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_deploy_patch_baseline": 4.305539447999763, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_maintenance_window": 4.245239149999634, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_parameter_defaults": 6.338836783000261, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameter_tag": 8.29931099300029, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameters": 8.293914510999912, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": 2.290908089999448, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_delete_nonexistent_stack_set": 0.023508513999786373, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": 0.024391292999553116, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke": 9.524535716000173, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost": 9.570091989999128, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost_with_path": 15.629601222999554, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_with_path": 15.593536947999837, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": 8.837343675999819, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_with_dependencies": 6.244419001999631, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_nested_statemachine_with_sync2": 17.424246233000304, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_retry_and_catch": 0.0023174980001385848, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": 4.57907960700004, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_definitionsubstitution": 9.275738479999745, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": 3.852487669999846, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": 2.8825359309994383, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": 3.913376287999654, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": 2.8896183710003243, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": 3.9378374019993316, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": 2.9164099049999095, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": 3.935985683999661, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": 3.901446407000094, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import": 10.501941236999755, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export": 0.47280953899962697, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export_then_create": 10.503031676999854, + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": 1.7679006319999644, + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": 3.816246033999505, + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": 2.7898777150003298, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": 3.9874643329999344, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": 4.045227463000174, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": 3.9940154119999534, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": 3.9891151009996975, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": 2.893346072999975, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": 0.0014976679999563203, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": 3.9784737679997306, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": 3.967565198000102, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": 2.8660770610003965, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": 1.9251459209995119, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": 2.8773825289999877, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": 2.859027580000202, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": 2.846093037999708, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": 3.963067651000074, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": 2.8471507509998446, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": 1.8343889600000693, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": 5.271533923999868, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": 3.8580872649999947, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[index-out-of-range]": 0.5455008059998363, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[non-integer-index]": 0.5517491329997029, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[non-list-list]": 0.5451639419998173, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_nested_select_within_other_intrinsics": 4.672090331000163, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": 3.911025702000188, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": 2.9090728539999873, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": 2.923613397000281, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": 2.9566623000005166, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": 4.071247751000101, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": 3.9328008679999584, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": 1.842570403000991, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": 2.8529626509998707, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": 3.900861778000035, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": 2.852985227999852, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": 2.875126079000438, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": 2.858295843000178, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": 0.8303112730000066, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": 0.8620797470002799, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": 2.846744931000103, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[false]": 4.521830470000168, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[true]": 5.604272257000503, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[json]": 4.109979316000135, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[yml]": 4.126937362999797, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_fn_transform": 5.607566508000218, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_for_attribute_fn_transform": 5.691895460000524, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[json]": 2.942868945999635, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[yml]": 2.9560577960000955, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_macro_fn_transform": 5.59008060799988, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_macro_with_intrinsic_function": 5.5888923409997915, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_multiple_fn_transform_order": 5.803185232000033, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_remove_transform_in_update_change_set": 5.565634921999845, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_serverless_fn_transform": 9.391267123999569, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_update_parameter_transform_in_update_change_set": 5.748311000000285, + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": 5.670586058000026, + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": 7.249163078000038, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": 3.8917238110002472, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": 3.925201976000153, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": 3.8973306469997624, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": 3.90968757499968, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": 0.8733115880004334, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": 2.861294906999774, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": 2.8603763579999395, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": 2.8625361350004823, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": 2.886203063999801, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": 2.8602072319995386, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": 3.975856001000011, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": 2.998176204000174, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": 3.9625589180000134, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": 4.090353426000092, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": 2.932191614999738, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": 2.8810573279997698, + "tests/aws/services/cloudformation/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": 1.9040150539999559, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": 3.8773958419997143, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": 3.867533692000052, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": 0.010046068000065134, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": 0.006568897000306606, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": 3.855042411999875, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": 3.902138038999965, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": 1.7902702559999852, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": 3.876543992999814, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": 8.790120364000359, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": 3.921563021000111, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": 3.935851475000163, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": 3.87846077399945, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_single_resource_static_update": 2.7249994439998773, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": 0.0013544029998229234, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": 0.001769887000136805, + "tests/aws/services/cloudformation/test_change_sets.py::test_describe_failed_change_set": 0.1490840589999607, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup": 4.013374728999679, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup_no_change": 4.0064011099998424, + "tests/aws/services/cloudformation/test_change_sets.py::test_list_change_sets": 4.2180569279994415, + "tests/aws/services/cloudformation/test_cloudformation_ui.py::TestCloudFormationUi::test_get_cloudformation_ui": 0.07812691099979929, + "tests/aws/services/cloudformation/test_cloudtrail_trace.py::test_cloudtrail_trace_example": 0.0013734250001107284, + "tests/aws/services/cloudformation/test_list_stacks.py::test_listing_stacks": 12.676702364000448, + "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_cfn_with_exports": 4.1592686249996405, + "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_import_values_across_stacks": 8.294583342999886, + "tests/aws/services/cloudformation/test_template_engine.py::TestImports::test_stack_imports": 8.314190620999852, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-0-False]": 0.11508478399991873, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-1-False]": 0.11171747000025789, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-0-False]": 0.10837719500023013, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-1-True]": 4.170756640999116, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-0-False]": 0.13980295300007128, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-1-True]": 4.204466807000244, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-0-True]": 4.172498845000064, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-1-True]": 4.175012408000839, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_base64_sub_and_getatt_functions": 4.152805059000002, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": 4.1480926160006675, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cidr_function": 0.0014816659995631198, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_find_map_function": 4.144273777999842, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_fn_select_has_intrinsic_function": 4.1508775300003435, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-northeast-1]": 4.462874144000125, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-southeast-2]": 4.408131613999558, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-central-1]": 4.330762527000388, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-west-1]": 4.337723859999642, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-1]": 4.2436663609996685, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-2]": 4.329648599000393, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-1]": 4.32769045699979, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-2]": 4.342231078999703, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": 4.159109832000468, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_split_length_and_join_functions": 4.198803694999697, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_not_ready": 4.170443689999502, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_number_type": 2.148120138000195, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_to_json_functions": 0.0015495689999625029, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_attribute_uses_macro": 5.838179292999939, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_capabilities_requirements": 3.3009925659994224, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": 0.025029328000073292, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": 1.7007004100000813, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": 1.7204820869997093, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": 1.6783374040001036, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": 1.708822444999896, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": 2.755572017000304, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_global_scope": 3.1048967510000693, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_macro_deployment": 1.2637196189998576, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_pyplate_param_type_list": 12.770031919999838, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_scope_order_and_parameters": 6.046968432000085, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": 3.8285572659997342, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": 3.8545056870002554, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": 2.8448701019997316, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_validate_lambda_internals": 3.1655879940003615, + "tests/aws/services/cloudformation/test_template_engine.py::TestPreviousValues::test_parameter_usepreviousvalue_behavior": 0.0014985800003159966, + "tests/aws/services/cloudformation/test_template_engine.py::TestPseudoParameters::test_stack_id": 4.133407439000166, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager.yaml]": 2.1821745890001694, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_full.yaml]": 4.229967042999306, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_partial.yaml]": 4.2149673580001945, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager_with_backslashes": 4.217401008000252, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": 4.198306694999701, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": 2.2094691179995607, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm": 4.345402657999784, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_missing_parameter": 0.03910567200000514, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_secure": 2.22037949200012, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_with_version": 4.35751766400017, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": 8.26307751999957, + "tests/aws/services/cloudformation/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": 2.330583822000335, + "tests/aws/services/cloudformation/test_template_engine.py::TestTypes::test_implicit_type_conversion": 4.185254361000261, + "tests/aws/services/cloudformation/test_unsupported.py::test_unsupported": 4.123089965000418, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_change_parameter_type": 3.9510530300003666, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_update_parameter_between_deployments": 3.992286458000308, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[json]": 2.811158785999993, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[query]": 3.646497738999983, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[smithy-rpc-v2-cbor]": 2.210046028999983, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[json]": 0.12727995499997746, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[query]": 0.1065351610000107, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[smithy-rpc-v2-cbor]": 0.07529961800000251, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_multi_protocol_client_fixture[json]": 0.01489379300028304, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_multi_protocol_client_fixture[query]": 0.015893451000010828, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_multi_protocol_client_fixture[smithy-rpc-v2-cbor]": 0.3700452079997376, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_alarm_lambda_target": 2.773849946000155, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[json]": 0.001240070000221749, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[query]": 0.001256842000202596, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[smithy-rpc-v2-cbor]": 0.0012104739998903824, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created[json]": 2.4531310379993556, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created[query]": 2.439698910000061, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created[smithy-rpc-v2-cbor]": 2.456522087999474, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[json]": 0.8162297150001905, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[query]": 0.8383222089996707, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[smithy-rpc-v2-cbor]": 0.8420433880000928, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[json]": 0.0013510169997061894, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[query]": 0.0014976439997553825, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[smithy-rpc-v2-cbor]": 0.0012795540001206973, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[json]": 0.1391789389999758, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[query]": 0.14652316299998347, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[smithy-rpc-v2-cbor]": 0.14883560900034354, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering[json]": 0.14334177599948816, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering[query]": 0.153504668000096, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering[smithy-rpc-v2-cbor]": 0.14818591000039305, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[json]": 0.10272551899970495, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[query]": 0.10795680499995797, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[smithy-rpc-v2-cbor]": 0.10678845900019951, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[json]": 0.08555109799999627, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[query]": 0.0944415680005477, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[smithy-rpc-v2-cbor]": 0.08652517899963641, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[json]": 0.10349636799992368, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[query]": 0.10725478900030794, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[smithy-rpc-v2-cbor]": 0.10654450599986376, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[json]": 1.3290069469999253, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[query]": 1.312179400000332, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[smithy-rpc-v2-cbor]": 1.3227914430003693, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data[json]": 2.06617417400048, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data[query]": 2.069872688000032, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data[smithy-rpc-v2-cbor]": 2.068143334999604, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data0]": 0.00129936799976349, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data1]": 0.0012749919997077086, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data2]": 0.0012466389998735394, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data0]": 0.001352178000161075, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data1]": 0.0012711340000350901, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data2]": 0.0012878259999524744, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data0]": 0.0013461150001603528, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data1]": 0.0013232330002210801, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data2]": 0.0013275709998197271, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[json]": 1.055730331000177, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[query]": 1.0553739580000183, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[smithy-rpc-v2-cbor]": 1.053150887999891, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination[json]": 2.226323171000331, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination[query]": 2.2111825159995533, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination[smithy-rpc-v2-cbor]": 2.209987622999961, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Average]": 0.037183048000315466, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Maximum]": 0.03699544000028254, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Minimum]": 0.038189097000213224, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-SampleCount]": 0.040695109000353114, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Sum]": 0.04025375800028996, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Average]": 0.04390169600037552, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Maximum]": 0.04310377599995263, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Minimum]": 0.044400484000561846, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-SampleCount]": 0.04270415900009539, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Sum]": 0.04833039700042718, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Average]": 0.03712328499977957, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Maximum]": 0.039108768999994936, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Minimum]": 0.039448419000109425, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-SampleCount]": 0.036719717999403656, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Sum]": 0.03708030100051474, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[json]": 0.03565284200021779, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[query]": 0.03555717800054481, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[smithy-rpc-v2-cbor]": 0.03643358099998295, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[json]": 0.04660945300020103, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[query]": 0.04776256500008458, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[smithy-rpc-v2-cbor]": 0.047501840999757405, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[json]": 0.04687158900014765, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[query]": 0.05432309700017868, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[smithy-rpc-v2-cbor]": 0.04814306799971746, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[json]": 0.19508392699981414, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[query]": 0.20280797399982475, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[smithy-rpc-v2-cbor]": 0.18838516400001026, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[json]": 0.04242199799955415, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[query]": 0.06538496999974086, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[smithy-rpc-v2-cbor]": 0.03927711900041686, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[json]": 0.04014669699972728, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[query]": 0.0411520249995192, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[smithy-rpc-v2-cbor]": 0.03782159899947146, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[json]": 0.038318341999911354, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[query]": 0.03865218799955983, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[smithy-rpc-v2-cbor]": 0.03630447300020023, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[json]": 0.0012025480000374955, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[query]": 0.0014056810000511177, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[smithy-rpc-v2-cbor]": 0.001235279999946215, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[json]": 0.5582274770004005, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[query]": 0.5612579469998309, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[smithy-rpc-v2-cbor]": 0.5885494449998987, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[json]": 0.02346306400022513, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[query]": 0.02722180799992202, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[smithy-rpc-v2-cbor]": 0.023649332000331924, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs0]": 0.037828029000138486, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs1]": 0.03448540099952879, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs2]": 0.03442138899981728, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs3]": 0.0363866370003052, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs4]": 0.035258117000012135, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs5]": 0.03389237800001865, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs6]": 0.03263129200013282, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs0]": 0.042508270000325865, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs1]": 0.047804937999899266, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs2]": 0.04514854000035484, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs3]": 0.04422484699989582, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs4]": 0.04235340399964116, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs5]": 0.04260199800000919, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs6]": 0.04090263300031438, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs0]": 0.04048827600081495, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs1]": 0.038895782999588846, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs2]": 0.03935646799982351, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs3]": 0.03927095900053246, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs4]": 0.03670974999977261, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs5]": 0.03743298900008085, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs6]": 0.03745755600039047, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination[json]": 6.148744980000174, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination[query]": 6.315771258000041, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination[smithy-rpc-v2-cbor]": 5.509591850000106, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness[json]": 2.060155138000482, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness[query]": 2.0651757139999063, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness[smithy-rpc-v2-cbor]": 2.078719511000145, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters[json]": 4.077581975000157, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters[query]": 4.095830658000068, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters[smithy-rpc-v2-cbor]": 4.090703062000102, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget[json]": 0.0012425450004229788, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget[query]": 0.0012268250002307468, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget[smithy-rpc-v2-cbor]": 0.0012336770000729302, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions[json]": 2.10566807000032, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions[query]": 2.1217468469994856, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions[smithy-rpc-v2-cbor]": 2.1227656890000617, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[json]": 0.057849103000080504, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[query]": 0.06389475800006039, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[smithy-rpc-v2-cbor]": 0.0660539560003599, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_parallel_put_metric_data_list_metrics": 0.2721236060001502, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms[json]": 0.09896848900007171, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms[query]": 0.0959065849997387, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms[smithy-rpc-v2-cbor]": 0.09274994299948958, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm[json]": 10.609439121999912, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm[query]": 10.62192488599976, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm[smithy-rpc-v2-cbor]": 10.610445672000424, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[json]": 0.09014131399999314, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[query]": 0.09353315600037604, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[smithy-rpc-v2-cbor]": 0.08795375600038824, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_gzip_with_query_protocol": 0.031797112000276684, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[json]": 0.04909554699997898, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[query]": 0.05382173400039392, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[smithy-rpc-v2-cbor]": 0.05160453600001347, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[json]": 0.030093587000010302, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[query]": 0.03787106000027052, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[smithy-rpc-v2-cbor]": 0.03352613399965776, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc[json]": 0.03248876299994663, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc[query]": 0.0360369059999357, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc[smithy-rpc-v2-cbor]": 0.03371178100042016, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_raw_metric_data_internal_endpoint": 0.027582355000049574, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[json]": 2.3574861120000605, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[query]": 2.351572683000086, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[smithy-rpc-v2-cbor]": 2.36579844400012, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[json]": 0.03498397199973624, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[query]": 0.035157470000285684, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[smithy-rpc-v2-cbor]": 0.04242645500016806, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[json]": 0.1270491889999903, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[query]": 0.13248689000010927, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[smithy-rpc-v2-cbor]": 0.1322659659999772, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[json]": 4.707727161999628, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[query]": 4.715214482999727, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[smithy-rpc-v2-cbor]": 4.7695376299998316, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_error": 2.7700554059999547, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_successful": 35.651009464, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSQSMetrics::test_alarm_number_of_messages_sent": 61.39199581700001, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSqsApproximateMetrics::test_sqs_approximate_metrics": 61.20439586499998, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_binary": 0.12465600800004495, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items": 0.10691843899996911, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items_streaming": 1.1594485540000505, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_existing_table": 0.1410978839999757, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_matching_schema": 0.10197668299991847, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_binary_data_with_stream": 2.3523887780000337, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_continuous_backup_update": 0.2767921720000004, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_create_duplicate_table": 0.11419986199996401, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_data_encoding_consistency": 0.968967919000022, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_delete_table": 0.153080546999945, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_batch_execute_statement": 0.1571138259999998, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_class": 0.21218027400004758, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_partial_sse_specification": 0.23494120700001986, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_sse_specification": 0.07104855099998986, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_statement_empy_parameter": 0.10863066599995364, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_transaction": 0.3029588950000175, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_get_batch_items": 0.09340237899999693, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_idempotent_writing": 0.14373054300000376, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_partiql_missing": 0.1282570770000575, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_pay_per_request": 0.04085504099998616, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_records_with_update_item": 0.001584020999985114, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_shard_iterator": 0.9230693839999731, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_stream_view_type": 1.3593261270000312, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_describe_with_exclusive_start_shard_id": 0.8158256819999679, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_shard_iterator_format": 2.894835404000048, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_update_table_without_sse_specification_change": 0.10831990900004485, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_with_kinesis_stream": 1.5318924630000197, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_empty_and_binary_values": 0.10278530099998306, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables": 0.10346463299998732, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables_version_2019": 0.5214963909999142, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PAY_PER_REQUEST]": 0.3132016199999157, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PROVISIONED]": 0.28893174500007035, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_invalid_query_index": 0.08320743500001981, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_kinesis_streaming_destination_crud": 0.5217216089999965, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_large_data_download": 0.45255535199993346, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_list_tags_of_resource": 0.10883556299995689, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_more_than_20_global_secondary_indexes": 0.361744986999895, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_multiple_update_expressions": 0.2422053500000061, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_non_ascii_chars": 2.6268016579999767, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_nosql_workbench_localhost_region": 0.07988597399997843, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_query_on_deleted_resource": 0.14557529799998292, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_in_put_item": 0.14289347799996222, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_on_conditions_check_failure": 0.20717741000004253, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_stream_destination_records": 14.016551161999985, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_streams_on_global_tables": 1.2724665430000073, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live": 0.258387250999931, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live_deletion": 0.49361070999998446, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_get_items": 0.11351019199997836, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming": 1.274445688999947, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming_for_different_tables": 1.1865290610000443, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_binary_data": 0.12717677700004515, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_canceled": 0.11547195800005738, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_items": 0.1473114740000483, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_local_secondary_index": 0.13604469900002414, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_query_index": 0.11391803400005074, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_enable_kinesis_streaming_destination": 0.0013837260000286733, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_non_existent_stream": 0.014922376000015447, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_stream_spec_and_region_replacement": 2.3438293310000518, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_table_v2_stream": 4.10500811199995, + "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3": 0.5140941900000371, + "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3_validation": 0.20375748300000396, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_route_table_association": 0.7572834110000031, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-id_manager]": 0.0684490290000781, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-tag]": 0.06992147099998647, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-id_manager]": 0.05654805900002202, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-tag]": 0.06451198099995281, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id": 0.06292141600005152, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id_and_vpc_id": 0.06391890999992711, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_tags": 0.055064076000007844, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_endpoint": 0.14662980899998956, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_with_custom_id": 0.051759314999969774, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpc_endpoints_with_filter": 0.6982113179999487, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpn_gateways_filter_by_vpc": 0.33465864499999043, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_get_security_groups_for_vpc": 0.3494684590000361, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[id]": 0.07722742999999355, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[name]": 0.05744512499995835, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_reserved_instance_api": 0.035505785999930595, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vcp_peering_difference_regions": 0.8950329499999725, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vpc_endpoint_dns_names": 0.2526122849999979, + "tests/aws/services/ec2/test_ec2.py::test_create_specific_vpc_id": 0.028391089000081138, + "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_ids": 0.24859146099998952, + "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_names": 0.24854252899996254, + "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filters": 0.2573725899999886, + "tests/aws/services/ec2/test_ec2.py::test_pickle_ec2_backend": 2.112566402000027, + "tests/aws/services/ec2/test_ec2.py::test_raise_create_volume_without_size": 0.01804233400002886, + "tests/aws/services/ec2/test_ec2.py::test_raise_duplicate_launch_template_name": 0.03537818300003437, + "tests/aws/services/ec2/test_ec2.py::test_raise_invalid_launch_template_name": 0.011758012999962375, + "tests/aws/services/ec2/test_ec2.py::test_raise_modify_to_invalid_default_version": 0.03596424500000239, + "tests/aws/services/ec2/test_ec2.py::test_raise_when_launch_template_data_missing": 0.012061418999962825, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_domain": 0.0012421010000593924, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_existing_domain_causes_exception": 0.001334154000005583, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_describe_domains": 0.001283448000037879, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_domain_version": 0.0012855030000196166, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_version_for_domain": 0.001325026000017715, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_versions": 0.00132579699999269, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_list_versions": 0.0014102340000476943, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_path_endpoint_strategy": 0.0013147859999662614, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_update_domain_config": 0.0012909220000096866, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth0]": 0.1949911010000278, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth1]": 0.1015455120000297, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth2]": 0.1020841179999934, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_invalid_parameters": 0.014765839999995478, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_name_validation": 0.046085833999995884, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[api-key]": 0.06025992699994731, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[basic]": 0.06161780299999009, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[oauth]": 0.060985553999955755, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection": 0.05576436700005161, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_invalid_parameters": 0.015537031000008028, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_name_validation": 0.015239974999985861, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params0]": 0.049214429000016935, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params1]": 0.04953966600004378, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params2]": 0.05122310799998786, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_delete_connection": 0.10599839199988992, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_list_connections": 0.04971785700007558, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_update_connection": 0.10198837900003355, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[custom]": 0.09932487499992249, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[default]": 0.06459558099999185, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_unknown_event_bus": 0.015450603000033425, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[custom]": 0.12262453900007131, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[default]": 0.10177178499998263, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_delete_archive_error_unknown_archive": 0.014466796000021986, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_describe_archive_error_unknown_archive": 0.014359471000034318, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_error_unknown_source_arn": 0.015051336999931664, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[custom]": 0.09812634000002163, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[default]": 0.06349232700006269, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-custom]": 0.5817773789999592, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-default]": 0.5432251150000411, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-custom]": 0.5777327320000154, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-default]": 0.5601550719999864, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[custom]": 0.1080779399999301, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[default]": 0.08966450900004475, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[custom]": 0.09390853100001095, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[default]": 0.06468560600001183, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_update_archive_error_unknown_archive": 0.001308263999931114, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_describe_replay_error_unknown_replay": 0.014317768000012165, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replay_with_limit": 0.21953543799997988, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_event_source_arn": 0.1089656939999486, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_prefix": 0.1656955880000055, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[custom]": 0.0012783880000029058, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[default]": 0.0012826750000272114, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_different_archive": 0.13541886500001965, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_name_same_archive": 0.07667484799998192, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[0]": 0.07195342900001833, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[10]": 0.0719420839999998, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_archive": 0.01571794200003751, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_event_bus": 0.09824061400001938, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::tests_concurrency_error_too_many_active_replays": 0.0013359960000229876, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions0]": 0.045992018999982065, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions1]": 0.12144351000000597, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions0]": 0.04851487199999838, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions1]": 0.15294600000004266, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_multiple_event_buses_same_name": 0.046447419999935846, + "tests/aws/services/events/test_events.py::TestEventBus::test_delete_default_event_bus": 0.01436366600000838, + "tests/aws/services/events/test_events.py::TestEventBus::test_describe_delete_not_existing_event_bus": 0.02389642000002823, + "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_limit": 0.23957604200001015, + "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_prefix": 0.0806352260000267, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[domain]": 0.3057911530000297, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[path]": 0.30398104800002557, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[standard]": 0.3098361229999682, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_nonexistent_event_bus": 0.1733256360000155, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_to_default_eventbus_for_custom_eventbus": 1.7498588950000453, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[custom]": 0.29645375900003046, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[default]": 0.0978406570000061, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission_non_existing_event_bus": 0.013903725000034228, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[custom]": 0.09485938899990742, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[default]": 0.07049043400007804, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-custom]": 0.04519672899999705, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-default]": 0.02376618799996777, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-custom]": 0.05251923399998759, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-default]": 0.030126171000006252, + "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_nested": 10.245785126000044, + "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_with_values_in_array": 5.301240906999965, + "tests/aws/services/events/test_events.py::TestEventRule::test_delete_rule_with_targets": 0.08178058499993313, + "tests/aws/services/events/test_events.py::TestEventRule::test_describe_nonexistent_rule": 0.01584935700003598, + "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[custom]": 0.10050070899995944, + "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[default]": 0.06425877699996363, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[custom]": 0.23041737200003354, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[default]": 0.17001968300007775, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[custom]": 0.12756943300001922, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[default]": 0.09593197600003123, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[custom]": 0.29425250799994274, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[default]": 0.27394962799996847, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_with_limit": 0.23466879099999005, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_pattern_to_single_matching_rules_single_target": 7.414126908000014, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_different_targets": 0.5653955709999536, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_single_target": 18.488806020000084, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_single_matching_rules_single_target": 10.529319838999982, + "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[custom]": 0.08993537700007437, + "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[default]": 0.05956947600003559, + "tests/aws/services/events/test_events.py::TestEventRule::test_put_multiple_rules_with_same_name": 0.08798471199997948, + "tests/aws/services/events/test_events.py::TestEventRule::test_update_rule_with_targets": 0.10443374600004063, + "tests/aws/services/events/test_events.py::TestEventTarget::test_add_exceed_fife_targets_per_rule": 0.09960708600004864, + "tests/aws/services/events/test_events.py::TestEventTarget::test_list_target_by_rule_limit": 0.14575810500002717, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[custom]": 0.11710008199997901, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[default]": 0.08418482299998686, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_across_different_rules": 0.12715759500002832, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_single_rule": 0.0911191750000171, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_across_different_rules": 0.1271466040000746, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_single_rule": 0.08317755299998453, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_target_id_validation": 0.10300018500004171, + "tests/aws/services/events/test_events.py::TestEvents::test_create_connection_validations": 0.014047441000002436, + "tests/aws/services/events/test_events.py::TestEvents::test_events_written_to_disk_are_timestamp_prefixed_for_chronological_ordering": 0.0012524599999892416, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[ARRAY]": 0.013905276999992111, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[MALFORMED_JSON]": 0.014060126000003947, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[SERIALIZED_STRING]": 0.013834785000085503, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[STRING]": 0.014338255000097888, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_with_too_big_detail": 0.018754596999997375, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail": 0.013726813000005222, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail_type": 0.01585790500001849, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[custom]": 0.04626658199998701, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[default]": 0.016297244999918803, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_response_entries_order": 0.3081194439999422, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_time": 0.33147174300006554, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_target_delivery_failure": 1.1629077079999774, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_time_field": 0.19857749799996327, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_without_source": 0.013793427000052816, + "tests/aws/services/events/test_events_cross_account_region.py::TestEventsCrossAccountRegion::test_put_events[custom-account]": 0.1648213139999939, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-account]": 0.4739626360000102, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region]": 0.4857943319999549, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region_account]": 0.4662358980000363, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-account]": 0.5073404420000429, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region]": 0.5166387740000573, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region_account]": 0.5149933709999459, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path": 0.19800168599994095, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_max_level_depth": 0.19938683700002002, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_multiple_targets": 0.31752255099996773, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail0]": 0.20137049299995624, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail1]": 0.1998306479999883, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" multiple list items\"]": 0.23755233299999645, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item multiple list items system account id payload user id\"]": 0.23624025000009397, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item\"]": 0.23853842599999098, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Payload of with path users-service/users/ and \"]": 0.24032532399996853, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : \"\"}]": 0.23494661699999142, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : }]": 0.2391056989999356, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"nested\": {\"level1\": {\"level2\": {\"level3\": \"users-service/users/\"} } }, \"bod\": \"\"}]": 0.23673493699993742, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": }]": 0.23582404200004703, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": [, \"hardcoded\"]}]": 0.23271717299996908, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"id\": , \"body\": }]": 0.23206338500000356, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"multi_replacement\": \"users//second/\"}]": 0.2382915410000237, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"singlelistitem\": }]": 0.2397499350000203, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"not_valid\": \"users-service/users/\", \"bod\": }]": 5.1610035199999515, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"payload\": \"\"}]": 5.165831165000043, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"singlelistitem\": \"\"}]": 5.159927456000048, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[\"Message containing all pre defined variables \"]": 0.23050062699996943, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[{\"originalEvent\": , \"originalEventJson\": }]": 0.2217760959999282, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_json": 0.4963251460000606, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"Event of type, at time , info extracted from detail \"]": 0.4242710249999959, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"{[/Check with special starting characters for event of type\"]": 0.4281432699999641, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_missing_keys": 0.14151847699992004, + "tests/aws/services/events/test_events_inputs.py::test_put_event_input_path_and_input_transformer": 0.10300098599998364, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_array_event_payload": 0.014769224000019676, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays]": 0.01473700399998279, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_NEG]": 0.016250972999955593, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_EXC]": 0.07737650000001395, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_null_NEG]": 0.01444279300000062, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean]": 0.013766240999984802, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean_NEG]": 0.014040590999911728, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_many_rules]": 0.014392761000010523, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match]": 0.015076067999984843, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match_NEG]": 0.016463436999970327, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or]": 0.014643366999962382, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or_NEG]": 0.014216971999985617, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase]": 0.013837410000007822, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_EXC]": 0.07429653500003042, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_NEG]": 0.013719434999984514, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list]": 0.014650921000054495, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_EXC]": 0.07397402199995895, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_NEG]": 0.014319102999991173, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number]": 0.014844083000014052, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_NEG]": 0.014242549000016425, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list]": 0.01709955199999058, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list_NEG]": 0.016098084999953244, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_zero]": 0.014260070999966956, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string]": 0.015044487999944067, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_NEG]": 0.01476335500001369, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list]": 0.02068115499992018, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_NEG]": 0.014804909999952542, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null]": 0.014085175000104755, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix]": 0.01426160599993409, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_NEG]": 0.015561771000022873, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_empty_EXC]": 0.07428485599996293, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_ignorecase_EXC]": 0.07523379399998475, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_EXC]": 0.07527784200004817, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list]": 0.014898268000024473, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_NEG]": 0.021546038999986195, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_type_EXC]": 0.07689632799997526, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix]": 0.014166528000032486, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_NEG]": 0.014248637000036979, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_empty_EXC]": 0.07367630100003453, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_ignorecase_EXC]": 0.07439290799999299, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_int_EXC]": 0.07423224000001483, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list]": 0.014122725999982322, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_NEG]": 0.014761318000012125, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_type_EXC]": 0.07548682000003737, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard]": 0.015540506999968784, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_NEG]": 0.014257317999977204, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_empty]": 0.015672594999898593, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list]": 0.01434771399993906, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_NEG]": 0.014598369999987426, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_type_EXC]": 0.07524496899998212, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_type_EXC]": 0.07373897399997986, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists]": 0.013918073000013464, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_NEG]": 0.015583042999992358, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false]": 0.01442360800001552, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false_NEG]": 0.014009017000034873, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase]": 0.016272308999987217, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_EXC]": 0.07391722899996012, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_NEG]": 0.014096141000038642, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty]": 0.014554510999971626, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty_NEG]": 0.013844446000064181, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_list_EXC]": 0.07682260000001406, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address]": 0.01576323999995566, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_EXC]": 0.0740842169999496, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_NEG]": 0.013829718000010871, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_ip_EXC]": 0.07894248399998105, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_mask_EXC]": 0.08115277199999582, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_type_EXC]": 0.08098299499999939, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6]": 0.014130528999999115, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_NEG]": 0.015380627999945773, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_bad_ip_EXC]": 0.07352327400002423, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_EXC]": 0.07549371899995094, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and]": 0.01581395400000929, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and_NEG]": 0.014268909000008989, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_number_EXC]": 0.07553367799994248, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_operatorcasing_EXC]": 0.07327252499999304, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_syntax_EXC]": 0.07728201899999476, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix]": 0.014616998999997577, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_NEG]": 0.014207315000021481, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_empty]": 0.014223287000049822, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_ignorecase]": 0.019804637000049752, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_int_EXC]": 0.07477866399995037, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_list_EXC]": 0.07539189300001681, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix]": 0.014445883000007598, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_NEG]": 0.014586162000057357, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_empty]": 0.014782528000012007, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase]": 0.016062568000052124, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase_NEG]": 0.016894706999948994, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_int_EXC]": 0.0773131610000064, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_list_EXC]": 0.072937990000014, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_complex_EXC]": 0.07405863699995052, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_empty_NEG]": 0.016804483999976583, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_int_EXC]": 0.07473737900005517, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_list_EXC]": 0.07462217499994495, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating]": 0.01586677300002748, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating_NEG]": 0.01380465099992989, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating]": 0.01468936699995993, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_NEG]": 0.0151713839999843, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_star_EXC]": 0.07884239999992815, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_simplified]": 0.01608221300000423, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event]": 0.0145057720000068, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event_NEG]": 0.020375953999973717, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern]": 0.014232584000069437, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern_NEG]": 0.014172383999948579, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dynamodb]": 0.013996119000012186, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb]": 0.014197158000001764, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb_NEG]": 0.014888713000004827, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_list_empty_NEG]": 0.017250607999926615, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[int_nolist_EXC]": 0.07519569499999079, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[key_case_sensitive_NEG]": 0.0142199979999873, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[list_within_dict]": 0.014119761999950242, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[minimal]": 0.014175944999976764, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[nested_json_NEG]": 0.015284716000053322, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value]": 0.0225036180000302, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value_NEG]": 0.013955663999979606, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[number_comparison_float]": 0.01940911000002643, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-int-float]": 0.015657472999976108, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-null_NEG]": 0.01408340299997235, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-string_NEG]": 0.014332366000019192, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_case_sensitive_EXC]": 0.07478767399999242, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_multiple_list]": 0.013959129999989273, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-anything-but]": 0.014398189999951683, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists-parent]": 0.014966101000027265, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists]": 0.013758525000071131, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but]": 0.013881814999990638, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but_NEG]": 0.019981379999990168, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[prefix]": 0.014259040000013101, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[sample1]": 0.014815910000038457, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string]": 0.01501801699993166, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_empty]": 0.015583773000059864, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_nolist_EXC]": 0.07485117599998148, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_source": 0.024772417000008318, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_escape_characters": 0.01334152700002278, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_multi_key": 0.012596724000047743, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_with_large_and_complex_payload": 0.026840837999998257, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_event_payload": 0.014060612000037054, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[[\"not\", \"a\", \"dict\", \"but valid json\"]]": 0.07505024699997875, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[this is valid json but not a dict]": 0.07363387800000964, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{\"not\": closed mark\"]": 0.07749024199995347, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{'bad': 'quotation'}]": 0.07488740199994481, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_plain_string_payload": 0.01489648000000443, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_event_with_content_base_rule_in_pattern": 0.2125037749999592, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_anything_but": 5.32403263599997, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_false": 5.262978658999941, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_true": 5.249971863000098, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::test_schedule_cron_target_sqs": 0.0013403819999666666, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 1 * * * *)]": 0.014249778000134938, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 dummy ? * MON-FRI *)]": 0.015488572000094791, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(7 20 * * NOT *)]": 0.013537992999886228, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(71 8 1 * ? *)]": 0.01338709500009827, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(INVALID)]": 0.013556583999957184, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(* * ? * SAT#3 *)]": 0.039524867999944036, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 10 * * ? *)]": 0.03902212600007715, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 12 * * ? *)]": 0.039991844000041965, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 18 ? * MON-FRI *)]": 0.03998815600004946, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT *)]": 0.03945478699984051, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT#3 *)]": 0.03811496100001932, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 8 1 * ? *)]": 0.04056264700011525, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/10 * ? * MON-FRI *)]": 0.039733622000085234, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/15 * * * ? *)]": 0.039946852999946714, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 0-2 ? * MON-FRI *)]": 0.038315744000101404, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 20-23 ? * MON-FRI *)]": 0.039884855999957836, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 5 ? JAN 1-5 2022)]": 0.03990890100010347, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 8-17 ? * MON-FRI *)]": 0.04050064000000475, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 10 ? * 6L 2002-2005)]": 0.03854088899993258, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 12 * * ? *)]": 0.03912135500002023, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(5,35 14 * * ? *)]": 0.040583050999998704, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_scheduled_rule_does_not_trigger_on_put_events": 3.098797039000033, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[ rate(10 minutes)]": 0.011782736999862209, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate( 10 minutes )]": 0.010935715999949025, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate()]": 0.01272831400024188, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(-10 minutes)]": 0.011175413999922057, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(0 minutes)]": 0.01160866400005034, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 days)]": 0.011537399999951958, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 hours)]": 0.012079271999937191, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 minutes)]": 0.01229389299999184, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 MINUTES)]": 0.010918001999925764, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 day)]": 0.012779326999975638, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 hour)]": 0.012875576999931582, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minute)]": 0.01221343200006686, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minutess)]": 0.011718728000005285, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 seconds)]": 0.012360175000026175, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 years)]": 0.011161657000116065, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10)]": 0.013167574000021887, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(foo minutes)]": 0.011234874999900057, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_schedule_rate": 0.0402682519999189, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_scheduled_rule_logs": 0.0014579930000309105, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_put_rule_with_schedule_custom_event_bus": 0.04621812400012004, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_custom_input_target_sqs": 60.11986226500005, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_target_sqs": 0.001341576000072564, + "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_create_event_bus_with_tags": 0.04632428599995819, + "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_list_tags_for_deleted_event_bus": 0.03768204900006822, + "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_list_tags_for_deleted_rule": 0.06985411400000885, + "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_put_rule_with_tags": 0.07012072900010935, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_custom]": 0.08043061799980933, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_default]": 0.025659442999995008, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_custom]": 0.1144373380000161, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_default]": 0.0837225559999979, + "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_event_bus]": 0.04150798400007716, + "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_rule]": 0.039595980999934, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_custom]": 0.07899769299990567, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_default]": 0.05171345799988103, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_custom]": 0.1268474440000773, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_default]": 0.07706992899989018, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth0]": 0.12711244299998725, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth1]": 0.11765732699996079, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth2]": 0.11986999599992032, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiGateway::test_put_events_with_target_api_gateway": 8.15067134700007, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetCloudWatchLogs::test_put_events_with_target_cloudwatch_logs": 0.22258106499998576, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination0]": 0.3131844029999229, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination1]": 0.3492358979999608, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination2]": 0.3262127779998991, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetFirehose::test_put_events_with_target_firehose": 1.1573409640001273, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetKinesis::test_put_events_with_target_kinesis": 0.9200141759999951, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda": 4.2721585279998635, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entries_partial_match": 4.3310349420000875, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entry": 4.34329892400001, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[domain]": 0.25569314500012297, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[path]": 0.25889670999993086, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[standard]": 0.27803015000006326, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs": 0.2082200599998032, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs_event_detail_match": 5.265115494999918, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetStepFunctions::test_put_events_with_target_statefunction_machine": 3.082819936000078, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_api_gateway": 5.745362673999921, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination0]": 4.408676894999985, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination1]": 4.452466942000115, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination2]": 4.427357289000042, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_lambda": 4.293536941999946, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_elasticsearch_s3_backup": 0.0014430450000872952, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source": 35.28502605099993, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source_multiple_delivery_streams": 41.521515327999964, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[domain]": 0.0013686949998827913, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[path]": 0.0013377169999557736, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[port]": 0.00134457100000418, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_s3_as_destination_with_file_extension": 1.1944170269999859, + "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[False]": 0.07698860999994395, + "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[True]": 1.6312629699998524, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_role_with_malformed_assume_role_policy_document": 0.020012918000020363, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_add_permission_boundary_afterwards": 0.11537089899991315, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_with_permission_boundary": 0.09831113200004893, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_role": 0.12178350900001078, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_root": 0.04201659000000291, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_user": 0.15553203299998586, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_role_with_path_lifecycle": 0.1076197659999707, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_detach_role_policy": 0.08618333200024608, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_iam_role_to_new_iam_user": 0.1061101380000764, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_describe_role": 0.13644321700007822, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_role_with_assume_role_policy": 0.1310539549999703, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_user_with_tags": 0.033444476999875405, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_delete_non_existent_policy_returns_no_such_entity": 0.016811741000083202, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_instance_profile_tags": 0.15954212899998765, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_list_roles_with_permission_boundary": 0.15987779299996419, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_recreate_iam_role": 0.053607905000149, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_role_attach_policy": 0.3554823359999091, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[ecs.amazonaws.com-AWSServiceRoleForECS]": 0.0013373570000112522, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[eks.amazonaws.com-AWSServiceRoleForAmazonEKS]": 0.0012612750000471351, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[group]": 0.18022979400018357, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[role]": 0.20425840000018525, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[user]": 0.21684722600002715, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_update_assume_role_policy": 0.10182629000007637, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_user_attach_policy": 0.36940406000007897, + "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_group_policy_encoding": 0.06031510100001469, + "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_role_policy_encoding": 0.16878935800002637, + "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_user_policy_encoding": 0.08903085299982649, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_already_exists": 0.0377422529999194, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_deletion": 9.443803702999958, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[accountdiscovery.ssm.amazonaws.com]": 0.23752084800003104, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[acm.amazonaws.com]": 0.236425152000038, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[appmesh.amazonaws.com]": 0.23591932500005441, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling-plans.amazonaws.com]": 0.23790745099995547, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling.amazonaws.com]": 0.23586615700003222, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[backup.amazonaws.com]": 0.23452704400006041, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[batch.amazonaws.com]": 0.23536637499989865, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cassandra.application-autoscaling.amazonaws.com]": 0.233326347000002, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cks.kms.amazonaws.com]": 0.24218515699988075, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cloudtrail.amazonaws.com]": 0.24111946700008957, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[codestar-notifications.amazonaws.com]": 0.24057556000013847, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[config.amazonaws.com]": 0.24120188299991696, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[connect.amazonaws.com]": 0.2400878799999191, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms-fleet-advisor.amazonaws.com]": 0.23661605099982808, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms.amazonaws.com]": 0.23681608600008985, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[docdb-elastic.amazonaws.com]": 0.23472724700013714, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2-instance-connect.amazonaws.com]": 0.23839326400002392, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2.application-autoscaling.amazonaws.com]": 0.23708535100001882, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecr.amazonaws.com]": 0.23807324699998844, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecs.amazonaws.com]": 0.25053279400003703, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-connector.amazonaws.com]": 0.24353402600002028, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-fargate.amazonaws.com]": 0.2515816410000298, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-nodegroup.amazonaws.com]": 0.23389030799989996, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks.amazonaws.com]": 0.24098162399991452, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticache.amazonaws.com]": 0.2377574040001491, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticbeanstalk.amazonaws.com]": 0.23510221300011835, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticfilesystem.amazonaws.com]": 1.1003349629999093, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticloadbalancing.amazonaws.com]": 0.2334998760001099, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[email.cognito-idp.amazonaws.com]": 0.23660922200008372, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emr-containers.amazonaws.com]": 0.23703105900005994, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emrwal.amazonaws.com]": 0.23604983900008847, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[fis.amazonaws.com]": 0.23631183300005887, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[grafana.amazonaws.com]": 0.23927999699992597, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[imagebuilder.amazonaws.com]": 0.23719040599996788, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[iotmanagedintegrations.amazonaws.com]": 0.2949682710000161, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafka.amazonaws.com]": 0.23562907000007272, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafkaconnect.amazonaws.com]": 0.2383439750000207, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lakeformation.amazonaws.com]": 0.23927425899989885, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lex.amazonaws.com]": 0.29387328500013155, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lexv2.amazonaws.com]": 0.23612717700018493, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lightsail.amazonaws.com]": 0.23766957700001967, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[m2.amazonaws.com]": 0.2365146619999905, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[memorydb.amazonaws.com]": 0.2364069699999618, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mq.amazonaws.com]": 0.23722694399998545, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mrk.kms.amazonaws.com]": 0.23770990200000597, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[notifications.amazonaws.com]": 0.23669516200004637, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[observability.aoss.amazonaws.com]": 0.23399929600009273, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opensearchservice.amazonaws.com]": 0.23962439000013092, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.apigateway.amazonaws.com]": 0.236731825999982, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.emr-serverless.amazonaws.com]": 0.24132388399993943, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsdatasync.ssm.amazonaws.com]": 0.24263866400008283, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsinsights.ssm.amazonaws.com]": 0.235813844000063, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[pullthroughcache.ecr.amazonaws.com]": 0.24081646599995565, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ram.amazonaws.com]": 0.23659848799991323, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rds.amazonaws.com]": 0.2377248420000342, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[redshift.amazonaws.com]": 0.23671476699996674, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.cassandra.amazonaws.com]": 0.2380671699999084, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.ecr.amazonaws.com]": 0.24236964600004285, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[repository.sync.codeconnections.amazonaws.com]": 0.23834563999992042, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[resource-explorer-2.amazonaws.com]": 0.2555888670000286, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rolesanywhere.amazonaws.com]": 0.2742257720000225, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[s3-outposts.amazonaws.com]": 0.24921174299993254, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ses.amazonaws.com]": 0.2401779000000488, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[shield.amazonaws.com]": 0.23697912300008284, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-incidents.amazonaws.com]": 0.24215020600013304, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-quicksetup.amazonaws.com]": 0.24041808099991613, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm.amazonaws.com]": 0.2410428270001148, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[sso.amazonaws.com]": 0.24290620599992963, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[vpcorigin.cloudfront.amazonaws.com]": 0.2394068729998935, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[waf.amazonaws.com]": 0.24088652500006447, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[wafv2.amazonaws.com]": 0.24069265299988274, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[autoscaling.amazonaws.com]": 0.12000415299996803, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[connect.amazonaws.com]": 0.12024076499994862, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[lexv2.amazonaws.com]": 0.12027017900015835, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[accountdiscovery.ssm.amazonaws.com]": 0.015942614000096, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[acm.amazonaws.com]": 0.015669434999949772, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[appmesh.amazonaws.com]": 0.016091824000000088, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[autoscaling-plans.amazonaws.com]": 0.016057498999998643, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[backup.amazonaws.com]": 0.015736869000079423, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[batch.amazonaws.com]": 0.015662854000083826, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cassandra.application-autoscaling.amazonaws.com]": 0.01547597500007214, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cks.kms.amazonaws.com]": 0.015872153999907823, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cloudtrail.amazonaws.com]": 0.015592650000030517, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[codestar-notifications.amazonaws.com]": 0.01605762999986382, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[config.amazonaws.com]": 0.015653174000021863, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms-fleet-advisor.amazonaws.com]": 0.015661089999980504, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms.amazonaws.com]": 0.015666529999975864, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[docdb-elastic.amazonaws.com]": 0.01591199700010293, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2-instance-connect.amazonaws.com]": 0.015887482999914937, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2.application-autoscaling.amazonaws.com]": 0.015860381000038615, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecr.amazonaws.com]": 0.01583890400013388, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecs.amazonaws.com]": 0.015879390000009153, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-connector.amazonaws.com]": 0.015888621000044623, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-fargate.amazonaws.com]": 0.015840406999927836, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-nodegroup.amazonaws.com]": 0.01621942199994919, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks.amazonaws.com]": 0.016415623999932905, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticache.amazonaws.com]": 0.015674152999963553, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticbeanstalk.amazonaws.com]": 0.015953566000007413, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticfilesystem.amazonaws.com]": 0.015871371999992334, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticloadbalancing.amazonaws.com]": 0.015740872000037598, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[email.cognito-idp.amazonaws.com]": 0.015461296999887963, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emr-containers.amazonaws.com]": 0.015745907000109582, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emrwal.amazonaws.com]": 0.016825304000008146, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[fis.amazonaws.com]": 0.01658694100001412, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[grafana.amazonaws.com]": 0.015877273999990393, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[imagebuilder.amazonaws.com]": 0.016222252000034132, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[iotmanagedintegrations.amazonaws.com]": 0.015717454999958136, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafka.amazonaws.com]": 0.015922987000067224, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafkaconnect.amazonaws.com]": 0.016659343999890552, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lakeformation.amazonaws.com]": 0.015837504000160152, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lex.amazonaws.com]": 0.01560044600000765, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lightsail.amazonaws.com]": 0.016531686000121226, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[m2.amazonaws.com]": 0.0158204779999096, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[memorydb.amazonaws.com]": 0.015830616000016562, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mq.amazonaws.com]": 0.015739126999960718, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mrk.kms.amazonaws.com]": 0.015962194999929125, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[notifications.amazonaws.com]": 0.015920342999947934, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[observability.aoss.amazonaws.com]": 0.01599846899989643, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opensearchservice.amazonaws.com]": 0.015627758000050562, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.apigateway.amazonaws.com]": 0.015717695999796888, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.emr-serverless.amazonaws.com]": 0.01593316200001027, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsdatasync.ssm.amazonaws.com]": 0.015808354000114377, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsinsights.ssm.amazonaws.com]": 0.015787648000014087, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[pullthroughcache.ecr.amazonaws.com]": 0.01600118599981215, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ram.amazonaws.com]": 0.015932376999899134, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rds.amazonaws.com]": 0.016099087000043255, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[redshift.amazonaws.com]": 0.015897878000032506, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.cassandra.amazonaws.com]": 0.018563428000106796, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.ecr.amazonaws.com]": 0.015469245999952363, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[repository.sync.codeconnections.amazonaws.com]": 0.015649419000169473, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[resource-explorer-2.amazonaws.com]": 0.015772808000065197, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rolesanywhere.amazonaws.com]": 0.015615754000009474, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[s3-outposts.amazonaws.com]": 0.01741100800006734, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ses.amazonaws.com]": 0.016188385000191374, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[shield.amazonaws.com]": 0.015915174000042498, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-incidents.amazonaws.com]": 0.015603532000000087, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-quicksetup.amazonaws.com]": 0.015793776000123216, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm.amazonaws.com]": 0.01594392499987407, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[sso.amazonaws.com]": 0.015998808999938774, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[vpcorigin.cloudfront.amazonaws.com]": 0.016663566000033825, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[waf.amazonaws.com]": 0.015498996999895098, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[wafv2.amazonaws.com]": 0.01619244999994862, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_service": 0.08443130000000565, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_user": 0.025679559999957746, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_delete_user_after_service_credential_created": 0.08533676000001833, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_id_match_user_mismatch": 0.10496085199997651, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_invalid_update_parameters": 0.08476566499996352, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_list_service_specific_credential_different_service": 0.08514647499998773, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[cassandra.amazonaws.com]": 0.11508663900008287, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[codecommit.amazonaws.com]": 0.11812451799994506, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[satisfiesregexbutstillinvalid]": 0.10094686800005093, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[totally-wrong-credential-id-with-hyphens]": 0.10170176799999808, + "tests/aws/services/iam/test_iam.py::TestRoles::test_role_with_tags": 0.07751543900008073, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_add_tags_to_stream": 0.6662428759999557, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_blob_handling": 0.6631036759999915, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_exceptions": 0.11803236399998696, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_shard_count": 0.6587421030001224, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_stream_name_raises": 0.03923553300012372, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records": 0.7337191930000699, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_empty_stream": 0.6706727120000551, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_next_shard_iterator": 0.6840778329999466, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_shard_iterator_with_surrounding_quotes": 0.6891836749999811, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_record_lifecycle_data_integrity": 0.8482045110000627, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_resource_policy_crud": 0.9541065870000693, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_stream_consumers": 1.3269096259998605, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard": 4.585791513999993, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_cbor_at_timestamp": 1.3565622189998976, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_timeout": 6.335671786000148, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp": 4.48483409500011, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp_cbor": 0.650991071000135, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_sequence_number_as_iterator": 4.540457367000158, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisJavaSDK::test_subscribe_to_shard_with_java_sdk_v2_lambda": 9.73590450600011, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_add_tags_to_stream": 0.6691482530001167, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_cbor_blob_handling": 0.6684586760000002, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_cbor_exceptions": 0.12082172099997024, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_shard_count": 0.6557085149999011, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_stream_name_raises": 0.039067087000148604, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records": 0.740460434000056, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_empty_stream": 0.6674212169999691, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_next_shard_iterator": 0.6775948690000178, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_shard_iterator_with_surrounding_quotes": 0.6706075799999098, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_record_lifecycle_data_integrity": 0.8466757350000762, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_resource_policy_crud": 0.8628125239999918, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_stream_consumers": 1.2878686159999688, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard": 4.541976765000072, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_cbor_at_timestamp": 4.376442415000042, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_timeout": 6.331846114999962, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp": 4.470410651999941, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp_cbor": 0.644519783000078, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_sequence_number_as_iterator": 4.482200308000074, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisPythonClient::test_run_kcl": 37.146918579000044, + "tests/aws/services/kms/test_kms.py::TestKMS::test_all_types_of_key_id_can_be_used_for_encryption": 0.07525084500014145, + "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_delete_deleted_key": 0.03903651800010266, + "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_use_disabled_or_deleted_keys": 0.0624945370000205, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_alias": 0.09774945000003754, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_custom_key_asymmetric": 0.04301571399992099, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_invalid_key": 0.035128239999949074, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_same_name_two_keys": 0.09220739500005948, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_valid_key": 0.05784264400006123, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key": 0.10303081600000041, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_id": 0.030442850999861548, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_hmac": 0.04035968599987427, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_symmetric_decrypt": 0.03237497000009171, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[lowercase_prefix]": 0.0717702270000018, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[too_long_key]": 0.07411716600017826, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[uppercase_prefix]": 0.07402220799986026, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_tag_and_untag": 0.10435492299995985, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_too_many_tags_raises_error": 0.07368844599989188, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_list_delete_alias": 0.07131185199989432, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_multi_region_key": 0.1471205140002212, + "tests/aws/services/kms/test_kms.py::TestKMS::test_derive_shared_secret": 0.19598962299994582, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_and_list_sign_key": 0.038187810999943395, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_with_alias_arn": 0.17071232100011002, + "tests/aws/services/kms/test_kms.py::TestKMS::test_disable_and_enable_key": 0.07048132900001747, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.12224947100003192, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.03639782900006594, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt_encryption_context": 0.1925487860000885, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_1]": 0.12749303900000086, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_256]": 0.10915568300003997, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_1]": 0.34787258199992266, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_256]": 0.4810007600001427, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_1]": 0.18832249400009005, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_256]": 0.5718246609999369, + "tests/aws/services/kms/test_kms.py::TestKMS::test_error_messaging_for_invalid_keys": 0.284109568999952, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_224-HMAC_SHA_224]": 0.11275924299991402, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_256-HMAC_SHA_256]": 0.11804133400005412, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_384-HMAC_SHA_384]": 0.1258368580000706, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_512-HMAC_SHA_512]": 0.11688147899985779, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1024]": 0.07222964099992168, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[12]": 0.07562730200004353, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1]": 0.0746975490000068, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[44]": 0.07282145599992873, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[91]": 0.07248519300003409, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[0]": 0.07411540199996125, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[1025]": 0.07348865300002672, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[None]": 0.08255926600008934, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_does_not_exist": 0.10782286599987856, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_in_different_region": 0.129439634000164, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_invalid_uuid": 0.09389854699998068, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_parameters_for_import": 0.21449805000008837, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_public_key": 0.08615242999997008, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_put_list_key_policies": 0.057031250999898475, + "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key": 0.10917485100003432, + "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key_invalid_operations": 0.08925669500001732, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_asymmetric": 0.2378090910001447, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P256]": 0.2829033510000727, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P384]": 0.21776286699991942, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P521]": 0.8178569530000459, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_SECG_P256K1]": 0.25918438600001537, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_224]": 0.5572874010000533, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_256]": 1.0659213359998603, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_384]": 0.6776559990000806, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_512]": 0.7964541060000556, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_rsa_aes_wrap_sha256": 2.1396233709999706, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_symmetric": 0.2876078530000541, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_224-HMAC_SHA_256]": 0.09140849200002776, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_256-INVALID]": 0.0921107710000797, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_key_usage": 0.9693602549999696, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_256-some different important message]": 0.10348100199996679, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_512-some important message]": 0.09858694600006856, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-INVALID-some important message]": 0.09800494100011292, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[180]": 0.10370539900009135, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[90]": 0.10208782999995947, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_status": 0.06748645199991188, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_encryption_decryption": 0.12994205799998326, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_limits": 0.2638635890000387, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_with_long_tag_value_raises_error": 0.07826206200002161, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_aliases_of_key": 0.07070787499992548, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_grants_with_invalid_key": 0.015868654999962928, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_keys": 0.029751414999964254, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_retirable_grants": 0.07939735200011455, + "tests/aws/services/kms/test_kms.py::TestKMS::test_non_multi_region_keys_should_not_have_multi_region_properties": 0.14488045799998872, + "tests/aws/services/kms/test_kms.py::TestKMS::test_plaintext_size_for_encrypt": 0.09962549800002307, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.2120501609999792, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.1245167820000006, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_incorrect_source_key": 0.11213603900000635, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_invalid_destination_key": 0.05133242400006566, + "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_key": 0.4439463340000884, + "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_replica_key_should_fail": 0.11052289999997811, + "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_id_and_key_id": 0.07566096199991534, + "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_token": 0.0842235669999809, + "tests/aws/services/kms/test_kms.py::TestKMS::test_revoke_grant": 0.07561201099997561, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_modifies_key_material": 0.10420845999999528, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_is_disabled": 0.5604534330000206, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_that_does_not_exist": 0.07832112000005509, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_with_imported_key_material": 0.001314804000003278, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_non_symmetric_key": 0.25958028499997, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_disabled": 0.10646822699993663, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_enabled": 0.1269322310000689, + "tests/aws/services/kms/test_kms.py::TestKMS::test_schedule_and_cancel_key_deletion": 0.05642230199998721, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P256-ECDSA_SHA_256]": 0.23560709900004895, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P384-ECDSA_SHA_384]": 0.2543413729999884, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.2250775210000029, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_256]": 0.6319831090000889, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_384]": 0.6755214449998448, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_512]": 0.6329130750000331, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 3.959218524999983, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 3.4174300860000812, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_1]": 0.10718424400010917, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_256]": 0.15189806000000772, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_1]": 0.2191987370000561, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_256]": 0.24307868800008237, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_1]": 0.8285668670001769, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_256]": 0.5098453329999302, + "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_and_untag": 0.11473213700003271, + "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_with_invalid_tag_key": 0.08985565799991946, + "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_key_with_duplicate_tag_keys_raises_error": 0.08984834199986835, + "tests/aws/services/kms/test_kms.py::TestKMS::test_unsupported_rotate_key_on_demand_with_imported_key_material": 0.029957978999959778, + "tests/aws/services/kms/test_kms.py::TestKMS::test_untag_key_partially": 0.10400753700002952, + "tests/aws/services/kms/test_kms.py::TestKMS::test_update_alias": 0.07822793599996203, + "tests/aws/services/kms/test_kms.py::TestKMS::test_update_and_add_tags_on_tagged_key": 0.10498147600003449, + "tests/aws/services/kms/test_kms.py::TestKMS::test_update_key_description": 0.06104831400000421, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P256-ECDSA_SHA_256]": 0.048955079999927875, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P384-ECDSA_SHA_384]": 0.05165268600001127, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.04865010700007133, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_256]": 0.3070599669999865, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_384]": 0.14540169199995034, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_512]": 0.1956617160000178, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 0.7498778799999855, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 1.282845500999997, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key": 0.03913119200001347, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair": 0.10479268699998556, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair_without_plaintext": 0.049937314999965565, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_without_plaintext": 0.03728065300003891, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key": 0.03846625300002415, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair": 0.09385802199994941, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_dry_run": 0.03170913299993572, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext": 0.051331244999914816, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext_dry_run": 0.0531491949999463, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_without_plaintext": 0.031767131000151494, + "tests/aws/services/kms/test_kms.py::TestKMSMultiAccounts::test_cross_accounts_access": 1.0834142170000405, + "tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.py::test_adding_tags": 19.539093563999927, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_deletion_event_source_mapping_with_dynamodb": 6.102337664000061, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_disabled_dynamodb_event_source_mapping": 12.171949401000006, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_duplicate_event_source_mappings": 5.566545625000117, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_filter_type]": 13.738606785000002, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_multiple_filters]": 0.0068561899998940135, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_or_filter]": 12.770586773999867, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[date_time_conversion]": 12.769140345999858, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_false_filter]": 12.759619010000051, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_filter_type]": 12.673679355000104, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[insert_same_entry_twice]": 12.675613689999977, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[numeric_filter]": 12.712465871000177, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[prefix_filter]": 12.671311755000033, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping": 14.70439154600001, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_on_failure_destination_config": 11.222816751999972, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_s3_on_failure_destination": 11.386065805000044, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_sns_on_failure_destination_config": 11.340038573999891, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[[{\"eventName\": [\"INSERT\"=123}]]": 4.473603445000435, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[single-string]": 4.467117740000049, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 14.734696914000097, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 14.766616272999954, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 14.813051587000018, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 14.76264406600012, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[null_item_identifier_failure]": 14.7334870520001, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 14.7123200179999, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failures": 15.036972384999899, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 9.697935729999926, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_dict_success]": 9.710972550999713, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_list_success]": 9.687635306999937, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_batch_item_failure_success]": 9.658695268000201, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_success]": 9.712800831999857, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_esm_with_not_existing_dynamodb_stream": 1.7379020909999099, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisEventFiltering::test_kinesis_event_filtering_json_pattern": 9.351899654000363, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping": 12.123220716000333, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping_multiple_lambdas_single_kinesis_event_stream": 19.481662102999962, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_disable_kinesis_event_source_mapping": 29.27592052700038, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_duplicate_event_source_mappings": 3.447744527000168, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_esm_with_not_existing_kinesis_stream": 1.4251973599998564, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_empty_provided": 11.22090860299977, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_async_invocation": 20.196567155000366, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_on_failure_destination_config": 9.207921322000175, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_s3_on_failure_destination": 9.291112459000033, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_sns_on_failure_destination_config": 9.223555858000054, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_trim_horizon": 26.318767419000324, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-before-ingestion]": 14.269330664000108, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-while-retrying]": 9.279857612000114, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded_discard_records": 19.338719025999808, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 12.22695027099985, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 12.251495277000004, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 12.201116766000041, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 13.21624410100003, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[null_item_identifier_failure]": 12.179542127000332, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 12.199320246000298, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failures": 12.264598445000047, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 7.125527097000031, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_dict_success]": 7.1570520139998735, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_list_success]": 7.157303230000025, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_string_success]": 7.104275113999847, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_batch_item_failure_success]": 7.143257565999875, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_success]": 7.137574090000044, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_duplicate_event_source_mappings": 2.671776353000041, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_event_source_mapping_default_batch_size": 3.487495602000081, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[and]": 6.462416768000139, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[exists]": 6.462338854999871, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-bigger]": 6.484703498000272, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-range]": 6.469228887000099, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-smaller]": 6.469964993000076, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[or]": 6.469548430000032, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-filter]": 0.0016606720000709174, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-matching]": 0.0019218679999539745, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[prefix]": 6.473829879000277, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[single]": 6.448515857999837, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[valid-json-filter]": 6.4730131060002805, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping": 6.448754007000161, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[10000]": 9.586044279000134, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[1000]": 9.61095664200002, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[100]": 9.602327203000186, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[15]": 9.589174010000079, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[10000]": 0.013637287999927139, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[1000]": 9.058388519000118, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[100]": 6.773113706999766, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[20]": 6.453428181999925, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_reserved_concurrency": 8.71703872199987, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_window_size_override": 26.87101008699983, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_update": 11.810413368000127, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[None]": 1.2844043700001748, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter2]": 2.385117679000359, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter3]": 1.2578402739998182, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[simple string]": 1.256579775000091, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_esm_with_not_existing_sqs_queue": 1.2381455900001583, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_failing_lambda_retries_after_visibility_timeout": 19.38961928499998, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_fifo_message_group_parallelism": 63.55886172500004, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_message_body_and_attributes_passed_correctly": 5.2326157200000125, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_redrive_policy_with_failing_lambda": 16.33036967399994, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures": 0.0019617919999745936, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_empty_json_batch_succeeds": 9.600262870000051, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_invalid_result_json_batch_fails": 15.944293937999873, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_on_lambda_error": 10.46249874199998, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_sqs_queue_as_lambda_dead_letter_queue": 6.306468979999863, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_alias_routingconfig": 3.774854516000005, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_lambda_alias_moving": 37.80165147300022, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[1]": 1.7769432549998783, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[2]": 1.7747804759999326, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_function_state": 1.2700841830001082, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_different_iam_keys_environment": 3.807665930999974, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_large_response": 1.6255023819999224, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response": 1.9005019910000556, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response_but_with_custom_limit": 1.6431831930001408, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_large_payloads": 1.863566577000256, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_ignore_architecture": 1.5904794219998166, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[nodejs]": 7.727244825000071, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[python]": 1.6967374900000323, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_host_prefix_api_operation": 13.969504820000111, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_init_environment": 3.3306744290002825, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_no_timeout": 3.658990530999972, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_timed_out_environment_reuse": 0.0018505350001305487, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_with_timeout": 3.6688465920001363, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_mixed_architecture": 0.0017290389998834144, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_arm": 0.0019343320000189124, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_x86": 1.849497695999844, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_ulimits": 1.6839137889999165, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_delete_lambda_during_sync_invoke": 0.00137293299940211, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_recreate_function": 3.453702032000365, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_block": 16.617687976999605, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_crud": 1.283731730000909, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_update": 1.3847263860002386, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_moves_with_alias": 0.002060696000171447, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_scheduling": 8.585584803000074, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency": 2.9287281410001924, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_on_alias": 3.053905483000108, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency": 8.642495405999853, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency_async_queue": 3.6552346879998368, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_provisioned_overlap": 9.618978092000816, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_error": 1.609167341000557, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_exit": 0.001988982000057149, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[body-n\\x87r\\x9e\\xe9\\xb5\\xd7I\\xee\\x9bmt]": 1.353904796000279, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[message-\\x99\\xeb,j\\x07\\xa1zYh]": 1.3548934179998469, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_error": 7.800451548000183, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit": 0.0013810180003019923, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit_segfault": 0.0012109310005143925, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_error": 1.425636245000078, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_timeout": 41.968216714999926, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_wrapper_not_found": 0.0016221579999182723, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[nodejs16.x]": 0.0020073660002708493, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[python3.10]": 0.0018761429996629886, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[nodejs16.x]": 2.317425625999931, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[python3.10]": 2.3033226069997, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event_error": 0.0015690790000917332, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": 2.319506492000073, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": 2.6865782029999536, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": 2.313405053999759, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": 2.6387833010003305, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[nodejs16.x]": 1.6309119879997525, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[python3.10]": 1.623645045999183, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[nodejs16.x]": 15.813433480000413, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[python3.10]": 7.746435490000067, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_qualifier": 1.9019169160001184, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invoke_exceptions": 0.10320941899999525, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_lambda_with_context": 0.0029127870002412237, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_upload_lambda_from_s3": 2.129957241000284, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_delete_function": 1.1653334100001302, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_alias": 1.2100018940000155, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_concurrency": 1.1834457669997391, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_invocation": 1.5702022199998282, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_tags": 1.1938104490000114, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function": 1.1864973680003459, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function_configuration": 1.1722147360001145, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_lambda_layer": 0.1427004250003847, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_list_versions_by_function": 1.1817483820000234, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_publish_version": 1.2331258129997877, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaPermissions::test_lambda_permission_url_invocation": 0.0016648660000555537, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_function_url_with_response_streaming": 7.938405253000155, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_update_function_url_config": 1.4785447689998819, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_default": 2.0410052570000516, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_trim_x_headers": 2.9943600080002852, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[BUFFERED]": 1.93905359900009, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[None]": 1.9279683730001125, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[RESPONSE_STREAM]": 0.012159704000168858, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_form_payload": 1.8806745419999515, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_headers_and_status": 1.6586140920001071, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invalid_invoke_mode": 1.4352354319998994, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[boolean]": 1.8288806030002434, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[dict]": 1.8197115729997222, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[float]": 1.8417397279999932, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response-json]": 1.8216079999999693, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response]": 1.8228076079999482, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[integer]": 1.8499917349997759, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[list-mixed]": 1.8187581490003595, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[string]": 1.8320846260003236, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id": 1.5985798579999937, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id_aliased": 1.6077459610000915, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_exception": 1.8434158059997117, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_non_existing_url": 0.01706250300026113, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_persists_after_alias_delete": 3.9523542629997337, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_async_invoke_queue_upon_function_update": 100.17100790900031, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_function_update_during_invoke": 0.0016117479999593343, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_handler_update": 3.339313079999556, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_versions_with_code_changes": 5.744227653000053, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_async_invoke_with_retry": 11.320661913000095, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_format": 0.0337875189998158, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke": 4.0056264960001045, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke_url": 3.869290203999981, + "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_code_signing_not_found_excs": 1.3611597699998583, + "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_function_code_signing_config": 1.3207078299997193, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings": 0.07984425700010434, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size": 1.5261632329998065, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size_config_update": 7.466087038000069, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_lifecycle": 2.6898677359999965, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_naming": 2.502068691000204, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_deletion": 1.2483986570000525, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_update": 1.2247517570001492, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_notfound_and_invalid_routingconfigs": 1.5069724289999158, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEndpoints::test_s3_code_url[localstack_host0]": 1.1918762670002252, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEndpoints::test_s3_code_url[localstack_host1]": 1.1855227149999337, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEndpoints::test_s3_code_url[localstack_host2]": 1.193306714999835, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_exceptions": 2.9189557659999537, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_lifecycle": 1.4090530159999162, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_filter_criteria_validation": 3.5983861900001557, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_self_managed": 0.001569402000086484, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": 3.4591853359997913, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": 1.9422431769999093, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_exceptions": 0.14435250899987295, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle": 7.758359959000018, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle_delete_function": 6.0902301920000355, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_function_name_variations": 16.181312729999945, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_create_lambda_exceptions": 0.1611859550000645, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_delete_on_nonexisting_version": 1.2679380070001116, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_arns": 2.611127668999643, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_lifecycle": 13.965233762000253, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-create_function]": 0.09690669999986312, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-delete_function]": 0.08042988499983039, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-get_function]": 0.07774769899970124, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-invoke]": 0.0795029760001853, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-create_function]": 0.09718526399979055, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-delete_function]": 0.08033341400005156, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-get_function]": 0.0795279690003099, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-invoke]": 0.08172547399976793, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-create_function]": 0.09317256000008456, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-delete_function]": 0.07956548699985433, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-get_function]": 0.07877618499992423, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-invoke]": 0.07728964300008556, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-create_function]": 0.0915923349998593, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-delete_function]": 0.07934115800003383, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-get_function]": 0.07854317499982244, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-invoke]": 0.00917423299983966, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-create_function]": 0.09439862599992921, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-delete_function]": 0.08110562799993204, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-get_function]": 0.07756306400005997, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-invoke]": 0.07784435799999301, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-create_function]": 0.00993420600002537, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-delete_function]": 0.08318418799990468, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-get_function]": 0.08172020100005284, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-invoke]": 0.010790625999788972, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-create_function]": 0.09322575300006974, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-delete_function]": 0.07974302499997066, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-get_function]": 0.07751704099973722, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-invoke]": 0.07814713799984929, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-create_function]": 0.09295170800010055, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-delete_function]": 0.0796517070000391, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-get_function]": 0.07820801799971377, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-invoke]": 0.07757854000010411, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-create_function]": 0.0928619889998572, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-delete_function]": 0.07933900099988023, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-get_function]": 0.07745431600028496, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-invoke]": 0.07640406599989547, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-create_function]": 0.09523165099994912, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-delete_function]": 0.07916874600027768, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-get_function]": 0.07689634700022907, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-invoke]": 0.07868826600019929, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-create_function]": 0.1022781629999372, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-delete_function]": 0.08555620499987526, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-get_function]": 0.08123639200016441, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-invoke]": 0.0925592080000115, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-create_function]": 0.11940647500000523, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-delete_function]": 0.009787160000314543, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-get_function]": 0.07980758099984087, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-invoke]": 0.08365348500001346, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-create_function]": 0.09505319999993844, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-delete_function]": 0.0790348489997541, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-get_function]": 0.07734486599974844, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-invoke]": 0.07819100699975934, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-create_function]": 0.09556850100011616, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-delete_function]": 0.07740989600029025, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-get_function]": 0.07705608400010533, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-invoke]": 0.07898846900025092, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-create_function]": 0.09483925499966972, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-delete_function]": 0.07741275300008965, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-get_function]": 0.07684864799989555, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-invoke]": 0.07655330200009303, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-create_function]": 0.09553111899981559, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-delete_function]": 0.08118155400006799, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-get_function]": 0.07828541800017774, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-invoke]": 0.0787381100001312, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-create_function]": 0.0998242919997665, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-delete_function]": 0.0825988699998561, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-get_function]": 0.08045580799989693, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-invoke]": 0.08186571900000672, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-create_function]": 0.09258256500015705, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-delete_function]": 0.07883430900028543, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-get_function]": 0.07583370099973763, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-invoke]": 0.07682908100014174, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[delete_function]": 1.2341862329999458, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function]": 1.2357378359999984, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_code_signing_config]": 1.2447863419999976, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_concurrency]": 1.2331815660002121, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_configuration]": 1.2453651789996911, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_event_invoke_config]": 1.2484696559999975, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_url_config]": 1.2458108739999716, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[invoke]": 1.2478288480001538, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_invoke": 0.07864329400013048, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": 0.0012454470002012386, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": 0.24479987999984587, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3": 2.196056768000062, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3_errors": 1.4253174380000928, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_zipfile": 1.476975191000065, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_code_updates": 2.357595589000084, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_config_updates": 1.2862788170000385, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_list_functions": 2.5866544390000854, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[delete_function]": 0.07943708299967511, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function]": 0.07946960399999625, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_code_signing_config]": 0.07917695700007243, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_concurrency]": 0.08024742699967646, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_configuration]": 0.07861296100031723, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_event_invoke_config]": 0.0787924070000372, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_url_config]": 0.08180243600008907, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function]": 1.237768461999849, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_configuration]": 1.237413854000124, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_event_invoke_config]": 1.2372900269999718, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[delete_function]": 0.09339908800006924, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function]": 0.08950060300003315, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function_configuration]": 0.09007958699999108, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_redundant_updates": 1.4294561499998508, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_update_lambda_exceptions": 1.2419897610000135, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_vpc_config": 2.099020168999914, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_and_image_config_crud": 1.444914739000069, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_crud": 3.6087379400000827, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_versions": 1.5208672100000058, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_zip_file_to_image": 1.4336479990001862, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes0]": 0.13284665800006223, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes1]": 0.1322108169999865, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_deterministic_version": 0.09139648000018497, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_exceptions": 0.3277695199999471, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_exceptions": 17.59035748899987, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_quota_exception": 16.458823675000076, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_lifecycle": 2.643783758000154, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_exceptions": 0.2507123740001589, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_lifecycle": 0.18940312500012624, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_s3_content": 0.22167809900020075, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_aws": 1.2566154129997358, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_fields": 1.3169450529999267, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_create_multiple_lambda_permissions": 1.256923363000169, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_lambda_permission_fn_versioning": 1.425892594999823, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_permission_exceptions": 1.3610277620000488, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_remove_multi_permissions": 1.3039002479999908, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_lambda_provisioned_lifecycle": 2.5250874379999004, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_exceptions": 1.4273166680000031, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_limits": 1.2894245090001277, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_allow": 1.2440557429997625, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_default_terminate": 1.2329553540000688, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_invalid_value": 1.2277464990002045, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency": 1.2642305920001036, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_exceptions": 1.2732510029998139, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_limits": 1.2304744119999214, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_basic": 13.959974920999912, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_permissions": 1.350482189000104, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_version_and_alias": 1.4652932850003708, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_lambda_envvars_near_limit_succeeds": 1.310029866999912, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_fails_multiple_keys": 16.1882492740001, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_variables_fails": 16.197543932999906, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_lambda": 12.048900620000268, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_request_create_lambda": 2.1221904720000566, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_unzipped_lambda": 4.7276464229998965, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_zipped_create_lambda": 1.6450437239998337, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_exceptions": 0.09433723300026031, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet8]": 3.3995466389999365, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java11]": 2.3949374940000325, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java17]": 2.386287237000033, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java21]": 3.4900049850000414, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.12]": 1.31538473899991, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.13]": 7.51159045099962, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet8]": 1.2749618470002133, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java11]": 1.296473209999931, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java17]": 1.2748645259998739, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java21]": 1.2906026549999297, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.12]": 1.2892904830000589, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.13]": 1.2684874850001506, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_esm_create": 1.618167000999847, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_fn_create": 1.2724506760000622, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[event_source_mapping]": 0.1226704810001138, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[lambda_function]": 0.11912567900026261, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[event_source_mapping]": 1.5263167329999305, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[lambda_function]": 1.3617147530003422, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_nonexisting_resource": 1.3167038249998768, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_exceptions": 1.3405402209998556, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_lifecycle": 1.517157499999712, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_limits": 1.4266448680002668, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_versions": 1.2881184849998135, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag": 1.1742348219997893, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_alias": 3.515004801999794, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_invalid_id": 1.1654222020001725, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_deletion_without_qualifier": 1.3781419300000834, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_exceptions": 7.683597218999921, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_lifecycle": 1.336087761999579, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_list_paging": 1.4136182499996721, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_version_on_create": 1.377443539999831, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_update": 1.4418671279997852, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_wrong_sha256": 1.2903443610000522, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_version_lifecycle": 1.5859621200002039, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_advanced_logging_configuration_format_switch": 1.4260140040000806, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_advanced_logging_configuration": 1.360485423, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config0]": 1.346933853000337, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config1]": 1.3544866539998566, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config2]": 1.36505804300009, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config3]": 1.3700794369999585, + "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_cross_region_arn_function_access": 1.2886335479997797, + "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_update_function_configuration_full_arn": 1.3511882110001352, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_disabled": 15.28985684600002, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[dotnetcore3.1]": 0.0932805739998912, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[go1.x]": 0.09605915799988907, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[java8]": 0.09706734199994571, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs12.x]": 0.09515151900018282, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs14.x]": 0.09513771200022347, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[provided]": 0.09448335899969607, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[python3.7]": 0.09420069199995851, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[ruby2.7]": 0.09718651699995462, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet6]": 1.9716081999999915, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet8]": 1.9185855260002427, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java11]": 5.017611704000046, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java17]": 4.330249219000052, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java21]": 4.2266401210004005, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java8.al2]": 5.9387268119999135, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs16.x]": 1.825586645000385, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs18.x]": 1.8509531779991448, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs20.x]": 1.7242943249998461, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs22.x]": 1.7350301700007549, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.10]": 1.792552868000712, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.11]": 1.7933644890003961, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.12]": 1.864934016999996, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.13]": 1.7974310790000345, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.8]": 1.8028182590001052, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.9]": 1.8059142080005586, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.2]": 2.572872259000178, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.3]": 2.529481393999504, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.4]": 2.101305271000456, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet6]": 3.6225527679998777, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet8]": 2.639297213000191, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java11]": 2.5319848310005, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java17]": 2.4719371790001787, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java21]": 2.4938243340002373, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java8.al2]": 3.6080355789999885, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs16.x]": 8.715589448999708, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs18.x]": 2.539816198999233, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs20.x]": 2.499069388999942, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs22.x]": 6.613958327000091, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2023]": 3.3057297870000184, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2]": 4.16912358799982, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.10]": 6.785254965999684, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.11]": 2.5656174240007203, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.12]": 2.611587856999904, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.13]": 2.6600024239996856, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.8]": 2.8263475529997777, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.9]": 6.695624887000122, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.2]": 8.738416017999498, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.3]": 8.783033908999641, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.4]": 8.723920859999907, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet6]": 3.3738126379998903, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet8]": 3.4016650269995807, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java11]": 3.5209068770009253, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java17]": 3.4014341950005473, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java21]": 3.437620197999422, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java8.al2]": 3.6149094119996334, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs16.x]": 3.2864813969999886, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs18.x]": 3.284145179999996, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs20.x]": 3.2495486610005173, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs22.x]": 3.269801438000286, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2023]": 3.314472888999717, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2]": 3.2970753810000133, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.10]": 3.243772856000305, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.11]": 3.294916967000063, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.12]": 4.340521111000271, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.13]": 3.2525403920003555, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.8]": 3.3282651060003445, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.9]": 3.2451308449999487, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.2]": 3.341104921999431, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.3]": 3.3973708669996086, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.4]": 3.3296546729998227, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet6]": 1.8247581950004133, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet8]": 1.8318357429998287, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java11]": 1.9657814089996464, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java17]": 1.8785884119997718, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java21]": 1.884928518000379, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java8.al2]": 2.119865010000467, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs16.x]": 1.7884648880003624, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs18.x]": 1.7396612669995193, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs20.x]": 1.7238376770005743, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs22.x]": 1.7107113060001211, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.10]": 1.7134672399997726, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.11]": 1.6952845929999967, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.12]": 1.7223263370001405, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.13]": 1.72885013000041, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.8]": 1.7236099300002934, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.9]": 2.8661994249996496, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.2]": 1.805479725999703, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.3]": 1.7763632480000524, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.4]": 1.7880888169997888, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet6]": 1.8496824189996914, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet8]": 1.8573296900003697, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java11]": 2.0042793269994945, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java17]": 1.8745346849996167, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java21]": 1.8958454540002094, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java8.al2]": 2.203468948999671, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs16.x]": 1.7734768069999518, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs18.x]": 1.792258034999577, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs20.x]": 1.7324157070002002, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs22.x]": 1.7448200240000915, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2023]": 1.7763813739993566, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2]": 1.7560374780005077, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.10]": 1.7168397719997301, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.11]": 1.6843507220000902, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.12]": 1.6998135549997642, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.13]": 1.737045353000667, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.8]": 1.75836364099996, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.9]": 1.698833084999933, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.2]": 1.8076482559995384, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.3]": 1.8046597690004091, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.4]": 1.8075289510002222, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDLQ::test_dead_letter_queue": 21.237125565000497, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationEventbridge::test_invoke_lambda_eventbridge": 15.81599990299992, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload0]": 1.9076070959999925, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload1]": 1.9321258869999838, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_lambda_destination_default_retries": 21.43546591700033, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_maxeventage": 63.099447291999695, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_retries": 22.56642078599998, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_additional_docker_flags": 1.6250885770000423, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_lambda_docker_networks": 6.6112471249998634, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[nodejs20.x]": 3.4466891220004072, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[python3.12]": 3.388316873999429, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_environment_placeholder": 0.4880693270001757, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_error_path_not_absolute": 0.026937281000300572, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_publish_version": 0.1270180349997645, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestLambdaDNS::test_lambda_localhost_localstack_cloud_connectivity": 1.6061296089997086, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[Active]": 2.6318831680000585, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[PassThrough]": 2.600750307999533, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_xray_trace_propagation": 1.581187645999762, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestCloudwatchLogs::test_multi_line_prints": 3.632768229999783, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2023]": 1.92320860899963, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2]": 1.9065978130001895, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2023]": 1.9773371139995106, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2]": 1.9826536389996363, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom-INTERFACE]": 3.0452167139997073, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequest-INTERFACE]": 3.046131130000049, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequestCustom-CUSTOM]": 3.0344581899998957, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_lambda_subscribe_sns_topic": 9.902181085000848, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_runtime_with_lib": 5.730901092999375, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java11]": 2.6918155740004295, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java17]": 2.5855882360001488, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java21]": 2.879764664999584, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java8.al2]": 2.815680366000379, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java11]": 1.7586527359999309, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java17]": 1.7304077270000562, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java21]": 1.854050210999958, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java8.al2]": 1.7436802410002201, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs16.x]": 4.704460786000254, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs18.x]": 4.735790853000253, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs20.x]": 4.706063764000191, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs22.x]": 4.679959966000297, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.10]": 1.7396797089995744, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.11]": 1.717828296999869, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.12]": 1.712753490999603, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.13]": 1.7002941840005406, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.8]": 1.700418451000587, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.9]": 1.7140749159993902, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.10]": 1.5784838829999899, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.11]": 1.5743654830002924, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.12]": 1.5789184179998301, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.13]": 1.5784883580004134, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.8]": 1.6151738119992842, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.9]": 1.613309997000215, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_group": 0.22247289300003104, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_stream": 0.4734315399996376, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_delivery_logs_for_sns": 1.094350631999987, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_filter_log_events_response_header": 0.058520929999758664, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_list_tags_log_group": 0.2528336170003058, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_metric_filters": 0.0014058539995858155, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_events_multi_bytes_msg": 0.06018758099980914, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_firehose": 0.5290895489997638, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_kinesis": 2.3642309579995526, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_lambda": 1.9569745380003951, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_resource_does_not_exist": 0.042624362000879046, + "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend": 0.176206445999469, + "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend_with_custom_endpoint": 0.20220733599990126, + "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint": 16.69602467200002, + "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint_disabled": 17.15874823899958, + "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_route_through_edge": 16.00811466399955, + "tests/aws/services/opensearch/test_opensearch.py::TestMultiClusterManager::test_multi_cluster": 28.98962941199943, + "tests/aws/services/opensearch/test_opensearch.py::TestMultiplexingClusterManager::test_multiplexing_cluster": 16.83103826000024, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_cloudformation_deployment": 22.34181647100013, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_custom_endpoint": 0.025610988999687834, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_name": 0.02675696699998298, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_existing_domain_causes_exception": 16.634077459999844, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_indices": 18.10921287600013, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_describe_domains": 16.676775760000055, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_lifecycle": 12.621857965000345, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_version": 16.654584985000383, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_path": 17.151445567000337, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_port": 16.599927720000323, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_exception_header_field": 0.012853248999363132, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_version_for_domain": 10.529920713000138, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_versions": 0.023109229000056075, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_document": 17.97161406899977, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_gzip_responses": 16.75748987099996, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_list_versions": 0.11356572900012907, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_search": 17.485556370999802, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_security_plugin": 24.56527201299923, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_sql_plugin": 22.83707307600025, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_update_domain_config": 16.724387572999603, + "tests/aws/services/opensearch/test_opensearch.py::TestSingletonClusterManager::test_endpoint_strategy_port_singleton_cluster": 16.398998222000046, + "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_cluster_security_groups": 0.041280231000655476, + "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_create_clusters": 0.23284682599978623, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_cloudformation_query": 0.0013502040001185378, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_create_group": 0.3328535460000239, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_different_region": 0.0013102979996801878, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_tag_query": 0.001366244000109873, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_type_filters": 0.0013516260000869806, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_search_resources": 0.0012218939996273548, + "tests/aws/services/resourcegroupstaggingapi/test_rgsa.py::TestRGSAIntegrations::test_get_resources": 0.6554781960003311, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_associate_vpc_with_hosted_zone": 0.5800650900000619, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone": 0.6148777179996614, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone_in_non_existent_vpc": 0.1649388499995439, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_private_hosted_zone": 0.676643452999997, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_crud_health_check": 0.21010402100000647, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_reusable_delegation_sets": 0.16522129100030725, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_associate_and_disassociate_resolver_rule": 0.5263741439998739, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[INBOUND-5]": 0.34117876099981004, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[OUTBOUND-10]": 0.2815388409994739, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_query_log_config": 0.31460391900054674, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule": 0.3680470630001764, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule_with_invalid_direction": 0.30752149100044335, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_endpoint": 0.0763859780004168, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_query_log_config": 0.1337224500002776, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_rule": 0.0802166789999319, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_resolver_endpoint": 0.29052185199952874, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_disassociate_non_existent_association": 0.07675981900001716, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_domain_lists": 0.1740745740003149, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules": 0.3205631890004952, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_empty_rule_group": 0.09645334700053354, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_missing_rule_group": 0.1370423249995838, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multipe_create_resolver_rule": 0.41771281499904944, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multiple_create_resolver_endpoint_with_same_req_id": 0.2937225370001215, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_route53resolver_bad_create_endpoint_security_groups": 0.1944827450001867, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_update_resolver_endpoint": 0.3037835440004528, + "tests/aws/services/s3/test_s3.py::TestS3::test_access_bucket_different_region": 0.0014311149998320616, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_availability": 0.03563191500006724, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_does_not_exist": 0.39390743900003145, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_exists": 0.22664170400048533, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_name_with_dots": 0.49739670999997543, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_operation_between_regions": 0.42815036800038797, + "tests/aws/services/s3/test_s3.py::TestS3::test_complete_multipart_parts_order": 0.5212038730001041, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_in_place_with_bucket_encryption": 0.15288744600002246, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_kms": 1.847800226000345, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character": 0.6463883850001366, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character_plus_for_space": 0.13492454400056886, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_head_bucket": 0.556593446000079, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_via_host_name": 0.04272206100040421, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_existing_name": 0.3824714100001074, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_no_such_bucket": 0.020142577999649802, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy": 0.11007689100006246, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy_expected_bucket_owner": 0.12606886899993697, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_with_content": 0.6849047020000398, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_keys_in_versioned_bucket": 0.49424523799962117, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys": 0.0922543770006996, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_in_non_existing_bucket": 0.024732660000154283, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_quiet": 0.0876091459995223, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_object_tagging": 0.12487830500003838, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_objects_encoding": 0.12122958699956143, + "tests/aws/services/s3/test_s3.py::TestS3::test_different_location_constraint": 0.5835461719993873, + "tests/aws/services/s3/test_s3.py::TestS3::test_download_fileobj_multiple_range_requests": 1.1113223829997878, + "tests/aws/services/s3/test_s3.py::TestS3::test_empty_bucket_fixture": 0.15848056699951485, + "tests/aws/services/s3/test_s3.py::TestS3::test_etag_on_get_object_call": 0.40174113200055217, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_notification_configuration_no_such_bucket": 0.022182074999818724, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy": 0.13108733300032327, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000000000020]": 0.07182867100027579, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000]": 0.07679088400027467, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[aa000000000$]": 0.07793785800049591, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[abcd]": 0.07478520699987712, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_versioning_order": 0.4686067819998243, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_obj_attrs_multi_headers_behavior": 0.0925742760000503, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_after_deleted_in_versioned_bucket": 0.12754891599934126, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes": 0.3282333430001927, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_versioned": 0.45207105499957834, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_with_space": 0.10515536399998382, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[False]": 0.09941725499948006, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[True]": 0.10067110799991497, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_no_such_bucket": 0.023855784999796015, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part": 0.26016301700065014, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[COMPOSITE]": 0.138903663000292, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[FULL_OBJECT]": 0.1394303349998154, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_with_anon_credentials": 0.4789050100002896, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_range_object_headers": 0.09739372199965146, + "tests/aws/services/s3/test_s3.py::TestS3::test_head_object_fields": 0.11071024700004273, + "tests/aws/services/s3/test_s3.py::TestS3::test_invalid_range_error": 0.09452628500002902, + "tests/aws/services/s3/test_s3.py::TestS3::test_metadata_header_character_decoding": 0.39177849699990475, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_and_list_parts": 0.20196719600062352, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_too_small": 0.12305001000004268, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_wrong_part": 0.1110787149996213, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_copy_object_etag": 0.14799262399992585, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_no_such_upload": 0.10011023600009139, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_overwrite_key": 0.13523088099964298, + "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[False]": 0.19929717800005164, + "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[True]": 0.20554226100011874, + "tests/aws/services/s3/test_s3.py::TestS3::test_precondition_failed_error": 0.1040177679997214, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_content_language_disposition": 0.7872401259996877, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_hash_prefix": 0.39535114900036206, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_utf8_key": 0.39381614299963985, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_inventory_config_order": 0.16949424399990676, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy": 0.10412678300008338, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_expected_bucket_owner": 0.20948596700009148, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000000000020]": 0.07331882600010431, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000]": 0.07545868500028519, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[aa000000000$]": 0.07478237999976045, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[abcd]": 0.07716094500028703, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_single_character_trailing_slash": 0.1645546140002807, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[a/%F0%9F%98%80/]": 0.41125686199984557, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[file%2Fname]": 0.4153871150001578, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key//]": 0.4130967710002551, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key/]": 0.41436055300027874, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123/]": 0.4141466330002004, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123]": 0.4131032929999492, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%percent]": 0.4120161500004542, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test@key/]": 0.41264673900013804, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_acl_on_delete_marker": 0.4920932769996398, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_checksum": 0.11112109700070505, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_content_encoding": 0.10936002100015685, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines": 0.09143400599987217, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig": 0.08981792399981714, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig_empty_body": 0.08639272300024459, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_with_trailing_checksum": 0.1118749850002132, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[DEEP_ARCHIVE-False]": 0.10715643100002126, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER-False]": 0.10769446699987384, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER_IR-True]": 0.10819315800063123, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[INTELLIGENT_TIERING-True]": 0.10795182799984104, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[ONEZONE_IA-True]": 0.10705553300022075, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[REDUCED_REDUNDANCY-True]": 0.10436111500030165, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD-True]": 0.1084896959996513, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD_IA-True]": 0.10717642900044666, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class_outposts": 0.08939534099954471, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_tagging_empty_list": 0.13706755500015788, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_with_md5_and_chunk_signature": 0.09201318699933836, + "tests/aws/services/s3/test_s3.py::TestS3::test_putobject_with_multiple_keys": 0.40017656700047155, + "tests/aws/services/s3/test_s3.py::TestS3::test_range_header_body_length": 0.1139273119997597, + "tests/aws/services/s3/test_s3.py::TestS3::test_range_key_not_exists": 0.07326382299925172, + "tests/aws/services/s3/test_s3.py::TestS3::test_region_header_exists_outside_us_east_1": 0.5012690190005742, + "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure": 0.17830833900006837, + "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure_get_obj_attrs": 0.13193758099987463, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_analytics_configurations": 0.24352889399960986, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects": 0.4378169190003973, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects_using_requests_with_acl": 0.0015288770000552176, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_public_objects_using_requests": 0.42677172799949403, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl": 0.18284208299974125, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl_exceptions": 0.24021505099972273, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_content_type_and_metadata": 0.46338415399986843, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_directive_copy": 0.42849802399996406, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_replace": 0.42308678600056737, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place": 0.4935041349999665, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_metadata_directive": 0.5231125850000353, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_storage_class": 0.4659259210006894, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_suspended_only": 0.5315309959996739, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_versioned": 0.5878815990004114, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_website_redirect_location": 0.43244674700008545, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_with_encryption": 0.7005700240001715, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_preconditions": 3.5161163590000797, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_storage_class": 0.44215684400023747, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32C]": 0.46307258800015916, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32]": 0.43670690599947193, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC64NVME]": 0.43522463499994046, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA1]": 0.4629811559998416, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA256]": 0.45361405800031207, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32C]": 0.45782711100036977, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32]": 0.4506696240000565, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC64NVME]": 0.4695559570000114, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA1]": 0.4632878860006713, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA256]": 0.47252701700017496, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_wrong_format": 0.43046842700050547, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[COPY]": 0.4443623770002887, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[None]": 0.4483863850000489, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[REPLACE]": 0.4429512090005119, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[COPY]": 0.5478163820002919, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[None]": 0.5594213089998448, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[REPLACE]": 0.5556130529998882, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_object_with_version_id": 0.4649089200001981, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_objects_trailing_slash": 0.07796578500028772, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_download_object_with_lambda": 4.313591648000056, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_header_overrides": 0.09946182699968631, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_headers": 0.1693278219995591, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[get_object]": 3.5033547059997545, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[head_object]": 3.502851503999864, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_hostname_with_subdomain": 0.022511846000270452, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_intelligent_tier_config": 0.1846471199996813, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_invalid_content_md5": 15.296445336000488, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_inventory_report_crud": 0.19139644499955466, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_lambda_integration": 10.575362100000348, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_acls": 0.23343668800043815, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_sse": 0.19904124500044418, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl": 0.19159279100040294, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl_exceptions": 0.2463385770001878, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expires": 0.4554310200001055, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_inventory_report_exceptions": 0.16925571799993122, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_more_than_1000_items": 15.606845604999762, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_object_versioned": 0.6212042409997593, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_raw_request_routing": 0.10796410200009632, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer": 0.08487042099977771, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer_exceptions": 0.09342851100018379, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_bucket_key_default": 0.23223016500060112, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_default_kms_key": 0.0014406229997803166, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key": 0.28746081199960827, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key_state": 0.3022960130001593, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_timestamp_precision": 0.11780194800030586, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_upload_download_gzip": 0.10007102800000212, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_bucket_name": 0.32407048499953817, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_key_names": 0.10683470099957049, + "tests/aws/services/s3/test_s3.py::TestS3::test_set_external_hostname": 0.15591663500072173, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_big_file": 0.565452266000193, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_multipart": 0.42814722700040875, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_with_xml_preamble": 0.39353056199934144, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_cancelled_valid_etag": 0.13196516800007885, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_newlines_valid_etag": 0.10851035699988643, + "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[False]": 0.15478296399942337, + "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[True]": 0.18092323799965015, + "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxy_does_not_decode_gzip": 0.09315321799977028, + "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxying_headers": 0.09632514500026446, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_date": 0.09270792400002392, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry": 0.14600835599958373, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry_versioned": 0.21530533000031937, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_multiple_rules": 0.13150569300023562, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_object_size_rules": 0.1312440750002679, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_tag_rules": 0.20801253400031783, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_bucket_lifecycle_configuration": 0.1395693030003713, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_lifecycle_configuration_on_bucket_deletion": 0.13658051899983548, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_lifecycle_expired_object_delete_marker": 0.11941938100017069, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_object_expiry_after_bucket_lifecycle_configuration": 0.13655838799968478, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_put_bucket_lifecycle_conf_exc": 0.17131165799946757, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_s3_transition_default_minimum_object_size": 0.13363396300064778, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging": 0.16345619000048828, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_accept_wrong_grants": 0.14004781700032254, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_cross_locations": 0.1824445220004236, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_wrong_target": 0.13133645800007798, + "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config": 0.6151046270001643, + "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config_without_filter": 0.5819036749999213, + "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_s3_get_deep_archive_object_restore": 0.5024507419998372, + "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_storage_class_deep_archive": 0.1737055439998585, + "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_access": 0.14070701399941754, + "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_copy_object": 0.10865801700037991, + "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_shared_bucket_namespace": 0.07728725700053474, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32C]": 0.49061367899957986, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32]": 0.5183371690000058, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA1]": 0.5104261149999729, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA256]": 0.5142565410005773, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_default": 0.24138455299998895, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32C]": 0.5554792799998722, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32]": 0.5744382820003011, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC64NVME]": 0.5868915289997858, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object_default": 0.16691302900017035, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32C]": 0.07721510400051557, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32]": 0.07729239699983737, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC64NVME]": 0.0751163820000329, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA1]": 0.07848550199969395, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA256]": 0.07600848200036125, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32C]": 0.08024931499994636, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32]": 0.07655595000005633, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC64NVME]": 0.0836652539996976, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA1]": 0.07511683099983202, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA256]": 0.07824184099990816, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32C]": 0.08213921300011862, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32]": 0.07730753299983917, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC64NVME]": 0.08022365499982698, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA1]": 0.08233737599994129, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA256]": 0.07609371800026565, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_composite": 6.286937272999694, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_full_object": 35.723331452999446, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_size_validation": 0.13453836600046998, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32C]": 5.584607071000391, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32]": 7.920285199000318, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC64NVME]": 7.211505239999951, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA1]": 8.737606642999708, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA256]": 6.218879280999772, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[COMPOSITE]": 0.20519400500006668, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[FULL_OBJECT]": 0.18588291800006118, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_delete_locked_object": 0.13641333399982614, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_get_object_legal_hold": 0.14958616700050698, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_legal_hold_exc": 0.1906691220005996, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_with_legal_hold": 0.11694607199979146, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_copy_object_legal_hold": 0.4540782340000078, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_legal_hold_lock_versioned": 0.4908959099993808, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_bucket_config_default_retention": 0.15427045799970074, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_delete_markers": 0.1362130709999292, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_extend_duration": 0.13868393100074172, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_copy_object_retention_lock": 0.43457355000009557, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_lock_mode_validation": 0.11411536699961289, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention": 6.185806250999576, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_compliance_mode": 6.156663158999891, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_exc": 0.27757289999999557, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_default_checksum": 0.10327428500022506, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3]": 0.10859275999928286, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3v4]": 0.11061208999990413, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": 0.3250650740005767, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": 0.2704991149998932, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": 0.22965626800032624, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_file_as_string": 0.31448510499967597, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_files": 0.13720413400005782, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_metadata": 0.10900799699993513, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_storage_class": 0.2985897949997707, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[invalid]": 0.16037197400009973, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[list]": 0.16010879499981456, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[notxml]": 0.1499793220000356, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[single]": 0.15809975900037898, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_wrong_content_type": 0.13462846799984618, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_expires": 3.1407435760006592, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3]": 0.14976841299994703, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3v4]": 0.15522411199981434, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3]": 0.1645636809998905, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3v4]": 0.16584259899991594, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3]": 0.1510694099997636, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3v4]": 0.1481256309998571, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_presigned_post_with_different_user_credentials": 0.208660676999898, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_redirect": 0.09429943499981164, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_status_201_response": 0.0833556900001895, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_delete_has_empty_content_length_header": 0.10368621899988284, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_object_ignores_request_body": 0.09564649499998268, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_request_expires_ignored_if_validation_disabled": 3.1234599349995733, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_head_has_correct_content_length_header": 0.09364780299983977, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_forward_slash_bucket": 0.1031929890000356, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_match": 0.10614787700023953, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_none_match": 0.1067344149996643, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_check_signature_validation_for_port_permutation": 0.11240479299976869, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_with_additional_query_params": 0.10323826500007272, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_double_encoded_credentials": 0.16756112699977166, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-False]": 0.22959763600010774, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-True]": 0.2227879780002695, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-False]": 0.23490470799970353, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-True]": 0.23447759100008625, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-False]": 2.1714172289994167, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-True]": 2.1746714870000687, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-False]": 2.17273669899987, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-True]": 2.1751660650002123, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-False]": 0.1289737970000715, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-True]": 0.12819696699989436, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-False]": 0.12860915200008094, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-True]": 0.13649179699950764, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_signed_headers_in_qs": 1.9880414630001724, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_x_amz_in_qs": 7.780856012000186, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_different_user_credentials": 0.2091497219998928, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_session_token": 0.12297336099936729, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object": 0.4109487390005597, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-False]": 0.10262192400023196, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-True]": 0.16359939499989196, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-False]": 0.09687508200022421, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-True]": 0.1634062820003237, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[False]": 0.48472462199970323, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[True]": 0.48876628100015296, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[False]": 0.49413586499986195, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[True]": 0.5014092450005592, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_copy_md5": 0.1214465149996613, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_case_sensitive_headers": 0.09092272700036119, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_content_type_same_as_upload_and_range": 0.10349502300005042, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_default_content_type": 0.09431971200001499, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3]": 0.10493481699995755, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3v4]": 0.10706557699950281, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_ignored_special_headers": 0.14242722899962246, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3]": 0.1049699730001521, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3v4]": 0.10709475000021484, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3]": 3.194650764000471, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3v4]": 3.199019183999553, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3]": 0.17224334499996985, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3v4]": 0.17293162100031623, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_same_header_and_qs_parameter": 0.18512965099989742, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3]": 1.3196900310003912, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3v4]": 0.22145837699963522, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32C]": 8.668408091000401, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32]": 7.718041926000751, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC64NVME]": 11.444973849000235, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA1]": 7.202316345000327, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA256]": 9.537320907999401, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_algorithm": 0.13975593799978014, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_automatic_sdk_calculation": 0.2877906730000177, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_with_content_encoding": 0.12023025100006635, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32C]": 0.13099604899980477, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32]": 0.13697178400025223, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC64NVME]": 0.13177105999966443, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[None]": 0.13150545300004524, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA1]": 0.13263240400010545, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA256]": 0.13207392300046195, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-False]": 0.10959772200021689, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-True]": 0.11074963300052332, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-False]": 0.1077513270001873, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-True]": 0.11479282299978877, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_copy_object_with_sse_c": 0.22657384400008596, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c": 0.459652334999646, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c_validation": 0.19472717500002545, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_object_retrieval_sse_c": 0.2647568060001504, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_default_checksum_with_sse_c": 0.1854524059999676, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_lifecycle_with_sse_c": 0.1850618140001643, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_validation_sse_c": 0.2141358100002435, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_sse_c_with_versioning": 0.2343968110003516, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_crud_website_configuration": 0.11852695500010668, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_object_website_redirect_location": 0.2808713979998174, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_conditions": 0.5735699169995314, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_empty_replace_prefix": 0.44932206800012864, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_order": 0.26814201400020465, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_redirects": 0.1648387980003463, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_hosting": 0.5816639469999245, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_index": 0.15312513600019884, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_validate_website_configuration": 0.21054697299996405, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_404": 0.2384250980003344, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_http_methods": 0.15633030999970288, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_index_lookup": 0.29542297499938286, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_no_such_website": 0.14624504300036278, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_redirect_all": 0.34026437099964824, + "tests/aws/services/s3/test_s3.py::TestS3TerraformRawRequests::test_terraform_request_sequence": 0.06260259999953632, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_crud": 0.10982299600027545, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_exc": 0.14696022900034222, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_bucket_with_objects": 0.3941128729998127, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_versioned_bucket_with_objects": 0.4229502299995147, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms": 0.22827332199994999, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms_aws_managed_key": 0.2615253520002625, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_s3": 0.11092375399994125, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption": 0.10381976999997278, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption_exc": 0.4336686700003156, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketNotificationConfiguration::test_bucket_notification_with_invalid_filter_rules": 0.13241483599995263, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketNotificationConfiguration::test_bucket_notification_with_missing_values_in_rule": 0.23791482499973426, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_crud": 0.1544599630005905, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_exc": 0.09137463099978049, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_crud": 0.1756744549998075, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_exc": 0.23216939000030834, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": 0.2726441039999372, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tags_delete_or_overwrite_object": 0.2026069280000229, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_bucket_tagging_none_value": 0.09370453600013207, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_tagging_none_value": 0.10473509100029332, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_with_tags": 0.2814053910005896, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_tagging_validation": 0.2026951310003824, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_bucket_ownership_controls_exc": 0.12461489399993297, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_crud_bucket_ownership_controls": 0.17175685200027146, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_crud": 0.13092497000025105, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_exc": 0.10695187299961617, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_bucket_versioning_crud": 0.17091869500063694, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_object_version_id_format": 0.10824634100072217, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": 0.0968112329996984, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": 0.09461589200009257, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": 0.09317315799989956, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": 0.09341186700021353, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration": 0.09342927999978201, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration_twice": 0.09086909599955106, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration": 0.07911206200014931, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration_not_exist": 0.07555175799961944, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations": 0.09412855999971725, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations_paginated": 0.939654075999897, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_overwrite_bucket_metrics_configuration": 0.1528091579998545, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_put_bucket_metrics_configuration": 0.142796668000301, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_no_copy_source_range": 0.18737982299990108, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_range": 0.3504060880004545, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_and_if_unmodified_since_match": 0.19703015999994022, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_failed": 0.11456609599963485, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_none_success": 0.18643513500046538, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_success": 0.1850737080003455, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_modified_since_in_future_success": 0.1890991949999261, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_modified_since_success": 0.1872147149997545, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_none_match_and_if_unmodified_since_match_failed": 0.11386610299996391, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_none_match_failed": 0.10887517499941168, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_unmodified_since_match_failed": 0.12030760700054088, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_unmodified_since_success": 0.18616978999943967, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_if_modified_since_failed": 1.1087142629999107, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object": 0.10280995400034953, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_on_suspended_bucket": 0.5449862370001028, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_versioned": 0.5375198740002816, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects": 0.09688189799953761, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects_versioned": 0.4391578390000177, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_range": 0.34701741900016714, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_with_version_unversioned_bucket": 0.41036094700029935, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_list_object_versions_order_unversioned": 0.45761144200014314, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_put_object_on_suspended_bucket": 0.5883510769999702, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_delete_object_with_no_locking": 0.11582512600080008, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_disable_versioning_on_locked_bucket": 0.0805648939999628, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_object_lock_configuration_exc": 0.08353629100020044, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_put_object_lock_configuration": 0.1064698469999712, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_exc": 0.12719455300020854, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_on_existing_bucket": 0.1348488809999253, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_etag": 0.1660244380000222, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_delete": 0.162616823999997, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put": 0.17093302599960225, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put_identical": 0.1609035319997929, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_delete": 0.17712965899954725, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_put": 0.11741415800042887, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match": 0.1386633570000413, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_and_if_none_match_validation": 0.07768420599995807, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_validation": 0.10263288200030729, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_versioned_bucket": 0.19288842600008138, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match": 0.11493544799986921, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_validation": 0.09484473399970739, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_versioned_bucket": 0.15365416400027243, + "tests/aws/services/s3/test_s3_api.py::TestS3PublicAccessBlock::test_crud_public_access_block": 0.11939993299984053, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_bucket_creation": 0.44419600299988815, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_listing": 0.34020609800018065, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_read": 1.7606022869999833, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_read_range": 1.6840314740002214, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_expose_headers": 0.26165130900017175, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_get_no_config": 0.1237979039997299, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_no_config": 0.2649947730005806, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket": 0.158071512999868, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket_ls_allowed": 0.09342131499988682, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_list_buckets": 0.08799382200004402, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_headers": 0.7237416950001716, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_methods": 0.6542369750000034, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_origins": 0.6157704550005292, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_no_config_localstack_allowed": 0.14757528599966463, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_fails_partial_origin": 0.42761956400045165, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_match_partial_origin": 0.1648472220003896, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_delete_cors": 0.18427277499995398, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_get_cors": 0.1765129350001189, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors": 0.16653353199990306, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_default_values": 0.45658614000012676, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_empty_origin": 0.15943824600026346, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_invalid_rules": 0.1586085820003973, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_s3_cors_disabled": 0.10856764600066526, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_bucket_region": 0.5025437939998483, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_prefix_with_case_sensitivity": 0.4287542929996562, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_when_continuation_token_is_empty": 0.4156340919998911, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_continuation_token": 0.4734223150003345, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_max_buckets": 0.41979768300052456, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multipart_uploads_marker_common_prefixes": 0.44186526700059403, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_next_marker": 0.5741249229995447, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_with_prefix_and_delimiter": 0.44451020699989385, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_s3_list_multiparts_timestamp_precision": 0.07874003899951276, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_object_versions_pagination_common_prefixes": 0.5323521170003005, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_markers": 0.650325724999675, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix": 0.5614163859995642, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination": 0.5726779689994146, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination_many_versions": 1.148513868000009, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_s3_list_object_versions_timestamp_precision": 0.10854225900038728, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_marker_common_prefixes": 0.5002471190000506, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_next_marker": 0.46824301200012997, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[%2F]": 0.4075148920001084, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[/]": 0.3915966960003061, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[]": 0.39615000300000247, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_empty_marker": 0.36415216800014605, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjectsV2]": 0.09150354199982758, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjects]": 0.09405699999979333, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_common_prefixes": 0.48483639400092216, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_start_after": 0.6139321869995911, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix": 0.4976965770001698, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix_and_delimiter": 0.48009164900031465, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_empty_part_number_marker": 0.10960839700010183, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_pagination": 0.14938962200039896, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_via_object_attrs_pagination": 0.27604011100038406, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_s3_list_parts_timestamp_precision": 0.0918357130003642, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put": 1.7497640480000882, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_in_different_region": 1.8056461039996066, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_versioned": 5.169573362999927, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_put_acl": 1.2411078839995753, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_restore_object": 2.459640582999782, + "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_by_presigned_request_via_dynamodb": 6.084695994999947, + "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_put_via_dynamodb": 2.9978575180007283, + "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_invalid_lambda_arn": 0.4023626550001609, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_not_exist": 0.316588330999366, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_notifications_with_filter": 1.686299543000132, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_invalid_topic_arn": 0.2444576469997628, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_object_created_put": 1.7166392600001927, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_delete_objects": 0.7663217660006012, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_case_insensitive": 0.11862335300065752, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_empty_value": 0.11682704799977728, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_invalid_sqs_arn": 0.3851042200003576, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_key_encoding": 0.5812207869994381, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_multiple_invalid_sqs_arns": 0.5316138610000962, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_notifications_with_filter": 0.7331682329991054, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_and_object_removed": 0.7947303360006117, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_complete_multipart_upload": 0.6227922230000331, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_copy": 0.6257197920003819, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put": 0.6785168999999769, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_versioned": 1.0693258129999776, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_with_presigned_url_upload": 0.908638627000073, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_put_acl": 0.785258634999991, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_delete_event": 0.6355096540005434, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_put_event": 0.6258236620001298, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_restore_object": 0.743333795000126, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_xray_header": 1.5719291640002666, + "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_lifecycle_public_access_block": 0.3348778980002862, + "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_public_access_block_validations": 0.07877420600016194, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_already_exists": 0.0012051980002070195, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_bucket_not_exists": 0.001295208000101411, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_lifecycle": 0.001303051999911986, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_name_validation": 0.001221819999955187, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_pagination": 0.0012014809999527643, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_public_access_block_configuration": 0.0012176219997854787, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_crud_public_access_block": 0.0012201560002722545, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_empty_public_access_block": 0.0011967120003646414, + "tests/aws/services/scheduler/test_scheduler.py::test_list_schedules": 0.07299728899988622, + "tests/aws/services/scheduler/test_scheduler.py::test_tag_resource": 0.0393742299997939, + "tests/aws/services/scheduler/test_scheduler.py::test_untag_resource": 0.03603147200010426, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[ rate(10 minutes)]": 0.01667461400029424, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31)]": 0.016262982000171178, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31T23:59:59Z)]": 0.016679671999554557, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron()]": 0.016257512000265706, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 1 * * * *)]": 0.02792194300036499, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 dummy ? * MON-FRI *)]": 0.01692124499959391, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(7 20 * * NOT *)]": 0.017603984000743367, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(71 8 1 * ? *)]": 0.017490922999513714, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(INVALID)]": 0.017407185999672947, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate( 10 minutes )]": 0.01759665200006566, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate()]": 0.016625469999780762, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(-10 minutes)]": 0.017929744999946706, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 minutess)]": 0.016189273000236426, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 seconds)]": 0.015897726999810402, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 years)]": 0.016117690000100993, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10)]": 0.01668105499993544, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(foo minutes)]": 0.017716748000111693, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_valid_schedule_expression": 0.08329794499968557, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times": 0.060524180000356864, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times_snapshots": 0.001344991000223672, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_can_recreate_delete_secret": 0.05819487500048126, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2]": 0.0970788740005446, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2c3-]": 0.0962901190005141, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name]": 0.09713370700001178, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[s-c64bdc03]": 0.1219062580003083, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets": 0.11499769900001411, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets_snapshot": 0.001355019000129687, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_version_from_empty_secret": 0.04289433300073142, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_with_custom_id": 0.023774842999500834, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_delete_non_existent_secret_returns_as_if_secret_exists": 0.02117032900059712, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version": 0.9697656360003748, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version_stage": 0.1902340859992364, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_exp_raised_on_creation_of_secret_scheduled_for_deletion": 0.042694821000168304, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_first_rotate_secret_with_missing_lambda_arn": 0.03796356700013348, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_force_delete_deleted_secret": 0.056402538000384084, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_random_exclude_characters_and_symbols": 0.015931550000459538, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value": 0.07407171400018342, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value_errors": 0.04274879799959308, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_custom_client_request_token_new_version_stages": 0.05900952900037737, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_duplicate_req": 0.055132751999735774, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_null_client_request_token_new_version_stages": 0.06115131700016718, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_duplicate_client_request_token": 0.0012615509999704955, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_non_provided_client_request_token": 0.05515936099982355, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv *?!]Name\\\\-]": 0.10333742400007395, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv Name]": 0.10253145400020003, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv*Name? ]": 0.10472035199973107, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[Inv Name]": 0.10664760700001352, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_accessed_date": 0.06172552599991832, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_updated_date": 0.0938670849996015, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_list_secrets_filtering": 0.21189353999943705, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[CreateSecret]": 0.026468202999694768, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[PutSecretValue]": 0.024789200999748573, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[RotateSecret]": 0.02488346900054239, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[UpdateSecret]": 0.02456495400019776, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_no_replacement": 0.19348553000008906, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_replacement": 0.1956941060002464, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_new_custom_client_request_token": 0.05355759199937893, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_version_stages": 0.11284068599979946, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_resource_policy": 0.05343851099996755, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_invalid_lambda_arn": 0.2053501329996834, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_multiple_times_with_lambda_success": 2.970870883000771, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[None]": 2.4195715609998842, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[True]": 2.3823056310002357, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists": 0.05317210099974545, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists_snapshots": 0.05976463200022408, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_not_found": 0.02915526700007831, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_restore": 0.05126885299978312, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_tags": 0.13079450199984421, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_version_not_found": 0.04997145100014677, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_description": 0.1190105229998153, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending": 0.2134648510000261, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle": 0.2717740970001614, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_1": 0.267972109999846, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_2": 0.28774183499990613, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_3": 0.24272180299976753, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_previous": 0.19378790600012508, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_return_type": 0.05332573699979548, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_with_non_provided_client_request_token": 0.051447372999973595, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access": 0.14516057200034993, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access_non_default_key": 0.11362850800014712, + "tests/aws/services/ses/test_ses.py::TestSES::test_cannot_create_event_for_no_topic": 0.04614824600002976, + "tests/aws/services/ses/test_ses.py::TestSES::test_clone_receipt_rule_set": 0.4684116180001183, + "tests/aws/services/ses/test_ses.py::TestSES::test_creating_event_destination_without_configuration_set": 0.07094300000062503, + "tests/aws/services/ses/test_ses.py::TestSES::test_delete_template": 0.0650293950006926, + "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set": 0.016092710000521038, + "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set_event_destination": 0.03390313999943828, + "tests/aws/services/ses/test_ses.py::TestSES::test_describe_config_set_event_destinations": 0.11264441599996644, + "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_domain": 0.011928948999866407, + "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_email": 0.026058887999624858, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-]": 0.016435322000688757, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-test]": 0.01677301600057035, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-]": 0.016850944000452728, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-test_invalid_value:123]": 0.01733078100005514, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test]": 0.01813305500036222, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test_invalid_value:123]": 0.016496927999924083, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name_len]": 0.014796482000292599, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_value_len]": 0.01504976699970939, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_priority_name_value]": 0.015447909999693366, + "tests/aws/services/ses/test_ses.py::TestSES::test_list_templates": 0.1486297859996739, + "tests/aws/services/ses/test_ses.py::TestSES::test_sending_to_deleted_topic": 0.455500868999934, + "tests/aws/services/ses/test_ses.py::TestSES::test_sent_message_counter": 0.1110661610000534, + "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_email": 1.371585430000323, + "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_email_ses_destination": 0.7114223890002904, + "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_raw_email": 1.3488006019997556, + "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_templated_email": 1.3497784619999038, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_invalid_type": 0.03581774599979326, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Bounce]": 0.01499329799980842, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Complaint]": 0.018143482000141375, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Delivery]": 0.01750189099993804, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Bounce]": 0.040550323999468674, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Complaint]": 0.044134042999758094, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Delivery]": 0.03981778300021688, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Bounce]": 0.043621854000321036, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Complaint]": 0.0404307009994227, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Delivery]": 0.040911843000230874, + "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-a-this-marketing-campaign]": 0.017104346999985864, + "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-b-that-campaign]": 0.01705299000013838, + "tests/aws/services/ses/test_ses.py::TestSES::test_trying_to_delete_event_destination_from_non_existent_configuration_set": 0.10231889400029104, + "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_email_can_retrospect": 1.786491496999588, + "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_email_raises_message_rejected": 0.015311477000068408, + "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_templated_email_can_retrospect": 0.08152223699926253, + "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[]": 0.2077597239995157, + "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[sns.us-east-1.amazonaws.com]": 0.15314737500011688, + "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_access": 0.13301658199952726, + "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_publish_to_sqs": 0.5403921259999152, + "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_access": 0.11177230800058169, + "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_delivery_sqs": 0.16662111300001925, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_create_platform_endpoint_check_idempotency": 0.0014652239997303695, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_disabled_endpoint": 0.09585106099939367, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_gcm": 0.0013624409998556075, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_platform_endpoint_is_dispatched": 0.16705854700012424, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_subscribe_platform_endpoint": 0.10476830099969447, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_empty_sns_message": 0.09867360199996256, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_message_structure_json_exc": 0.06433802100036701, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_batch_too_long_message": 0.08060597899975619, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_by_path_parameters": 0.1461495780004043, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_before_subscribe_topic": 0.15925975200025277, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_by_target_arn": 0.22601026400025148, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_non_existent_target": 0.03739077799946244, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_too_long_message": 0.0809699719998207, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_with_empty_subject": 0.04663797799958047, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_wrong_arn_format": 0.03485628599946722, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_topic_publish_another_region": 0.06687320099990757, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_unknown_topic_publish": 0.045796339999924385, + "tests/aws/services/sns/test_sns.py::TestSNSPublishDelivery::test_delivery_lambda": 2.1167528179998953, + "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_sms_can_retrospect": 0.2873195090005538, + "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_to_platform_endpoint_can_retrospect": 0.23784997600023416, + "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_subscription_tokens_can_retrospect": 1.1083832460003578, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms": 0.01791694199982885, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms_endpoint": 0.17037757199977932, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_wrong_phone_format": 0.05527289400015434, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_subscribe_sms_endpoint": 0.055267083999751776, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_create_subscriptions_with_attributes": 0.10042560399961076, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions": 0.39171506200000294, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions_by_topic_pagination": 1.7541828180001175, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_not_found_error_on_set_subscription_attributes": 0.3182221749998462, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_sns_confirm_subscription_wrong_token": 0.11702473499963162, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_idempotency": 0.12186695800028247, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_protocol": 0.037314724999760074, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_topic": 0.05633284299983643, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_from_non_existing_subscription": 0.1014632609999353, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_idempotency": 0.09873567399972671, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_wrong_arn_format": 0.05327393100014888, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_validate_set_sub_attributes": 0.2567477179995876, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionFirehose::test_publish_to_firehose_with_s3": 1.3216375330002847, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[False]": 2.6919450569998844, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[True]": 2.6808424959999684, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_http_subscription_response": 0.08456367600001613, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_multiple_subscriptions_http_endpoint": 1.732501916000274, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_redrive_policy_http_subscription": 0.666631757000232, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[False]": 1.6364068699995187, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[True]": 1.648674561000007, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[False]": 1.6144298719996186, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[True]": 1.6232071059998816, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_lambda_url_sig_validation": 2.1434537009999985, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[1]": 4.266658207999626, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[2]": 4.262278340000194, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_python_lambda_subscribe_sns_topic": 4.240641885999594, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_redrive_policy_lambda_subscription": 1.3589285960006237, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_sns_topic_as_lambda_dead_letter_queue": 2.3963951619998625, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_email_sender": 2.1245875639997394, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_topic_email_subscription_confirmation": 0.06959648700012622, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_attribute_raw_subscribe": 0.16244819099983943, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_empty_or_wrong_message_attributes": 0.32577201300046, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_not_missing": 0.2804674609997164, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_prefixes": 0.19570549099989876, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_structure_json_to_sqs": 0.22378459700030362, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_exceptions": 0.0742154020003909, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_from_sns_to_sqs": 0.7074655850001363, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_without_topic": 0.03691314100024101, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_message_group_id": 0.1615735760001371, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns": 0.262594810000337, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns_with_xray_propagation": 0.14839567000080933, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[1]": 0.1569465030001993, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[2]": 0.15702895699951114, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_unicode_chars": 0.1486039480000727, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[False]": 0.2169683899996926, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[True]": 0.22076564599910853, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_sqs_topic_subscription_confirmation": 0.08497214600038205, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_sqs_queue": 0.19036628600042604, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_to_sqs_with_queue_url": 0.05177121299993814, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscription_after_failure_to_deliver": 1.636144542999773, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[False]": 0.27768155000012484, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[True]": 0.28622776999964117, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[False]": 1.2042731639999147, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[True]": 1.1893723949997366, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs_ordering": 2.96489732200007, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[False]": 3.6084733509997022, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[True]": 3.6020166119997157, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[False]": 1.5699765829999706, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[True]": 1.5721326249999947, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_deduplication_on_topic_level": 1.688049924999632, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[False]": 0.3023276919998352, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[True]": 0.3034509569997681, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_with_target_arn": 0.03807672499988257, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_validations_for_fifo": 0.24380152599951543, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_check_idempotency": 0.09146287800012942, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_with_more_tags": 0.04258755699947869, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_after_delete_with_new_tags": 0.059196254999733355, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_test_arn": 0.2823058799995124, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_with_attributes": 0.2362524459999804, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_delete_topic_idempotency": 0.05525595000017347, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags": 0.0980973420000737, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_topic_delivery_policy_crud": 0.001294577000408026, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy": 0.3457501230000162, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy_attributes_array": 4.30117979500028, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_filter_policy": 5.357053337000252, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_empty_array_payload": 0.188670130999526, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_for_batch": 3.407110845999796, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_ip_address_condition": 0.3716640169996026, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_large_complex_payload": 0.19213371199975882, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[False]": 5.372784816000149, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[True]": 5.370167467000101, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_attributes": 0.6174587310001698, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_of_object_attributes": 0.377198276999934, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_dot_attribute": 5.648212536999836, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_or_attribute": 0.8674322130000292, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity": 0.05695097899979373, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity_with_or": 0.06360075699967638, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy": 0.11692719999973633, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_exists_operator": 0.1073459570002342, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_nested_anything_but_operator": 0.16035512499956894, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_numeric_operator": 0.22322914300002594, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_string_operators": 0.2376109740002903, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_set_subscription_filter_policy_scope": 0.13561585500019646, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property": 0.12530587599985665, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property_constraints": 0.18985079500043867, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[domain]": 0.10049642200056041, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[path]": 0.10343805299999076, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[standard]": 0.10812272099974507, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[domain]": 0.03428158699989581, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[path]": 0.032497113000772515, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[standard]": 0.035996762000195304, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs]": 0.09775940100007574, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs_query]": 0.10041708900007507, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs]": 3.153632347999519, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs_query]": 3.139560939000603, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_not_visible[sqs]": 6.25618009200025, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_not_visible[sqs_query]": 6.260902344999977, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs]": 0.10181982299991432, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs_query]": 0.10339828700034559, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs]": 0.1423902120004641, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs_query]": 0.18304358999967008, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs]": 2.103396603999954, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs_query]": 2.1052091229994403, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs]": 0.6600071549996755, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs_query]": 0.6615540099996906, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs]": 0.11364824100019177, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs_query]": 0.11191602300004888, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs]": 0.10148781700036125, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs_query]": 0.10294988099985858, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs]": 0.07273036499964292, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs_query]": 0.074890313999731, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs]": 0.08626171299920316, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs_query]": 0.08445831800008818, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs]": 0.12333916099942144, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs_query]": 0.12400173000014547, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_same_attributes_is_idempotent": 0.04180613299968172, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs]": 0.09521035599982497, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs_query]": 0.09445160899986149, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs]": 0.0012733220000882284, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs_query]": 0.001234280000517174, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs]": 0.12481575400033762, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs_query]": 0.1263576279998233, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs]": 0.03761760699990191, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs_query]": 0.03952227300032973, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs]": 0.04905982599984782, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs_query]": 0.04786149399978967, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs]": 1.5646157940000194, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs_query]": 1.5640715179993094, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs]": 0.047663233000093896, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs_query]": 0.048129074000371475, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs]": 0.0012503610000749177, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs_query]": 0.0012596280002981075, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_attributes_is_idempotent": 0.04219275600053152, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs]": 0.18869519100053367, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs_query]": 0.188720579999881, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_same_attributes_is_idempotent": 0.03929368399985833, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs]": 0.03237712999998621, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs_query]": 0.031055229999765288, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_without_attributes_is_idempotent": 0.03947538399961559, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs]": 0.07674356600000465, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs_query]": 0.07738451499972143, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs]": 0.0012798050001947558, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs_query]": 0.0011831239999082754, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_config": 0.043210893999912514, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs]": 0.0013766450001639896, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs_query]": 0.001272791000246798, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs]": 0.06819904899975882, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs_query]": 0.0638062329994682, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs]": 0.13685380899960364, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs_query]": 0.1406112720001147, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_message_attributes": 0.7790068139997857, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs]": 0.18640956399985953, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs_query]": 0.18998611400047594, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs]": 0.0013708650003536604, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs_query]": 0.0012137600001551618, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs]": 1.1235310360002586, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs_query]": 1.12840464699957, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs]": 0.0013228150000941241, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs_query]": 0.001222759000484075, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-]": 0.19178459199974895, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-invalid:id]": 0.06714789700026813, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.06700423600022987, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-]": 0.06861306500013598, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-invalid:id]": 0.06675896400020065, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.07032401800051957, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs]": 0.648133009999583, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs_query]": 0.6689296089998606, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs]": 0.14496617200029505, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs_query]": 0.14406387600001835, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs]": 0.11965512100005071, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs_query]": 0.12075262000053044, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs]": 0.03561284399984288, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs_query]": 0.03311309899982007, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_disallow_queue_name_with_slashes": 0.0013219229999776871, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs]": 6.189328901999943, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs_query]": 6.999780781000027, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs]": 0.20072038500074996, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs_query]": 0.0731942950001212, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_host_via_header_complete_message_lifecycle": 0.09929605400020591, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_hostname_via_host_header": 0.03352713699950982, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs]": 0.11091738400000395, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs_query]": 0.11552896600005624, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs]": 0.2770632559995647, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs_query]": 0.29076301099939883, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approximate_number_of_messages_not_visible[sqs]": 5.23756682200019, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approximate_number_of_messages_not_visible[sqs_query]": 6.292369363000034, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs]": 0.34713211799999044, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs_query]": 0.35337055000036344, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs]": 0.24829099799990217, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs_query]": 0.24655406100009714, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs]": 1.0999195519993918, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs_query]": 1.1404099500000484, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-False]": 1.1563005979996888, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-True]": 1.155783098000029, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-False]": 1.1550735129994791, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-True]": 1.155690826999944, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-False]": 1.1473802900004557, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-True]": 1.139292265000222, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-False]": 1.1495068740000534, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-True]": 1.138506810000763, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs]": 1.1626400260001901, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs_query]": 1.1684312290003618, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs]": 0.00130406900007074, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs_query]": 0.0012183799999547773, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs]": 0.18811560899985125, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs_query]": 0.19097158499971556, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs]": 0.16637581400027557, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs_query]": 0.1913700790000803, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs]": 0.1577526529995339, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs_query]": 0.158854625999993, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility": 2.131139436000012, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs]": 2.1414605550003216, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs_query]": 2.1435724039997694, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs]": 0.3162583939997603, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs_query]": 0.29838431200050763, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs]": 0.2626342650000879, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs_query]": 0.332348266000281, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs]": 0.1571287160004431, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs_query]": 0.1497240700000475, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs]": 2.11512474500023, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs_query]": 2.117957320000187, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_requires_suffix": 0.01869052499978352, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs]": 4.117708233000485, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs_query]": 4.127097821000007, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs]": 0.14256279200026256, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs_query]": 0.14419401899976947, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs]": 4.1206942059998255, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs_query]": 4.11692149000055, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs]": 0.26361298799974975, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs_query]": 0.27310130600017146, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs]": 0.14773580599967318, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs_query]": 0.15416444899983617, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs]": 2.211526860000049, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs_query]": 2.2017023070002324, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs]": 0.18827871300027255, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs_query]": 0.206288276000123, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs]": 0.10193211400019209, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs_query]": 0.11163119400043797, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs]": 0.08444457499945202, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs_query]": 0.08534739299966532, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_list_queues_with_query_auth": 0.023060615999838774, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs]": 0.03278106399920944, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs_query]": 0.04164392400025463, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[domain]": 0.056873033000101714, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[path]": 0.05586032900009741, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[standard]": 0.05558132900023338, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs]": 0.06125742599988371, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs_query]": 0.06280585900049118, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_inflight_message_requeue": 4.601648954999746, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs]": 0.1410071649997917, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs_query]": 0.15878042799977266, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_dead_letter_arn_rejected_before_lookup": 0.001326271999914752, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs]": 0.03531152999948972, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs_query]": 0.03424329100016621, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs]": 0.03493167899932814, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs_query]": 0.034451651999916066, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs]": 0.03916435100018134, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs_query]": 0.03983079799991174, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues": 0.10899360199982766, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_domain": 0.06909792200031006, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_standard": 0.06998386900067999, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_without_endpoint_strategy": 0.07816677600021649, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_pagination": 0.3186461280001822, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[\"{\\\\\"foo\\\\\": \\\\\"ba\\\\rr\\\\\"}\"]": 0.08674679599971569, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[{\"foo\": \"ba\\rr\", \"foo2\": \"ba"r"\"}]": 0.08708712199995716, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[empty]": 0.06634185700022499, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[spaces]": 0.06959543400034818, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[too_long]": 0.06517404199985322, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_success": 0.05452824500025599, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention": 3.083256314999744, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_fifo": 3.0810836329997073, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_with_inflight": 5.624934372000098, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs]": 0.1272934889998396, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs_query]": 0.12491753800031802, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs]": 0.0693878870001754, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs_query]": 0.07659332200046265, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs]": 0.07337673100028042, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs_query]": 0.07565964800051006, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_non_existent_queue": 0.1635939990001134, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs]": 0.23862482800086582, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs_query]": 0.23575156099968808, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs]": 0.053543034000085754, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs_query]": 0.05567006799992669, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs]": 0.1027016990001357, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs_query]": 0.10493010100026368, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs]": 0.19760692799991375, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs_query]": 0.24620958399964366, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs]": 1.2413372710002477, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs_query]": 1.2462030489996323, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs]": 0.10154147999992347, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs_query]": 0.10409943299964652, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs]": 3.169302904000233, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs_query]": 3.1968623600000683, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs]": 4.254872518999491, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs_query]": 4.312629573000322, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs]": 0.031208538000555563, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs_query]": 0.03131818100064265, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs]": 1.644043079000312, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs_query]": 1.9991240360004667, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs]": 1.0925791180002307, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs_query]": 1.0931653999991795, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs]": 0.22550998899987462, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs_query]": 0.23038279000047623, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs]": 0.0697768800000631, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs_query]": 0.06897148999996716, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs]": 0.31088079200026186, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs_query]": 0.2897951340000873, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs]": 0.1611026519999541, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs_query]": 0.1648235650004608, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs]": 0.10193476699987514, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs_query]": 0.10276156200052355, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs]": 0.10088348499994026, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs_query]": 0.10588759800020853, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs]": 0.10925697399943601, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs_query]": 0.10519987099996797, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs]": 0.0012296199997763324, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs_query]": 0.0011961670002165192, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs]": 2.093612412999846, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs_query]": 2.0894138689996, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_message_size": 0.2031209729998409, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs]": 0.14097454700004164, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs_query]": 0.12441417099944374, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs]": 0.12818788200002018, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs_query]": 0.12309957399929772, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs]": 0.1115640389998589, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs_query]": 0.11734971400028371, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs]": 1.7281163570005447, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs_query]": 2.0005093529998703, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs]": 0.12300892700068289, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs_query]": 0.1280528869997397, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs]": 0.12097204199972111, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs_query]": 0.12744143500049177, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs]": 0.03154163400040488, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs_query]": 0.031107669999983045, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs]": 0.14269334499931574, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs_query]": 0.15955116100030864, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs]": 1.485583153999869, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs_query]": 0.11683631100049752, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[empty]": 0.06616047699981209, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[spaces]": 0.06520347599962406, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[too_long]": 0.06683580099979736, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs]": 0.0718099939995227, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs_query]": 0.07385412600069685, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs]": 0.10585860600031083, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs_query]": 0.10517945800029338, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs]": 0.06754333399976531, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs_query]": 0.07047336600044218, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs]": 0.12463757700061251, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs_query]": 0.13014763500041227, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs]": 0.0012775000004694448, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs_query]": 0.0012247019999449549, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs]": 0.03571400999999241, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs_query]": 0.035914516000048025, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs]": 0.1541050739997445, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs_query]": 0.15587192599969057, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs]": 0.16039722400000755, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs_query]": 0.15865042299992638, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs]": 0.14089012900012676, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs_query]": 0.15548399899989818, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs]": 0.1517126260000623, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs_query]": 0.1496052449997478, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs]": 0.06929818500020701, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs_query]": 0.072508020999976, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs]": 0.06913107100035631, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs_query]": 0.06973786700018536, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_multiple_queues": 0.10032514199974685, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs]": 0.22326395799973398, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs_query]": 0.22534987200015166, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs]": 0.08921915099972466, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs_query]": 0.0904060190000564, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs]": 0.09650424900019061, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs_query]": 0.09526961099982145, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs]": 0.07252384899993558, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs_query]": 0.07584177499984435, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs]": 0.07717880799964405, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs_query]": 0.08030330200017488, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs]": 0.0520587440005329, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs_query]": 0.05129745200019897, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs]": 0.2178601090004122, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs_query]": 0.21859218600047825, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs]": 0.1948529379997126, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs_query]": 0.19947326100009377, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs]": 0.16500942399989071, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs_query]": 0.18200624700057233, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs]": 0.1679102459997921, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs_query]": 0.16596840499960308, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs]": 0.24300796200031982, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs_query]": 0.2445847680000952, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs]": 0.0013277819998620544, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs_query]": 0.0012621300002138014, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs]": 0.13409459300009985, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs_query]": 0.11681415799966999, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_standard_queue_cannot_have_fifo_suffix": 0.016181481000330677, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs]": 0.13845254500029114, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs_query]": 0.13553655900022932, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs]": 0.0904525509999985, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs_query]": 0.10283761600021535, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs]": 0.04456064699934359, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs_query]": 0.045628764999946725, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs]": 0.1051446329997816, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs_query]": 0.10784507999960624, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs]": 0.03965188400070474, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs_query]": 0.03942758299990601, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs]": 0.11068071499994403, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs_query]": 0.11317499799997677, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs]": 0.13450034099969344, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs_query]": 0.139476160999493, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs]": 0.04694319599957453, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs_query]": 0.0511181150000084, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs]": 1.0667736389996207, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs_query]": 1.0672898810003062, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs]": 1.0789726599996357, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs_query]": 1.0702264740002647, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[domain]": 0.1357706059998236, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[off]": 0.13112367900021127, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[path]": 0.1349605110003722, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[standard]": 0.1917628229998627, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_create_queue_fails": 0.033321869999781484, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[domain]": 0.05679991100078041, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[path]": 0.05683004499951494, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[standard]": 0.05375360600055501, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails": 0.03384233299993866, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails_json_format": 0.0015484040000046662, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs]": 0.05725436000011541, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs_query]": 0.0582559110002876, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_all": 0.06086819399979504, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_json_format": 0.0012619689996427041, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_of_fifo_queue": 0.04905103499959296, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_invalid_arg_returns_error": 0.04697998199981157, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_query_args": 0.04903073699961169, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[domain]": 0.049267259999851376, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[path]": 0.04740159000039057, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[standard]": 0.04987064699980692, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[domain]": 0.061671164999552275, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[path]": 0.060371527000370406, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[standard]": 0.05892663500026174, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[domain]": 0.04473431599944888, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[path]": 0.04253435299960984, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[standard]": 0.04293925899992246, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_send_and_receive_messages": 0.1277562039995246, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_json_format_returns_returns_xml": 0.03343292099998507, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_returns_unknown_operation": 0.034057365000535356, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_invalid_action_raises_exception": 0.04014072600057261, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_overwrite_queue_url_in_params": 0.05718692300069961, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_queue_url_format_path_strategy": 0.02453443399963362, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_send_message_via_queue_url_with_json_protocol": 1.087111148000531, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_valid_action_with_missing_parameter_raises_exception": 0.03583193500026027, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_as_botocore_endpoint_url[json-domain]": 0.1097651790000782, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_as_botocore_endpoint_url[json-path]": 0.11146125799950823, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_as_botocore_endpoint_url[json-standard]": 0.11145034599985593, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_as_botocore_endpoint_url[query-domain]": 0.11159369300003164, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_as_botocore_endpoint_url[query-path]": 0.11324684300006993, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_as_botocore_endpoint_url[query-standard]": 0.11648561899983179, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_with_invisible_messages[domain]": 0.1669657010006631, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_with_invisible_messages[path]": 0.16340134300025966, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_fifo_list_messages_with_invisible_messages[standard]": 0.1623430129998269, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_botocore_endpoint_url[json-domain]": 0.08249273699993864, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_botocore_endpoint_url[json-path]": 0.0850710060003621, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_botocore_endpoint_url[json-standard]": 0.09328979800011439, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_botocore_endpoint_url[query-domain]": 0.08467084599988084, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_botocore_endpoint_url[query-path]": 0.08494456000016726, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_botocore_endpoint_url[query-standard]": 0.09140686200044001, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_json[domain]": 0.08335970999996789, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_json[path]": 0.08517717599988828, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_as_json[standard]": 0.08535895300019547, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_has_no_side_effects[domain]": 0.10879557099997328, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_has_no_side_effects[path]": 0.10569760099951964, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_has_no_side_effects[standard]": 0.11174326600030327, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_delayed_messages[domain]": 0.1166038779997507, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_delayed_messages[path]": 0.116450602000441, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_delayed_messages[standard]": 0.11907575900022493, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_action_raises_error[json-domain]": 0.03126542400013932, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_action_raises_error[json-path]": 0.029977559000144538, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_action_raises_error[json-standard]": 0.033395923000171024, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_action_raises_error[query-domain]": 0.030354399000316334, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_action_raises_error[query-path]": 0.030636946000413445, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_action_raises_error[query-standard]": 0.03371987899981832, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_queue_url[domain]": 0.02182133399992381, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_queue_url[path]": 0.027840345000186062, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invalid_queue_url[standard]": 0.020859860999735247, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invisible_messages[domain]": 0.13479989099914746, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invisible_messages[path]": 0.1328532050006288, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_invisible_messages[standard]": 0.1335970200002521, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_non_existent_queue[domain]": 0.034122012999887374, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_non_existent_queue[path]": 0.034412899000017205, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_non_existent_queue[standard]": 0.03447472500056392, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_queue_url_in_path[domain]": 0.1063456730003054, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_queue_url_in_path[path]": 0.12074981400019169, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_with_queue_url_in_path[standard]": 0.11109597099994062, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_without_queue_url[domain]": 0.021424468999612145, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_without_queue_url[path]": 0.019053136000366067, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsDeveloperApi::test_list_messages_without_queue_url[standard]": 0.020732005999889225, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsOverrideHeaders::test_receive_message_override_max_number_of_messages": 0.5404043520002233, + "tests/aws/services/sqs/test_sqs_developer_api.py::TestSqsOverrideHeaders::test_receive_message_override_message_wait_time_seconds": 25.323759635999977, + "tests/aws/services/sqs/test_sqs_move_task.py::test_basic_move_task_workflow": 1.8399658890002684, + "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_source_arn_in_task_handle": 0.04808881700046186, + "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_handle": 0.05102502399995501, + "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_id_in_task_handle": 0.070766389000255, + "tests/aws/services/sqs/test_sqs_move_task.py::test_destination_needs_to_exist": 0.11300669000002017, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_cancel": 1.8807692529999258, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_delete_destination_queue_while_running": 1.9166870109997944, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_with_throughput_limit": 3.4144436009996753, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_default_destination": 1.8290882539999984, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_multiple_sources_as_default_destination": 2.5480937190000077, + "tests/aws/services/sqs/test_sqs_move_task.py::test_source_needs_redrive_policy": 0.09766449000017019, + "tests/aws/services/sqs/test_sqs_move_task.py::test_start_multiple_move_tasks": 0.759629496999878, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_describe_parameters": 0.01696661200003291, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_maintenance_window": 0.01599292199989577, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_secret": 0.03937400800032265, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameter_by_arn": 0.06356212399987271, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_and_secrets": 0.13389876500013997, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_by_path_and_filter_by_labels": 0.0694651720004913, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_secret_parameter": 0.07284651500003747, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[///b//c]": 0.06941538699993544, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[/b/c]": 0.06796022799971979, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_parameters_with_path": 0.1869178660003854, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_put_parameters": 0.08667811600025743, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[domain]": 0.12984959199957302, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[path]": 0.15758843000003253, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[standard]": 0.12951783500011516, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task": 1.1902226430001974, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_failure": 1.9905185380002877, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_no_worker_name": 1.9229628520001825, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_on_deleted": 0.3948248350002359, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_start_timeout": 5.6992578219997085, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_with_heartbeat": 6.016608636000456, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY]": 2.1970904680006242, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY_GLOBAL_QL_JSONATA]": 2.269730731999971, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EXPRESSION]": 38.73959147299999, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_LITERALS]": 2.9236314859999766, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_FALSE]": 0.831761723999989, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_TRUE]": 0.9164310640000508, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_CONSTANT_LITERALS]": 1.081394017000008, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_EMPTY]": 0.6063378830000374, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_PATHS]": 0.90581906999995, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_SCOPE_MAP]": 0.9807989779999673, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_VAR]": 1.2517423759999815, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_parallel_cases[BASE_SCOPE_PARALLEL]": 1.0187616399999797, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 1.6956755220000161, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_PARAMETERS]": 0.8940815749999729, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_RESULT]": 0.8509992759999818, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_catch_state": 2.583142261000006, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[CORRECT]": 0.9688652849999642, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[INCORRECT]": 0.892965098000019, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_wait_state": 0.6046863589999987, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_CHOICE]": 0.9596412810000174, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_FAIL]": 0.854236850999996, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INPUTPATH]": 0.8470256509999956, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.1495041929999843, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 1.7069828200000359, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_OUTPUTPATH]": 0.8867560289999687, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_PARAMETERS]": 0.9015212930000018, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_WAIT]": 0.8696183989999895, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.2011682459999804, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.1801570479999839, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 0.9761947770000177, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 0.9095731889999854, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 0.9782822240000257, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 1.114863506000006, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_PER_BATCH_PATH]": 0.0017311119999874336, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_state_assign_evaluation_order[BASE_EVALUATION_ORDER_PASS_STATE]": 0.0014354920000130278, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS]": 0.001413132999971367, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS_FIELD]": 0.0013397250000082295, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ASSIGN]": 1.0573789999999974, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT]": 1.0918045000000234, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_FIELD]": 1.1082281000000194, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_MULTIPLE_STATES]": 1.1118311109999865, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_variables_in_lambda_task[BASE_ASSIGN_FROM_LAMBDA_TASK_RESULT]": 2.626794177000022, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_decl_version_1_0": 0.41370319699996116, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_base": 3.3917723430000137, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_failure": 0.00144323600000007, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_execution_dateformat": 0.4228336609999701, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[0]]": 0.6998179799999775, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[10]]": 0.6857794730000251, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[*]]": 0.6345928650000019, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5].itemValue]": 0.6468369119999693, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5]]": 0.6446173619999627, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:]]": 0.6458532130000663, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[:1]]": 0.6423487389999991, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*].itemValue]": 0.6359501609999825, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*]]": 0.6522063439999783, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:].itemValue]": 0.6586342680000143, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:]]": 0.6449029139999993, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1].itemValue]": 0.6582005239999944, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1]]": 0.6393883810000034, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$[*]]": 0.6568717890000357, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_query_context_object_values": 1.2916421279999781, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail": 0.5852214169999854, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_empty": 0.5319537329999662, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_intrinsic": 0.6762413379999828, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_path": 0.6827631160000465, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path": 0.0013439820000371583, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path_base": 0.6816353689999914, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result": 0.4235873330000004, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_jsonpaths": 0.41356102299997133, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_null_input_output_paths": 0.7048943690000158, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1.5]": 0.6362103999999817, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1]": 0.653885695999918, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[0]": 0.6373557690000098, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1.5]": 0.6242134309999869, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1]": 1.4701744660000031, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24855]": 0.0014000569999552681, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24856]": 0.0012547559999802615, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000Z]": 0.4432489069999406, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000]": 0.6479090140000494, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.00Z]": 0.4354218989999481, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[Z]": 0.6408434479999983, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[]": 0.6595009390000541, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_executions_and_heartbeat_notifications": 0.0020032090000654534, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_heartbeat_notifications": 0.002602403000025788, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sns_publish_wait_for_task_token": 1.2874683530000084, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_PARALLEL_WAIT_FOR_TASK_TOKEN]": 0.007885615000020607, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_WAIT_FOR_TASK_TOKEN_CATCH]": 1.6422235599999908, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_token": 2.361604681000017, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_tok_with_heartbeat": 7.520997841999986, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token": 2.4310978919999684, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_call_chain": 4.1590370370000755, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_no_token_parameter": 5.711228739000035, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_timeout": 5.746088063000002, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync": 0.985175889000061, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync2": 1.0051435839999385, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_failure": 1.002220452999893, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_timeout": 7.346532499000034, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sync_with_task_token": 2.6560816090000117, + "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals": 14.057533159000002, + "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals_path": 14.54851601799993, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_boolean": 14.07695680400002, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_null": 14.779984507999984, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_numeric": 13.76370995800005, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_present": 14.124768061999987, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_string": 14.412845747000063, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_timestamp": 0.0037111560000084864, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals": 22.392381779000004, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals_path": 22.21474983300004, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than": 2.6155413969999586, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals": 2.669106174000035, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals_path": 2.666449630000045, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_path": 2.703213990999984, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than": 2.6466921940000248, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals": 2.6651008679999677, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals_path": 2.6317249859999947, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_path": 2.653719925999951, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals": 6.472137256999929, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals_path": 1.4613608579999209, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than": 1.842500052000048, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals": 1.4563713000000007, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals_path": 1.4483059269999785, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_path": 1.9130369069999915, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than": 1.4525353939999377, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals": 1.4552924789999793, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals_path": 1.4574919890000047, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_path": 1.4487556240000004, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals": 7.7002068379999855, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals_path": 1.475662836999959, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than": 1.4585675130000482, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals": 1.454430554000055, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals_path": 0.6718609270000115, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_path": 0.6572580689999086, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than": 1.4511545530000376, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals": 1.4367073509999955, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals_path": 0.6968024349999951, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_path": 0.6659636140001339, + "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comment_in_parameters": 0.44412617600005433, + "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comments_as_per_docs": 7.657039604999909, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_error_cause_path": 0.9207384970001158, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$.Execution.Input]": 0.9112104269999008, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$]": 0.6829352999999401, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$.Execution.Input]": 0.933717113000057, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$]": 0.7277401249999684, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_result_selector": 2.6493019079998703, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_variable": 0.9558019320000994, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_lambda_task": 2.3808702039999616, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke": 2.4580215940001153, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke_retry": 6.599305754999932, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_INTRINSIC]": 1.4636050670000031, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_JSONATA]": 1.4210514220001187, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH]": 1.2496084629999586, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH_CONTEXT]": 1.548342931999855, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_VARIABLE]": 1.503167932999986, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[EMPTY_CREDENTIALS]": 0.7533891010000389, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[INVALID_CREDENTIALS_FIELD]": 0.744623630999854, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_invalid_param": 0.0017897509999329486, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_put_item_no_such_table": 2.91332757400005, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_invalid_secret_name": 0.7360151300000553, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_no_such_bucket": 0.7153032289999146, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_s3_no_such_key": 0.7485641309999664, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.372627464999937, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_data_limit_exceeded_on_large_utf8_response": 2.429581538999969, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_start_large_input": 5.0312700420001875, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.335017616000073, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_data_limit_exceeded_on_large_utf8_response": 2.3937545339999815, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function": 2.4590246300000445, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function_catch": 2.539929175999987, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_custom_exception": 2.2798439900000176, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception": 2.3246745269999565, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception_catch": 2.591555625000069, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_invalid_param": 0.7621778620000441, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_invalid_table_name": 0.8126507149999043, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_no_such_table": 0.7501988669999946, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_invoke_timeout": 6.849358958999915, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function": 1.8964083009999513, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function_catch": 1.951046455000096, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_custom_exception": 2.458397307999917, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception": 2.378047891000165, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch": 2.4782958779999262, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.Payload]": 2.397692574999951, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.no.such.path]": 2.4630499229999714, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[None]": 2.4570057739998674, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sfn.py::TestTaskServiceSfn::test_start_execution_no_such_arn": 0.9991826439999159, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_empty_body": 0.0018949269999666285, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue": 1.0757080629999791, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue_no_catch": 1.0083143620000783, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_sqs_failure_in_wait_for_task_tok": 2.6103349509999134, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE]": 0.956835559999945, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_FALSE]": 0.9651893970000174, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE]": 0.7491998440000316, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE_FALSE]": 0.969816650000098, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS]": 1.190785837000135, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS_DOUBLE_QUOTES]": 1.0532044139999925, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[MAX_CONCURRENCY]": 0.9969959499999277, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_COUNT]": 1.8325543490001337, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_PERCENTAGE]": 1.008905987999924, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[ITEMS]": 2.1931953509999857, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[MAX_CONCURRENCY]": 2.196262746000002, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_COUNT]": 2.1719618609999998, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_PERCENTAGE]": 2.1820168760000342, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[HEARTBEAT_SECONDS]": 2.4928617439999243, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[TIMEOUT_SECONDS]": 0.0015987750000476808, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[HEARTBEAT_SECONDS]": 2.717386970000234, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[TIMEOUT_SECONDS]": 0.0018364769999834607, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_PASS_RESULT]": 1.2645768080000153, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_RAISE_FAILURE]": 1.1962060049999081, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_catch": 2.9381455080001615, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_query_runtime_memory": 1.5153374610000583, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_retry": 10.182059527999968, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_PASS_RESULT]": 0.5391500019999285, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_RAISE_FAILURE]": 0.4817176330000166, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_catch": 2.2923866009999756, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_query_runtime_memory": 1.2541421839999884, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_retry": 9.542196978999982, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_0": 0.43521627600000556, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_2": 2.9940132640000456, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_contains": 3.3989766649999638, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_get_item": 0.6550304740001138, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_length": 0.46514351400003306, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_partition": 8.693146518000049, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_range": 1.653620493999938, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_unique": 0.6559421759999395, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_partition": 6.774616142000241, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_range": 1.5123225019999609, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_decode": 0.9963757960000521, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_encode": 0.9922024219999912, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_context_json_path": 0.697502792999785, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_escape_sequence": 0.42711766899992654, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_1": 2.4217977819999987, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_2": 2.973157241000081, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_1": 0.6756444430001238, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_2": 0.6829451959998778, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_hash_calculations.py::TestHashCalculations::test_hash": 1.996756466000079, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge": 0.6632467730000826, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge_escaped_argument": 0.6983688580000944, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_to_string": 2.99752201900003, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_string_to_json": 3.378486374999966, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation_jsonata.py::TestJsonManipulationJSONata::test_parse": 2.3657660589998386, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_add": 7.14482663199999, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random": 1.4101467749999301, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random_seeded": 0.7319624829998475, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations_jsonata.py::TestMathOperationsJSONata::test_math_random_seeded": 0.0019245199999886609, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split": 2.6798777819999486, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split_context_object": 0.6670701240000199, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_unique_id_generation.py::TestUniqueIdGeneration::test_uuid": 0.4465262699999357, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_False]": 0.9544782240001268, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_True]": 0.9576517719999629, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_False]": 0.9996447399998942, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_True]": 0.9510461510000141, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_False]": 0.9473354569998946, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_True]": 0.944271694999884, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_deleted_log_group": 0.9494689399999743, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_log_group_with_multiple_runs": 1.5385741449999841, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_False]": 0.6671723279999924, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_True]": 0.6575617329998522, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_False]": 0.6525587200000018, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_True]": 0.6636563000000706, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_False]": 0.6551389510000263, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_True]": 0.6468277180000541, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_False]": 0.9407028159999982, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_True]": 0.9425986719999173, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_False]": 0.7237593730000071, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_True]": 0.7704059490000645, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_False]": 0.6410263470000928, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_True]": 0.6389551490000258, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_False]": 0.9350075799999331, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_True]": 0.9356525670000337, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_False]": 0.9295935559998725, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_True]": 0.9365297639999426, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_False]": 0.85719275200006, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_True]": 0.8595070640000131, + "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_happy_path": 0.4649218040000278, + "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_hybrid_path": 0.3859992779999857, + "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_retry_path": 7.287881313000071, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": 1.5104381679999506, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": 1.478903124000226, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": 1.449284513999828, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": 1.4797105509999255, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": 0.9850285460001942, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": 0.9213897380001299, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": 0.6459490959998675, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_retries": 3.3871937600000592, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": 0.9611778510001159, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": 0.7098648070000309, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": 2.443971835999946, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": 1.143267556000069, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": 0.9000230139998848, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": 0.8943130230001088, + "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_set": 0.004498179000052005, + "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_unset": 0.006515632999935406, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_DIRECT_EXPR]": 0.8565157799999952, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EMPTY]": 0.6196766530000559, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EXPR]": 0.9278250759998627, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_LITERALS]": 1.0117282930001466, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_lambda[BASE_LAMBDA]": 2.6625980130002063, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[BOOL]": 0.5990962449999415, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[FLOAT]": 0.599402044000044, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[INT]": 0.6076174589999255, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[JSONATA_EXPR]": 0.81654504800008, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_EMPY]": 0.5982697069999858, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_RICH]": 0.8333995429999277, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[NULL]": 0.6059017470000754, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[STR_LIT]": 0.6162246929998219, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_task_lambda[BASE_TASK_LAMBDA]": 2.51585411699989, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_FALSE]": 0.8268836849998706, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_TRUE]": 0.8622091830001182, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSONATA]": 0.4352644950000695, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSON_PATH]": 0.4342592240000158, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_jsonata_query_language_field_downgrade_exception": 0.0014316729999563904, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE]": 0.41461808400003974, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE_DEFAULT]": 0.412078612000073, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONATA_TO_JSONPATH]": 2.270800948000101, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONPATH_TO_JSONATA]": 2.253996518999884, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONATA_TO_JSONPATH]": 2.5104426620000595, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONPATH_TO_JSONATA]": 2.262945189999982, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONATA_OUTPUT_TO_JSONPATH]": 0.7825480929999458, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONPATH_OUTPUT_TO_JSONATA]": 0.7908677289999559, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_task_dataflow_to_state": 2.331101700000204, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONATA_ASSIGN_JSONPATH_REF]": 0.7950797889999421, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONPATH_ASSIGN_JSONATA_REF]": 0.6084580100000494, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_empty": 2.1258660859999736, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_states_runtime": 2.4124591620000047, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO]": 0.7537995860000137, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO_JSONATA]": 0.7489256409999143, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_condition_constant_jsonata": 0.4593965430000253, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE]": 0.682107487000053, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_JSONATA]": 0.6685266570001431, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_LITERAL_JSONATA]": 0.6940128429998822, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.6766513829999212, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.6692178070001091, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.8506772340000452, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.7641094570000178, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_ASSIGN]": 0.6632465460000958, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_OUTPUT]": 0.6539519860000382, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONPATH]": 0.6570053609998467, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_STRING_LITERALS]": 1.774072488999991, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_cause_jsonata": 0.6194552560000375, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_error_jsonata": 0.616403478000052, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION]": 0.0015169109999533248, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION_2]": 0.001550564000012855, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_ERRORPATH]": 0.6638188289999789, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_CONTEXTPATH]": 0.6409136470001613, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_JSONPATH]": 0.6394653449999623, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_CAUSEPATH]": 0.6666008660000671, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_HEARTBEATSECONDSPATH]": 0.0013240140000334577, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_INPUTPATH]": 0.6257105940001111, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_OUTPUTPATH]": 0.6468296749999354, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_TIMEOUTSECONDSPATH]": 0.001428806999911103, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_empty_retry": 2.183717161000004, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_base": 9.596061772000098, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_extended_input": 9.738075471999991, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_with_retry_extended_input": 10.0268381489999, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_batching_base_json_max_per_batch_jsonata": 0.0015926810000337355, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_decl": 0.8257868880000387, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_first_line": 0.8200754729999744, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json": 0.7974556520000533, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items": 0.8476931740001419, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items_jsonata": 0.8544814759999326, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[INVALID_ITEMS_PATH]": 0.8671537670001044, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_ITEM_READER]": 0.8629255779999312, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_PREVIOUS]": 0.9118303869998954, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_list_objects_v2": 0.8157886590001908, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_first_row_extra_fields": 0.7903731699999526, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_duplicate_headers": 0.787734238999974, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_extra_fields": 0.78771610900003, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_first_row_typed_headers": 0.7958535290000555, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[0]": 0.8215810080000665, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[100000000]": 0.7811037609999403, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[2]": 0.8058885340000188, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[-1]": 0.8069669940000495, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[0]": 0.7741167209999276, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[1.5]": 0.017960853999966275, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000000]": 0.7788026140000284, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000001]": 0.8154565490000323, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[2]": 0.7794856959999379, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_json_no_json_list_object": 0.770710294999958, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state": 0.8432103449999886, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition": 0.8542080670000587, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition_legacy": 0.883681700000011, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch": 0.7658226830000103, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_empty_fail": 0.7668815289999884, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_legacy": 0.7522838859999865, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector": 0.8009209409999585, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector_parameters": 1.068085822999933, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_items_path_from_previous": 0.7889239700000417, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_parameters": 0.804703969000002, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant": 1.4406893600000785, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant_lambda": 2.724264208000136, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_item_selector": 0.7860973200000672, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_parameters": 0.8210414730000366, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR]": 1.7329706230000284, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR_JSONATA]": 0.7717198610000651, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_parameters": 1.0853433269999186, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_singleton": 1.09245502400006, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[empty]": 0.6417151849999527, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[mixed]": 0.6719161620000023, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[singleton]": 0.6590245690000529, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[boolean]": 1.71658656600016, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[function]": 0.001453363999985413, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[null]": 0.7101921029999403, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[number]": 0.5350459969999974, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[object]": 0.7017062759999817, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[string]": 0.6985786380000718, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[boolean]": 0.721392933000061, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[null]": 0.7271091640001259, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[number]": 0.7692483060000086, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[object]": 0.7306621269999596, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[string]": 0.7253498249998529, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[empty]": 0.6682329929999469, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[mixed]": 0.7091676700000562, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[singleton]": 0.679793241000084, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[boolean]": 0.6560907530000577, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[null]": 0.8575762380000924, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[number]": 0.9289312989999416, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[object]": 0.8803756029999477, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[string]": 0.8518144079999956, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[boolean]": 0.7322244840001986, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[null]": 0.7331614480000326, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[number]": 0.7339278449999256, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[object]": 0.5429761970001437, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[string]": 0.7294905369999469, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_label": 0.6892345830001432, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy": 0.8386783539999669, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed": 0.7451264469999614, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_item_selector": 0.783849411999995, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_parameters": 0.766857765000168, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline": 0.7772348749999765, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_item_selector": 0.8111688850000291, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_parameters": 0.8147210529999711, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_reentrant": 1.5157979589998831, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested": 0.8582524990000593, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed": 0.8366607350000095, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed_no_max_max_concurrency": 11.096241853000038, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_no_processor_config": 0.7555171359999804, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_legacy": 1.7431363249999094, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_singleton_legacy": 1.0814228069999672, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_result_writer": 0.9679549370000586, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry": 3.715458570999999, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_legacy": 3.7319825869999477, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_multiple_retriers": 7.753578466000022, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[-1]": 0.6948851330000707, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[0]": 0.6937183109999978, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[1]": 0.7215072169998393, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[NoNumber]": 0.6976194980001083, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[tolerated_failure_count_value0]": 0.7462855730000229, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1.1]": 0.6778342559998691, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1]": 0.6689009859998123, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[0]": 0.7161152920000404, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1.1]": 0.6882725750000418, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100.1]": 0.6724968180000133, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100]": 0.6807517970000845, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1]": 0.7083904380000376, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[NoNumber]": 0.6740306050000981, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[tolerated_failure_percentage_value0]": 0.7043530979999559, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[count_literal]": 0.6787886369999114, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[percentage_literal]": 0.7028972760000443, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[0]": 0.6920890930000496, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[1]": 0.7022614250000743, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[NoNumber]": 0.6619229799999857, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[max_concurrency_value0]": 0.6895781929999885, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path_negative": 0.7260900409999067, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE]": 0.7991212970000561, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE_PARAMETERS]": 0.7141865920000328, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_catch": 0.6907764900000757, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_fail": 0.657077496999932, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_nested": 0.9090290560000085, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_order": 0.7898281930001758, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_retry": 3.631920515000047, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features": 6.550807052999971, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_jitter_none": 4.455392958999937, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_max_attempts_zero": 2.205439081999998, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_seconds_jsonata": 0.4220421500000384, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[NANOSECONDS]": 0.4080955399998629, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[SECONDS]": 0.4215188779999153, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_DATE]": 0.354175750999957, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_ISO]": 0.3457314120000774, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_TIME]": 0.3449061699999447, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[JSONATA]": 0.35548078700003316, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_T]": 0.36989638399995783, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_Z]": 0.34718054300003587, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_DATE]": 0.001255134999951224, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_ISO]": 0.0012106130000120174, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_TIME]": 0.001335714999981974, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NANOSECONDS]": 0.6345200830000977, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_T]": 0.0014879689999816037, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_Z]": 0.0012779280000358995, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[SECONDS]": 0.6811168309999402, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_DATE]": 0.641259122000065, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_ISO]": 0.6425941260000627, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_TIME]": 0.44567078800002946, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NANOSECONDS]": 0.6462392830001136, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_T]": 0.6466972619999751, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_Z]": 0.6373484059998873, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[SECONDS]": 0.655332493999822, + "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_path_based_on_data": 8.525910078999914, + "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_step_functions_calling_api_gateway": 13.399877156000002, + "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_wait_for_callback": 21.502328047000105, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_base": 2.978728528000147, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_error": 2.6306461910000962, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[HelloWorld]": 2.977694278000172, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[None]": 2.973088604000168, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[]": 2.9838867619998837, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[request_body3]": 2.9643499080002584, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header1]": 3.0003308660004677, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header2]": 3.040210503000253, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[singleStringHeader]": 0.002461710000034145, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_query_parameters": 3.1116020980000485, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_delete_item": 1.0046548470002108, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_get_item": 1.2152030489999106, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_update_get_item": 1.3682963510000263, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_list_secrets": 0.8629939989998547, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[binary]": 1.015403847000016, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[bytearray]": 2.2309794389998387, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_binary]": 1.056208166000033, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_str]": 1.0946390289998362, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[str]": 1.0297221479995642, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[bool]": 1.098107974999948, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[dict]": 1.0846987019997414, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[list]": 1.0825935170000776, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[num]": 1.0818839270000353, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[str]": 1.0930626490001032, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template0]": 0.8539934289997291, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template1]": 0.8465384029998404, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution": 0.915937920000033, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution_implicit_json_serialisation": 0.9958073200000399, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_DELETE_ITEM]": 1.0037659009999516, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_GET_ITEM]": 0.9714120390001426, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_QUERY]": 1.2190684070001225, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_UPDATE_GET_ITEM]": 1.3446013719999428, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_invalid_integration": 0.5198966529999325, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task": 0.0014105239999935293, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_raise_failure": 0.0013457939999170776, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync": 0.0013249849998828722, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync_raise_failure": 0.0012906510000902927, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_base": 1.9750714250001238, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_malformed_detail": 0.870598604999941, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_mixed_malformed_detail": 0.9015939409998737, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_no_source": 31.013957537999886, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_bytes_payload": 2.0451227040000504, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0.0]": 2.077203504000181, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_0]": 2.0493138610002006, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_1]": 2.0530038009999316, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[HelloWorld]": 2.0617401960000734, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[True]": 2.0501758319999226, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value5]": 2.0835334739997506, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value6]": 2.045453359000021, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_pipe": 3.7522378619999017, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_string_payload": 2.0775800480000726, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_lambda_task_filter_parameters_input": 2.146494283000038, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke": 2.4905850289999307, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_bytes_payload": 2.2899421140000413, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0.0]": 2.539797380999971, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_0]": 2.3666762539999127, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_1]": 2.521461226000156, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[HelloWorld]": 2.5336736459998974, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[True]": 2.320306366000068, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value5]": 2.5070964139999887, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value6]": 2.572920606000025, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_unsupported_param": 2.5388529829999698, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_list_functions": 0.0016159069998593623, + "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution": 0.9429495130000305, + "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution_input_json": 2.249640737000391, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params0-True]": 0.8859050380001463, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params1-False]": 0.9171690229998148, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[1]": 0.8620628969999871, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[HelloWorld]": 0.8498827190001066, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[None]": 0.8872691839999334, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[True]": 0.9035265850000087, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[]": 0.895389631000171, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[message1]": 0.8662667210001018, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base_error_topic_arn": 0.8711489669997263, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[\"HelloWorld\"]": 0.9669459040001129, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[HelloWorld]": 1.0336747189999187, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[message_value3]": 0.9907786109999961, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[{}]": 0.9722182850000536, + "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message": 1.0059260650000397, + "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_attributes": 1.061620038000001, + "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_unsupported_parameters": 1.022527104999881, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING]": 2.2851763609999125, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING_TO_JSONPATH]": 2.2795353289998275, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT]": 0.001723113000025478, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.0013117160001456796, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_VARIABLE_SAMPLING]": 0.0014117320001787448, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT]": 0.0014204189999418304, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.0012970069999482803, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_VARIABLE_SAMPLING]": 0.0013980779999656079, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT]": 2.5425632550000046, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT_TO_JSONPATH]": 2.286581460000207, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY]": 3.6223718720000306, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY_TO_JSONPATH]": 3.517191249000007, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dump]": 1.455828449999899, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dumps]": 1.5074513570000363, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dump]": 1.447662697999931, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dumps]": 2.4545108800000435, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_invalid_sm": 0.4661109989999659, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_valid_sm": 1.5502120230000855, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_definition_format_sm": 0.40055327999994006, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_sm_name": 0.3913319490000049, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_exact_duplicate_sm": 0.4290000799999234, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition": 0.45521460700001626, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition_and_role": 0.5969184049999967, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_role_arn": 0.5777497849999236, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_update_none": 0.4014350499999182, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_same_parameters": 0.5165326989999812, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_delete_nonexistent_sm": 0.3715536020001764, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution": 0.6318914969999696, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_arn_containing_punctuation": 0.6245696249998218, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_invalid_arn": 0.33634016199971484, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_no_such_state_machine": 0.6491895859999204, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_invalid_arn_sm": 0.3349278539999432, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_nonexistent_sm": 0.38232449099973564, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_sm_arn_containing_punctuation": 0.3786171080002987, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_state_machine_for_execution": 0.4436429790000602, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_invalid_arn": 0.3221701039999516, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_no_such_execution": 0.43469368700016275, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_reversed": 0.47089356200012844, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_arn": 0.37151989700009835, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_input": 0.6884124250002515, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_invalid_arn": 0.33663754499980314, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_no_such_state_machine": 0.37454473200000393, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_pagination": 1.2949013369998283, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_versions_pagination": 1.4038130930002808, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms": 0.5269642559999284, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms_pagination": 0.870764551999855, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution": 0.5319297689995892, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution_idempotent": 1.1434892390000186, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_sync_execution": 0.41012675399997534, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_state_machine_status_filter": 0.5427347660001942, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_stop_execution": 0.45740701900012937, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[\\x00activity]": 0.2694624930002192, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity name]": 0.26439781300018694, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\"name]": 0.26594500999976844, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity#name]": 0.26864252000018496, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity$name]": 0.2727922680001029, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity%name]": 0.2672791340000913, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity&name]": 0.2652647369998249, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity*name]": 0.2693485109998619, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity,name]": 0.27638648100014507, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity/name]": 0.2740626130000692, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity:name]": 0.26993699500008006, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity;name]": 0.27830236800014063, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activityname]": 0.26855861599983655, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity?name]": 0.2712787440000284, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity[name]": 0.26982070199983355, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\\\name]": 0.26892137800018645, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x1f]": 0.26962413699993704, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x7f]": 0.2797547980001127, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity]name]": 0.2645891119998396, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity^name]": 0.27367608200006543, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity`name]": 0.2742184400001406, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity{name]": 0.26574337600004583, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity|name]": 0.2732202839997626, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity}name]": 0.2718794780000735, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity~name]": 0.2812859360001312, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[ACTIVITY_NAME_ABC]": 0.36404359900029704, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[Activity1]": 0.3488405200002944, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]": 0.3364496690001033, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name.1]": 0.3446965629998431, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name_123]": 0.3587593030001699, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name.v2]": 0.34000516900005096, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name]": 0.3358813890001784, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activityName.with.dots]": 0.34054381299984016, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity_123.name]": 0.3461990840000908, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_activity_invalid_arn": 0.34742549700013114, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_deleted_activity": 0.3038433240001268, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_deleted": 0.29910061999999016, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_invalid_arn": 0.36540262600010465, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_list_activities": 0.30509806399982153, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_create_alias_single_router_config": 0.5769438019997324, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_delete_list": 0.766673055000183, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_invoke_describe_list": 0.7660878160002085, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_update_describe": 0.6879029660001379, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_no_such_alias_arn": 0.5915638789999775, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_revision_with_alias": 0.5749963620000926, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_version_with_alias": 1.8059554810001828, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_name": 0.6105705269997088, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_router_configs": 0.6439699699997163, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_not_idempotent": 0.6121604970001044, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_with_state_machine_arn": 0.5874254679999922, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_idempotent_create_alias": 0.6202524850000373, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_invalid_next_token": 0.6192526289999023, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[0]": 0.7004264319998583, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[1]": 0.6940009019999707, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_update_no_such_alias_arn": 0.6039409489999343, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_create_describe_delete": 0.6420727930001249, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_activity_task": 0.7679716450002161, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[SYNC]": 0.7271125379998011, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[WAIT_FOR_TASK_TOKEN]": 0.7341727190002985, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_async_describe_history_execution": 1.2579329950001465, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_sync_execution": 0.6730412439997053, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_deleted_log_group": 0.44795972099996106, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration0]": 0.38479956100013624, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration1]": 0.4176422549999188, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration0]": 0.3614154100000633, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration1]": 0.3367657810003948, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration2]": 0.3353904710002098, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-False]": 0.4431069149998166, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-True]": 0.4275375019999501, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-False]": 0.4168814479999128, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-True]": 0.42266378800036364, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-False]": 0.4202333239998097, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-True]": 0.42434028499974374, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-False]": 0.42655233799996495, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-True]": 0.4253541029997905, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_multiple_destinations": 0.3820960430002742, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_update_logging_configuration": 0.5599695299999894, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_list_map_runs_and_describe_map_run": 0.8090661280000404, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_empty_fail": 0.2982047190002959, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[ ]": 0.3096916599999986, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\"]": 0.28330881999977464, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[#]": 0.2857859370001279, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[$]": 0.2899813510000513, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[%]": 0.27936332100011896, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[&]": 0.2951200659999813, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[*]": 0.2820116160000907, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[,]": 0.28098302400007924, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[:]": 0.28148987399981706, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[;]": 0.28378896399999576, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[<]": 0.2872835620000842, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[>]": 0.2822845039997901, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[?]": 0.2810987899999873, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[[]": 0.2864168819999122, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\\\]": 0.2911610559997371, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\n]": 0.2840047610000056, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\r]": 0.2732440819997919, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\t]": 0.2913525949998075, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x00]": 0.2854353949999222, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x01]": 0.29611834599995746, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x02]": 0.3279465070002061, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x03]": 0.320164092999903, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x04]": 0.2817061470000226, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x05]": 0.28866665500004274, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x06]": 0.28059872400012864, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x07]": 0.2868114590000914, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x08]": 0.2802511180000238, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0b]": 0.2878253639999002, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0c]": 0.2874051059998237, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0e]": 0.289644730000191, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0f]": 0.2861423639997156, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x10]": 0.28188491700007035, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x11]": 0.2821781600000577, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x12]": 0.2896934630002761, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x13]": 0.30172197400020195, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x14]": 0.2858908870000505, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x15]": 0.295288919000086, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x16]": 0.2970946160003223, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x17]": 0.2854559299998982, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x18]": 0.2799820370000816, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x19]": 0.2822970520001036, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1a]": 0.28592698300008124, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1b]": 0.28291799999988143, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1c]": 0.29178949000015564, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1d]": 0.2956381480003074, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1e]": 0.28228567099995416, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1f]": 0.27987972599976274, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x7f]": 0.28626822799992624, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x80]": 0.2978710519996639, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x81]": 0.27896393499986516, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x82]": 0.28845693100015524, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x83]": 0.2881292319998465, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x84]": 0.28015330599987465, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x85]": 0.30609100100014075, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x86]": 0.3131425080000554, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x87]": 0.28532149900024706, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x88]": 0.28444473900003686, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x89]": 0.2914696660000118, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8a]": 0.2906591659998412, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8b]": 0.28784513400000833, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8c]": 0.28578058299990516, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8d]": 0.2958572559998629, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8e]": 0.2884580959998857, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8f]": 0.289518815999827, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x90]": 0.2830009819999759, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x91]": 0.28544497100006083, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x92]": 0.29064888699986113, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x93]": 0.2863837459997285, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x94]": 0.2909311329999582, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x95]": 0.28907287700008055, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x96]": 0.28387240299980476, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x97]": 0.2787383840000075, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x98]": 0.2830254379998678, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x99]": 0.2792593520000537, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9a]": 0.28292196699999295, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9b]": 0.2842076070003259, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9c]": 0.29497174800007997, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9d]": 0.29116712700010794, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9e]": 0.280328786000382, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9f]": 0.27714028700006565, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[]]": 0.28107411300015883, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[^]": 0.2800892999998723, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[`]": 0.2933119759998135, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[{]": 0.28501219700001457, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[|]": 0.292037333999815, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[}]": 0.2892943730000752, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[~]": 0.29101467300006334, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_too_long_fail": 0.30246001200021055, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_create_state_machine": 0.31471586999987267, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[None]": 0.3115964069997972, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list1]": 0.33941620500013414, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list2]": 0.359933017000003, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list3]": 0.30702289999999266, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list0]": 0.3290532080002322, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list1]": 0.32478021999986595, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list2]": 0.33326286900000923, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list3]": 0.3301218859999153, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list4]": 0.32579951699995036, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine_version": 0.31691086200021346, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys0]": 0.33380092900006275, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys1]": 0.3244383249998464, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys2]": 0.3266782090001925, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys3]": 0.3265721630000371, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_DICT]": 0.27368374699995, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_STRING]": 0.2813416110000162, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[NOT_A_DEF]": 0.2711640030001945, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[ILLEGAL_WFTT]": 0.29251751000015247, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_BASE_NO_STARTAT]": 0.2765057260000958, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_BASE_PASS]": 0.28299971499973253, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_BASE_NO_STARTAT]": 0.28795555199985756, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_BASE_PASS]": 0.2836752549999346, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 1.7557982299999821, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_PARAMETERS]": 0.8613147079997816, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_RESULT]": 0.8181745750000573, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_EVALUATION_ORDER_PASS_STATE]": 0.8697814649995053, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_CHOICE]": 0.898569481000095, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_FAIL]": 0.797702843000252, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INPUTPATH]": 0.7858166719997826, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.1834529559998828, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 1.5324656489999597, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_OUTPUTPATH]": 0.8493412809998517, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_PARAMETERS]": 0.8116150050000215, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_WAIT]": 0.6117544750002253, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.14703382100015, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.1541426180001508, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 0.9435908350003501, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 0.8425658740000017, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 0.8734228490002351, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 0.8731554370001504, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_CONDITION_CONSTANT_JSONATA]": 0.4598278770001798, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.47466705800002273, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_express_with_publish": 0.36909724599968285, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_no_version_description": 0.40268262400013555, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_with_version_description": 0.4028376249998473, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_publish": 0.36855261400000927, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_version_description_no_publish": 0.3396498659999452, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version": 0.46787771699996483, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version_with_revision": 1.7409699550000823, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_no_publish_on_creation": 0.40766628199980914, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_publish_on_creation": 0.3923105369997302, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_idempotent_publish": 0.4036295730002166, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_delete_version": 0.4318062539998664, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_state_machine_versions_pagination": 0.9050393189997976, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version": 0.5158844940001472, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_invalid_arn": 0.34471958599988284, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_no_such_machine": 0.38646450399983223, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_start_version_execution": 0.540428036999856, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_update_state_machine": 0.45051492600009624, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_version_ids_between_deletions": 0.41504477700004827, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_CHOICE_STATE]": 0.815037688999837, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_FAIL_STATE]": 0.6514022169999407, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_PASS_STATE]": 0.6616980649998823, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_RESULT_PASS_STATE]": 0.6837199870001314, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_SUCCEED_STATE]": 0.6680279370000335, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_PASS_STATE]": 0.7504055059998791, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_RESULT_PASS_STATE]": 0.7632075980002355, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_CHOICE_STATE]": 0.5718781839998428, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_FAIL_STATE]": 0.41024080000011054, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_PASS_STATE]": 0.41771617400013383, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_RESULT_PASS_STATE]": 0.4196921899999779, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_SUCCEED_STATE]": 0.40505238800005827, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_PASS_STATE]": 0.5008502449998105, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_RESULT_PASS_STATE]": 0.5093722219996835, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_CHOICE_STATE]": 0.8278172519999316, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_FAIL_STATE]": 0.6840251429996442, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_PASS_STATE]": 0.6622020620000058, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_RESULT_PASS_STATE]": 0.669345438000164, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_SUCCEED_STATE]": 0.658141373000035, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_PASS_STATE]": 0.7942755189997115, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_RESULT_PASS_STATE]": 0.7707484350003142, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[DEBUG]": 2.3081441649999306, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[INFO]": 2.3130773159998625, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[TRACE]": 2.3286053139997875, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[DEBUG]": 2.3062292409999827, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[INFO]": 2.2913872240001183, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[TRACE]": 2.318082245999676, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_choice_state_machine": 2.934022679999998, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_map_state_machine": 1.2224994480000078, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_state_machine": 1.5981542629999694, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_state_machines_in_parallel": 2.1759436429999823, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_events_state_machine": 0.0013729130000683654, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_intrinsic_functions": 1.279459959000178, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_try_catch_state_machine": 10.199330852000003, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_aws_sdk_task": 1.2237063790000775, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_default_logging_configuration": 0.07177152099984596, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-central-1]": 0.0012207409999973606, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-west-1]": 0.0012446759999420465, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-1]": 0.0019506080000155634, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-2]": 0.0012408879999838973, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_run_aws_sdk_secrets_manager": 3.403536338000322, + "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_no_timeout": 5.893610356999716, + "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_path_timeout": 5.95751552999991, + "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_timeout": 6.034737336000035, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_lambda": 6.851902800000062, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda": 6.881835702000217, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda_with_path": 6.840051506999771, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_global_timeout": 5.514187309000135, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_service_lambda_map_timeout": 0.0016153750000285072, + "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_assume_role_tag_validation": 0.16289301499978137, + "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_iam_role_chaining_override_transitive_tags": 0.2296982080001726, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_non_existent_role": 0.019445780000069135, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role": 0.22107210400008626, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_saml": 0.020377013000370425, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_web_identity": 0.01884468200023548, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_expiration_date_format": 0.01499617399986164, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[False]": 0.11107185700006994, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[True]": 0.10342340000011063, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_root": 0.016750987999557765, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[False]": 0.08004611699993802, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[True]": 0.19875780800020948, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_federation_token": 0.0937704450000183, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_sts_invalid_parameters": 0.0730474309998499, + "tests/aws/services/support/test_support.py::TestConfigService::test_support_case_lifecycle": 0.07528906100014865, + "tests/aws/services/swf/test_swf.py::TestSwf::test_run_workflow": 0.2067564740002581, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_deletion": 0.2104494590005288, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_start_transcription_job": 0.35872204300017074, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_get_transcription_job": 2.608120640999914, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_list_transcription_jobs": 2.5435078090001753, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_invalid_length": 32.44955604300003, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_speaker_labels": 0.001341499999853113, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_happy_path": 2.546047976999944, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_speaker_diarization": 0.0016446969998469285, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[None-None]": 2.3403817150001487, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-2-None]": 2.5885377179999978, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-3-test-output]": 2.4106673810001666, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-4-test-output.json]": 5.06222421099983, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-5-test-files/test-output.json]": 2.4235455749999346, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-6-test-files/test-output]": 4.989362425999843, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job_same_name": 2.9120818119997693, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.amr-hello my name is]": 2.1762116109998715, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.flac-hello my name is]": 2.185134156999993, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp3-hello my name is]": 2.1810205020001376, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp4-hello my name is]": 2.189077824000151, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.ogg-hello my name is]": 2.171964302000106, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.webm-hello my name is]": 2.1757555570000022, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mkv-one of the most vital]": 2.197480847999941, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mp4-one of the most vital]": 2.1884996569999657, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_unsupported_media_format_failure": 3.197422684000003, + "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_error_injection": 25.73564731400029, + "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_read_error_injection": 25.738237048999963, + "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_write_error_injection": 51.381935092000504, + "tests/aws/test_error_injection.py::TestErrorInjection::test_kinesis_error_injection": 2.02135969699998, + "tests/aws/test_integration.py::TestIntegration::test_firehose_extended_s3": 0.22325086300020303, + "tests/aws/test_integration.py::TestIntegration::test_firehose_kinesis_to_s3": 51.589191119999896, + "tests/aws/test_integration.py::TestIntegration::test_firehose_s3": 0.3558000069999707, + "tests/aws/test_integration.py::TestIntegration::test_lambda_streams_batch_and_transactions": 26.87984179299974, + "tests/aws/test_integration.py::TestIntegration::test_scheduled_lambda": 46.39182503899974, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.10]": 1.997800419999976, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.11]": 2.005218125999818, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.12]": 1.9817306749998806, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.13]": 1.9770219800000177, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.8]": 1.953781465999782, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.9]": 1.9604283910000504, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.10]": 7.985243243999776, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.11]": 7.92167346899987, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.12]": 1.8561595389999184, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.13]": 7.958425087000023, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.8]": 15.98100621499998, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.9]": 1.8654234789996735, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.10]": 4.0344607579997955, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.11]": 3.997534539999833, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.12]": 4.037196066999968, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.13]": 4.000019816000076, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.8]": 4.006171419999873, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.9]": 4.043954328999689, + "tests/aws/test_integration.py::test_kinesis_lambda_forward_chain": 0.002535214000090491, + "tests/aws/test_moto.py::test_call_include_response_metadata": 0.007765847000200665, + "tests/aws/test_moto.py::test_call_multi_region_backends": 0.017100282000001243, + "tests/aws/test_moto.py::test_call_non_implemented_operation": 0.04491914499999439, + "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[IO[bytes]]": 0.023063054999738597, + "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[bytes]": 0.028169944999945074, + "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[str]": 0.06021391900026174, + "tests/aws/test_moto.py::test_call_sqs_invalid_call_raises_http_exception": 0.014636858000130815, + "tests/aws/test_moto.py::test_call_with_es_creates_state_correctly": 0.06320221699979811, + "tests/aws/test_moto.py::test_call_with_modified_request": 0.012074723000068843, + "tests/aws/test_moto.py::test_call_with_sns_with_full_uri": 0.005441130999770394, + "tests/aws/test_moto.py::test_call_with_sqs_creates_state_correctly": 2.095916200999909, + "tests/aws/test_moto.py::test_call_with_sqs_invalid_call_raises_exception": 0.007622338999908607, + "tests/aws/test_moto.py::test_call_with_sqs_modifies_state_in_moto_backend": 0.011041840999951091, + "tests/aws/test_moto.py::test_call_with_sqs_returns_service_response": 0.006905204999839043, + "tests/aws/test_moto.py::test_moto_fallback_dispatcher": 0.010007336999933614, + "tests/aws/test_moto.py::test_moto_fallback_dispatcher_error_handling": 0.0412454610000168, + "tests/aws/test_moto.py::test_request_with_multi_protocol_non_default_protocol": 0.0012680499999078165, + "tests/aws/test_moto.py::test_request_with_response_header_location_fields": 0.10339789200020277, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_localstack_backends": 0.17225311499987583, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_moto_backends": 1.348460455999657, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_dynamodb": 0.33413467900004434, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_kinesis": 1.4324311980001312, + "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_api_gateway": 0.48391825599969707, + "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_sns": 0.09833296300007532, + "tests/aws/test_network_configuration.py::TestLambda::test_function_url": 1.1771073680001791, + "tests/aws/test_network_configuration.py::TestLambda::test_http_api_for_function_url": 0.0014825279999968188, + "tests/aws/test_network_configuration.py::TestOpenSearch::test_default_strategy": 16.882774949000122, + "tests/aws/test_network_configuration.py::TestOpenSearch::test_path_strategy": 16.675977731000103, + "tests/aws/test_network_configuration.py::TestOpenSearch::test_port_strategy": 16.588410995000004, + "tests/aws/test_network_configuration.py::TestS3::test_201_response": 0.0851535489998696, + "tests/aws/test_network_configuration.py::TestS3::test_multipart_upload": 0.10855221899987555, + "tests/aws/test_network_configuration.py::TestS3::test_non_us_east_1_location": 0.07016762000012022, + "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[domain]": 0.022150418999672183, + "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[standard]": 0.023223933999815927, + "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_with_external_port": 0.023408800000197516, + "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_without_external_port": 0.024459554000031858, + "tests/aws/test_network_configuration.py::TestSQS::test_path_strategy": 0.022869075000016892, + "tests/aws/test_notifications.py::TestNotifications::test_sns_to_sqs": 0.16105597299997498, + "tests/aws/test_notifications.py::TestNotifications::test_sqs_queue_names": 0.022074117000101978, + "tests/aws/test_serverless.py::TestServerless::test_apigateway_deployed": 0.04375949000018409, + "tests/aws/test_serverless.py::TestServerless::test_dynamodb_stream_handler_deployed": 0.0433799730001283, + "tests/aws/test_serverless.py::TestServerless::test_event_rules_deployed": 98.67178473599984, + "tests/aws/test_serverless.py::TestServerless::test_kinesis_stream_handler_deployed": 0.001249244000064209, + "tests/aws/test_serverless.py::TestServerless::test_lambda_with_configs_deployed": 0.03782760599983703, + "tests/aws/test_serverless.py::TestServerless::test_queue_handler_deployed": 0.0444987549999496, + "tests/aws/test_serverless.py::TestServerless::test_s3_bucket_deployed": 27.794429918000105, + "tests/aws/test_validate.py::TestMissingParameter::test_elasticache": 0.0012768149997555156, + "tests/aws/test_validate.py::TestMissingParameter::test_opensearch": 0.005046037999818509, + "tests/aws/test_validate.py::TestMissingParameter::test_sns": 0.0013006589999804419, + "tests/aws/test_validate.py::TestMissingParameter::test_sqs_create_queue": 0.001211224000371658, + "tests/aws/test_validate.py::TestMissingParameter::test_sqs_send_message": 0.0012062939999850641, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_container_starts_non_root": 0.0013143759997547022, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_custom_docker_flags": 0.0012123960000280931, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_logs": 0.001262487999838413, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_pulling_image_message": 0.0012681890000294516, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_restart": 0.0012832470001740148, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_already_existing": 0.0013125120001404866, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_already_running": 0.0012694920001194987, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_cli_within_container": 0.001236159999962183, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_wait_stop": 0.001203850000138118, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_status_services": 0.0012125949999699515, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_volume_dir_mounted_correctly": 0.0016644059999180172, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_wait_timeout_raises_exception": 0.0012617869999758113, + "tests/cli/test_cli.py::TestDNSServer::test_dns_port_not_published_by_default": 0.0012203100000078848, + "tests/cli/test_cli.py::TestDNSServer::test_dns_port_published_with_flag": 0.0012254289999873436, + "tests/cli/test_cli.py::TestHooks::test_prepare_host_hook_called_with_correct_dirs": 0.5863296360000732, + "tests/cli/test_cli.py::TestImports::test_import_venv": 0.007570002999727876, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_404_unfortunately_detected_as_s3_request": 0.046189517000129854, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_internal_failure_handler_http_errors": 0.01835274199993364, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_http_errors": 0.0015470180001102563, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_unexpected_errors": 0.0014265639999848645, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_patch_http_errors": 0.15189248000024236, + "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_http": 0.08159936800007017, + "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https": 0.07610825300002944, + "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https_localhost": 0.05950527600020905, + "tests/integration/aws/test_app.py::TestHttps::test_default_cert_works": 0.08117448699999841, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_return_response": 0.0012781779998931597, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_ssl_websockets": 0.0013071310002032988, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websocket_reject_through_edge_router": 0.0012996589998692798, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websockets_served_through_edge_router": 0.0016424240000105783, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_request_streaming": 0.13899842599994372, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_response_streaming": 0.1601849650000986, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_raw_header_handling": 0.18866339599981075, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_response_close_handlers_called_with_router": 0.13924651800016363, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-False]": 0.0014136699999198754, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-True]": 0.0014346090001708944, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-False]": 0.0014065259997551038, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-True]": 0.0014227870001377596, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-False]": 2.9760576409998976, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-True]": 2.9915073319998555, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-False]": 3.0260625230002915, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-True]": 2.240534951000427, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[CmdDockerClient]": 0.0014071489997604658, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[SdkDockerClient]": 22.27311114700001, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[CmdDockerClient]": 0.001536849999865808, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[SdkDockerClient]": 0.29455210600008286, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[CmdDockerClient]": 0.0014414809998015699, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[SdkDockerClient]": 0.203184224999859, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[CmdDockerClient]": 0.0015370899998288223, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[SdkDockerClient]": 0.24866048300009425, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[CmdDockerClient]": 0.001421615000026577, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[SdkDockerClient]": 0.2411842669998805, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[CmdDockerClient]": 0.001486154999838618, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[SdkDockerClient]": 0.24808871999994153, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[CmdDockerClient]": 0.0014302899999165675, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[SdkDockerClient]": 0.26957559099946593, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[CmdDockerClient]": 0.0014286070002071938, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[SdkDockerClient]": 0.00820101500085002, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[CmdDockerClient]": 0.0017722769998727017, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[SdkDockerClient]": 0.19508825600041746, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[CmdDockerClient]": 0.0015121530000214989, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[SdkDockerClient]": 0.3502553080002144, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[CmdDockerClient]": 0.0015219809999962308, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[SdkDockerClient]": 0.23179477100029544, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[CmdDockerClient]": 0.0015237949999118428, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[SdkDockerClient]": 0.007568006999917998, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[CmdDockerClient]": 0.0014508280000882223, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[SdkDockerClient]": 0.8921736500001316, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[CmdDockerClient]": 0.001425410999900123, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[SdkDockerClient]": 1.1906941550000738, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[CmdDockerClient]": 0.0014347889996315644, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[SdkDockerClient]": 0.026445284000146785, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[CmdDockerClient]": 0.0013992530000450643, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[SdkDockerClient]": 0.2277763529996264, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[CmdDockerClient]": 0.0015288440001768322, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[SdkDockerClient]": 0.2203259339999022, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-False]": 0.0014168949999202596, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-True]": 0.0014208030002009764, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-False]": 0.19046954200030086, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-True]": 0.2075875869995798, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-False]": 0.0015032859998882486, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-True]": 0.0014218850001270766, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-False]": 0.2049280499991255, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-True]": 0.19887853699992775, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[CmdDockerClient]": 0.0014125979998880211, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[SdkDockerClient]": 0.004677941999943869, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[CmdDockerClient]": 0.001400004000061017, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[SdkDockerClient]": 0.02971875400044155, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[CmdDockerClient]": 0.0014377550000972406, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[SdkDockerClient]": 0.028084823999506625, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[CmdDockerClient]": 0.0012893489999896701, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[SdkDockerClient]": 0.001277496000056999, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[CmdDockerClient]": 0.0014047330000721558, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[SdkDockerClient]": 2.1129838860001655, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[CmdDockerClient]": 0.007719670000142287, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[SdkDockerClient]": 0.005567737000546913, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[CmdDockerClient]": 0.0014342080000915303, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[SdkDockerClient]": 0.2565331099999639, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[CmdDockerClient]": 0.0014478119999239425, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[SdkDockerClient]": 0.25939388000006147, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[CmdDockerClient]": 0.0014301809999324178, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[SdkDockerClient]": 0.03279205299986643, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[CmdDockerClient]": 0.0015190359999905922, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[SdkDockerClient]": 0.2465640949999397, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[CmdDockerClient]": 0.0014955530000406725, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[SdkDockerClient]": 0.28799500600007377, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[CmdDockerClient]": 0.0014816859998063592, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[SdkDockerClient]": 0.24450984699979017, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[CmdDockerClient]": 0.0013814900000852504, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[SdkDockerClient]": 0.23804629499909424, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[CmdDockerClient]": 0.009688152999842714, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[SdkDockerClient]": 0.27905647699981273, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_all_container_names_should_include_even_stopped_containers[CmdDockerClient]": 0.0013947959996585269, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_all_container_names_should_include_even_stopped_containers[SdkDockerClient]": 0.43114839299960295, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[CmdDockerClient]": 0.0015033260001473536, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[SdkDockerClient]": 0.026059131000238267, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[CmdDockerClient]": 0.001402349000045433, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[SdkDockerClient]": 0.8397267449995525, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[CmdDockerClient]": 0.0014133600000150182, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[SdkDockerClient]": 1.6453665439998986, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[CmdDockerClient]": 0.001417405999973198, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[SdkDockerClient]": 0.006871675000184041, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[CmdDockerClient]": 0.001536890000124913, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[SdkDockerClient]": 0.8378609050000705, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[CmdDockerClient]": 0.0014113849997556827, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[SdkDockerClient]": 1.654897436000283, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[CmdDockerClient]": 0.0013954060000287427, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[SdkDockerClient]": 0.2121221589995912, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[CmdDockerClient]": 0.001448224000114351, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[SdkDockerClient]": 0.006564701000115747, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[CmdDockerClient]": 0.0014009960000294086, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[SdkDockerClient]": 0.2207406790003006, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[CmdDockerClient]": 0.0014079889999720763, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[SdkDockerClient]": 0.04228904799947486, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[CmdDockerClient]": 0.0013942339999175601, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[SdkDockerClient]": 0.4290057549997073, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[CmdDockerClient]": 0.001397399999859772, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[SdkDockerClient]": 0.20403712400002405, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[CmdDockerClient]": 0.0014304410001386714, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[SdkDockerClient]": 0.36810051600059523, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[CmdDockerClient]": 0.0014186099999733415, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[SdkDockerClient]": 0.005557447999763099, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[CmdDockerClient]": 0.001424970999778452, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[SdkDockerClient]": 0.2034879250004451, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[CmdDockerClient]": 0.0014068059999772231, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[SdkDockerClient]": 0.006777027000225644, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[CmdDockerClient]": 0.0014090409999880649, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[SdkDockerClient]": 0.1777478739995786, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[CmdDockerClient]": 0.0013837129999956232, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[SdkDockerClient]": 0.007043324000278517, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[CmdDockerClient]": 0.001435950999848501, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[SdkDockerClient]": 0.029575827999451576, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[CmdDockerClient]": 0.0014576519999991433, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[SdkDockerClient]": 0.4345225249999203, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[CmdDockerClient]": 0.0014135999999780324, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[SdkDockerClient]": 0.00681201300039902, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_running_container_names_should_ignore_stopped_containers[CmdDockerClient]": 0.001433768000197233, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_running_container_names_should_ignore_stopped_containers[SdkDockerClient]": 0.38895521499989627, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[CmdDockerClient]": 0.0015310979999867413, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[SdkDockerClient]": 0.02409923200002595, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[CmdDockerClient]": 0.0038493910001307086, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[SdkDockerClient]": 0.0287523969996073, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[CmdDockerClient]": 0.0014433149999604211, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[SdkDockerClient]": 0.21537140000009458, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[CmdDockerClient]": 0.0012763540000833018, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[SdkDockerClient]": 0.0012612669997906778, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[CmdDockerClient]": 0.0014136090001102275, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[SdkDockerClient]": 0.18625000499969246, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[CmdDockerClient]": 0.0014198409999153228, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[SdkDockerClient]": 0.03606343299998116, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[CmdDockerClient]": 0.0014843810001821112, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[SdkDockerClient]": 0.16143997000017407, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[CmdDockerClient]": 0.0014543659999617375, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[SdkDockerClient]": 0.007395306999569584, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[CmdDockerClient]": 0.0013839639998423081, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[SdkDockerClient]": 20.42964576300028, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[CmdDockerClient]": 0.0013869409999642812, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[SdkDockerClient]": 0.08077750999973432, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[CmdDockerClient]": 0.0014085589998558135, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[SdkDockerClient]": 0.08528943299961611, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[CmdDockerClient]": 0.0013926110000284098, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[SdkDockerClient]": 0.005855682999936107, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[CmdDockerClient]": 0.0013891839998905198, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[SdkDockerClient]": 0.006638671000018803, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[CmdDockerClient]": 0.0015118930002699926, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[SdkDockerClient]": 0.2408772379999391, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[CmdDockerClient]": 0.0015409160000672273, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[SdkDockerClient]": 0.005409332999988692, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[CmdDockerClient]": 0.0014396779999970022, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[SdkDockerClient]": 1.6896245600000839, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[CmdDockerClient]": 0.001400254000145651, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[SdkDockerClient]": 1.9145194080001602, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[CmdDockerClient]": 0.0014061059998766723, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[SdkDockerClient]": 2.056144004000089, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[CmdDockerClient]": 0.0014196110000739282, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[SdkDockerClient]": 1.6814630669996404, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[CmdDockerClient]": 0.0014013270001669298, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[SdkDockerClient]": 0.8287883560001319, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[CmdDockerClient]": 0.0014628320000156236, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[SdkDockerClient]": 2.652870971000084, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[CmdDockerClient]": 0.0014011270000082732, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[SdkDockerClient]": 0.015871176000473497, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[CmdDockerClient]": 0.0014079190000302333, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[SdkDockerClient]": 0.006878046000110771, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_anonymous_volumes[CmdDockerClient]": 0.001412547000199993, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_anonymous_volumes[SdkDockerClient]": 4.0073730350000005, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_throw_exception_when_container_doesnt_exist_and_not_forcing[CmdDockerClient]": 0.0014181579999785754, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_throw_exception_when_container_doesnt_exist_and_not_forcing[SdkDockerClient]": 0.005855182999766839, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_work_even_when_container_doesnt_exist_because_of_forcing[CmdDockerClient]": 0.0014562700000624318, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_work_even_when_container_doesnt_exist_because_of_forcing[SdkDockerClient]": 0.005137377000664856, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_work_when_container_is_running_and_checking_container_existence[CmdDockerClient]": 0.0017905320000863867, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_work_when_container_is_running_and_checking_container_existence[SdkDockerClient]": 0.21209447199953502, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_work_when_container_is_stopped_and_checking_container_existence[CmdDockerClient]": 0.0015122030001748499, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_container_should_work_when_container_is_stopped_and_checking_container_existence[SdkDockerClient]": 0.04503438100027779, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[CmdDockerClient]": 0.0015049199998884433, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[SdkDockerClient]": 0.005368405999888637, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[CmdDockerClient]": 0.004103630999907182, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[SdkDockerClient]": 0.005337657999916701, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[CmdDockerClient]": 0.014423642000110704, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[SdkDockerClient]": 0.1832504049993986, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[CmdDockerClient]": 0.0014079089999086136, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[SdkDockerClient]": 2.0021920860003775, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[CmdDockerClient]": 0.0021577840000190918, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[SdkDockerClient]": 0.11429065200036348, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[CmdDockerClient]": 0.0014009460001034313, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[SdkDockerClient]": 0.8514135099994746, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[CmdDockerClient]": 0.0015737879998596327, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[SdkDockerClient]": 0.20492800100009845, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[CmdDockerClient]": 0.0014318629998797405, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[SdkDockerClient]": 0.1872367970004234, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[CmdDockerClient]": 0.0014705460000641324, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[SdkDockerClient]": 0.19558967600005417, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[CmdDockerClient]": 0.0013927410000178497, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[SdkDockerClient]": 11.943760120999286, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-echo]": 0.0014983979999669828, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-entrypoint1]": 0.0014016079999237263, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-echo]": 0.1836204030000772, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-entrypoint1]": 0.1951503310001499, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[CmdDockerClient]": 0.0014208729999154457, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[SdkDockerClient]": 0.005221854999490461, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[CmdDockerClient]": 0.002254574999824399, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[SdkDockerClient]": 0.006226894000064931, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[CmdDockerClient]": 0.0014949510000406008, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[SdkDockerClient]": 0.1882260050001605, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[CmdDockerClient]": 0.0013739350001742423, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[SdkDockerClient]": 0.005472849999932805, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[CmdDockerClient]": 0.0014379449999069038, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[SdkDockerClient]": 0.15481095199947958, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[CmdDockerClient]": 0.0014174569998886, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[SdkDockerClient]": 0.0062288980002449534, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[CmdDockerClient]": 0.0015062110001053952, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[SdkDockerClient]": 0.005317191000358434, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[CmdDockerClient]": 0.0032817549995343143, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[SdkDockerClient]": 0.6129665449998356, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[CmdDockerClient]": 0.0014590129999305645, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[SdkDockerClient]": 0.006213016999936372, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[CmdDockerClient]": 0.0015389430000141147, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[SdkDockerClient]": 0.005961730000763055, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[CmdDockerClient]": 0.0034539109997240303, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[SdkDockerClient]": 0.040319997000551666, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[CmdDockerClient]": 0.0014096620002419513, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[SdkDockerClient]": 1.2273794510001608, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[CmdDockerClient]": 0.0014256220001698239, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[SdkDockerClient]": 0.20573961799937024, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[CmdDockerClient]": 0.0015288820000023406, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[SdkDockerClient]": 0.20969910099938716, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[CmdDockerClient]": 0.0014608890000999963, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[SdkDockerClient]": 5.811885718999747, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[CmdDockerClient]": 0.0033854499997687526, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[SdkDockerClient]": 0.2022555979997378, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[CmdDockerClient]": 0.001530227000330342, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[SdkDockerClient]": 0.5343384399998286, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[CmdDockerClient]": 0.0071732639999027015, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[SdkDockerClient]": 0.814026749000277, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[CmdDockerClient]": 0.0014604560001316713, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[SdkDockerClient]": 0.17843500500021037, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[CmdDockerClient]": 0.0015080250004757545, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[SdkDockerClient]": 0.20054101499999888, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[CmdDockerClient]": 0.014571857000191812, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[SdkDockerClient]": 0.16719234900028823, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[CmdDockerClient]": 0.0015210900000965921, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[SdkDockerClient]": 0.23906621800051653, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[CmdDockerClient]": 0.002241689000129554, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[SdkDockerClient]": 0.1576368050000383, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_no_retries": 0.13170184200043877, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_after_init": 1.2779327209996154, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_on_init": 1.1406676540000262, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_timeout_seconds": 0.021520688999771664, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[CmdDockerClient]": 0.0015078350002113439, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[SdkDockerClient]": 0.37259845500057054, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[CmdDockerClient]": 0.0033505329997751687, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[SdkDockerClient]": 0.1867143410004246, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[CmdDockerClient]": 0.0014463099996646633, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[SdkDockerClient]": 0.23435144599989144, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[CmdDockerClient]": 0.004714934999810794, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[SdkDockerClient]": 0.41643684400014536, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[CmdDockerClient]": 0.0019246319998273975, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[SdkDockerClient]": 0.391863383999862, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[CmdDockerClient]": 0.001962873000138643, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[SdkDockerClient]": 0.032850023999799305, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-None]": 0.001393712999743002, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-tcp]": 0.0015041299998301838, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-udp]": 0.0014287780004451633, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-None]": 1.4859149109997816, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-tcp]": 1.498148060999938, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-udp]": 1.501350339000055, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-None]": 0.0034080430004905793, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-tcp]": 0.0014754959997844708, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-udp]": 0.0014989800001785625, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-None]": 2.7217243439999947, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-tcp]": 2.6387296500001867, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-udp]": 2.901062312999784, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[CmdDockerClient]": 0.0033752079998521367, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[SdkDockerClient]": 0.3787502280001718, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-False]": 0.0015056309998726647, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-True]": 0.0015315490004468302, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-False]": 0.13489488100003655, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-True]": 0.12092069500022262, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[CmdDockerClient]": 0.0014564589996552968, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[SdkDockerClient]": 0.20942816699971445, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[CmdDockerClient]": 0.0014950600002521242, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[SdkDockerClient]": 0.7517742690001796, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[CmdDockerClient]": 0.0014962740001465136, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[SdkDockerClient]": 0.42006964999973206, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[CmdDockerClient]": 0.0015056209995236713, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[SdkDockerClient]": 0.17939532899981714, + "tests/integration/services/test_internal.py::TestHealthResource::test_get": 0.019109316000140097, + "tests/integration/services/test_internal.py::TestHealthResource::test_head": 0.015374293999229849, + "tests/integration/services/test_internal.py::TestInfoEndpoint::test_get": 0.040711614999963786, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[boot-True]": 0.023648329000025115, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[ready-True]": 0.020980114999929356, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[shutdown-False]": 0.020283478000237665, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[start-True]": 0.020978761999685958, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_nonexisting_stage": 0.021183413000017026, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_stages_have_completed": 2.782768469000075, + "tests/integration/test_config_endpoint.py::test_config_endpoint": 0.039892633000363276, + "tests/integration/test_config_service.py::TestConfigService::test_put_configuration_recorder": 0.18140216099982354, + "tests/integration/test_config_service.py::TestConfigService::test_put_delivery_channel": 0.1813844190000964, + "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher": 0.007053050999729749, + "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher_avoid_fallback": 0.004772130000219477, + "tests/integration/test_security.py::TestCSRF::test_CSRF": 0.11712493000004542, + "tests/integration/test_security.py::TestCSRF::test_additional_allowed_origins": 0.020309886999712035, + "tests/integration/test_security.py::TestCSRF::test_cors_apigw_not_applied": 0.09949914299977536, + "tests/integration/test_security.py::TestCSRF::test_cors_s3_override": 0.13386974199966062, + "tests/integration/test_security.py::TestCSRF::test_default_cors_headers": 0.013950815000043804, + "tests/integration/test_security.py::TestCSRF::test_disable_cors_checks": 0.021486006999566598, + "tests/integration/test_security.py::TestCSRF::test_disable_cors_headers": 0.02481705400032297, + "tests/integration/test_security.py::TestCSRF::test_internal_route_cors_headers[/_localstack/health]": 0.012596867999945971, + "tests/integration/test_security.py::TestCSRF::test_no_cors_without_origin_header": 0.01330875300027401, + "tests/integration/test_stores.py::test_nonstandard_regions": 0.13026484700048968, + "tests/integration/utils/test_diagnose.py::test_diagnose_resource": 0.25306075800017425 } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000..e5f81c35c2119 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,178 @@ +# AGENTS.md — LocalStack Pro + +LocalStack is a fully functional local AWS cloud stack that emulates AWS services (S3, Lambda, DynamoDB, etc.) for development and testing. It runs in Docker, provides AWS-like APIs locally, enabling offline workflows, integration testing, and CI without calling real AWS endpoints. + +This codebase contains both the AWS emulator in `localstack-core`. + +--- + +## ⚠️ Critical Hard Constraints + +**NEVER:** +- Modify `*.snapshot.json` or `*.validation.json` files manually — generated by running tests against AWS +- Use plain `assert` in validated tests — **ALWAYS** use `snapshot.match()` +- Create AWS resources directly in test bodies — **ALWAYS** use fixtures +- Hardcode account IDs or region names — use `account_id` and `region_name` fixtures +- Modify files in `aws/api/` — auto-generated from AWS API definitions +- Add new project dependencies without approval +- Run `git push` or modify repository history + +--- + +## Commands + +```bash +# Testing +pytest # Run test file +pytest -k # Run specific test +AWS_PROFILE=ls-sandbox TEST_TARGET=AWS_CLOUD SNAPSHOT_UPDATE=1 pytest # Run against AWS + +# Code quality +make lint # Lint check +make format # Format all +make format-modified # Format staged only +``` + +--- + +## Project Structure + +``` +localstack-core/ +├── localstack/core/ +│ ├── services// # Service implementations +│ │ ├── provider.py # API handlers +│ │ ├── models.py # State stores +│ │ └── resource_providers/ # CloudFormation resources +│ ├── aws/api// # Auto-generated API types (DO NOT EDIT) +│ └── providers.py # Service registration & Moto fallbacks +├── tests/aws/services// # Integration tests +├── tests/unit/ # Unit tests (prefer integration tests) +├── tests/bootstrap/ # Container startup tests +``` + +### Moto Fallback + +Moto is a library used to implement services not fully implemented in LocalStack. Requests can fall back to Moto operations, with the disadvantage that state is kept in Moto and Moto changes may break the implementation. Check `providers.py` for fallback configurations. + +--- + +## Implementing AWS Operations + +### Reference Implementation + +For a complete example of provider patterns, state management, and error handling, see the **CodeBuild** service: + +- **Provider:** `localstack-pro-core/localstack/pro/core/services/codebuild/provider.py` +- **Models (state store):** `localstack-pro-core/localstack/pro/core/services/codebuild/models.py` + +Refer to this implementation when: +- Creating a new service from scratch +- Understanding the `@handler` decorator pattern (with and without `expand=False`) +- Learning the `AccountRegionBundle` state management pattern +- Implementing service-specific exceptions +- Using helper functions like `get_store(context)` and utility functions outside the class + +### Key Conventions + +- **Types:** Import from `localstack.pro.core.aws.api.` (auto-generated, don't modify) +- **Errors:** Use service-specific exceptions from API module, or `CommonServiceException` +- **IDs:** Use `short_uid()` from `localstack.utils.strings` +- **ARNs:** Use helpers from `localstack.utils.aws.arns` +- **Logging:** `LOG = logging.getLogger(__name__)` at module top + +--- + +## Writing Parity Tests + +### Reference Implementation + +For complete examples of test patterns, fixtures, and snapshots, see the **Pipes** tests: + +- **Validation tests:** `localstack-pro-core/tests/aws/services/pipes/test_pipes_validation.py` +- **Integration tests:** `localstack-pro-core/tests/aws/services/pipes/test_pipes.py` +- **Fixtures (conftest):** `localstack-pro-core/tests/aws/services/pipes/conftest.py` + +Refer to these when: +- Writing `@markers.aws.validated` tests with snapshot matching +- Creating fixture factories with cleanup (see `pipes_create_pipe` in conftest) +- Understanding the `@pytest.mark.parametrize` pattern +- Testing error cases with `pytest.raises` and `snapshot.match` + +### Test Markers + +Use `@markers.aws.validated` for tests that run against AWS (preferred), or `@markers.aws.only_localstack` for implementation-specific assertions. + +### Core Patterns + +All test patterns are demonstrated in the reference files. Refer to: + +- **Validation tests with error handling:** `test_pipes_validation.py:9-35` — shows `@markers.aws.validated`, `pytest.raises`, and `snapshot.match` for error responses +- **Parametrized tests:** `test_pipes_validation.py:304-377` — shows `@pytest.mark.parametrize` with multiple test cases +- **Fixture with cleanup:** `conftest.py:55-76` — shows the `pipes_create_pipe` fixture factory pattern with `LOG.debug()` cleanup +- **Async resource handling:** `test_pipes.py:134-139` — shows `poll_condition` for waiting on resource state + +### Fixture Rules + +- **Return the entire response** from create operations +- **Store only names/ARNs** in cleanup list (not full responses) +- **Log cleanup errors** with `LOG.debug()` for debugging with `-s` + +### Transformers + +Add transformers **before** `snapshot.match()` for non-deterministic values: + +- **By key (preferred):** `snapshot.add_transformer(snapshot.transform.key_value("FooArn"))` +- **By regex:** `snapshot.add_transformer(snapshot.transform.regex(dynamic_value, ""))` +- **For sorting:** Use `SortingTransformer` from `localstack.testing.snapshots.transformer` + +### Key Fixtures + +- `aws_client` — Boto3 clients (e.g., `aws_client.s3`, `aws_client.lambda_`) +- `snapshot` — Snapshot matching and transformers +- `account_id`, `region_name` — Current account/region (never hardcode) +- `deploy_cfn_template` — Deploy CloudFormation with lifecycle management +- `cleanups` — Register arbitrary cleanup code for teardown + +For common resource fixtures (S3 buckets, KMS keys, etc.), check `localstack-core/localstack/testing/pytest/fixtures.py` + +--- + +## Development Process + +1. **Write a failing test first** — Capture AWS behavior with `TEST_TARGET=AWS_CLOUD SNAPSHOT_UPDATE=1` +2. **Find the provider** — Look in `services//provider.py` for the handler +3. **Check the store** — State issues often in `models.py` +4. **Compare with AWS docs** — Verify expected behavior +5. **Run test against LocalStack** — Ensure snapshot matches +6. **Iterate** — Run tests individually, see failures, fix, re-run + +**Important:** When developing tests for functionality that doesn't exist in LocalStack yet, run tests against AWS to ensure validity. Don't run with `TEST_TARGET=LOCALSTACK` until the functionality exists. + +--- + +## Best Practices + +### Always + +- Run new tests against AWS first +- Use fixtures for resource creation with cleanup +- Add transformers for dynamic values +- Follow patterns in nearby existing code +- Use simple, concise, Sphinx docstrings +- Use type hints +- Start simple (CRUD), then add edge cases, errors, pagination + +### Avoid + +- Over-commenting tests — test name and structure should be self-explanatory +- Adding comments for standard steps (transformers, snapshot matching) +- Creating new markers without discussion + +--- + +## Reference + +- **Testing Docs:** https://github.com/localstack/localstack/tree/main/docs/testing +- **Architecture:** https://github.com/localstack/localstack/blob/main/docs/localstack-concepts/README.md +- **Common Fixtures:** `localstack-core/localstack/testing/pytest/fixtures.py` diff --git a/CODEOWNERS b/CODEOWNERS index 808c3f75aec7c..d7537da30a6a7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,7 @@ ###################### # CODEOWNERS -/CODEOWNERS @thrau @dominikschubert @alexrashed +/CODEOWNERS @dominikschubert @alexrashed # README / Docs /docs/ @thrau @HarshCasper @@ -18,11 +18,16 @@ # Git, Pipelines, GitHub config /.github @alexrashed @dfangl @dominikschubert @silv-io @k-a-il -/.test_durations @alexrashed @silv-io @k-a-il +/.test_durations @silv-io @k-a-il /.git-blame-ignore-revs @alexrashed @thrau /bin/release-dev.sh @thrau @alexrashed /bin/release-helper.sh @thrau @alexrashed +# Python project, packaging, and dependencies +/pyproject.toml @alexrashed @silv-io @k-a-il @bentsku @aidehn +/requirements-*.txt @alexrashed @silv-io @k-a-il @bentsku @aidehn +/.pre-commit-config.yaml @alexrashed @silv-io @k-a-il @bentsku @aidehn + # ASF /localstack-core/localstack/aws/ @thrau /tests/unit/aws/ @thrau @@ -32,11 +37,6 @@ # you can overwrite this for single services afterwards /localstack-core/localstack/aws/api/ -# CLI -/localstack-core/localstack/cli/ @thrau @alexrashed -/tests/unit/cli/ @thrau @alexrashed -/tests/cli/ @thrau @alexrashed - # Plugins /localstack-core/localstack/plugins.py @thrau /localstack-core/localstack/config.py @thrau @@ -113,10 +113,10 @@ /tests/aws/services/cloudcontrol/ @simonrw # cloudformation -/localstack-core/localstack/aws/api/cloudformation/ @dominikschubert @pinzon @simonrw -/localstack-core/localstack/services/cloudformation/ @dominikschubert @pinzon @simonrw -/tests/aws/services/cloudformation/ @dominikschubert @pinzon @simonrw -/tests/unit/services/cloudformation/ @dominikschubert @pinzon @simonrw +/localstack-core/localstack/aws/api/cloudformation/ @simonrw @pinzon @dominikschubert +/localstack-core/localstack/services/cloudformation/ @simonrw @pinzon @dominikschubert +/tests/aws/services/cloudformation/ @simonrw @pinzon @dominikschubert +/tests/unit/services/cloudformation/ @simonrw @pinzon @dominikschubert # cloudwatch /localstack-core/localstack/aws/api/cloudwatch/ @pinzon @steffyP @@ -144,10 +144,10 @@ /tests/aws/services/es/ @alexrashed @silv-io # events -/localstack-core/localstack/aws/api/events/ @maxhoheiser @bentsku -/localstack-core/localstack/services/events/ @maxhoheiser @bentsku -/tests/aws/services/events/ @maxhoheiser @bentsku -/tests/unit/services/events/ @maxhoheiser @bentsku +/localstack-core/localstack/aws/api/events/ @bentsku +/localstack-core/localstack/services/events/ @bentsku +/tests/aws/services/events/ @bentsku +/tests/unit/services/events/ @bentsku # firehose /localstack-core/localstack/aws/api/firehose/ @pinzon @@ -160,16 +160,16 @@ /tests/aws/services/iam/ @dfangl @pinzon # kms -/localstack-core/localstack/aws/api/kms/ @sannya-singal -/localstack-core/localstack/services/kms/ @sannya-singal -/tests/aws/services/kms/ @sannya-singal -/tests/unit/services/kms/ @sannya-singal +/localstack-core/localstack/aws/api/kms/ @k-a-il +/localstack-core/localstack/services/kms/ @k-a-il +/tests/aws/services/kms/ @k-a-il +/tests/unit/services/kms/ @k-a-il # lambda -/localstack-core/localstack/aws/api/lambda_/ @joe4dev @dominikschubert @dfangl @gregfurman -/localstack-core/localstack/services/lambda_/ @joe4dev @dominikschubert @dfangl @gregfurman -/tests/aws/services/lambda_/ @joe4dev @dominikschubert @dfangl @gregfurman -/tests/unit/services/lambda_/ @joe4dev @dominikschubert @dfangl @gregfurman +/localstack-core/localstack/aws/api/lambda_/ @joe4dev @dfangl @gregfurman +/localstack-core/localstack/services/lambda_/ @joe4dev @dfangl @gregfurman +/tests/aws/services/lambda_/ @joe4dev @dfangl @gregfurman +/tests/unit/services/lambda_/ @joe4dev @dfangl @gregfurman # logs /localstack-core/localstack/aws/api/logs/ @pinzon @steffyP @@ -184,7 +184,12 @@ /tests/unit/services/opensearch/ @alexrashed @silv-io # pipes -/localstack-core/localstack/aws/api/pipes/ @tiurin @gregfurman @joe4dev +/localstack-core/localstack/aws/api/pipes/ @tiurin @gregfurman @joe4dev @carole-lavillonniere + +# resourcegroupstaggingapi +/localstack-core/localstack/aws/api/resourcegroupstaggingapi/ @aidehn +/localstack-core/localstack/services/resourcegroupstaggingapi/ @aidehn +/tests/aws/services/resourcegroupstaggingapi/ @aidehn # route53 /localstack-core/localstack/aws/api/route53/ @giograno @@ -192,15 +197,15 @@ /tests/aws/services/route53/ @giograno # route53resolver -/localstack-core/localstack/aws/api/route53resolver/ @macnev2013 @sannya-singal -/localstack-core/localstack/services/route53resolver/ @macnev2013 @sannya-singal -/tests/aws/services/route53resolver/ @macnev2013 @sannya-singal +/localstack-core/localstack/aws/api/route53resolver/ @macnev2013 +/localstack-core/localstack/services/route53resolver/ @macnev2013 +/tests/aws/services/route53resolver/ @macnev2013 # s3 -/localstack-core/localstack/aws/api/s3/ @bentsku @k-a-il -/localstack-core/localstack/services/s3/ @bentsku @k-a-il -/tests/aws/services/s3/ @bentsku @k-a-il -/tests/unit/services/s3/ @bentsku @k-a-il +/localstack-core/localstack/aws/api/s3/ @bentsku @k-a-il @aidehn +/localstack-core/localstack/services/s3/ @bentsku @k-a-il @aidehn +/tests/aws/services/s3/ @bentsku @k-a-il @aidehn +/tests/unit/services/s3/ @bentsku @k-a-il @aidehn # s3control /localstack-core/localstack/aws/api/s3control/ @bentsku @@ -208,9 +213,9 @@ /tests/aws/services/s3control/ @bentsku # secretsmanager -/localstack-core/localstack/aws/api/secretsmanager/ @dominikschubert @macnev2013 @MEPalma -/localstack-core/localstack/services/secretsmanager/ @dominikschubert @macnev2013 @MEPalma -/tests/aws/services/secretsmanager/ @dominikschubert @macnev2013 @MEPalma +/localstack-core/localstack/aws/api/secretsmanager/ @macnev2013 +/localstack-core/localstack/services/secretsmanager/ @macnev2013 +/tests/aws/services/secretsmanager/ @macnev2013 # ses /localstack-core/localstack/aws/api/ses/ @viren-nadkarni @@ -224,21 +229,21 @@ /tests/unit/services/sns/ @bentsku @baermat # sqs -/localstack-core/localstack/aws/api/sqs/ @thrau @baermat @gregfurman -/localstack-core/localstack/services/sqs/ @thrau @baermat @gregfurman -/tests/aws/services/sqs/ @thrau @baermat @gregfurman -/tests/unit/services/sqs/ @thrau @baermat @gregfurman +/localstack-core/localstack/aws/api/sqs/ @baermat @gregfurman @thrau +/localstack-core/localstack/services/sqs/ @baermat @gregfurman @thrau +/tests/aws/services/sqs/ @baermat @gregfurman @thrau +/tests/unit/services/sqs/ @baermat @gregfurman @thrau # ssm -/localstack-core/localstack/aws/api/ssm/ @dominikschubert -/localstack-core/localstack/services/ssm/ @dominikschubert -/tests/aws/services/ssm/ @dominikschubert +/localstack-core/localstack/aws/api/ssm/ @viren-nadkarni @dominikschubert +/localstack-core/localstack/services/ssm/ @viren-nadkarni @dominikschubert +/tests/aws/services/ssm/ @viren-nadkarni @dominikschubert # stepfunctions -/localstack-core/localstack/aws/api/stepfunctions/ @MEPalma @joe4dev @gregfurman -/localstack-core/localstack/services/stepfunctions/ @MEPalma @joe4dev @gregfurman -/tests/aws/services/stepfunctions/ @MEPalma @joe4dev @gregfurman -/tests/unit/services/stepfunctions/ @MEPalma @joe4dev @gregfurman +/localstack-core/localstack/aws/api/stepfunctions/ @tiurin @joe4dev @gregfurman +/localstack-core/localstack/services/stepfunctions/ @tiurin @joe4dev @gregfurman +/tests/aws/services/stepfunctions/ @tiurin @joe4dev @gregfurman +/tests/unit/services/stepfunctions/ @tiurin @joe4dev @gregfurman # sts /localstack-core/localstack/aws/api/sts/ @pinzon @dfangl @@ -246,6 +251,6 @@ /tests/aws/services/sts/ @pinzon @dfangl # transcribe -/localstack-core/localstack/aws/api/transcribe/ @sannya-singal -/localstack-core/localstack/services/transcribe/ @sannya-singal -/tests/aws/services/transcribe/ @sannya-singal +/localstack-core/localstack/aws/api/transcribe/ @silv-io @k-a-il +/localstack-core/localstack/services/transcribe/ @silv-io @k-a-il +/tests/aws/services/transcribe/ @silv-io @k-a-il diff --git a/DOCKER.md b/DOCKER.md index 9d102b1a0e942..5af3f0ab0a64d 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -1,10 +1,15 @@ +> # ℹ️ **Important** +> +> ## We’re moving toward a unified LocalStack for AWS image, updating how access works. +> ## For details on timing and impact please refer [to this blog post](https://localstack.cloud/2026-updates). +

- LocalStack - A fully functional local cloud stack + LocalStack - A fully functional local cloud stack

- GitHub Actions - Coverage Status + GitHub Actions + Coverage Status PyPI Version Docker Pulls PyPi downloads @@ -49,7 +54,7 @@ $ awslocal s3api list-buckets - This command reuses the image if it’s already on your machine, i.e. it will **not** pull the latest image automatically from Docker Hub. -- This command does not bind all ports that are potentially used by LocalStack, nor does it mount any volumes. When using Docker to manually start LocalStack, you will have to configure the container on your own (see [`docker-compose.yml`](https://github.com/localstack/localstack/blob/master/docker-compose.yml) and [Configuration](https://docs.localstack.cloud/references/configuration/)). This could be seen as the “expert mode” of starting LocalStack. If you are looking for a simpler method of starting LocalStack, please use the [LocalStack CLI](https://docs.localstack.cloud/getting-started/installation/#localstack-cli). +- This command does not bind all ports that are potentially used by LocalStack, nor does it mount any volumes. When using Docker to manually start LocalStack, you will have to configure the container on your own (see [`docker-compose.yml`](https://github.com/localstack/localstack/blob/main/docker-compose.yml) and [Configuration](https://docs.localstack.cloud/references/configuration/)). This could be seen as the “expert mode” of starting LocalStack. If you are looking for a simpler method of starting LocalStack, please use the [LocalStack CLI](https://docs.localstack.cloud/getting-started/installation/#localstack-cli). ### Docker Compose @@ -88,7 +93,7 @@ $ awslocal sqs list-queues **Notes** -- This command pulls the current nightly build from the `master` branch (if you don’t have the image locally) and **not** the latest supported version. If you want to use a specific version, set the appropriate localstack image tag at `services.localstack.image` in the `docker-compose.yml` file (for example `localstack/localstack:`). +- This command pulls the current nightly build from the `main` branch (if you don’t have the image locally) and **not** the latest supported version. If you want to use a specific version, set the appropriate localstack image tag at `services.localstack.image` in the `docker-compose.yml` file (for example `localstack/localstack:`). - This command reuses the image if it’s already on your machine, i.e. it will **not** pull the latest image automatically from Docker Hub. @@ -141,4 +146,4 @@ Copyright (c) 2017-2024 LocalStack maintainers and contributors. Copyright (c) 2016 Atlassian and others. -This version of LocalStack is released under the Apache License, Version 2.0 (see [LICENSE](https://github.com/localstack/localstack/blob/master/LICENSE.txt)). By downloading and using this software you agree to the [End-User License Agreement (EULA)](https://github.com/localstack/localstack/blob/master/doc/end_user_license_agreement). +This version of LocalStack is released under the Apache License, Version 2.0 (see [LICENSE](https://github.com/localstack/localstack/blob/main/LICENSE.txt)). By downloading and using this software you agree to the [End-User License Agreement (EULA)](https://github.com/localstack/localstack/blob/main/doc/end_user_license_agreement). diff --git a/Dockerfile b/Dockerfile index 38174aff318ed..8ae951ee1969a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # # base: Stage which installs necessary runtime dependencies (OS packages, etc.) # -FROM python:3.11.13-slim-bookworm@sha256:139020233cc412efe4c8135b0efe1c7569dc8b28ddd88bddb109b764f8977e30 AS base +FROM python:3.13.12-slim-trixie@sha256:8bc60ca09afaa8ea0d6d1220bde073bacfedd66a4bf8129cbdc8ef0e16c8a952 AS base ARG TARGETARCH # Install runtime OS package dependencies @@ -14,10 +14,9 @@ RUN --mount=type=cache,target=/var/cache/apt \ # patch for CVE-2024-45490, CVE-2024-45491, CVE-2024-45492 apt-get install --only-upgrade libexpat1 -# FIXME Node 18 actually shouldn't be necessary in Community, but we assume its presence in lots of tests # Install nodejs package from the dist release server. Note: we're installing from dist binaries, and not via -# `apt-get`, to avoid installing `python3.9` into the image (which otherwise comes as a dependency of nodejs). -# See https://github.com/nodejs/docker-node/blob/main/18/bullseye/Dockerfile +# `apt-get`, to avoid installing `python3.11` into the image (which otherwise comes as a dependency of nodejs). +# See https://github.com/nodejs/docker-node/blob/main/22/trixie/Dockerfile RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \ && case "${dpkgArch##*-}" in \ amd64) ARCH='x64';; \ @@ -27,7 +26,7 @@ RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \ # gpg keys listed at https://github.com/nodejs/node#release-keys && set -ex \ && for key in \ - C0D6248439F1D5604AAFFB4021D900FFDB233756 \ + 5BE8A3F6C8A5C01D106C0AD820B1A390B168D356 \ DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 \ CC68F5A3106FF448322E48ED27F5E38D5B0A215F \ 8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \ @@ -39,18 +38,18 @@ RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \ gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" || \ gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \ done \ - && curl -LO https://nodejs.org/dist/latest-v18.x/SHASUMS256.txt \ + && curl -LO https://nodejs.org/dist/latest-v22.x/SHASUMS256.txt \ && LATEST_VERSION_FILENAME=$(cat SHASUMS256.txt | grep -o "node-v.*-linux-$ARCH" | sort | uniq) \ && rm SHASUMS256.txt \ - && curl -fsSLO --compressed "https://nodejs.org/dist/latest-v18.x/$LATEST_VERSION_FILENAME.tar.xz" \ - && curl -fsSLO --compressed "https://nodejs.org/dist/latest-v18.x/SHASUMS256.txt.asc" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/latest-v22.x/$LATEST_VERSION_FILENAME.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/latest-v22.x/SHASUMS256.txt.asc" \ && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ && grep " $LATEST_VERSION_FILENAME.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ && tar -xJf "$LATEST_VERSION_FILENAME.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ && rm "$LATEST_VERSION_FILENAME.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ && ln -s /usr/local/bin/node /usr/local/bin/nodejs \ # upgrade npm to the latest version - && npm upgrade -g npm \ + && npm install -g npm@latest \ # smoke tests && node --version \ && npm --version \ @@ -79,7 +78,7 @@ RUN chmod 777 . && \ # install the entrypoint script ADD bin/docker-entrypoint.sh /usr/local/bin/ -# add the shipped hosts file to prevent performance degredation in windows container mode on windows +# add the shipped hosts file to prevent performance degradation in windows container mode on windows # (where hosts file is not mounted) See https://github.com/localstack/localstack/issues/5178 ADD bin/hosts /etc/hosts @@ -105,7 +104,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ apt-get update && \ # Install dependencies to add additional repos # g++ is a workaround to fix the JPype1 compile error on ARM Linux "gcc: fatal error: cannot execute ‘cc1plus’" - apt-get install -y gcc g++ + apt-get install -y --no-install-recommends gcc g++ # upgrade python build tools RUN --mount=type=cache,target=/root/.cache \ @@ -114,7 +113,7 @@ RUN --mount=type=cache,target=/root/.cache \ # add files necessary to install runtime dependencies ADD Makefile pyproject.toml requirements-runtime.txt ./ # add the localstack start scripts (necessary for the installation of the runtime dependencies, i.e. `pip install -e .`) -ADD bin/localstack bin/localstack.bat bin/localstack-supervisor bin/ +ADD bin/localstack-supervisor bin/ # Install dependencies for running the LocalStack runtime RUN --mount=type=cache,target=/root/.cache\ @@ -130,9 +129,9 @@ COPY --from=builder /opt/code/localstack/.venv /opt/code/localstack/.venv ARG LOCALSTACK_BUILD_VERSION # add project files necessary to install all dependencies -ADD Makefile pyproject.toml ./ +ADD Makefile pyproject.toml plux.ini ./ # add the localstack start scripts (necessary for the installation of the runtime dependencies, i.e. `pip install -e .`) -ADD bin/localstack bin/localstack.bat bin/localstack-supervisor bin/ +ADD bin/localstack-supervisor bin/ # add the code as late as possible ADD localstack-core/ /opt/code/localstack/localstack-core @@ -143,9 +142,10 @@ RUN --mount=type=cache,target=/root/.cache \ SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LOCALSTACK_CORE=${LOCALSTACK_BUILD_VERSION} \ pip install -e .[runtime] -# Generate the plugin entrypoints -RUN SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LOCALSTACK_CORE=${LOCALSTACK_BUILD_VERSION} \ - make entrypoints +# Install standalone CLI package +RUN --mount=type=cache,target=/root/.cache \ + . .venv/bin/activate && \ + pip install --upgrade --pre localstack # Generate service catalog cache in static libs dir RUN . .venv/bin/activate && python3 -m localstack.aws.spec @@ -162,9 +162,9 @@ RUN --mount=type=cache,target=/root/.cache \ chmod -R 777 /usr/lib/localstack # link the python package installer virtual environments into the localstack venv -RUN echo /var/lib/localstack/lib/python-packages/lib/python3.11/site-packages > localstack-var-python-packages-venv.pth && \ +RUN echo /var/lib/localstack/lib/python-packages/lib/python3.13/site-packages > localstack-var-python-packages-venv.pth && \ mv localstack-var-python-packages-venv.pth .venv/lib/python*/site-packages/ -RUN echo /usr/lib/localstack/python-packages/lib/python3.11/site-packages > localstack-static-python-packages-venv.pth && \ +RUN echo /usr/lib/localstack/python-packages/lib/python3.13/site-packages > localstack-static-python-packages-venv.pth && \ mv localstack-static-python-packages-venv.pth .venv/lib/python*/site-packages/ # expose edge service, external service ports, and debugpy diff --git a/Dockerfile.s3 b/Dockerfile.s3 index 59c4ef1cc0706..6ae631419c6ec 100644 --- a/Dockerfile.s3 +++ b/Dockerfile.s3 @@ -1,5 +1,5 @@ # base: Stage which installs necessary runtime dependencies (OS packages, filesystem...) -FROM python:3.11.13-slim-bookworm@sha256:139020233cc412efe4c8135b0efe1c7569dc8b28ddd88bddb109b764f8977e30 AS base +FROM python:3.13.12-slim-trixie@sha256:8bc60ca09afaa8ea0d6d1220bde073bacfedd66a4bf8129cbdc8ef0e16c8a952 AS base ARG TARGETARCH # set workdir @@ -58,7 +58,7 @@ RUN --mount=type=cache,target=/root/.cache \ # add files necessary to install all dependencies ADD Makefile pyproject.toml requirements-base-runtime.txt ./ # add the localstack start scripts (necessary for the installation of the runtime dependencies, i.e. `pip install -e .`) -ADD bin/localstack bin/localstack.bat bin/localstack-supervisor bin/ +ADD bin/localstack-supervisor bin/ # Install dependencies for running the LocalStack base runtime (for S3) RUN --mount=type=cache,target=/root/.cache \ @@ -66,7 +66,7 @@ RUN --mount=type=cache,target=/root/.cache \ # delete the botocore specs for other services (>80mb) # TODO: well now it's compressed and it's much lighter: 20mb maybe not worth it -RUN find .venv/lib/python3.11/site-packages/botocore/data/ -mindepth 1 -maxdepth 1 -type d -not -name s3 -exec rm -rf '{}' \; +RUN find .venv/lib/python3.13/site-packages/botocore/data/ -mindepth 1 -maxdepth 1 -type d -not -name s3 -exec rm -rf '{}' \; # final stage: Builds upon base stage and copies resources from builder stages @@ -76,9 +76,9 @@ COPY --from=builder /opt/code/localstack/.venv /opt/code/localstack/.venv ARG LOCALSTACK_BUILD_VERSION # add project files necessary to install all dependencies -ADD Makefile pyproject.toml requirements-base-runtime.txt ./ +ADD Makefile pyproject.toml plux.ini requirements-base-runtime.txt ./ # add the localstack start scripts (necessary for the installation of the runtime dependencies, i.e. `pip install -e .`) -ADD bin/localstack bin/localstack.bat bin/localstack-supervisor bin/ +ADD bin/localstack-supervisor bin/ # add the code as late as possible ADD localstack-core/ /opt/code/localstack/localstack-core @@ -89,17 +89,13 @@ RUN --mount=type=cache,target=/root/.cache \ SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LOCALSTACK_CORE=${LOCALSTACK_BUILD_VERSION} \ pip install -e .[base-runtime] -# Generate the plugin entrypoints -RUN SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LOCALSTACK_CORE=${LOCALSTACK_BUILD_VERSION} \ - make entrypoints - # Generate service catalog cache in static libs dir RUN . .venv/bin/activate && python3 -m localstack.aws.spec # link the python package installer virtual environments into the localstack venv -RUN echo /var/lib/localstack/lib/python-packages/lib/python3.11/site-packages > localstack-var-python-packages-venv.pth && \ +RUN echo /var/lib/localstack/lib/python-packages/lib/python3.13/site-packages > localstack-var-python-packages-venv.pth && \ mv localstack-var-python-packages-venv.pth .venv/lib/python*/site-packages/ -RUN echo /usr/lib/localstack/python-packages/lib/python3.11/site-packages > localstack-static-python-packages-venv.pth && \ +RUN echo /usr/lib/localstack/python-packages/lib/python3.13/site-packages > localstack-static-python-packages-venv.pth && \ mv localstack-static-python-packages-venv.pth .venv/lib/python*/site-packages/ # expose edge service and debugpy diff --git a/MANIFEST.in b/MANIFEST.in index 07442c11a993f..53076cee97ece 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ exclude .github/** -exclude .circleci/** exclude docs/** exclude tests/** exclude .test_durations diff --git a/Makefile b/Makefile index 4f926170e9272..2bd6322186309 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,9 @@ PYTEST_LOGLEVEL ?= warning uname_m := $(shell uname -m) ifeq ($(uname_m),x86_64) -platform = amd64 +PLATFORM ?= amd64 else -platform = arm64 +PLATFORM ?= arm64 endif ifeq ($(OS), Windows_NT) @@ -27,7 +27,7 @@ usage: ## Show this help $(VENV_ACTIVATE): pyproject.toml test -d $(VENV_DIR) || $(VENV_BIN) $(VENV_DIR) - $(VENV_RUN); $(PIP_CMD) install --upgrade pip setuptools wheel plux + $(VENV_RUN); $(PIP_CMD) install --upgrade pip setuptools wheel touch $(VENV_ACTIVATE) venv: $(VENV_ACTIVATE) ## Create a new (empty) virtual environment @@ -36,7 +36,8 @@ freeze: ## Run pip freeze -l in the virtual environment @$(VENV_RUN); pip freeze -l upgrade-pinned-dependencies: venv - $(VENV_RUN); $(PIP_CMD) install --upgrade pip-tools pre-commit + # TODO: Avoid pip 26.0 for now due to https://github.com/jazzband/pip-tools/issues/2319 + $(VENV_RUN); $(PIP_CMD) install --upgrade "pip<26.0" pip-tools pre-commit $(VENV_RUN); pip-compile --strip-extras --upgrade -o requirements-basic.txt pyproject.toml $(VENV_RUN); pip-compile --strip-extras --upgrade --extra runtime -o requirements-runtime.txt pyproject.toml $(VENV_RUN); pip-compile --strip-extras --upgrade --extra test -o requirements-test.txt pyproject.toml @@ -69,15 +70,15 @@ install-s3: venv ## Install dependencies for the localstack runtime for s3-o $(VENV_RUN); $(PIP_CMD) install -r requirements-base-runtime.txt $(VENV_RUN); $(PIP_CMD) install $(PIP_OPTS) -e ".[base-runtime]" -install: install-dev entrypoints ## Install full dependencies into venv +install: install-dev ## Install full dependencies into venv -entrypoints: ## Run plux to build entry points - $(VENV_RUN); python3 -c "from setuptools import setup; setup()" plugins egg_info - @# make sure that the entrypoints were correctly created and are non-empty - @test -s localstack-core/localstack_core.egg-info/entry_points.txt || (echo "Entrypoints were not correctly created! Aborting!" && exit 1) +entrypoints: install-dev + $(VENV_RUN); python -m plux entrypoints + @# make sure that the plux.ini file with the entrypoints has correctly been created + @test -s plux.ini || (echo "Entrypoints were not correctly created! Aborting!" && exit 1) -dist: entrypoints ## Build source and built (wheel) distributions of the current version - $(VENV_RUN); pip install --upgrade twine; python -m build +dist: ## Build source and built (wheel) distributions of the current version + $(VENV_RUN); pip install --upgrade build twine; python -m build publish: clean-dist dist ## Publish the library to the central PyPi repository # make sure the dist archive contains a non-empty entry_points.txt file before uploading @@ -88,10 +89,10 @@ coveralls: ## Publish coveralls metrics $(VENV_RUN); coveralls start: ## Manually start the local infrastructure for testing - ($(VENV_RUN); exec bin/localstack start --host) + ($(VENV_RUN); python3 -m localstack.runtime.main) docker-run-tests: ## Initializes the test environment and runs the tests in a docker container - docker run -e LOCALSTACK_INTERNAL_TEST_COLLECT_METRIC=1 --entrypoint= -v `pwd`/.git:/opt/code/localstack/.git -v `pwd`/requirements-test.txt:/opt/code/localstack/requirements-test.txt -v `pwd`/.test_durations:/opt/code/localstack/.test_durations -v `pwd`/tests/:/opt/code/localstack/tests/ -v `pwd`/dist/:/opt/code/localstack/dist/ -v `pwd`/target/:/opt/code/localstack/target/ -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/localstack:/var/lib/localstack \ + docker run -e LOCALSTACK_INTERNAL_TEST_COLLECT_METRIC=1 -e DOCKERHUB_USERNAME -e DOCKERHUB_PASSWORD --entrypoint= -v `pwd`/.git:/opt/code/localstack/.git -v `pwd`/requirements-test.txt:/opt/code/localstack/requirements-test.txt -v `pwd`/.test_durations:/opt/code/localstack/.test_durations -v `pwd`/tests/:/opt/code/localstack/tests/ -v `pwd`/dist/:/opt/code/localstack/dist/ -v `pwd`/target/:/opt/code/localstack/target/ -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/localstack:/var/lib/localstack \ $(IMAGE_NAME):$(DEFAULT_TAG) \ bash -c "make install-test && DEBUG=$(DEBUG) PYTEST_LOGLEVEL=$(PYTEST_LOGLEVEL) PYTEST_ARGS='$(PYTEST_ARGS)' COVERAGE_FILE='$(COVERAGE_FILE)' JUNIT_REPORTS_FILE=$(JUNIT_REPORTS_FILE) TEST_PATH='$(TEST_PATH)' LAMBDA_IGNORE_ARCHITECTURE=1 LAMBDA_INIT_POST_INVOKE_WAIT_MS=50 TINYBIRD_PYTEST_ARGS='$(TINYBIRD_PYTEST_ARGS)' TINYBIRD_DATASOURCE='$(TINYBIRD_DATASOURCE)' TINYBIRD_TOKEN='$(TINYBIRD_TOKEN)' TINYBIRD_URL='$(TINYBIRD_URL)' CI_REPOSITORY_NAME='$(CI_REPOSITORY_NAME)' CI_WORKFLOW_NAME='$(CI_WORKFLOW_NAME)' CI_COMMIT_BRANCH='$(CI_COMMIT_BRANCH)' CI_COMMIT_SHA='$(CI_COMMIT_SHA)' CI_JOB_URL='$(CI_JOB_URL)' CI_JOB_NAME='$(CI_JOB_NAME)' CI_JOB_ID='$(CI_JOB_ID)' CI='$(CI)' TEST_AWS_REGION_NAME='${TEST_AWS_REGION_NAME}' TEST_AWS_ACCESS_KEY_ID='${TEST_AWS_ACCESS_KEY_ID}' TEST_AWS_ACCOUNT_ID='${TEST_AWS_ACCOUNT_ID}' make test-coverage" @@ -122,25 +123,35 @@ lint: ## Run code linter to check code style, check if formatte $(VENV_RUN); pre-commit run check-pinned-deps-for-needed-upgrade --files pyproject.toml # run pre-commit hook manually here to ensure that this check runs in CI as well $(VENV_RUN); openapi-spec-validator localstack-core/localstack/openapi.yaml $(VENV_RUN); cd localstack-core && mypy --install-types --non-interactive + $(VENV_RUN); deptry . lint-modified: ## Run code linter to check code style, check if formatter would make changes on modified files, and check if dependency pins need to be updated because of modified files ($(VENV_RUN); python -m ruff check --output-format=full `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs` && python -m ruff format --check `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`) - $(VENV_RUN); pre-commit run check-pinned-deps-for-needed-upgrade --files $(git diff master --name-only) # run pre-commit hook manually here to ensure that this check runs in CI as well + $(VENV_RUN); pre-commit run check-pinned-deps-for-needed-upgrade --files $(git diff main --name-only) # run pre-commit hook manually here to ensure that this check runs in CI as well check-aws-markers: ## Lightweight check to ensure all AWS tests have proper compatibility markers set ($(VENV_RUN); python -m pytest --co tests/aws/) format: ## Run ruff to format the whole codebase - ($(VENV_RUN); python -m ruff format .; python -m ruff check --output-format=full --fix .) + ($(VENV_RUN); python -m ruff check --output-format=full --fix .; python -m ruff format .) format-modified: ## Run ruff to format only modified code - ($(VENV_RUN); python -m ruff format `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`; python -m ruff check --output-format=full --fix `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`) + ($(VENV_RUN); \ + python -m ruff check --output-format=full --fix `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`; \ + python -m ruff format `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`) + +asf-regenerate: ## Regenerate ASF APIs + $(VENV_RUN); python -m localstack.aws.scaffold upgrade + @echo 'Removing unused imports from generated modules' + $(VENV_RUN); python -m ruff check --select F401 --unsafe-fixes --fix localstack-core/localstack/aws/api/ --config "lint.preview = true" + $(VENV_RUN); python -m ruff check --output-format=full --fix localstack-core/localstack/aws/api/ + $(VENV_RUN); python -m ruff format localstack-core/localstack/aws/api/ init-precommit: ## install te pre-commit hook into your local git repository ($(VENV_RUN); pre-commit install) docker-build: - IMAGE_NAME=$(IMAGE_NAME) PLATFORM=$(platform) ./bin/docker-helper.sh build + IMAGE_NAME=$(IMAGE_NAME) PLATFORM=$(PLATFORM) ./bin/docker-helper.sh build clean: ## Clean up (npm dependencies, downloaded infrastructure code, compiled Java classes) rm -rf .filesystem @@ -154,4 +165,4 @@ clean-dist: ## Clean up python distribution directories rm -rf dist/ build/ rm -rf localstack-core/*.egg-info -.PHONY: usage freeze install-basic install-runtime install-test install-dev install entrypoints dist publish coveralls start docker-run-tests docker-cp-coverage test test-coverage lint lint-modified format format-modified init-precommit clean clean-dist upgrade-pinned-dependencies +.PHONY: usage freeze install-basic install-runtime install-test install-dev install entrypoints dist publish coveralls start docker-run-tests docker-cp-coverage test test-coverage lint lint-modified format format-modified asf-regenerate init-precommit clean clean-dist upgrade-pinned-dependencies diff --git a/README.md b/README.md index 4bc4c4ff512fb..0d78c2be49085 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,27 @@ -

-:zap: We are thrilled to announce the release of LocalStack 4.6 :zap: -

+> [!IMPORTANT] +> **Project Update: Consolidation into the Unified LocalStack Image** +> +> To provide a more reliable and streamlined experience, we are consolidating our development into a single, unified image. As part of this transition, this repository is now **archived and read-only**. +> +> This decision reflects our commitment to reducing fragmentation and focusing our resources on building the most robust AWS emulation layer possible. We are deeply grateful to the contributors who helped make this project into what it is today–your work remains integral to the future of the LocalStack ecosystem. +> +> What this means for your workflow: +> +> - LocalStack for AWS offers a [range of options](https://www.localstack.cloud/pricing) including a free Hobby plan for non-commercial use with the same capabilities as this project. +> - Your input is still vital to us. Please continue to share [bug reports here](https://github.com/orgs/localstack/discussions/categories/bugs) and [submit feature requests here](https://github.com/orgs/localstack/discussions/categories/feature-requests) or join our [Slack Community](https://slack.localstack.cloud). +> +> Thank you for your continued support as we grow together.

- LocalStack - A fully functional local cloud stack + LocalStack - The Leading Platform for Local Cloud Development

- GitHub Actions - Coverage Status + GitHub Actions + Coverage Status PyPI Version Docker Pulls PyPi downloads - Backers on Open Collective - Sponsors on Open Collective PyPI License Code style: black Ruff @@ -34,7 +42,7 @@ Contributing
📖 Docs • - 💻 Pro version • + 💻 LocalStack for AWS☑️ LocalStack coverage

@@ -66,7 +74,7 @@ If Brew is not installed on your machine, you can download the pre-built LocalSt - Visit [localstack/localstack-cli](https://github.com/localstack/localstack-cli/releases/latest) and download the latest release for your platform. - Extract the downloaded archive to a directory included in your `PATH` variable: - - For macOS/Linux, use the command: `sudo tar xvzf ~/Downloads/localstack-cli-*-darwin-*-onefile.tar.gz -C /usr/local/bin` + - For macOS/Linux, use the command: `sudo tar xvzf ~/Downloads/localstack-cli-*-darwin-*-onefile.tar.gz -C /usr/local/bin` ### PyPI (macOS, Linux, Windows) @@ -93,7 +101,7 @@ Start LocalStack inside a Docker container by running: / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,< /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_| -- LocalStack CLI: 4.6.0 +- LocalStack CLI: 4.9.0 - Profile: default - App: https://app.localstack.cloud @@ -153,9 +161,9 @@ To start using LocalStack, check out our [documentation](https://docs.localstack To use LocalStack with a graphical user interface, you can use the following UI clients: -* [LocalStack Web Application](https://app.localstack.cloud) -* [LocalStack Desktop](https://docs.localstack.cloud/user-guide/tools/localstack-desktop/) -* [LocalStack Docker Extension](https://docs.localstack.cloud/user-guide/tools/localstack-docker-extension/) +- [LocalStack Web Application](https://app.localstack.cloud) +- [LocalStack Desktop](https://docs.localstack.cloud/user-guide/tools/localstack-desktop/) +- [LocalStack Docker Extension](https://docs.localstack.cloud/user-guide/tools/localstack-docker-extension/) ## Releases @@ -179,7 +187,7 @@ upvote 👍 [feature requests](https://github.com/localstack/localstack/issues?q 🙋🏽 ask [support questions](https://docs.localstack.cloud/getting-started/help-and-support/), or 🗣️ discuss local cloud development: -- [LocalStack Slack Community](https://localstack.cloud/contact/) +- [LocalStack Slack Community](https://localstack.cloud/slack/) - [LocalStack GitHub Issue tracker](https://github.com/localstack/localstack/issues) ### Contributors @@ -211,7 +219,7 @@ You can also support this project by becoming a sponsor on [Open Collective](htt ## License -Copyright (c) 2017-2025 LocalStack maintainers and contributors. +Copyright (c) 2017-2026 LocalStack maintainers and contributors. Copyright (c) 2016 Atlassian and others. diff --git a/bin/docker-entrypoint.sh b/bin/docker-entrypoint.sh index 1c4a297fd1fa1..985d791907b61 100755 --- a/bin/docker-entrypoint.sh +++ b/bin/docker-entrypoint.sh @@ -3,17 +3,27 @@ set -eo pipefail shopt -s nullglob +if [ -z $LOCALSTACK_AUTH_TOKEN ]; then + echo "WARNING" + echo "================================================================================" + echo " You are starting the LocalStack Community Docker image." + echo " We move towards a unified LocalStack for AWS image in March 2026." + echo " Go to this page for more infos: https://localstack.cloud/2026-updates" + echo "================================================================================" + echo "" +fi + # When trying to activate pro features in the community version, raise a warning if [[ -n $LOCALSTACK_API_KEY || -n $LOCALSTACK_AUTH_TOKEN ]]; then echo "WARNING" - echo "============================================================================" - echo " It seems you are trying to use the LocalStack Pro version without using " - echo " the dedicated Pro image." + echo "================================================================================" + echo " It seems you are trying to use the LocalStack Pro version without using the" + echo " dedicated Pro image." echo " LocalStack will only start with community services enabled." echo " To fix this warning, use localstack/localstack-pro instead." echo "" echo " See: https://github.com/localstack/localstack/issues/7882" - echo "============================================================================" + echo "================================================================================" echo "" fi diff --git a/bin/docker-helper.sh b/bin/docker-helper.sh index 2b21a0f1ce4ce..58bf0736d2d9d 100755 --- a/bin/docker-helper.sh +++ b/bin/docker-helper.sh @@ -31,6 +31,9 @@ function usage() { echo " push-manifests" echo " Create and push the multi-arch Docker manifests for already pushed platform-specific images" echo "" + echo " get-release-version" + echo " Output the current release version if HEAD is at a release tag" + echo "" echo " help" echo " Show this message" } @@ -51,8 +54,7 @@ function _fail { function _get_current_version() { # check if setuptools_scm is installed, if not prompt to install. python3 is expected to be present if ! python3 -m pip -qqq show setuptools_scm > /dev/null ; then - echo "ERROR: setuptools_scm is not installed. Run 'pip install --upgrade setuptools setuptools_scm'" >&2 - exit 1 + _fail "ERROR: setuptools_scm is not installed. Run 'pip install --upgrade setuptools setuptools_scm'" fi python3 -m setuptools_scm } @@ -65,15 +67,28 @@ function _get_current_branch() { git branch --show-current } +function _get_current_tag() { + git describe --tags --exact-match 2> /dev/null || true +} + function _enforce_image_name() { if [ -z "$IMAGE_NAME" ]; then _fail "Mandatory parameter IMAGE_NAME missing."; fi } -function _enforce_main_branch() { - MAIN_BRANCH=${MAIN_BRANCH-"master"} +function _enforce_tagged_or_main_branch() { + MAIN_BRANCH=${MAIN_BRANCH-"main"} CURRENT_BRANCH=$(_get_current_branch) + CURRENT_TAG=$(_get_current_tag) echo "Current git branch: '$CURRENT_BRANCH'" - test "$CURRENT_BRANCH" == "$MAIN_BRANCH" || _fail "Current branch ($CURRENT_BRANCH) is not $MAIN_BRANCH." + echo "Current tag: '$CURRENT_TAG'" + test -n "$CURRENT_TAG" || test "$CURRENT_BRANCH" == "$MAIN_BRANCH" || _fail "Current branch ($CURRENT_BRANCH) is not $MAIN_BRANCH and current tag ($CURRENT_TAG) is not set." + + # if we're not on the main branch but have a tag, ensure it's a version tag like v1.2.3 + if [[ "$CURRENT_BRANCH" != "$MAIN_BRANCH" && -n "$CURRENT_TAG" ]]; then + if ! [[ "$CURRENT_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + _fail "tag '$CURRENT_TAG' is not a version tag of the shape ^v[0-9]+\.[0-9]+\.[0-9]+$" + fi + fi } function _enforce_no_fork() { @@ -110,6 +125,7 @@ function _set_version_defaults() { function cmd-build() { # start build of a platform-specific image (this target will get called for multiple archs like AMD64/ARM64) _enforce_image_name + _enforce_platform _set_version_defaults if [ ! -f "pyproject.toml" ]; then @@ -122,7 +138,11 @@ function cmd-build() { # --add-host: Fix for Centos host OS # --build-arg BUILDKIT_INLINE_CACHE=1: Instruct buildkit to inline the caching information into the image # --cache-from: Use the inlined caching information when building the image - DOCKER_BUILDKIT=1 docker buildx build --pull --progress=plain \ + # BUILDX_NO_DEFAULT_ATTESTATIONS=1 buildkit attestation will lead to the image being a single-platform image index if built with buildkit and containerd image store. + # The docker manifest command we use to create multi platform manifests does not support doing this from those image indexes, failing with ` is a manifest list` + # We might want to revert this to use attestation, but for now this locks the old behavior of the image manifest being a `application/vnd.docker.distribution.manifest.v2+json` + # Before switching back we also need to make sure other registries like ECR support these single platform image indexes + DOCKER_BUILDKIT=1 BUILDX_NO_DEFAULT_ATTESTATIONS=1 docker buildx build --platform linux/$PLATFORM --pull --progress=plain \ --cache-from "$IMAGE_NAME" --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg LOCALSTACK_PRE_RELEASE=$(_is_release_commit && echo "0" || echo "1") \ --build-arg LOCALSTACK_BUILD_GIT_HASH=$(git rev-parse --short HEAD) \ @@ -158,7 +178,7 @@ function cmd-load() { function cmd-push() { _enforce_image_name - _enforce_main_branch + _enforce_tagged_or_main_branch _enforce_no_fork _enforce_docker_credentials _enforce_platform @@ -213,7 +233,7 @@ function cmd-push() { function cmd-push-manifests() { _enforce_image_name - _enforce_main_branch + _enforce_tagged_or_main_branch _enforce_no_fork _enforce_docker_credentials _set_version_defaults @@ -275,6 +295,14 @@ function cmd-push-manifests() { fi } +function cmd-get-release-version() { + if _is_release_commit; then + _get_current_version + else + _fail "Not a release commit." + fi +} + ############## @@ -294,6 +322,7 @@ function main() { "load") cmd-load "$@" ;; "push") cmd-push "$@" ;; "push-manifests") cmd-push-manifests "$@" ;; + "get-release-version") cmd-get-release-version "$@" ;; "help") usage && exit 0 ;; *) usage && exit 1 ;; esac diff --git a/bin/localstack b/bin/localstack deleted file mode 100755 index 5cd199565d8bf..0000000000000 --- a/bin/localstack +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -import glob -import os -import sys - -PARENT_FOLDER = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -venv_dir = os.path.join(PARENT_FOLDER, ".venv") -insert_pos = min(len(sys.path), 2) -if os.path.isdir(venv_dir): - for path in glob.glob(os.path.join(venv_dir, "lib/python*/site-packages")): - sys.path.insert(insert_pos, path) - sys.path.insert(insert_pos, PARENT_FOLDER) - - -def main(): - from localstack.cli import main - - main.main() - - -if __name__ == "__main__": - main() diff --git a/bin/localstack.bat b/bin/localstack.bat deleted file mode 100644 index 05370a69c42ef..0000000000000 --- a/bin/localstack.bat +++ /dev/null @@ -1 +0,0 @@ -python "%~dp0\localstack" %* diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 35af4e8333b5d..57f575d8921ee 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -5,9 +5,9 @@ We welcome contributions to LocalStack! Please refer to the following sections t ## Sections - [Contribution Guidelines](#contribution-guidelines) -- [Development Environment Setup](development-environment-setup/README.md) -- [LocalStack Concepts](localstack-concepts/README.md) -- [Testing](testing/README.md) +- [Development Environment Setup](/docs/development-environment-setup/README.md) +- [LocalStack Concepts](/docs/localstack-concepts/README.md) +- [Testing](/docs/testing/README.md) ## Contribution Guidelines @@ -19,7 +19,7 @@ For pull requests (PRs), please stick to the following guidelines: * Fork localstack on your GitHub user account, make code changes there, and then create a PR against main localstack repository. * Add tests for any new features or bug fixes. Ideally, each PR increases the test coverage. Please read our [integration testing](testing/integration-tests/README.md) and [parity testing](testing/parity-testing/README.md) guides on how to write tests for AWS services. * Follow the existing code style. Run `make format` and `make lint` before checking in your code. - * Refer to [Development Environment Setup](development-environment-setup/README.md) if your local testing environment is not yet properly set up. + * Refer to [Development Environment Setup](/docs/development-environment-setup/README.md) if your local testing environment is not yet properly set up. * Document newly introduced methods and classes with pydoc, and add inline comments to code that is not self-documenting. * Separate unrelated changes into multiple PRs. * When creating a PR, classify the size of your change with setting a semver label: diff --git a/docs/development-environment-setup/README.md b/docs/development-environment-setup/README.md index 81284d433a263..6a9c82097df3b 100644 --- a/docs/development-environment-setup/README.md +++ b/docs/development-environment-setup/README.md @@ -1,7 +1,7 @@ # Development Environment Setup Before you get started with contributing to LocalStack, make sure you’ve familiarized yourself with LocalStack from the perspective of a user. -You can follow our [getting started guide](https://docs.localstack.cloud/get-started/). +You can follow our [getting started guide](https://docs.localstack.cloud/getting-started/). Once LocalStack runs in your Docker environment and you’ve played around with the LocalStack and `awslocal` CLI, you can move forward to set up your developer environment. ## Development requirements @@ -56,7 +56,7 @@ IMAGE_NAME="localstack/localstack" ./bin/docker-helper.sh build In host mode, additional dependencies (e.g., Java) are required for developing certain AWS-emulated services (e.g., DynamoDB). The required dependencies vary depending on the service, [Configuration](https://docs.localstack.cloud/references/configuration/), operating system, and system architecture (i.e., x86 vs ARM). -Refer to our official [Dockerfile](https://github.com/localstack/localstack/blob/master/Dockerfile) and our [package installer LPM](Concepts/index.md#packages-and-installers) for more details. +Refer to our official [Dockerfile](https://github.com/localstack/localstack/blob/main/Dockerfile) and our [package installer LPM](Concepts/index.md#packages-and-installers) for more details. #### Root Permissions @@ -69,8 +69,7 @@ LocalStack runs its own [DNS server](https://docs.localstack.cloud/user-guide/to #### Python Dependencies * [JPype1](https://pypi.org/project/JPype1/) might require `g++` to fix a compile error on ARM Linux `gcc: fatal error: cannot execute ‘cc1plus’` - * Used in EventBridge, EventBridge Pipes, and Lambda Event Source Mapping for a Java-based event ruler via the opt-in configuration `EVENT_RULE_ENGINE=java` - * Introduced in [#10615](https://github.com/localstack/localstack/pull/10615) + * Used in StepFunctions for JSONata #### Test Dependencies diff --git a/docs/localstack-concepts/README.md b/docs/localstack-concepts/README.md index 53f15bcc2d632..4dbfccb9fb80d 100644 --- a/docs/localstack-concepts/README.md +++ b/docs/localstack-concepts/README.md @@ -18,7 +18,7 @@ AWS is essentially a Remote Procedure Call (RPC) system, and ASF is our server-s AWS developed a specification language, [Smithy](https://awslabs.github.io/smithy/), which they use internally to define their APIs in a declarative way. They use these specs to generate client SDKs and client documentation. All these specifications are available, among other repositories, in the [botocore repository](https://github.com/boto/botocore/tree/develop/botocore/data). Botocore are the internals of the AWS Python SDK, which allows ASF to interpret and operate on the service specifications. Take a look at an example, [the `Invoke` operation of the `lambda` API](https://github.com/boto/botocore/blob/474e7a23d0fd178790579638cec9123d7e92d10b/botocore/data/lambda/2015-03-31/service-2.json#L564-L573): -```bash +```bash "Invoke":{ "name":"Invoke", "http":{ @@ -52,8 +52,8 @@ A service provider is an implementation of an AWS service API. Service providers A server-side protocol implementation requires a marshaller (a parser for incoming requests, and a serializer for outgoing responses). -- Our [protocol parser](https://github.com/localstack/localstack/blob/master/localstack-core/localstack/aws/protocol/parser.py) translates AWS HTTP requests into objects that can be used to call the respective function of the service provider. -- Our [protocol serializer](https://github.com/localstack/localstack/blob/master/localstack-core/localstack/aws/protocol/serializer.py) translates response objects coming from service provider functions into HTTP responses. +- Our [protocol parser](https://github.com/localstack/localstack/blob/main/localstack-core/localstack/aws/protocol/parser.py) translates AWS HTTP requests into objects that can be used to call the respective function of the service provider. +- Our [protocol serializer](https://github.com/localstack/localstack/blob/main/localstack-core/localstack/aws/protocol/serializer.py) translates response objects coming from service provider functions into HTTP responses. ## Service @@ -85,13 +85,13 @@ Sometimes we also use `moto` code directly, for example importing and accessing ## `@patch` -[The patch utility](https://github.com/localstack/localstack/blob/master/localstack-core/localstack/utils/patch.py) enables easy [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) of external functionality. We often use this to modify internal moto functionality. Sometimes it is easier to patch internals than to wrap the entire API method with the custom functionality. +[The patch utility](https://github.com/localstack/localstack/blob/main/localstack-core/localstack/utils/patch.py) enables easy [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) of external functionality. We often use this to modify internal moto functionality. Sometimes it is easier to patch internals than to wrap the entire API method with the custom functionality. ### Server -[Server]() is an abstract class that provides a basis for serving other backends that run in a separate process. For example, our Kinesis implementation uses [kinesis-mock](https://github.com/etspaceman/kinesis-mock/) as a backend that implements the Kinesis AWS API and also emulates its behavior. +[Server]() is an abstract class that provides a basis for serving other backends that run in a separate process. For example, our Kinesis implementation uses [kinesis-mock](https://github.com/etspaceman/kinesis-mock/) as a backend that implements the Kinesis AWS API and also emulates its behavior. -The provider [starts the kinesis-mock binary in a `Server`](https://github.com/localstack/localstack/blob/2e1e8b4e3e98965a7e99cd58ccdeaa6350a2a414/localstack/services/kinesis/kinesis_mock_server.py), and then forwards all incoming requests to it using `forward_request`. This is a similar construct to `call_moto`, only generalized to arbitrary HTTP AWS backends. +The provider [starts the kinesis-mock binary in a `Server`](https://github.com/localstack/localstack/blob/2e1e8b4e3e98965a7e99cd58ccdeaa6350a2a414/localstack/services/kinesis/kinesis_mock_server.py), and then forwards all incoming requests to it using `forward_request`. This is a similar construct to `call_moto`, only generalized to arbitrary HTTP AWS backends. A server is reachable through some URL (not necessarily HTTP), and the abstract class implements the lifecycle of the process (start, stop, is_started, is_running, etc). To create a new server, you only need to overwrite either `do_run`, or `do_start_thread`, with custom logic to start the binary. @@ -99,7 +99,7 @@ There are some existing useful utilities and specializations of `Server` which c ### External service ports -Some services create additional user-facing resources. For example, the RDS service starts a PostgreSQL server, and the ElastiCache service starts a Redis server, that users then directly connect to. +Some services create additional user-facing resources. For example, the RDS service starts a PostgreSQL server, and the ElastiCache service starts a Redis server, that users then directly connect to. These resources are not hidden behind the service API, and need to be exposed through an available network port. This is what the [external service port range](https://docs.localstack.cloud/references/external-ports/) is for. We expose this port range by default in the docker-compose template, or via the CLI. @@ -143,9 +143,9 @@ Plugins provided by [https://github.com/localstack/plux](https://github.com/loca - Hooks - Extensions -Key points to understand are that plugins use [Python entry points, which are part of the PyPA specification](https://packaging.python.org/en/latest/specifications/entry-points/). Entry points are discovered from the code during a build step rather than defined manually (this is the main differentiator of Plux to other code loading tools). In LocalStack, the `make entrypoints` make target does that, which is also part of `make install`. +Key points to understand are that plugins use [Python entry points, which are part of the PyPA specification](https://packaging.python.org/en/latest/specifications/entry-points/). Entry points are discovered from the code and maintained in the `plux.ini` file, which is version-controlled. This file is used at build time to generate the entry points for the Python package. -When you add new hooks or service providers, or any other plugin, make sure to run `make entrypoints`. +When you add new hooks or service providers, or any other plugin, make sure to run `make entrypoints` to update the `plux.ini` file, and commit the changes to version control. When writing plugins, it is important to understand that any code that sits in the same module as the plugin, will be imported when the plugin is _resolved_. That is, _before_ it is loaded. Resolving a plugin simply means discovering the entry points and loading the code the underlying entry point points to. This is why many times you will see imports deferred to the actual loading of the plugin. @@ -214,7 +214,7 @@ The `static_libs` directory should not be modified at container runtime, as it w This is the default target if a package is installed in the aforementioned way via `python -m localstack.cli.lpm install`. `var_libs` is the main and default location used for packages installed at runtime. -When starting the docker container, a host-volume is mounted at `var_libs`. +When starting the docker container, a host-volume is mounted at `var_libs`. The content of the directory will persist across multiple containers. ### Installation life-cycle @@ -237,7 +237,7 @@ For help with the specific commands, use `python -m localstack.cli.lpm The codebase contains a wealth of utility functions for various common tasks like handling strings, JSON/XML, threads/processes, collections, date/time conversions, and much more. -The utilities are grouped into multiple util modules inside the [localstack.utils]() package. Some of the most commonly used utils modules include: +The utilities are grouped into multiple util modules inside the [localstack.utils]() package. Some of the most commonly used utils modules include: - `.files` - file handling utilities (e.g., `load_file`, `save_file`, or `mkdir`) - `.json` - handle JSON content (e.g., `json_safe`, or `canonical_json`) diff --git a/docs/localstack-readme-banner.svg b/docs/localstack-readme-banner.svg index 1645ad03cf8ce..5ab747a53d05e 100644 --- a/docs/localstack-readme-banner.svg +++ b/docs/localstack-readme-banner.svg @@ -1,1389 +1,188 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/testing/integration-tests/README.md b/docs/testing/integration-tests/README.md index 99e2f40795d58..59c0bd070babe 100644 --- a/docs/testing/integration-tests/README.md +++ b/docs/testing/integration-tests/README.md @@ -1,6 +1,6 @@ # Integration tests -LocalStack has an extensive set of [integration tests](https://github.com/localstack/localstack/tree/master/tests/integration). This document describes how to run and write integration tests. +LocalStack has an extensive set of [integration tests](https://github.com/localstack/localstack/tree/main/tests/integration). This document describes how to run and write integration tests. ## Writing integration tests @@ -72,7 +72,7 @@ def test_something_on_multiple_buckets(s3_create_bucket): # both buckets will be deleted after the test returns ``` -You can find the list of available fixtures in the [fixtures.py](https://github.com/localstack/localstack/blob/master/localstack-core/localstack/testing/pytest/fixtures.py) file. +You can find the list of available fixtures in the [fixtures.py](https://github.com/localstack/localstack/blob/main/localstack-core/localstack/testing/pytest/fixtures.py) file. ## Running the test suite @@ -99,7 +99,7 @@ TEST_PATH="tests/integration/docker_utils/test_docker.py::TestDockerClient" make ### Test against a running LocalStack instance -When you run the integration tests, LocalStack is automatically started (via the pytest conftest mechanism in [tests/integration/conftest.py](https://github.com/localstack/localstack/blob/master/tests/integration/conftest.py)). +When you run the integration tests, LocalStack is automatically started (via the pytest conftest mechanism in [tests/integration/conftest.py](https://github.com/localstack/localstack/blob/main/tests/integration/conftest.py)). You can disable this behavior by setting the environment variable `TEST_SKIP_LOCALSTACK_START=1`. ### Test against Amazon Web Services diff --git a/docs/testing/parity-testing/README.md b/docs/testing/parity-testing/README.md index 9127dc5794b45..09efe8df4ffd5 100644 --- a/docs/testing/parity-testing/README.md +++ b/docs/testing/parity-testing/README.md @@ -1,5 +1,3 @@ -from conftest import aws_client - # Parity Testing Parity tests (also called snapshot tests) are a special form of integration tests that should verify and improve the correctness of LocalStack compared to AWS. @@ -85,7 +83,7 @@ The `snapshot` fixture uses some basic transformations by default, including: APIs for one service often require similar transformations. Therefore, we introduced some utilities that collect common transformations grouped by service. Ideally, the service-transformation already includes every transformation that is required. -The [TransformerUtility](https://github.com/localstack/localstack/blob/master/localstack-core/localstack/testing/snapshots/transformer_utility.py) already provides some collections of transformers for specific service APIs. +The [TransformerUtility](https://github.com/localstack/localstack/blob/main/localstack-core/localstack/testing/snapshots/transformer_utility.py) already provides some collections of transformers for specific service APIs. For example, to add common transformers for lambda, you can use: `snapshot.add_transformer(snapshot.transform.lambda_api()`. @@ -97,7 +95,7 @@ The Parity testing framework currently includes some basic transformer types: - `JsonPathTransformer` replaces the JSON path value directly, or by reference. [jsonpath-ng](https://pypi.org/project/jsonpath-ng/) is used for the JSON path evaluation. - `RegexTransformer` replaces the regex pattern globally. Please be aware that this will be applied on the json-string. The JSON will be transformed into a string, and the replacement happens globally - use it with care. -Hint: There are also some simplified transformers in [TransformerUtility](https://github.com/localstack/localstack/blob/master/localstack-core/localstack/testing/snapshots/transformer_utility.py). +Hint: There are also some simplified transformers in [TransformerUtility](https://github.com/localstack/localstack/blob/main/localstack-core/localstack/testing/snapshots/transformer_utility.py). ### Examples diff --git a/docs/testing/test-types/README.md b/docs/testing/test-types/README.md index 2cf9a8ca9a168..140cb2fb0865d 100644 --- a/docs/testing/test-types/README.md +++ b/docs/testing/test-types/README.md @@ -7,7 +7,7 @@ In the LocalStack codebase we differentiate between the following test types: - Integration Tests Depending on the workflow and its trigger not all of those tests are executed at once. -For ordinary pushes to `master` we only want to execute the Unit and Acceptance tests. +For ordinary pushes to `main` we only want to execute the Unit and Acceptance tests. On a regular schedule, however, we want to execute all tests to have as big of a coverage of our logic as possible. This differentiation also educates what we expect from the different types of tests. @@ -19,18 +19,18 @@ These tests should be able to complete their execution very quickly, so they nev If you need some kind of waiting mechanism in your unit test, it is most likely that you are not writing a unit test. A good example for a unit test is `tests.unit.testing.testselection.test_matching.test_service_dependency_resolving_with_dependencies`. -It tests whether an algorithm implemented inside of a bigger implementation performs as it is expected of it. +It tests whether an algorithm implemented inside of a bigger implementation performs as it is expected of it. ## Acceptance tests -We use acceptance tests to gain a quick understanding of whether the recently pushed commit to `master` fulfils minimally viable quality criteria. +We use acceptance tests to gain a quick understanding of whether the recently pushed commit to `main` fulfils minimally viable quality criteria. This means that these tests do not aim at maximum coverage but instead should test that the most important functionality works. This in general is the entire serving infrastructure and the main features of the most used services. As these tests are executed very often we need them to be as stable, fast and relevant as possible. We ensure this by the following criteria: -- It shows some kind of real-world usage. This is usually a scenario or architectural pattern with multiple services. +- It shows some kind of real-world usage. This is usually a scenario or architectural pattern with multiple services. - When composing these scenarios, the services should not overlap too much with already existing acceptance tests. We want to avoid redundancy where possible. At the same time we want to have our primary services and typical use-cases being covered. - Existing samples (from [our samples organization](https://github.com/localstack-samples)) might serve as a starting point for constructing such a scenario. However, keep in mind that we want to use many interacting resources in these tests, so the samples might need further expansion. diff --git a/localstack-core/localstack/aws/api/acm/__init__.py b/localstack-core/localstack/aws/api/acm/__init__.py index b62d3c2508d96..8d10782d79470 100644 --- a/localstack-core/localstack/aws/api/acm/__init__.py +++ b/localstack-core/localstack/aws/api/acm/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -261,10 +261,10 @@ class ValidationException(ServiceException): class Tag(TypedDict, total=False): Key: TagKey - Value: Optional[TagValue] + Value: TagValue | None -TagList = List[Tag] +TagList = list[Tag] class AddTagsToCertificateRequest(ServiceRequest): @@ -277,29 +277,29 @@ class AddTagsToCertificateRequest(ServiceRequest): class CertificateOptions(TypedDict, total=False): - CertificateTransparencyLoggingPreference: Optional[CertificateTransparencyLoggingPreference] - Export: Optional[CertificateExport] + CertificateTransparencyLoggingPreference: CertificateTransparencyLoggingPreference | None + Export: CertificateExport | None class ExtendedKeyUsage(TypedDict, total=False): - Name: Optional[ExtendedKeyUsageName] - OID: Optional[String] + Name: ExtendedKeyUsageName | None + OID: String | None -ExtendedKeyUsageList = List[ExtendedKeyUsage] +ExtendedKeyUsageList = list[ExtendedKeyUsage] class KeyUsage(TypedDict, total=False): - Name: Optional[KeyUsageName] + Name: KeyUsageName | None -KeyUsageList = List[KeyUsage] +KeyUsageList = list[KeyUsage] TStamp = datetime class HttpRedirect(TypedDict, total=False): - RedirectFrom: Optional[String] - RedirectTo: Optional[String] + RedirectFrom: String | None + RedirectTo: String | None class ResourceRecord(TypedDict, total=False): @@ -308,92 +308,92 @@ class ResourceRecord(TypedDict, total=False): Value: String -ValidationEmailList = List[String] +ValidationEmailList = list[String] class DomainValidation(TypedDict, total=False): DomainName: DomainNameString - ValidationEmails: Optional[ValidationEmailList] - ValidationDomain: Optional[DomainNameString] - ValidationStatus: Optional[DomainStatus] - ResourceRecord: Optional[ResourceRecord] - HttpRedirect: Optional[HttpRedirect] - ValidationMethod: Optional[ValidationMethod] + ValidationEmails: ValidationEmailList | None + ValidationDomain: DomainNameString | None + ValidationStatus: DomainStatus | None + ResourceRecord: ResourceRecord | None + HttpRedirect: HttpRedirect | None + ValidationMethod: ValidationMethod | None -DomainValidationList = List[DomainValidation] +DomainValidationList = list[DomainValidation] class RenewalSummary(TypedDict, total=False): RenewalStatus: RenewalStatus DomainValidationOptions: DomainValidationList - RenewalStatusReason: Optional[FailureReason] + RenewalStatusReason: FailureReason | None UpdatedAt: TStamp -InUseList = List[String] -DomainList = List[DomainNameString] +InUseList = list[String] +DomainList = list[DomainNameString] class CertificateDetail(TypedDict, total=False): - CertificateArn: Optional[Arn] - DomainName: Optional[DomainNameString] - SubjectAlternativeNames: Optional[DomainList] - ManagedBy: Optional[CertificateManagedBy] - DomainValidationOptions: Optional[DomainValidationList] - Serial: Optional[String] - Subject: Optional[String] - Issuer: Optional[String] - CreatedAt: Optional[TStamp] - IssuedAt: Optional[TStamp] - ImportedAt: Optional[TStamp] - Status: Optional[CertificateStatus] - RevokedAt: Optional[TStamp] - RevocationReason: Optional[RevocationReason] - NotBefore: Optional[TStamp] - NotAfter: Optional[TStamp] - KeyAlgorithm: Optional[KeyAlgorithm] - SignatureAlgorithm: Optional[String] - InUseBy: Optional[InUseList] - FailureReason: Optional[FailureReason] - Type: Optional[CertificateType] - RenewalSummary: Optional[RenewalSummary] - KeyUsages: Optional[KeyUsageList] - ExtendedKeyUsages: Optional[ExtendedKeyUsageList] - CertificateAuthorityArn: Optional[Arn] - RenewalEligibility: Optional[RenewalEligibility] - Options: Optional[CertificateOptions] - - -CertificateStatuses = List[CertificateStatus] -ExtendedKeyUsageNames = List[ExtendedKeyUsageName] -KeyUsageNames = List[KeyUsageName] + CertificateArn: Arn | None + DomainName: DomainNameString | None + SubjectAlternativeNames: DomainList | None + ManagedBy: CertificateManagedBy | None + DomainValidationOptions: DomainValidationList | None + Serial: String | None + Subject: String | None + Issuer: String | None + CreatedAt: TStamp | None + IssuedAt: TStamp | None + ImportedAt: TStamp | None + Status: CertificateStatus | None + RevokedAt: TStamp | None + RevocationReason: RevocationReason | None + NotBefore: TStamp | None + NotAfter: TStamp | None + KeyAlgorithm: KeyAlgorithm | None + SignatureAlgorithm: String | None + InUseBy: InUseList | None + FailureReason: FailureReason | None + Type: CertificateType | None + RenewalSummary: RenewalSummary | None + KeyUsages: KeyUsageList | None + ExtendedKeyUsages: ExtendedKeyUsageList | None + CertificateAuthorityArn: Arn | None + RenewalEligibility: RenewalEligibility | None + Options: CertificateOptions | None + + +CertificateStatuses = list[CertificateStatus] +ExtendedKeyUsageNames = list[ExtendedKeyUsageName] +KeyUsageNames = list[KeyUsageName] class CertificateSummary(TypedDict, total=False): - CertificateArn: Optional[Arn] - DomainName: Optional[DomainNameString] - SubjectAlternativeNameSummaries: Optional[DomainList] - HasAdditionalSubjectAlternativeNames: Optional[NullableBoolean] - Status: Optional[CertificateStatus] - Type: Optional[CertificateType] - KeyAlgorithm: Optional[KeyAlgorithm] - KeyUsages: Optional[KeyUsageNames] - ExtendedKeyUsages: Optional[ExtendedKeyUsageNames] - ExportOption: Optional[CertificateExport] - InUse: Optional[NullableBoolean] - Exported: Optional[NullableBoolean] - RenewalEligibility: Optional[RenewalEligibility] - NotBefore: Optional[TStamp] - NotAfter: Optional[TStamp] - CreatedAt: Optional[TStamp] - IssuedAt: Optional[TStamp] - ImportedAt: Optional[TStamp] - RevokedAt: Optional[TStamp] - ManagedBy: Optional[CertificateManagedBy] - - -CertificateSummaryList = List[CertificateSummary] + CertificateArn: Arn | None + DomainName: DomainNameString | None + SubjectAlternativeNameSummaries: DomainList | None + HasAdditionalSubjectAlternativeNames: NullableBoolean | None + Status: CertificateStatus | None + Type: CertificateType | None + KeyAlgorithm: KeyAlgorithm | None + KeyUsages: KeyUsageNames | None + ExtendedKeyUsages: ExtendedKeyUsageNames | None + ExportOption: CertificateExport | None + InUse: NullableBoolean | None + Exported: NullableBoolean | None + RenewalEligibility: RenewalEligibility | None + NotBefore: TStamp | None + NotAfter: TStamp | None + CreatedAt: TStamp | None + IssuedAt: TStamp | None + ImportedAt: TStamp | None + RevokedAt: TStamp | None + ManagedBy: CertificateManagedBy | None + + +CertificateSummaryList = list[CertificateSummary] class DeleteCertificateRequest(ServiceRequest): @@ -405,7 +405,7 @@ class DescribeCertificateRequest(ServiceRequest): class DescribeCertificateResponse(TypedDict, total=False): - Certificate: Optional[CertificateDetail] + Certificate: CertificateDetail | None class DomainValidationOption(TypedDict, total=False): @@ -413,11 +413,11 @@ class DomainValidationOption(TypedDict, total=False): ValidationDomain: DomainNameString -DomainValidationOptionList = List[DomainValidationOption] +DomainValidationOptionList = list[DomainValidationOption] class ExpiryEventsConfiguration(TypedDict, total=False): - DaysBeforeExpiry: Optional[PositiveInteger] + DaysBeforeExpiry: PositiveInteger | None PassphraseBlob = bytes @@ -429,26 +429,26 @@ class ExportCertificateRequest(ServiceRequest): class ExportCertificateResponse(TypedDict, total=False): - Certificate: Optional[CertificateBody] - CertificateChain: Optional[CertificateChain] - PrivateKey: Optional[PrivateKey] + Certificate: CertificateBody | None + CertificateChain: CertificateChain | None + PrivateKey: PrivateKey | None -ExtendedKeyUsageFilterList = List[ExtendedKeyUsageName] -KeyAlgorithmList = List[KeyAlgorithm] -KeyUsageFilterList = List[KeyUsageName] +ExtendedKeyUsageFilterList = list[ExtendedKeyUsageName] +KeyAlgorithmList = list[KeyAlgorithm] +KeyUsageFilterList = list[KeyUsageName] class Filters(TypedDict, total=False): - extendedKeyUsage: Optional[ExtendedKeyUsageFilterList] - keyUsage: Optional[KeyUsageFilterList] - keyTypes: Optional[KeyAlgorithmList] - exportOption: Optional[CertificateExport] - managedBy: Optional[CertificateManagedBy] + extendedKeyUsage: ExtendedKeyUsageFilterList | None + keyUsage: KeyUsageFilterList | None + keyTypes: KeyAlgorithmList | None + exportOption: CertificateExport | None + managedBy: CertificateManagedBy | None class GetAccountConfigurationResponse(TypedDict, total=False): - ExpiryEvents: Optional[ExpiryEventsConfiguration] + ExpiryEvents: ExpiryEventsConfiguration | None class GetCertificateRequest(ServiceRequest): @@ -456,37 +456,37 @@ class GetCertificateRequest(ServiceRequest): class GetCertificateResponse(TypedDict, total=False): - Certificate: Optional[CertificateBody] - CertificateChain: Optional[CertificateChain] + Certificate: CertificateBody | None + CertificateChain: CertificateChain | None PrivateKeyBlob = bytes class ImportCertificateRequest(ServiceRequest): - CertificateArn: Optional[Arn] + CertificateArn: Arn | None Certificate: CertificateBodyBlob PrivateKey: PrivateKeyBlob - CertificateChain: Optional[CertificateChainBlob] - Tags: Optional[TagList] + CertificateChain: CertificateChainBlob | None + Tags: TagList | None class ImportCertificateResponse(TypedDict, total=False): - CertificateArn: Optional[Arn] + CertificateArn: Arn | None class ListCertificatesRequest(ServiceRequest): - CertificateStatuses: Optional[CertificateStatuses] - Includes: Optional[Filters] - NextToken: Optional[NextToken] - MaxItems: Optional[MaxItems] - SortBy: Optional[SortBy] - SortOrder: Optional[SortOrder] + CertificateStatuses: CertificateStatuses | None + Includes: Filters | None + NextToken: NextToken | None + MaxItems: MaxItems | None + SortBy: SortBy | None + SortOrder: SortOrder | None class ListCertificatesResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - CertificateSummaryList: Optional[CertificateSummaryList] + NextToken: NextToken | None + CertificateSummaryList: CertificateSummaryList | None class ListTagsForCertificateRequest(ServiceRequest): @@ -494,11 +494,11 @@ class ListTagsForCertificateRequest(ServiceRequest): class ListTagsForCertificateResponse(TypedDict, total=False): - Tags: Optional[TagList] + Tags: TagList | None class PutAccountConfigurationRequest(ServiceRequest): - ExpiryEvents: Optional[ExpiryEventsConfiguration] + ExpiryEvents: ExpiryEventsConfiguration | None IdempotencyToken: IdempotencyToken @@ -513,19 +513,19 @@ class RenewCertificateRequest(ServiceRequest): class RequestCertificateRequest(ServiceRequest): DomainName: DomainNameString - ValidationMethod: Optional[ValidationMethod] - SubjectAlternativeNames: Optional[DomainList] - IdempotencyToken: Optional[IdempotencyToken] - DomainValidationOptions: Optional[DomainValidationOptionList] - Options: Optional[CertificateOptions] - CertificateAuthorityArn: Optional[PcaArn] - Tags: Optional[TagList] - KeyAlgorithm: Optional[KeyAlgorithm] - ManagedBy: Optional[CertificateManagedBy] + ValidationMethod: ValidationMethod | None + SubjectAlternativeNames: DomainList | None + IdempotencyToken: IdempotencyToken | None + DomainValidationOptions: DomainValidationOptionList | None + Options: CertificateOptions | None + CertificateAuthorityArn: PcaArn | None + Tags: TagList | None + KeyAlgorithm: KeyAlgorithm | None + ManagedBy: CertificateManagedBy | None class RequestCertificateResponse(TypedDict, total=False): - CertificateArn: Optional[Arn] + CertificateArn: Arn | None class ResendValidationEmailRequest(ServiceRequest): @@ -540,7 +540,7 @@ class RevokeCertificateRequest(ServiceRequest): class RevokeCertificateResponse(TypedDict, total=False): - CertificateArn: Optional[Arn] + CertificateArn: Arn | None class UpdateCertificateOptionsRequest(ServiceRequest): @@ -549,8 +549,8 @@ class UpdateCertificateOptionsRequest(ServiceRequest): class AcmApi: - service = "acm" - version = "2015-12-08" + service: str = "acm" + version: str = "2015-12-08" @handler("AddTagsToCertificate") def add_tags_to_certificate( diff --git a/localstack-core/localstack/aws/api/apigateway/__init__.py b/localstack-core/localstack/aws/api/apigateway/__init__.py index 0010dd6b5b24a..d47a5a760ea0d 100644 --- a/localstack-core/localstack/aws/api/apigateway/__init__.py +++ b/localstack-core/localstack/aws/api/apigateway/__init__.py @@ -1,6 +1,7 @@ +from collections.abc import Iterable from datetime import datetime from enum import StrEnum -from typing import IO, Dict, Iterable, List, Optional, TypedDict, Union +from typing import IO, TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -28,6 +29,13 @@ class ApiKeysFormat(StrEnum): csv = "csv" +class ApiStatus(StrEnum): + UPDATING = "UPDATING" + AVAILABLE = "AVAILABLE" + PENDING = "PENDING" + FAILED = "FAILED" + + class AuthorizerType(StrEnum): TOKEN = "TOKEN" REQUEST = "REQUEST" @@ -84,6 +92,12 @@ class DomainNameStatus(StrEnum): PENDING = "PENDING" PENDING_CERTIFICATE_REIMPORT = "PENDING_CERTIFICATE_REIMPORT" PENDING_OWNERSHIP_VERIFICATION = "PENDING_OWNERSHIP_VERIFICATION" + FAILED = "FAILED" + + +class EndpointAccessMode(StrEnum): + BASIC = "BASIC" + STRICT = "STRICT" class EndpointType(StrEnum): @@ -159,6 +173,11 @@ class ResourceOwner(StrEnum): OTHER_ACCOUNTS = "OTHER_ACCOUNTS" +class ResponseTransferMode(StrEnum): + BUFFERED = "BUFFERED" + STREAM = "STREAM" + + class RoutingMode(StrEnum): BASE_PATH_MAPPING_ONLY = "BASE_PATH_MAPPING_ONLY" ROUTING_RULE_ONLY = "ROUTING_RULE_ONLY" @@ -168,6 +187,15 @@ class RoutingMode(StrEnum): class SecurityPolicy(StrEnum): TLS_1_0 = "TLS_1_0" TLS_1_2 = "TLS_1_2" + SecurityPolicy_TLS13_1_3_2025_09 = "SecurityPolicy_TLS13_1_3_2025_09" + SecurityPolicy_TLS13_1_3_FIPS_2025_09 = "SecurityPolicy_TLS13_1_3_FIPS_2025_09" + SecurityPolicy_TLS13_1_2_PFS_PQ_2025_09 = "SecurityPolicy_TLS13_1_2_PFS_PQ_2025_09" + SecurityPolicy_TLS13_1_2_FIPS_PQ_2025_09 = "SecurityPolicy_TLS13_1_2_FIPS_PQ_2025_09" + SecurityPolicy_TLS13_1_2_PQ_2025_09 = "SecurityPolicy_TLS13_1_2_PQ_2025_09" + SecurityPolicy_TLS13_1_2_2021_06 = "SecurityPolicy_TLS13_1_2_2021_06" + SecurityPolicy_TLS13_2025_EDGE = "SecurityPolicy_TLS13_2025_EDGE" + SecurityPolicy_TLS12_PFS_2025_EDGE = "SecurityPolicy_TLS12_PFS_2025_EDGE" + SecurityPolicy_TLS12_2018_EDGE = "SecurityPolicy_TLS12_2018_EDGE" class UnauthorizedCacheControlHeaderStrategy(StrEnum): @@ -199,7 +227,7 @@ class LimitExceededException(ServiceException): code: str = "LimitExceededException" sender_fault: bool = False status_code: int = 429 - retryAfterSeconds: Optional[String] + retryAfterSeconds: String | None class NotFoundException(ServiceException): @@ -212,14 +240,14 @@ class ServiceUnavailableException(ServiceException): code: str = "ServiceUnavailableException" sender_fault: bool = False status_code: int = 503 - retryAfterSeconds: Optional[String] + retryAfterSeconds: String | None class TooManyRequestsException(ServiceException): code: str = "TooManyRequestsException" sender_fault: bool = False status_code: int = 429 - retryAfterSeconds: Optional[String] + retryAfterSeconds: String | None class UnauthorizedException(ServiceException): @@ -229,205 +257,194 @@ class UnauthorizedException(ServiceException): class AccessLogSettings(TypedDict, total=False): - format: Optional[String] - destinationArn: Optional[String] + format: String | None + destinationArn: String | None -ListOfString = List[String] +ListOfString = list[String] class ThrottleSettings(TypedDict, total=False): - burstLimit: Optional[Integer] - rateLimit: Optional[Double] + burstLimit: Integer | None + rateLimit: Double | None class Account(TypedDict, total=False): - cloudwatchRoleArn: Optional[String] - throttleSettings: Optional[ThrottleSettings] - features: Optional[ListOfString] - apiKeyVersion: Optional[String] + cloudwatchRoleArn: String | None + throttleSettings: ThrottleSettings | None + features: ListOfString | None + apiKeyVersion: String | None -MapOfStringToString = Dict[String, String] +MapOfStringToString = dict[String, String] Timestamp = datetime class ApiKey(TypedDict, total=False): - id: Optional[String] - value: Optional[String] - name: Optional[String] - customerId: Optional[String] - description: Optional[String] - enabled: Optional[Boolean] - createdDate: Optional[Timestamp] - lastUpdatedDate: Optional[Timestamp] - stageKeys: Optional[ListOfString] - tags: Optional[MapOfStringToString] + id: String | None + value: String | None + name: String | None + customerId: String | None + description: String | None + enabled: Boolean | None + createdDate: Timestamp | None + lastUpdatedDate: Timestamp | None + stageKeys: ListOfString | None + tags: MapOfStringToString | None class ApiKeyIds(TypedDict, total=False): - ids: Optional[ListOfString] - warnings: Optional[ListOfString] + ids: ListOfString | None + warnings: ListOfString | None -ListOfApiKey = List[ApiKey] +ListOfApiKey = list[ApiKey] class ApiKeys(TypedDict, total=False): - warnings: Optional[ListOfString] - position: Optional[String] - items: Optional[ListOfApiKey] + warnings: ListOfString | None + position: String | None + items: ListOfApiKey | None -MapOfApiStageThrottleSettings = Dict[String, ThrottleSettings] +MapOfApiStageThrottleSettings = dict[String, ThrottleSettings] class ApiStage(TypedDict, total=False): - apiId: Optional[String] - stage: Optional[String] - throttle: Optional[MapOfApiStageThrottleSettings] + apiId: String | None + stage: String | None + throttle: MapOfApiStageThrottleSettings | None -ListOfARNs = List[ProviderARN] -Authorizer = TypedDict( - "Authorizer", - { - "id": Optional[String], - "name": Optional[String], - "type": Optional[AuthorizerType], - "providerARNs": Optional[ListOfARNs], - "authType": Optional[String], - "authorizerUri": Optional[String], - "authorizerCredentials": Optional[String], - "identitySource": Optional[String], - "identityValidationExpression": Optional[String], - "authorizerResultTtlInSeconds": Optional[NullableInteger], - }, - total=False, -) -ListOfAuthorizer = List[Authorizer] +ListOfARNs = list[ProviderARN] + + +class Authorizer(TypedDict, total=False): + id: String | None + name: String | None + type: AuthorizerType | None + providerARNs: ListOfARNs | None + authType: String | None + authorizerUri: String | None + authorizerCredentials: String | None + identitySource: String | None + identityValidationExpression: String | None + authorizerResultTtlInSeconds: NullableInteger | None + + +ListOfAuthorizer = list[Authorizer] class Authorizers(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfAuthorizer] + position: String | None + items: ListOfAuthorizer | None class BasePathMapping(TypedDict, total=False): - basePath: Optional[String] - restApiId: Optional[String] - stage: Optional[String] + basePath: String | None + restApiId: String | None + stage: String | None -ListOfBasePathMapping = List[BasePathMapping] +ListOfBasePathMapping = list[BasePathMapping] class BasePathMappings(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfBasePathMapping] + position: String | None + items: ListOfBasePathMapping | None Blob = bytes class CanarySettings(TypedDict, total=False): - percentTraffic: Optional[Double] - deploymentId: Optional[String] - stageVariableOverrides: Optional[MapOfStringToString] - useStageCache: Optional[Boolean] + percentTraffic: Double | None + deploymentId: String | None + stageVariableOverrides: MapOfStringToString | None + useStageCache: Boolean | None class ClientCertificate(TypedDict, total=False): - clientCertificateId: Optional[String] - description: Optional[String] - pemEncodedCertificate: Optional[String] - createdDate: Optional[Timestamp] - expirationDate: Optional[Timestamp] - tags: Optional[MapOfStringToString] + clientCertificateId: String | None + description: String | None + pemEncodedCertificate: String | None + createdDate: Timestamp | None + expirationDate: Timestamp | None + tags: MapOfStringToString | None -ListOfClientCertificate = List[ClientCertificate] +ListOfClientCertificate = list[ClientCertificate] class ClientCertificates(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfClientCertificate] + position: String | None + items: ListOfClientCertificate | None class StageKey(TypedDict, total=False): - restApiId: Optional[String] - stageName: Optional[String] + restApiId: String | None + stageName: String | None -ListOfStageKeys = List[StageKey] +ListOfStageKeys = list[StageKey] class CreateApiKeyRequest(ServiceRequest): - name: Optional[String] - description: Optional[String] - enabled: Optional[Boolean] - generateDistinctId: Optional[Boolean] - value: Optional[String] - stageKeys: Optional[ListOfStageKeys] - customerId: Optional[String] - tags: Optional[MapOfStringToString] - - -CreateAuthorizerRequest = TypedDict( - "CreateAuthorizerRequest", - { - "restApiId": String, - "name": String, - "type": AuthorizerType, - "providerARNs": Optional[ListOfARNs], - "authType": Optional[String], - "authorizerUri": Optional[String], - "authorizerCredentials": Optional[String], - "identitySource": Optional[String], - "identityValidationExpression": Optional[String], - "authorizerResultTtlInSeconds": Optional[NullableInteger], - }, - total=False, -) + name: String | None + description: String | None + enabled: Boolean | None + generateDistinctId: Boolean | None + value: String | None + stageKeys: ListOfStageKeys | None + customerId: String | None + tags: MapOfStringToString | None + + +class CreateAuthorizerRequest(TypedDict, total=False): + restApiId: String + name: String + type: AuthorizerType + providerARNs: ListOfARNs | None + authType: String | None + authorizerUri: String | None + authorizerCredentials: String | None + identitySource: String | None + identityValidationExpression: String | None + authorizerResultTtlInSeconds: NullableInteger | None class CreateBasePathMappingRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] - basePath: Optional[String] + domainNameId: String | None + basePath: String | None restApiId: String - stage: Optional[String] + stage: String | None class DeploymentCanarySettings(TypedDict, total=False): - percentTraffic: Optional[Double] - stageVariableOverrides: Optional[MapOfStringToString] - useStageCache: Optional[Boolean] + percentTraffic: Double | None + stageVariableOverrides: MapOfStringToString | None + useStageCache: Boolean | None class CreateDeploymentRequest(ServiceRequest): restApiId: String - stageName: Optional[String] - stageDescription: Optional[String] - description: Optional[String] - cacheClusterEnabled: Optional[NullableBoolean] - cacheClusterSize: Optional[CacheClusterSize] - variables: Optional[MapOfStringToString] - canarySettings: Optional[DeploymentCanarySettings] - tracingEnabled: Optional[NullableBoolean] + stageName: String | None + stageDescription: String | None + description: String | None + cacheClusterEnabled: NullableBoolean | None + cacheClusterSize: CacheClusterSize | None + variables: MapOfStringToString | None + canarySettings: DeploymentCanarySettings | None + tracingEnabled: NullableBoolean | None -DocumentationPartLocation = TypedDict( - "DocumentationPartLocation", - { - "type": DocumentationPartType, - "path": Optional[String], - "method": Optional[String], - "statusCode": Optional[DocumentationPartLocationStatusCode], - "name": Optional[String], - }, - total=False, -) +class DocumentationPartLocation(TypedDict, total=False): + type: DocumentationPartType + path: String | None + method: String | None + statusCode: DocumentationPartLocationStatusCode | None + name: String | None class CreateDocumentationPartRequest(ServiceRequest): @@ -439,62 +456,63 @@ class CreateDocumentationPartRequest(ServiceRequest): class CreateDocumentationVersionRequest(ServiceRequest): restApiId: String documentationVersion: String - stageName: Optional[String] - description: Optional[String] + stageName: String | None + description: String | None class CreateDomainNameAccessAssociationRequest(ServiceRequest): domainNameArn: String accessAssociationSourceType: AccessAssociationSourceType accessAssociationSource: String - tags: Optional[MapOfStringToString] + tags: MapOfStringToString | None class MutualTlsAuthenticationInput(TypedDict, total=False): - truststoreUri: Optional[String] - truststoreVersion: Optional[String] + truststoreUri: String | None + truststoreVersion: String | None -ListOfEndpointType = List[EndpointType] +ListOfEndpointType = list[EndpointType] class EndpointConfiguration(TypedDict, total=False): - types: Optional[ListOfEndpointType] - ipAddressType: Optional[IpAddressType] - vpcEndpointIds: Optional[ListOfString] + types: ListOfEndpointType | None + ipAddressType: IpAddressType | None + vpcEndpointIds: ListOfString | None class CreateDomainNameRequest(ServiceRequest): domainName: String - certificateName: Optional[String] - certificateBody: Optional[String] - certificatePrivateKey: Optional[String] - certificateChain: Optional[String] - certificateArn: Optional[String] - regionalCertificateName: Optional[String] - regionalCertificateArn: Optional[String] - endpointConfiguration: Optional[EndpointConfiguration] - tags: Optional[MapOfStringToString] - securityPolicy: Optional[SecurityPolicy] - mutualTlsAuthentication: Optional[MutualTlsAuthenticationInput] - ownershipVerificationCertificateArn: Optional[String] - policy: Optional[String] - routingMode: Optional[RoutingMode] + certificateName: String | None + certificateBody: String | None + certificatePrivateKey: String | None + certificateChain: String | None + certificateArn: String | None + regionalCertificateName: String | None + regionalCertificateArn: String | None + endpointConfiguration: EndpointConfiguration | None + tags: MapOfStringToString | None + securityPolicy: SecurityPolicy | None + endpointAccessMode: EndpointAccessMode | None + mutualTlsAuthentication: MutualTlsAuthenticationInput | None + ownershipVerificationCertificateArn: String | None + policy: String | None + routingMode: RoutingMode | None class CreateModelRequest(ServiceRequest): restApiId: String name: String - description: Optional[String] - schema: Optional[String] + description: String | None + schema: String | None contentType: String class CreateRequestValidatorRequest(ServiceRequest): restApiId: String - name: Optional[String] - validateRequestBody: Optional[Boolean] - validateRequestParameters: Optional[Boolean] + name: String | None + validateRequestBody: Boolean | None + validateRequestParameters: Boolean | None class CreateResourceRequest(ServiceRequest): @@ -505,30 +523,32 @@ class CreateResourceRequest(ServiceRequest): class CreateRestApiRequest(ServiceRequest): name: String - description: Optional[String] - version: Optional[String] - cloneFrom: Optional[String] - binaryMediaTypes: Optional[ListOfString] - minimumCompressionSize: Optional[NullableInteger] - apiKeySource: Optional[ApiKeySourceType] - endpointConfiguration: Optional[EndpointConfiguration] - policy: Optional[String] - tags: Optional[MapOfStringToString] - disableExecuteApiEndpoint: Optional[Boolean] + description: String | None + version: String | None + cloneFrom: String | None + binaryMediaTypes: ListOfString | None + minimumCompressionSize: NullableInteger | None + apiKeySource: ApiKeySourceType | None + endpointConfiguration: EndpointConfiguration | None + policy: String | None + tags: MapOfStringToString | None + disableExecuteApiEndpoint: Boolean | None + securityPolicy: SecurityPolicy | None + endpointAccessMode: EndpointAccessMode | None class CreateStageRequest(ServiceRequest): restApiId: String stageName: String deploymentId: String - description: Optional[String] - cacheClusterEnabled: Optional[Boolean] - cacheClusterSize: Optional[CacheClusterSize] - variables: Optional[MapOfStringToString] - documentationVersion: Optional[String] - canarySettings: Optional[CanarySettings] - tracingEnabled: Optional[Boolean] - tags: Optional[MapOfStringToString] + description: String | None + cacheClusterEnabled: Boolean | None + cacheClusterSize: CacheClusterSize | None + variables: MapOfStringToString | None + documentationVersion: String | None + canarySettings: CanarySettings | None + tracingEnabled: Boolean | None + tags: MapOfStringToString | None class CreateUsagePlanKeyRequest(ServiceRequest): @@ -538,28 +558,28 @@ class CreateUsagePlanKeyRequest(ServiceRequest): class QuotaSettings(TypedDict, total=False): - limit: Optional[Integer] - offset: Optional[Integer] - period: Optional[QuotaPeriodType] + limit: Integer | None + offset: Integer | None + period: QuotaPeriodType | None -ListOfApiStage = List[ApiStage] +ListOfApiStage = list[ApiStage] class CreateUsagePlanRequest(ServiceRequest): name: String - description: Optional[String] - apiStages: Optional[ListOfApiStage] - throttle: Optional[ThrottleSettings] - quota: Optional[QuotaSettings] - tags: Optional[MapOfStringToString] + description: String | None + apiStages: ListOfApiStage | None + throttle: ThrottleSettings | None + quota: QuotaSettings | None + tags: MapOfStringToString | None class CreateVpcLinkRequest(ServiceRequest): name: String - description: Optional[String] + description: String | None targetArns: ListOfString - tags: Optional[MapOfStringToString] + tags: MapOfStringToString | None class DeleteApiKeyRequest(ServiceRequest): @@ -573,7 +593,7 @@ class DeleteAuthorizerRequest(ServiceRequest): class DeleteBasePathMappingRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] + domainNameId: String | None basePath: String @@ -602,7 +622,7 @@ class DeleteDomainNameAccessAssociationRequest(ServiceRequest): class DeleteDomainNameRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] + domainNameId: String | None class DeleteGatewayResponseRequest(ServiceRequest): @@ -674,121 +694,122 @@ class DeleteVpcLinkRequest(ServiceRequest): class MethodSnapshot(TypedDict, total=False): - authorizationType: Optional[String] - apiKeyRequired: Optional[Boolean] + authorizationType: String | None + apiKeyRequired: Boolean | None -MapOfMethodSnapshot = Dict[String, MethodSnapshot] -PathToMapOfMethodSnapshot = Dict[String, MapOfMethodSnapshot] +MapOfMethodSnapshot = dict[String, MethodSnapshot] +PathToMapOfMethodSnapshot = dict[String, MapOfMethodSnapshot] class Deployment(TypedDict, total=False): - id: Optional[String] - description: Optional[String] - createdDate: Optional[Timestamp] - apiSummary: Optional[PathToMapOfMethodSnapshot] + id: String | None + description: String | None + createdDate: Timestamp | None + apiSummary: PathToMapOfMethodSnapshot | None -ListOfDeployment = List[Deployment] +ListOfDeployment = list[Deployment] class Deployments(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfDeployment] + position: String | None + items: ListOfDeployment | None class DocumentationPart(TypedDict, total=False): - id: Optional[String] - location: Optional[DocumentationPartLocation] - properties: Optional[String] + id: String | None + location: DocumentationPartLocation | None + properties: String | None class DocumentationPartIds(TypedDict, total=False): - ids: Optional[ListOfString] - warnings: Optional[ListOfString] + ids: ListOfString | None + warnings: ListOfString | None -ListOfDocumentationPart = List[DocumentationPart] +ListOfDocumentationPart = list[DocumentationPart] class DocumentationParts(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfDocumentationPart] + position: String | None + items: ListOfDocumentationPart | None class DocumentationVersion(TypedDict, total=False): - version: Optional[String] - createdDate: Optional[Timestamp] - description: Optional[String] + version: String | None + createdDate: Timestamp | None + description: String | None -ListOfDocumentationVersion = List[DocumentationVersion] +ListOfDocumentationVersion = list[DocumentationVersion] class DocumentationVersions(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfDocumentationVersion] + position: String | None + items: ListOfDocumentationVersion | None class MutualTlsAuthentication(TypedDict, total=False): - truststoreUri: Optional[String] - truststoreVersion: Optional[String] - truststoreWarnings: Optional[ListOfString] + truststoreUri: String | None + truststoreVersion: String | None + truststoreWarnings: ListOfString | None class DomainName(TypedDict, total=False): - domainName: Optional[String] - domainNameId: Optional[String] - domainNameArn: Optional[String] - certificateName: Optional[String] - certificateArn: Optional[String] - certificateUploadDate: Optional[Timestamp] - regionalDomainName: Optional[String] - regionalHostedZoneId: Optional[String] - regionalCertificateName: Optional[String] - regionalCertificateArn: Optional[String] - distributionDomainName: Optional[String] - distributionHostedZoneId: Optional[String] - endpointConfiguration: Optional[EndpointConfiguration] - domainNameStatus: Optional[DomainNameStatus] - domainNameStatusMessage: Optional[String] - securityPolicy: Optional[SecurityPolicy] - tags: Optional[MapOfStringToString] - mutualTlsAuthentication: Optional[MutualTlsAuthentication] - ownershipVerificationCertificateArn: Optional[String] - managementPolicy: Optional[String] - policy: Optional[String] - routingMode: Optional[RoutingMode] + domainName: String | None + domainNameId: String | None + domainNameArn: String | None + certificateName: String | None + certificateArn: String | None + certificateUploadDate: Timestamp | None + regionalDomainName: String | None + regionalHostedZoneId: String | None + regionalCertificateName: String | None + regionalCertificateArn: String | None + distributionDomainName: String | None + distributionHostedZoneId: String | None + endpointConfiguration: EndpointConfiguration | None + domainNameStatus: DomainNameStatus | None + domainNameStatusMessage: String | None + securityPolicy: SecurityPolicy | None + endpointAccessMode: EndpointAccessMode | None + tags: MapOfStringToString | None + mutualTlsAuthentication: MutualTlsAuthentication | None + ownershipVerificationCertificateArn: String | None + managementPolicy: String | None + policy: String | None + routingMode: RoutingMode | None class DomainNameAccessAssociation(TypedDict, total=False): - domainNameAccessAssociationArn: Optional[String] - domainNameArn: Optional[String] - accessAssociationSourceType: Optional[AccessAssociationSourceType] - accessAssociationSource: Optional[String] - tags: Optional[MapOfStringToString] + domainNameAccessAssociationArn: String | None + domainNameArn: String | None + accessAssociationSourceType: AccessAssociationSourceType | None + accessAssociationSource: String | None + tags: MapOfStringToString | None -ListOfDomainNameAccessAssociation = List[DomainNameAccessAssociation] +ListOfDomainNameAccessAssociation = list[DomainNameAccessAssociation] class DomainNameAccessAssociations(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfDomainNameAccessAssociation] + position: String | None + items: ListOfDomainNameAccessAssociation | None -ListOfDomainName = List[DomainName] +ListOfDomainName = list[DomainName] class DomainNames(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfDomainName] + position: String | None + items: ListOfDomainName | None class ExportResponse(TypedDict, total=False): - body: Optional[Union[Blob, IO[Blob], Iterable[Blob]]] - contentType: Optional[String] - contentDisposition: Optional[String] + body: Blob | IO[Blob] | Iterable[Blob] | None + contentType: String | None + contentDisposition: String | None class FlushStageAuthorizersCacheRequest(ServiceRequest): @@ -802,24 +823,24 @@ class FlushStageCacheRequest(ServiceRequest): class GatewayResponse(TypedDict, total=False): - responseType: Optional[GatewayResponseType] - statusCode: Optional[StatusCode] - responseParameters: Optional[MapOfStringToString] - responseTemplates: Optional[MapOfStringToString] - defaultResponse: Optional[Boolean] + responseType: GatewayResponseType | None + statusCode: StatusCode | None + responseParameters: MapOfStringToString | None + responseTemplates: MapOfStringToString | None + defaultResponse: Boolean | None -ListOfGatewayResponse = List[GatewayResponse] +ListOfGatewayResponse = list[GatewayResponse] class GatewayResponses(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfGatewayResponse] + position: String | None + items: ListOfGatewayResponse | None class GenerateClientCertificateRequest(ServiceRequest): - description: Optional[String] - tags: Optional[MapOfStringToString] + description: String | None + tags: MapOfStringToString | None class GetAccountRequest(ServiceRequest): @@ -828,15 +849,15 @@ class GetAccountRequest(ServiceRequest): class GetApiKeyRequest(ServiceRequest): apiKey: String - includeValue: Optional[NullableBoolean] + includeValue: NullableBoolean | None class GetApiKeysRequest(ServiceRequest): - position: Optional[String] - limit: Optional[NullableInteger] - nameQuery: Optional[String] - customerId: Optional[String] - includeValues: Optional[NullableBoolean] + position: String | None + limit: NullableInteger | None + nameQuery: String | None + customerId: String | None + includeValues: NullableBoolean | None class GetAuthorizerRequest(ServiceRequest): @@ -846,21 +867,21 @@ class GetAuthorizerRequest(ServiceRequest): class GetAuthorizersRequest(ServiceRequest): restApiId: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetBasePathMappingRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] + domainNameId: String | None basePath: String class GetBasePathMappingsRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] - position: Optional[String] - limit: Optional[NullableInteger] + domainNameId: String | None + position: String | None + limit: NullableInteger | None class GetClientCertificateRequest(ServiceRequest): @@ -868,20 +889,20 @@ class GetClientCertificateRequest(ServiceRequest): class GetClientCertificatesRequest(ServiceRequest): - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetDeploymentRequest(ServiceRequest): restApiId: String deploymentId: String - embed: Optional[ListOfString] + embed: ListOfString | None class GetDeploymentsRequest(ServiceRequest): restApiId: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetDocumentationPartRequest(ServiceRequest): @@ -889,19 +910,14 @@ class GetDocumentationPartRequest(ServiceRequest): documentationPartId: String -GetDocumentationPartsRequest = TypedDict( - "GetDocumentationPartsRequest", - { - "restApiId": String, - "type": Optional[DocumentationPartType], - "nameQuery": Optional[String], - "path": Optional[String], - "position": Optional[String], - "limit": Optional[NullableInteger], - "locationStatus": Optional[LocationStatusType], - }, - total=False, -) +class GetDocumentationPartsRequest(TypedDict, total=False): + restApiId: String + type: DocumentationPartType | None + nameQuery: String | None + path: String | None + position: String | None + limit: NullableInteger | None + locationStatus: LocationStatusType | None class GetDocumentationVersionRequest(ServiceRequest): @@ -911,33 +927,33 @@ class GetDocumentationVersionRequest(ServiceRequest): class GetDocumentationVersionsRequest(ServiceRequest): restApiId: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetDomainNameAccessAssociationsRequest(ServiceRequest): - position: Optional[String] - limit: Optional[NullableInteger] - resourceOwner: Optional[ResourceOwner] + position: String | None + limit: NullableInteger | None + resourceOwner: ResourceOwner | None class GetDomainNameRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] + domainNameId: String | None class GetDomainNamesRequest(ServiceRequest): - position: Optional[String] - limit: Optional[NullableInteger] - resourceOwner: Optional[ResourceOwner] + position: String | None + limit: NullableInteger | None + resourceOwner: ResourceOwner | None class GetExportRequest(ServiceRequest): restApiId: String stageName: String exportType: String - parameters: Optional[MapOfStringToString] - accepts: Optional[String] + parameters: MapOfStringToString | None + accepts: String | None class GetGatewayResponseRequest(ServiceRequest): @@ -947,8 +963,8 @@ class GetGatewayResponseRequest(ServiceRequest): class GetGatewayResponsesRequest(ServiceRequest): restApiId: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetIntegrationRequest(ServiceRequest): @@ -980,7 +996,7 @@ class GetMethodResponseRequest(ServiceRequest): class GetModelRequest(ServiceRequest): restApiId: String modelName: String - flatten: Optional[Boolean] + flatten: Boolean | None class GetModelTemplateRequest(ServiceRequest): @@ -990,8 +1006,8 @@ class GetModelTemplateRequest(ServiceRequest): class GetModelsRequest(ServiceRequest): restApiId: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetRequestValidatorRequest(ServiceRequest): @@ -1001,21 +1017,21 @@ class GetRequestValidatorRequest(ServiceRequest): class GetRequestValidatorsRequest(ServiceRequest): restApiId: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetResourceRequest(ServiceRequest): restApiId: String resourceId: String - embed: Optional[ListOfString] + embed: ListOfString | None class GetResourcesRequest(ServiceRequest): restApiId: String - position: Optional[String] - limit: Optional[NullableInteger] - embed: Optional[ListOfString] + position: String | None + limit: NullableInteger | None + embed: ListOfString | None class GetRestApiRequest(ServiceRequest): @@ -1023,15 +1039,15 @@ class GetRestApiRequest(ServiceRequest): class GetRestApisRequest(ServiceRequest): - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetSdkRequest(ServiceRequest): restApiId: String stageName: String sdkType: String - parameters: Optional[MapOfStringToString] + parameters: MapOfStringToString | None class GetSdkTypeRequest(ServiceRequest): @@ -1039,8 +1055,8 @@ class GetSdkTypeRequest(ServiceRequest): class GetSdkTypesRequest(ServiceRequest): - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetStageRequest(ServiceRequest): @@ -1050,13 +1066,13 @@ class GetStageRequest(ServiceRequest): class GetStagesRequest(ServiceRequest): restApiId: String - deploymentId: Optional[String] + deploymentId: String | None class GetTagsRequest(ServiceRequest): resourceArn: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetUsagePlanKeyRequest(ServiceRequest): @@ -1066,9 +1082,9 @@ class GetUsagePlanKeyRequest(ServiceRequest): class GetUsagePlanKeysRequest(ServiceRequest): usagePlanId: String - position: Optional[String] - limit: Optional[NullableInteger] - nameQuery: Optional[String] + position: String | None + limit: NullableInteger | None + nameQuery: String | None class GetUsagePlanRequest(ServiceRequest): @@ -1076,18 +1092,18 @@ class GetUsagePlanRequest(ServiceRequest): class GetUsagePlansRequest(ServiceRequest): - position: Optional[String] - keyId: Optional[String] - limit: Optional[NullableInteger] + position: String | None + keyId: String | None + limit: NullableInteger | None class GetUsageRequest(ServiceRequest): usagePlanId: String - keyId: Optional[String] + keyId: String | None startDate: String endDate: String - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class GetVpcLinkRequest(ServiceRequest): @@ -1095,294 +1111,298 @@ class GetVpcLinkRequest(ServiceRequest): class GetVpcLinksRequest(ServiceRequest): - position: Optional[String] - limit: Optional[NullableInteger] + position: String | None + limit: NullableInteger | None class ImportApiKeysRequest(ServiceRequest): body: IO[Blob] format: ApiKeysFormat - failOnWarnings: Optional[Boolean] + failOnWarnings: Boolean | None class ImportDocumentationPartsRequest(ServiceRequest): body: IO[Blob] restApiId: String - mode: Optional[PutMode] - failOnWarnings: Optional[Boolean] + mode: PutMode | None + failOnWarnings: Boolean | None class ImportRestApiRequest(ServiceRequest): body: IO[Blob] - failOnWarnings: Optional[Boolean] - parameters: Optional[MapOfStringToString] + failOnWarnings: Boolean | None + parameters: MapOfStringToString | None class TlsConfig(TypedDict, total=False): - insecureSkipVerification: Optional[Boolean] + insecureSkipVerification: Boolean | None + + +MapOfStringToNullableString = dict[String, String | None] class IntegrationResponse(TypedDict, total=False): - statusCode: Optional[StatusCode] - selectionPattern: Optional[String] - responseParameters: Optional[MapOfStringToString] - responseTemplates: Optional[MapOfStringToString] - contentHandling: Optional[ContentHandlingStrategy] + statusCode: StatusCode | None + selectionPattern: String | None + responseParameters: MapOfStringToString | None + responseTemplates: MapOfStringToNullableString | None + contentHandling: ContentHandlingStrategy | None + + +MapOfIntegrationResponse = dict[String, IntegrationResponse] + + +class Integration(TypedDict, total=False): + type: IntegrationType | None + httpMethod: String | None + uri: String | None + connectionType: ConnectionType | None + connectionId: String | None + credentials: String | None + requestParameters: MapOfStringToString | None + requestTemplates: MapOfStringToString | None + passthroughBehavior: String | None + contentHandling: ContentHandlingStrategy | None + timeoutInMillis: Integer | None + cacheNamespace: String | None + cacheKeyParameters: ListOfString | None + integrationResponses: MapOfIntegrationResponse | None + tlsConfig: TlsConfig | None + responseTransferMode: ResponseTransferMode | None + integrationTarget: String | None -MapOfIntegrationResponse = Dict[String, IntegrationResponse] -Integration = TypedDict( - "Integration", - { - "type": Optional[IntegrationType], - "httpMethod": Optional[String], - "uri": Optional[String], - "connectionType": Optional[ConnectionType], - "connectionId": Optional[String], - "credentials": Optional[String], - "requestParameters": Optional[MapOfStringToString], - "requestTemplates": Optional[MapOfStringToString], - "passthroughBehavior": Optional[String], - "contentHandling": Optional[ContentHandlingStrategy], - "timeoutInMillis": Optional[Integer], - "cacheNamespace": Optional[String], - "cacheKeyParameters": Optional[ListOfString], - "integrationResponses": Optional[MapOfIntegrationResponse], - "tlsConfig": Optional[TlsConfig], - }, - total=False, -) Long = int -ListOfLong = List[Long] +ListOfLong = list[Long] class Model(TypedDict, total=False): - id: Optional[String] - name: Optional[String] - description: Optional[String] - schema: Optional[String] - contentType: Optional[String] + id: String | None + name: String | None + description: String | None + schema: String | None + contentType: String | None -ListOfModel = List[Model] +ListOfModel = list[Model] PatchOperation = TypedDict( "PatchOperation", { - "op": Optional[Op], - "path": Optional[String], - "value": Optional[String], - "from": Optional[String], + "op": Op | None, + "path": String | None, + "value": String | None, + "from": String | None, }, total=False, ) -ListOfPatchOperation = List[PatchOperation] +ListOfPatchOperation = list[PatchOperation] class RequestValidator(TypedDict, total=False): - id: Optional[String] - name: Optional[String] - validateRequestBody: Optional[Boolean] - validateRequestParameters: Optional[Boolean] + id: String | None + name: String | None + validateRequestBody: Boolean | None + validateRequestParameters: Boolean | None -ListOfRequestValidator = List[RequestValidator] -MapOfStringToBoolean = Dict[String, NullableBoolean] +ListOfRequestValidator = list[RequestValidator] +MapOfStringToBoolean = dict[String, NullableBoolean] class MethodResponse(TypedDict, total=False): - statusCode: Optional[StatusCode] - responseParameters: Optional[MapOfStringToBoolean] - responseModels: Optional[MapOfStringToString] + statusCode: StatusCode | None + responseParameters: MapOfStringToBoolean | None + responseModels: MapOfStringToString | None -MapOfMethodResponse = Dict[String, MethodResponse] +MapOfMethodResponse = dict[String, MethodResponse] class Method(TypedDict, total=False): - httpMethod: Optional[String] - authorizationType: Optional[String] - authorizerId: Optional[String] - apiKeyRequired: Optional[NullableBoolean] - requestValidatorId: Optional[String] - operationName: Optional[String] - requestParameters: Optional[MapOfStringToBoolean] - requestModels: Optional[MapOfStringToString] - methodResponses: Optional[MapOfMethodResponse] - methodIntegration: Optional[Integration] - authorizationScopes: Optional[ListOfString] + httpMethod: String | None + authorizationType: String | None + authorizerId: String | None + apiKeyRequired: NullableBoolean | None + requestValidatorId: String | None + operationName: String | None + requestParameters: MapOfStringToBoolean | None + requestModels: MapOfStringToString | None + methodResponses: MapOfMethodResponse | None + methodIntegration: Integration | None + authorizationScopes: ListOfString | None -MapOfMethod = Dict[String, Method] +MapOfMethod = dict[String, Method] class Resource(TypedDict, total=False): - id: Optional[String] - parentId: Optional[String] - pathPart: Optional[String] - path: Optional[String] - resourceMethods: Optional[MapOfMethod] + id: String | None + parentId: String | None + pathPart: String | None + path: String | None + resourceMethods: MapOfMethod | None -ListOfResource = List[Resource] +ListOfResource = list[Resource] class RestApi(TypedDict, total=False): - id: Optional[String] - name: Optional[String] - description: Optional[String] - createdDate: Optional[Timestamp] - version: Optional[String] - warnings: Optional[ListOfString] - binaryMediaTypes: Optional[ListOfString] - minimumCompressionSize: Optional[NullableInteger] - apiKeySource: Optional[ApiKeySourceType] - endpointConfiguration: Optional[EndpointConfiguration] - policy: Optional[String] - tags: Optional[MapOfStringToString] - disableExecuteApiEndpoint: Optional[Boolean] - rootResourceId: Optional[String] - - -ListOfRestApi = List[RestApi] + id: String | None + name: String | None + description: String | None + createdDate: Timestamp | None + version: String | None + warnings: ListOfString | None + binaryMediaTypes: ListOfString | None + minimumCompressionSize: NullableInteger | None + apiKeySource: ApiKeySourceType | None + endpointConfiguration: EndpointConfiguration | None + policy: String | None + tags: MapOfStringToString | None + disableExecuteApiEndpoint: Boolean | None + rootResourceId: String | None + securityPolicy: SecurityPolicy | None + endpointAccessMode: EndpointAccessMode | None + apiStatus: ApiStatus | None + apiStatusMessage: String | None + + +ListOfRestApi = list[RestApi] class SdkConfigurationProperty(TypedDict, total=False): - name: Optional[String] - friendlyName: Optional[String] - description: Optional[String] - required: Optional[Boolean] - defaultValue: Optional[String] + name: String | None + friendlyName: String | None + description: String | None + required: Boolean | None + defaultValue: String | None -ListOfSdkConfigurationProperty = List[SdkConfigurationProperty] +ListOfSdkConfigurationProperty = list[SdkConfigurationProperty] class SdkType(TypedDict, total=False): - id: Optional[String] - friendlyName: Optional[String] - description: Optional[String] - configurationProperties: Optional[ListOfSdkConfigurationProperty] + id: String | None + friendlyName: String | None + description: String | None + configurationProperties: ListOfSdkConfigurationProperty | None -ListOfSdkType = List[SdkType] +ListOfSdkType = list[SdkType] class MethodSetting(TypedDict, total=False): - metricsEnabled: Optional[Boolean] - loggingLevel: Optional[String] - dataTraceEnabled: Optional[Boolean] - throttlingBurstLimit: Optional[Integer] - throttlingRateLimit: Optional[Double] - cachingEnabled: Optional[Boolean] - cacheTtlInSeconds: Optional[Integer] - cacheDataEncrypted: Optional[Boolean] - requireAuthorizationForCacheControl: Optional[Boolean] - unauthorizedCacheControlHeaderStrategy: Optional[UnauthorizedCacheControlHeaderStrategy] + metricsEnabled: Boolean | None + loggingLevel: String | None + dataTraceEnabled: Boolean | None + throttlingBurstLimit: Integer | None + throttlingRateLimit: Double | None + cachingEnabled: Boolean | None + cacheTtlInSeconds: Integer | None + cacheDataEncrypted: Boolean | None + requireAuthorizationForCacheControl: Boolean | None + unauthorizedCacheControlHeaderStrategy: UnauthorizedCacheControlHeaderStrategy | None -MapOfMethodSettings = Dict[String, MethodSetting] +MapOfMethodSettings = dict[String, MethodSetting] class Stage(TypedDict, total=False): - deploymentId: Optional[String] - clientCertificateId: Optional[String] - stageName: Optional[String] - description: Optional[String] - cacheClusterEnabled: Optional[Boolean] - cacheClusterSize: Optional[CacheClusterSize] - cacheClusterStatus: Optional[CacheClusterStatus] - methodSettings: Optional[MapOfMethodSettings] - variables: Optional[MapOfStringToString] - documentationVersion: Optional[String] - accessLogSettings: Optional[AccessLogSettings] - canarySettings: Optional[CanarySettings] - tracingEnabled: Optional[Boolean] - webAclArn: Optional[String] - tags: Optional[MapOfStringToString] - createdDate: Optional[Timestamp] - lastUpdatedDate: Optional[Timestamp] - - -ListOfStage = List[Stage] -ListOfUsage = List[ListOfLong] + deploymentId: String | None + clientCertificateId: String | None + stageName: String | None + description: String | None + cacheClusterEnabled: Boolean | None + cacheClusterSize: CacheClusterSize | None + cacheClusterStatus: CacheClusterStatus | None + methodSettings: MapOfMethodSettings | None + variables: MapOfStringToString | None + documentationVersion: String | None + accessLogSettings: AccessLogSettings | None + canarySettings: CanarySettings | None + tracingEnabled: Boolean | None + webAclArn: String | None + tags: MapOfStringToString | None + createdDate: Timestamp | None + lastUpdatedDate: Timestamp | None + + +ListOfStage = list[Stage] +ListOfUsage = list[ListOfLong] class UsagePlan(TypedDict, total=False): - id: Optional[String] - name: Optional[String] - description: Optional[String] - apiStages: Optional[ListOfApiStage] - throttle: Optional[ThrottleSettings] - quota: Optional[QuotaSettings] - productCode: Optional[String] - tags: Optional[MapOfStringToString] - - -ListOfUsagePlan = List[UsagePlan] -UsagePlanKey = TypedDict( - "UsagePlanKey", - { - "id": Optional[String], - "type": Optional[String], - "value": Optional[String], - "name": Optional[String], - }, - total=False, -) -ListOfUsagePlanKey = List[UsagePlanKey] + id: String | None + name: String | None + description: String | None + apiStages: ListOfApiStage | None + throttle: ThrottleSettings | None + quota: QuotaSettings | None + productCode: String | None + tags: MapOfStringToString | None + + +ListOfUsagePlan = list[UsagePlan] + + +class UsagePlanKey(TypedDict, total=False): + id: String | None + type: String | None + value: String | None + name: String | None + + +ListOfUsagePlanKey = list[UsagePlanKey] class VpcLink(TypedDict, total=False): - id: Optional[String] - name: Optional[String] - description: Optional[String] - targetArns: Optional[ListOfString] - status: Optional[VpcLinkStatus] - statusMessage: Optional[String] - tags: Optional[MapOfStringToString] + id: String | None + name: String | None + description: String | None + targetArns: ListOfString | None + status: VpcLinkStatus | None + statusMessage: String | None + tags: MapOfStringToString | None -ListOfVpcLink = List[VpcLink] -MapOfKeyUsages = Dict[String, ListOfUsage] -MapOfStringToList = Dict[String, ListOfString] +ListOfVpcLink = list[VpcLink] +MapOfKeyUsages = dict[String, ListOfUsage] +MapOfStringToList = dict[String, ListOfString] class Models(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfModel] + position: String | None + items: ListOfModel | None class PutGatewayResponseRequest(ServiceRequest): restApiId: String responseType: GatewayResponseType - statusCode: Optional[StatusCode] - responseParameters: Optional[MapOfStringToString] - responseTemplates: Optional[MapOfStringToString] + statusCode: StatusCode | None + responseParameters: MapOfStringToString | None + responseTemplates: MapOfStringToString | None -PutIntegrationRequest = TypedDict( - "PutIntegrationRequest", - { - "restApiId": String, - "resourceId": String, - "httpMethod": String, - "type": IntegrationType, - "integrationHttpMethod": Optional[String], - "uri": Optional[String], - "connectionType": Optional[ConnectionType], - "connectionId": Optional[String], - "credentials": Optional[String], - "requestParameters": Optional[MapOfStringToString], - "requestTemplates": Optional[MapOfStringToString], - "passthroughBehavior": Optional[String], - "cacheNamespace": Optional[String], - "cacheKeyParameters": Optional[ListOfString], - "contentHandling": Optional[ContentHandlingStrategy], - "timeoutInMillis": Optional[NullableInteger], - "tlsConfig": Optional[TlsConfig], - }, - total=False, -) +class PutIntegrationRequest(TypedDict, total=False): + restApiId: String + resourceId: String + httpMethod: String + type: IntegrationType + integrationHttpMethod: String | None + uri: String | None + connectionType: ConnectionType | None + connectionId: String | None + credentials: String | None + requestParameters: MapOfStringToString | None + requestTemplates: MapOfStringToString | None + passthroughBehavior: String | None + cacheNamespace: String | None + cacheKeyParameters: ListOfString | None + contentHandling: ContentHandlingStrategy | None + timeoutInMillis: NullableInteger | None + tlsConfig: TlsConfig | None + responseTransferMode: ResponseTransferMode | None + integrationTarget: String | None class PutIntegrationResponseRequest(ServiceRequest): @@ -1390,10 +1410,10 @@ class PutIntegrationResponseRequest(ServiceRequest): resourceId: String httpMethod: String statusCode: StatusCode - selectionPattern: Optional[String] - responseParameters: Optional[MapOfStringToString] - responseTemplates: Optional[MapOfStringToString] - contentHandling: Optional[ContentHandlingStrategy] + selectionPattern: String | None + responseParameters: MapOfStringToString | None + responseTemplates: MapOfStringToString | None + contentHandling: ContentHandlingStrategy | None class PutMethodRequest(ServiceRequest): @@ -1401,13 +1421,13 @@ class PutMethodRequest(ServiceRequest): resourceId: String httpMethod: String authorizationType: String - authorizerId: Optional[String] - apiKeyRequired: Optional[Boolean] - operationName: Optional[String] - requestParameters: Optional[MapOfStringToBoolean] - requestModels: Optional[MapOfStringToString] - requestValidatorId: Optional[String] - authorizationScopes: Optional[ListOfString] + authorizerId: String | None + apiKeyRequired: Boolean | None + operationName: String | None + requestParameters: MapOfStringToBoolean | None + requestModels: MapOfStringToString | None + requestValidatorId: String | None + authorizationScopes: ListOfString | None class PutMethodResponseRequest(ServiceRequest): @@ -1415,16 +1435,16 @@ class PutMethodResponseRequest(ServiceRequest): resourceId: String httpMethod: String statusCode: StatusCode - responseParameters: Optional[MapOfStringToBoolean] - responseModels: Optional[MapOfStringToString] + responseParameters: MapOfStringToBoolean | None + responseModels: MapOfStringToString | None class PutRestApiRequest(ServiceRequest): body: IO[Blob] restApiId: String - mode: Optional[PutMode] - failOnWarnings: Optional[Boolean] - parameters: Optional[MapOfStringToString] + mode: PutMode | None + failOnWarnings: Boolean | None + parameters: MapOfStringToString | None class RejectDomainNameAccessAssociationRequest(ServiceRequest): @@ -1433,33 +1453,33 @@ class RejectDomainNameAccessAssociationRequest(ServiceRequest): class RequestValidators(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfRequestValidator] + position: String | None + items: ListOfRequestValidator | None class Resources(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfResource] + position: String | None + items: ListOfResource | None class RestApis(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfRestApi] + position: String | None + items: ListOfRestApi | None class SdkResponse(TypedDict, total=False): - body: Optional[Union[Blob, IO[Blob], Iterable[Blob]]] - contentType: Optional[String] - contentDisposition: Optional[String] + body: Blob | IO[Blob] | Iterable[Blob] | None + contentType: String | None + contentDisposition: String | None class SdkTypes(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfSdkType] + position: String | None + items: ListOfSdkType | None class Stages(TypedDict, total=False): - item: Optional[ListOfStage] + item: ListOfStage | None class TagResourceRequest(ServiceRequest): @@ -1468,53 +1488,53 @@ class TagResourceRequest(ServiceRequest): class Tags(TypedDict, total=False): - tags: Optional[MapOfStringToString] + tags: MapOfStringToString | None class Template(TypedDict, total=False): - value: Optional[String] + value: String | None class TestInvokeAuthorizerRequest(ServiceRequest): restApiId: String authorizerId: String - headers: Optional[MapOfStringToString] - multiValueHeaders: Optional[MapOfStringToList] - pathWithQueryString: Optional[String] - body: Optional[String] - stageVariables: Optional[MapOfStringToString] - additionalContext: Optional[MapOfStringToString] + headers: MapOfStringToString | None + multiValueHeaders: MapOfStringToList | None + pathWithQueryString: String | None + body: String | None + stageVariables: MapOfStringToString | None + additionalContext: MapOfStringToString | None class TestInvokeAuthorizerResponse(TypedDict, total=False): - clientStatus: Optional[Integer] - log: Optional[String] - latency: Optional[Long] - principalId: Optional[String] - policy: Optional[String] - authorization: Optional[MapOfStringToList] - claims: Optional[MapOfStringToString] + clientStatus: Integer | None + log: String | None + latency: Long | None + principalId: String | None + policy: String | None + authorization: MapOfStringToList | None + claims: MapOfStringToString | None class TestInvokeMethodRequest(ServiceRequest): restApiId: String resourceId: String httpMethod: String - pathWithQueryString: Optional[String] - body: Optional[String] - headers: Optional[MapOfStringToString] - multiValueHeaders: Optional[MapOfStringToList] - clientCertificateId: Optional[String] - stageVariables: Optional[MapOfStringToString] + pathWithQueryString: String | None + body: String | None + headers: MapOfStringToString | None + multiValueHeaders: MapOfStringToList | None + clientCertificateId: String | None + stageVariables: MapOfStringToString | None class TestInvokeMethodResponse(TypedDict, total=False): - status: Optional[Integer] - body: Optional[String] - headers: Optional[MapOfStringToString] - multiValueHeaders: Optional[MapOfStringToList] - log: Optional[String] - latency: Optional[Long] + status: Integer | None + body: String | None + headers: MapOfStringToString | None + multiValueHeaders: MapOfStringToList | None + log: String | None + latency: Long | None class UntagResourceRequest(ServiceRequest): @@ -1523,67 +1543,67 @@ class UntagResourceRequest(ServiceRequest): class UpdateAccountRequest(ServiceRequest): - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateApiKeyRequest(ServiceRequest): apiKey: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateAuthorizerRequest(ServiceRequest): restApiId: String authorizerId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateBasePathMappingRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] + domainNameId: String | None basePath: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateClientCertificateRequest(ServiceRequest): clientCertificateId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateDeploymentRequest(ServiceRequest): restApiId: String deploymentId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateDocumentationPartRequest(ServiceRequest): restApiId: String documentationPartId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateDocumentationVersionRequest(ServiceRequest): restApiId: String documentationVersion: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateDomainNameRequest(ServiceRequest): domainName: String - domainNameId: Optional[String] - patchOperations: Optional[ListOfPatchOperation] + domainNameId: String | None + patchOperations: ListOfPatchOperation | None class UpdateGatewayResponseRequest(ServiceRequest): restApiId: String responseType: GatewayResponseType - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateIntegrationRequest(ServiceRequest): restApiId: String resourceId: String httpMethod: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateIntegrationResponseRequest(ServiceRequest): @@ -1591,14 +1611,14 @@ class UpdateIntegrationResponseRequest(ServiceRequest): resourceId: String httpMethod: String statusCode: StatusCode - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateMethodRequest(ServiceRequest): restApiId: String resourceId: String httpMethod: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateMethodResponseRequest(ServiceRequest): @@ -1606,80 +1626,80 @@ class UpdateMethodResponseRequest(ServiceRequest): resourceId: String httpMethod: String statusCode: StatusCode - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateModelRequest(ServiceRequest): restApiId: String modelName: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateRequestValidatorRequest(ServiceRequest): restApiId: String requestValidatorId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateResourceRequest(ServiceRequest): restApiId: String resourceId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateRestApiRequest(ServiceRequest): restApiId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateStageRequest(ServiceRequest): restApiId: String stageName: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateUsagePlanRequest(ServiceRequest): usagePlanId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateUsageRequest(ServiceRequest): usagePlanId: String keyId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class UpdateVpcLinkRequest(ServiceRequest): vpcLinkId: String - patchOperations: Optional[ListOfPatchOperation] + patchOperations: ListOfPatchOperation | None class Usage(TypedDict, total=False): - usagePlanId: Optional[String] - startDate: Optional[String] - endDate: Optional[String] - position: Optional[String] - items: Optional[MapOfKeyUsages] + usagePlanId: String | None + startDate: String | None + endDate: String | None + position: String | None + items: MapOfKeyUsages | None class UsagePlanKeys(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfUsagePlanKey] + position: String | None + items: ListOfUsagePlanKey | None class UsagePlans(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfUsagePlan] + position: String | None + items: ListOfUsagePlan | None class VpcLinks(TypedDict, total=False): - position: Optional[String] - items: Optional[ListOfVpcLink] + position: String | None + items: ListOfVpcLink | None class ApigatewayApi: - service = "apigateway" - version = "2015-07-09" + service: str = "apigateway" + version: str = "2015-07-09" @handler("CreateApiKey") def create_api_key( @@ -1771,6 +1791,7 @@ def create_domain_name( endpoint_configuration: EndpointConfiguration | None = None, tags: MapOfStringToString | None = None, security_policy: SecurityPolicy | None = None, + endpoint_access_mode: EndpointAccessMode | None = None, mutual_tls_authentication: MutualTlsAuthenticationInput | None = None, ownership_verification_certificate_arn: String | None = None, policy: String | None = None, @@ -1842,6 +1863,8 @@ def create_rest_api( policy: String | None = None, tags: MapOfStringToString | None = None, disable_execute_api_endpoint: Boolean | None = None, + security_policy: SecurityPolicy | None = None, + endpoint_access_mode: EndpointAccessMode | None = None, **kwargs, ) -> RestApi: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/cloudcontrol/__init__.py b/localstack-core/localstack/aws/api/cloudcontrol/__init__.py index 7420a35c50e8c..fdb07c98fed3e 100644 --- a/localstack-core/localstack/aws/api/cloudcontrol/__init__.py +++ b/localstack-core/localstack/aws/api/cloudcontrol/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -191,62 +191,62 @@ class CancelResourceRequestInput(ServiceRequest): class ProgressEvent(TypedDict, total=False): - TypeName: Optional[TypeName] - Identifier: Optional[Identifier] - RequestToken: Optional[RequestToken] - HooksRequestToken: Optional[RequestToken] - Operation: Optional[Operation] - OperationStatus: Optional[OperationStatus] - EventTime: Optional[Timestamp] - ResourceModel: Optional[Properties] - StatusMessage: Optional[StatusMessage] - ErrorCode: Optional[HandlerErrorCode] - RetryAfter: Optional[Timestamp] + TypeName: TypeName | None + Identifier: Identifier | None + RequestToken: RequestToken | None + HooksRequestToken: RequestToken | None + Operation: Operation | None + OperationStatus: OperationStatus | None + EventTime: Timestamp | None + ResourceModel: Properties | None + StatusMessage: StatusMessage | None + ErrorCode: HandlerErrorCode | None + RetryAfter: Timestamp | None class CancelResourceRequestOutput(TypedDict, total=False): - ProgressEvent: Optional[ProgressEvent] + ProgressEvent: ProgressEvent | None class CreateResourceInput(ServiceRequest): TypeName: TypeName - TypeVersionId: Optional[TypeVersionId] - RoleArn: Optional[RoleArn] - ClientToken: Optional[ClientToken] + TypeVersionId: TypeVersionId | None + RoleArn: RoleArn | None + ClientToken: ClientToken | None DesiredState: Properties class CreateResourceOutput(TypedDict, total=False): - ProgressEvent: Optional[ProgressEvent] + ProgressEvent: ProgressEvent | None class DeleteResourceInput(ServiceRequest): TypeName: TypeName - TypeVersionId: Optional[TypeVersionId] - RoleArn: Optional[RoleArn] - ClientToken: Optional[ClientToken] + TypeVersionId: TypeVersionId | None + RoleArn: RoleArn | None + ClientToken: ClientToken | None Identifier: Identifier class DeleteResourceOutput(TypedDict, total=False): - ProgressEvent: Optional[ProgressEvent] + ProgressEvent: ProgressEvent | None class GetResourceInput(ServiceRequest): TypeName: TypeName - TypeVersionId: Optional[TypeVersionId] - RoleArn: Optional[RoleArn] + TypeVersionId: TypeVersionId | None + RoleArn: RoleArn | None Identifier: Identifier class ResourceDescription(TypedDict, total=False): - Identifier: Optional[Identifier] - Properties: Optional[Properties] + Identifier: Identifier | None + Properties: Properties | None class GetResourceOutput(TypedDict, total=False): - TypeName: Optional[TypeName] - ResourceDescription: Optional[ResourceDescription] + TypeName: TypeName | None + ResourceDescription: ResourceDescription | None class GetResourceRequestStatusInput(ServiceRequest): @@ -254,81 +254,81 @@ class GetResourceRequestStatusInput(ServiceRequest): class HookProgressEvent(TypedDict, total=False): - HookTypeName: Optional[TypeName] - HookTypeVersionId: Optional[TypeVersionId] - HookTypeArn: Optional[HookTypeArn] - InvocationPoint: Optional[HookInvocationPoint] - HookStatus: Optional[HookStatus] - HookEventTime: Optional[Timestamp] - HookStatusMessage: Optional[StatusMessage] - FailureMode: Optional[HookFailureMode] + HookTypeName: TypeName | None + HookTypeVersionId: TypeVersionId | None + HookTypeArn: HookTypeArn | None + InvocationPoint: HookInvocationPoint | None + HookStatus: HookStatus | None + HookEventTime: Timestamp | None + HookStatusMessage: StatusMessage | None + FailureMode: HookFailureMode | None -HooksProgressEvent = List[HookProgressEvent] +HooksProgressEvent = list[HookProgressEvent] class GetResourceRequestStatusOutput(TypedDict, total=False): - ProgressEvent: Optional[ProgressEvent] - HooksProgressEvent: Optional[HooksProgressEvent] + ProgressEvent: ProgressEvent | None + HooksProgressEvent: HooksProgressEvent | None -OperationStatuses = List[OperationStatus] -Operations = List[Operation] +OperationStatuses = list[OperationStatus] +Operations = list[Operation] class ResourceRequestStatusFilter(TypedDict, total=False): - Operations: Optional[Operations] - OperationStatuses: Optional[OperationStatuses] + Operations: Operations | None + OperationStatuses: OperationStatuses | None class ListResourceRequestsInput(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - ResourceRequestStatusFilter: Optional[ResourceRequestStatusFilter] + MaxResults: MaxResults | None + NextToken: NextToken | None + ResourceRequestStatusFilter: ResourceRequestStatusFilter | None -ResourceRequestStatusSummaries = List[ProgressEvent] +ResourceRequestStatusSummaries = list[ProgressEvent] class ListResourceRequestsOutput(TypedDict, total=False): - ResourceRequestStatusSummaries: Optional[ResourceRequestStatusSummaries] - NextToken: Optional[NextToken] + ResourceRequestStatusSummaries: ResourceRequestStatusSummaries | None + NextToken: NextToken | None class ListResourcesInput(ServiceRequest): TypeName: TypeName - TypeVersionId: Optional[TypeVersionId] - RoleArn: Optional[RoleArn] - NextToken: Optional[HandlerNextToken] - MaxResults: Optional[MaxResults] - ResourceModel: Optional[Properties] + TypeVersionId: TypeVersionId | None + RoleArn: RoleArn | None + NextToken: HandlerNextToken | None + MaxResults: MaxResults | None + ResourceModel: Properties | None -ResourceDescriptions = List[ResourceDescription] +ResourceDescriptions = list[ResourceDescription] class ListResourcesOutput(TypedDict, total=False): - TypeName: Optional[TypeName] - ResourceDescriptions: Optional[ResourceDescriptions] - NextToken: Optional[HandlerNextToken] + TypeName: TypeName | None + ResourceDescriptions: ResourceDescriptions | None + NextToken: HandlerNextToken | None class UpdateResourceInput(ServiceRequest): TypeName: TypeName - TypeVersionId: Optional[TypeVersionId] - RoleArn: Optional[RoleArn] - ClientToken: Optional[ClientToken] + TypeVersionId: TypeVersionId | None + RoleArn: RoleArn | None + ClientToken: ClientToken | None Identifier: Identifier PatchDocument: PatchDocument class UpdateResourceOutput(TypedDict, total=False): - ProgressEvent: Optional[ProgressEvent] + ProgressEvent: ProgressEvent | None class CloudcontrolApi: - service = "cloudcontrol" - version = "2021-09-30" + service: str = "cloudcontrol" + version: str = "2021-09-30" @handler("CancelResourceRequest") def cancel_resource_request( diff --git a/localstack-core/localstack/aws/api/cloudformation/__init__.py b/localstack-core/localstack/aws/api/cloudformation/__init__.py index 7306ed543e1c0..6888630444021 100644 --- a/localstack-core/localstack/aws/api/cloudformation/__init__.py +++ b/localstack-core/localstack/aws/api/cloudformation/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -11,6 +11,8 @@ AfterContext = str AfterValue = str AllowedValue = str +AnnotationName = str +AnnotationRemediationLink = str Arn = str AutoDeploymentNullable = bool AutoUpdate = bool @@ -41,16 +43,20 @@ ExecutionStatusReason = str ExportName = str ExportValue = str +FailedEventsFilter = bool FailedStackInstancesCount = int FailureToleranceCount = int FailureTolerancePercentage = int GeneratedTemplateId = str GeneratedTemplateName = str HookInvocationCount = int +HookInvocationId = str HookResultId = str HookStatusReason = str +HookTargetId = str HookTargetTypeName = str HookType = str +HookTypeArn = str HookTypeConfigurationVersionId = str HookTypeName = str HookTypeVersionId = str @@ -81,6 +87,7 @@ NoEcho = bool NotificationARN = str NumberOfResources = int +OperationId = str OperationResultFilterValues = str OptionalSecureUrl = str OrganizationalUnitId = str @@ -91,6 +98,7 @@ ParameterValue = str PercentageCompleted = float PhysicalResourceId = str +PreviousDeploymentContext = str PrivateTypeArn = str Properties = str PropertyDescription = str @@ -105,8 +113,12 @@ RefreshAllResources = bool Region = str RegistrationToken = str +RemediationMessageRemediationMessage = str +RemediationMessageStatusMessage = str RequestToken = str RequiredProperty = bool +ResourceDriftActualValue = str +ResourceDriftPreviousValue = str ResourceIdentifier = str ResourceIdentifierPropertyKey = str ResourceIdentifierPropertyValue = str @@ -185,6 +197,9 @@ Url = str UsePreviousTemplate = bool UsePreviousValue = bool +ValidationName = str +ValidationPath = str +ValidationStatusReason = str Value = str Version = str @@ -202,10 +217,40 @@ class AccountGateStatus(StrEnum): SKIPPED = "SKIPPED" +class AfterValueFrom(StrEnum): + TEMPLATE = "TEMPLATE" + + +class AnnotationSeverityLevel(StrEnum): + INFORMATIONAL = "INFORMATIONAL" + LOW = "LOW" + MEDIUM = "MEDIUM" + HIGH = "HIGH" + CRITICAL = "CRITICAL" + + +class AnnotationStatus(StrEnum): + PASSED = "PASSED" + FAILED = "FAILED" + SKIPPED = "SKIPPED" + + class AttributeChangeType(StrEnum): Add = "Add" Remove = "Remove" Modify = "Modify" + SyncWithActual = "SyncWithActual" + + +class BeaconStackOperationStatus(StrEnum): + IN_PROGRESS = "IN_PROGRESS" + SUCCEEDED = "SUCCEEDED" + FAILED = "FAILED" + + +class BeforeValueFrom(StrEnum): + PREVIOUS_DEPLOYMENT_STATE = "PREVIOUS_DEPLOYMENT_STATE" + ACTUAL_STATE = "ACTUAL_STATE" class CallAs(StrEnum): @@ -232,6 +277,7 @@ class ChangeAction(StrEnum): Remove = "Remove" Import = "Import" Dynamic = "Dynamic" + SyncWithActual = "SyncWithActual" class ChangeSetHooksStatus(StrEnum): @@ -263,6 +309,7 @@ class ChangeSource(StrEnum): ResourceAttribute = "ResourceAttribute" DirectModification = "DirectModification" Automatic = "Automatic" + NoModification = "NoModification" class ChangeType(StrEnum): @@ -279,6 +326,10 @@ class DeletionMode(StrEnum): FORCE_DELETE_STACK = "FORCE_DELETE_STACK" +class DeploymentMode(StrEnum): + REVERT_DRIFT = "REVERT_DRIFT" + + class DeprecatedStatus(StrEnum): LIVE = "LIVE" DEPRECATED = "DEPRECATED" @@ -295,11 +346,24 @@ class DifferenceType(StrEnum): NOT_EQUAL = "NOT_EQUAL" +class DriftIgnoredReason(StrEnum): + MANAGED_BY_AWS = "MANAGED_BY_AWS" + WRITE_ONLY_PROPERTY = "WRITE_ONLY_PROPERTY" + + class EvaluationType(StrEnum): Static = "Static" Dynamic = "Dynamic" +class EventType(StrEnum): + STACK_EVENT = "STACK_EVENT" + PROGRESS_EVENT = "PROGRESS_EVENT" + VALIDATION_ERROR = "VALIDATION_ERROR" + PROVISIONING_ERROR = "PROVISIONING_ERROR" + HOOK_INVOCATION_ERROR = "HOOK_INVOCATION_ERROR" + + class ExecutionStatus(StrEnum): UNAVAILABLE = "UNAVAILABLE" AVAILABLE = "AVAILABLE" @@ -375,6 +439,13 @@ class HookStatus(StrEnum): HOOK_FAILED = "HOOK_FAILED" +class HookTargetAction(StrEnum): + CREATE = "CREATE" + UPDATE = "UPDATE" + DELETE = "DELETE" + IMPORT = "IMPORT" + + class HookTargetType(StrEnum): RESOURCE = "RESOURCE" @@ -415,6 +486,15 @@ class OperationStatus(StrEnum): FAILED = "FAILED" +class OperationType(StrEnum): + CREATE_STACK = "CREATE_STACK" + UPDATE_STACK = "UPDATE_STACK" + DELETE_STACK = "DELETE_STACK" + CONTINUE_ROLLBACK = "CONTINUE_ROLLBACK" + ROLLBACK = "ROLLBACK" + CREATE_CHANGESET = "CREATE_CHANGESET" + + class OrganizationStatus(StrEnum): ENABLED = "ENABLED" DISABLED = "DISABLED" @@ -611,6 +691,7 @@ class StackResourceDriftStatus(StrEnum): DELETED = "DELETED" NOT_CHECKED = "NOT_CHECKED" UNKNOWN = "UNKNOWN" + UNSUPPORTED = "UNSUPPORTED" class StackSetDriftDetectionStatus(StrEnum): @@ -705,6 +786,11 @@ class TypeTestsStatus(StrEnum): NOT_TESTED = "NOT_TESTED" +class ValidationStatus(StrEnum): + FAILED = "FAILED" + SKIPPED = "SKIPPED" + + class VersionBump(StrEnum): MAJOR = "MAJOR" MINOR = "MINOR" @@ -720,6 +806,7 @@ class WarningType(StrEnum): UNSUPPORTED_PROPERTIES = "UNSUPPORTED_PROPERTIES" MUTUALLY_EXCLUSIVE_TYPES = "MUTUALLY_EXCLUSIVE_TYPES" EXCLUDED_PROPERTIES = "EXCLUDED_PROPERTIES" + EXCLUDED_RESOURCES = "EXCLUDED_RESOURCES" class AlreadyExistsException(ServiceException): @@ -897,17 +984,17 @@ class TypeNotFoundException(ServiceException): class AccountGateResult(TypedDict, total=False): - Status: Optional[AccountGateStatus] - StatusReason: Optional[AccountGateStatusReason] + Status: AccountGateStatus | None + StatusReason: AccountGateStatusReason | None class AccountLimit(TypedDict, total=False): - Name: Optional[LimitName] - Value: Optional[LimitValue] + Name: LimitName | None + Value: LimitValue | None -AccountLimitList = List[AccountLimit] -AccountList = List[Account] +AccountLimitList = list[AccountLimit] +AccountList = list[Account] class ActivateOrganizationsAccessInput(ServiceRequest): @@ -927,46 +1014,60 @@ class LoggingConfig(TypedDict, total=False): class ActivateTypeInput(ServiceRequest): - Type: Optional[ThirdPartyType] - PublicTypeArn: Optional[ThirdPartyTypeArn] - PublisherId: Optional[PublisherId] - TypeName: Optional[TypeName] - TypeNameAlias: Optional[TypeName] - AutoUpdate: Optional[AutoUpdate] - LoggingConfig: Optional[LoggingConfig] - ExecutionRoleArn: Optional[RoleArn] - VersionBump: Optional[VersionBump] - MajorVersion: Optional[MajorVersion] + Type: ThirdPartyType | None + PublicTypeArn: ThirdPartyTypeArn | None + PublisherId: PublisherId | None + TypeName: TypeName | None + TypeNameAlias: TypeName | None + AutoUpdate: AutoUpdate | None + LoggingConfig: LoggingConfig | None + ExecutionRoleArn: RoleArn | None + VersionBump: VersionBump | None + MajorVersion: MajorVersion | None class ActivateTypeOutput(TypedDict, total=False): - Arn: Optional[PrivateTypeArn] + Arn: PrivateTypeArn | None + + +AllowedValues = list[AllowedValue] + + +class Annotation(TypedDict, total=False): + AnnotationName: AnnotationName | None + Status: AnnotationStatus | None + StatusMessage: RemediationMessageStatusMessage | None + RemediationMessage: RemediationMessageRemediationMessage | None + RemediationLink: AnnotationRemediationLink | None + SeverityLevel: AnnotationSeverityLevel | None -AllowedValues = List[AllowedValue] +AnnotationList = list[Annotation] +StackSetARNList = list[StackSetARN] class AutoDeployment(TypedDict, total=False): - Enabled: Optional[AutoDeploymentNullable] - RetainStacksOnAccountRemoval: Optional[RetainStacksOnAccountRemovalNullable] + Enabled: AutoDeploymentNullable | None + RetainStacksOnAccountRemoval: RetainStacksOnAccountRemovalNullable | None + DependsOn: StackSetARNList | None class TypeConfigurationIdentifier(TypedDict, total=False): - TypeArn: Optional[TypeArn] - TypeConfigurationAlias: Optional[TypeConfigurationAlias] - TypeConfigurationArn: Optional[TypeConfigurationArn] - Type: Optional[ThirdPartyType] - TypeName: Optional[TypeName] + TypeArn: TypeArn | None + TypeConfigurationAlias: TypeConfigurationAlias | None + TypeConfigurationArn: TypeConfigurationArn | None + Type: ThirdPartyType | None + TypeName: TypeName | None class BatchDescribeTypeConfigurationsError(TypedDict, total=False): - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] - TypeConfigurationIdentifier: Optional[TypeConfigurationIdentifier] + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None + TypeConfigurationIdentifier: TypeConfigurationIdentifier | None -BatchDescribeTypeConfigurationsErrors = List[BatchDescribeTypeConfigurationsError] -TypeConfigurationIdentifiers = List[TypeConfigurationIdentifier] +BatchDescribeTypeConfigurationsErrors = list[BatchDescribeTypeConfigurationsError] +TypeConfigurationIdentifiers = list[TypeConfigurationIdentifier] class BatchDescribeTypeConfigurationsInput(ServiceRequest): @@ -977,137 +1078,157 @@ class BatchDescribeTypeConfigurationsInput(ServiceRequest): class TypeConfigurationDetails(TypedDict, total=False): - Arn: Optional[TypeConfigurationArn] - Alias: Optional[TypeConfigurationAlias] - Configuration: Optional[TypeConfiguration] - LastUpdated: Optional[Timestamp] - TypeArn: Optional[TypeArn] - TypeName: Optional[TypeName] - IsDefaultConfiguration: Optional[IsDefaultConfiguration] + Arn: TypeConfigurationArn | None + Alias: TypeConfigurationAlias | None + Configuration: TypeConfiguration | None + LastUpdated: Timestamp | None + TypeArn: TypeArn | None + TypeName: TypeName | None + IsDefaultConfiguration: IsDefaultConfiguration | None -TypeConfigurationDetailsList = List[TypeConfigurationDetails] -UnprocessedTypeConfigurations = List[TypeConfigurationIdentifier] +TypeConfigurationDetailsList = list[TypeConfigurationDetails] +UnprocessedTypeConfigurations = list[TypeConfigurationIdentifier] class BatchDescribeTypeConfigurationsOutput(TypedDict, total=False): - Errors: Optional[BatchDescribeTypeConfigurationsErrors] - UnprocessedTypeConfigurations: Optional[UnprocessedTypeConfigurations] - TypeConfigurations: Optional[TypeConfigurationDetailsList] + Errors: BatchDescribeTypeConfigurationsErrors | None + UnprocessedTypeConfigurations: UnprocessedTypeConfigurations | None + TypeConfigurations: TypeConfigurationDetailsList | None class CancelUpdateStackInput(ServiceRequest): StackName: StackName - ClientRequestToken: Optional[ClientRequestToken] + ClientRequestToken: ClientRequestToken | None -Capabilities = List[Capability] +Capabilities = list[Capability] class ModuleInfo(TypedDict, total=False): - TypeHierarchy: Optional[TypeHierarchy] - LogicalIdHierarchy: Optional[LogicalIdHierarchy] + TypeHierarchy: TypeHierarchy | None + LogicalIdHierarchy: LogicalIdHierarchy | None + + +class LiveResourceDrift(TypedDict, total=False): + PreviousValue: ResourceDriftPreviousValue | None + ActualValue: ResourceDriftActualValue | None + DriftDetectionTimestamp: Timestamp | None class ResourceTargetDefinition(TypedDict, total=False): - Attribute: Optional[ResourceAttribute] - Name: Optional[PropertyName] - RequiresRecreation: Optional[RequiresRecreation] - Path: Optional[ResourcePropertyPath] - BeforeValue: Optional[BeforeValue] - AfterValue: Optional[AfterValue] - AttributeChangeType: Optional[AttributeChangeType] + Attribute: ResourceAttribute | None + Name: PropertyName | None + RequiresRecreation: RequiresRecreation | None + Path: ResourcePropertyPath | None + BeforeValue: BeforeValue | None + AfterValue: AfterValue | None + BeforeValueFrom: BeforeValueFrom | None + AfterValueFrom: AfterValueFrom | None + Drift: LiveResourceDrift | None + AttributeChangeType: AttributeChangeType | None class ResourceChangeDetail(TypedDict, total=False): - Target: Optional[ResourceTargetDefinition] - Evaluation: Optional[EvaluationType] - ChangeSource: Optional[ChangeSource] - CausingEntity: Optional[CausingEntity] + Target: ResourceTargetDefinition | None + Evaluation: EvaluationType | None + ChangeSource: ChangeSource | None + CausingEntity: CausingEntity | None + + +ResourceChangeDetails = list[ResourceChangeDetail] -ResourceChangeDetails = List[ResourceChangeDetail] -Scope = List[ResourceAttribute] +class ResourceDriftIgnoredAttribute(TypedDict, total=False): + Path: ResourcePropertyPath | None + Reason: DriftIgnoredReason | None + + +ResourceDriftIgnoredAttributes = list[ResourceDriftIgnoredAttribute] +Scope = list[ResourceAttribute] class ResourceChange(TypedDict, total=False): - PolicyAction: Optional[PolicyAction] - Action: Optional[ChangeAction] - LogicalResourceId: Optional[LogicalResourceId] - PhysicalResourceId: Optional[PhysicalResourceId] - ResourceType: Optional[ResourceType] - Replacement: Optional[Replacement] - Scope: Optional[Scope] - Details: Optional[ResourceChangeDetails] - ChangeSetId: Optional[ChangeSetId] - ModuleInfo: Optional[ModuleInfo] - BeforeContext: Optional[BeforeContext] - AfterContext: Optional[AfterContext] + PolicyAction: PolicyAction | None + Action: ChangeAction | None + LogicalResourceId: LogicalResourceId | None + PhysicalResourceId: PhysicalResourceId | None + ResourceType: ResourceType | None + Replacement: Replacement | None + Scope: Scope | None + ResourceDriftStatus: StackResourceDriftStatus | None + ResourceDriftIgnoredAttributes: ResourceDriftIgnoredAttributes | None + Details: ResourceChangeDetails | None + ChangeSetId: ChangeSetId | None + ModuleInfo: ModuleInfo | None + BeforeContext: BeforeContext | None + AfterContext: AfterContext | None + PreviousDeploymentContext: PreviousDeploymentContext | None class Change(TypedDict, total=False): - Type: Optional[ChangeType] - HookInvocationCount: Optional[HookInvocationCount] - ResourceChange: Optional[ResourceChange] + Type: ChangeType | None + HookInvocationCount: HookInvocationCount | None + ResourceChange: ResourceChange | None class ChangeSetHookResourceTargetDetails(TypedDict, total=False): - LogicalResourceId: Optional[LogicalResourceId] - ResourceType: Optional[HookTargetTypeName] - ResourceAction: Optional[ChangeAction] + LogicalResourceId: LogicalResourceId | None + ResourceType: HookTargetTypeName | None + ResourceAction: ChangeAction | None class ChangeSetHookTargetDetails(TypedDict, total=False): - TargetType: Optional[HookTargetType] - ResourceTargetDetails: Optional[ChangeSetHookResourceTargetDetails] + TargetType: HookTargetType | None + ResourceTargetDetails: ChangeSetHookResourceTargetDetails | None class ChangeSetHook(TypedDict, total=False): - InvocationPoint: Optional[HookInvocationPoint] - FailureMode: Optional[HookFailureMode] - TypeName: Optional[HookTypeName] - TypeVersionId: Optional[HookTypeVersionId] - TypeConfigurationVersionId: Optional[HookTypeConfigurationVersionId] - TargetDetails: Optional[ChangeSetHookTargetDetails] + InvocationPoint: HookInvocationPoint | None + FailureMode: HookFailureMode | None + TypeName: HookTypeName | None + TypeVersionId: HookTypeVersionId | None + TypeConfigurationVersionId: HookTypeConfigurationVersionId | None + TargetDetails: ChangeSetHookTargetDetails | None -ChangeSetHooks = List[ChangeSetHook] +ChangeSetHooks = list[ChangeSetHook] CreationTime = datetime class ChangeSetSummary(TypedDict, total=False): - StackId: Optional[StackId] - StackName: Optional[StackName] - ChangeSetId: Optional[ChangeSetId] - ChangeSetName: Optional[ChangeSetName] - ExecutionStatus: Optional[ExecutionStatus] - Status: Optional[ChangeSetStatus] - StatusReason: Optional[ChangeSetStatusReason] - CreationTime: Optional[CreationTime] - Description: Optional[Description] - IncludeNestedStacks: Optional[IncludeNestedStacks] - ParentChangeSetId: Optional[ChangeSetId] - RootChangeSetId: Optional[ChangeSetId] - ImportExistingResources: Optional[ImportExistingResources] - - -ChangeSetSummaries = List[ChangeSetSummary] -Changes = List[Change] -ResourcesToSkip = List[ResourceToSkip] + StackId: StackId | None + StackName: StackName | None + ChangeSetId: ChangeSetId | None + ChangeSetName: ChangeSetName | None + ExecutionStatus: ExecutionStatus | None + Status: ChangeSetStatus | None + StatusReason: ChangeSetStatusReason | None + CreationTime: CreationTime | None + Description: Description | None + IncludeNestedStacks: IncludeNestedStacks | None + ParentChangeSetId: ChangeSetId | None + RootChangeSetId: ChangeSetId | None + ImportExistingResources: ImportExistingResources | None + + +ChangeSetSummaries = list[ChangeSetSummary] +Changes = list[Change] +ResourcesToSkip = list[ResourceToSkip] class ContinueUpdateRollbackInput(ServiceRequest): StackName: StackNameOrId - RoleARN: Optional[RoleARN] - ResourcesToSkip: Optional[ResourcesToSkip] - ClientRequestToken: Optional[ClientRequestToken] + RoleARN: RoleARN | None + ResourcesToSkip: ResourcesToSkip | None + ClientRequestToken: ClientRequestToken | None class ContinueUpdateRollbackOutput(TypedDict, total=False): pass -ResourceIdentifierProperties = Dict[ResourceIdentifierPropertyKey, ResourceIdentifierPropertyValue] +ResourceIdentifierProperties = dict[ResourceIdentifierPropertyKey, ResourceIdentifierPropertyValue] class ResourceToImport(TypedDict, total=False): @@ -1116,7 +1237,7 @@ class ResourceToImport(TypedDict, total=False): ResourceIdentifier: ResourceIdentifierProperties -ResourcesToImport = List[ResourceToImport] +ResourcesToImport = list[ResourceToImport] class Tag(TypedDict, total=False): @@ -1124,8 +1245,8 @@ class Tag(TypedDict, total=False): Value: TagValue -Tags = List[Tag] -NotificationARNs = List[NotificationARN] +Tags = list[Tag] +NotificationARNs = list[NotificationARN] class RollbackTrigger(TypedDict, total=False): @@ -1133,149 +1254,151 @@ class RollbackTrigger(TypedDict, total=False): Type: Type -RollbackTriggers = List[RollbackTrigger] +RollbackTriggers = list[RollbackTrigger] class RollbackConfiguration(TypedDict, total=False): - RollbackTriggers: Optional[RollbackTriggers] - MonitoringTimeInMinutes: Optional[MonitoringTimeInMinutes] + RollbackTriggers: RollbackTriggers | None + MonitoringTimeInMinutes: MonitoringTimeInMinutes | None -ResourceTypes = List[ResourceType] +ResourceTypes = list[ResourceType] class Parameter(TypedDict, total=False): - ParameterKey: Optional[ParameterKey] - ParameterValue: Optional[ParameterValue] - UsePreviousValue: Optional[UsePreviousValue] - ResolvedValue: Optional[ParameterValue] + ParameterKey: ParameterKey | None + ParameterValue: ParameterValue | None + UsePreviousValue: UsePreviousValue | None + ResolvedValue: ParameterValue | None -Parameters = List[Parameter] +Parameters = list[Parameter] class CreateChangeSetInput(ServiceRequest): StackName: StackNameOrId - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] - UsePreviousTemplate: Optional[UsePreviousTemplate] - Parameters: Optional[Parameters] - Capabilities: Optional[Capabilities] - ResourceTypes: Optional[ResourceTypes] - RoleARN: Optional[RoleARN] - RollbackConfiguration: Optional[RollbackConfiguration] - NotificationARNs: Optional[NotificationARNs] - Tags: Optional[Tags] + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None + UsePreviousTemplate: UsePreviousTemplate | None + Parameters: Parameters | None + Capabilities: Capabilities | None + ResourceTypes: ResourceTypes | None + RoleARN: RoleARN | None + RollbackConfiguration: RollbackConfiguration | None + NotificationARNs: NotificationARNs | None + Tags: Tags | None ChangeSetName: ChangeSetName - ClientToken: Optional[ClientToken] - Description: Optional[Description] - ChangeSetType: Optional[ChangeSetType] - ResourcesToImport: Optional[ResourcesToImport] - IncludeNestedStacks: Optional[IncludeNestedStacks] - OnStackFailure: Optional[OnStackFailure] - ImportExistingResources: Optional[ImportExistingResources] + ClientToken: ClientToken | None + Description: Description | None + ChangeSetType: ChangeSetType | None + ResourcesToImport: ResourcesToImport | None + IncludeNestedStacks: IncludeNestedStacks | None + OnStackFailure: OnStackFailure | None + ImportExistingResources: ImportExistingResources | None + DeploymentMode: DeploymentMode | None class CreateChangeSetOutput(TypedDict, total=False): - Id: Optional[ChangeSetId] - StackId: Optional[StackId] + Id: ChangeSetId | None + StackId: StackId | None class TemplateConfiguration(TypedDict, total=False): - DeletionPolicy: Optional[GeneratedTemplateDeletionPolicy] - UpdateReplacePolicy: Optional[GeneratedTemplateUpdateReplacePolicy] + DeletionPolicy: GeneratedTemplateDeletionPolicy | None + UpdateReplacePolicy: GeneratedTemplateUpdateReplacePolicy | None class ResourceDefinition(TypedDict, total=False): ResourceType: ResourceType - LogicalResourceId: Optional[LogicalResourceId] + LogicalResourceId: LogicalResourceId | None ResourceIdentifier: ResourceIdentifierProperties -ResourceDefinitions = List[ResourceDefinition] +ResourceDefinitions = list[ResourceDefinition] class CreateGeneratedTemplateInput(ServiceRequest): - Resources: Optional[ResourceDefinitions] + Resources: ResourceDefinitions | None GeneratedTemplateName: GeneratedTemplateName - StackName: Optional[StackName] - TemplateConfiguration: Optional[TemplateConfiguration] + StackName: StackName | None + TemplateConfiguration: TemplateConfiguration | None class CreateGeneratedTemplateOutput(TypedDict, total=False): - GeneratedTemplateId: Optional[GeneratedTemplateId] + GeneratedTemplateId: GeneratedTemplateId | None class CreateStackInput(ServiceRequest): StackName: StackName - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] - Parameters: Optional[Parameters] - DisableRollback: Optional[DisableRollback] - RollbackConfiguration: Optional[RollbackConfiguration] - TimeoutInMinutes: Optional[TimeoutMinutes] - NotificationARNs: Optional[NotificationARNs] - Capabilities: Optional[Capabilities] - ResourceTypes: Optional[ResourceTypes] - RoleARN: Optional[RoleARN] - OnFailure: Optional[OnFailure] - StackPolicyBody: Optional[StackPolicyBody] - StackPolicyURL: Optional[StackPolicyURL] - Tags: Optional[Tags] - ClientRequestToken: Optional[ClientRequestToken] - EnableTerminationProtection: Optional[EnableTerminationProtection] - RetainExceptOnCreate: Optional[RetainExceptOnCreate] - - -RegionList = List[Region] + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None + Parameters: Parameters | None + DisableRollback: DisableRollback | None + RollbackConfiguration: RollbackConfiguration | None + TimeoutInMinutes: TimeoutMinutes | None + NotificationARNs: NotificationARNs | None + Capabilities: Capabilities | None + ResourceTypes: ResourceTypes | None + RoleARN: RoleARN | None + OnFailure: OnFailure | None + StackPolicyBody: StackPolicyBody | None + StackPolicyURL: StackPolicyURL | None + Tags: Tags | None + ClientRequestToken: ClientRequestToken | None + EnableTerminationProtection: EnableTerminationProtection | None + RetainExceptOnCreate: RetainExceptOnCreate | None + + +RegionList = list[Region] class StackSetOperationPreferences(TypedDict, total=False): - RegionConcurrencyType: Optional[RegionConcurrencyType] - RegionOrder: Optional[RegionList] - FailureToleranceCount: Optional[FailureToleranceCount] - FailureTolerancePercentage: Optional[FailureTolerancePercentage] - MaxConcurrentCount: Optional[MaxConcurrentCount] - MaxConcurrentPercentage: Optional[MaxConcurrentPercentage] - ConcurrencyMode: Optional[ConcurrencyMode] + RegionConcurrencyType: RegionConcurrencyType | None + RegionOrder: RegionList | None + FailureToleranceCount: FailureToleranceCount | None + FailureTolerancePercentage: FailureTolerancePercentage | None + MaxConcurrentCount: MaxConcurrentCount | None + MaxConcurrentPercentage: MaxConcurrentPercentage | None + ConcurrencyMode: ConcurrencyMode | None -OrganizationalUnitIdList = List[OrganizationalUnitId] +OrganizationalUnitIdList = list[OrganizationalUnitId] class DeploymentTargets(TypedDict, total=False): - Accounts: Optional[AccountList] - AccountsUrl: Optional[AccountsUrl] - OrganizationalUnitIds: Optional[OrganizationalUnitIdList] - AccountFilterType: Optional[AccountFilterType] + Accounts: AccountList | None + AccountsUrl: AccountsUrl | None + OrganizationalUnitIds: OrganizationalUnitIdList | None + AccountFilterType: AccountFilterType | None class CreateStackInstancesInput(ServiceRequest): StackSetName: StackSetName - Accounts: Optional[AccountList] - DeploymentTargets: Optional[DeploymentTargets] + Accounts: AccountList | None + DeploymentTargets: DeploymentTargets | None Regions: RegionList - ParameterOverrides: Optional[Parameters] - OperationPreferences: Optional[StackSetOperationPreferences] - OperationId: Optional[ClientRequestToken] - CallAs: Optional[CallAs] + ParameterOverrides: Parameters | None + OperationPreferences: StackSetOperationPreferences | None + OperationId: ClientRequestToken | None + CallAs: CallAs | None class CreateStackInstancesOutput(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] + OperationId: ClientRequestToken | None class CreateStackOutput(TypedDict, total=False): - StackId: Optional[StackId] + StackId: StackId | None + OperationId: OperationId | None class StackDefinition(TypedDict, total=False): - StackName: Optional[StackName] - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] + StackName: StackName | None + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None -StackDefinitions = List[StackDefinition] +StackDefinitions = list[StackDefinition] class ResourceLocation(TypedDict, total=False): @@ -1288,13 +1411,13 @@ class ResourceMapping(TypedDict, total=False): Destination: ResourceLocation -ResourceMappings = List[ResourceMapping] +ResourceMappings = list[ResourceMapping] class CreateStackRefactorInput(ServiceRequest): - Description: Optional[Description] - EnableStackCreation: Optional[EnableStackCreation] - ResourceMappings: Optional[ResourceMappings] + Description: Description | None + EnableStackCreation: EnableStackCreation | None + ResourceMappings: ResourceMappings | None StackDefinitions: StackDefinitions @@ -1303,29 +1426,29 @@ class CreateStackRefactorOutput(TypedDict, total=False): class ManagedExecution(TypedDict, total=False): - Active: Optional[ManagedExecutionNullable] + Active: ManagedExecutionNullable | None class CreateStackSetInput(ServiceRequest): StackSetName: StackSetName - Description: Optional[Description] - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] - StackId: Optional[StackId] - Parameters: Optional[Parameters] - Capabilities: Optional[Capabilities] - Tags: Optional[Tags] - AdministrationRoleARN: Optional[RoleARN] - ExecutionRoleName: Optional[ExecutionRoleName] - PermissionModel: Optional[PermissionModels] - AutoDeployment: Optional[AutoDeployment] - CallAs: Optional[CallAs] - ClientRequestToken: Optional[ClientRequestToken] - ManagedExecution: Optional[ManagedExecution] + Description: Description | None + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None + StackId: StackId | None + Parameters: Parameters | None + Capabilities: Capabilities | None + Tags: Tags | None + AdministrationRoleARN: RoleARN | None + ExecutionRoleName: ExecutionRoleName | None + PermissionModel: PermissionModels | None + AutoDeployment: AutoDeployment | None + CallAs: CallAs | None + ClientRequestToken: ClientRequestToken | None + ManagedExecution: ManagedExecution | None class CreateStackSetOutput(TypedDict, total=False): - StackSetId: Optional[StackSetId] + StackSetId: StackSetId | None class DeactivateOrganizationsAccessInput(ServiceRequest): @@ -1337,9 +1460,9 @@ class DeactivateOrganizationsAccessOutput(TypedDict, total=False): class DeactivateTypeInput(ServiceRequest): - TypeName: Optional[TypeName] - Type: Optional[ThirdPartyType] - Arn: Optional[PrivateTypeArn] + TypeName: TypeName | None + Type: ThirdPartyType | None + Arn: PrivateTypeArn | None class DeactivateTypeOutput(TypedDict, total=False): @@ -1348,7 +1471,7 @@ class DeactivateTypeOutput(TypedDict, total=False): class DeleteChangeSetInput(ServiceRequest): ChangeSetName: ChangeSetNameOrId - StackName: Optional[StackNameOrId] + StackName: StackNameOrId | None class DeleteChangeSetOutput(TypedDict, total=False): @@ -1359,35 +1482,35 @@ class DeleteGeneratedTemplateInput(ServiceRequest): GeneratedTemplateName: GeneratedTemplateName -RetainResources = List[LogicalResourceId] +RetainResources = list[LogicalResourceId] class DeleteStackInput(ServiceRequest): StackName: StackName - RetainResources: Optional[RetainResources] - RoleARN: Optional[RoleARN] - ClientRequestToken: Optional[ClientRequestToken] - DeletionMode: Optional[DeletionMode] + RetainResources: RetainResources | None + RoleARN: RoleARN | None + ClientRequestToken: ClientRequestToken | None + DeletionMode: DeletionMode | None class DeleteStackInstancesInput(ServiceRequest): StackSetName: StackSetName - Accounts: Optional[AccountList] - DeploymentTargets: Optional[DeploymentTargets] + Accounts: AccountList | None + DeploymentTargets: DeploymentTargets | None Regions: RegionList - OperationPreferences: Optional[StackSetOperationPreferences] + OperationPreferences: StackSetOperationPreferences | None RetainStacks: RetainStacks - OperationId: Optional[ClientRequestToken] - CallAs: Optional[CallAs] + OperationId: ClientRequestToken | None + CallAs: CallAs | None class DeleteStackInstancesOutput(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] + OperationId: ClientRequestToken | None class DeleteStackSetInput(ServiceRequest): StackSetName: StackSetName - CallAs: Optional[CallAs] + CallAs: CallAs | None class DeleteStackSetOutput(TypedDict, total=False): @@ -1398,10 +1521,10 @@ class DeleteStackSetOutput(TypedDict, total=False): class DeregisterTypeInput(ServiceRequest): - Arn: Optional[PrivateTypeArn] - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - VersionId: Optional[TypeVersionId] + Arn: PrivateTypeArn | None + Type: RegistryType | None + TypeName: TypeName | None + VersionId: TypeVersionId | None class DeregisterTypeOutput(TypedDict, total=False): @@ -1409,60 +1532,112 @@ class DeregisterTypeOutput(TypedDict, total=False): class DescribeAccountLimitsInput(ServiceRequest): - NextToken: Optional[NextToken] + NextToken: NextToken | None class DescribeAccountLimitsOutput(TypedDict, total=False): - AccountLimits: Optional[AccountLimitList] - NextToken: Optional[NextToken] + AccountLimits: AccountLimitList | None + NextToken: NextToken | None class DescribeChangeSetHooksInput(ServiceRequest): ChangeSetName: ChangeSetNameOrId - StackName: Optional[StackNameOrId] - NextToken: Optional[NextToken] - LogicalResourceId: Optional[LogicalResourceId] + StackName: StackNameOrId | None + NextToken: NextToken | None + LogicalResourceId: LogicalResourceId | None class DescribeChangeSetHooksOutput(TypedDict, total=False): - ChangeSetId: Optional[ChangeSetId] - ChangeSetName: Optional[ChangeSetName] - Hooks: Optional[ChangeSetHooks] - Status: Optional[ChangeSetHooksStatus] - NextToken: Optional[NextToken] - StackId: Optional[StackId] - StackName: Optional[StackName] + ChangeSetId: ChangeSetId | None + ChangeSetName: ChangeSetName | None + Hooks: ChangeSetHooks | None + Status: ChangeSetHooksStatus | None + NextToken: NextToken | None + StackId: StackId | None + StackName: StackName | None class DescribeChangeSetInput(ServiceRequest): ChangeSetName: ChangeSetNameOrId - StackName: Optional[StackNameOrId] - NextToken: Optional[NextToken] - IncludePropertyValues: Optional[IncludePropertyValues] + StackName: StackNameOrId | None + NextToken: NextToken | None + IncludePropertyValues: IncludePropertyValues | None class DescribeChangeSetOutput(TypedDict, total=False): - ChangeSetName: Optional[ChangeSetName] - ChangeSetId: Optional[ChangeSetId] - StackId: Optional[StackId] - StackName: Optional[StackName] - Description: Optional[Description] - Parameters: Optional[Parameters] - CreationTime: Optional[CreationTime] - ExecutionStatus: Optional[ExecutionStatus] - Status: Optional[ChangeSetStatus] - StatusReason: Optional[ChangeSetStatusReason] - NotificationARNs: Optional[NotificationARNs] - RollbackConfiguration: Optional[RollbackConfiguration] - Capabilities: Optional[Capabilities] - Tags: Optional[Tags] - Changes: Optional[Changes] - NextToken: Optional[NextToken] - IncludeNestedStacks: Optional[IncludeNestedStacks] - ParentChangeSetId: Optional[ChangeSetId] - RootChangeSetId: Optional[ChangeSetId] - OnStackFailure: Optional[OnStackFailure] - ImportExistingResources: Optional[ImportExistingResources] + ChangeSetName: ChangeSetName | None + ChangeSetId: ChangeSetId | None + StackId: StackId | None + StackName: StackName | None + Description: Description | None + Parameters: Parameters | None + CreationTime: CreationTime | None + ExecutionStatus: ExecutionStatus | None + Status: ChangeSetStatus | None + StatusReason: ChangeSetStatusReason | None + StackDriftStatus: StackDriftStatus | None + NotificationARNs: NotificationARNs | None + RollbackConfiguration: RollbackConfiguration | None + Capabilities: Capabilities | None + Tags: Tags | None + Changes: Changes | None + NextToken: NextToken | None + IncludeNestedStacks: IncludeNestedStacks | None + ParentChangeSetId: ChangeSetId | None + RootChangeSetId: ChangeSetId | None + OnStackFailure: OnStackFailure | None + ImportExistingResources: ImportExistingResources | None + DeploymentMode: DeploymentMode | None + + +class EventFilter(TypedDict, total=False): + FailedEvents: FailedEventsFilter | None + + +class DescribeEventsInput(ServiceRequest): + StackName: StackNameOrId | None + ChangeSetName: ChangeSetNameOrId | None + OperationId: OperationId | None + Filters: EventFilter | None + NextToken: NextToken | None + + +class OperationEvent(TypedDict, total=False): + EventId: EventId | None + StackId: StackId | None + OperationId: OperationId | None + OperationType: OperationType | None + OperationStatus: BeaconStackOperationStatus | None + EventType: EventType | None + LogicalResourceId: LogicalResourceId | None + PhysicalResourceId: PhysicalResourceId | None + ResourceType: ResourceType | None + Timestamp: Timestamp | None + StartTime: Timestamp | None + EndTime: Timestamp | None + ResourceStatus: ResourceStatus | None + ResourceStatusReason: ResourceStatusReason | None + ResourceProperties: ResourceProperties | None + ClientRequestToken: ClientRequestToken | None + HookType: HookType | None + HookStatus: HookStatus | None + HookStatusReason: HookStatusReason | None + HookInvocationPoint: HookInvocationPoint | None + HookFailureMode: HookFailureMode | None + DetailedStatus: DetailedStatus | None + ValidationFailureMode: HookFailureMode | None + ValidationName: ValidationName | None + ValidationStatus: ValidationStatus | None + ValidationStatusReason: ValidationStatusReason | None + ValidationPath: ValidationPath | None + + +OperationEvents = list[OperationEvent] + + +class DescribeEventsOutput(TypedDict, total=False): + OperationEvents: OperationEvents | None + NextToken: NextToken | None class DescribeGeneratedTemplateInput(ServiceRequest): @@ -1470,102 +1645,102 @@ class DescribeGeneratedTemplateInput(ServiceRequest): class TemplateProgress(TypedDict, total=False): - ResourcesSucceeded: Optional[ResourcesSucceeded] - ResourcesFailed: Optional[ResourcesFailed] - ResourcesProcessing: Optional[ResourcesProcessing] - ResourcesPending: Optional[ResourcesPending] + ResourcesSucceeded: ResourcesSucceeded | None + ResourcesFailed: ResourcesFailed | None + ResourcesProcessing: ResourcesProcessing | None + ResourcesPending: ResourcesPending | None LastUpdatedTime = datetime class WarningProperty(TypedDict, total=False): - PropertyPath: Optional[PropertyPath] - Required: Optional[RequiredProperty] - Description: Optional[PropertyDescription] + PropertyPath: PropertyPath | None + Required: RequiredProperty | None + Description: PropertyDescription | None -WarningProperties = List[WarningProperty] +WarningProperties = list[WarningProperty] class WarningDetail(TypedDict, total=False): - Type: Optional[WarningType] - Properties: Optional[WarningProperties] + Type: WarningType | None + Properties: WarningProperties | None -WarningDetails = List[WarningDetail] +WarningDetails = list[WarningDetail] class ResourceDetail(TypedDict, total=False): - ResourceType: Optional[ResourceType] - LogicalResourceId: Optional[LogicalResourceId] - ResourceIdentifier: Optional[ResourceIdentifierProperties] - ResourceStatus: Optional[GeneratedTemplateResourceStatus] - ResourceStatusReason: Optional[ResourceStatusReason] - Warnings: Optional[WarningDetails] + ResourceType: ResourceType | None + LogicalResourceId: LogicalResourceId | None + ResourceIdentifier: ResourceIdentifierProperties | None + ResourceStatus: GeneratedTemplateResourceStatus | None + ResourceStatusReason: ResourceStatusReason | None + Warnings: WarningDetails | None -ResourceDetails = List[ResourceDetail] +ResourceDetails = list[ResourceDetail] class DescribeGeneratedTemplateOutput(TypedDict, total=False): - GeneratedTemplateId: Optional[GeneratedTemplateId] - GeneratedTemplateName: Optional[GeneratedTemplateName] - Resources: Optional[ResourceDetails] - Status: Optional[GeneratedTemplateStatus] - StatusReason: Optional[TemplateStatusReason] - CreationTime: Optional[CreationTime] - LastUpdatedTime: Optional[LastUpdatedTime] - Progress: Optional[TemplateProgress] - StackId: Optional[StackId] - TemplateConfiguration: Optional[TemplateConfiguration] - TotalWarnings: Optional[TotalWarnings] + GeneratedTemplateId: GeneratedTemplateId | None + GeneratedTemplateName: GeneratedTemplateName | None + Resources: ResourceDetails | None + Status: GeneratedTemplateStatus | None + StatusReason: TemplateStatusReason | None + CreationTime: CreationTime | None + LastUpdatedTime: LastUpdatedTime | None + Progress: TemplateProgress | None + StackId: StackId | None + TemplateConfiguration: TemplateConfiguration | None + TotalWarnings: TotalWarnings | None class DescribeOrganizationsAccessInput(ServiceRequest): - CallAs: Optional[CallAs] + CallAs: CallAs | None class DescribeOrganizationsAccessOutput(TypedDict, total=False): - Status: Optional[OrganizationStatus] + Status: OrganizationStatus | None class DescribePublisherInput(ServiceRequest): - PublisherId: Optional[PublisherId] + PublisherId: PublisherId | None class DescribePublisherOutput(TypedDict, total=False): - PublisherId: Optional[PublisherId] - PublisherStatus: Optional[PublisherStatus] - IdentityProvider: Optional[IdentityProvider] - PublisherProfile: Optional[PublisherProfile] + PublisherId: PublisherId | None + PublisherStatus: PublisherStatus | None + IdentityProvider: IdentityProvider | None + PublisherProfile: PublisherProfile | None class DescribeResourceScanInput(ServiceRequest): ResourceScanId: ResourceScanId -ResourceTypeFilters = List[ResourceTypeFilter] +ResourceTypeFilters = list[ResourceTypeFilter] class ScanFilter(TypedDict, total=False): - Types: Optional[ResourceTypeFilters] + Types: ResourceTypeFilters | None -ScanFilters = List[ScanFilter] +ScanFilters = list[ScanFilter] class DescribeResourceScanOutput(TypedDict, total=False): - ResourceScanId: Optional[ResourceScanId] - Status: Optional[ResourceScanStatus] - StatusReason: Optional[ResourceScanStatusReason] - StartTime: Optional[Timestamp] - EndTime: Optional[Timestamp] - PercentageCompleted: Optional[PercentageCompleted] - ResourceTypes: Optional[ResourceTypes] - ResourcesScanned: Optional[ResourcesScanned] - ResourcesRead: Optional[ResourcesRead] - ScanFilters: Optional[ScanFilters] + ResourceScanId: ResourceScanId | None + Status: ResourceScanStatus | None + StatusReason: ResourceScanStatusReason | None + StartTime: Timestamp | None + EndTime: Timestamp | None + PercentageCompleted: PercentageCompleted | None + ResourceTypes: ResourceTypes | None + ResourcesScanned: ResourcesScanned | None + ResourcesRead: ResourcesRead | None + ScanFilters: ScanFilters | None class DescribeStackDriftDetectionStatusInput(ServiceRequest): @@ -1575,101 +1750,103 @@ class DescribeStackDriftDetectionStatusInput(ServiceRequest): class DescribeStackDriftDetectionStatusOutput(TypedDict, total=False): StackId: StackId StackDriftDetectionId: StackDriftDetectionId - StackDriftStatus: Optional[StackDriftStatus] + StackDriftStatus: StackDriftStatus | None DetectionStatus: StackDriftDetectionStatus - DetectionStatusReason: Optional[StackDriftDetectionStatusReason] - DriftedStackResourceCount: Optional[BoxedInteger] + DetectionStatusReason: StackDriftDetectionStatusReason | None + DriftedStackResourceCount: BoxedInteger | None Timestamp: Timestamp class DescribeStackEventsInput(ServiceRequest): - StackName: Optional[StackName] - NextToken: Optional[NextToken] + StackName: StackName + NextToken: NextToken | None class StackEvent(TypedDict, total=False): StackId: StackId EventId: EventId StackName: StackName - LogicalResourceId: Optional[LogicalResourceId] - PhysicalResourceId: Optional[PhysicalResourceId] - ResourceType: Optional[ResourceType] + OperationId: OperationId | None + LogicalResourceId: LogicalResourceId | None + PhysicalResourceId: PhysicalResourceId | None + ResourceType: ResourceType | None Timestamp: Timestamp - ResourceStatus: Optional[ResourceStatus] - ResourceStatusReason: Optional[ResourceStatusReason] - ResourceProperties: Optional[ResourceProperties] - ClientRequestToken: Optional[ClientRequestToken] - HookType: Optional[HookType] - HookStatus: Optional[HookStatus] - HookStatusReason: Optional[HookStatusReason] - HookInvocationPoint: Optional[HookInvocationPoint] - HookFailureMode: Optional[HookFailureMode] - DetailedStatus: Optional[DetailedStatus] + ResourceStatus: ResourceStatus | None + ResourceStatusReason: ResourceStatusReason | None + ResourceProperties: ResourceProperties | None + ClientRequestToken: ClientRequestToken | None + HookType: HookType | None + HookStatus: HookStatus | None + HookStatusReason: HookStatusReason | None + HookInvocationPoint: HookInvocationPoint | None + HookInvocationId: HookInvocationId | None + HookFailureMode: HookFailureMode | None + DetailedStatus: DetailedStatus | None -StackEvents = List[StackEvent] +StackEvents = list[StackEvent] class DescribeStackEventsOutput(TypedDict, total=False): - StackEvents: Optional[StackEvents] - NextToken: Optional[NextToken] + StackEvents: StackEvents | None + NextToken: NextToken | None class DescribeStackInstanceInput(ServiceRequest): StackSetName: StackSetName StackInstanceAccount: Account StackInstanceRegion: Region - CallAs: Optional[CallAs] + CallAs: CallAs | None class StackInstanceComprehensiveStatus(TypedDict, total=False): - DetailedStatus: Optional[StackInstanceDetailedStatus] + DetailedStatus: StackInstanceDetailedStatus | None class StackInstance(TypedDict, total=False): - StackSetId: Optional[StackSetId] - Region: Optional[Region] - Account: Optional[Account] - StackId: Optional[StackId] - ParameterOverrides: Optional[Parameters] - Status: Optional[StackInstanceStatus] - StackInstanceStatus: Optional[StackInstanceComprehensiveStatus] - StatusReason: Optional[Reason] - OrganizationalUnitId: Optional[OrganizationalUnitId] - DriftStatus: Optional[StackDriftStatus] - LastDriftCheckTimestamp: Optional[Timestamp] - LastOperationId: Optional[ClientRequestToken] + StackSetId: StackSetId | None + Region: Region | None + Account: Account | None + StackId: StackId | None + ParameterOverrides: Parameters | None + Status: StackInstanceStatus | None + StackInstanceStatus: StackInstanceComprehensiveStatus | None + StatusReason: Reason | None + OrganizationalUnitId: OrganizationalUnitId | None + DriftStatus: StackDriftStatus | None + LastDriftCheckTimestamp: Timestamp | None + LastOperationId: ClientRequestToken | None class DescribeStackInstanceOutput(TypedDict, total=False): - StackInstance: Optional[StackInstance] + StackInstance: StackInstance | None class DescribeStackRefactorInput(ServiceRequest): StackRefactorId: StackRefactorId -StackIds = List[StackId] +StackIds = list[StackId] class DescribeStackRefactorOutput(TypedDict, total=False): - Description: Optional[Description] - StackRefactorId: Optional[StackRefactorId] - StackIds: Optional[StackIds] - ExecutionStatus: Optional[StackRefactorExecutionStatus] - ExecutionStatusReason: Optional[ExecutionStatusReason] - Status: Optional[StackRefactorStatus] - StatusReason: Optional[StackRefactorStatusReason] + Description: Description | None + StackRefactorId: StackRefactorId | None + StackIds: StackIds | None + ExecutionStatus: StackRefactorExecutionStatus | None + ExecutionStatusReason: ExecutionStatusReason | None + Status: StackRefactorStatus | None + StatusReason: StackRefactorStatusReason | None -StackResourceDriftStatusFilters = List[StackResourceDriftStatus] +StackResourceDriftStatusFilters = list[StackResourceDriftStatus] class DescribeStackResourceDriftsInput(ServiceRequest): StackName: StackNameOrId - StackResourceDriftStatusFilters: Optional[StackResourceDriftStatusFilters] - NextToken: Optional[NextToken] - MaxResults: Optional[BoxedMaxResults] + StackResourceDriftStatusFilters: StackResourceDriftStatusFilters | None + NextToken: NextToken | None + MaxResults: BoxedMaxResults | None class PropertyDifference(TypedDict, total=False): @@ -1679,7 +1856,7 @@ class PropertyDifference(TypedDict, total=False): DifferenceType: DifferenceType -PropertyDifferences = List[PropertyDifference] +PropertyDifferences = list[PropertyDifference] class PhysicalResourceIdContextKeyValuePair(TypedDict, total=False): @@ -1687,30 +1864,30 @@ class PhysicalResourceIdContextKeyValuePair(TypedDict, total=False): Value: Value -PhysicalResourceIdContext = List[PhysicalResourceIdContextKeyValuePair] +PhysicalResourceIdContext = list[PhysicalResourceIdContextKeyValuePair] class StackResourceDrift(TypedDict, total=False): StackId: StackId LogicalResourceId: LogicalResourceId - PhysicalResourceId: Optional[PhysicalResourceId] - PhysicalResourceIdContext: Optional[PhysicalResourceIdContext] + PhysicalResourceId: PhysicalResourceId | None + PhysicalResourceIdContext: PhysicalResourceIdContext | None ResourceType: ResourceType - ExpectedProperties: Optional[Properties] - ActualProperties: Optional[Properties] - PropertyDifferences: Optional[PropertyDifferences] + ExpectedProperties: Properties | None + ActualProperties: Properties | None + PropertyDifferences: PropertyDifferences | None StackResourceDriftStatus: StackResourceDriftStatus Timestamp: Timestamp - ModuleInfo: Optional[ModuleInfo] - DriftStatusReason: Optional[StackResourceDriftStatusReason] + ModuleInfo: ModuleInfo | None + DriftStatusReason: StackResourceDriftStatusReason | None -StackResourceDrifts = List[StackResourceDrift] +StackResourceDrifts = list[StackResourceDrift] class DescribeStackResourceDriftsOutput(TypedDict, total=False): StackResourceDrifts: StackResourceDrifts - NextToken: Optional[NextToken] + NextToken: NextToken | None class DescribeStackResourceInput(ServiceRequest): @@ -1720,232 +1897,241 @@ class DescribeStackResourceInput(ServiceRequest): class StackResourceDriftInformation(TypedDict, total=False): StackResourceDriftStatus: StackResourceDriftStatus - LastCheckTimestamp: Optional[Timestamp] + LastCheckTimestamp: Timestamp | None class StackResourceDetail(TypedDict, total=False): - StackName: Optional[StackName] - StackId: Optional[StackId] + StackName: StackName | None + StackId: StackId | None LogicalResourceId: LogicalResourceId - PhysicalResourceId: Optional[PhysicalResourceId] + PhysicalResourceId: PhysicalResourceId | None ResourceType: ResourceType LastUpdatedTimestamp: Timestamp ResourceStatus: ResourceStatus - ResourceStatusReason: Optional[ResourceStatusReason] - Description: Optional[Description] - Metadata: Optional[Metadata] - DriftInformation: Optional[StackResourceDriftInformation] - ModuleInfo: Optional[ModuleInfo] + ResourceStatusReason: ResourceStatusReason | None + Description: Description | None + Metadata: Metadata | None + DriftInformation: StackResourceDriftInformation | None + ModuleInfo: ModuleInfo | None class DescribeStackResourceOutput(TypedDict, total=False): - StackResourceDetail: Optional[StackResourceDetail] + StackResourceDetail: StackResourceDetail | None class DescribeStackResourcesInput(ServiceRequest): - StackName: Optional[StackName] - LogicalResourceId: Optional[LogicalResourceId] - PhysicalResourceId: Optional[PhysicalResourceId] + StackName: StackName | None + LogicalResourceId: LogicalResourceId | None + PhysicalResourceId: PhysicalResourceId | None class StackResource(TypedDict, total=False): - StackName: Optional[StackName] - StackId: Optional[StackId] + StackName: StackName | None + StackId: StackId | None LogicalResourceId: LogicalResourceId - PhysicalResourceId: Optional[PhysicalResourceId] + PhysicalResourceId: PhysicalResourceId | None ResourceType: ResourceType Timestamp: Timestamp ResourceStatus: ResourceStatus - ResourceStatusReason: Optional[ResourceStatusReason] - Description: Optional[Description] - DriftInformation: Optional[StackResourceDriftInformation] - ModuleInfo: Optional[ModuleInfo] + ResourceStatusReason: ResourceStatusReason | None + Description: Description | None + DriftInformation: StackResourceDriftInformation | None + ModuleInfo: ModuleInfo | None -StackResources = List[StackResource] +StackResources = list[StackResource] class DescribeStackResourcesOutput(TypedDict, total=False): - StackResources: Optional[StackResources] + StackResources: StackResources | None class DescribeStackSetInput(ServiceRequest): StackSetName: StackSetName - CallAs: Optional[CallAs] + CallAs: CallAs | None class DescribeStackSetOperationInput(ServiceRequest): StackSetName: StackSetName OperationId: ClientRequestToken - CallAs: Optional[CallAs] + CallAs: CallAs | None class StackSetOperationStatusDetails(TypedDict, total=False): - FailedStackInstancesCount: Optional[FailedStackInstancesCount] + FailedStackInstancesCount: FailedStackInstancesCount | None class StackSetDriftDetectionDetails(TypedDict, total=False): - DriftStatus: Optional[StackSetDriftStatus] - DriftDetectionStatus: Optional[StackSetDriftDetectionStatus] - LastDriftCheckTimestamp: Optional[Timestamp] - TotalStackInstancesCount: Optional[TotalStackInstancesCount] - DriftedStackInstancesCount: Optional[DriftedStackInstancesCount] - InSyncStackInstancesCount: Optional[InSyncStackInstancesCount] - InProgressStackInstancesCount: Optional[InProgressStackInstancesCount] - FailedStackInstancesCount: Optional[FailedStackInstancesCount] + DriftStatus: StackSetDriftStatus | None + DriftDetectionStatus: StackSetDriftDetectionStatus | None + LastDriftCheckTimestamp: Timestamp | None + TotalStackInstancesCount: TotalStackInstancesCount | None + DriftedStackInstancesCount: DriftedStackInstancesCount | None + InSyncStackInstancesCount: InSyncStackInstancesCount | None + InProgressStackInstancesCount: InProgressStackInstancesCount | None + FailedStackInstancesCount: FailedStackInstancesCount | None class StackSetOperation(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] - StackSetId: Optional[StackSetId] - Action: Optional[StackSetOperationAction] - Status: Optional[StackSetOperationStatus] - OperationPreferences: Optional[StackSetOperationPreferences] - RetainStacks: Optional[RetainStacksNullable] - AdministrationRoleARN: Optional[RoleARN] - ExecutionRoleName: Optional[ExecutionRoleName] - CreationTimestamp: Optional[Timestamp] - EndTimestamp: Optional[Timestamp] - DeploymentTargets: Optional[DeploymentTargets] - StackSetDriftDetectionDetails: Optional[StackSetDriftDetectionDetails] - StatusReason: Optional[StackSetOperationStatusReason] - StatusDetails: Optional[StackSetOperationStatusDetails] + OperationId: ClientRequestToken | None + StackSetId: StackSetId | None + Action: StackSetOperationAction | None + Status: StackSetOperationStatus | None + OperationPreferences: StackSetOperationPreferences | None + RetainStacks: RetainStacksNullable | None + AdministrationRoleARN: RoleARN | None + ExecutionRoleName: ExecutionRoleName | None + CreationTimestamp: Timestamp | None + EndTimestamp: Timestamp | None + DeploymentTargets: DeploymentTargets | None + StackSetDriftDetectionDetails: StackSetDriftDetectionDetails | None + StatusReason: StackSetOperationStatusReason | None + StatusDetails: StackSetOperationStatusDetails | None class DescribeStackSetOperationOutput(TypedDict, total=False): - StackSetOperation: Optional[StackSetOperation] + StackSetOperation: StackSetOperation | None class StackSet(TypedDict, total=False): - StackSetName: Optional[StackSetName] - StackSetId: Optional[StackSetId] - Description: Optional[Description] - Status: Optional[StackSetStatus] - TemplateBody: Optional[TemplateBody] - Parameters: Optional[Parameters] - Capabilities: Optional[Capabilities] - Tags: Optional[Tags] - StackSetARN: Optional[StackSetARN] - AdministrationRoleARN: Optional[RoleARN] - ExecutionRoleName: Optional[ExecutionRoleName] - StackSetDriftDetectionDetails: Optional[StackSetDriftDetectionDetails] - AutoDeployment: Optional[AutoDeployment] - PermissionModel: Optional[PermissionModels] - OrganizationalUnitIds: Optional[OrganizationalUnitIdList] - ManagedExecution: Optional[ManagedExecution] - Regions: Optional[RegionList] + StackSetName: StackSetName | None + StackSetId: StackSetId | None + Description: Description | None + Status: StackSetStatus | None + TemplateBody: TemplateBody | None + Parameters: Parameters | None + Capabilities: Capabilities | None + Tags: Tags | None + StackSetARN: StackSetARN | None + AdministrationRoleARN: RoleARN | None + ExecutionRoleName: ExecutionRoleName | None + StackSetDriftDetectionDetails: StackSetDriftDetectionDetails | None + AutoDeployment: AutoDeployment | None + PermissionModel: PermissionModels | None + OrganizationalUnitIds: OrganizationalUnitIdList | None + ManagedExecution: ManagedExecution | None + Regions: RegionList | None class DescribeStackSetOutput(TypedDict, total=False): - StackSet: Optional[StackSet] + StackSet: StackSet | None class DescribeStacksInput(ServiceRequest): - StackName: Optional[StackName] - NextToken: Optional[NextToken] + StackName: StackName | None + NextToken: NextToken | None + + +class OperationEntry(TypedDict, total=False): + OperationType: OperationType | None + OperationId: OperationId | None + + +LastOperations = list[OperationEntry] class StackDriftInformation(TypedDict, total=False): StackDriftStatus: StackDriftStatus - LastCheckTimestamp: Optional[Timestamp] + LastCheckTimestamp: Timestamp | None class Output(TypedDict, total=False): - OutputKey: Optional[OutputKey] - OutputValue: Optional[OutputValue] - Description: Optional[Description] - ExportName: Optional[ExportName] + OutputKey: OutputKey | None + OutputValue: OutputValue | None + Description: Description | None + ExportName: ExportName | None -Outputs = List[Output] +Outputs = list[Output] class Stack(TypedDict, total=False): - StackId: Optional[StackId] + StackId: StackId | None StackName: StackName - ChangeSetId: Optional[ChangeSetId] - Description: Optional[Description] - Parameters: Optional[Parameters] + ChangeSetId: ChangeSetId | None + Description: Description | None + Parameters: Parameters | None CreationTime: CreationTime - DeletionTime: Optional[DeletionTime] - LastUpdatedTime: Optional[LastUpdatedTime] - RollbackConfiguration: Optional[RollbackConfiguration] + DeletionTime: DeletionTime | None + LastUpdatedTime: LastUpdatedTime | None + RollbackConfiguration: RollbackConfiguration | None StackStatus: StackStatus - StackStatusReason: Optional[StackStatusReason] - DisableRollback: Optional[DisableRollback] - NotificationARNs: Optional[NotificationARNs] - TimeoutInMinutes: Optional[TimeoutMinutes] - Capabilities: Optional[Capabilities] - Outputs: Optional[Outputs] - RoleARN: Optional[RoleARN] - Tags: Optional[Tags] - EnableTerminationProtection: Optional[EnableTerminationProtection] - ParentId: Optional[StackId] - RootId: Optional[StackId] - DriftInformation: Optional[StackDriftInformation] - RetainExceptOnCreate: Optional[RetainExceptOnCreate] - DeletionMode: Optional[DeletionMode] - DetailedStatus: Optional[DetailedStatus] - - -Stacks = List[Stack] + StackStatusReason: StackStatusReason | None + DisableRollback: DisableRollback | None + NotificationARNs: NotificationARNs | None + TimeoutInMinutes: TimeoutMinutes | None + Capabilities: Capabilities | None + Outputs: Outputs | None + RoleARN: RoleARN | None + Tags: Tags | None + EnableTerminationProtection: EnableTerminationProtection | None + ParentId: StackId | None + RootId: StackId | None + DriftInformation: StackDriftInformation | None + RetainExceptOnCreate: RetainExceptOnCreate | None + DeletionMode: DeletionMode | None + DetailedStatus: DetailedStatus | None + LastOperations: LastOperations | None + + +Stacks = list[Stack] class DescribeStacksOutput(TypedDict, total=False): - Stacks: Optional[Stacks] - NextToken: Optional[NextToken] + Stacks: Stacks | None + NextToken: NextToken | None class DescribeTypeInput(ServiceRequest): - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - Arn: Optional[TypeArn] - VersionId: Optional[TypeVersionId] - PublisherId: Optional[PublisherId] - PublicVersionNumber: Optional[PublicVersionNumber] + Type: RegistryType | None + TypeName: TypeName | None + Arn: TypeArn | None + VersionId: TypeVersionId | None + PublisherId: PublisherId | None + PublicVersionNumber: PublicVersionNumber | None -SupportedMajorVersions = List[SupportedMajorVersion] +SupportedMajorVersions = list[SupportedMajorVersion] class RequiredActivatedType(TypedDict, total=False): - TypeNameAlias: Optional[TypeName] - OriginalTypeName: Optional[TypeName] - PublisherId: Optional[PublisherId] - SupportedMajorVersions: Optional[SupportedMajorVersions] + TypeNameAlias: TypeName | None + OriginalTypeName: TypeName | None + PublisherId: PublisherId | None + SupportedMajorVersions: SupportedMajorVersions | None -RequiredActivatedTypes = List[RequiredActivatedType] +RequiredActivatedTypes = list[RequiredActivatedType] class DescribeTypeOutput(TypedDict, total=False): - Arn: Optional[TypeArn] - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - DefaultVersionId: Optional[TypeVersionId] - IsDefaultVersion: Optional[IsDefaultVersion] - TypeTestsStatus: Optional[TypeTestsStatus] - TypeTestsStatusDescription: Optional[TypeTestsStatusDescription] - Description: Optional[Description] - Schema: Optional[TypeSchema] - ProvisioningType: Optional[ProvisioningType] - DeprecatedStatus: Optional[DeprecatedStatus] - LoggingConfig: Optional[LoggingConfig] - RequiredActivatedTypes: Optional[RequiredActivatedTypes] - ExecutionRoleArn: Optional[RoleArn] - Visibility: Optional[Visibility] - SourceUrl: Optional[OptionalSecureUrl] - DocumentationUrl: Optional[OptionalSecureUrl] - LastUpdated: Optional[Timestamp] - TimeCreated: Optional[Timestamp] - ConfigurationSchema: Optional[ConfigurationSchema] - PublisherId: Optional[PublisherId] - OriginalTypeName: Optional[TypeName] - OriginalTypeArn: Optional[TypeArn] - PublicVersionNumber: Optional[PublicVersionNumber] - LatestPublicVersion: Optional[PublicVersionNumber] - IsActivated: Optional[IsActivated] - AutoUpdate: Optional[AutoUpdate] + Arn: TypeArn | None + Type: RegistryType | None + TypeName: TypeName | None + DefaultVersionId: TypeVersionId | None + IsDefaultVersion: IsDefaultVersion | None + TypeTestsStatus: TypeTestsStatus | None + TypeTestsStatusDescription: TypeTestsStatusDescription | None + Description: Description | None + Schema: TypeSchema | None + ProvisioningType: ProvisioningType | None + DeprecatedStatus: DeprecatedStatus | None + LoggingConfig: LoggingConfig | None + RequiredActivatedTypes: RequiredActivatedTypes | None + ExecutionRoleArn: RoleArn | None + Visibility: Visibility | None + SourceUrl: OptionalSecureUrl | None + DocumentationUrl: OptionalSecureUrl | None + LastUpdated: Timestamp | None + TimeCreated: Timestamp | None + ConfigurationSchema: ConfigurationSchema | None + PublisherId: PublisherId | None + OriginalTypeName: TypeName | None + OriginalTypeArn: TypeArn | None + PublicVersionNumber: PublicVersionNumber | None + LatestPublicVersion: PublicVersionNumber | None + IsActivated: IsActivated | None + AutoUpdate: AutoUpdate | None class DescribeTypeRegistrationInput(ServiceRequest): @@ -1953,18 +2139,18 @@ class DescribeTypeRegistrationInput(ServiceRequest): class DescribeTypeRegistrationOutput(TypedDict, total=False): - ProgressStatus: Optional[RegistrationStatus] - Description: Optional[Description] - TypeArn: Optional[TypeArn] - TypeVersionArn: Optional[TypeArn] + ProgressStatus: RegistrationStatus | None + Description: Description | None + TypeArn: TypeArn | None + TypeVersionArn: TypeArn | None -LogicalResourceIds = List[LogicalResourceId] +LogicalResourceIds = list[LogicalResourceId] class DetectStackDriftInput(ServiceRequest): StackName: StackNameOrId - LogicalResourceIds: Optional[LogicalResourceIds] + LogicalResourceIds: LogicalResourceIds | None class DetectStackDriftOutput(TypedDict, total=False): @@ -1982,31 +2168,31 @@ class DetectStackResourceDriftOutput(TypedDict, total=False): class DetectStackSetDriftInput(ServiceRequest): StackSetName: StackSetNameOrId - OperationPreferences: Optional[StackSetOperationPreferences] - OperationId: Optional[ClientRequestToken] - CallAs: Optional[CallAs] + OperationPreferences: StackSetOperationPreferences | None + OperationId: ClientRequestToken | None + CallAs: CallAs | None class DetectStackSetDriftOutput(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] + OperationId: ClientRequestToken | None class EstimateTemplateCostInput(ServiceRequest): - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] - Parameters: Optional[Parameters] + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None + Parameters: Parameters | None class EstimateTemplateCostOutput(TypedDict, total=False): - Url: Optional[Url] + Url: Url | None class ExecuteChangeSetInput(ServiceRequest): ChangeSetName: ChangeSetNameOrId - StackName: Optional[StackNameOrId] - ClientRequestToken: Optional[ClientRequestToken] - DisableRollback: Optional[DisableRollback] - RetainExceptOnCreate: Optional[RetainExceptOnCreate] + StackName: StackNameOrId | None + ClientRequestToken: ClientRequestToken | None + DisableRollback: DisableRollback | None + RetainExceptOnCreate: RetainExceptOnCreate | None class ExecuteChangeSetOutput(TypedDict, total=False): @@ -2018,22 +2204,49 @@ class ExecuteStackRefactorInput(ServiceRequest): class Export(TypedDict, total=False): - ExportingStackId: Optional[StackId] - Name: Optional[ExportName] - Value: Optional[ExportValue] + ExportingStackId: StackId | None + Name: ExportName | None + Value: ExportValue | None -Exports = List[Export] +Exports = list[Export] class GetGeneratedTemplateInput(ServiceRequest): - Format: Optional[TemplateFormat] + Format: TemplateFormat | None GeneratedTemplateName: GeneratedTemplateName class GetGeneratedTemplateOutput(TypedDict, total=False): - Status: Optional[GeneratedTemplateStatus] - TemplateBody: Optional[TemplateBody] + Status: GeneratedTemplateStatus | None + TemplateBody: TemplateBody | None + + +class GetHookResultInput(ServiceRequest): + HookResultId: HookInvocationId | None + + +class HookTarget(TypedDict, total=False): + TargetType: HookTargetType + TargetTypeName: HookTargetTypeName + TargetId: HookTargetId + Action: HookTargetAction + + +class GetHookResultOutput(TypedDict, total=False): + HookResultId: HookInvocationId | None + InvocationPoint: HookInvocationPoint | None + FailureMode: HookFailureMode | None + TypeName: HookTypeName | None + OriginalTypeName: HookTypeName | None + TypeVersionId: HookTypeVersionId | None + TypeConfigurationVersionId: HookTypeConfigurationVersionId | None + TypeArn: HookTypeArn | None + Status: HookStatus | None + HookStatusReason: HookStatusReason | None + InvokedAt: Timestamp | None + Target: HookTarget | None + Annotations: AnnotationList | None class GetStackPolicyInput(ServiceRequest): @@ -2041,180 +2254,188 @@ class GetStackPolicyInput(ServiceRequest): class GetStackPolicyOutput(TypedDict, total=False): - StackPolicyBody: Optional[StackPolicyBody] + StackPolicyBody: StackPolicyBody | None class GetTemplateInput(ServiceRequest): - StackName: Optional[StackName] - ChangeSetName: Optional[ChangeSetNameOrId] - TemplateStage: Optional[TemplateStage] + StackName: StackName | None + ChangeSetName: ChangeSetNameOrId | None + TemplateStage: TemplateStage | None -StageList = List[TemplateStage] +StageList = list[TemplateStage] class GetTemplateOutput(TypedDict, total=False): - TemplateBody: Optional[TemplateBody] - StagesAvailable: Optional[StageList] + TemplateBody: TemplateBody | None + StagesAvailable: StageList | None class TemplateSummaryConfig(TypedDict, total=False): - TreatUnrecognizedResourceTypesAsWarnings: Optional[TreatUnrecognizedResourceTypesAsWarnings] + TreatUnrecognizedResourceTypesAsWarnings: TreatUnrecognizedResourceTypesAsWarnings | None class GetTemplateSummaryInput(ServiceRequest): - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] - StackName: Optional[StackNameOrId] - StackSetName: Optional[StackSetNameOrId] - CallAs: Optional[CallAs] - TemplateSummaryConfig: Optional[TemplateSummaryConfig] + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None + StackName: StackNameOrId | None + StackSetName: StackSetNameOrId | None + CallAs: CallAs | None + TemplateSummaryConfig: TemplateSummaryConfig | None class Warnings(TypedDict, total=False): - UnrecognizedResourceTypes: Optional[ResourceTypes] + UnrecognizedResourceTypes: ResourceTypes | None -ResourceIdentifiers = List[ResourceIdentifierPropertyKey] +ResourceIdentifiers = list[ResourceIdentifierPropertyKey] class ResourceIdentifierSummary(TypedDict, total=False): - ResourceType: Optional[ResourceType] - LogicalResourceIds: Optional[LogicalResourceIds] - ResourceIdentifiers: Optional[ResourceIdentifiers] + ResourceType: ResourceType | None + LogicalResourceIds: LogicalResourceIds | None + ResourceIdentifiers: ResourceIdentifiers | None -ResourceIdentifierSummaries = List[ResourceIdentifierSummary] -TransformsList = List[TransformName] +ResourceIdentifierSummaries = list[ResourceIdentifierSummary] +TransformsList = list[TransformName] class ParameterConstraints(TypedDict, total=False): - AllowedValues: Optional[AllowedValues] + AllowedValues: AllowedValues | None class ParameterDeclaration(TypedDict, total=False): - ParameterKey: Optional[ParameterKey] - DefaultValue: Optional[ParameterValue] - ParameterType: Optional[ParameterType] - NoEcho: Optional[NoEcho] - Description: Optional[Description] - ParameterConstraints: Optional[ParameterConstraints] + ParameterKey: ParameterKey | None + DefaultValue: ParameterValue | None + ParameterType: ParameterType | None + NoEcho: NoEcho | None + Description: Description | None + ParameterConstraints: ParameterConstraints | None -ParameterDeclarations = List[ParameterDeclaration] +ParameterDeclarations = list[ParameterDeclaration] class GetTemplateSummaryOutput(TypedDict, total=False): - Parameters: Optional[ParameterDeclarations] - Description: Optional[Description] - Capabilities: Optional[Capabilities] - CapabilitiesReason: Optional[CapabilitiesReason] - ResourceTypes: Optional[ResourceTypes] - Version: Optional[Version] - Metadata: Optional[Metadata] - DeclaredTransforms: Optional[TransformsList] - ResourceIdentifierSummaries: Optional[ResourceIdentifierSummaries] - Warnings: Optional[Warnings] + Parameters: ParameterDeclarations | None + Description: Description | None + Capabilities: Capabilities | None + CapabilitiesReason: CapabilitiesReason | None + ResourceTypes: ResourceTypes | None + Version: Version | None + Metadata: Metadata | None + DeclaredTransforms: TransformsList | None + ResourceIdentifierSummaries: ResourceIdentifierSummaries | None + Warnings: Warnings | None class HookResultSummary(TypedDict, total=False): - InvocationPoint: Optional[HookInvocationPoint] - FailureMode: Optional[HookFailureMode] - TypeName: Optional[HookTypeName] - TypeVersionId: Optional[HookTypeVersionId] - TypeConfigurationVersionId: Optional[HookTypeConfigurationVersionId] - Status: Optional[HookStatus] - HookStatusReason: Optional[HookStatusReason] + HookResultId: HookInvocationId | None + InvocationPoint: HookInvocationPoint | None + FailureMode: HookFailureMode | None + TypeName: HookTypeName | None + TypeVersionId: HookTypeVersionId | None + TypeConfigurationVersionId: HookTypeConfigurationVersionId | None + Status: HookStatus | None + HookStatusReason: HookStatusReason | None + InvokedAt: Timestamp | None + TargetType: ListHookResultsTargetType | None + TargetId: HookResultId | None + TypeArn: HookTypeArn | None + HookExecutionTarget: HookResultId | None -HookResultSummaries = List[HookResultSummary] -StackIdList = List[StackId] +HookResultSummaries = list[HookResultSummary] +StackIdList = list[StackId] class ImportStacksToStackSetInput(ServiceRequest): StackSetName: StackSetNameOrId - StackIds: Optional[StackIdList] - StackIdsUrl: Optional[StackIdsUrl] - OrganizationalUnitIds: Optional[OrganizationalUnitIdList] - OperationPreferences: Optional[StackSetOperationPreferences] - OperationId: Optional[ClientRequestToken] - CallAs: Optional[CallAs] + StackIds: StackIdList | None + StackIdsUrl: StackIdsUrl | None + OrganizationalUnitIds: OrganizationalUnitIdList | None + OperationPreferences: StackSetOperationPreferences | None + OperationId: ClientRequestToken | None + CallAs: CallAs | None class ImportStacksToStackSetOutput(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] + OperationId: ClientRequestToken | None -Imports = List[StackName] -JazzLogicalResourceIds = List[LogicalResourceId] -JazzResourceIdentifierProperties = Dict[ +Imports = list[StackName] +JazzLogicalResourceIds = list[LogicalResourceId] +JazzResourceIdentifierProperties = dict[ JazzResourceIdentifierPropertyKey, JazzResourceIdentifierPropertyValue ] class ListChangeSetsInput(ServiceRequest): StackName: StackNameOrId - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListChangeSetsOutput(TypedDict, total=False): - Summaries: Optional[ChangeSetSummaries] - NextToken: Optional[NextToken] + Summaries: ChangeSetSummaries | None + NextToken: NextToken | None class ListExportsInput(ServiceRequest): - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListExportsOutput(TypedDict, total=False): - Exports: Optional[Exports] - NextToken: Optional[NextToken] + Exports: Exports | None + NextToken: NextToken | None class ListGeneratedTemplatesInput(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + NextToken: NextToken | None + MaxResults: MaxResults | None class TemplateSummary(TypedDict, total=False): - GeneratedTemplateId: Optional[GeneratedTemplateId] - GeneratedTemplateName: Optional[GeneratedTemplateName] - Status: Optional[GeneratedTemplateStatus] - StatusReason: Optional[TemplateStatusReason] - CreationTime: Optional[CreationTime] - LastUpdatedTime: Optional[LastUpdatedTime] - NumberOfResources: Optional[NumberOfResources] + GeneratedTemplateId: GeneratedTemplateId | None + GeneratedTemplateName: GeneratedTemplateName | None + Status: GeneratedTemplateStatus | None + StatusReason: TemplateStatusReason | None + CreationTime: CreationTime | None + LastUpdatedTime: LastUpdatedTime | None + NumberOfResources: NumberOfResources | None -TemplateSummaries = List[TemplateSummary] +TemplateSummaries = list[TemplateSummary] class ListGeneratedTemplatesOutput(TypedDict, total=False): - Summaries: Optional[TemplateSummaries] - NextToken: Optional[NextToken] + Summaries: TemplateSummaries | None + NextToken: NextToken | None class ListHookResultsInput(ServiceRequest): - TargetType: ListHookResultsTargetType - TargetId: HookResultId - NextToken: Optional[NextToken] + TargetType: ListHookResultsTargetType | None + TargetId: HookResultId | None + TypeArn: HookTypeArn | None + Status: HookStatus | None + NextToken: NextToken | None class ListHookResultsOutput(TypedDict, total=False): - TargetType: Optional[ListHookResultsTargetType] - TargetId: Optional[HookResultId] - HookResults: Optional[HookResultSummaries] - NextToken: Optional[NextToken] + TargetType: ListHookResultsTargetType | None + TargetId: HookResultId | None + HookResults: HookResultSummaries | None + NextToken: NextToken | None class ListImportsInput(ServiceRequest): ExportName: ExportName - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListImportsOutput(TypedDict, total=False): - Imports: Optional[Imports] - NextToken: Optional[NextToken] + Imports: Imports | None + NextToken: NextToken | None class ScannedResourceIdentifier(TypedDict, total=False): @@ -2222,475 +2443,476 @@ class ScannedResourceIdentifier(TypedDict, total=False): ResourceIdentifier: JazzResourceIdentifierProperties -ScannedResourceIdentifiers = List[ScannedResourceIdentifier] +ScannedResourceIdentifiers = list[ScannedResourceIdentifier] class ListResourceScanRelatedResourcesInput(ServiceRequest): ResourceScanId: ResourceScanId Resources: ScannedResourceIdentifiers - NextToken: Optional[NextToken] - MaxResults: Optional[BoxedMaxResults] + NextToken: NextToken | None + MaxResults: BoxedMaxResults | None class ScannedResource(TypedDict, total=False): - ResourceType: Optional[ResourceType] - ResourceIdentifier: Optional[JazzResourceIdentifierProperties] - ManagedByStack: Optional[ManagedByStack] + ResourceType: ResourceType | None + ResourceIdentifier: JazzResourceIdentifierProperties | None + ManagedByStack: ManagedByStack | None -RelatedResources = List[ScannedResource] +RelatedResources = list[ScannedResource] class ListResourceScanRelatedResourcesOutput(TypedDict, total=False): - RelatedResources: Optional[RelatedResources] - NextToken: Optional[NextToken] + RelatedResources: RelatedResources | None + NextToken: NextToken | None class ListResourceScanResourcesInput(ServiceRequest): ResourceScanId: ResourceScanId - ResourceIdentifier: Optional[ResourceIdentifier] - ResourceTypePrefix: Optional[ResourceTypePrefix] - TagKey: Optional[TagKey] - TagValue: Optional[TagValue] - NextToken: Optional[NextToken] - MaxResults: Optional[ResourceScannerMaxResults] + ResourceIdentifier: ResourceIdentifier | None + ResourceTypePrefix: ResourceTypePrefix | None + TagKey: TagKey | None + TagValue: TagValue | None + NextToken: NextToken | None + MaxResults: ResourceScannerMaxResults | None -ScannedResources = List[ScannedResource] +ScannedResources = list[ScannedResource] class ListResourceScanResourcesOutput(TypedDict, total=False): - Resources: Optional[ScannedResources] - NextToken: Optional[NextToken] + Resources: ScannedResources | None + NextToken: NextToken | None class ListResourceScansInput(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[ResourceScannerMaxResults] - ScanTypeFilter: Optional[ScanType] + NextToken: NextToken | None + MaxResults: ResourceScannerMaxResults | None + ScanTypeFilter: ScanType | None class ResourceScanSummary(TypedDict, total=False): - ResourceScanId: Optional[ResourceScanId] - Status: Optional[ResourceScanStatus] - StatusReason: Optional[ResourceScanStatusReason] - StartTime: Optional[Timestamp] - EndTime: Optional[Timestamp] - PercentageCompleted: Optional[PercentageCompleted] - ScanType: Optional[ScanType] + ResourceScanId: ResourceScanId | None + Status: ResourceScanStatus | None + StatusReason: ResourceScanStatusReason | None + StartTime: Timestamp | None + EndTime: Timestamp | None + PercentageCompleted: PercentageCompleted | None + ScanType: ScanType | None -ResourceScanSummaries = List[ResourceScanSummary] +ResourceScanSummaries = list[ResourceScanSummary] class ListResourceScansOutput(TypedDict, total=False): - ResourceScanSummaries: Optional[ResourceScanSummaries] - NextToken: Optional[NextToken] + ResourceScanSummaries: ResourceScanSummaries | None + NextToken: NextToken | None class ListStackInstanceResourceDriftsInput(ServiceRequest): StackSetName: StackSetNameOrId - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - StackInstanceResourceDriftStatuses: Optional[StackResourceDriftStatusFilters] + NextToken: NextToken | None + MaxResults: MaxResults | None + StackInstanceResourceDriftStatuses: StackResourceDriftStatusFilters | None StackInstanceAccount: Account StackInstanceRegion: Region OperationId: ClientRequestToken - CallAs: Optional[CallAs] + CallAs: CallAs | None class StackInstanceResourceDriftsSummary(TypedDict, total=False): StackId: StackId LogicalResourceId: LogicalResourceId - PhysicalResourceId: Optional[PhysicalResourceId] - PhysicalResourceIdContext: Optional[PhysicalResourceIdContext] + PhysicalResourceId: PhysicalResourceId | None + PhysicalResourceIdContext: PhysicalResourceIdContext | None ResourceType: ResourceType - PropertyDifferences: Optional[PropertyDifferences] + PropertyDifferences: PropertyDifferences | None StackResourceDriftStatus: StackResourceDriftStatus Timestamp: Timestamp -StackInstanceResourceDriftsSummaries = List[StackInstanceResourceDriftsSummary] +StackInstanceResourceDriftsSummaries = list[StackInstanceResourceDriftsSummary] class ListStackInstanceResourceDriftsOutput(TypedDict, total=False): - Summaries: Optional[StackInstanceResourceDriftsSummaries] - NextToken: Optional[NextToken] + Summaries: StackInstanceResourceDriftsSummaries | None + NextToken: NextToken | None class StackInstanceFilter(TypedDict, total=False): - Name: Optional[StackInstanceFilterName] - Values: Optional[StackInstanceFilterValues] + Name: StackInstanceFilterName | None + Values: StackInstanceFilterValues | None -StackInstanceFilters = List[StackInstanceFilter] +StackInstanceFilters = list[StackInstanceFilter] class ListStackInstancesInput(ServiceRequest): StackSetName: StackSetName - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - Filters: Optional[StackInstanceFilters] - StackInstanceAccount: Optional[Account] - StackInstanceRegion: Optional[Region] - CallAs: Optional[CallAs] + NextToken: NextToken | None + MaxResults: MaxResults | None + Filters: StackInstanceFilters | None + StackInstanceAccount: Account | None + StackInstanceRegion: Region | None + CallAs: CallAs | None class StackInstanceSummary(TypedDict, total=False): - StackSetId: Optional[StackSetId] - Region: Optional[Region] - Account: Optional[Account] - StackId: Optional[StackId] - Status: Optional[StackInstanceStatus] - StatusReason: Optional[Reason] - StackInstanceStatus: Optional[StackInstanceComprehensiveStatus] - OrganizationalUnitId: Optional[OrganizationalUnitId] - DriftStatus: Optional[StackDriftStatus] - LastDriftCheckTimestamp: Optional[Timestamp] - LastOperationId: Optional[ClientRequestToken] + StackSetId: StackSetId | None + Region: Region | None + Account: Account | None + StackId: StackId | None + Status: StackInstanceStatus | None + StatusReason: Reason | None + StackInstanceStatus: StackInstanceComprehensiveStatus | None + OrganizationalUnitId: OrganizationalUnitId | None + DriftStatus: StackDriftStatus | None + LastDriftCheckTimestamp: Timestamp | None + LastOperationId: ClientRequestToken | None -StackInstanceSummaries = List[StackInstanceSummary] +StackInstanceSummaries = list[StackInstanceSummary] class ListStackInstancesOutput(TypedDict, total=False): - Summaries: Optional[StackInstanceSummaries] - NextToken: Optional[NextToken] + Summaries: StackInstanceSummaries | None + NextToken: NextToken | None class ListStackRefactorActionsInput(ServiceRequest): StackRefactorId: StackRefactorId - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + NextToken: NextToken | None + MaxResults: MaxResults | None -StackRefactorUntagResources = List[TagKey] -StackRefactorTagResources = List[Tag] +StackRefactorUntagResources = list[TagKey] +StackRefactorTagResources = list[Tag] class StackRefactorAction(TypedDict, total=False): - Action: Optional[StackRefactorActionType] - Entity: Optional[StackRefactorActionEntity] - PhysicalResourceId: Optional[PhysicalResourceId] - ResourceIdentifier: Optional[StackRefactorResourceIdentifier] - Description: Optional[Description] - Detection: Optional[StackRefactorDetection] - DetectionReason: Optional[DetectionReason] - TagResources: Optional[StackRefactorTagResources] - UntagResources: Optional[StackRefactorUntagResources] - ResourceMapping: Optional[ResourceMapping] + Action: StackRefactorActionType | None + Entity: StackRefactorActionEntity | None + PhysicalResourceId: PhysicalResourceId | None + ResourceIdentifier: StackRefactorResourceIdentifier | None + Description: Description | None + Detection: StackRefactorDetection | None + DetectionReason: DetectionReason | None + TagResources: StackRefactorTagResources | None + UntagResources: StackRefactorUntagResources | None + ResourceMapping: ResourceMapping | None -StackRefactorActions = List[StackRefactorAction] +StackRefactorActions = list[StackRefactorAction] class ListStackRefactorActionsOutput(TypedDict, total=False): StackRefactorActions: StackRefactorActions - NextToken: Optional[NextToken] + NextToken: NextToken | None -StackRefactorExecutionStatusFilter = List[StackRefactorExecutionStatus] +StackRefactorExecutionStatusFilter = list[StackRefactorExecutionStatus] class ListStackRefactorsInput(ServiceRequest): - ExecutionStatusFilter: Optional[StackRefactorExecutionStatusFilter] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + ExecutionStatusFilter: StackRefactorExecutionStatusFilter | None + NextToken: NextToken | None + MaxResults: MaxResults | None class StackRefactorSummary(TypedDict, total=False): - StackRefactorId: Optional[StackRefactorId] - Description: Optional[Description] - ExecutionStatus: Optional[StackRefactorExecutionStatus] - ExecutionStatusReason: Optional[ExecutionStatusReason] - Status: Optional[StackRefactorStatus] - StatusReason: Optional[StackRefactorStatusReason] + StackRefactorId: StackRefactorId | None + Description: Description | None + ExecutionStatus: StackRefactorExecutionStatus | None + ExecutionStatusReason: ExecutionStatusReason | None + Status: StackRefactorStatus | None + StatusReason: StackRefactorStatusReason | None -StackRefactorSummaries = List[StackRefactorSummary] +StackRefactorSummaries = list[StackRefactorSummary] class ListStackRefactorsOutput(TypedDict, total=False): StackRefactorSummaries: StackRefactorSummaries - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListStackResourcesInput(ServiceRequest): StackName: StackName - NextToken: Optional[NextToken] + NextToken: NextToken | None class StackResourceDriftInformationSummary(TypedDict, total=False): StackResourceDriftStatus: StackResourceDriftStatus - LastCheckTimestamp: Optional[Timestamp] + LastCheckTimestamp: Timestamp | None class StackResourceSummary(TypedDict, total=False): LogicalResourceId: LogicalResourceId - PhysicalResourceId: Optional[PhysicalResourceId] + PhysicalResourceId: PhysicalResourceId | None ResourceType: ResourceType LastUpdatedTimestamp: Timestamp ResourceStatus: ResourceStatus - ResourceStatusReason: Optional[ResourceStatusReason] - DriftInformation: Optional[StackResourceDriftInformationSummary] - ModuleInfo: Optional[ModuleInfo] + ResourceStatusReason: ResourceStatusReason | None + DriftInformation: StackResourceDriftInformationSummary | None + ModuleInfo: ModuleInfo | None -StackResourceSummaries = List[StackResourceSummary] +StackResourceSummaries = list[StackResourceSummary] class ListStackResourcesOutput(TypedDict, total=False): - StackResourceSummaries: Optional[StackResourceSummaries] - NextToken: Optional[NextToken] + StackResourceSummaries: StackResourceSummaries | None + NextToken: NextToken | None class ListStackSetAutoDeploymentTargetsInput(ServiceRequest): StackSetName: StackSetNameOrId - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - CallAs: Optional[CallAs] + NextToken: NextToken | None + MaxResults: MaxResults | None + CallAs: CallAs | None class StackSetAutoDeploymentTargetSummary(TypedDict, total=False): - OrganizationalUnitId: Optional[OrganizationalUnitId] - Regions: Optional[RegionList] + OrganizationalUnitId: OrganizationalUnitId | None + Regions: RegionList | None -StackSetAutoDeploymentTargetSummaries = List[StackSetAutoDeploymentTargetSummary] +StackSetAutoDeploymentTargetSummaries = list[StackSetAutoDeploymentTargetSummary] class ListStackSetAutoDeploymentTargetsOutput(TypedDict, total=False): - Summaries: Optional[StackSetAutoDeploymentTargetSummaries] - NextToken: Optional[NextToken] + Summaries: StackSetAutoDeploymentTargetSummaries | None + NextToken: NextToken | None class OperationResultFilter(TypedDict, total=False): - Name: Optional[OperationResultFilterName] - Values: Optional[OperationResultFilterValues] + Name: OperationResultFilterName | None + Values: OperationResultFilterValues | None -OperationResultFilters = List[OperationResultFilter] +OperationResultFilters = list[OperationResultFilter] class ListStackSetOperationResultsInput(ServiceRequest): StackSetName: StackSetName OperationId: ClientRequestToken - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - CallAs: Optional[CallAs] - Filters: Optional[OperationResultFilters] + NextToken: NextToken | None + MaxResults: MaxResults | None + CallAs: CallAs | None + Filters: OperationResultFilters | None class StackSetOperationResultSummary(TypedDict, total=False): - Account: Optional[Account] - Region: Optional[Region] - Status: Optional[StackSetOperationResultStatus] - StatusReason: Optional[Reason] - AccountGateResult: Optional[AccountGateResult] - OrganizationalUnitId: Optional[OrganizationalUnitId] + Account: Account | None + Region: Region | None + Status: StackSetOperationResultStatus | None + StatusReason: Reason | None + AccountGateResult: AccountGateResult | None + OrganizationalUnitId: OrganizationalUnitId | None -StackSetOperationResultSummaries = List[StackSetOperationResultSummary] +StackSetOperationResultSummaries = list[StackSetOperationResultSummary] class ListStackSetOperationResultsOutput(TypedDict, total=False): - Summaries: Optional[StackSetOperationResultSummaries] - NextToken: Optional[NextToken] + Summaries: StackSetOperationResultSummaries | None + NextToken: NextToken | None class ListStackSetOperationsInput(ServiceRequest): StackSetName: StackSetName - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - CallAs: Optional[CallAs] + NextToken: NextToken | None + MaxResults: MaxResults | None + CallAs: CallAs | None class StackSetOperationSummary(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] - Action: Optional[StackSetOperationAction] - Status: Optional[StackSetOperationStatus] - CreationTimestamp: Optional[Timestamp] - EndTimestamp: Optional[Timestamp] - StatusReason: Optional[StackSetOperationStatusReason] - StatusDetails: Optional[StackSetOperationStatusDetails] - OperationPreferences: Optional[StackSetOperationPreferences] + OperationId: ClientRequestToken | None + Action: StackSetOperationAction | None + Status: StackSetOperationStatus | None + CreationTimestamp: Timestamp | None + EndTimestamp: Timestamp | None + StatusReason: StackSetOperationStatusReason | None + StatusDetails: StackSetOperationStatusDetails | None + OperationPreferences: StackSetOperationPreferences | None -StackSetOperationSummaries = List[StackSetOperationSummary] +StackSetOperationSummaries = list[StackSetOperationSummary] class ListStackSetOperationsOutput(TypedDict, total=False): - Summaries: Optional[StackSetOperationSummaries] - NextToken: Optional[NextToken] + Summaries: StackSetOperationSummaries | None + NextToken: NextToken | None class ListStackSetsInput(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - Status: Optional[StackSetStatus] - CallAs: Optional[CallAs] + NextToken: NextToken | None + MaxResults: MaxResults | None + Status: StackSetStatus | None + CallAs: CallAs | None class StackSetSummary(TypedDict, total=False): - StackSetName: Optional[StackSetName] - StackSetId: Optional[StackSetId] - Description: Optional[Description] - Status: Optional[StackSetStatus] - AutoDeployment: Optional[AutoDeployment] - PermissionModel: Optional[PermissionModels] - DriftStatus: Optional[StackDriftStatus] - LastDriftCheckTimestamp: Optional[Timestamp] - ManagedExecution: Optional[ManagedExecution] + StackSetName: StackSetName | None + StackSetId: StackSetId | None + Description: Description | None + Status: StackSetStatus | None + AutoDeployment: AutoDeployment | None + PermissionModel: PermissionModels | None + DriftStatus: StackDriftStatus | None + LastDriftCheckTimestamp: Timestamp | None + ManagedExecution: ManagedExecution | None -StackSetSummaries = List[StackSetSummary] +StackSetSummaries = list[StackSetSummary] class ListStackSetsOutput(TypedDict, total=False): - Summaries: Optional[StackSetSummaries] - NextToken: Optional[NextToken] + Summaries: StackSetSummaries | None + NextToken: NextToken | None -StackStatusFilter = List[StackStatus] +StackStatusFilter = list[StackStatus] class ListStacksInput(ServiceRequest): - NextToken: Optional[NextToken] - StackStatusFilter: Optional[StackStatusFilter] + NextToken: NextToken | None + StackStatusFilter: StackStatusFilter | None class StackDriftInformationSummary(TypedDict, total=False): StackDriftStatus: StackDriftStatus - LastCheckTimestamp: Optional[Timestamp] + LastCheckTimestamp: Timestamp | None class StackSummary(TypedDict, total=False): - StackId: Optional[StackId] + StackId: StackId | None StackName: StackName - TemplateDescription: Optional[TemplateDescription] + TemplateDescription: TemplateDescription | None CreationTime: CreationTime - LastUpdatedTime: Optional[LastUpdatedTime] - DeletionTime: Optional[DeletionTime] + LastUpdatedTime: LastUpdatedTime | None + DeletionTime: DeletionTime | None StackStatus: StackStatus - StackStatusReason: Optional[StackStatusReason] - ParentId: Optional[StackId] - RootId: Optional[StackId] - DriftInformation: Optional[StackDriftInformationSummary] + StackStatusReason: StackStatusReason | None + ParentId: StackId | None + RootId: StackId | None + DriftInformation: StackDriftInformationSummary | None + LastOperations: LastOperations | None -StackSummaries = List[StackSummary] +StackSummaries = list[StackSummary] class ListStacksOutput(TypedDict, total=False): - StackSummaries: Optional[StackSummaries] - NextToken: Optional[NextToken] + StackSummaries: StackSummaries | None + NextToken: NextToken | None class ListTypeRegistrationsInput(ServiceRequest): - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - TypeArn: Optional[TypeArn] - RegistrationStatusFilter: Optional[RegistrationStatus] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Type: RegistryType | None + TypeName: TypeName | None + TypeArn: TypeArn | None + RegistrationStatusFilter: RegistrationStatus | None + MaxResults: MaxResults | None + NextToken: NextToken | None -RegistrationTokenList = List[RegistrationToken] +RegistrationTokenList = list[RegistrationToken] class ListTypeRegistrationsOutput(TypedDict, total=False): - RegistrationTokenList: Optional[RegistrationTokenList] - NextToken: Optional[NextToken] + RegistrationTokenList: RegistrationTokenList | None + NextToken: NextToken | None class ListTypeVersionsInput(ServiceRequest): - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - Arn: Optional[TypeArn] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - DeprecatedStatus: Optional[DeprecatedStatus] - PublisherId: Optional[PublisherId] + Type: RegistryType | None + TypeName: TypeName | None + Arn: TypeArn | None + MaxResults: MaxResults | None + NextToken: NextToken | None + DeprecatedStatus: DeprecatedStatus | None + PublisherId: PublisherId | None class TypeVersionSummary(TypedDict, total=False): - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - VersionId: Optional[TypeVersionId] - IsDefaultVersion: Optional[IsDefaultVersion] - Arn: Optional[TypeArn] - TimeCreated: Optional[Timestamp] - Description: Optional[Description] - PublicVersionNumber: Optional[PublicVersionNumber] + Type: RegistryType | None + TypeName: TypeName | None + VersionId: TypeVersionId | None + IsDefaultVersion: IsDefaultVersion | None + Arn: TypeArn | None + TimeCreated: Timestamp | None + Description: Description | None + PublicVersionNumber: PublicVersionNumber | None -TypeVersionSummaries = List[TypeVersionSummary] +TypeVersionSummaries = list[TypeVersionSummary] class ListTypeVersionsOutput(TypedDict, total=False): - TypeVersionSummaries: Optional[TypeVersionSummaries] - NextToken: Optional[NextToken] + TypeVersionSummaries: TypeVersionSummaries | None + NextToken: NextToken | None class TypeFilters(TypedDict, total=False): - Category: Optional[Category] - PublisherId: Optional[PublisherId] - TypeNamePrefix: Optional[TypeNamePrefix] + Category: Category | None + PublisherId: PublisherId | None + TypeNamePrefix: TypeNamePrefix | None class ListTypesInput(ServiceRequest): - Visibility: Optional[Visibility] - ProvisioningType: Optional[ProvisioningType] - DeprecatedStatus: Optional[DeprecatedStatus] - Type: Optional[RegistryType] - Filters: Optional[TypeFilters] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Visibility: Visibility | None + ProvisioningType: ProvisioningType | None + DeprecatedStatus: DeprecatedStatus | None + Type: RegistryType | None + Filters: TypeFilters | None + MaxResults: MaxResults | None + NextToken: NextToken | None class TypeSummary(TypedDict, total=False): - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - DefaultVersionId: Optional[TypeVersionId] - TypeArn: Optional[TypeArn] - LastUpdated: Optional[Timestamp] - Description: Optional[Description] - PublisherId: Optional[PublisherId] - OriginalTypeName: Optional[TypeName] - PublicVersionNumber: Optional[PublicVersionNumber] - LatestPublicVersion: Optional[PublicVersionNumber] - PublisherIdentity: Optional[IdentityProvider] - PublisherName: Optional[PublisherName] - IsActivated: Optional[IsActivated] + Type: RegistryType | None + TypeName: TypeName | None + DefaultVersionId: TypeVersionId | None + TypeArn: TypeArn | None + LastUpdated: Timestamp | None + Description: Description | None + PublisherId: PublisherId | None + OriginalTypeName: TypeName | None + PublicVersionNumber: PublicVersionNumber | None + LatestPublicVersion: PublicVersionNumber | None + PublisherIdentity: IdentityProvider | None + PublisherName: PublisherName | None + IsActivated: IsActivated | None -TypeSummaries = List[TypeSummary] +TypeSummaries = list[TypeSummary] class ListTypesOutput(TypedDict, total=False): - TypeSummaries: Optional[TypeSummaries] - NextToken: Optional[NextToken] + TypeSummaries: TypeSummaries | None + NextToken: NextToken | None class PublishTypeInput(ServiceRequest): - Type: Optional[ThirdPartyType] - Arn: Optional[PrivateTypeArn] - TypeName: Optional[TypeName] - PublicVersionNumber: Optional[PublicVersionNumber] + Type: ThirdPartyType | None + Arn: PrivateTypeArn | None + TypeName: TypeName | None + PublicVersionNumber: PublicVersionNumber | None class PublishTypeOutput(TypedDict, total=False): - PublicTypeArn: Optional[TypeArn] + PublicTypeArn: TypeArn | None class RecordHandlerProgressInput(ServiceRequest): BearerToken: ClientToken OperationStatus: OperationStatus - CurrentOperationStatus: Optional[OperationStatus] - StatusMessage: Optional[StatusMessage] - ErrorCode: Optional[HandlerErrorCode] - ResourceModel: Optional[ResourceModel] - ClientRequestToken: Optional[ClientRequestToken] + CurrentOperationStatus: OperationStatus | None + StatusMessage: StatusMessage | None + ErrorCode: HandlerErrorCode | None + ResourceModel: ResourceModel | None + ClientRequestToken: ClientRequestToken | None class RecordHandlerProgressOutput(TypedDict, total=False): @@ -2698,61 +2920,62 @@ class RecordHandlerProgressOutput(TypedDict, total=False): class RegisterPublisherInput(ServiceRequest): - AcceptTermsAndConditions: Optional[AcceptTermsAndConditions] - ConnectionArn: Optional[ConnectionArn] + AcceptTermsAndConditions: AcceptTermsAndConditions | None + ConnectionArn: ConnectionArn | None class RegisterPublisherOutput(TypedDict, total=False): - PublisherId: Optional[PublisherId] + PublisherId: PublisherId | None class RegisterTypeInput(ServiceRequest): - Type: Optional[RegistryType] + Type: RegistryType | None TypeName: TypeName SchemaHandlerPackage: S3Url - LoggingConfig: Optional[LoggingConfig] - ExecutionRoleArn: Optional[RoleArn] - ClientRequestToken: Optional[RequestToken] + LoggingConfig: LoggingConfig | None + ExecutionRoleArn: RoleArn | None + ClientRequestToken: RequestToken | None class RegisterTypeOutput(TypedDict, total=False): - RegistrationToken: Optional[RegistrationToken] + RegistrationToken: RegistrationToken | None class RollbackStackInput(ServiceRequest): StackName: StackNameOrId - RoleARN: Optional[RoleARN] - ClientRequestToken: Optional[ClientRequestToken] - RetainExceptOnCreate: Optional[RetainExceptOnCreate] + RoleARN: RoleARN | None + ClientRequestToken: ClientRequestToken | None + RetainExceptOnCreate: RetainExceptOnCreate | None class RollbackStackOutput(TypedDict, total=False): - StackId: Optional[StackId] + StackId: StackId | None + OperationId: OperationId | None class SetStackPolicyInput(ServiceRequest): StackName: StackName - StackPolicyBody: Optional[StackPolicyBody] - StackPolicyURL: Optional[StackPolicyURL] + StackPolicyBody: StackPolicyBody | None + StackPolicyURL: StackPolicyURL | None class SetTypeConfigurationInput(ServiceRequest): - TypeArn: Optional[TypeArn] + TypeArn: TypeArn | None Configuration: TypeConfiguration - ConfigurationAlias: Optional[TypeConfigurationAlias] - TypeName: Optional[TypeName] - Type: Optional[ThirdPartyType] + ConfigurationAlias: TypeConfigurationAlias | None + TypeName: TypeName | None + Type: ThirdPartyType | None class SetTypeConfigurationOutput(TypedDict, total=False): - ConfigurationArn: Optional[TypeConfigurationArn] + ConfigurationArn: TypeConfigurationArn | None class SetTypeDefaultVersionInput(ServiceRequest): - Arn: Optional[PrivateTypeArn] - Type: Optional[RegistryType] - TypeName: Optional[TypeName] - VersionId: Optional[TypeVersionId] + Arn: PrivateTypeArn | None + Type: RegistryType | None + TypeName: TypeName | None + VersionId: TypeVersionId | None class SetTypeDefaultVersionOutput(TypedDict, total=False): @@ -2767,18 +2990,18 @@ class SignalResourceInput(ServiceRequest): class StartResourceScanInput(ServiceRequest): - ClientRequestToken: Optional[ClientRequestToken] - ScanFilters: Optional[ScanFilters] + ClientRequestToken: ClientRequestToken | None + ScanFilters: ScanFilters | None class StartResourceScanOutput(TypedDict, total=False): - ResourceScanId: Optional[ResourceScanId] + ResourceScanId: ResourceScanId | None class StopStackSetOperationInput(ServiceRequest): StackSetName: StackSetName OperationId: ClientRequestToken - CallAs: Optional[CallAs] + CallAs: CallAs | None class StopStackSetOperationOutput(TypedDict, total=False): @@ -2786,104 +3009,105 @@ class StopStackSetOperationOutput(TypedDict, total=False): class TemplateParameter(TypedDict, total=False): - ParameterKey: Optional[ParameterKey] - DefaultValue: Optional[ParameterValue] - NoEcho: Optional[NoEcho] - Description: Optional[Description] + ParameterKey: ParameterKey | None + DefaultValue: ParameterValue | None + NoEcho: NoEcho | None + Description: Description | None -TemplateParameters = List[TemplateParameter] +TemplateParameters = list[TemplateParameter] class TestTypeInput(ServiceRequest): - Arn: Optional[TypeArn] - Type: Optional[ThirdPartyType] - TypeName: Optional[TypeName] - VersionId: Optional[TypeVersionId] - LogDeliveryBucket: Optional[S3Bucket] + Arn: TypeArn | None + Type: ThirdPartyType | None + TypeName: TypeName | None + VersionId: TypeVersionId | None + LogDeliveryBucket: S3Bucket | None class TestTypeOutput(TypedDict, total=False): - TypeVersionArn: Optional[TypeArn] + TypeVersionArn: TypeArn | None class UpdateGeneratedTemplateInput(ServiceRequest): GeneratedTemplateName: GeneratedTemplateName - NewGeneratedTemplateName: Optional[GeneratedTemplateName] - AddResources: Optional[ResourceDefinitions] - RemoveResources: Optional[JazzLogicalResourceIds] - RefreshAllResources: Optional[RefreshAllResources] - TemplateConfiguration: Optional[TemplateConfiguration] + NewGeneratedTemplateName: GeneratedTemplateName | None + AddResources: ResourceDefinitions | None + RemoveResources: JazzLogicalResourceIds | None + RefreshAllResources: RefreshAllResources | None + TemplateConfiguration: TemplateConfiguration | None class UpdateGeneratedTemplateOutput(TypedDict, total=False): - GeneratedTemplateId: Optional[GeneratedTemplateId] + GeneratedTemplateId: GeneratedTemplateId | None class UpdateStackInput(ServiceRequest): StackName: StackName - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] - UsePreviousTemplate: Optional[UsePreviousTemplate] - StackPolicyDuringUpdateBody: Optional[StackPolicyDuringUpdateBody] - StackPolicyDuringUpdateURL: Optional[StackPolicyDuringUpdateURL] - Parameters: Optional[Parameters] - Capabilities: Optional[Capabilities] - ResourceTypes: Optional[ResourceTypes] - RoleARN: Optional[RoleARN] - RollbackConfiguration: Optional[RollbackConfiguration] - StackPolicyBody: Optional[StackPolicyBody] - StackPolicyURL: Optional[StackPolicyURL] - NotificationARNs: Optional[NotificationARNs] - Tags: Optional[Tags] - DisableRollback: Optional[DisableRollback] - ClientRequestToken: Optional[ClientRequestToken] - RetainExceptOnCreate: Optional[RetainExceptOnCreate] + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None + UsePreviousTemplate: UsePreviousTemplate | None + StackPolicyDuringUpdateBody: StackPolicyDuringUpdateBody | None + StackPolicyDuringUpdateURL: StackPolicyDuringUpdateURL | None + Parameters: Parameters | None + Capabilities: Capabilities | None + ResourceTypes: ResourceTypes | None + RoleARN: RoleARN | None + RollbackConfiguration: RollbackConfiguration | None + StackPolicyBody: StackPolicyBody | None + StackPolicyURL: StackPolicyURL | None + NotificationARNs: NotificationARNs | None + Tags: Tags | None + DisableRollback: DisableRollback | None + ClientRequestToken: ClientRequestToken | None + RetainExceptOnCreate: RetainExceptOnCreate | None class UpdateStackInstancesInput(ServiceRequest): StackSetName: StackSetNameOrId - Accounts: Optional[AccountList] - DeploymentTargets: Optional[DeploymentTargets] + Accounts: AccountList | None + DeploymentTargets: DeploymentTargets | None Regions: RegionList - ParameterOverrides: Optional[Parameters] - OperationPreferences: Optional[StackSetOperationPreferences] - OperationId: Optional[ClientRequestToken] - CallAs: Optional[CallAs] + ParameterOverrides: Parameters | None + OperationPreferences: StackSetOperationPreferences | None + OperationId: ClientRequestToken | None + CallAs: CallAs | None class UpdateStackInstancesOutput(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] + OperationId: ClientRequestToken | None class UpdateStackOutput(TypedDict, total=False): - StackId: Optional[StackId] + StackId: StackId | None + OperationId: OperationId | None class UpdateStackSetInput(ServiceRequest): StackSetName: StackSetName - Description: Optional[Description] - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] - UsePreviousTemplate: Optional[UsePreviousTemplate] - Parameters: Optional[Parameters] - Capabilities: Optional[Capabilities] - Tags: Optional[Tags] - OperationPreferences: Optional[StackSetOperationPreferences] - AdministrationRoleARN: Optional[RoleARN] - ExecutionRoleName: Optional[ExecutionRoleName] - DeploymentTargets: Optional[DeploymentTargets] - PermissionModel: Optional[PermissionModels] - AutoDeployment: Optional[AutoDeployment] - OperationId: Optional[ClientRequestToken] - Accounts: Optional[AccountList] - Regions: Optional[RegionList] - CallAs: Optional[CallAs] - ManagedExecution: Optional[ManagedExecution] + Description: Description | None + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None + UsePreviousTemplate: UsePreviousTemplate | None + Parameters: Parameters | None + Capabilities: Capabilities | None + Tags: Tags | None + OperationPreferences: StackSetOperationPreferences | None + AdministrationRoleARN: RoleARN | None + ExecutionRoleName: ExecutionRoleName | None + DeploymentTargets: DeploymentTargets | None + PermissionModel: PermissionModels | None + AutoDeployment: AutoDeployment | None + OperationId: ClientRequestToken | None + Accounts: AccountList | None + Regions: RegionList | None + CallAs: CallAs | None + ManagedExecution: ManagedExecution | None class UpdateStackSetOutput(TypedDict, total=False): - OperationId: Optional[ClientRequestToken] + OperationId: ClientRequestToken | None class UpdateTerminationProtectionInput(ServiceRequest): @@ -2892,25 +3116,25 @@ class UpdateTerminationProtectionInput(ServiceRequest): class UpdateTerminationProtectionOutput(TypedDict, total=False): - StackId: Optional[StackId] + StackId: StackId | None class ValidateTemplateInput(ServiceRequest): - TemplateBody: Optional[TemplateBody] - TemplateURL: Optional[TemplateURL] + TemplateBody: TemplateBody | None + TemplateURL: TemplateURL | None class ValidateTemplateOutput(TypedDict, total=False): - Parameters: Optional[TemplateParameters] - Description: Optional[Description] - Capabilities: Optional[Capabilities] - CapabilitiesReason: Optional[CapabilitiesReason] - DeclaredTransforms: Optional[TransformsList] + Parameters: TemplateParameters | None + Description: Description | None + Capabilities: Capabilities | None + CapabilitiesReason: CapabilitiesReason | None + DeclaredTransforms: TransformsList | None class CloudformationApi: - service = "cloudformation" - version = "2010-05-15" + service: str = "cloudformation" + version: str = "2010-05-15" @handler("ActivateOrganizationsAccess") def activate_organizations_access( @@ -2978,6 +3202,7 @@ def create_change_set( include_nested_stacks: IncludeNestedStacks | None = None, on_stack_failure: OnStackFailure | None = None, import_existing_resources: ImportExistingResources | None = None, + deployment_mode: DeploymentMode | None = None, **kwargs, ) -> CreateChangeSetOutput: raise NotImplementedError @@ -3174,6 +3399,19 @@ def describe_change_set_hooks( ) -> DescribeChangeSetHooksOutput: raise NotImplementedError + @handler("DescribeEvents") + def describe_events( + self, + context: RequestContext, + stack_name: StackNameOrId | None = None, + change_set_name: ChangeSetNameOrId | None = None, + operation_id: OperationId | None = None, + filters: EventFilter | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> DescribeEventsOutput: + raise NotImplementedError + @handler("DescribeGeneratedTemplate") def describe_generated_template( self, context: RequestContext, generated_template_name: GeneratedTemplateName, **kwargs @@ -3208,7 +3446,7 @@ def describe_stack_drift_detection_status( def describe_stack_events( self, context: RequestContext, - stack_name: StackName | None = None, + stack_name: StackName, next_token: NextToken | None = None, **kwargs, ) -> DescribeStackEventsOutput: @@ -3380,6 +3618,12 @@ def get_generated_template( ) -> GetGeneratedTemplateOutput: raise NotImplementedError + @handler("GetHookResult") + def get_hook_result( + self, context: RequestContext, hook_result_id: HookInvocationId | None = None, **kwargs + ) -> GetHookResultOutput: + raise NotImplementedError + @handler("GetStackPolicy") def get_stack_policy( self, context: RequestContext, stack_name: StackName, **kwargs @@ -3456,8 +3700,10 @@ def list_generated_templates( def list_hook_results( self, context: RequestContext, - target_type: ListHookResultsTargetType, - target_id: HookResultId, + target_type: ListHookResultsTargetType | None = None, + target_id: HookResultId | None = None, + type_arn: HookTypeArn | None = None, + status: HookStatus | None = None, next_token: NextToken | None = None, **kwargs, ) -> ListHookResultsOutput: diff --git a/localstack-core/localstack/aws/api/cloudwatch/__init__.py b/localstack-core/localstack/aws/api/cloudwatch/__init__.py index e05e85a069dee..cfbc60d767caf 100644 --- a/localstack-core/localstack/aws/api/cloudwatch/__init__.py +++ b/localstack-core/localstack/aws/api/cloudwatch/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -16,7 +16,11 @@ AmazonResourceName = str AnomalyDetectorMetricStat = str AnomalyDetectorMetricTimezone = str +Arn = str +AttributeName = str +AttributeValue = str AwsQueryErrorMessage = str +ContributorId = str DashboardArn = str DashboardBody = str DashboardErrorMessage = str @@ -27,6 +31,7 @@ DatapointsToAlarm = int DimensionName = str DimensionValue = str +Duration = str EntityAttributesMapKeyString = str EntityAttributesMapValueString = str EntityKeyAttributesMapKeyString = str @@ -35,6 +40,7 @@ EvaluateLowSampleCountPercentile = str EvaluationPeriods = int ExceptionType = str +Expression = str ExtendedStatistic = str FailureCode = str FailureDescription = str @@ -74,6 +80,8 @@ MetricStreamState = str MetricStreamStatistic = str MetricWidget = str +MuteType = str +Name = str Namespace = str NextToken = str OutputFormat = str @@ -93,6 +101,7 @@ TagValue = str TemplateName = str Threshold = float +Timezone = str TreatMissingData = str @@ -102,6 +111,12 @@ class ActionsSuppressedBy(StrEnum): Alarm = "Alarm" +class AlarmMuteRuleStatus(StrEnum): + SCHEDULED = "SCHEDULED" + ACTIVE = "ACTIVE" + EXPIRED = "EXPIRED" + + class AlarmType(StrEnum): CompositeAlarm = "CompositeAlarm" MetricAlarm = "MetricAlarm" @@ -130,12 +145,16 @@ class ComparisonOperator(StrEnum): class EvaluationState(StrEnum): PARTIAL_DATA = "PARTIAL_DATA" + EVALUATION_FAILURE = "EVALUATION_FAILURE" + EVALUATION_ERROR = "EVALUATION_ERROR" class HistoryItemType(StrEnum): ConfigurationUpdate = "ConfigurationUpdate" StateUpdate = "StateUpdate" Action = "Action" + AlarmContributorStateUpdate = "AlarmContributorStateUpdate" + AlarmContributorAction = "AlarmContributorAction" class MetricStreamOutputFormat(StrEnum): @@ -213,22 +232,22 @@ class ConcurrentModificationException(ServiceException): class ConflictException(ServiceException): code: str = "ConflictException" sender_fault: bool = False - status_code: int = 400 + status_code: int = 409 class DashboardValidationMessage(TypedDict, total=False): - DataPath: Optional[DataPath] - Message: Optional[Message] + DataPath: DataPath | None + Message: Message | None -DashboardValidationMessages = List[DashboardValidationMessage] +DashboardValidationMessages = list[DashboardValidationMessage] class DashboardInvalidInputError(ServiceException): code: str = "InvalidParameterInput" sender_fault: bool = True status_code: int = 400 - dashboardValidationMessages: Optional[DashboardValidationMessages] + dashboardValidationMessages: DashboardValidationMessages | None class DashboardNotFoundError(ServiceException): @@ -295,25 +314,50 @@ class ResourceNotFoundException(ServiceException): code: str = "ResourceNotFoundException" sender_fault: bool = True status_code: int = 404 - ResourceType: Optional[ResourceType] - ResourceId: Optional[ResourceId] + ResourceType: ResourceType | None + ResourceId: ResourceId | None Timestamp = datetime +ContributorAttributes = dict[AttributeName, AttributeValue] + + +class AlarmContributor(TypedDict, total=False): + ContributorId: ContributorId + ContributorAttributes: ContributorAttributes + StateReason: StateReason + StateTransitionedTimestamp: Timestamp | None + + +AlarmContributors = list[AlarmContributor] class AlarmHistoryItem(TypedDict, total=False): - AlarmName: Optional[AlarmName] - AlarmType: Optional[AlarmType] - Timestamp: Optional[Timestamp] - HistoryItemType: Optional[HistoryItemType] - HistorySummary: Optional[HistorySummary] - HistoryData: Optional[HistoryData] + AlarmName: AlarmName | None + AlarmContributorId: ContributorId | None + AlarmType: AlarmType | None + Timestamp: Timestamp | None + HistoryItemType: HistoryItemType | None + HistorySummary: HistorySummary | None + HistoryData: HistoryData | None + AlarmContributorAttributes: ContributorAttributes | None -AlarmHistoryItems = List[AlarmHistoryItem] -AlarmNames = List[AlarmName] -AlarmTypes = List[AlarmType] +AlarmHistoryItems = list[AlarmHistoryItem] +AlarmMuteRuleStatuses = list[AlarmMuteRuleStatus] + + +class AlarmMuteRuleSummary(TypedDict, total=False): + AlarmMuteRuleArn: Arn | None + ExpireDate: Timestamp | None + Status: AlarmMuteRuleStatus | None + MuteType: MuteType | None + LastUpdatedTimestamp: Timestamp | None + + +AlarmMuteRuleSummaries = list[AlarmMuteRuleSummary] +AlarmNames = list[AlarmName] +AlarmTypes = list[AlarmType] class Dimension(TypedDict, total=False): @@ -321,49 +365,49 @@ class Dimension(TypedDict, total=False): Value: DimensionValue -Dimensions = List[Dimension] +Dimensions = list[Dimension] class Metric(TypedDict, total=False): - Namespace: Optional[Namespace] - MetricName: Optional[MetricName] - Dimensions: Optional[Dimensions] + Namespace: Namespace | None + MetricName: MetricName | None + Dimensions: Dimensions | None class MetricStat(TypedDict, total=False): Metric: Metric Period: Period Stat: Stat - Unit: Optional[StandardUnit] + Unit: StandardUnit | None class MetricDataQuery(TypedDict, total=False): Id: MetricId - MetricStat: Optional[MetricStat] - Expression: Optional[MetricExpression] - Label: Optional[MetricLabel] - ReturnData: Optional[ReturnData] - Period: Optional[Period] - AccountId: Optional[AccountId] + MetricStat: MetricStat | None + Expression: MetricExpression | None + Label: MetricLabel | None + ReturnData: ReturnData | None + Period: Period | None + AccountId: AccountId | None -MetricDataQueries = List[MetricDataQuery] +MetricDataQueries = list[MetricDataQuery] class MetricMathAnomalyDetector(TypedDict, total=False): - MetricDataQueries: Optional[MetricDataQueries] + MetricDataQueries: MetricDataQueries | None class SingleMetricAnomalyDetector(TypedDict, total=False): - AccountId: Optional[AccountId] - Namespace: Optional[Namespace] - MetricName: Optional[MetricName] - Dimensions: Optional[Dimensions] - Stat: Optional[AnomalyDetectorMetricStat] + AccountId: AccountId | None + Namespace: Namespace | None + MetricName: MetricName | None + Dimensions: Dimensions | None + Stat: AnomalyDetectorMetricStat | None class MetricCharacteristics(TypedDict, total=False): - PeriodicSpikes: Optional[PeriodicSpikes] + PeriodicSpikes: PeriodicSpikes | None class Range(TypedDict, total=False): @@ -371,94 +415,98 @@ class Range(TypedDict, total=False): EndTime: Timestamp -AnomalyDetectorExcludedTimeRanges = List[Range] +AnomalyDetectorExcludedTimeRanges = list[Range] class AnomalyDetectorConfiguration(TypedDict, total=False): - ExcludedTimeRanges: Optional[AnomalyDetectorExcludedTimeRanges] - MetricTimezone: Optional[AnomalyDetectorMetricTimezone] + ExcludedTimeRanges: AnomalyDetectorExcludedTimeRanges | None + MetricTimezone: AnomalyDetectorMetricTimezone | None class AnomalyDetector(TypedDict, total=False): - Namespace: Optional[Namespace] - MetricName: Optional[MetricName] - Dimensions: Optional[Dimensions] - Stat: Optional[AnomalyDetectorMetricStat] - Configuration: Optional[AnomalyDetectorConfiguration] - StateValue: Optional[AnomalyDetectorStateValue] - MetricCharacteristics: Optional[MetricCharacteristics] - SingleMetricAnomalyDetector: Optional[SingleMetricAnomalyDetector] - MetricMathAnomalyDetector: Optional[MetricMathAnomalyDetector] + Namespace: Namespace | None + MetricName: MetricName | None + Dimensions: Dimensions | None + Stat: AnomalyDetectorMetricStat | None + Configuration: AnomalyDetectorConfiguration | None + StateValue: AnomalyDetectorStateValue | None + MetricCharacteristics: MetricCharacteristics | None + SingleMetricAnomalyDetector: SingleMetricAnomalyDetector | None + MetricMathAnomalyDetector: MetricMathAnomalyDetector | None -AnomalyDetectorTypes = List[AnomalyDetectorType] -AnomalyDetectors = List[AnomalyDetector] +AnomalyDetectorTypes = list[AnomalyDetectorType] +AnomalyDetectors = list[AnomalyDetector] class PartialFailure(TypedDict, total=False): - FailureResource: Optional[FailureResource] - ExceptionType: Optional[ExceptionType] - FailureCode: Optional[FailureCode] - FailureDescription: Optional[FailureDescription] + FailureResource: FailureResource | None + ExceptionType: ExceptionType | None + FailureCode: FailureCode | None + FailureDescription: FailureDescription | None -BatchFailures = List[PartialFailure] -ResourceList = List[ResourceName] +BatchFailures = list[PartialFailure] +ResourceList = list[ResourceName] class CompositeAlarm(TypedDict, total=False): - ActionsEnabled: Optional[ActionsEnabled] - AlarmActions: Optional[ResourceList] - AlarmArn: Optional[AlarmArn] - AlarmConfigurationUpdatedTimestamp: Optional[Timestamp] - AlarmDescription: Optional[AlarmDescription] - AlarmName: Optional[AlarmName] - AlarmRule: Optional[AlarmRule] - InsufficientDataActions: Optional[ResourceList] - OKActions: Optional[ResourceList] - StateReason: Optional[StateReason] - StateReasonData: Optional[StateReasonData] - StateUpdatedTimestamp: Optional[Timestamp] - StateValue: Optional[StateValue] - StateTransitionedTimestamp: Optional[Timestamp] - ActionsSuppressedBy: Optional[ActionsSuppressedBy] - ActionsSuppressedReason: Optional[ActionsSuppressedReason] - ActionsSuppressor: Optional[AlarmArn] - ActionsSuppressorWaitPeriod: Optional[SuppressorPeriod] - ActionsSuppressorExtensionPeriod: Optional[SuppressorPeriod] - - -CompositeAlarms = List[CompositeAlarm] -Counts = List[DatapointValue] + ActionsEnabled: ActionsEnabled | None + AlarmActions: ResourceList | None + AlarmArn: AlarmArn | None + AlarmConfigurationUpdatedTimestamp: Timestamp | None + AlarmDescription: AlarmDescription | None + AlarmName: AlarmName | None + AlarmRule: AlarmRule | None + InsufficientDataActions: ResourceList | None + OKActions: ResourceList | None + StateReason: StateReason | None + StateReasonData: StateReasonData | None + StateUpdatedTimestamp: Timestamp | None + StateValue: StateValue | None + StateTransitionedTimestamp: Timestamp | None + ActionsSuppressedBy: ActionsSuppressedBy | None + ActionsSuppressedReason: ActionsSuppressedReason | None + ActionsSuppressor: AlarmArn | None + ActionsSuppressorWaitPeriod: SuppressorPeriod | None + ActionsSuppressorExtensionPeriod: SuppressorPeriod | None + + +CompositeAlarms = list[CompositeAlarm] +Counts = list[DatapointValue] Size = int LastModified = datetime class DashboardEntry(TypedDict, total=False): - DashboardName: Optional[DashboardName] - DashboardArn: Optional[DashboardArn] - LastModified: Optional[LastModified] - Size: Optional[Size] + DashboardName: DashboardName | None + DashboardArn: DashboardArn | None + LastModified: LastModified | None + Size: Size | None -DashboardEntries = List[DashboardEntry] -DashboardNames = List[DashboardName] -DatapointValueMap = Dict[ExtendedStatistic, DatapointValue] +DashboardEntries = list[DashboardEntry] +DashboardNames = list[DashboardName] +DatapointValueMap = dict[ExtendedStatistic, DatapointValue] class Datapoint(TypedDict, total=False): - Timestamp: Optional[Timestamp] - SampleCount: Optional[DatapointValue] - Average: Optional[DatapointValue] - Sum: Optional[DatapointValue] - Minimum: Optional[DatapointValue] - Maximum: Optional[DatapointValue] - Unit: Optional[StandardUnit] - ExtendedStatistics: Optional[DatapointValueMap] + Timestamp: Timestamp | None + SampleCount: DatapointValue | None + Average: DatapointValue | None + Sum: DatapointValue | None + Minimum: DatapointValue | None + Maximum: DatapointValue | None + Unit: StandardUnit | None + ExtendedStatistics: DatapointValueMap | None -DatapointValues = List[DatapointValue] -Datapoints = List[Datapoint] +DatapointValues = list[DatapointValue] +Datapoints = list[Datapoint] + + +class DeleteAlarmMuteRuleInput(ServiceRequest): + AlarmMuteRuleName: Name class DeleteAlarmsInput(ServiceRequest): @@ -466,12 +514,12 @@ class DeleteAlarmsInput(ServiceRequest): class DeleteAnomalyDetectorInput(ServiceRequest): - Namespace: Optional[Namespace] - MetricName: Optional[MetricName] - Dimensions: Optional[Dimensions] - Stat: Optional[AnomalyDetectorMetricStat] - SingleMetricAnomalyDetector: Optional[SingleMetricAnomalyDetector] - MetricMathAnomalyDetector: Optional[MetricMathAnomalyDetector] + Namespace: Namespace | None + MetricName: MetricName | None + Dimensions: Dimensions | None + Stat: AnomalyDetectorMetricStat | None + SingleMetricAnomalyDetector: SingleMetricAnomalyDetector | None + MetricMathAnomalyDetector: MetricMathAnomalyDetector | None class DeleteAnomalyDetectorOutput(TypedDict, total=False): @@ -486,7 +534,7 @@ class DeleteDashboardsOutput(TypedDict, total=False): pass -InsightRuleNames = List[InsightRuleName] +InsightRuleNames = list[InsightRuleName] class DeleteInsightRulesInput(ServiceRequest): @@ -494,7 +542,7 @@ class DeleteInsightRulesInput(ServiceRequest): class DeleteInsightRulesOutput(TypedDict, total=False): - Failures: Optional[BatchFailures] + Failures: BatchFailures | None class DeleteMetricStreamInput(ServiceRequest): @@ -505,106 +553,117 @@ class DeleteMetricStreamOutput(TypedDict, total=False): pass +class DescribeAlarmContributorsInput(ServiceRequest): + AlarmName: AlarmName + NextToken: NextToken | None + + +class DescribeAlarmContributorsOutput(TypedDict, total=False): + AlarmContributors: AlarmContributors + NextToken: NextToken | None + + class DescribeAlarmHistoryInput(ServiceRequest): - AlarmName: Optional[AlarmName] - AlarmTypes: Optional[AlarmTypes] - HistoryItemType: Optional[HistoryItemType] - StartDate: Optional[Timestamp] - EndDate: Optional[Timestamp] - MaxRecords: Optional[MaxRecords] - NextToken: Optional[NextToken] - ScanBy: Optional[ScanBy] + AlarmName: AlarmName | None + AlarmContributorId: ContributorId | None + AlarmTypes: AlarmTypes | None + HistoryItemType: HistoryItemType | None + StartDate: Timestamp | None + EndDate: Timestamp | None + MaxRecords: MaxRecords | None + NextToken: NextToken | None + ScanBy: ScanBy | None class DescribeAlarmHistoryOutput(TypedDict, total=False): - AlarmHistoryItems: Optional[AlarmHistoryItems] - NextToken: Optional[NextToken] + AlarmHistoryItems: AlarmHistoryItems | None + NextToken: NextToken | None class DescribeAlarmsForMetricInput(ServiceRequest): MetricName: MetricName Namespace: Namespace - Statistic: Optional[Statistic] - ExtendedStatistic: Optional[ExtendedStatistic] - Dimensions: Optional[Dimensions] - Period: Optional[Period] - Unit: Optional[StandardUnit] + Statistic: Statistic | None + ExtendedStatistic: ExtendedStatistic | None + Dimensions: Dimensions | None + Period: Period | None + Unit: StandardUnit | None class MetricAlarm(TypedDict, total=False): - AlarmName: Optional[AlarmName] - AlarmArn: Optional[AlarmArn] - AlarmDescription: Optional[AlarmDescription] - AlarmConfigurationUpdatedTimestamp: Optional[Timestamp] - ActionsEnabled: Optional[ActionsEnabled] - OKActions: Optional[ResourceList] - AlarmActions: Optional[ResourceList] - InsufficientDataActions: Optional[ResourceList] - StateValue: Optional[StateValue] - StateReason: Optional[StateReason] - StateReasonData: Optional[StateReasonData] - StateUpdatedTimestamp: Optional[Timestamp] - MetricName: Optional[MetricName] - Namespace: Optional[Namespace] - Statistic: Optional[Statistic] - ExtendedStatistic: Optional[ExtendedStatistic] - Dimensions: Optional[Dimensions] - Period: Optional[Period] - Unit: Optional[StandardUnit] - EvaluationPeriods: Optional[EvaluationPeriods] - DatapointsToAlarm: Optional[DatapointsToAlarm] - Threshold: Optional[Threshold] - ComparisonOperator: Optional[ComparisonOperator] - TreatMissingData: Optional[TreatMissingData] - EvaluateLowSampleCountPercentile: Optional[EvaluateLowSampleCountPercentile] - Metrics: Optional[MetricDataQueries] - ThresholdMetricId: Optional[MetricId] - EvaluationState: Optional[EvaluationState] - StateTransitionedTimestamp: Optional[Timestamp] - - -MetricAlarms = List[MetricAlarm] + AlarmName: AlarmName | None + AlarmArn: AlarmArn | None + AlarmDescription: AlarmDescription | None + AlarmConfigurationUpdatedTimestamp: Timestamp | None + ActionsEnabled: ActionsEnabled | None + OKActions: ResourceList | None + AlarmActions: ResourceList | None + InsufficientDataActions: ResourceList | None + StateValue: StateValue | None + StateReason: StateReason | None + StateReasonData: StateReasonData | None + StateUpdatedTimestamp: Timestamp | None + MetricName: MetricName | None + Namespace: Namespace | None + Statistic: Statistic | None + ExtendedStatistic: ExtendedStatistic | None + Dimensions: Dimensions | None + Period: Period | None + Unit: StandardUnit | None + EvaluationPeriods: EvaluationPeriods | None + DatapointsToAlarm: DatapointsToAlarm | None + Threshold: Threshold | None + ComparisonOperator: ComparisonOperator | None + TreatMissingData: TreatMissingData | None + EvaluateLowSampleCountPercentile: EvaluateLowSampleCountPercentile | None + Metrics: MetricDataQueries | None + ThresholdMetricId: MetricId | None + EvaluationState: EvaluationState | None + StateTransitionedTimestamp: Timestamp | None + + +MetricAlarms = list[MetricAlarm] class DescribeAlarmsForMetricOutput(TypedDict, total=False): - MetricAlarms: Optional[MetricAlarms] + MetricAlarms: MetricAlarms | None class DescribeAlarmsInput(ServiceRequest): - AlarmNames: Optional[AlarmNames] - AlarmNamePrefix: Optional[AlarmNamePrefix] - AlarmTypes: Optional[AlarmTypes] - ChildrenOfAlarmName: Optional[AlarmName] - ParentsOfAlarmName: Optional[AlarmName] - StateValue: Optional[StateValue] - ActionPrefix: Optional[ActionPrefix] - MaxRecords: Optional[MaxRecords] - NextToken: Optional[NextToken] + AlarmNames: AlarmNames | None + AlarmNamePrefix: AlarmNamePrefix | None + AlarmTypes: AlarmTypes | None + ChildrenOfAlarmName: AlarmName | None + ParentsOfAlarmName: AlarmName | None + StateValue: StateValue | None + ActionPrefix: ActionPrefix | None + MaxRecords: MaxRecords | None + NextToken: NextToken | None class DescribeAlarmsOutput(TypedDict, total=False): - CompositeAlarms: Optional[CompositeAlarms] - MetricAlarms: Optional[MetricAlarms] - NextToken: Optional[NextToken] + CompositeAlarms: CompositeAlarms | None + MetricAlarms: MetricAlarms | None + NextToken: NextToken | None class DescribeAnomalyDetectorsInput(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxReturnedResultsCount] - Namespace: Optional[Namespace] - MetricName: Optional[MetricName] - Dimensions: Optional[Dimensions] - AnomalyDetectorTypes: Optional[AnomalyDetectorTypes] + NextToken: NextToken | None + MaxResults: MaxReturnedResultsCount | None + Namespace: Namespace | None + MetricName: MetricName | None + Dimensions: Dimensions | None + AnomalyDetectorTypes: AnomalyDetectorTypes | None class DescribeAnomalyDetectorsOutput(TypedDict, total=False): - AnomalyDetectors: Optional[AnomalyDetectors] - NextToken: Optional[NextToken] + AnomalyDetectors: AnomalyDetectors | None + NextToken: NextToken | None class DescribeInsightRulesInput(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[InsightRuleMaxResults] + NextToken: NextToken | None + MaxResults: InsightRuleMaxResults | None class InsightRule(TypedDict, total=False): @@ -612,24 +671,24 @@ class InsightRule(TypedDict, total=False): State: InsightRuleState Schema: InsightRuleSchema Definition: InsightRuleDefinition - ManagedRule: Optional[InsightRuleIsManaged] - ApplyOnTransformedLogs: Optional[InsightRuleOnTransformedLogs] + ManagedRule: InsightRuleIsManaged | None + ApplyOnTransformedLogs: InsightRuleOnTransformedLogs | None -InsightRules = List[InsightRule] +InsightRules = list[InsightRule] class DescribeInsightRulesOutput(TypedDict, total=False): - NextToken: Optional[NextToken] - InsightRules: Optional[InsightRules] + NextToken: NextToken | None + InsightRules: InsightRules | None class DimensionFilter(TypedDict, total=False): Name: DimensionName - Value: Optional[DimensionValue] + Value: DimensionValue | None -DimensionFilters = List[DimensionFilter] +DimensionFilters = list[DimensionFilter] class DisableAlarmActionsInput(ServiceRequest): @@ -641,7 +700,7 @@ class DisableInsightRulesInput(ServiceRequest): class DisableInsightRulesOutput(TypedDict, total=False): - Failures: Optional[BatchFailures] + Failures: BatchFailures | None class EnableAlarmActionsInput(ServiceRequest): @@ -653,19 +712,19 @@ class EnableInsightRulesInput(ServiceRequest): class EnableInsightRulesOutput(TypedDict, total=False): - Failures: Optional[BatchFailures] + Failures: BatchFailures | None -EntityAttributesMap = Dict[EntityAttributesMapKeyString, EntityAttributesMapValueString] -EntityKeyAttributesMap = Dict[EntityKeyAttributesMapKeyString, EntityKeyAttributesMapValueString] +EntityAttributesMap = dict[EntityAttributesMapKeyString, EntityAttributesMapValueString] +EntityKeyAttributesMap = dict[EntityKeyAttributesMapKeyString, EntityKeyAttributesMapValueString] class Entity(TypedDict, total=False): - KeyAttributes: Optional[EntityKeyAttributesMap] - Attributes: Optional[EntityAttributesMap] + KeyAttributes: EntityKeyAttributesMap | None + Attributes: EntityAttributesMap | None -Values = List[DatapointValue] +Values = list[DatapointValue] class StatisticSet(TypedDict, total=False): @@ -677,26 +736,60 @@ class StatisticSet(TypedDict, total=False): class MetricDatum(TypedDict, total=False): MetricName: MetricName - Dimensions: Optional[Dimensions] - Timestamp: Optional[Timestamp] - Value: Optional[DatapointValue] - StatisticValues: Optional[StatisticSet] - Values: Optional[Values] - Counts: Optional[Counts] - Unit: Optional[StandardUnit] - StorageResolution: Optional[StorageResolution] + Dimensions: Dimensions | None + Timestamp: Timestamp | None + Value: DatapointValue | None + StatisticValues: StatisticSet | None + Values: Values | None + Counts: Counts | None + Unit: StandardUnit | None + StorageResolution: StorageResolution | None -MetricData = List[MetricDatum] +MetricData = list[MetricDatum] class EntityMetricData(TypedDict, total=False): - Entity: Optional[Entity] - MetricData: Optional[MetricData] + Entity: Entity | None + MetricData: MetricData | None + + +EntityMetricDataList = list[EntityMetricData] +ExtendedStatistics = list[ExtendedStatistic] + + +class GetAlarmMuteRuleInput(ServiceRequest): + AlarmMuteRuleName: Name + +MuteTargetAlarmNameList = list[Name] -EntityMetricDataList = List[EntityMetricData] -ExtendedStatistics = List[ExtendedStatistic] + +class MuteTargets(TypedDict, total=False): + AlarmNames: MuteTargetAlarmNameList + + +class Schedule(TypedDict, total=False): + Expression: Expression + Duration: Duration + Timezone: Timezone | None + + +class Rule(TypedDict, total=False): + Schedule: Schedule + + +class GetAlarmMuteRuleOutput(TypedDict, total=False): + Name: Name | None + AlarmMuteRuleArn: Arn | None + Description: AlarmDescription | None + Rule: Rule | None + MuteTargets: MuteTargets | None + StartDate: Timestamp | None + ExpireDate: Timestamp | None + Status: AlarmMuteRuleStatus | None + LastUpdatedTimestamp: Timestamp | None + MuteType: MuteType | None class GetDashboardInput(ServiceRequest): @@ -704,12 +797,12 @@ class GetDashboardInput(ServiceRequest): class GetDashboardOutput(TypedDict, total=False): - DashboardArn: Optional[DashboardArn] - DashboardBody: Optional[DashboardBody] - DashboardName: Optional[DashboardName] + DashboardArn: DashboardArn | None + DashboardBody: DashboardBody | None + DashboardName: DashboardName | None -InsightRuleMetricList = List[InsightRuleMetricName] +InsightRuleMetricList = list[InsightRuleMetricName] class GetInsightRuleReportInput(ServiceRequest): @@ -717,23 +810,23 @@ class GetInsightRuleReportInput(ServiceRequest): StartTime: Timestamp EndTime: Timestamp Period: Period - MaxContributorCount: Optional[InsightRuleUnboundInteger] - Metrics: Optional[InsightRuleMetricList] - OrderBy: Optional[InsightRuleOrderBy] + MaxContributorCount: InsightRuleUnboundInteger | None + Metrics: InsightRuleMetricList | None + OrderBy: InsightRuleOrderBy | None class InsightRuleMetricDatapoint(TypedDict, total=False): Timestamp: Timestamp - UniqueContributors: Optional[InsightRuleUnboundDouble] - MaxContributorValue: Optional[InsightRuleUnboundDouble] - SampleCount: Optional[InsightRuleUnboundDouble] - Average: Optional[InsightRuleUnboundDouble] - Sum: Optional[InsightRuleUnboundDouble] - Minimum: Optional[InsightRuleUnboundDouble] - Maximum: Optional[InsightRuleUnboundDouble] + UniqueContributors: InsightRuleUnboundDouble | None + MaxContributorValue: InsightRuleUnboundDouble | None + SampleCount: InsightRuleUnboundDouble | None + Average: InsightRuleUnboundDouble | None + Sum: InsightRuleUnboundDouble | None + Minimum: InsightRuleUnboundDouble | None + Maximum: InsightRuleUnboundDouble | None -InsightRuleMetricDatapoints = List[InsightRuleMetricDatapoint] +InsightRuleMetricDatapoints = list[InsightRuleMetricDatapoint] class InsightRuleContributorDatapoint(TypedDict, total=False): @@ -741,8 +834,8 @@ class InsightRuleContributorDatapoint(TypedDict, total=False): ApproximateValue: InsightRuleUnboundDouble -InsightRuleContributorDatapoints = List[InsightRuleContributorDatapoint] -InsightRuleContributorKeys = List[InsightRuleContributorKey] +InsightRuleContributorDatapoints = list[InsightRuleContributorDatapoint] +InsightRuleContributorKeys = list[InsightRuleContributorKey] class InsightRuleContributor(TypedDict, total=False): @@ -751,86 +844,86 @@ class InsightRuleContributor(TypedDict, total=False): Datapoints: InsightRuleContributorDatapoints -InsightRuleContributors = List[InsightRuleContributor] +InsightRuleContributors = list[InsightRuleContributor] InsightRuleUnboundLong = int -InsightRuleContributorKeyLabels = List[InsightRuleContributorKeyLabel] +InsightRuleContributorKeyLabels = list[InsightRuleContributorKeyLabel] class GetInsightRuleReportOutput(TypedDict, total=False): - KeyLabels: Optional[InsightRuleContributorKeyLabels] - AggregationStatistic: Optional[InsightRuleAggregationStatistic] - AggregateValue: Optional[InsightRuleUnboundDouble] - ApproximateUniqueCount: Optional[InsightRuleUnboundLong] - Contributors: Optional[InsightRuleContributors] - MetricDatapoints: Optional[InsightRuleMetricDatapoints] + KeyLabels: InsightRuleContributorKeyLabels | None + AggregationStatistic: InsightRuleAggregationStatistic | None + AggregateValue: InsightRuleUnboundDouble | None + ApproximateUniqueCount: InsightRuleUnboundLong | None + Contributors: InsightRuleContributors | None + MetricDatapoints: InsightRuleMetricDatapoints | None class LabelOptions(TypedDict, total=False): - Timezone: Optional[GetMetricDataLabelTimezone] + Timezone: GetMetricDataLabelTimezone | None class GetMetricDataInput(ServiceRequest): MetricDataQueries: MetricDataQueries StartTime: Timestamp EndTime: Timestamp - NextToken: Optional[NextToken] - ScanBy: Optional[ScanBy] - MaxDatapoints: Optional[GetMetricDataMaxDatapoints] - LabelOptions: Optional[LabelOptions] + NextToken: NextToken | None + ScanBy: ScanBy | None + MaxDatapoints: GetMetricDataMaxDatapoints | None + LabelOptions: LabelOptions | None class MessageData(TypedDict, total=False): - Code: Optional[MessageDataCode] - Value: Optional[MessageDataValue] + Code: MessageDataCode | None + Value: MessageDataValue | None -MetricDataResultMessages = List[MessageData] -Timestamps = List[Timestamp] +MetricDataResultMessages = list[MessageData] +Timestamps = list[Timestamp] class MetricDataResult(TypedDict, total=False): - Id: Optional[MetricId] - Label: Optional[MetricLabel] - Timestamps: Optional[Timestamps] - Values: Optional[DatapointValues] - StatusCode: Optional[StatusCode] - Messages: Optional[MetricDataResultMessages] + Id: MetricId | None + Label: MetricLabel | None + Timestamps: Timestamps | None + Values: DatapointValues | None + StatusCode: StatusCode | None + Messages: MetricDataResultMessages | None -MetricDataResults = List[MetricDataResult] +MetricDataResults = list[MetricDataResult] class GetMetricDataOutput(TypedDict, total=False): - MetricDataResults: Optional[MetricDataResults] - NextToken: Optional[NextToken] - Messages: Optional[MetricDataResultMessages] + MetricDataResults: MetricDataResults | None + NextToken: NextToken | None + Messages: MetricDataResultMessages | None -Statistics = List[Statistic] +Statistics = list[Statistic] class GetMetricStatisticsInput(ServiceRequest): Namespace: Namespace MetricName: MetricName - Dimensions: Optional[Dimensions] + Dimensions: Dimensions | None StartTime: Timestamp EndTime: Timestamp Period: Period - Statistics: Optional[Statistics] - ExtendedStatistics: Optional[ExtendedStatistics] - Unit: Optional[StandardUnit] + Statistics: Statistics | None + ExtendedStatistics: ExtendedStatistics | None + Unit: StandardUnit | None class GetMetricStatisticsOutput(TypedDict, total=False): - Label: Optional[MetricLabel] - Datapoints: Optional[Datapoints] + Label: MetricLabel | None + Datapoints: Datapoints | None class GetMetricStreamInput(ServiceRequest): Name: MetricStreamName -MetricStreamStatisticsAdditionalStatistics = List[MetricStreamStatistic] +MetricStreamStatisticsAdditionalStatistics = list[MetricStreamStatistic] class MetricStreamStatisticsMetric(TypedDict, total=False): @@ -838,7 +931,7 @@ class MetricStreamStatisticsMetric(TypedDict, total=False): MetricName: MetricName -MetricStreamStatisticsIncludeMetrics = List[MetricStreamStatisticsMetric] +MetricStreamStatisticsIncludeMetrics = list[MetricStreamStatisticsMetric] class MetricStreamStatisticsConfiguration(TypedDict, total=False): @@ -846,59 +939,71 @@ class MetricStreamStatisticsConfiguration(TypedDict, total=False): AdditionalStatistics: MetricStreamStatisticsAdditionalStatistics -MetricStreamStatisticsConfigurations = List[MetricStreamStatisticsConfiguration] -MetricStreamFilterMetricNames = List[MetricName] +MetricStreamStatisticsConfigurations = list[MetricStreamStatisticsConfiguration] +MetricStreamFilterMetricNames = list[MetricName] class MetricStreamFilter(TypedDict, total=False): - Namespace: Optional[Namespace] - MetricNames: Optional[MetricStreamFilterMetricNames] + Namespace: Namespace | None + MetricNames: MetricStreamFilterMetricNames | None -MetricStreamFilters = List[MetricStreamFilter] +MetricStreamFilters = list[MetricStreamFilter] class GetMetricStreamOutput(TypedDict, total=False): - Arn: Optional[AmazonResourceName] - Name: Optional[MetricStreamName] - IncludeFilters: Optional[MetricStreamFilters] - ExcludeFilters: Optional[MetricStreamFilters] - FirehoseArn: Optional[AmazonResourceName] - RoleArn: Optional[AmazonResourceName] - State: Optional[MetricStreamState] - CreationDate: Optional[Timestamp] - LastUpdateDate: Optional[Timestamp] - OutputFormat: Optional[MetricStreamOutputFormat] - StatisticsConfigurations: Optional[MetricStreamStatisticsConfigurations] - IncludeLinkedAccountsMetrics: Optional[IncludeLinkedAccountsMetrics] + Arn: AmazonResourceName | None + Name: MetricStreamName | None + IncludeFilters: MetricStreamFilters | None + ExcludeFilters: MetricStreamFilters | None + FirehoseArn: AmazonResourceName | None + RoleArn: AmazonResourceName | None + State: MetricStreamState | None + CreationDate: Timestamp | None + LastUpdateDate: Timestamp | None + OutputFormat: MetricStreamOutputFormat | None + StatisticsConfigurations: MetricStreamStatisticsConfigurations | None + IncludeLinkedAccountsMetrics: IncludeLinkedAccountsMetrics | None class GetMetricWidgetImageInput(ServiceRequest): MetricWidget: MetricWidget - OutputFormat: Optional[OutputFormat] + OutputFormat: OutputFormat | None MetricWidgetImage = bytes class GetMetricWidgetImageOutput(TypedDict, total=False): - MetricWidgetImage: Optional[MetricWidgetImage] + MetricWidgetImage: MetricWidgetImage | None + + +class ListAlarmMuteRulesInput(ServiceRequest): + AlarmName: Name | None + Statuses: AlarmMuteRuleStatuses | None + MaxRecords: MaxRecords | None + NextToken: NextToken | None + + +class ListAlarmMuteRulesOutput(TypedDict, total=False): + AlarmMuteRuleSummaries: AlarmMuteRuleSummaries | None + NextToken: NextToken | None class ListDashboardsInput(ServiceRequest): - DashboardNamePrefix: Optional[DashboardNamePrefix] - NextToken: Optional[NextToken] + DashboardNamePrefix: DashboardNamePrefix | None + NextToken: NextToken | None class ListDashboardsOutput(TypedDict, total=False): - DashboardEntries: Optional[DashboardEntries] - NextToken: Optional[NextToken] + DashboardEntries: DashboardEntries | None + NextToken: NextToken | None class ListManagedInsightRulesInput(ServiceRequest): ResourceARN: AmazonResourceName - NextToken: Optional[NextToken] - MaxResults: Optional[InsightRuleMaxResults] + NextToken: NextToken | None + MaxResults: InsightRuleMaxResults | None class ManagedRuleState(TypedDict, total=False): @@ -907,60 +1012,60 @@ class ManagedRuleState(TypedDict, total=False): class ManagedRuleDescription(TypedDict, total=False): - TemplateName: Optional[TemplateName] - ResourceARN: Optional[AmazonResourceName] - RuleState: Optional[ManagedRuleState] + TemplateName: TemplateName | None + ResourceARN: AmazonResourceName | None + RuleState: ManagedRuleState | None -ManagedRuleDescriptions = List[ManagedRuleDescription] +ManagedRuleDescriptions = list[ManagedRuleDescription] class ListManagedInsightRulesOutput(TypedDict, total=False): - ManagedRules: Optional[ManagedRuleDescriptions] - NextToken: Optional[NextToken] + ManagedRules: ManagedRuleDescriptions | None + NextToken: NextToken | None class ListMetricStreamsInput(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[ListMetricStreamsMaxResults] + NextToken: NextToken | None + MaxResults: ListMetricStreamsMaxResults | None class MetricStreamEntry(TypedDict, total=False): - Arn: Optional[AmazonResourceName] - CreationDate: Optional[Timestamp] - LastUpdateDate: Optional[Timestamp] - Name: Optional[MetricStreamName] - FirehoseArn: Optional[AmazonResourceName] - State: Optional[MetricStreamState] - OutputFormat: Optional[MetricStreamOutputFormat] + Arn: AmazonResourceName | None + CreationDate: Timestamp | None + LastUpdateDate: Timestamp | None + Name: MetricStreamName | None + FirehoseArn: AmazonResourceName | None + State: MetricStreamState | None + OutputFormat: MetricStreamOutputFormat | None -MetricStreamEntries = List[MetricStreamEntry] +MetricStreamEntries = list[MetricStreamEntry] class ListMetricStreamsOutput(TypedDict, total=False): - NextToken: Optional[NextToken] - Entries: Optional[MetricStreamEntries] + NextToken: NextToken | None + Entries: MetricStreamEntries | None class ListMetricsInput(ServiceRequest): - Namespace: Optional[Namespace] - MetricName: Optional[MetricName] - Dimensions: Optional[DimensionFilters] - NextToken: Optional[NextToken] - RecentlyActive: Optional[RecentlyActive] - IncludeLinkedAccounts: Optional[IncludeLinkedAccounts] - OwningAccount: Optional[AccountId] + Namespace: Namespace | None + MetricName: MetricName | None + Dimensions: DimensionFilters | None + NextToken: NextToken | None + RecentlyActive: RecentlyActive | None + IncludeLinkedAccounts: IncludeLinkedAccounts | None + OwningAccount: AccountId | None -OwningAccounts = List[AccountId] -Metrics = List[Metric] +OwningAccounts = list[AccountId] +Metrics = list[Metric] class ListMetricsOutput(TypedDict, total=False): - Metrics: Optional[Metrics] - NextToken: Optional[NextToken] - OwningAccounts: Optional[OwningAccounts] + Metrics: Metrics | None + NextToken: NextToken | None + OwningAccounts: OwningAccounts | None class ListTagsForResourceInput(ServiceRequest): @@ -972,32 +1077,42 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] class ListTagsForResourceOutput(TypedDict, total=False): - Tags: Optional[TagList] + Tags: TagList | None class ManagedRule(TypedDict, total=False): TemplateName: TemplateName ResourceARN: AmazonResourceName - Tags: Optional[TagList] + Tags: TagList | None + +ManagedRules = list[ManagedRule] +MetricStreamNames = list[MetricStreamName] -ManagedRules = List[ManagedRule] -MetricStreamNames = List[MetricStreamName] + +class PutAlarmMuteRuleInput(ServiceRequest): + Name: Name + Description: AlarmDescription | None + Rule: Rule + MuteTargets: MuteTargets | None + Tags: TagList | None + StartDate: Timestamp | None + ExpireDate: Timestamp | None class PutAnomalyDetectorInput(ServiceRequest): - Namespace: Optional[Namespace] - MetricName: Optional[MetricName] - Dimensions: Optional[Dimensions] - Stat: Optional[AnomalyDetectorMetricStat] - Configuration: Optional[AnomalyDetectorConfiguration] - MetricCharacteristics: Optional[MetricCharacteristics] - SingleMetricAnomalyDetector: Optional[SingleMetricAnomalyDetector] - MetricMathAnomalyDetector: Optional[MetricMathAnomalyDetector] + Namespace: Namespace | None + MetricName: MetricName | None + Dimensions: Dimensions | None + Stat: AnomalyDetectorMetricStat | None + Configuration: AnomalyDetectorConfiguration | None + MetricCharacteristics: MetricCharacteristics | None + SingleMetricAnomalyDetector: SingleMetricAnomalyDetector | None + MetricMathAnomalyDetector: MetricMathAnomalyDetector | None class PutAnomalyDetectorOutput(TypedDict, total=False): @@ -1005,17 +1120,17 @@ class PutAnomalyDetectorOutput(TypedDict, total=False): class PutCompositeAlarmInput(ServiceRequest): - ActionsEnabled: Optional[ActionsEnabled] - AlarmActions: Optional[ResourceList] - AlarmDescription: Optional[AlarmDescription] + ActionsEnabled: ActionsEnabled | None + AlarmActions: ResourceList | None + AlarmDescription: AlarmDescription | None AlarmName: AlarmName AlarmRule: AlarmRule - InsufficientDataActions: Optional[ResourceList] - OKActions: Optional[ResourceList] - Tags: Optional[TagList] - ActionsSuppressor: Optional[AlarmArn] - ActionsSuppressorWaitPeriod: Optional[SuppressorPeriod] - ActionsSuppressorExtensionPeriod: Optional[SuppressorPeriod] + InsufficientDataActions: ResourceList | None + OKActions: ResourceList | None + Tags: TagList | None + ActionsSuppressor: AlarmArn | None + ActionsSuppressorWaitPeriod: SuppressorPeriod | None + ActionsSuppressorExtensionPeriod: SuppressorPeriod | None class PutDashboardInput(ServiceRequest): @@ -1024,15 +1139,15 @@ class PutDashboardInput(ServiceRequest): class PutDashboardOutput(TypedDict, total=False): - DashboardValidationMessages: Optional[DashboardValidationMessages] + DashboardValidationMessages: DashboardValidationMessages | None class PutInsightRuleInput(ServiceRequest): RuleName: InsightRuleName - RuleState: Optional[InsightRuleState] + RuleState: InsightRuleState | None RuleDefinition: InsightRuleDefinition - Tags: Optional[TagList] - ApplyOnTransformedLogs: Optional[InsightRuleOnTransformedLogs] + Tags: TagList | None + ApplyOnTransformedLogs: InsightRuleOnTransformedLogs | None class PutInsightRuleOutput(TypedDict, total=False): @@ -1044,62 +1159,62 @@ class PutManagedInsightRulesInput(ServiceRequest): class PutManagedInsightRulesOutput(TypedDict, total=False): - Failures: Optional[BatchFailures] + Failures: BatchFailures | None class PutMetricAlarmInput(ServiceRequest): AlarmName: AlarmName - AlarmDescription: Optional[AlarmDescription] - ActionsEnabled: Optional[ActionsEnabled] - OKActions: Optional[ResourceList] - AlarmActions: Optional[ResourceList] - InsufficientDataActions: Optional[ResourceList] - MetricName: Optional[MetricName] - Namespace: Optional[Namespace] - Statistic: Optional[Statistic] - ExtendedStatistic: Optional[ExtendedStatistic] - Dimensions: Optional[Dimensions] - Period: Optional[Period] - Unit: Optional[StandardUnit] + AlarmDescription: AlarmDescription | None + ActionsEnabled: ActionsEnabled | None + OKActions: ResourceList | None + AlarmActions: ResourceList | None + InsufficientDataActions: ResourceList | None + MetricName: MetricName | None + Namespace: Namespace | None + Statistic: Statistic | None + ExtendedStatistic: ExtendedStatistic | None + Dimensions: Dimensions | None + Period: Period | None + Unit: StandardUnit | None EvaluationPeriods: EvaluationPeriods - DatapointsToAlarm: Optional[DatapointsToAlarm] - Threshold: Optional[Threshold] + DatapointsToAlarm: DatapointsToAlarm | None + Threshold: Threshold | None ComparisonOperator: ComparisonOperator - TreatMissingData: Optional[TreatMissingData] - EvaluateLowSampleCountPercentile: Optional[EvaluateLowSampleCountPercentile] - Metrics: Optional[MetricDataQueries] - Tags: Optional[TagList] - ThresholdMetricId: Optional[MetricId] + TreatMissingData: TreatMissingData | None + EvaluateLowSampleCountPercentile: EvaluateLowSampleCountPercentile | None + Metrics: MetricDataQueries | None + Tags: TagList | None + ThresholdMetricId: MetricId | None class PutMetricDataInput(ServiceRequest): Namespace: Namespace - MetricData: Optional[MetricData] - EntityMetricData: Optional[EntityMetricDataList] - StrictEntityValidation: Optional[StrictEntityValidation] + MetricData: MetricData | None + EntityMetricData: EntityMetricDataList | None + StrictEntityValidation: StrictEntityValidation | None class PutMetricStreamInput(ServiceRequest): Name: MetricStreamName - IncludeFilters: Optional[MetricStreamFilters] - ExcludeFilters: Optional[MetricStreamFilters] + IncludeFilters: MetricStreamFilters | None + ExcludeFilters: MetricStreamFilters | None FirehoseArn: AmazonResourceName RoleArn: AmazonResourceName OutputFormat: MetricStreamOutputFormat - Tags: Optional[TagList] - StatisticsConfigurations: Optional[MetricStreamStatisticsConfigurations] - IncludeLinkedAccountsMetrics: Optional[IncludeLinkedAccountsMetrics] + Tags: TagList | None + StatisticsConfigurations: MetricStreamStatisticsConfigurations | None + IncludeLinkedAccountsMetrics: IncludeLinkedAccountsMetrics | None class PutMetricStreamOutput(TypedDict, total=False): - Arn: Optional[AmazonResourceName] + Arn: AmazonResourceName | None class SetAlarmStateInput(ServiceRequest): AlarmName: AlarmName StateValue: StateValue StateReason: StateReason - StateReasonData: Optional[StateReasonData] + StateReasonData: StateReasonData | None class StartMetricStreamsInput(ServiceRequest): @@ -1118,7 +1233,7 @@ class StopMetricStreamsOutput(TypedDict, total=False): pass -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceInput(ServiceRequest): @@ -1140,8 +1255,14 @@ class UntagResourceOutput(TypedDict, total=False): class CloudwatchApi: - service = "cloudwatch" - version = "2010-08-01" + service: str = "cloudwatch" + version: str = "2010-08-01" + + @handler("DeleteAlarmMuteRule") + def delete_alarm_mute_rule( + self, context: RequestContext, alarm_mute_rule_name: Name, **kwargs + ) -> None: + raise NotImplementedError @handler("DeleteAlarms") def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwargs) -> None: @@ -1179,11 +1300,22 @@ def delete_metric_stream( ) -> DeleteMetricStreamOutput: raise NotImplementedError + @handler("DescribeAlarmContributors") + def describe_alarm_contributors( + self, + context: RequestContext, + alarm_name: AlarmName, + next_token: NextToken | None = None, + **kwargs, + ) -> DescribeAlarmContributorsOutput: + raise NotImplementedError + @handler("DescribeAlarmHistory") def describe_alarm_history( self, context: RequestContext, alarm_name: AlarmName | None = None, + alarm_contributor_id: ContributorId | None = None, alarm_types: AlarmTypes | None = None, history_item_type: HistoryItemType | None = None, start_date: Timestamp | None = None, @@ -1275,6 +1407,12 @@ def enable_insight_rules( ) -> EnableInsightRulesOutput: raise NotImplementedError + @handler("GetAlarmMuteRule") + def get_alarm_mute_rule( + self, context: RequestContext, alarm_mute_rule_name: Name, **kwargs + ) -> GetAlarmMuteRuleOutput: + raise NotImplementedError + @handler("GetDashboard") def get_dashboard( self, context: RequestContext, dashboard_name: DashboardName, **kwargs @@ -1344,6 +1482,18 @@ def get_metric_widget_image( ) -> GetMetricWidgetImageOutput: raise NotImplementedError + @handler("ListAlarmMuteRules") + def list_alarm_mute_rules( + self, + context: RequestContext, + alarm_name: Name | None = None, + statuses: AlarmMuteRuleStatuses | None = None, + max_records: MaxRecords | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> ListAlarmMuteRulesOutput: + raise NotImplementedError + @handler("ListDashboards") def list_dashboards( self, @@ -1396,6 +1546,21 @@ def list_tags_for_resource( ) -> ListTagsForResourceOutput: raise NotImplementedError + @handler("PutAlarmMuteRule") + def put_alarm_mute_rule( + self, + context: RequestContext, + name: Name, + rule: Rule, + description: AlarmDescription | None = None, + mute_targets: MuteTargets | None = None, + tags: TagList | None = None, + start_date: Timestamp | None = None, + expire_date: Timestamp | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + @handler("PutAnomalyDetector") def put_anomaly_detector( self, diff --git a/localstack-core/localstack/aws/api/config/__init__.py b/localstack-core/localstack/aws/api/config/__init__.py index 13e5026cd3aba..6e137a8b1ed42 100644 --- a/localstack-core/localstack/aws/api/config/__init__.py +++ b/localstack-core/localstack/aws/api/config/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -291,12 +291,16 @@ class RemediationExecutionState(StrEnum): IN_PROGRESS = "IN_PROGRESS" SUCCEEDED = "SUCCEEDED" FAILED = "FAILED" + UNKNOWN = "UNKNOWN" class RemediationExecutionStepState(StrEnum): SUCCEEDED = "SUCCEEDED" PENDING = "PENDING" FAILED = "FAILED" + IN_PROGRESS = "IN_PROGRESS" + EXITED = "EXITED" + UNKNOWN = "UNKNOWN" class RemediationTargetType(StrEnum): @@ -776,6 +780,99 @@ class ResourceType(StrEnum): AWS_SageMaker_InferenceExperiment = "AWS::SageMaker::InferenceExperiment" AWS_SecurityHub_Standard = "AWS::SecurityHub::Standard" AWS_Transfer_Profile = "AWS::Transfer::Profile" + AWS_CloudFormation_StackSet = "AWS::CloudFormation::StackSet" + AWS_MediaPackageV2_Channel = "AWS::MediaPackageV2::Channel" + AWS_S3_AccessGrantsLocation = "AWS::S3::AccessGrantsLocation" + AWS_S3_AccessGrant = "AWS::S3::AccessGrant" + AWS_S3_AccessGrantsInstance = "AWS::S3::AccessGrantsInstance" + AWS_EMRServerless_Application = "AWS::EMRServerless::Application" + AWS_Config_AggregationAuthorization = "AWS::Config::AggregationAuthorization" + AWS_Bedrock_ApplicationInferenceProfile = "AWS::Bedrock::ApplicationInferenceProfile" + AWS_ApiGatewayV2_Integration = "AWS::ApiGatewayV2::Integration" + AWS_SageMaker_MlflowTrackingServer = "AWS::SageMaker::MlflowTrackingServer" + AWS_SageMaker_ModelBiasJobDefinition = "AWS::SageMaker::ModelBiasJobDefinition" + AWS_SecretsManager_RotationSchedule = "AWS::SecretsManager::RotationSchedule" + AWS_Deadline_QueueFleetAssociation = "AWS::Deadline::QueueFleetAssociation" + AWS_ECR_RepositoryCreationTemplate = "AWS::ECR::RepositoryCreationTemplate" + AWS_CloudFormation_LambdaHook = "AWS::CloudFormation::LambdaHook" + AWS_EC2_SubnetNetworkAclAssociation = "AWS::EC2::SubnetNetworkAclAssociation" + AWS_ApiGateway_UsagePlan = "AWS::ApiGateway::UsagePlan" + AWS_AppConfig_Extension = "AWS::AppConfig::Extension" + AWS_Deadline_Fleet = "AWS::Deadline::Fleet" + AWS_EMR_Studio = "AWS::EMR::Studio" + AWS_S3Tables_TableBucket = "AWS::S3Tables::TableBucket" + AWS_CloudFront_RealtimeLogConfig = "AWS::CloudFront::RealtimeLogConfig" + AWS_BackupGateway_Hypervisor = "AWS::BackupGateway::Hypervisor" + AWS_BCMDataExports_Export = "AWS::BCMDataExports::Export" + AWS_CloudFormation_GuardHook = "AWS::CloudFormation::GuardHook" + AWS_CloudFront_PublicKey = "AWS::CloudFront::PublicKey" + AWS_CloudTrail_EventDataStore = "AWS::CloudTrail::EventDataStore" + AWS_EntityResolution_IdMappingWorkflow = "AWS::EntityResolution::IdMappingWorkflow" + AWS_EntityResolution_SchemaMapping = "AWS::EntityResolution::SchemaMapping" + AWS_IoT_DomainConfiguration = "AWS::IoT::DomainConfiguration" + AWS_PCAConnectorAD_DirectoryRegistration = "AWS::PCAConnectorAD::DirectoryRegistration" + AWS_RDS_Integration = "AWS::RDS::Integration" + AWS_Config_ConformancePack = "AWS::Config::ConformancePack" + AWS_RolesAnywhere_Profile = "AWS::RolesAnywhere::Profile" + AWS_CodeArtifact_Domain = "AWS::CodeArtifact::Domain" + AWS_Backup_RestoreTestingPlan = "AWS::Backup::RestoreTestingPlan" + AWS_Config_StoredQuery = "AWS::Config::StoredQuery" + AWS_SageMaker_DataQualityJobDefinition = "AWS::SageMaker::DataQualityJobDefinition" + AWS_SageMaker_ModelExplainabilityJobDefinition = ( + "AWS::SageMaker::ModelExplainabilityJobDefinition" + ) + AWS_SageMaker_ModelQualityJobDefinition = "AWS::SageMaker::ModelQualityJobDefinition" + AWS_SageMaker_StudioLifecycleConfig = "AWS::SageMaker::StudioLifecycleConfig" + AWS_SES_DedicatedIpPool = "AWS::SES::DedicatedIpPool" + AWS_SES_MailManagerTrafficPolicy = "AWS::SES::MailManagerTrafficPolicy" + AWS_SSM_ResourceDataSync = "AWS::SSM::ResourceDataSync" + AWS_BedrockAgentCore_Runtime = "AWS::BedrockAgentCore::Runtime" + AWS_BedrockAgentCore_BrowserCustom = "AWS::BedrockAgentCore::BrowserCustom" + AWS_ElasticLoadBalancingV2_TargetGroup = "AWS::ElasticLoadBalancingV2::TargetGroup" + AWS_EMRContainers_VirtualCluster = "AWS::EMRContainers::VirtualCluster" + AWS_EntityResolution_MatchingWorkflow = "AWS::EntityResolution::MatchingWorkflow" + AWS_IoTCoreDeviceAdvisor_SuiteDefinition = "AWS::IoTCoreDeviceAdvisor::SuiteDefinition" + AWS_EC2_SecurityGroupVpcAssociation = "AWS::EC2::SecurityGroupVpcAssociation" + AWS_EC2_VerifiedAccessInstance = "AWS::EC2::VerifiedAccessInstance" + AWS_KafkaConnect_CustomPlugin = "AWS::KafkaConnect::CustomPlugin" + AWS_NetworkManager_TransitGatewayPeering = "AWS::NetworkManager::TransitGatewayPeering" + AWS_OpenSearchServerless_SecurityConfig = "AWS::OpenSearchServerless::SecurityConfig" + AWS_Redshift_Integration = "AWS::Redshift::Integration" + AWS_RolesAnywhere_TrustAnchor = "AWS::RolesAnywhere::TrustAnchor" + AWS_Route53Profiles_ProfileAssociation = "AWS::Route53Profiles::ProfileAssociation" + AWS_SSMIncidents_ResponsePlan = "AWS::SSMIncidents::ResponsePlan" + AWS_Transfer_Server = "AWS::Transfer::Server" + AWS_Glue_Database = "AWS::Glue::Database" + AWS_Organizations_OrganizationalUnit = "AWS::Organizations::OrganizationalUnit" + AWS_EC2_IPAMPoolCidr = "AWS::EC2::IPAMPoolCidr" + AWS_EC2_VPCGatewayAttachment = "AWS::EC2::VPCGatewayAttachment" + AWS_Bedrock_Prompt = "AWS::Bedrock::Prompt" + AWS_Comprehend_Flywheel = "AWS::Comprehend::Flywheel" + AWS_DataSync_Agent = "AWS::DataSync::Agent" + AWS_MediaTailor_LiveSource = "AWS::MediaTailor::LiveSource" + AWS_MSK_ServerlessCluster = "AWS::MSK::ServerlessCluster" + AWS_IoTSiteWise_Asset = "AWS::IoTSiteWise::Asset" + AWS_B2BI_Capability = "AWS::B2BI::Capability" + AWS_CloudFront_KeyValueStore = "AWS::CloudFront::KeyValueStore" + AWS_Deadline_Monitor = "AWS::Deadline::Monitor" + AWS_GuardDuty_MalwareProtectionPlan = "AWS::GuardDuty::MalwareProtectionPlan" + AWS_Location_APIKey = "AWS::Location::APIKey" + AWS_MediaPackageV2_OriginEndpoint = "AWS::MediaPackageV2::OriginEndpoint" + AWS_PCAConnectorAD_Connector = "AWS::PCAConnectorAD::Connector" + AWS_S3Tables_TableBucketPolicy = "AWS::S3Tables::TableBucketPolicy" + AWS_SecretsManager_ResourcePolicy = "AWS::SecretsManager::ResourcePolicy" + AWS_SSMContacts_Contact = "AWS::SSMContacts::Contact" + AWS_IoT_ThingGroup = "AWS::IoT::ThingGroup" + AWS_ImageBuilder_LifecyclePolicy = "AWS::ImageBuilder::LifecyclePolicy" + AWS_GameLift_Build = "AWS::GameLift::Build" + AWS_ECR_ReplicationConfiguration = "AWS::ECR::ReplicationConfiguration" + AWS_EC2_SubnetCidrBlock = "AWS::EC2::SubnetCidrBlock" + AWS_Connect_SecurityProfile = "AWS::Connect::SecurityProfile" + AWS_CleanRoomsML_TrainingDataset = "AWS::CleanRoomsML::TrainingDataset" + AWS_AppStream_AppBlockBuilder = "AWS::AppStream::AppBlockBuilder" + AWS_Route53_DNSSEC = "AWS::Route53::DNSSEC" + AWS_SageMaker_UserProfile = "AWS::SageMaker::UserProfile" + AWS_ApiGateway_Method = "AWS::ApiGateway::Method" class ResourceValueType(StrEnum): @@ -1127,120 +1224,120 @@ class ValidationException(ServiceException): status_code: int = 400 -AggregatorRegionList = List[String] -AccountAggregationSourceAccountList = List[AccountId] +AggregatorRegionList = list[String] +AccountAggregationSourceAccountList = list[AccountId] class AccountAggregationSource(TypedDict, total=False): AccountIds: AccountAggregationSourceAccountList - AllAwsRegions: Optional[Boolean] - AwsRegions: Optional[AggregatorRegionList] + AllAwsRegions: Boolean | None + AwsRegions: AggregatorRegionList | None -AccountAggregationSourceList = List[AccountAggregationSource] +AccountAggregationSourceList = list[AccountAggregationSource] class ComplianceContributorCount(TypedDict, total=False): - CappedCount: Optional[Integer] - CapExceeded: Optional[Boolean] + CappedCount: Integer | None + CapExceeded: Boolean | None class Compliance(TypedDict, total=False): - ComplianceType: Optional[ComplianceType] - ComplianceContributorCount: Optional[ComplianceContributorCount] + ComplianceType: ComplianceType | None + ComplianceContributorCount: ComplianceContributorCount | None class AggregateComplianceByConfigRule(TypedDict, total=False): - ConfigRuleName: Optional[ConfigRuleName] - Compliance: Optional[Compliance] - AccountId: Optional[AccountId] - AwsRegion: Optional[AwsRegion] + ConfigRuleName: ConfigRuleName | None + Compliance: Compliance | None + AccountId: AccountId | None + AwsRegion: AwsRegion | None -AggregateComplianceByConfigRuleList = List[AggregateComplianceByConfigRule] +AggregateComplianceByConfigRuleList = list[AggregateComplianceByConfigRule] class AggregateConformancePackCompliance(TypedDict, total=False): - ComplianceType: Optional[ConformancePackComplianceType] - CompliantRuleCount: Optional[Integer] - NonCompliantRuleCount: Optional[Integer] - TotalRuleCount: Optional[Integer] + ComplianceType: ConformancePackComplianceType | None + CompliantRuleCount: Integer | None + NonCompliantRuleCount: Integer | None + TotalRuleCount: Integer | None class AggregateComplianceByConformancePack(TypedDict, total=False): - ConformancePackName: Optional[ConformancePackName] - Compliance: Optional[AggregateConformancePackCompliance] - AccountId: Optional[AccountId] - AwsRegion: Optional[AwsRegion] + ConformancePackName: ConformancePackName | None + Compliance: AggregateConformancePackCompliance | None + AccountId: AccountId | None + AwsRegion: AwsRegion | None -AggregateComplianceByConformancePackList = List[AggregateComplianceByConformancePack] +AggregateComplianceByConformancePackList = list[AggregateComplianceByConformancePack] Date = datetime class ComplianceSummary(TypedDict, total=False): - CompliantResourceCount: Optional[ComplianceContributorCount] - NonCompliantResourceCount: Optional[ComplianceContributorCount] - ComplianceSummaryTimestamp: Optional[Date] + CompliantResourceCount: ComplianceContributorCount | None + NonCompliantResourceCount: ComplianceContributorCount | None + ComplianceSummaryTimestamp: Date | None class AggregateComplianceCount(TypedDict, total=False): - GroupName: Optional[StringWithCharLimit256] - ComplianceSummary: Optional[ComplianceSummary] + GroupName: StringWithCharLimit256 | None + ComplianceSummary: ComplianceSummary | None -AggregateComplianceCountList = List[AggregateComplianceCount] +AggregateComplianceCountList = list[AggregateComplianceCount] class AggregateConformancePackComplianceCount(TypedDict, total=False): - CompliantConformancePackCount: Optional[Integer] - NonCompliantConformancePackCount: Optional[Integer] + CompliantConformancePackCount: Integer | None + NonCompliantConformancePackCount: Integer | None class AggregateConformancePackComplianceFilters(TypedDict, total=False): - ConformancePackName: Optional[ConformancePackName] - ComplianceType: Optional[ConformancePackComplianceType] - AccountId: Optional[AccountId] - AwsRegion: Optional[AwsRegion] + ConformancePackName: ConformancePackName | None + ComplianceType: ConformancePackComplianceType | None + AccountId: AccountId | None + AwsRegion: AwsRegion | None class AggregateConformancePackComplianceSummary(TypedDict, total=False): - ComplianceSummary: Optional[AggregateConformancePackComplianceCount] - GroupName: Optional[StringWithCharLimit256] + ComplianceSummary: AggregateConformancePackComplianceCount | None + GroupName: StringWithCharLimit256 | None class AggregateConformancePackComplianceSummaryFilters(TypedDict, total=False): - AccountId: Optional[AccountId] - AwsRegion: Optional[AwsRegion] + AccountId: AccountId | None + AwsRegion: AwsRegion | None -AggregateConformancePackComplianceSummaryList = List[AggregateConformancePackComplianceSummary] +AggregateConformancePackComplianceSummaryList = list[AggregateConformancePackComplianceSummary] class EvaluationResultQualifier(TypedDict, total=False): - ConfigRuleName: Optional[ConfigRuleName] - ResourceType: Optional[StringWithCharLimit256] - ResourceId: Optional[BaseResourceId] - EvaluationMode: Optional[EvaluationMode] + ConfigRuleName: ConfigRuleName | None + ResourceType: StringWithCharLimit256 | None + ResourceId: BaseResourceId | None + EvaluationMode: EvaluationMode | None class EvaluationResultIdentifier(TypedDict, total=False): - EvaluationResultQualifier: Optional[EvaluationResultQualifier] - OrderingTimestamp: Optional[Date] - ResourceEvaluationId: Optional[ResourceEvaluationId] + EvaluationResultQualifier: EvaluationResultQualifier | None + OrderingTimestamp: Date | None + ResourceEvaluationId: ResourceEvaluationId | None class AggregateEvaluationResult(TypedDict, total=False): - EvaluationResultIdentifier: Optional[EvaluationResultIdentifier] - ComplianceType: Optional[ComplianceType] - ResultRecordedTime: Optional[Date] - ConfigRuleInvokedTime: Optional[Date] - Annotation: Optional[StringWithCharLimit256] - AccountId: Optional[AccountId] - AwsRegion: Optional[AwsRegion] + EvaluationResultIdentifier: EvaluationResultIdentifier | None + ComplianceType: ComplianceType | None + ResultRecordedTime: Date | None + ConfigRuleInvokedTime: Date | None + Annotation: StringWithCharLimit256 | None + AccountId: AccountId | None + AwsRegion: AwsRegion | None -AggregateEvaluationResultList = List[AggregateEvaluationResult] +AggregateEvaluationResultList = list[AggregateEvaluationResult] class AggregateResourceIdentifier(TypedDict, total=False): @@ -1248,53 +1345,53 @@ class AggregateResourceIdentifier(TypedDict, total=False): SourceRegion: AwsRegion ResourceId: ResourceId ResourceType: ResourceType - ResourceName: Optional[ResourceName] + ResourceName: ResourceName | None class AggregatedSourceStatus(TypedDict, total=False): - SourceId: Optional[String] - SourceType: Optional[AggregatedSourceType] - AwsRegion: Optional[AwsRegion] - LastUpdateStatus: Optional[AggregatedSourceStatusType] - LastUpdateTime: Optional[Date] - LastErrorCode: Optional[String] - LastErrorMessage: Optional[String] + SourceId: String | None + SourceType: AggregatedSourceType | None + AwsRegion: AwsRegion | None + LastUpdateStatus: AggregatedSourceStatusType | None + LastUpdateTime: Date | None + LastErrorCode: String | None + LastErrorMessage: String | None -AggregatedSourceStatusList = List[AggregatedSourceStatus] -AggregatedSourceStatusTypeList = List[AggregatedSourceStatusType] +AggregatedSourceStatusList = list[AggregatedSourceStatus] +AggregatedSourceStatusTypeList = list[AggregatedSourceStatusType] class AggregationAuthorization(TypedDict, total=False): - AggregationAuthorizationArn: Optional[String] - AuthorizedAccountId: Optional[AccountId] - AuthorizedAwsRegion: Optional[AwsRegion] - CreationTime: Optional[Date] + AggregationAuthorizationArn: String | None + AuthorizedAccountId: AccountId | None + AuthorizedAwsRegion: AwsRegion | None + CreationTime: Date | None -AggregationAuthorizationList = List[AggregationAuthorization] -ResourceTypeValueList = List[ResourceTypeValue] +AggregationAuthorizationList = list[AggregationAuthorization] +ResourceTypeValueList = list[ResourceTypeValue] class AggregatorFilterResourceType(TypedDict, total=False): - Type: Optional[AggregatorFilterType] - Value: Optional[ResourceTypeValueList] + Type: AggregatorFilterType | None + Value: ResourceTypeValueList | None -ServicePrincipalValueList = List[ServicePrincipalValue] +ServicePrincipalValueList = list[ServicePrincipalValue] class AggregatorFilterServicePrincipal(TypedDict, total=False): - Type: Optional[AggregatorFilterType] - Value: Optional[ServicePrincipalValueList] + Type: AggregatorFilterType | None + Value: ServicePrincipalValueList | None class AggregatorFilters(TypedDict, total=False): - ResourceType: Optional[AggregatorFilterResourceType] - ServicePrincipal: Optional[AggregatorFilterServicePrincipal] + ResourceType: AggregatorFilterResourceType | None + ServicePrincipal: AggregatorFilterServicePrincipal | None -ResourceTypeList = List[ResourceType] +ResourceTypeList = list[ResourceType] class AssociateResourceTypesRequest(ServiceRequest): @@ -1302,47 +1399,47 @@ class AssociateResourceTypesRequest(ServiceRequest): ResourceTypes: ResourceTypeList -RecordingModeResourceTypesList = List[ResourceType] +RecordingModeResourceTypesList = list[ResourceType] class RecordingModeOverride(TypedDict, total=False): - description: Optional[Description] + description: Description | None resourceTypes: RecordingModeResourceTypesList recordingFrequency: RecordingFrequency -RecordingModeOverrides = List[RecordingModeOverride] +RecordingModeOverrides = list[RecordingModeOverride] class RecordingMode(TypedDict, total=False): recordingFrequency: RecordingFrequency - recordingModeOverrides: Optional[RecordingModeOverrides] + recordingModeOverrides: RecordingModeOverrides | None class RecordingStrategy(TypedDict, total=False): - useOnly: Optional[RecordingStrategyType] + useOnly: RecordingStrategyType | None class ExclusionByResourceTypes(TypedDict, total=False): - resourceTypes: Optional[ResourceTypeList] + resourceTypes: ResourceTypeList | None class RecordingGroup(TypedDict, total=False): - allSupported: Optional[AllSupported] - includeGlobalResourceTypes: Optional[IncludeGlobalResourceTypes] - resourceTypes: Optional[ResourceTypeList] - exclusionByResourceTypes: Optional[ExclusionByResourceTypes] - recordingStrategy: Optional[RecordingStrategy] + allSupported: AllSupported | None + includeGlobalResourceTypes: IncludeGlobalResourceTypes | None + resourceTypes: ResourceTypeList | None + exclusionByResourceTypes: ExclusionByResourceTypes | None + recordingStrategy: RecordingStrategy | None class ConfigurationRecorder(TypedDict, total=False): - arn: Optional[AmazonResourceName] - name: Optional[RecorderName] - roleARN: Optional[String] - recordingGroup: Optional[RecordingGroup] - recordingMode: Optional[RecordingMode] - recordingScope: Optional[RecordingScope] - servicePrincipal: Optional[ServicePrincipal] + arn: AmazonResourceName | None + name: RecorderName | None + roleARN: String | None + recordingGroup: RecordingGroup | None + recordingMode: RecordingMode | None + recordingScope: RecordingScope | None + servicePrincipal: ServicePrincipal | None class AssociateResourceTypesResponse(TypedDict, total=False): @@ -1351,32 +1448,32 @@ class AssociateResourceTypesResponse(TypedDict, total=False): AutoRemediationAttemptSeconds = int ConfigurationItemDeliveryTime = datetime -SupplementaryConfiguration = Dict[SupplementaryConfigurationName, SupplementaryConfigurationValue] +SupplementaryConfiguration = dict[SupplementaryConfigurationName, SupplementaryConfigurationValue] ResourceCreationTime = datetime ConfigurationItemCaptureTime = datetime class BaseConfigurationItem(TypedDict, total=False): - version: Optional[Version] - accountId: Optional[AccountId] - configurationItemCaptureTime: Optional[ConfigurationItemCaptureTime] - configurationItemStatus: Optional[ConfigurationItemStatus] - configurationStateId: Optional[ConfigurationStateId] - arn: Optional[ARN] - resourceType: Optional[ResourceType] - resourceId: Optional[ResourceId] - resourceName: Optional[ResourceName] - awsRegion: Optional[AwsRegion] - availabilityZone: Optional[AvailabilityZone] - resourceCreationTime: Optional[ResourceCreationTime] - configuration: Optional[Configuration] - supplementaryConfiguration: Optional[SupplementaryConfiguration] - recordingFrequency: Optional[RecordingFrequency] - configurationItemDeliveryTime: Optional[ConfigurationItemDeliveryTime] - - -BaseConfigurationItems = List[BaseConfigurationItem] -ResourceIdentifiersList = List[AggregateResourceIdentifier] + version: Version | None + accountId: AccountId | None + configurationItemCaptureTime: ConfigurationItemCaptureTime | None + configurationItemStatus: ConfigurationItemStatus | None + configurationStateId: ConfigurationStateId | None + arn: ARN | None + resourceType: ResourceType | None + resourceId: ResourceId | None + resourceName: ResourceName | None + awsRegion: AwsRegion | None + availabilityZone: AvailabilityZone | None + resourceCreationTime: ResourceCreationTime | None + configuration: Configuration | None + supplementaryConfiguration: SupplementaryConfiguration | None + recordingFrequency: RecordingFrequency | None + configurationItemDeliveryTime: ConfigurationItemDeliveryTime | None + + +BaseConfigurationItems = list[BaseConfigurationItem] +ResourceIdentifiersList = list[AggregateResourceIdentifier] class BatchGetAggregateResourceConfigRequest(ServiceRequest): @@ -1384,12 +1481,12 @@ class BatchGetAggregateResourceConfigRequest(ServiceRequest): ResourceIdentifiers: ResourceIdentifiersList -UnprocessedResourceIdentifierList = List[AggregateResourceIdentifier] +UnprocessedResourceIdentifierList = list[AggregateResourceIdentifier] class BatchGetAggregateResourceConfigResponse(TypedDict, total=False): - BaseConfigurationItems: Optional[BaseConfigurationItems] - UnprocessedResourceIdentifiers: Optional[UnprocessedResourceIdentifierList] + BaseConfigurationItems: BaseConfigurationItems | None + UnprocessedResourceIdentifiers: UnprocessedResourceIdentifierList | None class ResourceKey(TypedDict, total=False): @@ -1397,7 +1494,7 @@ class ResourceKey(TypedDict, total=False): resourceId: ResourceId -ResourceKeys = List[ResourceKey] +ResourceKeys = list[ResourceKey] class BatchGetResourceConfigRequest(ServiceRequest): @@ -1405,256 +1502,256 @@ class BatchGetResourceConfigRequest(ServiceRequest): class BatchGetResourceConfigResponse(TypedDict, total=False): - baseConfigurationItems: Optional[BaseConfigurationItems] - unprocessedResourceKeys: Optional[ResourceKeys] + baseConfigurationItems: BaseConfigurationItems | None + unprocessedResourceKeys: ResourceKeys | None class ComplianceByConfigRule(TypedDict, total=False): - ConfigRuleName: Optional[StringWithCharLimit64] - Compliance: Optional[Compliance] + ConfigRuleName: StringWithCharLimit64 | None + Compliance: Compliance | None -ComplianceByConfigRules = List[ComplianceByConfigRule] +ComplianceByConfigRules = list[ComplianceByConfigRule] class ComplianceByResource(TypedDict, total=False): - ResourceType: Optional[StringWithCharLimit256] - ResourceId: Optional[BaseResourceId] - Compliance: Optional[Compliance] + ResourceType: StringWithCharLimit256 | None + ResourceId: BaseResourceId | None + Compliance: Compliance | None -ComplianceByResources = List[ComplianceByResource] -ComplianceResourceTypes = List[StringWithCharLimit256] +ComplianceByResources = list[ComplianceByResource] +ComplianceResourceTypes = list[StringWithCharLimit256] class ComplianceSummaryByResourceType(TypedDict, total=False): - ResourceType: Optional[StringWithCharLimit256] - ComplianceSummary: Optional[ComplianceSummary] + ResourceType: StringWithCharLimit256 | None + ComplianceSummary: ComplianceSummary | None -ComplianceSummariesByResourceType = List[ComplianceSummaryByResourceType] -ComplianceTypes = List[ComplianceType] +ComplianceSummariesByResourceType = list[ComplianceSummaryByResourceType] +ComplianceTypes = list[ComplianceType] class ConfigExportDeliveryInfo(TypedDict, total=False): - lastStatus: Optional[DeliveryStatus] - lastErrorCode: Optional[String] - lastErrorMessage: Optional[String] - lastAttemptTime: Optional[Date] - lastSuccessfulTime: Optional[Date] - nextDeliveryTime: Optional[Date] + lastStatus: DeliveryStatus | None + lastErrorCode: String | None + lastErrorMessage: String | None + lastAttemptTime: Date | None + lastSuccessfulTime: Date | None + nextDeliveryTime: Date | None class EvaluationModeConfiguration(TypedDict, total=False): - Mode: Optional[EvaluationMode] + Mode: EvaluationMode | None -EvaluationModes = List[EvaluationModeConfiguration] +EvaluationModes = list[EvaluationModeConfiguration] class CustomPolicyDetails(TypedDict, total=False): PolicyRuntime: PolicyRuntime PolicyText: PolicyText - EnableDebugLogDelivery: Optional[Boolean] + EnableDebugLogDelivery: Boolean | None class SourceDetail(TypedDict, total=False): - EventSource: Optional[EventSource] - MessageType: Optional[MessageType] - MaximumExecutionFrequency: Optional[MaximumExecutionFrequency] + EventSource: EventSource | None + MessageType: MessageType | None + MaximumExecutionFrequency: MaximumExecutionFrequency | None -SourceDetails = List[SourceDetail] +SourceDetails = list[SourceDetail] class Source(TypedDict, total=False): Owner: Owner - SourceIdentifier: Optional[StringWithCharLimit256] - SourceDetails: Optional[SourceDetails] - CustomPolicyDetails: Optional[CustomPolicyDetails] + SourceIdentifier: StringWithCharLimit256 | None + SourceDetails: SourceDetails | None + CustomPolicyDetails: CustomPolicyDetails | None class Scope(TypedDict, total=False): - ComplianceResourceTypes: Optional[ComplianceResourceTypes] - TagKey: Optional[StringWithCharLimit128] - TagValue: Optional[StringWithCharLimit256] - ComplianceResourceId: Optional[BaseResourceId] + ComplianceResourceTypes: ComplianceResourceTypes | None + TagKey: StringWithCharLimit128 | None + TagValue: StringWithCharLimit256 | None + ComplianceResourceId: BaseResourceId | None class ConfigRule(TypedDict, total=False): - ConfigRuleName: Optional[ConfigRuleName] - ConfigRuleArn: Optional[StringWithCharLimit256] - ConfigRuleId: Optional[StringWithCharLimit64] - Description: Optional[EmptiableStringWithCharLimit256] - Scope: Optional[Scope] + ConfigRuleName: ConfigRuleName | None + ConfigRuleArn: StringWithCharLimit256 | None + ConfigRuleId: StringWithCharLimit64 | None + Description: EmptiableStringWithCharLimit256 | None + Scope: Scope | None Source: Source - InputParameters: Optional[StringWithCharLimit1024] - MaximumExecutionFrequency: Optional[MaximumExecutionFrequency] - ConfigRuleState: Optional[ConfigRuleState] - CreatedBy: Optional[StringWithCharLimit256] - EvaluationModes: Optional[EvaluationModes] + InputParameters: StringWithCharLimit1024 | None + MaximumExecutionFrequency: MaximumExecutionFrequency | None + ConfigRuleState: ConfigRuleState | None + CreatedBy: StringWithCharLimit256 | None + EvaluationModes: EvaluationModes | None class ConfigRuleComplianceFilters(TypedDict, total=False): - ConfigRuleName: Optional[ConfigRuleName] - ComplianceType: Optional[ComplianceType] - AccountId: Optional[AccountId] - AwsRegion: Optional[AwsRegion] + ConfigRuleName: ConfigRuleName | None + ComplianceType: ComplianceType | None + AccountId: AccountId | None + AwsRegion: AwsRegion | None class ConfigRuleComplianceSummaryFilters(TypedDict, total=False): - AccountId: Optional[AccountId] - AwsRegion: Optional[AwsRegion] + AccountId: AccountId | None + AwsRegion: AwsRegion | None class ConfigRuleEvaluationStatus(TypedDict, total=False): - ConfigRuleName: Optional[ConfigRuleName] - ConfigRuleArn: Optional[String] - ConfigRuleId: Optional[String] - LastSuccessfulInvocationTime: Optional[Date] - LastFailedInvocationTime: Optional[Date] - LastSuccessfulEvaluationTime: Optional[Date] - LastFailedEvaluationTime: Optional[Date] - FirstActivatedTime: Optional[Date] - LastDeactivatedTime: Optional[Date] - LastErrorCode: Optional[String] - LastErrorMessage: Optional[String] - FirstEvaluationStarted: Optional[Boolean] - LastDebugLogDeliveryStatus: Optional[String] - LastDebugLogDeliveryStatusReason: Optional[String] - LastDebugLogDeliveryTime: Optional[Date] - - -ConfigRuleEvaluationStatusList = List[ConfigRuleEvaluationStatus] -ConfigRuleNames = List[ConfigRuleName] -ConfigRules = List[ConfigRule] + ConfigRuleName: ConfigRuleName | None + ConfigRuleArn: String | None + ConfigRuleId: String | None + LastSuccessfulInvocationTime: Date | None + LastFailedInvocationTime: Date | None + LastSuccessfulEvaluationTime: Date | None + LastFailedEvaluationTime: Date | None + FirstActivatedTime: Date | None + LastDeactivatedTime: Date | None + LastErrorCode: String | None + LastErrorMessage: String | None + FirstEvaluationStarted: Boolean | None + LastDebugLogDeliveryStatus: String | None + LastDebugLogDeliveryStatusReason: String | None + LastDebugLogDeliveryTime: Date | None + + +ConfigRuleEvaluationStatusList = list[ConfigRuleEvaluationStatus] +ConfigRuleNames = list[ConfigRuleName] +ConfigRules = list[ConfigRule] class ConfigSnapshotDeliveryProperties(TypedDict, total=False): - deliveryFrequency: Optional[MaximumExecutionFrequency] + deliveryFrequency: MaximumExecutionFrequency | None class ConfigStreamDeliveryInfo(TypedDict, total=False): - lastStatus: Optional[DeliveryStatus] - lastErrorCode: Optional[String] - lastErrorMessage: Optional[String] - lastStatusChangeTime: Optional[Date] + lastStatus: DeliveryStatus | None + lastErrorCode: String | None + lastErrorMessage: String | None + lastStatusChangeTime: Date | None class OrganizationAggregationSource(TypedDict, total=False): RoleArn: String - AwsRegions: Optional[AggregatorRegionList] - AllAwsRegions: Optional[Boolean] + AwsRegions: AggregatorRegionList | None + AllAwsRegions: Boolean | None class ConfigurationAggregator(TypedDict, total=False): - ConfigurationAggregatorName: Optional[ConfigurationAggregatorName] - ConfigurationAggregatorArn: Optional[ConfigurationAggregatorArn] - AccountAggregationSources: Optional[AccountAggregationSourceList] - OrganizationAggregationSource: Optional[OrganizationAggregationSource] - CreationTime: Optional[Date] - LastUpdatedTime: Optional[Date] - CreatedBy: Optional[StringWithCharLimit256] - AggregatorFilters: Optional[AggregatorFilters] + ConfigurationAggregatorName: ConfigurationAggregatorName | None + ConfigurationAggregatorArn: ConfigurationAggregatorArn | None + AccountAggregationSources: AccountAggregationSourceList | None + OrganizationAggregationSource: OrganizationAggregationSource | None + CreationTime: Date | None + LastUpdatedTime: Date | None + CreatedBy: StringWithCharLimit256 | None + AggregatorFilters: AggregatorFilters | None -ConfigurationAggregatorList = List[ConfigurationAggregator] -ConfigurationAggregatorNameList = List[ConfigurationAggregatorName] +ConfigurationAggregatorList = list[ConfigurationAggregator] +ConfigurationAggregatorNameList = list[ConfigurationAggregatorName] class Relationship(TypedDict, total=False): - resourceType: Optional[ResourceType] - resourceId: Optional[ResourceId] - resourceName: Optional[ResourceName] - relationshipName: Optional[RelationshipName] + resourceType: ResourceType | None + resourceId: ResourceId | None + resourceName: ResourceName | None + relationshipName: RelationshipName | None -RelationshipList = List[Relationship] -RelatedEventList = List[RelatedEvent] -Tags = Dict[Name, Value] +RelationshipList = list[Relationship] +RelatedEventList = list[RelatedEvent] +Tags = dict[Name, Value] class ConfigurationItem(TypedDict, total=False): - version: Optional[Version] - accountId: Optional[AccountId] - configurationItemCaptureTime: Optional[ConfigurationItemCaptureTime] - configurationItemStatus: Optional[ConfigurationItemStatus] - configurationStateId: Optional[ConfigurationStateId] - configurationItemMD5Hash: Optional[ConfigurationItemMD5Hash] - arn: Optional[ARN] - resourceType: Optional[ResourceType] - resourceId: Optional[ResourceId] - resourceName: Optional[ResourceName] - awsRegion: Optional[AwsRegion] - availabilityZone: Optional[AvailabilityZone] - resourceCreationTime: Optional[ResourceCreationTime] - tags: Optional[Tags] - relatedEvents: Optional[RelatedEventList] - relationships: Optional[RelationshipList] - configuration: Optional[Configuration] - supplementaryConfiguration: Optional[SupplementaryConfiguration] - recordingFrequency: Optional[RecordingFrequency] - configurationItemDeliveryTime: Optional[ConfigurationItemDeliveryTime] - - -ConfigurationItemList = List[ConfigurationItem] -ConfigurationRecorderFilterValues = List[ConfigurationRecorderFilterValue] + version: Version | None + accountId: AccountId | None + configurationItemCaptureTime: ConfigurationItemCaptureTime | None + configurationItemStatus: ConfigurationItemStatus | None + configurationStateId: ConfigurationStateId | None + configurationItemMD5Hash: ConfigurationItemMD5Hash | None + arn: ARN | None + resourceType: ResourceType | None + resourceId: ResourceId | None + resourceName: ResourceName | None + awsRegion: AwsRegion | None + availabilityZone: AvailabilityZone | None + resourceCreationTime: ResourceCreationTime | None + tags: Tags | None + relatedEvents: RelatedEventList | None + relationships: RelationshipList | None + configuration: Configuration | None + supplementaryConfiguration: SupplementaryConfiguration | None + recordingFrequency: RecordingFrequency | None + configurationItemDeliveryTime: ConfigurationItemDeliveryTime | None + + +ConfigurationItemList = list[ConfigurationItem] +ConfigurationRecorderFilterValues = list[ConfigurationRecorderFilterValue] class ConfigurationRecorderFilter(TypedDict, total=False): - filterName: Optional[ConfigurationRecorderFilterName] - filterValue: Optional[ConfigurationRecorderFilterValues] + filterName: ConfigurationRecorderFilterName | None + filterValue: ConfigurationRecorderFilterValues | None -ConfigurationRecorderFilterList = List[ConfigurationRecorderFilter] -ConfigurationRecorderList = List[ConfigurationRecorder] -ConfigurationRecorderNameList = List[RecorderName] +ConfigurationRecorderFilterList = list[ConfigurationRecorderFilter] +ConfigurationRecorderList = list[ConfigurationRecorder] +ConfigurationRecorderNameList = list[RecorderName] class ConfigurationRecorderStatus(TypedDict, total=False): - arn: Optional[AmazonResourceName] - name: Optional[String] - lastStartTime: Optional[Date] - lastStopTime: Optional[Date] - recording: Optional[Boolean] - lastStatus: Optional[RecorderStatus] - lastErrorCode: Optional[String] - lastErrorMessage: Optional[String] - lastStatusChangeTime: Optional[Date] - servicePrincipal: Optional[ServicePrincipal] + arn: AmazonResourceName | None + name: String | None + lastStartTime: Date | None + lastStopTime: Date | None + recording: Boolean | None + lastStatus: RecorderStatus | None + lastErrorCode: String | None + lastErrorMessage: String | None + lastStatusChangeTime: Date | None + servicePrincipal: ServicePrincipal | None -ConfigurationRecorderStatusList = List[ConfigurationRecorderStatus] +ConfigurationRecorderStatusList = list[ConfigurationRecorderStatus] class ConfigurationRecorderSummary(TypedDict, total=False): arn: AmazonResourceName name: RecorderName - servicePrincipal: Optional[ServicePrincipal] + servicePrincipal: ServicePrincipal | None recordingScope: RecordingScope -ConfigurationRecorderSummaries = List[ConfigurationRecorderSummary] -ConformancePackConfigRuleNames = List[StringWithCharLimit64] +ConfigurationRecorderSummaries = list[ConfigurationRecorderSummary] +ConformancePackConfigRuleNames = list[StringWithCharLimit64] class ConformancePackComplianceFilters(TypedDict, total=False): - ConfigRuleNames: Optional[ConformancePackConfigRuleNames] - ComplianceType: Optional[ConformancePackComplianceType] + ConfigRuleNames: ConformancePackConfigRuleNames | None + ComplianceType: ConformancePackComplianceType | None -ConformancePackComplianceResourceIds = List[StringWithCharLimit256] +ConformancePackComplianceResourceIds = list[StringWithCharLimit256] LastUpdatedTime = datetime class ConformancePackComplianceScore(TypedDict, total=False): - Score: Optional[ComplianceScore] - ConformancePackName: Optional[ConformancePackName] - LastUpdatedTime: Optional[LastUpdatedTime] + Score: ComplianceScore | None + ConformancePackName: ConformancePackName | None + LastUpdatedTime: LastUpdatedTime | None -ConformancePackComplianceScores = List[ConformancePackComplianceScore] -ConformancePackNameFilter = List[ConformancePackName] +ConformancePackComplianceScores = list[ConformancePackComplianceScore] +ConformancePackNameFilter = list[ConformancePackName] class ConformancePackComplianceScoresFilters(TypedDict, total=False): @@ -1666,12 +1763,12 @@ class ConformancePackComplianceSummary(TypedDict, total=False): ConformancePackComplianceStatus: ConformancePackComplianceType -ConformancePackComplianceSummaryList = List[ConformancePackComplianceSummary] +ConformancePackComplianceSummaryList = list[ConformancePackComplianceSummary] class TemplateSSMDocumentDetails(TypedDict, total=False): DocumentName: SSMDocumentName - DocumentVersion: Optional[SSMDocumentVersion] + DocumentVersion: SSMDocumentVersion | None class ConformancePackInputParameter(TypedDict, total=False): @@ -1679,29 +1776,29 @@ class ConformancePackInputParameter(TypedDict, total=False): ParameterValue: ParameterValue -ConformancePackInputParameters = List[ConformancePackInputParameter] +ConformancePackInputParameters = list[ConformancePackInputParameter] class ConformancePackDetail(TypedDict, total=False): ConformancePackName: ConformancePackName ConformancePackArn: ConformancePackArn ConformancePackId: ConformancePackId - DeliveryS3Bucket: Optional[DeliveryS3Bucket] - DeliveryS3KeyPrefix: Optional[DeliveryS3KeyPrefix] - ConformancePackInputParameters: Optional[ConformancePackInputParameters] - LastUpdateRequestedTime: Optional[Date] - CreatedBy: Optional[StringWithCharLimit256] - TemplateSSMDocumentDetails: Optional[TemplateSSMDocumentDetails] + DeliveryS3Bucket: DeliveryS3Bucket | None + DeliveryS3KeyPrefix: DeliveryS3KeyPrefix | None + ConformancePackInputParameters: ConformancePackInputParameters | None + LastUpdateRequestedTime: Date | None + CreatedBy: StringWithCharLimit256 | None + TemplateSSMDocumentDetails: TemplateSSMDocumentDetails | None -ConformancePackDetailList = List[ConformancePackDetail] +ConformancePackDetailList = list[ConformancePackDetail] class ConformancePackEvaluationFilters(TypedDict, total=False): - ConfigRuleNames: Optional[ConformancePackConfigRuleNames] - ComplianceType: Optional[ConformancePackComplianceType] - ResourceType: Optional[StringWithCharLimit256] - ResourceIds: Optional[ConformancePackComplianceResourceIds] + ConfigRuleNames: ConformancePackConfigRuleNames | None + ComplianceType: ConformancePackComplianceType | None + ResourceType: StringWithCharLimit256 | None + ResourceIds: ConformancePackComplianceResourceIds | None class ConformancePackEvaluationResult(TypedDict, total=False): @@ -1709,22 +1806,22 @@ class ConformancePackEvaluationResult(TypedDict, total=False): EvaluationResultIdentifier: EvaluationResultIdentifier ConfigRuleInvokedTime: Date ResultRecordedTime: Date - Annotation: Optional[Annotation] + Annotation: Annotation | None -ConformancePackNamesList = List[ConformancePackName] -ConformancePackNamesToSummarizeList = List[ConformancePackName] -ControlsList = List[StringWithCharLimit128] +ConformancePackNamesList = list[ConformancePackName] +ConformancePackNamesToSummarizeList = list[ConformancePackName] +ControlsList = list[StringWithCharLimit128] class ConformancePackRuleCompliance(TypedDict, total=False): - ConfigRuleName: Optional[ConfigRuleName] - ComplianceType: Optional[ConformancePackComplianceType] - Controls: Optional[ControlsList] + ConfigRuleName: ConfigRuleName | None + ComplianceType: ConformancePackComplianceType | None + Controls: ControlsList | None -ConformancePackRuleComplianceList = List[ConformancePackRuleCompliance] -ConformancePackRuleEvaluationResultsList = List[ConformancePackEvaluationResult] +ConformancePackRuleComplianceList = list[ConformancePackRuleCompliance] +ConformancePackRuleEvaluationResultsList = list[ConformancePackEvaluationResult] class ConformancePackStatusDetail(TypedDict, total=False): @@ -1733,13 +1830,13 @@ class ConformancePackStatusDetail(TypedDict, total=False): ConformancePackArn: ConformancePackArn ConformancePackState: ConformancePackState StackArn: StackArn - ConformancePackStatusReason: Optional[ConformancePackStatusReason] + ConformancePackStatusReason: ConformancePackStatusReason | None LastUpdateRequestedTime: Date - LastUpdateCompletedTime: Optional[Date] + LastUpdateCompletedTime: Date | None -ConformancePackStatusDetailsList = List[ConformancePackStatusDetail] -DebugLogDeliveryAccounts = List[AccountId] +ConformancePackStatusDetailsList = list[ConformancePackStatusDetail] +DebugLogDeliveryAccounts = list[AccountId] class DeleteAggregationAuthorizationRequest(ServiceRequest): @@ -1790,7 +1887,7 @@ class DeletePendingAggregationRequestRequest(ServiceRequest): class DeleteRemediationConfigurationRequest(ServiceRequest): ConfigRuleName: ConfigRuleName - ResourceType: Optional[String] + ResourceType: String | None class DeleteRemediationConfigurationResponse(TypedDict, total=False): @@ -1798,11 +1895,11 @@ class DeleteRemediationConfigurationResponse(TypedDict, total=False): class RemediationExceptionResourceKey(TypedDict, total=False): - ResourceType: Optional[StringWithCharLimit256] - ResourceId: Optional[StringWithCharLimit1024] + ResourceType: StringWithCharLimit256 | None + ResourceId: StringWithCharLimit1024 | None -RemediationExceptionResourceKeys = List[RemediationExceptionResourceKey] +RemediationExceptionResourceKeys = list[RemediationExceptionResourceKey] class DeleteRemediationExceptionsRequest(ServiceRequest): @@ -1811,15 +1908,15 @@ class DeleteRemediationExceptionsRequest(ServiceRequest): class FailedDeleteRemediationExceptionsBatch(TypedDict, total=False): - FailureMessage: Optional[String] - FailedItems: Optional[RemediationExceptionResourceKeys] + FailureMessage: String | None + FailedItems: RemediationExceptionResourceKeys | None -FailedDeleteRemediationExceptionsBatches = List[FailedDeleteRemediationExceptionsBatch] +FailedDeleteRemediationExceptionsBatches = list[FailedDeleteRemediationExceptionsBatch] class DeleteRemediationExceptionsResponse(TypedDict, total=False): - FailedBatches: Optional[FailedDeleteRemediationExceptionsBatches] + FailedBatches: FailedDeleteRemediationExceptionsBatches | None class DeleteResourceConfigRequest(ServiceRequest): @@ -1853,368 +1950,368 @@ class DeliverConfigSnapshotRequest(ServiceRequest): class DeliverConfigSnapshotResponse(TypedDict, total=False): - configSnapshotId: Optional[String] + configSnapshotId: String | None class DeliveryChannel(TypedDict, total=False): - name: Optional[ChannelName] - s3BucketName: Optional[String] - s3KeyPrefix: Optional[String] - s3KmsKeyArn: Optional[String] - snsTopicARN: Optional[String] - configSnapshotDeliveryProperties: Optional[ConfigSnapshotDeliveryProperties] + name: ChannelName | None + s3BucketName: String | None + s3KeyPrefix: String | None + s3KmsKeyArn: String | None + snsTopicARN: String | None + configSnapshotDeliveryProperties: ConfigSnapshotDeliveryProperties | None -DeliveryChannelList = List[DeliveryChannel] -DeliveryChannelNameList = List[ChannelName] +DeliveryChannelList = list[DeliveryChannel] +DeliveryChannelNameList = list[ChannelName] class DeliveryChannelStatus(TypedDict, total=False): - name: Optional[String] - configSnapshotDeliveryInfo: Optional[ConfigExportDeliveryInfo] - configHistoryDeliveryInfo: Optional[ConfigExportDeliveryInfo] - configStreamDeliveryInfo: Optional[ConfigStreamDeliveryInfo] + name: String | None + configSnapshotDeliveryInfo: ConfigExportDeliveryInfo | None + configHistoryDeliveryInfo: ConfigExportDeliveryInfo | None + configStreamDeliveryInfo: ConfigStreamDeliveryInfo | None -DeliveryChannelStatusList = List[DeliveryChannelStatus] +DeliveryChannelStatusList = list[DeliveryChannelStatus] class DescribeAggregateComplianceByConfigRulesRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName - Filters: Optional[ConfigRuleComplianceFilters] - Limit: Optional[GroupByAPILimit] - NextToken: Optional[NextToken] + Filters: ConfigRuleComplianceFilters | None + Limit: GroupByAPILimit | None + NextToken: NextToken | None class DescribeAggregateComplianceByConfigRulesResponse(TypedDict, total=False): - AggregateComplianceByConfigRules: Optional[AggregateComplianceByConfigRuleList] - NextToken: Optional[NextToken] + AggregateComplianceByConfigRules: AggregateComplianceByConfigRuleList | None + NextToken: NextToken | None class DescribeAggregateComplianceByConformancePacksRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName - Filters: Optional[AggregateConformancePackComplianceFilters] - Limit: Optional[Limit] - NextToken: Optional[NextToken] + Filters: AggregateConformancePackComplianceFilters | None + Limit: Limit | None + NextToken: NextToken | None class DescribeAggregateComplianceByConformancePacksResponse(TypedDict, total=False): - AggregateComplianceByConformancePacks: Optional[AggregateComplianceByConformancePackList] - NextToken: Optional[NextToken] + AggregateComplianceByConformancePacks: AggregateComplianceByConformancePackList | None + NextToken: NextToken | None class DescribeAggregationAuthorizationsRequest(ServiceRequest): - Limit: Optional[Limit] - NextToken: Optional[String] + Limit: Limit | None + NextToken: String | None class DescribeAggregationAuthorizationsResponse(TypedDict, total=False): - AggregationAuthorizations: Optional[AggregationAuthorizationList] - NextToken: Optional[String] + AggregationAuthorizations: AggregationAuthorizationList | None + NextToken: String | None class DescribeComplianceByConfigRuleRequest(ServiceRequest): - ConfigRuleNames: Optional[ConfigRuleNames] - ComplianceTypes: Optional[ComplianceTypes] - NextToken: Optional[String] + ConfigRuleNames: ConfigRuleNames | None + ComplianceTypes: ComplianceTypes | None + NextToken: String | None class DescribeComplianceByConfigRuleResponse(TypedDict, total=False): - ComplianceByConfigRules: Optional[ComplianceByConfigRules] - NextToken: Optional[String] + ComplianceByConfigRules: ComplianceByConfigRules | None + NextToken: String | None class DescribeComplianceByResourceRequest(ServiceRequest): - ResourceType: Optional[StringWithCharLimit256] - ResourceId: Optional[BaseResourceId] - ComplianceTypes: Optional[ComplianceTypes] - Limit: Optional[Limit] - NextToken: Optional[NextToken] + ResourceType: StringWithCharLimit256 | None + ResourceId: BaseResourceId | None + ComplianceTypes: ComplianceTypes | None + Limit: Limit | None + NextToken: NextToken | None class DescribeComplianceByResourceResponse(TypedDict, total=False): - ComplianceByResources: Optional[ComplianceByResources] - NextToken: Optional[NextToken] + ComplianceByResources: ComplianceByResources | None + NextToken: NextToken | None class DescribeConfigRuleEvaluationStatusRequest(ServiceRequest): - ConfigRuleNames: Optional[ConfigRuleNames] - NextToken: Optional[String] - Limit: Optional[RuleLimit] + ConfigRuleNames: ConfigRuleNames | None + NextToken: String | None + Limit: RuleLimit | None class DescribeConfigRuleEvaluationStatusResponse(TypedDict, total=False): - ConfigRulesEvaluationStatus: Optional[ConfigRuleEvaluationStatusList] - NextToken: Optional[String] + ConfigRulesEvaluationStatus: ConfigRuleEvaluationStatusList | None + NextToken: String | None class DescribeConfigRulesFilters(TypedDict, total=False): - EvaluationMode: Optional[EvaluationMode] + EvaluationMode: EvaluationMode | None class DescribeConfigRulesRequest(ServiceRequest): - ConfigRuleNames: Optional[ConfigRuleNames] - NextToken: Optional[String] - Filters: Optional[DescribeConfigRulesFilters] + ConfigRuleNames: ConfigRuleNames | None + NextToken: String | None + Filters: DescribeConfigRulesFilters | None class DescribeConfigRulesResponse(TypedDict, total=False): - ConfigRules: Optional[ConfigRules] - NextToken: Optional[String] + ConfigRules: ConfigRules | None + NextToken: String | None class DescribeConfigurationAggregatorSourcesStatusRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName - UpdateStatus: Optional[AggregatedSourceStatusTypeList] - NextToken: Optional[String] - Limit: Optional[Limit] + UpdateStatus: AggregatedSourceStatusTypeList | None + NextToken: String | None + Limit: Limit | None class DescribeConfigurationAggregatorSourcesStatusResponse(TypedDict, total=False): - AggregatedSourceStatusList: Optional[AggregatedSourceStatusList] - NextToken: Optional[String] + AggregatedSourceStatusList: AggregatedSourceStatusList | None + NextToken: String | None class DescribeConfigurationAggregatorsRequest(ServiceRequest): - ConfigurationAggregatorNames: Optional[ConfigurationAggregatorNameList] - NextToken: Optional[String] - Limit: Optional[Limit] + ConfigurationAggregatorNames: ConfigurationAggregatorNameList | None + NextToken: String | None + Limit: Limit | None class DescribeConfigurationAggregatorsResponse(TypedDict, total=False): - ConfigurationAggregators: Optional[ConfigurationAggregatorList] - NextToken: Optional[String] + ConfigurationAggregators: ConfigurationAggregatorList | None + NextToken: String | None class DescribeConfigurationRecorderStatusRequest(ServiceRequest): - ConfigurationRecorderNames: Optional[ConfigurationRecorderNameList] - ServicePrincipal: Optional[ServicePrincipal] - Arn: Optional[AmazonResourceName] + ConfigurationRecorderNames: ConfigurationRecorderNameList | None + ServicePrincipal: ServicePrincipal | None + Arn: AmazonResourceName | None class DescribeConfigurationRecorderStatusResponse(TypedDict, total=False): - ConfigurationRecordersStatus: Optional[ConfigurationRecorderStatusList] + ConfigurationRecordersStatus: ConfigurationRecorderStatusList | None class DescribeConfigurationRecordersRequest(ServiceRequest): - ConfigurationRecorderNames: Optional[ConfigurationRecorderNameList] - ServicePrincipal: Optional[ServicePrincipal] - Arn: Optional[AmazonResourceName] + ConfigurationRecorderNames: ConfigurationRecorderNameList | None + ServicePrincipal: ServicePrincipal | None + Arn: AmazonResourceName | None class DescribeConfigurationRecordersResponse(TypedDict, total=False): - ConfigurationRecorders: Optional[ConfigurationRecorderList] + ConfigurationRecorders: ConfigurationRecorderList | None class DescribeConformancePackComplianceRequest(ServiceRequest): ConformancePackName: ConformancePackName - Filters: Optional[ConformancePackComplianceFilters] - Limit: Optional[DescribeConformancePackComplianceLimit] - NextToken: Optional[NextToken] + Filters: ConformancePackComplianceFilters | None + Limit: DescribeConformancePackComplianceLimit | None + NextToken: NextToken | None class DescribeConformancePackComplianceResponse(TypedDict, total=False): ConformancePackName: ConformancePackName ConformancePackRuleComplianceList: ConformancePackRuleComplianceList - NextToken: Optional[NextToken] + NextToken: NextToken | None class DescribeConformancePackStatusRequest(ServiceRequest): - ConformancePackNames: Optional[ConformancePackNamesList] - Limit: Optional[PageSizeLimit] - NextToken: Optional[NextToken] + ConformancePackNames: ConformancePackNamesList | None + Limit: PageSizeLimit | None + NextToken: NextToken | None class DescribeConformancePackStatusResponse(TypedDict, total=False): - ConformancePackStatusDetails: Optional[ConformancePackStatusDetailsList] - NextToken: Optional[NextToken] + ConformancePackStatusDetails: ConformancePackStatusDetailsList | None + NextToken: NextToken | None class DescribeConformancePacksRequest(ServiceRequest): - ConformancePackNames: Optional[ConformancePackNamesList] - Limit: Optional[PageSizeLimit] - NextToken: Optional[NextToken] + ConformancePackNames: ConformancePackNamesList | None + Limit: PageSizeLimit | None + NextToken: NextToken | None class DescribeConformancePacksResponse(TypedDict, total=False): - ConformancePackDetails: Optional[ConformancePackDetailList] - NextToken: Optional[NextToken] + ConformancePackDetails: ConformancePackDetailList | None + NextToken: NextToken | None class DescribeDeliveryChannelStatusRequest(ServiceRequest): - DeliveryChannelNames: Optional[DeliveryChannelNameList] + DeliveryChannelNames: DeliveryChannelNameList | None class DescribeDeliveryChannelStatusResponse(TypedDict, total=False): - DeliveryChannelsStatus: Optional[DeliveryChannelStatusList] + DeliveryChannelsStatus: DeliveryChannelStatusList | None class DescribeDeliveryChannelsRequest(ServiceRequest): - DeliveryChannelNames: Optional[DeliveryChannelNameList] + DeliveryChannelNames: DeliveryChannelNameList | None class DescribeDeliveryChannelsResponse(TypedDict, total=False): - DeliveryChannels: Optional[DeliveryChannelList] + DeliveryChannels: DeliveryChannelList | None -OrganizationConfigRuleNames = List[StringWithCharLimit64] +OrganizationConfigRuleNames = list[StringWithCharLimit64] class DescribeOrganizationConfigRuleStatusesRequest(ServiceRequest): - OrganizationConfigRuleNames: Optional[OrganizationConfigRuleNames] - Limit: Optional[CosmosPageLimit] - NextToken: Optional[String] + OrganizationConfigRuleNames: OrganizationConfigRuleNames | None + Limit: CosmosPageLimit | None + NextToken: String | None class OrganizationConfigRuleStatus(TypedDict, total=False): OrganizationConfigRuleName: OrganizationConfigRuleName OrganizationRuleStatus: OrganizationRuleStatus - ErrorCode: Optional[String] - ErrorMessage: Optional[String] - LastUpdateTime: Optional[Date] + ErrorCode: String | None + ErrorMessage: String | None + LastUpdateTime: Date | None -OrganizationConfigRuleStatuses = List[OrganizationConfigRuleStatus] +OrganizationConfigRuleStatuses = list[OrganizationConfigRuleStatus] class DescribeOrganizationConfigRuleStatusesResponse(TypedDict, total=False): - OrganizationConfigRuleStatuses: Optional[OrganizationConfigRuleStatuses] - NextToken: Optional[String] + OrganizationConfigRuleStatuses: OrganizationConfigRuleStatuses | None + NextToken: String | None class DescribeOrganizationConfigRulesRequest(ServiceRequest): - OrganizationConfigRuleNames: Optional[OrganizationConfigRuleNames] - Limit: Optional[CosmosPageLimit] - NextToken: Optional[String] + OrganizationConfigRuleNames: OrganizationConfigRuleNames | None + Limit: CosmosPageLimit | None + NextToken: String | None -ResourceTypesScope = List[StringWithCharLimit256] -OrganizationConfigRuleTriggerTypeNoSNs = List[OrganizationConfigRuleTriggerTypeNoSN] +ResourceTypesScope = list[StringWithCharLimit256] +OrganizationConfigRuleTriggerTypeNoSNs = list[OrganizationConfigRuleTriggerTypeNoSN] class OrganizationCustomPolicyRuleMetadataNoPolicy(TypedDict, total=False): - Description: Optional[StringWithCharLimit256Min0] - OrganizationConfigRuleTriggerTypes: Optional[OrganizationConfigRuleTriggerTypeNoSNs] - InputParameters: Optional[StringWithCharLimit2048] - MaximumExecutionFrequency: Optional[MaximumExecutionFrequency] - ResourceTypesScope: Optional[ResourceTypesScope] - ResourceIdScope: Optional[StringWithCharLimit768] - TagKeyScope: Optional[StringWithCharLimit128] - TagValueScope: Optional[StringWithCharLimit256] - PolicyRuntime: Optional[PolicyRuntime] - DebugLogDeliveryAccounts: Optional[DebugLogDeliveryAccounts] + Description: StringWithCharLimit256Min0 | None + OrganizationConfigRuleTriggerTypes: OrganizationConfigRuleTriggerTypeNoSNs | None + InputParameters: StringWithCharLimit2048 | None + MaximumExecutionFrequency: MaximumExecutionFrequency | None + ResourceTypesScope: ResourceTypesScope | None + ResourceIdScope: StringWithCharLimit768 | None + TagKeyScope: StringWithCharLimit128 | None + TagValueScope: StringWithCharLimit256 | None + PolicyRuntime: PolicyRuntime | None + DebugLogDeliveryAccounts: DebugLogDeliveryAccounts | None -ExcludedAccounts = List[AccountId] -OrganizationConfigRuleTriggerTypes = List[OrganizationConfigRuleTriggerType] +ExcludedAccounts = list[AccountId] +OrganizationConfigRuleTriggerTypes = list[OrganizationConfigRuleTriggerType] class OrganizationCustomRuleMetadata(TypedDict, total=False): - Description: Optional[StringWithCharLimit256Min0] + Description: StringWithCharLimit256Min0 | None LambdaFunctionArn: StringWithCharLimit256 OrganizationConfigRuleTriggerTypes: OrganizationConfigRuleTriggerTypes - InputParameters: Optional[StringWithCharLimit2048] - MaximumExecutionFrequency: Optional[MaximumExecutionFrequency] - ResourceTypesScope: Optional[ResourceTypesScope] - ResourceIdScope: Optional[StringWithCharLimit768] - TagKeyScope: Optional[StringWithCharLimit128] - TagValueScope: Optional[StringWithCharLimit256] + InputParameters: StringWithCharLimit2048 | None + MaximumExecutionFrequency: MaximumExecutionFrequency | None + ResourceTypesScope: ResourceTypesScope | None + ResourceIdScope: StringWithCharLimit768 | None + TagKeyScope: StringWithCharLimit128 | None + TagValueScope: StringWithCharLimit256 | None class OrganizationManagedRuleMetadata(TypedDict, total=False): - Description: Optional[StringWithCharLimit256Min0] + Description: StringWithCharLimit256Min0 | None RuleIdentifier: StringWithCharLimit256 - InputParameters: Optional[StringWithCharLimit2048] - MaximumExecutionFrequency: Optional[MaximumExecutionFrequency] - ResourceTypesScope: Optional[ResourceTypesScope] - ResourceIdScope: Optional[StringWithCharLimit768] - TagKeyScope: Optional[StringWithCharLimit128] - TagValueScope: Optional[StringWithCharLimit256] + InputParameters: StringWithCharLimit2048 | None + MaximumExecutionFrequency: MaximumExecutionFrequency | None + ResourceTypesScope: ResourceTypesScope | None + ResourceIdScope: StringWithCharLimit768 | None + TagKeyScope: StringWithCharLimit128 | None + TagValueScope: StringWithCharLimit256 | None class OrganizationConfigRule(TypedDict, total=False): OrganizationConfigRuleName: OrganizationConfigRuleName OrganizationConfigRuleArn: StringWithCharLimit256 - OrganizationManagedRuleMetadata: Optional[OrganizationManagedRuleMetadata] - OrganizationCustomRuleMetadata: Optional[OrganizationCustomRuleMetadata] - ExcludedAccounts: Optional[ExcludedAccounts] - LastUpdateTime: Optional[Date] - OrganizationCustomPolicyRuleMetadata: Optional[OrganizationCustomPolicyRuleMetadataNoPolicy] + OrganizationManagedRuleMetadata: OrganizationManagedRuleMetadata | None + OrganizationCustomRuleMetadata: OrganizationCustomRuleMetadata | None + ExcludedAccounts: ExcludedAccounts | None + LastUpdateTime: Date | None + OrganizationCustomPolicyRuleMetadata: OrganizationCustomPolicyRuleMetadataNoPolicy | None -OrganizationConfigRules = List[OrganizationConfigRule] +OrganizationConfigRules = list[OrganizationConfigRule] class DescribeOrganizationConfigRulesResponse(TypedDict, total=False): - OrganizationConfigRules: Optional[OrganizationConfigRules] - NextToken: Optional[String] + OrganizationConfigRules: OrganizationConfigRules | None + NextToken: String | None -OrganizationConformancePackNames = List[OrganizationConformancePackName] +OrganizationConformancePackNames = list[OrganizationConformancePackName] class DescribeOrganizationConformancePackStatusesRequest(ServiceRequest): - OrganizationConformancePackNames: Optional[OrganizationConformancePackNames] - Limit: Optional[CosmosPageLimit] - NextToken: Optional[String] + OrganizationConformancePackNames: OrganizationConformancePackNames | None + Limit: CosmosPageLimit | None + NextToken: String | None class OrganizationConformancePackStatus(TypedDict, total=False): OrganizationConformancePackName: OrganizationConformancePackName Status: OrganizationResourceStatus - ErrorCode: Optional[String] - ErrorMessage: Optional[String] - LastUpdateTime: Optional[Date] + ErrorCode: String | None + ErrorMessage: String | None + LastUpdateTime: Date | None -OrganizationConformancePackStatuses = List[OrganizationConformancePackStatus] +OrganizationConformancePackStatuses = list[OrganizationConformancePackStatus] class DescribeOrganizationConformancePackStatusesResponse(TypedDict, total=False): - OrganizationConformancePackStatuses: Optional[OrganizationConformancePackStatuses] - NextToken: Optional[String] + OrganizationConformancePackStatuses: OrganizationConformancePackStatuses | None + NextToken: String | None class DescribeOrganizationConformancePacksRequest(ServiceRequest): - OrganizationConformancePackNames: Optional[OrganizationConformancePackNames] - Limit: Optional[CosmosPageLimit] - NextToken: Optional[String] + OrganizationConformancePackNames: OrganizationConformancePackNames | None + Limit: CosmosPageLimit | None + NextToken: String | None class OrganizationConformancePack(TypedDict, total=False): OrganizationConformancePackName: OrganizationConformancePackName OrganizationConformancePackArn: StringWithCharLimit256 - DeliveryS3Bucket: Optional[DeliveryS3Bucket] - DeliveryS3KeyPrefix: Optional[DeliveryS3KeyPrefix] - ConformancePackInputParameters: Optional[ConformancePackInputParameters] - ExcludedAccounts: Optional[ExcludedAccounts] + DeliveryS3Bucket: DeliveryS3Bucket | None + DeliveryS3KeyPrefix: DeliveryS3KeyPrefix | None + ConformancePackInputParameters: ConformancePackInputParameters | None + ExcludedAccounts: ExcludedAccounts | None LastUpdateTime: Date -OrganizationConformancePacks = List[OrganizationConformancePack] +OrganizationConformancePacks = list[OrganizationConformancePack] class DescribeOrganizationConformancePacksResponse(TypedDict, total=False): - OrganizationConformancePacks: Optional[OrganizationConformancePacks] - NextToken: Optional[String] + OrganizationConformancePacks: OrganizationConformancePacks | None + NextToken: String | None class DescribePendingAggregationRequestsRequest(ServiceRequest): - Limit: Optional[DescribePendingAggregationRequestsLimit] - NextToken: Optional[String] + Limit: DescribePendingAggregationRequestsLimit | None + NextToken: String | None class PendingAggregationRequest(TypedDict, total=False): - RequesterAccountId: Optional[AccountId] - RequesterAwsRegion: Optional[AwsRegion] + RequesterAccountId: AccountId | None + RequesterAwsRegion: AwsRegion | None -PendingAggregationRequestList = List[PendingAggregationRequest] +PendingAggregationRequestList = list[PendingAggregationRequest] class DescribePendingAggregationRequestsResponse(TypedDict, total=False): - PendingAggregationRequests: Optional[PendingAggregationRequestList] - NextToken: Optional[String] + PendingAggregationRequests: PendingAggregationRequestList | None + NextToken: String | None class DescribeRemediationConfigurationsRequest(ServiceRequest): @@ -2222,15 +2319,15 @@ class DescribeRemediationConfigurationsRequest(ServiceRequest): class SsmControls(TypedDict, total=False): - ConcurrentExecutionRatePercentage: Optional[Percentage] - ErrorPercentage: Optional[Percentage] + ConcurrentExecutionRatePercentage: Percentage | None + ErrorPercentage: Percentage | None class ExecutionControls(TypedDict, total=False): - SsmControls: Optional[SsmControls] + SsmControls: SsmControls | None -StaticParameterValues = List[StringWithCharLimit256] +StaticParameterValues = list[StringWithCharLimit256] class StaticValue(TypedDict, total=False): @@ -2242,98 +2339,98 @@ class ResourceValue(TypedDict, total=False): class RemediationParameterValue(TypedDict, total=False): - ResourceValue: Optional[ResourceValue] - StaticValue: Optional[StaticValue] + ResourceValue: ResourceValue | None + StaticValue: StaticValue | None -RemediationParameters = Dict[StringWithCharLimit256, RemediationParameterValue] +RemediationParameters = dict[StringWithCharLimit256, RemediationParameterValue] class RemediationConfiguration(TypedDict, total=False): ConfigRuleName: ConfigRuleName TargetType: RemediationTargetType TargetId: StringWithCharLimit256 - TargetVersion: Optional[String] - Parameters: Optional[RemediationParameters] - ResourceType: Optional[String] - Automatic: Optional[Boolean] - ExecutionControls: Optional[ExecutionControls] - MaximumAutomaticAttempts: Optional[AutoRemediationAttempts] - RetryAttemptSeconds: Optional[AutoRemediationAttemptSeconds] - Arn: Optional[StringWithCharLimit1024] - CreatedByService: Optional[StringWithCharLimit1024] + TargetVersion: String | None + Parameters: RemediationParameters | None + ResourceType: String | None + Automatic: Boolean | None + ExecutionControls: ExecutionControls | None + MaximumAutomaticAttempts: AutoRemediationAttempts | None + RetryAttemptSeconds: AutoRemediationAttemptSeconds | None + Arn: StringWithCharLimit1024 | None + CreatedByService: StringWithCharLimit1024 | None -RemediationConfigurations = List[RemediationConfiguration] +RemediationConfigurations = list[RemediationConfiguration] class DescribeRemediationConfigurationsResponse(TypedDict, total=False): - RemediationConfigurations: Optional[RemediationConfigurations] + RemediationConfigurations: RemediationConfigurations | None class DescribeRemediationExceptionsRequest(ServiceRequest): ConfigRuleName: ConfigRuleName - ResourceKeys: Optional[RemediationExceptionResourceKeys] - Limit: Optional[Limit] - NextToken: Optional[String] + ResourceKeys: RemediationExceptionResourceKeys | None + Limit: Limit | None + NextToken: String | None class RemediationException(TypedDict, total=False): ConfigRuleName: ConfigRuleName ResourceType: StringWithCharLimit256 ResourceId: StringWithCharLimit1024 - Message: Optional[StringWithCharLimit1024] - ExpirationTime: Optional[Date] + Message: StringWithCharLimit1024 | None + ExpirationTime: Date | None -RemediationExceptions = List[RemediationException] +RemediationExceptions = list[RemediationException] class DescribeRemediationExceptionsResponse(TypedDict, total=False): - RemediationExceptions: Optional[RemediationExceptions] - NextToken: Optional[String] + RemediationExceptions: RemediationExceptions | None + NextToken: String | None class DescribeRemediationExecutionStatusRequest(ServiceRequest): ConfigRuleName: ConfigRuleName - ResourceKeys: Optional[ResourceKeys] - Limit: Optional[Limit] - NextToken: Optional[String] + ResourceKeys: ResourceKeys | None + Limit: Limit | None + NextToken: String | None class RemediationExecutionStep(TypedDict, total=False): - Name: Optional[String] - State: Optional[RemediationExecutionStepState] - ErrorMessage: Optional[String] - StartTime: Optional[Date] - StopTime: Optional[Date] + Name: String | None + State: RemediationExecutionStepState | None + ErrorMessage: String | None + StartTime: Date | None + StopTime: Date | None -RemediationExecutionSteps = List[RemediationExecutionStep] +RemediationExecutionSteps = list[RemediationExecutionStep] class RemediationExecutionStatus(TypedDict, total=False): - ResourceKey: Optional[ResourceKey] - State: Optional[RemediationExecutionState] - StepDetails: Optional[RemediationExecutionSteps] - InvocationTime: Optional[Date] - LastUpdatedTime: Optional[Date] + ResourceKey: ResourceKey | None + State: RemediationExecutionState | None + StepDetails: RemediationExecutionSteps | None + InvocationTime: Date | None + LastUpdatedTime: Date | None -RemediationExecutionStatuses = List[RemediationExecutionStatus] +RemediationExecutionStatuses = list[RemediationExecutionStatus] class DescribeRemediationExecutionStatusResponse(TypedDict, total=False): - RemediationExecutionStatuses: Optional[RemediationExecutionStatuses] - NextToken: Optional[String] + RemediationExecutionStatuses: RemediationExecutionStatuses | None + NextToken: String | None -RetentionConfigurationNameList = List[RetentionConfigurationName] +RetentionConfigurationNameList = list[RetentionConfigurationName] class DescribeRetentionConfigurationsRequest(ServiceRequest): - RetentionConfigurationNames: Optional[RetentionConfigurationNameList] - NextToken: Optional[NextToken] + RetentionConfigurationNames: RetentionConfigurationNameList | None + NextToken: NextToken | None class RetentionConfiguration(TypedDict, total=False): @@ -2341,12 +2438,12 @@ class RetentionConfiguration(TypedDict, total=False): RetentionPeriodInDays: RetentionPeriodInDays -RetentionConfigurationList = List[RetentionConfiguration] +RetentionConfigurationList = list[RetentionConfiguration] class DescribeRetentionConfigurationsResponse(TypedDict, total=False): - RetentionConfigurations: Optional[RetentionConfigurationList] - NextToken: Optional[NextToken] + RetentionConfigurations: RetentionConfigurationList | None + NextToken: NextToken | None class DisassociateResourceTypesRequest(ServiceRequest): @@ -2358,7 +2455,7 @@ class DisassociateResourceTypesResponse(TypedDict, total=False): ConfigurationRecorder: ConfigurationRecorder -DiscoveredResourceIdentifierList = List[AggregateResourceIdentifier] +DiscoveredResourceIdentifierList = list[AggregateResourceIdentifier] EarlierTime = datetime OrderingTimestamp = datetime @@ -2367,63 +2464,63 @@ class Evaluation(TypedDict, total=False): ComplianceResourceType: StringWithCharLimit256 ComplianceResourceId: BaseResourceId ComplianceType: ComplianceType - Annotation: Optional[StringWithCharLimit256] + Annotation: StringWithCharLimit256 | None OrderingTimestamp: OrderingTimestamp class EvaluationContext(TypedDict, total=False): - EvaluationContextIdentifier: Optional[EvaluationContextIdentifier] + EvaluationContextIdentifier: EvaluationContextIdentifier | None class EvaluationResult(TypedDict, total=False): - EvaluationResultIdentifier: Optional[EvaluationResultIdentifier] - ComplianceType: Optional[ComplianceType] - ResultRecordedTime: Optional[Date] - ConfigRuleInvokedTime: Optional[Date] - Annotation: Optional[StringWithCharLimit256] - ResultToken: Optional[String] + EvaluationResultIdentifier: EvaluationResultIdentifier | None + ComplianceType: ComplianceType | None + ResultRecordedTime: Date | None + ConfigRuleInvokedTime: Date | None + Annotation: StringWithCharLimit256 | None + ResultToken: String | None -EvaluationResults = List[EvaluationResult] +EvaluationResults = list[EvaluationResult] class EvaluationStatus(TypedDict, total=False): Status: ResourceEvaluationStatus - FailureReason: Optional[StringWithCharLimit1024] + FailureReason: StringWithCharLimit1024 | None -Evaluations = List[Evaluation] +Evaluations = list[Evaluation] class ExternalEvaluation(TypedDict, total=False): ComplianceResourceType: StringWithCharLimit256 ComplianceResourceId: BaseResourceId ComplianceType: ComplianceType - Annotation: Optional[StringWithCharLimit256] + Annotation: StringWithCharLimit256 | None OrderingTimestamp: OrderingTimestamp class FailedRemediationBatch(TypedDict, total=False): - FailureMessage: Optional[String] - FailedItems: Optional[RemediationConfigurations] + FailureMessage: String | None + FailedItems: RemediationConfigurations | None -FailedRemediationBatches = List[FailedRemediationBatch] +FailedRemediationBatches = list[FailedRemediationBatch] class FailedRemediationExceptionBatch(TypedDict, total=False): - FailureMessage: Optional[String] - FailedItems: Optional[RemediationExceptions] + FailureMessage: String | None + FailedItems: RemediationExceptions | None -FailedRemediationExceptionBatches = List[FailedRemediationExceptionBatch] +FailedRemediationExceptionBatches = list[FailedRemediationExceptionBatch] class FieldInfo(TypedDict, total=False): - Name: Optional[FieldName] + Name: FieldName | None -FieldInfoList = List[FieldInfo] +FieldInfoList = list[FieldInfo] class GetAggregateComplianceDetailsByConfigRuleRequest(ServiceRequest): @@ -2431,58 +2528,58 @@ class GetAggregateComplianceDetailsByConfigRuleRequest(ServiceRequest): ConfigRuleName: ConfigRuleName AccountId: AccountId AwsRegion: AwsRegion - ComplianceType: Optional[ComplianceType] - Limit: Optional[Limit] - NextToken: Optional[NextToken] + ComplianceType: ComplianceType | None + Limit: Limit | None + NextToken: NextToken | None class GetAggregateComplianceDetailsByConfigRuleResponse(TypedDict, total=False): - AggregateEvaluationResults: Optional[AggregateEvaluationResultList] - NextToken: Optional[NextToken] + AggregateEvaluationResults: AggregateEvaluationResultList | None + NextToken: NextToken | None class GetAggregateConfigRuleComplianceSummaryRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName - Filters: Optional[ConfigRuleComplianceSummaryFilters] - GroupByKey: Optional[ConfigRuleComplianceSummaryGroupKey] - Limit: Optional[GroupByAPILimit] - NextToken: Optional[NextToken] + Filters: ConfigRuleComplianceSummaryFilters | None + GroupByKey: ConfigRuleComplianceSummaryGroupKey | None + Limit: GroupByAPILimit | None + NextToken: NextToken | None class GetAggregateConfigRuleComplianceSummaryResponse(TypedDict, total=False): - GroupByKey: Optional[StringWithCharLimit256] - AggregateComplianceCounts: Optional[AggregateComplianceCountList] - NextToken: Optional[NextToken] + GroupByKey: StringWithCharLimit256 | None + AggregateComplianceCounts: AggregateComplianceCountList | None + NextToken: NextToken | None class GetAggregateConformancePackComplianceSummaryRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName - Filters: Optional[AggregateConformancePackComplianceSummaryFilters] - GroupByKey: Optional[AggregateConformancePackComplianceSummaryGroupKey] - Limit: Optional[Limit] - NextToken: Optional[NextToken] + Filters: AggregateConformancePackComplianceSummaryFilters | None + GroupByKey: AggregateConformancePackComplianceSummaryGroupKey | None + Limit: Limit | None + NextToken: NextToken | None class GetAggregateConformancePackComplianceSummaryResponse(TypedDict, total=False): - AggregateConformancePackComplianceSummaries: Optional[ - AggregateConformancePackComplianceSummaryList - ] - GroupByKey: Optional[StringWithCharLimit256] - NextToken: Optional[NextToken] + AggregateConformancePackComplianceSummaries: ( + AggregateConformancePackComplianceSummaryList | None + ) + GroupByKey: StringWithCharLimit256 | None + NextToken: NextToken | None class ResourceCountFilters(TypedDict, total=False): - ResourceType: Optional[ResourceType] - AccountId: Optional[AccountId] - Region: Optional[AwsRegion] + ResourceType: ResourceType | None + AccountId: AccountId | None + Region: AwsRegion | None class GetAggregateDiscoveredResourceCountsRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName - Filters: Optional[ResourceCountFilters] - GroupByKey: Optional[ResourceCountGroupKey] - Limit: Optional[GroupByAPILimit] - NextToken: Optional[NextToken] + Filters: ResourceCountFilters | None + GroupByKey: ResourceCountGroupKey | None + Limit: GroupByAPILimit | None + NextToken: NextToken | None Long = int @@ -2493,14 +2590,14 @@ class GroupedResourceCount(TypedDict, total=False): ResourceCount: Long -GroupedResourceCountList = List[GroupedResourceCount] +GroupedResourceCountList = list[GroupedResourceCount] class GetAggregateDiscoveredResourceCountsResponse(TypedDict, total=False): TotalDiscoveredResources: Long - GroupByKey: Optional[StringWithCharLimit256] - GroupedResourceCounts: Optional[GroupedResourceCountList] - NextToken: Optional[NextToken] + GroupByKey: StringWithCharLimit256 | None + GroupedResourceCounts: GroupedResourceCountList | None + NextToken: NextToken | None class GetAggregateResourceConfigRequest(ServiceRequest): @@ -2509,159 +2606,157 @@ class GetAggregateResourceConfigRequest(ServiceRequest): class GetAggregateResourceConfigResponse(TypedDict, total=False): - ConfigurationItem: Optional[ConfigurationItem] + ConfigurationItem: ConfigurationItem | None class GetComplianceDetailsByConfigRuleRequest(ServiceRequest): ConfigRuleName: StringWithCharLimit64 - ComplianceTypes: Optional[ComplianceTypes] - Limit: Optional[Limit] - NextToken: Optional[NextToken] + ComplianceTypes: ComplianceTypes | None + Limit: Limit | None + NextToken: NextToken | None class GetComplianceDetailsByConfigRuleResponse(TypedDict, total=False): - EvaluationResults: Optional[EvaluationResults] - NextToken: Optional[NextToken] + EvaluationResults: EvaluationResults | None + NextToken: NextToken | None class GetComplianceDetailsByResourceRequest(ServiceRequest): - ResourceType: Optional[StringWithCharLimit256] - ResourceId: Optional[BaseResourceId] - ComplianceTypes: Optional[ComplianceTypes] - NextToken: Optional[String] - ResourceEvaluationId: Optional[ResourceEvaluationId] + ResourceType: StringWithCharLimit256 | None + ResourceId: BaseResourceId | None + ComplianceTypes: ComplianceTypes | None + NextToken: String | None + ResourceEvaluationId: ResourceEvaluationId | None class GetComplianceDetailsByResourceResponse(TypedDict, total=False): - EvaluationResults: Optional[EvaluationResults] - NextToken: Optional[String] + EvaluationResults: EvaluationResults | None + NextToken: String | None class GetComplianceSummaryByConfigRuleResponse(TypedDict, total=False): - ComplianceSummary: Optional[ComplianceSummary] + ComplianceSummary: ComplianceSummary | None -ResourceTypes = List[StringWithCharLimit256] +ResourceTypes = list[StringWithCharLimit256] class GetComplianceSummaryByResourceTypeRequest(ServiceRequest): - ResourceTypes: Optional[ResourceTypes] + ResourceTypes: ResourceTypes | None class GetComplianceSummaryByResourceTypeResponse(TypedDict, total=False): - ComplianceSummariesByResourceType: Optional[ComplianceSummariesByResourceType] + ComplianceSummariesByResourceType: ComplianceSummariesByResourceType | None class GetConformancePackComplianceDetailsRequest(ServiceRequest): ConformancePackName: ConformancePackName - Filters: Optional[ConformancePackEvaluationFilters] - Limit: Optional[GetConformancePackComplianceDetailsLimit] - NextToken: Optional[NextToken] + Filters: ConformancePackEvaluationFilters | None + Limit: GetConformancePackComplianceDetailsLimit | None + NextToken: NextToken | None class GetConformancePackComplianceDetailsResponse(TypedDict, total=False): ConformancePackName: ConformancePackName - ConformancePackRuleEvaluationResults: Optional[ConformancePackRuleEvaluationResultsList] - NextToken: Optional[NextToken] + ConformancePackRuleEvaluationResults: ConformancePackRuleEvaluationResultsList | None + NextToken: NextToken | None class GetConformancePackComplianceSummaryRequest(ServiceRequest): ConformancePackNames: ConformancePackNamesToSummarizeList - Limit: Optional[PageSizeLimit] - NextToken: Optional[NextToken] + Limit: PageSizeLimit | None + NextToken: NextToken | None class GetConformancePackComplianceSummaryResponse(TypedDict, total=False): - ConformancePackComplianceSummaryList: Optional[ConformancePackComplianceSummaryList] - NextToken: Optional[NextToken] + ConformancePackComplianceSummaryList: ConformancePackComplianceSummaryList | None + NextToken: NextToken | None class GetCustomRulePolicyRequest(ServiceRequest): - ConfigRuleName: Optional[ConfigRuleName] + ConfigRuleName: ConfigRuleName | None class GetCustomRulePolicyResponse(TypedDict, total=False): - PolicyText: Optional[PolicyText] + PolicyText: PolicyText | None class GetDiscoveredResourceCountsRequest(ServiceRequest): - resourceTypes: Optional[ResourceTypes] - limit: Optional[Limit] - nextToken: Optional[NextToken] + resourceTypes: ResourceTypes | None + limit: Limit | None + nextToken: NextToken | None class ResourceCount(TypedDict, total=False): - resourceType: Optional[ResourceType] - count: Optional[Long] + resourceType: ResourceType | None + count: Long | None -ResourceCounts = List[ResourceCount] +ResourceCounts = list[ResourceCount] class GetDiscoveredResourceCountsResponse(TypedDict, total=False): - totalDiscoveredResources: Optional[Long] - resourceCounts: Optional[ResourceCounts] - nextToken: Optional[NextToken] + totalDiscoveredResources: Long | None + resourceCounts: ResourceCounts | None + nextToken: NextToken | None class StatusDetailFilters(TypedDict, total=False): - AccountId: Optional[AccountId] - MemberAccountRuleStatus: Optional[MemberAccountRuleStatus] + AccountId: AccountId | None + MemberAccountRuleStatus: MemberAccountRuleStatus | None class GetOrganizationConfigRuleDetailedStatusRequest(ServiceRequest): OrganizationConfigRuleName: OrganizationConfigRuleName - Filters: Optional[StatusDetailFilters] - Limit: Optional[CosmosPageLimit] - NextToken: Optional[String] + Filters: StatusDetailFilters | None + Limit: CosmosPageLimit | None + NextToken: String | None class MemberAccountStatus(TypedDict, total=False): AccountId: AccountId ConfigRuleName: StringWithCharLimit64 MemberAccountRuleStatus: MemberAccountRuleStatus - ErrorCode: Optional[String] - ErrorMessage: Optional[String] - LastUpdateTime: Optional[Date] + ErrorCode: String | None + ErrorMessage: String | None + LastUpdateTime: Date | None -OrganizationConfigRuleDetailedStatus = List[MemberAccountStatus] +OrganizationConfigRuleDetailedStatus = list[MemberAccountStatus] class GetOrganizationConfigRuleDetailedStatusResponse(TypedDict, total=False): - OrganizationConfigRuleDetailedStatus: Optional[OrganizationConfigRuleDetailedStatus] - NextToken: Optional[String] + OrganizationConfigRuleDetailedStatus: OrganizationConfigRuleDetailedStatus | None + NextToken: String | None class OrganizationResourceDetailedStatusFilters(TypedDict, total=False): - AccountId: Optional[AccountId] - Status: Optional[OrganizationResourceDetailedStatus] + AccountId: AccountId | None + Status: OrganizationResourceDetailedStatus | None class GetOrganizationConformancePackDetailedStatusRequest(ServiceRequest): OrganizationConformancePackName: OrganizationConformancePackName - Filters: Optional[OrganizationResourceDetailedStatusFilters] - Limit: Optional[CosmosPageLimit] - NextToken: Optional[String] + Filters: OrganizationResourceDetailedStatusFilters | None + Limit: CosmosPageLimit | None + NextToken: String | None class OrganizationConformancePackDetailedStatus(TypedDict, total=False): AccountId: AccountId ConformancePackName: StringWithCharLimit256 Status: OrganizationResourceDetailedStatus - ErrorCode: Optional[String] - ErrorMessage: Optional[String] - LastUpdateTime: Optional[Date] + ErrorCode: String | None + ErrorMessage: String | None + LastUpdateTime: Date | None -OrganizationConformancePackDetailedStatuses = List[OrganizationConformancePackDetailedStatus] +OrganizationConformancePackDetailedStatuses = list[OrganizationConformancePackDetailedStatus] class GetOrganizationConformancePackDetailedStatusResponse(TypedDict, total=False): - OrganizationConformancePackDetailedStatuses: Optional[ - OrganizationConformancePackDetailedStatuses - ] - NextToken: Optional[String] + OrganizationConformancePackDetailedStatuses: OrganizationConformancePackDetailedStatuses | None + NextToken: String | None class GetOrganizationCustomRulePolicyRequest(ServiceRequest): @@ -2669,7 +2764,7 @@ class GetOrganizationCustomRulePolicyRequest(ServiceRequest): class GetOrganizationCustomRulePolicyResponse(TypedDict, total=False): - PolicyText: Optional[PolicyText] + PolicyText: PolicyText | None LaterTime = datetime @@ -2678,16 +2773,16 @@ class GetOrganizationCustomRulePolicyResponse(TypedDict, total=False): class GetResourceConfigHistoryRequest(ServiceRequest): resourceType: ResourceType resourceId: ResourceId - laterTime: Optional[LaterTime] - earlierTime: Optional[EarlierTime] - chronologicalOrder: Optional[ChronologicalOrder] - limit: Optional[Limit] - nextToken: Optional[NextToken] + laterTime: LaterTime | None + earlierTime: EarlierTime | None + chronologicalOrder: ChronologicalOrder | None + limit: Limit | None + nextToken: NextToken | None class GetResourceConfigHistoryResponse(TypedDict, total=False): - configurationItems: Optional[ConfigurationItemList] - nextToken: Optional[NextToken] + configurationItems: ConfigurationItemList | None + nextToken: NextToken | None class GetResourceEvaluationSummaryRequest(ServiceRequest): @@ -2698,17 +2793,17 @@ class ResourceDetails(TypedDict, total=False): ResourceId: BaseResourceId ResourceType: StringWithCharLimit256 ResourceConfiguration: ResourceConfiguration - ResourceConfigurationSchemaType: Optional[ResourceConfigurationSchemaType] + ResourceConfigurationSchemaType: ResourceConfigurationSchemaType | None class GetResourceEvaluationSummaryResponse(TypedDict, total=False): - ResourceEvaluationId: Optional[ResourceEvaluationId] - EvaluationMode: Optional[EvaluationMode] - EvaluationStatus: Optional[EvaluationStatus] - EvaluationStartTimestamp: Optional[Date] - Compliance: Optional[ComplianceType] - EvaluationContext: Optional[EvaluationContext] - ResourceDetails: Optional[ResourceDetails] + ResourceEvaluationId: ResourceEvaluationId | None + EvaluationMode: EvaluationMode | None + EvaluationStatus: EvaluationStatus | None + EvaluationStartTimestamp: Date | None + Compliance: ComplianceType | None + EvaluationContext: EvaluationContext | None + ResourceDetails: ResourceDetails | None class GetStoredQueryRequest(ServiceRequest): @@ -2716,222 +2811,223 @@ class GetStoredQueryRequest(ServiceRequest): class StoredQuery(TypedDict, total=False): - QueryId: Optional[QueryId] - QueryArn: Optional[QueryArn] + QueryId: QueryId | None + QueryArn: QueryArn | None QueryName: QueryName - Description: Optional[QueryDescription] - Expression: Optional[QueryExpression] + Description: QueryDescription | None + Expression: QueryExpression | None class GetStoredQueryResponse(TypedDict, total=False): - StoredQuery: Optional[StoredQuery] + StoredQuery: StoredQuery | None class ResourceFilters(TypedDict, total=False): - AccountId: Optional[AccountId] - ResourceId: Optional[ResourceId] - ResourceName: Optional[ResourceName] - Region: Optional[AwsRegion] + AccountId: AccountId | None + ResourceId: ResourceId | None + ResourceName: ResourceName | None + Region: AwsRegion | None class ListAggregateDiscoveredResourcesRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName ResourceType: ResourceType - Filters: Optional[ResourceFilters] - Limit: Optional[Limit] - NextToken: Optional[NextToken] + Filters: ResourceFilters | None + Limit: Limit | None + NextToken: NextToken | None class ListAggregateDiscoveredResourcesResponse(TypedDict, total=False): - ResourceIdentifiers: Optional[DiscoveredResourceIdentifierList] - NextToken: Optional[NextToken] + ResourceIdentifiers: DiscoveredResourceIdentifierList | None + NextToken: NextToken | None class ListConfigurationRecordersRequest(ServiceRequest): - Filters: Optional[ConfigurationRecorderFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: ConfigurationRecorderFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListConfigurationRecordersResponse(TypedDict, total=False): ConfigurationRecorderSummaries: ConfigurationRecorderSummaries - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListConformancePackComplianceScoresRequest(ServiceRequest): - Filters: Optional[ConformancePackComplianceScoresFilters] - SortOrder: Optional[SortOrder] - SortBy: Optional[SortBy] - Limit: Optional[PageSizeLimit] - NextToken: Optional[NextToken] + Filters: ConformancePackComplianceScoresFilters | None + SortOrder: SortOrder | None + SortBy: SortBy | None + Limit: PageSizeLimit | None + NextToken: NextToken | None class ListConformancePackComplianceScoresResponse(TypedDict, total=False): - NextToken: Optional[NextToken] + NextToken: NextToken | None ConformancePackComplianceScores: ConformancePackComplianceScores -ResourceIdList = List[ResourceId] +ResourceIdList = list[ResourceId] class ListDiscoveredResourcesRequest(ServiceRequest): resourceType: ResourceType - resourceIds: Optional[ResourceIdList] - resourceName: Optional[ResourceName] - limit: Optional[Limit] - includeDeletedResources: Optional[Boolean] - nextToken: Optional[NextToken] + resourceIds: ResourceIdList | None + resourceName: ResourceName | None + limit: Limit | None + includeDeletedResources: Boolean | None + nextToken: NextToken | None ResourceDeletionTime = datetime class ResourceIdentifier(TypedDict, total=False): - resourceType: Optional[ResourceType] - resourceId: Optional[ResourceId] - resourceName: Optional[ResourceName] - resourceDeletionTime: Optional[ResourceDeletionTime] + resourceType: ResourceType | None + resourceId: ResourceId | None + resourceName: ResourceName | None + resourceDeletionTime: ResourceDeletionTime | None -ResourceIdentifierList = List[ResourceIdentifier] +ResourceIdentifierList = list[ResourceIdentifier] class ListDiscoveredResourcesResponse(TypedDict, total=False): - resourceIdentifiers: Optional[ResourceIdentifierList] - nextToken: Optional[NextToken] + resourceIdentifiers: ResourceIdentifierList | None + nextToken: NextToken | None class TimeWindow(TypedDict, total=False): - StartTime: Optional[Date] - EndTime: Optional[Date] + StartTime: Date | None + EndTime: Date | None class ResourceEvaluationFilters(TypedDict, total=False): - EvaluationMode: Optional[EvaluationMode] - TimeWindow: Optional[TimeWindow] - EvaluationContextIdentifier: Optional[EvaluationContextIdentifier] + EvaluationMode: EvaluationMode | None + TimeWindow: TimeWindow | None + EvaluationContextIdentifier: EvaluationContextIdentifier | None class ListResourceEvaluationsRequest(ServiceRequest): - Filters: Optional[ResourceEvaluationFilters] - Limit: Optional[ListResourceEvaluationsPageItemLimit] - NextToken: Optional[String] + Filters: ResourceEvaluationFilters | None + Limit: ListResourceEvaluationsPageItemLimit | None + NextToken: String | None class ResourceEvaluation(TypedDict, total=False): - ResourceEvaluationId: Optional[ResourceEvaluationId] - EvaluationMode: Optional[EvaluationMode] - EvaluationStartTimestamp: Optional[Date] + ResourceEvaluationId: ResourceEvaluationId | None + EvaluationMode: EvaluationMode | None + EvaluationStartTimestamp: Date | None -ResourceEvaluations = List[ResourceEvaluation] +ResourceEvaluations = list[ResourceEvaluation] class ListResourceEvaluationsResponse(TypedDict, total=False): - ResourceEvaluations: Optional[ResourceEvaluations] - NextToken: Optional[String] + ResourceEvaluations: ResourceEvaluations | None + NextToken: String | None class ListStoredQueriesRequest(ServiceRequest): - NextToken: Optional[String] - MaxResults: Optional[Limit] + NextToken: String | None + MaxResults: Limit | None class StoredQueryMetadata(TypedDict, total=False): QueryId: QueryId QueryArn: QueryArn QueryName: QueryName - Description: Optional[QueryDescription] + Description: QueryDescription | None -StoredQueryMetadataList = List[StoredQueryMetadata] +StoredQueryMetadataList = list[StoredQueryMetadata] class ListStoredQueriesResponse(TypedDict, total=False): - StoredQueryMetadata: Optional[StoredQueryMetadataList] - NextToken: Optional[String] + StoredQueryMetadata: StoredQueryMetadataList | None + NextToken: String | None class ListTagsForResourceRequest(ServiceRequest): ResourceArn: AmazonResourceName - Limit: Optional[Limit] - NextToken: Optional[NextToken] + Limit: Limit | None + NextToken: NextToken | None class Tag(TypedDict, total=False): - Key: Optional[TagKey] - Value: Optional[TagValue] + Key: TagKey | None + Value: TagValue | None -TagList = List[Tag] +TagList = list[Tag] class ListTagsForResourceResponse(TypedDict, total=False): - Tags: Optional[TagList] - NextToken: Optional[NextToken] + Tags: TagList | None + NextToken: NextToken | None class OrganizationCustomPolicyRuleMetadata(TypedDict, total=False): - Description: Optional[StringWithCharLimit256Min0] - OrganizationConfigRuleTriggerTypes: Optional[OrganizationConfigRuleTriggerTypeNoSNs] - InputParameters: Optional[StringWithCharLimit2048] - MaximumExecutionFrequency: Optional[MaximumExecutionFrequency] - ResourceTypesScope: Optional[ResourceTypesScope] - ResourceIdScope: Optional[StringWithCharLimit768] - TagKeyScope: Optional[StringWithCharLimit128] - TagValueScope: Optional[StringWithCharLimit256] + Description: StringWithCharLimit256Min0 | None + OrganizationConfigRuleTriggerTypes: OrganizationConfigRuleTriggerTypeNoSNs | None + InputParameters: StringWithCharLimit2048 | None + MaximumExecutionFrequency: MaximumExecutionFrequency | None + ResourceTypesScope: ResourceTypesScope | None + ResourceIdScope: StringWithCharLimit768 | None + TagKeyScope: StringWithCharLimit128 | None + TagValueScope: StringWithCharLimit256 | None PolicyRuntime: PolicyRuntime PolicyText: PolicyText - DebugLogDeliveryAccounts: Optional[DebugLogDeliveryAccounts] + DebugLogDeliveryAccounts: DebugLogDeliveryAccounts | None -TagsList = List[Tag] +TagsList = list[Tag] class PutAggregationAuthorizationRequest(ServiceRequest): AuthorizedAccountId: AccountId AuthorizedAwsRegion: AwsRegion - Tags: Optional[TagsList] + Tags: TagsList | None class PutAggregationAuthorizationResponse(TypedDict, total=False): - AggregationAuthorization: Optional[AggregationAuthorization] + AggregationAuthorization: AggregationAuthorization | None class PutConfigRuleRequest(ServiceRequest): ConfigRule: ConfigRule - Tags: Optional[TagsList] + Tags: TagsList | None class PutConfigurationAggregatorRequest(ServiceRequest): ConfigurationAggregatorName: ConfigurationAggregatorName - AccountAggregationSources: Optional[AccountAggregationSourceList] - OrganizationAggregationSource: Optional[OrganizationAggregationSource] - Tags: Optional[TagsList] - AggregatorFilters: Optional[AggregatorFilters] + AccountAggregationSources: AccountAggregationSourceList | None + OrganizationAggregationSource: OrganizationAggregationSource | None + Tags: TagsList | None + AggregatorFilters: AggregatorFilters | None class PutConfigurationAggregatorResponse(TypedDict, total=False): - ConfigurationAggregator: Optional[ConfigurationAggregator] + ConfigurationAggregator: ConfigurationAggregator | None class PutConfigurationRecorderRequest(ServiceRequest): ConfigurationRecorder: ConfigurationRecorder - Tags: Optional[TagsList] + Tags: TagsList | None class PutConformancePackRequest(ServiceRequest): ConformancePackName: ConformancePackName - TemplateS3Uri: Optional[TemplateS3Uri] - TemplateBody: Optional[TemplateBody] - DeliveryS3Bucket: Optional[DeliveryS3Bucket] - DeliveryS3KeyPrefix: Optional[DeliveryS3KeyPrefix] - ConformancePackInputParameters: Optional[ConformancePackInputParameters] - TemplateSSMDocumentDetails: Optional[TemplateSSMDocumentDetails] + TemplateS3Uri: TemplateS3Uri | None + TemplateBody: TemplateBody | None + DeliveryS3Bucket: DeliveryS3Bucket | None + DeliveryS3KeyPrefix: DeliveryS3KeyPrefix | None + ConformancePackInputParameters: ConformancePackInputParameters | None + TemplateSSMDocumentDetails: TemplateSSMDocumentDetails | None + Tags: TagsList | None class PutConformancePackResponse(TypedDict, total=False): - ConformancePackArn: Optional[ConformancePackArn] + ConformancePackArn: ConformancePackArn | None class PutDeliveryChannelRequest(ServiceRequest): @@ -2939,13 +3035,13 @@ class PutDeliveryChannelRequest(ServiceRequest): class PutEvaluationsRequest(ServiceRequest): - Evaluations: Optional[Evaluations] + Evaluations: Evaluations | None ResultToken: String - TestMode: Optional[Boolean] + TestMode: Boolean | None class PutEvaluationsResponse(TypedDict, total=False): - FailedEvaluations: Optional[Evaluations] + FailedEvaluations: Evaluations | None class PutExternalEvaluationRequest(ServiceRequest): @@ -2959,28 +3055,28 @@ class PutExternalEvaluationResponse(TypedDict, total=False): class PutOrganizationConfigRuleRequest(ServiceRequest): OrganizationConfigRuleName: OrganizationConfigRuleName - OrganizationManagedRuleMetadata: Optional[OrganizationManagedRuleMetadata] - OrganizationCustomRuleMetadata: Optional[OrganizationCustomRuleMetadata] - ExcludedAccounts: Optional[ExcludedAccounts] - OrganizationCustomPolicyRuleMetadata: Optional[OrganizationCustomPolicyRuleMetadata] + OrganizationManagedRuleMetadata: OrganizationManagedRuleMetadata | None + OrganizationCustomRuleMetadata: OrganizationCustomRuleMetadata | None + ExcludedAccounts: ExcludedAccounts | None + OrganizationCustomPolicyRuleMetadata: OrganizationCustomPolicyRuleMetadata | None class PutOrganizationConfigRuleResponse(TypedDict, total=False): - OrganizationConfigRuleArn: Optional[StringWithCharLimit256] + OrganizationConfigRuleArn: StringWithCharLimit256 | None class PutOrganizationConformancePackRequest(ServiceRequest): OrganizationConformancePackName: OrganizationConformancePackName - TemplateS3Uri: Optional[TemplateS3Uri] - TemplateBody: Optional[TemplateBody] - DeliveryS3Bucket: Optional[DeliveryS3Bucket] - DeliveryS3KeyPrefix: Optional[DeliveryS3KeyPrefix] - ConformancePackInputParameters: Optional[ConformancePackInputParameters] - ExcludedAccounts: Optional[ExcludedAccounts] + TemplateS3Uri: TemplateS3Uri | None + TemplateBody: TemplateBody | None + DeliveryS3Bucket: DeliveryS3Bucket | None + DeliveryS3KeyPrefix: DeliveryS3KeyPrefix | None + ConformancePackInputParameters: ConformancePackInputParameters | None + ExcludedAccounts: ExcludedAccounts | None class PutOrganizationConformancePackResponse(TypedDict, total=False): - OrganizationConformancePackArn: Optional[StringWithCharLimit256] + OrganizationConformancePackArn: StringWithCharLimit256 | None class PutRemediationConfigurationsRequest(ServiceRequest): @@ -2988,27 +3084,27 @@ class PutRemediationConfigurationsRequest(ServiceRequest): class PutRemediationConfigurationsResponse(TypedDict, total=False): - FailedBatches: Optional[FailedRemediationBatches] + FailedBatches: FailedRemediationBatches | None class PutRemediationExceptionsRequest(ServiceRequest): ConfigRuleName: ConfigRuleName ResourceKeys: RemediationExceptionResourceKeys - Message: Optional[StringWithCharLimit1024] - ExpirationTime: Optional[Date] + Message: StringWithCharLimit1024 | None + ExpirationTime: Date | None class PutRemediationExceptionsResponse(TypedDict, total=False): - FailedBatches: Optional[FailedRemediationExceptionBatches] + FailedBatches: FailedRemediationExceptionBatches | None class PutResourceConfigRequest(ServiceRequest): ResourceType: ResourceTypeString SchemaVersionId: SchemaVersionId ResourceId: ResourceId - ResourceName: Optional[ResourceName] + ResourceName: ResourceName | None Configuration: Configuration - Tags: Optional[Tags] + Tags: Tags | None class PutRetentionConfigurationRequest(ServiceRequest): @@ -3016,64 +3112,64 @@ class PutRetentionConfigurationRequest(ServiceRequest): class PutRetentionConfigurationResponse(TypedDict, total=False): - RetentionConfiguration: Optional[RetentionConfiguration] + RetentionConfiguration: RetentionConfiguration | None class PutServiceLinkedConfigurationRecorderRequest(ServiceRequest): ServicePrincipal: ServicePrincipal - Tags: Optional[TagsList] + Tags: TagsList | None class PutServiceLinkedConfigurationRecorderResponse(TypedDict, total=False): - Arn: Optional[AmazonResourceName] - Name: Optional[RecorderName] + Arn: AmazonResourceName | None + Name: RecorderName | None class PutStoredQueryRequest(ServiceRequest): StoredQuery: StoredQuery - Tags: Optional[TagsList] + Tags: TagsList | None class PutStoredQueryResponse(TypedDict, total=False): - QueryArn: Optional[QueryArn] + QueryArn: QueryArn | None class QueryInfo(TypedDict, total=False): - SelectFields: Optional[FieldInfoList] + SelectFields: FieldInfoList | None -ReevaluateConfigRuleNames = List[ConfigRuleName] -Results = List[String] +ReevaluateConfigRuleNames = list[ConfigRuleName] +Results = list[String] class SelectAggregateResourceConfigRequest(ServiceRequest): Expression: Expression ConfigurationAggregatorName: ConfigurationAggregatorName - Limit: Optional[Limit] - MaxResults: Optional[Limit] - NextToken: Optional[NextToken] + Limit: Limit | None + MaxResults: Limit | None + NextToken: NextToken | None class SelectAggregateResourceConfigResponse(TypedDict, total=False): - Results: Optional[Results] - QueryInfo: Optional[QueryInfo] - NextToken: Optional[NextToken] + Results: Results | None + QueryInfo: QueryInfo | None + NextToken: NextToken | None class SelectResourceConfigRequest(ServiceRequest): Expression: Expression - Limit: Optional[Limit] - NextToken: Optional[NextToken] + Limit: Limit | None + NextToken: NextToken | None class SelectResourceConfigResponse(TypedDict, total=False): - Results: Optional[Results] - QueryInfo: Optional[QueryInfo] - NextToken: Optional[NextToken] + Results: Results | None + QueryInfo: QueryInfo | None + NextToken: NextToken | None class StartConfigRulesEvaluationRequest(ServiceRequest): - ConfigRuleNames: Optional[ReevaluateConfigRuleNames] + ConfigRuleNames: ReevaluateConfigRuleNames | None class StartConfigRulesEvaluationResponse(TypedDict, total=False): @@ -3090,27 +3186,27 @@ class StartRemediationExecutionRequest(ServiceRequest): class StartRemediationExecutionResponse(TypedDict, total=False): - FailureMessage: Optional[String] - FailedItems: Optional[ResourceKeys] + FailureMessage: String | None + FailedItems: ResourceKeys | None class StartResourceEvaluationRequest(ServiceRequest): ResourceDetails: ResourceDetails - EvaluationContext: Optional[EvaluationContext] + EvaluationContext: EvaluationContext | None EvaluationMode: EvaluationMode - EvaluationTimeout: Optional[EvaluationTimeout] - ClientToken: Optional[ClientToken] + EvaluationTimeout: EvaluationTimeout | None + ClientToken: ClientToken | None class StartResourceEvaluationResponse(TypedDict, total=False): - ResourceEvaluationId: Optional[ResourceEvaluationId] + ResourceEvaluationId: ResourceEvaluationId | None class StopConfigurationRecorderRequest(ServiceRequest): ConfigurationRecorderName: RecorderName -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceRequest(ServiceRequest): @@ -3124,8 +3220,8 @@ class UntagResourceRequest(ServiceRequest): class ConfigApi: - service = "config" - version = "2014-11-12" + service: str = "config" + version: str = "2014-11-12" @handler("AssociateResourceTypes") def associate_resource_types( @@ -3907,6 +4003,7 @@ def put_conformance_pack( delivery_s3_key_prefix: DeliveryS3KeyPrefix | None = None, conformance_pack_input_parameters: ConformancePackInputParameters | None = None, template_ssm_document_details: TemplateSSMDocumentDetails | None = None, + tags: TagsList | None = None, **kwargs, ) -> PutConformancePackResponse: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/core.py b/localstack-core/localstack/aws/api/core.py index dbe32d7973284..d05ef5125b586 100644 --- a/localstack-core/localstack/aws/api/core.py +++ b/localstack-core/localstack/aws/api/core.py @@ -1,11 +1,10 @@ import functools +from collections.abc import Callable from typing import ( Any, - Callable, NamedTuple, ParamSpec, Protocol, - Type, TypedDict, TypeVar, ) @@ -14,6 +13,7 @@ from rolo.gateway import RequestContext as RoloRequestContext from localstack.aws.connect import InternalRequestParameters +from localstack.aws.spec import ProtocolName from localstack.http import Request, Response from localstack.utils.strings import long_uid @@ -42,7 +42,7 @@ class ServiceException(Exception): message: str def __init__(self, *args: Any, **kwargs: Any): - super(ServiceException, self).__init__(*args) + super().__init__(*args) if len(args) >= 1: self.message = args[0] @@ -61,13 +61,13 @@ class CommonServiceException(ServiceException): """ def __init__(self, code: str, message: str, status_code: int = 400, sender_fault: bool = False): - super(CommonServiceException, self).__init__(message) + super().__init__(message) self.code = code self.status_code = status_code self.sender_fault = sender_fault -Operation = Type[ServiceRequest] +Operation = type[ServiceRequest] class ServiceOperation(NamedTuple): @@ -89,6 +89,8 @@ class RequestContext(RoloRequestContext): """The underlying incoming HTTP request.""" service: ServiceModel | None """The botocore ServiceModel of the service the request is made to.""" + protocol: ProtocolName | None + """The botocore Protocol for the service the request is made to.""" operation: OperationModel | None """The botocore OperationModel of the AWS operation being invoked.""" region: str @@ -113,6 +115,7 @@ class RequestContext(RoloRequestContext): def __init__(self, request: Request): super().__init__(request) self.service = None + self.protocol = None self.operation = None self.region = None # type: ignore[assignment] # type=str, because we know it will always be set downstream self.partition = "aws" # Sensible default - will be overwritten by region-handler diff --git a/localstack-core/localstack/aws/api/dynamodb/__init__.py b/localstack-core/localstack/aws/api/dynamodb/__init__.py index abb79fbbad4b9..8c8e1a6d58903 100644 --- a/localstack-core/localstack/aws/api/dynamodb/__init__.py +++ b/localstack-core/localstack/aws/api/dynamodb/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -8,6 +8,7 @@ AttributeName = str AutoScalingPolicyName = str AutoScalingRoleArn = str +AvailabilityErrorMessage = str Backfilling = bool BackupArn = str BackupName = str @@ -61,10 +62,12 @@ PolicyRevisionId = str PositiveIntegerObject = int ProjectionExpression = str +Reason = str RecoveryPeriodInDays = int RegionName = str ReplicaStatusDescription = str ReplicaStatusPercentProgress = str +Resource = str ResourceArnString = str ResourcePolicy = str RestoreInProgress = bool @@ -169,6 +172,11 @@ class ContributorInsightsAction(StrEnum): DISABLE = "DISABLE" +class ContributorInsightsMode(StrEnum): + ACCESSED_AND_THROTTLED_KEYS = "ACCESSED_AND_THROTTLED_KEYS" + THROTTLED_KEYS = "THROTTLED_KEYS" + + class ContributorInsightsStatus(StrEnum): ENABLING = "ENABLING" ENABLED = "ENABLED" @@ -207,6 +215,12 @@ class ExportViewType(StrEnum): NEW_AND_OLD_IMAGES = "NEW_AND_OLD_IMAGES" +class GlobalTableSettingsReplicationMode(StrEnum): + ENABLED = "ENABLED" + DISABLED = "DISABLED" + ENABLED_WITH_OVERRIDES = "ENABLED_WITH_OVERRIDES" + + class GlobalTableStatus(StrEnum): CREATING = "CREATING" ACTIVE = "ACTIVE" @@ -379,32 +393,32 @@ class BackupNotFoundException(ServiceException): class AttributeValue(TypedDict, total=False): - S: Optional["StringAttributeValue"] - N: Optional["NumberAttributeValue"] - B: Optional["BinaryAttributeValue"] - SS: Optional["StringSetAttributeValue"] - NS: Optional["NumberSetAttributeValue"] - BS: Optional["BinarySetAttributeValue"] - M: Optional["MapAttributeValue"] - L: Optional["ListAttributeValue"] - NULL: Optional["NullAttributeValue"] - BOOL: Optional["BooleanAttributeValue"] - - -ListAttributeValue = List[AttributeValue] -MapAttributeValue = Dict[AttributeName, AttributeValue] + S: "StringAttributeValue | None" + N: "NumberAttributeValue | None" + B: "BinaryAttributeValue | None" + SS: "StringSetAttributeValue | None" + NS: "NumberSetAttributeValue | None" + BS: "BinarySetAttributeValue | None" + M: "MapAttributeValue | None" + L: "ListAttributeValue | None" + NULL: "NullAttributeValue | None" + BOOL: "BooleanAttributeValue | None" + + +ListAttributeValue = list[AttributeValue] +MapAttributeValue = dict[AttributeName, AttributeValue] BinaryAttributeValue = bytes -BinarySetAttributeValue = List[BinaryAttributeValue] -NumberSetAttributeValue = List[NumberAttributeValue] -StringSetAttributeValue = List[StringAttributeValue] -AttributeMap = Dict[AttributeName, AttributeValue] +BinarySetAttributeValue = list[BinaryAttributeValue] +NumberSetAttributeValue = list[NumberAttributeValue] +StringSetAttributeValue = list[StringAttributeValue] +AttributeMap = dict[AttributeName, AttributeValue] class ConditionalCheckFailedException(ServiceException): code: str = "ConditionalCheckFailedException" sender_fault: bool = False status_code: int = 400 - Item: Optional[AttributeMap] + Item: AttributeMap | None class ContinuousBackupsUnavailableException(ServiceException): @@ -509,10 +523,19 @@ class PolicyNotFoundException(ServiceException): status_code: int = 400 +class ThrottlingReason(TypedDict, total=False): + reason: Reason | None + resource: Resource | None + + +ThrottlingReasonList = list[ThrottlingReason] + + class ProvisionedThroughputExceededException(ServiceException): code: str = "ProvisionedThroughputExceededException" sender_fault: bool = False status_code: int = 400 + ThrottlingReasons: ThrottlingReasonList | None class ReplicaAlreadyExistsException(ServiceException): @@ -537,6 +560,7 @@ class RequestLimitExceeded(ServiceException): code: str = "RequestLimitExceeded" sender_fault: bool = False status_code: int = 400 + ThrottlingReasons: ThrottlingReasonList | None class ResourceInUseException(ServiceException): @@ -569,20 +593,27 @@ class TableNotFoundException(ServiceException): status_code: int = 400 +class ThrottlingException(ServiceException): + code: str = "ThrottlingException" + sender_fault: bool = False + status_code: int = 400 + throttlingReasons: ThrottlingReasonList | None + + class CancellationReason(TypedDict, total=False): - Item: Optional[AttributeMap] - Code: Optional[Code] - Message: Optional[ErrorMessage] + Item: AttributeMap | None + Code: Code | None + Message: ErrorMessage | None -CancellationReasonList = List[CancellationReason] +CancellationReasonList = list[CancellationReason] class TransactionCanceledException(ServiceException): code: str = "TransactionCanceledException" sender_fault: bool = False status_code: int = 400 - CancellationReasons: Optional[CancellationReasonList] + CancellationReasons: CancellationReasonList | None class TransactionConflictException(ServiceException): @@ -601,9 +632,9 @@ class TransactionInProgressException(ServiceException): class ArchivalSummary(TypedDict, total=False): - ArchivalDateTime: Optional[Date] - ArchivalReason: Optional[ArchivalReason] - ArchivalBackupArn: Optional[BackupArn] + ArchivalDateTime: Date | None + ArchivalReason: ArchivalReason | None + ArchivalBackupArn: BackupArn | None class AttributeDefinition(TypedDict, total=False): @@ -611,45 +642,45 @@ class AttributeDefinition(TypedDict, total=False): AttributeType: ScalarAttributeType -AttributeDefinitions = List[AttributeDefinition] -AttributeNameList = List[AttributeName] +AttributeDefinitions = list[AttributeDefinition] +AttributeNameList = list[AttributeName] class AttributeValueUpdate(TypedDict, total=False): - Value: Optional[AttributeValue] - Action: Optional[AttributeAction] + Value: AttributeValue | None + Action: AttributeAction | None -AttributeUpdates = Dict[AttributeName, AttributeValueUpdate] -AttributeValueList = List[AttributeValue] +AttributeUpdates = dict[AttributeName, AttributeValueUpdate] +AttributeValueList = list[AttributeValue] class AutoScalingTargetTrackingScalingPolicyConfigurationDescription(TypedDict, total=False): - DisableScaleIn: Optional[BooleanObject] - ScaleInCooldown: Optional[IntegerObject] - ScaleOutCooldown: Optional[IntegerObject] + DisableScaleIn: BooleanObject | None + ScaleInCooldown: IntegerObject | None + ScaleOutCooldown: IntegerObject | None TargetValue: DoubleObject class AutoScalingPolicyDescription(TypedDict, total=False): - PolicyName: Optional[AutoScalingPolicyName] - TargetTrackingScalingPolicyConfiguration: Optional[ - AutoScalingTargetTrackingScalingPolicyConfigurationDescription - ] + PolicyName: AutoScalingPolicyName | None + TargetTrackingScalingPolicyConfiguration: ( + AutoScalingTargetTrackingScalingPolicyConfigurationDescription | None + ) -AutoScalingPolicyDescriptionList = List[AutoScalingPolicyDescription] +AutoScalingPolicyDescriptionList = list[AutoScalingPolicyDescription] class AutoScalingTargetTrackingScalingPolicyConfigurationUpdate(TypedDict, total=False): - DisableScaleIn: Optional[BooleanObject] - ScaleInCooldown: Optional[IntegerObject] - ScaleOutCooldown: Optional[IntegerObject] + DisableScaleIn: BooleanObject | None + ScaleInCooldown: IntegerObject | None + ScaleOutCooldown: IntegerObject | None TargetValue: DoubleObject class AutoScalingPolicyUpdate(TypedDict, total=False): - PolicyName: Optional[AutoScalingPolicyName] + PolicyName: AutoScalingPolicyName | None TargetTrackingScalingPolicyConfiguration: ( AutoScalingTargetTrackingScalingPolicyConfigurationUpdate ) @@ -659,47 +690,47 @@ class AutoScalingPolicyUpdate(TypedDict, total=False): class AutoScalingSettingsDescription(TypedDict, total=False): - MinimumUnits: Optional[PositiveLongObject] - MaximumUnits: Optional[PositiveLongObject] - AutoScalingDisabled: Optional[BooleanObject] - AutoScalingRoleArn: Optional[String] - ScalingPolicies: Optional[AutoScalingPolicyDescriptionList] + MinimumUnits: PositiveLongObject | None + MaximumUnits: PositiveLongObject | None + AutoScalingDisabled: BooleanObject | None + AutoScalingRoleArn: String | None + ScalingPolicies: AutoScalingPolicyDescriptionList | None class AutoScalingSettingsUpdate(TypedDict, total=False): - MinimumUnits: Optional[PositiveLongObject] - MaximumUnits: Optional[PositiveLongObject] - AutoScalingDisabled: Optional[BooleanObject] - AutoScalingRoleArn: Optional[AutoScalingRoleArn] - ScalingPolicyUpdate: Optional[AutoScalingPolicyUpdate] + MinimumUnits: PositiveLongObject | None + MaximumUnits: PositiveLongObject | None + AutoScalingDisabled: BooleanObject | None + AutoScalingRoleArn: AutoScalingRoleArn | None + ScalingPolicyUpdate: AutoScalingPolicyUpdate | None BackupCreationDateTime = datetime class SSEDescription(TypedDict, total=False): - Status: Optional[SSEStatus] - SSEType: Optional[SSEType] - KMSMasterKeyArn: Optional[KMSMasterKeyArn] - InaccessibleEncryptionDateTime: Optional[Date] + Status: SSEStatus | None + SSEType: SSEType | None + KMSMasterKeyArn: KMSMasterKeyArn | None + InaccessibleEncryptionDateTime: Date | None class TimeToLiveDescription(TypedDict, total=False): - TimeToLiveStatus: Optional[TimeToLiveStatus] - AttributeName: Optional[TimeToLiveAttributeName] + TimeToLiveStatus: TimeToLiveStatus | None + AttributeName: TimeToLiveAttributeName | None class StreamSpecification(TypedDict, total=False): StreamEnabled: StreamEnabled - StreamViewType: Optional[StreamViewType] + StreamViewType: StreamViewType | None LongObject = int class OnDemandThroughput(TypedDict, total=False): - MaxReadRequestUnits: Optional[LongObject] - MaxWriteRequestUnits: Optional[LongObject] + MaxReadRequestUnits: LongObject | None + MaxWriteRequestUnits: LongObject | None class ProvisionedThroughput(TypedDict, total=False): @@ -707,12 +738,12 @@ class ProvisionedThroughput(TypedDict, total=False): WriteCapacityUnits: PositiveLongObject -NonKeyAttributeNameList = List[NonKeyAttributeName] +NonKeyAttributeNameList = list[NonKeyAttributeName] class Projection(TypedDict, total=False): - ProjectionType: Optional[ProjectionType] - NonKeyAttributes: Optional[NonKeyAttributeNameList] + ProjectionType: ProjectionType | None + NonKeyAttributes: NonKeyAttributeNameList | None class KeySchemaElement(TypedDict, total=False): @@ -720,35 +751,35 @@ class KeySchemaElement(TypedDict, total=False): KeyType: KeyType -KeySchema = List[KeySchemaElement] +KeySchema = list[KeySchemaElement] class GlobalSecondaryIndexInfo(TypedDict, total=False): - IndexName: Optional[IndexName] - KeySchema: Optional[KeySchema] - Projection: Optional[Projection] - ProvisionedThroughput: Optional[ProvisionedThroughput] - OnDemandThroughput: Optional[OnDemandThroughput] + IndexName: IndexName | None + KeySchema: KeySchema | None + Projection: Projection | None + ProvisionedThroughput: ProvisionedThroughput | None + OnDemandThroughput: OnDemandThroughput | None -GlobalSecondaryIndexes = List[GlobalSecondaryIndexInfo] +GlobalSecondaryIndexes = list[GlobalSecondaryIndexInfo] class LocalSecondaryIndexInfo(TypedDict, total=False): - IndexName: Optional[IndexName] - KeySchema: Optional[KeySchema] - Projection: Optional[Projection] + IndexName: IndexName | None + KeySchema: KeySchema | None + Projection: Projection | None -LocalSecondaryIndexes = List[LocalSecondaryIndexInfo] +LocalSecondaryIndexes = list[LocalSecondaryIndexInfo] class SourceTableFeatureDetails(TypedDict, total=False): - LocalSecondaryIndexes: Optional[LocalSecondaryIndexes] - GlobalSecondaryIndexes: Optional[GlobalSecondaryIndexes] - StreamDescription: Optional[StreamSpecification] - TimeToLiveDescription: Optional[TimeToLiveDescription] - SSEDescription: Optional[SSEDescription] + LocalSecondaryIndexes: LocalSecondaryIndexes | None + GlobalSecondaryIndexes: GlobalSecondaryIndexes | None + StreamDescription: StreamSpecification | None + TimeToLiveDescription: TimeToLiveDescription | None + SSEDescription: SSEDescription | None ItemCount = int @@ -758,14 +789,14 @@ class SourceTableFeatureDetails(TypedDict, total=False): class SourceTableDetails(TypedDict, total=False): TableName: TableName TableId: TableId - TableArn: Optional[TableArn] - TableSizeBytes: Optional[LongObject] + TableArn: TableArn | None + TableSizeBytes: LongObject | None KeySchema: KeySchema TableCreationDateTime: TableCreationDateTime ProvisionedThroughput: ProvisionedThroughput - OnDemandThroughput: Optional[OnDemandThroughput] - ItemCount: Optional[ItemCount] - BillingMode: Optional[BillingMode] + OnDemandThroughput: OnDemandThroughput | None + ItemCount: ItemCount | None + BillingMode: BillingMode | None BackupSizeBytes = int @@ -774,129 +805,129 @@ class SourceTableDetails(TypedDict, total=False): class BackupDetails(TypedDict, total=False): BackupArn: BackupArn BackupName: BackupName - BackupSizeBytes: Optional[BackupSizeBytes] + BackupSizeBytes: BackupSizeBytes | None BackupStatus: BackupStatus BackupType: BackupType BackupCreationDateTime: BackupCreationDateTime - BackupExpiryDateTime: Optional[Date] + BackupExpiryDateTime: Date | None class BackupDescription(TypedDict, total=False): - BackupDetails: Optional[BackupDetails] - SourceTableDetails: Optional[SourceTableDetails] - SourceTableFeatureDetails: Optional[SourceTableFeatureDetails] + BackupDetails: BackupDetails | None + SourceTableDetails: SourceTableDetails | None + SourceTableFeatureDetails: SourceTableFeatureDetails | None class BackupSummary(TypedDict, total=False): - TableName: Optional[TableName] - TableId: Optional[TableId] - TableArn: Optional[TableArn] - BackupArn: Optional[BackupArn] - BackupName: Optional[BackupName] - BackupCreationDateTime: Optional[BackupCreationDateTime] - BackupExpiryDateTime: Optional[Date] - BackupStatus: Optional[BackupStatus] - BackupType: Optional[BackupType] - BackupSizeBytes: Optional[BackupSizeBytes] + TableName: TableName | None + TableId: TableId | None + TableArn: TableArn | None + BackupArn: BackupArn | None + BackupName: BackupName | None + BackupCreationDateTime: BackupCreationDateTime | None + BackupExpiryDateTime: Date | None + BackupStatus: BackupStatus | None + BackupType: BackupType | None + BackupSizeBytes: BackupSizeBytes | None -BackupSummaries = List[BackupSummary] -PreparedStatementParameters = List[AttributeValue] +BackupSummaries = list[BackupSummary] +PreparedStatementParameters = list[AttributeValue] class BatchStatementRequest(TypedDict, total=False): Statement: PartiQLStatement - Parameters: Optional[PreparedStatementParameters] - ConsistentRead: Optional[ConsistentRead] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + Parameters: PreparedStatementParameters | None + ConsistentRead: ConsistentRead | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None -PartiQLBatchRequest = List[BatchStatementRequest] +PartiQLBatchRequest = list[BatchStatementRequest] class BatchExecuteStatementInput(ServiceRequest): Statements: PartiQLBatchRequest - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] + ReturnConsumedCapacity: ReturnConsumedCapacity | None class Capacity(TypedDict, total=False): - ReadCapacityUnits: Optional[ConsumedCapacityUnits] - WriteCapacityUnits: Optional[ConsumedCapacityUnits] - CapacityUnits: Optional[ConsumedCapacityUnits] + ReadCapacityUnits: ConsumedCapacityUnits | None + WriteCapacityUnits: ConsumedCapacityUnits | None + CapacityUnits: ConsumedCapacityUnits | None -SecondaryIndexesCapacityMap = Dict[IndexName, Capacity] +SecondaryIndexesCapacityMap = dict[IndexName, Capacity] class ConsumedCapacity(TypedDict, total=False): - TableName: Optional[TableArn] - CapacityUnits: Optional[ConsumedCapacityUnits] - ReadCapacityUnits: Optional[ConsumedCapacityUnits] - WriteCapacityUnits: Optional[ConsumedCapacityUnits] - Table: Optional[Capacity] - LocalSecondaryIndexes: Optional[SecondaryIndexesCapacityMap] - GlobalSecondaryIndexes: Optional[SecondaryIndexesCapacityMap] + TableName: TableArn | None + CapacityUnits: ConsumedCapacityUnits | None + ReadCapacityUnits: ConsumedCapacityUnits | None + WriteCapacityUnits: ConsumedCapacityUnits | None + Table: Capacity | None + LocalSecondaryIndexes: SecondaryIndexesCapacityMap | None + GlobalSecondaryIndexes: SecondaryIndexesCapacityMap | None -ConsumedCapacityMultiple = List[ConsumedCapacity] +ConsumedCapacityMultiple = list[ConsumedCapacity] class BatchStatementError(TypedDict, total=False): - Code: Optional[BatchStatementErrorCodeEnum] - Message: Optional[String] - Item: Optional[AttributeMap] + Code: BatchStatementErrorCodeEnum | None + Message: String | None + Item: AttributeMap | None class BatchStatementResponse(TypedDict, total=False): - Error: Optional[BatchStatementError] - TableName: Optional[TableName] - Item: Optional[AttributeMap] + Error: BatchStatementError | None + TableName: TableName | None + Item: AttributeMap | None -PartiQLBatchResponse = List[BatchStatementResponse] +PartiQLBatchResponse = list[BatchStatementResponse] class BatchExecuteStatementOutput(TypedDict, total=False): - Responses: Optional[PartiQLBatchResponse] - ConsumedCapacity: Optional[ConsumedCapacityMultiple] + Responses: PartiQLBatchResponse | None + ConsumedCapacity: ConsumedCapacityMultiple | None -ExpressionAttributeNameMap = Dict[ExpressionAttributeNameVariable, AttributeName] -Key = Dict[AttributeName, AttributeValue] -KeyList = List[Key] +ExpressionAttributeNameMap = dict[ExpressionAttributeNameVariable, AttributeName] +Key = dict[AttributeName, AttributeValue] +KeyList = list[Key] class KeysAndAttributes(TypedDict, total=False): Keys: KeyList - AttributesToGet: Optional[AttributeNameList] - ConsistentRead: Optional[ConsistentRead] - ProjectionExpression: Optional[ProjectionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] + AttributesToGet: AttributeNameList | None + ConsistentRead: ConsistentRead | None + ProjectionExpression: ProjectionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None -BatchGetRequestMap = Dict[TableArn, KeysAndAttributes] +BatchGetRequestMap = dict[TableArn, KeysAndAttributes] class BatchGetItemInput(ServiceRequest): RequestItems: BatchGetRequestMap - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] + ReturnConsumedCapacity: ReturnConsumedCapacity | None -ItemList = List[AttributeMap] -BatchGetResponseMap = Dict[TableArn, ItemList] +ItemList = list[AttributeMap] +BatchGetResponseMap = dict[TableArn, ItemList] class BatchGetItemOutput(TypedDict, total=False): - Responses: Optional[BatchGetResponseMap] - UnprocessedKeys: Optional[BatchGetRequestMap] - ConsumedCapacity: Optional[ConsumedCapacityMultiple] + Responses: BatchGetResponseMap | None + UnprocessedKeys: BatchGetRequestMap | None + ConsumedCapacity: ConsumedCapacityMultiple | None class DeleteRequest(TypedDict, total=False): Key: Key -PutItemInputAttributeMap = Dict[AttributeName, AttributeValue] +PutItemInputAttributeMap = dict[AttributeName, AttributeValue] class PutRequest(TypedDict, total=False): @@ -904,86 +935,87 @@ class PutRequest(TypedDict, total=False): class WriteRequest(TypedDict, total=False): - PutRequest: Optional[PutRequest] - DeleteRequest: Optional[DeleteRequest] + PutRequest: PutRequest | None + DeleteRequest: DeleteRequest | None -WriteRequests = List[WriteRequest] -BatchWriteItemRequestMap = Dict[TableArn, WriteRequests] +WriteRequests = list[WriteRequest] +BatchWriteItemRequestMap = dict[TableArn, WriteRequests] class BatchWriteItemInput(ServiceRequest): RequestItems: BatchWriteItemRequestMap - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - ReturnItemCollectionMetrics: Optional[ReturnItemCollectionMetrics] + ReturnConsumedCapacity: ReturnConsumedCapacity | None + ReturnItemCollectionMetrics: ReturnItemCollectionMetrics | None -ItemCollectionSizeEstimateRange = List[ItemCollectionSizeEstimateBound] -ItemCollectionKeyAttributeMap = Dict[AttributeName, AttributeValue] +ItemCollectionSizeEstimateRange = list[ItemCollectionSizeEstimateBound] +ItemCollectionKeyAttributeMap = dict[AttributeName, AttributeValue] class ItemCollectionMetrics(TypedDict, total=False): - ItemCollectionKey: Optional[ItemCollectionKeyAttributeMap] - SizeEstimateRangeGB: Optional[ItemCollectionSizeEstimateRange] + ItemCollectionKey: ItemCollectionKeyAttributeMap | None + SizeEstimateRangeGB: ItemCollectionSizeEstimateRange | None -ItemCollectionMetricsMultiple = List[ItemCollectionMetrics] -ItemCollectionMetricsPerTable = Dict[TableArn, ItemCollectionMetricsMultiple] +ItemCollectionMetricsMultiple = list[ItemCollectionMetrics] +ItemCollectionMetricsPerTable = dict[TableArn, ItemCollectionMetricsMultiple] class BatchWriteItemOutput(TypedDict, total=False): - UnprocessedItems: Optional[BatchWriteItemRequestMap] - ItemCollectionMetrics: Optional[ItemCollectionMetricsPerTable] - ConsumedCapacity: Optional[ConsumedCapacityMultiple] + UnprocessedItems: BatchWriteItemRequestMap | None + ItemCollectionMetrics: ItemCollectionMetricsPerTable | None + ConsumedCapacity: ConsumedCapacityMultiple | None BilledSizeBytes = int class BillingModeSummary(TypedDict, total=False): - BillingMode: Optional[BillingMode] - LastUpdateToPayPerRequestDateTime: Optional[Date] + BillingMode: BillingMode | None + LastUpdateToPayPerRequestDateTime: Date | None class Condition(TypedDict, total=False): - AttributeValueList: Optional[AttributeValueList] + AttributeValueList: AttributeValueList | None ComparisonOperator: ComparisonOperator -ExpressionAttributeValueMap = Dict[ExpressionAttributeValueVariable, AttributeValue] +ExpressionAttributeValueMap = dict[ExpressionAttributeValueVariable, AttributeValue] class ConditionCheck(TypedDict, total=False): Key: Key TableName: TableArn ConditionExpression: ConditionExpression - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class PointInTimeRecoveryDescription(TypedDict, total=False): - PointInTimeRecoveryStatus: Optional[PointInTimeRecoveryStatus] - RecoveryPeriodInDays: Optional[RecoveryPeriodInDays] - EarliestRestorableDateTime: Optional[Date] - LatestRestorableDateTime: Optional[Date] + PointInTimeRecoveryStatus: PointInTimeRecoveryStatus | None + RecoveryPeriodInDays: RecoveryPeriodInDays | None + EarliestRestorableDateTime: Date | None + LatestRestorableDateTime: Date | None class ContinuousBackupsDescription(TypedDict, total=False): ContinuousBackupsStatus: ContinuousBackupsStatus - PointInTimeRecoveryDescription: Optional[PointInTimeRecoveryDescription] + PointInTimeRecoveryDescription: PointInTimeRecoveryDescription | None -ContributorInsightsRuleList = List[ContributorInsightsRule] +ContributorInsightsRuleList = list[ContributorInsightsRule] class ContributorInsightsSummary(TypedDict, total=False): - TableName: Optional[TableName] - IndexName: Optional[IndexName] - ContributorInsightsStatus: Optional[ContributorInsightsStatus] + TableName: TableName | None + IndexName: IndexName | None + ContributorInsightsStatus: ContributorInsightsStatus | None + ContributorInsightsMode: ContributorInsightsMode | None -ContributorInsightsSummaries = List[ContributorInsightsSummary] +ContributorInsightsSummaries = list[ContributorInsightsSummary] class CreateBackupInput(ServiceRequest): @@ -992,28 +1024,28 @@ class CreateBackupInput(ServiceRequest): class CreateBackupOutput(TypedDict, total=False): - BackupDetails: Optional[BackupDetails] + BackupDetails: BackupDetails | None class WarmThroughput(TypedDict, total=False): - ReadUnitsPerSecond: Optional[LongObject] - WriteUnitsPerSecond: Optional[LongObject] + ReadUnitsPerSecond: LongObject | None + WriteUnitsPerSecond: LongObject | None class CreateGlobalSecondaryIndexAction(TypedDict, total=False): IndexName: IndexName KeySchema: KeySchema Projection: Projection - ProvisionedThroughput: Optional[ProvisionedThroughput] - OnDemandThroughput: Optional[OnDemandThroughput] - WarmThroughput: Optional[WarmThroughput] + ProvisionedThroughput: ProvisionedThroughput | None + OnDemandThroughput: OnDemandThroughput | None + WarmThroughput: WarmThroughput | None class Replica(TypedDict, total=False): - RegionName: Optional[RegionName] + RegionName: RegionName | None -ReplicaList = List[Replica] +ReplicaList = list[Replica] class CreateGlobalTableInput(ServiceRequest): @@ -1022,67 +1054,68 @@ class CreateGlobalTableInput(ServiceRequest): class TableClassSummary(TypedDict, total=False): - TableClass: Optional[TableClass] - LastUpdateDateTime: Optional[Date] + TableClass: TableClass | None + LastUpdateDateTime: Date | None class GlobalSecondaryIndexWarmThroughputDescription(TypedDict, total=False): - ReadUnitsPerSecond: Optional[PositiveLongObject] - WriteUnitsPerSecond: Optional[PositiveLongObject] - Status: Optional[IndexStatus] + ReadUnitsPerSecond: PositiveLongObject | None + WriteUnitsPerSecond: PositiveLongObject | None + Status: IndexStatus | None class OnDemandThroughputOverride(TypedDict, total=False): - MaxReadRequestUnits: Optional[LongObject] + MaxReadRequestUnits: LongObject | None class ProvisionedThroughputOverride(TypedDict, total=False): - ReadCapacityUnits: Optional[PositiveLongObject] + ReadCapacityUnits: PositiveLongObject | None class ReplicaGlobalSecondaryIndexDescription(TypedDict, total=False): - IndexName: Optional[IndexName] - ProvisionedThroughputOverride: Optional[ProvisionedThroughputOverride] - OnDemandThroughputOverride: Optional[OnDemandThroughputOverride] - WarmThroughput: Optional[GlobalSecondaryIndexWarmThroughputDescription] + IndexName: IndexName | None + ProvisionedThroughputOverride: ProvisionedThroughputOverride | None + OnDemandThroughputOverride: OnDemandThroughputOverride | None + WarmThroughput: GlobalSecondaryIndexWarmThroughputDescription | None -ReplicaGlobalSecondaryIndexDescriptionList = List[ReplicaGlobalSecondaryIndexDescription] +ReplicaGlobalSecondaryIndexDescriptionList = list[ReplicaGlobalSecondaryIndexDescription] class TableWarmThroughputDescription(TypedDict, total=False): - ReadUnitsPerSecond: Optional[PositiveLongObject] - WriteUnitsPerSecond: Optional[PositiveLongObject] - Status: Optional[TableStatus] + ReadUnitsPerSecond: PositiveLongObject | None + WriteUnitsPerSecond: PositiveLongObject | None + Status: TableStatus | None class ReplicaDescription(TypedDict, total=False): - RegionName: Optional[RegionName] - ReplicaStatus: Optional[ReplicaStatus] - ReplicaStatusDescription: Optional[ReplicaStatusDescription] - ReplicaStatusPercentProgress: Optional[ReplicaStatusPercentProgress] - KMSMasterKeyId: Optional[KMSMasterKeyId] - ProvisionedThroughputOverride: Optional[ProvisionedThroughputOverride] - OnDemandThroughputOverride: Optional[OnDemandThroughputOverride] - WarmThroughput: Optional[TableWarmThroughputDescription] - GlobalSecondaryIndexes: Optional[ReplicaGlobalSecondaryIndexDescriptionList] - ReplicaInaccessibleDateTime: Optional[Date] - ReplicaTableClassSummary: Optional[TableClassSummary] + RegionName: RegionName | None + ReplicaStatus: ReplicaStatus | None + ReplicaStatusDescription: ReplicaStatusDescription | None + ReplicaStatusPercentProgress: ReplicaStatusPercentProgress | None + KMSMasterKeyId: KMSMasterKeyId | None + ProvisionedThroughputOverride: ProvisionedThroughputOverride | None + OnDemandThroughputOverride: OnDemandThroughputOverride | None + WarmThroughput: TableWarmThroughputDescription | None + GlobalSecondaryIndexes: ReplicaGlobalSecondaryIndexDescriptionList | None + ReplicaInaccessibleDateTime: Date | None + ReplicaTableClassSummary: TableClassSummary | None + GlobalTableSettingsReplicationMode: GlobalTableSettingsReplicationMode | None -ReplicaDescriptionList = List[ReplicaDescription] +ReplicaDescriptionList = list[ReplicaDescription] class GlobalTableDescription(TypedDict, total=False): - ReplicationGroup: Optional[ReplicaDescriptionList] - GlobalTableArn: Optional[GlobalTableArnString] - CreationDateTime: Optional[Date] - GlobalTableStatus: Optional[GlobalTableStatus] - GlobalTableName: Optional[TableName] + ReplicationGroup: ReplicaDescriptionList | None + GlobalTableArn: GlobalTableArnString | None + CreationDateTime: Date | None + GlobalTableStatus: GlobalTableStatus | None + GlobalTableName: TableName | None class CreateGlobalTableOutput(TypedDict, total=False): - GlobalTableDescription: Optional[GlobalTableDescription] + GlobalTableDescription: GlobalTableDescription | None class CreateGlobalTableWitnessGroupMemberAction(TypedDict, total=False): @@ -1095,20 +1128,20 @@ class CreateReplicaAction(TypedDict, total=False): class ReplicaGlobalSecondaryIndex(TypedDict, total=False): IndexName: IndexName - ProvisionedThroughputOverride: Optional[ProvisionedThroughputOverride] - OnDemandThroughputOverride: Optional[OnDemandThroughputOverride] + ProvisionedThroughputOverride: ProvisionedThroughputOverride | None + OnDemandThroughputOverride: OnDemandThroughputOverride | None -ReplicaGlobalSecondaryIndexList = List[ReplicaGlobalSecondaryIndex] +ReplicaGlobalSecondaryIndexList = list[ReplicaGlobalSecondaryIndex] class CreateReplicationGroupMemberAction(TypedDict, total=False): RegionName: RegionName - KMSMasterKeyId: Optional[KMSMasterKeyId] - ProvisionedThroughputOverride: Optional[ProvisionedThroughputOverride] - OnDemandThroughputOverride: Optional[OnDemandThroughputOverride] - GlobalSecondaryIndexes: Optional[ReplicaGlobalSecondaryIndexList] - TableClassOverride: Optional[TableClass] + KMSMasterKeyId: KMSMasterKeyId | None + ProvisionedThroughputOverride: ProvisionedThroughputOverride | None + OnDemandThroughputOverride: OnDemandThroughputOverride | None + GlobalSecondaryIndexes: ReplicaGlobalSecondaryIndexList | None + TableClassOverride: TableClass | None class Tag(TypedDict, total=False): @@ -1116,25 +1149,25 @@ class Tag(TypedDict, total=False): Value: TagValueString -TagList = List[Tag] +TagList = list[Tag] class SSESpecification(TypedDict, total=False): - Enabled: Optional[SSEEnabled] - SSEType: Optional[SSEType] - KMSMasterKeyId: Optional[KMSMasterKeyId] + Enabled: SSEEnabled | None + SSEType: SSEType | None + KMSMasterKeyId: KMSMasterKeyId | None class GlobalSecondaryIndex(TypedDict, total=False): IndexName: IndexName KeySchema: KeySchema Projection: Projection - ProvisionedThroughput: Optional[ProvisionedThroughput] - OnDemandThroughput: Optional[OnDemandThroughput] - WarmThroughput: Optional[WarmThroughput] + ProvisionedThroughput: ProvisionedThroughput | None + OnDemandThroughput: OnDemandThroughput | None + WarmThroughput: WarmThroughput | None -GlobalSecondaryIndexList = List[GlobalSecondaryIndex] +GlobalSecondaryIndexList = list[GlobalSecondaryIndex] class LocalSecondaryIndex(TypedDict, total=False): @@ -1143,129 +1176,132 @@ class LocalSecondaryIndex(TypedDict, total=False): Projection: Projection -LocalSecondaryIndexList = List[LocalSecondaryIndex] +LocalSecondaryIndexList = list[LocalSecondaryIndex] class CreateTableInput(ServiceRequest): - AttributeDefinitions: AttributeDefinitions + AttributeDefinitions: AttributeDefinitions | None TableName: TableArn - KeySchema: KeySchema - LocalSecondaryIndexes: Optional[LocalSecondaryIndexList] - GlobalSecondaryIndexes: Optional[GlobalSecondaryIndexList] - BillingMode: Optional[BillingMode] - ProvisionedThroughput: Optional[ProvisionedThroughput] - StreamSpecification: Optional[StreamSpecification] - SSESpecification: Optional[SSESpecification] - Tags: Optional[TagList] - TableClass: Optional[TableClass] - DeletionProtectionEnabled: Optional[DeletionProtectionEnabled] - WarmThroughput: Optional[WarmThroughput] - ResourcePolicy: Optional[ResourcePolicy] - OnDemandThroughput: Optional[OnDemandThroughput] + KeySchema: KeySchema | None + LocalSecondaryIndexes: LocalSecondaryIndexList | None + GlobalSecondaryIndexes: GlobalSecondaryIndexList | None + BillingMode: BillingMode | None + ProvisionedThroughput: ProvisionedThroughput | None + StreamSpecification: StreamSpecification | None + SSESpecification: SSESpecification | None + Tags: TagList | None + TableClass: TableClass | None + DeletionProtectionEnabled: DeletionProtectionEnabled | None + WarmThroughput: WarmThroughput | None + ResourcePolicy: ResourcePolicy | None + OnDemandThroughput: OnDemandThroughput | None + GlobalTableSourceArn: TableArn | None + GlobalTableSettingsReplicationMode: GlobalTableSettingsReplicationMode | None class RestoreSummary(TypedDict, total=False): - SourceBackupArn: Optional[BackupArn] - SourceTableArn: Optional[TableArn] + SourceBackupArn: BackupArn | None + SourceTableArn: TableArn | None RestoreDateTime: Date RestoreInProgress: RestoreInProgress class GlobalTableWitnessDescription(TypedDict, total=False): - RegionName: Optional[RegionName] - WitnessStatus: Optional[WitnessStatus] + RegionName: RegionName | None + WitnessStatus: WitnessStatus | None -GlobalTableWitnessDescriptionList = List[GlobalTableWitnessDescription] +GlobalTableWitnessDescriptionList = list[GlobalTableWitnessDescription] NonNegativeLongObject = int class ProvisionedThroughputDescription(TypedDict, total=False): - LastIncreaseDateTime: Optional[Date] - LastDecreaseDateTime: Optional[Date] - NumberOfDecreasesToday: Optional[PositiveLongObject] - ReadCapacityUnits: Optional[NonNegativeLongObject] - WriteCapacityUnits: Optional[NonNegativeLongObject] + LastIncreaseDateTime: Date | None + LastDecreaseDateTime: Date | None + NumberOfDecreasesToday: PositiveLongObject | None + ReadCapacityUnits: NonNegativeLongObject | None + WriteCapacityUnits: NonNegativeLongObject | None class GlobalSecondaryIndexDescription(TypedDict, total=False): - IndexName: Optional[IndexName] - KeySchema: Optional[KeySchema] - Projection: Optional[Projection] - IndexStatus: Optional[IndexStatus] - Backfilling: Optional[Backfilling] - ProvisionedThroughput: Optional[ProvisionedThroughputDescription] - IndexSizeBytes: Optional[LongObject] - ItemCount: Optional[LongObject] - IndexArn: Optional[String] - OnDemandThroughput: Optional[OnDemandThroughput] - WarmThroughput: Optional[GlobalSecondaryIndexWarmThroughputDescription] + IndexName: IndexName | None + KeySchema: KeySchema | None + Projection: Projection | None + IndexStatus: IndexStatus | None + Backfilling: Backfilling | None + ProvisionedThroughput: ProvisionedThroughputDescription | None + IndexSizeBytes: LongObject | None + ItemCount: LongObject | None + IndexArn: String | None + OnDemandThroughput: OnDemandThroughput | None + WarmThroughput: GlobalSecondaryIndexWarmThroughputDescription | None -GlobalSecondaryIndexDescriptionList = List[GlobalSecondaryIndexDescription] +GlobalSecondaryIndexDescriptionList = list[GlobalSecondaryIndexDescription] class LocalSecondaryIndexDescription(TypedDict, total=False): - IndexName: Optional[IndexName] - KeySchema: Optional[KeySchema] - Projection: Optional[Projection] - IndexSizeBytes: Optional[LongObject] - ItemCount: Optional[LongObject] - IndexArn: Optional[String] + IndexName: IndexName | None + KeySchema: KeySchema | None + Projection: Projection | None + IndexSizeBytes: LongObject | None + ItemCount: LongObject | None + IndexArn: String | None -LocalSecondaryIndexDescriptionList = List[LocalSecondaryIndexDescription] +LocalSecondaryIndexDescriptionList = list[LocalSecondaryIndexDescription] class TableDescription(TypedDict, total=False): - AttributeDefinitions: Optional[AttributeDefinitions] - TableName: Optional[TableName] - KeySchema: Optional[KeySchema] - TableStatus: Optional[TableStatus] - CreationDateTime: Optional[Date] - ProvisionedThroughput: Optional[ProvisionedThroughputDescription] - TableSizeBytes: Optional[LongObject] - ItemCount: Optional[LongObject] - TableArn: Optional[String] - TableId: Optional[TableId] - BillingModeSummary: Optional[BillingModeSummary] - LocalSecondaryIndexes: Optional[LocalSecondaryIndexDescriptionList] - GlobalSecondaryIndexes: Optional[GlobalSecondaryIndexDescriptionList] - StreamSpecification: Optional[StreamSpecification] - LatestStreamLabel: Optional[String] - LatestStreamArn: Optional[StreamArn] - GlobalTableVersion: Optional[String] - Replicas: Optional[ReplicaDescriptionList] - GlobalTableWitnesses: Optional[GlobalTableWitnessDescriptionList] - RestoreSummary: Optional[RestoreSummary] - SSEDescription: Optional[SSEDescription] - ArchivalSummary: Optional[ArchivalSummary] - TableClassSummary: Optional[TableClassSummary] - DeletionProtectionEnabled: Optional[DeletionProtectionEnabled] - OnDemandThroughput: Optional[OnDemandThroughput] - WarmThroughput: Optional[TableWarmThroughputDescription] - MultiRegionConsistency: Optional[MultiRegionConsistency] + AttributeDefinitions: AttributeDefinitions | None + TableName: TableName | None + KeySchema: KeySchema | None + TableStatus: TableStatus | None + CreationDateTime: Date | None + ProvisionedThroughput: ProvisionedThroughputDescription | None + TableSizeBytes: LongObject | None + ItemCount: LongObject | None + TableArn: String | None + TableId: TableId | None + BillingModeSummary: BillingModeSummary | None + LocalSecondaryIndexes: LocalSecondaryIndexDescriptionList | None + GlobalSecondaryIndexes: GlobalSecondaryIndexDescriptionList | None + StreamSpecification: StreamSpecification | None + LatestStreamLabel: String | None + LatestStreamArn: StreamArn | None + GlobalTableVersion: String | None + Replicas: ReplicaDescriptionList | None + GlobalTableWitnesses: GlobalTableWitnessDescriptionList | None + GlobalTableSettingsReplicationMode: GlobalTableSettingsReplicationMode | None + RestoreSummary: RestoreSummary | None + SSEDescription: SSEDescription | None + ArchivalSummary: ArchivalSummary | None + TableClassSummary: TableClassSummary | None + DeletionProtectionEnabled: DeletionProtectionEnabled | None + OnDemandThroughput: OnDemandThroughput | None + WarmThroughput: TableWarmThroughputDescription | None + MultiRegionConsistency: MultiRegionConsistency | None class CreateTableOutput(TypedDict, total=False): - TableDescription: Optional[TableDescription] + TableDescription: TableDescription | None -CsvHeaderList = List[CsvHeader] +CsvHeaderList = list[CsvHeader] class CsvOptions(TypedDict, total=False): - Delimiter: Optional[CsvDelimiter] - HeaderList: Optional[CsvHeaderList] + Delimiter: CsvDelimiter | None + HeaderList: CsvHeaderList | None class Delete(TypedDict, total=False): Key: Key TableName: TableArn - ConditionExpression: Optional[ConditionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + ConditionExpression: ConditionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class DeleteBackupInput(ServiceRequest): @@ -1273,7 +1309,7 @@ class DeleteBackupInput(ServiceRequest): class DeleteBackupOutput(TypedDict, total=False): - BackupDescription: Optional[BackupDescription] + BackupDescription: BackupDescription | None class DeleteGlobalSecondaryIndexAction(TypedDict, total=False): @@ -1285,33 +1321,33 @@ class DeleteGlobalTableWitnessGroupMemberAction(TypedDict, total=False): class ExpectedAttributeValue(TypedDict, total=False): - Value: Optional[AttributeValue] - Exists: Optional[BooleanObject] - ComparisonOperator: Optional[ComparisonOperator] - AttributeValueList: Optional[AttributeValueList] + Value: AttributeValue | None + Exists: BooleanObject | None + ComparisonOperator: ComparisonOperator | None + AttributeValueList: AttributeValueList | None -ExpectedAttributeMap = Dict[AttributeName, ExpectedAttributeValue] +ExpectedAttributeMap = dict[AttributeName, ExpectedAttributeValue] class DeleteItemInput(ServiceRequest): TableName: TableArn Key: Key - Expected: Optional[ExpectedAttributeMap] - ConditionalOperator: Optional[ConditionalOperator] - ReturnValues: Optional[ReturnValue] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - ReturnItemCollectionMetrics: Optional[ReturnItemCollectionMetrics] - ConditionExpression: Optional[ConditionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + Expected: ExpectedAttributeMap | None + ConditionalOperator: ConditionalOperator | None + ReturnValues: ReturnValue | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None + ReturnItemCollectionMetrics: ReturnItemCollectionMetrics | None + ConditionExpression: ConditionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class DeleteItemOutput(TypedDict, total=False): - Attributes: Optional[AttributeMap] - ConsumedCapacity: Optional[ConsumedCapacity] - ItemCollectionMetrics: Optional[ItemCollectionMetrics] + Attributes: AttributeMap | None + ConsumedCapacity: ConsumedCapacity | None + ItemCollectionMetrics: ItemCollectionMetrics | None class DeleteReplicaAction(TypedDict, total=False): @@ -1324,11 +1360,11 @@ class DeleteReplicationGroupMemberAction(TypedDict, total=False): class DeleteResourcePolicyInput(ServiceRequest): ResourceArn: ResourceArnString - ExpectedRevisionId: Optional[PolicyRevisionId] + ExpectedRevisionId: PolicyRevisionId | None class DeleteResourcePolicyOutput(TypedDict, total=False): - RevisionId: Optional[PolicyRevisionId] + RevisionId: PolicyRevisionId | None class DeleteTableInput(ServiceRequest): @@ -1336,7 +1372,7 @@ class DeleteTableInput(ServiceRequest): class DeleteTableOutput(TypedDict, total=False): - TableDescription: Optional[TableDescription] + TableDescription: TableDescription | None class DescribeBackupInput(ServiceRequest): @@ -1344,7 +1380,7 @@ class DescribeBackupInput(ServiceRequest): class DescribeBackupOutput(TypedDict, total=False): - BackupDescription: Optional[BackupDescription] + BackupDescription: BackupDescription | None class DescribeContinuousBackupsInput(ServiceRequest): @@ -1352,29 +1388,30 @@ class DescribeContinuousBackupsInput(ServiceRequest): class DescribeContinuousBackupsOutput(TypedDict, total=False): - ContinuousBackupsDescription: Optional[ContinuousBackupsDescription] + ContinuousBackupsDescription: ContinuousBackupsDescription | None class DescribeContributorInsightsInput(ServiceRequest): TableName: TableArn - IndexName: Optional[IndexName] + IndexName: IndexName | None class FailureException(TypedDict, total=False): - ExceptionName: Optional[ExceptionName] - ExceptionDescription: Optional[ExceptionDescription] + ExceptionName: ExceptionName | None + ExceptionDescription: ExceptionDescription | None LastUpdateDateTime = datetime class DescribeContributorInsightsOutput(TypedDict, total=False): - TableName: Optional[TableName] - IndexName: Optional[IndexName] - ContributorInsightsRuleList: Optional[ContributorInsightsRuleList] - ContributorInsightsStatus: Optional[ContributorInsightsStatus] - LastUpdateDateTime: Optional[LastUpdateDateTime] - FailureException: Optional[FailureException] + TableName: TableName | None + IndexName: IndexName | None + ContributorInsightsRuleList: ContributorInsightsRuleList | None + ContributorInsightsStatus: ContributorInsightsStatus | None + LastUpdateDateTime: LastUpdateDateTime | None + FailureException: FailureException | None + ContributorInsightsMode: ContributorInsightsMode | None class DescribeEndpointsRequest(ServiceRequest): @@ -1389,7 +1426,7 @@ class Endpoint(TypedDict, total=False): CachePeriodInMinutes: Long -Endpoints = List[Endpoint] +Endpoints = list[Endpoint] class DescribeEndpointsResponse(TypedDict, total=False): @@ -1405,9 +1442,9 @@ class DescribeExportInput(ServiceRequest): class IncrementalExportSpecification(TypedDict, total=False): - ExportFromTime: Optional[ExportFromTime] - ExportToTime: Optional[ExportToTime] - ExportViewType: Optional[ExportViewType] + ExportFromTime: ExportFromTime | None + ExportToTime: ExportToTime | None + ExportViewType: ExportViewType | None ExportTime = datetime @@ -1416,31 +1453,31 @@ class IncrementalExportSpecification(TypedDict, total=False): class ExportDescription(TypedDict, total=False): - ExportArn: Optional[ExportArn] - ExportStatus: Optional[ExportStatus] - StartTime: Optional[ExportStartTime] - EndTime: Optional[ExportEndTime] - ExportManifest: Optional[ExportManifest] - TableArn: Optional[TableArn] - TableId: Optional[TableId] - ExportTime: Optional[ExportTime] - ClientToken: Optional[ClientToken] - S3Bucket: Optional[S3Bucket] - S3BucketOwner: Optional[S3BucketOwner] - S3Prefix: Optional[S3Prefix] - S3SseAlgorithm: Optional[S3SseAlgorithm] - S3SseKmsKeyId: Optional[S3SseKmsKeyId] - FailureCode: Optional[FailureCode] - FailureMessage: Optional[FailureMessage] - ExportFormat: Optional[ExportFormat] - BilledSizeBytes: Optional[BilledSizeBytes] - ItemCount: Optional[ItemCount] - ExportType: Optional[ExportType] - IncrementalExportSpecification: Optional[IncrementalExportSpecification] + ExportArn: ExportArn | None + ExportStatus: ExportStatus | None + StartTime: ExportStartTime | None + EndTime: ExportEndTime | None + ExportManifest: ExportManifest | None + TableArn: TableArn | None + TableId: TableId | None + ExportTime: ExportTime | None + ClientToken: ClientToken | None + S3Bucket: S3Bucket | None + S3BucketOwner: S3BucketOwner | None + S3Prefix: S3Prefix | None + S3SseAlgorithm: S3SseAlgorithm | None + S3SseKmsKeyId: S3SseKmsKeyId | None + FailureCode: FailureCode | None + FailureMessage: FailureMessage | None + ExportFormat: ExportFormat | None + BilledSizeBytes: BilledSizeBytes | None + ItemCount: ItemCount | None + ExportType: ExportType | None + IncrementalExportSpecification: IncrementalExportSpecification | None class DescribeExportOutput(TypedDict, total=False): - ExportDescription: Optional[ExportDescription] + ExportDescription: ExportDescription | None class DescribeGlobalTableInput(ServiceRequest): @@ -1448,7 +1485,7 @@ class DescribeGlobalTableInput(ServiceRequest): class DescribeGlobalTableOutput(TypedDict, total=False): - GlobalTableDescription: Optional[GlobalTableDescription] + GlobalTableDescription: GlobalTableDescription | None class DescribeGlobalTableSettingsInput(ServiceRequest): @@ -1457,38 +1494,36 @@ class DescribeGlobalTableSettingsInput(ServiceRequest): class ReplicaGlobalSecondaryIndexSettingsDescription(TypedDict, total=False): IndexName: IndexName - IndexStatus: Optional[IndexStatus] - ProvisionedReadCapacityUnits: Optional[PositiveLongObject] - ProvisionedReadCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] - ProvisionedWriteCapacityUnits: Optional[PositiveLongObject] - ProvisionedWriteCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] + IndexStatus: IndexStatus | None + ProvisionedReadCapacityUnits: PositiveLongObject | None + ProvisionedReadCapacityAutoScalingSettings: AutoScalingSettingsDescription | None + ProvisionedWriteCapacityUnits: PositiveLongObject | None + ProvisionedWriteCapacityAutoScalingSettings: AutoScalingSettingsDescription | None -ReplicaGlobalSecondaryIndexSettingsDescriptionList = List[ +ReplicaGlobalSecondaryIndexSettingsDescriptionList = list[ ReplicaGlobalSecondaryIndexSettingsDescription ] class ReplicaSettingsDescription(TypedDict, total=False): RegionName: RegionName - ReplicaStatus: Optional[ReplicaStatus] - ReplicaBillingModeSummary: Optional[BillingModeSummary] - ReplicaProvisionedReadCapacityUnits: Optional[NonNegativeLongObject] - ReplicaProvisionedReadCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] - ReplicaProvisionedWriteCapacityUnits: Optional[NonNegativeLongObject] - ReplicaProvisionedWriteCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] - ReplicaGlobalSecondaryIndexSettings: Optional[ - ReplicaGlobalSecondaryIndexSettingsDescriptionList - ] - ReplicaTableClassSummary: Optional[TableClassSummary] + ReplicaStatus: ReplicaStatus | None + ReplicaBillingModeSummary: BillingModeSummary | None + ReplicaProvisionedReadCapacityUnits: NonNegativeLongObject | None + ReplicaProvisionedReadCapacityAutoScalingSettings: AutoScalingSettingsDescription | None + ReplicaProvisionedWriteCapacityUnits: NonNegativeLongObject | None + ReplicaProvisionedWriteCapacityAutoScalingSettings: AutoScalingSettingsDescription | None + ReplicaGlobalSecondaryIndexSettings: ReplicaGlobalSecondaryIndexSettingsDescriptionList | None + ReplicaTableClassSummary: TableClassSummary | None -ReplicaSettingsDescriptionList = List[ReplicaSettingsDescription] +ReplicaSettingsDescriptionList = list[ReplicaSettingsDescription] class DescribeGlobalTableSettingsOutput(TypedDict, total=False): - GlobalTableName: Optional[TableName] - ReplicaSettings: Optional[ReplicaSettingsDescriptionList] + GlobalTableName: TableName | None + ReplicaSettings: ReplicaSettingsDescriptionList | None class DescribeImportInput(ServiceRequest): @@ -1505,46 +1540,46 @@ class TableCreationParameters(TypedDict, total=False): TableName: TableName AttributeDefinitions: AttributeDefinitions KeySchema: KeySchema - BillingMode: Optional[BillingMode] - ProvisionedThroughput: Optional[ProvisionedThroughput] - OnDemandThroughput: Optional[OnDemandThroughput] - SSESpecification: Optional[SSESpecification] - GlobalSecondaryIndexes: Optional[GlobalSecondaryIndexList] + BillingMode: BillingMode | None + ProvisionedThroughput: ProvisionedThroughput | None + OnDemandThroughput: OnDemandThroughput | None + SSESpecification: SSESpecification | None + GlobalSecondaryIndexes: GlobalSecondaryIndexList | None class InputFormatOptions(TypedDict, total=False): - Csv: Optional[CsvOptions] + Csv: CsvOptions | None ErrorCount = int class S3BucketSource(TypedDict, total=False): - S3BucketOwner: Optional[S3BucketOwner] + S3BucketOwner: S3BucketOwner | None S3Bucket: S3Bucket - S3KeyPrefix: Optional[S3Prefix] + S3KeyPrefix: S3Prefix | None class ImportTableDescription(TypedDict, total=False): - ImportArn: Optional[ImportArn] - ImportStatus: Optional[ImportStatus] - TableArn: Optional[TableArn] - TableId: Optional[TableId] - ClientToken: Optional[ClientToken] - S3BucketSource: Optional[S3BucketSource] - ErrorCount: Optional[ErrorCount] - CloudWatchLogGroupArn: Optional[CloudWatchLogGroupArn] - InputFormat: Optional[InputFormat] - InputFormatOptions: Optional[InputFormatOptions] - InputCompressionType: Optional[InputCompressionType] - TableCreationParameters: Optional[TableCreationParameters] - StartTime: Optional[ImportStartTime] - EndTime: Optional[ImportEndTime] - ProcessedSizeBytes: Optional[LongObject] - ProcessedItemCount: Optional[ProcessedItemCount] - ImportedItemCount: Optional[ImportedItemCount] - FailureCode: Optional[FailureCode] - FailureMessage: Optional[FailureMessage] + ImportArn: ImportArn | None + ImportStatus: ImportStatus | None + TableArn: TableArn | None + TableId: TableId | None + ClientToken: ClientToken | None + S3BucketSource: S3BucketSource | None + ErrorCount: ErrorCount | None + CloudWatchLogGroupArn: CloudWatchLogGroupArn | None + InputFormat: InputFormat | None + InputFormatOptions: InputFormatOptions | None + InputCompressionType: InputCompressionType | None + TableCreationParameters: TableCreationParameters | None + StartTime: ImportStartTime | None + EndTime: ImportEndTime | None + ProcessedSizeBytes: LongObject | None + ProcessedItemCount: ProcessedItemCount | None + ImportedItemCount: ImportedItemCount | None + FailureCode: FailureCode | None + FailureMessage: FailureMessage | None class DescribeImportOutput(TypedDict, total=False): @@ -1556,18 +1591,18 @@ class DescribeKinesisStreamingDestinationInput(ServiceRequest): class KinesisDataStreamDestination(TypedDict, total=False): - StreamArn: Optional[StreamArn] - DestinationStatus: Optional[DestinationStatus] - DestinationStatusDescription: Optional[String] - ApproximateCreationDateTimePrecision: Optional[ApproximateCreationDateTimePrecision] + StreamArn: StreamArn | None + DestinationStatus: DestinationStatus | None + DestinationStatusDescription: String | None + ApproximateCreationDateTimePrecision: ApproximateCreationDateTimePrecision | None -KinesisDataStreamDestinations = List[KinesisDataStreamDestination] +KinesisDataStreamDestinations = list[KinesisDataStreamDestination] class DescribeKinesisStreamingDestinationOutput(TypedDict, total=False): - TableName: Optional[TableName] - KinesisDataStreamDestinations: Optional[KinesisDataStreamDestinations] + TableName: TableName | None + KinesisDataStreamDestinations: KinesisDataStreamDestinations | None class DescribeLimitsInput(ServiceRequest): @@ -1575,10 +1610,10 @@ class DescribeLimitsInput(ServiceRequest): class DescribeLimitsOutput(TypedDict, total=False): - AccountMaxReadCapacityUnits: Optional[PositiveLongObject] - AccountMaxWriteCapacityUnits: Optional[PositiveLongObject] - TableMaxReadCapacityUnits: Optional[PositiveLongObject] - TableMaxWriteCapacityUnits: Optional[PositiveLongObject] + AccountMaxReadCapacityUnits: PositiveLongObject | None + AccountMaxWriteCapacityUnits: PositiveLongObject | None + TableMaxReadCapacityUnits: PositiveLongObject | None + TableMaxWriteCapacityUnits: PositiveLongObject | None class DescribeTableInput(ServiceRequest): @@ -1586,7 +1621,7 @@ class DescribeTableInput(ServiceRequest): class DescribeTableOutput(TypedDict, total=False): - Table: Optional[TableDescription] + Table: TableDescription | None class DescribeTableReplicaAutoScalingInput(ServiceRequest): @@ -1594,36 +1629,36 @@ class DescribeTableReplicaAutoScalingInput(ServiceRequest): class ReplicaGlobalSecondaryIndexAutoScalingDescription(TypedDict, total=False): - IndexName: Optional[IndexName] - IndexStatus: Optional[IndexStatus] - ProvisionedReadCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] - ProvisionedWriteCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] + IndexName: IndexName | None + IndexStatus: IndexStatus | None + ProvisionedReadCapacityAutoScalingSettings: AutoScalingSettingsDescription | None + ProvisionedWriteCapacityAutoScalingSettings: AutoScalingSettingsDescription | None -ReplicaGlobalSecondaryIndexAutoScalingDescriptionList = List[ +ReplicaGlobalSecondaryIndexAutoScalingDescriptionList = list[ ReplicaGlobalSecondaryIndexAutoScalingDescription ] class ReplicaAutoScalingDescription(TypedDict, total=False): - RegionName: Optional[RegionName] - GlobalSecondaryIndexes: Optional[ReplicaGlobalSecondaryIndexAutoScalingDescriptionList] - ReplicaProvisionedReadCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] - ReplicaProvisionedWriteCapacityAutoScalingSettings: Optional[AutoScalingSettingsDescription] - ReplicaStatus: Optional[ReplicaStatus] + RegionName: RegionName | None + GlobalSecondaryIndexes: ReplicaGlobalSecondaryIndexAutoScalingDescriptionList | None + ReplicaProvisionedReadCapacityAutoScalingSettings: AutoScalingSettingsDescription | None + ReplicaProvisionedWriteCapacityAutoScalingSettings: AutoScalingSettingsDescription | None + ReplicaStatus: ReplicaStatus | None -ReplicaAutoScalingDescriptionList = List[ReplicaAutoScalingDescription] +ReplicaAutoScalingDescriptionList = list[ReplicaAutoScalingDescription] class TableAutoScalingDescription(TypedDict, total=False): - TableName: Optional[TableName] - TableStatus: Optional[TableStatus] - Replicas: Optional[ReplicaAutoScalingDescriptionList] + TableName: TableName | None + TableStatus: TableStatus | None + Replicas: ReplicaAutoScalingDescriptionList | None class DescribeTableReplicaAutoScalingOutput(TypedDict, total=False): - TableAutoScalingDescription: Optional[TableAutoScalingDescription] + TableAutoScalingDescription: TableAutoScalingDescription | None class DescribeTimeToLiveInput(ServiceRequest): @@ -1631,107 +1666,107 @@ class DescribeTimeToLiveInput(ServiceRequest): class DescribeTimeToLiveOutput(TypedDict, total=False): - TimeToLiveDescription: Optional[TimeToLiveDescription] + TimeToLiveDescription: TimeToLiveDescription | None class EnableKinesisStreamingConfiguration(TypedDict, total=False): - ApproximateCreationDateTimePrecision: Optional[ApproximateCreationDateTimePrecision] + ApproximateCreationDateTimePrecision: ApproximateCreationDateTimePrecision | None class ExecuteStatementInput(ServiceRequest): Statement: PartiQLStatement - Parameters: Optional[PreparedStatementParameters] - ConsistentRead: Optional[ConsistentRead] - NextToken: Optional[PartiQLNextToken] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - Limit: Optional[PositiveIntegerObject] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + Parameters: PreparedStatementParameters | None + ConsistentRead: ConsistentRead | None + NextToken: PartiQLNextToken | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None + Limit: PositiveIntegerObject | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class ExecuteStatementOutput(TypedDict, total=False): - Items: Optional[ItemList] - NextToken: Optional[PartiQLNextToken] - ConsumedCapacity: Optional[ConsumedCapacity] - LastEvaluatedKey: Optional[Key] + Items: ItemList | None + NextToken: PartiQLNextToken | None + ConsumedCapacity: ConsumedCapacity | None + LastEvaluatedKey: Key | None class ParameterizedStatement(TypedDict, total=False): Statement: PartiQLStatement - Parameters: Optional[PreparedStatementParameters] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + Parameters: PreparedStatementParameters | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None -ParameterizedStatements = List[ParameterizedStatement] +ParameterizedStatements = list[ParameterizedStatement] class ExecuteTransactionInput(ServiceRequest): TransactStatements: ParameterizedStatements - ClientRequestToken: Optional[ClientRequestToken] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] + ClientRequestToken: ClientRequestToken | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None class ItemResponse(TypedDict, total=False): - Item: Optional[AttributeMap] + Item: AttributeMap | None -ItemResponseList = List[ItemResponse] +ItemResponseList = list[ItemResponse] class ExecuteTransactionOutput(TypedDict, total=False): - Responses: Optional[ItemResponseList] - ConsumedCapacity: Optional[ConsumedCapacityMultiple] + Responses: ItemResponseList | None + ConsumedCapacity: ConsumedCapacityMultiple | None class ExportSummary(TypedDict, total=False): - ExportArn: Optional[ExportArn] - ExportStatus: Optional[ExportStatus] - ExportType: Optional[ExportType] + ExportArn: ExportArn | None + ExportStatus: ExportStatus | None + ExportType: ExportType | None -ExportSummaries = List[ExportSummary] +ExportSummaries = list[ExportSummary] class ExportTableToPointInTimeInput(ServiceRequest): TableArn: TableArn - ExportTime: Optional[ExportTime] - ClientToken: Optional[ClientToken] + ExportTime: ExportTime | None + ClientToken: ClientToken | None S3Bucket: S3Bucket - S3BucketOwner: Optional[S3BucketOwner] - S3Prefix: Optional[S3Prefix] - S3SseAlgorithm: Optional[S3SseAlgorithm] - S3SseKmsKeyId: Optional[S3SseKmsKeyId] - ExportFormat: Optional[ExportFormat] - ExportType: Optional[ExportType] - IncrementalExportSpecification: Optional[IncrementalExportSpecification] + S3BucketOwner: S3BucketOwner | None + S3Prefix: S3Prefix | None + S3SseAlgorithm: S3SseAlgorithm | None + S3SseKmsKeyId: S3SseKmsKeyId | None + ExportFormat: ExportFormat | None + ExportType: ExportType | None + IncrementalExportSpecification: IncrementalExportSpecification | None class ExportTableToPointInTimeOutput(TypedDict, total=False): - ExportDescription: Optional[ExportDescription] + ExportDescription: ExportDescription | None -FilterConditionMap = Dict[AttributeName, Condition] +FilterConditionMap = dict[AttributeName, Condition] class Get(TypedDict, total=False): Key: Key TableName: TableArn - ProjectionExpression: Optional[ProjectionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] + ProjectionExpression: ProjectionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None class GetItemInput(ServiceRequest): TableName: TableArn Key: Key - AttributesToGet: Optional[AttributeNameList] - ConsistentRead: Optional[ConsistentRead] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - ProjectionExpression: Optional[ProjectionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] + AttributesToGet: AttributeNameList | None + ConsistentRead: ConsistentRead | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None + ProjectionExpression: ProjectionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None class GetItemOutput(TypedDict, total=False): - Item: Optional[AttributeMap] - ConsumedCapacity: Optional[ConsumedCapacity] + Item: AttributeMap | None + ConsumedCapacity: ConsumedCapacity | None class GetResourcePolicyInput(ServiceRequest): @@ -1739,79 +1774,79 @@ class GetResourcePolicyInput(ServiceRequest): class GetResourcePolicyOutput(TypedDict, total=False): - Policy: Optional[ResourcePolicy] - RevisionId: Optional[PolicyRevisionId] + Policy: ResourcePolicy | None + RevisionId: PolicyRevisionId | None class GlobalSecondaryIndexAutoScalingUpdate(TypedDict, total=False): - IndexName: Optional[IndexName] - ProvisionedWriteCapacityAutoScalingUpdate: Optional[AutoScalingSettingsUpdate] + IndexName: IndexName | None + ProvisionedWriteCapacityAutoScalingUpdate: AutoScalingSettingsUpdate | None -GlobalSecondaryIndexAutoScalingUpdateList = List[GlobalSecondaryIndexAutoScalingUpdate] +GlobalSecondaryIndexAutoScalingUpdateList = list[GlobalSecondaryIndexAutoScalingUpdate] class UpdateGlobalSecondaryIndexAction(TypedDict, total=False): IndexName: IndexName - ProvisionedThroughput: Optional[ProvisionedThroughput] - OnDemandThroughput: Optional[OnDemandThroughput] - WarmThroughput: Optional[WarmThroughput] + ProvisionedThroughput: ProvisionedThroughput | None + OnDemandThroughput: OnDemandThroughput | None + WarmThroughput: WarmThroughput | None class GlobalSecondaryIndexUpdate(TypedDict, total=False): - Update: Optional[UpdateGlobalSecondaryIndexAction] - Create: Optional[CreateGlobalSecondaryIndexAction] - Delete: Optional[DeleteGlobalSecondaryIndexAction] + Update: UpdateGlobalSecondaryIndexAction | None + Create: CreateGlobalSecondaryIndexAction | None + Delete: DeleteGlobalSecondaryIndexAction | None -GlobalSecondaryIndexUpdateList = List[GlobalSecondaryIndexUpdate] +GlobalSecondaryIndexUpdateList = list[GlobalSecondaryIndexUpdate] class GlobalTable(TypedDict, total=False): - GlobalTableName: Optional[TableName] - ReplicationGroup: Optional[ReplicaList] + GlobalTableName: TableName | None + ReplicationGroup: ReplicaList | None class GlobalTableGlobalSecondaryIndexSettingsUpdate(TypedDict, total=False): IndexName: IndexName - ProvisionedWriteCapacityUnits: Optional[PositiveLongObject] - ProvisionedWriteCapacityAutoScalingSettingsUpdate: Optional[AutoScalingSettingsUpdate] + ProvisionedWriteCapacityUnits: PositiveLongObject | None + ProvisionedWriteCapacityAutoScalingSettingsUpdate: AutoScalingSettingsUpdate | None -GlobalTableGlobalSecondaryIndexSettingsUpdateList = List[ +GlobalTableGlobalSecondaryIndexSettingsUpdateList = list[ GlobalTableGlobalSecondaryIndexSettingsUpdate ] -GlobalTableList = List[GlobalTable] +GlobalTableList = list[GlobalTable] class GlobalTableWitnessGroupUpdate(TypedDict, total=False): - Create: Optional[CreateGlobalTableWitnessGroupMemberAction] - Delete: Optional[DeleteGlobalTableWitnessGroupMemberAction] + Create: CreateGlobalTableWitnessGroupMemberAction | None + Delete: DeleteGlobalTableWitnessGroupMemberAction | None -GlobalTableWitnessGroupUpdateList = List[GlobalTableWitnessGroupUpdate] +GlobalTableWitnessGroupUpdateList = list[GlobalTableWitnessGroupUpdate] class ImportSummary(TypedDict, total=False): - ImportArn: Optional[ImportArn] - ImportStatus: Optional[ImportStatus] - TableArn: Optional[TableArn] - S3BucketSource: Optional[S3BucketSource] - CloudWatchLogGroupArn: Optional[CloudWatchLogGroupArn] - InputFormat: Optional[InputFormat] - StartTime: Optional[ImportStartTime] - EndTime: Optional[ImportEndTime] + ImportArn: ImportArn | None + ImportStatus: ImportStatus | None + TableArn: TableArn | None + S3BucketSource: S3BucketSource | None + CloudWatchLogGroupArn: CloudWatchLogGroupArn | None + InputFormat: InputFormat | None + StartTime: ImportStartTime | None + EndTime: ImportEndTime | None -ImportSummaryList = List[ImportSummary] +ImportSummaryList = list[ImportSummary] class ImportTableInput(ServiceRequest): - ClientToken: Optional[ClientToken] + ClientToken: ClientToken | None S3BucketSource: S3BucketSource InputFormat: InputFormat - InputFormatOptions: Optional[InputFormatOptions] - InputCompressionType: Optional[InputCompressionType] + InputFormatOptions: InputFormatOptions | None + InputCompressionType: InputCompressionType | None TableCreationParameters: TableCreationParameters @@ -1819,20 +1854,20 @@ class ImportTableOutput(TypedDict, total=False): ImportTableDescription: ImportTableDescription -KeyConditions = Dict[AttributeName, Condition] +KeyConditions = dict[AttributeName, Condition] class KinesisStreamingDestinationInput(ServiceRequest): TableName: TableArn StreamArn: StreamArn - EnableKinesisStreamingConfiguration: Optional[EnableKinesisStreamingConfiguration] + EnableKinesisStreamingConfiguration: EnableKinesisStreamingConfiguration | None class KinesisStreamingDestinationOutput(TypedDict, total=False): - TableName: Optional[TableName] - StreamArn: Optional[StreamArn] - DestinationStatus: Optional[DestinationStatus] - EnableKinesisStreamingConfiguration: Optional[EnableKinesisStreamingConfiguration] + TableName: TableName | None + StreamArn: StreamArn | None + DestinationStatus: DestinationStatus | None + EnableKinesisStreamingConfiguration: EnableKinesisStreamingConfiguration | None TimeRangeUpperBound = datetime @@ -1840,287 +1875,285 @@ class KinesisStreamingDestinationOutput(TypedDict, total=False): class ListBackupsInput(ServiceRequest): - TableName: Optional[TableArn] - Limit: Optional[BackupsInputLimit] - TimeRangeLowerBound: Optional[TimeRangeLowerBound] - TimeRangeUpperBound: Optional[TimeRangeUpperBound] - ExclusiveStartBackupArn: Optional[BackupArn] - BackupType: Optional[BackupTypeFilter] + TableName: TableArn | None + Limit: BackupsInputLimit | None + TimeRangeLowerBound: TimeRangeLowerBound | None + TimeRangeUpperBound: TimeRangeUpperBound | None + ExclusiveStartBackupArn: BackupArn | None + BackupType: BackupTypeFilter | None class ListBackupsOutput(TypedDict, total=False): - BackupSummaries: Optional[BackupSummaries] - LastEvaluatedBackupArn: Optional[BackupArn] + BackupSummaries: BackupSummaries | None + LastEvaluatedBackupArn: BackupArn | None class ListContributorInsightsInput(ServiceRequest): - TableName: Optional[TableArn] - NextToken: Optional[NextTokenString] - MaxResults: Optional[ListContributorInsightsLimit] + TableName: TableArn | None + NextToken: NextTokenString | None + MaxResults: ListContributorInsightsLimit | None class ListContributorInsightsOutput(TypedDict, total=False): - ContributorInsightsSummaries: Optional[ContributorInsightsSummaries] - NextToken: Optional[NextTokenString] + ContributorInsightsSummaries: ContributorInsightsSummaries | None + NextToken: NextTokenString | None class ListExportsInput(ServiceRequest): - TableArn: Optional[TableArn] - MaxResults: Optional[ListExportsMaxLimit] - NextToken: Optional[ExportNextToken] + TableArn: TableArn | None + MaxResults: ListExportsMaxLimit | None + NextToken: ExportNextToken | None class ListExportsOutput(TypedDict, total=False): - ExportSummaries: Optional[ExportSummaries] - NextToken: Optional[ExportNextToken] + ExportSummaries: ExportSummaries | None + NextToken: ExportNextToken | None class ListGlobalTablesInput(ServiceRequest): - ExclusiveStartGlobalTableName: Optional[TableName] - Limit: Optional[PositiveIntegerObject] - RegionName: Optional[RegionName] + ExclusiveStartGlobalTableName: TableName | None + Limit: PositiveIntegerObject | None + RegionName: RegionName | None class ListGlobalTablesOutput(TypedDict, total=False): - GlobalTables: Optional[GlobalTableList] - LastEvaluatedGlobalTableName: Optional[TableName] + GlobalTables: GlobalTableList | None + LastEvaluatedGlobalTableName: TableName | None class ListImportsInput(ServiceRequest): - TableArn: Optional[TableArn] - PageSize: Optional[ListImportsMaxLimit] - NextToken: Optional[ImportNextToken] + TableArn: TableArn | None + PageSize: ListImportsMaxLimit | None + NextToken: ImportNextToken | None class ListImportsOutput(TypedDict, total=False): - ImportSummaryList: Optional[ImportSummaryList] - NextToken: Optional[ImportNextToken] + ImportSummaryList: ImportSummaryList | None + NextToken: ImportNextToken | None class ListTablesInput(ServiceRequest): - ExclusiveStartTableName: Optional[TableName] - Limit: Optional[ListTablesInputLimit] + ExclusiveStartTableName: TableName | None + Limit: ListTablesInputLimit | None -TableNameList = List[TableName] +TableNameList = list[TableName] class ListTablesOutput(TypedDict, total=False): - TableNames: Optional[TableNameList] - LastEvaluatedTableName: Optional[TableName] + TableNames: TableNameList | None + LastEvaluatedTableName: TableName | None class ListTagsOfResourceInput(ServiceRequest): ResourceArn: ResourceArnString - NextToken: Optional[NextTokenString] + NextToken: NextTokenString | None class ListTagsOfResourceOutput(TypedDict, total=False): - Tags: Optional[TagList] - NextToken: Optional[NextTokenString] + Tags: TagList | None + NextToken: NextTokenString | None class PointInTimeRecoverySpecification(TypedDict, total=False): PointInTimeRecoveryEnabled: BooleanObject - RecoveryPeriodInDays: Optional[RecoveryPeriodInDays] + RecoveryPeriodInDays: RecoveryPeriodInDays | None class Put(TypedDict, total=False): Item: PutItemInputAttributeMap TableName: TableArn - ConditionExpression: Optional[ConditionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + ConditionExpression: ConditionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class PutItemInput(ServiceRequest): TableName: TableArn Item: PutItemInputAttributeMap - Expected: Optional[ExpectedAttributeMap] - ReturnValues: Optional[ReturnValue] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - ReturnItemCollectionMetrics: Optional[ReturnItemCollectionMetrics] - ConditionalOperator: Optional[ConditionalOperator] - ConditionExpression: Optional[ConditionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + Expected: ExpectedAttributeMap | None + ReturnValues: ReturnValue | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None + ReturnItemCollectionMetrics: ReturnItemCollectionMetrics | None + ConditionalOperator: ConditionalOperator | None + ConditionExpression: ConditionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class PutItemOutput(TypedDict, total=False): - Attributes: Optional[AttributeMap] - ConsumedCapacity: Optional[ConsumedCapacity] - ItemCollectionMetrics: Optional[ItemCollectionMetrics] + Attributes: AttributeMap | None + ConsumedCapacity: ConsumedCapacity | None + ItemCollectionMetrics: ItemCollectionMetrics | None class PutResourcePolicyInput(ServiceRequest): ResourceArn: ResourceArnString Policy: ResourcePolicy - ExpectedRevisionId: Optional[PolicyRevisionId] - ConfirmRemoveSelfResourceAccess: Optional[ConfirmRemoveSelfResourceAccess] + ExpectedRevisionId: PolicyRevisionId | None + ConfirmRemoveSelfResourceAccess: ConfirmRemoveSelfResourceAccess | None class PutResourcePolicyOutput(TypedDict, total=False): - RevisionId: Optional[PolicyRevisionId] + RevisionId: PolicyRevisionId | None class QueryInput(ServiceRequest): TableName: TableArn - IndexName: Optional[IndexName] - Select: Optional[Select] - AttributesToGet: Optional[AttributeNameList] - Limit: Optional[PositiveIntegerObject] - ConsistentRead: Optional[ConsistentRead] - KeyConditions: Optional[KeyConditions] - QueryFilter: Optional[FilterConditionMap] - ConditionalOperator: Optional[ConditionalOperator] - ScanIndexForward: Optional[BooleanObject] - ExclusiveStartKey: Optional[Key] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - ProjectionExpression: Optional[ProjectionExpression] - FilterExpression: Optional[ConditionExpression] - KeyConditionExpression: Optional[KeyExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] + IndexName: IndexName | None + Select: Select | None + AttributesToGet: AttributeNameList | None + Limit: PositiveIntegerObject | None + ConsistentRead: ConsistentRead | None + KeyConditions: KeyConditions | None + QueryFilter: FilterConditionMap | None + ConditionalOperator: ConditionalOperator | None + ScanIndexForward: BooleanObject | None + ExclusiveStartKey: Key | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None + ProjectionExpression: ProjectionExpression | None + FilterExpression: ConditionExpression | None + KeyConditionExpression: KeyExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None class QueryOutput(TypedDict, total=False): - Items: Optional[ItemList] - Count: Optional[Integer] - ScannedCount: Optional[Integer] - LastEvaluatedKey: Optional[Key] - ConsumedCapacity: Optional[ConsumedCapacity] + Items: ItemList | None + Count: Integer | None + ScannedCount: Integer | None + LastEvaluatedKey: Key | None + ConsumedCapacity: ConsumedCapacity | None class ReplicaGlobalSecondaryIndexAutoScalingUpdate(TypedDict, total=False): - IndexName: Optional[IndexName] - ProvisionedReadCapacityAutoScalingUpdate: Optional[AutoScalingSettingsUpdate] + IndexName: IndexName | None + ProvisionedReadCapacityAutoScalingUpdate: AutoScalingSettingsUpdate | None -ReplicaGlobalSecondaryIndexAutoScalingUpdateList = List[ +ReplicaGlobalSecondaryIndexAutoScalingUpdateList = list[ ReplicaGlobalSecondaryIndexAutoScalingUpdate ] class ReplicaAutoScalingUpdate(TypedDict, total=False): RegionName: RegionName - ReplicaGlobalSecondaryIndexUpdates: Optional[ReplicaGlobalSecondaryIndexAutoScalingUpdateList] - ReplicaProvisionedReadCapacityAutoScalingUpdate: Optional[AutoScalingSettingsUpdate] + ReplicaGlobalSecondaryIndexUpdates: ReplicaGlobalSecondaryIndexAutoScalingUpdateList | None + ReplicaProvisionedReadCapacityAutoScalingUpdate: AutoScalingSettingsUpdate | None -ReplicaAutoScalingUpdateList = List[ReplicaAutoScalingUpdate] +ReplicaAutoScalingUpdateList = list[ReplicaAutoScalingUpdate] class ReplicaGlobalSecondaryIndexSettingsUpdate(TypedDict, total=False): IndexName: IndexName - ProvisionedReadCapacityUnits: Optional[PositiveLongObject] - ProvisionedReadCapacityAutoScalingSettingsUpdate: Optional[AutoScalingSettingsUpdate] + ProvisionedReadCapacityUnits: PositiveLongObject | None + ProvisionedReadCapacityAutoScalingSettingsUpdate: AutoScalingSettingsUpdate | None -ReplicaGlobalSecondaryIndexSettingsUpdateList = List[ReplicaGlobalSecondaryIndexSettingsUpdate] +ReplicaGlobalSecondaryIndexSettingsUpdateList = list[ReplicaGlobalSecondaryIndexSettingsUpdate] class ReplicaSettingsUpdate(TypedDict, total=False): RegionName: RegionName - ReplicaProvisionedReadCapacityUnits: Optional[PositiveLongObject] - ReplicaProvisionedReadCapacityAutoScalingSettingsUpdate: Optional[AutoScalingSettingsUpdate] - ReplicaGlobalSecondaryIndexSettingsUpdate: Optional[ - ReplicaGlobalSecondaryIndexSettingsUpdateList - ] - ReplicaTableClass: Optional[TableClass] + ReplicaProvisionedReadCapacityUnits: PositiveLongObject | None + ReplicaProvisionedReadCapacityAutoScalingSettingsUpdate: AutoScalingSettingsUpdate | None + ReplicaGlobalSecondaryIndexSettingsUpdate: ReplicaGlobalSecondaryIndexSettingsUpdateList | None + ReplicaTableClass: TableClass | None -ReplicaSettingsUpdateList = List[ReplicaSettingsUpdate] +ReplicaSettingsUpdateList = list[ReplicaSettingsUpdate] class ReplicaUpdate(TypedDict, total=False): - Create: Optional[CreateReplicaAction] - Delete: Optional[DeleteReplicaAction] + Create: CreateReplicaAction | None + Delete: DeleteReplicaAction | None -ReplicaUpdateList = List[ReplicaUpdate] +ReplicaUpdateList = list[ReplicaUpdate] class UpdateReplicationGroupMemberAction(TypedDict, total=False): RegionName: RegionName - KMSMasterKeyId: Optional[KMSMasterKeyId] - ProvisionedThroughputOverride: Optional[ProvisionedThroughputOverride] - OnDemandThroughputOverride: Optional[OnDemandThroughputOverride] - GlobalSecondaryIndexes: Optional[ReplicaGlobalSecondaryIndexList] - TableClassOverride: Optional[TableClass] + KMSMasterKeyId: KMSMasterKeyId | None + ProvisionedThroughputOverride: ProvisionedThroughputOverride | None + OnDemandThroughputOverride: OnDemandThroughputOverride | None + GlobalSecondaryIndexes: ReplicaGlobalSecondaryIndexList | None + TableClassOverride: TableClass | None class ReplicationGroupUpdate(TypedDict, total=False): - Create: Optional[CreateReplicationGroupMemberAction] - Update: Optional[UpdateReplicationGroupMemberAction] - Delete: Optional[DeleteReplicationGroupMemberAction] + Create: CreateReplicationGroupMemberAction | None + Update: UpdateReplicationGroupMemberAction | None + Delete: DeleteReplicationGroupMemberAction | None -ReplicationGroupUpdateList = List[ReplicationGroupUpdate] +ReplicationGroupUpdateList = list[ReplicationGroupUpdate] class RestoreTableFromBackupInput(ServiceRequest): TargetTableName: TableName BackupArn: BackupArn - BillingModeOverride: Optional[BillingMode] - GlobalSecondaryIndexOverride: Optional[GlobalSecondaryIndexList] - LocalSecondaryIndexOverride: Optional[LocalSecondaryIndexList] - ProvisionedThroughputOverride: Optional[ProvisionedThroughput] - OnDemandThroughputOverride: Optional[OnDemandThroughput] - SSESpecificationOverride: Optional[SSESpecification] + BillingModeOverride: BillingMode | None + GlobalSecondaryIndexOverride: GlobalSecondaryIndexList | None + LocalSecondaryIndexOverride: LocalSecondaryIndexList | None + ProvisionedThroughputOverride: ProvisionedThroughput | None + OnDemandThroughputOverride: OnDemandThroughput | None + SSESpecificationOverride: SSESpecification | None class RestoreTableFromBackupOutput(TypedDict, total=False): - TableDescription: Optional[TableDescription] + TableDescription: TableDescription | None class RestoreTableToPointInTimeInput(ServiceRequest): - SourceTableArn: Optional[TableArn] - SourceTableName: Optional[TableName] + SourceTableArn: TableArn | None + SourceTableName: TableName | None TargetTableName: TableName - UseLatestRestorableTime: Optional[BooleanObject] - RestoreDateTime: Optional[Date] - BillingModeOverride: Optional[BillingMode] - GlobalSecondaryIndexOverride: Optional[GlobalSecondaryIndexList] - LocalSecondaryIndexOverride: Optional[LocalSecondaryIndexList] - ProvisionedThroughputOverride: Optional[ProvisionedThroughput] - OnDemandThroughputOverride: Optional[OnDemandThroughput] - SSESpecificationOverride: Optional[SSESpecification] + UseLatestRestorableTime: BooleanObject | None + RestoreDateTime: Date | None + BillingModeOverride: BillingMode | None + GlobalSecondaryIndexOverride: GlobalSecondaryIndexList | None + LocalSecondaryIndexOverride: LocalSecondaryIndexList | None + ProvisionedThroughputOverride: ProvisionedThroughput | None + OnDemandThroughputOverride: OnDemandThroughput | None + SSESpecificationOverride: SSESpecification | None class RestoreTableToPointInTimeOutput(TypedDict, total=False): - TableDescription: Optional[TableDescription] + TableDescription: TableDescription | None class ScanInput(ServiceRequest): TableName: TableArn - IndexName: Optional[IndexName] - AttributesToGet: Optional[AttributeNameList] - Limit: Optional[PositiveIntegerObject] - Select: Optional[Select] - ScanFilter: Optional[FilterConditionMap] - ConditionalOperator: Optional[ConditionalOperator] - ExclusiveStartKey: Optional[Key] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - TotalSegments: Optional[ScanTotalSegments] - Segment: Optional[ScanSegment] - ProjectionExpression: Optional[ProjectionExpression] - FilterExpression: Optional[ConditionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ConsistentRead: Optional[ConsistentRead] + IndexName: IndexName | None + AttributesToGet: AttributeNameList | None + Limit: PositiveIntegerObject | None + Select: Select | None + ScanFilter: FilterConditionMap | None + ConditionalOperator: ConditionalOperator | None + ExclusiveStartKey: Key | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None + TotalSegments: ScanTotalSegments | None + Segment: ScanSegment | None + ProjectionExpression: ProjectionExpression | None + FilterExpression: ConditionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ConsistentRead: ConsistentRead | None class ScanOutput(TypedDict, total=False): - Items: Optional[ItemList] - Count: Optional[Integer] - ScannedCount: Optional[Integer] - LastEvaluatedKey: Optional[Key] - ConsumedCapacity: Optional[ConsumedCapacity] + Items: ItemList | None + Count: Integer | None + ScannedCount: Integer | None + LastEvaluatedKey: Key | None + ConsumedCapacity: ConsumedCapacity | None -TagKeyList = List[TagKeyString] +TagKeyList = list[TagKeyString] class TagResourceInput(ServiceRequest): @@ -2137,49 +2170,49 @@ class TransactGetItem(TypedDict, total=False): Get: Get -TransactGetItemList = List[TransactGetItem] +TransactGetItemList = list[TransactGetItem] class TransactGetItemsInput(ServiceRequest): TransactItems: TransactGetItemList - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] + ReturnConsumedCapacity: ReturnConsumedCapacity | None class TransactGetItemsOutput(TypedDict, total=False): - ConsumedCapacity: Optional[ConsumedCapacityMultiple] - Responses: Optional[ItemResponseList] + ConsumedCapacity: ConsumedCapacityMultiple | None + Responses: ItemResponseList | None class Update(TypedDict, total=False): Key: Key UpdateExpression: UpdateExpression TableName: TableArn - ConditionExpression: Optional[ConditionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + ConditionExpression: ConditionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class TransactWriteItem(TypedDict, total=False): - ConditionCheck: Optional[ConditionCheck] - Put: Optional[Put] - Delete: Optional[Delete] - Update: Optional[Update] + ConditionCheck: ConditionCheck | None + Put: Put | None + Delete: Delete | None + Update: Update | None -TransactWriteItemList = List[TransactWriteItem] +TransactWriteItemList = list[TransactWriteItem] class TransactWriteItemsInput(ServiceRequest): TransactItems: TransactWriteItemList - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - ReturnItemCollectionMetrics: Optional[ReturnItemCollectionMetrics] - ClientRequestToken: Optional[ClientRequestToken] + ReturnConsumedCapacity: ReturnConsumedCapacity | None + ReturnItemCollectionMetrics: ReturnItemCollectionMetrics | None + ClientRequestToken: ClientRequestToken | None class TransactWriteItemsOutput(TypedDict, total=False): - ConsumedCapacity: Optional[ConsumedCapacityMultiple] - ItemCollectionMetrics: Optional[ItemCollectionMetricsPerTable] + ConsumedCapacity: ConsumedCapacityMultiple | None + ItemCollectionMetrics: ItemCollectionMetricsPerTable | None class UntagResourceInput(ServiceRequest): @@ -2193,19 +2226,21 @@ class UpdateContinuousBackupsInput(ServiceRequest): class UpdateContinuousBackupsOutput(TypedDict, total=False): - ContinuousBackupsDescription: Optional[ContinuousBackupsDescription] + ContinuousBackupsDescription: ContinuousBackupsDescription | None class UpdateContributorInsightsInput(ServiceRequest): TableName: TableArn - IndexName: Optional[IndexName] + IndexName: IndexName | None ContributorInsightsAction: ContributorInsightsAction + ContributorInsightsMode: ContributorInsightsMode | None class UpdateContributorInsightsOutput(TypedDict, total=False): - TableName: Optional[TableName] - IndexName: Optional[IndexName] - ContributorInsightsStatus: Optional[ContributorInsightsStatus] + TableName: TableName | None + IndexName: IndexName | None + ContributorInsightsStatus: ContributorInsightsStatus | None + ContributorInsightsMode: ContributorInsightsMode | None class UpdateGlobalTableInput(ServiceRequest): @@ -2214,96 +2249,95 @@ class UpdateGlobalTableInput(ServiceRequest): class UpdateGlobalTableOutput(TypedDict, total=False): - GlobalTableDescription: Optional[GlobalTableDescription] + GlobalTableDescription: GlobalTableDescription | None class UpdateGlobalTableSettingsInput(ServiceRequest): GlobalTableName: TableName - GlobalTableBillingMode: Optional[BillingMode] - GlobalTableProvisionedWriteCapacityUnits: Optional[PositiveLongObject] - GlobalTableProvisionedWriteCapacityAutoScalingSettingsUpdate: Optional[ - AutoScalingSettingsUpdate - ] - GlobalTableGlobalSecondaryIndexSettingsUpdate: Optional[ - GlobalTableGlobalSecondaryIndexSettingsUpdateList - ] - ReplicaSettingsUpdate: Optional[ReplicaSettingsUpdateList] + GlobalTableBillingMode: BillingMode | None + GlobalTableProvisionedWriteCapacityUnits: PositiveLongObject | None + GlobalTableProvisionedWriteCapacityAutoScalingSettingsUpdate: AutoScalingSettingsUpdate | None + GlobalTableGlobalSecondaryIndexSettingsUpdate: ( + GlobalTableGlobalSecondaryIndexSettingsUpdateList | None + ) + ReplicaSettingsUpdate: ReplicaSettingsUpdateList | None class UpdateGlobalTableSettingsOutput(TypedDict, total=False): - GlobalTableName: Optional[TableName] - ReplicaSettings: Optional[ReplicaSettingsDescriptionList] + GlobalTableName: TableName | None + ReplicaSettings: ReplicaSettingsDescriptionList | None class UpdateItemInput(ServiceRequest): TableName: TableArn Key: Key - AttributeUpdates: Optional[AttributeUpdates] - Expected: Optional[ExpectedAttributeMap] - ConditionalOperator: Optional[ConditionalOperator] - ReturnValues: Optional[ReturnValue] - ReturnConsumedCapacity: Optional[ReturnConsumedCapacity] - ReturnItemCollectionMetrics: Optional[ReturnItemCollectionMetrics] - UpdateExpression: Optional[UpdateExpression] - ConditionExpression: Optional[ConditionExpression] - ExpressionAttributeNames: Optional[ExpressionAttributeNameMap] - ExpressionAttributeValues: Optional[ExpressionAttributeValueMap] - ReturnValuesOnConditionCheckFailure: Optional[ReturnValuesOnConditionCheckFailure] + AttributeUpdates: AttributeUpdates | None + Expected: ExpectedAttributeMap | None + ConditionalOperator: ConditionalOperator | None + ReturnValues: ReturnValue | None + ReturnConsumedCapacity: ReturnConsumedCapacity | None + ReturnItemCollectionMetrics: ReturnItemCollectionMetrics | None + UpdateExpression: UpdateExpression | None + ConditionExpression: ConditionExpression | None + ExpressionAttributeNames: ExpressionAttributeNameMap | None + ExpressionAttributeValues: ExpressionAttributeValueMap | None + ReturnValuesOnConditionCheckFailure: ReturnValuesOnConditionCheckFailure | None class UpdateItemOutput(TypedDict, total=False): - Attributes: Optional[AttributeMap] - ConsumedCapacity: Optional[ConsumedCapacity] - ItemCollectionMetrics: Optional[ItemCollectionMetrics] + Attributes: AttributeMap | None + ConsumedCapacity: ConsumedCapacity | None + ItemCollectionMetrics: ItemCollectionMetrics | None class UpdateKinesisStreamingConfiguration(TypedDict, total=False): - ApproximateCreationDateTimePrecision: Optional[ApproximateCreationDateTimePrecision] + ApproximateCreationDateTimePrecision: ApproximateCreationDateTimePrecision | None class UpdateKinesisStreamingDestinationInput(ServiceRequest): TableName: TableArn StreamArn: StreamArn - UpdateKinesisStreamingConfiguration: Optional[UpdateKinesisStreamingConfiguration] + UpdateKinesisStreamingConfiguration: UpdateKinesisStreamingConfiguration | None class UpdateKinesisStreamingDestinationOutput(TypedDict, total=False): - TableName: Optional[TableName] - StreamArn: Optional[StreamArn] - DestinationStatus: Optional[DestinationStatus] - UpdateKinesisStreamingConfiguration: Optional[UpdateKinesisStreamingConfiguration] + TableName: TableName | None + StreamArn: StreamArn | None + DestinationStatus: DestinationStatus | None + UpdateKinesisStreamingConfiguration: UpdateKinesisStreamingConfiguration | None class UpdateTableInput(ServiceRequest): - AttributeDefinitions: Optional[AttributeDefinitions] + AttributeDefinitions: AttributeDefinitions | None TableName: TableArn - BillingMode: Optional[BillingMode] - ProvisionedThroughput: Optional[ProvisionedThroughput] - GlobalSecondaryIndexUpdates: Optional[GlobalSecondaryIndexUpdateList] - StreamSpecification: Optional[StreamSpecification] - SSESpecification: Optional[SSESpecification] - ReplicaUpdates: Optional[ReplicationGroupUpdateList] - TableClass: Optional[TableClass] - DeletionProtectionEnabled: Optional[DeletionProtectionEnabled] - MultiRegionConsistency: Optional[MultiRegionConsistency] - GlobalTableWitnessUpdates: Optional[GlobalTableWitnessGroupUpdateList] - OnDemandThroughput: Optional[OnDemandThroughput] - WarmThroughput: Optional[WarmThroughput] + BillingMode: BillingMode | None + ProvisionedThroughput: ProvisionedThroughput | None + GlobalSecondaryIndexUpdates: GlobalSecondaryIndexUpdateList | None + StreamSpecification: StreamSpecification | None + SSESpecification: SSESpecification | None + ReplicaUpdates: ReplicationGroupUpdateList | None + TableClass: TableClass | None + DeletionProtectionEnabled: DeletionProtectionEnabled | None + MultiRegionConsistency: MultiRegionConsistency | None + GlobalTableWitnessUpdates: GlobalTableWitnessGroupUpdateList | None + OnDemandThroughput: OnDemandThroughput | None + WarmThroughput: WarmThroughput | None + GlobalTableSettingsReplicationMode: GlobalTableSettingsReplicationMode | None class UpdateTableOutput(TypedDict, total=False): - TableDescription: Optional[TableDescription] + TableDescription: TableDescription | None class UpdateTableReplicaAutoScalingInput(ServiceRequest): - GlobalSecondaryIndexUpdates: Optional[GlobalSecondaryIndexAutoScalingUpdateList] + GlobalSecondaryIndexUpdates: GlobalSecondaryIndexAutoScalingUpdateList | None TableName: TableArn - ProvisionedWriteCapacityAutoScalingUpdate: Optional[AutoScalingSettingsUpdate] - ReplicaUpdates: Optional[ReplicaAutoScalingUpdateList] + ProvisionedWriteCapacityAutoScalingUpdate: AutoScalingSettingsUpdate | None + ReplicaUpdates: ReplicaAutoScalingUpdateList | None class UpdateTableReplicaAutoScalingOutput(TypedDict, total=False): - TableAutoScalingDescription: Optional[TableAutoScalingDescription] + TableAutoScalingDescription: TableAutoScalingDescription | None class UpdateTimeToLiveInput(ServiceRequest): @@ -2312,12 +2346,12 @@ class UpdateTimeToLiveInput(ServiceRequest): class UpdateTimeToLiveOutput(TypedDict, total=False): - TimeToLiveSpecification: Optional[TimeToLiveSpecification] + TimeToLiveSpecification: TimeToLiveSpecification | None class DynamodbApi: - service = "dynamodb" - version = "2012-08-10" + service: str = "dynamodb" + version: str = "2012-08-10" @handler("BatchExecuteStatement") def batch_execute_statement( @@ -2370,9 +2404,9 @@ def create_global_table( def create_table( self, context: RequestContext, - attribute_definitions: AttributeDefinitions, table_name: TableArn, - key_schema: KeySchema, + attribute_definitions: AttributeDefinitions | None = None, + key_schema: KeySchema | None = None, local_secondary_indexes: LocalSecondaryIndexList | None = None, global_secondary_indexes: GlobalSecondaryIndexList | None = None, billing_mode: BillingMode | None = None, @@ -2385,6 +2419,8 @@ def create_table( warm_throughput: WarmThroughput | None = None, resource_policy: ResourcePolicy | None = None, on_demand_throughput: OnDemandThroughput | None = None, + global_table_source_arn: TableArn | None = None, + global_table_settings_replication_mode: GlobalTableSettingsReplicationMode | None = None, **kwargs, ) -> CreateTableOutput: raise NotImplementedError @@ -2858,6 +2894,7 @@ def update_contributor_insights( table_name: TableArn, contributor_insights_action: ContributorInsightsAction, index_name: IndexName | None = None, + contributor_insights_mode: ContributorInsightsMode | None = None, **kwargs, ) -> UpdateContributorInsightsOutput: raise NotImplementedError @@ -2938,6 +2975,7 @@ def update_table( global_table_witness_updates: GlobalTableWitnessGroupUpdateList | None = None, on_demand_throughput: OnDemandThroughput | None = None, warm_throughput: WarmThroughput | None = None, + global_table_settings_replication_mode: GlobalTableSettingsReplicationMode | None = None, **kwargs, ) -> UpdateTableOutput: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/dynamodbstreams/__init__.py b/localstack-core/localstack/aws/api/dynamodbstreams/__init__.py index a9ecabeff5864..d387a1bf3d58e 100644 --- a/localstack-core/localstack/aws/api/dynamodbstreams/__init__.py +++ b/localstack-core/localstack/aws/api/dynamodbstreams/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -31,6 +31,10 @@ class OperationType(StrEnum): REMOVE = "REMOVE" +class ShardFilterType(StrEnum): + CHILD_SHARDS = "CHILD_SHARDS" + + class ShardIteratorType(StrEnum): TRIM_HORIZON = "TRIM_HORIZON" LATEST = "LATEST" @@ -83,46 +87,52 @@ class TrimmedDataAccessException(ServiceException): class AttributeValue(TypedDict, total=False): - S: Optional["StringAttributeValue"] - N: Optional["NumberAttributeValue"] - B: Optional["BinaryAttributeValue"] - SS: Optional["StringSetAttributeValue"] - NS: Optional["NumberSetAttributeValue"] - BS: Optional["BinarySetAttributeValue"] - M: Optional["MapAttributeValue"] - L: Optional["ListAttributeValue"] - NULL: Optional["NullAttributeValue"] - BOOL: Optional["BooleanAttributeValue"] - - -ListAttributeValue = List[AttributeValue] -MapAttributeValue = Dict[AttributeName, AttributeValue] + S: "StringAttributeValue | None" + N: "NumberAttributeValue | None" + B: "BinaryAttributeValue | None" + SS: "StringSetAttributeValue | None" + NS: "NumberSetAttributeValue | None" + BS: "BinarySetAttributeValue | None" + M: "MapAttributeValue | None" + L: "ListAttributeValue | None" + NULL: "NullAttributeValue | None" + BOOL: "BooleanAttributeValue | None" + + +ListAttributeValue = list[AttributeValue] +MapAttributeValue = dict[AttributeName, AttributeValue] BinaryAttributeValue = bytes -BinarySetAttributeValue = List[BinaryAttributeValue] -NumberSetAttributeValue = List[NumberAttributeValue] -StringSetAttributeValue = List[StringAttributeValue] -AttributeMap = Dict[AttributeName, AttributeValue] +BinarySetAttributeValue = list[BinaryAttributeValue] +NumberSetAttributeValue = list[NumberAttributeValue] +StringSetAttributeValue = list[StringAttributeValue] +AttributeMap = dict[AttributeName, AttributeValue] Date = datetime +class ShardFilter(TypedDict, total=False): + Type: ShardFilterType | None + ShardId: ShardId | None + + class DescribeStreamInput(ServiceRequest): StreamArn: StreamArn - Limit: Optional[PositiveIntegerObject] - ExclusiveStartShardId: Optional[ShardId] + Limit: PositiveIntegerObject | None + ExclusiveStartShardId: ShardId | None + ShardFilter: ShardFilter | None class SequenceNumberRange(TypedDict, total=False): - StartingSequenceNumber: Optional[SequenceNumber] - EndingSequenceNumber: Optional[SequenceNumber] + StartingSequenceNumber: SequenceNumber | None + EndingSequenceNumber: SequenceNumber | None class Shard(TypedDict, total=False): - ShardId: Optional[ShardId] - SequenceNumberRange: Optional[SequenceNumberRange] - ParentShardId: Optional[ShardId] + ShardId: ShardId | None + SequenceNumberRange: SequenceNumberRange | None + ParentShardId: ShardId | None -ShardDescriptionList = List[Shard] +ShardDescriptionList = list[Shard] class KeySchemaElement(TypedDict, total=False): @@ -130,100 +140,100 @@ class KeySchemaElement(TypedDict, total=False): KeyType: KeyType -KeySchema = List[KeySchemaElement] +KeySchema = list[KeySchemaElement] class StreamDescription(TypedDict, total=False): - StreamArn: Optional[StreamArn] - StreamLabel: Optional[String] - StreamStatus: Optional[StreamStatus] - StreamViewType: Optional[StreamViewType] - CreationRequestDateTime: Optional[Date] - TableName: Optional[TableName] - KeySchema: Optional[KeySchema] - Shards: Optional[ShardDescriptionList] - LastEvaluatedShardId: Optional[ShardId] + StreamArn: StreamArn | None + StreamLabel: String | None + StreamStatus: StreamStatus | None + StreamViewType: StreamViewType | None + CreationRequestDateTime: Date | None + TableName: TableName | None + KeySchema: KeySchema | None + Shards: ShardDescriptionList | None + LastEvaluatedShardId: ShardId | None class DescribeStreamOutput(TypedDict, total=False): - StreamDescription: Optional[StreamDescription] + StreamDescription: StreamDescription | None class GetRecordsInput(ServiceRequest): ShardIterator: ShardIterator - Limit: Optional[PositiveIntegerObject] + Limit: PositiveIntegerObject | None class Identity(TypedDict, total=False): - PrincipalId: Optional[String] - Type: Optional[String] + PrincipalId: String | None + Type: String | None PositiveLongObject = int class StreamRecord(TypedDict, total=False): - ApproximateCreationDateTime: Optional[Date] - Keys: Optional[AttributeMap] - NewImage: Optional[AttributeMap] - OldImage: Optional[AttributeMap] - SequenceNumber: Optional[SequenceNumber] - SizeBytes: Optional[PositiveLongObject] - StreamViewType: Optional[StreamViewType] + ApproximateCreationDateTime: Date | None + Keys: AttributeMap | None + NewImage: AttributeMap | None + OldImage: AttributeMap | None + SequenceNumber: SequenceNumber | None + SizeBytes: PositiveLongObject | None + StreamViewType: StreamViewType | None class Record(TypedDict, total=False): - eventID: Optional[String] - eventName: Optional[OperationType] - eventVersion: Optional[String] - eventSource: Optional[String] - awsRegion: Optional[String] - dynamodb: Optional[StreamRecord] - userIdentity: Optional[Identity] + eventID: String | None + eventName: OperationType | None + eventVersion: String | None + eventSource: String | None + awsRegion: String | None + dynamodb: StreamRecord | None + userIdentity: Identity | None -RecordList = List[Record] +RecordList = list[Record] class GetRecordsOutput(TypedDict, total=False): - Records: Optional[RecordList] - NextShardIterator: Optional[ShardIterator] + Records: RecordList | None + NextShardIterator: ShardIterator | None class GetShardIteratorInput(ServiceRequest): StreamArn: StreamArn ShardId: ShardId ShardIteratorType: ShardIteratorType - SequenceNumber: Optional[SequenceNumber] + SequenceNumber: SequenceNumber | None class GetShardIteratorOutput(TypedDict, total=False): - ShardIterator: Optional[ShardIterator] + ShardIterator: ShardIterator | None class ListStreamsInput(ServiceRequest): - TableName: Optional[TableName] - Limit: Optional[PositiveIntegerObject] - ExclusiveStartStreamArn: Optional[StreamArn] + TableName: TableName | None + Limit: PositiveIntegerObject | None + ExclusiveStartStreamArn: StreamArn | None class Stream(TypedDict, total=False): - StreamArn: Optional[StreamArn] - TableName: Optional[TableName] - StreamLabel: Optional[String] + StreamArn: StreamArn | None + TableName: TableName | None + StreamLabel: String | None -StreamList = List[Stream] +StreamList = list[Stream] class ListStreamsOutput(TypedDict, total=False): - Streams: Optional[StreamList] - LastEvaluatedStreamArn: Optional[StreamArn] + Streams: StreamList | None + LastEvaluatedStreamArn: StreamArn | None class DynamodbstreamsApi: - service = "dynamodbstreams" - version = "2012-08-10" + service: str = "dynamodbstreams" + version: str = "2012-08-10" @handler("DescribeStream") def describe_stream( @@ -232,6 +242,7 @@ def describe_stream( stream_arn: StreamArn, limit: PositiveIntegerObject | None = None, exclusive_start_shard_id: ShardId | None = None, + shard_filter: ShardFilter | None = None, **kwargs, ) -> DescribeStreamOutput: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/ec2/__init__.py b/localstack-core/localstack/aws/api/ec2/__init__.py index faeb55f9d6697..ee187ac92316a 100644 --- a/localstack-core/localstack/aws/api/ec2/__init__.py +++ b/localstack-core/localstack/aws/api/ec2/__init__.py @@ -1,17 +1,12 @@ from datetime import datetime from enum import StrEnum -from typing import List, Optional, TypedDict +from typing import TypedDict -from localstack.aws.api import ( - RequestContext, - ServiceRequest, - handler, -) -from localstack.aws.api import ( - ServiceException as ServiceException, -) +from localstack.aws.api import RequestContext, ServiceRequest, handler +from localstack.aws.api import ServiceException as ServiceException AccountID = str +AdditionalFlexibleNetworkInterfaces = int AddressMaxResults = int AllocationId = str AllowedInstanceType = str @@ -25,6 +20,7 @@ BaselineIops = int BaselineThroughputInMBps = float Boolean = bool +BoxedBoolean = bool BoxedDouble = float BoxedInteger = int BundleId = str @@ -32,6 +28,7 @@ CancelCapacityReservationFleetErrorCode = str CancelCapacityReservationFleetErrorMessage = str CapacityBlockId = str +CapacityManagerDataExportId = str CapacityReservationFleetId = str CapacityReservationId = str CarrierGatewayId = str @@ -70,8 +67,10 @@ DescribeCapacityBlockOfferingsMaxResults = int DescribeCapacityBlockStatusMaxResults = int DescribeCapacityBlocksMaxResults = int +DescribeCapacityManagerDataExportsRequestMaxResults = int DescribeCapacityReservationBillingRequestsRequestMaxResults = int DescribeCapacityReservationFleetsMaxResults = int +DescribeCapacityReservationTopologyMaxResults = int DescribeCapacityReservationsMaxResults = int DescribeClassicLinkInstancesMaxResults = int DescribeClientVpnAuthorizationRulesMaxResults = int @@ -89,8 +88,12 @@ DescribeFutureCapacityMaxResults = int DescribeHostReservationsMaxResults = int DescribeIamInstanceProfileAssociationsMaxResults = int +DescribeImageReferencesMaxResults = int +DescribeImageUsageReportEntriesMaxResults = int +DescribeImageUsageReportsMaxResults = int DescribeInstanceCreditSpecificationsMaxResults = int DescribeInstanceImageMetadataMaxResults = int +DescribeInstanceSqlHaStatesRequestMaxResultsInteger = int DescribeInstanceTopologyMaxResults = int DescribeInternetGatewaysMaxResults = int DescribeIpamByoasnMaxResults = int @@ -107,6 +110,9 @@ DescribeReplaceRootVolumeTasksMaxResults = int DescribeRouteTablesMaxResults = int DescribeScheduledInstanceAvailabilityMaxResults = int +DescribeSecondaryInterfacesMaxResults = int +DescribeSecondaryNetworksMaxResults = int +DescribeSecondarySubnetsMaxResults = int DescribeSecurityGroupRulesMaxResults = int DescribeSecurityGroupVpcAssociationsMaxResults = int DescribeSecurityGroupsMaxResults = int @@ -126,6 +132,7 @@ DescribeVpcBlockPublicAccessExclusionsMaxResults = int DescribeVpcClassicLinkDnsSupportMaxResults = int DescribeVpcClassicLinkDnsSupportNextToken = str +DescribeVpcEncryptionControlsMaxResults = int DescribeVpcPeeringConnectionsMaxResults = int DescribeVpcsMaxResults = int DhcpOptionsId = str @@ -134,6 +141,7 @@ Double = float DoubleWithConstraints = float DrainSeconds = int +EbsCardIndex = int EfaSupportedFlag = bool EgressOnlyInternetGatewayId = str EipAllocationPublicIp = str @@ -165,17 +173,26 @@ GetSecurityGroupsForVpcRequestMaxResults = int GetSubnetCidrReservationsMaxResults = int GetVerifiedAccessEndpointTargetsMaxResults = int +GetVpcResourcesBlockingEncryptionEnforcementMaxResults = int GpuDeviceCount = int GpuDeviceManufacturerName = str GpuDeviceMemorySize = int GpuDeviceName = str +GpuPartitionSize = float HibernationFlag = bool HostReservationId = str Hour = int IamInstanceProfileAssociationId = str ImageId = str +ImageName = str +ImageNameRequest = str ImageProvider = str ImageProviderRequest = str +ImageUsageReportId = str +ImageUsageReportState = str +ImageUsageReportStateReason = str +ImageUsageResourceTypeName = str +ImageUsageResourceTypeOptionValue = str ImportImageTaskId = str ImportManifestUrl = str ImportSnapshotTaskId = str @@ -202,12 +219,16 @@ IpamId = str IpamMaxResults = int IpamNetmaskLength = int +IpamPolicyId = str IpamPoolAllocationId = str IpamPoolCidrId = str IpamPoolId = str +IpamPrefixListResolverId = str +IpamPrefixListResolverTargetId = str IpamResourceDiscoveryAssociationId = str IpamResourceDiscoveryId = str IpamScopeId = str +Ipv4AddressesPerSecondaryInterface = int Ipv4PoolCoipId = str Ipv4PoolEc2Id = str Ipv6Address = str @@ -234,18 +255,26 @@ LocalGatewayVirtualInterfaceGroupId = str LocalGatewayVirtualInterfaceId = str Location = str +LogicalGpuCount = int MacModificationTaskId = str +MarketplaceProductCode = str +MarketplaceProductCodeRequest = str MaxIpv4AddrPerInterface = int MaxIpv6AddrPerInterface = int MaxNetworkInterfaces = int MaxResults = int MaxResultsParam = int MaximumBandwidthInMbps = int +MaximumDaysSinceCreatedValue = int +MaximumDaysSinceDeprecatedValue = int +MaximumEbsAttachments = int +MaximumEbsCards = int MaximumEfaInterfaces = int MaximumEnaQueueCount = int MaximumEnaQueueCountPerInterface = int MaximumIops = int MaximumNetworkCards = int +MaximumSecondaryNetworkInterfaces = int MaximumThroughputInMBps = float MediaDeviceCount = int MediaDeviceManufacturerName = str @@ -280,9 +309,11 @@ OutpostLagMaxResults = int PasswordData = str PeakBandwidthInGbps = float +Period = int PlacementGroupArn = str PlacementGroupId = str PlacementGroupName = str +PlacementGroupNameWithResolver = str PoolMaxResults = int Port = int PrefixListMaxResults = int @@ -304,6 +335,7 @@ ReservedInstancesOfferingId = str ResourceArn = str ResourceConfigurationArn = str +ResourceTypeOptionValue = str RestoreSnapshotTierRequestTemporaryRestoreDays = int ResultRange = int RetentionPeriodRequestDays = int @@ -320,6 +352,13 @@ S3StorageUploadPolicy = str S3StorageUploadPolicySignature = str ScheduledInstanceId = str +SecondaryInterfaceId = str +SecondaryNetworkCidrAssociationId = str +SecondaryNetworkId = str +SecondaryNetworkSupportedFlag = bool +SecondarySubnetCidrAssociationId = str +SecondarySubnetId = str +SecretArn = str SecurityGroupId = str SecurityGroupName = str SecurityGroupRuleId = str @@ -355,6 +394,7 @@ TransitGatewayConnectPeerId = str TransitGatewayId = str TransitGatewayMaxResults = int +TransitGatewayMeteringPolicyId = str TransitGatewayMulticastDomainId = str TransitGatewayPolicyTableId = str TransitGatewayRouteTableAnnouncementId = str @@ -378,10 +418,12 @@ VpcId = str VpcPeeringConnectionId = str VpcPeeringConnectionIdWithResolver = str +VpnConcentratorId = str VpnConnectionDeviceSampleConfiguration = str VpnConnectionDeviceTypeId = str VpnConnectionId = str VpnGatewayId = str +Workload = str customerGatewayConfiguration = str maxResults = int preSharedKey = str @@ -411,12 +453,20 @@ class AcceleratorName(StrEnum): a10g = "a10g" h100 = "h100" t4g = "t4g" + l40s = "l40s" + l4 = "l4" + gaudi_hl_205 = "gaudi-hl-205" + inferentia2 = "inferentia2" + trainium = "trainium" + trainium2 = "trainium2" + u30 = "u30" class AcceleratorType(StrEnum): gpu = "gpu" fpga = "fpga" inference = "inference" + media = "media" class AccountAttributeName(StrEnum): @@ -549,6 +599,11 @@ class AssociationStatusCode(StrEnum): disassociated = "disassociated" +class AttachmentLimitType(StrEnum): + shared = "shared" + dedicated = "dedicated" + + class AttachmentStatus(StrEnum): attaching = "attaching" attached = "attached" @@ -571,6 +626,21 @@ class AutoPlacement(StrEnum): off = "off" +class AutoProvisionZonesState(StrEnum): + enabled = "enabled" + disabled = "disabled" + + +class AutoScalingIpsState(StrEnum): + enabled = "enabled" + disabled = "disabled" + + +class AvailabilityMode(StrEnum): + zonal = "zonal" + regional = "regional" + + class AvailabilityZoneOptInStatus(StrEnum): opt_in_not_required = "opt-in-not-required" opted_in = "opted-in" @@ -650,8 +720,10 @@ class ByoipCidrState(StrEnum): deprovisioned = "deprovisioned" failed_deprovision = "failed-deprovision" failed_provision = "failed-provision" + pending_advertising = "pending-advertising" pending_deprovision = "pending-deprovision" pending_provision = "pending-provision" + pending_withdrawal = "pending-withdrawal" provisioned = "provisioned" provisioned_not_publicly_advertisable = "provisioned-not-publicly-advertisable" @@ -699,6 +771,18 @@ class CapacityBlockResourceState(StrEnum): payment_failed = "payment-failed" +class CapacityManagerDataExportStatus(StrEnum): + pending = "pending" + in_progress = "in-progress" + delivered = "delivered" + failed = "failed" + + +class CapacityManagerStatus(StrEnum): + enabled = "enabled" + disabled = "disabled" + + class CapacityReservationBillingRequestStatus(StrEnum): pending = "pending" accepted = "accepted" @@ -777,6 +861,11 @@ class CapacityReservationType(StrEnum): capacity_block = "capacity-block" +class CapacityTenancy(StrEnum): + default = "default" + dedicated = "dedicated" + + class CarrierGatewayState(StrEnum): pending = "pending" available = "available" @@ -828,6 +917,11 @@ class ClientVpnRouteStatusCode(StrEnum): deleting = "deleting" +class Comparison(StrEnum): + equals = "equals" + in_ = "in" + + class ConnectionNotificationState(StrEnum): Enabled = "Enabled" Disabled = "Disabled" @@ -873,6 +967,12 @@ class DatafeedSubscriptionState(StrEnum): Inactive = "Inactive" +class DefaultHttpTokensEnforcedState(StrEnum): + disabled = "disabled" + enabled = "enabled" + no_preference = "no-preference" + + class DefaultInstanceMetadataEndpointState(StrEnum): disabled = "disabled" enabled = "enabled" @@ -993,6 +1093,9 @@ class Ec2InstanceConnectEndpointState(StrEnum): delete_in_progress = "delete-in-progress" delete_complete = "delete-complete" delete_failed = "delete-failed" + update_in_progress = "update-in-progress" + update_complete = "update-complete" + update_failed = "update-failed" class EkPubKeyFormat(StrEnum): @@ -1020,11 +1123,29 @@ class EnaSupport(StrEnum): required = "required" +class EncryptionStateValue(StrEnum): + enabling = "enabling" + enabled = "enabled" + disabling = "disabling" + disabled = "disabled" + + +class EncryptionSupportOptionValue(StrEnum): + enable = "enable" + disable = "disable" + + class EndDateType(StrEnum): unlimited = "unlimited" limited = "limited" +class EndpointIpAddressType(StrEnum): + ipv4 = "ipv4" + ipv6 = "ipv6" + dual_stack = "dual-stack" + + class EphemeralNvmeSupport(StrEnum): unsupported = "unsupported" supported = "supported" @@ -1085,6 +1206,26 @@ class FastSnapshotRestoreStateCode(StrEnum): disabled = "disabled" +class FilterByDimension(StrEnum): + resource_region = "resource-region" + availability_zone_id = "availability-zone-id" + account_id = "account-id" + instance_family = "instance-family" + instance_type = "instance-type" + instance_platform = "instance-platform" + reservation_arn = "reservation-arn" + reservation_id = "reservation-id" + reservation_type = "reservation-type" + reservation_create_timestamp = "reservation-create-timestamp" + reservation_start_timestamp = "reservation-start-timestamp" + reservation_end_timestamp = "reservation-end-timestamp" + reservation_end_date_type = "reservation-end-date-type" + tenancy = "tenancy" + reservation_state = "reservation-state" + reservation_instance_match_criteria = "reservation-instance-match-criteria" + reservation_unused_financial_owner = "reservation-unused-financial-owner" + + class FindingsFound(StrEnum): true = "true" false = "false" @@ -1158,6 +1299,7 @@ class FlowLogsResourceType(StrEnum): NetworkInterface = "NetworkInterface" TransitGateway = "TransitGateway" TransitGatewayAttachment = "TransitGatewayAttachment" + RegionalNatGateway = "RegionalNatGateway" class FpgaImageAttributeName(StrEnum): @@ -1185,6 +1327,33 @@ class GatewayType(StrEnum): ipsec_1 = "ipsec.1" +class GroupBy(StrEnum): + resource_region = "resource-region" + availability_zone_id = "availability-zone-id" + account_id = "account-id" + instance_family = "instance-family" + instance_type = "instance-type" + instance_platform = "instance-platform" + reservation_arn = "reservation-arn" + reservation_id = "reservation-id" + reservation_type = "reservation-type" + reservation_create_timestamp = "reservation-create-timestamp" + reservation_start_timestamp = "reservation-start-timestamp" + reservation_end_timestamp = "reservation-end-timestamp" + reservation_end_date_type = "reservation-end-date-type" + tenancy = "tenancy" + reservation_state = "reservation-state" + reservation_instance_match_criteria = "reservation-instance-match-criteria" + reservation_unused_financial_owner = "reservation-unused-financial-owner" + + +class HaStatus(StrEnum): + processing = "processing" + active = "active" + standby = "standby" + invalid = "invalid" + + class HostMaintenance(StrEnum): on = "on" off = "off" @@ -1206,6 +1375,11 @@ class HostnameType(StrEnum): resource_name = "resource-name" +class HttpTokensEnforcedState(StrEnum): + disabled = "disabled" + enabled = "enabled" + + class HttpTokensState(StrEnum): optional = "optional" required = "required" @@ -1252,6 +1426,19 @@ class ImageBlockPublicAccessEnabledState(StrEnum): block_new_sharing = "block-new-sharing" +class ImageReferenceOptionName(StrEnum): + state_name = "state-name" + version_depth = "version-depth" + + +class ImageReferenceResourceType(StrEnum): + ec2_Instance = "ec2:Instance" + ec2_LaunchTemplate = "ec2:LaunchTemplate" + ssm_Parameter = "ssm:Parameter" + imagebuilder_ImageRecipe = "imagebuilder:ImageRecipe" + imagebuilder_ContainerRecipe = "imagebuilder:ContainerRecipe" + + class ImageState(StrEnum): pending = "pending" available = "available" @@ -1273,6 +1460,18 @@ class ImdsSupportValues(StrEnum): v2_0 = "v2.0" +class IngestionStatus(StrEnum): + initial_ingestion_in_progress = "initial-ingestion-in-progress" + ingestion_complete = "ingestion-complete" + ingestion_failed = "ingestion-failed" + + +class InitializationType(StrEnum): + default = "default" + provisioned_rate = "provisioned-rate" + volume_copy = "volume-copy" + + class InstanceAttributeName(StrEnum): instanceType = "instanceType" kernel = "kernel" @@ -1340,6 +1539,7 @@ class InstanceLifecycleType(StrEnum): spot = "spot" scheduled = "scheduled" capacity_block = "capacity-block" + interruptible_capacity_reservation = "interruptible-capacity-reservation" class InstanceMatchCriteria(StrEnum): @@ -2332,6 +2532,273 @@ class InstanceType(StrEnum): r8gd_48xlarge = "r8gd.48xlarge" r8gd_metal_24xl = "r8gd.metal-24xl" r8gd_metal_48xl = "r8gd.metal-48xl" + c8gn_medium = "c8gn.medium" + c8gn_large = "c8gn.large" + c8gn_xlarge = "c8gn.xlarge" + c8gn_2xlarge = "c8gn.2xlarge" + c8gn_4xlarge = "c8gn.4xlarge" + c8gn_8xlarge = "c8gn.8xlarge" + c8gn_12xlarge = "c8gn.12xlarge" + c8gn_16xlarge = "c8gn.16xlarge" + c8gn_24xlarge = "c8gn.24xlarge" + c8gn_48xlarge = "c8gn.48xlarge" + c8gn_metal_24xl = "c8gn.metal-24xl" + c8gn_metal_48xl = "c8gn.metal-48xl" + f2_6xlarge = "f2.6xlarge" + p6e_gb200_36xlarge = "p6e-gb200.36xlarge" + g6f_large = "g6f.large" + g6f_xlarge = "g6f.xlarge" + g6f_2xlarge = "g6f.2xlarge" + g6f_4xlarge = "g6f.4xlarge" + gr6f_4xlarge = "gr6f.4xlarge" + p5_4xlarge = "p5.4xlarge" + r8i_large = "r8i.large" + r8i_xlarge = "r8i.xlarge" + r8i_2xlarge = "r8i.2xlarge" + r8i_4xlarge = "r8i.4xlarge" + r8i_8xlarge = "r8i.8xlarge" + r8i_12xlarge = "r8i.12xlarge" + r8i_16xlarge = "r8i.16xlarge" + r8i_24xlarge = "r8i.24xlarge" + r8i_32xlarge = "r8i.32xlarge" + r8i_48xlarge = "r8i.48xlarge" + r8i_96xlarge = "r8i.96xlarge" + r8i_metal_48xl = "r8i.metal-48xl" + r8i_metal_96xl = "r8i.metal-96xl" + r8i_flex_large = "r8i-flex.large" + r8i_flex_xlarge = "r8i-flex.xlarge" + r8i_flex_2xlarge = "r8i-flex.2xlarge" + r8i_flex_4xlarge = "r8i-flex.4xlarge" + r8i_flex_8xlarge = "r8i-flex.8xlarge" + r8i_flex_12xlarge = "r8i-flex.12xlarge" + r8i_flex_16xlarge = "r8i-flex.16xlarge" + m8i_large = "m8i.large" + m8i_xlarge = "m8i.xlarge" + m8i_2xlarge = "m8i.2xlarge" + m8i_4xlarge = "m8i.4xlarge" + m8i_8xlarge = "m8i.8xlarge" + m8i_12xlarge = "m8i.12xlarge" + m8i_16xlarge = "m8i.16xlarge" + m8i_24xlarge = "m8i.24xlarge" + m8i_32xlarge = "m8i.32xlarge" + m8i_48xlarge = "m8i.48xlarge" + m8i_96xlarge = "m8i.96xlarge" + m8i_metal_48xl = "m8i.metal-48xl" + m8i_metal_96xl = "m8i.metal-96xl" + m8i_flex_large = "m8i-flex.large" + m8i_flex_xlarge = "m8i-flex.xlarge" + m8i_flex_2xlarge = "m8i-flex.2xlarge" + m8i_flex_4xlarge = "m8i-flex.4xlarge" + m8i_flex_8xlarge = "m8i-flex.8xlarge" + m8i_flex_12xlarge = "m8i-flex.12xlarge" + m8i_flex_16xlarge = "m8i-flex.16xlarge" + i8ge_large = "i8ge.large" + i8ge_xlarge = "i8ge.xlarge" + i8ge_2xlarge = "i8ge.2xlarge" + i8ge_3xlarge = "i8ge.3xlarge" + i8ge_6xlarge = "i8ge.6xlarge" + i8ge_12xlarge = "i8ge.12xlarge" + i8ge_18xlarge = "i8ge.18xlarge" + i8ge_24xlarge = "i8ge.24xlarge" + i8ge_48xlarge = "i8ge.48xlarge" + i8ge_metal_24xl = "i8ge.metal-24xl" + i8ge_metal_48xl = "i8ge.metal-48xl" + mac_m4_metal = "mac-m4.metal" + mac_m4pro_metal = "mac-m4pro.metal" + r8gn_medium = "r8gn.medium" + r8gn_large = "r8gn.large" + r8gn_xlarge = "r8gn.xlarge" + r8gn_2xlarge = "r8gn.2xlarge" + r8gn_4xlarge = "r8gn.4xlarge" + r8gn_8xlarge = "r8gn.8xlarge" + r8gn_12xlarge = "r8gn.12xlarge" + r8gn_16xlarge = "r8gn.16xlarge" + r8gn_24xlarge = "r8gn.24xlarge" + r8gn_48xlarge = "r8gn.48xlarge" + r8gn_metal_24xl = "r8gn.metal-24xl" + r8gn_metal_48xl = "r8gn.metal-48xl" + c8i_large = "c8i.large" + c8i_xlarge = "c8i.xlarge" + c8i_2xlarge = "c8i.2xlarge" + c8i_4xlarge = "c8i.4xlarge" + c8i_8xlarge = "c8i.8xlarge" + c8i_12xlarge = "c8i.12xlarge" + c8i_16xlarge = "c8i.16xlarge" + c8i_24xlarge = "c8i.24xlarge" + c8i_32xlarge = "c8i.32xlarge" + c8i_48xlarge = "c8i.48xlarge" + c8i_96xlarge = "c8i.96xlarge" + c8i_metal_48xl = "c8i.metal-48xl" + c8i_metal_96xl = "c8i.metal-96xl" + c8i_flex_large = "c8i-flex.large" + c8i_flex_xlarge = "c8i-flex.xlarge" + c8i_flex_2xlarge = "c8i-flex.2xlarge" + c8i_flex_4xlarge = "c8i-flex.4xlarge" + c8i_flex_8xlarge = "c8i-flex.8xlarge" + c8i_flex_12xlarge = "c8i-flex.12xlarge" + c8i_flex_16xlarge = "c8i-flex.16xlarge" + r8gb_medium = "r8gb.medium" + r8gb_large = "r8gb.large" + r8gb_xlarge = "r8gb.xlarge" + r8gb_2xlarge = "r8gb.2xlarge" + r8gb_4xlarge = "r8gb.4xlarge" + r8gb_8xlarge = "r8gb.8xlarge" + r8gb_12xlarge = "r8gb.12xlarge" + r8gb_16xlarge = "r8gb.16xlarge" + r8gb_24xlarge = "r8gb.24xlarge" + r8gb_metal_24xl = "r8gb.metal-24xl" + m8a_medium = "m8a.medium" + m8a_large = "m8a.large" + m8a_xlarge = "m8a.xlarge" + m8a_2xlarge = "m8a.2xlarge" + m8a_4xlarge = "m8a.4xlarge" + m8a_8xlarge = "m8a.8xlarge" + m8a_12xlarge = "m8a.12xlarge" + m8a_16xlarge = "m8a.16xlarge" + m8a_24xlarge = "m8a.24xlarge" + m8a_48xlarge = "m8a.48xlarge" + m8a_metal_24xl = "m8a.metal-24xl" + m8a_metal_48xl = "m8a.metal-48xl" + trn2_3xlarge = "trn2.3xlarge" + r8a_medium = "r8a.medium" + r8a_large = "r8a.large" + r8a_xlarge = "r8a.xlarge" + r8a_2xlarge = "r8a.2xlarge" + r8a_4xlarge = "r8a.4xlarge" + r8a_8xlarge = "r8a.8xlarge" + r8a_12xlarge = "r8a.12xlarge" + r8a_16xlarge = "r8a.16xlarge" + r8a_24xlarge = "r8a.24xlarge" + r8a_48xlarge = "r8a.48xlarge" + r8a_metal_24xl = "r8a.metal-24xl" + r8a_metal_48xl = "r8a.metal-48xl" + p6_b300_48xlarge = "p6-b300.48xlarge" + c8a_medium = "c8a.medium" + c8a_large = "c8a.large" + c8a_xlarge = "c8a.xlarge" + c8a_2xlarge = "c8a.2xlarge" + c8a_4xlarge = "c8a.4xlarge" + c8a_8xlarge = "c8a.8xlarge" + c8a_12xlarge = "c8a.12xlarge" + c8a_16xlarge = "c8a.16xlarge" + c8a_24xlarge = "c8a.24xlarge" + c8a_48xlarge = "c8a.48xlarge" + c8a_metal_24xl = "c8a.metal-24xl" + c8a_metal_48xl = "c8a.metal-48xl" + c8gb_12xlarge = "c8gb.12xlarge" + c8gb_16xlarge = "c8gb.16xlarge" + c8gb_24xlarge = "c8gb.24xlarge" + c8gb_2xlarge = "c8gb.2xlarge" + c8gb_4xlarge = "c8gb.4xlarge" + c8gb_8xlarge = "c8gb.8xlarge" + c8gb_large = "c8gb.large" + c8gb_medium = "c8gb.medium" + c8gb_metal_24xl = "c8gb.metal-24xl" + c8gb_xlarge = "c8gb.xlarge" + c8gb_48xlarge = "c8gb.48xlarge" + c8gb_metal_48xl = "c8gb.metal-48xl" + m8gb_12xlarge = "m8gb.12xlarge" + m8gb_16xlarge = "m8gb.16xlarge" + m8gb_24xlarge = "m8gb.24xlarge" + m8gb_2xlarge = "m8gb.2xlarge" + m8gb_4xlarge = "m8gb.4xlarge" + m8gb_8xlarge = "m8gb.8xlarge" + m8gb_large = "m8gb.large" + m8gb_medium = "m8gb.medium" + m8gb_xlarge = "m8gb.xlarge" + m8gb_48xlarge = "m8gb.48xlarge" + m8gb_metal_24xl = "m8gb.metal-24xl" + m8gb_metal_48xl = "m8gb.metal-48xl" + m8gn_12xlarge = "m8gn.12xlarge" + m8gn_16xlarge = "m8gn.16xlarge" + m8gn_24xlarge = "m8gn.24xlarge" + m8gn_2xlarge = "m8gn.2xlarge" + m8gn_48xlarge = "m8gn.48xlarge" + m8gn_4xlarge = "m8gn.4xlarge" + m8gn_8xlarge = "m8gn.8xlarge" + m8gn_large = "m8gn.large" + m8gn_medium = "m8gn.medium" + m8gn_xlarge = "m8gn.xlarge" + m8gn_metal_24xl = "m8gn.metal-24xl" + m8gn_metal_48xl = "m8gn.metal-48xl" + x8aedz_12xlarge = "x8aedz.12xlarge" + x8aedz_24xlarge = "x8aedz.24xlarge" + x8aedz_3xlarge = "x8aedz.3xlarge" + x8aedz_6xlarge = "x8aedz.6xlarge" + x8aedz_large = "x8aedz.large" + x8aedz_metal_12xl = "x8aedz.metal-12xl" + x8aedz_metal_24xl = "x8aedz.metal-24xl" + x8aedz_xlarge = "x8aedz.xlarge" + m8azn_medium = "m8azn.medium" + m8azn_large = "m8azn.large" + m8azn_xlarge = "m8azn.xlarge" + m8azn_3xlarge = "m8azn.3xlarge" + m8azn_6xlarge = "m8azn.6xlarge" + m8azn_12xlarge = "m8azn.12xlarge" + m8azn_24xlarge = "m8azn.24xlarge" + m8azn_metal_12xl = "m8azn.metal-12xl" + m8azn_metal_24xl = "m8azn.metal-24xl" + x8i_large = "x8i.large" + x8i_xlarge = "x8i.xlarge" + x8i_2xlarge = "x8i.2xlarge" + x8i_4xlarge = "x8i.4xlarge" + x8i_8xlarge = "x8i.8xlarge" + x8i_12xlarge = "x8i.12xlarge" + x8i_16xlarge = "x8i.16xlarge" + x8i_24xlarge = "x8i.24xlarge" + x8i_32xlarge = "x8i.32xlarge" + x8i_48xlarge = "x8i.48xlarge" + x8i_64xlarge = "x8i.64xlarge" + x8i_96xlarge = "x8i.96xlarge" + x8i_metal_48xl = "x8i.metal-48xl" + x8i_metal_96xl = "x8i.metal-96xl" + mac_m4max_metal = "mac-m4max.metal" + g7e_2xlarge = "g7e.2xlarge" + g7e_4xlarge = "g7e.4xlarge" + g7e_8xlarge = "g7e.8xlarge" + g7e_12xlarge = "g7e.12xlarge" + g7e_24xlarge = "g7e.24xlarge" + g7e_48xlarge = "g7e.48xlarge" + r8id_large = "r8id.large" + r8id_xlarge = "r8id.xlarge" + r8id_2xlarge = "r8id.2xlarge" + r8id_4xlarge = "r8id.4xlarge" + r8id_8xlarge = "r8id.8xlarge" + r8id_12xlarge = "r8id.12xlarge" + r8id_16xlarge = "r8id.16xlarge" + r8id_24xlarge = "r8id.24xlarge" + r8id_32xlarge = "r8id.32xlarge" + r8id_48xlarge = "r8id.48xlarge" + r8id_96xlarge = "r8id.96xlarge" + r8id_metal_48xl = "r8id.metal-48xl" + r8id_metal_96xl = "r8id.metal-96xl" + c8id_large = "c8id.large" + c8id_xlarge = "c8id.xlarge" + c8id_2xlarge = "c8id.2xlarge" + c8id_4xlarge = "c8id.4xlarge" + c8id_8xlarge = "c8id.8xlarge" + c8id_12xlarge = "c8id.12xlarge" + c8id_16xlarge = "c8id.16xlarge" + c8id_24xlarge = "c8id.24xlarge" + c8id_32xlarge = "c8id.32xlarge" + c8id_48xlarge = "c8id.48xlarge" + c8id_96xlarge = "c8id.96xlarge" + c8id_metal_48xl = "c8id.metal-48xl" + c8id_metal_96xl = "c8id.metal-96xl" + m8id_large = "m8id.large" + m8id_xlarge = "m8id.xlarge" + m8id_2xlarge = "m8id.2xlarge" + m8id_4xlarge = "m8id.4xlarge" + m8id_8xlarge = "m8id.8xlarge" + m8id_12xlarge = "m8id.12xlarge" + m8id_16xlarge = "m8id.16xlarge" + m8id_24xlarge = "m8id.24xlarge" + m8id_32xlarge = "m8id.32xlarge" + m8id_48xlarge = "m8id.48xlarge" + m8id_96xlarge = "m8id.96xlarge" + m8id_metal_48xl = "m8id.metal-48xl" + m8id_metal_96xl = "m8id.metal-96xl" + hpc8a_96xlarge = "hpc8a.96xlarge" class InstanceTypeHypervisor(StrEnum): @@ -2360,6 +2827,19 @@ class InternetGatewayExclusionMode(StrEnum): allow_egress = "allow-egress" +class InterruptibleCapacityReservationAllocationStatus(StrEnum): + pending = "pending" + active = "active" + updating = "updating" + canceling = "canceling" + canceled = "canceled" + failed = "failed" + + +class InterruptionType(StrEnum): + adhoc = "adhoc" + + class IpAddressType(StrEnum): ipv4 = "ipv4" dualstack = "dualstack" @@ -2429,6 +2909,33 @@ class IpamOverlapStatus(StrEnum): ignored = "ignored" +class IpamPolicyManagedBy(StrEnum): + account = "account" + delegated_administrator_for_ipam = "delegated-administrator-for-ipam" + + +class IpamPolicyResourceType(StrEnum): + alb = "alb" + eip = "eip" + rds = "rds" + rnat = "rnat" + + +class IpamPolicyState(StrEnum): + create_in_progress = "create-in-progress" + create_complete = "create-complete" + create_failed = "create-failed" + modify_in_progress = "modify-in-progress" + modify_complete = "modify-complete" + modify_failed = "modify-failed" + delete_in_progress = "delete-in-progress" + delete_complete = "delete-complete" + delete_failed = "delete-failed" + isolate_in_progress = "isolate-in-progress" + isolate_complete = "isolate-complete" + restore_in_progress = "restore-in-progress" + + class IpamPoolAllocationResourceType(StrEnum): ipam_pool = "ipam-pool" vpc = "vpc" @@ -2436,10 +2943,12 @@ class IpamPoolAllocationResourceType(StrEnum): custom = "custom" subnet = "subnet" eip = "eip" + anycast_ip_list = "anycast-ip-list" class IpamPoolAwsService(StrEnum): ec2 = "ec2" + global_services = "global-services" class IpamPoolCidrFailureCode(StrEnum): @@ -2482,6 +2991,57 @@ class IpamPoolState(StrEnum): restore_in_progress = "restore-in-progress" +class IpamPrefixListResolverRuleConditionOperation(StrEnum): + equals = "equals" + not_equals = "not-equals" + subnet_of = "subnet-of" + + +class IpamPrefixListResolverRuleType(StrEnum): + static_cidr = "static-cidr" + ipam_resource_cidr = "ipam-resource-cidr" + ipam_pool_cidr = "ipam-pool-cidr" + + +class IpamPrefixListResolverState(StrEnum): + create_in_progress = "create-in-progress" + create_complete = "create-complete" + create_failed = "create-failed" + modify_in_progress = "modify-in-progress" + modify_complete = "modify-complete" + modify_failed = "modify-failed" + delete_in_progress = "delete-in-progress" + delete_complete = "delete-complete" + delete_failed = "delete-failed" + isolate_in_progress = "isolate-in-progress" + isolate_complete = "isolate-complete" + restore_in_progress = "restore-in-progress" + + +class IpamPrefixListResolverTargetState(StrEnum): + create_in_progress = "create-in-progress" + create_complete = "create-complete" + create_failed = "create-failed" + modify_in_progress = "modify-in-progress" + modify_complete = "modify-complete" + modify_failed = "modify-failed" + sync_in_progress = "sync-in-progress" + sync_complete = "sync-complete" + sync_failed = "sync-failed" + delete_in_progress = "delete-in-progress" + delete_complete = "delete-complete" + delete_failed = "delete-failed" + isolate_in_progress = "isolate-in-progress" + isolate_complete = "isolate-complete" + restore_in_progress = "restore-in-progress" + + +class IpamPrefixListResolverVersionCreationStatus(StrEnum): + pending = "pending" + success = "success" + failure = "failure" + + class IpamPublicAddressAssociationStatus(StrEnum): associated = "associated" disassociated = "disassociated" @@ -2496,6 +3056,7 @@ class IpamPublicAddressAwsService(StrEnum): site_to_site_vpn = "site-to-site-vpn" load_balancer = "load-balancer" global_accelerator = "global-accelerator" + cloudfront = "cloudfront" other = "other" @@ -2506,6 +3067,7 @@ class IpamPublicAddressType(StrEnum): amazon_owned_contig = "amazon-owned-contig" byoip = "byoip" ec2_public_ip = "ec2-public-ip" + anycast_ip_list_ip = "anycast-ip-list-ip" class IpamResourceCidrIpSource(StrEnum): @@ -2548,6 +3110,11 @@ class IpamResourceType(StrEnum): public_ipv4_pool = "public-ipv4-pool" ipv6_pool = "ipv6-pool" eni = "eni" + anycast_ip_list = "anycast-ip-list" + + +class IpamScopeExternalAuthorityType(StrEnum): + infoblox = "infoblox" class IpamScopeState(StrEnum): @@ -2757,6 +3324,7 @@ class ManagedBy(StrEnum): class MarketType(StrEnum): spot = "spot" capacity_block = "capacity-block" + interruptible_capacity_reservation = "interruptible-capacity-reservation" class MembershipType(StrEnum): @@ -2770,6 +3338,58 @@ class MetadataDefaultHttpTokensState(StrEnum): no_preference = "no-preference" +class Metric(StrEnum): + reservation_total_capacity_hrs_vcpu = "reservation-total-capacity-hrs-vcpu" + reservation_total_capacity_hrs_inst = "reservation-total-capacity-hrs-inst" + reservation_max_size_vcpu = "reservation-max-size-vcpu" + reservation_max_size_inst = "reservation-max-size-inst" + reservation_min_size_vcpu = "reservation-min-size-vcpu" + reservation_min_size_inst = "reservation-min-size-inst" + reservation_unused_total_capacity_hrs_vcpu = "reservation-unused-total-capacity-hrs-vcpu" + reservation_unused_total_capacity_hrs_inst = "reservation-unused-total-capacity-hrs-inst" + reservation_unused_total_estimated_cost = "reservation-unused-total-estimated-cost" + reservation_max_unused_size_vcpu = "reservation-max-unused-size-vcpu" + reservation_max_unused_size_inst = "reservation-max-unused-size-inst" + reservation_min_unused_size_vcpu = "reservation-min-unused-size-vcpu" + reservation_min_unused_size_inst = "reservation-min-unused-size-inst" + reservation_max_utilization = "reservation-max-utilization" + reservation_min_utilization = "reservation-min-utilization" + reservation_avg_utilization_vcpu = "reservation-avg-utilization-vcpu" + reservation_avg_utilization_inst = "reservation-avg-utilization-inst" + reservation_total_count = "reservation-total-count" + reservation_total_estimated_cost = "reservation-total-estimated-cost" + reservation_avg_future_size_vcpu = "reservation-avg-future-size-vcpu" + reservation_avg_future_size_inst = "reservation-avg-future-size-inst" + reservation_min_future_size_vcpu = "reservation-min-future-size-vcpu" + reservation_min_future_size_inst = "reservation-min-future-size-inst" + reservation_max_future_size_vcpu = "reservation-max-future-size-vcpu" + reservation_max_future_size_inst = "reservation-max-future-size-inst" + reservation_avg_committed_size_vcpu = "reservation-avg-committed-size-vcpu" + reservation_avg_committed_size_inst = "reservation-avg-committed-size-inst" + reservation_max_committed_size_vcpu = "reservation-max-committed-size-vcpu" + reservation_max_committed_size_inst = "reservation-max-committed-size-inst" + reservation_min_committed_size_vcpu = "reservation-min-committed-size-vcpu" + reservation_min_committed_size_inst = "reservation-min-committed-size-inst" + reserved_total_usage_hrs_vcpu = "reserved-total-usage-hrs-vcpu" + reserved_total_usage_hrs_inst = "reserved-total-usage-hrs-inst" + reserved_total_estimated_cost = "reserved-total-estimated-cost" + unreserved_total_usage_hrs_vcpu = "unreserved-total-usage-hrs-vcpu" + unreserved_total_usage_hrs_inst = "unreserved-total-usage-hrs-inst" + unreserved_total_estimated_cost = "unreserved-total-estimated-cost" + spot_total_usage_hrs_vcpu = "spot-total-usage-hrs-vcpu" + spot_total_usage_hrs_inst = "spot-total-usage-hrs-inst" + spot_total_estimated_cost = "spot-total-estimated-cost" + spot_avg_run_time_before_interruption_inst = "spot-avg-run-time-before-interruption-inst" + spot_max_run_time_before_interruption_inst = "spot-max-run-time-before-interruption-inst" + spot_min_run_time_before_interruption_inst = "spot-min-run-time-before-interruption-inst" + spot_total_interruptions_inst = "spot-total-interruptions-inst" + spot_total_interruptions_vcpu = "spot-total-interruptions-vcpu" + spot_total_count_inst = "spot-total-count-inst" + spot_total_count_vcpu = "spot-total-count-vcpu" + spot_interruption_rate_inst = "spot-interruption-rate-inst" + spot_interruption_rate_vcpu = "spot-interruption-rate-vcpu" + + class MetricType(StrEnum): aggregate_latency = "aggregate-latency" @@ -2805,6 +3425,25 @@ class NatGatewayAddressStatus(StrEnum): failed = "failed" +class NatGatewayApplianceModifyState(StrEnum): + modifying = "modifying" + completed = "completed" + failed = "failed" + + +class NatGatewayApplianceState(StrEnum): + attaching = "attaching" + attached = "attached" + detaching = "detaching" + detached = "detached" + attach_failed = "attach-failed" + detach_failed = "detach-failed" + + +class NatGatewayApplianceType(StrEnum): + network_firewall_proxy = "network-firewall-proxy" + + class NatGatewayState(StrEnum): pending = "pending" failed = "failed" @@ -2813,6 +3452,11 @@ class NatGatewayState(StrEnum): deleted = "deleted" +class NestedVirtualizationSpecification(StrEnum): + enabled = "enabled" + disabled = "disabled" + + class NetworkInterfaceAttribute(StrEnum): description = "description" groupSet = "groupSet" @@ -2898,6 +3542,11 @@ class OperationType(StrEnum): remove = "remove" +class OutputFormat(StrEnum): + csv = "csv" + parquet = "parquet" + + class PartitionLoadFrequency(StrEnum): none = "none" daily = "daily" @@ -3054,13 +3703,30 @@ class ReportStatusType(StrEnum): impaired = "impaired" +class ReservationEndDateType(StrEnum): + limited = "limited" + unlimited = "unlimited" + + class ReservationState(StrEnum): + active = "active" + expired = "expired" + cancelled = "cancelled" + scheduled = "scheduled" + pending = "pending" + failed = "failed" + delayed = "delayed" + unsupported = "unsupported" payment_pending = "payment-pending" payment_failed = "payment-failed" - active = "active" retired = "retired" +class ReservationType(StrEnum): + capacity_block = "capacity-block" + odcr = "odcr" + + class ReservedInstanceState(StrEnum): payment_pending = "payment-pending" active = "active" @@ -3096,6 +3762,7 @@ class ResourceType(StrEnum): fpga_image = "fpga-image" host_reservation = "host-reservation" image = "image" + image_usage_report = "image-usage-report" import_image_task = "import-image-task" import_snapshot_task = "import-snapshot-task" instance = "instance" @@ -3145,6 +3812,7 @@ class ResourceType(StrEnum): transit_gateway_connect_peer = "transit-gateway-connect-peer" transit_gateway_multicast_domain = "transit-gateway-multicast-domain" transit_gateway_policy_table = "transit-gateway-policy-table" + transit_gateway_metering_policy = "transit-gateway-metering-policy" transit_gateway_route_table = "transit-gateway-route-table" transit_gateway_route_table_announcement = "transit-gateway-route-table-announcement" volume = "volume" @@ -3167,6 +3835,7 @@ class ResourceType(StrEnum): verified_access_trust_provider = "verified-access-trust-provider" vpn_connection_device_type = "vpn-connection-device-type" vpc_block_public_access_exclusion = "vpc-block-public-access-exclusion" + vpc_encryption_control = "vpc-encryption-control" route_server = "route-server" route_server_endpoint = "route-server-endpoint" route_server_peer = "route-server-peer" @@ -3177,6 +3846,14 @@ class ResourceType(StrEnum): ipam_external_resource_verification_token = "ipam-external-resource-verification-token" capacity_block = "capacity-block" mac_modification_task = "mac-modification-task" + ipam_prefix_list_resolver = "ipam-prefix-list-resolver" + ipam_policy = "ipam-policy" + ipam_prefix_list_resolver_target = "ipam-prefix-list-resolver-target" + secondary_interface = "secondary-interface" + secondary_network = "secondary-network" + secondary_subnet = "secondary-subnet" + capacity_manager_data_export = "capacity-manager-data-export" + vpn_concentrator = "vpn-concentrator" class RootDeviceType(StrEnum): @@ -3188,6 +3865,7 @@ class RouteOrigin(StrEnum): CreateRouteTable = "CreateRouteTable" CreateRoute = "CreateRoute" EnableVgwRoutePropagation = "EnableVgwRoutePropagation" + Advertisement = "Advertisement" class RouteServerAssociationState(StrEnum): @@ -3272,6 +3950,7 @@ class RouteServerState(StrEnum): class RouteState(StrEnum): active = "active" blackhole = "blackhole" + filtered = "filtered" class RouteTableAssociationStateCode(StrEnum): @@ -3293,6 +3972,59 @@ class SSEType(StrEnum): none = "none" +class Schedule(StrEnum): + hourly = "hourly" + + +class SecondaryInterfaceStatus(StrEnum): + available = "available" + in_use = "in-use" + + +class SecondaryInterfaceType(StrEnum): + secondary = "secondary" + + +class SecondaryNetworkCidrBlockAssociationState(StrEnum): + associating = "associating" + associated = "associated" + association_failed = "association-failed" + disassociating = "disassociating" + disassociated = "disassociated" + disassociation_failed = "disassociation-failed" + + +class SecondaryNetworkState(StrEnum): + create_in_progress = "create-in-progress" + create_complete = "create-complete" + create_failed = "create-failed" + delete_in_progress = "delete-in-progress" + delete_complete = "delete-complete" + delete_failed = "delete-failed" + + +class SecondaryNetworkType(StrEnum): + rdma = "rdma" + + +class SecondarySubnetCidrBlockAssociationState(StrEnum): + associating = "associating" + associated = "associated" + association_failed = "association-failed" + disassociating = "disassociating" + disassociated = "disassociated" + disassociation_failed = "disassociation-failed" + + +class SecondarySubnetState(StrEnum): + create_in_progress = "create-in-progress" + create_complete = "create-complete" + create_failed = "create-failed" + delete_in_progress = "delete-in-progress" + delete_complete = "delete-complete" + delete_failed = "delete-failed" + + class SecurityGroupReferencingSupportValue(StrEnum): enable = "enable" disable = "disable" @@ -3328,6 +4060,7 @@ class ServiceManaged(StrEnum): alb = "alb" nlb = "nlb" rnat = "rnat" + rds = "rds" class ServiceState(StrEnum): @@ -3414,6 +4147,11 @@ class SpreadLevel(StrEnum): rack = "rack" +class SqlServerLicenseUsage(StrEnum): + full = "full" + waived = "waived" + + class State(StrEnum): PendingAcceptance = "PendingAcceptance" Pending = "Pending" @@ -3489,6 +4227,7 @@ class SummaryStatus(StrEnum): class SupportedAdditionalProcessorFeature(StrEnum): amd_sev_snp = "amd-sev-snp" + nested_virtualization = "nested-virtualization" class TargetCapacityUnitType(StrEnum): @@ -3538,6 +4277,12 @@ class TrafficDirection(StrEnum): egress = "egress" +class TrafficIpAddressType(StrEnum): + ipv4 = "ipv4" + ipv6 = "ipv6" + dual_stack = "dual-stack" + + class TrafficMirrorFilterRuleField(StrEnum): destination_port_range = "destination-port-range" source_port_range = "source-port-range" @@ -3587,10 +4332,12 @@ class TransitGatewayAssociationState(StrEnum): class TransitGatewayAttachmentResourceType(StrEnum): vpc = "vpc" vpn = "vpn" + vpn_concentrator = "vpn-concentrator" direct_connect_gateway = "direct-connect-gateway" connect = "connect" peering = "peering" tgw_peering = "tgw-peering" + network_function = "network-function" class TransitGatewayAttachmentState(StrEnum): @@ -3616,6 +4363,25 @@ class TransitGatewayConnectPeerState(StrEnum): deleted = "deleted" +class TransitGatewayMeteringPayerType(StrEnum): + source_attachment_owner = "source-attachment-owner" + destination_attachment_owner = "destination-attachment-owner" + transit_gateway_owner = "transit-gateway-owner" + + +class TransitGatewayMeteringPolicyEntryState(StrEnum): + available = "available" + deleted = "deleted" + + +class TransitGatewayMeteringPolicyState(StrEnum): + available = "available" + deleted = "deleted" + pending = "pending" + modifying = "modifying" + deleting = "deleting" + + class TransitGatewayMulitcastDomainAssociationState(StrEnum): pendingAcceptance = "pendingAcceptance" associating = "associating" @@ -3809,11 +4575,13 @@ class VolumeStatusInfoStatus(StrEnum): ok = "ok" impaired = "impaired" insufficient_data = "insufficient-data" + warning = "warning" class VolumeStatusName(StrEnum): io_enabled = "io-enabled" io_performance = "io-performance" + initialization_state = "initialization-state" class VolumeType(StrEnum): @@ -3872,6 +4640,11 @@ class VpcEncryptionControlExclusionState(StrEnum): disabled = "disabled" +class VpcEncryptionControlExclusionStateInput(StrEnum): + enable = "enable" + disable = "disable" + + class VpcEncryptionControlMode(StrEnum): monitor = "monitor" enforce = "enforce" @@ -3918,6 +4691,10 @@ class VpcTenancy(StrEnum): default = "default" +class VpnConcentratorType(StrEnum): + ipsec_1 = "ipsec.1" + + class VpnEcmpSupportValue(StrEnum): enable = "enable" disable = "disable" @@ -3938,6 +4715,11 @@ class VpnStaticRouteSource(StrEnum): Static = "Static" +class VpnTunnelBandwidth(StrEnum): + standard = "standard" + large = "large" + + class VpnTunnelProvisioningStatus(StrEnum): available = "available" pending = "pending" @@ -3960,851 +4742,852 @@ class scope(StrEnum): class AcceleratorCount(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None class AcceleratorCountRequest(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None -AcceleratorManufacturerSet = List[AcceleratorManufacturer] -AcceleratorNameSet = List[AcceleratorName] +AcceleratorManufacturerSet = list[AcceleratorManufacturer] +AcceleratorNameSet = list[AcceleratorName] class AcceleratorTotalMemoryMiB(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None class AcceleratorTotalMemoryMiBRequest(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None -AcceleratorTypeSet = List[AcceleratorType] +AcceleratorTypeSet = list[AcceleratorType] class Tag(TypedDict, total=False): - Key: Optional[String] - Value: Optional[String] + Key: String | None + Value: String | None -TagList = List[Tag] +TagList = list[Tag] class TagSpecification(TypedDict, total=False): - ResourceType: Optional[ResourceType] - Tags: Optional[TagList] + ResourceType: ResourceType | None + Tags: TagList | None -TagSpecificationList = List[TagSpecification] +TagSpecificationList = list[TagSpecification] class AcceptAddressTransferRequest(ServiceRequest): Address: String - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None MillisecondDateTime = datetime class AddressTransfer(TypedDict, total=False): - PublicIp: Optional[String] - AllocationId: Optional[String] - TransferAccountId: Optional[String] - TransferOfferExpirationTimestamp: Optional[MillisecondDateTime] - TransferOfferAcceptedTimestamp: Optional[MillisecondDateTime] - AddressTransferStatus: Optional[AddressTransferStatus] + PublicIp: String | None + AllocationId: String | None + TransferAccountId: String | None + TransferOfferExpirationTimestamp: MillisecondDateTime | None + TransferOfferAcceptedTimestamp: MillisecondDateTime | None + AddressTransferStatus: AddressTransferStatus | None class AcceptAddressTransferResult(TypedDict, total=False): - AddressTransfer: Optional[AddressTransfer] + AddressTransfer: AddressTransfer | None class AcceptCapacityReservationBillingOwnershipRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None CapacityReservationId: CapacityReservationId class AcceptCapacityReservationBillingOwnershipResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class TargetConfigurationRequest(TypedDict, total=False): - InstanceCount: Optional[Integer] + InstanceCount: Integer | None OfferingId: ReservedInstancesOfferingId -TargetConfigurationRequestSet = List[TargetConfigurationRequest] -ReservedInstanceIdSet = List[ReservationId] +TargetConfigurationRequestSet = list[TargetConfigurationRequest] +ReservedInstanceIdSet = list[ReservationId] class AcceptReservedInstancesExchangeQuoteRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ReservedInstanceIds: ReservedInstanceIdSet - TargetConfigurations: Optional[TargetConfigurationRequestSet] + TargetConfigurations: TargetConfigurationRequestSet | None class AcceptReservedInstancesExchangeQuoteResult(TypedDict, total=False): - ExchangeId: Optional[String] + ExchangeId: String | None -ValueStringList = List[String] +ValueStringList = list[String] class AcceptTransitGatewayMulticastDomainAssociationsRequest(ServiceRequest): - TransitGatewayMulticastDomainId: Optional[TransitGatewayMulticastDomainId] - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - SubnetIds: Optional[ValueStringList] - DryRun: Optional[Boolean] + TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId | None + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + SubnetIds: ValueStringList | None + DryRun: Boolean | None class SubnetAssociation(TypedDict, total=False): - SubnetId: Optional[String] - State: Optional[TransitGatewayMulitcastDomainAssociationState] + SubnetId: String | None + State: TransitGatewayMulitcastDomainAssociationState | None -SubnetAssociationList = List[SubnetAssociation] +SubnetAssociationList = list[SubnetAssociation] class TransitGatewayMulticastDomainAssociations(TypedDict, total=False): - TransitGatewayMulticastDomainId: Optional[String] - TransitGatewayAttachmentId: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - ResourceOwnerId: Optional[String] - Subnets: Optional[SubnetAssociationList] + TransitGatewayMulticastDomainId: String | None + TransitGatewayAttachmentId: String | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + ResourceOwnerId: String | None + Subnets: SubnetAssociationList | None class AcceptTransitGatewayMulticastDomainAssociationsResult(TypedDict, total=False): - Associations: Optional[TransitGatewayMulticastDomainAssociations] + Associations: TransitGatewayMulticastDomainAssociations | None class AcceptTransitGatewayPeeringAttachmentRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None DateTime = datetime class PeeringAttachmentStatus(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None class TransitGatewayPeeringAttachmentOptions(TypedDict, total=False): - DynamicRouting: Optional[DynamicRoutingValue] + DynamicRouting: DynamicRoutingValue | None class PeeringTgwInfo(TypedDict, total=False): - TransitGatewayId: Optional[String] - CoreNetworkId: Optional[String] - OwnerId: Optional[String] - Region: Optional[String] + TransitGatewayId: String | None + CoreNetworkId: String | None + OwnerId: String | None + Region: String | None class TransitGatewayPeeringAttachment(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[String] - AccepterTransitGatewayAttachmentId: Optional[String] - RequesterTgwInfo: Optional[PeeringTgwInfo] - AccepterTgwInfo: Optional[PeeringTgwInfo] - Options: Optional[TransitGatewayPeeringAttachmentOptions] - Status: Optional[PeeringAttachmentStatus] - State: Optional[TransitGatewayAttachmentState] - CreationTime: Optional[DateTime] - Tags: Optional[TagList] + TransitGatewayAttachmentId: String | None + AccepterTransitGatewayAttachmentId: String | None + RequesterTgwInfo: PeeringTgwInfo | None + AccepterTgwInfo: PeeringTgwInfo | None + Options: TransitGatewayPeeringAttachmentOptions | None + Status: PeeringAttachmentStatus | None + State: TransitGatewayAttachmentState | None + CreationTime: DateTime | None + Tags: TagList | None class AcceptTransitGatewayPeeringAttachmentResult(TypedDict, total=False): - TransitGatewayPeeringAttachment: Optional[TransitGatewayPeeringAttachment] + TransitGatewayPeeringAttachment: TransitGatewayPeeringAttachment | None class AcceptTransitGatewayVpcAttachmentRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class TransitGatewayVpcAttachmentOptions(TypedDict, total=False): - DnsSupport: Optional[DnsSupportValue] - SecurityGroupReferencingSupport: Optional[SecurityGroupReferencingSupportValue] - Ipv6Support: Optional[Ipv6SupportValue] - ApplianceModeSupport: Optional[ApplianceModeSupportValue] + DnsSupport: DnsSupportValue | None + SecurityGroupReferencingSupport: SecurityGroupReferencingSupportValue | None + Ipv6Support: Ipv6SupportValue | None + ApplianceModeSupport: ApplianceModeSupportValue | None class TransitGatewayVpcAttachment(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[String] - TransitGatewayId: Optional[String] - VpcId: Optional[String] - VpcOwnerId: Optional[String] - State: Optional[TransitGatewayAttachmentState] - SubnetIds: Optional[ValueStringList] - CreationTime: Optional[DateTime] - Options: Optional[TransitGatewayVpcAttachmentOptions] - Tags: Optional[TagList] + TransitGatewayAttachmentId: String | None + TransitGatewayId: String | None + VpcId: String | None + VpcOwnerId: String | None + State: TransitGatewayAttachmentState | None + SubnetIds: ValueStringList | None + CreationTime: DateTime | None + Options: TransitGatewayVpcAttachmentOptions | None + Tags: TagList | None class AcceptTransitGatewayVpcAttachmentResult(TypedDict, total=False): - TransitGatewayVpcAttachment: Optional[TransitGatewayVpcAttachment] + TransitGatewayVpcAttachment: TransitGatewayVpcAttachment | None -VpcEndpointIdList = List[VpcEndpointId] +VpcEndpointIdList = list[VpcEndpointId] class AcceptVpcEndpointConnectionsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceId: VpcEndpointServiceId VpcEndpointIds: VpcEndpointIdList class UnsuccessfulItemError(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None class UnsuccessfulItem(TypedDict, total=False): - Error: Optional[UnsuccessfulItemError] - ResourceId: Optional[String] + Error: UnsuccessfulItemError | None + ResourceId: String | None -UnsuccessfulItemSet = List[UnsuccessfulItem] +UnsuccessfulItemSet = list[UnsuccessfulItem] class AcceptVpcEndpointConnectionsResult(TypedDict, total=False): - Unsuccessful: Optional[UnsuccessfulItemSet] + Unsuccessful: UnsuccessfulItemSet | None class AcceptVpcPeeringConnectionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VpcPeeringConnectionId: VpcPeeringConnectionIdWithResolver class VpcPeeringConnectionStateReason(TypedDict, total=False): - Code: Optional[VpcPeeringConnectionStateReasonCode] - Message: Optional[String] + Code: VpcPeeringConnectionStateReasonCode | None + Message: String | None class VpcPeeringConnectionOptionsDescription(TypedDict, total=False): - AllowDnsResolutionFromRemoteVpc: Optional[Boolean] - AllowEgressFromLocalClassicLinkToRemoteVpc: Optional[Boolean] - AllowEgressFromLocalVpcToRemoteClassicLink: Optional[Boolean] + AllowDnsResolutionFromRemoteVpc: Boolean | None + AllowEgressFromLocalClassicLinkToRemoteVpc: Boolean | None + AllowEgressFromLocalVpcToRemoteClassicLink: Boolean | None class CidrBlock(TypedDict, total=False): - CidrBlock: Optional[String] + CidrBlock: String | None -CidrBlockSet = List[CidrBlock] +CidrBlockSet = list[CidrBlock] class Ipv6CidrBlock(TypedDict, total=False): - Ipv6CidrBlock: Optional[String] + Ipv6CidrBlock: String | None -Ipv6CidrBlockSet = List[Ipv6CidrBlock] +Ipv6CidrBlockSet = list[Ipv6CidrBlock] class VpcPeeringConnectionVpcInfo(TypedDict, total=False): - CidrBlock: Optional[String] - Ipv6CidrBlockSet: Optional[Ipv6CidrBlockSet] - CidrBlockSet: Optional[CidrBlockSet] - OwnerId: Optional[String] - PeeringOptions: Optional[VpcPeeringConnectionOptionsDescription] - VpcId: Optional[String] - Region: Optional[String] + CidrBlock: String | None + Ipv6CidrBlockSet: Ipv6CidrBlockSet | None + CidrBlockSet: CidrBlockSet | None + OwnerId: String | None + PeeringOptions: VpcPeeringConnectionOptionsDescription | None + VpcId: String | None + Region: String | None class VpcPeeringConnection(TypedDict, total=False): - AccepterVpcInfo: Optional[VpcPeeringConnectionVpcInfo] - ExpirationTime: Optional[DateTime] - RequesterVpcInfo: Optional[VpcPeeringConnectionVpcInfo] - Status: Optional[VpcPeeringConnectionStateReason] - Tags: Optional[TagList] - VpcPeeringConnectionId: Optional[String] + AccepterVpcInfo: VpcPeeringConnectionVpcInfo | None + ExpirationTime: DateTime | None + RequesterVpcInfo: VpcPeeringConnectionVpcInfo | None + Status: VpcPeeringConnectionStateReason | None + Tags: TagList | None + VpcPeeringConnectionId: String | None class AcceptVpcPeeringConnectionResult(TypedDict, total=False): - VpcPeeringConnection: Optional[VpcPeeringConnection] + VpcPeeringConnection: VpcPeeringConnection | None class PortRange(TypedDict, total=False): - From: Optional[Integer] - To: Optional[Integer] + From: Integer | None + To: Integer | None -PortRangeList = List[PortRange] +PortRangeList = list[PortRange] class FirewallStatefulRule(TypedDict, total=False): - RuleGroupArn: Optional[ResourceArn] - Sources: Optional[ValueStringList] - Destinations: Optional[ValueStringList] - SourcePorts: Optional[PortRangeList] - DestinationPorts: Optional[PortRangeList] - Protocol: Optional[String] - RuleAction: Optional[String] - Direction: Optional[String] + RuleGroupArn: ResourceArn | None + Sources: ValueStringList | None + Destinations: ValueStringList | None + SourcePorts: PortRangeList | None + DestinationPorts: PortRangeList | None + Protocol: String | None + RuleAction: String | None + Direction: String | None -ProtocolIntList = List[ProtocolInt] +ProtocolIntList = list[ProtocolInt] class FirewallStatelessRule(TypedDict, total=False): - RuleGroupArn: Optional[ResourceArn] - Sources: Optional[ValueStringList] - Destinations: Optional[ValueStringList] - SourcePorts: Optional[PortRangeList] - DestinationPorts: Optional[PortRangeList] - Protocols: Optional[ProtocolIntList] - RuleAction: Optional[String] - Priority: Optional[Priority] + RuleGroupArn: ResourceArn | None + Sources: ValueStringList | None + Destinations: ValueStringList | None + SourcePorts: PortRangeList | None + DestinationPorts: PortRangeList | None + Protocols: ProtocolIntList | None + RuleAction: String | None + Priority: Priority | None class AnalysisComponent(TypedDict, total=False): - Id: Optional[String] - Arn: Optional[String] - Name: Optional[String] + Id: String | None + Arn: String | None + Name: String | None class TransitGatewayRouteTableRoute(TypedDict, total=False): - DestinationCidr: Optional[String] - State: Optional[String] - RouteOrigin: Optional[String] - PrefixListId: Optional[String] - AttachmentId: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[String] + DestinationCidr: String | None + State: String | None + RouteOrigin: String | None + PrefixListId: String | None + AttachmentId: String | None + ResourceId: String | None + ResourceType: String | None -AnalysisComponentList = List[AnalysisComponent] +AnalysisComponentList = list[AnalysisComponent] class AnalysisSecurityGroupRule(TypedDict, total=False): - Cidr: Optional[String] - Direction: Optional[String] - SecurityGroupId: Optional[String] - PortRange: Optional[PortRange] - PrefixListId: Optional[String] - Protocol: Optional[String] + Cidr: String | None + Direction: String | None + SecurityGroupId: String | None + PortRange: PortRange | None + PrefixListId: String | None + Protocol: String | None class AnalysisRouteTableRoute(TypedDict, total=False): - DestinationCidr: Optional[String] - DestinationPrefixListId: Optional[String] - EgressOnlyInternetGatewayId: Optional[String] - GatewayId: Optional[String] - InstanceId: Optional[String] - NatGatewayId: Optional[String] - NetworkInterfaceId: Optional[String] - Origin: Optional[String] - TransitGatewayId: Optional[String] - VpcPeeringConnectionId: Optional[String] - State: Optional[String] - CarrierGatewayId: Optional[String] - CoreNetworkArn: Optional[ResourceArn] - LocalGatewayId: Optional[String] + DestinationCidr: String | None + DestinationPrefixListId: String | None + EgressOnlyInternetGatewayId: String | None + GatewayId: String | None + InstanceId: String | None + NatGatewayId: String | None + NetworkInterfaceId: String | None + Origin: String | None + TransitGatewayId: String | None + VpcPeeringConnectionId: String | None + State: String | None + CarrierGatewayId: String | None + CoreNetworkArn: ResourceArn | None + LocalGatewayId: String | None -StringList = List[String] +StringList = list[String] class AnalysisLoadBalancerTarget(TypedDict, total=False): - Address: Optional[IpAddress] - AvailabilityZone: Optional[String] - AvailabilityZoneId: Optional[String] - Instance: Optional[AnalysisComponent] - Port: Optional[Port] + Address: IpAddress | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + Instance: AnalysisComponent | None + Port: Port | None class AnalysisLoadBalancerListener(TypedDict, total=False): - LoadBalancerPort: Optional[Port] - InstancePort: Optional[Port] + LoadBalancerPort: Port | None + InstancePort: Port | None -IpAddressList = List[IpAddress] +IpAddressList = list[IpAddress] class AnalysisAclRule(TypedDict, total=False): - Cidr: Optional[String] - Egress: Optional[Boolean] - PortRange: Optional[PortRange] - Protocol: Optional[String] - RuleAction: Optional[String] - RuleNumber: Optional[Integer] + Cidr: String | None + Egress: Boolean | None + PortRange: PortRange | None + Protocol: String | None + RuleAction: String | None + RuleNumber: Integer | None class Explanation(TypedDict, total=False): - Acl: Optional[AnalysisComponent] - AclRule: Optional[AnalysisAclRule] - Address: Optional[IpAddress] - Addresses: Optional[IpAddressList] - AttachedTo: Optional[AnalysisComponent] - AvailabilityZones: Optional[ValueStringList] - AvailabilityZoneIds: Optional[ValueStringList] - Cidrs: Optional[ValueStringList] - Component: Optional[AnalysisComponent] - CustomerGateway: Optional[AnalysisComponent] - Destination: Optional[AnalysisComponent] - DestinationVpc: Optional[AnalysisComponent] - Direction: Optional[String] - ExplanationCode: Optional[String] - IngressRouteTable: Optional[AnalysisComponent] - InternetGateway: Optional[AnalysisComponent] - LoadBalancerArn: Optional[ResourceArn] - ClassicLoadBalancerListener: Optional[AnalysisLoadBalancerListener] - LoadBalancerListenerPort: Optional[Port] - LoadBalancerTarget: Optional[AnalysisLoadBalancerTarget] - LoadBalancerTargetGroup: Optional[AnalysisComponent] - LoadBalancerTargetGroups: Optional[AnalysisComponentList] - LoadBalancerTargetPort: Optional[Port] - ElasticLoadBalancerListener: Optional[AnalysisComponent] - MissingComponent: Optional[String] - NatGateway: Optional[AnalysisComponent] - NetworkInterface: Optional[AnalysisComponent] - PacketField: Optional[String] - VpcPeeringConnection: Optional[AnalysisComponent] - Port: Optional[Port] - PortRanges: Optional[PortRangeList] - PrefixList: Optional[AnalysisComponent] - Protocols: Optional[StringList] - RouteTableRoute: Optional[AnalysisRouteTableRoute] - RouteTable: Optional[AnalysisComponent] - SecurityGroup: Optional[AnalysisComponent] - SecurityGroupRule: Optional[AnalysisSecurityGroupRule] - SecurityGroups: Optional[AnalysisComponentList] - SourceVpc: Optional[AnalysisComponent] - State: Optional[String] - Subnet: Optional[AnalysisComponent] - SubnetRouteTable: Optional[AnalysisComponent] - Vpc: Optional[AnalysisComponent] - VpcEndpoint: Optional[AnalysisComponent] - VpnConnection: Optional[AnalysisComponent] - VpnGateway: Optional[AnalysisComponent] - TransitGateway: Optional[AnalysisComponent] - TransitGatewayRouteTable: Optional[AnalysisComponent] - TransitGatewayRouteTableRoute: Optional[TransitGatewayRouteTableRoute] - TransitGatewayAttachment: Optional[AnalysisComponent] - ComponentAccount: Optional[ComponentAccount] - ComponentRegion: Optional[ComponentRegion] - FirewallStatelessRule: Optional[FirewallStatelessRule] - FirewallStatefulRule: Optional[FirewallStatefulRule] - - -ExplanationList = List[Explanation] + Acl: AnalysisComponent | None + AclRule: AnalysisAclRule | None + Address: IpAddress | None + Addresses: IpAddressList | None + AttachedTo: AnalysisComponent | None + AvailabilityZones: ValueStringList | None + AvailabilityZoneIds: ValueStringList | None + Cidrs: ValueStringList | None + Component: AnalysisComponent | None + CustomerGateway: AnalysisComponent | None + Destination: AnalysisComponent | None + DestinationVpc: AnalysisComponent | None + Direction: String | None + ExplanationCode: String | None + IngressRouteTable: AnalysisComponent | None + InternetGateway: AnalysisComponent | None + LoadBalancerArn: ResourceArn | None + ClassicLoadBalancerListener: AnalysisLoadBalancerListener | None + LoadBalancerListenerPort: Port | None + LoadBalancerTarget: AnalysisLoadBalancerTarget | None + LoadBalancerTargetGroup: AnalysisComponent | None + LoadBalancerTargetGroups: AnalysisComponentList | None + LoadBalancerTargetPort: Port | None + ElasticLoadBalancerListener: AnalysisComponent | None + MissingComponent: String | None + NatGateway: AnalysisComponent | None + NetworkInterface: AnalysisComponent | None + PacketField: String | None + VpcPeeringConnection: AnalysisComponent | None + Port: Port | None + PortRanges: PortRangeList | None + PrefixList: AnalysisComponent | None + Protocols: StringList | None + RouteTableRoute: AnalysisRouteTableRoute | None + RouteTable: AnalysisComponent | None + SecurityGroup: AnalysisComponent | None + SecurityGroupRule: AnalysisSecurityGroupRule | None + SecurityGroups: AnalysisComponentList | None + SourceVpc: AnalysisComponent | None + State: String | None + Subnet: AnalysisComponent | None + SubnetRouteTable: AnalysisComponent | None + Vpc: AnalysisComponent | None + VpcEndpoint: AnalysisComponent | None + VpnConnection: AnalysisComponent | None + VpnGateway: AnalysisComponent | None + TransitGateway: AnalysisComponent | None + TransitGatewayRouteTable: AnalysisComponent | None + TransitGatewayRouteTableRoute: TransitGatewayRouteTableRoute | None + TransitGatewayAttachment: AnalysisComponent | None + ComponentAccount: ComponentAccount | None + ComponentRegion: ComponentRegion | None + FirewallStatelessRule: FirewallStatelessRule | None + FirewallStatefulRule: FirewallStatefulRule | None + + +ExplanationList = list[Explanation] class RuleOption(TypedDict, total=False): - Keyword: Optional[String] - Settings: Optional[StringList] + Keyword: String | None + Settings: StringList | None -RuleOptionList = List[RuleOption] +RuleOptionList = list[RuleOption] class RuleGroupRuleOptionsPair(TypedDict, total=False): - RuleGroupArn: Optional[ResourceArn] - RuleOptions: Optional[RuleOptionList] + RuleGroupArn: ResourceArn | None + RuleOptions: RuleOptionList | None -RuleGroupRuleOptionsPairList = List[RuleGroupRuleOptionsPair] +RuleGroupRuleOptionsPairList = list[RuleGroupRuleOptionsPair] class RuleGroupTypePair(TypedDict, total=False): - RuleGroupArn: Optional[ResourceArn] - RuleGroupType: Optional[String] + RuleGroupArn: ResourceArn | None + RuleGroupType: String | None -RuleGroupTypePairList = List[RuleGroupTypePair] +RuleGroupTypePairList = list[RuleGroupTypePair] class AdditionalDetail(TypedDict, total=False): - AdditionalDetailType: Optional[String] - Component: Optional[AnalysisComponent] - VpcEndpointService: Optional[AnalysisComponent] - RuleOptions: Optional[RuleOptionList] - RuleGroupTypePairs: Optional[RuleGroupTypePairList] - RuleGroupRuleOptionsPairs: Optional[RuleGroupRuleOptionsPairList] - ServiceName: Optional[String] - LoadBalancers: Optional[AnalysisComponentList] + AdditionalDetailType: String | None + Component: AnalysisComponent | None + VpcEndpointService: AnalysisComponent | None + RuleOptions: RuleOptionList | None + RuleGroupTypePairs: RuleGroupTypePairList | None + RuleGroupRuleOptionsPairs: RuleGroupRuleOptionsPairList | None + ServiceName: String | None + LoadBalancers: AnalysisComponentList | None -AdditionalDetailList = List[AdditionalDetail] +AdditionalDetailList = list[AdditionalDetail] class AnalysisPacketHeader(TypedDict, total=False): - DestinationAddresses: Optional[IpAddressList] - DestinationPortRanges: Optional[PortRangeList] - Protocol: Optional[String] - SourceAddresses: Optional[IpAddressList] - SourcePortRanges: Optional[PortRangeList] + DestinationAddresses: IpAddressList | None + DestinationPortRanges: PortRangeList | None + Protocol: String | None + SourceAddresses: IpAddressList | None + SourcePortRanges: PortRangeList | None class PathComponent(TypedDict, total=False): - SequenceNumber: Optional[Integer] - AclRule: Optional[AnalysisAclRule] - AttachedTo: Optional[AnalysisComponent] - Component: Optional[AnalysisComponent] - DestinationVpc: Optional[AnalysisComponent] - OutboundHeader: Optional[AnalysisPacketHeader] - InboundHeader: Optional[AnalysisPacketHeader] - RouteTableRoute: Optional[AnalysisRouteTableRoute] - SecurityGroupRule: Optional[AnalysisSecurityGroupRule] - SourceVpc: Optional[AnalysisComponent] - Subnet: Optional[AnalysisComponent] - Vpc: Optional[AnalysisComponent] - AdditionalDetails: Optional[AdditionalDetailList] - TransitGateway: Optional[AnalysisComponent] - TransitGatewayRouteTableRoute: Optional[TransitGatewayRouteTableRoute] - Explanations: Optional[ExplanationList] - ElasticLoadBalancerListener: Optional[AnalysisComponent] - FirewallStatelessRule: Optional[FirewallStatelessRule] - FirewallStatefulRule: Optional[FirewallStatefulRule] - ServiceName: Optional[String] - - -PathComponentList = List[PathComponent] + SequenceNumber: Integer | None + AclRule: AnalysisAclRule | None + AttachedTo: AnalysisComponent | None + Component: AnalysisComponent | None + DestinationVpc: AnalysisComponent | None + OutboundHeader: AnalysisPacketHeader | None + InboundHeader: AnalysisPacketHeader | None + RouteTableRoute: AnalysisRouteTableRoute | None + SecurityGroupRule: AnalysisSecurityGroupRule | None + SourceVpc: AnalysisComponent | None + Subnet: AnalysisComponent | None + Vpc: AnalysisComponent | None + AdditionalDetails: AdditionalDetailList | None + TransitGateway: AnalysisComponent | None + TransitGatewayRouteTableRoute: TransitGatewayRouteTableRoute | None + Explanations: ExplanationList | None + ElasticLoadBalancerListener: AnalysisComponent | None + FirewallStatelessRule: FirewallStatelessRule | None + FirewallStatefulRule: FirewallStatefulRule | None + ServiceName: String | None + + +PathComponentList = list[PathComponent] class AccessScopeAnalysisFinding(TypedDict, total=False): - NetworkInsightsAccessScopeAnalysisId: Optional[NetworkInsightsAccessScopeAnalysisId] - NetworkInsightsAccessScopeId: Optional[NetworkInsightsAccessScopeId] - FindingId: Optional[String] - FindingComponents: Optional[PathComponentList] + NetworkInsightsAccessScopeAnalysisId: NetworkInsightsAccessScopeAnalysisId | None + NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId | None + FindingId: String | None + FindingComponents: PathComponentList | None -AccessScopeAnalysisFindingList = List[AccessScopeAnalysisFinding] +AccessScopeAnalysisFindingList = list[AccessScopeAnalysisFinding] class ResourceStatement(TypedDict, total=False): - Resources: Optional[ValueStringList] - ResourceTypes: Optional[ValueStringList] + Resources: ValueStringList | None + ResourceTypes: ValueStringList | None class ThroughResourcesStatement(TypedDict, total=False): - ResourceStatement: Optional[ResourceStatement] + ResourceStatement: ResourceStatement | None -ThroughResourcesStatementList = List[ThroughResourcesStatement] -ProtocolList = List[Protocol] +ThroughResourcesStatementList = list[ThroughResourcesStatement] +ProtocolList = list[Protocol] class PacketHeaderStatement(TypedDict, total=False): - SourceAddresses: Optional[ValueStringList] - DestinationAddresses: Optional[ValueStringList] - SourcePorts: Optional[ValueStringList] - DestinationPorts: Optional[ValueStringList] - SourcePrefixLists: Optional[ValueStringList] - DestinationPrefixLists: Optional[ValueStringList] - Protocols: Optional[ProtocolList] + SourceAddresses: ValueStringList | None + DestinationAddresses: ValueStringList | None + SourcePorts: ValueStringList | None + DestinationPorts: ValueStringList | None + SourcePrefixLists: ValueStringList | None + DestinationPrefixLists: ValueStringList | None + Protocols: ProtocolList | None class PathStatement(TypedDict, total=False): - PacketHeaderStatement: Optional[PacketHeaderStatement] - ResourceStatement: Optional[ResourceStatement] + PacketHeaderStatement: PacketHeaderStatement | None + ResourceStatement: ResourceStatement | None class AccessScopePath(TypedDict, total=False): - Source: Optional[PathStatement] - Destination: Optional[PathStatement] - ThroughResources: Optional[ThroughResourcesStatementList] + Source: PathStatement | None + Destination: PathStatement | None + ThroughResources: ThroughResourcesStatementList | None -AccessScopePathList = List[AccessScopePath] +AccessScopePathList = list[AccessScopePath] class ResourceStatementRequest(TypedDict, total=False): - Resources: Optional[ValueStringList] - ResourceTypes: Optional[ValueStringList] + Resources: ValueStringList | None + ResourceTypes: ValueStringList | None class ThroughResourcesStatementRequest(TypedDict, total=False): - ResourceStatement: Optional[ResourceStatementRequest] + ResourceStatement: ResourceStatementRequest | None -ThroughResourcesStatementRequestList = List[ThroughResourcesStatementRequest] +ThroughResourcesStatementRequestList = list[ThroughResourcesStatementRequest] class PacketHeaderStatementRequest(TypedDict, total=False): - SourceAddresses: Optional[ValueStringList] - DestinationAddresses: Optional[ValueStringList] - SourcePorts: Optional[ValueStringList] - DestinationPorts: Optional[ValueStringList] - SourcePrefixLists: Optional[ValueStringList] - DestinationPrefixLists: Optional[ValueStringList] - Protocols: Optional[ProtocolList] + SourceAddresses: ValueStringList | None + DestinationAddresses: ValueStringList | None + SourcePorts: ValueStringList | None + DestinationPorts: ValueStringList | None + SourcePrefixLists: ValueStringList | None + DestinationPrefixLists: ValueStringList | None + Protocols: ProtocolList | None class PathStatementRequest(TypedDict, total=False): - PacketHeaderStatement: Optional[PacketHeaderStatementRequest] - ResourceStatement: Optional[ResourceStatementRequest] + PacketHeaderStatement: PacketHeaderStatementRequest | None + ResourceStatement: ResourceStatementRequest | None class AccessScopePathRequest(TypedDict, total=False): - Source: Optional[PathStatementRequest] - Destination: Optional[PathStatementRequest] - ThroughResources: Optional[ThroughResourcesStatementRequestList] + Source: PathStatementRequest | None + Destination: PathStatementRequest | None + ThroughResources: ThroughResourcesStatementRequestList | None -AccessScopePathListRequest = List[AccessScopePathRequest] +AccessScopePathListRequest = list[AccessScopePathRequest] class AccountAttributeValue(TypedDict, total=False): - AttributeValue: Optional[String] + AttributeValue: String | None -AccountAttributeValueList = List[AccountAttributeValue] +AccountAttributeValueList = list[AccountAttributeValue] class AccountAttribute(TypedDict, total=False): - AttributeName: Optional[String] - AttributeValues: Optional[AccountAttributeValueList] + AttributeName: String | None + AttributeValues: AccountAttributeValueList | None -AccountAttributeList = List[AccountAttribute] -AccountAttributeNameStringList = List[AccountAttributeName] +AccountAttributeList = list[AccountAttribute] +AccountAttributeNameStringList = list[AccountAttributeName] class ActiveInstance(TypedDict, total=False): - InstanceId: Optional[String] - InstanceType: Optional[String] - SpotInstanceRequestId: Optional[String] - InstanceHealth: Optional[InstanceHealthStatus] + InstanceId: String | None + InstanceType: String | None + SpotInstanceRequestId: String | None + InstanceHealth: InstanceHealthStatus | None -ActiveInstanceSet = List[ActiveInstance] +ActiveInstanceSet = list[ActiveInstance] class ActiveVpnTunnelStatus(TypedDict, total=False): - Phase1EncryptionAlgorithm: Optional[String] - Phase2EncryptionAlgorithm: Optional[String] - Phase1IntegrityAlgorithm: Optional[String] - Phase2IntegrityAlgorithm: Optional[String] - Phase1DHGroup: Optional[Integer] - Phase2DHGroup: Optional[Integer] - IkeVersion: Optional[String] - ProvisioningStatus: Optional[VpnTunnelProvisioningStatus] - ProvisioningStatusReason: Optional[String] + Phase1EncryptionAlgorithm: String | None + Phase2EncryptionAlgorithm: String | None + Phase1IntegrityAlgorithm: String | None + Phase2IntegrityAlgorithm: String | None + Phase1DHGroup: Integer | None + Phase2DHGroup: Integer | None + IkeVersion: String | None + ProvisioningStatus: VpnTunnelProvisioningStatus | None + ProvisioningStatusReason: String | None class AddIpamOperatingRegion(TypedDict, total=False): - RegionName: Optional[String] + RegionName: String | None -AddIpamOperatingRegionSet = List[AddIpamOperatingRegion] +AddIpamOperatingRegionSet = list[AddIpamOperatingRegion] class AddIpamOrganizationalUnitExclusion(TypedDict, total=False): - OrganizationsEntityPath: Optional[String] + OrganizationsEntityPath: String | None -AddIpamOrganizationalUnitExclusionSet = List[AddIpamOrganizationalUnitExclusion] +AddIpamOrganizationalUnitExclusionSet = list[AddIpamOrganizationalUnitExclusion] class AddPrefixListEntry(TypedDict, total=False): Cidr: String - Description: Optional[String] + Description: String | None -AddPrefixListEntries = List[AddPrefixListEntry] +AddPrefixListEntries = list[AddPrefixListEntry] class AddedPrincipal(TypedDict, total=False): - PrincipalType: Optional[PrincipalType] - Principal: Optional[String] - ServicePermissionId: Optional[String] - ServiceId: Optional[String] + PrincipalType: PrincipalType | None + Principal: String | None + ServicePermissionId: String | None + ServiceId: String | None -AddedPrincipalSet = List[AddedPrincipal] +AddedPrincipalSet = list[AddedPrincipal] class Address(TypedDict, total=False): - AllocationId: Optional[String] - AssociationId: Optional[String] - Domain: Optional[DomainType] - NetworkInterfaceId: Optional[String] - NetworkInterfaceOwnerId: Optional[String] - PrivateIpAddress: Optional[String] - Tags: Optional[TagList] - PublicIpv4Pool: Optional[String] - NetworkBorderGroup: Optional[String] - CustomerOwnedIp: Optional[String] - CustomerOwnedIpv4Pool: Optional[String] - CarrierIp: Optional[String] - SubnetId: Optional[String] - ServiceManaged: Optional[ServiceManaged] - InstanceId: Optional[String] - PublicIp: Optional[String] + AllocationId: String | None + AssociationId: String | None + Domain: DomainType | None + NetworkInterfaceId: String | None + NetworkInterfaceOwnerId: String | None + PrivateIpAddress: String | None + Tags: TagList | None + PublicIpv4Pool: String | None + NetworkBorderGroup: String | None + CustomerOwnedIp: String | None + CustomerOwnedIpv4Pool: String | None + CarrierIp: String | None + SubnetId: String | None + ServiceManaged: ServiceManaged | None + InstanceId: String | None + PublicIp: String | None class PtrUpdateStatus(TypedDict, total=False): - Value: Optional[String] - Status: Optional[String] - Reason: Optional[String] + Value: String | None + Status: String | None + Reason: String | None class AddressAttribute(TypedDict, total=False): - PublicIp: Optional[PublicIpAddress] - AllocationId: Optional[AllocationId] - PtrRecord: Optional[String] - PtrRecordUpdate: Optional[PtrUpdateStatus] + PublicIp: PublicIpAddress | None + AllocationId: AllocationId | None + PtrRecord: String | None + PtrRecordUpdate: PtrUpdateStatus | None -AddressList = List[Address] -AddressSet = List[AddressAttribute] -AddressTransferList = List[AddressTransfer] +AddressList = list[Address] +AddressSet = list[AddressAttribute] +AddressTransferList = list[AddressTransfer] class AdvertiseByoipCidrRequest(ServiceRequest): Cidr: String - Asn: Optional[String] - DryRun: Optional[Boolean] - NetworkBorderGroup: Optional[String] + Asn: String | None + DryRun: Boolean | None + NetworkBorderGroup: String | None class AsnAssociation(TypedDict, total=False): - Asn: Optional[String] - Cidr: Optional[String] - StatusMessage: Optional[String] - State: Optional[AsnAssociationState] + Asn: String | None + Cidr: String | None + StatusMessage: String | None + State: AsnAssociationState | None -AsnAssociationSet = List[AsnAssociation] +AsnAssociationSet = list[AsnAssociation] class ByoipCidr(TypedDict, total=False): - Cidr: Optional[String] - Description: Optional[String] - AsnAssociations: Optional[AsnAssociationSet] - StatusMessage: Optional[String] - State: Optional[ByoipCidrState] - NetworkBorderGroup: Optional[String] + Cidr: String | None + Description: String | None + AsnAssociations: AsnAssociationSet | None + StatusMessage: String | None + State: ByoipCidrState | None + NetworkBorderGroup: String | None + AdvertisementType: String | None class AdvertiseByoipCidrResult(TypedDict, total=False): - ByoipCidr: Optional[ByoipCidr] + ByoipCidr: ByoipCidr | None class AllocateAddressRequest(ServiceRequest): - Domain: Optional[DomainType] - Address: Optional[PublicIpAddress] - PublicIpv4Pool: Optional[Ipv4PoolEc2Id] - NetworkBorderGroup: Optional[String] - CustomerOwnedIpv4Pool: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - IpamPoolId: Optional[IpamPoolId] - DryRun: Optional[Boolean] + Domain: DomainType | None + Address: PublicIpAddress | None + PublicIpv4Pool: Ipv4PoolEc2Id | None + NetworkBorderGroup: String | None + CustomerOwnedIpv4Pool: String | None + TagSpecifications: TagSpecificationList | None + IpamPoolId: IpamPoolId | None + DryRun: Boolean | None class AllocateAddressResult(TypedDict, total=False): - AllocationId: Optional[String] - PublicIpv4Pool: Optional[String] - NetworkBorderGroup: Optional[String] - Domain: Optional[DomainType] - CustomerOwnedIp: Optional[String] - CustomerOwnedIpv4Pool: Optional[String] - CarrierIp: Optional[String] - PublicIp: Optional[String] + AllocationId: String | None + PublicIpv4Pool: String | None + NetworkBorderGroup: String | None + Domain: DomainType | None + CustomerOwnedIp: String | None + CustomerOwnedIpv4Pool: String | None + CarrierIp: String | None + PublicIp: String | None -AssetIdList = List[AssetId] +AssetIdList = list[AssetId] class AllocateHostsRequest(ServiceRequest): - InstanceFamily: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - HostRecovery: Optional[HostRecovery] - OutpostArn: Optional[String] - HostMaintenance: Optional[HostMaintenance] - AssetIds: Optional[AssetIdList] - AvailabilityZoneId: Optional[AvailabilityZoneId] - AutoPlacement: Optional[AutoPlacement] - ClientToken: Optional[String] - InstanceType: Optional[String] - Quantity: Optional[Integer] - AvailabilityZone: Optional[String] + InstanceFamily: String | None + TagSpecifications: TagSpecificationList | None + HostRecovery: HostRecovery | None + OutpostArn: String | None + HostMaintenance: HostMaintenance | None + AssetIds: AssetIdList | None + AvailabilityZoneId: AvailabilityZoneId | None + AutoPlacement: AutoPlacement | None + ClientToken: String | None + InstanceType: String | None + Quantity: Integer | None + AvailabilityZone: AvailabilityZoneName | None -ResponseHostIdList = List[String] +ResponseHostIdList = list[String] class AllocateHostsResult(TypedDict, total=False): - HostIds: Optional[ResponseHostIdList] + HostIds: ResponseHostIdList | None -IpamPoolAllocationDisallowedCidrs = List[String] -IpamPoolAllocationAllowedCidrs = List[String] +IpamPoolAllocationDisallowedCidrs = list[String] +IpamPoolAllocationAllowedCidrs = list[String] class AllocateIpamPoolCidrRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId - Cidr: Optional[String] - NetmaskLength: Optional[Integer] - ClientToken: Optional[String] - Description: Optional[String] - PreviewNextCidr: Optional[Boolean] - AllowedCidrs: Optional[IpamPoolAllocationAllowedCidrs] - DisallowedCidrs: Optional[IpamPoolAllocationDisallowedCidrs] + Cidr: String | None + NetmaskLength: Integer | None + ClientToken: String | None + Description: String | None + PreviewNextCidr: Boolean | None + AllowedCidrs: IpamPoolAllocationAllowedCidrs | None + DisallowedCidrs: IpamPoolAllocationDisallowedCidrs | None class IpamPoolAllocation(TypedDict, total=False): - Cidr: Optional[String] - IpamPoolAllocationId: Optional[IpamPoolAllocationId] - Description: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[IpamPoolAllocationResourceType] - ResourceRegion: Optional[String] - ResourceOwner: Optional[String] + Cidr: String | None + IpamPoolAllocationId: IpamPoolAllocationId | None + Description: String | None + ResourceId: String | None + ResourceType: IpamPoolAllocationResourceType | None + ResourceRegion: String | None + ResourceOwner: String | None class AllocateIpamPoolCidrResult(TypedDict, total=False): - IpamPoolAllocation: Optional[IpamPoolAllocation] + IpamPoolAllocation: IpamPoolAllocation | None -AllocationIdList = List[AllocationId] -AllocationIds = List[AllocationId] -AllowedInstanceTypeSet = List[AllowedInstanceType] +AllocationIdList = list[AllocationId] +AllocationIds = list[AllocationId] +AllowedInstanceTypeSet = list[AllowedInstanceType] class AllowedPrincipal(TypedDict, total=False): - PrincipalType: Optional[PrincipalType] - Principal: Optional[String] - ServicePermissionId: Optional[String] - Tags: Optional[TagList] - ServiceId: Optional[String] + PrincipalType: PrincipalType | None + Principal: String | None + ServicePermissionId: String | None + Tags: TagList | None + ServiceId: String | None -AllowedPrincipalSet = List[AllowedPrincipal] +AllowedPrincipalSet = list[AllowedPrincipal] class AlternatePathHint(TypedDict, total=False): - ComponentId: Optional[String] - ComponentArn: Optional[String] + ComponentId: String | None + ComponentArn: String | None -AlternatePathHintList = List[AlternatePathHint] -ClientVpnSecurityGroupIdSet = List[SecurityGroupId] +AlternatePathHintList = list[AlternatePathHint] +ClientVpnSecurityGroupIdSet = list[SecurityGroupId] class ApplySecurityGroupsToClientVpnTargetNetworkRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId VpcId: VpcId SecurityGroupIds: ClientVpnSecurityGroupIdSet - DryRun: Optional[Boolean] + DryRun: Boolean | None class ApplySecurityGroupsToClientVpnTargetNetworkResult(TypedDict, total=False): - SecurityGroupIds: Optional[ClientVpnSecurityGroupIdSet] + SecurityGroupIds: ClientVpnSecurityGroupIdSet | None -ArchitectureTypeList = List[ArchitectureType] -ArchitectureTypeSet = List[ArchitectureType] -ArnList = List[ResourceArn] -AsPath = List[String] +ArchitectureTypeList = list[ArchitectureType] +ArchitectureTypeSet = list[ArchitectureType] +ArnList = list[ResourceArn] +AsPath = list[String] class AsnAuthorizationContext(TypedDict, total=False): @@ -4812,147 +5595,149 @@ class AsnAuthorizationContext(TypedDict, total=False): Signature: String -Ipv6AddressList = List[String] -IpPrefixList = List[String] +Ipv6AddressList = list[String] +IpPrefixList = list[String] class AssignIpv6AddressesRequest(ServiceRequest): - Ipv6PrefixCount: Optional[Integer] - Ipv6Prefixes: Optional[IpPrefixList] + Ipv6PrefixCount: Integer | None + Ipv6Prefixes: IpPrefixList | None NetworkInterfaceId: NetworkInterfaceId - Ipv6Addresses: Optional[Ipv6AddressList] - Ipv6AddressCount: Optional[Integer] + Ipv6Addresses: Ipv6AddressList | None + Ipv6AddressCount: Integer | None class AssignIpv6AddressesResult(TypedDict, total=False): - AssignedIpv6Addresses: Optional[Ipv6AddressList] - AssignedIpv6Prefixes: Optional[IpPrefixList] - NetworkInterfaceId: Optional[String] + AssignedIpv6Addresses: Ipv6AddressList | None + AssignedIpv6Prefixes: IpPrefixList | None + NetworkInterfaceId: String | None -PrivateIpAddressStringList = List[String] +PrivateIpAddressStringList = list[String] class AssignPrivateIpAddressesRequest(ServiceRequest): - Ipv4Prefixes: Optional[IpPrefixList] - Ipv4PrefixCount: Optional[Integer] + Ipv4Prefixes: IpPrefixList | None + Ipv4PrefixCount: Integer | None NetworkInterfaceId: NetworkInterfaceId - PrivateIpAddresses: Optional[PrivateIpAddressStringList] - SecondaryPrivateIpAddressCount: Optional[Integer] - AllowReassignment: Optional[Boolean] + PrivateIpAddresses: PrivateIpAddressStringList | None + SecondaryPrivateIpAddressCount: Integer | None + AllowReassignment: Boolean | None class Ipv4PrefixSpecification(TypedDict, total=False): - Ipv4Prefix: Optional[String] + Ipv4Prefix: String | None -Ipv4PrefixesList = List[Ipv4PrefixSpecification] +Ipv4PrefixesList = list[Ipv4PrefixSpecification] class AssignedPrivateIpAddress(TypedDict, total=False): - PrivateIpAddress: Optional[String] + PrivateIpAddress: String | None -AssignedPrivateIpAddressList = List[AssignedPrivateIpAddress] +AssignedPrivateIpAddressList = list[AssignedPrivateIpAddress] class AssignPrivateIpAddressesResult(TypedDict, total=False): - NetworkInterfaceId: Optional[String] - AssignedPrivateIpAddresses: Optional[AssignedPrivateIpAddressList] - AssignedIpv4Prefixes: Optional[Ipv4PrefixesList] + NetworkInterfaceId: String | None + AssignedPrivateIpAddresses: AssignedPrivateIpAddressList | None + AssignedIpv4Prefixes: Ipv4PrefixesList | None -IpList = List[String] +IpList = list[String] class AssignPrivateNatGatewayAddressRequest(ServiceRequest): NatGatewayId: NatGatewayId - PrivateIpAddresses: Optional[IpList] - PrivateIpAddressCount: Optional[PrivateIpAddressCount] - DryRun: Optional[Boolean] + PrivateIpAddresses: IpList | None + PrivateIpAddressCount: PrivateIpAddressCount | None + DryRun: Boolean | None class NatGatewayAddress(TypedDict, total=False): - AllocationId: Optional[String] - NetworkInterfaceId: Optional[String] - PrivateIp: Optional[String] - PublicIp: Optional[String] - AssociationId: Optional[String] - IsPrimary: Optional[Boolean] - FailureMessage: Optional[String] - Status: Optional[NatGatewayAddressStatus] + AllocationId: String | None + NetworkInterfaceId: String | None + PrivateIp: String | None + PublicIp: String | None + AssociationId: String | None + IsPrimary: Boolean | None + FailureMessage: String | None + Status: NatGatewayAddressStatus | None + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None -NatGatewayAddressList = List[NatGatewayAddress] +NatGatewayAddressList = list[NatGatewayAddress] class AssignPrivateNatGatewayAddressResult(TypedDict, total=False): - NatGatewayId: Optional[NatGatewayId] - NatGatewayAddresses: Optional[NatGatewayAddressList] + NatGatewayId: NatGatewayId | None + NatGatewayAddresses: NatGatewayAddressList | None class AssociateAddressRequest(ServiceRequest): - AllocationId: Optional[AllocationId] - InstanceId: Optional[InstanceId] - PublicIp: Optional[EipAllocationPublicIp] - DryRun: Optional[Boolean] - NetworkInterfaceId: Optional[NetworkInterfaceId] - PrivateIpAddress: Optional[String] - AllowReassociation: Optional[Boolean] + AllocationId: AllocationId | None + InstanceId: InstanceId | None + PublicIp: EipAllocationPublicIp | None + DryRun: Boolean | None + NetworkInterfaceId: NetworkInterfaceId | None + PrivateIpAddress: String | None + AllowReassociation: Boolean | None class AssociateAddressResult(TypedDict, total=False): - AssociationId: Optional[String] + AssociationId: String | None class AssociateCapacityReservationBillingOwnerRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None CapacityReservationId: CapacityReservationId UnusedReservationBillingOwnerId: AccountID class AssociateCapacityReservationBillingOwnerResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class AssociateClientVpnTargetNetworkRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId SubnetId: SubnetId - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None class AssociationStatus(TypedDict, total=False): - Code: Optional[AssociationStatusCode] - Message: Optional[String] + Code: AssociationStatusCode | None + Message: String | None class AssociateClientVpnTargetNetworkResult(TypedDict, total=False): - AssociationId: Optional[String] - Status: Optional[AssociationStatus] + AssociationId: String | None + Status: AssociationStatus | None class AssociateDhcpOptionsRequest(ServiceRequest): DhcpOptionsId: DefaultingDhcpOptionsId VpcId: VpcId - DryRun: Optional[Boolean] + DryRun: Boolean | None class AssociateEnclaveCertificateIamRoleRequest(ServiceRequest): CertificateArn: CertificateId RoleArn: RoleId - DryRun: Optional[Boolean] + DryRun: Boolean | None class AssociateEnclaveCertificateIamRoleResult(TypedDict, total=False): - CertificateS3BucketName: Optional[String] - CertificateS3ObjectKey: Optional[String] - EncryptionKmsKeyId: Optional[String] + CertificateS3BucketName: String | None + CertificateS3ObjectKey: String | None + EncryptionKmsKeyId: String | None class IamInstanceProfileSpecification(TypedDict, total=False): - Arn: Optional[String] - Name: Optional[String] + Arn: String | None + Name: String | None class AssociateIamInstanceProfileRequest(ServiceRequest): @@ -4961,927 +5746,956 @@ class AssociateIamInstanceProfileRequest(ServiceRequest): class IamInstanceProfile(TypedDict, total=False): - Arn: Optional[String] - Id: Optional[String] + Arn: String | None + Id: String | None class IamInstanceProfileAssociation(TypedDict, total=False): - AssociationId: Optional[String] - InstanceId: Optional[String] - IamInstanceProfile: Optional[IamInstanceProfile] - State: Optional[IamInstanceProfileAssociationState] - Timestamp: Optional[DateTime] + AssociationId: String | None + InstanceId: String | None + IamInstanceProfile: IamInstanceProfile | None + State: IamInstanceProfileAssociationState | None + Timestamp: DateTime | None class AssociateIamInstanceProfileResult(TypedDict, total=False): - IamInstanceProfileAssociation: Optional[IamInstanceProfileAssociation] + IamInstanceProfileAssociation: IamInstanceProfileAssociation | None -DedicatedHostIdList = List[DedicatedHostId] -InstanceIdList = List[InstanceId] +DedicatedHostIdList = list[DedicatedHostId] +InstanceIdList = list[InstanceId] class InstanceEventWindowAssociationRequest(TypedDict, total=False): - InstanceIds: Optional[InstanceIdList] - InstanceTags: Optional[TagList] - DedicatedHostIds: Optional[DedicatedHostIdList] + InstanceIds: InstanceIdList | None + InstanceTags: TagList | None + DedicatedHostIds: DedicatedHostIdList | None class AssociateInstanceEventWindowRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceEventWindowId: InstanceEventWindowId AssociationTarget: InstanceEventWindowAssociationRequest class InstanceEventWindowAssociationTarget(TypedDict, total=False): - InstanceIds: Optional[InstanceIdList] - Tags: Optional[TagList] - DedicatedHostIds: Optional[DedicatedHostIdList] + InstanceIds: InstanceIdList | None + Tags: TagList | None + DedicatedHostIds: DedicatedHostIdList | None class InstanceEventWindowTimeRange(TypedDict, total=False): - StartWeekDay: Optional[WeekDay] - StartHour: Optional[Hour] - EndWeekDay: Optional[WeekDay] - EndHour: Optional[Hour] + StartWeekDay: WeekDay | None + StartHour: Hour | None + EndWeekDay: WeekDay | None + EndHour: Hour | None -InstanceEventWindowTimeRangeList = List[InstanceEventWindowTimeRange] +InstanceEventWindowTimeRangeList = list[InstanceEventWindowTimeRange] class InstanceEventWindow(TypedDict, total=False): - InstanceEventWindowId: Optional[InstanceEventWindowId] - TimeRanges: Optional[InstanceEventWindowTimeRangeList] - Name: Optional[String] - CronExpression: Optional[InstanceEventWindowCronExpression] - AssociationTarget: Optional[InstanceEventWindowAssociationTarget] - State: Optional[InstanceEventWindowState] - Tags: Optional[TagList] + InstanceEventWindowId: InstanceEventWindowId | None + TimeRanges: InstanceEventWindowTimeRangeList | None + Name: String | None + CronExpression: InstanceEventWindowCronExpression | None + AssociationTarget: InstanceEventWindowAssociationTarget | None + State: InstanceEventWindowState | None + Tags: TagList | None class AssociateInstanceEventWindowResult(TypedDict, total=False): - InstanceEventWindow: Optional[InstanceEventWindow] + InstanceEventWindow: InstanceEventWindow | None class AssociateIpamByoasnRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Asn: String Cidr: String class AssociateIpamByoasnResult(TypedDict, total=False): - AsnAssociation: Optional[AsnAssociation] + AsnAssociation: AsnAssociation | None class AssociateIpamResourceDiscoveryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamId: IpamId IpamResourceDiscoveryId: IpamResourceDiscoveryId - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] + TagSpecifications: TagSpecificationList | None + ClientToken: String | None class IpamResourceDiscoveryAssociation(TypedDict, total=False): - OwnerId: Optional[String] - IpamResourceDiscoveryAssociationId: Optional[IpamResourceDiscoveryAssociationId] - IpamResourceDiscoveryAssociationArn: Optional[String] - IpamResourceDiscoveryId: Optional[IpamResourceDiscoveryId] - IpamId: Optional[IpamId] - IpamArn: Optional[ResourceArn] - IpamRegion: Optional[String] - IsDefault: Optional[Boolean] - ResourceDiscoveryStatus: Optional[IpamAssociatedResourceDiscoveryStatus] - State: Optional[IpamResourceDiscoveryAssociationState] - Tags: Optional[TagList] + OwnerId: String | None + IpamResourceDiscoveryAssociationId: IpamResourceDiscoveryAssociationId | None + IpamResourceDiscoveryAssociationArn: String | None + IpamResourceDiscoveryId: IpamResourceDiscoveryId | None + IpamId: IpamId | None + IpamArn: ResourceArn | None + IpamRegion: String | None + IsDefault: Boolean | None + ResourceDiscoveryStatus: IpamAssociatedResourceDiscoveryStatus | None + State: IpamResourceDiscoveryAssociationState | None + Tags: TagList | None class AssociateIpamResourceDiscoveryResult(TypedDict, total=False): - IpamResourceDiscoveryAssociation: Optional[IpamResourceDiscoveryAssociation] + IpamResourceDiscoveryAssociation: IpamResourceDiscoveryAssociation | None class AssociateNatGatewayAddressRequest(ServiceRequest): NatGatewayId: NatGatewayId AllocationIds: AllocationIdList - PrivateIpAddresses: Optional[IpList] - DryRun: Optional[Boolean] + PrivateIpAddresses: IpList | None + DryRun: Boolean | None + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None class AssociateNatGatewayAddressResult(TypedDict, total=False): - NatGatewayId: Optional[NatGatewayId] - NatGatewayAddresses: Optional[NatGatewayAddressList] + NatGatewayId: NatGatewayId | None + NatGatewayAddresses: NatGatewayAddressList | None class AssociateRouteServerRequest(ServiceRequest): RouteServerId: RouteServerId VpcId: VpcId - DryRun: Optional[Boolean] + DryRun: Boolean | None class RouteServerAssociation(TypedDict, total=False): - RouteServerId: Optional[RouteServerId] - VpcId: Optional[VpcId] - State: Optional[RouteServerAssociationState] + RouteServerId: RouteServerId | None + VpcId: VpcId | None + State: RouteServerAssociationState | None class AssociateRouteServerResult(TypedDict, total=False): - RouteServerAssociation: Optional[RouteServerAssociation] + RouteServerAssociation: RouteServerAssociation | None class AssociateRouteTableRequest(ServiceRequest): - GatewayId: Optional[RouteGatewayId] - DryRun: Optional[Boolean] - SubnetId: Optional[SubnetId] + GatewayId: RouteGatewayId | None + PublicIpv4Pool: Ipv4PoolEc2Id | None + DryRun: Boolean | None + SubnetId: SubnetId | None RouteTableId: RouteTableId class RouteTableAssociationState(TypedDict, total=False): - State: Optional[RouteTableAssociationStateCode] - StatusMessage: Optional[String] + State: RouteTableAssociationStateCode | None + StatusMessage: String | None class AssociateRouteTableResult(TypedDict, total=False): - AssociationId: Optional[String] - AssociationState: Optional[RouteTableAssociationState] + AssociationId: String | None + AssociationState: RouteTableAssociationState | None class AssociateSecurityGroupVpcRequest(ServiceRequest): GroupId: SecurityGroupId VpcId: VpcId - DryRun: Optional[Boolean] + DryRun: Boolean | None class AssociateSecurityGroupVpcResult(TypedDict, total=False): - State: Optional[SecurityGroupVpcAssociationState] + State: SecurityGroupVpcAssociationState | None class AssociateSubnetCidrBlockRequest(ServiceRequest): - Ipv6IpamPoolId: Optional[IpamPoolId] - Ipv6NetmaskLength: Optional[NetmaskLength] + Ipv6IpamPoolId: IpamPoolId | None + Ipv6NetmaskLength: NetmaskLength | None SubnetId: SubnetId - Ipv6CidrBlock: Optional[String] + Ipv6CidrBlock: String | None class SubnetCidrBlockState(TypedDict, total=False): - State: Optional[SubnetCidrBlockStateCode] - StatusMessage: Optional[String] + State: SubnetCidrBlockStateCode | None + StatusMessage: String | None class SubnetIpv6CidrBlockAssociation(TypedDict, total=False): - AssociationId: Optional[SubnetCidrAssociationId] - Ipv6CidrBlock: Optional[String] - Ipv6CidrBlockState: Optional[SubnetCidrBlockState] - Ipv6AddressAttribute: Optional[Ipv6AddressAttribute] - IpSource: Optional[IpSource] + AssociationId: SubnetCidrAssociationId | None + Ipv6CidrBlock: String | None + Ipv6CidrBlockState: SubnetCidrBlockState | None + Ipv6AddressAttribute: Ipv6AddressAttribute | None + IpSource: IpSource | None class AssociateSubnetCidrBlockResult(TypedDict, total=False): - Ipv6CidrBlockAssociation: Optional[SubnetIpv6CidrBlockAssociation] - SubnetId: Optional[String] + Ipv6CidrBlockAssociation: SubnetIpv6CidrBlockAssociation | None + SubnetId: String | None -TransitGatewaySubnetIdList = List[SubnetId] +TransitGatewaySubnetIdList = list[SubnetId] class AssociateTransitGatewayMulticastDomainRequest(ServiceRequest): TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId TransitGatewayAttachmentId: TransitGatewayAttachmentId SubnetIds: TransitGatewaySubnetIdList - DryRun: Optional[Boolean] + DryRun: Boolean | None class AssociateTransitGatewayMulticastDomainResult(TypedDict, total=False): - Associations: Optional[TransitGatewayMulticastDomainAssociations] + Associations: TransitGatewayMulticastDomainAssociations | None class AssociateTransitGatewayPolicyTableRequest(ServiceRequest): TransitGatewayPolicyTableId: TransitGatewayPolicyTableId TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class TransitGatewayPolicyTableAssociation(TypedDict, total=False): - TransitGatewayPolicyTableId: Optional[TransitGatewayPolicyTableId] - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - State: Optional[TransitGatewayAssociationState] + TransitGatewayPolicyTableId: TransitGatewayPolicyTableId | None + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + State: TransitGatewayAssociationState | None class AssociateTransitGatewayPolicyTableResult(TypedDict, total=False): - Association: Optional[TransitGatewayPolicyTableAssociation] + Association: TransitGatewayPolicyTableAssociation | None class AssociateTransitGatewayRouteTableRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class TransitGatewayAssociation(TypedDict, total=False): - TransitGatewayRouteTableId: Optional[TransitGatewayRouteTableId] - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - State: Optional[TransitGatewayAssociationState] + TransitGatewayRouteTableId: TransitGatewayRouteTableId | None + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + State: TransitGatewayAssociationState | None class AssociateTransitGatewayRouteTableResult(TypedDict, total=False): - Association: Optional[TransitGatewayAssociation] + Association: TransitGatewayAssociation | None class AssociateTrunkInterfaceRequest(ServiceRequest): BranchInterfaceId: NetworkInterfaceId TrunkInterfaceId: NetworkInterfaceId - VlanId: Optional[Integer] - GreKey: Optional[Integer] - ClientToken: Optional[String] - DryRun: Optional[Boolean] + VlanId: Integer | None + GreKey: Integer | None + ClientToken: String | None + DryRun: Boolean | None class TrunkInterfaceAssociation(TypedDict, total=False): - AssociationId: Optional[TrunkInterfaceAssociationId] - BranchInterfaceId: Optional[String] - TrunkInterfaceId: Optional[String] - InterfaceProtocol: Optional[InterfaceProtocolType] - VlanId: Optional[Integer] - GreKey: Optional[Integer] - Tags: Optional[TagList] + AssociationId: TrunkInterfaceAssociationId | None + BranchInterfaceId: String | None + TrunkInterfaceId: String | None + InterfaceProtocol: InterfaceProtocolType | None + VlanId: Integer | None + GreKey: Integer | None + Tags: TagList | None class AssociateTrunkInterfaceResult(TypedDict, total=False): - InterfaceAssociation: Optional[TrunkInterfaceAssociation] - ClientToken: Optional[String] + InterfaceAssociation: TrunkInterfaceAssociation | None + ClientToken: String | None class AssociateVpcCidrBlockRequest(ServiceRequest): - CidrBlock: Optional[String] - Ipv6CidrBlockNetworkBorderGroup: Optional[String] - Ipv6Pool: Optional[Ipv6PoolEc2Id] - Ipv6CidrBlock: Optional[String] - Ipv4IpamPoolId: Optional[IpamPoolId] - Ipv4NetmaskLength: Optional[NetmaskLength] - Ipv6IpamPoolId: Optional[IpamPoolId] - Ipv6NetmaskLength: Optional[NetmaskLength] + CidrBlock: String | None + Ipv6CidrBlockNetworkBorderGroup: String | None + Ipv6Pool: Ipv6PoolEc2Id | None + Ipv6CidrBlock: String | None + Ipv4IpamPoolId: IpamPoolId | None + Ipv4NetmaskLength: NetmaskLength | None + Ipv6IpamPoolId: IpamPoolId | None + Ipv6NetmaskLength: NetmaskLength | None VpcId: VpcId - AmazonProvidedIpv6CidrBlock: Optional[Boolean] + AmazonProvidedIpv6CidrBlock: Boolean | None class VpcCidrBlockState(TypedDict, total=False): - State: Optional[VpcCidrBlockStateCode] - StatusMessage: Optional[String] + State: VpcCidrBlockStateCode | None + StatusMessage: String | None class VpcCidrBlockAssociation(TypedDict, total=False): - AssociationId: Optional[String] - CidrBlock: Optional[String] - CidrBlockState: Optional[VpcCidrBlockState] + AssociationId: String | None + CidrBlock: String | None + CidrBlockState: VpcCidrBlockState | None class VpcIpv6CidrBlockAssociation(TypedDict, total=False): - AssociationId: Optional[String] - Ipv6CidrBlock: Optional[String] - Ipv6CidrBlockState: Optional[VpcCidrBlockState] - NetworkBorderGroup: Optional[String] - Ipv6Pool: Optional[String] - Ipv6AddressAttribute: Optional[Ipv6AddressAttribute] - IpSource: Optional[IpSource] + AssociationId: String | None + Ipv6CidrBlock: String | None + Ipv6CidrBlockState: VpcCidrBlockState | None + NetworkBorderGroup: String | None + Ipv6Pool: String | None + Ipv6AddressAttribute: Ipv6AddressAttribute | None + IpSource: IpSource | None class AssociateVpcCidrBlockResult(TypedDict, total=False): - Ipv6CidrBlockAssociation: Optional[VpcIpv6CidrBlockAssociation] - CidrBlockAssociation: Optional[VpcCidrBlockAssociation] - VpcId: Optional[String] + Ipv6CidrBlockAssociation: VpcIpv6CidrBlockAssociation | None + CidrBlockAssociation: VpcCidrBlockAssociation | None + VpcId: String | None class AssociatedRole(TypedDict, total=False): - AssociatedRoleArn: Optional[ResourceArn] - CertificateS3BucketName: Optional[String] - CertificateS3ObjectKey: Optional[String] - EncryptionKmsKeyId: Optional[String] + AssociatedRoleArn: ResourceArn | None + CertificateS3BucketName: String | None + CertificateS3ObjectKey: String | None + EncryptionKmsKeyId: String | None -AssociatedRolesList = List[AssociatedRole] -AssociatedSubnetList = List[SubnetId] +AssociatedRolesList = list[AssociatedRole] +AssociatedSubnetList = list[SubnetId] class AssociatedTargetNetwork(TypedDict, total=False): - NetworkId: Optional[String] - NetworkType: Optional[AssociatedNetworkType] + NetworkId: String | None + NetworkType: AssociatedNetworkType | None -AssociatedTargetNetworkSet = List[AssociatedTargetNetwork] -AssociationIdList = List[IamInstanceProfileAssociationId] +AssociatedTargetNetworkSet = list[AssociatedTargetNetwork] +AssociationIdList = list[IamInstanceProfileAssociationId] class AthenaIntegration(TypedDict, total=False): IntegrationResultS3DestinationArn: String PartitionLoadFrequency: PartitionLoadFrequency - PartitionStartDate: Optional[MillisecondDateTime] - PartitionEndDate: Optional[MillisecondDateTime] + PartitionStartDate: MillisecondDateTime | None + PartitionEndDate: MillisecondDateTime | None -AthenaIntegrationsSet = List[AthenaIntegration] -GroupIdStringList = List[SecurityGroupId] +AthenaIntegrationsSet = list[AthenaIntegration] +GroupIdStringList = list[SecurityGroupId] class AttachClassicLinkVpcRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId VpcId: VpcId Groups: GroupIdStringList class AttachClassicLinkVpcResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class AttachInternetGatewayRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InternetGatewayId: InternetGatewayId VpcId: VpcId class EnaSrdUdpSpecification(TypedDict, total=False): - EnaSrdUdpEnabled: Optional[Boolean] + EnaSrdUdpEnabled: Boolean | None class EnaSrdSpecification(TypedDict, total=False): - EnaSrdEnabled: Optional[Boolean] - EnaSrdUdpSpecification: Optional[EnaSrdUdpSpecification] + EnaSrdEnabled: Boolean | None + EnaSrdUdpSpecification: EnaSrdUdpSpecification | None class AttachNetworkInterfaceRequest(ServiceRequest): - NetworkCardIndex: Optional[Integer] - EnaSrdSpecification: Optional[EnaSrdSpecification] - EnaQueueCount: Optional[Integer] - DryRun: Optional[Boolean] + NetworkCardIndex: Integer | None + EnaSrdSpecification: EnaSrdSpecification | None + EnaQueueCount: Integer | None + DryRun: Boolean | None NetworkInterfaceId: NetworkInterfaceId InstanceId: InstanceId DeviceIndex: Integer class AttachNetworkInterfaceResult(TypedDict, total=False): - AttachmentId: Optional[String] - NetworkCardIndex: Optional[Integer] + AttachmentId: String | None + NetworkCardIndex: Integer | None class AttachVerifiedAccessTrustProviderRequest(ServiceRequest): VerifiedAccessInstanceId: VerifiedAccessInstanceId VerifiedAccessTrustProviderId: VerifiedAccessTrustProviderId - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None class VerifiedAccessInstanceCustomSubDomain(TypedDict, total=False): - SubDomain: Optional[String] - Nameservers: Optional[ValueStringList] + SubDomain: String | None + Nameservers: ValueStringList | None class VerifiedAccessTrustProviderCondensed(TypedDict, total=False): - VerifiedAccessTrustProviderId: Optional[String] - Description: Optional[String] - TrustProviderType: Optional[TrustProviderType] - UserTrustProviderType: Optional[UserTrustProviderType] - DeviceTrustProviderType: Optional[DeviceTrustProviderType] + VerifiedAccessTrustProviderId: String | None + Description: String | None + TrustProviderType: TrustProviderType | None + UserTrustProviderType: UserTrustProviderType | None + DeviceTrustProviderType: DeviceTrustProviderType | None -VerifiedAccessTrustProviderCondensedList = List[VerifiedAccessTrustProviderCondensed] +VerifiedAccessTrustProviderCondensedList = list[VerifiedAccessTrustProviderCondensed] class VerifiedAccessInstance(TypedDict, total=False): - VerifiedAccessInstanceId: Optional[String] - Description: Optional[String] - VerifiedAccessTrustProviders: Optional[VerifiedAccessTrustProviderCondensedList] - CreationTime: Optional[String] - LastUpdatedTime: Optional[String] - Tags: Optional[TagList] - FipsEnabled: Optional[Boolean] - CidrEndpointsCustomSubDomain: Optional[VerifiedAccessInstanceCustomSubDomain] + VerifiedAccessInstanceId: String | None + Description: String | None + VerifiedAccessTrustProviders: VerifiedAccessTrustProviderCondensedList | None + CreationTime: String | None + LastUpdatedTime: String | None + Tags: TagList | None + FipsEnabled: Boolean | None + CidrEndpointsCustomSubDomain: VerifiedAccessInstanceCustomSubDomain | None class NativeApplicationOidcOptions(TypedDict, total=False): - PublicSigningKeyEndpoint: Optional[String] - Issuer: Optional[String] - AuthorizationEndpoint: Optional[String] - TokenEndpoint: Optional[String] - UserInfoEndpoint: Optional[String] - ClientId: Optional[String] - Scope: Optional[String] + PublicSigningKeyEndpoint: String | None + Issuer: String | None + AuthorizationEndpoint: String | None + TokenEndpoint: String | None + UserInfoEndpoint: String | None + ClientId: String | None + Scope: String | None class VerifiedAccessSseSpecificationResponse(TypedDict, total=False): - CustomerManagedKeyEnabled: Optional[Boolean] - KmsKeyArn: Optional[KmsKeyArn] + CustomerManagedKeyEnabled: Boolean | None + KmsKeyArn: KmsKeyArn | None class DeviceOptions(TypedDict, total=False): - TenantId: Optional[String] - PublicSigningKeyUrl: Optional[String] + TenantId: String | None + PublicSigningKeyUrl: String | None class OidcOptions(TypedDict, total=False): - Issuer: Optional[String] - AuthorizationEndpoint: Optional[String] - TokenEndpoint: Optional[String] - UserInfoEndpoint: Optional[String] - ClientId: Optional[String] - ClientSecret: Optional[ClientSecretType] - Scope: Optional[String] + Issuer: String | None + AuthorizationEndpoint: String | None + TokenEndpoint: String | None + UserInfoEndpoint: String | None + ClientId: String | None + ClientSecret: ClientSecretType | None + Scope: String | None class VerifiedAccessTrustProvider(TypedDict, total=False): - VerifiedAccessTrustProviderId: Optional[String] - Description: Optional[String] - TrustProviderType: Optional[TrustProviderType] - UserTrustProviderType: Optional[UserTrustProviderType] - DeviceTrustProviderType: Optional[DeviceTrustProviderType] - OidcOptions: Optional[OidcOptions] - DeviceOptions: Optional[DeviceOptions] - PolicyReferenceName: Optional[String] - CreationTime: Optional[String] - LastUpdatedTime: Optional[String] - Tags: Optional[TagList] - SseSpecification: Optional[VerifiedAccessSseSpecificationResponse] - NativeApplicationOidcOptions: Optional[NativeApplicationOidcOptions] + VerifiedAccessTrustProviderId: String | None + Description: String | None + TrustProviderType: TrustProviderType | None + UserTrustProviderType: UserTrustProviderType | None + DeviceTrustProviderType: DeviceTrustProviderType | None + OidcOptions: OidcOptions | None + DeviceOptions: DeviceOptions | None + PolicyReferenceName: String | None + CreationTime: String | None + LastUpdatedTime: String | None + Tags: TagList | None + SseSpecification: VerifiedAccessSseSpecificationResponse | None + NativeApplicationOidcOptions: NativeApplicationOidcOptions | None class AttachVerifiedAccessTrustProviderResult(TypedDict, total=False): - VerifiedAccessTrustProvider: Optional[VerifiedAccessTrustProvider] - VerifiedAccessInstance: Optional[VerifiedAccessInstance] + VerifiedAccessTrustProvider: VerifiedAccessTrustProvider | None + VerifiedAccessInstance: VerifiedAccessInstance | None class AttachVolumeRequest(ServiceRequest): Device: String InstanceId: InstanceId VolumeId: VolumeId - DryRun: Optional[Boolean] + EbsCardIndex: BoxedInteger | None + DryRun: Boolean | None class AttachVpnGatewayRequest(ServiceRequest): VpcId: VpcId VpnGatewayId: VpnGatewayId - DryRun: Optional[Boolean] + DryRun: Boolean | None class VpcAttachment(TypedDict, total=False): - VpcId: Optional[String] - State: Optional[AttachmentStatus] + VpcId: String | None + State: AttachmentStatus | None class AttachVpnGatewayResult(TypedDict, total=False): - VpcAttachment: Optional[VpcAttachment] + VpcAttachment: VpcAttachment | None class AttachmentEnaSrdUdpSpecification(TypedDict, total=False): - EnaSrdUdpEnabled: Optional[Boolean] + EnaSrdUdpEnabled: Boolean | None class AttachmentEnaSrdSpecification(TypedDict, total=False): - EnaSrdEnabled: Optional[Boolean] - EnaSrdUdpSpecification: Optional[AttachmentEnaSrdUdpSpecification] + EnaSrdEnabled: Boolean | None + EnaSrdUdpSpecification: AttachmentEnaSrdUdpSpecification | None class AttributeBooleanValue(TypedDict, total=False): - Value: Optional[Boolean] + Value: Boolean | None class RegionalSummary(TypedDict, total=False): - RegionName: Optional[String] - NumberOfMatchedAccounts: Optional[Integer] - NumberOfUnmatchedAccounts: Optional[Integer] + RegionName: String | None + NumberOfMatchedAccounts: Integer | None + NumberOfUnmatchedAccounts: Integer | None -RegionalSummaryList = List[RegionalSummary] +RegionalSummaryList = list[RegionalSummary] class AttributeSummary(TypedDict, total=False): - AttributeName: Optional[String] - MostFrequentValue: Optional[String] - NumberOfMatchedAccounts: Optional[Integer] - NumberOfUnmatchedAccounts: Optional[Integer] - RegionalSummaries: Optional[RegionalSummaryList] + AttributeName: String | None + MostFrequentValue: String | None + NumberOfMatchedAccounts: Integer | None + NumberOfUnmatchedAccounts: Integer | None + RegionalSummaries: RegionalSummaryList | None -AttributeSummaryList = List[AttributeSummary] +AttributeSummaryList = list[AttributeSummary] class AttributeValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None class ClientVpnAuthorizationRuleStatus(TypedDict, total=False): - Code: Optional[ClientVpnAuthorizationRuleStatusCode] - Message: Optional[String] + Code: ClientVpnAuthorizationRuleStatusCode | None + Message: String | None class AuthorizationRule(TypedDict, total=False): - ClientVpnEndpointId: Optional[String] - Description: Optional[String] - GroupId: Optional[String] - AccessAll: Optional[Boolean] - DestinationCidr: Optional[String] - Status: Optional[ClientVpnAuthorizationRuleStatus] + ClientVpnEndpointId: String | None + Description: String | None + GroupId: String | None + AccessAll: Boolean | None + DestinationCidr: String | None + Status: ClientVpnAuthorizationRuleStatus | None -AuthorizationRuleSet = List[AuthorizationRule] +AuthorizationRuleSet = list[AuthorizationRule] class AuthorizeClientVpnIngressRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId TargetNetworkCidr: String - AccessGroupId: Optional[String] - AuthorizeAllGroups: Optional[Boolean] - Description: Optional[String] - ClientToken: Optional[String] - DryRun: Optional[Boolean] + AccessGroupId: String | None + AuthorizeAllGroups: Boolean | None + Description: String | None + ClientToken: String | None + DryRun: Boolean | None class AuthorizeClientVpnIngressResult(TypedDict, total=False): - Status: Optional[ClientVpnAuthorizationRuleStatus] + Status: ClientVpnAuthorizationRuleStatus | None class PrefixListId(TypedDict, total=False): - Description: Optional[String] - PrefixListId: Optional[String] + Description: String | None + PrefixListId: String | None -PrefixListIdList = List[PrefixListId] +PrefixListIdList = list[PrefixListId] class Ipv6Range(TypedDict, total=False): - Description: Optional[String] - CidrIpv6: Optional[String] + Description: String | None + CidrIpv6: String | None -Ipv6RangeList = List[Ipv6Range] +Ipv6RangeList = list[Ipv6Range] class IpRange(TypedDict, total=False): - Description: Optional[String] - CidrIp: Optional[String] + Description: String | None + CidrIp: String | None -IpRangeList = List[IpRange] +IpRangeList = list[IpRange] class UserIdGroupPair(TypedDict, total=False): - Description: Optional[String] - UserId: Optional[String] - GroupName: Optional[String] - GroupId: Optional[String] - VpcId: Optional[String] - VpcPeeringConnectionId: Optional[String] - PeeringStatus: Optional[String] + Description: String | None + UserId: String | None + GroupName: String | None + GroupId: String | None + VpcId: String | None + VpcPeeringConnectionId: String | None + PeeringStatus: String | None -UserIdGroupPairList = List[UserIdGroupPair] +UserIdGroupPairList = list[UserIdGroupPair] class IpPermission(TypedDict, total=False): - IpProtocol: Optional[String] - FromPort: Optional[Integer] - ToPort: Optional[Integer] - UserIdGroupPairs: Optional[UserIdGroupPairList] - IpRanges: Optional[IpRangeList] - Ipv6Ranges: Optional[Ipv6RangeList] - PrefixListIds: Optional[PrefixListIdList] + IpProtocol: String | None + FromPort: Integer | None + ToPort: Integer | None + UserIdGroupPairs: UserIdGroupPairList | None + IpRanges: IpRangeList | None + Ipv6Ranges: Ipv6RangeList | None + PrefixListIds: PrefixListIdList | None -IpPermissionList = List[IpPermission] +IpPermissionList = list[IpPermission] class AuthorizeSecurityGroupEgressRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None GroupId: SecurityGroupId - SourceSecurityGroupName: Optional[String] - SourceSecurityGroupOwnerId: Optional[String] - IpProtocol: Optional[String] - FromPort: Optional[Integer] - ToPort: Optional[Integer] - CidrIp: Optional[String] - IpPermissions: Optional[IpPermissionList] + SourceSecurityGroupName: String | None + SourceSecurityGroupOwnerId: String | None + IpProtocol: String | None + FromPort: Integer | None + ToPort: Integer | None + CidrIp: String | None + IpPermissions: IpPermissionList | None class ReferencedSecurityGroup(TypedDict, total=False): - GroupId: Optional[String] - PeeringStatus: Optional[String] - UserId: Optional[String] - VpcId: Optional[String] - VpcPeeringConnectionId: Optional[String] + GroupId: String | None + PeeringStatus: String | None + UserId: String | None + VpcId: String | None + VpcPeeringConnectionId: String | None class SecurityGroupRule(TypedDict, total=False): - SecurityGroupRuleId: Optional[SecurityGroupRuleId] - GroupId: Optional[SecurityGroupId] - GroupOwnerId: Optional[String] - IsEgress: Optional[Boolean] - IpProtocol: Optional[String] - FromPort: Optional[Integer] - ToPort: Optional[Integer] - CidrIpv4: Optional[String] - CidrIpv6: Optional[String] - PrefixListId: Optional[PrefixListResourceId] - ReferencedGroupInfo: Optional[ReferencedSecurityGroup] - Description: Optional[String] - Tags: Optional[TagList] - SecurityGroupRuleArn: Optional[String] + SecurityGroupRuleId: SecurityGroupRuleId | None + GroupId: SecurityGroupId | None + GroupOwnerId: String | None + IsEgress: Boolean | None + IpProtocol: String | None + FromPort: Integer | None + ToPort: Integer | None + CidrIpv4: String | None + CidrIpv6: String | None + PrefixListId: PrefixListResourceId | None + ReferencedGroupInfo: ReferencedSecurityGroup | None + Description: String | None + Tags: TagList | None + SecurityGroupRuleArn: String | None -SecurityGroupRuleList = List[SecurityGroupRule] +SecurityGroupRuleList = list[SecurityGroupRule] class AuthorizeSecurityGroupEgressResult(TypedDict, total=False): - Return: Optional[Boolean] - SecurityGroupRules: Optional[SecurityGroupRuleList] + Return: Boolean | None + SecurityGroupRules: SecurityGroupRuleList | None class AuthorizeSecurityGroupIngressRequest(ServiceRequest): - CidrIp: Optional[String] - FromPort: Optional[Integer] - GroupId: Optional[SecurityGroupId] - GroupName: Optional[SecurityGroupName] - IpPermissions: Optional[IpPermissionList] - IpProtocol: Optional[String] - SourceSecurityGroupName: Optional[String] - SourceSecurityGroupOwnerId: Optional[String] - ToPort: Optional[Integer] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + CidrIp: String | None + FromPort: Integer | None + GroupId: SecurityGroupId | None + GroupName: SecurityGroupName | None + IpPermissions: IpPermissionList | None + IpProtocol: String | None + SourceSecurityGroupName: String | None + SourceSecurityGroupOwnerId: String | None + ToPort: Integer | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class AuthorizeSecurityGroupIngressResult(TypedDict, total=False): - Return: Optional[Boolean] - SecurityGroupRules: Optional[SecurityGroupRuleList] + Return: Boolean | None + SecurityGroupRules: SecurityGroupRuleList | None -class AvailabilityZoneMessage(TypedDict, total=False): - Message: Optional[String] +class AvailabilityZoneSubGeography(TypedDict, total=False): + Name: String | None -AvailabilityZoneMessageList = List[AvailabilityZoneMessage] +AvailabilityZoneSubGeographyList = list[AvailabilityZoneSubGeography] -class AvailabilityZone(TypedDict, total=False): - OptInStatus: Optional[AvailabilityZoneOptInStatus] - Messages: Optional[AvailabilityZoneMessageList] - RegionName: Optional[String] - ZoneName: Optional[String] - ZoneId: Optional[String] - GroupName: Optional[String] - NetworkBorderGroup: Optional[String] - ZoneType: Optional[String] - ParentZoneName: Optional[String] - ParentZoneId: Optional[String] - GroupLongName: Optional[String] - State: Optional[AvailabilityZoneState] +class AvailabilityZoneGeography(TypedDict, total=False): + Name: String | None -AvailabilityZoneList = List[AvailabilityZone] -AvailabilityZoneStringList = List[String] +AvailabilityZoneGeographyList = list[AvailabilityZoneGeography] -class InstanceCapacity(TypedDict, total=False): - AvailableCapacity: Optional[Integer] - InstanceType: Optional[String] - TotalCapacity: Optional[Integer] +class AvailabilityZoneMessage(TypedDict, total=False): + Message: String | None -AvailableInstanceCapacityList = List[InstanceCapacity] +AvailabilityZoneMessageList = list[AvailabilityZoneMessage] -class AvailableCapacity(TypedDict, total=False): - AvailableInstanceCapacity: Optional[AvailableInstanceCapacityList] - AvailableVCpus: Optional[Integer] +class AvailabilityZone(TypedDict, total=False): + OptInStatus: AvailabilityZoneOptInStatus | None + Messages: AvailabilityZoneMessageList | None + RegionName: String | None + ZoneName: String | None + ZoneId: String | None + GroupName: String | None + NetworkBorderGroup: String | None + ZoneType: String | None + ParentZoneName: String | None + ParentZoneId: String | None + GroupLongName: String | None + Geography: AvailabilityZoneGeographyList | None + SubGeography: AvailabilityZoneSubGeographyList | None + State: AvailabilityZoneState | None + + +class AvailabilityZoneAddress(TypedDict, total=False): + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None + AllocationIds: AllocationIdList | None + + +AvailabilityZoneAddresses = list[AvailabilityZoneAddress] +AvailabilityZoneIdStringList = list[String] +AvailabilityZoneList = list[AvailabilityZone] +AvailabilityZoneStringList = list[String] + + +class InstanceCapacity(TypedDict, total=False): + AvailableCapacity: Integer | None + InstanceType: String | None + TotalCapacity: Integer | None + + +AvailableInstanceCapacityList = list[InstanceCapacity] + + +class AvailableCapacity(TypedDict, total=False): + AvailableInstanceCapacity: AvailableInstanceCapacityList | None + AvailableVCpus: Integer | None -BandwidthWeightingTypeList = List[BandwidthWeightingType] +BandwidthWeightingTypeList = list[BandwidthWeightingType] class BaselineEbsBandwidthMbps(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None class BaselineEbsBandwidthMbpsRequest(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None class PerformanceFactorReference(TypedDict, total=False): - InstanceFamily: Optional[String] + InstanceFamily: String | None -PerformanceFactorReferenceSet = List[PerformanceFactorReference] +PerformanceFactorReferenceSet = list[PerformanceFactorReference] class CpuPerformanceFactor(TypedDict, total=False): - References: Optional[PerformanceFactorReferenceSet] + References: PerformanceFactorReferenceSet | None class BaselinePerformanceFactors(TypedDict, total=False): - Cpu: Optional[CpuPerformanceFactor] + Cpu: CpuPerformanceFactor | None class PerformanceFactorReferenceRequest(TypedDict, total=False): - InstanceFamily: Optional[String] + InstanceFamily: String | None -PerformanceFactorReferenceSetRequest = List[PerformanceFactorReferenceRequest] +PerformanceFactorReferenceSetRequest = list[PerformanceFactorReferenceRequest] class CpuPerformanceFactorRequest(TypedDict, total=False): - References: Optional[PerformanceFactorReferenceSetRequest] + References: PerformanceFactorReferenceSetRequest | None class BaselinePerformanceFactorsRequest(TypedDict, total=False): - Cpu: Optional[CpuPerformanceFactorRequest] + Cpu: CpuPerformanceFactorRequest | None -BillingProductList = List[String] +BillingProductList = list[String] Blob = bytes class BlobAttributeValue(TypedDict, total=False): - Value: Optional[Blob] + Value: Blob | None class EbsBlockDevice(TypedDict, total=False): - DeleteOnTermination: Optional[Boolean] - Iops: Optional[Integer] - SnapshotId: Optional[SnapshotId] - VolumeSize: Optional[Integer] - VolumeType: Optional[VolumeType] - KmsKeyId: Optional[String] - Throughput: Optional[Integer] - OutpostArn: Optional[String] - AvailabilityZone: Optional[String] - Encrypted: Optional[Boolean] - VolumeInitializationRate: Optional[Integer] - AvailabilityZoneId: Optional[String] + DeleteOnTermination: Boolean | None + Iops: Integer | None + SnapshotId: SnapshotId | None + VolumeSize: Integer | None + VolumeType: VolumeType | None + KmsKeyId: String | None + Throughput: Integer | None + OutpostArn: String | None + AvailabilityZone: String | None + Encrypted: Boolean | None + VolumeInitializationRate: Integer | None + AvailabilityZoneId: String | None + EbsCardIndex: Integer | None class BlockDeviceMapping(TypedDict, total=False): - Ebs: Optional[EbsBlockDevice] - NoDevice: Optional[String] - DeviceName: Optional[String] - VirtualName: Optional[String] + Ebs: EbsBlockDevice | None + NoDevice: String | None + DeviceName: String | None + VirtualName: String | None -BlockDeviceMappingList = List[BlockDeviceMapping] -BlockDeviceMappingRequestList = List[BlockDeviceMapping] +BlockDeviceMappingList = list[BlockDeviceMapping] +BlockDeviceMappingRequestList = list[BlockDeviceMapping] class EbsBlockDeviceResponse(TypedDict, total=False): - Encrypted: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Iops: Optional[Integer] - Throughput: Optional[Integer] - KmsKeyId: Optional[KmsKeyId] - SnapshotId: Optional[SnapshotId] - VolumeSize: Optional[Integer] - VolumeType: Optional[VolumeType] + Encrypted: Boolean | None + DeleteOnTermination: Boolean | None + Iops: Integer | None + Throughput: Integer | None + KmsKeyId: KmsKeyId | None + SnapshotId: SnapshotId | None + VolumeSize: Integer | None + VolumeType: VolumeType | None class BlockDeviceMappingResponse(TypedDict, total=False): - DeviceName: Optional[String] - VirtualName: Optional[String] - Ebs: Optional[EbsBlockDeviceResponse] - NoDevice: Optional[String] + DeviceName: String | None + VirtualName: String | None + Ebs: EbsBlockDeviceResponse | None + NoDevice: String | None -BlockDeviceMappingResponseList = List[BlockDeviceMappingResponse] +BlockDeviceMappingResponseList = list[BlockDeviceMappingResponse] class BlockPublicAccessStates(TypedDict, total=False): - InternetGatewayBlockMode: Optional[BlockPublicAccessMode] + InternetGatewayBlockMode: BlockPublicAccessMode | None -BootModeTypeList = List[BootModeType] +BootModeTypeList = list[BootModeType] BoxedLong = int -BundleIdStringList = List[BundleId] +BundleIdStringList = list[BundleId] class S3Storage(TypedDict, total=False): - AWSAccessKeyId: Optional[String] - Bucket: Optional[String] - Prefix: Optional[String] - UploadPolicy: Optional[Blob] - UploadPolicySignature: Optional[S3StorageUploadPolicySignature] + AWSAccessKeyId: String | None + Bucket: String | None + Prefix: String | None + UploadPolicy: Blob | None + UploadPolicySignature: S3StorageUploadPolicySignature | None class Storage(TypedDict, total=False): - S3: Optional[S3Storage] + S3: S3Storage | None class BundleInstanceRequest(ServiceRequest): InstanceId: InstanceId Storage: Storage - DryRun: Optional[Boolean] + DryRun: Boolean | None class BundleTaskError(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None class BundleTask(TypedDict, total=False): - InstanceId: Optional[String] - BundleId: Optional[String] - State: Optional[BundleTaskState] - StartTime: Optional[DateTime] - UpdateTime: Optional[DateTime] - Storage: Optional[Storage] - Progress: Optional[String] - BundleTaskError: Optional[BundleTaskError] + InstanceId: String | None + BundleId: String | None + State: BundleTaskState | None + StartTime: DateTime | None + UpdateTime: DateTime | None + Storage: Storage | None + Progress: String | None + BundleTaskError: BundleTaskError | None class BundleInstanceResult(TypedDict, total=False): - BundleTask: Optional[BundleTask] + BundleTask: BundleTask | None -BundleTaskList = List[BundleTask] +BundleTaskList = list[BundleTask] class Byoasn(TypedDict, total=False): - Asn: Optional[String] - IpamId: Optional[IpamId] - StatusMessage: Optional[String] - State: Optional[AsnState] + Asn: String | None + IpamId: IpamId | None + StatusMessage: String | None + State: AsnState | None -ByoasnSet = List[Byoasn] -ByoipCidrSet = List[ByoipCidr] +ByoasnSet = list[Byoasn] +ByoipCidrSet = list[ByoipCidr] class CancelBundleTaskRequest(ServiceRequest): BundleId: BundleId - DryRun: Optional[Boolean] + DryRun: Boolean | None class CancelBundleTaskResult(TypedDict, total=False): - BundleTask: Optional[BundleTask] + BundleTask: BundleTask | None class CancelCapacityReservationFleetError(TypedDict, total=False): - Code: Optional[CancelCapacityReservationFleetErrorCode] - Message: Optional[CancelCapacityReservationFleetErrorMessage] + Code: CancelCapacityReservationFleetErrorCode | None + Message: CancelCapacityReservationFleetErrorMessage | None -CapacityReservationFleetIdSet = List[CapacityReservationFleetId] +CapacityReservationFleetIdSet = list[CapacityReservationFleetId] class CancelCapacityReservationFleetsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None CapacityReservationFleetIds: CapacityReservationFleetIdSet class FailedCapacityReservationFleetCancellationResult(TypedDict, total=False): - CapacityReservationFleetId: Optional[CapacityReservationFleetId] - CancelCapacityReservationFleetError: Optional[CancelCapacityReservationFleetError] + CapacityReservationFleetId: CapacityReservationFleetId | None + CancelCapacityReservationFleetError: CancelCapacityReservationFleetError | None -FailedCapacityReservationFleetCancellationResultSet = List[ +FailedCapacityReservationFleetCancellationResultSet = list[ FailedCapacityReservationFleetCancellationResult ] class CapacityReservationFleetCancellationState(TypedDict, total=False): - CurrentFleetState: Optional[CapacityReservationFleetState] - PreviousFleetState: Optional[CapacityReservationFleetState] - CapacityReservationFleetId: Optional[CapacityReservationFleetId] + CurrentFleetState: CapacityReservationFleetState | None + PreviousFleetState: CapacityReservationFleetState | None + CapacityReservationFleetId: CapacityReservationFleetId | None -CapacityReservationFleetCancellationStateSet = List[CapacityReservationFleetCancellationState] +CapacityReservationFleetCancellationStateSet = list[CapacityReservationFleetCancellationState] class CancelCapacityReservationFleetsResult(TypedDict, total=False): - SuccessfulFleetCancellations: Optional[CapacityReservationFleetCancellationStateSet] - FailedFleetCancellations: Optional[FailedCapacityReservationFleetCancellationResultSet] + SuccessfulFleetCancellations: CapacityReservationFleetCancellationStateSet | None + FailedFleetCancellations: FailedCapacityReservationFleetCancellationResultSet | None class CancelCapacityReservationRequest(ServiceRequest): CapacityReservationId: CapacityReservationId - DryRun: Optional[Boolean] + DryRun: Boolean | None class CancelCapacityReservationResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class CancelConversionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ConversionTaskId: ConversionTaskId - ReasonMessage: Optional[String] + ReasonMessage: String | None class CancelDeclarativePoliciesReportRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ReportId: DeclarativePoliciesReportId class CancelDeclarativePoliciesReportResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class CancelExportTaskRequest(ServiceRequest): @@ -5890,23 +6704,23 @@ class CancelExportTaskRequest(ServiceRequest): class CancelImageLaunchPermissionRequest(ServiceRequest): ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None class CancelImageLaunchPermissionResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class CancelImportTaskRequest(ServiceRequest): - CancelReason: Optional[String] - DryRun: Optional[Boolean] - ImportTaskId: Optional[ImportTaskId] + CancelReason: String | None + DryRun: Boolean | None + ImportTaskId: ImportTaskId | None class CancelImportTaskResult(TypedDict, total=False): - ImportTaskId: Optional[String] - PreviousState: Optional[String] - State: Optional[String] + ImportTaskId: String | None + PreviousState: String | None + State: String | None class CancelReservedInstancesListingRequest(ServiceRequest): @@ -5917,346 +6731,434 @@ class CancelReservedInstancesListingRequest(ServiceRequest): class PriceSchedule(TypedDict, total=False): - Active: Optional[Boolean] - CurrencyCode: Optional[CurrencyCodeValues] - Price: Optional[Double] - Term: Optional[Long] + Active: Boolean | None + CurrencyCode: CurrencyCodeValues | None + Price: Double | None + Term: Long | None -PriceScheduleList = List[PriceSchedule] +PriceScheduleList = list[PriceSchedule] class InstanceCount(TypedDict, total=False): - InstanceCount: Optional[Integer] - State: Optional[ListingState] + InstanceCount: Integer | None + State: ListingState | None -InstanceCountList = List[InstanceCount] +InstanceCountList = list[InstanceCount] class ReservedInstancesListing(TypedDict, total=False): - ClientToken: Optional[String] - CreateDate: Optional[DateTime] - InstanceCounts: Optional[InstanceCountList] - PriceSchedules: Optional[PriceScheduleList] - ReservedInstancesId: Optional[String] - ReservedInstancesListingId: Optional[String] - Status: Optional[ListingStatus] - StatusMessage: Optional[String] - Tags: Optional[TagList] - UpdateDate: Optional[DateTime] + ClientToken: String | None + CreateDate: DateTime | None + InstanceCounts: InstanceCountList | None + PriceSchedules: PriceScheduleList | None + ReservedInstancesId: String | None + ReservedInstancesListingId: String | None + Status: ListingStatus | None + StatusMessage: String | None + Tags: TagList | None + UpdateDate: DateTime | None -ReservedInstancesListingList = List[ReservedInstancesListing] +ReservedInstancesListingList = list[ReservedInstancesListing] class CancelReservedInstancesListingResult(TypedDict, total=False): - ReservedInstancesListings: Optional[ReservedInstancesListingList] + ReservedInstancesListings: ReservedInstancesListingList | None class CancelSpotFleetRequestsError(TypedDict, total=False): - Code: Optional[CancelBatchErrorCode] - Message: Optional[String] + Code: CancelBatchErrorCode | None + Message: String | None class CancelSpotFleetRequestsErrorItem(TypedDict, total=False): - Error: Optional[CancelSpotFleetRequestsError] - SpotFleetRequestId: Optional[String] + Error: CancelSpotFleetRequestsError | None + SpotFleetRequestId: String | None -CancelSpotFleetRequestsErrorSet = List[CancelSpotFleetRequestsErrorItem] -SpotFleetRequestIdList = List[SpotFleetRequestId] +CancelSpotFleetRequestsErrorSet = list[CancelSpotFleetRequestsErrorItem] +SpotFleetRequestIdList = list[SpotFleetRequestId] class CancelSpotFleetRequestsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None SpotFleetRequestIds: SpotFleetRequestIdList TerminateInstances: Boolean class CancelSpotFleetRequestsSuccessItem(TypedDict, total=False): - CurrentSpotFleetRequestState: Optional[BatchState] - PreviousSpotFleetRequestState: Optional[BatchState] - SpotFleetRequestId: Optional[String] + CurrentSpotFleetRequestState: BatchState | None + PreviousSpotFleetRequestState: BatchState | None + SpotFleetRequestId: String | None -CancelSpotFleetRequestsSuccessSet = List[CancelSpotFleetRequestsSuccessItem] +CancelSpotFleetRequestsSuccessSet = list[CancelSpotFleetRequestsSuccessItem] class CancelSpotFleetRequestsResponse(TypedDict, total=False): - SuccessfulFleetRequests: Optional[CancelSpotFleetRequestsSuccessSet] - UnsuccessfulFleetRequests: Optional[CancelSpotFleetRequestsErrorSet] + SuccessfulFleetRequests: CancelSpotFleetRequestsSuccessSet | None + UnsuccessfulFleetRequests: CancelSpotFleetRequestsErrorSet | None -SpotInstanceRequestIdList = List[SpotInstanceRequestId] +SpotInstanceRequestIdList = list[SpotInstanceRequestId] class CancelSpotInstanceRequestsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None SpotInstanceRequestIds: SpotInstanceRequestIdList class CancelledSpotInstanceRequest(TypedDict, total=False): - SpotInstanceRequestId: Optional[String] - State: Optional[CancelSpotInstanceRequestState] + SpotInstanceRequestId: String | None + State: CancelSpotInstanceRequestState | None -CancelledSpotInstanceRequestList = List[CancelledSpotInstanceRequest] +CancelledSpotInstanceRequestList = list[CancelledSpotInstanceRequest] class CancelSpotInstanceRequestsResult(TypedDict, total=False): - CancelledSpotInstanceRequests: Optional[CancelledSpotInstanceRequestList] + CancelledSpotInstanceRequests: CancelledSpotInstanceRequestList | None class CapacityAllocation(TypedDict, total=False): - AllocationType: Optional[AllocationType] - Count: Optional[Integer] + AllocationType: AllocationType | None + Count: Integer | None -CapacityAllocations = List[CapacityAllocation] -CapacityReservationIdSet = List[CapacityReservationId] +CapacityAllocations = list[CapacityAllocation] +CapacityReservationIdSet = list[CapacityReservationId] class CapacityBlock(TypedDict, total=False): - CapacityBlockId: Optional[CapacityBlockId] - UltraserverType: Optional[String] - AvailabilityZone: Optional[String] - AvailabilityZoneId: Optional[String] - CapacityReservationIds: Optional[CapacityReservationIdSet] - StartDate: Optional[MillisecondDateTime] - EndDate: Optional[MillisecondDateTime] - CreateDate: Optional[MillisecondDateTime] - State: Optional[CapacityBlockResourceState] - Tags: Optional[TagList] + CapacityBlockId: CapacityBlockId | None + UltraserverType: String | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + CapacityReservationIds: CapacityReservationIdSet | None + StartDate: MillisecondDateTime | None + EndDate: MillisecondDateTime | None + CreateDate: MillisecondDateTime | None + State: CapacityBlockResourceState | None + Tags: TagList | None class CapacityBlockExtension(TypedDict, total=False): - CapacityReservationId: Optional[CapacityReservationId] - InstanceType: Optional[String] - InstanceCount: Optional[Integer] - AvailabilityZone: Optional[AvailabilityZoneName] - AvailabilityZoneId: Optional[AvailabilityZoneId] - CapacityBlockExtensionOfferingId: Optional[OfferingId] - CapacityBlockExtensionDurationHours: Optional[Integer] - CapacityBlockExtensionStatus: Optional[CapacityBlockExtensionStatus] - CapacityBlockExtensionPurchaseDate: Optional[MillisecondDateTime] - CapacityBlockExtensionStartDate: Optional[MillisecondDateTime] - CapacityBlockExtensionEndDate: Optional[MillisecondDateTime] - UpfrontFee: Optional[String] - CurrencyCode: Optional[String] + CapacityReservationId: CapacityReservationId | None + InstanceType: String | None + InstanceCount: Integer | None + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None + CapacityBlockExtensionOfferingId: OfferingId | None + CapacityBlockExtensionDurationHours: Integer | None + CapacityBlockExtensionStatus: CapacityBlockExtensionStatus | None + CapacityBlockExtensionPurchaseDate: MillisecondDateTime | None + CapacityBlockExtensionStartDate: MillisecondDateTime | None + CapacityBlockExtensionEndDate: MillisecondDateTime | None + UpfrontFee: String | None + CurrencyCode: String | None + ZoneType: String | None class CapacityBlockExtensionOffering(TypedDict, total=False): - CapacityBlockExtensionOfferingId: Optional[OfferingId] - InstanceType: Optional[String] - InstanceCount: Optional[Integer] - AvailabilityZone: Optional[AvailabilityZoneName] - AvailabilityZoneId: Optional[AvailabilityZoneId] - StartDate: Optional[MillisecondDateTime] - CapacityBlockExtensionStartDate: Optional[MillisecondDateTime] - CapacityBlockExtensionEndDate: Optional[MillisecondDateTime] - CapacityBlockExtensionDurationHours: Optional[Integer] - UpfrontFee: Optional[String] - CurrencyCode: Optional[String] - Tenancy: Optional[CapacityReservationTenancy] + CapacityBlockExtensionOfferingId: OfferingId | None + InstanceType: String | None + InstanceCount: Integer | None + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None + StartDate: MillisecondDateTime | None + CapacityBlockExtensionStartDate: MillisecondDateTime | None + CapacityBlockExtensionEndDate: MillisecondDateTime | None + CapacityBlockExtensionDurationHours: Integer | None + UpfrontFee: String | None + CurrencyCode: String | None + Tenancy: CapacityReservationTenancy | None + ZoneType: String | None + + +CapacityBlockExtensionOfferingSet = list[CapacityBlockExtensionOffering] +CapacityBlockExtensionSet = list[CapacityBlockExtension] +CapacityBlockIds = list[CapacityBlockId] -CapacityBlockExtensionOfferingSet = List[CapacityBlockExtensionOffering] -CapacityBlockExtensionSet = List[CapacityBlockExtension] -CapacityBlockIds = List[CapacityBlockId] +class CapacityBlockOffering(TypedDict, total=False): + CapacityBlockOfferingId: OfferingId | None + InstanceType: String | None + AvailabilityZone: String | None + InstanceCount: Integer | None + StartDate: MillisecondDateTime | None + EndDate: MillisecondDateTime | None + CapacityBlockDurationHours: Integer | None + UpfrontFee: String | None + CurrencyCode: String | None + Tenancy: CapacityReservationTenancy | None + UltraserverType: String | None + UltraserverCount: BoxedInteger | None + CapacityBlockDurationMinutes: Integer | None + ZoneType: String | None + + +CapacityBlockOfferingSet = list[CapacityBlockOffering] +CapacityBlockSet = list[CapacityBlock] -class CapacityBlockOffering(TypedDict, total=False): - CapacityBlockOfferingId: Optional[OfferingId] - InstanceType: Optional[String] - AvailabilityZone: Optional[String] - InstanceCount: Optional[Integer] - StartDate: Optional[MillisecondDateTime] - EndDate: Optional[MillisecondDateTime] - CapacityBlockDurationHours: Optional[Integer] - UpfrontFee: Optional[String] - CurrencyCode: Optional[String] - Tenancy: Optional[CapacityReservationTenancy] - UltraserverType: Optional[String] - UltraserverCount: Optional[BoxedInteger] - CapacityBlockDurationMinutes: Optional[Integer] +class CapacityReservationStatus(TypedDict, total=False): + CapacityReservationId: CapacityReservationId | None + TotalCapacity: Integer | None + TotalAvailableCapacity: Integer | None + TotalUnavailableCapacity: Integer | None -CapacityBlockOfferingSet = List[CapacityBlockOffering] -CapacityBlockSet = List[CapacityBlock] +CapacityReservationStatusSet = list[CapacityReservationStatus] -class CapacityReservationStatus(TypedDict, total=False): - CapacityReservationId: Optional[CapacityReservationId] - TotalCapacity: Optional[Integer] - TotalAvailableCapacity: Optional[Integer] - TotalUnavailableCapacity: Optional[Integer] +class CapacityBlockStatus(TypedDict, total=False): + CapacityBlockId: CapacityBlockId | None + InterconnectStatus: CapacityBlockInterconnectStatus | None + TotalCapacity: Integer | None + TotalAvailableCapacity: Integer | None + TotalUnavailableCapacity: Integer | None + CapacityReservationStatuses: CapacityReservationStatusSet | None -CapacityReservationStatusSet = List[CapacityReservationStatus] +CapacityBlockStatusSet = list[CapacityBlockStatus] +ConditionValueList = list[String] -class CapacityBlockStatus(TypedDict, total=False): - CapacityBlockId: Optional[CapacityBlockId] - InterconnectStatus: Optional[CapacityBlockInterconnectStatus] - TotalCapacity: Optional[Integer] - TotalAvailableCapacity: Optional[Integer] - TotalUnavailableCapacity: Optional[Integer] - CapacityReservationStatuses: Optional[CapacityReservationStatusSet] +class DimensionCondition(TypedDict, total=False): + Dimension: FilterByDimension | None + Comparison: Comparison | None + Values: ConditionValueList | None + +class CapacityManagerCondition(TypedDict, total=False): + DimensionCondition: DimensionCondition | None -CapacityBlockStatusSet = List[CapacityBlockStatus] + +CapacityManagerConditionSet = list[CapacityManagerCondition] +CapacityManagerDataExportIdSet = list[CapacityManagerDataExportId] + + +class CapacityManagerDataExportResponse(TypedDict, total=False): + CapacityManagerDataExportId: CapacityManagerDataExportId | None + S3BucketName: String | None + S3BucketPrefix: String | None + Schedule: Schedule | None + OutputFormat: OutputFormat | None + CreateTime: MillisecondDateTime | None + LatestDeliveryStatus: CapacityManagerDataExportStatus | None + LatestDeliveryStatusMessage: String | None + LatestDeliveryS3LocationUri: String | None + LatestDeliveryTime: MillisecondDateTime | None + Tags: TagList | None + + +CapacityManagerDataExportResponseSet = list[CapacityManagerDataExportResponse] + + +class CapacityManagerDimension(TypedDict, total=False): + ResourceRegion: String | None + AvailabilityZoneId: String | None + AccountId: String | None + InstanceFamily: String | None + InstanceType: String | None + InstancePlatform: String | None + ReservationArn: String | None + ReservationId: String | None + ReservationType: ReservationType | None + ReservationCreateTimestamp: MillisecondDateTime | None + ReservationStartTimestamp: MillisecondDateTime | None + ReservationEndTimestamp: MillisecondDateTime | None + ReservationEndDateType: ReservationEndDateType | None + Tenancy: CapacityTenancy | None + ReservationState: ReservationState | None + ReservationInstanceMatchCriteria: String | None + ReservationUnusedFinancialOwner: String | None + + +class InterruptionInfo(TypedDict, total=False): + SourceCapacityReservationId: String | None + InterruptionType: InterruptionType | None + + +class InterruptibleCapacityAllocation(TypedDict, total=False): + InstanceCount: Integer | None + TargetInstanceCount: Integer | None + Status: InterruptibleCapacityReservationAllocationStatus | None + InterruptibleCapacityReservationId: String | None + InterruptionType: InterruptionType | None class CapacityReservationCommitmentInfo(TypedDict, total=False): - CommittedInstanceCount: Optional[Integer] - CommitmentEndDate: Optional[MillisecondDateTime] + CommittedInstanceCount: Integer | None + CommitmentEndDate: MillisecondDateTime | None class CapacityReservation(TypedDict, total=False): - CapacityReservationId: Optional[String] - OwnerId: Optional[String] - CapacityReservationArn: Optional[String] - AvailabilityZoneId: Optional[String] - InstanceType: Optional[String] - InstancePlatform: Optional[CapacityReservationInstancePlatform] - AvailabilityZone: Optional[String] - Tenancy: Optional[CapacityReservationTenancy] - TotalInstanceCount: Optional[Integer] - AvailableInstanceCount: Optional[Integer] - EbsOptimized: Optional[Boolean] - EphemeralStorage: Optional[Boolean] - State: Optional[CapacityReservationState] - StartDate: Optional[MillisecondDateTime] - EndDate: Optional[DateTime] - EndDateType: Optional[EndDateType] - InstanceMatchCriteria: Optional[InstanceMatchCriteria] - CreateDate: Optional[DateTime] - Tags: Optional[TagList] - OutpostArn: Optional[OutpostArn] - CapacityReservationFleetId: Optional[String] - PlacementGroupArn: Optional[PlacementGroupArn] - CapacityAllocations: Optional[CapacityAllocations] - ReservationType: Optional[CapacityReservationType] - UnusedReservationBillingOwnerId: Optional[AccountID] - CommitmentInfo: Optional[CapacityReservationCommitmentInfo] - DeliveryPreference: Optional[CapacityReservationDeliveryPreference] - CapacityBlockId: Optional[CapacityBlockId] + CapacityReservationId: String | None + OwnerId: String | None + CapacityReservationArn: String | None + AvailabilityZoneId: String | None + InstanceType: String | None + InstancePlatform: CapacityReservationInstancePlatform | None + AvailabilityZone: String | None + Tenancy: CapacityReservationTenancy | None + TotalInstanceCount: Integer | None + AvailableInstanceCount: Integer | None + EbsOptimized: Boolean | None + EphemeralStorage: Boolean | None + State: CapacityReservationState | None + StartDate: MillisecondDateTime | None + EndDate: DateTime | None + EndDateType: EndDateType | None + InstanceMatchCriteria: InstanceMatchCriteria | None + CreateDate: DateTime | None + Tags: TagList | None + OutpostArn: OutpostArn | None + CapacityReservationFleetId: String | None + PlacementGroupArn: PlacementGroupArn | None + CapacityAllocations: CapacityAllocations | None + ReservationType: CapacityReservationType | None + UnusedReservationBillingOwnerId: AccountID | None + CommitmentInfo: CapacityReservationCommitmentInfo | None + DeliveryPreference: CapacityReservationDeliveryPreference | None + CapacityBlockId: CapacityBlockId | None + Interruptible: BoxedBoolean | None + InterruptibleCapacityAllocation: InterruptibleCapacityAllocation | None + InterruptionInfo: InterruptionInfo | None class CapacityReservationInfo(TypedDict, total=False): - InstanceType: Optional[String] - AvailabilityZone: Optional[AvailabilityZoneName] - Tenancy: Optional[CapacityReservationTenancy] - AvailabilityZoneId: Optional[AvailabilityZoneId] + InstanceType: String | None + AvailabilityZone: AvailabilityZoneName | None + Tenancy: CapacityReservationTenancy | None + AvailabilityZoneId: AvailabilityZoneId | None class CapacityReservationBillingRequest(TypedDict, total=False): - CapacityReservationId: Optional[String] - RequestedBy: Optional[String] - UnusedReservationBillingOwnerId: Optional[AccountID] - LastUpdateTime: Optional[MillisecondDateTime] - Status: Optional[CapacityReservationBillingRequestStatus] - StatusMessage: Optional[String] - CapacityReservationInfo: Optional[CapacityReservationInfo] + CapacityReservationId: String | None + RequestedBy: String | None + UnusedReservationBillingOwnerId: AccountID | None + LastUpdateTime: MillisecondDateTime | None + Status: CapacityReservationBillingRequestStatus | None + StatusMessage: String | None + CapacityReservationInfo: CapacityReservationInfo | None -CapacityReservationBillingRequestSet = List[CapacityReservationBillingRequest] +CapacityReservationBillingRequestSet = list[CapacityReservationBillingRequest] CapacityReservationCommitmentDuration = int class FleetCapacityReservation(TypedDict, total=False): - CapacityReservationId: Optional[CapacityReservationId] - AvailabilityZoneId: Optional[String] - InstanceType: Optional[InstanceType] - InstancePlatform: Optional[CapacityReservationInstancePlatform] - AvailabilityZone: Optional[String] - TotalInstanceCount: Optional[Integer] - FulfilledCapacity: Optional[Double] - EbsOptimized: Optional[Boolean] - CreateDate: Optional[MillisecondDateTime] - Weight: Optional[DoubleWithConstraints] - Priority: Optional[IntegerWithConstraints] + CapacityReservationId: CapacityReservationId | None + AvailabilityZoneId: String | None + InstanceType: InstanceType | None + InstancePlatform: CapacityReservationInstancePlatform | None + AvailabilityZone: String | None + TotalInstanceCount: Integer | None + FulfilledCapacity: Double | None + EbsOptimized: Boolean | None + CreateDate: MillisecondDateTime | None + Weight: DoubleWithConstraints | None + Priority: IntegerWithConstraints | None -FleetCapacityReservationSet = List[FleetCapacityReservation] +FleetCapacityReservationSet = list[FleetCapacityReservation] class CapacityReservationFleet(TypedDict, total=False): - CapacityReservationFleetId: Optional[CapacityReservationFleetId] - CapacityReservationFleetArn: Optional[String] - State: Optional[CapacityReservationFleetState] - TotalTargetCapacity: Optional[Integer] - TotalFulfilledCapacity: Optional[Double] - Tenancy: Optional[FleetCapacityReservationTenancy] - EndDate: Optional[MillisecondDateTime] - CreateTime: Optional[MillisecondDateTime] - InstanceMatchCriteria: Optional[FleetInstanceMatchCriteria] - AllocationStrategy: Optional[String] - InstanceTypeSpecifications: Optional[FleetCapacityReservationSet] - Tags: Optional[TagList] + CapacityReservationFleetId: CapacityReservationFleetId | None + CapacityReservationFleetArn: String | None + State: CapacityReservationFleetState | None + TotalTargetCapacity: Integer | None + TotalFulfilledCapacity: Double | None + Tenancy: FleetCapacityReservationTenancy | None + EndDate: MillisecondDateTime | None + CreateTime: MillisecondDateTime | None + InstanceMatchCriteria: FleetInstanceMatchCriteria | None + AllocationStrategy: String | None + InstanceTypeSpecifications: FleetCapacityReservationSet | None + Tags: TagList | None -CapacityReservationFleetSet = List[CapacityReservationFleet] +CapacityReservationFleetSet = list[CapacityReservationFleet] class CapacityReservationGroup(TypedDict, total=False): - GroupArn: Optional[String] - OwnerId: Optional[String] + GroupArn: String | None + OwnerId: String | None -CapacityReservationGroupSet = List[CapacityReservationGroup] +CapacityReservationGroupSet = list[CapacityReservationGroup] class CapacityReservationOptions(TypedDict, total=False): - UsageStrategy: Optional[FleetCapacityReservationUsageStrategy] + UsageStrategy: FleetCapacityReservationUsageStrategy | None class CapacityReservationOptionsRequest(TypedDict, total=False): - UsageStrategy: Optional[FleetCapacityReservationUsageStrategy] + UsageStrategy: FleetCapacityReservationUsageStrategy | None -CapacityReservationSet = List[CapacityReservation] +CapacityReservationSet = list[CapacityReservation] class CapacityReservationTarget(TypedDict, total=False): - CapacityReservationId: Optional[CapacityReservationId] - CapacityReservationResourceGroupArn: Optional[String] + CapacityReservationId: CapacityReservationId | None + CapacityReservationResourceGroupArn: String | None class CapacityReservationSpecification(TypedDict, total=False): - CapacityReservationPreference: Optional[CapacityReservationPreference] - CapacityReservationTarget: Optional[CapacityReservationTarget] + CapacityReservationPreference: CapacityReservationPreference | None + CapacityReservationTarget: CapacityReservationTarget | None class CapacityReservationTargetResponse(TypedDict, total=False): - CapacityReservationId: Optional[String] - CapacityReservationResourceGroupArn: Optional[String] + CapacityReservationId: String | None + CapacityReservationResourceGroupArn: String | None class CapacityReservationSpecificationResponse(TypedDict, total=False): - CapacityReservationPreference: Optional[CapacityReservationPreference] - CapacityReservationTarget: Optional[CapacityReservationTargetResponse] + CapacityReservationPreference: CapacityReservationPreference | None + CapacityReservationTarget: CapacityReservationTargetResponse | None + + +NetworkNodeSet = list[String] + + +class CapacityReservationTopology(TypedDict, total=False): + CapacityReservationId: String | None + CapacityBlockId: String | None + State: String | None + InstanceType: String | None + GroupName: String | None + NetworkNodes: NetworkNodeSet | None + AvailabilityZoneId: String | None + AvailabilityZone: String | None + + +CapacityReservationTopologySet = list[CapacityReservationTopology] class CarrierGateway(TypedDict, total=False): - CarrierGatewayId: Optional[CarrierGatewayId] - VpcId: Optional[VpcId] - State: Optional[CarrierGatewayState] - OwnerId: Optional[String] - Tags: Optional[TagList] + CarrierGatewayId: CarrierGatewayId | None + VpcId: VpcId | None + State: CarrierGatewayState | None + OwnerId: String | None + Tags: TagList | None -CarrierGatewayIdSet = List[CarrierGatewayId] -CarrierGatewaySet = List[CarrierGateway] +CarrierGatewayIdSet = list[CarrierGatewayId] +CarrierGatewaySet = list[CarrierGateway] class CertificateAuthentication(TypedDict, total=False): - ClientRootCertificateChain: Optional[String] + ClientRootCertificateChain: String | None class CertificateAuthenticationRequest(TypedDict, total=False): - ClientRootCertificateChainArn: Optional[String] + ClientRootCertificateChainArn: String | None class CidrAuthorizationContext(TypedDict, total=False): @@ -6265,1543 +7167,1850 @@ class CidrAuthorizationContext(TypedDict, total=False): class ClassicLinkDnsSupport(TypedDict, total=False): - ClassicLinkDnsSupported: Optional[Boolean] - VpcId: Optional[String] + ClassicLinkDnsSupported: Boolean | None + VpcId: String | None -ClassicLinkDnsSupportList = List[ClassicLinkDnsSupport] +ClassicLinkDnsSupportList = list[ClassicLinkDnsSupport] class GroupIdentifier(TypedDict, total=False): - GroupId: Optional[String] - GroupName: Optional[String] + GroupId: String | None + GroupName: String | None -GroupIdentifierList = List[GroupIdentifier] +GroupIdentifierList = list[GroupIdentifier] class ClassicLinkInstance(TypedDict, total=False): - Groups: Optional[GroupIdentifierList] - InstanceId: Optional[String] - Tags: Optional[TagList] - VpcId: Optional[String] + Groups: GroupIdentifierList | None + InstanceId: String | None + Tags: TagList | None + VpcId: String | None -ClassicLinkInstanceList = List[ClassicLinkInstance] +ClassicLinkInstanceList = list[ClassicLinkInstance] class ClassicLoadBalancer(TypedDict, total=False): - Name: Optional[String] + Name: String | None -ClassicLoadBalancers = List[ClassicLoadBalancer] +ClassicLoadBalancers = list[ClassicLoadBalancer] class ClassicLoadBalancersConfig(TypedDict, total=False): - ClassicLoadBalancers: Optional[ClassicLoadBalancers] + ClassicLoadBalancers: ClassicLoadBalancers | None class ClientCertificateRevocationListStatus(TypedDict, total=False): - Code: Optional[ClientCertificateRevocationListStatusCode] - Message: Optional[String] + Code: ClientCertificateRevocationListStatusCode | None + Message: String | None class ClientConnectOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - LambdaFunctionArn: Optional[String] + Enabled: Boolean | None + LambdaFunctionArn: String | None class ClientVpnEndpointAttributeStatus(TypedDict, total=False): - Code: Optional[ClientVpnEndpointAttributeStatusCode] - Message: Optional[String] + Code: ClientVpnEndpointAttributeStatusCode | None + Message: String | None class ClientConnectResponseOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - LambdaFunctionArn: Optional[String] - Status: Optional[ClientVpnEndpointAttributeStatus] + Enabled: Boolean | None + LambdaFunctionArn: String | None + Status: ClientVpnEndpointAttributeStatus | None class ClientData(TypedDict, total=False): - Comment: Optional[String] - UploadEnd: Optional[DateTime] - UploadSize: Optional[Double] - UploadStart: Optional[DateTime] + Comment: String | None + UploadEnd: DateTime | None + UploadSize: Double | None + UploadStart: DateTime | None class ClientLoginBannerOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - BannerText: Optional[String] + Enabled: Boolean | None + BannerText: String | None class ClientLoginBannerResponseOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - BannerText: Optional[String] + Enabled: Boolean | None + BannerText: String | None class ClientRouteEnforcementOptions(TypedDict, total=False): - Enforced: Optional[Boolean] + Enforced: Boolean | None class ClientRouteEnforcementResponseOptions(TypedDict, total=False): - Enforced: Optional[Boolean] + Enforced: Boolean | None class FederatedAuthentication(TypedDict, total=False): - SamlProviderArn: Optional[String] - SelfServiceSamlProviderArn: Optional[String] + SamlProviderArn: String | None + SelfServiceSamlProviderArn: String | None class DirectoryServiceAuthentication(TypedDict, total=False): - DirectoryId: Optional[String] + DirectoryId: String | None class ClientVpnAuthentication(TypedDict, total=False): - Type: Optional[ClientVpnAuthenticationType] - ActiveDirectory: Optional[DirectoryServiceAuthentication] - MutualAuthentication: Optional[CertificateAuthentication] - FederatedAuthentication: Optional[FederatedAuthentication] + Type: ClientVpnAuthenticationType | None + ActiveDirectory: DirectoryServiceAuthentication | None + MutualAuthentication: CertificateAuthentication | None + FederatedAuthentication: FederatedAuthentication | None -ClientVpnAuthenticationList = List[ClientVpnAuthentication] +ClientVpnAuthenticationList = list[ClientVpnAuthentication] class FederatedAuthenticationRequest(TypedDict, total=False): - SAMLProviderArn: Optional[String] - SelfServiceSAMLProviderArn: Optional[String] + SAMLProviderArn: String | None + SelfServiceSAMLProviderArn: String | None class DirectoryServiceAuthenticationRequest(TypedDict, total=False): - DirectoryId: Optional[String] + DirectoryId: String | None class ClientVpnAuthenticationRequest(TypedDict, total=False): - Type: Optional[ClientVpnAuthenticationType] - ActiveDirectory: Optional[DirectoryServiceAuthenticationRequest] - MutualAuthentication: Optional[CertificateAuthenticationRequest] - FederatedAuthentication: Optional[FederatedAuthenticationRequest] + Type: ClientVpnAuthenticationType | None + ActiveDirectory: DirectoryServiceAuthenticationRequest | None + MutualAuthentication: CertificateAuthenticationRequest | None + FederatedAuthentication: FederatedAuthenticationRequest | None -ClientVpnAuthenticationRequestList = List[ClientVpnAuthenticationRequest] +ClientVpnAuthenticationRequestList = list[ClientVpnAuthenticationRequest] class ClientVpnConnectionStatus(TypedDict, total=False): - Code: Optional[ClientVpnConnectionStatusCode] - Message: Optional[String] + Code: ClientVpnConnectionStatusCode | None + Message: String | None class ClientVpnConnection(TypedDict, total=False): - ClientVpnEndpointId: Optional[String] - Timestamp: Optional[String] - ConnectionId: Optional[String] - Username: Optional[String] - ConnectionEstablishedTime: Optional[String] - IngressBytes: Optional[String] - EgressBytes: Optional[String] - IngressPackets: Optional[String] - EgressPackets: Optional[String] - ClientIp: Optional[String] - CommonName: Optional[String] - Status: Optional[ClientVpnConnectionStatus] - ConnectionEndTime: Optional[String] - PostureComplianceStatuses: Optional[ValueStringList] - - -ClientVpnConnectionSet = List[ClientVpnConnection] + ClientVpnEndpointId: String | None + Timestamp: String | None + ConnectionId: String | None + Username: String | None + ConnectionEstablishedTime: String | None + IngressBytes: String | None + EgressBytes: String | None + IngressPackets: String | None + EgressPackets: String | None + ClientIp: String | None + ClientIpv6Address: String | None + CommonName: String | None + Status: ClientVpnConnectionStatus | None + ConnectionEndTime: String | None + PostureComplianceStatuses: ValueStringList | None + + +ClientVpnConnectionSet = list[ClientVpnConnection] class ConnectionLogResponseOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - CloudwatchLogGroup: Optional[String] - CloudwatchLogStream: Optional[String] + Enabled: Boolean | None + CloudwatchLogGroup: String | None + CloudwatchLogStream: String | None class ClientVpnEndpointStatus(TypedDict, total=False): - Code: Optional[ClientVpnEndpointStatusCode] - Message: Optional[String] + Code: ClientVpnEndpointStatusCode | None + Message: String | None class ClientVpnEndpoint(TypedDict, total=False): - ClientVpnEndpointId: Optional[String] - Description: Optional[String] - Status: Optional[ClientVpnEndpointStatus] - CreationTime: Optional[String] - DeletionTime: Optional[String] - DnsName: Optional[String] - ClientCidrBlock: Optional[String] - DnsServers: Optional[ValueStringList] - SplitTunnel: Optional[Boolean] - VpnProtocol: Optional[VpnProtocol] - TransportProtocol: Optional[TransportProtocol] - VpnPort: Optional[Integer] - AssociatedTargetNetworks: Optional[AssociatedTargetNetworkSet] - ServerCertificateArn: Optional[String] - AuthenticationOptions: Optional[ClientVpnAuthenticationList] - ConnectionLogOptions: Optional[ConnectionLogResponseOptions] - Tags: Optional[TagList] - SecurityGroupIds: Optional[ClientVpnSecurityGroupIdSet] - VpcId: Optional[VpcId] - SelfServicePortalUrl: Optional[String] - ClientConnectOptions: Optional[ClientConnectResponseOptions] - SessionTimeoutHours: Optional[Integer] - ClientLoginBannerOptions: Optional[ClientLoginBannerResponseOptions] - ClientRouteEnforcementOptions: Optional[ClientRouteEnforcementResponseOptions] - DisconnectOnSessionTimeout: Optional[Boolean] - - -ClientVpnEndpointIdList = List[ClientVpnEndpointId] + ClientVpnEndpointId: String | None + Description: String | None + Status: ClientVpnEndpointStatus | None + CreationTime: String | None + DeletionTime: String | None + DnsName: String | None + ClientCidrBlock: String | None + DnsServers: ValueStringList | None + SplitTunnel: Boolean | None + VpnProtocol: VpnProtocol | None + TransportProtocol: TransportProtocol | None + VpnPort: Integer | None + AssociatedTargetNetworks: AssociatedTargetNetworkSet | None + ServerCertificateArn: String | None + AuthenticationOptions: ClientVpnAuthenticationList | None + ConnectionLogOptions: ConnectionLogResponseOptions | None + Tags: TagList | None + SecurityGroupIds: ClientVpnSecurityGroupIdSet | None + VpcId: VpcId | None + SelfServicePortalUrl: String | None + ClientConnectOptions: ClientConnectResponseOptions | None + SessionTimeoutHours: Integer | None + ClientLoginBannerOptions: ClientLoginBannerResponseOptions | None + ClientRouteEnforcementOptions: ClientRouteEnforcementResponseOptions | None + DisconnectOnSessionTimeout: Boolean | None + EndpointIpAddressType: EndpointIpAddressType | None + TrafficIpAddressType: TrafficIpAddressType | None + + +ClientVpnEndpointIdList = list[ClientVpnEndpointId] class ClientVpnRouteStatus(TypedDict, total=False): - Code: Optional[ClientVpnRouteStatusCode] - Message: Optional[String] + Code: ClientVpnRouteStatusCode | None + Message: String | None class ClientVpnRoute(TypedDict, total=False): - ClientVpnEndpointId: Optional[String] - DestinationCidr: Optional[String] - TargetSubnet: Optional[String] - Type: Optional[String] - Origin: Optional[String] - Status: Optional[ClientVpnRouteStatus] - Description: Optional[String] + ClientVpnEndpointId: String | None + DestinationCidr: String | None + TargetSubnet: String | None + Type: String | None + Origin: String | None + Status: ClientVpnRouteStatus | None + Description: String | None -ClientVpnRouteSet = List[ClientVpnRoute] +ClientVpnRouteSet = list[ClientVpnRoute] class CloudWatchLogOptions(TypedDict, total=False): - LogEnabled: Optional[Boolean] - LogGroupArn: Optional[String] - LogOutputFormat: Optional[String] + LogEnabled: Boolean | None + LogGroupArn: String | None + LogOutputFormat: String | None + BgpLogEnabled: Boolean | None + BgpLogGroupArn: String | None + BgpLogOutputFormat: String | None class CloudWatchLogOptionsSpecification(TypedDict, total=False): - LogEnabled: Optional[Boolean] - LogGroupArn: Optional[CloudWatchLogGroupArn] - LogOutputFormat: Optional[String] + LogEnabled: Boolean | None + LogGroupArn: CloudWatchLogGroupArn | None + LogOutputFormat: String | None + BgpLogEnabled: Boolean | None + BgpLogGroupArn: CloudWatchLogGroupArn | None + BgpLogOutputFormat: String | None class CoipAddressUsage(TypedDict, total=False): - AllocationId: Optional[String] - AwsAccountId: Optional[String] - AwsService: Optional[String] - CoIp: Optional[String] + AllocationId: String | None + AwsAccountId: String | None + AwsService: String | None + CoIp: String | None -CoipAddressUsageSet = List[CoipAddressUsage] +CoipAddressUsageSet = list[CoipAddressUsage] class CoipCidr(TypedDict, total=False): - Cidr: Optional[String] - CoipPoolId: Optional[Ipv4PoolCoipId] - LocalGatewayRouteTableId: Optional[String] + Cidr: String | None + CoipPoolId: Ipv4PoolCoipId | None + LocalGatewayRouteTableId: String | None class CoipPool(TypedDict, total=False): - PoolId: Optional[Ipv4PoolCoipId] - PoolCidrs: Optional[ValueStringList] - LocalGatewayRouteTableId: Optional[LocalGatewayRoutetableId] - Tags: Optional[TagList] - PoolArn: Optional[ResourceArn] + PoolId: Ipv4PoolCoipId | None + PoolCidrs: ValueStringList | None + LocalGatewayRouteTableId: LocalGatewayRoutetableId | None + Tags: TagList | None + PoolArn: ResourceArn | None -CoipPoolIdSet = List[Ipv4PoolCoipId] -CoipPoolSet = List[CoipPool] +CoipPoolIdSet = list[Ipv4PoolCoipId] +CoipPoolSet = list[CoipPool] class ConfirmProductInstanceRequest(ServiceRequest): InstanceId: InstanceId ProductCode: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class ConfirmProductInstanceResult(TypedDict, total=False): - Return: Optional[Boolean] - OwnerId: Optional[String] + Return: Boolean | None + OwnerId: String | None class ConnectionLogOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - CloudwatchLogGroup: Optional[String] - CloudwatchLogStream: Optional[String] + Enabled: Boolean | None + CloudwatchLogGroup: String | None + CloudwatchLogStream: String | None class ConnectionNotification(TypedDict, total=False): - ConnectionNotificationId: Optional[String] - ServiceId: Optional[String] - VpcEndpointId: Optional[String] - ConnectionNotificationType: Optional[ConnectionNotificationType] - ConnectionNotificationArn: Optional[String] - ConnectionEvents: Optional[ValueStringList] - ConnectionNotificationState: Optional[ConnectionNotificationState] - ServiceRegion: Optional[String] + ConnectionNotificationId: String | None + ServiceId: String | None + VpcEndpointId: String | None + ConnectionNotificationType: ConnectionNotificationType | None + ConnectionNotificationArn: String | None + ConnectionEvents: ValueStringList | None + ConnectionNotificationState: ConnectionNotificationState | None + ServiceRegion: String | None -ConnectionNotificationIdsList = List[ConnectionNotificationId] -ConnectionNotificationSet = List[ConnectionNotification] +ConnectionNotificationIdsList = list[ConnectionNotificationId] +ConnectionNotificationSet = list[ConnectionNotification] class ConnectionTrackingConfiguration(TypedDict, total=False): - TcpEstablishedTimeout: Optional[Integer] - UdpStreamTimeout: Optional[Integer] - UdpTimeout: Optional[Integer] + TcpEstablishedTimeout: Integer | None + UdpStreamTimeout: Integer | None + UdpTimeout: Integer | None class ConnectionTrackingSpecification(TypedDict, total=False): - TcpEstablishedTimeout: Optional[Integer] - UdpTimeout: Optional[Integer] - UdpStreamTimeout: Optional[Integer] + TcpEstablishedTimeout: Integer | None + UdpTimeout: Integer | None + UdpStreamTimeout: Integer | None class ConnectionTrackingSpecificationRequest(TypedDict, total=False): - TcpEstablishedTimeout: Optional[Integer] - UdpStreamTimeout: Optional[Integer] - UdpTimeout: Optional[Integer] + TcpEstablishedTimeout: Integer | None + UdpStreamTimeout: Integer | None + UdpTimeout: Integer | None class ConnectionTrackingSpecificationResponse(TypedDict, total=False): - TcpEstablishedTimeout: Optional[Integer] - UdpStreamTimeout: Optional[Integer] - UdpTimeout: Optional[Integer] + TcpEstablishedTimeout: Integer | None + UdpStreamTimeout: Integer | None + UdpTimeout: Integer | None -ConversionIdStringList = List[ConversionTaskId] +ConversionIdStringList = list[ConversionTaskId] class DiskImageVolumeDescription(TypedDict, total=False): - Id: Optional[String] - Size: Optional[Long] + Id: String | None + Size: Long | None class DiskImageDescription(TypedDict, total=False): - Checksum: Optional[String] - Format: Optional[DiskImageFormat] - ImportManifestUrl: Optional[ImportManifestUrl] - Size: Optional[Long] + Checksum: String | None + Format: DiskImageFormat | None + ImportManifestUrl: ImportManifestUrl | None + Size: Long | None class ImportVolumeTaskDetails(TypedDict, total=False): - AvailabilityZone: Optional[String] - BytesConverted: Optional[Long] - Description: Optional[String] - Image: Optional[DiskImageDescription] - Volume: Optional[DiskImageVolumeDescription] + AvailabilityZone: String | None + AvailabilityZoneId: String | None + BytesConverted: Long | None + Description: String | None + Image: DiskImageDescription | None + Volume: DiskImageVolumeDescription | None class ImportInstanceVolumeDetailItem(TypedDict, total=False): - AvailabilityZone: Optional[String] - BytesConverted: Optional[Long] - Description: Optional[String] - Image: Optional[DiskImageDescription] - Status: Optional[String] - StatusMessage: Optional[String] - Volume: Optional[DiskImageVolumeDescription] + AvailabilityZone: String | None + AvailabilityZoneId: String | None + BytesConverted: Long | None + Description: String | None + Image: DiskImageDescription | None + Status: String | None + StatusMessage: String | None + Volume: DiskImageVolumeDescription | None -ImportInstanceVolumeDetailSet = List[ImportInstanceVolumeDetailItem] +ImportInstanceVolumeDetailSet = list[ImportInstanceVolumeDetailItem] class ImportInstanceTaskDetails(TypedDict, total=False): - Description: Optional[String] - InstanceId: Optional[String] - Platform: Optional[PlatformValues] - Volumes: Optional[ImportInstanceVolumeDetailSet] + Description: String | None + InstanceId: String | None + Platform: PlatformValues | None + Volumes: ImportInstanceVolumeDetailSet | None class ConversionTask(TypedDict, total=False): - ConversionTaskId: Optional[String] - ExpirationTime: Optional[String] - ImportInstance: Optional[ImportInstanceTaskDetails] - ImportVolume: Optional[ImportVolumeTaskDetails] - State: Optional[ConversionTaskState] - StatusMessage: Optional[String] - Tags: Optional[TagList] + ConversionTaskId: String | None + ExpirationTime: String | None + ImportInstance: ImportInstanceTaskDetails | None + ImportVolume: ImportVolumeTaskDetails | None + State: ConversionTaskState | None + StatusMessage: String | None + Tags: TagList | None class CopyFpgaImageRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None SourceFpgaImageId: String - Description: Optional[String] - Name: Optional[String] + Description: String | None + Name: String | None SourceRegion: String - ClientToken: Optional[String] + ClientToken: String | None class CopyFpgaImageResult(TypedDict, total=False): - FpgaImageId: Optional[String] + FpgaImageId: String | None class CopyImageRequest(ServiceRequest): - ClientToken: Optional[String] - Description: Optional[String] - Encrypted: Optional[Boolean] - KmsKeyId: Optional[KmsKeyId] + ClientToken: String | None + Description: String | None + Encrypted: Boolean | None + KmsKeyId: KmsKeyId | None Name: String SourceImageId: String SourceRegion: String - DestinationOutpostArn: Optional[String] - CopyImageTags: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] - SnapshotCopyCompletionDurationMinutes: Optional[Long] - DryRun: Optional[Boolean] + DestinationOutpostArn: String | None + CopyImageTags: Boolean | None + TagSpecifications: TagSpecificationList | None + SnapshotCopyCompletionDurationMinutes: Long | None + DestinationAvailabilityZone: String | None + DestinationAvailabilityZoneId: String | None + DryRun: Boolean | None class CopyImageResult(TypedDict, total=False): - ImageId: Optional[String] + ImageId: String | None class CopySnapshotRequest(ServiceRequest): - Description: Optional[String] - DestinationOutpostArn: Optional[String] - DestinationRegion: Optional[String] - Encrypted: Optional[Boolean] - KmsKeyId: Optional[KmsKeyId] - PresignedUrl: Optional[CopySnapshotRequestPSU] + Description: String | None + DestinationOutpostArn: String | None + DestinationRegion: String | None + Encrypted: Boolean | None + KmsKeyId: KmsKeyId | None + PresignedUrl: CopySnapshotRequestPSU | None SourceRegion: String SourceSnapshotId: String - TagSpecifications: Optional[TagSpecificationList] - CompletionDurationMinutes: Optional[SnapshotCompletionDurationMinutesRequest] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + CompletionDurationMinutes: SnapshotCompletionDurationMinutesRequest | None + DestinationAvailabilityZone: String | None + DryRun: Boolean | None class CopySnapshotResult(TypedDict, total=False): - Tags: Optional[TagList] - SnapshotId: Optional[String] + Tags: TagList | None + SnapshotId: String | None + + +class CopyVolumesRequest(ServiceRequest): + SourceVolumeId: VolumeId + Iops: Integer | None + Size: Integer | None + VolumeType: VolumeType | None + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None + MultiAttachEnabled: Boolean | None + Throughput: Integer | None + ClientToken: String | None + + +class VolumeAttachment(TypedDict, total=False): + DeleteOnTermination: Boolean | None + AssociatedResource: String | None + InstanceOwningService: String | None + EbsCardIndex: Integer | None + VolumeId: String | None + InstanceId: String | None + Device: String | None + State: VolumeAttachmentState | None + AttachTime: DateTime | None + + +VolumeAttachmentList = list[VolumeAttachment] + + +class OperatorResponse(TypedDict, total=False): + Managed: Boolean | None + Principal: String | None + + +class Volume(TypedDict, total=False): + AvailabilityZoneId: String | None + OutpostArn: String | None + SourceVolumeId: String | None + Iops: Integer | None + Tags: TagList | None + VolumeType: VolumeType | None + FastRestored: Boolean | None + MultiAttachEnabled: Boolean | None + Throughput: Integer | None + SseType: SSEType | None + Operator: OperatorResponse | None + VolumeInitializationRate: Integer | None + VolumeId: String | None + Size: Integer | None + SnapshotId: String | None + AvailabilityZone: String | None + State: VolumeState | None + CreateTime: DateTime | None + Attachments: VolumeAttachmentList | None + Encrypted: Boolean | None + KmsKeyId: String | None -CoreCountList = List[CoreCount] -CpuManufacturerSet = List[CpuManufacturer] +VolumeList = list[Volume] + + +class CopyVolumesResult(TypedDict, total=False): + Volumes: VolumeList | None + + +CoreCountList = list[CoreCount] +CpuManufacturerSet = list[CpuManufacturer] class CpuOptions(TypedDict, total=False): - CoreCount: Optional[Integer] - ThreadsPerCore: Optional[Integer] - AmdSevSnp: Optional[AmdSevSnpSpecification] + CoreCount: Integer | None + ThreadsPerCore: Integer | None + AmdSevSnp: AmdSevSnpSpecification | None + NestedVirtualization: NestedVirtualizationSpecification | None class CpuOptionsRequest(TypedDict, total=False): - CoreCount: Optional[Integer] - ThreadsPerCore: Optional[Integer] - AmdSevSnp: Optional[AmdSevSnpSpecification] + CoreCount: Integer | None + ThreadsPerCore: Integer | None + AmdSevSnp: AmdSevSnpSpecification | None + NestedVirtualization: NestedVirtualizationSpecification | None + + +class CreateCapacityManagerDataExportRequest(ServiceRequest): + S3BucketName: String + S3BucketPrefix: String | None + Schedule: Schedule + OutputFormat: OutputFormat + ClientToken: String | None + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None + + +class CreateCapacityManagerDataExportResult(TypedDict, total=False): + CapacityManagerDataExportId: CapacityManagerDataExportId | None class CreateCapacityReservationBySplittingRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] + DryRun: Boolean | None + ClientToken: String | None SourceCapacityReservationId: CapacityReservationId InstanceCount: Integer - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None class CreateCapacityReservationBySplittingResult(TypedDict, total=False): - SourceCapacityReservation: Optional[CapacityReservation] - DestinationCapacityReservation: Optional[CapacityReservation] - InstanceCount: Optional[Integer] + SourceCapacityReservation: CapacityReservation | None + DestinationCapacityReservation: CapacityReservation | None + InstanceCount: Integer | None class ReservationFleetInstanceSpecification(TypedDict, total=False): - InstanceType: Optional[InstanceType] - InstancePlatform: Optional[CapacityReservationInstancePlatform] - Weight: Optional[DoubleWithConstraints] - AvailabilityZone: Optional[String] - AvailabilityZoneId: Optional[String] - EbsOptimized: Optional[Boolean] - Priority: Optional[IntegerWithConstraints] + InstanceType: InstanceType | None + InstancePlatform: CapacityReservationInstancePlatform | None + Weight: DoubleWithConstraints | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + EbsOptimized: Boolean | None + Priority: IntegerWithConstraints | None -ReservationFleetInstanceSpecificationList = List[ReservationFleetInstanceSpecification] +ReservationFleetInstanceSpecificationList = list[ReservationFleetInstanceSpecification] class CreateCapacityReservationFleetRequest(ServiceRequest): - AllocationStrategy: Optional[String] - ClientToken: Optional[String] + AllocationStrategy: String | None + ClientToken: String | None InstanceTypeSpecifications: ReservationFleetInstanceSpecificationList - Tenancy: Optional[FleetCapacityReservationTenancy] + Tenancy: FleetCapacityReservationTenancy | None TotalTargetCapacity: Integer - EndDate: Optional[MillisecondDateTime] - InstanceMatchCriteria: Optional[FleetInstanceMatchCriteria] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + EndDate: MillisecondDateTime | None + InstanceMatchCriteria: FleetInstanceMatchCriteria | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class CreateCapacityReservationFleetResult(TypedDict, total=False): - CapacityReservationFleetId: Optional[CapacityReservationFleetId] - State: Optional[CapacityReservationFleetState] - TotalTargetCapacity: Optional[Integer] - TotalFulfilledCapacity: Optional[Double] - InstanceMatchCriteria: Optional[FleetInstanceMatchCriteria] - AllocationStrategy: Optional[String] - CreateTime: Optional[MillisecondDateTime] - EndDate: Optional[MillisecondDateTime] - Tenancy: Optional[FleetCapacityReservationTenancy] - FleetCapacityReservations: Optional[FleetCapacityReservationSet] - Tags: Optional[TagList] + CapacityReservationFleetId: CapacityReservationFleetId | None + State: CapacityReservationFleetState | None + TotalTargetCapacity: Integer | None + TotalFulfilledCapacity: Double | None + InstanceMatchCriteria: FleetInstanceMatchCriteria | None + AllocationStrategy: String | None + CreateTime: MillisecondDateTime | None + EndDate: MillisecondDateTime | None + Tenancy: FleetCapacityReservationTenancy | None + FleetCapacityReservations: FleetCapacityReservationSet | None + Tags: TagList | None class CreateCapacityReservationRequest(ServiceRequest): - ClientToken: Optional[String] + ClientToken: String | None InstanceType: String InstancePlatform: CapacityReservationInstancePlatform - AvailabilityZone: Optional[AvailabilityZoneName] - AvailabilityZoneId: Optional[AvailabilityZoneId] - Tenancy: Optional[CapacityReservationTenancy] + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None + Tenancy: CapacityReservationTenancy | None InstanceCount: Integer - EbsOptimized: Optional[Boolean] - EphemeralStorage: Optional[Boolean] - EndDate: Optional[DateTime] - EndDateType: Optional[EndDateType] - InstanceMatchCriteria: Optional[InstanceMatchCriteria] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - OutpostArn: Optional[OutpostArn] - PlacementGroupArn: Optional[PlacementGroupArn] - StartDate: Optional[MillisecondDateTime] - CommitmentDuration: Optional[CapacityReservationCommitmentDuration] - DeliveryPreference: Optional[CapacityReservationDeliveryPreference] + EbsOptimized: Boolean | None + EphemeralStorage: Boolean | None + EndDate: DateTime | None + EndDateType: EndDateType | None + InstanceMatchCriteria: InstanceMatchCriteria | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + OutpostArn: OutpostArn | None + PlacementGroupArn: PlacementGroupArn | None + StartDate: MillisecondDateTime | None + CommitmentDuration: CapacityReservationCommitmentDuration | None + DeliveryPreference: CapacityReservationDeliveryPreference | None class CreateCapacityReservationResult(TypedDict, total=False): - CapacityReservation: Optional[CapacityReservation] + CapacityReservation: CapacityReservation | None class CreateCarrierGatewayRequest(ServiceRequest): VpcId: VpcId - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - ClientToken: Optional[String] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + ClientToken: String | None class CreateCarrierGatewayResult(TypedDict, total=False): - CarrierGateway: Optional[CarrierGateway] + CarrierGateway: CarrierGateway | None class CreateClientVpnEndpointRequest(ServiceRequest): - ClientCidrBlock: String + ClientCidrBlock: String | None ServerCertificateArn: String AuthenticationOptions: ClientVpnAuthenticationRequestList ConnectionLogOptions: ConnectionLogOptions - DnsServers: Optional[ValueStringList] - TransportProtocol: Optional[TransportProtocol] - VpnPort: Optional[Integer] - Description: Optional[String] - SplitTunnel: Optional[Boolean] - DryRun: Optional[Boolean] - ClientToken: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - SecurityGroupIds: Optional[ClientVpnSecurityGroupIdSet] - VpcId: Optional[VpcId] - SelfServicePortal: Optional[SelfServicePortal] - ClientConnectOptions: Optional[ClientConnectOptions] - SessionTimeoutHours: Optional[Integer] - ClientLoginBannerOptions: Optional[ClientLoginBannerOptions] - ClientRouteEnforcementOptions: Optional[ClientRouteEnforcementOptions] - DisconnectOnSessionTimeout: Optional[Boolean] + DnsServers: ValueStringList | None + TransportProtocol: TransportProtocol | None + VpnPort: Integer | None + Description: String | None + SplitTunnel: Boolean | None + DryRun: Boolean | None + ClientToken: String | None + TagSpecifications: TagSpecificationList | None + SecurityGroupIds: ClientVpnSecurityGroupIdSet | None + VpcId: VpcId | None + SelfServicePortal: SelfServicePortal | None + ClientConnectOptions: ClientConnectOptions | None + SessionTimeoutHours: Integer | None + ClientLoginBannerOptions: ClientLoginBannerOptions | None + ClientRouteEnforcementOptions: ClientRouteEnforcementOptions | None + DisconnectOnSessionTimeout: Boolean | None + EndpointIpAddressType: EndpointIpAddressType | None + TrafficIpAddressType: TrafficIpAddressType | None class CreateClientVpnEndpointResult(TypedDict, total=False): - ClientVpnEndpointId: Optional[String] - Status: Optional[ClientVpnEndpointStatus] - DnsName: Optional[String] + ClientVpnEndpointId: String | None + Status: ClientVpnEndpointStatus | None + DnsName: String | None class CreateClientVpnRouteRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId DestinationCidrBlock: String TargetVpcSubnetId: SubnetId - Description: Optional[String] - ClientToken: Optional[String] - DryRun: Optional[Boolean] + Description: String | None + ClientToken: String | None + DryRun: Boolean | None class CreateClientVpnRouteResult(TypedDict, total=False): - Status: Optional[ClientVpnRouteStatus] + Status: ClientVpnRouteStatus | None class CreateCoipCidrRequest(ServiceRequest): Cidr: String CoipPoolId: Ipv4PoolCoipId - DryRun: Optional[Boolean] + DryRun: Boolean | None class CreateCoipCidrResult(TypedDict, total=False): - CoipCidr: Optional[CoipCidr] + CoipCidr: CoipCidr | None class CreateCoipPoolRequest(ServiceRequest): LocalGatewayRouteTableId: LocalGatewayRoutetableId - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class CreateCoipPoolResult(TypedDict, total=False): - CoipPool: Optional[CoipPool] + CoipPool: CoipPool | None class CreateCustomerGatewayRequest(ServiceRequest): - BgpAsn: Optional[Integer] - PublicIp: Optional[String] - CertificateArn: Optional[String] + BgpAsn: Integer | None + PublicIp: String | None + CertificateArn: String | None Type: GatewayType - TagSpecifications: Optional[TagSpecificationList] - DeviceName: Optional[String] - IpAddress: Optional[String] - BgpAsnExtended: Optional[Long] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DeviceName: String | None + IpAddress: String | None + BgpAsnExtended: Long | None + DryRun: Boolean | None class CustomerGateway(TypedDict, total=False): - CertificateArn: Optional[String] - DeviceName: Optional[String] - Tags: Optional[TagList] - BgpAsnExtended: Optional[String] - CustomerGatewayId: Optional[String] - State: Optional[String] - Type: Optional[String] - IpAddress: Optional[String] - BgpAsn: Optional[String] + CertificateArn: String | None + DeviceName: String | None + Tags: TagList | None + BgpAsnExtended: String | None + CustomerGatewayId: String | None + State: String | None + Type: String | None + IpAddress: String | None + BgpAsn: String | None class CreateCustomerGatewayResult(TypedDict, total=False): - CustomerGateway: Optional[CustomerGateway] + CustomerGateway: CustomerGateway | None class CreateDefaultSubnetRequest(ServiceRequest): - AvailabilityZone: AvailabilityZoneName - DryRun: Optional[Boolean] - Ipv6Native: Optional[Boolean] + AvailabilityZone: AvailabilityZoneName | None + DryRun: Boolean | None + Ipv6Native: Boolean | None + AvailabilityZoneId: AvailabilityZoneId | None class PrivateDnsNameOptionsOnLaunch(TypedDict, total=False): - HostnameType: Optional[HostnameType] - EnableResourceNameDnsARecord: Optional[Boolean] - EnableResourceNameDnsAAAARecord: Optional[Boolean] + HostnameType: HostnameType | None + EnableResourceNameDnsARecord: Boolean | None + EnableResourceNameDnsAAAARecord: Boolean | None -SubnetIpv6CidrBlockAssociationSet = List[SubnetIpv6CidrBlockAssociation] +SubnetIpv6CidrBlockAssociationSet = list[SubnetIpv6CidrBlockAssociation] class Subnet(TypedDict, total=False): - AvailabilityZoneId: Optional[String] - EnableLniAtDeviceIndex: Optional[Integer] - MapCustomerOwnedIpOnLaunch: Optional[Boolean] - CustomerOwnedIpv4Pool: Optional[CoipPoolId] - OwnerId: Optional[String] - AssignIpv6AddressOnCreation: Optional[Boolean] - Ipv6CidrBlockAssociationSet: Optional[SubnetIpv6CidrBlockAssociationSet] - Tags: Optional[TagList] - SubnetArn: Optional[String] - OutpostArn: Optional[String] - EnableDns64: Optional[Boolean] - Ipv6Native: Optional[Boolean] - PrivateDnsNameOptionsOnLaunch: Optional[PrivateDnsNameOptionsOnLaunch] - BlockPublicAccessStates: Optional[BlockPublicAccessStates] - Type: Optional[String] - SubnetId: Optional[String] - State: Optional[SubnetState] - VpcId: Optional[String] - CidrBlock: Optional[String] - AvailableIpAddressCount: Optional[Integer] - AvailabilityZone: Optional[String] - DefaultForAz: Optional[Boolean] - MapPublicIpOnLaunch: Optional[Boolean] + AvailabilityZoneId: String | None + EnableLniAtDeviceIndex: Integer | None + MapCustomerOwnedIpOnLaunch: Boolean | None + CustomerOwnedIpv4Pool: CoipPoolId | None + OwnerId: String | None + AssignIpv6AddressOnCreation: Boolean | None + Ipv6CidrBlockAssociationSet: SubnetIpv6CidrBlockAssociationSet | None + Tags: TagList | None + SubnetArn: String | None + OutpostArn: String | None + EnableDns64: Boolean | None + Ipv6Native: Boolean | None + PrivateDnsNameOptionsOnLaunch: PrivateDnsNameOptionsOnLaunch | None + BlockPublicAccessStates: BlockPublicAccessStates | None + Type: String | None + SubnetId: String | None + State: SubnetState | None + VpcId: String | None + CidrBlock: String | None + AvailableIpAddressCount: Integer | None + AvailabilityZone: String | None + DefaultForAz: Boolean | None + MapPublicIpOnLaunch: Boolean | None class CreateDefaultSubnetResult(TypedDict, total=False): - Subnet: Optional[Subnet] + Subnet: Subnet | None class CreateDefaultVpcRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class VpcEncryptionControlExclusion(TypedDict, total=False): - State: Optional[VpcEncryptionControlExclusionState] - StateMessage: Optional[String] + State: VpcEncryptionControlExclusionState | None + StateMessage: String | None class VpcEncryptionControlExclusions(TypedDict, total=False): - InternetGateway: Optional[VpcEncryptionControlExclusion] - EgressOnlyInternetGateway: Optional[VpcEncryptionControlExclusion] - NatGateway: Optional[VpcEncryptionControlExclusion] - VirtualPrivateGateway: Optional[VpcEncryptionControlExclusion] - VpcPeering: Optional[VpcEncryptionControlExclusion] + InternetGateway: VpcEncryptionControlExclusion | None + EgressOnlyInternetGateway: VpcEncryptionControlExclusion | None + NatGateway: VpcEncryptionControlExclusion | None + VirtualPrivateGateway: VpcEncryptionControlExclusion | None + VpcPeering: VpcEncryptionControlExclusion | None + Lambda: VpcEncryptionControlExclusion | None + VpcLattice: VpcEncryptionControlExclusion | None + ElasticFileSystem: VpcEncryptionControlExclusion | None class VpcEncryptionControl(TypedDict, total=False): - VpcId: Optional[VpcId] - VpcEncryptionControlId: Optional[VpcEncryptionControlId] - Mode: Optional[VpcEncryptionControlMode] - State: Optional[VpcEncryptionControlState] - StateMessage: Optional[String] - ResourceExclusions: Optional[VpcEncryptionControlExclusions] - Tags: Optional[TagList] + VpcId: VpcId | None + VpcEncryptionControlId: VpcEncryptionControlId | None + Mode: VpcEncryptionControlMode | None + State: VpcEncryptionControlState | None + StateMessage: String | None + ResourceExclusions: VpcEncryptionControlExclusions | None + Tags: TagList | None -VpcCidrBlockAssociationSet = List[VpcCidrBlockAssociation] -VpcIpv6CidrBlockAssociationSet = List[VpcIpv6CidrBlockAssociation] +VpcCidrBlockAssociationSet = list[VpcCidrBlockAssociation] +VpcIpv6CidrBlockAssociationSet = list[VpcIpv6CidrBlockAssociation] class Vpc(TypedDict, total=False): - OwnerId: Optional[String] - InstanceTenancy: Optional[Tenancy] - Ipv6CidrBlockAssociationSet: Optional[VpcIpv6CidrBlockAssociationSet] - CidrBlockAssociationSet: Optional[VpcCidrBlockAssociationSet] - IsDefault: Optional[Boolean] - EncryptionControl: Optional[VpcEncryptionControl] - Tags: Optional[TagList] - BlockPublicAccessStates: Optional[BlockPublicAccessStates] - VpcId: Optional[String] - State: Optional[VpcState] - CidrBlock: Optional[String] - DhcpOptionsId: Optional[String] + OwnerId: String | None + InstanceTenancy: Tenancy | None + Ipv6CidrBlockAssociationSet: VpcIpv6CidrBlockAssociationSet | None + CidrBlockAssociationSet: VpcCidrBlockAssociationSet | None + IsDefault: Boolean | None + EncryptionControl: VpcEncryptionControl | None + Tags: TagList | None + BlockPublicAccessStates: BlockPublicAccessStates | None + VpcId: String | None + State: VpcState | None + CidrBlock: String | None + DhcpOptionsId: String | None class CreateDefaultVpcResult(TypedDict, total=False): - Vpc: Optional[Vpc] + Vpc: Vpc | None class CreateDelegateMacVolumeOwnershipTaskRequest(ServiceRequest): - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None InstanceId: InstanceId MacCredentials: SensitiveMacCredentials - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None class MacSystemIntegrityProtectionConfiguration(TypedDict, total=False): - AppleInternal: Optional[MacSystemIntegrityProtectionSettingStatus] - BaseSystem: Optional[MacSystemIntegrityProtectionSettingStatus] - DebuggingRestrictions: Optional[MacSystemIntegrityProtectionSettingStatus] - DTraceRestrictions: Optional[MacSystemIntegrityProtectionSettingStatus] - FilesystemProtections: Optional[MacSystemIntegrityProtectionSettingStatus] - KextSigning: Optional[MacSystemIntegrityProtectionSettingStatus] - NvramProtections: Optional[MacSystemIntegrityProtectionSettingStatus] - Status: Optional[MacSystemIntegrityProtectionSettingStatus] + AppleInternal: MacSystemIntegrityProtectionSettingStatus | None + BaseSystem: MacSystemIntegrityProtectionSettingStatus | None + DebuggingRestrictions: MacSystemIntegrityProtectionSettingStatus | None + DTraceRestrictions: MacSystemIntegrityProtectionSettingStatus | None + FilesystemProtections: MacSystemIntegrityProtectionSettingStatus | None + KextSigning: MacSystemIntegrityProtectionSettingStatus | None + NvramProtections: MacSystemIntegrityProtectionSettingStatus | None + Status: MacSystemIntegrityProtectionSettingStatus | None class MacModificationTask(TypedDict, total=False): - InstanceId: Optional[InstanceId] - MacModificationTaskId: Optional[MacModificationTaskId] - MacSystemIntegrityProtectionConfig: Optional[MacSystemIntegrityProtectionConfiguration] - StartTime: Optional[MillisecondDateTime] - Tags: Optional[TagList] - TaskState: Optional[MacModificationTaskState] - TaskType: Optional[MacModificationTaskType] + InstanceId: InstanceId | None + MacModificationTaskId: MacModificationTaskId | None + MacSystemIntegrityProtectionConfig: MacSystemIntegrityProtectionConfiguration | None + StartTime: MillisecondDateTime | None + Tags: TagList | None + TaskState: MacModificationTaskState | None + TaskType: MacModificationTaskType | None class CreateDelegateMacVolumeOwnershipTaskResult(TypedDict, total=False): - MacModificationTask: Optional[MacModificationTask] + MacModificationTask: MacModificationTask | None class NewDhcpConfiguration(TypedDict, total=False): - Key: Optional[String] - Values: Optional[ValueStringList] + Key: String | None + Values: ValueStringList | None -NewDhcpConfigurationList = List[NewDhcpConfiguration] +NewDhcpConfigurationList = list[NewDhcpConfiguration] class CreateDhcpOptionsRequest(ServiceRequest): DhcpConfigurations: NewDhcpConfigurationList - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None -DhcpConfigurationValueList = List[AttributeValue] +DhcpConfigurationValueList = list[AttributeValue] class DhcpConfiguration(TypedDict, total=False): - Key: Optional[String] - Values: Optional[DhcpConfigurationValueList] + Key: String | None + Values: DhcpConfigurationValueList | None -DhcpConfigurationList = List[DhcpConfiguration] +DhcpConfigurationList = list[DhcpConfiguration] class DhcpOptions(TypedDict, total=False): - OwnerId: Optional[String] - Tags: Optional[TagList] - DhcpOptionsId: Optional[String] - DhcpConfigurations: Optional[DhcpConfigurationList] + OwnerId: String | None + Tags: TagList | None + DhcpOptionsId: String | None + DhcpConfigurations: DhcpConfigurationList | None class CreateDhcpOptionsResult(TypedDict, total=False): - DhcpOptions: Optional[DhcpOptions] + DhcpOptions: DhcpOptions | None class CreateEgressOnlyInternetGatewayRequest(ServiceRequest): - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None VpcId: VpcId - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None class InternetGatewayAttachment(TypedDict, total=False): - State: Optional[AttachmentStatus] - VpcId: Optional[String] + State: AttachmentStatus | None + VpcId: String | None -InternetGatewayAttachmentList = List[InternetGatewayAttachment] +InternetGatewayAttachmentList = list[InternetGatewayAttachment] class EgressOnlyInternetGateway(TypedDict, total=False): - Attachments: Optional[InternetGatewayAttachmentList] - EgressOnlyInternetGatewayId: Optional[EgressOnlyInternetGatewayId] - Tags: Optional[TagList] + Attachments: InternetGatewayAttachmentList | None + EgressOnlyInternetGatewayId: EgressOnlyInternetGatewayId | None + Tags: TagList | None class CreateEgressOnlyInternetGatewayResult(TypedDict, total=False): - ClientToken: Optional[String] - EgressOnlyInternetGateway: Optional[EgressOnlyInternetGateway] + ClientToken: String | None + EgressOnlyInternetGateway: EgressOnlyInternetGateway | None class NetworkBandwidthGbps(TypedDict, total=False): - Min: Optional[Double] - Max: Optional[Double] + Min: Double | None + Max: Double | None class TotalLocalStorageGB(TypedDict, total=False): - Min: Optional[Double] - Max: Optional[Double] + Min: Double | None + Max: Double | None -LocalStorageTypeSet = List[LocalStorageType] +LocalStorageTypeSet = list[LocalStorageType] class NetworkInterfaceCount(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None -InstanceGenerationSet = List[InstanceGeneration] -ExcludedInstanceTypeSet = List[ExcludedInstanceType] +InstanceGenerationSet = list[InstanceGeneration] +ExcludedInstanceTypeSet = list[ExcludedInstanceType] class MemoryGiBPerVCpu(TypedDict, total=False): - Min: Optional[Double] - Max: Optional[Double] + Min: Double | None + Max: Double | None class MemoryMiB(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None class VCpuCountRange(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None class InstanceRequirements(TypedDict, total=False): - VCpuCount: Optional[VCpuCountRange] - MemoryMiB: Optional[MemoryMiB] - CpuManufacturers: Optional[CpuManufacturerSet] - MemoryGiBPerVCpu: Optional[MemoryGiBPerVCpu] - ExcludedInstanceTypes: Optional[ExcludedInstanceTypeSet] - InstanceGenerations: Optional[InstanceGenerationSet] - SpotMaxPricePercentageOverLowestPrice: Optional[Integer] - OnDemandMaxPricePercentageOverLowestPrice: Optional[Integer] - BareMetal: Optional[BareMetal] - BurstablePerformance: Optional[BurstablePerformance] - RequireHibernateSupport: Optional[Boolean] - NetworkInterfaceCount: Optional[NetworkInterfaceCount] - LocalStorage: Optional[LocalStorage] - LocalStorageTypes: Optional[LocalStorageTypeSet] - TotalLocalStorageGB: Optional[TotalLocalStorageGB] - BaselineEbsBandwidthMbps: Optional[BaselineEbsBandwidthMbps] - AcceleratorTypes: Optional[AcceleratorTypeSet] - AcceleratorCount: Optional[AcceleratorCount] - AcceleratorManufacturers: Optional[AcceleratorManufacturerSet] - AcceleratorNames: Optional[AcceleratorNameSet] - AcceleratorTotalMemoryMiB: Optional[AcceleratorTotalMemoryMiB] - NetworkBandwidthGbps: Optional[NetworkBandwidthGbps] - AllowedInstanceTypes: Optional[AllowedInstanceTypeSet] - MaxSpotPriceAsPercentageOfOptimalOnDemandPrice: Optional[Integer] - BaselinePerformanceFactors: Optional[BaselinePerformanceFactors] + VCpuCount: VCpuCountRange | None + MemoryMiB: MemoryMiB | None + CpuManufacturers: CpuManufacturerSet | None + MemoryGiBPerVCpu: MemoryGiBPerVCpu | None + ExcludedInstanceTypes: ExcludedInstanceTypeSet | None + InstanceGenerations: InstanceGenerationSet | None + SpotMaxPricePercentageOverLowestPrice: Integer | None + OnDemandMaxPricePercentageOverLowestPrice: Integer | None + BareMetal: BareMetal | None + BurstablePerformance: BurstablePerformance | None + RequireHibernateSupport: Boolean | None + NetworkInterfaceCount: NetworkInterfaceCount | None + LocalStorage: LocalStorage | None + LocalStorageTypes: LocalStorageTypeSet | None + TotalLocalStorageGB: TotalLocalStorageGB | None + BaselineEbsBandwidthMbps: BaselineEbsBandwidthMbps | None + AcceleratorTypes: AcceleratorTypeSet | None + AcceleratorCount: AcceleratorCount | None + AcceleratorManufacturers: AcceleratorManufacturerSet | None + AcceleratorNames: AcceleratorNameSet | None + AcceleratorTotalMemoryMiB: AcceleratorTotalMemoryMiB | None + NetworkBandwidthGbps: NetworkBandwidthGbps | None + AllowedInstanceTypes: AllowedInstanceTypeSet | None + MaxSpotPriceAsPercentageOfOptimalOnDemandPrice: Integer | None + BaselinePerformanceFactors: BaselinePerformanceFactors | None + RequireEncryptionInTransit: Boolean | None class PlacementResponse(TypedDict, total=False): - GroupName: Optional[PlacementGroupName] + GroupName: PlacementGroupName | None class FleetLaunchTemplateOverrides(TypedDict, total=False): - InstanceType: Optional[InstanceType] - MaxPrice: Optional[String] - SubnetId: Optional[String] - AvailabilityZone: Optional[String] - WeightedCapacity: Optional[Double] - Priority: Optional[Double] - Placement: Optional[PlacementResponse] - InstanceRequirements: Optional[InstanceRequirements] - ImageId: Optional[ImageId] - BlockDeviceMappings: Optional[BlockDeviceMappingResponseList] + InstanceType: InstanceType | None + MaxPrice: String | None + SubnetId: String | None + AvailabilityZone: AvailabilityZoneName | None + WeightedCapacity: Double | None + Priority: Double | None + Placement: PlacementResponse | None + InstanceRequirements: InstanceRequirements | None + ImageId: ImageId | None + BlockDeviceMappings: BlockDeviceMappingResponseList | None + AvailabilityZoneId: AvailabilityZoneId | None class FleetLaunchTemplateSpecification(TypedDict, total=False): - LaunchTemplateId: Optional[String] - LaunchTemplateName: Optional[LaunchTemplateName] - Version: Optional[String] + LaunchTemplateId: String | None + LaunchTemplateName: LaunchTemplateName | None + Version: String | None class LaunchTemplateAndOverridesResponse(TypedDict, total=False): - LaunchTemplateSpecification: Optional[FleetLaunchTemplateSpecification] - Overrides: Optional[FleetLaunchTemplateOverrides] + LaunchTemplateSpecification: FleetLaunchTemplateSpecification | None + Overrides: FleetLaunchTemplateOverrides | None class CreateFleetError(TypedDict, total=False): - LaunchTemplateAndOverrides: Optional[LaunchTemplateAndOverridesResponse] - Lifecycle: Optional[InstanceLifecycle] - ErrorCode: Optional[String] - ErrorMessage: Optional[String] + LaunchTemplateAndOverrides: LaunchTemplateAndOverridesResponse | None + Lifecycle: InstanceLifecycle | None + ErrorCode: String | None + ErrorMessage: String | None -CreateFleetErrorsSet = List[CreateFleetError] -InstanceIdsSet = List[InstanceId] +CreateFleetErrorsSet = list[CreateFleetError] +InstanceIdsSet = list[InstanceId] class CreateFleetInstance(TypedDict, total=False): - LaunchTemplateAndOverrides: Optional[LaunchTemplateAndOverridesResponse] - Lifecycle: Optional[InstanceLifecycle] - InstanceIds: Optional[InstanceIdsSet] - InstanceType: Optional[InstanceType] - Platform: Optional[PlatformValues] + LaunchTemplateAndOverrides: LaunchTemplateAndOverridesResponse | None + Lifecycle: InstanceLifecycle | None + InstanceIds: InstanceIdsSet | None + InstanceType: InstanceType | None + Platform: PlatformValues | None -CreateFleetInstancesSet = List[CreateFleetInstance] +CreateFleetInstancesSet = list[CreateFleetInstance] class TargetCapacitySpecificationRequest(TypedDict, total=False): TotalTargetCapacity: Integer - OnDemandTargetCapacity: Optional[Integer] - SpotTargetCapacity: Optional[Integer] - DefaultTargetCapacityType: Optional[DefaultTargetCapacityType] - TargetCapacityUnitType: Optional[TargetCapacityUnitType] + OnDemandTargetCapacity: Integer | None + SpotTargetCapacity: Integer | None + DefaultTargetCapacityType: DefaultTargetCapacityType | None + TargetCapacityUnitType: TargetCapacityUnitType | None class NetworkBandwidthGbpsRequest(TypedDict, total=False): - Min: Optional[Double] - Max: Optional[Double] + Min: Double | None + Max: Double | None class TotalLocalStorageGBRequest(TypedDict, total=False): - Min: Optional[Double] - Max: Optional[Double] + Min: Double | None + Max: Double | None class NetworkInterfaceCountRequest(TypedDict, total=False): - Min: Optional[Integer] - Max: Optional[Integer] + Min: Integer | None + Max: Integer | None class MemoryGiBPerVCpuRequest(TypedDict, total=False): - Min: Optional[Double] - Max: Optional[Double] + Min: Double | None + Max: Double | None class MemoryMiBRequest(TypedDict, total=False): Min: Integer - Max: Optional[Integer] + Max: Integer | None class VCpuCountRangeRequest(TypedDict, total=False): Min: Integer - Max: Optional[Integer] + Max: Integer | None class InstanceRequirementsRequest(TypedDict, total=False): VCpuCount: VCpuCountRangeRequest MemoryMiB: MemoryMiBRequest - CpuManufacturers: Optional[CpuManufacturerSet] - MemoryGiBPerVCpu: Optional[MemoryGiBPerVCpuRequest] - ExcludedInstanceTypes: Optional[ExcludedInstanceTypeSet] - InstanceGenerations: Optional[InstanceGenerationSet] - SpotMaxPricePercentageOverLowestPrice: Optional[Integer] - OnDemandMaxPricePercentageOverLowestPrice: Optional[Integer] - BareMetal: Optional[BareMetal] - BurstablePerformance: Optional[BurstablePerformance] - RequireHibernateSupport: Optional[Boolean] - NetworkInterfaceCount: Optional[NetworkInterfaceCountRequest] - LocalStorage: Optional[LocalStorage] - LocalStorageTypes: Optional[LocalStorageTypeSet] - TotalLocalStorageGB: Optional[TotalLocalStorageGBRequest] - BaselineEbsBandwidthMbps: Optional[BaselineEbsBandwidthMbpsRequest] - AcceleratorTypes: Optional[AcceleratorTypeSet] - AcceleratorCount: Optional[AcceleratorCountRequest] - AcceleratorManufacturers: Optional[AcceleratorManufacturerSet] - AcceleratorNames: Optional[AcceleratorNameSet] - AcceleratorTotalMemoryMiB: Optional[AcceleratorTotalMemoryMiBRequest] - NetworkBandwidthGbps: Optional[NetworkBandwidthGbpsRequest] - AllowedInstanceTypes: Optional[AllowedInstanceTypeSet] - MaxSpotPriceAsPercentageOfOptimalOnDemandPrice: Optional[Integer] - BaselinePerformanceFactors: Optional[BaselinePerformanceFactorsRequest] + CpuManufacturers: CpuManufacturerSet | None + MemoryGiBPerVCpu: MemoryGiBPerVCpuRequest | None + ExcludedInstanceTypes: ExcludedInstanceTypeSet | None + InstanceGenerations: InstanceGenerationSet | None + SpotMaxPricePercentageOverLowestPrice: Integer | None + OnDemandMaxPricePercentageOverLowestPrice: Integer | None + BareMetal: BareMetal | None + BurstablePerformance: BurstablePerformance | None + RequireHibernateSupport: Boolean | None + NetworkInterfaceCount: NetworkInterfaceCountRequest | None + LocalStorage: LocalStorage | None + LocalStorageTypes: LocalStorageTypeSet | None + TotalLocalStorageGB: TotalLocalStorageGBRequest | None + BaselineEbsBandwidthMbps: BaselineEbsBandwidthMbpsRequest | None + AcceleratorTypes: AcceleratorTypeSet | None + AcceleratorCount: AcceleratorCountRequest | None + AcceleratorManufacturers: AcceleratorManufacturerSet | None + AcceleratorNames: AcceleratorNameSet | None + AcceleratorTotalMemoryMiB: AcceleratorTotalMemoryMiBRequest | None + NetworkBandwidthGbps: NetworkBandwidthGbpsRequest | None + AllowedInstanceTypes: AllowedInstanceTypeSet | None + MaxSpotPriceAsPercentageOfOptimalOnDemandPrice: Integer | None + BaselinePerformanceFactors: BaselinePerformanceFactorsRequest | None + RequireEncryptionInTransit: Boolean | None class FleetEbsBlockDeviceRequest(TypedDict, total=False): - Encrypted: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Iops: Optional[Integer] - Throughput: Optional[Integer] - KmsKeyId: Optional[KmsKeyId] - SnapshotId: Optional[SnapshotId] - VolumeSize: Optional[Integer] - VolumeType: Optional[VolumeType] + Encrypted: Boolean | None + DeleteOnTermination: Boolean | None + Iops: Integer | None + Throughput: Integer | None + KmsKeyId: KmsKeyId | None + SnapshotId: SnapshotId | None + VolumeSize: Integer | None + VolumeType: VolumeType | None class FleetBlockDeviceMappingRequest(TypedDict, total=False): - DeviceName: Optional[String] - VirtualName: Optional[String] - Ebs: Optional[FleetEbsBlockDeviceRequest] - NoDevice: Optional[String] + DeviceName: String | None + VirtualName: String | None + Ebs: FleetEbsBlockDeviceRequest | None + NoDevice: String | None -FleetBlockDeviceMappingRequestList = List[FleetBlockDeviceMappingRequest] +FleetBlockDeviceMappingRequestList = list[FleetBlockDeviceMappingRequest] class Placement(TypedDict, total=False): - Affinity: Optional[String] - GroupName: Optional[PlacementGroupName] - PartitionNumber: Optional[Integer] - HostId: Optional[String] - Tenancy: Optional[Tenancy] - SpreadDomain: Optional[String] - HostResourceGroupArn: Optional[String] - GroupId: Optional[PlacementGroupId] - AvailabilityZone: Optional[String] + AvailabilityZoneId: AvailabilityZoneId | None + Affinity: String | None + GroupName: PlacementGroupName | None + PartitionNumber: Integer | None + HostId: String | None + Tenancy: Tenancy | None + SpreadDomain: String | None + HostResourceGroupArn: String | None + GroupId: PlacementGroupId | None + AvailabilityZone: String | None class FleetLaunchTemplateOverridesRequest(TypedDict, total=False): - InstanceType: Optional[InstanceType] - MaxPrice: Optional[String] - SubnetId: Optional[SubnetId] - AvailabilityZone: Optional[String] - WeightedCapacity: Optional[Double] - Priority: Optional[Double] - Placement: Optional[Placement] - BlockDeviceMappings: Optional[FleetBlockDeviceMappingRequestList] - InstanceRequirements: Optional[InstanceRequirementsRequest] - ImageId: Optional[String] + InstanceType: InstanceType | None + MaxPrice: String | None + SubnetId: SubnetId | None + AvailabilityZone: AvailabilityZoneName | None + WeightedCapacity: Double | None + Priority: Double | None + Placement: Placement | None + BlockDeviceMappings: FleetBlockDeviceMappingRequestList | None + InstanceRequirements: InstanceRequirementsRequest | None + ImageId: String | None + AvailabilityZoneId: AvailabilityZoneId | None -FleetLaunchTemplateOverridesListRequest = List[FleetLaunchTemplateOverridesRequest] +FleetLaunchTemplateOverridesListRequest = list[FleetLaunchTemplateOverridesRequest] class FleetLaunchTemplateSpecificationRequest(TypedDict, total=False): - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[LaunchTemplateName] - Version: Optional[String] + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: LaunchTemplateName | None + Version: String | None class FleetLaunchTemplateConfigRequest(TypedDict, total=False): - LaunchTemplateSpecification: Optional[FleetLaunchTemplateSpecificationRequest] - Overrides: Optional[FleetLaunchTemplateOverridesListRequest] + LaunchTemplateSpecification: FleetLaunchTemplateSpecificationRequest | None + Overrides: FleetLaunchTemplateOverridesListRequest | None -FleetLaunchTemplateConfigListRequest = List[FleetLaunchTemplateConfigRequest] +FleetLaunchTemplateConfigListRequest = list[FleetLaunchTemplateConfigRequest] class OnDemandOptionsRequest(TypedDict, total=False): - AllocationStrategy: Optional[FleetOnDemandAllocationStrategy] - CapacityReservationOptions: Optional[CapacityReservationOptionsRequest] - SingleInstanceType: Optional[Boolean] - SingleAvailabilityZone: Optional[Boolean] - MinTargetCapacity: Optional[Integer] - MaxTotalPrice: Optional[String] + AllocationStrategy: FleetOnDemandAllocationStrategy | None + CapacityReservationOptions: CapacityReservationOptionsRequest | None + SingleInstanceType: Boolean | None + SingleAvailabilityZone: Boolean | None + MinTargetCapacity: Integer | None + MaxTotalPrice: String | None class FleetSpotCapacityRebalanceRequest(TypedDict, total=False): - ReplacementStrategy: Optional[FleetReplacementStrategy] - TerminationDelay: Optional[Integer] + ReplacementStrategy: FleetReplacementStrategy | None + TerminationDelay: Integer | None class FleetSpotMaintenanceStrategiesRequest(TypedDict, total=False): - CapacityRebalance: Optional[FleetSpotCapacityRebalanceRequest] + CapacityRebalance: FleetSpotCapacityRebalanceRequest | None class SpotOptionsRequest(TypedDict, total=False): - AllocationStrategy: Optional[SpotAllocationStrategy] - MaintenanceStrategies: Optional[FleetSpotMaintenanceStrategiesRequest] - InstanceInterruptionBehavior: Optional[SpotInstanceInterruptionBehavior] - InstancePoolsToUseCount: Optional[Integer] - SingleInstanceType: Optional[Boolean] - SingleAvailabilityZone: Optional[Boolean] - MinTargetCapacity: Optional[Integer] - MaxTotalPrice: Optional[String] + AllocationStrategy: SpotAllocationStrategy | None + MaintenanceStrategies: FleetSpotMaintenanceStrategiesRequest | None + InstanceInterruptionBehavior: SpotInstanceInterruptionBehavior | None + InstancePoolsToUseCount: Integer | None + SingleInstanceType: Boolean | None + SingleAvailabilityZone: Boolean | None + MinTargetCapacity: Integer | None + MaxTotalPrice: String | None class CreateFleetRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] - SpotOptions: Optional[SpotOptionsRequest] - OnDemandOptions: Optional[OnDemandOptionsRequest] - ExcessCapacityTerminationPolicy: Optional[FleetExcessCapacityTerminationPolicy] + DryRun: Boolean | None + ClientToken: String | None + SpotOptions: SpotOptionsRequest | None + OnDemandOptions: OnDemandOptionsRequest | None + ExcessCapacityTerminationPolicy: FleetExcessCapacityTerminationPolicy | None LaunchTemplateConfigs: FleetLaunchTemplateConfigListRequest TargetCapacitySpecification: TargetCapacitySpecificationRequest - TerminateInstancesWithExpiration: Optional[Boolean] - Type: Optional[FleetType] - ValidFrom: Optional[DateTime] - ValidUntil: Optional[DateTime] - ReplaceUnhealthyInstances: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] - Context: Optional[String] + TerminateInstancesWithExpiration: Boolean | None + Type: FleetType | None + ValidFrom: DateTime | None + ValidUntil: DateTime | None + ReplaceUnhealthyInstances: Boolean | None + TagSpecifications: TagSpecificationList | None + Context: String | None class CreateFleetResult(TypedDict, total=False): - FleetId: Optional[FleetId] - Errors: Optional[CreateFleetErrorsSet] - Instances: Optional[CreateFleetInstancesSet] + FleetId: FleetId | None + Errors: CreateFleetErrorsSet | None + Instances: CreateFleetInstancesSet | None class DestinationOptionsRequest(TypedDict, total=False): - FileFormat: Optional[DestinationFileFormat] - HiveCompatiblePartitions: Optional[Boolean] - PerHourPartition: Optional[Boolean] + FileFormat: DestinationFileFormat | None + HiveCompatiblePartitions: Boolean | None + PerHourPartition: Boolean | None -FlowLogResourceIds = List[FlowLogResourceId] +FlowLogResourceIds = list[FlowLogResourceId] class CreateFlowLogsRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] - DeliverLogsPermissionArn: Optional[String] - DeliverCrossAccountRole: Optional[String] - LogGroupName: Optional[String] + DryRun: Boolean | None + ClientToken: String | None + DeliverLogsPermissionArn: String | None + DeliverCrossAccountRole: String | None + LogGroupName: String | None ResourceIds: FlowLogResourceIds ResourceType: FlowLogsResourceType - TrafficType: Optional[TrafficType] - LogDestinationType: Optional[LogDestinationType] - LogDestination: Optional[String] - LogFormat: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - MaxAggregationInterval: Optional[Integer] - DestinationOptions: Optional[DestinationOptionsRequest] + TrafficType: TrafficType | None + LogDestinationType: LogDestinationType | None + LogDestination: String | None + LogFormat: String | None + TagSpecifications: TagSpecificationList | None + MaxAggregationInterval: Integer | None + DestinationOptions: DestinationOptionsRequest | None class CreateFlowLogsResult(TypedDict, total=False): - ClientToken: Optional[String] - FlowLogIds: Optional[ValueStringList] - Unsuccessful: Optional[UnsuccessfulItemSet] + ClientToken: String | None + FlowLogIds: ValueStringList | None + Unsuccessful: UnsuccessfulItemSet | None class StorageLocation(TypedDict, total=False): - Bucket: Optional[String] - Key: Optional[String] + Bucket: String | None + Key: String | None class CreateFpgaImageRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InputStorageLocation: StorageLocation - LogsStorageLocation: Optional[StorageLocation] - Description: Optional[String] - Name: Optional[String] - ClientToken: Optional[String] - TagSpecifications: Optional[TagSpecificationList] + LogsStorageLocation: StorageLocation | None + Description: String | None + Name: String | None + ClientToken: String | None + TagSpecifications: TagSpecificationList | None class CreateFpgaImageResult(TypedDict, total=False): - FpgaImageId: Optional[String] - FpgaImageGlobalId: Optional[String] + FpgaImageId: String | None + FpgaImageGlobalId: String | None class CreateImageRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - SnapshotLocation: Optional[SnapshotLocationEnum] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + SnapshotLocation: SnapshotLocationEnum | None + DryRun: Boolean | None InstanceId: InstanceId Name: String - Description: Optional[String] - NoReboot: Optional[Boolean] - BlockDeviceMappings: Optional[BlockDeviceMappingRequestList] + Description: String | None + NoReboot: Boolean | None + BlockDeviceMappings: BlockDeviceMappingRequestList | None class CreateImageResult(TypedDict, total=False): - ImageId: Optional[String] + ImageId: String | None + + +ImageUsageReportUserIdStringList = list[String] +ImageUsageResourceTypeOptionValuesList = list[ImageUsageResourceTypeOptionValue] + + +class ImageUsageResourceTypeOptionRequest(TypedDict, total=False): + OptionName: String | None + OptionValues: ImageUsageResourceTypeOptionValuesList | None + + +ImageUsageResourceTypeOptionRequestList = list[ImageUsageResourceTypeOptionRequest] + + +class ImageUsageResourceTypeRequest(TypedDict, total=False): + ResourceType: ImageUsageResourceTypeName | None + ResourceTypeOptions: ImageUsageResourceTypeOptionRequestList | None -SecurityGroupIdStringListRequest = List[SecurityGroupId] +ImageUsageResourceTypeRequestList = list[ImageUsageResourceTypeRequest] + + +class CreateImageUsageReportRequest(ServiceRequest): + ImageId: ImageId + DryRun: Boolean | None + ResourceTypes: ImageUsageResourceTypeRequestList + AccountIds: ImageUsageReportUserIdStringList | None + ClientToken: String | None + TagSpecifications: TagSpecificationList | None + + +class CreateImageUsageReportResult(TypedDict, total=False): + ReportId: ImageUsageReportId | None + + +SecurityGroupIdStringListRequest = list[SecurityGroupId] class CreateInstanceConnectEndpointRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None SubnetId: SubnetId - SecurityGroupIds: Optional[SecurityGroupIdStringListRequest] - PreserveClientIp: Optional[Boolean] - ClientToken: Optional[String] - TagSpecifications: Optional[TagSpecificationList] + SecurityGroupIds: SecurityGroupIdStringListRequest | None + PreserveClientIp: Boolean | None + ClientToken: String | None + TagSpecifications: TagSpecificationList | None + IpAddressType: IpAddressType | None + +class InstanceConnectEndpointDnsNames(TypedDict, total=False): + DnsName: String | None + FipsDnsName: String | None -SecurityGroupIdSet = List[SecurityGroupId] -NetworkInterfaceIdSet = List[String] + +class InstanceConnectEndpointPublicDnsNames(TypedDict, total=False): + Ipv4: InstanceConnectEndpointDnsNames | None + Dualstack: InstanceConnectEndpointDnsNames | None + + +SecurityGroupIdSet = list[SecurityGroupId] +NetworkInterfaceIdSet = list[String] class Ec2InstanceConnectEndpoint(TypedDict, total=False): - OwnerId: Optional[String] - InstanceConnectEndpointId: Optional[InstanceConnectEndpointId] - InstanceConnectEndpointArn: Optional[ResourceArn] - State: Optional[Ec2InstanceConnectEndpointState] - StateMessage: Optional[String] - DnsName: Optional[String] - FipsDnsName: Optional[String] - NetworkInterfaceIds: Optional[NetworkInterfaceIdSet] - VpcId: Optional[VpcId] - AvailabilityZone: Optional[String] - CreatedAt: Optional[MillisecondDateTime] - SubnetId: Optional[SubnetId] - PreserveClientIp: Optional[Boolean] - SecurityGroupIds: Optional[SecurityGroupIdSet] - Tags: Optional[TagList] + OwnerId: String | None + InstanceConnectEndpointId: InstanceConnectEndpointId | None + InstanceConnectEndpointArn: ResourceArn | None + State: Ec2InstanceConnectEndpointState | None + StateMessage: String | None + DnsName: String | None + FipsDnsName: String | None + NetworkInterfaceIds: NetworkInterfaceIdSet | None + VpcId: VpcId | None + AvailabilityZone: String | None + CreatedAt: MillisecondDateTime | None + SubnetId: SubnetId | None + PreserveClientIp: Boolean | None + SecurityGroupIds: SecurityGroupIdSet | None + Tags: TagList | None + IpAddressType: IpAddressType | None + PublicDnsNames: InstanceConnectEndpointPublicDnsNames | None + AvailabilityZoneId: AvailabilityZoneId | None class CreateInstanceConnectEndpointResult(TypedDict, total=False): - InstanceConnectEndpoint: Optional[Ec2InstanceConnectEndpoint] - ClientToken: Optional[String] + InstanceConnectEndpoint: Ec2InstanceConnectEndpoint | None + ClientToken: String | None class InstanceEventWindowTimeRangeRequest(TypedDict, total=False): - StartWeekDay: Optional[WeekDay] - StartHour: Optional[Hour] - EndWeekDay: Optional[WeekDay] - EndHour: Optional[Hour] + StartWeekDay: WeekDay | None + StartHour: Hour | None + EndWeekDay: WeekDay | None + EndHour: Hour | None -InstanceEventWindowTimeRangeRequestSet = List[InstanceEventWindowTimeRangeRequest] +InstanceEventWindowTimeRangeRequestSet = list[InstanceEventWindowTimeRangeRequest] class CreateInstanceEventWindowRequest(ServiceRequest): - DryRun: Optional[Boolean] - Name: Optional[String] - TimeRanges: Optional[InstanceEventWindowTimeRangeRequestSet] - CronExpression: Optional[InstanceEventWindowCronExpression] - TagSpecifications: Optional[TagSpecificationList] + DryRun: Boolean | None + Name: String | None + TimeRanges: InstanceEventWindowTimeRangeRequestSet | None + CronExpression: InstanceEventWindowCronExpression | None + TagSpecifications: TagSpecificationList | None class CreateInstanceEventWindowResult(TypedDict, total=False): - InstanceEventWindow: Optional[InstanceEventWindow] + InstanceEventWindow: InstanceEventWindow | None class ExportToS3TaskSpecification(TypedDict, total=False): - DiskImageFormat: Optional[DiskImageFormat] - ContainerFormat: Optional[ContainerFormat] - S3Bucket: Optional[String] - S3Prefix: Optional[String] + DiskImageFormat: DiskImageFormat | None + ContainerFormat: ContainerFormat | None + S3Bucket: String | None + S3Prefix: String | None class CreateInstanceExportTaskRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - Description: Optional[String] + TagSpecifications: TagSpecificationList | None + Description: String | None InstanceId: InstanceId TargetEnvironment: ExportEnvironment ExportToS3Task: ExportToS3TaskSpecification class InstanceExportDetails(TypedDict, total=False): - InstanceId: Optional[String] - TargetEnvironment: Optional[ExportEnvironment] + InstanceId: String | None + TargetEnvironment: ExportEnvironment | None class ExportToS3Task(TypedDict, total=False): - ContainerFormat: Optional[ContainerFormat] - DiskImageFormat: Optional[DiskImageFormat] - S3Bucket: Optional[String] - S3Key: Optional[String] + ContainerFormat: ContainerFormat | None + DiskImageFormat: DiskImageFormat | None + S3Bucket: String | None + S3Key: String | None class ExportTask(TypedDict, total=False): - Description: Optional[String] - ExportTaskId: Optional[String] - ExportToS3Task: Optional[ExportToS3Task] - InstanceExportDetails: Optional[InstanceExportDetails] - State: Optional[ExportTaskState] - StatusMessage: Optional[String] - Tags: Optional[TagList] + Description: String | None + ExportTaskId: String | None + ExportToS3Task: ExportToS3Task | None + InstanceExportDetails: InstanceExportDetails | None + State: ExportTaskState | None + StatusMessage: String | None + Tags: TagList | None class CreateInstanceExportTaskResult(TypedDict, total=False): - ExportTask: Optional[ExportTask] + ExportTask: ExportTask | None class CreateInternetGatewayRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class InternetGateway(TypedDict, total=False): - Attachments: Optional[InternetGatewayAttachmentList] - InternetGatewayId: Optional[String] - OwnerId: Optional[String] - Tags: Optional[TagList] + Attachments: InternetGatewayAttachmentList | None + InternetGatewayId: String | None + OwnerId: String | None + Tags: TagList | None class CreateInternetGatewayResult(TypedDict, total=False): - InternetGateway: Optional[InternetGateway] + InternetGateway: InternetGateway | None + + +class CreateInterruptibleCapacityReservationAllocationRequest(ServiceRequest): + CapacityReservationId: CapacityReservationId + InstanceCount: Integer + ClientToken: String | None + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None + + +class CreateInterruptibleCapacityReservationAllocationResult(TypedDict, total=False): + SourceCapacityReservationId: CapacityReservationId | None + TargetInstanceCount: Integer | None + Status: InterruptibleCapacityReservationAllocationStatus | None + InterruptionType: InterruptionType | None class CreateIpamExternalResourceVerificationTokenRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamId: IpamId - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] + TagSpecifications: TagSpecificationList | None + ClientToken: String | None class IpamExternalResourceVerificationToken(TypedDict, total=False): - IpamExternalResourceVerificationTokenId: Optional[IpamExternalResourceVerificationTokenId] - IpamExternalResourceVerificationTokenArn: Optional[ResourceArn] - IpamId: Optional[IpamId] - IpamArn: Optional[ResourceArn] - IpamRegion: Optional[String] - TokenValue: Optional[String] - TokenName: Optional[String] - NotAfter: Optional[MillisecondDateTime] - Status: Optional[TokenState] - Tags: Optional[TagList] - State: Optional[IpamExternalResourceVerificationTokenState] + IpamExternalResourceVerificationTokenId: IpamExternalResourceVerificationTokenId | None + IpamExternalResourceVerificationTokenArn: ResourceArn | None + IpamId: IpamId | None + IpamArn: ResourceArn | None + IpamRegion: String | None + TokenValue: String | None + TokenName: String | None + NotAfter: MillisecondDateTime | None + Status: TokenState | None + Tags: TagList | None + State: IpamExternalResourceVerificationTokenState | None class CreateIpamExternalResourceVerificationTokenResult(TypedDict, total=False): - IpamExternalResourceVerificationToken: Optional[IpamExternalResourceVerificationToken] + IpamExternalResourceVerificationToken: IpamExternalResourceVerificationToken | None + + +class CreateIpamPolicyRequest(ServiceRequest): + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + IpamId: IpamId + + +class IpamPolicy(TypedDict, total=False): + OwnerId: String | None + IpamPolicyId: IpamPolicyId | None + IpamPolicyArn: ResourceArn | None + IpamPolicyRegion: String | None + State: IpamPolicyState | None + StateMessage: String | None + Tags: TagList | None + IpamId: IpamId | None + + +class CreateIpamPolicyResult(TypedDict, total=False): + IpamPolicy: IpamPolicy | None class IpamPoolSourceResourceRequest(TypedDict, total=False): - ResourceId: Optional[String] - ResourceType: Optional[IpamPoolSourceResourceType] - ResourceRegion: Optional[String] - ResourceOwner: Optional[String] + ResourceId: String | None + ResourceType: IpamPoolSourceResourceType | None + ResourceRegion: String | None + ResourceOwner: String | None class RequestIpamResourceTag(TypedDict, total=False): - Key: Optional[String] - Value: Optional[String] + Key: String | None + Value: String | None -RequestIpamResourceTagList = List[RequestIpamResourceTag] +RequestIpamResourceTagList = list[RequestIpamResourceTag] class CreateIpamPoolRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamScopeId: IpamScopeId - Locale: Optional[String] - SourceIpamPoolId: Optional[IpamPoolId] - Description: Optional[String] + Locale: String | None + SourceIpamPoolId: IpamPoolId | None + Description: String | None AddressFamily: AddressFamily - AutoImport: Optional[Boolean] - PubliclyAdvertisable: Optional[Boolean] - AllocationMinNetmaskLength: Optional[IpamNetmaskLength] - AllocationMaxNetmaskLength: Optional[IpamNetmaskLength] - AllocationDefaultNetmaskLength: Optional[IpamNetmaskLength] - AllocationResourceTags: Optional[RequestIpamResourceTagList] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - AwsService: Optional[IpamPoolAwsService] - PublicIpSource: Optional[IpamPoolPublicIpSource] - SourceResource: Optional[IpamPoolSourceResourceRequest] + AutoImport: Boolean | None + PubliclyAdvertisable: Boolean | None + AllocationMinNetmaskLength: IpamNetmaskLength | None + AllocationMaxNetmaskLength: IpamNetmaskLength | None + AllocationDefaultNetmaskLength: IpamNetmaskLength | None + AllocationResourceTags: RequestIpamResourceTagList | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + AwsService: IpamPoolAwsService | None + PublicIpSource: IpamPoolPublicIpSource | None + SourceResource: IpamPoolSourceResourceRequest | None class IpamPoolSourceResource(TypedDict, total=False): - ResourceId: Optional[String] - ResourceType: Optional[IpamPoolSourceResourceType] - ResourceRegion: Optional[String] - ResourceOwner: Optional[String] + ResourceId: String | None + ResourceType: IpamPoolSourceResourceType | None + ResourceRegion: String | None + ResourceOwner: String | None class IpamResourceTag(TypedDict, total=False): - Key: Optional[String] - Value: Optional[String] + Key: String | None + Value: String | None -IpamResourceTagList = List[IpamResourceTag] +IpamResourceTagList = list[IpamResourceTag] class IpamPool(TypedDict, total=False): - OwnerId: Optional[String] - IpamPoolId: Optional[IpamPoolId] - SourceIpamPoolId: Optional[IpamPoolId] - IpamPoolArn: Optional[ResourceArn] - IpamScopeArn: Optional[ResourceArn] - IpamScopeType: Optional[IpamScopeType] - IpamArn: Optional[ResourceArn] - IpamRegion: Optional[String] - Locale: Optional[String] - PoolDepth: Optional[Integer] - State: Optional[IpamPoolState] - StateMessage: Optional[String] - Description: Optional[String] - AutoImport: Optional[Boolean] - PubliclyAdvertisable: Optional[Boolean] - AddressFamily: Optional[AddressFamily] - AllocationMinNetmaskLength: Optional[IpamNetmaskLength] - AllocationMaxNetmaskLength: Optional[IpamNetmaskLength] - AllocationDefaultNetmaskLength: Optional[IpamNetmaskLength] - AllocationResourceTags: Optional[IpamResourceTagList] - Tags: Optional[TagList] - AwsService: Optional[IpamPoolAwsService] - PublicIpSource: Optional[IpamPoolPublicIpSource] - SourceResource: Optional[IpamPoolSourceResource] + OwnerId: String | None + IpamPoolId: IpamPoolId | None + SourceIpamPoolId: IpamPoolId | None + IpamPoolArn: ResourceArn | None + IpamScopeArn: ResourceArn | None + IpamScopeType: IpamScopeType | None + IpamArn: ResourceArn | None + IpamRegion: String | None + Locale: String | None + PoolDepth: Integer | None + State: IpamPoolState | None + StateMessage: String | None + Description: String | None + AutoImport: Boolean | None + PubliclyAdvertisable: Boolean | None + AddressFamily: AddressFamily | None + AllocationMinNetmaskLength: IpamNetmaskLength | None + AllocationMaxNetmaskLength: IpamNetmaskLength | None + AllocationDefaultNetmaskLength: IpamNetmaskLength | None + AllocationResourceTags: IpamResourceTagList | None + Tags: TagList | None + AwsService: IpamPoolAwsService | None + PublicIpSource: IpamPoolPublicIpSource | None + SourceResource: IpamPoolSourceResource | None class CreateIpamPoolResult(TypedDict, total=False): - IpamPool: Optional[IpamPool] + IpamPool: IpamPool | None + + +class IpamPrefixListResolverRuleConditionRequest(TypedDict, total=False): + Operation: IpamPrefixListResolverRuleConditionOperation + IpamPoolId: String | None + ResourceId: String | None + ResourceOwner: String | None + ResourceRegion: String | None + ResourceTag: RequestIpamResourceTag | None + Cidr: String | None + + +IpamPrefixListResolverRuleConditionRequestSet = list[IpamPrefixListResolverRuleConditionRequest] + + +class IpamPrefixListResolverRuleRequest(TypedDict, total=False): + RuleType: IpamPrefixListResolverRuleType + StaticCidr: String | None + IpamScopeId: IpamScopeId | None + ResourceType: IpamResourceType | None + Conditions: IpamPrefixListResolverRuleConditionRequestSet | None + + +IpamPrefixListResolverRuleRequestSet = list[IpamPrefixListResolverRuleRequest] + + +class CreateIpamPrefixListResolverRequest(ServiceRequest): + DryRun: Boolean | None + IpamId: IpamId + Description: String | None + AddressFamily: AddressFamily + Rules: IpamPrefixListResolverRuleRequestSet | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + + +class IpamPrefixListResolver(TypedDict, total=False): + OwnerId: String | None + IpamPrefixListResolverId: IpamPrefixListResolverId | None + IpamPrefixListResolverArn: ResourceArn | None + IpamArn: ResourceArn | None + IpamRegion: String | None + Description: String | None + AddressFamily: AddressFamily | None + State: IpamPrefixListResolverState | None + Tags: TagList | None + LastVersionCreationStatus: IpamPrefixListResolverVersionCreationStatus | None + LastVersionCreationStatusMessage: String | None + + +class CreateIpamPrefixListResolverResult(TypedDict, total=False): + IpamPrefixListResolver: IpamPrefixListResolver | None + + +class CreateIpamPrefixListResolverTargetRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverId: IpamPrefixListResolverId + PrefixListId: String + PrefixListRegion: String + DesiredVersion: BoxedLong | None + TrackLatestVersion: Boolean + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + + +class IpamPrefixListResolverTarget(TypedDict, total=False): + IpamPrefixListResolverTargetId: IpamPrefixListResolverTargetId | None + IpamPrefixListResolverTargetArn: ResourceArn | None + IpamPrefixListResolverId: IpamPrefixListResolverId | None + OwnerId: String | None + PrefixListId: PrefixListResourceId | None + PrefixListRegion: String | None + DesiredVersion: BoxedLong | None + LastSyncedVersion: BoxedLong | None + TrackLatestVersion: Boolean | None + StateMessage: String | None + State: IpamPrefixListResolverTargetState | None + Tags: TagList | None + + +class CreateIpamPrefixListResolverTargetResult(TypedDict, total=False): + IpamPrefixListResolverTarget: IpamPrefixListResolverTarget | None class CreateIpamRequest(ServiceRequest): - DryRun: Optional[Boolean] - Description: Optional[String] - OperatingRegions: Optional[AddIpamOperatingRegionSet] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - Tier: Optional[IpamTier] - EnablePrivateGua: Optional[Boolean] - MeteredAccount: Optional[IpamMeteredAccount] + DryRun: Boolean | None + Description: String | None + OperatingRegions: AddIpamOperatingRegionSet | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + Tier: IpamTier | None + EnablePrivateGua: Boolean | None + MeteredAccount: IpamMeteredAccount | None class CreateIpamResourceDiscoveryRequest(ServiceRequest): - DryRun: Optional[Boolean] - Description: Optional[String] - OperatingRegions: Optional[AddIpamOperatingRegionSet] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] + DryRun: Boolean | None + Description: String | None + OperatingRegions: AddIpamOperatingRegionSet | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None class IpamOrganizationalUnitExclusion(TypedDict, total=False): - OrganizationsEntityPath: Optional[String] + OrganizationsEntityPath: String | None -IpamOrganizationalUnitExclusionSet = List[IpamOrganizationalUnitExclusion] +IpamOrganizationalUnitExclusionSet = list[IpamOrganizationalUnitExclusion] class IpamOperatingRegion(TypedDict, total=False): - RegionName: Optional[String] + RegionName: String | None -IpamOperatingRegionSet = List[IpamOperatingRegion] +IpamOperatingRegionSet = list[IpamOperatingRegion] class IpamResourceDiscovery(TypedDict, total=False): - OwnerId: Optional[String] - IpamResourceDiscoveryId: Optional[IpamResourceDiscoveryId] - IpamResourceDiscoveryArn: Optional[String] - IpamResourceDiscoveryRegion: Optional[String] - Description: Optional[String] - OperatingRegions: Optional[IpamOperatingRegionSet] - IsDefault: Optional[Boolean] - State: Optional[IpamResourceDiscoveryState] - Tags: Optional[TagList] - OrganizationalUnitExclusions: Optional[IpamOrganizationalUnitExclusionSet] + OwnerId: String | None + IpamResourceDiscoveryId: IpamResourceDiscoveryId | None + IpamResourceDiscoveryArn: String | None + IpamResourceDiscoveryRegion: String | None + Description: String | None + OperatingRegions: IpamOperatingRegionSet | None + IsDefault: Boolean | None + State: IpamResourceDiscoveryState | None + Tags: TagList | None + OrganizationalUnitExclusions: IpamOrganizationalUnitExclusionSet | None class CreateIpamResourceDiscoveryResult(TypedDict, total=False): - IpamResourceDiscovery: Optional[IpamResourceDiscovery] + IpamResourceDiscovery: IpamResourceDiscovery | None class Ipam(TypedDict, total=False): - OwnerId: Optional[String] - IpamId: Optional[IpamId] - IpamArn: Optional[ResourceArn] - IpamRegion: Optional[String] - PublicDefaultScopeId: Optional[IpamScopeId] - PrivateDefaultScopeId: Optional[IpamScopeId] - ScopeCount: Optional[Integer] - Description: Optional[String] - OperatingRegions: Optional[IpamOperatingRegionSet] - State: Optional[IpamState] - Tags: Optional[TagList] - DefaultResourceDiscoveryId: Optional[IpamResourceDiscoveryId] - DefaultResourceDiscoveryAssociationId: Optional[IpamResourceDiscoveryAssociationId] - ResourceDiscoveryAssociationCount: Optional[Integer] - StateMessage: Optional[String] - Tier: Optional[IpamTier] - EnablePrivateGua: Optional[Boolean] - MeteredAccount: Optional[IpamMeteredAccount] + OwnerId: String | None + IpamId: IpamId | None + IpamArn: ResourceArn | None + IpamRegion: String | None + PublicDefaultScopeId: IpamScopeId | None + PrivateDefaultScopeId: IpamScopeId | None + ScopeCount: Integer | None + Description: String | None + OperatingRegions: IpamOperatingRegionSet | None + State: IpamState | None + Tags: TagList | None + DefaultResourceDiscoveryId: IpamResourceDiscoveryId | None + DefaultResourceDiscoveryAssociationId: IpamResourceDiscoveryAssociationId | None + ResourceDiscoveryAssociationCount: Integer | None + StateMessage: String | None + Tier: IpamTier | None + EnablePrivateGua: Boolean | None + MeteredAccount: IpamMeteredAccount | None class CreateIpamResult(TypedDict, total=False): - Ipam: Optional[Ipam] + Ipam: Ipam | None + + +class ExternalAuthorityConfiguration(TypedDict, total=False): + Type: IpamScopeExternalAuthorityType | None + ExternalResourceIdentifier: String | None class CreateIpamScopeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamId: IpamId - Description: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] + Description: String | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + ExternalAuthorityConfiguration: ExternalAuthorityConfiguration | None + + +class IpamScopeExternalAuthorityConfiguration(TypedDict, total=False): + Type: IpamScopeExternalAuthorityType | None + ExternalResourceIdentifier: String | None class IpamScope(TypedDict, total=False): - OwnerId: Optional[String] - IpamScopeId: Optional[IpamScopeId] - IpamScopeArn: Optional[ResourceArn] - IpamArn: Optional[ResourceArn] - IpamRegion: Optional[String] - IpamScopeType: Optional[IpamScopeType] - IsDefault: Optional[Boolean] - Description: Optional[String] - PoolCount: Optional[Integer] - State: Optional[IpamScopeState] - Tags: Optional[TagList] + OwnerId: String | None + IpamScopeId: IpamScopeId | None + IpamScopeArn: ResourceArn | None + IpamArn: ResourceArn | None + IpamRegion: String | None + IpamScopeType: IpamScopeType | None + IsDefault: Boolean | None + Description: String | None + PoolCount: Integer | None + State: IpamScopeState | None + Tags: TagList | None + ExternalAuthorityConfiguration: IpamScopeExternalAuthorityConfiguration | None class CreateIpamScopeResult(TypedDict, total=False): - IpamScope: Optional[IpamScope] + IpamScope: IpamScope | None class CreateKeyPairRequest(ServiceRequest): KeyName: String - KeyType: Optional[KeyType] - TagSpecifications: Optional[TagSpecificationList] - KeyFormat: Optional[KeyFormat] - DryRun: Optional[Boolean] + KeyType: KeyType | None + TagSpecifications: TagSpecificationList | None + KeyFormat: KeyFormat | None + DryRun: Boolean | None class OperatorRequest(TypedDict, total=False): - Principal: Optional[String] + Principal: String | None + + +class SecondaryInterfacePrivateIpAddressSpecificationRequest(TypedDict, total=False): + PrivateIpAddress: String | None + + +SecondaryInterfacePrivateIpAddressSpecificationListRequest = list[ + SecondaryInterfacePrivateIpAddressSpecificationRequest +] + + +class LaunchTemplateInstanceSecondaryInterfaceSpecificationRequest(TypedDict, total=False): + DeleteOnTermination: Boolean | None + DeviceIndex: Integer | None + PrivateIpAddresses: SecondaryInterfacePrivateIpAddressSpecificationListRequest | None + PrivateIpAddressCount: Integer | None + SecondarySubnetId: SecondarySubnetId | None + InterfaceType: SecondaryInterfaceType | None + NetworkCardIndex: Integer | None + + +LaunchTemplateInstanceSecondaryInterfaceSpecificationRequestList = list[ + LaunchTemplateInstanceSecondaryInterfaceSpecificationRequest +] class LaunchTemplateNetworkPerformanceOptionsRequest(TypedDict, total=False): - BandwidthWeighting: Optional[InstanceBandwidthWeighting] + BandwidthWeighting: InstanceBandwidthWeighting | None class LaunchTemplateInstanceMaintenanceOptionsRequest(TypedDict, total=False): - AutoRecovery: Optional[LaunchTemplateAutoRecoveryState] + AutoRecovery: LaunchTemplateAutoRecoveryState | None class LaunchTemplatePrivateDnsNameOptionsRequest(TypedDict, total=False): - HostnameType: Optional[HostnameType] - EnableResourceNameDnsARecord: Optional[Boolean] - EnableResourceNameDnsAAAARecord: Optional[Boolean] + HostnameType: HostnameType | None + EnableResourceNameDnsARecord: Boolean | None + EnableResourceNameDnsAAAARecord: Boolean | None class LaunchTemplateEnclaveOptionsRequest(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class LaunchTemplateInstanceMetadataOptionsRequest(TypedDict, total=False): - HttpTokens: Optional[LaunchTemplateHttpTokensState] - HttpPutResponseHopLimit: Optional[Integer] - HttpEndpoint: Optional[LaunchTemplateInstanceMetadataEndpointState] - HttpProtocolIpv6: Optional[LaunchTemplateInstanceMetadataProtocolIpv6] - InstanceMetadataTags: Optional[LaunchTemplateInstanceMetadataTagsState] + HttpTokens: LaunchTemplateHttpTokensState | None + HttpPutResponseHopLimit: Integer | None + HttpEndpoint: LaunchTemplateInstanceMetadataEndpointState | None + HttpProtocolIpv6: LaunchTemplateInstanceMetadataProtocolIpv6 | None + InstanceMetadataTags: LaunchTemplateInstanceMetadataTagsState | None class LaunchTemplateHibernationOptionsRequest(TypedDict, total=False): - Configured: Optional[Boolean] + Configured: Boolean | None class LaunchTemplateLicenseConfigurationRequest(TypedDict, total=False): - LicenseConfigurationArn: Optional[String] + LicenseConfigurationArn: String | None -LaunchTemplateLicenseSpecificationListRequest = List[LaunchTemplateLicenseConfigurationRequest] +LaunchTemplateLicenseSpecificationListRequest = list[LaunchTemplateLicenseConfigurationRequest] class LaunchTemplateCapacityReservationSpecificationRequest(TypedDict, total=False): - CapacityReservationPreference: Optional[CapacityReservationPreference] - CapacityReservationTarget: Optional[CapacityReservationTarget] + CapacityReservationPreference: CapacityReservationPreference | None + CapacityReservationTarget: CapacityReservationTarget | None class LaunchTemplateCpuOptionsRequest(TypedDict, total=False): - CoreCount: Optional[Integer] - ThreadsPerCore: Optional[Integer] - AmdSevSnp: Optional[AmdSevSnpSpecification] + CoreCount: Integer | None + ThreadsPerCore: Integer | None + AmdSevSnp: AmdSevSnpSpecification | None + NestedVirtualization: NestedVirtualizationSpecification | None class CreditSpecificationRequest(TypedDict, total=False): @@ -7809,628 +9018,650 @@ class CreditSpecificationRequest(TypedDict, total=False): class LaunchTemplateSpotMarketOptionsRequest(TypedDict, total=False): - MaxPrice: Optional[String] - SpotInstanceType: Optional[SpotInstanceType] - BlockDurationMinutes: Optional[Integer] - ValidUntil: Optional[DateTime] - InstanceInterruptionBehavior: Optional[InstanceInterruptionBehavior] + MaxPrice: String | None + SpotInstanceType: SpotInstanceType | None + BlockDurationMinutes: Integer | None + ValidUntil: DateTime | None + InstanceInterruptionBehavior: InstanceInterruptionBehavior | None class LaunchTemplateInstanceMarketOptionsRequest(TypedDict, total=False): - MarketType: Optional[MarketType] - SpotOptions: Optional[LaunchTemplateSpotMarketOptionsRequest] + MarketType: MarketType | None + SpotOptions: LaunchTemplateSpotMarketOptionsRequest | None -SecurityGroupStringList = List[SecurityGroupName] -SecurityGroupIdStringList = List[SecurityGroupId] +SecurityGroupStringList = list[SecurityGroupName] +SecurityGroupIdStringList = list[SecurityGroupId] class LaunchTemplateElasticInferenceAccelerator(TypedDict, total=False): Type: String - Count: Optional[LaunchTemplateElasticInferenceAcceleratorCount] + Count: LaunchTemplateElasticInferenceAcceleratorCount | None -LaunchTemplateElasticInferenceAcceleratorList = List[LaunchTemplateElasticInferenceAccelerator] +LaunchTemplateElasticInferenceAcceleratorList = list[LaunchTemplateElasticInferenceAccelerator] class ElasticGpuSpecification(TypedDict, total=False): Type: String -ElasticGpuSpecificationList = List[ElasticGpuSpecification] +ElasticGpuSpecificationList = list[ElasticGpuSpecification] class LaunchTemplateTagSpecificationRequest(TypedDict, total=False): - ResourceType: Optional[ResourceType] - Tags: Optional[TagList] + ResourceType: ResourceType | None + Tags: TagList | None -LaunchTemplateTagSpecificationRequestList = List[LaunchTemplateTagSpecificationRequest] +LaunchTemplateTagSpecificationRequestList = list[LaunchTemplateTagSpecificationRequest] class LaunchTemplatePlacementRequest(TypedDict, total=False): - AvailabilityZone: Optional[String] - Affinity: Optional[String] - GroupName: Optional[PlacementGroupName] - HostId: Optional[DedicatedHostId] - Tenancy: Optional[Tenancy] - SpreadDomain: Optional[String] - HostResourceGroupArn: Optional[String] - PartitionNumber: Optional[Integer] - GroupId: Optional[PlacementGroupId] + AvailabilityZone: String | None + AvailabilityZoneId: AvailabilityZoneId | None + Affinity: String | None + GroupName: PlacementGroupName | None + HostId: DedicatedHostId | None + Tenancy: Tenancy | None + SpreadDomain: String | None + HostResourceGroupArn: String | None + PartitionNumber: Integer | None + GroupId: PlacementGroupId | None class LaunchTemplatesMonitoringRequest(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class EnaSrdUdpSpecificationRequest(TypedDict, total=False): - EnaSrdUdpEnabled: Optional[Boolean] + EnaSrdUdpEnabled: Boolean | None class EnaSrdSpecificationRequest(TypedDict, total=False): - EnaSrdEnabled: Optional[Boolean] - EnaSrdUdpSpecification: Optional[EnaSrdUdpSpecificationRequest] + EnaSrdEnabled: Boolean | None + EnaSrdUdpSpecification: EnaSrdUdpSpecificationRequest | None class Ipv6PrefixSpecificationRequest(TypedDict, total=False): - Ipv6Prefix: Optional[String] + Ipv6Prefix: String | None -Ipv6PrefixList = List[Ipv6PrefixSpecificationRequest] +Ipv6PrefixList = list[Ipv6PrefixSpecificationRequest] class Ipv4PrefixSpecificationRequest(TypedDict, total=False): - Ipv4Prefix: Optional[String] + Ipv4Prefix: String | None -Ipv4PrefixList = List[Ipv4PrefixSpecificationRequest] +Ipv4PrefixList = list[Ipv4PrefixSpecificationRequest] class PrivateIpAddressSpecification(TypedDict, total=False): - Primary: Optional[Boolean] - PrivateIpAddress: Optional[String] + Primary: Boolean | None + PrivateIpAddress: String | None -PrivateIpAddressSpecificationList = List[PrivateIpAddressSpecification] +PrivateIpAddressSpecificationList = list[PrivateIpAddressSpecification] class InstanceIpv6AddressRequest(TypedDict, total=False): - Ipv6Address: Optional[String] + Ipv6Address: String | None -InstanceIpv6AddressListRequest = List[InstanceIpv6AddressRequest] +InstanceIpv6AddressListRequest = list[InstanceIpv6AddressRequest] class LaunchTemplateInstanceNetworkInterfaceSpecificationRequest(TypedDict, total=False): - AssociateCarrierIpAddress: Optional[Boolean] - AssociatePublicIpAddress: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Description: Optional[String] - DeviceIndex: Optional[Integer] - Groups: Optional[SecurityGroupIdStringList] - InterfaceType: Optional[String] - Ipv6AddressCount: Optional[Integer] - Ipv6Addresses: Optional[InstanceIpv6AddressListRequest] - NetworkInterfaceId: Optional[NetworkInterfaceId] - PrivateIpAddress: Optional[String] - PrivateIpAddresses: Optional[PrivateIpAddressSpecificationList] - SecondaryPrivateIpAddressCount: Optional[Integer] - SubnetId: Optional[SubnetId] - NetworkCardIndex: Optional[Integer] - Ipv4Prefixes: Optional[Ipv4PrefixList] - Ipv4PrefixCount: Optional[Integer] - Ipv6Prefixes: Optional[Ipv6PrefixList] - Ipv6PrefixCount: Optional[Integer] - PrimaryIpv6: Optional[Boolean] - EnaSrdSpecification: Optional[EnaSrdSpecificationRequest] - ConnectionTrackingSpecification: Optional[ConnectionTrackingSpecificationRequest] - EnaQueueCount: Optional[Integer] - - -LaunchTemplateInstanceNetworkInterfaceSpecificationRequestList = List[ + AssociateCarrierIpAddress: Boolean | None + AssociatePublicIpAddress: Boolean | None + DeleteOnTermination: Boolean | None + Description: String | None + DeviceIndex: Integer | None + Groups: SecurityGroupIdStringList | None + InterfaceType: String | None + Ipv6AddressCount: Integer | None + Ipv6Addresses: InstanceIpv6AddressListRequest | None + NetworkInterfaceId: NetworkInterfaceId | None + PrivateIpAddress: String | None + PrivateIpAddresses: PrivateIpAddressSpecificationList | None + SecondaryPrivateIpAddressCount: Integer | None + SubnetId: SubnetId | None + NetworkCardIndex: Integer | None + Ipv4Prefixes: Ipv4PrefixList | None + Ipv4PrefixCount: Integer | None + Ipv6Prefixes: Ipv6PrefixList | None + Ipv6PrefixCount: Integer | None + PrimaryIpv6: Boolean | None + EnaSrdSpecification: EnaSrdSpecificationRequest | None + ConnectionTrackingSpecification: ConnectionTrackingSpecificationRequest | None + EnaQueueCount: Integer | None + + +LaunchTemplateInstanceNetworkInterfaceSpecificationRequestList = list[ LaunchTemplateInstanceNetworkInterfaceSpecificationRequest ] class LaunchTemplateEbsBlockDeviceRequest(TypedDict, total=False): - Encrypted: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Iops: Optional[Integer] - KmsKeyId: Optional[KmsKeyId] - SnapshotId: Optional[SnapshotId] - VolumeSize: Optional[Integer] - VolumeType: Optional[VolumeType] - Throughput: Optional[Integer] - VolumeInitializationRate: Optional[Integer] + Encrypted: Boolean | None + DeleteOnTermination: Boolean | None + Iops: Integer | None + KmsKeyId: KmsKeyId | None + SnapshotId: SnapshotId | None + VolumeSize: Integer | None + VolumeType: VolumeType | None + Throughput: Integer | None + VolumeInitializationRate: Integer | None + EbsCardIndex: Integer | None class LaunchTemplateBlockDeviceMappingRequest(TypedDict, total=False): - DeviceName: Optional[String] - VirtualName: Optional[String] - Ebs: Optional[LaunchTemplateEbsBlockDeviceRequest] - NoDevice: Optional[String] + DeviceName: String | None + VirtualName: String | None + Ebs: LaunchTemplateEbsBlockDeviceRequest | None + NoDevice: String | None -LaunchTemplateBlockDeviceMappingRequestList = List[LaunchTemplateBlockDeviceMappingRequest] +LaunchTemplateBlockDeviceMappingRequestList = list[LaunchTemplateBlockDeviceMappingRequest] class LaunchTemplateIamInstanceProfileSpecificationRequest(TypedDict, total=False): - Arn: Optional[String] - Name: Optional[String] + Arn: String | None + Name: String | None class RequestLaunchTemplateData(TypedDict, total=False): - KernelId: Optional[KernelId] - EbsOptimized: Optional[Boolean] - IamInstanceProfile: Optional[LaunchTemplateIamInstanceProfileSpecificationRequest] - BlockDeviceMappings: Optional[LaunchTemplateBlockDeviceMappingRequestList] - NetworkInterfaces: Optional[LaunchTemplateInstanceNetworkInterfaceSpecificationRequestList] - ImageId: Optional[ImageId] - InstanceType: Optional[InstanceType] - KeyName: Optional[KeyPairName] - Monitoring: Optional[LaunchTemplatesMonitoringRequest] - Placement: Optional[LaunchTemplatePlacementRequest] - RamDiskId: Optional[RamdiskId] - DisableApiTermination: Optional[Boolean] - InstanceInitiatedShutdownBehavior: Optional[ShutdownBehavior] - UserData: Optional[SensitiveUserData] - TagSpecifications: Optional[LaunchTemplateTagSpecificationRequestList] - ElasticGpuSpecifications: Optional[ElasticGpuSpecificationList] - ElasticInferenceAccelerators: Optional[LaunchTemplateElasticInferenceAcceleratorList] - SecurityGroupIds: Optional[SecurityGroupIdStringList] - SecurityGroups: Optional[SecurityGroupStringList] - InstanceMarketOptions: Optional[LaunchTemplateInstanceMarketOptionsRequest] - CreditSpecification: Optional[CreditSpecificationRequest] - CpuOptions: Optional[LaunchTemplateCpuOptionsRequest] - CapacityReservationSpecification: Optional[ - LaunchTemplateCapacityReservationSpecificationRequest - ] - LicenseSpecifications: Optional[LaunchTemplateLicenseSpecificationListRequest] - HibernationOptions: Optional[LaunchTemplateHibernationOptionsRequest] - MetadataOptions: Optional[LaunchTemplateInstanceMetadataOptionsRequest] - EnclaveOptions: Optional[LaunchTemplateEnclaveOptionsRequest] - InstanceRequirements: Optional[InstanceRequirementsRequest] - PrivateDnsNameOptions: Optional[LaunchTemplatePrivateDnsNameOptionsRequest] - MaintenanceOptions: Optional[LaunchTemplateInstanceMaintenanceOptionsRequest] - DisableApiStop: Optional[Boolean] - Operator: Optional[OperatorRequest] - NetworkPerformanceOptions: Optional[LaunchTemplateNetworkPerformanceOptionsRequest] + KernelId: KernelId | None + EbsOptimized: Boolean | None + IamInstanceProfile: LaunchTemplateIamInstanceProfileSpecificationRequest | None + BlockDeviceMappings: LaunchTemplateBlockDeviceMappingRequestList | None + NetworkInterfaces: LaunchTemplateInstanceNetworkInterfaceSpecificationRequestList | None + ImageId: ImageId | None + InstanceType: InstanceType | None + KeyName: KeyPairName | None + Monitoring: LaunchTemplatesMonitoringRequest | None + Placement: LaunchTemplatePlacementRequest | None + RamDiskId: RamdiskId | None + DisableApiTermination: Boolean | None + InstanceInitiatedShutdownBehavior: ShutdownBehavior | None + UserData: SensitiveUserData | None + TagSpecifications: LaunchTemplateTagSpecificationRequestList | None + ElasticGpuSpecifications: ElasticGpuSpecificationList | None + ElasticInferenceAccelerators: LaunchTemplateElasticInferenceAcceleratorList | None + SecurityGroupIds: SecurityGroupIdStringList | None + SecurityGroups: SecurityGroupStringList | None + InstanceMarketOptions: LaunchTemplateInstanceMarketOptionsRequest | None + CreditSpecification: CreditSpecificationRequest | None + CpuOptions: LaunchTemplateCpuOptionsRequest | None + CapacityReservationSpecification: LaunchTemplateCapacityReservationSpecificationRequest | None + LicenseSpecifications: LaunchTemplateLicenseSpecificationListRequest | None + HibernationOptions: LaunchTemplateHibernationOptionsRequest | None + MetadataOptions: LaunchTemplateInstanceMetadataOptionsRequest | None + EnclaveOptions: LaunchTemplateEnclaveOptionsRequest | None + InstanceRequirements: InstanceRequirementsRequest | None + PrivateDnsNameOptions: LaunchTemplatePrivateDnsNameOptionsRequest | None + MaintenanceOptions: LaunchTemplateInstanceMaintenanceOptionsRequest | None + DisableApiStop: Boolean | None + Operator: OperatorRequest | None + NetworkPerformanceOptions: LaunchTemplateNetworkPerformanceOptionsRequest | None + SecondaryInterfaces: LaunchTemplateInstanceSecondaryInterfaceSpecificationRequestList | None class CreateLaunchTemplateRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] + DryRun: Boolean | None + ClientToken: String | None LaunchTemplateName: LaunchTemplateName - VersionDescription: Optional[VersionDescription] + VersionDescription: VersionDescription | None LaunchTemplateData: RequestLaunchTemplateData - Operator: Optional[OperatorRequest] - TagSpecifications: Optional[TagSpecificationList] + Operator: OperatorRequest | None + TagSpecifications: TagSpecificationList | None class ValidationError(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None -ErrorSet = List[ValidationError] +ErrorSet = list[ValidationError] class ValidationWarning(TypedDict, total=False): - Errors: Optional[ErrorSet] - - -class OperatorResponse(TypedDict, total=False): - Managed: Optional[Boolean] - Principal: Optional[String] + Errors: ErrorSet | None class LaunchTemplate(TypedDict, total=False): - LaunchTemplateId: Optional[String] - LaunchTemplateName: Optional[LaunchTemplateName] - CreateTime: Optional[DateTime] - CreatedBy: Optional[String] - DefaultVersionNumber: Optional[Long] - LatestVersionNumber: Optional[Long] - Tags: Optional[TagList] - Operator: Optional[OperatorResponse] + LaunchTemplateId: String | None + LaunchTemplateName: LaunchTemplateName | None + CreateTime: DateTime | None + CreatedBy: String | None + DefaultVersionNumber: Long | None + LatestVersionNumber: Long | None + Tags: TagList | None + Operator: OperatorResponse | None class CreateLaunchTemplateResult(TypedDict, total=False): - LaunchTemplate: Optional[LaunchTemplate] - Warning: Optional[ValidationWarning] + LaunchTemplate: LaunchTemplate | None + Warning: ValidationWarning | None class CreateLaunchTemplateVersionRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[LaunchTemplateName] - SourceVersion: Optional[String] - VersionDescription: Optional[VersionDescription] + DryRun: Boolean | None + ClientToken: String | None + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: LaunchTemplateName | None + SourceVersion: String | None + VersionDescription: VersionDescription | None LaunchTemplateData: RequestLaunchTemplateData - ResolveAlias: Optional[Boolean] + ResolveAlias: Boolean | None + + +class SecondaryInterfacePrivateIpAddressSpecification(TypedDict, total=False): + PrivateIpAddress: String | None + + +SecondaryInterfacePrivateIpAddressSpecificationList = list[ + SecondaryInterfacePrivateIpAddressSpecification +] + + +class LaunchTemplateInstanceSecondaryInterfaceSpecification(TypedDict, total=False): + DeleteOnTermination: Boolean | None + DeviceIndex: Integer | None + PrivateIpAddresses: SecondaryInterfacePrivateIpAddressSpecificationList | None + PrivateIpAddressCount: Integer | None + SecondarySubnetId: SecondarySubnetId | None + InterfaceType: SecondaryInterfaceType | None + NetworkCardIndex: Integer | None + + +LaunchTemplateInstanceSecondaryInterfaceSpecificationList = list[ + LaunchTemplateInstanceSecondaryInterfaceSpecification +] class LaunchTemplateNetworkPerformanceOptions(TypedDict, total=False): - BandwidthWeighting: Optional[InstanceBandwidthWeighting] + BandwidthWeighting: InstanceBandwidthWeighting | None class LaunchTemplateInstanceMaintenanceOptions(TypedDict, total=False): - AutoRecovery: Optional[LaunchTemplateAutoRecoveryState] + AutoRecovery: LaunchTemplateAutoRecoveryState | None class LaunchTemplatePrivateDnsNameOptions(TypedDict, total=False): - HostnameType: Optional[HostnameType] - EnableResourceNameDnsARecord: Optional[Boolean] - EnableResourceNameDnsAAAARecord: Optional[Boolean] + HostnameType: HostnameType | None + EnableResourceNameDnsARecord: Boolean | None + EnableResourceNameDnsAAAARecord: Boolean | None class LaunchTemplateEnclaveOptions(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class LaunchTemplateInstanceMetadataOptions(TypedDict, total=False): - State: Optional[LaunchTemplateInstanceMetadataOptionsState] - HttpTokens: Optional[LaunchTemplateHttpTokensState] - HttpPutResponseHopLimit: Optional[Integer] - HttpEndpoint: Optional[LaunchTemplateInstanceMetadataEndpointState] - HttpProtocolIpv6: Optional[LaunchTemplateInstanceMetadataProtocolIpv6] - InstanceMetadataTags: Optional[LaunchTemplateInstanceMetadataTagsState] + State: LaunchTemplateInstanceMetadataOptionsState | None + HttpTokens: LaunchTemplateHttpTokensState | None + HttpPutResponseHopLimit: Integer | None + HttpEndpoint: LaunchTemplateInstanceMetadataEndpointState | None + HttpProtocolIpv6: LaunchTemplateInstanceMetadataProtocolIpv6 | None + InstanceMetadataTags: LaunchTemplateInstanceMetadataTagsState | None class LaunchTemplateHibernationOptions(TypedDict, total=False): - Configured: Optional[Boolean] + Configured: Boolean | None class LaunchTemplateLicenseConfiguration(TypedDict, total=False): - LicenseConfigurationArn: Optional[String] + LicenseConfigurationArn: String | None -LaunchTemplateLicenseList = List[LaunchTemplateLicenseConfiguration] +LaunchTemplateLicenseList = list[LaunchTemplateLicenseConfiguration] class LaunchTemplateCapacityReservationSpecificationResponse(TypedDict, total=False): - CapacityReservationPreference: Optional[CapacityReservationPreference] - CapacityReservationTarget: Optional[CapacityReservationTargetResponse] + CapacityReservationPreference: CapacityReservationPreference | None + CapacityReservationTarget: CapacityReservationTargetResponse | None class LaunchTemplateCpuOptions(TypedDict, total=False): - CoreCount: Optional[Integer] - ThreadsPerCore: Optional[Integer] - AmdSevSnp: Optional[AmdSevSnpSpecification] + CoreCount: Integer | None + ThreadsPerCore: Integer | None + AmdSevSnp: AmdSevSnpSpecification | None + NestedVirtualization: NestedVirtualizationSpecification | None class CreditSpecification(TypedDict, total=False): - CpuCredits: Optional[String] + CpuCredits: String | None class LaunchTemplateSpotMarketOptions(TypedDict, total=False): - MaxPrice: Optional[String] - SpotInstanceType: Optional[SpotInstanceType] - BlockDurationMinutes: Optional[Integer] - ValidUntil: Optional[DateTime] - InstanceInterruptionBehavior: Optional[InstanceInterruptionBehavior] + MaxPrice: String | None + SpotInstanceType: SpotInstanceType | None + BlockDurationMinutes: Integer | None + ValidUntil: DateTime | None + InstanceInterruptionBehavior: InstanceInterruptionBehavior | None class LaunchTemplateInstanceMarketOptions(TypedDict, total=False): - MarketType: Optional[MarketType] - SpotOptions: Optional[LaunchTemplateSpotMarketOptions] + MarketType: MarketType | None + SpotOptions: LaunchTemplateSpotMarketOptions | None class LaunchTemplateElasticInferenceAcceleratorResponse(TypedDict, total=False): - Type: Optional[String] - Count: Optional[Integer] + Type: String | None + Count: Integer | None -LaunchTemplateElasticInferenceAcceleratorResponseList = List[ +LaunchTemplateElasticInferenceAcceleratorResponseList = list[ LaunchTemplateElasticInferenceAcceleratorResponse ] class ElasticGpuSpecificationResponse(TypedDict, total=False): - Type: Optional[String] + Type: String | None -ElasticGpuSpecificationResponseList = List[ElasticGpuSpecificationResponse] +ElasticGpuSpecificationResponseList = list[ElasticGpuSpecificationResponse] class LaunchTemplateTagSpecification(TypedDict, total=False): - ResourceType: Optional[ResourceType] - Tags: Optional[TagList] + ResourceType: ResourceType | None + Tags: TagList | None -LaunchTemplateTagSpecificationList = List[LaunchTemplateTagSpecification] +LaunchTemplateTagSpecificationList = list[LaunchTemplateTagSpecification] class LaunchTemplatePlacement(TypedDict, total=False): - AvailabilityZone: Optional[String] - Affinity: Optional[String] - GroupName: Optional[String] - HostId: Optional[String] - Tenancy: Optional[Tenancy] - SpreadDomain: Optional[String] - HostResourceGroupArn: Optional[String] - PartitionNumber: Optional[Integer] - GroupId: Optional[PlacementGroupId] + AvailabilityZone: String | None + AvailabilityZoneId: AvailabilityZoneId | None + Affinity: String | None + GroupName: String | None + HostId: String | None + Tenancy: Tenancy | None + SpreadDomain: String | None + HostResourceGroupArn: String | None + PartitionNumber: Integer | None + GroupId: PlacementGroupId | None class LaunchTemplatesMonitoring(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class LaunchTemplateEnaSrdUdpSpecification(TypedDict, total=False): - EnaSrdUdpEnabled: Optional[Boolean] + EnaSrdUdpEnabled: Boolean | None class LaunchTemplateEnaSrdSpecification(TypedDict, total=False): - EnaSrdEnabled: Optional[Boolean] - EnaSrdUdpSpecification: Optional[LaunchTemplateEnaSrdUdpSpecification] + EnaSrdEnabled: Boolean | None + EnaSrdUdpSpecification: LaunchTemplateEnaSrdUdpSpecification | None class Ipv6PrefixSpecificationResponse(TypedDict, total=False): - Ipv6Prefix: Optional[String] + Ipv6Prefix: String | None -Ipv6PrefixListResponse = List[Ipv6PrefixSpecificationResponse] +Ipv6PrefixListResponse = list[Ipv6PrefixSpecificationResponse] class Ipv4PrefixSpecificationResponse(TypedDict, total=False): - Ipv4Prefix: Optional[String] + Ipv4Prefix: String | None -Ipv4PrefixListResponse = List[Ipv4PrefixSpecificationResponse] +Ipv4PrefixListResponse = list[Ipv4PrefixSpecificationResponse] class InstanceIpv6Address(TypedDict, total=False): - Ipv6Address: Optional[String] - IsPrimaryIpv6: Optional[Boolean] + Ipv6Address: String | None + IsPrimaryIpv6: Boolean | None -InstanceIpv6AddressList = List[InstanceIpv6Address] +InstanceIpv6AddressList = list[InstanceIpv6Address] class LaunchTemplateInstanceNetworkInterfaceSpecification(TypedDict, total=False): - AssociateCarrierIpAddress: Optional[Boolean] - AssociatePublicIpAddress: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Description: Optional[String] - DeviceIndex: Optional[Integer] - Groups: Optional[GroupIdStringList] - InterfaceType: Optional[String] - Ipv6AddressCount: Optional[Integer] - Ipv6Addresses: Optional[InstanceIpv6AddressList] - NetworkInterfaceId: Optional[NetworkInterfaceId] - PrivateIpAddress: Optional[String] - PrivateIpAddresses: Optional[PrivateIpAddressSpecificationList] - SecondaryPrivateIpAddressCount: Optional[Integer] - SubnetId: Optional[SubnetId] - NetworkCardIndex: Optional[Integer] - Ipv4Prefixes: Optional[Ipv4PrefixListResponse] - Ipv4PrefixCount: Optional[Integer] - Ipv6Prefixes: Optional[Ipv6PrefixListResponse] - Ipv6PrefixCount: Optional[Integer] - PrimaryIpv6: Optional[Boolean] - EnaSrdSpecification: Optional[LaunchTemplateEnaSrdSpecification] - ConnectionTrackingSpecification: Optional[ConnectionTrackingSpecification] - EnaQueueCount: Optional[Integer] - - -LaunchTemplateInstanceNetworkInterfaceSpecificationList = List[ + AssociateCarrierIpAddress: Boolean | None + AssociatePublicIpAddress: Boolean | None + DeleteOnTermination: Boolean | None + Description: String | None + DeviceIndex: Integer | None + Groups: GroupIdStringList | None + InterfaceType: String | None + Ipv6AddressCount: Integer | None + Ipv6Addresses: InstanceIpv6AddressList | None + NetworkInterfaceId: NetworkInterfaceId | None + PrivateIpAddress: String | None + PrivateIpAddresses: PrivateIpAddressSpecificationList | None + SecondaryPrivateIpAddressCount: Integer | None + SubnetId: SubnetId | None + NetworkCardIndex: Integer | None + Ipv4Prefixes: Ipv4PrefixListResponse | None + Ipv4PrefixCount: Integer | None + Ipv6Prefixes: Ipv6PrefixListResponse | None + Ipv6PrefixCount: Integer | None + PrimaryIpv6: Boolean | None + EnaSrdSpecification: LaunchTemplateEnaSrdSpecification | None + ConnectionTrackingSpecification: ConnectionTrackingSpecification | None + EnaQueueCount: Integer | None + + +LaunchTemplateInstanceNetworkInterfaceSpecificationList = list[ LaunchTemplateInstanceNetworkInterfaceSpecification ] class LaunchTemplateEbsBlockDevice(TypedDict, total=False): - Encrypted: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Iops: Optional[Integer] - KmsKeyId: Optional[KmsKeyId] - SnapshotId: Optional[SnapshotId] - VolumeSize: Optional[Integer] - VolumeType: Optional[VolumeType] - Throughput: Optional[Integer] - VolumeInitializationRate: Optional[Integer] + Encrypted: Boolean | None + DeleteOnTermination: Boolean | None + Iops: Integer | None + KmsKeyId: KmsKeyId | None + SnapshotId: SnapshotId | None + VolumeSize: Integer | None + VolumeType: VolumeType | None + Throughput: Integer | None + VolumeInitializationRate: Integer | None + EbsCardIndex: Integer | None class LaunchTemplateBlockDeviceMapping(TypedDict, total=False): - DeviceName: Optional[String] - VirtualName: Optional[String] - Ebs: Optional[LaunchTemplateEbsBlockDevice] - NoDevice: Optional[String] + DeviceName: String | None + VirtualName: String | None + Ebs: LaunchTemplateEbsBlockDevice | None + NoDevice: String | None -LaunchTemplateBlockDeviceMappingList = List[LaunchTemplateBlockDeviceMapping] +LaunchTemplateBlockDeviceMappingList = list[LaunchTemplateBlockDeviceMapping] class LaunchTemplateIamInstanceProfileSpecification(TypedDict, total=False): - Arn: Optional[String] - Name: Optional[String] + Arn: String | None + Name: String | None class ResponseLaunchTemplateData(TypedDict, total=False): - KernelId: Optional[String] - EbsOptimized: Optional[Boolean] - IamInstanceProfile: Optional[LaunchTemplateIamInstanceProfileSpecification] - BlockDeviceMappings: Optional[LaunchTemplateBlockDeviceMappingList] - NetworkInterfaces: Optional[LaunchTemplateInstanceNetworkInterfaceSpecificationList] - ImageId: Optional[String] - InstanceType: Optional[InstanceType] - KeyName: Optional[String] - Monitoring: Optional[LaunchTemplatesMonitoring] - Placement: Optional[LaunchTemplatePlacement] - RamDiskId: Optional[String] - DisableApiTermination: Optional[Boolean] - InstanceInitiatedShutdownBehavior: Optional[ShutdownBehavior] - UserData: Optional[SensitiveUserData] - TagSpecifications: Optional[LaunchTemplateTagSpecificationList] - ElasticGpuSpecifications: Optional[ElasticGpuSpecificationResponseList] - ElasticInferenceAccelerators: Optional[LaunchTemplateElasticInferenceAcceleratorResponseList] - SecurityGroupIds: Optional[ValueStringList] - SecurityGroups: Optional[ValueStringList] - InstanceMarketOptions: Optional[LaunchTemplateInstanceMarketOptions] - CreditSpecification: Optional[CreditSpecification] - CpuOptions: Optional[LaunchTemplateCpuOptions] - CapacityReservationSpecification: Optional[ - LaunchTemplateCapacityReservationSpecificationResponse - ] - LicenseSpecifications: Optional[LaunchTemplateLicenseList] - HibernationOptions: Optional[LaunchTemplateHibernationOptions] - MetadataOptions: Optional[LaunchTemplateInstanceMetadataOptions] - EnclaveOptions: Optional[LaunchTemplateEnclaveOptions] - InstanceRequirements: Optional[InstanceRequirements] - PrivateDnsNameOptions: Optional[LaunchTemplatePrivateDnsNameOptions] - MaintenanceOptions: Optional[LaunchTemplateInstanceMaintenanceOptions] - DisableApiStop: Optional[Boolean] - Operator: Optional[OperatorResponse] - NetworkPerformanceOptions: Optional[LaunchTemplateNetworkPerformanceOptions] + KernelId: String | None + EbsOptimized: Boolean | None + IamInstanceProfile: LaunchTemplateIamInstanceProfileSpecification | None + BlockDeviceMappings: LaunchTemplateBlockDeviceMappingList | None + NetworkInterfaces: LaunchTemplateInstanceNetworkInterfaceSpecificationList | None + ImageId: String | None + InstanceType: InstanceType | None + KeyName: String | None + Monitoring: LaunchTemplatesMonitoring | None + Placement: LaunchTemplatePlacement | None + RamDiskId: String | None + DisableApiTermination: Boolean | None + InstanceInitiatedShutdownBehavior: ShutdownBehavior | None + UserData: SensitiveUserData | None + TagSpecifications: LaunchTemplateTagSpecificationList | None + ElasticGpuSpecifications: ElasticGpuSpecificationResponseList | None + ElasticInferenceAccelerators: LaunchTemplateElasticInferenceAcceleratorResponseList | None + SecurityGroupIds: ValueStringList | None + SecurityGroups: ValueStringList | None + InstanceMarketOptions: LaunchTemplateInstanceMarketOptions | None + CreditSpecification: CreditSpecification | None + CpuOptions: LaunchTemplateCpuOptions | None + CapacityReservationSpecification: LaunchTemplateCapacityReservationSpecificationResponse | None + LicenseSpecifications: LaunchTemplateLicenseList | None + HibernationOptions: LaunchTemplateHibernationOptions | None + MetadataOptions: LaunchTemplateInstanceMetadataOptions | None + EnclaveOptions: LaunchTemplateEnclaveOptions | None + InstanceRequirements: InstanceRequirements | None + PrivateDnsNameOptions: LaunchTemplatePrivateDnsNameOptions | None + MaintenanceOptions: LaunchTemplateInstanceMaintenanceOptions | None + DisableApiStop: Boolean | None + Operator: OperatorResponse | None + NetworkPerformanceOptions: LaunchTemplateNetworkPerformanceOptions | None + SecondaryInterfaces: LaunchTemplateInstanceSecondaryInterfaceSpecificationList | None class LaunchTemplateVersion(TypedDict, total=False): - LaunchTemplateId: Optional[String] - LaunchTemplateName: Optional[LaunchTemplateName] - VersionNumber: Optional[Long] - VersionDescription: Optional[VersionDescription] - CreateTime: Optional[DateTime] - CreatedBy: Optional[String] - DefaultVersion: Optional[Boolean] - LaunchTemplateData: Optional[ResponseLaunchTemplateData] - Operator: Optional[OperatorResponse] + LaunchTemplateId: String | None + LaunchTemplateName: LaunchTemplateName | None + VersionNumber: Long | None + VersionDescription: VersionDescription | None + CreateTime: DateTime | None + CreatedBy: String | None + DefaultVersion: Boolean | None + LaunchTemplateData: ResponseLaunchTemplateData | None + Operator: OperatorResponse | None class CreateLaunchTemplateVersionResult(TypedDict, total=False): - LaunchTemplateVersion: Optional[LaunchTemplateVersion] - Warning: Optional[ValidationWarning] + LaunchTemplateVersion: LaunchTemplateVersion | None + Warning: ValidationWarning | None class CreateLocalGatewayRouteRequest(ServiceRequest): - DestinationCidrBlock: Optional[String] + DestinationCidrBlock: String | None LocalGatewayRouteTableId: LocalGatewayRoutetableId - LocalGatewayVirtualInterfaceGroupId: Optional[LocalGatewayVirtualInterfaceGroupId] - DryRun: Optional[Boolean] - NetworkInterfaceId: Optional[NetworkInterfaceId] - DestinationPrefixListId: Optional[PrefixListResourceId] + LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId | None + DryRun: Boolean | None + NetworkInterfaceId: NetworkInterfaceId | None + DestinationPrefixListId: PrefixListResourceId | None class LocalGatewayRoute(TypedDict, total=False): - DestinationCidrBlock: Optional[String] - LocalGatewayVirtualInterfaceGroupId: Optional[LocalGatewayVirtualInterfaceGroupId] - Type: Optional[LocalGatewayRouteType] - State: Optional[LocalGatewayRouteState] - LocalGatewayRouteTableId: Optional[LocalGatewayRoutetableId] - LocalGatewayRouteTableArn: Optional[ResourceArn] - OwnerId: Optional[String] - SubnetId: Optional[SubnetId] - CoipPoolId: Optional[CoipPoolId] - NetworkInterfaceId: Optional[NetworkInterfaceId] - DestinationPrefixListId: Optional[PrefixListResourceId] + DestinationCidrBlock: String | None + LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId | None + Type: LocalGatewayRouteType | None + State: LocalGatewayRouteState | None + LocalGatewayRouteTableId: LocalGatewayRoutetableId | None + LocalGatewayRouteTableArn: ResourceArn | None + OwnerId: String | None + SubnetId: SubnetId | None + CoipPoolId: CoipPoolId | None + NetworkInterfaceId: NetworkInterfaceId | None + DestinationPrefixListId: PrefixListResourceId | None class CreateLocalGatewayRouteResult(TypedDict, total=False): - Route: Optional[LocalGatewayRoute] + Route: LocalGatewayRoute | None class CreateLocalGatewayRouteTableRequest(ServiceRequest): LocalGatewayId: LocalGatewayId - Mode: Optional[LocalGatewayRouteTableMode] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + Mode: LocalGatewayRouteTableMode | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class StateReason(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None class LocalGatewayRouteTable(TypedDict, total=False): - LocalGatewayRouteTableId: Optional[String] - LocalGatewayRouteTableArn: Optional[ResourceArn] - LocalGatewayId: Optional[LocalGatewayId] - OutpostArn: Optional[String] - OwnerId: Optional[String] - State: Optional[String] - Tags: Optional[TagList] - Mode: Optional[LocalGatewayRouteTableMode] - StateReason: Optional[StateReason] + LocalGatewayRouteTableId: String | None + LocalGatewayRouteTableArn: ResourceArn | None + LocalGatewayId: LocalGatewayId | None + OutpostArn: String | None + OwnerId: String | None + State: String | None + Tags: TagList | None + Mode: LocalGatewayRouteTableMode | None + StateReason: StateReason | None class CreateLocalGatewayRouteTableResult(TypedDict, total=False): - LocalGatewayRouteTable: Optional[LocalGatewayRouteTable] + LocalGatewayRouteTable: LocalGatewayRouteTable | None class CreateLocalGatewayRouteTableVirtualInterfaceGroupAssociationRequest(ServiceRequest): LocalGatewayRouteTableId: LocalGatewayRoutetableId LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class LocalGatewayRouteTableVirtualInterfaceGroupAssociation(TypedDict, total=False): - LocalGatewayRouteTableVirtualInterfaceGroupAssociationId: Optional[ - LocalGatewayRouteTableVirtualInterfaceGroupAssociationId - ] - LocalGatewayVirtualInterfaceGroupId: Optional[LocalGatewayVirtualInterfaceGroupId] - LocalGatewayId: Optional[String] - LocalGatewayRouteTableId: Optional[LocalGatewayId] - LocalGatewayRouteTableArn: Optional[ResourceArn] - OwnerId: Optional[String] - State: Optional[String] - Tags: Optional[TagList] + LocalGatewayRouteTableVirtualInterfaceGroupAssociationId: ( + LocalGatewayRouteTableVirtualInterfaceGroupAssociationId | None + ) + LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId | None + LocalGatewayId: String | None + LocalGatewayRouteTableId: LocalGatewayId | None + LocalGatewayRouteTableArn: ResourceArn | None + OwnerId: String | None + State: String | None + Tags: TagList | None class CreateLocalGatewayRouteTableVirtualInterfaceGroupAssociationResult(TypedDict, total=False): - LocalGatewayRouteTableVirtualInterfaceGroupAssociation: Optional[ - LocalGatewayRouteTableVirtualInterfaceGroupAssociation - ] + LocalGatewayRouteTableVirtualInterfaceGroupAssociation: ( + LocalGatewayRouteTableVirtualInterfaceGroupAssociation | None + ) class CreateLocalGatewayRouteTableVpcAssociationRequest(ServiceRequest): LocalGatewayRouteTableId: LocalGatewayRoutetableId VpcId: VpcId - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class LocalGatewayRouteTableVpcAssociation(TypedDict, total=False): - LocalGatewayRouteTableVpcAssociationId: Optional[LocalGatewayRouteTableVpcAssociationId] - LocalGatewayRouteTableId: Optional[String] - LocalGatewayRouteTableArn: Optional[ResourceArn] - LocalGatewayId: Optional[String] - VpcId: Optional[String] - OwnerId: Optional[String] - State: Optional[String] - Tags: Optional[TagList] + LocalGatewayRouteTableVpcAssociationId: LocalGatewayRouteTableVpcAssociationId | None + LocalGatewayRouteTableId: String | None + LocalGatewayRouteTableArn: ResourceArn | None + LocalGatewayId: String | None + VpcId: String | None + OwnerId: String | None + State: String | None + Tags: TagList | None class CreateLocalGatewayRouteTableVpcAssociationResult(TypedDict, total=False): - LocalGatewayRouteTableVpcAssociation: Optional[LocalGatewayRouteTableVpcAssociation] + LocalGatewayRouteTableVpcAssociation: LocalGatewayRouteTableVpcAssociation | None class CreateLocalGatewayVirtualInterfaceGroupRequest(ServiceRequest): LocalGatewayId: LocalGatewayId - LocalBgpAsn: Optional[Integer] - LocalBgpAsnExtended: Optional[Long] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + LocalBgpAsn: Integer | None + LocalBgpAsnExtended: Long | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None -LocalGatewayVirtualInterfaceIdSet = List[LocalGatewayVirtualInterfaceId] +LocalGatewayVirtualInterfaceIdSet = list[LocalGatewayVirtualInterfaceId] class LocalGatewayVirtualInterfaceGroup(TypedDict, total=False): - LocalGatewayVirtualInterfaceGroupId: Optional[LocalGatewayVirtualInterfaceGroupId] - LocalGatewayVirtualInterfaceIds: Optional[LocalGatewayVirtualInterfaceIdSet] - LocalGatewayId: Optional[String] - OwnerId: Optional[String] - LocalBgpAsn: Optional[Integer] - LocalBgpAsnExtended: Optional[Long] - LocalGatewayVirtualInterfaceGroupArn: Optional[ResourceArn] - Tags: Optional[TagList] - ConfigurationState: Optional[LocalGatewayVirtualInterfaceGroupConfigurationState] + LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId | None + LocalGatewayVirtualInterfaceIds: LocalGatewayVirtualInterfaceIdSet | None + LocalGatewayId: String | None + OwnerId: String | None + LocalBgpAsn: Integer | None + LocalBgpAsnExtended: Long | None + LocalGatewayVirtualInterfaceGroupArn: ResourceArn | None + Tags: TagList | None + ConfigurationState: LocalGatewayVirtualInterfaceGroupConfigurationState | None class CreateLocalGatewayVirtualInterfaceGroupResult(TypedDict, total=False): - LocalGatewayVirtualInterfaceGroup: Optional[LocalGatewayVirtualInterfaceGroup] + LocalGatewayVirtualInterfaceGroup: LocalGatewayVirtualInterfaceGroup | None class CreateLocalGatewayVirtualInterfaceRequest(ServiceRequest): @@ -8439,487 +9670,515 @@ class CreateLocalGatewayVirtualInterfaceRequest(ServiceRequest): Vlan: Integer LocalAddress: String PeerAddress: String - PeerBgpAsn: Optional[Integer] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - PeerBgpAsnExtended: Optional[Long] + PeerBgpAsn: Integer | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + PeerBgpAsnExtended: Long | None class LocalGatewayVirtualInterface(TypedDict, total=False): - LocalGatewayVirtualInterfaceId: Optional[LocalGatewayVirtualInterfaceId] - LocalGatewayId: Optional[String] - LocalGatewayVirtualInterfaceGroupId: Optional[LocalGatewayVirtualInterfaceGroupId] - LocalGatewayVirtualInterfaceArn: Optional[ResourceArn] - OutpostLagId: Optional[String] - Vlan: Optional[Integer] - LocalAddress: Optional[String] - PeerAddress: Optional[String] - LocalBgpAsn: Optional[Integer] - PeerBgpAsn: Optional[Integer] - PeerBgpAsnExtended: Optional[Long] - OwnerId: Optional[String] - Tags: Optional[TagList] - ConfigurationState: Optional[LocalGatewayVirtualInterfaceConfigurationState] + LocalGatewayVirtualInterfaceId: LocalGatewayVirtualInterfaceId | None + LocalGatewayId: String | None + LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId | None + LocalGatewayVirtualInterfaceArn: ResourceArn | None + OutpostLagId: String | None + Vlan: Integer | None + LocalAddress: String | None + PeerAddress: String | None + LocalBgpAsn: Integer | None + PeerBgpAsn: Integer | None + PeerBgpAsnExtended: Long | None + OwnerId: String | None + Tags: TagList | None + ConfigurationState: LocalGatewayVirtualInterfaceConfigurationState | None class CreateLocalGatewayVirtualInterfaceResult(TypedDict, total=False): - LocalGatewayVirtualInterface: Optional[LocalGatewayVirtualInterface] + LocalGatewayVirtualInterface: LocalGatewayVirtualInterface | None class MacSystemIntegrityProtectionConfigurationRequest(TypedDict, total=False): - AppleInternal: Optional[MacSystemIntegrityProtectionSettingStatus] - BaseSystem: Optional[MacSystemIntegrityProtectionSettingStatus] - DebuggingRestrictions: Optional[MacSystemIntegrityProtectionSettingStatus] - DTraceRestrictions: Optional[MacSystemIntegrityProtectionSettingStatus] - FilesystemProtections: Optional[MacSystemIntegrityProtectionSettingStatus] - KextSigning: Optional[MacSystemIntegrityProtectionSettingStatus] - NvramProtections: Optional[MacSystemIntegrityProtectionSettingStatus] + AppleInternal: MacSystemIntegrityProtectionSettingStatus | None + BaseSystem: MacSystemIntegrityProtectionSettingStatus | None + DebuggingRestrictions: MacSystemIntegrityProtectionSettingStatus | None + DTraceRestrictions: MacSystemIntegrityProtectionSettingStatus | None + FilesystemProtections: MacSystemIntegrityProtectionSettingStatus | None + KextSigning: MacSystemIntegrityProtectionSettingStatus | None + NvramProtections: MacSystemIntegrityProtectionSettingStatus | None class CreateMacSystemIntegrityProtectionModificationTaskRequest(ServiceRequest): - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None InstanceId: InstanceId - MacCredentials: Optional[SensitiveMacCredentials] - MacSystemIntegrityProtectionConfiguration: Optional[ - MacSystemIntegrityProtectionConfigurationRequest - ] + MacCredentials: SensitiveMacCredentials | None + MacSystemIntegrityProtectionConfiguration: ( + MacSystemIntegrityProtectionConfigurationRequest | None + ) MacSystemIntegrityProtectionStatus: MacSystemIntegrityProtectionSettingStatus - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None class CreateMacSystemIntegrityProtectionModificationTaskResult(TypedDict, total=False): - MacModificationTask: Optional[MacModificationTask] + MacModificationTask: MacModificationTask | None class CreateManagedPrefixListRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PrefixListName: String - Entries: Optional[AddPrefixListEntries] + Entries: AddPrefixListEntries | None MaxEntries: Integer - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None AddressFamily: String - ClientToken: Optional[String] + ClientToken: String | None class ManagedPrefixList(TypedDict, total=False): - PrefixListId: Optional[PrefixListResourceId] - AddressFamily: Optional[String] - State: Optional[PrefixListState] - StateMessage: Optional[String] - PrefixListArn: Optional[ResourceArn] - PrefixListName: Optional[String] - MaxEntries: Optional[Integer] - Version: Optional[Long] - Tags: Optional[TagList] - OwnerId: Optional[String] + PrefixListId: PrefixListResourceId | None + AddressFamily: String | None + State: PrefixListState | None + StateMessage: String | None + PrefixListArn: ResourceArn | None + PrefixListName: String | None + MaxEntries: Integer | None + Version: Long | None + Tags: TagList | None + OwnerId: String | None + IpamPrefixListResolverTargetId: String | None + IpamPrefixListResolverSyncEnabled: Boolean | None class CreateManagedPrefixListResult(TypedDict, total=False): - PrefixList: Optional[ManagedPrefixList] + PrefixList: ManagedPrefixList | None class CreateNatGatewayRequest(ServiceRequest): - AllocationId: Optional[AllocationId] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - SubnetId: SubnetId - TagSpecifications: Optional[TagSpecificationList] - ConnectivityType: Optional[ConnectivityType] - PrivateIpAddress: Optional[String] - SecondaryAllocationIds: Optional[AllocationIdList] - SecondaryPrivateIpAddresses: Optional[IpList] - SecondaryPrivateIpAddressCount: Optional[PrivateIpAddressCount] + AvailabilityMode: AvailabilityMode | None + AllocationId: AllocationId | None + ClientToken: String | None + DryRun: Boolean | None + SubnetId: SubnetId | None + VpcId: VpcId | None + AvailabilityZoneAddresses: AvailabilityZoneAddresses | None + TagSpecifications: TagSpecificationList | None + ConnectivityType: ConnectivityType | None + PrivateIpAddress: String | None + SecondaryAllocationIds: AllocationIdList | None + SecondaryPrivateIpAddresses: IpList | None + SecondaryPrivateIpAddressCount: PrivateIpAddressCount | None + + +class NatGatewayAttachedAppliance(TypedDict, total=False): + Type: NatGatewayApplianceType | None + ApplianceArn: String | None + VpcEndpointId: String | None + AttachmentState: NatGatewayApplianceState | None + ModificationState: NatGatewayApplianceModifyState | None + FailureCode: String | None + FailureMessage: String | None + + +NatGatewayAttachedApplianceList = list[NatGatewayAttachedAppliance] class ProvisionedBandwidth(TypedDict, total=False): - ProvisionTime: Optional[DateTime] - Provisioned: Optional[String] - RequestTime: Optional[DateTime] - Requested: Optional[String] - Status: Optional[String] + ProvisionTime: DateTime | None + Provisioned: String | None + RequestTime: DateTime | None + Requested: String | None + Status: String | None class NatGateway(TypedDict, total=False): - CreateTime: Optional[DateTime] - DeleteTime: Optional[DateTime] - FailureCode: Optional[String] - FailureMessage: Optional[String] - NatGatewayAddresses: Optional[NatGatewayAddressList] - NatGatewayId: Optional[String] - ProvisionedBandwidth: Optional[ProvisionedBandwidth] - State: Optional[NatGatewayState] - SubnetId: Optional[String] - VpcId: Optional[String] - Tags: Optional[TagList] - ConnectivityType: Optional[ConnectivityType] + CreateTime: DateTime | None + DeleteTime: DateTime | None + FailureCode: String | None + FailureMessage: String | None + NatGatewayAddresses: NatGatewayAddressList | None + NatGatewayId: String | None + ProvisionedBandwidth: ProvisionedBandwidth | None + State: NatGatewayState | None + SubnetId: String | None + VpcId: String | None + Tags: TagList | None + ConnectivityType: ConnectivityType | None + AvailabilityMode: AvailabilityMode | None + AutoScalingIps: AutoScalingIpsState | None + AutoProvisionZones: AutoProvisionZonesState | None + AttachedAppliances: NatGatewayAttachedApplianceList | None + RouteTableId: String | None class CreateNatGatewayResult(TypedDict, total=False): - ClientToken: Optional[String] - NatGateway: Optional[NatGateway] + ClientToken: String | None + NatGateway: NatGateway | None class IcmpTypeCode(TypedDict, total=False): - Code: Optional[Integer] - Type: Optional[Integer] + Code: Integer | None + Type: Integer | None class CreateNetworkAclEntryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkAclId: NetworkAclId RuleNumber: Integer Protocol: String RuleAction: RuleAction Egress: Boolean - CidrBlock: Optional[String] - Ipv6CidrBlock: Optional[String] - IcmpTypeCode: Optional[IcmpTypeCode] - PortRange: Optional[PortRange] + CidrBlock: String | None + Ipv6CidrBlock: String | None + IcmpTypeCode: IcmpTypeCode | None + PortRange: PortRange | None class CreateNetworkAclRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + DryRun: Boolean | None VpcId: VpcId class NetworkAclEntry(TypedDict, total=False): - CidrBlock: Optional[String] - Egress: Optional[Boolean] - IcmpTypeCode: Optional[IcmpTypeCode] - Ipv6CidrBlock: Optional[String] - PortRange: Optional[PortRange] - Protocol: Optional[String] - RuleAction: Optional[RuleAction] - RuleNumber: Optional[Integer] + CidrBlock: String | None + Egress: Boolean | None + IcmpTypeCode: IcmpTypeCode | None + Ipv6CidrBlock: String | None + PortRange: PortRange | None + Protocol: String | None + RuleAction: RuleAction | None + RuleNumber: Integer | None -NetworkAclEntryList = List[NetworkAclEntry] +NetworkAclEntryList = list[NetworkAclEntry] class NetworkAclAssociation(TypedDict, total=False): - NetworkAclAssociationId: Optional[String] - NetworkAclId: Optional[String] - SubnetId: Optional[String] + NetworkAclAssociationId: String | None + NetworkAclId: String | None + SubnetId: String | None -NetworkAclAssociationList = List[NetworkAclAssociation] +NetworkAclAssociationList = list[NetworkAclAssociation] class NetworkAcl(TypedDict, total=False): - Associations: Optional[NetworkAclAssociationList] - Entries: Optional[NetworkAclEntryList] - IsDefault: Optional[Boolean] - NetworkAclId: Optional[String] - Tags: Optional[TagList] - VpcId: Optional[String] - OwnerId: Optional[String] + Associations: NetworkAclAssociationList | None + Entries: NetworkAclEntryList | None + IsDefault: Boolean | None + NetworkAclId: String | None + Tags: TagList | None + VpcId: String | None + OwnerId: String | None class CreateNetworkAclResult(TypedDict, total=False): - NetworkAcl: Optional[NetworkAcl] - ClientToken: Optional[String] + NetworkAcl: NetworkAcl | None + ClientToken: String | None class CreateNetworkInsightsAccessScopeRequest(ServiceRequest): - MatchPaths: Optional[AccessScopePathListRequest] - ExcludePaths: Optional[AccessScopePathListRequest] + MatchPaths: AccessScopePathListRequest | None + ExcludePaths: AccessScopePathListRequest | None ClientToken: String - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class NetworkInsightsAccessScopeContent(TypedDict, total=False): - NetworkInsightsAccessScopeId: Optional[NetworkInsightsAccessScopeId] - MatchPaths: Optional[AccessScopePathList] - ExcludePaths: Optional[AccessScopePathList] + NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId | None + MatchPaths: AccessScopePathList | None + ExcludePaths: AccessScopePathList | None class NetworkInsightsAccessScope(TypedDict, total=False): - NetworkInsightsAccessScopeId: Optional[NetworkInsightsAccessScopeId] - NetworkInsightsAccessScopeArn: Optional[ResourceArn] - CreatedDate: Optional[MillisecondDateTime] - UpdatedDate: Optional[MillisecondDateTime] - Tags: Optional[TagList] + NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId | None + NetworkInsightsAccessScopeArn: ResourceArn | None + CreatedDate: MillisecondDateTime | None + UpdatedDate: MillisecondDateTime | None + Tags: TagList | None class CreateNetworkInsightsAccessScopeResult(TypedDict, total=False): - NetworkInsightsAccessScope: Optional[NetworkInsightsAccessScope] - NetworkInsightsAccessScopeContent: Optional[NetworkInsightsAccessScopeContent] + NetworkInsightsAccessScope: NetworkInsightsAccessScope | None + NetworkInsightsAccessScopeContent: NetworkInsightsAccessScopeContent | None class RequestFilterPortRange(TypedDict, total=False): - FromPort: Optional[Port] - ToPort: Optional[Port] + FromPort: Port | None + ToPort: Port | None class PathRequestFilter(TypedDict, total=False): - SourceAddress: Optional[IpAddress] - SourcePortRange: Optional[RequestFilterPortRange] - DestinationAddress: Optional[IpAddress] - DestinationPortRange: Optional[RequestFilterPortRange] + SourceAddress: IpAddress | None + SourcePortRange: RequestFilterPortRange | None + DestinationAddress: IpAddress | None + DestinationPortRange: RequestFilterPortRange | None class CreateNetworkInsightsPathRequest(ServiceRequest): - SourceIp: Optional[IpAddress] - DestinationIp: Optional[IpAddress] + SourceIp: IpAddress | None + DestinationIp: IpAddress | None Source: NetworkInsightsResourceId - Destination: Optional[NetworkInsightsResourceId] + Destination: NetworkInsightsResourceId | None Protocol: Protocol - DestinationPort: Optional[Port] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + DestinationPort: Port | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None ClientToken: String - FilterAtSource: Optional[PathRequestFilter] - FilterAtDestination: Optional[PathRequestFilter] + FilterAtSource: PathRequestFilter | None + FilterAtDestination: PathRequestFilter | None class FilterPortRange(TypedDict, total=False): - FromPort: Optional[Port] - ToPort: Optional[Port] + FromPort: Port | None + ToPort: Port | None class PathFilter(TypedDict, total=False): - SourceAddress: Optional[IpAddress] - SourcePortRange: Optional[FilterPortRange] - DestinationAddress: Optional[IpAddress] - DestinationPortRange: Optional[FilterPortRange] + SourceAddress: IpAddress | None + SourcePortRange: FilterPortRange | None + DestinationAddress: IpAddress | None + DestinationPortRange: FilterPortRange | None class NetworkInsightsPath(TypedDict, total=False): - NetworkInsightsPathId: Optional[NetworkInsightsPathId] - NetworkInsightsPathArn: Optional[ResourceArn] - CreatedDate: Optional[MillisecondDateTime] - Source: Optional[String] - Destination: Optional[String] - SourceArn: Optional[ResourceArn] - DestinationArn: Optional[ResourceArn] - SourceIp: Optional[IpAddress] - DestinationIp: Optional[IpAddress] - Protocol: Optional[Protocol] - DestinationPort: Optional[Integer] - Tags: Optional[TagList] - FilterAtSource: Optional[PathFilter] - FilterAtDestination: Optional[PathFilter] + NetworkInsightsPathId: NetworkInsightsPathId | None + NetworkInsightsPathArn: ResourceArn | None + CreatedDate: MillisecondDateTime | None + Source: String | None + Destination: String | None + SourceArn: ResourceArn | None + DestinationArn: ResourceArn | None + SourceIp: IpAddress | None + DestinationIp: IpAddress | None + Protocol: Protocol | None + DestinationPort: Integer | None + Tags: TagList | None + FilterAtSource: PathFilter | None + FilterAtDestination: PathFilter | None class CreateNetworkInsightsPathResult(TypedDict, total=False): - NetworkInsightsPath: Optional[NetworkInsightsPath] + NetworkInsightsPath: NetworkInsightsPath | None class CreateNetworkInterfacePermissionRequest(ServiceRequest): NetworkInterfaceId: NetworkInterfaceId - AwsAccountId: Optional[String] - AwsService: Optional[String] + AwsAccountId: String | None + AwsService: String | None Permission: InterfacePermissionType - DryRun: Optional[Boolean] + DryRun: Boolean | None class NetworkInterfacePermissionState(TypedDict, total=False): - State: Optional[NetworkInterfacePermissionStateCode] - StatusMessage: Optional[String] + State: NetworkInterfacePermissionStateCode | None + StatusMessage: String | None class NetworkInterfacePermission(TypedDict, total=False): - NetworkInterfacePermissionId: Optional[String] - NetworkInterfaceId: Optional[String] - AwsAccountId: Optional[String] - AwsService: Optional[String] - Permission: Optional[InterfacePermissionType] - PermissionState: Optional[NetworkInterfacePermissionState] + NetworkInterfacePermissionId: String | None + NetworkInterfaceId: String | None + AwsAccountId: String | None + AwsService: String | None + Permission: InterfacePermissionType | None + PermissionState: NetworkInterfacePermissionState | None class CreateNetworkInterfacePermissionResult(TypedDict, total=False): - InterfacePermission: Optional[NetworkInterfacePermission] + InterfacePermission: NetworkInterfacePermission | None class CreateNetworkInterfaceRequest(ServiceRequest): - Ipv4Prefixes: Optional[Ipv4PrefixList] - Ipv4PrefixCount: Optional[Integer] - Ipv6Prefixes: Optional[Ipv6PrefixList] - Ipv6PrefixCount: Optional[Integer] - InterfaceType: Optional[NetworkInterfaceCreationType] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - EnablePrimaryIpv6: Optional[Boolean] - ConnectionTrackingSpecification: Optional[ConnectionTrackingSpecificationRequest] - Operator: Optional[OperatorRequest] + Ipv4Prefixes: Ipv4PrefixList | None + Ipv4PrefixCount: Integer | None + Ipv6Prefixes: Ipv6PrefixList | None + Ipv6PrefixCount: Integer | None + InterfaceType: NetworkInterfaceCreationType | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + EnablePrimaryIpv6: Boolean | None + ConnectionTrackingSpecification: ConnectionTrackingSpecificationRequest | None + Operator: OperatorRequest | None SubnetId: SubnetId - Description: Optional[String] - PrivateIpAddress: Optional[String] - Groups: Optional[SecurityGroupIdStringList] - PrivateIpAddresses: Optional[PrivateIpAddressSpecificationList] - SecondaryPrivateIpAddressCount: Optional[Integer] - Ipv6Addresses: Optional[InstanceIpv6AddressList] - Ipv6AddressCount: Optional[Integer] - DryRun: Optional[Boolean] + Description: String | None + PrivateIpAddress: String | None + Groups: SecurityGroupIdStringList | None + PrivateIpAddresses: PrivateIpAddressSpecificationList | None + SecondaryPrivateIpAddressCount: Integer | None + Ipv6Addresses: InstanceIpv6AddressList | None + Ipv6AddressCount: Integer | None + DryRun: Boolean | None class Ipv6PrefixSpecification(TypedDict, total=False): - Ipv6Prefix: Optional[String] + Ipv6Prefix: String | None -Ipv6PrefixesList = List[Ipv6PrefixSpecification] +Ipv6PrefixesList = list[Ipv6PrefixSpecification] class NetworkInterfaceAssociation(TypedDict, total=False): - AllocationId: Optional[String] - AssociationId: Optional[String] - IpOwnerId: Optional[String] - PublicDnsName: Optional[String] - PublicIp: Optional[String] - CustomerOwnedIp: Optional[String] - CarrierIp: Optional[String] + AllocationId: String | None + AssociationId: String | None + IpOwnerId: String | None + PublicDnsName: String | None + PublicIp: String | None + CustomerOwnedIp: String | None + CarrierIp: String | None class NetworkInterfacePrivateIpAddress(TypedDict, total=False): - Association: Optional[NetworkInterfaceAssociation] - Primary: Optional[Boolean] - PrivateDnsName: Optional[String] - PrivateIpAddress: Optional[String] + Association: NetworkInterfaceAssociation | None + Primary: Boolean | None + PrivateDnsName: String | None + PrivateIpAddress: String | None -NetworkInterfacePrivateIpAddressList = List[NetworkInterfacePrivateIpAddress] +NetworkInterfacePrivateIpAddressList = list[NetworkInterfacePrivateIpAddress] class PublicIpDnsNameOptions(TypedDict, total=False): - DnsHostnameType: Optional[String] - PublicIpv4DnsName: Optional[String] - PublicIpv6DnsName: Optional[String] - PublicDualStackDnsName: Optional[String] + DnsHostnameType: String | None + PublicIpv4DnsName: String | None + PublicIpv6DnsName: String | None + PublicDualStackDnsName: String | None class NetworkInterfaceIpv6Address(TypedDict, total=False): - Ipv6Address: Optional[String] - PublicIpv6DnsName: Optional[String] - IsPrimaryIpv6: Optional[Boolean] + Ipv6Address: String | None + PublicIpv6DnsName: String | None + IsPrimaryIpv6: Boolean | None -NetworkInterfaceIpv6AddressesList = List[NetworkInterfaceIpv6Address] +NetworkInterfaceIpv6AddressesList = list[NetworkInterfaceIpv6Address] class NetworkInterfaceAttachment(TypedDict, total=False): - AttachTime: Optional[DateTime] - AttachmentId: Optional[String] - DeleteOnTermination: Optional[Boolean] - DeviceIndex: Optional[Integer] - NetworkCardIndex: Optional[Integer] - InstanceId: Optional[String] - InstanceOwnerId: Optional[String] - Status: Optional[AttachmentStatus] - EnaSrdSpecification: Optional[AttachmentEnaSrdSpecification] - EnaQueueCount: Optional[Integer] + AttachTime: DateTime | None + AttachmentId: String | None + DeleteOnTermination: Boolean | None + DeviceIndex: Integer | None + NetworkCardIndex: Integer | None + InstanceId: String | None + InstanceOwnerId: String | None + Status: AttachmentStatus | None + EnaSrdSpecification: AttachmentEnaSrdSpecification | None + EnaQueueCount: Integer | None class NetworkInterface(TypedDict, total=False): - Association: Optional[NetworkInterfaceAssociation] - Attachment: Optional[NetworkInterfaceAttachment] - AvailabilityZone: Optional[String] - ConnectionTrackingConfiguration: Optional[ConnectionTrackingConfiguration] - Description: Optional[String] - Groups: Optional[GroupIdentifierList] - InterfaceType: Optional[NetworkInterfaceType] - Ipv6Addresses: Optional[NetworkInterfaceIpv6AddressesList] - MacAddress: Optional[String] - NetworkInterfaceId: Optional[String] - OutpostArn: Optional[String] - OwnerId: Optional[String] - PrivateDnsName: Optional[String] - PublicDnsName: Optional[String] - PublicIpDnsNameOptions: Optional[PublicIpDnsNameOptions] - PrivateIpAddress: Optional[String] - PrivateIpAddresses: Optional[NetworkInterfacePrivateIpAddressList] - Ipv4Prefixes: Optional[Ipv4PrefixesList] - Ipv6Prefixes: Optional[Ipv6PrefixesList] - RequesterId: Optional[String] - RequesterManaged: Optional[Boolean] - SourceDestCheck: Optional[Boolean] - Status: Optional[NetworkInterfaceStatus] - SubnetId: Optional[String] - TagSet: Optional[TagList] - VpcId: Optional[String] - DenyAllIgwTraffic: Optional[Boolean] - Ipv6Native: Optional[Boolean] - Ipv6Address: Optional[String] - Operator: Optional[OperatorResponse] - AssociatedSubnets: Optional[AssociatedSubnetList] + Association: NetworkInterfaceAssociation | None + Attachment: NetworkInterfaceAttachment | None + AvailabilityZone: String | None + ConnectionTrackingConfiguration: ConnectionTrackingConfiguration | None + Description: String | None + Groups: GroupIdentifierList | None + InterfaceType: NetworkInterfaceType | None + Ipv6Addresses: NetworkInterfaceIpv6AddressesList | None + MacAddress: String | None + NetworkInterfaceId: String | None + OutpostArn: String | None + OwnerId: String | None + PrivateDnsName: String | None + PublicDnsName: String | None + PublicIpDnsNameOptions: PublicIpDnsNameOptions | None + PrivateIpAddress: String | None + PrivateIpAddresses: NetworkInterfacePrivateIpAddressList | None + Ipv4Prefixes: Ipv4PrefixesList | None + Ipv6Prefixes: Ipv6PrefixesList | None + RequesterId: String | None + RequesterManaged: Boolean | None + SourceDestCheck: Boolean | None + Status: NetworkInterfaceStatus | None + SubnetId: String | None + TagSet: TagList | None + VpcId: String | None + DenyAllIgwTraffic: Boolean | None + Ipv6Native: Boolean | None + Ipv6Address: String | None + Operator: OperatorResponse | None + AssociatedSubnets: AssociatedSubnetList | None + AvailabilityZoneId: String | None class CreateNetworkInterfaceResult(TypedDict, total=False): - NetworkInterface: Optional[NetworkInterface] - ClientToken: Optional[String] + NetworkInterface: NetworkInterface | None + ClientToken: String | None class CreatePlacementGroupRequest(ServiceRequest): - PartitionCount: Optional[Integer] - TagSpecifications: Optional[TagSpecificationList] - SpreadLevel: Optional[SpreadLevel] - DryRun: Optional[Boolean] - GroupName: Optional[String] - Strategy: Optional[PlacementStrategy] + PartitionCount: Integer | None + TagSpecifications: TagSpecificationList | None + SpreadLevel: SpreadLevel | None + LinkedGroupId: PlacementGroupId | None + Operator: OperatorRequest | None + DryRun: Boolean | None + GroupName: String | None + Strategy: PlacementStrategy | None class PlacementGroup(TypedDict, total=False): - GroupName: Optional[String] - State: Optional[PlacementGroupState] - Strategy: Optional[PlacementStrategy] - PartitionCount: Optional[Integer] - GroupId: Optional[String] - Tags: Optional[TagList] - GroupArn: Optional[String] - SpreadLevel: Optional[SpreadLevel] + GroupName: String | None + State: PlacementGroupState | None + Strategy: PlacementStrategy | None + PartitionCount: Integer | None + GroupId: String | None + Tags: TagList | None + GroupArn: String | None + SpreadLevel: SpreadLevel | None + LinkedGroupId: PlacementGroupId | None + Operator: OperatorResponse | None class CreatePlacementGroupResult(TypedDict, total=False): - PlacementGroup: Optional[PlacementGroup] + PlacementGroup: PlacementGroup | None class CreatePublicIpv4PoolRequest(ServiceRequest): - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] - NetworkBorderGroup: Optional[String] + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None + NetworkBorderGroup: String | None class CreatePublicIpv4PoolResult(TypedDict, total=False): - PoolId: Optional[Ipv4PoolEc2Id] + PoolId: Ipv4PoolEc2Id | None class CreateReplaceRootVolumeTaskRequest(ServiceRequest): InstanceId: InstanceId - SnapshotId: Optional[SnapshotId] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] - ImageId: Optional[ImageId] - DeleteReplacedRootVolume: Optional[Boolean] - VolumeInitializationRate: Optional[Long] + SnapshotId: SnapshotId | None + ClientToken: String | None + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None + ImageId: ImageId | None + DeleteReplacedRootVolume: Boolean | None + VolumeInitializationRate: Long | None class ReplaceRootVolumeTask(TypedDict, total=False): - ReplaceRootVolumeTaskId: Optional[ReplaceRootVolumeTaskId] - InstanceId: Optional[String] - TaskState: Optional[ReplaceRootVolumeTaskState] - StartTime: Optional[String] - CompleteTime: Optional[String] - Tags: Optional[TagList] - ImageId: Optional[ImageId] - SnapshotId: Optional[SnapshotId] - DeleteReplacedRootVolume: Optional[Boolean] + ReplaceRootVolumeTaskId: ReplaceRootVolumeTaskId | None + InstanceId: String | None + TaskState: ReplaceRootVolumeTaskState | None + StartTime: String | None + CompleteTime: String | None + Tags: TagList | None + ImageId: ImageId | None + SnapshotId: SnapshotId | None + DeleteReplacedRootVolume: Boolean | None class CreateReplaceRootVolumeTaskResult(TypedDict, total=False): - ReplaceRootVolumeTask: Optional[ReplaceRootVolumeTask] + ReplaceRootVolumeTask: ReplaceRootVolumeTask | None class PriceScheduleSpecification(TypedDict, total=False): - Term: Optional[Long] - Price: Optional[Double] - CurrencyCode: Optional[CurrencyCodeValues] + Term: Long | None + Price: Double | None + CurrencyCode: CurrencyCodeValues | None -PriceScheduleSpecificationList = List[PriceScheduleSpecification] +PriceScheduleSpecificationList = list[PriceScheduleSpecification] class CreateReservedInstancesListingRequest(ServiceRequest): @@ -8930,414 +10189,489 @@ class CreateReservedInstancesListingRequest(ServiceRequest): class CreateReservedInstancesListingResult(TypedDict, total=False): - ReservedInstancesListings: Optional[ReservedInstancesListingList] + ReservedInstancesListings: ReservedInstancesListingList | None class CreateRestoreImageTaskRequest(ServiceRequest): Bucket: String ObjectKey: String - Name: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + Name: String | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class CreateRestoreImageTaskResult(TypedDict, total=False): - ImageId: Optional[String] + ImageId: String | None class CreateRouteRequest(ServiceRequest): - DestinationPrefixListId: Optional[PrefixListResourceId] - VpcEndpointId: Optional[VpcEndpointId] - TransitGatewayId: Optional[TransitGatewayId] - LocalGatewayId: Optional[LocalGatewayId] - CarrierGatewayId: Optional[CarrierGatewayId] - CoreNetworkArn: Optional[CoreNetworkArn] - OdbNetworkArn: Optional[OdbNetworkArn] - DryRun: Optional[Boolean] + DestinationPrefixListId: PrefixListResourceId | None + VpcEndpointId: VpcEndpointId | None + TransitGatewayId: TransitGatewayId | None + LocalGatewayId: LocalGatewayId | None + CarrierGatewayId: CarrierGatewayId | None + CoreNetworkArn: CoreNetworkArn | None + OdbNetworkArn: OdbNetworkArn | None + DryRun: Boolean | None RouteTableId: RouteTableId - DestinationCidrBlock: Optional[String] - GatewayId: Optional[RouteGatewayId] - DestinationIpv6CidrBlock: Optional[String] - EgressOnlyInternetGatewayId: Optional[EgressOnlyInternetGatewayId] - InstanceId: Optional[InstanceId] - NetworkInterfaceId: Optional[NetworkInterfaceId] - VpcPeeringConnectionId: Optional[VpcPeeringConnectionId] - NatGatewayId: Optional[NatGatewayId] + DestinationCidrBlock: String | None + GatewayId: RouteGatewayId | None + DestinationIpv6CidrBlock: String | None + EgressOnlyInternetGatewayId: EgressOnlyInternetGatewayId | None + InstanceId: InstanceId | None + NetworkInterfaceId: NetworkInterfaceId | None + VpcPeeringConnectionId: VpcPeeringConnectionId | None + NatGatewayId: NatGatewayId | None class CreateRouteResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class CreateRouteServerEndpointRequest(ServiceRequest): RouteServerId: RouteServerId SubnetId: SubnetId - ClientToken: Optional[String] - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] + ClientToken: String | None + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None class RouteServerEndpoint(TypedDict, total=False): - RouteServerId: Optional[RouteServerId] - RouteServerEndpointId: Optional[RouteServerEndpointId] - VpcId: Optional[VpcId] - SubnetId: Optional[SubnetId] - EniId: Optional[NetworkInterfaceId] - EniAddress: Optional[String] - State: Optional[RouteServerEndpointState] - FailureReason: Optional[String] - Tags: Optional[TagList] + RouteServerId: RouteServerId | None + RouteServerEndpointId: RouteServerEndpointId | None + VpcId: VpcId | None + SubnetId: SubnetId | None + EniId: NetworkInterfaceId | None + EniAddress: String | None + State: RouteServerEndpointState | None + FailureReason: String | None + Tags: TagList | None class CreateRouteServerEndpointResult(TypedDict, total=False): - RouteServerEndpoint: Optional[RouteServerEndpoint] + RouteServerEndpoint: RouteServerEndpoint | None class RouteServerBgpOptionsRequest(TypedDict, total=False): PeerAsn: Long - PeerLivenessDetection: Optional[RouteServerPeerLivenessMode] + PeerLivenessDetection: RouteServerPeerLivenessMode | None class CreateRouteServerPeerRequest(ServiceRequest): RouteServerEndpointId: RouteServerEndpointId PeerAddress: String BgpOptions: RouteServerBgpOptionsRequest - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None class RouteServerBfdStatus(TypedDict, total=False): - Status: Optional[RouteServerBfdState] + Status: RouteServerBfdState | None class RouteServerBgpStatus(TypedDict, total=False): - Status: Optional[RouteServerBgpState] + Status: RouteServerBgpState | None class RouteServerBgpOptions(TypedDict, total=False): - PeerAsn: Optional[Long] - PeerLivenessDetection: Optional[RouteServerPeerLivenessMode] + PeerAsn: Long | None + PeerLivenessDetection: RouteServerPeerLivenessMode | None class RouteServerPeer(TypedDict, total=False): - RouteServerPeerId: Optional[RouteServerPeerId] - RouteServerEndpointId: Optional[RouteServerEndpointId] - RouteServerId: Optional[RouteServerId] - VpcId: Optional[VpcId] - SubnetId: Optional[SubnetId] - State: Optional[RouteServerPeerState] - FailureReason: Optional[String] - EndpointEniId: Optional[NetworkInterfaceId] - EndpointEniAddress: Optional[String] - PeerAddress: Optional[String] - BgpOptions: Optional[RouteServerBgpOptions] - BgpStatus: Optional[RouteServerBgpStatus] - BfdStatus: Optional[RouteServerBfdStatus] - Tags: Optional[TagList] + RouteServerPeerId: RouteServerPeerId | None + RouteServerEndpointId: RouteServerEndpointId | None + RouteServerId: RouteServerId | None + VpcId: VpcId | None + SubnetId: SubnetId | None + State: RouteServerPeerState | None + FailureReason: String | None + EndpointEniId: NetworkInterfaceId | None + EndpointEniAddress: String | None + PeerAddress: String | None + BgpOptions: RouteServerBgpOptions | None + BgpStatus: RouteServerBgpStatus | None + BfdStatus: RouteServerBfdStatus | None + Tags: TagList | None class CreateRouteServerPeerResult(TypedDict, total=False): - RouteServerPeer: Optional[RouteServerPeer] + RouteServerPeer: RouteServerPeer | None class CreateRouteServerRequest(ServiceRequest): AmazonSideAsn: Long - ClientToken: Optional[String] - DryRun: Optional[Boolean] - PersistRoutes: Optional[RouteServerPersistRoutesAction] - PersistRoutesDuration: Optional[BoxedLong] - SnsNotificationsEnabled: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] + ClientToken: String | None + DryRun: Boolean | None + PersistRoutes: RouteServerPersistRoutesAction | None + PersistRoutesDuration: BoxedLong | None + SnsNotificationsEnabled: Boolean | None + TagSpecifications: TagSpecificationList | None class RouteServer(TypedDict, total=False): - RouteServerId: Optional[RouteServerId] - AmazonSideAsn: Optional[Long] - State: Optional[RouteServerState] - Tags: Optional[TagList] - PersistRoutesState: Optional[RouteServerPersistRoutesState] - PersistRoutesDuration: Optional[BoxedLong] - SnsNotificationsEnabled: Optional[Boolean] - SnsTopicArn: Optional[String] + RouteServerId: RouteServerId | None + AmazonSideAsn: Long | None + State: RouteServerState | None + Tags: TagList | None + PersistRoutesState: RouteServerPersistRoutesState | None + PersistRoutesDuration: BoxedLong | None + SnsNotificationsEnabled: Boolean | None + SnsTopicArn: String | None class CreateRouteServerResult(TypedDict, total=False): - RouteServer: Optional[RouteServer] + RouteServer: RouteServer | None class CreateRouteTableRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + DryRun: Boolean | None VpcId: VpcId class Route(TypedDict, total=False): - DestinationCidrBlock: Optional[String] - DestinationIpv6CidrBlock: Optional[String] - DestinationPrefixListId: Optional[String] - EgressOnlyInternetGatewayId: Optional[String] - GatewayId: Optional[String] - InstanceId: Optional[String] - InstanceOwnerId: Optional[String] - NatGatewayId: Optional[String] - TransitGatewayId: Optional[String] - LocalGatewayId: Optional[String] - CarrierGatewayId: Optional[CarrierGatewayId] - NetworkInterfaceId: Optional[String] - Origin: Optional[RouteOrigin] - State: Optional[RouteState] - VpcPeeringConnectionId: Optional[String] - CoreNetworkArn: Optional[CoreNetworkArn] - OdbNetworkArn: Optional[OdbNetworkArn] - - -RouteList = List[Route] + DestinationCidrBlock: String | None + DestinationIpv6CidrBlock: String | None + DestinationPrefixListId: String | None + EgressOnlyInternetGatewayId: String | None + GatewayId: String | None + InstanceId: String | None + InstanceOwnerId: String | None + NatGatewayId: String | None + TransitGatewayId: String | None + LocalGatewayId: String | None + CarrierGatewayId: CarrierGatewayId | None + NetworkInterfaceId: String | None + Origin: RouteOrigin | None + State: RouteState | None + VpcPeeringConnectionId: String | None + CoreNetworkArn: CoreNetworkArn | None + OdbNetworkArn: OdbNetworkArn | None + IpAddress: String | None + + +RouteList = list[Route] class PropagatingVgw(TypedDict, total=False): - GatewayId: Optional[String] + GatewayId: String | None -PropagatingVgwList = List[PropagatingVgw] +PropagatingVgwList = list[PropagatingVgw] class RouteTableAssociation(TypedDict, total=False): - Main: Optional[Boolean] - RouteTableAssociationId: Optional[String] - RouteTableId: Optional[String] - SubnetId: Optional[String] - GatewayId: Optional[String] - AssociationState: Optional[RouteTableAssociationState] + Main: Boolean | None + RouteTableAssociationId: String | None + RouteTableId: String | None + SubnetId: String | None + GatewayId: String | None + PublicIpv4Pool: String | None + AssociationState: RouteTableAssociationState | None -RouteTableAssociationList = List[RouteTableAssociation] +RouteTableAssociationList = list[RouteTableAssociation] class RouteTable(TypedDict, total=False): - Associations: Optional[RouteTableAssociationList] - PropagatingVgws: Optional[PropagatingVgwList] - RouteTableId: Optional[String] - Routes: Optional[RouteList] - Tags: Optional[TagList] - VpcId: Optional[String] - OwnerId: Optional[String] + Associations: RouteTableAssociationList | None + PropagatingVgws: PropagatingVgwList | None + RouteTableId: String | None + Routes: RouteList | None + Tags: TagList | None + VpcId: String | None + OwnerId: String | None class CreateRouteTableResult(TypedDict, total=False): - RouteTable: Optional[RouteTable] - ClientToken: Optional[String] + RouteTable: RouteTable | None + ClientToken: String | None + + +class CreateSecondaryNetworkRequest(ServiceRequest): + ClientToken: String | None + DryRun: Boolean | None + Ipv4CidrBlock: String + NetworkType: SecondaryNetworkType + TagSpecifications: TagSpecificationList | None + + +class SecondaryNetworkIpv4CidrBlockAssociation(TypedDict, total=False): + AssociationId: SecondaryNetworkCidrAssociationId | None + CidrBlock: String | None + State: SecondaryNetworkCidrBlockAssociationState | None + StateReason: String | None + + +SecondaryNetworkIpv4CidrBlockAssociationList = list[SecondaryNetworkIpv4CidrBlockAssociation] + + +class SecondaryNetwork(TypedDict, total=False): + SecondaryNetworkId: SecondaryNetworkId | None + SecondaryNetworkArn: String | None + OwnerId: String | None + Type: SecondaryNetworkType | None + State: SecondaryNetworkState | None + StateReason: String | None + Ipv4CidrBlockAssociations: SecondaryNetworkIpv4CidrBlockAssociationList | None + Tags: TagList | None + + +class CreateSecondaryNetworkResult(TypedDict, total=False): + SecondaryNetwork: SecondaryNetwork | None + ClientToken: String | None + + +class CreateSecondarySubnetRequest(ServiceRequest): + ClientToken: String | None + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None + DryRun: Boolean | None + Ipv4CidrBlock: String + SecondaryNetworkId: SecondaryNetworkId + TagSpecifications: TagSpecificationList | None + + +class SecondarySubnetIpv4CidrBlockAssociation(TypedDict, total=False): + AssociationId: SecondarySubnetCidrAssociationId | None + CidrBlock: String | None + State: SecondarySubnetCidrBlockAssociationState | None + StateReason: String | None + + +SecondarySubnetIpv4CidrBlockAssociationList = list[SecondarySubnetIpv4CidrBlockAssociation] + + +class SecondarySubnet(TypedDict, total=False): + SecondarySubnetId: SecondarySubnetId | None + SecondarySubnetArn: String | None + SecondaryNetworkId: SecondaryNetworkId | None + SecondaryNetworkType: SecondaryNetworkType | None + OwnerId: String | None + AvailabilityZoneId: AvailabilityZoneId | None + AvailabilityZone: AvailabilityZoneName | None + Ipv4CidrBlockAssociations: SecondarySubnetIpv4CidrBlockAssociationList | None + State: SecondarySubnetState | None + StateReason: String | None + Tags: TagList | None + + +class CreateSecondarySubnetResult(TypedDict, total=False): + SecondarySubnet: SecondarySubnet | None + ClientToken: String | None class CreateSecurityGroupRequest(ServiceRequest): Description: String GroupName: String - VpcId: Optional[VpcId] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + VpcId: VpcId | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class CreateSecurityGroupResult(TypedDict, total=False): - GroupId: Optional[String] - Tags: Optional[TagList] - SecurityGroupArn: Optional[String] + GroupId: String | None + Tags: TagList | None + SecurityGroupArn: String | None class CreateSnapshotRequest(ServiceRequest): - Description: Optional[String] - OutpostArn: Optional[String] + Description: String | None + OutpostArn: String | None VolumeId: VolumeId - TagSpecifications: Optional[TagSpecificationList] - Location: Optional[SnapshotLocationEnum] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + Location: SnapshotLocationEnum | None + DryRun: Boolean | None -VolumeIdStringList = List[VolumeId] +VolumeIdStringList = list[VolumeId] class InstanceSpecification(TypedDict, total=False): InstanceId: InstanceIdWithVolumeResolver - ExcludeBootVolume: Optional[Boolean] - ExcludeDataVolumeIds: Optional[VolumeIdStringList] + ExcludeBootVolume: Boolean | None + ExcludeDataVolumeIds: VolumeIdStringList | None class CreateSnapshotsRequest(ServiceRequest): - Description: Optional[String] + Description: String | None InstanceSpecification: InstanceSpecification - OutpostArn: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - CopyTagsFromSource: Optional[CopyTagsFromSource] - Location: Optional[SnapshotLocationEnum] + OutpostArn: String | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + CopyTagsFromSource: CopyTagsFromSource | None + Location: SnapshotLocationEnum | None class SnapshotInfo(TypedDict, total=False): - Description: Optional[String] - Tags: Optional[TagList] - Encrypted: Optional[Boolean] - VolumeId: Optional[String] - State: Optional[SnapshotState] - VolumeSize: Optional[Integer] - StartTime: Optional[MillisecondDateTime] - Progress: Optional[String] - OwnerId: Optional[String] - SnapshotId: Optional[String] - OutpostArn: Optional[String] - SseType: Optional[SSEType] - AvailabilityZone: Optional[String] + Description: String | None + Tags: TagList | None + Encrypted: Boolean | None + VolumeId: String | None + State: SnapshotState | None + VolumeSize: Integer | None + StartTime: MillisecondDateTime | None + Progress: String | None + OwnerId: String | None + SnapshotId: String | None + OutpostArn: String | None + SseType: SSEType | None + AvailabilityZone: String | None -SnapshotSet = List[SnapshotInfo] +SnapshotSet = list[SnapshotInfo] class CreateSnapshotsResult(TypedDict, total=False): - Snapshots: Optional[SnapshotSet] + Snapshots: SnapshotSet | None class CreateSpotDatafeedSubscriptionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Bucket: String - Prefix: Optional[String] + Prefix: String | None class SpotInstanceStateFault(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None class SpotDatafeedSubscription(TypedDict, total=False): - Bucket: Optional[String] - Fault: Optional[SpotInstanceStateFault] - OwnerId: Optional[String] - Prefix: Optional[String] - State: Optional[DatafeedSubscriptionState] + Bucket: String | None + Fault: SpotInstanceStateFault | None + OwnerId: String | None + Prefix: String | None + State: DatafeedSubscriptionState | None class CreateSpotDatafeedSubscriptionResult(TypedDict, total=False): - SpotDatafeedSubscription: Optional[SpotDatafeedSubscription] + SpotDatafeedSubscription: SpotDatafeedSubscription | None class S3ObjectTag(TypedDict, total=False): - Key: Optional[String] - Value: Optional[String] + Key: String | None + Value: String | None -S3ObjectTagList = List[S3ObjectTag] +S3ObjectTagList = list[S3ObjectTag] class CreateStoreImageTaskRequest(ServiceRequest): ImageId: ImageId Bucket: String - S3ObjectTags: Optional[S3ObjectTagList] - DryRun: Optional[Boolean] + S3ObjectTags: S3ObjectTagList | None + DryRun: Boolean | None class CreateStoreImageTaskResult(TypedDict, total=False): - ObjectKey: Optional[String] + ObjectKey: String | None class CreateSubnetCidrReservationRequest(ServiceRequest): SubnetId: SubnetId Cidr: String ReservationType: SubnetCidrReservationType - Description: Optional[String] - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] + Description: String | None + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None class SubnetCidrReservation(TypedDict, total=False): - SubnetCidrReservationId: Optional[SubnetCidrReservationId] - SubnetId: Optional[SubnetId] - Cidr: Optional[String] - ReservationType: Optional[SubnetCidrReservationType] - OwnerId: Optional[String] - Description: Optional[String] - Tags: Optional[TagList] + SubnetCidrReservationId: SubnetCidrReservationId | None + SubnetId: SubnetId | None + Cidr: String | None + ReservationType: SubnetCidrReservationType | None + OwnerId: String | None + Description: String | None + Tags: TagList | None class CreateSubnetCidrReservationResult(TypedDict, total=False): - SubnetCidrReservation: Optional[SubnetCidrReservation] + SubnetCidrReservation: SubnetCidrReservation | None class CreateSubnetRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - AvailabilityZone: Optional[String] - AvailabilityZoneId: Optional[String] - CidrBlock: Optional[String] - Ipv6CidrBlock: Optional[String] - OutpostArn: Optional[String] + TagSpecifications: TagSpecificationList | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + CidrBlock: String | None + Ipv6CidrBlock: String | None + OutpostArn: String | None VpcId: VpcId - Ipv6Native: Optional[Boolean] - Ipv4IpamPoolId: Optional[IpamPoolId] - Ipv4NetmaskLength: Optional[NetmaskLength] - Ipv6IpamPoolId: Optional[IpamPoolId] - Ipv6NetmaskLength: Optional[NetmaskLength] - DryRun: Optional[Boolean] + Ipv6Native: Boolean | None + Ipv4IpamPoolId: IpamPoolId | None + Ipv4NetmaskLength: NetmaskLength | None + Ipv6IpamPoolId: IpamPoolId | None + Ipv6NetmaskLength: NetmaskLength | None + DryRun: Boolean | None class CreateSubnetResult(TypedDict, total=False): - Subnet: Optional[Subnet] + Subnet: Subnet | None -ResourceIdList = List[TaggableResourceId] +ResourceIdList = list[TaggableResourceId] class CreateTagsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Resources: ResourceIdList Tags: TagList class CreateTrafficMirrorFilterRequest(ServiceRequest): - Description: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - ClientToken: Optional[String] + Description: String | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + ClientToken: String | None -TrafficMirrorNetworkServiceList = List[TrafficMirrorNetworkService] +TrafficMirrorNetworkServiceList = list[TrafficMirrorNetworkService] class TrafficMirrorPortRange(TypedDict, total=False): - FromPort: Optional[Integer] - ToPort: Optional[Integer] + FromPort: Integer | None + ToPort: Integer | None class TrafficMirrorFilterRule(TypedDict, total=False): - TrafficMirrorFilterRuleId: Optional[String] - TrafficMirrorFilterId: Optional[String] - TrafficDirection: Optional[TrafficDirection] - RuleNumber: Optional[Integer] - RuleAction: Optional[TrafficMirrorRuleAction] - Protocol: Optional[Integer] - DestinationPortRange: Optional[TrafficMirrorPortRange] - SourcePortRange: Optional[TrafficMirrorPortRange] - DestinationCidrBlock: Optional[String] - SourceCidrBlock: Optional[String] - Description: Optional[String] - Tags: Optional[TagList] + TrafficMirrorFilterRuleId: String | None + TrafficMirrorFilterId: String | None + TrafficDirection: TrafficDirection | None + RuleNumber: Integer | None + RuleAction: TrafficMirrorRuleAction | None + Protocol: Integer | None + DestinationPortRange: TrafficMirrorPortRange | None + SourcePortRange: TrafficMirrorPortRange | None + DestinationCidrBlock: String | None + SourceCidrBlock: String | None + Description: String | None + Tags: TagList | None -TrafficMirrorFilterRuleList = List[TrafficMirrorFilterRule] +TrafficMirrorFilterRuleList = list[TrafficMirrorFilterRule] class TrafficMirrorFilter(TypedDict, total=False): - TrafficMirrorFilterId: Optional[String] - IngressFilterRules: Optional[TrafficMirrorFilterRuleList] - EgressFilterRules: Optional[TrafficMirrorFilterRuleList] - NetworkServices: Optional[TrafficMirrorNetworkServiceList] - Description: Optional[String] - Tags: Optional[TagList] + TrafficMirrorFilterId: String | None + IngressFilterRules: TrafficMirrorFilterRuleList | None + EgressFilterRules: TrafficMirrorFilterRuleList | None + NetworkServices: TrafficMirrorNetworkServiceList | None + Description: String | None + Tags: TagList | None class CreateTrafficMirrorFilterResult(TypedDict, total=False): - TrafficMirrorFilter: Optional[TrafficMirrorFilter] - ClientToken: Optional[String] + TrafficMirrorFilter: TrafficMirrorFilter | None + ClientToken: String | None class TrafficMirrorPortRangeRequest(TypedDict, total=False): - FromPort: Optional[Integer] - ToPort: Optional[Integer] + FromPort: Integer | None + ToPort: Integer | None class CreateTrafficMirrorFilterRuleRequest(ServiceRequest): @@ -9345,126 +10679,126 @@ class CreateTrafficMirrorFilterRuleRequest(ServiceRequest): TrafficDirection: TrafficDirection RuleNumber: Integer RuleAction: TrafficMirrorRuleAction - DestinationPortRange: Optional[TrafficMirrorPortRangeRequest] - SourcePortRange: Optional[TrafficMirrorPortRangeRequest] - Protocol: Optional[Integer] + DestinationPortRange: TrafficMirrorPortRangeRequest | None + SourcePortRange: TrafficMirrorPortRangeRequest | None + Protocol: Integer | None DestinationCidrBlock: String SourceCidrBlock: String - Description: Optional[String] - DryRun: Optional[Boolean] - ClientToken: Optional[String] - TagSpecifications: Optional[TagSpecificationList] + Description: String | None + DryRun: Boolean | None + ClientToken: String | None + TagSpecifications: TagSpecificationList | None class CreateTrafficMirrorFilterRuleResult(TypedDict, total=False): - TrafficMirrorFilterRule: Optional[TrafficMirrorFilterRule] - ClientToken: Optional[String] + TrafficMirrorFilterRule: TrafficMirrorFilterRule | None + ClientToken: String | None class CreateTrafficMirrorSessionRequest(ServiceRequest): NetworkInterfaceId: NetworkInterfaceId TrafficMirrorTargetId: TrafficMirrorTargetId TrafficMirrorFilterId: TrafficMirrorFilterId - PacketLength: Optional[Integer] + PacketLength: Integer | None SessionNumber: Integer - VirtualNetworkId: Optional[Integer] - Description: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - ClientToken: Optional[String] + VirtualNetworkId: Integer | None + Description: String | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + ClientToken: String | None class TrafficMirrorSession(TypedDict, total=False): - TrafficMirrorSessionId: Optional[String] - TrafficMirrorTargetId: Optional[String] - TrafficMirrorFilterId: Optional[String] - NetworkInterfaceId: Optional[String] - OwnerId: Optional[String] - PacketLength: Optional[Integer] - SessionNumber: Optional[Integer] - VirtualNetworkId: Optional[Integer] - Description: Optional[String] - Tags: Optional[TagList] + TrafficMirrorSessionId: String | None + TrafficMirrorTargetId: String | None + TrafficMirrorFilterId: String | None + NetworkInterfaceId: String | None + OwnerId: String | None + PacketLength: Integer | None + SessionNumber: Integer | None + VirtualNetworkId: Integer | None + Description: String | None + Tags: TagList | None class CreateTrafficMirrorSessionResult(TypedDict, total=False): - TrafficMirrorSession: Optional[TrafficMirrorSession] - ClientToken: Optional[String] + TrafficMirrorSession: TrafficMirrorSession | None + ClientToken: String | None class CreateTrafficMirrorTargetRequest(ServiceRequest): - NetworkInterfaceId: Optional[NetworkInterfaceId] - NetworkLoadBalancerArn: Optional[String] - Description: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - ClientToken: Optional[String] - GatewayLoadBalancerEndpointId: Optional[VpcEndpointId] + NetworkInterfaceId: NetworkInterfaceId | None + NetworkLoadBalancerArn: String | None + Description: String | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + ClientToken: String | None + GatewayLoadBalancerEndpointId: VpcEndpointId | None class TrafficMirrorTarget(TypedDict, total=False): - TrafficMirrorTargetId: Optional[String] - NetworkInterfaceId: Optional[String] - NetworkLoadBalancerArn: Optional[String] - Type: Optional[TrafficMirrorTargetType] - Description: Optional[String] - OwnerId: Optional[String] - Tags: Optional[TagList] - GatewayLoadBalancerEndpointId: Optional[String] + TrafficMirrorTargetId: String | None + NetworkInterfaceId: String | None + NetworkLoadBalancerArn: String | None + Type: TrafficMirrorTargetType | None + Description: String | None + OwnerId: String | None + Tags: TagList | None + GatewayLoadBalancerEndpointId: String | None class CreateTrafficMirrorTargetResult(TypedDict, total=False): - TrafficMirrorTarget: Optional[TrafficMirrorTarget] - ClientToken: Optional[String] + TrafficMirrorTarget: TrafficMirrorTarget | None + ClientToken: String | None -InsideCidrBlocksStringList = List[String] +InsideCidrBlocksStringList = list[String] class TransitGatewayConnectRequestBgpOptions(TypedDict, total=False): - PeerAsn: Optional[Long] + PeerAsn: Long | None class CreateTransitGatewayConnectPeerRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - TransitGatewayAddress: Optional[String] + TransitGatewayAddress: String | None PeerAddress: String - BgpOptions: Optional[TransitGatewayConnectRequestBgpOptions] + BgpOptions: TransitGatewayConnectRequestBgpOptions | None InsideCidrBlocks: InsideCidrBlocksStringList - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class TransitGatewayAttachmentBgpConfiguration(TypedDict, total=False): - TransitGatewayAsn: Optional[Long] - PeerAsn: Optional[Long] - TransitGatewayAddress: Optional[String] - PeerAddress: Optional[String] - BgpStatus: Optional[BgpStatus] + TransitGatewayAsn: Long | None + PeerAsn: Long | None + TransitGatewayAddress: String | None + PeerAddress: String | None + BgpStatus: BgpStatus | None -TransitGatewayAttachmentBgpConfigurationList = List[TransitGatewayAttachmentBgpConfiguration] +TransitGatewayAttachmentBgpConfigurationList = list[TransitGatewayAttachmentBgpConfiguration] class TransitGatewayConnectPeerConfiguration(TypedDict, total=False): - TransitGatewayAddress: Optional[String] - PeerAddress: Optional[String] - InsideCidrBlocks: Optional[InsideCidrBlocksStringList] - Protocol: Optional[ProtocolValue] - BgpConfigurations: Optional[TransitGatewayAttachmentBgpConfigurationList] + TransitGatewayAddress: String | None + PeerAddress: String | None + InsideCidrBlocks: InsideCidrBlocksStringList | None + Protocol: ProtocolValue | None + BgpConfigurations: TransitGatewayAttachmentBgpConfigurationList | None class TransitGatewayConnectPeer(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - TransitGatewayConnectPeerId: Optional[TransitGatewayConnectPeerId] - State: Optional[TransitGatewayConnectPeerState] - CreationTime: Optional[DateTime] - ConnectPeerConfiguration: Optional[TransitGatewayConnectPeerConfiguration] - Tags: Optional[TagList] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + TransitGatewayConnectPeerId: TransitGatewayConnectPeerId | None + State: TransitGatewayConnectPeerState | None + CreationTime: DateTime | None + ConnectPeerConfiguration: TransitGatewayConnectPeerConfiguration | None + Tags: TagList | None class CreateTransitGatewayConnectPeerResult(TypedDict, total=False): - TransitGatewayConnectPeer: Optional[TransitGatewayConnectPeer] + TransitGatewayConnectPeer: TransitGatewayConnectPeer | None class CreateTransitGatewayConnectRequestOptions(TypedDict, total=False): @@ -9474,64 +10808,128 @@ class CreateTransitGatewayConnectRequestOptions(TypedDict, total=False): class CreateTransitGatewayConnectRequest(ServiceRequest): TransportTransitGatewayAttachmentId: TransitGatewayAttachmentId Options: CreateTransitGatewayConnectRequestOptions - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class TransitGatewayConnectOptions(TypedDict, total=False): - Protocol: Optional[ProtocolValue] + Protocol: ProtocolValue | None class TransitGatewayConnect(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - TransportTransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - TransitGatewayId: Optional[TransitGatewayId] - State: Optional[TransitGatewayAttachmentState] - CreationTime: Optional[DateTime] - Options: Optional[TransitGatewayConnectOptions] - Tags: Optional[TagList] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + TransportTransitGatewayAttachmentId: TransitGatewayAttachmentId | None + TransitGatewayId: TransitGatewayId | None + State: TransitGatewayAttachmentState | None + CreationTime: DateTime | None + Options: TransitGatewayConnectOptions | None + Tags: TagList | None class CreateTransitGatewayConnectResult(TypedDict, total=False): - TransitGatewayConnect: Optional[TransitGatewayConnect] + TransitGatewayConnect: TransitGatewayConnect | None + + +class CreateTransitGatewayMeteringPolicyEntryRequest(ServiceRequest): + TransitGatewayMeteringPolicyId: TransitGatewayMeteringPolicyId + PolicyRuleNumber: Integer + SourceTransitGatewayAttachmentId: TransitGatewayAttachmentId | None + SourceTransitGatewayAttachmentType: TransitGatewayAttachmentResourceType | None + SourceCidrBlock: String | None + SourcePortRange: String | None + DestinationTransitGatewayAttachmentId: TransitGatewayAttachmentId | None + DestinationTransitGatewayAttachmentType: TransitGatewayAttachmentResourceType | None + DestinationCidrBlock: String | None + DestinationPortRange: String | None + Protocol: String | None + MeteredAccount: TransitGatewayMeteringPayerType + DryRun: Boolean | None + + +class TransitGatewayMeteringPolicyRule(TypedDict, total=False): + SourceTransitGatewayAttachmentId: TransitGatewayAttachmentId | None + SourceTransitGatewayAttachmentType: TransitGatewayAttachmentResourceType | None + SourceCidrBlock: String | None + SourcePortRange: String | None + DestinationTransitGatewayAttachmentId: TransitGatewayAttachmentId | None + DestinationTransitGatewayAttachmentType: TransitGatewayAttachmentResourceType | None + DestinationCidrBlock: String | None + DestinationPortRange: String | None + Protocol: String | None + + +class TransitGatewayMeteringPolicyEntry(TypedDict, total=False): + PolicyRuleNumber: String | None + MeteredAccount: TransitGatewayMeteringPayerType | None + State: TransitGatewayMeteringPolicyEntryState | None + UpdatedAt: MillisecondDateTime | None + UpdateEffectiveAt: MillisecondDateTime | None + MeteringPolicyRule: TransitGatewayMeteringPolicyRule | None + + +class CreateTransitGatewayMeteringPolicyEntryResult(TypedDict, total=False): + TransitGatewayMeteringPolicyEntry: TransitGatewayMeteringPolicyEntry | None + + +TransitGatewayAttachmentIdStringList = list[TransitGatewayAttachmentId] + + +class CreateTransitGatewayMeteringPolicyRequest(ServiceRequest): + TransitGatewayId: TransitGatewayId + MiddleboxAttachmentIds: TransitGatewayAttachmentIdStringList | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + + +class TransitGatewayMeteringPolicy(TypedDict, total=False): + TransitGatewayMeteringPolicyId: TransitGatewayMeteringPolicyId | None + TransitGatewayId: TransitGatewayId | None + MiddleboxAttachmentIds: ValueStringList | None + State: TransitGatewayMeteringPolicyState | None + UpdateEffectiveAt: MillisecondDateTime | None + Tags: TagList | None + + +class CreateTransitGatewayMeteringPolicyResult(TypedDict, total=False): + TransitGatewayMeteringPolicy: TransitGatewayMeteringPolicy | None class CreateTransitGatewayMulticastDomainRequestOptions(TypedDict, total=False): - Igmpv2Support: Optional[Igmpv2SupportValue] - StaticSourcesSupport: Optional[StaticSourcesSupportValue] - AutoAcceptSharedAssociations: Optional[AutoAcceptSharedAssociationsValue] + Igmpv2Support: Igmpv2SupportValue | None + StaticSourcesSupport: StaticSourcesSupportValue | None + AutoAcceptSharedAssociations: AutoAcceptSharedAssociationsValue | None class CreateTransitGatewayMulticastDomainRequest(ServiceRequest): TransitGatewayId: TransitGatewayId - Options: Optional[CreateTransitGatewayMulticastDomainRequestOptions] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + Options: CreateTransitGatewayMulticastDomainRequestOptions | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class TransitGatewayMulticastDomainOptions(TypedDict, total=False): - Igmpv2Support: Optional[Igmpv2SupportValue] - StaticSourcesSupport: Optional[StaticSourcesSupportValue] - AutoAcceptSharedAssociations: Optional[AutoAcceptSharedAssociationsValue] + Igmpv2Support: Igmpv2SupportValue | None + StaticSourcesSupport: StaticSourcesSupportValue | None + AutoAcceptSharedAssociations: AutoAcceptSharedAssociationsValue | None class TransitGatewayMulticastDomain(TypedDict, total=False): - TransitGatewayMulticastDomainId: Optional[String] - TransitGatewayId: Optional[String] - TransitGatewayMulticastDomainArn: Optional[String] - OwnerId: Optional[String] - Options: Optional[TransitGatewayMulticastDomainOptions] - State: Optional[TransitGatewayMulticastDomainState] - CreationTime: Optional[DateTime] - Tags: Optional[TagList] + TransitGatewayMulticastDomainId: String | None + TransitGatewayId: String | None + TransitGatewayMulticastDomainArn: String | None + OwnerId: String | None + Options: TransitGatewayMulticastDomainOptions | None + State: TransitGatewayMulticastDomainState | None + CreationTime: DateTime | None + Tags: TagList | None class CreateTransitGatewayMulticastDomainResult(TypedDict, total=False): - TransitGatewayMulticastDomain: Optional[TransitGatewayMulticastDomain] + TransitGatewayMulticastDomain: TransitGatewayMulticastDomain | None class CreateTransitGatewayPeeringAttachmentRequestOptions(TypedDict, total=False): - DynamicRouting: Optional[DynamicRoutingValue] + DynamicRouting: DynamicRoutingValue | None class CreateTransitGatewayPeeringAttachmentRequest(ServiceRequest): @@ -9539,943 +10937,1003 @@ class CreateTransitGatewayPeeringAttachmentRequest(ServiceRequest): PeerTransitGatewayId: TransitAssociationGatewayId PeerAccountId: String PeerRegion: String - Options: Optional[CreateTransitGatewayPeeringAttachmentRequestOptions] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + Options: CreateTransitGatewayPeeringAttachmentRequestOptions | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class CreateTransitGatewayPeeringAttachmentResult(TypedDict, total=False): - TransitGatewayPeeringAttachment: Optional[TransitGatewayPeeringAttachment] + TransitGatewayPeeringAttachment: TransitGatewayPeeringAttachment | None class CreateTransitGatewayPolicyTableRequest(ServiceRequest): TransitGatewayId: TransitGatewayId - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class TransitGatewayPolicyTable(TypedDict, total=False): - TransitGatewayPolicyTableId: Optional[TransitGatewayPolicyTableId] - TransitGatewayId: Optional[TransitGatewayId] - State: Optional[TransitGatewayPolicyTableState] - CreationTime: Optional[DateTime] - Tags: Optional[TagList] + TransitGatewayPolicyTableId: TransitGatewayPolicyTableId | None + TransitGatewayId: TransitGatewayId | None + State: TransitGatewayPolicyTableState | None + CreationTime: DateTime | None + Tags: TagList | None class CreateTransitGatewayPolicyTableResult(TypedDict, total=False): - TransitGatewayPolicyTable: Optional[TransitGatewayPolicyTable] + TransitGatewayPolicyTable: TransitGatewayPolicyTable | None class CreateTransitGatewayPrefixListReferenceRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId PrefixListId: PrefixListResourceId - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - Blackhole: Optional[Boolean] - DryRun: Optional[Boolean] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + Blackhole: Boolean | None + DryRun: Boolean | None class TransitGatewayPrefixListAttachment(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - ResourceId: Optional[String] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + ResourceType: TransitGatewayAttachmentResourceType | None + ResourceId: String | None class TransitGatewayPrefixListReference(TypedDict, total=False): - TransitGatewayRouteTableId: Optional[TransitGatewayRouteTableId] - PrefixListId: Optional[PrefixListResourceId] - PrefixListOwnerId: Optional[String] - State: Optional[TransitGatewayPrefixListReferenceState] - Blackhole: Optional[Boolean] - TransitGatewayAttachment: Optional[TransitGatewayPrefixListAttachment] + TransitGatewayRouteTableId: TransitGatewayRouteTableId | None + PrefixListId: PrefixListResourceId | None + PrefixListOwnerId: String | None + State: TransitGatewayPrefixListReferenceState | None + Blackhole: Boolean | None + TransitGatewayAttachment: TransitGatewayPrefixListAttachment | None class CreateTransitGatewayPrefixListReferenceResult(TypedDict, total=False): - TransitGatewayPrefixListReference: Optional[TransitGatewayPrefixListReference] + TransitGatewayPrefixListReference: TransitGatewayPrefixListReference | None -TransitGatewayCidrBlockStringList = List[String] +TransitGatewayCidrBlockStringList = list[String] class TransitGatewayRequestOptions(TypedDict, total=False): - AmazonSideAsn: Optional[Long] - AutoAcceptSharedAttachments: Optional[AutoAcceptSharedAttachmentsValue] - DefaultRouteTableAssociation: Optional[DefaultRouteTableAssociationValue] - DefaultRouteTablePropagation: Optional[DefaultRouteTablePropagationValue] - VpnEcmpSupport: Optional[VpnEcmpSupportValue] - DnsSupport: Optional[DnsSupportValue] - SecurityGroupReferencingSupport: Optional[SecurityGroupReferencingSupportValue] - MulticastSupport: Optional[MulticastSupportValue] - TransitGatewayCidrBlocks: Optional[TransitGatewayCidrBlockStringList] + AmazonSideAsn: Long | None + AutoAcceptSharedAttachments: AutoAcceptSharedAttachmentsValue | None + DefaultRouteTableAssociation: DefaultRouteTableAssociationValue | None + DefaultRouteTablePropagation: DefaultRouteTablePropagationValue | None + VpnEcmpSupport: VpnEcmpSupportValue | None + DnsSupport: DnsSupportValue | None + SecurityGroupReferencingSupport: SecurityGroupReferencingSupportValue | None + MulticastSupport: MulticastSupportValue | None + TransitGatewayCidrBlocks: TransitGatewayCidrBlockStringList | None class CreateTransitGatewayRequest(ServiceRequest): - Description: Optional[String] - Options: Optional[TransitGatewayRequestOptions] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + Description: String | None + Options: TransitGatewayRequestOptions | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + + +class EncryptionSupport(TypedDict, total=False): + EncryptionState: EncryptionStateValue | None + StateMessage: String | None class TransitGatewayOptions(TypedDict, total=False): - AmazonSideAsn: Optional[Long] - TransitGatewayCidrBlocks: Optional[ValueStringList] - AutoAcceptSharedAttachments: Optional[AutoAcceptSharedAttachmentsValue] - DefaultRouteTableAssociation: Optional[DefaultRouteTableAssociationValue] - AssociationDefaultRouteTableId: Optional[String] - DefaultRouteTablePropagation: Optional[DefaultRouteTablePropagationValue] - PropagationDefaultRouteTableId: Optional[String] - VpnEcmpSupport: Optional[VpnEcmpSupportValue] - DnsSupport: Optional[DnsSupportValue] - SecurityGroupReferencingSupport: Optional[SecurityGroupReferencingSupportValue] - MulticastSupport: Optional[MulticastSupportValue] + AmazonSideAsn: Long | None + TransitGatewayCidrBlocks: ValueStringList | None + AutoAcceptSharedAttachments: AutoAcceptSharedAttachmentsValue | None + DefaultRouteTableAssociation: DefaultRouteTableAssociationValue | None + AssociationDefaultRouteTableId: String | None + DefaultRouteTablePropagation: DefaultRouteTablePropagationValue | None + PropagationDefaultRouteTableId: String | None + VpnEcmpSupport: VpnEcmpSupportValue | None + DnsSupport: DnsSupportValue | None + SecurityGroupReferencingSupport: SecurityGroupReferencingSupportValue | None + MulticastSupport: MulticastSupportValue | None + EncryptionSupport: EncryptionSupport | None class TransitGateway(TypedDict, total=False): - TransitGatewayId: Optional[String] - TransitGatewayArn: Optional[String] - State: Optional[TransitGatewayState] - OwnerId: Optional[String] - Description: Optional[String] - CreationTime: Optional[DateTime] - Options: Optional[TransitGatewayOptions] - Tags: Optional[TagList] + TransitGatewayId: String | None + TransitGatewayArn: String | None + State: TransitGatewayState | None + OwnerId: String | None + Description: String | None + CreationTime: DateTime | None + Options: TransitGatewayOptions | None + Tags: TagList | None class CreateTransitGatewayResult(TypedDict, total=False): - TransitGateway: Optional[TransitGateway] + TransitGateway: TransitGateway | None class CreateTransitGatewayRouteRequest(ServiceRequest): DestinationCidrBlock: String TransitGatewayRouteTableId: TransitGatewayRouteTableId - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - Blackhole: Optional[Boolean] - DryRun: Optional[Boolean] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + Blackhole: Boolean | None + DryRun: Boolean | None class TransitGatewayRouteAttachment(TypedDict, total=False): - ResourceId: Optional[String] - TransitGatewayAttachmentId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] + ResourceId: String | None + TransitGatewayAttachmentId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None -TransitGatewayRouteAttachmentList = List[TransitGatewayRouteAttachment] +TransitGatewayRouteAttachmentList = list[TransitGatewayRouteAttachment] class TransitGatewayRoute(TypedDict, total=False): - DestinationCidrBlock: Optional[String] - PrefixListId: Optional[PrefixListResourceId] - TransitGatewayRouteTableAnnouncementId: Optional[TransitGatewayRouteTableAnnouncementId] - TransitGatewayAttachments: Optional[TransitGatewayRouteAttachmentList] - Type: Optional[TransitGatewayRouteType] - State: Optional[TransitGatewayRouteState] + DestinationCidrBlock: String | None + PrefixListId: PrefixListResourceId | None + TransitGatewayRouteTableAnnouncementId: TransitGatewayRouteTableAnnouncementId | None + TransitGatewayAttachments: TransitGatewayRouteAttachmentList | None + Type: TransitGatewayRouteType | None + State: TransitGatewayRouteState | None class CreateTransitGatewayRouteResult(TypedDict, total=False): - Route: Optional[TransitGatewayRoute] + Route: TransitGatewayRoute | None class CreateTransitGatewayRouteTableAnnouncementRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId PeeringAttachmentId: TransitGatewayAttachmentId - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class TransitGatewayRouteTableAnnouncement(TypedDict, total=False): - TransitGatewayRouteTableAnnouncementId: Optional[TransitGatewayRouteTableAnnouncementId] - TransitGatewayId: Optional[TransitGatewayId] - CoreNetworkId: Optional[String] - PeerTransitGatewayId: Optional[TransitGatewayId] - PeerCoreNetworkId: Optional[String] - PeeringAttachmentId: Optional[TransitGatewayAttachmentId] - AnnouncementDirection: Optional[TransitGatewayRouteTableAnnouncementDirection] - TransitGatewayRouteTableId: Optional[TransitGatewayRouteTableId] - State: Optional[TransitGatewayRouteTableAnnouncementState] - CreationTime: Optional[DateTime] - Tags: Optional[TagList] + TransitGatewayRouteTableAnnouncementId: TransitGatewayRouteTableAnnouncementId | None + TransitGatewayId: TransitGatewayId | None + CoreNetworkId: String | None + PeerTransitGatewayId: TransitGatewayId | None + PeerCoreNetworkId: String | None + PeeringAttachmentId: TransitGatewayAttachmentId | None + AnnouncementDirection: TransitGatewayRouteTableAnnouncementDirection | None + TransitGatewayRouteTableId: TransitGatewayRouteTableId | None + State: TransitGatewayRouteTableAnnouncementState | None + CreationTime: DateTime | None + Tags: TagList | None class CreateTransitGatewayRouteTableAnnouncementResult(TypedDict, total=False): - TransitGatewayRouteTableAnnouncement: Optional[TransitGatewayRouteTableAnnouncement] + TransitGatewayRouteTableAnnouncement: TransitGatewayRouteTableAnnouncement | None class CreateTransitGatewayRouteTableRequest(ServiceRequest): TransitGatewayId: TransitGatewayId - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class TransitGatewayRouteTable(TypedDict, total=False): - TransitGatewayRouteTableId: Optional[String] - TransitGatewayId: Optional[String] - State: Optional[TransitGatewayRouteTableState] - DefaultAssociationRouteTable: Optional[Boolean] - DefaultPropagationRouteTable: Optional[Boolean] - CreationTime: Optional[DateTime] - Tags: Optional[TagList] + TransitGatewayRouteTableId: String | None + TransitGatewayId: String | None + State: TransitGatewayRouteTableState | None + DefaultAssociationRouteTable: Boolean | None + DefaultPropagationRouteTable: Boolean | None + CreationTime: DateTime | None + Tags: TagList | None class CreateTransitGatewayRouteTableResult(TypedDict, total=False): - TransitGatewayRouteTable: Optional[TransitGatewayRouteTable] + TransitGatewayRouteTable: TransitGatewayRouteTable | None class CreateTransitGatewayVpcAttachmentRequestOptions(TypedDict, total=False): - DnsSupport: Optional[DnsSupportValue] - SecurityGroupReferencingSupport: Optional[SecurityGroupReferencingSupportValue] - Ipv6Support: Optional[Ipv6SupportValue] - ApplianceModeSupport: Optional[ApplianceModeSupportValue] + DnsSupport: DnsSupportValue | None + SecurityGroupReferencingSupport: SecurityGroupReferencingSupportValue | None + Ipv6Support: Ipv6SupportValue | None + ApplianceModeSupport: ApplianceModeSupportValue | None class CreateTransitGatewayVpcAttachmentRequest(ServiceRequest): TransitGatewayId: TransitGatewayId VpcId: VpcId SubnetIds: TransitGatewaySubnetIdList - Options: Optional[CreateTransitGatewayVpcAttachmentRequestOptions] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + Options: CreateTransitGatewayVpcAttachmentRequestOptions | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None class CreateTransitGatewayVpcAttachmentResult(TypedDict, total=False): - TransitGatewayVpcAttachment: Optional[TransitGatewayVpcAttachment] + TransitGatewayVpcAttachment: TransitGatewayVpcAttachment | None class CreateVerifiedAccessEndpointPortRange(TypedDict, total=False): - FromPort: Optional[VerifiedAccessEndpointPortNumber] - ToPort: Optional[VerifiedAccessEndpointPortNumber] + FromPort: VerifiedAccessEndpointPortNumber | None + ToPort: VerifiedAccessEndpointPortNumber | None -CreateVerifiedAccessEndpointPortRangeList = List[CreateVerifiedAccessEndpointPortRange] -CreateVerifiedAccessEndpointSubnetIdList = List[SubnetId] +CreateVerifiedAccessEndpointPortRangeList = list[CreateVerifiedAccessEndpointPortRange] +CreateVerifiedAccessEndpointSubnetIdList = list[SubnetId] class CreateVerifiedAccessEndpointCidrOptions(TypedDict, total=False): - Protocol: Optional[VerifiedAccessEndpointProtocol] - SubnetIds: Optional[CreateVerifiedAccessEndpointSubnetIdList] - Cidr: Optional[String] - PortRanges: Optional[CreateVerifiedAccessEndpointPortRangeList] + Protocol: VerifiedAccessEndpointProtocol | None + SubnetIds: CreateVerifiedAccessEndpointSubnetIdList | None + Cidr: String | None + PortRanges: CreateVerifiedAccessEndpointPortRangeList | None class CreateVerifiedAccessEndpointEniOptions(TypedDict, total=False): - NetworkInterfaceId: Optional[NetworkInterfaceId] - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - PortRanges: Optional[CreateVerifiedAccessEndpointPortRangeList] + NetworkInterfaceId: NetworkInterfaceId | None + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + PortRanges: CreateVerifiedAccessEndpointPortRangeList | None class CreateVerifiedAccessEndpointLoadBalancerOptions(TypedDict, total=False): - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - LoadBalancerArn: Optional[LoadBalancerArn] - SubnetIds: Optional[CreateVerifiedAccessEndpointSubnetIdList] - PortRanges: Optional[CreateVerifiedAccessEndpointPortRangeList] + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + LoadBalancerArn: LoadBalancerArn | None + SubnetIds: CreateVerifiedAccessEndpointSubnetIdList | None + PortRanges: CreateVerifiedAccessEndpointPortRangeList | None class CreateVerifiedAccessEndpointRdsOptions(TypedDict, total=False): - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - RdsDbInstanceArn: Optional[RdsDbInstanceArn] - RdsDbClusterArn: Optional[RdsDbClusterArn] - RdsDbProxyArn: Optional[RdsDbProxyArn] - RdsEndpoint: Optional[String] - SubnetIds: Optional[CreateVerifiedAccessEndpointSubnetIdList] + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + RdsDbInstanceArn: RdsDbInstanceArn | None + RdsDbClusterArn: RdsDbClusterArn | None + RdsDbProxyArn: RdsDbProxyArn | None + RdsEndpoint: String | None + SubnetIds: CreateVerifiedAccessEndpointSubnetIdList | None class VerifiedAccessSseSpecificationRequest(TypedDict, total=False): - CustomerManagedKeyEnabled: Optional[Boolean] - KmsKeyArn: Optional[KmsKeyArn] + CustomerManagedKeyEnabled: Boolean | None + KmsKeyArn: KmsKeyArn | None -SecurityGroupIdList = List[SecurityGroupId] +SecurityGroupIdList = list[SecurityGroupId] class CreateVerifiedAccessEndpointRequest(ServiceRequest): VerifiedAccessGroupId: VerifiedAccessGroupId EndpointType: VerifiedAccessEndpointType AttachmentType: VerifiedAccessEndpointAttachmentType - DomainCertificateArn: Optional[CertificateArn] - ApplicationDomain: Optional[String] - EndpointDomainPrefix: Optional[String] - SecurityGroupIds: Optional[SecurityGroupIdList] - LoadBalancerOptions: Optional[CreateVerifiedAccessEndpointLoadBalancerOptions] - NetworkInterfaceOptions: Optional[CreateVerifiedAccessEndpointEniOptions] - Description: Optional[String] - PolicyDocument: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - SseSpecification: Optional[VerifiedAccessSseSpecificationRequest] - RdsOptions: Optional[CreateVerifiedAccessEndpointRdsOptions] - CidrOptions: Optional[CreateVerifiedAccessEndpointCidrOptions] + DomainCertificateArn: CertificateArn | None + ApplicationDomain: String | None + EndpointDomainPrefix: String | None + SecurityGroupIds: SecurityGroupIdList | None + LoadBalancerOptions: CreateVerifiedAccessEndpointLoadBalancerOptions | None + NetworkInterfaceOptions: CreateVerifiedAccessEndpointEniOptions | None + Description: String | None + PolicyDocument: String | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + DryRun: Boolean | None + SseSpecification: VerifiedAccessSseSpecificationRequest | None + RdsOptions: CreateVerifiedAccessEndpointRdsOptions | None + CidrOptions: CreateVerifiedAccessEndpointCidrOptions | None -VerifiedAccessEndpointSubnetIdList = List[SubnetId] +VerifiedAccessEndpointSubnetIdList = list[SubnetId] class VerifiedAccessEndpointPortRange(TypedDict, total=False): - FromPort: Optional[VerifiedAccessEndpointPortNumber] - ToPort: Optional[VerifiedAccessEndpointPortNumber] + FromPort: VerifiedAccessEndpointPortNumber | None + ToPort: VerifiedAccessEndpointPortNumber | None -VerifiedAccessEndpointPortRangeList = List[VerifiedAccessEndpointPortRange] +VerifiedAccessEndpointPortRangeList = list[VerifiedAccessEndpointPortRange] class VerifiedAccessEndpointCidrOptions(TypedDict, total=False): - Cidr: Optional[String] - PortRanges: Optional[VerifiedAccessEndpointPortRangeList] - Protocol: Optional[VerifiedAccessEndpointProtocol] - SubnetIds: Optional[VerifiedAccessEndpointSubnetIdList] + Cidr: String | None + PortRanges: VerifiedAccessEndpointPortRangeList | None + Protocol: VerifiedAccessEndpointProtocol | None + SubnetIds: VerifiedAccessEndpointSubnetIdList | None class VerifiedAccessEndpointRdsOptions(TypedDict, total=False): - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - RdsDbInstanceArn: Optional[String] - RdsDbClusterArn: Optional[String] - RdsDbProxyArn: Optional[String] - RdsEndpoint: Optional[String] - SubnetIds: Optional[VerifiedAccessEndpointSubnetIdList] + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + RdsDbInstanceArn: String | None + RdsDbClusterArn: String | None + RdsDbProxyArn: String | None + RdsEndpoint: String | None + SubnetIds: VerifiedAccessEndpointSubnetIdList | None class VerifiedAccessEndpointStatus(TypedDict, total=False): - Code: Optional[VerifiedAccessEndpointStatusCode] - Message: Optional[String] + Code: VerifiedAccessEndpointStatusCode | None + Message: String | None class VerifiedAccessEndpointEniOptions(TypedDict, total=False): - NetworkInterfaceId: Optional[NetworkInterfaceId] - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - PortRanges: Optional[VerifiedAccessEndpointPortRangeList] + NetworkInterfaceId: NetworkInterfaceId | None + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + PortRanges: VerifiedAccessEndpointPortRangeList | None class VerifiedAccessEndpointLoadBalancerOptions(TypedDict, total=False): - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - LoadBalancerArn: Optional[String] - SubnetIds: Optional[VerifiedAccessEndpointSubnetIdList] - PortRanges: Optional[VerifiedAccessEndpointPortRangeList] + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + LoadBalancerArn: String | None + SubnetIds: VerifiedAccessEndpointSubnetIdList | None + PortRanges: VerifiedAccessEndpointPortRangeList | None class VerifiedAccessEndpoint(TypedDict, total=False): - VerifiedAccessInstanceId: Optional[String] - VerifiedAccessGroupId: Optional[String] - VerifiedAccessEndpointId: Optional[String] - ApplicationDomain: Optional[String] - EndpointType: Optional[VerifiedAccessEndpointType] - AttachmentType: Optional[VerifiedAccessEndpointAttachmentType] - DomainCertificateArn: Optional[String] - EndpointDomain: Optional[String] - DeviceValidationDomain: Optional[String] - SecurityGroupIds: Optional[SecurityGroupIdList] - LoadBalancerOptions: Optional[VerifiedAccessEndpointLoadBalancerOptions] - NetworkInterfaceOptions: Optional[VerifiedAccessEndpointEniOptions] - Status: Optional[VerifiedAccessEndpointStatus] - Description: Optional[String] - CreationTime: Optional[String] - LastUpdatedTime: Optional[String] - DeletionTime: Optional[String] - Tags: Optional[TagList] - SseSpecification: Optional[VerifiedAccessSseSpecificationResponse] - RdsOptions: Optional[VerifiedAccessEndpointRdsOptions] - CidrOptions: Optional[VerifiedAccessEndpointCidrOptions] + VerifiedAccessInstanceId: String | None + VerifiedAccessGroupId: String | None + VerifiedAccessEndpointId: String | None + ApplicationDomain: String | None + EndpointType: VerifiedAccessEndpointType | None + AttachmentType: VerifiedAccessEndpointAttachmentType | None + DomainCertificateArn: String | None + EndpointDomain: String | None + DeviceValidationDomain: String | None + SecurityGroupIds: SecurityGroupIdList | None + LoadBalancerOptions: VerifiedAccessEndpointLoadBalancerOptions | None + NetworkInterfaceOptions: VerifiedAccessEndpointEniOptions | None + Status: VerifiedAccessEndpointStatus | None + Description: String | None + CreationTime: String | None + LastUpdatedTime: String | None + DeletionTime: String | None + Tags: TagList | None + SseSpecification: VerifiedAccessSseSpecificationResponse | None + RdsOptions: VerifiedAccessEndpointRdsOptions | None + CidrOptions: VerifiedAccessEndpointCidrOptions | None class CreateVerifiedAccessEndpointResult(TypedDict, total=False): - VerifiedAccessEndpoint: Optional[VerifiedAccessEndpoint] + VerifiedAccessEndpoint: VerifiedAccessEndpoint | None class CreateVerifiedAccessGroupRequest(ServiceRequest): VerifiedAccessInstanceId: VerifiedAccessInstanceId - Description: Optional[String] - PolicyDocument: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - SseSpecification: Optional[VerifiedAccessSseSpecificationRequest] + Description: String | None + PolicyDocument: String | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + DryRun: Boolean | None + SseSpecification: VerifiedAccessSseSpecificationRequest | None class VerifiedAccessGroup(TypedDict, total=False): - VerifiedAccessGroupId: Optional[String] - VerifiedAccessInstanceId: Optional[String] - Description: Optional[String] - Owner: Optional[String] - VerifiedAccessGroupArn: Optional[String] - CreationTime: Optional[String] - LastUpdatedTime: Optional[String] - DeletionTime: Optional[String] - Tags: Optional[TagList] - SseSpecification: Optional[VerifiedAccessSseSpecificationResponse] + VerifiedAccessGroupId: String | None + VerifiedAccessInstanceId: String | None + Description: String | None + Owner: String | None + VerifiedAccessGroupArn: String | None + CreationTime: String | None + LastUpdatedTime: String | None + DeletionTime: String | None + Tags: TagList | None + SseSpecification: VerifiedAccessSseSpecificationResponse | None class CreateVerifiedAccessGroupResult(TypedDict, total=False): - VerifiedAccessGroup: Optional[VerifiedAccessGroup] + VerifiedAccessGroup: VerifiedAccessGroup | None class CreateVerifiedAccessInstanceRequest(ServiceRequest): - Description: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - FIPSEnabled: Optional[Boolean] - CidrEndpointsCustomSubDomain: Optional[String] + Description: String | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + DryRun: Boolean | None + FIPSEnabled: Boolean | None + CidrEndpointsCustomSubDomain: String | None class CreateVerifiedAccessInstanceResult(TypedDict, total=False): - VerifiedAccessInstance: Optional[VerifiedAccessInstance] + VerifiedAccessInstance: VerifiedAccessInstance | None class CreateVerifiedAccessNativeApplicationOidcOptions(TypedDict, total=False): - PublicSigningKeyEndpoint: Optional[String] - Issuer: Optional[String] - AuthorizationEndpoint: Optional[String] - TokenEndpoint: Optional[String] - UserInfoEndpoint: Optional[String] - ClientId: Optional[String] - ClientSecret: Optional[ClientSecretType] - Scope: Optional[String] + PublicSigningKeyEndpoint: String | None + Issuer: String | None + AuthorizationEndpoint: String | None + TokenEndpoint: String | None + UserInfoEndpoint: String | None + ClientId: String | None + ClientSecret: ClientSecretType | None + Scope: String | None class CreateVerifiedAccessTrustProviderDeviceOptions(TypedDict, total=False): - TenantId: Optional[String] - PublicSigningKeyUrl: Optional[String] + TenantId: String | None + PublicSigningKeyUrl: String | None class CreateVerifiedAccessTrustProviderOidcOptions(TypedDict, total=False): - Issuer: Optional[String] - AuthorizationEndpoint: Optional[String] - TokenEndpoint: Optional[String] - UserInfoEndpoint: Optional[String] - ClientId: Optional[String] - ClientSecret: Optional[ClientSecretType] - Scope: Optional[String] + Issuer: String | None + AuthorizationEndpoint: String | None + TokenEndpoint: String | None + UserInfoEndpoint: String | None + ClientId: String | None + ClientSecret: ClientSecretType | None + Scope: String | None class CreateVerifiedAccessTrustProviderRequest(ServiceRequest): TrustProviderType: TrustProviderType - UserTrustProviderType: Optional[UserTrustProviderType] - DeviceTrustProviderType: Optional[DeviceTrustProviderType] - OidcOptions: Optional[CreateVerifiedAccessTrustProviderOidcOptions] - DeviceOptions: Optional[CreateVerifiedAccessTrustProviderDeviceOptions] + UserTrustProviderType: UserTrustProviderType | None + DeviceTrustProviderType: DeviceTrustProviderType | None + OidcOptions: CreateVerifiedAccessTrustProviderOidcOptions | None + DeviceOptions: CreateVerifiedAccessTrustProviderDeviceOptions | None PolicyReferenceName: String - Description: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - SseSpecification: Optional[VerifiedAccessSseSpecificationRequest] - NativeApplicationOidcOptions: Optional[CreateVerifiedAccessNativeApplicationOidcOptions] + Description: String | None + TagSpecifications: TagSpecificationList | None + ClientToken: String | None + DryRun: Boolean | None + SseSpecification: VerifiedAccessSseSpecificationRequest | None + NativeApplicationOidcOptions: CreateVerifiedAccessNativeApplicationOidcOptions | None class CreateVerifiedAccessTrustProviderResult(TypedDict, total=False): - VerifiedAccessTrustProvider: Optional[VerifiedAccessTrustProvider] + VerifiedAccessTrustProvider: VerifiedAccessTrustProvider | None class CreateVolumePermission(TypedDict, total=False): - UserId: Optional[String] - Group: Optional[PermissionGroup] + UserId: String | None + Group: PermissionGroup | None -CreateVolumePermissionList = List[CreateVolumePermission] +CreateVolumePermissionList = list[CreateVolumePermission] class CreateVolumePermissionModifications(TypedDict, total=False): - Add: Optional[CreateVolumePermissionList] - Remove: Optional[CreateVolumePermissionList] + Add: CreateVolumePermissionList | None + Remove: CreateVolumePermissionList | None class CreateVolumeRequest(ServiceRequest): - AvailabilityZone: AvailabilityZoneName - Encrypted: Optional[Boolean] - Iops: Optional[Integer] - KmsKeyId: Optional[KmsKeyId] - OutpostArn: Optional[String] - Size: Optional[Integer] - SnapshotId: Optional[SnapshotId] - VolumeType: Optional[VolumeType] - TagSpecifications: Optional[TagSpecificationList] - MultiAttachEnabled: Optional[Boolean] - Throughput: Optional[Integer] - ClientToken: Optional[String] - VolumeInitializationRate: Optional[Integer] - Operator: Optional[OperatorRequest] - DryRun: Optional[Boolean] + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None + Encrypted: Boolean | None + Iops: Integer | None + KmsKeyId: KmsKeyId | None + OutpostArn: String | None + Size: Integer | None + SnapshotId: SnapshotId | None + VolumeType: VolumeType | None + TagSpecifications: TagSpecificationList | None + MultiAttachEnabled: Boolean | None + Throughput: Integer | None + ClientToken: String | None + VolumeInitializationRate: Integer | None + Operator: OperatorRequest | None + DryRun: Boolean | None class CreateVpcBlockPublicAccessExclusionRequest(ServiceRequest): - DryRun: Optional[Boolean] - SubnetId: Optional[SubnetId] - VpcId: Optional[VpcId] + DryRun: Boolean | None + SubnetId: SubnetId | None + VpcId: VpcId | None InternetGatewayExclusionMode: InternetGatewayExclusionMode - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None class VpcBlockPublicAccessExclusion(TypedDict, total=False): - ExclusionId: Optional[VpcBlockPublicAccessExclusionId] - InternetGatewayExclusionMode: Optional[InternetGatewayExclusionMode] - ResourceArn: Optional[ResourceArn] - State: Optional[VpcBlockPublicAccessExclusionState] - Reason: Optional[String] - CreationTimestamp: Optional[MillisecondDateTime] - LastUpdateTimestamp: Optional[MillisecondDateTime] - DeletionTimestamp: Optional[MillisecondDateTime] - Tags: Optional[TagList] + ExclusionId: VpcBlockPublicAccessExclusionId | None + InternetGatewayExclusionMode: InternetGatewayExclusionMode | None + ResourceArn: ResourceArn | None + State: VpcBlockPublicAccessExclusionState | None + Reason: String | None + CreationTimestamp: MillisecondDateTime | None + LastUpdateTimestamp: MillisecondDateTime | None + DeletionTimestamp: MillisecondDateTime | None + Tags: TagList | None class CreateVpcBlockPublicAccessExclusionResult(TypedDict, total=False): - VpcBlockPublicAccessExclusion: Optional[VpcBlockPublicAccessExclusion] + VpcBlockPublicAccessExclusion: VpcBlockPublicAccessExclusion | None + + +class CreateVpcEncryptionControlRequest(ServiceRequest): + DryRun: Boolean | None + VpcId: VpcId + TagSpecifications: TagSpecificationList | None + + +class CreateVpcEncryptionControlResult(TypedDict, total=False): + VpcEncryptionControl: VpcEncryptionControl | None class CreateVpcEndpointConnectionNotificationRequest(ServiceRequest): - DryRun: Optional[Boolean] - ServiceId: Optional[VpcEndpointServiceId] - VpcEndpointId: Optional[VpcEndpointId] + DryRun: Boolean | None + ServiceId: VpcEndpointServiceId | None + VpcEndpointId: VpcEndpointId | None ConnectionNotificationArn: String ConnectionEvents: ValueStringList - ClientToken: Optional[String] + ClientToken: String | None class CreateVpcEndpointConnectionNotificationResult(TypedDict, total=False): - ConnectionNotification: Optional[ConnectionNotification] - ClientToken: Optional[String] + ConnectionNotification: ConnectionNotification | None + ClientToken: String | None class SubnetConfiguration(TypedDict, total=False): - SubnetId: Optional[SubnetId] - Ipv4: Optional[String] - Ipv6: Optional[String] + SubnetId: SubnetId | None + Ipv4: String | None + Ipv6: String | None -SubnetConfigurationsList = List[SubnetConfiguration] +SubnetConfigurationsList = list[SubnetConfiguration] +PrivateDnsSpecifiedDomainSet = list[String] class DnsOptionsSpecification(TypedDict, total=False): - DnsRecordIpType: Optional[DnsRecordIpType] - PrivateDnsOnlyForInboundResolverEndpoint: Optional[Boolean] + DnsRecordIpType: DnsRecordIpType | None + PrivateDnsOnlyForInboundResolverEndpoint: Boolean | None + PrivateDnsPreference: String | None + PrivateDnsSpecifiedDomains: PrivateDnsSpecifiedDomainSet | None -VpcEndpointSecurityGroupIdList = List[SecurityGroupId] -VpcEndpointSubnetIdList = List[SubnetId] -VpcEndpointRouteTableIdList = List[RouteTableId] +VpcEndpointSecurityGroupIdList = list[SecurityGroupId] +VpcEndpointSubnetIdList = list[SubnetId] +VpcEndpointRouteTableIdList = list[RouteTableId] class CreateVpcEndpointRequest(ServiceRequest): - DryRun: Optional[Boolean] - VpcEndpointType: Optional[VpcEndpointType] + DryRun: Boolean | None + VpcEndpointType: VpcEndpointType | None VpcId: VpcId - ServiceName: Optional[String] - PolicyDocument: Optional[String] - RouteTableIds: Optional[VpcEndpointRouteTableIdList] - SubnetIds: Optional[VpcEndpointSubnetIdList] - SecurityGroupIds: Optional[VpcEndpointSecurityGroupIdList] - IpAddressType: Optional[IpAddressType] - DnsOptions: Optional[DnsOptionsSpecification] - ClientToken: Optional[String] - PrivateDnsEnabled: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] - SubnetConfigurations: Optional[SubnetConfigurationsList] - ServiceNetworkArn: Optional[ServiceNetworkArn] - ResourceConfigurationArn: Optional[ResourceConfigurationArn] - ServiceRegion: Optional[String] + ServiceName: String | None + PolicyDocument: String | None + RouteTableIds: VpcEndpointRouteTableIdList | None + SubnetIds: VpcEndpointSubnetIdList | None + SecurityGroupIds: VpcEndpointSecurityGroupIdList | None + IpAddressType: IpAddressType | None + DnsOptions: DnsOptionsSpecification | None + ClientToken: String | None + PrivateDnsEnabled: Boolean | None + TagSpecifications: TagSpecificationList | None + SubnetConfigurations: SubnetConfigurationsList | None + ServiceNetworkArn: ServiceNetworkArn | None + ResourceConfigurationArn: ResourceConfigurationArn | None + ServiceRegion: String | None class SubnetIpPrefixes(TypedDict, total=False): - SubnetId: Optional[String] - IpPrefixes: Optional[ValueStringList] + SubnetId: String | None + IpPrefixes: ValueStringList | None -SubnetIpPrefixesList = List[SubnetIpPrefixes] +SubnetIpPrefixesList = list[SubnetIpPrefixes] class LastError(TypedDict, total=False): - Message: Optional[String] - Code: Optional[String] + Message: String | None + Code: String | None class DnsEntry(TypedDict, total=False): - DnsName: Optional[String] - HostedZoneId: Optional[String] + DnsName: String | None + HostedZoneId: String | None -DnsEntrySet = List[DnsEntry] +DnsEntrySet = list[DnsEntry] class DnsOptions(TypedDict, total=False): - DnsRecordIpType: Optional[DnsRecordIpType] - PrivateDnsOnlyForInboundResolverEndpoint: Optional[Boolean] + DnsRecordIpType: DnsRecordIpType | None + PrivateDnsOnlyForInboundResolverEndpoint: Boolean | None + PrivateDnsPreference: String | None + PrivateDnsSpecifiedDomains: PrivateDnsSpecifiedDomainSet | None class SecurityGroupIdentifier(TypedDict, total=False): - GroupId: Optional[String] - GroupName: Optional[String] + GroupId: String | None + GroupName: String | None -GroupIdentifierSet = List[SecurityGroupIdentifier] +GroupIdentifierSet = list[SecurityGroupIdentifier] class VpcEndpoint(TypedDict, total=False): - VpcEndpointId: Optional[String] - VpcEndpointType: Optional[VpcEndpointType] - VpcId: Optional[String] - ServiceName: Optional[String] - State: Optional[State] - PolicyDocument: Optional[String] - RouteTableIds: Optional[ValueStringList] - SubnetIds: Optional[ValueStringList] - Groups: Optional[GroupIdentifierSet] - IpAddressType: Optional[IpAddressType] - DnsOptions: Optional[DnsOptions] - PrivateDnsEnabled: Optional[Boolean] - RequesterManaged: Optional[Boolean] - NetworkInterfaceIds: Optional[ValueStringList] - DnsEntries: Optional[DnsEntrySet] - CreationTimestamp: Optional[MillisecondDateTime] - Tags: Optional[TagList] - OwnerId: Optional[String] - LastError: Optional[LastError] - Ipv4Prefixes: Optional[SubnetIpPrefixesList] - Ipv6Prefixes: Optional[SubnetIpPrefixesList] - FailureReason: Optional[String] - ServiceNetworkArn: Optional[ServiceNetworkArn] - ResourceConfigurationArn: Optional[ResourceConfigurationArn] - ServiceRegion: Optional[String] + VpcEndpointId: String | None + VpcEndpointType: VpcEndpointType | None + VpcId: String | None + ServiceName: String | None + State: State | None + PolicyDocument: String | None + RouteTableIds: ValueStringList | None + SubnetIds: ValueStringList | None + Groups: GroupIdentifierSet | None + IpAddressType: IpAddressType | None + DnsOptions: DnsOptions | None + PrivateDnsEnabled: Boolean | None + RequesterManaged: Boolean | None + NetworkInterfaceIds: ValueStringList | None + DnsEntries: DnsEntrySet | None + CreationTimestamp: MillisecondDateTime | None + Tags: TagList | None + OwnerId: String | None + LastError: LastError | None + Ipv4Prefixes: SubnetIpPrefixesList | None + Ipv6Prefixes: SubnetIpPrefixesList | None + FailureReason: String | None + ServiceNetworkArn: ServiceNetworkArn | None + ResourceConfigurationArn: ResourceConfigurationArn | None + ServiceRegion: String | None class CreateVpcEndpointResult(TypedDict, total=False): - VpcEndpoint: Optional[VpcEndpoint] - ClientToken: Optional[String] + VpcEndpoint: VpcEndpoint | None + ClientToken: String | None class CreateVpcEndpointServiceConfigurationRequest(ServiceRequest): - DryRun: Optional[Boolean] - AcceptanceRequired: Optional[Boolean] - PrivateDnsName: Optional[String] - NetworkLoadBalancerArns: Optional[ValueStringList] - GatewayLoadBalancerArns: Optional[ValueStringList] - SupportedIpAddressTypes: Optional[ValueStringList] - SupportedRegions: Optional[ValueStringList] - ClientToken: Optional[String] - TagSpecifications: Optional[TagSpecificationList] + DryRun: Boolean | None + AcceptanceRequired: Boolean | None + PrivateDnsName: String | None + NetworkLoadBalancerArns: ValueStringList | None + GatewayLoadBalancerArns: ValueStringList | None + SupportedIpAddressTypes: ValueStringList | None + SupportedRegions: ValueStringList | None + ClientToken: String | None + TagSpecifications: TagSpecificationList | None class SupportedRegionDetail(TypedDict, total=False): - Region: Optional[String] - ServiceState: Optional[String] + Region: String | None + ServiceState: String | None -SupportedRegionSet = List[SupportedRegionDetail] +SupportedRegionSet = list[SupportedRegionDetail] class PrivateDnsNameConfiguration(TypedDict, total=False): - State: Optional[DnsNameState] - Type: Optional[String] - Value: Optional[String] - Name: Optional[String] + State: DnsNameState | None + Type: String | None + Value: String | None + Name: String | None -SupportedIpAddressTypes = List[ServiceConnectivityType] +SupportedIpAddressTypes = list[ServiceConnectivityType] class ServiceTypeDetail(TypedDict, total=False): - ServiceType: Optional[ServiceType] + ServiceType: ServiceType | None -ServiceTypeDetailSet = List[ServiceTypeDetail] +ServiceTypeDetailSet = list[ServiceTypeDetail] class ServiceConfiguration(TypedDict, total=False): - ServiceType: Optional[ServiceTypeDetailSet] - ServiceId: Optional[String] - ServiceName: Optional[String] - ServiceState: Optional[ServiceState] - AvailabilityZones: Optional[ValueStringList] - AcceptanceRequired: Optional[Boolean] - ManagesVpcEndpoints: Optional[Boolean] - NetworkLoadBalancerArns: Optional[ValueStringList] - GatewayLoadBalancerArns: Optional[ValueStringList] - SupportedIpAddressTypes: Optional[SupportedIpAddressTypes] - BaseEndpointDnsNames: Optional[ValueStringList] - PrivateDnsName: Optional[String] - PrivateDnsNameConfiguration: Optional[PrivateDnsNameConfiguration] - PayerResponsibility: Optional[PayerResponsibility] - Tags: Optional[TagList] - SupportedRegions: Optional[SupportedRegionSet] - RemoteAccessEnabled: Optional[Boolean] + ServiceType: ServiceTypeDetailSet | None + ServiceId: String | None + ServiceName: String | None + ServiceState: ServiceState | None + AvailabilityZoneIds: ValueStringList | None + AvailabilityZones: ValueStringList | None + AcceptanceRequired: Boolean | None + ManagesVpcEndpoints: Boolean | None + NetworkLoadBalancerArns: ValueStringList | None + GatewayLoadBalancerArns: ValueStringList | None + SupportedIpAddressTypes: SupportedIpAddressTypes | None + BaseEndpointDnsNames: ValueStringList | None + PrivateDnsName: String | None + PrivateDnsNameConfiguration: PrivateDnsNameConfiguration | None + PayerResponsibility: PayerResponsibility | None + Tags: TagList | None + SupportedRegions: SupportedRegionSet | None + RemoteAccessEnabled: Boolean | None class CreateVpcEndpointServiceConfigurationResult(TypedDict, total=False): - ServiceConfiguration: Optional[ServiceConfiguration] - ClientToken: Optional[String] + ServiceConfiguration: ServiceConfiguration | None + ClientToken: String | None class CreateVpcPeeringConnectionRequest(ServiceRequest): - PeerRegion: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + PeerRegion: String | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None VpcId: VpcId - PeerVpcId: Optional[String] - PeerOwnerId: Optional[String] + PeerVpcId: String | None + PeerOwnerId: String | None class CreateVpcPeeringConnectionResult(TypedDict, total=False): - VpcPeeringConnection: Optional[VpcPeeringConnection] + VpcPeeringConnection: VpcPeeringConnection | None + + +class VpcEncryptionControlConfiguration(TypedDict, total=False): + Mode: VpcEncryptionControlMode + InternetGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + EgressOnlyInternetGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + NatGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + VirtualPrivateGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + VpcPeeringExclusion: VpcEncryptionControlExclusionStateInput | None + LambdaExclusion: VpcEncryptionControlExclusionStateInput | None + VpcLatticeExclusion: VpcEncryptionControlExclusionStateInput | None + ElasticFileSystemExclusion: VpcEncryptionControlExclusionStateInput | None class CreateVpcRequest(ServiceRequest): - CidrBlock: Optional[String] - Ipv6Pool: Optional[Ipv6PoolEc2Id] - Ipv6CidrBlock: Optional[String] - Ipv4IpamPoolId: Optional[IpamPoolId] - Ipv4NetmaskLength: Optional[NetmaskLength] - Ipv6IpamPoolId: Optional[IpamPoolId] - Ipv6NetmaskLength: Optional[NetmaskLength] - Ipv6CidrBlockNetworkBorderGroup: Optional[String] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] - InstanceTenancy: Optional[Tenancy] - AmazonProvidedIpv6CidrBlock: Optional[Boolean] + CidrBlock: String | None + Ipv6Pool: Ipv6PoolEc2Id | None + Ipv6CidrBlock: String | None + Ipv4IpamPoolId: IpamPoolId | None + Ipv4NetmaskLength: NetmaskLength | None + Ipv6IpamPoolId: IpamPoolId | None + Ipv6NetmaskLength: NetmaskLength | None + Ipv6CidrBlockNetworkBorderGroup: String | None + VpcEncryptionControl: VpcEncryptionControlConfiguration | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + InstanceTenancy: Tenancy | None + AmazonProvidedIpv6CidrBlock: Boolean | None class CreateVpcResult(TypedDict, total=False): - Vpc: Optional[Vpc] + Vpc: Vpc | None + + +class CreateVpnConcentratorRequest(ServiceRequest): + Type: VpnConcentratorType + TransitGatewayId: TransitGatewayId | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None + + +class VpnConcentrator(TypedDict, total=False): + VpnConcentratorId: String | None + State: String | None + TransitGatewayId: String | None + TransitGatewayAttachmentId: String | None + Type: String | None + Tags: TagList | None + + +class CreateVpnConcentratorResult(TypedDict, total=False): + VpnConcentrator: VpnConcentrator | None class VpnTunnelLogOptionsSpecification(TypedDict, total=False): - CloudWatchLogOptions: Optional[CloudWatchLogOptionsSpecification] + CloudWatchLogOptions: CloudWatchLogOptionsSpecification | None class IKEVersionsRequestListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -IKEVersionsRequestList = List[IKEVersionsRequestListValue] +IKEVersionsRequestList = list[IKEVersionsRequestListValue] class Phase2DHGroupNumbersRequestListValue(TypedDict, total=False): - Value: Optional[Integer] + Value: Integer | None -Phase2DHGroupNumbersRequestList = List[Phase2DHGroupNumbersRequestListValue] +Phase2DHGroupNumbersRequestList = list[Phase2DHGroupNumbersRequestListValue] class Phase1DHGroupNumbersRequestListValue(TypedDict, total=False): - Value: Optional[Integer] + Value: Integer | None -Phase1DHGroupNumbersRequestList = List[Phase1DHGroupNumbersRequestListValue] +Phase1DHGroupNumbersRequestList = list[Phase1DHGroupNumbersRequestListValue] class Phase2IntegrityAlgorithmsRequestListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase2IntegrityAlgorithmsRequestList = List[Phase2IntegrityAlgorithmsRequestListValue] +Phase2IntegrityAlgorithmsRequestList = list[Phase2IntegrityAlgorithmsRequestListValue] class Phase1IntegrityAlgorithmsRequestListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase1IntegrityAlgorithmsRequestList = List[Phase1IntegrityAlgorithmsRequestListValue] +Phase1IntegrityAlgorithmsRequestList = list[Phase1IntegrityAlgorithmsRequestListValue] class Phase2EncryptionAlgorithmsRequestListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase2EncryptionAlgorithmsRequestList = List[Phase2EncryptionAlgorithmsRequestListValue] +Phase2EncryptionAlgorithmsRequestList = list[Phase2EncryptionAlgorithmsRequestListValue] class Phase1EncryptionAlgorithmsRequestListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase1EncryptionAlgorithmsRequestList = List[Phase1EncryptionAlgorithmsRequestListValue] +Phase1EncryptionAlgorithmsRequestList = list[Phase1EncryptionAlgorithmsRequestListValue] class VpnTunnelOptionsSpecification(TypedDict, total=False): - TunnelInsideCidr: Optional[String] - TunnelInsideIpv6Cidr: Optional[String] - PreSharedKey: Optional[preSharedKey] - Phase1LifetimeSeconds: Optional[Integer] - Phase2LifetimeSeconds: Optional[Integer] - RekeyMarginTimeSeconds: Optional[Integer] - RekeyFuzzPercentage: Optional[Integer] - ReplayWindowSize: Optional[Integer] - DPDTimeoutSeconds: Optional[Integer] - DPDTimeoutAction: Optional[String] - Phase1EncryptionAlgorithms: Optional[Phase1EncryptionAlgorithmsRequestList] - Phase2EncryptionAlgorithms: Optional[Phase2EncryptionAlgorithmsRequestList] - Phase1IntegrityAlgorithms: Optional[Phase1IntegrityAlgorithmsRequestList] - Phase2IntegrityAlgorithms: Optional[Phase2IntegrityAlgorithmsRequestList] - Phase1DHGroupNumbers: Optional[Phase1DHGroupNumbersRequestList] - Phase2DHGroupNumbers: Optional[Phase2DHGroupNumbersRequestList] - IKEVersions: Optional[IKEVersionsRequestList] - StartupAction: Optional[String] - LogOptions: Optional[VpnTunnelLogOptionsSpecification] - EnableTunnelLifecycleControl: Optional[Boolean] - - -VpnTunnelOptionsSpecificationsList = List[VpnTunnelOptionsSpecification] + TunnelInsideCidr: String | None + TunnelInsideIpv6Cidr: String | None + PreSharedKey: preSharedKey | None + Phase1LifetimeSeconds: Integer | None + Phase2LifetimeSeconds: Integer | None + RekeyMarginTimeSeconds: Integer | None + RekeyFuzzPercentage: Integer | None + ReplayWindowSize: Integer | None + DPDTimeoutSeconds: Integer | None + DPDTimeoutAction: String | None + Phase1EncryptionAlgorithms: Phase1EncryptionAlgorithmsRequestList | None + Phase2EncryptionAlgorithms: Phase2EncryptionAlgorithmsRequestList | None + Phase1IntegrityAlgorithms: Phase1IntegrityAlgorithmsRequestList | None + Phase2IntegrityAlgorithms: Phase2IntegrityAlgorithmsRequestList | None + Phase1DHGroupNumbers: Phase1DHGroupNumbersRequestList | None + Phase2DHGroupNumbers: Phase2DHGroupNumbersRequestList | None + IKEVersions: IKEVersionsRequestList | None + StartupAction: String | None + LogOptions: VpnTunnelLogOptionsSpecification | None + EnableTunnelLifecycleControl: Boolean | None + + +VpnTunnelOptionsSpecificationsList = list[VpnTunnelOptionsSpecification] class VpnConnectionOptionsSpecification(TypedDict, total=False): - EnableAcceleration: Optional[Boolean] - TunnelInsideIpVersion: Optional[TunnelInsideIpVersion] - TunnelOptions: Optional[VpnTunnelOptionsSpecificationsList] - LocalIpv4NetworkCidr: Optional[String] - RemoteIpv4NetworkCidr: Optional[String] - LocalIpv6NetworkCidr: Optional[String] - RemoteIpv6NetworkCidr: Optional[String] - OutsideIpAddressType: Optional[String] - TransportTransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - StaticRoutesOnly: Optional[Boolean] + EnableAcceleration: Boolean | None + TunnelInsideIpVersion: TunnelInsideIpVersion | None + TunnelOptions: VpnTunnelOptionsSpecificationsList | None + LocalIpv4NetworkCidr: String | None + RemoteIpv4NetworkCidr: String | None + LocalIpv6NetworkCidr: String | None + RemoteIpv6NetworkCidr: String | None + OutsideIpAddressType: String | None + TransportTransitGatewayAttachmentId: TransitGatewayAttachmentId | None + TunnelBandwidth: VpnTunnelBandwidth | None + StaticRoutesOnly: Boolean | None class CreateVpnConnectionRequest(ServiceRequest): CustomerGatewayId: CustomerGatewayId Type: String - VpnGatewayId: Optional[VpnGatewayId] - TransitGatewayId: Optional[TransitGatewayId] - TagSpecifications: Optional[TagSpecificationList] - PreSharedKeyStorage: Optional[String] - DryRun: Optional[Boolean] - Options: Optional[VpnConnectionOptionsSpecification] + VpnGatewayId: VpnGatewayId | None + TransitGatewayId: TransitGatewayId | None + VpnConcentratorId: VpnConcentratorId | None + TagSpecifications: TagSpecificationList | None + PreSharedKeyStorage: String | None + DryRun: Boolean | None + Options: VpnConnectionOptionsSpecification | None class VgwTelemetry(TypedDict, total=False): - AcceptedRouteCount: Optional[Integer] - LastStatusChange: Optional[DateTime] - OutsideIpAddress: Optional[String] - Status: Optional[TelemetryStatus] - StatusMessage: Optional[String] - CertificateArn: Optional[String] + AcceptedRouteCount: Integer | None + LastStatusChange: DateTime | None + OutsideIpAddress: String | None + Status: TelemetryStatus | None + StatusMessage: String | None + CertificateArn: String | None -VgwTelemetryList = List[VgwTelemetry] +VgwTelemetryList = list[VgwTelemetry] class VpnStaticRoute(TypedDict, total=False): - DestinationCidrBlock: Optional[String] - Source: Optional[VpnStaticRouteSource] - State: Optional[VpnState] + DestinationCidrBlock: String | None + Source: VpnStaticRouteSource | None + State: VpnState | None -VpnStaticRouteList = List[VpnStaticRoute] +VpnStaticRouteList = list[VpnStaticRoute] class VpnTunnelLogOptions(TypedDict, total=False): - CloudWatchLogOptions: Optional[CloudWatchLogOptions] + CloudWatchLogOptions: CloudWatchLogOptions | None class IKEVersionsListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -IKEVersionsList = List[IKEVersionsListValue] +IKEVersionsList = list[IKEVersionsListValue] class Phase2DHGroupNumbersListValue(TypedDict, total=False): - Value: Optional[Integer] + Value: Integer | None -Phase2DHGroupNumbersList = List[Phase2DHGroupNumbersListValue] +Phase2DHGroupNumbersList = list[Phase2DHGroupNumbersListValue] class Phase1DHGroupNumbersListValue(TypedDict, total=False): - Value: Optional[Integer] + Value: Integer | None -Phase1DHGroupNumbersList = List[Phase1DHGroupNumbersListValue] +Phase1DHGroupNumbersList = list[Phase1DHGroupNumbersListValue] class Phase2IntegrityAlgorithmsListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase2IntegrityAlgorithmsList = List[Phase2IntegrityAlgorithmsListValue] +Phase2IntegrityAlgorithmsList = list[Phase2IntegrityAlgorithmsListValue] class Phase1IntegrityAlgorithmsListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase1IntegrityAlgorithmsList = List[Phase1IntegrityAlgorithmsListValue] +Phase1IntegrityAlgorithmsList = list[Phase1IntegrityAlgorithmsListValue] class Phase2EncryptionAlgorithmsListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase2EncryptionAlgorithmsList = List[Phase2EncryptionAlgorithmsListValue] +Phase2EncryptionAlgorithmsList = list[Phase2EncryptionAlgorithmsListValue] class Phase1EncryptionAlgorithmsListValue(TypedDict, total=False): - Value: Optional[String] + Value: String | None -Phase1EncryptionAlgorithmsList = List[Phase1EncryptionAlgorithmsListValue] +Phase1EncryptionAlgorithmsList = list[Phase1EncryptionAlgorithmsListValue] class TunnelOption(TypedDict, total=False): - OutsideIpAddress: Optional[String] - TunnelInsideCidr: Optional[String] - TunnelInsideIpv6Cidr: Optional[String] - PreSharedKey: Optional[preSharedKey] - Phase1LifetimeSeconds: Optional[Integer] - Phase2LifetimeSeconds: Optional[Integer] - RekeyMarginTimeSeconds: Optional[Integer] - RekeyFuzzPercentage: Optional[Integer] - ReplayWindowSize: Optional[Integer] - DpdTimeoutSeconds: Optional[Integer] - DpdTimeoutAction: Optional[String] - Phase1EncryptionAlgorithms: Optional[Phase1EncryptionAlgorithmsList] - Phase2EncryptionAlgorithms: Optional[Phase2EncryptionAlgorithmsList] - Phase1IntegrityAlgorithms: Optional[Phase1IntegrityAlgorithmsList] - Phase2IntegrityAlgorithms: Optional[Phase2IntegrityAlgorithmsList] - Phase1DHGroupNumbers: Optional[Phase1DHGroupNumbersList] - Phase2DHGroupNumbers: Optional[Phase2DHGroupNumbersList] - IkeVersions: Optional[IKEVersionsList] - StartupAction: Optional[String] - LogOptions: Optional[VpnTunnelLogOptions] - EnableTunnelLifecycleControl: Optional[Boolean] - - -TunnelOptionsList = List[TunnelOption] + OutsideIpAddress: String | None + TunnelInsideCidr: String | None + TunnelInsideIpv6Cidr: String | None + PreSharedKey: preSharedKey | None + Phase1LifetimeSeconds: Integer | None + Phase2LifetimeSeconds: Integer | None + RekeyMarginTimeSeconds: Integer | None + RekeyFuzzPercentage: Integer | None + ReplayWindowSize: Integer | None + DpdTimeoutSeconds: Integer | None + DpdTimeoutAction: String | None + Phase1EncryptionAlgorithms: Phase1EncryptionAlgorithmsList | None + Phase2EncryptionAlgorithms: Phase2EncryptionAlgorithmsList | None + Phase1IntegrityAlgorithms: Phase1IntegrityAlgorithmsList | None + Phase2IntegrityAlgorithms: Phase2IntegrityAlgorithmsList | None + Phase1DHGroupNumbers: Phase1DHGroupNumbersList | None + Phase2DHGroupNumbers: Phase2DHGroupNumbersList | None + IkeVersions: IKEVersionsList | None + StartupAction: String | None + LogOptions: VpnTunnelLogOptions | None + EnableTunnelLifecycleControl: Boolean | None + + +TunnelOptionsList = list[TunnelOption] class VpnConnectionOptions(TypedDict, total=False): - EnableAcceleration: Optional[Boolean] - StaticRoutesOnly: Optional[Boolean] - LocalIpv4NetworkCidr: Optional[String] - RemoteIpv4NetworkCidr: Optional[String] - LocalIpv6NetworkCidr: Optional[String] - RemoteIpv6NetworkCidr: Optional[String] - OutsideIpAddressType: Optional[String] - TransportTransitGatewayAttachmentId: Optional[String] - TunnelInsideIpVersion: Optional[TunnelInsideIpVersion] - TunnelOptions: Optional[TunnelOptionsList] + EnableAcceleration: Boolean | None + StaticRoutesOnly: Boolean | None + LocalIpv4NetworkCidr: String | None + RemoteIpv4NetworkCidr: String | None + LocalIpv6NetworkCidr: String | None + RemoteIpv6NetworkCidr: String | None + OutsideIpAddressType: String | None + TransportTransitGatewayAttachmentId: String | None + TunnelInsideIpVersion: TunnelInsideIpVersion | None + TunnelOptions: TunnelOptionsList | None + TunnelBandwidth: VpnTunnelBandwidth | None class VpnConnection(TypedDict, total=False): - Category: Optional[String] - TransitGatewayId: Optional[String] - CoreNetworkArn: Optional[String] - CoreNetworkAttachmentArn: Optional[String] - GatewayAssociationState: Optional[GatewayAssociationState] - Options: Optional[VpnConnectionOptions] - Routes: Optional[VpnStaticRouteList] - Tags: Optional[TagList] - VgwTelemetry: Optional[VgwTelemetryList] - PreSharedKeyArn: Optional[String] - VpnConnectionId: Optional[String] - State: Optional[VpnState] - CustomerGatewayConfiguration: Optional[customerGatewayConfiguration] - Type: Optional[GatewayType] - CustomerGatewayId: Optional[String] - VpnGatewayId: Optional[String] + Category: String | None + TransitGatewayId: String | None + VpnConcentratorId: String | None + CoreNetworkArn: String | None + CoreNetworkAttachmentArn: String | None + GatewayAssociationState: GatewayAssociationState | None + Options: VpnConnectionOptions | None + Routes: VpnStaticRouteList | None + Tags: TagList | None + VgwTelemetry: VgwTelemetryList | None + PreSharedKeyArn: String | None + VpnConnectionId: String | None + State: VpnState | None + CustomerGatewayConfiguration: customerGatewayConfiguration | None + Type: GatewayType | None + CustomerGatewayId: String | None + VpnGatewayId: String | None class CreateVpnConnectionResult(TypedDict, total=False): - VpnConnection: Optional[VpnConnection] + VpnConnection: VpnConnection | None class CreateVpnConnectionRouteRequest(ServiceRequest): @@ -10484,862 +11942,970 @@ class CreateVpnConnectionRouteRequest(ServiceRequest): class CreateVpnGatewayRequest(ServiceRequest): - AvailabilityZone: Optional[String] + AvailabilityZone: String | None Type: GatewayType - TagSpecifications: Optional[TagSpecificationList] - AmazonSideAsn: Optional[Long] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + AmazonSideAsn: Long | None + DryRun: Boolean | None -VpcAttachmentList = List[VpcAttachment] +VpcAttachmentList = list[VpcAttachment] class VpnGateway(TypedDict, total=False): - AmazonSideAsn: Optional[Long] - Tags: Optional[TagList] - VpnGatewayId: Optional[String] - State: Optional[VpnState] - Type: Optional[GatewayType] - AvailabilityZone: Optional[String] - VpcAttachments: Optional[VpcAttachmentList] + AmazonSideAsn: Long | None + Tags: TagList | None + VpnGatewayId: String | None + State: VpnState | None + Type: GatewayType | None + AvailabilityZone: String | None + VpcAttachments: VpcAttachmentList | None class CreateVpnGatewayResult(TypedDict, total=False): - VpnGateway: Optional[VpnGateway] + VpnGateway: VpnGateway | None + + +class CreationDateCondition(TypedDict, total=False): + MaximumDaysSinceCreated: MaximumDaysSinceCreatedValue | None + +class CreationDateConditionRequest(TypedDict, total=False): + MaximumDaysSinceCreated: MaximumDaysSinceCreatedValue | None -CustomerGatewayIdStringList = List[CustomerGatewayId] -CustomerGatewayList = List[CustomerGateway] + +CustomerGatewayIdStringList = list[CustomerGatewayId] +CustomerGatewayList = list[CustomerGateway] class DataQuery(TypedDict, total=False): - Id: Optional[String] - Source: Optional[String] - Destination: Optional[String] - Metric: Optional[MetricType] - Statistic: Optional[StatisticType] - Period: Optional[PeriodType] + Id: String | None + Source: String | None + Destination: String | None + Metric: MetricType | None + Statistic: StatisticType | None + Period: PeriodType | None -DataQueries = List[DataQuery] +DataQueries = list[DataQuery] class MetricPoint(TypedDict, total=False): - StartDate: Optional[MillisecondDateTime] - EndDate: Optional[MillisecondDateTime] - Value: Optional[Float] - Status: Optional[String] + StartDate: MillisecondDateTime | None + EndDate: MillisecondDateTime | None + Value: Float | None + Status: String | None -MetricPoints = List[MetricPoint] +MetricPoints = list[MetricPoint] class DataResponse(TypedDict, total=False): - Id: Optional[String] - Source: Optional[String] - Destination: Optional[String] - Metric: Optional[MetricType] - Statistic: Optional[StatisticType] - Period: Optional[PeriodType] - MetricPoints: Optional[MetricPoints] + Id: String | None + Source: String | None + Destination: String | None + Metric: MetricType | None + Statistic: StatisticType | None + Period: PeriodType | None + MetricPoints: MetricPoints | None -DataResponses = List[DataResponse] +DataResponses = list[DataResponse] class DeclarativePoliciesReport(TypedDict, total=False): - ReportId: Optional[String] - S3Bucket: Optional[String] - S3Prefix: Optional[String] - TargetId: Optional[String] - StartTime: Optional[MillisecondDateTime] - EndTime: Optional[MillisecondDateTime] - Status: Optional[ReportState] - Tags: Optional[TagList] + ReportId: String | None + S3Bucket: String | None + S3Prefix: String | None + TargetId: String | None + StartTime: MillisecondDateTime | None + EndTime: MillisecondDateTime | None + Status: ReportState | None + Tags: TagList | None + + +DeclarativePoliciesReportList = list[DeclarativePoliciesReport] + +class DeleteCapacityManagerDataExportRequest(ServiceRequest): + CapacityManagerDataExportId: CapacityManagerDataExportId + DryRun: Boolean | None -DeclarativePoliciesReportList = List[DeclarativePoliciesReport] + +class DeleteCapacityManagerDataExportResult(TypedDict, total=False): + CapacityManagerDataExportId: CapacityManagerDataExportId | None class DeleteCarrierGatewayRequest(ServiceRequest): CarrierGatewayId: CarrierGatewayId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteCarrierGatewayResult(TypedDict, total=False): - CarrierGateway: Optional[CarrierGateway] + CarrierGateway: CarrierGateway | None class DeleteClientVpnEndpointRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteClientVpnEndpointResult(TypedDict, total=False): - Status: Optional[ClientVpnEndpointStatus] + Status: ClientVpnEndpointStatus | None class DeleteClientVpnRouteRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - TargetVpcSubnetId: Optional[SubnetId] + TargetVpcSubnetId: SubnetId | None DestinationCidrBlock: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteClientVpnRouteResult(TypedDict, total=False): - Status: Optional[ClientVpnRouteStatus] + Status: ClientVpnRouteStatus | None class DeleteCoipCidrRequest(ServiceRequest): Cidr: String CoipPoolId: Ipv4PoolCoipId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteCoipCidrResult(TypedDict, total=False): - CoipCidr: Optional[CoipCidr] + CoipCidr: CoipCidr | None class DeleteCoipPoolRequest(ServiceRequest): CoipPoolId: Ipv4PoolCoipId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteCoipPoolResult(TypedDict, total=False): - CoipPool: Optional[CoipPool] + CoipPool: CoipPool | None class DeleteCustomerGatewayRequest(ServiceRequest): CustomerGatewayId: CustomerGatewayId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteDhcpOptionsRequest(ServiceRequest): DhcpOptionsId: DhcpOptionsId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteEgressOnlyInternetGatewayRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None EgressOnlyInternetGatewayId: EgressOnlyInternetGatewayId class DeleteEgressOnlyInternetGatewayResult(TypedDict, total=False): - ReturnCode: Optional[Boolean] + ReturnCode: Boolean | None class DeleteFleetError(TypedDict, total=False): - Code: Optional[DeleteFleetErrorCode] - Message: Optional[String] + Code: DeleteFleetErrorCode | None + Message: String | None class DeleteFleetErrorItem(TypedDict, total=False): - Error: Optional[DeleteFleetError] - FleetId: Optional[FleetId] + Error: DeleteFleetError | None + FleetId: FleetId | None -DeleteFleetErrorSet = List[DeleteFleetErrorItem] +DeleteFleetErrorSet = list[DeleteFleetErrorItem] class DeleteFleetSuccessItem(TypedDict, total=False): - CurrentFleetState: Optional[FleetStateCode] - PreviousFleetState: Optional[FleetStateCode] - FleetId: Optional[FleetId] + CurrentFleetState: FleetStateCode | None + PreviousFleetState: FleetStateCode | None + FleetId: FleetId | None -DeleteFleetSuccessSet = List[DeleteFleetSuccessItem] -FleetIdSet = List[FleetId] +DeleteFleetSuccessSet = list[DeleteFleetSuccessItem] +FleetIdSet = list[FleetId] class DeleteFleetsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None FleetIds: FleetIdSet TerminateInstances: Boolean class DeleteFleetsResult(TypedDict, total=False): - SuccessfulFleetDeletions: Optional[DeleteFleetSuccessSet] - UnsuccessfulFleetDeletions: Optional[DeleteFleetErrorSet] + SuccessfulFleetDeletions: DeleteFleetSuccessSet | None + UnsuccessfulFleetDeletions: DeleteFleetErrorSet | None -FlowLogIdList = List[VpcFlowLogId] +FlowLogIdList = list[VpcFlowLogId] class DeleteFlowLogsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None FlowLogIds: FlowLogIdList class DeleteFlowLogsResult(TypedDict, total=False): - Unsuccessful: Optional[UnsuccessfulItemSet] + Unsuccessful: UnsuccessfulItemSet | None class DeleteFpgaImageRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None FpgaImageId: FpgaImageId class DeleteFpgaImageResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None + + +class DeleteImageUsageReportRequest(ServiceRequest): + ReportId: ImageUsageReportId + DryRun: Boolean | None + + +class DeleteImageUsageReportResult(TypedDict, total=False): + Return: Boolean | None class DeleteInstanceConnectEndpointRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceConnectEndpointId: InstanceConnectEndpointId class DeleteInstanceConnectEndpointResult(TypedDict, total=False): - InstanceConnectEndpoint: Optional[Ec2InstanceConnectEndpoint] + InstanceConnectEndpoint: Ec2InstanceConnectEndpoint | None class DeleteInstanceEventWindowRequest(ServiceRequest): - DryRun: Optional[Boolean] - ForceDelete: Optional[Boolean] + DryRun: Boolean | None + ForceDelete: Boolean | None InstanceEventWindowId: InstanceEventWindowId class InstanceEventWindowStateChange(TypedDict, total=False): - InstanceEventWindowId: Optional[InstanceEventWindowId] - State: Optional[InstanceEventWindowState] + InstanceEventWindowId: InstanceEventWindowId | None + State: InstanceEventWindowState | None class DeleteInstanceEventWindowResult(TypedDict, total=False): - InstanceEventWindowState: Optional[InstanceEventWindowStateChange] + InstanceEventWindowState: InstanceEventWindowStateChange | None class DeleteInternetGatewayRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InternetGatewayId: InternetGatewayId class DeleteIpamExternalResourceVerificationTokenRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamExternalResourceVerificationTokenId: IpamExternalResourceVerificationTokenId class DeleteIpamExternalResourceVerificationTokenResult(TypedDict, total=False): - IpamExternalResourceVerificationToken: Optional[IpamExternalResourceVerificationToken] + IpamExternalResourceVerificationToken: IpamExternalResourceVerificationToken | None + + +class DeleteIpamPolicyRequest(ServiceRequest): + DryRun: Boolean | None + IpamPolicyId: IpamPolicyId + + +class DeleteIpamPolicyResult(TypedDict, total=False): + IpamPolicy: IpamPolicy | None class DeleteIpamPoolRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId - Cascade: Optional[Boolean] + Cascade: Boolean | None class DeleteIpamPoolResult(TypedDict, total=False): - IpamPool: Optional[IpamPool] + IpamPool: IpamPool | None + + +class DeleteIpamPrefixListResolverRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverId: IpamPrefixListResolverId + + +class DeleteIpamPrefixListResolverResult(TypedDict, total=False): + IpamPrefixListResolver: IpamPrefixListResolver | None + + +class DeleteIpamPrefixListResolverTargetRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverTargetId: IpamPrefixListResolverTargetId + + +class DeleteIpamPrefixListResolverTargetResult(TypedDict, total=False): + IpamPrefixListResolverTarget: IpamPrefixListResolverTarget | None class DeleteIpamRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamId: IpamId - Cascade: Optional[Boolean] + Cascade: Boolean | None class DeleteIpamResourceDiscoveryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamResourceDiscoveryId: IpamResourceDiscoveryId class DeleteIpamResourceDiscoveryResult(TypedDict, total=False): - IpamResourceDiscovery: Optional[IpamResourceDiscovery] + IpamResourceDiscovery: IpamResourceDiscovery | None class DeleteIpamResult(TypedDict, total=False): - Ipam: Optional[Ipam] + Ipam: Ipam | None class DeleteIpamScopeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamScopeId: IpamScopeId class DeleteIpamScopeResult(TypedDict, total=False): - IpamScope: Optional[IpamScope] + IpamScope: IpamScope | None class DeleteKeyPairRequest(ServiceRequest): - KeyName: Optional[KeyPairNameWithResolver] - KeyPairId: Optional[KeyPairId] - DryRun: Optional[Boolean] + KeyName: KeyPairNameWithResolver | None + KeyPairId: KeyPairId | None + DryRun: Boolean | None class DeleteKeyPairResult(TypedDict, total=False): - Return: Optional[Boolean] - KeyPairId: Optional[String] + Return: Boolean | None + KeyPairId: String | None class DeleteLaunchTemplateRequest(ServiceRequest): - DryRun: Optional[Boolean] - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[LaunchTemplateName] + DryRun: Boolean | None + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: LaunchTemplateName | None class DeleteLaunchTemplateResult(TypedDict, total=False): - LaunchTemplate: Optional[LaunchTemplate] + LaunchTemplate: LaunchTemplate | None -VersionStringList = List[String] +VersionStringList = list[String] class DeleteLaunchTemplateVersionsRequest(ServiceRequest): - DryRun: Optional[Boolean] - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[LaunchTemplateName] + DryRun: Boolean | None + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: LaunchTemplateName | None Versions: VersionStringList class ResponseError(TypedDict, total=False): - Code: Optional[LaunchTemplateErrorCode] - Message: Optional[String] + Code: LaunchTemplateErrorCode | None + Message: String | None class DeleteLaunchTemplateVersionsResponseErrorItem(TypedDict, total=False): - LaunchTemplateId: Optional[String] - LaunchTemplateName: Optional[String] - VersionNumber: Optional[Long] - ResponseError: Optional[ResponseError] + LaunchTemplateId: String | None + LaunchTemplateName: String | None + VersionNumber: Long | None + ResponseError: ResponseError | None -DeleteLaunchTemplateVersionsResponseErrorSet = List[DeleteLaunchTemplateVersionsResponseErrorItem] +DeleteLaunchTemplateVersionsResponseErrorSet = list[DeleteLaunchTemplateVersionsResponseErrorItem] class DeleteLaunchTemplateVersionsResponseSuccessItem(TypedDict, total=False): - LaunchTemplateId: Optional[String] - LaunchTemplateName: Optional[String] - VersionNumber: Optional[Long] + LaunchTemplateId: String | None + LaunchTemplateName: String | None + VersionNumber: Long | None -DeleteLaunchTemplateVersionsResponseSuccessSet = List[ +DeleteLaunchTemplateVersionsResponseSuccessSet = list[ DeleteLaunchTemplateVersionsResponseSuccessItem ] class DeleteLaunchTemplateVersionsResult(TypedDict, total=False): - SuccessfullyDeletedLaunchTemplateVersions: Optional[ - DeleteLaunchTemplateVersionsResponseSuccessSet - ] - UnsuccessfullyDeletedLaunchTemplateVersions: Optional[ - DeleteLaunchTemplateVersionsResponseErrorSet - ] + SuccessfullyDeletedLaunchTemplateVersions: DeleteLaunchTemplateVersionsResponseSuccessSet | None + UnsuccessfullyDeletedLaunchTemplateVersions: DeleteLaunchTemplateVersionsResponseErrorSet | None class DeleteLocalGatewayRouteRequest(ServiceRequest): - DestinationCidrBlock: Optional[String] + DestinationCidrBlock: String | None LocalGatewayRouteTableId: LocalGatewayRoutetableId - DryRun: Optional[Boolean] - DestinationPrefixListId: Optional[PrefixListResourceId] + DryRun: Boolean | None + DestinationPrefixListId: PrefixListResourceId | None class DeleteLocalGatewayRouteResult(TypedDict, total=False): - Route: Optional[LocalGatewayRoute] + Route: LocalGatewayRoute | None class DeleteLocalGatewayRouteTableRequest(ServiceRequest): LocalGatewayRouteTableId: LocalGatewayRoutetableId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteLocalGatewayRouteTableResult(TypedDict, total=False): - LocalGatewayRouteTable: Optional[LocalGatewayRouteTable] + LocalGatewayRouteTable: LocalGatewayRouteTable | None class DeleteLocalGatewayRouteTableVirtualInterfaceGroupAssociationRequest(ServiceRequest): LocalGatewayRouteTableVirtualInterfaceGroupAssociationId: ( LocalGatewayRouteTableVirtualInterfaceGroupAssociationId ) - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteLocalGatewayRouteTableVirtualInterfaceGroupAssociationResult(TypedDict, total=False): - LocalGatewayRouteTableVirtualInterfaceGroupAssociation: Optional[ - LocalGatewayRouteTableVirtualInterfaceGroupAssociation - ] + LocalGatewayRouteTableVirtualInterfaceGroupAssociation: ( + LocalGatewayRouteTableVirtualInterfaceGroupAssociation | None + ) class DeleteLocalGatewayRouteTableVpcAssociationRequest(ServiceRequest): LocalGatewayRouteTableVpcAssociationId: LocalGatewayRouteTableVpcAssociationId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteLocalGatewayRouteTableVpcAssociationResult(TypedDict, total=False): - LocalGatewayRouteTableVpcAssociation: Optional[LocalGatewayRouteTableVpcAssociation] + LocalGatewayRouteTableVpcAssociation: LocalGatewayRouteTableVpcAssociation | None class DeleteLocalGatewayVirtualInterfaceGroupRequest(ServiceRequest): LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteLocalGatewayVirtualInterfaceGroupResult(TypedDict, total=False): - LocalGatewayVirtualInterfaceGroup: Optional[LocalGatewayVirtualInterfaceGroup] + LocalGatewayVirtualInterfaceGroup: LocalGatewayVirtualInterfaceGroup | None class DeleteLocalGatewayVirtualInterfaceRequest(ServiceRequest): LocalGatewayVirtualInterfaceId: LocalGatewayVirtualInterfaceId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteLocalGatewayVirtualInterfaceResult(TypedDict, total=False): - LocalGatewayVirtualInterface: Optional[LocalGatewayVirtualInterface] + LocalGatewayVirtualInterface: LocalGatewayVirtualInterface | None class DeleteManagedPrefixListRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PrefixListId: PrefixListResourceId class DeleteManagedPrefixListResult(TypedDict, total=False): - PrefixList: Optional[ManagedPrefixList] + PrefixList: ManagedPrefixList | None class DeleteNatGatewayRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NatGatewayId: NatGatewayId class DeleteNatGatewayResult(TypedDict, total=False): - NatGatewayId: Optional[String] + NatGatewayId: String | None class DeleteNetworkAclEntryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkAclId: NetworkAclId RuleNumber: Integer Egress: Boolean class DeleteNetworkAclRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkAclId: NetworkAclId class DeleteNetworkInsightsAccessScopeAnalysisRequest(ServiceRequest): NetworkInsightsAccessScopeAnalysisId: NetworkInsightsAccessScopeAnalysisId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteNetworkInsightsAccessScopeAnalysisResult(TypedDict, total=False): - NetworkInsightsAccessScopeAnalysisId: Optional[NetworkInsightsAccessScopeAnalysisId] + NetworkInsightsAccessScopeAnalysisId: NetworkInsightsAccessScopeAnalysisId | None class DeleteNetworkInsightsAccessScopeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId class DeleteNetworkInsightsAccessScopeResult(TypedDict, total=False): - NetworkInsightsAccessScopeId: Optional[NetworkInsightsAccessScopeId] + NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId | None class DeleteNetworkInsightsAnalysisRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkInsightsAnalysisId: NetworkInsightsAnalysisId class DeleteNetworkInsightsAnalysisResult(TypedDict, total=False): - NetworkInsightsAnalysisId: Optional[NetworkInsightsAnalysisId] + NetworkInsightsAnalysisId: NetworkInsightsAnalysisId | None class DeleteNetworkInsightsPathRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkInsightsPathId: NetworkInsightsPathId class DeleteNetworkInsightsPathResult(TypedDict, total=False): - NetworkInsightsPathId: Optional[NetworkInsightsPathId] + NetworkInsightsPathId: NetworkInsightsPathId | None class DeleteNetworkInterfacePermissionRequest(ServiceRequest): NetworkInterfacePermissionId: NetworkInterfacePermissionId - Force: Optional[Boolean] - DryRun: Optional[Boolean] + Force: Boolean | None + DryRun: Boolean | None class DeleteNetworkInterfacePermissionResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DeleteNetworkInterfaceRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkInterfaceId: NetworkInterfaceId class DeletePlacementGroupRequest(ServiceRequest): - DryRun: Optional[Boolean] - GroupName: PlacementGroupName + DryRun: Boolean | None + GroupName: PlacementGroupNameWithResolver class DeletePublicIpv4PoolRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PoolId: Ipv4PoolEc2Id - NetworkBorderGroup: Optional[String] + NetworkBorderGroup: String | None class DeletePublicIpv4PoolResult(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class DeleteQueuedReservedInstancesError(TypedDict, total=False): - Code: Optional[DeleteQueuedReservedInstancesErrorCode] - Message: Optional[String] + Code: DeleteQueuedReservedInstancesErrorCode | None + Message: String | None -DeleteQueuedReservedInstancesIdList = List[ReservationId] +DeleteQueuedReservedInstancesIdList = list[ReservationId] class DeleteQueuedReservedInstancesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ReservedInstancesIds: DeleteQueuedReservedInstancesIdList class FailedQueuedPurchaseDeletion(TypedDict, total=False): - Error: Optional[DeleteQueuedReservedInstancesError] - ReservedInstancesId: Optional[String] + Error: DeleteQueuedReservedInstancesError | None + ReservedInstancesId: String | None -FailedQueuedPurchaseDeletionSet = List[FailedQueuedPurchaseDeletion] +FailedQueuedPurchaseDeletionSet = list[FailedQueuedPurchaseDeletion] class SuccessfulQueuedPurchaseDeletion(TypedDict, total=False): - ReservedInstancesId: Optional[String] + ReservedInstancesId: String | None -SuccessfulQueuedPurchaseDeletionSet = List[SuccessfulQueuedPurchaseDeletion] +SuccessfulQueuedPurchaseDeletionSet = list[SuccessfulQueuedPurchaseDeletion] class DeleteQueuedReservedInstancesResult(TypedDict, total=False): - SuccessfulQueuedPurchaseDeletions: Optional[SuccessfulQueuedPurchaseDeletionSet] - FailedQueuedPurchaseDeletions: Optional[FailedQueuedPurchaseDeletionSet] + SuccessfulQueuedPurchaseDeletions: SuccessfulQueuedPurchaseDeletionSet | None + FailedQueuedPurchaseDeletions: FailedQueuedPurchaseDeletionSet | None class DeleteRouteRequest(ServiceRequest): - DestinationPrefixListId: Optional[PrefixListResourceId] - DryRun: Optional[Boolean] + DestinationPrefixListId: PrefixListResourceId | None + DryRun: Boolean | None RouteTableId: RouteTableId - DestinationCidrBlock: Optional[String] - DestinationIpv6CidrBlock: Optional[String] + DestinationCidrBlock: String | None + DestinationIpv6CidrBlock: String | None class DeleteRouteServerEndpointRequest(ServiceRequest): RouteServerEndpointId: RouteServerEndpointId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteRouteServerEndpointResult(TypedDict, total=False): - RouteServerEndpoint: Optional[RouteServerEndpoint] + RouteServerEndpoint: RouteServerEndpoint | None class DeleteRouteServerPeerRequest(ServiceRequest): RouteServerPeerId: RouteServerPeerId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteRouteServerPeerResult(TypedDict, total=False): - RouteServerPeer: Optional[RouteServerPeer] + RouteServerPeer: RouteServerPeer | None class DeleteRouteServerRequest(ServiceRequest): RouteServerId: RouteServerId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteRouteServerResult(TypedDict, total=False): - RouteServer: Optional[RouteServer] + RouteServer: RouteServer | None class DeleteRouteTableRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None RouteTableId: RouteTableId +class DeleteSecondaryNetworkRequest(ServiceRequest): + ClientToken: String | None + DryRun: Boolean | None + SecondaryNetworkId: SecondaryNetworkId + + +class DeleteSecondaryNetworkResult(TypedDict, total=False): + SecondaryNetwork: SecondaryNetwork | None + ClientToken: String | None + + +class DeleteSecondarySubnetRequest(ServiceRequest): + ClientToken: String | None + DryRun: Boolean | None + SecondarySubnetId: SecondarySubnetId + + +class DeleteSecondarySubnetResult(TypedDict, total=False): + SecondarySubnet: SecondarySubnet | None + ClientToken: String | None + + class DeleteSecurityGroupRequest(ServiceRequest): - GroupId: Optional[SecurityGroupId] - GroupName: Optional[SecurityGroupName] - DryRun: Optional[Boolean] + GroupId: SecurityGroupId | None + GroupName: SecurityGroupName | None + DryRun: Boolean | None class DeleteSecurityGroupResult(TypedDict, total=False): - Return: Optional[Boolean] - GroupId: Optional[SecurityGroupId] + Return: Boolean | None + GroupId: SecurityGroupId | None class DeleteSnapshotRequest(ServiceRequest): SnapshotId: SnapshotId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteSnapshotReturnCode(TypedDict, total=False): - SnapshotId: Optional[SnapshotId] - ReturnCode: Optional[SnapshotReturnCodes] + SnapshotId: SnapshotId | None + ReturnCode: SnapshotReturnCodes | None -DeleteSnapshotResultSet = List[DeleteSnapshotReturnCode] +DeleteSnapshotResultSet = list[DeleteSnapshotReturnCode] class DeleteSpotDatafeedSubscriptionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteSubnetCidrReservationRequest(ServiceRequest): SubnetCidrReservationId: SubnetCidrReservationId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteSubnetCidrReservationResult(TypedDict, total=False): - DeletedSubnetCidrReservation: Optional[SubnetCidrReservation] + DeletedSubnetCidrReservation: SubnetCidrReservation | None class DeleteSubnetRequest(ServiceRequest): SubnetId: SubnetId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTagsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Resources: ResourceIdList - Tags: Optional[TagList] + Tags: TagList | None class DeleteTrafficMirrorFilterRequest(ServiceRequest): TrafficMirrorFilterId: TrafficMirrorFilterId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTrafficMirrorFilterResult(TypedDict, total=False): - TrafficMirrorFilterId: Optional[String] + TrafficMirrorFilterId: String | None class DeleteTrafficMirrorFilterRuleRequest(ServiceRequest): TrafficMirrorFilterRuleId: TrafficMirrorFilterRuleIdWithResolver - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTrafficMirrorFilterRuleResult(TypedDict, total=False): - TrafficMirrorFilterRuleId: Optional[String] + TrafficMirrorFilterRuleId: String | None class DeleteTrafficMirrorSessionRequest(ServiceRequest): TrafficMirrorSessionId: TrafficMirrorSessionId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTrafficMirrorSessionResult(TypedDict, total=False): - TrafficMirrorSessionId: Optional[String] + TrafficMirrorSessionId: String | None class DeleteTrafficMirrorTargetRequest(ServiceRequest): TrafficMirrorTargetId: TrafficMirrorTargetId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTrafficMirrorTargetResult(TypedDict, total=False): - TrafficMirrorTargetId: Optional[String] + TrafficMirrorTargetId: String | None class DeleteTransitGatewayConnectPeerRequest(ServiceRequest): TransitGatewayConnectPeerId: TransitGatewayConnectPeerId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayConnectPeerResult(TypedDict, total=False): - TransitGatewayConnectPeer: Optional[TransitGatewayConnectPeer] + TransitGatewayConnectPeer: TransitGatewayConnectPeer | None class DeleteTransitGatewayConnectRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayConnectResult(TypedDict, total=False): - TransitGatewayConnect: Optional[TransitGatewayConnect] + TransitGatewayConnect: TransitGatewayConnect | None + + +class DeleteTransitGatewayMeteringPolicyEntryRequest(ServiceRequest): + TransitGatewayMeteringPolicyId: TransitGatewayMeteringPolicyId + PolicyRuleNumber: Integer + DryRun: Boolean | None + + +class DeleteTransitGatewayMeteringPolicyEntryResult(TypedDict, total=False): + TransitGatewayMeteringPolicyEntry: TransitGatewayMeteringPolicyEntry | None + + +class DeleteTransitGatewayMeteringPolicyRequest(ServiceRequest): + TransitGatewayMeteringPolicyId: TransitGatewayMeteringPolicyId + DryRun: Boolean | None + + +class DeleteTransitGatewayMeteringPolicyResult(TypedDict, total=False): + TransitGatewayMeteringPolicy: TransitGatewayMeteringPolicy | None class DeleteTransitGatewayMulticastDomainRequest(ServiceRequest): TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayMulticastDomainResult(TypedDict, total=False): - TransitGatewayMulticastDomain: Optional[TransitGatewayMulticastDomain] + TransitGatewayMulticastDomain: TransitGatewayMulticastDomain | None class DeleteTransitGatewayPeeringAttachmentRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayPeeringAttachmentResult(TypedDict, total=False): - TransitGatewayPeeringAttachment: Optional[TransitGatewayPeeringAttachment] + TransitGatewayPeeringAttachment: TransitGatewayPeeringAttachment | None class DeleteTransitGatewayPolicyTableRequest(ServiceRequest): TransitGatewayPolicyTableId: TransitGatewayPolicyTableId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayPolicyTableResult(TypedDict, total=False): - TransitGatewayPolicyTable: Optional[TransitGatewayPolicyTable] + TransitGatewayPolicyTable: TransitGatewayPolicyTable | None class DeleteTransitGatewayPrefixListReferenceRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId PrefixListId: PrefixListResourceId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayPrefixListReferenceResult(TypedDict, total=False): - TransitGatewayPrefixListReference: Optional[TransitGatewayPrefixListReference] + TransitGatewayPrefixListReference: TransitGatewayPrefixListReference | None class DeleteTransitGatewayRequest(ServiceRequest): TransitGatewayId: TransitGatewayId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayResult(TypedDict, total=False): - TransitGateway: Optional[TransitGateway] + TransitGateway: TransitGateway | None class DeleteTransitGatewayRouteRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId DestinationCidrBlock: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayRouteResult(TypedDict, total=False): - Route: Optional[TransitGatewayRoute] + Route: TransitGatewayRoute | None class DeleteTransitGatewayRouteTableAnnouncementRequest(ServiceRequest): TransitGatewayRouteTableAnnouncementId: TransitGatewayRouteTableAnnouncementId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayRouteTableAnnouncementResult(TypedDict, total=False): - TransitGatewayRouteTableAnnouncement: Optional[TransitGatewayRouteTableAnnouncement] + TransitGatewayRouteTableAnnouncement: TransitGatewayRouteTableAnnouncement | None class DeleteTransitGatewayRouteTableRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayRouteTableResult(TypedDict, total=False): - TransitGatewayRouteTable: Optional[TransitGatewayRouteTable] + TransitGatewayRouteTable: TransitGatewayRouteTable | None class DeleteTransitGatewayVpcAttachmentRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteTransitGatewayVpcAttachmentResult(TypedDict, total=False): - TransitGatewayVpcAttachment: Optional[TransitGatewayVpcAttachment] + TransitGatewayVpcAttachment: TransitGatewayVpcAttachment | None class DeleteVerifiedAccessEndpointRequest(ServiceRequest): VerifiedAccessEndpointId: VerifiedAccessEndpointId - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None class DeleteVerifiedAccessEndpointResult(TypedDict, total=False): - VerifiedAccessEndpoint: Optional[VerifiedAccessEndpoint] + VerifiedAccessEndpoint: VerifiedAccessEndpoint | None class DeleteVerifiedAccessGroupRequest(ServiceRequest): VerifiedAccessGroupId: VerifiedAccessGroupId - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None class DeleteVerifiedAccessGroupResult(TypedDict, total=False): - VerifiedAccessGroup: Optional[VerifiedAccessGroup] + VerifiedAccessGroup: VerifiedAccessGroup | None class DeleteVerifiedAccessInstanceRequest(ServiceRequest): VerifiedAccessInstanceId: VerifiedAccessInstanceId - DryRun: Optional[Boolean] - ClientToken: Optional[String] + DryRun: Boolean | None + ClientToken: String | None class DeleteVerifiedAccessInstanceResult(TypedDict, total=False): - VerifiedAccessInstance: Optional[VerifiedAccessInstance] + VerifiedAccessInstance: VerifiedAccessInstance | None class DeleteVerifiedAccessTrustProviderRequest(ServiceRequest): VerifiedAccessTrustProviderId: VerifiedAccessTrustProviderId - DryRun: Optional[Boolean] - ClientToken: Optional[String] + DryRun: Boolean | None + ClientToken: String | None class DeleteVerifiedAccessTrustProviderResult(TypedDict, total=False): - VerifiedAccessTrustProvider: Optional[VerifiedAccessTrustProvider] + VerifiedAccessTrustProvider: VerifiedAccessTrustProvider | None class DeleteVolumeRequest(ServiceRequest): VolumeId: VolumeId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteVpcBlockPublicAccessExclusionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ExclusionId: VpcBlockPublicAccessExclusionId class DeleteVpcBlockPublicAccessExclusionResult(TypedDict, total=False): - VpcBlockPublicAccessExclusion: Optional[VpcBlockPublicAccessExclusion] + VpcBlockPublicAccessExclusion: VpcBlockPublicAccessExclusion | None + + +class DeleteVpcEncryptionControlRequest(ServiceRequest): + DryRun: Boolean | None + VpcEncryptionControlId: VpcEncryptionControlId + + +class DeleteVpcEncryptionControlResult(TypedDict, total=False): + VpcEncryptionControl: VpcEncryptionControl | None class DeleteVpcEndpointConnectionNotificationsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ConnectionNotificationIds: ConnectionNotificationIdsList class DeleteVpcEndpointConnectionNotificationsResult(TypedDict, total=False): - Unsuccessful: Optional[UnsuccessfulItemSet] + Unsuccessful: UnsuccessfulItemSet | None -VpcEndpointServiceIdList = List[VpcEndpointServiceId] +VpcEndpointServiceIdList = list[VpcEndpointServiceId] class DeleteVpcEndpointServiceConfigurationsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceIds: VpcEndpointServiceIdList class DeleteVpcEndpointServiceConfigurationsResult(TypedDict, total=False): - Unsuccessful: Optional[UnsuccessfulItemSet] + Unsuccessful: UnsuccessfulItemSet | None class DeleteVpcEndpointsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VpcEndpointIds: VpcEndpointIdList class DeleteVpcEndpointsResult(TypedDict, total=False): - Unsuccessful: Optional[UnsuccessfulItemSet] + Unsuccessful: UnsuccessfulItemSet | None class DeleteVpcPeeringConnectionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VpcPeeringConnectionId: VpcPeeringConnectionId class DeleteVpcPeeringConnectionResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DeleteVpcRequest(ServiceRequest): VpcId: VpcId - DryRun: Optional[Boolean] + DryRun: Boolean | None + + +class DeleteVpnConcentratorRequest(ServiceRequest): + VpnConcentratorId: VpnConcentratorId + DryRun: Boolean | None + + +class DeleteVpnConcentratorResult(TypedDict, total=False): + Return: Boolean | None class DeleteVpnConnectionRequest(ServiceRequest): VpnConnectionId: VpnConnectionId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeleteVpnConnectionRouteRequest(ServiceRequest): @@ -11349,3207 +12915,3525 @@ class DeleteVpnConnectionRouteRequest(ServiceRequest): class DeleteVpnGatewayRequest(ServiceRequest): VpnGatewayId: VpnGatewayId - DryRun: Optional[Boolean] + DryRun: Boolean | None + + +class DeprecationTimeCondition(TypedDict, total=False): + MaximumDaysSinceDeprecated: MaximumDaysSinceDeprecatedValue | None + + +class DeprecationTimeConditionRequest(TypedDict, total=False): + MaximumDaysSinceDeprecated: MaximumDaysSinceDeprecatedValue | None class DeprovisionByoipCidrRequest(ServiceRequest): Cidr: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class DeprovisionByoipCidrResult(TypedDict, total=False): - ByoipCidr: Optional[ByoipCidr] + ByoipCidr: ByoipCidr | None class DeprovisionIpamByoasnRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamId: IpamId Asn: String class DeprovisionIpamByoasnResult(TypedDict, total=False): - Byoasn: Optional[Byoasn] + Byoasn: Byoasn | None class DeprovisionIpamPoolCidrRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId - Cidr: Optional[String] + Cidr: String | None class IpamPoolCidrFailureReason(TypedDict, total=False): - Code: Optional[IpamPoolCidrFailureCode] - Message: Optional[String] + Code: IpamPoolCidrFailureCode | None + Message: String | None class IpamPoolCidr(TypedDict, total=False): - Cidr: Optional[String] - State: Optional[IpamPoolCidrState] - FailureReason: Optional[IpamPoolCidrFailureReason] - IpamPoolCidrId: Optional[IpamPoolCidrId] - NetmaskLength: Optional[Integer] + Cidr: String | None + State: IpamPoolCidrState | None + FailureReason: IpamPoolCidrFailureReason | None + IpamPoolCidrId: IpamPoolCidrId | None + NetmaskLength: Integer | None class DeprovisionIpamPoolCidrResult(TypedDict, total=False): - IpamPoolCidr: Optional[IpamPoolCidr] + IpamPoolCidr: IpamPoolCidr | None class DeprovisionPublicIpv4PoolCidrRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PoolId: Ipv4PoolEc2Id Cidr: String -DeprovisionedAddressSet = List[String] +DeprovisionedAddressSet = list[String] class DeprovisionPublicIpv4PoolCidrResult(TypedDict, total=False): - PoolId: Optional[Ipv4PoolEc2Id] - DeprovisionedAddresses: Optional[DeprovisionedAddressSet] + PoolId: Ipv4PoolEc2Id | None + DeprovisionedAddresses: DeprovisionedAddressSet | None class DeregisterImageRequest(ServiceRequest): ImageId: ImageId - DeleteAssociatedSnapshots: Optional[Boolean] - DryRun: Optional[Boolean] + DeleteAssociatedSnapshots: Boolean | None + DryRun: Boolean | None class DeregisterImageResult(TypedDict, total=False): - Return: Optional[Boolean] - DeleteSnapshotResults: Optional[DeleteSnapshotResultSet] + Return: Boolean | None + DeleteSnapshotResults: DeleteSnapshotResultSet | None -InstanceTagKeySet = List[String] +InstanceTagKeySet = list[String] class DeregisterInstanceTagAttributeRequest(TypedDict, total=False): - IncludeAllTagsOfInstance: Optional[Boolean] - InstanceTagKeys: Optional[InstanceTagKeySet] + IncludeAllTagsOfInstance: Boolean | None + InstanceTagKeys: InstanceTagKeySet | None class DeregisterInstanceEventNotificationAttributesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceTagAttribute: DeregisterInstanceTagAttributeRequest class InstanceTagNotificationAttribute(TypedDict, total=False): - InstanceTagKeys: Optional[InstanceTagKeySet] - IncludeAllTagsOfInstance: Optional[Boolean] + InstanceTagKeys: InstanceTagKeySet | None + IncludeAllTagsOfInstance: Boolean | None class DeregisterInstanceEventNotificationAttributesResult(TypedDict, total=False): - InstanceTagAttribute: Optional[InstanceTagNotificationAttribute] + InstanceTagAttribute: InstanceTagNotificationAttribute | None -TransitGatewayNetworkInterfaceIdList = List[NetworkInterfaceId] +TransitGatewayNetworkInterfaceIdList = list[NetworkInterfaceId] class DeregisterTransitGatewayMulticastGroupMembersRequest(ServiceRequest): - TransitGatewayMulticastDomainId: Optional[TransitGatewayMulticastDomainId] - GroupIpAddress: Optional[String] - NetworkInterfaceIds: Optional[TransitGatewayNetworkInterfaceIdList] - DryRun: Optional[Boolean] + TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId | None + GroupIpAddress: String | None + NetworkInterfaceIds: TransitGatewayNetworkInterfaceIdList | None + DryRun: Boolean | None class TransitGatewayMulticastDeregisteredGroupMembers(TypedDict, total=False): - TransitGatewayMulticastDomainId: Optional[String] - DeregisteredNetworkInterfaceIds: Optional[ValueStringList] - GroupIpAddress: Optional[String] + TransitGatewayMulticastDomainId: String | None + DeregisteredNetworkInterfaceIds: ValueStringList | None + GroupIpAddress: String | None class DeregisterTransitGatewayMulticastGroupMembersResult(TypedDict, total=False): - DeregisteredMulticastGroupMembers: Optional[TransitGatewayMulticastDeregisteredGroupMembers] + DeregisteredMulticastGroupMembers: TransitGatewayMulticastDeregisteredGroupMembers | None class DeregisterTransitGatewayMulticastGroupSourcesRequest(ServiceRequest): - TransitGatewayMulticastDomainId: Optional[TransitGatewayMulticastDomainId] - GroupIpAddress: Optional[String] - NetworkInterfaceIds: Optional[TransitGatewayNetworkInterfaceIdList] - DryRun: Optional[Boolean] + TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId | None + GroupIpAddress: String | None + NetworkInterfaceIds: TransitGatewayNetworkInterfaceIdList | None + DryRun: Boolean | None class TransitGatewayMulticastDeregisteredGroupSources(TypedDict, total=False): - TransitGatewayMulticastDomainId: Optional[String] - DeregisteredNetworkInterfaceIds: Optional[ValueStringList] - GroupIpAddress: Optional[String] + TransitGatewayMulticastDomainId: String | None + DeregisteredNetworkInterfaceIds: ValueStringList | None + GroupIpAddress: String | None class DeregisterTransitGatewayMulticastGroupSourcesResult(TypedDict, total=False): - DeregisteredMulticastGroupSources: Optional[TransitGatewayMulticastDeregisteredGroupSources] + DeregisteredMulticastGroupSources: TransitGatewayMulticastDeregisteredGroupSources | None class DescribeAccountAttributesRequest(ServiceRequest): - DryRun: Optional[Boolean] - AttributeNames: Optional[AccountAttributeNameStringList] + DryRun: Boolean | None + AttributeNames: AccountAttributeNameStringList | None class DescribeAccountAttributesResult(TypedDict, total=False): - AccountAttributes: Optional[AccountAttributeList] + AccountAttributes: AccountAttributeList | None class DescribeAddressTransfersRequest(ServiceRequest): - AllocationIds: Optional[AllocationIdList] - NextToken: Optional[String] - MaxResults: Optional[DescribeAddressTransfersMaxResults] - DryRun: Optional[Boolean] + AllocationIds: AllocationIdList | None + NextToken: String | None + MaxResults: DescribeAddressTransfersMaxResults | None + DryRun: Boolean | None class DescribeAddressTransfersResult(TypedDict, total=False): - AddressTransfers: Optional[AddressTransferList] - NextToken: Optional[String] + AddressTransfers: AddressTransferList | None + NextToken: String | None class DescribeAddressesAttributeRequest(ServiceRequest): - AllocationIds: Optional[AllocationIds] - Attribute: Optional[AddressAttributeName] - NextToken: Optional[NextToken] - MaxResults: Optional[AddressMaxResults] - DryRun: Optional[Boolean] + AllocationIds: AllocationIds | None + Attribute: AddressAttributeName | None + NextToken: NextToken | None + MaxResults: AddressMaxResults | None + DryRun: Boolean | None class DescribeAddressesAttributeResult(TypedDict, total=False): - Addresses: Optional[AddressSet] - NextToken: Optional[NextToken] + Addresses: AddressSet | None + NextToken: NextToken | None class Filter(TypedDict, total=False): - Name: Optional[String] - Values: Optional[ValueStringList] + Name: String | None + Values: ValueStringList | None -FilterList = List[Filter] -PublicIpStringList = List[String] +FilterList = list[Filter] +PublicIpStringList = list[String] class DescribeAddressesRequest(ServiceRequest): - PublicIps: Optional[PublicIpStringList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - AllocationIds: Optional[AllocationIdList] + PublicIps: PublicIpStringList | None + DryRun: Boolean | None + Filters: FilterList | None + AllocationIds: AllocationIdList | None class DescribeAddressesResult(TypedDict, total=False): - Addresses: Optional[AddressList] + Addresses: AddressList | None class DescribeAggregateIdFormatRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class IdFormat(TypedDict, total=False): - Deadline: Optional[DateTime] - Resource: Optional[String] - UseLongIds: Optional[Boolean] + Deadline: DateTime | None + Resource: String | None + UseLongIds: Boolean | None -IdFormatList = List[IdFormat] +IdFormatList = list[IdFormat] class DescribeAggregateIdFormatResult(TypedDict, total=False): - UseLongIdsAggregated: Optional[Boolean] - Statuses: Optional[IdFormatList] + UseLongIdsAggregated: Boolean | None + Statuses: IdFormatList | None -ZoneIdStringList = List[String] -ZoneNameStringList = List[String] +ZoneIdStringList = list[String] +ZoneNameStringList = list[String] class DescribeAvailabilityZonesRequest(ServiceRequest): - ZoneNames: Optional[ZoneNameStringList] - ZoneIds: Optional[ZoneIdStringList] - AllAvailabilityZones: Optional[Boolean] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + ZoneNames: ZoneNameStringList | None + ZoneIds: ZoneIdStringList | None + AllAvailabilityZones: Boolean | None + DryRun: Boolean | None + Filters: FilterList | None class DescribeAvailabilityZonesResult(TypedDict, total=False): - AvailabilityZones: Optional[AvailabilityZoneList] + AvailabilityZones: AvailabilityZoneList | None class DescribeAwsNetworkPerformanceMetricSubscriptionsRequest(ServiceRequest): - MaxResults: Optional[MaxResultsParam] - NextToken: Optional[String] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + MaxResults: MaxResultsParam | None + NextToken: String | None + Filters: FilterList | None + DryRun: Boolean | None class Subscription(TypedDict, total=False): - Source: Optional[String] - Destination: Optional[String] - Metric: Optional[MetricType] - Statistic: Optional[StatisticType] - Period: Optional[PeriodType] + Source: String | None + Destination: String | None + Metric: MetricType | None + Statistic: StatisticType | None + Period: PeriodType | None -SubscriptionList = List[Subscription] +SubscriptionList = list[Subscription] class DescribeAwsNetworkPerformanceMetricSubscriptionsResult(TypedDict, total=False): - NextToken: Optional[String] - Subscriptions: Optional[SubscriptionList] + NextToken: String | None + Subscriptions: SubscriptionList | None class DescribeBundleTasksRequest(ServiceRequest): - BundleIds: Optional[BundleIdStringList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + BundleIds: BundleIdStringList | None + DryRun: Boolean | None + Filters: FilterList | None class DescribeBundleTasksResult(TypedDict, total=False): - BundleTasks: Optional[BundleTaskList] + BundleTasks: BundleTaskList | None class DescribeByoipCidrsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None MaxResults: DescribeByoipCidrsMaxResults - NextToken: Optional[NextToken] + NextToken: NextToken | None class DescribeByoipCidrsResult(TypedDict, total=False): - ByoipCidrs: Optional[ByoipCidrSet] - NextToken: Optional[String] + ByoipCidrs: ByoipCidrSet | None + NextToken: String | None class DescribeCapacityBlockExtensionHistoryRequest(ServiceRequest): - CapacityReservationIds: Optional[CapacityReservationIdSet] - NextToken: Optional[String] - MaxResults: Optional[DescribeFutureCapacityMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + CapacityReservationIds: CapacityReservationIdSet | None + NextToken: String | None + MaxResults: DescribeFutureCapacityMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None class DescribeCapacityBlockExtensionHistoryResult(TypedDict, total=False): - CapacityBlockExtensions: Optional[CapacityBlockExtensionSet] - NextToken: Optional[String] + CapacityBlockExtensions: CapacityBlockExtensionSet | None + NextToken: String | None class DescribeCapacityBlockExtensionOfferingsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None CapacityBlockExtensionDurationHours: Integer CapacityReservationId: CapacityReservationId - NextToken: Optional[String] - MaxResults: Optional[DescribeCapacityBlockExtensionOfferingsMaxResults] + NextToken: String | None + MaxResults: DescribeCapacityBlockExtensionOfferingsMaxResults | None class DescribeCapacityBlockExtensionOfferingsResult(TypedDict, total=False): - CapacityBlockExtensionOfferings: Optional[CapacityBlockExtensionOfferingSet] - NextToken: Optional[String] + CapacityBlockExtensionOfferings: CapacityBlockExtensionOfferingSet | None + NextToken: String | None class DescribeCapacityBlockOfferingsRequest(ServiceRequest): - DryRun: Optional[Boolean] - InstanceType: Optional[String] - InstanceCount: Optional[Integer] - StartDateRange: Optional[MillisecondDateTime] - EndDateRange: Optional[MillisecondDateTime] + DryRun: Boolean | None + InstanceType: String | None + InstanceCount: Integer | None + StartDateRange: MillisecondDateTime | None + EndDateRange: MillisecondDateTime | None CapacityDurationHours: Integer - NextToken: Optional[String] - MaxResults: Optional[DescribeCapacityBlockOfferingsMaxResults] - UltraserverType: Optional[String] - UltraserverCount: Optional[Integer] + NextToken: String | None + MaxResults: DescribeCapacityBlockOfferingsMaxResults | None + UltraserverType: String | None + UltraserverCount: Integer | None + AllAvailabilityZones: Boolean | None class DescribeCapacityBlockOfferingsResult(TypedDict, total=False): - CapacityBlockOfferings: Optional[CapacityBlockOfferingSet] - NextToken: Optional[String] + CapacityBlockOfferings: CapacityBlockOfferingSet | None + NextToken: String | None class DescribeCapacityBlockStatusRequest(ServiceRequest): - CapacityBlockIds: Optional[CapacityBlockIds] - NextToken: Optional[String] - MaxResults: Optional[DescribeCapacityBlockStatusMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + CapacityBlockIds: CapacityBlockIds | None + NextToken: String | None + MaxResults: DescribeCapacityBlockStatusMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None class DescribeCapacityBlockStatusResult(TypedDict, total=False): - CapacityBlockStatuses: Optional[CapacityBlockStatusSet] - NextToken: Optional[String] + CapacityBlockStatuses: CapacityBlockStatusSet | None + NextToken: String | None class DescribeCapacityBlocksRequest(ServiceRequest): - CapacityBlockIds: Optional[CapacityBlockIds] - NextToken: Optional[String] - MaxResults: Optional[DescribeCapacityBlocksMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + CapacityBlockIds: CapacityBlockIds | None + NextToken: String | None + MaxResults: DescribeCapacityBlocksMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None class DescribeCapacityBlocksResult(TypedDict, total=False): - CapacityBlocks: Optional[CapacityBlockSet] - NextToken: Optional[String] + CapacityBlocks: CapacityBlockSet | None + NextToken: String | None + + +class DescribeCapacityManagerDataExportsRequest(ServiceRequest): + CapacityManagerDataExportIds: CapacityManagerDataExportIdSet | None + MaxResults: DescribeCapacityManagerDataExportsRequestMaxResults | None + NextToken: String | None + DryRun: Boolean | None + Filters: FilterList | None + + +class DescribeCapacityManagerDataExportsResult(TypedDict, total=False): + CapacityManagerDataExports: CapacityManagerDataExportResponseSet | None + NextToken: String | None class DescribeCapacityReservationBillingRequestsRequest(ServiceRequest): - CapacityReservationIds: Optional[CapacityReservationIdSet] + CapacityReservationIds: CapacityReservationIdSet | None Role: CallerRole - NextToken: Optional[String] - MaxResults: Optional[DescribeCapacityReservationBillingRequestsRequestMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + NextToken: String | None + MaxResults: DescribeCapacityReservationBillingRequestsRequestMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None class DescribeCapacityReservationBillingRequestsResult(TypedDict, total=False): - NextToken: Optional[String] - CapacityReservationBillingRequests: Optional[CapacityReservationBillingRequestSet] + NextToken: String | None + CapacityReservationBillingRequests: CapacityReservationBillingRequestSet | None class DescribeCapacityReservationFleetsRequest(ServiceRequest): - CapacityReservationFleetIds: Optional[CapacityReservationFleetIdSet] - NextToken: Optional[String] - MaxResults: Optional[DescribeCapacityReservationFleetsMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + CapacityReservationFleetIds: CapacityReservationFleetIdSet | None + NextToken: String | None + MaxResults: DescribeCapacityReservationFleetsMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None class DescribeCapacityReservationFleetsResult(TypedDict, total=False): - CapacityReservationFleets: Optional[CapacityReservationFleetSet] - NextToken: Optional[String] + CapacityReservationFleets: CapacityReservationFleetSet | None + NextToken: String | None + + +class DescribeCapacityReservationTopologyRequest(ServiceRequest): + DryRun: Boolean | None + NextToken: String | None + MaxResults: DescribeCapacityReservationTopologyMaxResults | None + CapacityReservationIds: CapacityReservationIdSet | None + Filters: FilterList | None + + +class DescribeCapacityReservationTopologyResult(TypedDict, total=False): + NextToken: String | None + CapacityReservations: CapacityReservationTopologySet | None class DescribeCapacityReservationsRequest(ServiceRequest): - CapacityReservationIds: Optional[CapacityReservationIdSet] - NextToken: Optional[String] - MaxResults: Optional[DescribeCapacityReservationsMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + CapacityReservationIds: CapacityReservationIdSet | None + NextToken: String | None + MaxResults: DescribeCapacityReservationsMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None class DescribeCapacityReservationsResult(TypedDict, total=False): - NextToken: Optional[String] - CapacityReservations: Optional[CapacityReservationSet] + NextToken: String | None + CapacityReservations: CapacityReservationSet | None class DescribeCarrierGatewaysRequest(ServiceRequest): - CarrierGatewayIds: Optional[CarrierGatewayIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[CarrierGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + CarrierGatewayIds: CarrierGatewayIdSet | None + Filters: FilterList | None + MaxResults: CarrierGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class DescribeCarrierGatewaysResult(TypedDict, total=False): - CarrierGateways: Optional[CarrierGatewaySet] - NextToken: Optional[String] + CarrierGateways: CarrierGatewaySet | None + NextToken: String | None -InstanceIdStringList = List[InstanceId] +InstanceIdStringList = list[InstanceId] class DescribeClassicLinkInstancesRequest(ServiceRequest): - DryRun: Optional[Boolean] - InstanceIds: Optional[InstanceIdStringList] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[DescribeClassicLinkInstancesMaxResults] + DryRun: Boolean | None + InstanceIds: InstanceIdStringList | None + Filters: FilterList | None + NextToken: String | None + MaxResults: DescribeClassicLinkInstancesMaxResults | None class DescribeClassicLinkInstancesResult(TypedDict, total=False): - Instances: Optional[ClassicLinkInstanceList] - NextToken: Optional[String] + Instances: ClassicLinkInstanceList | None + NextToken: String | None class DescribeClientVpnAuthorizationRulesRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - DryRun: Optional[Boolean] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - MaxResults: Optional[DescribeClientVpnAuthorizationRulesMaxResults] + DryRun: Boolean | None + NextToken: NextToken | None + Filters: FilterList | None + MaxResults: DescribeClientVpnAuthorizationRulesMaxResults | None class DescribeClientVpnAuthorizationRulesResult(TypedDict, total=False): - AuthorizationRules: Optional[AuthorizationRuleSet] - NextToken: Optional[NextToken] + AuthorizationRules: AuthorizationRuleSet | None + NextToken: NextToken | None class DescribeClientVpnConnectionsRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - Filters: Optional[FilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[DescribeClientVpnConnectionsMaxResults] - DryRun: Optional[Boolean] + Filters: FilterList | None + NextToken: NextToken | None + MaxResults: DescribeClientVpnConnectionsMaxResults | None + DryRun: Boolean | None class DescribeClientVpnConnectionsResult(TypedDict, total=False): - Connections: Optional[ClientVpnConnectionSet] - NextToken: Optional[NextToken] + Connections: ClientVpnConnectionSet | None + NextToken: NextToken | None class DescribeClientVpnEndpointsRequest(ServiceRequest): - ClientVpnEndpointIds: Optional[ClientVpnEndpointIdList] - MaxResults: Optional[DescribeClientVpnEndpointMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + ClientVpnEndpointIds: ClientVpnEndpointIdList | None + MaxResults: DescribeClientVpnEndpointMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + DryRun: Boolean | None -EndpointSet = List[ClientVpnEndpoint] +EndpointSet = list[ClientVpnEndpoint] class DescribeClientVpnEndpointsResult(TypedDict, total=False): - ClientVpnEndpoints: Optional[EndpointSet] - NextToken: Optional[NextToken] + ClientVpnEndpoints: EndpointSet | None + NextToken: NextToken | None class DescribeClientVpnRoutesRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - Filters: Optional[FilterList] - MaxResults: Optional[DescribeClientVpnRoutesMaxResults] - NextToken: Optional[NextToken] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: DescribeClientVpnRoutesMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None class DescribeClientVpnRoutesResult(TypedDict, total=False): - Routes: Optional[ClientVpnRouteSet] - NextToken: Optional[NextToken] + Routes: ClientVpnRouteSet | None + NextToken: NextToken | None class DescribeClientVpnTargetNetworksRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - AssociationIds: Optional[ValueStringList] - MaxResults: Optional[DescribeClientVpnTargetNetworksMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + AssociationIds: ValueStringList | None + MaxResults: DescribeClientVpnTargetNetworksMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + DryRun: Boolean | None class TargetNetwork(TypedDict, total=False): - AssociationId: Optional[String] - VpcId: Optional[String] - TargetNetworkId: Optional[String] - ClientVpnEndpointId: Optional[String] - Status: Optional[AssociationStatus] - SecurityGroups: Optional[ValueStringList] + AssociationId: String | None + VpcId: String | None + TargetNetworkId: String | None + ClientVpnEndpointId: String | None + Status: AssociationStatus | None + SecurityGroups: ValueStringList | None -TargetNetworkSet = List[TargetNetwork] +TargetNetworkSet = list[TargetNetwork] class DescribeClientVpnTargetNetworksResult(TypedDict, total=False): - ClientVpnTargetNetworks: Optional[TargetNetworkSet] - NextToken: Optional[NextToken] + ClientVpnTargetNetworks: TargetNetworkSet | None + NextToken: NextToken | None class DescribeCoipPoolsRequest(ServiceRequest): - PoolIds: Optional[CoipPoolIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[CoipPoolMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + PoolIds: CoipPoolIdSet | None + Filters: FilterList | None + MaxResults: CoipPoolMaxResults | None + NextToken: String | None + DryRun: Boolean | None class DescribeCoipPoolsResult(TypedDict, total=False): - CoipPools: Optional[CoipPoolSet] - NextToken: Optional[String] + CoipPools: CoipPoolSet | None + NextToken: String | None -DescribeConversionTaskList = List[ConversionTask] +DescribeConversionTaskList = list[ConversionTask] class DescribeConversionTasksRequest(ServiceRequest): - DryRun: Optional[Boolean] - ConversionTaskIds: Optional[ConversionIdStringList] + DryRun: Boolean | None + ConversionTaskIds: ConversionIdStringList | None class DescribeConversionTasksResult(TypedDict, total=False): - ConversionTasks: Optional[DescribeConversionTaskList] + ConversionTasks: DescribeConversionTaskList | None class DescribeCustomerGatewaysRequest(ServiceRequest): - CustomerGatewayIds: Optional[CustomerGatewayIdStringList] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + CustomerGatewayIds: CustomerGatewayIdStringList | None + Filters: FilterList | None + DryRun: Boolean | None class DescribeCustomerGatewaysResult(TypedDict, total=False): - CustomerGateways: Optional[CustomerGatewayList] + CustomerGateways: CustomerGatewayList | None class DescribeDeclarativePoliciesReportsRequest(ServiceRequest): - DryRun: Optional[Boolean] - NextToken: Optional[String] - MaxResults: Optional[DeclarativePoliciesMaxResults] - ReportIds: Optional[ValueStringList] + DryRun: Boolean | None + NextToken: String | None + MaxResults: DeclarativePoliciesMaxResults | None + ReportIds: ValueStringList | None class DescribeDeclarativePoliciesReportsResult(TypedDict, total=False): - NextToken: Optional[String] - Reports: Optional[DeclarativePoliciesReportList] + NextToken: String | None + Reports: DeclarativePoliciesReportList | None -DhcpOptionsIdStringList = List[DhcpOptionsId] +DhcpOptionsIdStringList = list[DhcpOptionsId] class DescribeDhcpOptionsRequest(ServiceRequest): - DhcpOptionsIds: Optional[DhcpOptionsIdStringList] - NextToken: Optional[String] - MaxResults: Optional[DescribeDhcpOptionsMaxResults] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + DhcpOptionsIds: DhcpOptionsIdStringList | None + NextToken: String | None + MaxResults: DescribeDhcpOptionsMaxResults | None + DryRun: Boolean | None + Filters: FilterList | None -DhcpOptionsList = List[DhcpOptions] +DhcpOptionsList = list[DhcpOptions] class DescribeDhcpOptionsResult(TypedDict, total=False): - NextToken: Optional[String] - DhcpOptions: Optional[DhcpOptionsList] + NextToken: String | None + DhcpOptions: DhcpOptionsList | None -EgressOnlyInternetGatewayIdList = List[EgressOnlyInternetGatewayId] +EgressOnlyInternetGatewayIdList = list[EgressOnlyInternetGatewayId] class DescribeEgressOnlyInternetGatewaysRequest(ServiceRequest): - DryRun: Optional[Boolean] - EgressOnlyInternetGatewayIds: Optional[EgressOnlyInternetGatewayIdList] - MaxResults: Optional[DescribeEgressOnlyInternetGatewaysMaxResults] - NextToken: Optional[String] - Filters: Optional[FilterList] + DryRun: Boolean | None + EgressOnlyInternetGatewayIds: EgressOnlyInternetGatewayIdList | None + MaxResults: DescribeEgressOnlyInternetGatewaysMaxResults | None + NextToken: String | None + Filters: FilterList | None -EgressOnlyInternetGatewayList = List[EgressOnlyInternetGateway] +EgressOnlyInternetGatewayList = list[EgressOnlyInternetGateway] class DescribeEgressOnlyInternetGatewaysResult(TypedDict, total=False): - EgressOnlyInternetGateways: Optional[EgressOnlyInternetGatewayList] - NextToken: Optional[String] + EgressOnlyInternetGateways: EgressOnlyInternetGatewayList | None + NextToken: String | None -ElasticGpuIdSet = List[ElasticGpuId] +ElasticGpuIdSet = list[ElasticGpuId] class DescribeElasticGpusRequest(ServiceRequest): - ElasticGpuIds: Optional[ElasticGpuIdSet] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[DescribeElasticGpusMaxResults] - NextToken: Optional[String] + ElasticGpuIds: ElasticGpuIdSet | None + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: DescribeElasticGpusMaxResults | None + NextToken: String | None class ElasticGpuHealth(TypedDict, total=False): - Status: Optional[ElasticGpuStatus] + Status: ElasticGpuStatus | None class ElasticGpus(TypedDict, total=False): - ElasticGpuId: Optional[String] - AvailabilityZone: Optional[String] - ElasticGpuType: Optional[String] - ElasticGpuHealth: Optional[ElasticGpuHealth] - ElasticGpuState: Optional[ElasticGpuState] - InstanceId: Optional[String] - Tags: Optional[TagList] + ElasticGpuId: String | None + AvailabilityZone: String | None + ElasticGpuType: String | None + ElasticGpuHealth: ElasticGpuHealth | None + ElasticGpuState: ElasticGpuState | None + InstanceId: String | None + Tags: TagList | None -ElasticGpuSet = List[ElasticGpus] +ElasticGpuSet = list[ElasticGpus] class DescribeElasticGpusResult(TypedDict, total=False): - ElasticGpuSet: Optional[ElasticGpuSet] - MaxResults: Optional[Integer] - NextToken: Optional[String] + ElasticGpuSet: ElasticGpuSet | None + MaxResults: Integer | None + NextToken: String | None -ExportImageTaskIdList = List[ExportImageTaskId] +ExportImageTaskIdList = list[ExportImageTaskId] class DescribeExportImageTasksRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - ExportImageTaskIds: Optional[ExportImageTaskIdList] - MaxResults: Optional[DescribeExportImageTasksMaxResults] - NextToken: Optional[NextToken] + DryRun: Boolean | None + Filters: FilterList | None + ExportImageTaskIds: ExportImageTaskIdList | None + MaxResults: DescribeExportImageTasksMaxResults | None + NextToken: NextToken | None class ExportTaskS3Location(TypedDict, total=False): - S3Bucket: Optional[String] - S3Prefix: Optional[String] + S3Bucket: String | None + S3Prefix: String | None class ExportImageTask(TypedDict, total=False): - Description: Optional[String] - ExportImageTaskId: Optional[String] - ImageId: Optional[String] - Progress: Optional[String] - S3ExportLocation: Optional[ExportTaskS3Location] - Status: Optional[String] - StatusMessage: Optional[String] - Tags: Optional[TagList] + Description: String | None + ExportImageTaskId: String | None + ImageId: String | None + Progress: String | None + S3ExportLocation: ExportTaskS3Location | None + Status: String | None + StatusMessage: String | None + Tags: TagList | None -ExportImageTaskList = List[ExportImageTask] +ExportImageTaskList = list[ExportImageTask] class DescribeExportImageTasksResult(TypedDict, total=False): - ExportImageTasks: Optional[ExportImageTaskList] - NextToken: Optional[NextToken] + ExportImageTasks: ExportImageTaskList | None + NextToken: NextToken | None -ExportTaskIdStringList = List[ExportTaskId] +ExportTaskIdStringList = list[ExportTaskId] class DescribeExportTasksRequest(ServiceRequest): - Filters: Optional[FilterList] - ExportTaskIds: Optional[ExportTaskIdStringList] + Filters: FilterList | None + ExportTaskIds: ExportTaskIdStringList | None -ExportTaskList = List[ExportTask] +ExportTaskList = list[ExportTask] class DescribeExportTasksResult(TypedDict, total=False): - ExportTasks: Optional[ExportTaskList] + ExportTasks: ExportTaskList | None -FastLaunchImageIdList = List[ImageId] +FastLaunchImageIdList = list[ImageId] class DescribeFastLaunchImagesRequest(ServiceRequest): - ImageIds: Optional[FastLaunchImageIdList] - Filters: Optional[FilterList] - MaxResults: Optional[DescribeFastLaunchImagesRequestMaxResults] - NextToken: Optional[NextToken] - DryRun: Optional[Boolean] + ImageIds: FastLaunchImageIdList | None + Filters: FilterList | None + MaxResults: DescribeFastLaunchImagesRequestMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None class FastLaunchLaunchTemplateSpecificationResponse(TypedDict, total=False): - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[String] - Version: Optional[String] + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: String | None + Version: String | None class FastLaunchSnapshotConfigurationResponse(TypedDict, total=False): - TargetResourceCount: Optional[Integer] + TargetResourceCount: Integer | None class DescribeFastLaunchImagesSuccessItem(TypedDict, total=False): - ImageId: Optional[ImageId] - ResourceType: Optional[FastLaunchResourceType] - SnapshotConfiguration: Optional[FastLaunchSnapshotConfigurationResponse] - LaunchTemplate: Optional[FastLaunchLaunchTemplateSpecificationResponse] - MaxParallelLaunches: Optional[Integer] - OwnerId: Optional[String] - State: Optional[FastLaunchStateCode] - StateTransitionReason: Optional[String] - StateTransitionTime: Optional[MillisecondDateTime] + ImageId: ImageId | None + ResourceType: FastLaunchResourceType | None + SnapshotConfiguration: FastLaunchSnapshotConfigurationResponse | None + LaunchTemplate: FastLaunchLaunchTemplateSpecificationResponse | None + MaxParallelLaunches: Integer | None + OwnerId: String | None + State: FastLaunchStateCode | None + StateTransitionReason: String | None + StateTransitionTime: MillisecondDateTime | None -DescribeFastLaunchImagesSuccessSet = List[DescribeFastLaunchImagesSuccessItem] +DescribeFastLaunchImagesSuccessSet = list[DescribeFastLaunchImagesSuccessItem] class DescribeFastLaunchImagesResult(TypedDict, total=False): - FastLaunchImages: Optional[DescribeFastLaunchImagesSuccessSet] - NextToken: Optional[NextToken] + FastLaunchImages: DescribeFastLaunchImagesSuccessSet | None + NextToken: NextToken | None class DescribeFastSnapshotRestoreSuccessItem(TypedDict, total=False): - SnapshotId: Optional[String] - AvailabilityZone: Optional[String] - State: Optional[FastSnapshotRestoreStateCode] - StateTransitionReason: Optional[String] - OwnerId: Optional[String] - OwnerAlias: Optional[String] - EnablingTime: Optional[MillisecondDateTime] - OptimizingTime: Optional[MillisecondDateTime] - EnabledTime: Optional[MillisecondDateTime] - DisablingTime: Optional[MillisecondDateTime] - DisabledTime: Optional[MillisecondDateTime] + SnapshotId: String | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + State: FastSnapshotRestoreStateCode | None + StateTransitionReason: String | None + OwnerId: String | None + OwnerAlias: String | None + EnablingTime: MillisecondDateTime | None + OptimizingTime: MillisecondDateTime | None + EnabledTime: MillisecondDateTime | None + DisablingTime: MillisecondDateTime | None + DisabledTime: MillisecondDateTime | None -DescribeFastSnapshotRestoreSuccessSet = List[DescribeFastSnapshotRestoreSuccessItem] +DescribeFastSnapshotRestoreSuccessSet = list[DescribeFastSnapshotRestoreSuccessItem] class DescribeFastSnapshotRestoresRequest(ServiceRequest): - Filters: Optional[FilterList] - MaxResults: Optional[DescribeFastSnapshotRestoresMaxResults] - NextToken: Optional[NextToken] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: DescribeFastSnapshotRestoresMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None class DescribeFastSnapshotRestoresResult(TypedDict, total=False): - FastSnapshotRestores: Optional[DescribeFastSnapshotRestoreSuccessSet] - NextToken: Optional[NextToken] + FastSnapshotRestores: DescribeFastSnapshotRestoreSuccessSet | None + NextToken: NextToken | None class DescribeFleetError(TypedDict, total=False): - LaunchTemplateAndOverrides: Optional[LaunchTemplateAndOverridesResponse] - Lifecycle: Optional[InstanceLifecycle] - ErrorCode: Optional[String] - ErrorMessage: Optional[String] + LaunchTemplateAndOverrides: LaunchTemplateAndOverridesResponse | None + Lifecycle: InstanceLifecycle | None + ErrorCode: String | None + ErrorMessage: String | None class DescribeFleetHistoryRequest(ServiceRequest): - DryRun: Optional[Boolean] - EventType: Optional[FleetEventType] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + EventType: FleetEventType | None + MaxResults: Integer | None + NextToken: String | None FleetId: FleetId StartTime: DateTime class EventInformation(TypedDict, total=False): - EventDescription: Optional[String] - EventSubType: Optional[String] - InstanceId: Optional[String] + EventDescription: String | None + EventSubType: String | None + InstanceId: String | None class HistoryRecordEntry(TypedDict, total=False): - EventInformation: Optional[EventInformation] - EventType: Optional[FleetEventType] - Timestamp: Optional[DateTime] + EventInformation: EventInformation | None + EventType: FleetEventType | None + Timestamp: DateTime | None -HistoryRecordSet = List[HistoryRecordEntry] +HistoryRecordSet = list[HistoryRecordEntry] class DescribeFleetHistoryResult(TypedDict, total=False): - HistoryRecords: Optional[HistoryRecordSet] - LastEvaluatedTime: Optional[DateTime] - NextToken: Optional[String] - FleetId: Optional[FleetId] - StartTime: Optional[DateTime] + HistoryRecords: HistoryRecordSet | None + LastEvaluatedTime: DateTime | None + NextToken: String | None + FleetId: FleetId | None + StartTime: DateTime | None class DescribeFleetInstancesRequest(ServiceRequest): - DryRun: Optional[Boolean] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + MaxResults: Integer | None + NextToken: String | None FleetId: FleetId - Filters: Optional[FilterList] + Filters: FilterList | None class DescribeFleetInstancesResult(TypedDict, total=False): - ActiveInstances: Optional[ActiveInstanceSet] - NextToken: Optional[String] - FleetId: Optional[FleetId] + ActiveInstances: ActiveInstanceSet | None + NextToken: String | None + FleetId: FleetId | None -DescribeFleetsErrorSet = List[DescribeFleetError] +DescribeFleetsErrorSet = list[DescribeFleetError] class DescribeFleetsInstances(TypedDict, total=False): - LaunchTemplateAndOverrides: Optional[LaunchTemplateAndOverridesResponse] - Lifecycle: Optional[InstanceLifecycle] - InstanceIds: Optional[InstanceIdsSet] - InstanceType: Optional[InstanceType] - Platform: Optional[PlatformValues] + LaunchTemplateAndOverrides: LaunchTemplateAndOverridesResponse | None + Lifecycle: InstanceLifecycle | None + InstanceIds: InstanceIdsSet | None + InstanceType: InstanceType | None + Platform: PlatformValues | None -DescribeFleetsInstancesSet = List[DescribeFleetsInstances] +DescribeFleetsInstancesSet = list[DescribeFleetsInstances] class DescribeFleetsRequest(ServiceRequest): - DryRun: Optional[Boolean] - MaxResults: Optional[Integer] - NextToken: Optional[String] - FleetIds: Optional[FleetIdSet] - Filters: Optional[FilterList] + DryRun: Boolean | None + MaxResults: Integer | None + NextToken: String | None + FleetIds: FleetIdSet | None + Filters: FilterList | None class OnDemandOptions(TypedDict, total=False): - AllocationStrategy: Optional[FleetOnDemandAllocationStrategy] - CapacityReservationOptions: Optional[CapacityReservationOptions] - SingleInstanceType: Optional[Boolean] - SingleAvailabilityZone: Optional[Boolean] - MinTargetCapacity: Optional[Integer] - MaxTotalPrice: Optional[String] + AllocationStrategy: FleetOnDemandAllocationStrategy | None + CapacityReservationOptions: CapacityReservationOptions | None + SingleInstanceType: Boolean | None + SingleAvailabilityZone: Boolean | None + MinTargetCapacity: Integer | None + MaxTotalPrice: String | None class FleetSpotCapacityRebalance(TypedDict, total=False): - ReplacementStrategy: Optional[FleetReplacementStrategy] - TerminationDelay: Optional[Integer] + ReplacementStrategy: FleetReplacementStrategy | None + TerminationDelay: Integer | None class FleetSpotMaintenanceStrategies(TypedDict, total=False): - CapacityRebalance: Optional[FleetSpotCapacityRebalance] + CapacityRebalance: FleetSpotCapacityRebalance | None class SpotOptions(TypedDict, total=False): - AllocationStrategy: Optional[SpotAllocationStrategy] - MaintenanceStrategies: Optional[FleetSpotMaintenanceStrategies] - InstanceInterruptionBehavior: Optional[SpotInstanceInterruptionBehavior] - InstancePoolsToUseCount: Optional[Integer] - SingleInstanceType: Optional[Boolean] - SingleAvailabilityZone: Optional[Boolean] - MinTargetCapacity: Optional[Integer] - MaxTotalPrice: Optional[String] + AllocationStrategy: SpotAllocationStrategy | None + MaintenanceStrategies: FleetSpotMaintenanceStrategies | None + InstanceInterruptionBehavior: SpotInstanceInterruptionBehavior | None + InstancePoolsToUseCount: Integer | None + SingleInstanceType: Boolean | None + SingleAvailabilityZone: Boolean | None + MinTargetCapacity: Integer | None + MaxTotalPrice: String | None class TargetCapacitySpecification(TypedDict, total=False): - TotalTargetCapacity: Optional[Integer] - OnDemandTargetCapacity: Optional[Integer] - SpotTargetCapacity: Optional[Integer] - DefaultTargetCapacityType: Optional[DefaultTargetCapacityType] - TargetCapacityUnitType: Optional[TargetCapacityUnitType] + TotalTargetCapacity: Integer | None + OnDemandTargetCapacity: Integer | None + SpotTargetCapacity: Integer | None + DefaultTargetCapacityType: DefaultTargetCapacityType | None + TargetCapacityUnitType: TargetCapacityUnitType | None -FleetLaunchTemplateOverridesList = List[FleetLaunchTemplateOverrides] +FleetLaunchTemplateOverridesList = list[FleetLaunchTemplateOverrides] class FleetLaunchTemplateConfig(TypedDict, total=False): - LaunchTemplateSpecification: Optional[FleetLaunchTemplateSpecification] - Overrides: Optional[FleetLaunchTemplateOverridesList] + LaunchTemplateSpecification: FleetLaunchTemplateSpecification | None + Overrides: FleetLaunchTemplateOverridesList | None -FleetLaunchTemplateConfigList = List[FleetLaunchTemplateConfig] +FleetLaunchTemplateConfigList = list[FleetLaunchTemplateConfig] class FleetData(TypedDict, total=False): - ActivityStatus: Optional[FleetActivityStatus] - CreateTime: Optional[DateTime] - FleetId: Optional[FleetId] - FleetState: Optional[FleetStateCode] - ClientToken: Optional[String] - ExcessCapacityTerminationPolicy: Optional[FleetExcessCapacityTerminationPolicy] - FulfilledCapacity: Optional[Double] - FulfilledOnDemandCapacity: Optional[Double] - LaunchTemplateConfigs: Optional[FleetLaunchTemplateConfigList] - TargetCapacitySpecification: Optional[TargetCapacitySpecification] - TerminateInstancesWithExpiration: Optional[Boolean] - Type: Optional[FleetType] - ValidFrom: Optional[DateTime] - ValidUntil: Optional[DateTime] - ReplaceUnhealthyInstances: Optional[Boolean] - SpotOptions: Optional[SpotOptions] - OnDemandOptions: Optional[OnDemandOptions] - Tags: Optional[TagList] - Errors: Optional[DescribeFleetsErrorSet] - Instances: Optional[DescribeFleetsInstancesSet] - Context: Optional[String] - - -FleetSet = List[FleetData] + ActivityStatus: FleetActivityStatus | None + CreateTime: DateTime | None + FleetId: FleetId | None + FleetState: FleetStateCode | None + ClientToken: String | None + ExcessCapacityTerminationPolicy: FleetExcessCapacityTerminationPolicy | None + FulfilledCapacity: Double | None + FulfilledOnDemandCapacity: Double | None + LaunchTemplateConfigs: FleetLaunchTemplateConfigList | None + TargetCapacitySpecification: TargetCapacitySpecification | None + TerminateInstancesWithExpiration: Boolean | None + Type: FleetType | None + ValidFrom: DateTime | None + ValidUntil: DateTime | None + ReplaceUnhealthyInstances: Boolean | None + SpotOptions: SpotOptions | None + OnDemandOptions: OnDemandOptions | None + Tags: TagList | None + Errors: DescribeFleetsErrorSet | None + Instances: DescribeFleetsInstancesSet | None + Context: String | None + + +FleetSet = list[FleetData] class DescribeFleetsResult(TypedDict, total=False): - NextToken: Optional[String] - Fleets: Optional[FleetSet] + NextToken: String | None + Fleets: FleetSet | None class DescribeFlowLogsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filter: Optional[FilterList] - FlowLogIds: Optional[FlowLogIdList] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + Filter: FilterList | None + FlowLogIds: FlowLogIdList | None + MaxResults: Integer | None + NextToken: String | None class DestinationOptionsResponse(TypedDict, total=False): - FileFormat: Optional[DestinationFileFormat] - HiveCompatiblePartitions: Optional[Boolean] - PerHourPartition: Optional[Boolean] + FileFormat: DestinationFileFormat | None + HiveCompatiblePartitions: Boolean | None + PerHourPartition: Boolean | None class FlowLog(TypedDict, total=False): - CreationTime: Optional[MillisecondDateTime] - DeliverLogsErrorMessage: Optional[String] - DeliverLogsPermissionArn: Optional[String] - DeliverCrossAccountRole: Optional[String] - DeliverLogsStatus: Optional[String] - FlowLogId: Optional[String] - FlowLogStatus: Optional[String] - LogGroupName: Optional[String] - ResourceId: Optional[String] - TrafficType: Optional[TrafficType] - LogDestinationType: Optional[LogDestinationType] - LogDestination: Optional[String] - LogFormat: Optional[String] - Tags: Optional[TagList] - MaxAggregationInterval: Optional[Integer] - DestinationOptions: Optional[DestinationOptionsResponse] - - -FlowLogSet = List[FlowLog] + CreationTime: MillisecondDateTime | None + DeliverLogsErrorMessage: String | None + DeliverLogsPermissionArn: String | None + DeliverCrossAccountRole: String | None + DeliverLogsStatus: String | None + FlowLogId: String | None + FlowLogStatus: String | None + LogGroupName: String | None + ResourceId: String | None + TrafficType: TrafficType | None + LogDestinationType: LogDestinationType | None + LogDestination: String | None + LogFormat: String | None + Tags: TagList | None + MaxAggregationInterval: Integer | None + DestinationOptions: DestinationOptionsResponse | None + + +FlowLogSet = list[FlowLog] class DescribeFlowLogsResult(TypedDict, total=False): - FlowLogs: Optional[FlowLogSet] - NextToken: Optional[String] + FlowLogs: FlowLogSet | None + NextToken: String | None class DescribeFpgaImageAttributeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None FpgaImageId: FpgaImageId Attribute: FpgaImageAttributeName class ProductCode(TypedDict, total=False): - ProductCodeId: Optional[String] - ProductCodeType: Optional[ProductCodeValues] + ProductCodeId: String | None + ProductCodeType: ProductCodeValues | None -ProductCodeList = List[ProductCode] +ProductCodeList = list[ProductCode] class LoadPermission(TypedDict, total=False): - UserId: Optional[String] - Group: Optional[PermissionGroup] + UserId: String | None + Group: PermissionGroup | None -LoadPermissionList = List[LoadPermission] +LoadPermissionList = list[LoadPermission] class FpgaImageAttribute(TypedDict, total=False): - FpgaImageId: Optional[String] - Name: Optional[String] - Description: Optional[String] - LoadPermissions: Optional[LoadPermissionList] - ProductCodes: Optional[ProductCodeList] + FpgaImageId: String | None + Name: String | None + Description: String | None + LoadPermissions: LoadPermissionList | None + ProductCodes: ProductCodeList | None class DescribeFpgaImageAttributeResult(TypedDict, total=False): - FpgaImageAttribute: Optional[FpgaImageAttribute] + FpgaImageAttribute: FpgaImageAttribute | None -OwnerStringList = List[String] -FpgaImageIdList = List[FpgaImageId] +OwnerStringList = list[String] +FpgaImageIdList = list[FpgaImageId] class DescribeFpgaImagesRequest(ServiceRequest): - DryRun: Optional[Boolean] - FpgaImageIds: Optional[FpgaImageIdList] - Owners: Optional[OwnerStringList] - Filters: Optional[FilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[DescribeFpgaImagesMaxResults] + DryRun: Boolean | None + FpgaImageIds: FpgaImageIdList | None + Owners: OwnerStringList | None + Filters: FilterList | None + NextToken: NextToken | None + MaxResults: DescribeFpgaImagesMaxResults | None -InstanceTypesList = List[String] +InstanceTypesList = list[String] class FpgaImageState(TypedDict, total=False): - Code: Optional[FpgaImageStateCode] - Message: Optional[String] + Code: FpgaImageStateCode | None + Message: String | None class PciId(TypedDict, total=False): - DeviceId: Optional[String] - VendorId: Optional[String] - SubsystemId: Optional[String] - SubsystemVendorId: Optional[String] + DeviceId: String | None + VendorId: String | None + SubsystemId: String | None + SubsystemVendorId: String | None class FpgaImage(TypedDict, total=False): - FpgaImageId: Optional[String] - FpgaImageGlobalId: Optional[String] - Name: Optional[String] - Description: Optional[String] - ShellVersion: Optional[String] - PciId: Optional[PciId] - State: Optional[FpgaImageState] - CreateTime: Optional[DateTime] - UpdateTime: Optional[DateTime] - OwnerId: Optional[String] - OwnerAlias: Optional[String] - ProductCodes: Optional[ProductCodeList] - Tags: Optional[TagList] - Public: Optional[Boolean] - DataRetentionSupport: Optional[Boolean] - InstanceTypes: Optional[InstanceTypesList] - - -FpgaImageList = List[FpgaImage] + FpgaImageId: String | None + FpgaImageGlobalId: String | None + Name: String | None + Description: String | None + ShellVersion: String | None + PciId: PciId | None + State: FpgaImageState | None + CreateTime: DateTime | None + UpdateTime: DateTime | None + OwnerId: String | None + OwnerAlias: String | None + ProductCodes: ProductCodeList | None + Tags: TagList | None + Public: Boolean | None + DataRetentionSupport: Boolean | None + InstanceTypes: InstanceTypesList | None + + +FpgaImageList = list[FpgaImage] class DescribeFpgaImagesResult(TypedDict, total=False): - FpgaImages: Optional[FpgaImageList] - NextToken: Optional[NextToken] + FpgaImages: FpgaImageList | None + NextToken: NextToken | None class DescribeHostReservationOfferingsRequest(ServiceRequest): - Filter: Optional[FilterList] - MaxDuration: Optional[Integer] - MaxResults: Optional[DescribeHostReservationsMaxResults] - MinDuration: Optional[Integer] - NextToken: Optional[String] - OfferingId: Optional[OfferingId] + Filter: FilterList | None + MaxDuration: Integer | None + MaxResults: DescribeHostReservationsMaxResults | None + MinDuration: Integer | None + NextToken: String | None + OfferingId: OfferingId | None class HostOffering(TypedDict, total=False): - CurrencyCode: Optional[CurrencyCodeValues] - Duration: Optional[Integer] - HourlyPrice: Optional[String] - InstanceFamily: Optional[String] - OfferingId: Optional[OfferingId] - PaymentOption: Optional[PaymentOption] - UpfrontPrice: Optional[String] + CurrencyCode: CurrencyCodeValues | None + Duration: Integer | None + HourlyPrice: String | None + InstanceFamily: String | None + OfferingId: OfferingId | None + PaymentOption: PaymentOption | None + UpfrontPrice: String | None -HostOfferingSet = List[HostOffering] +HostOfferingSet = list[HostOffering] class DescribeHostReservationOfferingsResult(TypedDict, total=False): - NextToken: Optional[String] - OfferingSet: Optional[HostOfferingSet] + NextToken: String | None + OfferingSet: HostOfferingSet | None -HostReservationIdSet = List[HostReservationId] +HostReservationIdSet = list[HostReservationId] class DescribeHostReservationsRequest(ServiceRequest): - Filter: Optional[FilterList] - HostReservationIdSet: Optional[HostReservationIdSet] - MaxResults: Optional[Integer] - NextToken: Optional[String] + Filter: FilterList | None + HostReservationIdSet: HostReservationIdSet | None + MaxResults: Integer | None + NextToken: String | None -ResponseHostIdSet = List[String] +ResponseHostIdSet = list[String] class HostReservation(TypedDict, total=False): - Count: Optional[Integer] - CurrencyCode: Optional[CurrencyCodeValues] - Duration: Optional[Integer] - End: Optional[DateTime] - HostIdSet: Optional[ResponseHostIdSet] - HostReservationId: Optional[HostReservationId] - HourlyPrice: Optional[String] - InstanceFamily: Optional[String] - OfferingId: Optional[OfferingId] - PaymentOption: Optional[PaymentOption] - Start: Optional[DateTime] - State: Optional[ReservationState] - UpfrontPrice: Optional[String] - Tags: Optional[TagList] + Count: Integer | None + CurrencyCode: CurrencyCodeValues | None + Duration: Integer | None + End: DateTime | None + HostIdSet: ResponseHostIdSet | None + HostReservationId: HostReservationId | None + HourlyPrice: String | None + InstanceFamily: String | None + OfferingId: OfferingId | None + PaymentOption: PaymentOption | None + Start: DateTime | None + State: ReservationState | None + UpfrontPrice: String | None + Tags: TagList | None -HostReservationSet = List[HostReservation] +HostReservationSet = list[HostReservation] class DescribeHostReservationsResult(TypedDict, total=False): - HostReservationSet: Optional[HostReservationSet] - NextToken: Optional[String] + HostReservationSet: HostReservationSet | None + NextToken: String | None -RequestHostIdList = List[DedicatedHostId] +RequestHostIdList = list[DedicatedHostId] class DescribeHostsRequest(ServiceRequest): - HostIds: Optional[RequestHostIdList] - NextToken: Optional[String] - MaxResults: Optional[Integer] - Filter: Optional[FilterList] + HostIds: RequestHostIdList | None + NextToken: String | None + MaxResults: Integer | None + Filter: FilterList | None class HostInstance(TypedDict, total=False): - InstanceId: Optional[String] - InstanceType: Optional[String] - OwnerId: Optional[String] + InstanceId: String | None + InstanceType: String | None + OwnerId: String | None -HostInstanceList = List[HostInstance] +HostInstanceList = list[HostInstance] class HostProperties(TypedDict, total=False): - Cores: Optional[Integer] - InstanceType: Optional[String] - InstanceFamily: Optional[String] - Sockets: Optional[Integer] - TotalVCpus: Optional[Integer] + Cores: Integer | None + InstanceType: String | None + InstanceFamily: String | None + Sockets: Integer | None + TotalVCpus: Integer | None class Host(TypedDict, total=False): - AutoPlacement: Optional[AutoPlacement] - AvailabilityZone: Optional[String] - AvailableCapacity: Optional[AvailableCapacity] - ClientToken: Optional[String] - HostId: Optional[String] - HostProperties: Optional[HostProperties] - HostReservationId: Optional[String] - Instances: Optional[HostInstanceList] - State: Optional[AllocationState] - AllocationTime: Optional[DateTime] - ReleaseTime: Optional[DateTime] - Tags: Optional[TagList] - HostRecovery: Optional[HostRecovery] - AllowsMultipleInstanceTypes: Optional[AllowsMultipleInstanceTypes] - OwnerId: Optional[String] - AvailabilityZoneId: Optional[String] - MemberOfServiceLinkedResourceGroup: Optional[Boolean] - OutpostArn: Optional[String] - HostMaintenance: Optional[HostMaintenance] - AssetId: Optional[AssetId] - - -HostList = List[Host] + AutoPlacement: AutoPlacement | None + AvailabilityZone: String | None + AvailableCapacity: AvailableCapacity | None + ClientToken: String | None + HostId: String | None + HostProperties: HostProperties | None + HostReservationId: String | None + Instances: HostInstanceList | None + State: AllocationState | None + AllocationTime: DateTime | None + ReleaseTime: DateTime | None + Tags: TagList | None + HostRecovery: HostRecovery | None + AllowsMultipleInstanceTypes: AllowsMultipleInstanceTypes | None + OwnerId: String | None + AvailabilityZoneId: String | None + MemberOfServiceLinkedResourceGroup: Boolean | None + OutpostArn: String | None + HostMaintenance: HostMaintenance | None + AssetId: AssetId | None + + +HostList = list[Host] class DescribeHostsResult(TypedDict, total=False): - Hosts: Optional[HostList] - NextToken: Optional[String] + Hosts: HostList | None + NextToken: String | None class DescribeIamInstanceProfileAssociationsRequest(ServiceRequest): - AssociationIds: Optional[AssociationIdList] - Filters: Optional[FilterList] - MaxResults: Optional[DescribeIamInstanceProfileAssociationsMaxResults] - NextToken: Optional[NextToken] + AssociationIds: AssociationIdList | None + Filters: FilterList | None + MaxResults: DescribeIamInstanceProfileAssociationsMaxResults | None + NextToken: NextToken | None -IamInstanceProfileAssociationSet = List[IamInstanceProfileAssociation] +IamInstanceProfileAssociationSet = list[IamInstanceProfileAssociation] class DescribeIamInstanceProfileAssociationsResult(TypedDict, total=False): - IamInstanceProfileAssociations: Optional[IamInstanceProfileAssociationSet] - NextToken: Optional[NextToken] + IamInstanceProfileAssociations: IamInstanceProfileAssociationSet | None + NextToken: NextToken | None class DescribeIdFormatRequest(ServiceRequest): - Resource: Optional[String] + Resource: String | None class DescribeIdFormatResult(TypedDict, total=False): - Statuses: Optional[IdFormatList] + Statuses: IdFormatList | None class DescribeIdentityIdFormatRequest(ServiceRequest): - Resource: Optional[String] + Resource: String | None PrincipalArn: String class DescribeIdentityIdFormatResult(TypedDict, total=False): - Statuses: Optional[IdFormatList] + Statuses: IdFormatList | None class DescribeImageAttributeRequest(ServiceRequest): Attribute: ImageAttributeName ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None -ImageIdStringList = List[ImageId] -ExecutableByStringList = List[String] +DescribeImageReferencesImageIdStringList = list[ImageId] +ResourceTypeOptionValuesList = list[ResourceTypeOptionValue] -class DescribeImagesRequest(ServiceRequest): - ExecutableUsers: Optional[ExecutableByStringList] - ImageIds: Optional[ImageIdStringList] - Owners: Optional[OwnerStringList] - IncludeDeprecated: Optional[Boolean] - IncludeDisabled: Optional[Boolean] - MaxResults: Optional[Integer] - NextToken: Optional[String] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] +class ResourceTypeOption(TypedDict, total=False): + OptionName: ImageReferenceOptionName | None + OptionValues: ResourceTypeOptionValuesList | None -class Image(TypedDict, total=False): - PlatformDetails: Optional[String] - UsageOperation: Optional[String] - BlockDeviceMappings: Optional[BlockDeviceMappingList] - Description: Optional[String] - EnaSupport: Optional[Boolean] - Hypervisor: Optional[HypervisorType] - ImageOwnerAlias: Optional[String] - Name: Optional[String] - RootDeviceName: Optional[String] - RootDeviceType: Optional[DeviceType] - SriovNetSupport: Optional[String] - StateReason: Optional[StateReason] - Tags: Optional[TagList] - VirtualizationType: Optional[VirtualizationType] - BootMode: Optional[BootModeValues] - TpmSupport: Optional[TpmSupportValues] - DeprecationTime: Optional[String] - ImdsSupport: Optional[ImdsSupportValues] - SourceInstanceId: Optional[String] - DeregistrationProtection: Optional[String] - LastLaunchedTime: Optional[String] - ImageAllowed: Optional[Boolean] - SourceImageId: Optional[String] - SourceImageRegion: Optional[String] - ImageId: Optional[String] - ImageLocation: Optional[String] - State: Optional[ImageState] - OwnerId: Optional[String] - CreationDate: Optional[String] - Public: Optional[Boolean] - ProductCodes: Optional[ProductCodeList] - Architecture: Optional[ArchitectureValues] - ImageType: Optional[ImageTypeValues] - KernelId: Optional[String] - RamdiskId: Optional[String] - Platform: Optional[PlatformValues] - - -ImageList = List[Image] +ResourceTypeOptionList = list[ResourceTypeOption] -class DescribeImagesResult(TypedDict, total=False): - NextToken: Optional[String] - Images: Optional[ImageList] +class ResourceTypeRequest(TypedDict, total=False): + ResourceType: ImageReferenceResourceType | None + ResourceTypeOptions: ResourceTypeOptionList | None -ImportTaskIdList = List[ImportImageTaskId] +ResourceTypeRequestList = list[ResourceTypeRequest] -class DescribeImportImageTasksRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - ImportTaskIds: Optional[ImportTaskIdList] - MaxResults: Optional[Integer] - NextToken: Optional[String] +class DescribeImageReferencesRequest(ServiceRequest): + ImageIds: DescribeImageReferencesImageIdStringList + IncludeAllResourceTypes: Boolean | None + ResourceTypes: ResourceTypeRequestList | None + NextToken: String | None + DryRun: Boolean | None + MaxResults: DescribeImageReferencesMaxResults | None -class ImportImageLicenseConfigurationResponse(TypedDict, total=False): - LicenseConfigurationArn: Optional[String] +class ImageReference(TypedDict, total=False): + ImageId: ImageId | None + ResourceType: ImageReferenceResourceType | None + Arn: String | None -ImportImageLicenseSpecificationListResponse = List[ImportImageLicenseConfigurationResponse] +ImageReferenceList = list[ImageReference] -class UserBucketDetails(TypedDict, total=False): - S3Bucket: Optional[String] - S3Key: Optional[String] +class DescribeImageReferencesResult(TypedDict, total=False): + NextToken: String | None + ImageReferences: ImageReferenceList | None -class SnapshotDetail(TypedDict, total=False): - Description: Optional[String] - DeviceName: Optional[String] - DiskImageSize: Optional[Double] - Format: Optional[String] - Progress: Optional[String] - SnapshotId: Optional[String] - Status: Optional[String] - StatusMessage: Optional[String] - Url: Optional[SensitiveUrl] - UserBucket: Optional[UserBucketDetails] +ImageUsageReportIdStringList = list[ImageUsageReportId] +DescribeImageUsageReportsImageIdStringList = list[ImageId] -SnapshotDetailList = List[SnapshotDetail] +class DescribeImageUsageReportEntriesRequest(ServiceRequest): + ImageIds: DescribeImageUsageReportsImageIdStringList | None + ReportIds: ImageUsageReportIdStringList | None + NextToken: String | None + Filters: FilterList | None + DryRun: Boolean | None + MaxResults: DescribeImageUsageReportEntriesMaxResults | None -class ImportImageTask(TypedDict, total=False): - Architecture: Optional[String] - Description: Optional[String] - Encrypted: Optional[Boolean] - Hypervisor: Optional[String] - ImageId: Optional[String] - ImportTaskId: Optional[String] - KmsKeyId: Optional[String] - LicenseType: Optional[String] - Platform: Optional[String] - Progress: Optional[String] - SnapshotDetails: Optional[SnapshotDetailList] - Status: Optional[String] - StatusMessage: Optional[String] - Tags: Optional[TagList] - LicenseSpecifications: Optional[ImportImageLicenseSpecificationListResponse] - UsageOperation: Optional[String] - BootMode: Optional[BootModeValues] - - -ImportImageTaskList = List[ImportImageTask] +class ImageUsageReportEntry(TypedDict, total=False): + ResourceType: ImageUsageResourceTypeName | None + ReportId: ImageUsageReportId | None + UsageCount: Long | None + AccountId: String | None + ImageId: ImageId | None + ReportCreationTime: MillisecondDateTime | None -class DescribeImportImageTasksResult(TypedDict, total=False): - ImportImageTasks: Optional[ImportImageTaskList] - NextToken: Optional[String] +ImageUsageReportEntryList = list[ImageUsageReportEntry] -ImportSnapshotTaskIdList = List[ImportSnapshotTaskId] +class DescribeImageUsageReportEntriesResult(TypedDict, total=False): + NextToken: String | None + ImageUsageReportEntries: ImageUsageReportEntryList | None -class DescribeImportSnapshotTasksRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - ImportTaskIds: Optional[ImportSnapshotTaskIdList] - MaxResults: Optional[Integer] - NextToken: Optional[String] +class DescribeImageUsageReportsRequest(ServiceRequest): + ImageIds: DescribeImageUsageReportsImageIdStringList | None + ReportIds: ImageUsageReportIdStringList | None + NextToken: String | None + Filters: FilterList | None + DryRun: Boolean | None + MaxResults: DescribeImageUsageReportsMaxResults | None + + +UserIdList = list[String] + + +class ImageUsageResourceTypeOption(TypedDict, total=False): + OptionName: String | None + OptionValues: ImageUsageResourceTypeOptionValuesList | None + + +ImageUsageResourceTypeOptionList = list[ImageUsageResourceTypeOption] + + +class ImageUsageResourceType(TypedDict, total=False): + ResourceType: ImageUsageResourceTypeName | None + ResourceTypeOptions: ImageUsageResourceTypeOptionList | None + + +ImageUsageResourceTypeList = list[ImageUsageResourceType] + + +class ImageUsageReport(TypedDict, total=False): + ImageId: ImageId | None + ReportId: ImageUsageReportId | None + ResourceTypes: ImageUsageResourceTypeList | None + AccountIds: UserIdList | None + State: ImageUsageReportState | None + StateReason: ImageUsageReportStateReason | None + CreationTime: MillisecondDateTime | None + ExpirationTime: MillisecondDateTime | None + Tags: TagList | None + + +ImageUsageReportList = list[ImageUsageReport] + + +class DescribeImageUsageReportsResult(TypedDict, total=False): + NextToken: String | None + ImageUsageReports: ImageUsageReportList | None + + +ImageIdStringList = list[ImageId] +ExecutableByStringList = list[String] + + +class DescribeImagesRequest(ServiceRequest): + ExecutableUsers: ExecutableByStringList | None + ImageIds: ImageIdStringList | None + Owners: OwnerStringList | None + IncludeDeprecated: Boolean | None + IncludeDisabled: Boolean | None + MaxResults: Integer | None + NextToken: String | None + DryRun: Boolean | None + Filters: FilterList | None + + +class Image(TypedDict, total=False): + PlatformDetails: String | None + UsageOperation: String | None + BlockDeviceMappings: BlockDeviceMappingList | None + Description: String | None + EnaSupport: Boolean | None + Hypervisor: HypervisorType | None + ImageOwnerAlias: String | None + Name: String | None + RootDeviceName: String | None + RootDeviceType: DeviceType | None + SriovNetSupport: String | None + StateReason: StateReason | None + Tags: TagList | None + VirtualizationType: VirtualizationType | None + BootMode: BootModeValues | None + TpmSupport: TpmSupportValues | None + DeprecationTime: String | None + ImdsSupport: ImdsSupportValues | None + SourceInstanceId: String | None + DeregistrationProtection: String | None + LastLaunchedTime: String | None + ImageAllowed: Boolean | None + SourceImageId: String | None + SourceImageRegion: String | None + FreeTierEligible: Boolean | None + ImageId: String | None + ImageLocation: String | None + State: ImageState | None + OwnerId: String | None + CreationDate: String | None + Public: Boolean | None + ProductCodes: ProductCodeList | None + Architecture: ArchitectureValues | None + ImageType: ImageTypeValues | None + KernelId: String | None + RamdiskId: String | None + Platform: PlatformValues | None + + +ImageList = list[Image] + + +class DescribeImagesResult(TypedDict, total=False): + NextToken: String | None + Images: ImageList | None + + +ImportTaskIdList = list[ImportImageTaskId] + + +class DescribeImportImageTasksRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + ImportTaskIds: ImportTaskIdList | None + MaxResults: Integer | None + NextToken: String | None + + +class ImportImageLicenseConfigurationResponse(TypedDict, total=False): + LicenseConfigurationArn: String | None + + +ImportImageLicenseSpecificationListResponse = list[ImportImageLicenseConfigurationResponse] + + +class UserBucketDetails(TypedDict, total=False): + S3Bucket: String | None + S3Key: String | None + + +class SnapshotDetail(TypedDict, total=False): + Description: String | None + DeviceName: String | None + DiskImageSize: Double | None + Format: String | None + Progress: String | None + SnapshotId: String | None + Status: String | None + StatusMessage: String | None + Url: SensitiveUrl | None + UserBucket: UserBucketDetails | None + + +SnapshotDetailList = list[SnapshotDetail] + + +class ImportImageTask(TypedDict, total=False): + Architecture: String | None + Description: String | None + Encrypted: Boolean | None + Hypervisor: String | None + ImageId: String | None + ImportTaskId: String | None + KmsKeyId: String | None + LicenseType: String | None + Platform: String | None + Progress: String | None + SnapshotDetails: SnapshotDetailList | None + Status: String | None + StatusMessage: String | None + Tags: TagList | None + LicenseSpecifications: ImportImageLicenseSpecificationListResponse | None + UsageOperation: String | None + BootMode: BootModeValues | None + + +ImportImageTaskList = list[ImportImageTask] + + +class DescribeImportImageTasksResult(TypedDict, total=False): + ImportImageTasks: ImportImageTaskList | None + NextToken: String | None + + +ImportSnapshotTaskIdList = list[ImportSnapshotTaskId] + + +class DescribeImportSnapshotTasksRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + ImportTaskIds: ImportSnapshotTaskIdList | None + MaxResults: Integer | None + NextToken: String | None class SnapshotTaskDetail(TypedDict, total=False): - Description: Optional[String] - DiskImageSize: Optional[Double] - Encrypted: Optional[Boolean] - Format: Optional[String] - KmsKeyId: Optional[String] - Progress: Optional[String] - SnapshotId: Optional[String] - Status: Optional[String] - StatusMessage: Optional[String] - Url: Optional[SensitiveUrl] - UserBucket: Optional[UserBucketDetails] + Description: String | None + DiskImageSize: Double | None + Encrypted: Boolean | None + Format: String | None + KmsKeyId: String | None + Progress: String | None + SnapshotId: String | None + Status: String | None + StatusMessage: String | None + Url: SensitiveUrl | None + UserBucket: UserBucketDetails | None class ImportSnapshotTask(TypedDict, total=False): - Description: Optional[String] - ImportTaskId: Optional[String] - SnapshotTaskDetail: Optional[SnapshotTaskDetail] - Tags: Optional[TagList] + Description: String | None + ImportTaskId: String | None + SnapshotTaskDetail: SnapshotTaskDetail | None + Tags: TagList | None -ImportSnapshotTaskList = List[ImportSnapshotTask] +ImportSnapshotTaskList = list[ImportSnapshotTask] class DescribeImportSnapshotTasksResult(TypedDict, total=False): - ImportSnapshotTasks: Optional[ImportSnapshotTaskList] - NextToken: Optional[String] + ImportSnapshotTasks: ImportSnapshotTaskList | None + NextToken: String | None class DescribeInstanceAttributeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId Attribute: InstanceAttributeName class DescribeInstanceConnectEndpointsRequest(ServiceRequest): - DryRun: Optional[Boolean] - MaxResults: Optional[InstanceConnectEndpointMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - InstanceConnectEndpointIds: Optional[ValueStringList] + DryRun: Boolean | None + MaxResults: InstanceConnectEndpointMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + InstanceConnectEndpointIds: ValueStringList | None -InstanceConnectEndpointSet = List[Ec2InstanceConnectEndpoint] +InstanceConnectEndpointSet = list[Ec2InstanceConnectEndpoint] class DescribeInstanceConnectEndpointsResult(TypedDict, total=False): - InstanceConnectEndpoints: Optional[InstanceConnectEndpointSet] - NextToken: Optional[NextToken] + InstanceConnectEndpoints: InstanceConnectEndpointSet | None + NextToken: NextToken | None class DescribeInstanceCreditSpecificationsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - InstanceIds: Optional[InstanceIdStringList] - MaxResults: Optional[DescribeInstanceCreditSpecificationsMaxResults] - NextToken: Optional[String] + DryRun: Boolean | None + Filters: FilterList | None + InstanceIds: InstanceIdStringList | None + MaxResults: DescribeInstanceCreditSpecificationsMaxResults | None + NextToken: String | None class InstanceCreditSpecification(TypedDict, total=False): - InstanceId: Optional[String] - CpuCredits: Optional[String] + InstanceId: String | None + CpuCredits: String | None -InstanceCreditSpecificationList = List[InstanceCreditSpecification] +InstanceCreditSpecificationList = list[InstanceCreditSpecification] class DescribeInstanceCreditSpecificationsResult(TypedDict, total=False): - InstanceCreditSpecifications: Optional[InstanceCreditSpecificationList] - NextToken: Optional[String] + InstanceCreditSpecifications: InstanceCreditSpecificationList | None + NextToken: String | None class DescribeInstanceEventNotificationAttributesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DescribeInstanceEventNotificationAttributesResult(TypedDict, total=False): - InstanceTagAttribute: Optional[InstanceTagNotificationAttribute] + InstanceTagAttribute: InstanceTagNotificationAttribute | None -InstanceEventWindowIdSet = List[InstanceEventWindowId] +InstanceEventWindowIdSet = list[InstanceEventWindowId] class DescribeInstanceEventWindowsRequest(ServiceRequest): - DryRun: Optional[Boolean] - InstanceEventWindowIds: Optional[InstanceEventWindowIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[ResultRange] - NextToken: Optional[String] + DryRun: Boolean | None + InstanceEventWindowIds: InstanceEventWindowIdSet | None + Filters: FilterList | None + MaxResults: ResultRange | None + NextToken: String | None -InstanceEventWindowSet = List[InstanceEventWindow] +InstanceEventWindowSet = list[InstanceEventWindow] class DescribeInstanceEventWindowsResult(TypedDict, total=False): - InstanceEventWindows: Optional[InstanceEventWindowSet] - NextToken: Optional[String] + InstanceEventWindows: InstanceEventWindowSet | None + NextToken: String | None class DescribeInstanceImageMetadataRequest(ServiceRequest): - Filters: Optional[FilterList] - InstanceIds: Optional[InstanceIdStringList] - MaxResults: Optional[DescribeInstanceImageMetadataMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + InstanceIds: InstanceIdStringList | None + MaxResults: DescribeInstanceImageMetadataMaxResults | None + NextToken: String | None + DryRun: Boolean | None class ImageMetadata(TypedDict, total=False): - ImageId: Optional[ImageId] - Name: Optional[String] - OwnerId: Optional[String] - State: Optional[ImageState] - ImageOwnerAlias: Optional[String] - CreationDate: Optional[String] - DeprecationTime: Optional[String] - ImageAllowed: Optional[Boolean] - IsPublic: Optional[Boolean] + ImageId: ImageId | None + Name: String | None + OwnerId: String | None + State: ImageState | None + ImageOwnerAlias: String | None + CreationDate: String | None + DeprecationTime: String | None + ImageAllowed: Boolean | None + IsPublic: Boolean | None class InstanceState(TypedDict, total=False): - Code: Optional[Integer] - Name: Optional[InstanceStateName] + Code: Integer | None + Name: InstanceStateName | None class InstanceImageMetadata(TypedDict, total=False): - InstanceId: Optional[InstanceId] - InstanceType: Optional[InstanceType] - LaunchTime: Optional[MillisecondDateTime] - AvailabilityZone: Optional[String] - ZoneId: Optional[String] - State: Optional[InstanceState] - OwnerId: Optional[String] - Tags: Optional[TagList] - ImageMetadata: Optional[ImageMetadata] - Operator: Optional[OperatorResponse] + InstanceId: InstanceId | None + InstanceType: InstanceType | None + LaunchTime: MillisecondDateTime | None + AvailabilityZone: String | None + ZoneId: String | None + State: InstanceState | None + OwnerId: String | None + Tags: TagList | None + ImageMetadata: ImageMetadata | None + Operator: OperatorResponse | None -InstanceImageMetadataList = List[InstanceImageMetadata] +InstanceImageMetadataList = list[InstanceImageMetadata] class DescribeInstanceImageMetadataResult(TypedDict, total=False): - InstanceImageMetadata: Optional[InstanceImageMetadataList] - NextToken: Optional[String] + InstanceImageMetadata: InstanceImageMetadataList | None + NextToken: String | None + + +class DescribeInstanceSqlHaHistoryStatesRequest(ServiceRequest): + InstanceIds: InstanceIdStringList | None + StartTime: MillisecondDateTime | None + EndTime: MillisecondDateTime | None + NextToken: NextToken | None + MaxResults: DescribeInstanceSqlHaStatesRequestMaxResultsInteger | None + Filters: FilterList | None + DryRun: Boolean | None + + +class RegisteredInstance(TypedDict, total=False): + InstanceId: InstanceId | None + SqlServerLicenseUsage: SqlServerLicenseUsage | None + HaStatus: HaStatus | None + ProcessingStatus: String | None + LastUpdatedTime: MillisecondDateTime | None + SqlServerCredentials: String | None + Tags: TagList | None + + +RegisteredInstanceList = list[RegisteredInstance] + + +class DescribeInstanceSqlHaHistoryStatesResult(TypedDict, total=False): + Instances: RegisteredInstanceList | None + NextToken: NextToken | None + + +class DescribeInstanceSqlHaStatesRequest(ServiceRequest): + InstanceIds: InstanceIdStringList | None + NextToken: NextToken | None + MaxResults: DescribeInstanceSqlHaStatesRequestMaxResultsInteger | None + Filters: FilterList | None + DryRun: Boolean | None + + +class DescribeInstanceSqlHaStatesResult(TypedDict, total=False): + Instances: RegisteredInstanceList | None + NextToken: NextToken | None class DescribeInstanceStatusRequest(ServiceRequest): - InstanceIds: Optional[InstanceIdStringList] - MaxResults: Optional[Integer] - NextToken: Optional[String] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - IncludeAllInstances: Optional[Boolean] + InstanceIds: InstanceIdStringList | None + MaxResults: Integer | None + NextToken: String | None + DryRun: Boolean | None + Filters: FilterList | None + IncludeAllInstances: Boolean | None class EbsStatusDetails(TypedDict, total=False): - ImpairedSince: Optional[MillisecondDateTime] - Name: Optional[StatusName] - Status: Optional[StatusType] + ImpairedSince: MillisecondDateTime | None + Name: StatusName | None + Status: StatusType | None -EbsStatusDetailsList = List[EbsStatusDetails] +EbsStatusDetailsList = list[EbsStatusDetails] class EbsStatusSummary(TypedDict, total=False): - Details: Optional[EbsStatusDetailsList] - Status: Optional[SummaryStatus] + Details: EbsStatusDetailsList | None + Status: SummaryStatus | None class InstanceStatusDetails(TypedDict, total=False): - ImpairedSince: Optional[DateTime] - Name: Optional[StatusName] - Status: Optional[StatusType] + ImpairedSince: DateTime | None + Name: StatusName | None + Status: StatusType | None -InstanceStatusDetailsList = List[InstanceStatusDetails] +InstanceStatusDetailsList = list[InstanceStatusDetails] class InstanceStatusSummary(TypedDict, total=False): - Details: Optional[InstanceStatusDetailsList] - Status: Optional[SummaryStatus] + Details: InstanceStatusDetailsList | None + Status: SummaryStatus | None class InstanceStatusEvent(TypedDict, total=False): - InstanceEventId: Optional[InstanceEventId] - Code: Optional[EventCode] - Description: Optional[String] - NotAfter: Optional[DateTime] - NotBefore: Optional[DateTime] - NotBeforeDeadline: Optional[DateTime] + InstanceEventId: InstanceEventId | None + Code: EventCode | None + Description: String | None + NotAfter: DateTime | None + NotBefore: DateTime | None + NotBeforeDeadline: DateTime | None -InstanceStatusEventList = List[InstanceStatusEvent] +InstanceStatusEventList = list[InstanceStatusEvent] class InstanceStatus(TypedDict, total=False): - AvailabilityZone: Optional[String] - OutpostArn: Optional[String] - Operator: Optional[OperatorResponse] - Events: Optional[InstanceStatusEventList] - InstanceId: Optional[String] - InstanceState: Optional[InstanceState] - InstanceStatus: Optional[InstanceStatusSummary] - SystemStatus: Optional[InstanceStatusSummary] - AttachedEbsStatus: Optional[EbsStatusSummary] + AvailabilityZone: String | None + AvailabilityZoneId: AvailabilityZoneId | None + OutpostArn: String | None + Operator: OperatorResponse | None + Events: InstanceStatusEventList | None + InstanceId: String | None + InstanceState: InstanceState | None + InstanceStatus: InstanceStatusSummary | None + SystemStatus: InstanceStatusSummary | None + AttachedEbsStatus: EbsStatusSummary | None -InstanceStatusList = List[InstanceStatus] +InstanceStatusList = list[InstanceStatus] class DescribeInstanceStatusResult(TypedDict, total=False): - InstanceStatuses: Optional[InstanceStatusList] - NextToken: Optional[String] + InstanceStatuses: InstanceStatusList | None + NextToken: String | None -DescribeInstanceTopologyGroupNameSet = List[PlacementGroupName] -DescribeInstanceTopologyInstanceIdSet = List[InstanceId] +DescribeInstanceTopologyGroupNameSet = list[PlacementGroupName] +DescribeInstanceTopologyInstanceIdSet = list[InstanceId] class DescribeInstanceTopologyRequest(ServiceRequest): - DryRun: Optional[Boolean] - NextToken: Optional[String] - MaxResults: Optional[DescribeInstanceTopologyMaxResults] - InstanceIds: Optional[DescribeInstanceTopologyInstanceIdSet] - GroupNames: Optional[DescribeInstanceTopologyGroupNameSet] - Filters: Optional[FilterList] + DryRun: Boolean | None + NextToken: String | None + MaxResults: DescribeInstanceTopologyMaxResults | None + InstanceIds: DescribeInstanceTopologyInstanceIdSet | None + GroupNames: DescribeInstanceTopologyGroupNameSet | None + Filters: FilterList | None -NetworkNodesList = List[String] +NetworkNodesList = list[String] class InstanceTopology(TypedDict, total=False): - InstanceId: Optional[String] - InstanceType: Optional[String] - GroupName: Optional[String] - NetworkNodes: Optional[NetworkNodesList] - AvailabilityZone: Optional[String] - ZoneId: Optional[String] - CapacityBlockId: Optional[String] + InstanceId: String | None + InstanceType: String | None + GroupName: String | None + NetworkNodes: NetworkNodesList | None + AvailabilityZone: String | None + ZoneId: String | None + CapacityBlockId: String | None -InstanceSet = List[InstanceTopology] +InstanceSet = list[InstanceTopology] class DescribeInstanceTopologyResult(TypedDict, total=False): - Instances: Optional[InstanceSet] - NextToken: Optional[String] + Instances: InstanceSet | None + NextToken: String | None class DescribeInstanceTypeOfferingsRequest(ServiceRequest): - DryRun: Optional[Boolean] - LocationType: Optional[LocationType] - Filters: Optional[FilterList] - MaxResults: Optional[DITOMaxResults] - NextToken: Optional[NextToken] + DryRun: Boolean | None + LocationType: LocationType | None + Filters: FilterList | None + MaxResults: DITOMaxResults | None + NextToken: NextToken | None class InstanceTypeOffering(TypedDict, total=False): - InstanceType: Optional[InstanceType] - LocationType: Optional[LocationType] - Location: Optional[Location] + InstanceType: InstanceType | None + LocationType: LocationType | None + Location: Location | None -InstanceTypeOfferingsList = List[InstanceTypeOffering] +InstanceTypeOfferingsList = list[InstanceTypeOffering] class DescribeInstanceTypeOfferingsResult(TypedDict, total=False): - InstanceTypeOfferings: Optional[InstanceTypeOfferingsList] - NextToken: Optional[NextToken] + InstanceTypeOfferings: InstanceTypeOfferingsList | None + NextToken: NextToken | None -RequestInstanceTypeList = List[InstanceType] +RequestInstanceTypeList = list[InstanceType] class DescribeInstanceTypesRequest(ServiceRequest): - DryRun: Optional[Boolean] - InstanceTypes: Optional[RequestInstanceTypeList] - Filters: Optional[FilterList] - MaxResults: Optional[DITMaxResults] - NextToken: Optional[NextToken] + DryRun: Boolean | None + InstanceTypes: RequestInstanceTypeList | None + Filters: FilterList | None + MaxResults: DITMaxResults | None + NextToken: NextToken | None class NeuronDeviceMemoryInfo(TypedDict, total=False): - SizeInMiB: Optional[NeuronDeviceMemorySize] + SizeInMiB: NeuronDeviceMemorySize | None class NeuronDeviceCoreInfo(TypedDict, total=False): - Count: Optional[NeuronDeviceCoreCount] - Version: Optional[NeuronDeviceCoreVersion] + Count: NeuronDeviceCoreCount | None + Version: NeuronDeviceCoreVersion | None class NeuronDeviceInfo(TypedDict, total=False): - Count: Optional[NeuronDeviceCount] - Name: Optional[NeuronDeviceName] - CoreInfo: Optional[NeuronDeviceCoreInfo] - MemoryInfo: Optional[NeuronDeviceMemoryInfo] + Count: NeuronDeviceCount | None + Name: NeuronDeviceName | None + CoreInfo: NeuronDeviceCoreInfo | None + MemoryInfo: NeuronDeviceMemoryInfo | None -NeuronDeviceInfoList = List[NeuronDeviceInfo] +NeuronDeviceInfoList = list[NeuronDeviceInfo] class NeuronInfo(TypedDict, total=False): - NeuronDevices: Optional[NeuronDeviceInfoList] - TotalNeuronDeviceMemoryInMiB: Optional[TotalNeuronMemory] + NeuronDevices: NeuronDeviceInfoList | None + TotalNeuronDeviceMemoryInMiB: TotalNeuronMemory | None class MediaDeviceMemoryInfo(TypedDict, total=False): - SizeInMiB: Optional[MediaDeviceMemorySize] + SizeInMiB: MediaDeviceMemorySize | None class MediaDeviceInfo(TypedDict, total=False): - Count: Optional[MediaDeviceCount] - Name: Optional[MediaDeviceName] - Manufacturer: Optional[MediaDeviceManufacturerName] - MemoryInfo: Optional[MediaDeviceMemoryInfo] + Count: MediaDeviceCount | None + Name: MediaDeviceName | None + Manufacturer: MediaDeviceManufacturerName | None + MemoryInfo: MediaDeviceMemoryInfo | None -MediaDeviceInfoList = List[MediaDeviceInfo] +MediaDeviceInfoList = list[MediaDeviceInfo] class MediaAcceleratorInfo(TypedDict, total=False): - Accelerators: Optional[MediaDeviceInfoList] - TotalMediaMemoryInMiB: Optional[TotalMediaMemory] + Accelerators: MediaDeviceInfoList | None + TotalMediaMemoryInMiB: TotalMediaMemory | None -NitroTpmSupportedVersionsList = List[NitroTpmSupportedVersionType] +NitroTpmSupportedVersionsList = list[NitroTpmSupportedVersionType] class NitroTpmInfo(TypedDict, total=False): - SupportedVersions: Optional[NitroTpmSupportedVersionsList] + SupportedVersions: NitroTpmSupportedVersionsList | None class InferenceDeviceMemoryInfo(TypedDict, total=False): - SizeInMiB: Optional[InferenceDeviceMemorySize] + SizeInMiB: InferenceDeviceMemorySize | None class InferenceDeviceInfo(TypedDict, total=False): - Count: Optional[InferenceDeviceCount] - Name: Optional[InferenceDeviceName] - Manufacturer: Optional[InferenceDeviceManufacturerName] - MemoryInfo: Optional[InferenceDeviceMemoryInfo] + Count: InferenceDeviceCount | None + Name: InferenceDeviceName | None + Manufacturer: InferenceDeviceManufacturerName | None + MemoryInfo: InferenceDeviceMemoryInfo | None -InferenceDeviceInfoList = List[InferenceDeviceInfo] +InferenceDeviceInfoList = list[InferenceDeviceInfo] class InferenceAcceleratorInfo(TypedDict, total=False): - Accelerators: Optional[InferenceDeviceInfoList] - TotalInferenceMemoryInMiB: Optional[totalInferenceMemory] + Accelerators: InferenceDeviceInfoList | None + TotalInferenceMemoryInMiB: totalInferenceMemory | None -PlacementGroupStrategyList = List[PlacementGroupStrategy] +PlacementGroupStrategyList = list[PlacementGroupStrategy] class PlacementGroupInfo(TypedDict, total=False): - SupportedStrategies: Optional[PlacementGroupStrategyList] + SupportedStrategies: PlacementGroupStrategyList | None class FpgaDeviceMemoryInfo(TypedDict, total=False): - SizeInMiB: Optional[FpgaDeviceMemorySize] + SizeInMiB: FpgaDeviceMemorySize | None class FpgaDeviceInfo(TypedDict, total=False): - Name: Optional[FpgaDeviceName] - Manufacturer: Optional[FpgaDeviceManufacturerName] - Count: Optional[FpgaDeviceCount] - MemoryInfo: Optional[FpgaDeviceMemoryInfo] + Name: FpgaDeviceName | None + Manufacturer: FpgaDeviceManufacturerName | None + Count: FpgaDeviceCount | None + MemoryInfo: FpgaDeviceMemoryInfo | None -FpgaDeviceInfoList = List[FpgaDeviceInfo] +FpgaDeviceInfoList = list[FpgaDeviceInfo] class FpgaInfo(TypedDict, total=False): - Fpgas: Optional[FpgaDeviceInfoList] - TotalFpgaMemoryInMiB: Optional[totalFpgaMemory] + Fpgas: FpgaDeviceInfoList | None + TotalFpgaMemoryInMiB: totalFpgaMemory | None class GpuDeviceMemoryInfo(TypedDict, total=False): - SizeInMiB: Optional[GpuDeviceMemorySize] + SizeInMiB: GpuDeviceMemorySize | None + + +WorkloadsList = list[Workload] class GpuDeviceInfo(TypedDict, total=False): - Name: Optional[GpuDeviceName] - Manufacturer: Optional[GpuDeviceManufacturerName] - Count: Optional[GpuDeviceCount] - MemoryInfo: Optional[GpuDeviceMemoryInfo] + Name: GpuDeviceName | None + Manufacturer: GpuDeviceManufacturerName | None + Count: GpuDeviceCount | None + LogicalGpuCount: LogicalGpuCount | None + GpuPartitionSize: GpuPartitionSize | None + Workloads: WorkloadsList | None + MemoryInfo: GpuDeviceMemoryInfo | None -GpuDeviceInfoList = List[GpuDeviceInfo] +GpuDeviceInfoList = list[GpuDeviceInfo] class GpuInfo(TypedDict, total=False): - Gpus: Optional[GpuDeviceInfoList] - TotalGpuMemoryInMiB: Optional[totalGpuMemory] + Gpus: GpuDeviceInfoList | None + TotalGpuMemoryInMiB: totalGpuMemory | None class EfaInfo(TypedDict, total=False): - MaximumEfaInterfaces: Optional[MaximumEfaInterfaces] + MaximumEfaInterfaces: MaximumEfaInterfaces | None class NetworkCardInfo(TypedDict, total=False): - NetworkCardIndex: Optional[NetworkCardIndex] - NetworkPerformance: Optional[NetworkPerformance] - MaximumNetworkInterfaces: Optional[MaxNetworkInterfaces] - BaselineBandwidthInGbps: Optional[BaselineBandwidthInGbps] - PeakBandwidthInGbps: Optional[PeakBandwidthInGbps] - DefaultEnaQueueCountPerInterface: Optional[DefaultEnaQueueCountPerInterface] - MaximumEnaQueueCount: Optional[MaximumEnaQueueCount] - MaximumEnaQueueCountPerInterface: Optional[MaximumEnaQueueCountPerInterface] + NetworkCardIndex: NetworkCardIndex | None + NetworkPerformance: NetworkPerformance | None + MaximumNetworkInterfaces: MaxNetworkInterfaces | None + AdditionalFlexibleNetworkInterfaces: AdditionalFlexibleNetworkInterfaces | None + BaselineBandwidthInGbps: BaselineBandwidthInGbps | None + PeakBandwidthInGbps: PeakBandwidthInGbps | None + DefaultEnaQueueCountPerInterface: DefaultEnaQueueCountPerInterface | None + MaximumEnaQueueCount: MaximumEnaQueueCount | None + MaximumEnaQueueCountPerInterface: MaximumEnaQueueCountPerInterface | None -NetworkCardInfoList = List[NetworkCardInfo] +NetworkCardInfoList = list[NetworkCardInfo] class NetworkInfo(TypedDict, total=False): - NetworkPerformance: Optional[NetworkPerformance] - MaximumNetworkInterfaces: Optional[MaxNetworkInterfaces] - MaximumNetworkCards: Optional[MaximumNetworkCards] - DefaultNetworkCardIndex: Optional[DefaultNetworkCardIndex] - NetworkCards: Optional[NetworkCardInfoList] - Ipv4AddressesPerInterface: Optional[MaxIpv4AddrPerInterface] - Ipv6AddressesPerInterface: Optional[MaxIpv6AddrPerInterface] - Ipv6Supported: Optional[Ipv6Flag] - EnaSupport: Optional[EnaSupport] - EfaSupported: Optional[EfaSupportedFlag] - EfaInfo: Optional[EfaInfo] - EncryptionInTransitSupported: Optional[EncryptionInTransitSupported] - EnaSrdSupported: Optional[EnaSrdSupported] - BandwidthWeightings: Optional[BandwidthWeightingTypeList] - FlexibleEnaQueuesSupport: Optional[FlexibleEnaQueuesSupport] + NetworkPerformance: NetworkPerformance | None + MaximumNetworkInterfaces: MaxNetworkInterfaces | None + MaximumNetworkCards: MaximumNetworkCards | None + DefaultNetworkCardIndex: DefaultNetworkCardIndex | None + NetworkCards: NetworkCardInfoList | None + Ipv4AddressesPerInterface: MaxIpv4AddrPerInterface | None + Ipv6AddressesPerInterface: MaxIpv6AddrPerInterface | None + Ipv6Supported: Ipv6Flag | None + EnaSupport: EnaSupport | None + EfaSupported: EfaSupportedFlag | None + EfaInfo: EfaInfo | None + EncryptionInTransitSupported: EncryptionInTransitSupported | None + EnaSrdSupported: EnaSrdSupported | None + BandwidthWeightings: BandwidthWeightingTypeList | None + FlexibleEnaQueuesSupport: FlexibleEnaQueuesSupport | None + SecondaryNetworkSupported: SecondaryNetworkSupportedFlag | None + MaximumSecondaryNetworkInterfaces: MaximumSecondaryNetworkInterfaces | None + Ipv4AddressesPerSecondaryInterface: Ipv4AddressesPerSecondaryInterface | None + + +class EbsCardInfo(TypedDict, total=False): + EbsCardIndex: EbsCardIndex | None + BaselineBandwidthInMbps: BaselineBandwidthInMbps | None + BaselineThroughputInMBps: BaselineThroughputInMBps | None + BaselineIops: BaselineIops | None + MaximumBandwidthInMbps: MaximumBandwidthInMbps | None + MaximumThroughputInMBps: MaximumThroughputInMBps | None + MaximumIops: MaximumIops | None + + +EbsCardInfoList = list[EbsCardInfo] class EbsOptimizedInfo(TypedDict, total=False): - BaselineBandwidthInMbps: Optional[BaselineBandwidthInMbps] - BaselineThroughputInMBps: Optional[BaselineThroughputInMBps] - BaselineIops: Optional[BaselineIops] - MaximumBandwidthInMbps: Optional[MaximumBandwidthInMbps] - MaximumThroughputInMBps: Optional[MaximumThroughputInMBps] - MaximumIops: Optional[MaximumIops] + BaselineBandwidthInMbps: BaselineBandwidthInMbps | None + BaselineThroughputInMBps: BaselineThroughputInMBps | None + BaselineIops: BaselineIops | None + MaximumBandwidthInMbps: MaximumBandwidthInMbps | None + MaximumThroughputInMBps: MaximumThroughputInMBps | None + MaximumIops: MaximumIops | None class EbsInfo(TypedDict, total=False): - EbsOptimizedSupport: Optional[EbsOptimizedSupport] - EncryptionSupport: Optional[EbsEncryptionSupport] - EbsOptimizedInfo: Optional[EbsOptimizedInfo] - NvmeSupport: Optional[EbsNvmeSupport] + EbsOptimizedSupport: EbsOptimizedSupport | None + EncryptionSupport: EbsEncryptionSupport | None + EbsOptimizedInfo: EbsOptimizedInfo | None + NvmeSupport: EbsNvmeSupport | None + MaximumEbsAttachments: MaximumEbsAttachments | None + AttachmentLimitType: AttachmentLimitType | None + MaximumEbsCards: MaximumEbsCards | None + EbsCards: EbsCardInfoList | None DiskSize = int class DiskInfo(TypedDict, total=False): - SizeInGB: Optional[DiskSize] - Count: Optional[DiskCount] - Type: Optional[DiskType] + SizeInGB: DiskSize | None + Count: DiskCount | None + Type: DiskType | None -DiskInfoList = List[DiskInfo] +DiskInfoList = list[DiskInfo] class InstanceStorageInfo(TypedDict, total=False): - TotalSizeInGB: Optional[DiskSize] - Disks: Optional[DiskInfoList] - NvmeSupport: Optional[EphemeralNvmeSupport] - EncryptionSupport: Optional[InstanceStorageEncryptionSupport] + TotalSizeInGB: DiskSize | None + Disks: DiskInfoList | None + NvmeSupport: EphemeralNvmeSupport | None + EncryptionSupport: InstanceStorageEncryptionSupport | None MemorySize = int class MemoryInfo(TypedDict, total=False): - SizeInMiB: Optional[MemorySize] + SizeInMiB: MemorySize | None -ThreadsPerCoreList = List[ThreadsPerCore] +ThreadsPerCoreList = list[ThreadsPerCore] class VCpuInfo(TypedDict, total=False): - DefaultVCpus: Optional[VCpuCount] - DefaultCores: Optional[CoreCount] - DefaultThreadsPerCore: Optional[ThreadsPerCore] - ValidCores: Optional[CoreCountList] - ValidThreadsPerCore: Optional[ThreadsPerCoreList] + DefaultVCpus: VCpuCount | None + DefaultCores: CoreCount | None + DefaultThreadsPerCore: ThreadsPerCore | None + ValidCores: CoreCountList | None + ValidThreadsPerCore: ThreadsPerCoreList | None -SupportedAdditionalProcessorFeatureList = List[SupportedAdditionalProcessorFeature] +SupportedAdditionalProcessorFeatureList = list[SupportedAdditionalProcessorFeature] class ProcessorInfo(TypedDict, total=False): - SupportedArchitectures: Optional[ArchitectureTypeList] - SustainedClockSpeedInGhz: Optional[ProcessorSustainedClockSpeed] - SupportedFeatures: Optional[SupportedAdditionalProcessorFeatureList] - Manufacturer: Optional[CpuManufacturerName] + SupportedArchitectures: ArchitectureTypeList | None + SustainedClockSpeedInGhz: ProcessorSustainedClockSpeed | None + SupportedFeatures: SupportedAdditionalProcessorFeatureList | None + Manufacturer: CpuManufacturerName | None -VirtualizationTypeList = List[VirtualizationType] -RootDeviceTypeList = List[RootDeviceType] -UsageClassTypeList = List[UsageClassType] +VirtualizationTypeList = list[VirtualizationType] +RootDeviceTypeList = list[RootDeviceType] +UsageClassTypeList = list[UsageClassType] class InstanceTypeInfo(TypedDict, total=False): - InstanceType: Optional[InstanceType] - CurrentGeneration: Optional[CurrentGenerationFlag] - FreeTierEligible: Optional[FreeTierEligibleFlag] - SupportedUsageClasses: Optional[UsageClassTypeList] - SupportedRootDeviceTypes: Optional[RootDeviceTypeList] - SupportedVirtualizationTypes: Optional[VirtualizationTypeList] - BareMetal: Optional[BareMetalFlag] - Hypervisor: Optional[InstanceTypeHypervisor] - ProcessorInfo: Optional[ProcessorInfo] - VCpuInfo: Optional[VCpuInfo] - MemoryInfo: Optional[MemoryInfo] - InstanceStorageSupported: Optional[InstanceStorageFlag] - InstanceStorageInfo: Optional[InstanceStorageInfo] - EbsInfo: Optional[EbsInfo] - NetworkInfo: Optional[NetworkInfo] - GpuInfo: Optional[GpuInfo] - FpgaInfo: Optional[FpgaInfo] - PlacementGroupInfo: Optional[PlacementGroupInfo] - InferenceAcceleratorInfo: Optional[InferenceAcceleratorInfo] - HibernationSupported: Optional[HibernationFlag] - BurstablePerformanceSupported: Optional[BurstablePerformanceFlag] - DedicatedHostsSupported: Optional[DedicatedHostFlag] - AutoRecoverySupported: Optional[AutoRecoveryFlag] - SupportedBootModes: Optional[BootModeTypeList] - NitroEnclavesSupport: Optional[NitroEnclavesSupport] - NitroTpmSupport: Optional[NitroTpmSupport] - NitroTpmInfo: Optional[NitroTpmInfo] - MediaAcceleratorInfo: Optional[MediaAcceleratorInfo] - NeuronInfo: Optional[NeuronInfo] - PhcSupport: Optional[PhcSupport] - RebootMigrationSupport: Optional[RebootMigrationSupport] - - -InstanceTypeInfoList = List[InstanceTypeInfo] + InstanceType: InstanceType | None + CurrentGeneration: CurrentGenerationFlag | None + FreeTierEligible: FreeTierEligibleFlag | None + SupportedUsageClasses: UsageClassTypeList | None + SupportedRootDeviceTypes: RootDeviceTypeList | None + SupportedVirtualizationTypes: VirtualizationTypeList | None + BareMetal: BareMetalFlag | None + Hypervisor: InstanceTypeHypervisor | None + ProcessorInfo: ProcessorInfo | None + VCpuInfo: VCpuInfo | None + MemoryInfo: MemoryInfo | None + InstanceStorageSupported: InstanceStorageFlag | None + InstanceStorageInfo: InstanceStorageInfo | None + EbsInfo: EbsInfo | None + NetworkInfo: NetworkInfo | None + GpuInfo: GpuInfo | None + FpgaInfo: FpgaInfo | None + PlacementGroupInfo: PlacementGroupInfo | None + InferenceAcceleratorInfo: InferenceAcceleratorInfo | None + HibernationSupported: HibernationFlag | None + BurstablePerformanceSupported: BurstablePerformanceFlag | None + DedicatedHostsSupported: DedicatedHostFlag | None + AutoRecoverySupported: AutoRecoveryFlag | None + SupportedBootModes: BootModeTypeList | None + NitroEnclavesSupport: NitroEnclavesSupport | None + NitroTpmSupport: NitroTpmSupport | None + NitroTpmInfo: NitroTpmInfo | None + MediaAcceleratorInfo: MediaAcceleratorInfo | None + NeuronInfo: NeuronInfo | None + PhcSupport: PhcSupport | None + RebootMigrationSupport: RebootMigrationSupport | None + + +InstanceTypeInfoList = list[InstanceTypeInfo] class DescribeInstanceTypesResult(TypedDict, total=False): - InstanceTypes: Optional[InstanceTypeInfoList] - NextToken: Optional[NextToken] + InstanceTypes: InstanceTypeInfoList | None + NextToken: NextToken | None class DescribeInstancesRequest(ServiceRequest): - InstanceIds: Optional[InstanceIdStringList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[Integer] + InstanceIds: InstanceIdStringList | None + DryRun: Boolean | None + Filters: FilterList | None + NextToken: String | None + MaxResults: Integer | None class Monitoring(TypedDict, total=False): - State: Optional[MonitoringState] + State: MonitoringState | None + + +class InstanceSecondaryInterfacePrivateIpAddress(TypedDict, total=False): + PrivateIpAddress: String | None + + +InstanceSecondaryInterfacePrivateIpAddressList = list[InstanceSecondaryInterfacePrivateIpAddress] + + +class InstanceSecondaryInterfaceAttachment(TypedDict, total=False): + AttachTime: MillisecondDateTime | None + AttachmentId: String | None + DeleteOnTermination: Boolean | None + DeviceIndex: Integer | None + Status: AttachmentStatus | None + NetworkCardIndex: Integer | None + + +class InstanceSecondaryInterface(TypedDict, total=False): + Attachment: InstanceSecondaryInterfaceAttachment | None + MacAddress: String | None + SecondaryInterfaceId: SecondaryInterfaceId | None + OwnerId: String | None + PrivateIpAddresses: InstanceSecondaryInterfacePrivateIpAddressList | None + SourceDestCheck: Boolean | None + Status: SecondaryInterfaceStatus | None + SecondarySubnetId: SecondarySubnetId | None + SecondaryNetworkId: SecondaryNetworkId | None + InterfaceType: SecondaryInterfaceType | None + + +InstanceSecondaryInterfaceList = list[InstanceSecondaryInterface] class InstanceNetworkPerformanceOptions(TypedDict, total=False): - BandwidthWeighting: Optional[InstanceBandwidthWeighting] + BandwidthWeighting: InstanceBandwidthWeighting | None class InstanceMaintenanceOptions(TypedDict, total=False): - AutoRecovery: Optional[InstanceAutoRecoveryState] - RebootMigration: Optional[InstanceRebootMigrationState] + AutoRecovery: InstanceAutoRecoveryState | None + RebootMigration: InstanceRebootMigrationState | None class PrivateDnsNameOptionsResponse(TypedDict, total=False): - HostnameType: Optional[HostnameType] - EnableResourceNameDnsARecord: Optional[Boolean] - EnableResourceNameDnsAAAARecord: Optional[Boolean] + HostnameType: HostnameType | None + EnableResourceNameDnsARecord: Boolean | None + EnableResourceNameDnsAAAARecord: Boolean | None class EnclaveOptions(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class InstanceMetadataOptionsResponse(TypedDict, total=False): - State: Optional[InstanceMetadataOptionsState] - HttpTokens: Optional[HttpTokensState] - HttpPutResponseHopLimit: Optional[Integer] - HttpEndpoint: Optional[InstanceMetadataEndpointState] - HttpProtocolIpv6: Optional[InstanceMetadataProtocolState] - InstanceMetadataTags: Optional[InstanceMetadataTagsState] + State: InstanceMetadataOptionsState | None + HttpTokens: HttpTokensState | None + HttpPutResponseHopLimit: Integer | None + HttpEndpoint: InstanceMetadataEndpointState | None + HttpProtocolIpv6: InstanceMetadataProtocolState | None + InstanceMetadataTags: InstanceMetadataTagsState | None class LicenseConfiguration(TypedDict, total=False): - LicenseConfigurationArn: Optional[String] + LicenseConfigurationArn: String | None -LicenseList = List[LicenseConfiguration] +LicenseList = list[LicenseConfiguration] class HibernationOptions(TypedDict, total=False): - Configured: Optional[Boolean] + Configured: Boolean | None class InstanceIpv6Prefix(TypedDict, total=False): - Ipv6Prefix: Optional[String] + Ipv6Prefix: String | None -InstanceIpv6PrefixList = List[InstanceIpv6Prefix] +InstanceIpv6PrefixList = list[InstanceIpv6Prefix] class InstanceIpv4Prefix(TypedDict, total=False): - Ipv4Prefix: Optional[String] + Ipv4Prefix: String | None -InstanceIpv4PrefixList = List[InstanceIpv4Prefix] +InstanceIpv4PrefixList = list[InstanceIpv4Prefix] class InstanceNetworkInterfaceAssociation(TypedDict, total=False): - CarrierIp: Optional[String] - CustomerOwnedIp: Optional[String] - IpOwnerId: Optional[String] - PublicDnsName: Optional[String] - PublicIp: Optional[String] + CarrierIp: String | None + CustomerOwnedIp: String | None + IpOwnerId: String | None + PublicDnsName: String | None + PublicIp: String | None class InstancePrivateIpAddress(TypedDict, total=False): - Association: Optional[InstanceNetworkInterfaceAssociation] - Primary: Optional[Boolean] - PrivateDnsName: Optional[String] - PrivateIpAddress: Optional[String] + Association: InstanceNetworkInterfaceAssociation | None + Primary: Boolean | None + PrivateDnsName: String | None + PrivateIpAddress: String | None -InstancePrivateIpAddressList = List[InstancePrivateIpAddress] +InstancePrivateIpAddressList = list[InstancePrivateIpAddress] class InstanceAttachmentEnaSrdUdpSpecification(TypedDict, total=False): - EnaSrdUdpEnabled: Optional[Boolean] + EnaSrdUdpEnabled: Boolean | None class InstanceAttachmentEnaSrdSpecification(TypedDict, total=False): - EnaSrdEnabled: Optional[Boolean] - EnaSrdUdpSpecification: Optional[InstanceAttachmentEnaSrdUdpSpecification] + EnaSrdEnabled: Boolean | None + EnaSrdUdpSpecification: InstanceAttachmentEnaSrdUdpSpecification | None class InstanceNetworkInterfaceAttachment(TypedDict, total=False): - AttachTime: Optional[DateTime] - AttachmentId: Optional[String] - DeleteOnTermination: Optional[Boolean] - DeviceIndex: Optional[Integer] - Status: Optional[AttachmentStatus] - NetworkCardIndex: Optional[Integer] - EnaSrdSpecification: Optional[InstanceAttachmentEnaSrdSpecification] - EnaQueueCount: Optional[Integer] + AttachTime: DateTime | None + AttachmentId: String | None + DeleteOnTermination: Boolean | None + DeviceIndex: Integer | None + Status: AttachmentStatus | None + NetworkCardIndex: Integer | None + EnaSrdSpecification: InstanceAttachmentEnaSrdSpecification | None + EnaQueueCount: Integer | None class InstanceNetworkInterface(TypedDict, total=False): - Association: Optional[InstanceNetworkInterfaceAssociation] - Attachment: Optional[InstanceNetworkInterfaceAttachment] - Description: Optional[String] - Groups: Optional[GroupIdentifierList] - Ipv6Addresses: Optional[InstanceIpv6AddressList] - MacAddress: Optional[String] - NetworkInterfaceId: Optional[String] - OwnerId: Optional[String] - PrivateDnsName: Optional[String] - PrivateIpAddress: Optional[String] - PrivateIpAddresses: Optional[InstancePrivateIpAddressList] - SourceDestCheck: Optional[Boolean] - Status: Optional[NetworkInterfaceStatus] - SubnetId: Optional[String] - VpcId: Optional[String] - InterfaceType: Optional[String] - Ipv4Prefixes: Optional[InstanceIpv4PrefixList] - Ipv6Prefixes: Optional[InstanceIpv6PrefixList] - ConnectionTrackingConfiguration: Optional[ConnectionTrackingSpecificationResponse] - Operator: Optional[OperatorResponse] - - -InstanceNetworkInterfaceList = List[InstanceNetworkInterface] + Association: InstanceNetworkInterfaceAssociation | None + Attachment: InstanceNetworkInterfaceAttachment | None + Description: String | None + Groups: GroupIdentifierList | None + Ipv6Addresses: InstanceIpv6AddressList | None + MacAddress: String | None + NetworkInterfaceId: String | None + OwnerId: String | None + PrivateDnsName: String | None + PrivateIpAddress: String | None + PrivateIpAddresses: InstancePrivateIpAddressList | None + SourceDestCheck: Boolean | None + Status: NetworkInterfaceStatus | None + SubnetId: String | None + VpcId: String | None + InterfaceType: String | None + Ipv4Prefixes: InstanceIpv4PrefixList | None + Ipv6Prefixes: InstanceIpv6PrefixList | None + ConnectionTrackingConfiguration: ConnectionTrackingSpecificationResponse | None + Operator: OperatorResponse | None + + +InstanceNetworkInterfaceList = list[InstanceNetworkInterface] class ElasticInferenceAcceleratorAssociation(TypedDict, total=False): - ElasticInferenceAcceleratorArn: Optional[String] - ElasticInferenceAcceleratorAssociationId: Optional[String] - ElasticInferenceAcceleratorAssociationState: Optional[String] - ElasticInferenceAcceleratorAssociationTime: Optional[DateTime] + ElasticInferenceAcceleratorArn: String | None + ElasticInferenceAcceleratorAssociationId: String | None + ElasticInferenceAcceleratorAssociationState: String | None + ElasticInferenceAcceleratorAssociationTime: DateTime | None -ElasticInferenceAcceleratorAssociationList = List[ElasticInferenceAcceleratorAssociation] +ElasticInferenceAcceleratorAssociationList = list[ElasticInferenceAcceleratorAssociation] class ElasticGpuAssociation(TypedDict, total=False): - ElasticGpuId: Optional[ElasticGpuId] - ElasticGpuAssociationId: Optional[String] - ElasticGpuAssociationState: Optional[String] - ElasticGpuAssociationTime: Optional[String] + ElasticGpuId: ElasticGpuId | None + ElasticGpuAssociationId: String | None + ElasticGpuAssociationState: String | None + ElasticGpuAssociationTime: String | None -ElasticGpuAssociationList = List[ElasticGpuAssociation] +ElasticGpuAssociationList = list[ElasticGpuAssociation] class EbsInstanceBlockDevice(TypedDict, total=False): - AttachTime: Optional[DateTime] - DeleteOnTermination: Optional[Boolean] - Status: Optional[AttachmentStatus] - VolumeId: Optional[String] - AssociatedResource: Optional[String] - VolumeOwnerId: Optional[String] - Operator: Optional[OperatorResponse] + AttachTime: DateTime | None + DeleteOnTermination: Boolean | None + Status: AttachmentStatus | None + VolumeId: String | None + AssociatedResource: String | None + VolumeOwnerId: String | None + Operator: OperatorResponse | None + EbsCardIndex: Integer | None class InstanceBlockDeviceMapping(TypedDict, total=False): - DeviceName: Optional[String] - Ebs: Optional[EbsInstanceBlockDevice] + DeviceName: String | None + Ebs: EbsInstanceBlockDevice | None -InstanceBlockDeviceMappingList = List[InstanceBlockDeviceMapping] +InstanceBlockDeviceMappingList = list[InstanceBlockDeviceMapping] class Instance(TypedDict, total=False): - Architecture: Optional[ArchitectureValues] - BlockDeviceMappings: Optional[InstanceBlockDeviceMappingList] - ClientToken: Optional[String] - EbsOptimized: Optional[Boolean] - EnaSupport: Optional[Boolean] - Hypervisor: Optional[HypervisorType] - IamInstanceProfile: Optional[IamInstanceProfile] - InstanceLifecycle: Optional[InstanceLifecycleType] - ElasticGpuAssociations: Optional[ElasticGpuAssociationList] - ElasticInferenceAcceleratorAssociations: Optional[ElasticInferenceAcceleratorAssociationList] - NetworkInterfaces: Optional[InstanceNetworkInterfaceList] - OutpostArn: Optional[String] - RootDeviceName: Optional[String] - RootDeviceType: Optional[DeviceType] - SecurityGroups: Optional[GroupIdentifierList] - SourceDestCheck: Optional[Boolean] - SpotInstanceRequestId: Optional[String] - SriovNetSupport: Optional[String] - StateReason: Optional[StateReason] - Tags: Optional[TagList] - VirtualizationType: Optional[VirtualizationType] - CpuOptions: Optional[CpuOptions] - CapacityBlockId: Optional[String] - CapacityReservationId: Optional[String] - CapacityReservationSpecification: Optional[CapacityReservationSpecificationResponse] - HibernationOptions: Optional[HibernationOptions] - Licenses: Optional[LicenseList] - MetadataOptions: Optional[InstanceMetadataOptionsResponse] - EnclaveOptions: Optional[EnclaveOptions] - BootMode: Optional[BootModeValues] - PlatformDetails: Optional[String] - UsageOperation: Optional[String] - UsageOperationUpdateTime: Optional[MillisecondDateTime] - PrivateDnsNameOptions: Optional[PrivateDnsNameOptionsResponse] - Ipv6Address: Optional[String] - TpmSupport: Optional[String] - MaintenanceOptions: Optional[InstanceMaintenanceOptions] - CurrentInstanceBootMode: Optional[InstanceBootModeValues] - NetworkPerformanceOptions: Optional[InstanceNetworkPerformanceOptions] - Operator: Optional[OperatorResponse] - InstanceId: Optional[String] - ImageId: Optional[String] - State: Optional[InstanceState] - PrivateDnsName: Optional[String] - PublicDnsName: Optional[String] - StateTransitionReason: Optional[String] - KeyName: Optional[String] - AmiLaunchIndex: Optional[Integer] - ProductCodes: Optional[ProductCodeList] - InstanceType: Optional[InstanceType] - LaunchTime: Optional[DateTime] - Placement: Optional[Placement] - KernelId: Optional[String] - RamdiskId: Optional[String] - Platform: Optional[PlatformValues] - Monitoring: Optional[Monitoring] - SubnetId: Optional[String] - VpcId: Optional[String] - PrivateIpAddress: Optional[String] - PublicIpAddress: Optional[String] - - -InstanceList = List[Instance] + Architecture: ArchitectureValues | None + BlockDeviceMappings: InstanceBlockDeviceMappingList | None + ClientToken: String | None + EbsOptimized: Boolean | None + EnaSupport: Boolean | None + Hypervisor: HypervisorType | None + IamInstanceProfile: IamInstanceProfile | None + InstanceLifecycle: InstanceLifecycleType | None + ElasticGpuAssociations: ElasticGpuAssociationList | None + ElasticInferenceAcceleratorAssociations: ElasticInferenceAcceleratorAssociationList | None + NetworkInterfaces: InstanceNetworkInterfaceList | None + OutpostArn: String | None + RootDeviceName: String | None + RootDeviceType: DeviceType | None + SecurityGroups: GroupIdentifierList | None + SourceDestCheck: Boolean | None + SpotInstanceRequestId: String | None + SriovNetSupport: String | None + StateReason: StateReason | None + Tags: TagList | None + VirtualizationType: VirtualizationType | None + CpuOptions: CpuOptions | None + CapacityBlockId: String | None + CapacityReservationId: String | None + CapacityReservationSpecification: CapacityReservationSpecificationResponse | None + HibernationOptions: HibernationOptions | None + Licenses: LicenseList | None + MetadataOptions: InstanceMetadataOptionsResponse | None + EnclaveOptions: EnclaveOptions | None + BootMode: BootModeValues | None + PlatformDetails: String | None + UsageOperation: String | None + UsageOperationUpdateTime: MillisecondDateTime | None + PrivateDnsNameOptions: PrivateDnsNameOptionsResponse | None + Ipv6Address: String | None + TpmSupport: String | None + MaintenanceOptions: InstanceMaintenanceOptions | None + CurrentInstanceBootMode: InstanceBootModeValues | None + NetworkPerformanceOptions: InstanceNetworkPerformanceOptions | None + Operator: OperatorResponse | None + SecondaryInterfaces: InstanceSecondaryInterfaceList | None + InstanceId: String | None + ImageId: String | None + State: InstanceState | None + PrivateDnsName: String | None + PublicDnsName: String | None + StateTransitionReason: String | None + KeyName: String | None + AmiLaunchIndex: Integer | None + ProductCodes: ProductCodeList | None + InstanceType: InstanceType | None + LaunchTime: DateTime | None + Placement: Placement | None + KernelId: String | None + RamdiskId: String | None + Platform: PlatformValues | None + Monitoring: Monitoring | None + SubnetId: String | None + VpcId: String | None + PrivateIpAddress: String | None + PublicIpAddress: String | None + + +InstanceList = list[Instance] class Reservation(TypedDict, total=False): - ReservationId: Optional[String] - OwnerId: Optional[String] - RequesterId: Optional[String] - Groups: Optional[GroupIdentifierList] - Instances: Optional[InstanceList] + ReservationId: String | None + OwnerId: String | None + RequesterId: String | None + Groups: GroupIdentifierList | None + Instances: InstanceList | None -ReservationList = List[Reservation] +ReservationList = list[Reservation] class DescribeInstancesResult(TypedDict, total=False): - NextToken: Optional[String] - Reservations: Optional[ReservationList] + NextToken: String | None + Reservations: ReservationList | None -InternetGatewayIdList = List[InternetGatewayId] +InternetGatewayIdList = list[InternetGatewayId] class DescribeInternetGatewaysRequest(ServiceRequest): - NextToken: Optional[String] - MaxResults: Optional[DescribeInternetGatewaysMaxResults] - DryRun: Optional[Boolean] - InternetGatewayIds: Optional[InternetGatewayIdList] - Filters: Optional[FilterList] + NextToken: String | None + MaxResults: DescribeInternetGatewaysMaxResults | None + DryRun: Boolean | None + InternetGatewayIds: InternetGatewayIdList | None + Filters: FilterList | None -InternetGatewayList = List[InternetGateway] +InternetGatewayList = list[InternetGateway] class DescribeInternetGatewaysResult(TypedDict, total=False): - InternetGateways: Optional[InternetGatewayList] - NextToken: Optional[String] + InternetGateways: InternetGatewayList | None + NextToken: String | None class DescribeIpamByoasnRequest(ServiceRequest): - DryRun: Optional[Boolean] - MaxResults: Optional[DescribeIpamByoasnMaxResults] - NextToken: Optional[NextToken] + DryRun: Boolean | None + MaxResults: DescribeIpamByoasnMaxResults | None + NextToken: NextToken | None class DescribeIpamByoasnResult(TypedDict, total=False): - Byoasns: Optional[ByoasnSet] - NextToken: Optional[String] + Byoasns: ByoasnSet | None + NextToken: String | None class DescribeIpamExternalResourceVerificationTokensRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[IpamMaxResults] - IpamExternalResourceVerificationTokenIds: Optional[ValueStringList] + DryRun: Boolean | None + Filters: FilterList | None + NextToken: NextToken | None + MaxResults: IpamMaxResults | None + IpamExternalResourceVerificationTokenIds: ValueStringList | None -IpamExternalResourceVerificationTokenSet = List[IpamExternalResourceVerificationToken] +IpamExternalResourceVerificationTokenSet = list[IpamExternalResourceVerificationToken] class DescribeIpamExternalResourceVerificationTokensResult(TypedDict, total=False): - NextToken: Optional[NextToken] - IpamExternalResourceVerificationTokens: Optional[IpamExternalResourceVerificationTokenSet] + NextToken: NextToken | None + IpamExternalResourceVerificationTokens: IpamExternalResourceVerificationTokenSet | None + + +class DescribeIpamPoliciesRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + IpamPolicyIds: ValueStringList | None + + +IpamPolicySet = list[IpamPolicy] + + +class DescribeIpamPoliciesResult(TypedDict, total=False): + NextToken: NextToken | None + IpamPolicies: IpamPolicySet | None class DescribeIpamPoolsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[IpamMaxResults] - NextToken: Optional[NextToken] - IpamPoolIds: Optional[ValueStringList] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + IpamPoolIds: ValueStringList | None -IpamPoolSet = List[IpamPool] +IpamPoolSet = list[IpamPool] class DescribeIpamPoolsResult(TypedDict, total=False): - NextToken: Optional[NextToken] - IpamPools: Optional[IpamPoolSet] + NextToken: NextToken | None + IpamPools: IpamPoolSet | None + + +class DescribeIpamPrefixListResolverTargetsRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + IpamPrefixListResolverTargetIds: ValueStringList | None + IpamPrefixListResolverId: IpamPrefixListResolverId | None + + +IpamPrefixListResolverTargetSet = list[IpamPrefixListResolverTarget] + + +class DescribeIpamPrefixListResolverTargetsResult(TypedDict, total=False): + NextToken: NextToken | None + IpamPrefixListResolverTargets: IpamPrefixListResolverTargetSet | None + + +class DescribeIpamPrefixListResolversRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + IpamPrefixListResolverIds: ValueStringList | None + + +IpamPrefixListResolverSet = list[IpamPrefixListResolver] + + +class DescribeIpamPrefixListResolversResult(TypedDict, total=False): + NextToken: NextToken | None + IpamPrefixListResolvers: IpamPrefixListResolverSet | None class DescribeIpamResourceDiscoveriesRequest(ServiceRequest): - DryRun: Optional[Boolean] - IpamResourceDiscoveryIds: Optional[ValueStringList] - NextToken: Optional[NextToken] - MaxResults: Optional[IpamMaxResults] - Filters: Optional[FilterList] + DryRun: Boolean | None + IpamResourceDiscoveryIds: ValueStringList | None + NextToken: NextToken | None + MaxResults: IpamMaxResults | None + Filters: FilterList | None -IpamResourceDiscoverySet = List[IpamResourceDiscovery] +IpamResourceDiscoverySet = list[IpamResourceDiscovery] class DescribeIpamResourceDiscoveriesResult(TypedDict, total=False): - IpamResourceDiscoveries: Optional[IpamResourceDiscoverySet] - NextToken: Optional[NextToken] + IpamResourceDiscoveries: IpamResourceDiscoverySet | None + NextToken: NextToken | None class DescribeIpamResourceDiscoveryAssociationsRequest(ServiceRequest): - DryRun: Optional[Boolean] - IpamResourceDiscoveryAssociationIds: Optional[ValueStringList] - NextToken: Optional[NextToken] - MaxResults: Optional[IpamMaxResults] - Filters: Optional[FilterList] + DryRun: Boolean | None + IpamResourceDiscoveryAssociationIds: ValueStringList | None + NextToken: NextToken | None + MaxResults: IpamMaxResults | None + Filters: FilterList | None -IpamResourceDiscoveryAssociationSet = List[IpamResourceDiscoveryAssociation] +IpamResourceDiscoveryAssociationSet = list[IpamResourceDiscoveryAssociation] class DescribeIpamResourceDiscoveryAssociationsResult(TypedDict, total=False): - IpamResourceDiscoveryAssociations: Optional[IpamResourceDiscoveryAssociationSet] - NextToken: Optional[NextToken] + IpamResourceDiscoveryAssociations: IpamResourceDiscoveryAssociationSet | None + NextToken: NextToken | None class DescribeIpamScopesRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[IpamMaxResults] - NextToken: Optional[NextToken] - IpamScopeIds: Optional[ValueStringList] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + IpamScopeIds: ValueStringList | None -IpamScopeSet = List[IpamScope] +IpamScopeSet = list[IpamScope] class DescribeIpamScopesResult(TypedDict, total=False): - NextToken: Optional[NextToken] - IpamScopes: Optional[IpamScopeSet] + NextToken: NextToken | None + IpamScopes: IpamScopeSet | None class DescribeIpamsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[IpamMaxResults] - NextToken: Optional[NextToken] - IpamIds: Optional[ValueStringList] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + IpamIds: ValueStringList | None -IpamSet = List[Ipam] +IpamSet = list[Ipam] class DescribeIpamsResult(TypedDict, total=False): - NextToken: Optional[NextToken] - Ipams: Optional[IpamSet] + NextToken: NextToken | None + Ipams: IpamSet | None -Ipv6PoolIdList = List[Ipv6PoolEc2Id] +Ipv6PoolIdList = list[Ipv6PoolEc2Id] class DescribeIpv6PoolsRequest(ServiceRequest): - PoolIds: Optional[Ipv6PoolIdList] - NextToken: Optional[NextToken] - MaxResults: Optional[Ipv6PoolMaxResults] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + PoolIds: Ipv6PoolIdList | None + NextToken: NextToken | None + MaxResults: Ipv6PoolMaxResults | None + DryRun: Boolean | None + Filters: FilterList | None class PoolCidrBlock(TypedDict, total=False): - Cidr: Optional[String] + Cidr: String | None -PoolCidrBlocksSet = List[PoolCidrBlock] +PoolCidrBlocksSet = list[PoolCidrBlock] class Ipv6Pool(TypedDict, total=False): - PoolId: Optional[String] - Description: Optional[String] - PoolCidrBlocks: Optional[PoolCidrBlocksSet] - Tags: Optional[TagList] + PoolId: String | None + Description: String | None + PoolCidrBlocks: PoolCidrBlocksSet | None + Tags: TagList | None -Ipv6PoolSet = List[Ipv6Pool] +Ipv6PoolSet = list[Ipv6Pool] class DescribeIpv6PoolsResult(TypedDict, total=False): - Ipv6Pools: Optional[Ipv6PoolSet] - NextToken: Optional[NextToken] + Ipv6Pools: Ipv6PoolSet | None + NextToken: NextToken | None -KeyPairIdStringList = List[KeyPairId] -KeyNameStringList = List[KeyPairName] +KeyPairIdStringList = list[KeyPairId] +KeyNameStringList = list[KeyPairName] class DescribeKeyPairsRequest(ServiceRequest): - KeyNames: Optional[KeyNameStringList] - KeyPairIds: Optional[KeyPairIdStringList] - IncludePublicKey: Optional[Boolean] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + KeyNames: KeyNameStringList | None + KeyPairIds: KeyPairIdStringList | None + IncludePublicKey: Boolean | None + DryRun: Boolean | None + Filters: FilterList | None class KeyPairInfo(TypedDict, total=False): - KeyPairId: Optional[String] - KeyType: Optional[KeyType] - Tags: Optional[TagList] - PublicKey: Optional[String] - CreateTime: Optional[MillisecondDateTime] - KeyName: Optional[String] - KeyFingerprint: Optional[String] + KeyPairId: String | None + KeyType: KeyType | None + Tags: TagList | None + PublicKey: String | None + CreateTime: MillisecondDateTime | None + KeyName: String | None + KeyFingerprint: String | None -KeyPairList = List[KeyPairInfo] +KeyPairList = list[KeyPairInfo] class DescribeKeyPairsResult(TypedDict, total=False): - KeyPairs: Optional[KeyPairList] + KeyPairs: KeyPairList | None class DescribeLaunchTemplateVersionsRequest(ServiceRequest): - DryRun: Optional[Boolean] - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[LaunchTemplateName] - Versions: Optional[VersionStringList] - MinVersion: Optional[String] - MaxVersion: Optional[String] - NextToken: Optional[String] - MaxResults: Optional[Integer] - Filters: Optional[FilterList] - ResolveAlias: Optional[Boolean] + DryRun: Boolean | None + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: LaunchTemplateName | None + Versions: VersionStringList | None + MinVersion: String | None + MaxVersion: String | None + NextToken: String | None + MaxResults: Integer | None + Filters: FilterList | None + ResolveAlias: Boolean | None -LaunchTemplateVersionSet = List[LaunchTemplateVersion] +LaunchTemplateVersionSet = list[LaunchTemplateVersion] class DescribeLaunchTemplateVersionsResult(TypedDict, total=False): - LaunchTemplateVersions: Optional[LaunchTemplateVersionSet] - NextToken: Optional[String] + LaunchTemplateVersions: LaunchTemplateVersionSet | None + NextToken: String | None -LaunchTemplateNameStringList = List[LaunchTemplateName] -LaunchTemplateIdStringList = List[LaunchTemplateId] +LaunchTemplateNameStringList = list[LaunchTemplateName] +LaunchTemplateIdStringList = list[LaunchTemplateId] class DescribeLaunchTemplatesRequest(ServiceRequest): - DryRun: Optional[Boolean] - LaunchTemplateIds: Optional[LaunchTemplateIdStringList] - LaunchTemplateNames: Optional[LaunchTemplateNameStringList] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[DescribeLaunchTemplatesMaxResults] + DryRun: Boolean | None + LaunchTemplateIds: LaunchTemplateIdStringList | None + LaunchTemplateNames: LaunchTemplateNameStringList | None + Filters: FilterList | None + NextToken: String | None + MaxResults: DescribeLaunchTemplatesMaxResults | None -LaunchTemplateSet = List[LaunchTemplate] +LaunchTemplateSet = list[LaunchTemplate] class DescribeLaunchTemplatesResult(TypedDict, total=False): - LaunchTemplates: Optional[LaunchTemplateSet] - NextToken: Optional[String] + LaunchTemplates: LaunchTemplateSet | None + NextToken: String | None -LocalGatewayRouteTableVirtualInterfaceGroupAssociationIdSet = List[ +LocalGatewayRouteTableVirtualInterfaceGroupAssociationIdSet = list[ LocalGatewayRouteTableVirtualInterfaceGroupAssociationId ] class DescribeLocalGatewayRouteTableVirtualInterfaceGroupAssociationsRequest(ServiceRequest): - LocalGatewayRouteTableVirtualInterfaceGroupAssociationIds: Optional[ - LocalGatewayRouteTableVirtualInterfaceGroupAssociationIdSet - ] - Filters: Optional[FilterList] - MaxResults: Optional[LocalGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + LocalGatewayRouteTableVirtualInterfaceGroupAssociationIds: ( + LocalGatewayRouteTableVirtualInterfaceGroupAssociationIdSet | None + ) + Filters: FilterList | None + MaxResults: LocalGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -LocalGatewayRouteTableVirtualInterfaceGroupAssociationSet = List[ +LocalGatewayRouteTableVirtualInterfaceGroupAssociationSet = list[ LocalGatewayRouteTableVirtualInterfaceGroupAssociation ] class DescribeLocalGatewayRouteTableVirtualInterfaceGroupAssociationsResult(TypedDict, total=False): - LocalGatewayRouteTableVirtualInterfaceGroupAssociations: Optional[ - LocalGatewayRouteTableVirtualInterfaceGroupAssociationSet - ] - NextToken: Optional[String] + LocalGatewayRouteTableVirtualInterfaceGroupAssociations: ( + LocalGatewayRouteTableVirtualInterfaceGroupAssociationSet | None + ) + NextToken: String | None -LocalGatewayRouteTableVpcAssociationIdSet = List[LocalGatewayRouteTableVpcAssociationId] +LocalGatewayRouteTableVpcAssociationIdSet = list[LocalGatewayRouteTableVpcAssociationId] class DescribeLocalGatewayRouteTableVpcAssociationsRequest(ServiceRequest): - LocalGatewayRouteTableVpcAssociationIds: Optional[LocalGatewayRouteTableVpcAssociationIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[LocalGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + LocalGatewayRouteTableVpcAssociationIds: LocalGatewayRouteTableVpcAssociationIdSet | None + Filters: FilterList | None + MaxResults: LocalGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -LocalGatewayRouteTableVpcAssociationSet = List[LocalGatewayRouteTableVpcAssociation] +LocalGatewayRouteTableVpcAssociationSet = list[LocalGatewayRouteTableVpcAssociation] class DescribeLocalGatewayRouteTableVpcAssociationsResult(TypedDict, total=False): - LocalGatewayRouteTableVpcAssociations: Optional[LocalGatewayRouteTableVpcAssociationSet] - NextToken: Optional[String] + LocalGatewayRouteTableVpcAssociations: LocalGatewayRouteTableVpcAssociationSet | None + NextToken: String | None -LocalGatewayRouteTableIdSet = List[LocalGatewayRoutetableId] +LocalGatewayRouteTableIdSet = list[LocalGatewayRoutetableId] class DescribeLocalGatewayRouteTablesRequest(ServiceRequest): - LocalGatewayRouteTableIds: Optional[LocalGatewayRouteTableIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[LocalGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + LocalGatewayRouteTableIds: LocalGatewayRouteTableIdSet | None + Filters: FilterList | None + MaxResults: LocalGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -LocalGatewayRouteTableSet = List[LocalGatewayRouteTable] +LocalGatewayRouteTableSet = list[LocalGatewayRouteTable] class DescribeLocalGatewayRouteTablesResult(TypedDict, total=False): - LocalGatewayRouteTables: Optional[LocalGatewayRouteTableSet] - NextToken: Optional[String] + LocalGatewayRouteTables: LocalGatewayRouteTableSet | None + NextToken: String | None -LocalGatewayVirtualInterfaceGroupIdSet = List[LocalGatewayVirtualInterfaceGroupId] +LocalGatewayVirtualInterfaceGroupIdSet = list[LocalGatewayVirtualInterfaceGroupId] class DescribeLocalGatewayVirtualInterfaceGroupsRequest(ServiceRequest): - LocalGatewayVirtualInterfaceGroupIds: Optional[LocalGatewayVirtualInterfaceGroupIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[LocalGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + LocalGatewayVirtualInterfaceGroupIds: LocalGatewayVirtualInterfaceGroupIdSet | None + Filters: FilterList | None + MaxResults: LocalGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -LocalGatewayVirtualInterfaceGroupSet = List[LocalGatewayVirtualInterfaceGroup] +LocalGatewayVirtualInterfaceGroupSet = list[LocalGatewayVirtualInterfaceGroup] class DescribeLocalGatewayVirtualInterfaceGroupsResult(TypedDict, total=False): - LocalGatewayVirtualInterfaceGroups: Optional[LocalGatewayVirtualInterfaceGroupSet] - NextToken: Optional[String] + LocalGatewayVirtualInterfaceGroups: LocalGatewayVirtualInterfaceGroupSet | None + NextToken: String | None class DescribeLocalGatewayVirtualInterfacesRequest(ServiceRequest): - LocalGatewayVirtualInterfaceIds: Optional[LocalGatewayVirtualInterfaceIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[LocalGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + LocalGatewayVirtualInterfaceIds: LocalGatewayVirtualInterfaceIdSet | None + Filters: FilterList | None + MaxResults: LocalGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -LocalGatewayVirtualInterfaceSet = List[LocalGatewayVirtualInterface] +LocalGatewayVirtualInterfaceSet = list[LocalGatewayVirtualInterface] class DescribeLocalGatewayVirtualInterfacesResult(TypedDict, total=False): - LocalGatewayVirtualInterfaces: Optional[LocalGatewayVirtualInterfaceSet] - NextToken: Optional[String] + LocalGatewayVirtualInterfaces: LocalGatewayVirtualInterfaceSet | None + NextToken: String | None -LocalGatewayIdSet = List[LocalGatewayId] +LocalGatewayIdSet = list[LocalGatewayId] class DescribeLocalGatewaysRequest(ServiceRequest): - LocalGatewayIds: Optional[LocalGatewayIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[LocalGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + LocalGatewayIds: LocalGatewayIdSet | None + Filters: FilterList | None + MaxResults: LocalGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class LocalGateway(TypedDict, total=False): - LocalGatewayId: Optional[LocalGatewayId] - OutpostArn: Optional[String] - OwnerId: Optional[String] - State: Optional[String] - Tags: Optional[TagList] + LocalGatewayId: LocalGatewayId | None + OutpostArn: String | None + OwnerId: String | None + State: String | None + Tags: TagList | None -LocalGatewaySet = List[LocalGateway] +LocalGatewaySet = list[LocalGateway] class DescribeLocalGatewaysResult(TypedDict, total=False): - LocalGateways: Optional[LocalGatewaySet] - NextToken: Optional[String] + LocalGateways: LocalGatewaySet | None + NextToken: String | None -SnapshotIdStringList = List[SnapshotId] +SnapshotIdStringList = list[SnapshotId] class DescribeLockedSnapshotsRequest(ServiceRequest): - Filters: Optional[FilterList] - MaxResults: Optional[DescribeLockedSnapshotsMaxResults] - NextToken: Optional[String] - SnapshotIds: Optional[SnapshotIdStringList] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: DescribeLockedSnapshotsMaxResults | None + NextToken: String | None + SnapshotIds: SnapshotIdStringList | None + DryRun: Boolean | None class LockedSnapshotsInfo(TypedDict, total=False): - OwnerId: Optional[String] - SnapshotId: Optional[String] - LockState: Optional[LockState] - LockDuration: Optional[RetentionPeriodResponseDays] - CoolOffPeriod: Optional[CoolOffPeriodResponseHours] - CoolOffPeriodExpiresOn: Optional[MillisecondDateTime] - LockCreatedOn: Optional[MillisecondDateTime] - LockDurationStartTime: Optional[MillisecondDateTime] - LockExpiresOn: Optional[MillisecondDateTime] + OwnerId: String | None + SnapshotId: String | None + LockState: LockState | None + LockDuration: RetentionPeriodResponseDays | None + CoolOffPeriod: CoolOffPeriodResponseHours | None + CoolOffPeriodExpiresOn: MillisecondDateTime | None + LockCreatedOn: MillisecondDateTime | None + LockDurationStartTime: MillisecondDateTime | None + LockExpiresOn: MillisecondDateTime | None -LockedSnapshotsInfoList = List[LockedSnapshotsInfo] +LockedSnapshotsInfoList = list[LockedSnapshotsInfo] class DescribeLockedSnapshotsResult(TypedDict, total=False): - Snapshots: Optional[LockedSnapshotsInfoList] - NextToken: Optional[String] + Snapshots: LockedSnapshotsInfoList | None + NextToken: String | None class DescribeMacHostsRequest(ServiceRequest): - Filters: Optional[FilterList] - HostIds: Optional[RequestHostIdList] - MaxResults: Optional[DescribeMacHostsRequestMaxResults] - NextToken: Optional[String] + Filters: FilterList | None + HostIds: RequestHostIdList | None + MaxResults: DescribeMacHostsRequestMaxResults | None + NextToken: String | None -MacOSVersionStringList = List[String] +MacOSVersionStringList = list[String] class MacHost(TypedDict, total=False): - HostId: Optional[DedicatedHostId] - MacOSLatestSupportedVersions: Optional[MacOSVersionStringList] + HostId: DedicatedHostId | None + MacOSLatestSupportedVersions: MacOSVersionStringList | None -MacHostList = List[MacHost] +MacHostList = list[MacHost] class DescribeMacHostsResult(TypedDict, total=False): - MacHosts: Optional[MacHostList] - NextToken: Optional[String] + MacHosts: MacHostList | None + NextToken: String | None -MacModificationTaskIdList = List[MacModificationTaskId] +MacModificationTaskIdList = list[MacModificationTaskId] class DescribeMacModificationTasksRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MacModificationTaskIds: Optional[MacModificationTaskIdList] - MaxResults: Optional[DescribeMacModificationTasksMaxResults] - NextToken: Optional[String] + DryRun: Boolean | None + Filters: FilterList | None + MacModificationTaskIds: MacModificationTaskIdList | None + MaxResults: DescribeMacModificationTasksMaxResults | None + NextToken: String | None -MacModificationTaskList = List[MacModificationTask] +MacModificationTaskList = list[MacModificationTask] class DescribeMacModificationTasksResult(TypedDict, total=False): - MacModificationTasks: Optional[MacModificationTaskList] - NextToken: Optional[String] + MacModificationTasks: MacModificationTaskList | None + NextToken: String | None class DescribeManagedPrefixListsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[PrefixListMaxResults] - NextToken: Optional[NextToken] - PrefixListIds: Optional[ValueStringList] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: PrefixListMaxResults | None + NextToken: NextToken | None + PrefixListIds: ValueStringList | None -ManagedPrefixListSet = List[ManagedPrefixList] +ManagedPrefixListSet = list[ManagedPrefixList] class DescribeManagedPrefixListsResult(TypedDict, total=False): - NextToken: Optional[NextToken] - PrefixLists: Optional[ManagedPrefixListSet] + NextToken: NextToken | None + PrefixLists: ManagedPrefixListSet | None class DescribeMovingAddressesRequest(ServiceRequest): - DryRun: Optional[Boolean] - PublicIps: Optional[ValueStringList] - NextToken: Optional[String] - Filters: Optional[FilterList] - MaxResults: Optional[DescribeMovingAddressesMaxResults] + DryRun: Boolean | None + PublicIps: ValueStringList | None + NextToken: String | None + Filters: FilterList | None + MaxResults: DescribeMovingAddressesMaxResults | None class MovingAddressStatus(TypedDict, total=False): - MoveStatus: Optional[MoveStatus] - PublicIp: Optional[String] + MoveStatus: MoveStatus | None + PublicIp: String | None -MovingAddressStatusSet = List[MovingAddressStatus] +MovingAddressStatusSet = list[MovingAddressStatus] class DescribeMovingAddressesResult(TypedDict, total=False): - MovingAddressStatuses: Optional[MovingAddressStatusSet] - NextToken: Optional[String] + MovingAddressStatuses: MovingAddressStatusSet | None + NextToken: String | None -NatGatewayIdStringList = List[NatGatewayId] +NatGatewayIdStringList = list[NatGatewayId] class DescribeNatGatewaysRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filter: Optional[FilterList] - MaxResults: Optional[DescribeNatGatewaysMaxResults] - NatGatewayIds: Optional[NatGatewayIdStringList] - NextToken: Optional[String] + DryRun: Boolean | None + Filter: FilterList | None + MaxResults: DescribeNatGatewaysMaxResults | None + NatGatewayIds: NatGatewayIdStringList | None + NextToken: String | None -NatGatewayList = List[NatGateway] +NatGatewayList = list[NatGateway] class DescribeNatGatewaysResult(TypedDict, total=False): - NatGateways: Optional[NatGatewayList] - NextToken: Optional[String] + NatGateways: NatGatewayList | None + NextToken: String | None -NetworkAclIdStringList = List[NetworkAclId] +NetworkAclIdStringList = list[NetworkAclId] class DescribeNetworkAclsRequest(ServiceRequest): - NextToken: Optional[String] - MaxResults: Optional[DescribeNetworkAclsMaxResults] - DryRun: Optional[Boolean] - NetworkAclIds: Optional[NetworkAclIdStringList] - Filters: Optional[FilterList] + NextToken: String | None + MaxResults: DescribeNetworkAclsMaxResults | None + DryRun: Boolean | None + NetworkAclIds: NetworkAclIdStringList | None + Filters: FilterList | None -NetworkAclList = List[NetworkAcl] +NetworkAclList = list[NetworkAcl] class DescribeNetworkAclsResult(TypedDict, total=False): - NetworkAcls: Optional[NetworkAclList] - NextToken: Optional[String] + NetworkAcls: NetworkAclList | None + NextToken: String | None -NetworkInsightsAccessScopeAnalysisIdList = List[NetworkInsightsAccessScopeAnalysisId] +NetworkInsightsAccessScopeAnalysisIdList = list[NetworkInsightsAccessScopeAnalysisId] class DescribeNetworkInsightsAccessScopeAnalysesRequest(ServiceRequest): - NetworkInsightsAccessScopeAnalysisIds: Optional[NetworkInsightsAccessScopeAnalysisIdList] - NetworkInsightsAccessScopeId: Optional[NetworkInsightsAccessScopeId] - AnalysisStartTimeBegin: Optional[MillisecondDateTime] - AnalysisStartTimeEnd: Optional[MillisecondDateTime] - Filters: Optional[FilterList] - MaxResults: Optional[NetworkInsightsMaxResults] - DryRun: Optional[Boolean] - NextToken: Optional[NextToken] + NetworkInsightsAccessScopeAnalysisIds: NetworkInsightsAccessScopeAnalysisIdList | None + NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId | None + AnalysisStartTimeBegin: MillisecondDateTime | None + AnalysisStartTimeEnd: MillisecondDateTime | None + Filters: FilterList | None + MaxResults: NetworkInsightsMaxResults | None + DryRun: Boolean | None + NextToken: NextToken | None class NetworkInsightsAccessScopeAnalysis(TypedDict, total=False): - NetworkInsightsAccessScopeAnalysisId: Optional[NetworkInsightsAccessScopeAnalysisId] - NetworkInsightsAccessScopeAnalysisArn: Optional[ResourceArn] - NetworkInsightsAccessScopeId: Optional[NetworkInsightsAccessScopeId] - Status: Optional[AnalysisStatus] - StatusMessage: Optional[String] - WarningMessage: Optional[String] - StartDate: Optional[MillisecondDateTime] - EndDate: Optional[MillisecondDateTime] - FindingsFound: Optional[FindingsFound] - AnalyzedEniCount: Optional[Integer] - Tags: Optional[TagList] + NetworkInsightsAccessScopeAnalysisId: NetworkInsightsAccessScopeAnalysisId | None + NetworkInsightsAccessScopeAnalysisArn: ResourceArn | None + NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId | None + Status: AnalysisStatus | None + StatusMessage: String | None + WarningMessage: String | None + StartDate: MillisecondDateTime | None + EndDate: MillisecondDateTime | None + FindingsFound: FindingsFound | None + AnalyzedEniCount: Integer | None + Tags: TagList | None -NetworkInsightsAccessScopeAnalysisList = List[NetworkInsightsAccessScopeAnalysis] +NetworkInsightsAccessScopeAnalysisList = list[NetworkInsightsAccessScopeAnalysis] class DescribeNetworkInsightsAccessScopeAnalysesResult(TypedDict, total=False): - NetworkInsightsAccessScopeAnalyses: Optional[NetworkInsightsAccessScopeAnalysisList] - NextToken: Optional[String] + NetworkInsightsAccessScopeAnalyses: NetworkInsightsAccessScopeAnalysisList | None + NextToken: String | None -NetworkInsightsAccessScopeIdList = List[NetworkInsightsAccessScopeId] +NetworkInsightsAccessScopeIdList = list[NetworkInsightsAccessScopeId] class DescribeNetworkInsightsAccessScopesRequest(ServiceRequest): - NetworkInsightsAccessScopeIds: Optional[NetworkInsightsAccessScopeIdList] - Filters: Optional[FilterList] - MaxResults: Optional[NetworkInsightsMaxResults] - DryRun: Optional[Boolean] - NextToken: Optional[NextToken] + NetworkInsightsAccessScopeIds: NetworkInsightsAccessScopeIdList | None + Filters: FilterList | None + MaxResults: NetworkInsightsMaxResults | None + DryRun: Boolean | None + NextToken: NextToken | None -NetworkInsightsAccessScopeList = List[NetworkInsightsAccessScope] +NetworkInsightsAccessScopeList = list[NetworkInsightsAccessScope] class DescribeNetworkInsightsAccessScopesResult(TypedDict, total=False): - NetworkInsightsAccessScopes: Optional[NetworkInsightsAccessScopeList] - NextToken: Optional[String] + NetworkInsightsAccessScopes: NetworkInsightsAccessScopeList | None + NextToken: String | None -NetworkInsightsAnalysisIdList = List[NetworkInsightsAnalysisId] +NetworkInsightsAnalysisIdList = list[NetworkInsightsAnalysisId] class DescribeNetworkInsightsAnalysesRequest(ServiceRequest): - NetworkInsightsAnalysisIds: Optional[NetworkInsightsAnalysisIdList] - NetworkInsightsPathId: Optional[NetworkInsightsPathId] - AnalysisStartTime: Optional[MillisecondDateTime] - AnalysisEndTime: Optional[MillisecondDateTime] - Filters: Optional[FilterList] - MaxResults: Optional[NetworkInsightsMaxResults] - DryRun: Optional[Boolean] - NextToken: Optional[NextToken] + NetworkInsightsAnalysisIds: NetworkInsightsAnalysisIdList | None + NetworkInsightsPathId: NetworkInsightsPathId | None + AnalysisStartTime: MillisecondDateTime | None + AnalysisEndTime: MillisecondDateTime | None + Filters: FilterList | None + MaxResults: NetworkInsightsMaxResults | None + DryRun: Boolean | None + NextToken: NextToken | None class NetworkInsightsAnalysis(TypedDict, total=False): - NetworkInsightsAnalysisId: Optional[NetworkInsightsAnalysisId] - NetworkInsightsAnalysisArn: Optional[ResourceArn] - NetworkInsightsPathId: Optional[NetworkInsightsPathId] - AdditionalAccounts: Optional[ValueStringList] - FilterInArns: Optional[ArnList] - FilterOutArns: Optional[ArnList] - StartDate: Optional[MillisecondDateTime] - Status: Optional[AnalysisStatus] - StatusMessage: Optional[String] - WarningMessage: Optional[String] - NetworkPathFound: Optional[Boolean] - ForwardPathComponents: Optional[PathComponentList] - ReturnPathComponents: Optional[PathComponentList] - Explanations: Optional[ExplanationList] - AlternatePathHints: Optional[AlternatePathHintList] - SuggestedAccounts: Optional[ValueStringList] - Tags: Optional[TagList] - - -NetworkInsightsAnalysisList = List[NetworkInsightsAnalysis] + NetworkInsightsAnalysisId: NetworkInsightsAnalysisId | None + NetworkInsightsAnalysisArn: ResourceArn | None + NetworkInsightsPathId: NetworkInsightsPathId | None + AdditionalAccounts: ValueStringList | None + FilterInArns: ArnList | None + FilterOutArns: ArnList | None + StartDate: MillisecondDateTime | None + Status: AnalysisStatus | None + StatusMessage: String | None + WarningMessage: String | None + NetworkPathFound: Boolean | None + ForwardPathComponents: PathComponentList | None + ReturnPathComponents: PathComponentList | None + Explanations: ExplanationList | None + AlternatePathHints: AlternatePathHintList | None + SuggestedAccounts: ValueStringList | None + Tags: TagList | None + + +NetworkInsightsAnalysisList = list[NetworkInsightsAnalysis] class DescribeNetworkInsightsAnalysesResult(TypedDict, total=False): - NetworkInsightsAnalyses: Optional[NetworkInsightsAnalysisList] - NextToken: Optional[String] + NetworkInsightsAnalyses: NetworkInsightsAnalysisList | None + NextToken: String | None -NetworkInsightsPathIdList = List[NetworkInsightsPathId] +NetworkInsightsPathIdList = list[NetworkInsightsPathId] class DescribeNetworkInsightsPathsRequest(ServiceRequest): - NetworkInsightsPathIds: Optional[NetworkInsightsPathIdList] - Filters: Optional[FilterList] - MaxResults: Optional[NetworkInsightsMaxResults] - DryRun: Optional[Boolean] - NextToken: Optional[NextToken] + NetworkInsightsPathIds: NetworkInsightsPathIdList | None + Filters: FilterList | None + MaxResults: NetworkInsightsMaxResults | None + DryRun: Boolean | None + NextToken: NextToken | None -NetworkInsightsPathList = List[NetworkInsightsPath] +NetworkInsightsPathList = list[NetworkInsightsPath] class DescribeNetworkInsightsPathsResult(TypedDict, total=False): - NetworkInsightsPaths: Optional[NetworkInsightsPathList] - NextToken: Optional[String] + NetworkInsightsPaths: NetworkInsightsPathList | None + NextToken: String | None class DescribeNetworkInterfaceAttributeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkInterfaceId: NetworkInterfaceId - Attribute: Optional[NetworkInterfaceAttribute] + Attribute: NetworkInterfaceAttribute | None class DescribeNetworkInterfaceAttributeResult(TypedDict, total=False): - Attachment: Optional[NetworkInterfaceAttachment] - Description: Optional[AttributeValue] - Groups: Optional[GroupIdentifierList] - NetworkInterfaceId: Optional[String] - SourceDestCheck: Optional[AttributeBooleanValue] - AssociatePublicIpAddress: Optional[Boolean] + Attachment: NetworkInterfaceAttachment | None + Description: AttributeValue | None + Groups: GroupIdentifierList | None + NetworkInterfaceId: String | None + SourceDestCheck: AttributeBooleanValue | None + AssociatePublicIpAddress: Boolean | None -NetworkInterfacePermissionIdList = List[NetworkInterfacePermissionId] +NetworkInterfacePermissionIdList = list[NetworkInterfacePermissionId] class DescribeNetworkInterfacePermissionsRequest(ServiceRequest): - NetworkInterfacePermissionIds: Optional[NetworkInterfacePermissionIdList] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[DescribeNetworkInterfacePermissionsMaxResults] + NetworkInterfacePermissionIds: NetworkInterfacePermissionIdList | None + Filters: FilterList | None + NextToken: String | None + MaxResults: DescribeNetworkInterfacePermissionsMaxResults | None -NetworkInterfacePermissionList = List[NetworkInterfacePermission] +NetworkInterfacePermissionList = list[NetworkInterfacePermission] class DescribeNetworkInterfacePermissionsResult(TypedDict, total=False): - NetworkInterfacePermissions: Optional[NetworkInterfacePermissionList] - NextToken: Optional[String] + NetworkInterfacePermissions: NetworkInterfacePermissionList | None + NextToken: String | None -NetworkInterfaceIdList = List[NetworkInterfaceId] +NetworkInterfaceIdList = list[NetworkInterfaceId] class DescribeNetworkInterfacesRequest(ServiceRequest): - NextToken: Optional[String] - MaxResults: Optional[DescribeNetworkInterfacesMaxResults] - DryRun: Optional[Boolean] - NetworkInterfaceIds: Optional[NetworkInterfaceIdList] - Filters: Optional[FilterList] + NextToken: String | None + MaxResults: DescribeNetworkInterfacesMaxResults | None + DryRun: Boolean | None + NetworkInterfaceIds: NetworkInterfaceIdList | None + Filters: FilterList | None -NetworkInterfaceList = List[NetworkInterface] +NetworkInterfaceList = list[NetworkInterface] class DescribeNetworkInterfacesResult(TypedDict, total=False): - NetworkInterfaces: Optional[NetworkInterfaceList] - NextToken: Optional[String] + NetworkInterfaces: NetworkInterfaceList | None + NextToken: String | None -OutpostLagIdSet = List[OutpostLagId] +OutpostLagIdSet = list[OutpostLagId] class DescribeOutpostLagsRequest(ServiceRequest): - OutpostLagIds: Optional[OutpostLagIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[OutpostLagMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + OutpostLagIds: OutpostLagIdSet | None + Filters: FilterList | None + MaxResults: OutpostLagMaxResults | None + NextToken: String | None + DryRun: Boolean | None -ServiceLinkVirtualInterfaceIdSet = List[ServiceLinkVirtualInterfaceId] +ServiceLinkVirtualInterfaceIdSet = list[ServiceLinkVirtualInterfaceId] class OutpostLag(TypedDict, total=False): - OutpostArn: Optional[String] - OwnerId: Optional[String] - State: Optional[String] - OutpostLagId: Optional[OutpostLagId] - LocalGatewayVirtualInterfaceIds: Optional[LocalGatewayVirtualInterfaceIdSet] - ServiceLinkVirtualInterfaceIds: Optional[ServiceLinkVirtualInterfaceIdSet] - Tags: Optional[TagList] + OutpostArn: String | None + OwnerId: String | None + State: String | None + OutpostLagId: OutpostLagId | None + LocalGatewayVirtualInterfaceIds: LocalGatewayVirtualInterfaceIdSet | None + ServiceLinkVirtualInterfaceIds: ServiceLinkVirtualInterfaceIdSet | None + Tags: TagList | None -OutpostLagSet = List[OutpostLag] +OutpostLagSet = list[OutpostLag] class DescribeOutpostLagsResult(TypedDict, total=False): - OutpostLags: Optional[OutpostLagSet] - NextToken: Optional[String] + OutpostLags: OutpostLagSet | None + NextToken: String | None -PlacementGroupStringList = List[PlacementGroupName] -PlacementGroupIdStringList = List[PlacementGroupId] +PlacementGroupStringList = list[PlacementGroupName] +PlacementGroupIdStringList = list[PlacementGroupId] class DescribePlacementGroupsRequest(ServiceRequest): - GroupIds: Optional[PlacementGroupIdStringList] - DryRun: Optional[Boolean] - GroupNames: Optional[PlacementGroupStringList] - Filters: Optional[FilterList] + GroupIds: PlacementGroupIdStringList | None + DryRun: Boolean | None + GroupNames: PlacementGroupStringList | None + Filters: FilterList | None -PlacementGroupList = List[PlacementGroup] +PlacementGroupList = list[PlacementGroup] class DescribePlacementGroupsResult(TypedDict, total=False): - PlacementGroups: Optional[PlacementGroupList] + PlacementGroups: PlacementGroupList | None -PrefixListResourceIdStringList = List[PrefixListResourceId] +PrefixListResourceIdStringList = list[PrefixListResourceId] class DescribePrefixListsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] - PrefixListIds: Optional[PrefixListResourceIdStringList] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None + PrefixListIds: PrefixListResourceIdStringList | None class PrefixList(TypedDict, total=False): - Cidrs: Optional[ValueStringList] - PrefixListId: Optional[String] - PrefixListName: Optional[String] + Cidrs: ValueStringList | None + PrefixListId: String | None + PrefixListName: String | None -PrefixListSet = List[PrefixList] +PrefixListSet = list[PrefixList] class DescribePrefixListsResult(TypedDict, total=False): - NextToken: Optional[String] - PrefixLists: Optional[PrefixListSet] + NextToken: String | None + PrefixLists: PrefixListSet | None -ResourceList = List[String] +ResourceList = list[String] class DescribePrincipalIdFormatRequest(ServiceRequest): - DryRun: Optional[Boolean] - Resources: Optional[ResourceList] - MaxResults: Optional[DescribePrincipalIdFormatMaxResults] - NextToken: Optional[String] + DryRun: Boolean | None + Resources: ResourceList | None + MaxResults: DescribePrincipalIdFormatMaxResults | None + NextToken: String | None class PrincipalIdFormat(TypedDict, total=False): - Arn: Optional[String] - Statuses: Optional[IdFormatList] + Arn: String | None + Statuses: IdFormatList | None -PrincipalIdFormatList = List[PrincipalIdFormat] +PrincipalIdFormatList = list[PrincipalIdFormat] class DescribePrincipalIdFormatResult(TypedDict, total=False): - Principals: Optional[PrincipalIdFormatList] - NextToken: Optional[String] + Principals: PrincipalIdFormatList | None + NextToken: String | None -PublicIpv4PoolIdStringList = List[Ipv4PoolEc2Id] +PublicIpv4PoolIdStringList = list[Ipv4PoolEc2Id] class DescribePublicIpv4PoolsRequest(ServiceRequest): - PoolIds: Optional[PublicIpv4PoolIdStringList] - NextToken: Optional[NextToken] - MaxResults: Optional[PoolMaxResults] - Filters: Optional[FilterList] + PoolIds: PublicIpv4PoolIdStringList | None + NextToken: NextToken | None + MaxResults: PoolMaxResults | None + Filters: FilterList | None class PublicIpv4PoolRange(TypedDict, total=False): - FirstAddress: Optional[String] - LastAddress: Optional[String] - AddressCount: Optional[Integer] - AvailableAddressCount: Optional[Integer] + FirstAddress: String | None + LastAddress: String | None + AddressCount: Integer | None + AvailableAddressCount: Integer | None -PublicIpv4PoolRangeSet = List[PublicIpv4PoolRange] +PublicIpv4PoolRangeSet = list[PublicIpv4PoolRange] class PublicIpv4Pool(TypedDict, total=False): - PoolId: Optional[String] - Description: Optional[String] - PoolAddressRanges: Optional[PublicIpv4PoolRangeSet] - TotalAddressCount: Optional[Integer] - TotalAvailableAddressCount: Optional[Integer] - NetworkBorderGroup: Optional[String] - Tags: Optional[TagList] + PoolId: String | None + Description: String | None + PoolAddressRanges: PublicIpv4PoolRangeSet | None + TotalAddressCount: Integer | None + TotalAvailableAddressCount: Integer | None + NetworkBorderGroup: String | None + Tags: TagList | None -PublicIpv4PoolSet = List[PublicIpv4Pool] +PublicIpv4PoolSet = list[PublicIpv4Pool] class DescribePublicIpv4PoolsResult(TypedDict, total=False): - PublicIpv4Pools: Optional[PublicIpv4PoolSet] - NextToken: Optional[String] + PublicIpv4Pools: PublicIpv4PoolSet | None + NextToken: String | None -RegionNameStringList = List[String] +RegionNameStringList = list[String] class DescribeRegionsRequest(ServiceRequest): - RegionNames: Optional[RegionNameStringList] - AllRegions: Optional[Boolean] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + RegionNames: RegionNameStringList | None + AllRegions: Boolean | None + DryRun: Boolean | None + Filters: FilterList | None + + +class RegionGeography(TypedDict, total=False): + Name: String | None + + +RegionGeographyList = list[RegionGeography] class Region(TypedDict, total=False): - OptInStatus: Optional[String] - RegionName: Optional[String] - Endpoint: Optional[String] + OptInStatus: String | None + Geography: RegionGeographyList | None + RegionName: String | None + Endpoint: String | None -RegionList = List[Region] +RegionList = list[Region] class DescribeRegionsResult(TypedDict, total=False): - Regions: Optional[RegionList] + Regions: RegionList | None -ReplaceRootVolumeTaskIds = List[ReplaceRootVolumeTaskId] +ReplaceRootVolumeTaskIds = list[ReplaceRootVolumeTaskId] class DescribeReplaceRootVolumeTasksRequest(ServiceRequest): - ReplaceRootVolumeTaskIds: Optional[ReplaceRootVolumeTaskIds] - Filters: Optional[FilterList] - MaxResults: Optional[DescribeReplaceRootVolumeTasksMaxResults] - NextToken: Optional[NextToken] - DryRun: Optional[Boolean] + ReplaceRootVolumeTaskIds: ReplaceRootVolumeTaskIds | None + Filters: FilterList | None + MaxResults: DescribeReplaceRootVolumeTasksMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None -ReplaceRootVolumeTasks = List[ReplaceRootVolumeTask] +ReplaceRootVolumeTasks = list[ReplaceRootVolumeTask] class DescribeReplaceRootVolumeTasksResult(TypedDict, total=False): - ReplaceRootVolumeTasks: Optional[ReplaceRootVolumeTasks] - NextToken: Optional[String] + ReplaceRootVolumeTasks: ReplaceRootVolumeTasks | None + NextToken: String | None class DescribeReservedInstancesListingsRequest(ServiceRequest): - ReservedInstancesId: Optional[ReservationId] - ReservedInstancesListingId: Optional[ReservedInstancesListingId] - Filters: Optional[FilterList] + ReservedInstancesId: ReservationId | None + ReservedInstancesListingId: ReservedInstancesListingId | None + Filters: FilterList | None class DescribeReservedInstancesListingsResult(TypedDict, total=False): - ReservedInstancesListings: Optional[ReservedInstancesListingList] + ReservedInstancesListings: ReservedInstancesListingList | None -ReservedInstancesModificationIdStringList = List[ReservedInstancesModificationId] +ReservedInstancesModificationIdStringList = list[ReservedInstancesModificationId] class DescribeReservedInstancesModificationsRequest(ServiceRequest): - ReservedInstancesModificationIds: Optional[ReservedInstancesModificationIdStringList] - NextToken: Optional[String] - Filters: Optional[FilterList] + ReservedInstancesModificationIds: ReservedInstancesModificationIdStringList | None + NextToken: String | None + Filters: FilterList | None class ReservedInstancesId(TypedDict, total=False): - ReservedInstancesId: Optional[String] + ReservedInstancesId: String | None -ReservedIntancesIds = List[ReservedInstancesId] +ReservedIntancesIds = list[ReservedInstancesId] class ReservedInstancesConfiguration(TypedDict, total=False): - AvailabilityZone: Optional[String] - InstanceCount: Optional[Integer] - InstanceType: Optional[InstanceType] - Platform: Optional[String] - Scope: Optional[scope] - AvailabilityZoneId: Optional[String] + AvailabilityZone: String | None + InstanceCount: Integer | None + InstanceType: InstanceType | None + Platform: String | None + Scope: scope | None + AvailabilityZoneId: String | None class ReservedInstancesModificationResult(TypedDict, total=False): - ReservedInstancesId: Optional[String] - TargetConfiguration: Optional[ReservedInstancesConfiguration] + ReservedInstancesId: String | None + TargetConfiguration: ReservedInstancesConfiguration | None -ReservedInstancesModificationResultList = List[ReservedInstancesModificationResult] +ReservedInstancesModificationResultList = list[ReservedInstancesModificationResult] class ReservedInstancesModification(TypedDict, total=False): - ClientToken: Optional[String] - CreateDate: Optional[DateTime] - EffectiveDate: Optional[DateTime] - ModificationResults: Optional[ReservedInstancesModificationResultList] - ReservedInstancesIds: Optional[ReservedIntancesIds] - ReservedInstancesModificationId: Optional[String] - Status: Optional[String] - StatusMessage: Optional[String] - UpdateDate: Optional[DateTime] + ClientToken: String | None + CreateDate: DateTime | None + EffectiveDate: DateTime | None + ModificationResults: ReservedInstancesModificationResultList | None + ReservedInstancesIds: ReservedIntancesIds | None + ReservedInstancesModificationId: String | None + Status: String | None + StatusMessage: String | None + UpdateDate: DateTime | None -ReservedInstancesModificationList = List[ReservedInstancesModification] +ReservedInstancesModificationList = list[ReservedInstancesModification] class DescribeReservedInstancesModificationsResult(TypedDict, total=False): - NextToken: Optional[String] - ReservedInstancesModifications: Optional[ReservedInstancesModificationList] + NextToken: String | None + ReservedInstancesModifications: ReservedInstancesModificationList | None -ReservedInstancesOfferingIdStringList = List[ReservedInstancesOfferingId] +ReservedInstancesOfferingIdStringList = list[ReservedInstancesOfferingId] class DescribeReservedInstancesOfferingsRequest(ServiceRequest): - AvailabilityZone: Optional[String] - IncludeMarketplace: Optional[Boolean] - InstanceType: Optional[InstanceType] - MaxDuration: Optional[Long] - MaxInstanceCount: Optional[Integer] - MinDuration: Optional[Long] - OfferingClass: Optional[OfferingClassType] - ProductDescription: Optional[RIProductDescription] - ReservedInstancesOfferingIds: Optional[ReservedInstancesOfferingIdStringList] - AvailabilityZoneId: Optional[AvailabilityZoneId] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - InstanceTenancy: Optional[Tenancy] - OfferingType: Optional[OfferingTypeValues] - NextToken: Optional[String] - MaxResults: Optional[Integer] + AvailabilityZone: String | None + IncludeMarketplace: Boolean | None + InstanceType: InstanceType | None + MaxDuration: Long | None + MaxInstanceCount: Integer | None + MinDuration: Long | None + OfferingClass: OfferingClassType | None + ProductDescription: RIProductDescription | None + ReservedInstancesOfferingIds: ReservedInstancesOfferingIdStringList | None + AvailabilityZoneId: AvailabilityZoneId | None + DryRun: Boolean | None + Filters: FilterList | None + InstanceTenancy: Tenancy | None + OfferingType: OfferingTypeValues | None + NextToken: String | None + MaxResults: Integer | None class RecurringCharge(TypedDict, total=False): - Amount: Optional[Double] - Frequency: Optional[RecurringChargeFrequency] + Amount: Double | None + Frequency: RecurringChargeFrequency | None -RecurringChargesList = List[RecurringCharge] +RecurringChargesList = list[RecurringCharge] class PricingDetail(TypedDict, total=False): - Count: Optional[Integer] - Price: Optional[Double] + Count: Integer | None + Price: Double | None -PricingDetailsList = List[PricingDetail] +PricingDetailsList = list[PricingDetail] class ReservedInstancesOffering(TypedDict, total=False): - CurrencyCode: Optional[CurrencyCodeValues] - InstanceTenancy: Optional[Tenancy] - Marketplace: Optional[Boolean] - OfferingClass: Optional[OfferingClassType] - OfferingType: Optional[OfferingTypeValues] - PricingDetails: Optional[PricingDetailsList] - RecurringCharges: Optional[RecurringChargesList] - Scope: Optional[scope] - AvailabilityZoneId: Optional[AvailabilityZoneId] - ReservedInstancesOfferingId: Optional[String] - InstanceType: Optional[InstanceType] - AvailabilityZone: Optional[String] - Duration: Optional[Long] - UsagePrice: Optional[Float] - FixedPrice: Optional[Float] - ProductDescription: Optional[RIProductDescription] - - -ReservedInstancesOfferingList = List[ReservedInstancesOffering] + CurrencyCode: CurrencyCodeValues | None + InstanceTenancy: Tenancy | None + Marketplace: Boolean | None + OfferingClass: OfferingClassType | None + OfferingType: OfferingTypeValues | None + PricingDetails: PricingDetailsList | None + RecurringCharges: RecurringChargesList | None + Scope: scope | None + AvailabilityZoneId: AvailabilityZoneId | None + ReservedInstancesOfferingId: String | None + InstanceType: InstanceType | None + AvailabilityZone: String | None + Duration: Long | None + UsagePrice: Float | None + FixedPrice: Float | None + ProductDescription: RIProductDescription | None + + +ReservedInstancesOfferingList = list[ReservedInstancesOffering] class DescribeReservedInstancesOfferingsResult(TypedDict, total=False): - NextToken: Optional[String] - ReservedInstancesOfferings: Optional[ReservedInstancesOfferingList] + NextToken: String | None + ReservedInstancesOfferings: ReservedInstancesOfferingList | None -ReservedInstancesIdStringList = List[ReservationId] +ReservedInstancesIdStringList = list[ReservationId] class DescribeReservedInstancesRequest(ServiceRequest): - OfferingClass: Optional[OfferingClassType] - ReservedInstancesIds: Optional[ReservedInstancesIdStringList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - OfferingType: Optional[OfferingTypeValues] + OfferingClass: OfferingClassType | None + ReservedInstancesIds: ReservedInstancesIdStringList | None + DryRun: Boolean | None + Filters: FilterList | None + OfferingType: OfferingTypeValues | None class ReservedInstances(TypedDict, total=False): - CurrencyCode: Optional[CurrencyCodeValues] - InstanceTenancy: Optional[Tenancy] - OfferingClass: Optional[OfferingClassType] - OfferingType: Optional[OfferingTypeValues] - RecurringCharges: Optional[RecurringChargesList] - Scope: Optional[scope] - Tags: Optional[TagList] - AvailabilityZoneId: Optional[String] - ReservedInstancesId: Optional[String] - InstanceType: Optional[InstanceType] - AvailabilityZone: Optional[String] - Start: Optional[DateTime] - End: Optional[DateTime] - Duration: Optional[Long] - UsagePrice: Optional[Float] - FixedPrice: Optional[Float] - InstanceCount: Optional[Integer] - ProductDescription: Optional[RIProductDescription] - State: Optional[ReservedInstanceState] - - -ReservedInstancesList = List[ReservedInstances] + CurrencyCode: CurrencyCodeValues | None + InstanceTenancy: Tenancy | None + OfferingClass: OfferingClassType | None + OfferingType: OfferingTypeValues | None + RecurringCharges: RecurringChargesList | None + Scope: scope | None + Tags: TagList | None + AvailabilityZoneId: String | None + ReservedInstancesId: String | None + InstanceType: InstanceType | None + AvailabilityZone: String | None + Start: DateTime | None + End: DateTime | None + Duration: Long | None + UsagePrice: Float | None + FixedPrice: Float | None + InstanceCount: Integer | None + ProductDescription: RIProductDescription | None + State: ReservedInstanceState | None + + +ReservedInstancesList = list[ReservedInstances] class DescribeReservedInstancesResult(TypedDict, total=False): - ReservedInstances: Optional[ReservedInstancesList] + ReservedInstances: ReservedInstancesList | None -RouteServerEndpointIdsList = List[RouteServerEndpointId] +RouteServerEndpointIdsList = list[RouteServerEndpointId] class DescribeRouteServerEndpointsRequest(ServiceRequest): - RouteServerEndpointIds: Optional[RouteServerEndpointIdsList] - NextToken: Optional[String] - MaxResults: Optional[RouteServerMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + RouteServerEndpointIds: RouteServerEndpointIdsList | None + NextToken: String | None + MaxResults: RouteServerMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None -RouteServerEndpointsList = List[RouteServerEndpoint] +RouteServerEndpointsList = list[RouteServerEndpoint] class DescribeRouteServerEndpointsResult(TypedDict, total=False): - RouteServerEndpoints: Optional[RouteServerEndpointsList] - NextToken: Optional[String] + RouteServerEndpoints: RouteServerEndpointsList | None + NextToken: String | None -RouteServerPeerIdsList = List[RouteServerPeerId] +RouteServerPeerIdsList = list[RouteServerPeerId] class DescribeRouteServerPeersRequest(ServiceRequest): - RouteServerPeerIds: Optional[RouteServerPeerIdsList] - NextToken: Optional[String] - MaxResults: Optional[RouteServerMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + RouteServerPeerIds: RouteServerPeerIdsList | None + NextToken: String | None + MaxResults: RouteServerMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None -RouteServerPeersList = List[RouteServerPeer] +RouteServerPeersList = list[RouteServerPeer] class DescribeRouteServerPeersResult(TypedDict, total=False): - RouteServerPeers: Optional[RouteServerPeersList] - NextToken: Optional[String] + RouteServerPeers: RouteServerPeersList | None + NextToken: String | None -RouteServerIdsList = List[RouteServerId] +RouteServerIdsList = list[RouteServerId] class DescribeRouteServersRequest(ServiceRequest): - RouteServerIds: Optional[RouteServerIdsList] - NextToken: Optional[String] - MaxResults: Optional[RouteServerMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + RouteServerIds: RouteServerIdsList | None + NextToken: String | None + MaxResults: RouteServerMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None -RouteServersList = List[RouteServer] +RouteServersList = list[RouteServer] class DescribeRouteServersResult(TypedDict, total=False): - RouteServers: Optional[RouteServersList] - NextToken: Optional[String] + RouteServers: RouteServersList | None + NextToken: String | None -RouteTableIdStringList = List[RouteTableId] +RouteTableIdStringList = list[RouteTableId] class DescribeRouteTablesRequest(ServiceRequest): - NextToken: Optional[String] - MaxResults: Optional[DescribeRouteTablesMaxResults] - DryRun: Optional[Boolean] - RouteTableIds: Optional[RouteTableIdStringList] - Filters: Optional[FilterList] + NextToken: String | None + MaxResults: DescribeRouteTablesMaxResults | None + DryRun: Boolean | None + RouteTableIds: RouteTableIdStringList | None + Filters: FilterList | None -RouteTableList = List[RouteTable] +RouteTableList = list[RouteTable] class DescribeRouteTablesResult(TypedDict, total=False): - RouteTables: Optional[RouteTableList] - NextToken: Optional[String] + RouteTables: RouteTableList | None + NextToken: String | None -OccurrenceDayRequestSet = List[Integer] +OccurrenceDayRequestSet = list[Integer] class ScheduledInstanceRecurrenceRequest(TypedDict, total=False): - Frequency: Optional[String] - Interval: Optional[Integer] - OccurrenceDays: Optional[OccurrenceDayRequestSet] - OccurrenceRelativeToEnd: Optional[Boolean] - OccurrenceUnit: Optional[String] + Frequency: String | None + Interval: Integer | None + OccurrenceDays: OccurrenceDayRequestSet | None + OccurrenceRelativeToEnd: Boolean | None + OccurrenceUnit: String | None class SlotDateTimeRangeRequest(TypedDict, total=False): @@ -14558,538 +16442,633 @@ class SlotDateTimeRangeRequest(TypedDict, total=False): class DescribeScheduledInstanceAvailabilityRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + DryRun: Boolean | None + Filters: FilterList | None FirstSlotStartTimeRange: SlotDateTimeRangeRequest - MaxResults: Optional[DescribeScheduledInstanceAvailabilityMaxResults] - MaxSlotDurationInHours: Optional[Integer] - MinSlotDurationInHours: Optional[Integer] - NextToken: Optional[String] + MaxResults: DescribeScheduledInstanceAvailabilityMaxResults | None + MaxSlotDurationInHours: Integer | None + MinSlotDurationInHours: Integer | None + NextToken: String | None Recurrence: ScheduledInstanceRecurrenceRequest -OccurrenceDaySet = List[Integer] +OccurrenceDaySet = list[Integer] class ScheduledInstanceRecurrence(TypedDict, total=False): - Frequency: Optional[String] - Interval: Optional[Integer] - OccurrenceDaySet: Optional[OccurrenceDaySet] - OccurrenceRelativeToEnd: Optional[Boolean] - OccurrenceUnit: Optional[String] + Frequency: String | None + Interval: Integer | None + OccurrenceDaySet: OccurrenceDaySet | None + OccurrenceRelativeToEnd: Boolean | None + OccurrenceUnit: String | None class ScheduledInstanceAvailability(TypedDict, total=False): - AvailabilityZone: Optional[String] - AvailableInstanceCount: Optional[Integer] - FirstSlotStartTime: Optional[DateTime] - HourlyPrice: Optional[String] - InstanceType: Optional[String] - MaxTermDurationInDays: Optional[Integer] - MinTermDurationInDays: Optional[Integer] - NetworkPlatform: Optional[String] - Platform: Optional[String] - PurchaseToken: Optional[String] - Recurrence: Optional[ScheduledInstanceRecurrence] - SlotDurationInHours: Optional[Integer] - TotalScheduledInstanceHours: Optional[Integer] + AvailabilityZone: String | None + AvailableInstanceCount: Integer | None + FirstSlotStartTime: DateTime | None + HourlyPrice: String | None + InstanceType: String | None + MaxTermDurationInDays: Integer | None + MinTermDurationInDays: Integer | None + NetworkPlatform: String | None + Platform: String | None + PurchaseToken: String | None + Recurrence: ScheduledInstanceRecurrence | None + SlotDurationInHours: Integer | None + TotalScheduledInstanceHours: Integer | None -ScheduledInstanceAvailabilitySet = List[ScheduledInstanceAvailability] +ScheduledInstanceAvailabilitySet = list[ScheduledInstanceAvailability] class DescribeScheduledInstanceAvailabilityResult(TypedDict, total=False): - NextToken: Optional[String] - ScheduledInstanceAvailabilitySet: Optional[ScheduledInstanceAvailabilitySet] + NextToken: String | None + ScheduledInstanceAvailabilitySet: ScheduledInstanceAvailabilitySet | None class SlotStartTimeRangeRequest(TypedDict, total=False): - EarliestTime: Optional[DateTime] - LatestTime: Optional[DateTime] + EarliestTime: DateTime | None + LatestTime: DateTime | None -ScheduledInstanceIdRequestSet = List[ScheduledInstanceId] +ScheduledInstanceIdRequestSet = list[ScheduledInstanceId] class DescribeScheduledInstancesRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] - ScheduledInstanceIds: Optional[ScheduledInstanceIdRequestSet] - SlotStartTimeRange: Optional[SlotStartTimeRangeRequest] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None + ScheduledInstanceIds: ScheduledInstanceIdRequestSet | None + SlotStartTimeRange: SlotStartTimeRangeRequest | None class ScheduledInstance(TypedDict, total=False): - AvailabilityZone: Optional[String] - CreateDate: Optional[DateTime] - HourlyPrice: Optional[String] - InstanceCount: Optional[Integer] - InstanceType: Optional[String] - NetworkPlatform: Optional[String] - NextSlotStartTime: Optional[DateTime] - Platform: Optional[String] - PreviousSlotEndTime: Optional[DateTime] - Recurrence: Optional[ScheduledInstanceRecurrence] - ScheduledInstanceId: Optional[String] - SlotDurationInHours: Optional[Integer] - TermEndDate: Optional[DateTime] - TermStartDate: Optional[DateTime] - TotalScheduledInstanceHours: Optional[Integer] - - -ScheduledInstanceSet = List[ScheduledInstance] + AvailabilityZone: String | None + CreateDate: DateTime | None + HourlyPrice: String | None + InstanceCount: Integer | None + InstanceType: String | None + NetworkPlatform: String | None + NextSlotStartTime: DateTime | None + Platform: String | None + PreviousSlotEndTime: DateTime | None + Recurrence: ScheduledInstanceRecurrence | None + ScheduledInstanceId: String | None + SlotDurationInHours: Integer | None + TermEndDate: DateTime | None + TermStartDate: DateTime | None + TotalScheduledInstanceHours: Integer | None + + +ScheduledInstanceSet = list[ScheduledInstance] class DescribeScheduledInstancesResult(TypedDict, total=False): - NextToken: Optional[String] - ScheduledInstanceSet: Optional[ScheduledInstanceSet] + NextToken: String | None + ScheduledInstanceSet: ScheduledInstanceSet | None + + +SecondaryInterfaceIdList = list[SecondaryInterfaceId] + + +class DescribeSecondaryInterfacesRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: DescribeSecondaryInterfacesMaxResults | None + NextToken: String | None + SecondaryInterfaceIds: SecondaryInterfaceIdList | None + + +class SecondaryInterfaceIpv4Address(TypedDict, total=False): + PrivateIpAddress: String | None + + +SecondaryInterfaceIpv4AddressList = list[SecondaryInterfaceIpv4Address] + + +class SecondaryInterfaceAttachment(TypedDict, total=False): + AttachmentId: String | None + AttachTime: MillisecondDateTime | None + DeleteOnTermination: Boolean | None + DeviceIndex: Integer | None + InstanceId: String | None + InstanceOwnerId: String | None + NetworkCardIndex: Integer | None + Status: AttachmentStatus | None + + +class SecondaryInterface(TypedDict, total=False): + AvailabilityZone: AvailabilityZoneName | None + AvailabilityZoneId: AvailabilityZoneId | None + Attachment: SecondaryInterfaceAttachment | None + MacAddress: String | None + OwnerId: String | None + PrivateIpv4Addresses: SecondaryInterfaceIpv4AddressList | None + SecondaryInterfaceId: SecondaryInterfaceId | None + SecondaryInterfaceArn: String | None + SecondaryInterfaceType: SecondaryInterfaceType | None + SecondarySubnetId: SecondarySubnetId | None + SecondaryNetworkId: SecondaryNetworkId | None + SecondaryNetworkType: SecondaryNetworkType | None + SourceDestCheck: Boolean | None + Status: SecondaryInterfaceStatus | None + Tags: TagList | None + + +SecondaryInterfaceList = list[SecondaryInterface] + + +class DescribeSecondaryInterfacesResult(TypedDict, total=False): + SecondaryInterfaces: SecondaryInterfaceList | None + NextToken: String | None + + +SecondaryNetworkIdList = list[SecondaryNetworkId] + + +class DescribeSecondaryNetworksRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: DescribeSecondaryNetworksMaxResults | None + NextToken: String | None + SecondaryNetworkIds: SecondaryNetworkIdList | None + + +SecondaryNetworkList = list[SecondaryNetwork] + +class DescribeSecondaryNetworksResult(TypedDict, total=False): + SecondaryNetworks: SecondaryNetworkList | None + NextToken: String | None -GroupIds = List[SecurityGroupId] + +SecondarySubnetIdList = list[SecondarySubnetId] + + +class DescribeSecondarySubnetsRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: DescribeSecondarySubnetsMaxResults | None + NextToken: String | None + SecondarySubnetIds: SecondarySubnetIdList | None + + +SecondarySubnetList = list[SecondarySubnet] + + +class DescribeSecondarySubnetsResult(TypedDict, total=False): + SecondarySubnets: SecondarySubnetList | None + NextToken: String | None + + +GroupIds = list[SecurityGroupId] class DescribeSecurityGroupReferencesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None GroupId: GroupIds class SecurityGroupReference(TypedDict, total=False): - GroupId: Optional[String] - ReferencingVpcId: Optional[String] - VpcPeeringConnectionId: Optional[String] - TransitGatewayId: Optional[String] + GroupId: String | None + ReferencingVpcId: String | None + VpcPeeringConnectionId: String | None + TransitGatewayId: String | None -SecurityGroupReferences = List[SecurityGroupReference] +SecurityGroupReferences = list[SecurityGroupReference] class DescribeSecurityGroupReferencesResult(TypedDict, total=False): - SecurityGroupReferenceSet: Optional[SecurityGroupReferences] + SecurityGroupReferenceSet: SecurityGroupReferences | None -SecurityGroupRuleIdList = List[String] +SecurityGroupRuleIdList = list[String] class DescribeSecurityGroupRulesRequest(ServiceRequest): - Filters: Optional[FilterList] - SecurityGroupRuleIds: Optional[SecurityGroupRuleIdList] - DryRun: Optional[Boolean] - NextToken: Optional[String] - MaxResults: Optional[DescribeSecurityGroupRulesMaxResults] + Filters: FilterList | None + SecurityGroupRuleIds: SecurityGroupRuleIdList | None + DryRun: Boolean | None + NextToken: String | None + MaxResults: DescribeSecurityGroupRulesMaxResults | None class DescribeSecurityGroupRulesResult(TypedDict, total=False): - SecurityGroupRules: Optional[SecurityGroupRuleList] - NextToken: Optional[String] + SecurityGroupRules: SecurityGroupRuleList | None + NextToken: String | None class DescribeSecurityGroupVpcAssociationsRequest(ServiceRequest): - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[DescribeSecurityGroupVpcAssociationsMaxResults] - DryRun: Optional[Boolean] + Filters: FilterList | None + NextToken: String | None + MaxResults: DescribeSecurityGroupVpcAssociationsMaxResults | None + DryRun: Boolean | None class SecurityGroupVpcAssociation(TypedDict, total=False): - GroupId: Optional[SecurityGroupId] - VpcId: Optional[VpcId] - VpcOwnerId: Optional[String] - State: Optional[SecurityGroupVpcAssociationState] - StateReason: Optional[String] - GroupOwnerId: Optional[String] + GroupId: SecurityGroupId | None + VpcId: VpcId | None + VpcOwnerId: String | None + State: SecurityGroupVpcAssociationState | None + StateReason: String | None + GroupOwnerId: String | None -SecurityGroupVpcAssociationList = List[SecurityGroupVpcAssociation] +SecurityGroupVpcAssociationList = list[SecurityGroupVpcAssociation] class DescribeSecurityGroupVpcAssociationsResult(TypedDict, total=False): - SecurityGroupVpcAssociations: Optional[SecurityGroupVpcAssociationList] - NextToken: Optional[String] + SecurityGroupVpcAssociations: SecurityGroupVpcAssociationList | None + NextToken: String | None -GroupNameStringList = List[SecurityGroupName] +GroupNameStringList = list[SecurityGroupName] class DescribeSecurityGroupsRequest(ServiceRequest): - GroupIds: Optional[GroupIdStringList] - GroupNames: Optional[GroupNameStringList] - NextToken: Optional[String] - MaxResults: Optional[DescribeSecurityGroupsMaxResults] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + GroupIds: GroupIdStringList | None + GroupNames: GroupNameStringList | None + NextToken: String | None + MaxResults: DescribeSecurityGroupsMaxResults | None + DryRun: Boolean | None + Filters: FilterList | None class SecurityGroup(TypedDict, total=False): - GroupId: Optional[String] - IpPermissionsEgress: Optional[IpPermissionList] - Tags: Optional[TagList] - VpcId: Optional[String] - SecurityGroupArn: Optional[String] - OwnerId: Optional[String] - GroupName: Optional[String] - Description: Optional[String] - IpPermissions: Optional[IpPermissionList] + GroupId: String | None + IpPermissionsEgress: IpPermissionList | None + Tags: TagList | None + VpcId: String | None + SecurityGroupArn: String | None + OwnerId: String | None + GroupName: String | None + Description: String | None + IpPermissions: IpPermissionList | None -SecurityGroupList = List[SecurityGroup] +SecurityGroupList = list[SecurityGroup] class DescribeSecurityGroupsResult(TypedDict, total=False): - NextToken: Optional[String] - SecurityGroups: Optional[SecurityGroupList] + NextToken: String | None + SecurityGroups: SecurityGroupList | None class DescribeServiceLinkVirtualInterfacesRequest(ServiceRequest): - ServiceLinkVirtualInterfaceIds: Optional[ServiceLinkVirtualInterfaceIdSet] - Filters: Optional[FilterList] - MaxResults: Optional[ServiceLinkMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + ServiceLinkVirtualInterfaceIds: ServiceLinkVirtualInterfaceIdSet | None + Filters: FilterList | None + MaxResults: ServiceLinkMaxResults | None + NextToken: String | None + DryRun: Boolean | None class ServiceLinkVirtualInterface(TypedDict, total=False): - ServiceLinkVirtualInterfaceId: Optional[ServiceLinkVirtualInterfaceId] - ServiceLinkVirtualInterfaceArn: Optional[ResourceArn] - OutpostId: Optional[String] - OutpostArn: Optional[String] - OwnerId: Optional[String] - LocalAddress: Optional[String] - PeerAddress: Optional[String] - PeerBgpAsn: Optional[Long] - Vlan: Optional[Integer] - OutpostLagId: Optional[OutpostLagId] - Tags: Optional[TagList] - ConfigurationState: Optional[ServiceLinkVirtualInterfaceConfigurationState] + ServiceLinkVirtualInterfaceId: ServiceLinkVirtualInterfaceId | None + ServiceLinkVirtualInterfaceArn: ResourceArn | None + OutpostId: String | None + OutpostArn: String | None + OwnerId: String | None + LocalAddress: String | None + PeerAddress: String | None + PeerBgpAsn: Long | None + Vlan: Integer | None + OutpostLagId: OutpostLagId | None + Tags: TagList | None + ConfigurationState: ServiceLinkVirtualInterfaceConfigurationState | None -ServiceLinkVirtualInterfaceSet = List[ServiceLinkVirtualInterface] +ServiceLinkVirtualInterfaceSet = list[ServiceLinkVirtualInterface] class DescribeServiceLinkVirtualInterfacesResult(TypedDict, total=False): - ServiceLinkVirtualInterfaces: Optional[ServiceLinkVirtualInterfaceSet] - NextToken: Optional[String] + ServiceLinkVirtualInterfaces: ServiceLinkVirtualInterfaceSet | None + NextToken: String | None class DescribeSnapshotAttributeRequest(ServiceRequest): Attribute: SnapshotAttributeName SnapshotId: SnapshotId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DescribeSnapshotAttributeResult(TypedDict, total=False): - ProductCodes: Optional[ProductCodeList] - SnapshotId: Optional[String] - CreateVolumePermissions: Optional[CreateVolumePermissionList] + ProductCodes: ProductCodeList | None + SnapshotId: String | None + CreateVolumePermissions: CreateVolumePermissionList | None class DescribeSnapshotTierStatusRequest(ServiceRequest): - Filters: Optional[FilterList] - DryRun: Optional[Boolean] - NextToken: Optional[String] - MaxResults: Optional[DescribeSnapshotTierStatusMaxResults] + Filters: FilterList | None + DryRun: Boolean | None + NextToken: String | None + MaxResults: DescribeSnapshotTierStatusMaxResults | None class SnapshotTierStatus(TypedDict, total=False): - SnapshotId: Optional[SnapshotId] - VolumeId: Optional[VolumeId] - Status: Optional[SnapshotState] - OwnerId: Optional[String] - Tags: Optional[TagList] - StorageTier: Optional[StorageTier] - LastTieringStartTime: Optional[MillisecondDateTime] - LastTieringProgress: Optional[Integer] - LastTieringOperationStatus: Optional[TieringOperationStatus] - LastTieringOperationStatusDetail: Optional[String] - ArchivalCompleteTime: Optional[MillisecondDateTime] - RestoreExpiryTime: Optional[MillisecondDateTime] + SnapshotId: SnapshotId | None + VolumeId: VolumeId | None + Status: SnapshotState | None + OwnerId: String | None + Tags: TagList | None + StorageTier: StorageTier | None + LastTieringStartTime: MillisecondDateTime | None + LastTieringProgress: Integer | None + LastTieringOperationStatus: TieringOperationStatus | None + LastTieringOperationStatusDetail: String | None + ArchivalCompleteTime: MillisecondDateTime | None + RestoreExpiryTime: MillisecondDateTime | None -snapshotTierStatusSet = List[SnapshotTierStatus] +snapshotTierStatusSet = list[SnapshotTierStatus] class DescribeSnapshotTierStatusResult(TypedDict, total=False): - SnapshotTierStatuses: Optional[snapshotTierStatusSet] - NextToken: Optional[String] + SnapshotTierStatuses: snapshotTierStatusSet | None + NextToken: String | None -RestorableByStringList = List[String] +RestorableByStringList = list[String] class DescribeSnapshotsRequest(ServiceRequest): - MaxResults: Optional[Integer] - NextToken: Optional[String] - OwnerIds: Optional[OwnerStringList] - RestorableByUserIds: Optional[RestorableByStringList] - SnapshotIds: Optional[SnapshotIdStringList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + MaxResults: Integer | None + NextToken: String | None + OwnerIds: OwnerStringList | None + RestorableByUserIds: RestorableByStringList | None + SnapshotIds: SnapshotIdStringList | None + DryRun: Boolean | None + Filters: FilterList | None class Snapshot(TypedDict, total=False): - OwnerAlias: Optional[String] - OutpostArn: Optional[String] - Tags: Optional[TagList] - StorageTier: Optional[StorageTier] - RestoreExpiryTime: Optional[MillisecondDateTime] - SseType: Optional[SSEType] - AvailabilityZone: Optional[String] - TransferType: Optional[TransferType] - CompletionDurationMinutes: Optional[SnapshotCompletionDurationMinutesResponse] - CompletionTime: Optional[MillisecondDateTime] - FullSnapshotSizeInBytes: Optional[Long] - SnapshotId: Optional[String] - VolumeId: Optional[String] - State: Optional[SnapshotState] - StateMessage: Optional[String] - StartTime: Optional[DateTime] - Progress: Optional[String] - OwnerId: Optional[String] - Description: Optional[String] - VolumeSize: Optional[Integer] - Encrypted: Optional[Boolean] - KmsKeyId: Optional[String] - DataEncryptionKeyId: Optional[String] - - -SnapshotList = List[Snapshot] + OwnerAlias: String | None + OutpostArn: String | None + Tags: TagList | None + StorageTier: StorageTier | None + RestoreExpiryTime: MillisecondDateTime | None + SseType: SSEType | None + AvailabilityZone: String | None + TransferType: TransferType | None + CompletionDurationMinutes: SnapshotCompletionDurationMinutesResponse | None + CompletionTime: MillisecondDateTime | None + FullSnapshotSizeInBytes: Long | None + SnapshotId: String | None + VolumeId: String | None + State: SnapshotState | None + StateMessage: String | None + StartTime: DateTime | None + Progress: String | None + OwnerId: String | None + Description: String | None + VolumeSize: Integer | None + Encrypted: Boolean | None + KmsKeyId: String | None + DataEncryptionKeyId: String | None + + +SnapshotList = list[Snapshot] class DescribeSnapshotsResult(TypedDict, total=False): - NextToken: Optional[String] - Snapshots: Optional[SnapshotList] + NextToken: String | None + Snapshots: SnapshotList | None class DescribeSpotDatafeedSubscriptionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DescribeSpotDatafeedSubscriptionResult(TypedDict, total=False): - SpotDatafeedSubscription: Optional[SpotDatafeedSubscription] + SpotDatafeedSubscription: SpotDatafeedSubscription | None class DescribeSpotFleetInstancesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None SpotFleetRequestId: SpotFleetRequestId - NextToken: Optional[String] - MaxResults: Optional[DescribeSpotFleetInstancesMaxResults] + NextToken: String | None + MaxResults: DescribeSpotFleetInstancesMaxResults | None class DescribeSpotFleetInstancesResponse(TypedDict, total=False): - ActiveInstances: Optional[ActiveInstanceSet] - NextToken: Optional[String] - SpotFleetRequestId: Optional[String] + ActiveInstances: ActiveInstanceSet | None + NextToken: String | None + SpotFleetRequestId: String | None class DescribeSpotFleetRequestHistoryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None SpotFleetRequestId: SpotFleetRequestId - EventType: Optional[EventType] + EventType: EventType | None StartTime: DateTime - NextToken: Optional[String] - MaxResults: Optional[DescribeSpotFleetRequestHistoryMaxResults] + NextToken: String | None + MaxResults: DescribeSpotFleetRequestHistoryMaxResults | None class HistoryRecord(TypedDict, total=False): - EventInformation: Optional[EventInformation] - EventType: Optional[EventType] - Timestamp: Optional[DateTime] + EventInformation: EventInformation | None + EventType: EventType | None + Timestamp: DateTime | None -HistoryRecords = List[HistoryRecord] +HistoryRecords = list[HistoryRecord] class DescribeSpotFleetRequestHistoryResponse(TypedDict, total=False): - HistoryRecords: Optional[HistoryRecords] - LastEvaluatedTime: Optional[DateTime] - NextToken: Optional[String] - SpotFleetRequestId: Optional[String] - StartTime: Optional[DateTime] + HistoryRecords: HistoryRecords | None + LastEvaluatedTime: DateTime | None + NextToken: String | None + SpotFleetRequestId: String | None + StartTime: DateTime | None class DescribeSpotFleetRequestsRequest(ServiceRequest): - DryRun: Optional[Boolean] - SpotFleetRequestIds: Optional[SpotFleetRequestIdList] - NextToken: Optional[String] - MaxResults: Optional[Integer] + DryRun: Boolean | None + SpotFleetRequestIds: SpotFleetRequestIdList | None + NextToken: String | None + MaxResults: Integer | None class TargetGroup(TypedDict, total=False): - Arn: Optional[String] + Arn: String | None -TargetGroups = List[TargetGroup] +TargetGroups = list[TargetGroup] class TargetGroupsConfig(TypedDict, total=False): - TargetGroups: Optional[TargetGroups] + TargetGroups: TargetGroups | None class LoadBalancersConfig(TypedDict, total=False): - ClassicLoadBalancersConfig: Optional[ClassicLoadBalancersConfig] - TargetGroupsConfig: Optional[TargetGroupsConfig] + ClassicLoadBalancersConfig: ClassicLoadBalancersConfig | None + TargetGroupsConfig: TargetGroupsConfig | None class LaunchTemplateOverrides(TypedDict, total=False): - InstanceType: Optional[InstanceType] - SpotPrice: Optional[String] - SubnetId: Optional[SubnetId] - AvailabilityZone: Optional[String] - WeightedCapacity: Optional[Double] - Priority: Optional[Double] - InstanceRequirements: Optional[InstanceRequirements] + InstanceType: InstanceType | None + SpotPrice: String | None + SubnetId: SubnetId | None + AvailabilityZone: String | None + WeightedCapacity: Double | None + Priority: Double | None + InstanceRequirements: InstanceRequirements | None + AvailabilityZoneId: AvailabilityZoneId | None -LaunchTemplateOverridesList = List[LaunchTemplateOverrides] +LaunchTemplateOverridesList = list[LaunchTemplateOverrides] class LaunchTemplateConfig(TypedDict, total=False): - LaunchTemplateSpecification: Optional[FleetLaunchTemplateSpecification] - Overrides: Optional[LaunchTemplateOverridesList] + LaunchTemplateSpecification: FleetLaunchTemplateSpecification | None + Overrides: LaunchTemplateOverridesList | None -LaunchTemplateConfigList = List[LaunchTemplateConfig] +LaunchTemplateConfigList = list[LaunchTemplateConfig] class SpotFleetTagSpecification(TypedDict, total=False): - ResourceType: Optional[ResourceType] - Tags: Optional[TagList] + ResourceType: ResourceType | None + Tags: TagList | None -SpotFleetTagSpecificationList = List[SpotFleetTagSpecification] +SpotFleetTagSpecificationList = list[SpotFleetTagSpecification] class SpotPlacement(TypedDict, total=False): - AvailabilityZone: Optional[String] - GroupName: Optional[PlacementGroupName] - Tenancy: Optional[Tenancy] + AvailabilityZone: String | None + GroupName: PlacementGroupName | None + Tenancy: Tenancy | None + AvailabilityZoneId: String | None class InstanceNetworkInterfaceSpecification(TypedDict, total=False): - AssociatePublicIpAddress: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Description: Optional[String] - DeviceIndex: Optional[Integer] - Groups: Optional[SecurityGroupIdStringList] - Ipv6AddressCount: Optional[Integer] - Ipv6Addresses: Optional[InstanceIpv6AddressList] - NetworkInterfaceId: Optional[NetworkInterfaceId] - PrivateIpAddress: Optional[String] - PrivateIpAddresses: Optional[PrivateIpAddressSpecificationList] - SecondaryPrivateIpAddressCount: Optional[Integer] - SubnetId: Optional[String] - AssociateCarrierIpAddress: Optional[Boolean] - InterfaceType: Optional[String] - NetworkCardIndex: Optional[Integer] - Ipv4Prefixes: Optional[Ipv4PrefixList] - Ipv4PrefixCount: Optional[Integer] - Ipv6Prefixes: Optional[Ipv6PrefixList] - Ipv6PrefixCount: Optional[Integer] - PrimaryIpv6: Optional[Boolean] - EnaSrdSpecification: Optional[EnaSrdSpecificationRequest] - ConnectionTrackingSpecification: Optional[ConnectionTrackingSpecificationRequest] - EnaQueueCount: Optional[Integer] - - -InstanceNetworkInterfaceSpecificationList = List[InstanceNetworkInterfaceSpecification] + AssociatePublicIpAddress: Boolean | None + DeleteOnTermination: Boolean | None + Description: String | None + DeviceIndex: Integer | None + Groups: SecurityGroupIdStringList | None + Ipv6AddressCount: Integer | None + Ipv6Addresses: InstanceIpv6AddressList | None + NetworkInterfaceId: NetworkInterfaceId | None + PrivateIpAddress: String | None + PrivateIpAddresses: PrivateIpAddressSpecificationList | None + SecondaryPrivateIpAddressCount: Integer | None + SubnetId: String | None + AssociateCarrierIpAddress: Boolean | None + InterfaceType: String | None + NetworkCardIndex: Integer | None + Ipv4Prefixes: Ipv4PrefixList | None + Ipv4PrefixCount: Integer | None + Ipv6Prefixes: Ipv6PrefixList | None + Ipv6PrefixCount: Integer | None + PrimaryIpv6: Boolean | None + EnaSrdSpecification: EnaSrdSpecificationRequest | None + ConnectionTrackingSpecification: ConnectionTrackingSpecificationRequest | None + EnaQueueCount: Integer | None + + +InstanceNetworkInterfaceSpecificationList = list[InstanceNetworkInterfaceSpecification] class SpotFleetMonitoring(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class SpotFleetLaunchSpecification(TypedDict, total=False): - AddressingType: Optional[String] - BlockDeviceMappings: Optional[BlockDeviceMappingList] - EbsOptimized: Optional[Boolean] - IamInstanceProfile: Optional[IamInstanceProfileSpecification] - ImageId: Optional[ImageId] - InstanceType: Optional[InstanceType] - KernelId: Optional[String] - KeyName: Optional[KeyPairName] - Monitoring: Optional[SpotFleetMonitoring] - NetworkInterfaces: Optional[InstanceNetworkInterfaceSpecificationList] - Placement: Optional[SpotPlacement] - RamdiskId: Optional[String] - SpotPrice: Optional[String] - SubnetId: Optional[SubnetId] - UserData: Optional[SensitiveUserData] - WeightedCapacity: Optional[Double] - TagSpecifications: Optional[SpotFleetTagSpecificationList] - InstanceRequirements: Optional[InstanceRequirements] - SecurityGroups: Optional[GroupIdentifierList] - - -LaunchSpecsList = List[SpotFleetLaunchSpecification] + AddressingType: String | None + BlockDeviceMappings: BlockDeviceMappingList | None + EbsOptimized: Boolean | None + IamInstanceProfile: IamInstanceProfileSpecification | None + ImageId: ImageId | None + InstanceType: InstanceType | None + KernelId: String | None + KeyName: KeyPairName | None + Monitoring: SpotFleetMonitoring | None + NetworkInterfaces: InstanceNetworkInterfaceSpecificationList | None + Placement: SpotPlacement | None + RamdiskId: String | None + SpotPrice: String | None + SubnetId: SubnetId | None + UserData: SensitiveUserData | None + WeightedCapacity: Double | None + TagSpecifications: SpotFleetTagSpecificationList | None + InstanceRequirements: InstanceRequirements | None + SecurityGroups: GroupIdentifierList | None + + +LaunchSpecsList = list[SpotFleetLaunchSpecification] class SpotCapacityRebalance(TypedDict, total=False): - ReplacementStrategy: Optional[ReplacementStrategy] - TerminationDelay: Optional[Integer] + ReplacementStrategy: ReplacementStrategy | None + TerminationDelay: Integer | None class SpotMaintenanceStrategies(TypedDict, total=False): - CapacityRebalance: Optional[SpotCapacityRebalance] + CapacityRebalance: SpotCapacityRebalance | None class SpotFleetRequestConfigData(TypedDict, total=False): - AllocationStrategy: Optional[AllocationStrategy] - OnDemandAllocationStrategy: Optional[OnDemandAllocationStrategy] - SpotMaintenanceStrategies: Optional[SpotMaintenanceStrategies] - ClientToken: Optional[String] - ExcessCapacityTerminationPolicy: Optional[ExcessCapacityTerminationPolicy] - FulfilledCapacity: Optional[Double] - OnDemandFulfilledCapacity: Optional[Double] + AllocationStrategy: AllocationStrategy | None + OnDemandAllocationStrategy: OnDemandAllocationStrategy | None + SpotMaintenanceStrategies: SpotMaintenanceStrategies | None + ClientToken: String | None + ExcessCapacityTerminationPolicy: ExcessCapacityTerminationPolicy | None + FulfilledCapacity: Double | None + OnDemandFulfilledCapacity: Double | None IamFleetRole: String - LaunchSpecifications: Optional[LaunchSpecsList] - LaunchTemplateConfigs: Optional[LaunchTemplateConfigList] - SpotPrice: Optional[String] + LaunchSpecifications: LaunchSpecsList | None + LaunchTemplateConfigs: LaunchTemplateConfigList | None + SpotPrice: String | None TargetCapacity: Integer - OnDemandTargetCapacity: Optional[Integer] - OnDemandMaxTotalPrice: Optional[String] - SpotMaxTotalPrice: Optional[String] - TerminateInstancesWithExpiration: Optional[Boolean] - Type: Optional[FleetType] - ValidFrom: Optional[DateTime] - ValidUntil: Optional[DateTime] - ReplaceUnhealthyInstances: Optional[Boolean] - InstanceInterruptionBehavior: Optional[InstanceInterruptionBehavior] - LoadBalancersConfig: Optional[LoadBalancersConfig] - InstancePoolsToUseCount: Optional[Integer] - Context: Optional[String] - TargetCapacityUnitType: Optional[TargetCapacityUnitType] - TagSpecifications: Optional[TagSpecificationList] + OnDemandTargetCapacity: Integer | None + OnDemandMaxTotalPrice: String | None + SpotMaxTotalPrice: String | None + TerminateInstancesWithExpiration: Boolean | None + Type: FleetType | None + ValidFrom: DateTime | None + ValidUntil: DateTime | None + ReplaceUnhealthyInstances: Boolean | None + InstanceInterruptionBehavior: InstanceInterruptionBehavior | None + LoadBalancersConfig: LoadBalancersConfig | None + InstancePoolsToUseCount: Integer | None + Context: String | None + TargetCapacityUnitType: TargetCapacityUnitType | None + TagSpecifications: TagSpecificationList | None class SpotFleetRequestConfig(TypedDict, total=False): - ActivityStatus: Optional[ActivityStatus] - CreateTime: Optional[MillisecondDateTime] - SpotFleetRequestConfig: Optional[SpotFleetRequestConfigData] - SpotFleetRequestId: Optional[String] - SpotFleetRequestState: Optional[BatchState] - Tags: Optional[TagList] + ActivityStatus: ActivityStatus | None + CreateTime: MillisecondDateTime | None + SpotFleetRequestConfig: SpotFleetRequestConfigData | None + SpotFleetRequestId: String | None + SpotFleetRequestState: BatchState | None + Tags: TagList | None -SpotFleetRequestConfigSet = List[SpotFleetRequestConfig] +SpotFleetRequestConfigSet = list[SpotFleetRequestConfig] class DescribeSpotFleetRequestsResponse(TypedDict, total=False): - NextToken: Optional[String] - SpotFleetRequestConfigs: Optional[SpotFleetRequestConfigSet] + NextToken: String | None + SpotFleetRequestConfigs: SpotFleetRequestConfigSet | None class DescribeSpotInstanceRequestsRequest(ServiceRequest): - NextToken: Optional[String] - MaxResults: Optional[Integer] - DryRun: Optional[Boolean] - SpotInstanceRequestIds: Optional[SpotInstanceRequestIdList] - Filters: Optional[FilterList] + NextToken: String | None + MaxResults: Integer | None + DryRun: Boolean | None + SpotInstanceRequestIds: SpotInstanceRequestIdList | None + Filters: FilterList | None class SpotInstanceStatus(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] - UpdateTime: Optional[DateTime] + Code: String | None + Message: String | None + UpdateTime: DateTime | None class RunInstancesMonitoringEnabled(TypedDict, total=False): @@ -15097,1421 +17076,1479 @@ class RunInstancesMonitoringEnabled(TypedDict, total=False): class LaunchSpecification(TypedDict, total=False): - UserData: Optional[SensitiveUserData] - AddressingType: Optional[String] - BlockDeviceMappings: Optional[BlockDeviceMappingList] - EbsOptimized: Optional[Boolean] - IamInstanceProfile: Optional[IamInstanceProfileSpecification] - ImageId: Optional[String] - InstanceType: Optional[InstanceType] - KernelId: Optional[String] - KeyName: Optional[String] - NetworkInterfaces: Optional[InstanceNetworkInterfaceSpecificationList] - Placement: Optional[SpotPlacement] - RamdiskId: Optional[String] - SubnetId: Optional[String] - SecurityGroups: Optional[GroupIdentifierList] - Monitoring: Optional[RunInstancesMonitoringEnabled] + UserData: SensitiveUserData | None + AddressingType: String | None + BlockDeviceMappings: BlockDeviceMappingList | None + EbsOptimized: Boolean | None + IamInstanceProfile: IamInstanceProfileSpecification | None + ImageId: String | None + InstanceType: InstanceType | None + KernelId: String | None + KeyName: String | None + NetworkInterfaces: InstanceNetworkInterfaceSpecificationList | None + Placement: SpotPlacement | None + RamdiskId: String | None + SubnetId: String | None + SecurityGroups: GroupIdentifierList | None + Monitoring: RunInstancesMonitoringEnabled | None class SpotInstanceRequest(TypedDict, total=False): - ActualBlockHourlyPrice: Optional[String] - AvailabilityZoneGroup: Optional[String] - BlockDurationMinutes: Optional[Integer] - CreateTime: Optional[DateTime] - Fault: Optional[SpotInstanceStateFault] - InstanceId: Optional[InstanceId] - LaunchGroup: Optional[String] - LaunchSpecification: Optional[LaunchSpecification] - LaunchedAvailabilityZone: Optional[String] - ProductDescription: Optional[RIProductDescription] - SpotInstanceRequestId: Optional[String] - SpotPrice: Optional[String] - State: Optional[SpotInstanceState] - Status: Optional[SpotInstanceStatus] - Tags: Optional[TagList] - Type: Optional[SpotInstanceType] - ValidFrom: Optional[DateTime] - ValidUntil: Optional[DateTime] - InstanceInterruptionBehavior: Optional[InstanceInterruptionBehavior] - - -SpotInstanceRequestList = List[SpotInstanceRequest] + ActualBlockHourlyPrice: String | None + AvailabilityZoneGroup: String | None + BlockDurationMinutes: Integer | None + CreateTime: DateTime | None + Fault: SpotInstanceStateFault | None + InstanceId: InstanceId | None + LaunchGroup: String | None + LaunchSpecification: LaunchSpecification | None + LaunchedAvailabilityZone: String | None + LaunchedAvailabilityZoneId: String | None + ProductDescription: RIProductDescription | None + SpotInstanceRequestId: String | None + SpotPrice: String | None + State: SpotInstanceState | None + Status: SpotInstanceStatus | None + Tags: TagList | None + Type: SpotInstanceType | None + ValidFrom: DateTime | None + ValidUntil: DateTime | None + InstanceInterruptionBehavior: InstanceInterruptionBehavior | None + + +SpotInstanceRequestList = list[SpotInstanceRequest] class DescribeSpotInstanceRequestsResult(TypedDict, total=False): - SpotInstanceRequests: Optional[SpotInstanceRequestList] - NextToken: Optional[String] + SpotInstanceRequests: SpotInstanceRequestList | None + NextToken: String | None -ProductDescriptionList = List[String] -InstanceTypeList = List[InstanceType] +ProductDescriptionList = list[String] +InstanceTypeList = list[InstanceType] class DescribeSpotPriceHistoryRequest(ServiceRequest): - DryRun: Optional[Boolean] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] - InstanceTypes: Optional[InstanceTypeList] - ProductDescriptions: Optional[ProductDescriptionList] - Filters: Optional[FilterList] - AvailabilityZone: Optional[String] - MaxResults: Optional[Integer] - NextToken: Optional[String] + AvailabilityZoneId: AvailabilityZoneId | None + DryRun: Boolean | None + StartTime: DateTime | None + EndTime: DateTime | None + InstanceTypes: InstanceTypeList | None + ProductDescriptions: ProductDescriptionList | None + Filters: FilterList | None + AvailabilityZone: String | None + MaxResults: Integer | None + NextToken: String | None class SpotPrice(TypedDict, total=False): - AvailabilityZone: Optional[String] - InstanceType: Optional[InstanceType] - ProductDescription: Optional[RIProductDescription] - SpotPrice: Optional[String] - Timestamp: Optional[DateTime] + AvailabilityZone: String | None + AvailabilityZoneId: String | None + InstanceType: InstanceType | None + ProductDescription: RIProductDescription | None + SpotPrice: String | None + Timestamp: DateTime | None -SpotPriceHistoryList = List[SpotPrice] +SpotPriceHistoryList = list[SpotPrice] class DescribeSpotPriceHistoryResult(TypedDict, total=False): - NextToken: Optional[String] - SpotPriceHistory: Optional[SpotPriceHistoryList] + NextToken: String | None + SpotPriceHistory: SpotPriceHistoryList | None class DescribeStaleSecurityGroupsRequest(ServiceRequest): - DryRun: Optional[Boolean] - MaxResults: Optional[DescribeStaleSecurityGroupsMaxResults] - NextToken: Optional[DescribeStaleSecurityGroupsNextToken] + DryRun: Boolean | None + MaxResults: DescribeStaleSecurityGroupsMaxResults | None + NextToken: DescribeStaleSecurityGroupsNextToken | None VpcId: VpcId -UserIdGroupPairSet = List[UserIdGroupPair] -PrefixListIdSet = List[String] -IpRanges = List[String] +UserIdGroupPairSet = list[UserIdGroupPair] +PrefixListIdSet = list[String] +IpRanges = list[String] class StaleIpPermission(TypedDict, total=False): - FromPort: Optional[Integer] - IpProtocol: Optional[String] - IpRanges: Optional[IpRanges] - PrefixListIds: Optional[PrefixListIdSet] - ToPort: Optional[Integer] - UserIdGroupPairs: Optional[UserIdGroupPairSet] + FromPort: Integer | None + IpProtocol: String | None + IpRanges: IpRanges | None + PrefixListIds: PrefixListIdSet | None + ToPort: Integer | None + UserIdGroupPairs: UserIdGroupPairSet | None -StaleIpPermissionSet = List[StaleIpPermission] +StaleIpPermissionSet = list[StaleIpPermission] class StaleSecurityGroup(TypedDict, total=False): - Description: Optional[String] - GroupId: Optional[String] - GroupName: Optional[String] - StaleIpPermissions: Optional[StaleIpPermissionSet] - StaleIpPermissionsEgress: Optional[StaleIpPermissionSet] - VpcId: Optional[String] + Description: String | None + GroupId: String | None + GroupName: String | None + StaleIpPermissions: StaleIpPermissionSet | None + StaleIpPermissionsEgress: StaleIpPermissionSet | None + VpcId: String | None -StaleSecurityGroupSet = List[StaleSecurityGroup] +StaleSecurityGroupSet = list[StaleSecurityGroup] class DescribeStaleSecurityGroupsResult(TypedDict, total=False): - NextToken: Optional[String] - StaleSecurityGroupSet: Optional[StaleSecurityGroupSet] + NextToken: String | None + StaleSecurityGroupSet: StaleSecurityGroupSet | None -ImageIdList = List[ImageId] +ImageIdList = list[ImageId] class DescribeStoreImageTasksRequest(ServiceRequest): - ImageIds: Optional[ImageIdList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[DescribeStoreImageTasksRequestMaxResults] + ImageIds: ImageIdList | None + DryRun: Boolean | None + Filters: FilterList | None + NextToken: String | None + MaxResults: DescribeStoreImageTasksRequestMaxResults | None class StoreImageTaskResult(TypedDict, total=False): - AmiId: Optional[String] - TaskStartTime: Optional[MillisecondDateTime] - Bucket: Optional[String] - S3objectKey: Optional[String] - ProgressPercentage: Optional[Integer] - StoreTaskState: Optional[String] - StoreTaskFailureReason: Optional[String] + AmiId: String | None + TaskStartTime: MillisecondDateTime | None + Bucket: String | None + S3objectKey: String | None + ProgressPercentage: Integer | None + StoreTaskState: String | None + StoreTaskFailureReason: String | None -StoreImageTaskResultSet = List[StoreImageTaskResult] +StoreImageTaskResultSet = list[StoreImageTaskResult] class DescribeStoreImageTasksResult(TypedDict, total=False): - StoreImageTaskResults: Optional[StoreImageTaskResultSet] - NextToken: Optional[String] + StoreImageTaskResults: StoreImageTaskResultSet | None + NextToken: String | None -SubnetIdStringList = List[SubnetId] +SubnetIdStringList = list[SubnetId] class DescribeSubnetsRequest(ServiceRequest): - Filters: Optional[FilterList] - SubnetIds: Optional[SubnetIdStringList] - NextToken: Optional[String] - MaxResults: Optional[DescribeSubnetsMaxResults] - DryRun: Optional[Boolean] + Filters: FilterList | None + SubnetIds: SubnetIdStringList | None + NextToken: String | None + MaxResults: DescribeSubnetsMaxResults | None + DryRun: Boolean | None -SubnetList = List[Subnet] +SubnetList = list[Subnet] class DescribeSubnetsResult(TypedDict, total=False): - NextToken: Optional[String] - Subnets: Optional[SubnetList] + NextToken: String | None + Subnets: SubnetList | None class DescribeTagsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None class TagDescription(TypedDict, total=False): - Key: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[ResourceType] - Value: Optional[String] + Key: String | None + ResourceId: String | None + ResourceType: ResourceType | None + Value: String | None -TagDescriptionList = List[TagDescription] +TagDescriptionList = list[TagDescription] class DescribeTagsResult(TypedDict, total=False): - NextToken: Optional[String] - Tags: Optional[TagDescriptionList] + NextToken: String | None + Tags: TagDescriptionList | None -TrafficMirrorFilterRuleIdList = List[TrafficMirrorFilterRuleIdWithResolver] +TrafficMirrorFilterRuleIdList = list[TrafficMirrorFilterRuleIdWithResolver] class DescribeTrafficMirrorFilterRulesRequest(ServiceRequest): - TrafficMirrorFilterRuleIds: Optional[TrafficMirrorFilterRuleIdList] - TrafficMirrorFilterId: Optional[TrafficMirrorFilterId] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[TrafficMirroringMaxResults] - NextToken: Optional[NextToken] + TrafficMirrorFilterRuleIds: TrafficMirrorFilterRuleIdList | None + TrafficMirrorFilterId: TrafficMirrorFilterId | None + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: TrafficMirroringMaxResults | None + NextToken: NextToken | None -TrafficMirrorFilterRuleSet = List[TrafficMirrorFilterRule] +TrafficMirrorFilterRuleSet = list[TrafficMirrorFilterRule] class DescribeTrafficMirrorFilterRulesResult(TypedDict, total=False): - TrafficMirrorFilterRules: Optional[TrafficMirrorFilterRuleSet] - NextToken: Optional[String] + TrafficMirrorFilterRules: TrafficMirrorFilterRuleSet | None + NextToken: String | None -TrafficMirrorFilterIdList = List[TrafficMirrorFilterId] +TrafficMirrorFilterIdList = list[TrafficMirrorFilterId] class DescribeTrafficMirrorFiltersRequest(ServiceRequest): - TrafficMirrorFilterIds: Optional[TrafficMirrorFilterIdList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[TrafficMirroringMaxResults] - NextToken: Optional[NextToken] + TrafficMirrorFilterIds: TrafficMirrorFilterIdList | None + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: TrafficMirroringMaxResults | None + NextToken: NextToken | None -TrafficMirrorFilterSet = List[TrafficMirrorFilter] +TrafficMirrorFilterSet = list[TrafficMirrorFilter] class DescribeTrafficMirrorFiltersResult(TypedDict, total=False): - TrafficMirrorFilters: Optional[TrafficMirrorFilterSet] - NextToken: Optional[String] + TrafficMirrorFilters: TrafficMirrorFilterSet | None + NextToken: String | None -TrafficMirrorSessionIdList = List[TrafficMirrorSessionId] +TrafficMirrorSessionIdList = list[TrafficMirrorSessionId] class DescribeTrafficMirrorSessionsRequest(ServiceRequest): - TrafficMirrorSessionIds: Optional[TrafficMirrorSessionIdList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[TrafficMirroringMaxResults] - NextToken: Optional[NextToken] + TrafficMirrorSessionIds: TrafficMirrorSessionIdList | None + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: TrafficMirroringMaxResults | None + NextToken: NextToken | None -TrafficMirrorSessionSet = List[TrafficMirrorSession] +TrafficMirrorSessionSet = list[TrafficMirrorSession] class DescribeTrafficMirrorSessionsResult(TypedDict, total=False): - TrafficMirrorSessions: Optional[TrafficMirrorSessionSet] - NextToken: Optional[String] + TrafficMirrorSessions: TrafficMirrorSessionSet | None + NextToken: String | None -TrafficMirrorTargetIdList = List[TrafficMirrorTargetId] +TrafficMirrorTargetIdList = list[TrafficMirrorTargetId] class DescribeTrafficMirrorTargetsRequest(ServiceRequest): - TrafficMirrorTargetIds: Optional[TrafficMirrorTargetIdList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[TrafficMirroringMaxResults] - NextToken: Optional[NextToken] + TrafficMirrorTargetIds: TrafficMirrorTargetIdList | None + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: TrafficMirroringMaxResults | None + NextToken: NextToken | None -TrafficMirrorTargetSet = List[TrafficMirrorTarget] +TrafficMirrorTargetSet = list[TrafficMirrorTarget] class DescribeTrafficMirrorTargetsResult(TypedDict, total=False): - TrafficMirrorTargets: Optional[TrafficMirrorTargetSet] - NextToken: Optional[String] - - -TransitGatewayAttachmentIdStringList = List[TransitGatewayAttachmentId] + TrafficMirrorTargets: TrafficMirrorTargetSet | None + NextToken: String | None class DescribeTransitGatewayAttachmentsRequest(ServiceRequest): - TransitGatewayAttachmentIds: Optional[TransitGatewayAttachmentIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayAttachmentIds: TransitGatewayAttachmentIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class TransitGatewayAttachmentAssociation(TypedDict, total=False): - TransitGatewayRouteTableId: Optional[String] - State: Optional[TransitGatewayAssociationState] + TransitGatewayRouteTableId: String | None + State: TransitGatewayAssociationState | None class TransitGatewayAttachment(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[String] - TransitGatewayId: Optional[String] - TransitGatewayOwnerId: Optional[String] - ResourceOwnerId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - ResourceId: Optional[String] - State: Optional[TransitGatewayAttachmentState] - Association: Optional[TransitGatewayAttachmentAssociation] - CreationTime: Optional[DateTime] - Tags: Optional[TagList] + TransitGatewayAttachmentId: String | None + TransitGatewayId: String | None + TransitGatewayOwnerId: String | None + ResourceOwnerId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + ResourceId: String | None + State: TransitGatewayAttachmentState | None + Association: TransitGatewayAttachmentAssociation | None + CreationTime: DateTime | None + Tags: TagList | None -TransitGatewayAttachmentList = List[TransitGatewayAttachment] +TransitGatewayAttachmentList = list[TransitGatewayAttachment] class DescribeTransitGatewayAttachmentsResult(TypedDict, total=False): - TransitGatewayAttachments: Optional[TransitGatewayAttachmentList] - NextToken: Optional[String] + TransitGatewayAttachments: TransitGatewayAttachmentList | None + NextToken: String | None -TransitGatewayConnectPeerIdStringList = List[TransitGatewayConnectPeerId] +TransitGatewayConnectPeerIdStringList = list[TransitGatewayConnectPeerId] class DescribeTransitGatewayConnectPeersRequest(ServiceRequest): - TransitGatewayConnectPeerIds: Optional[TransitGatewayConnectPeerIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayConnectPeerIds: TransitGatewayConnectPeerIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayConnectPeerList = List[TransitGatewayConnectPeer] +TransitGatewayConnectPeerList = list[TransitGatewayConnectPeer] class DescribeTransitGatewayConnectPeersResult(TypedDict, total=False): - TransitGatewayConnectPeers: Optional[TransitGatewayConnectPeerList] - NextToken: Optional[String] + TransitGatewayConnectPeers: TransitGatewayConnectPeerList | None + NextToken: String | None class DescribeTransitGatewayConnectsRequest(ServiceRequest): - TransitGatewayAttachmentIds: Optional[TransitGatewayAttachmentIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayAttachmentIds: TransitGatewayAttachmentIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayConnectList = List[TransitGatewayConnect] +TransitGatewayConnectList = list[TransitGatewayConnect] class DescribeTransitGatewayConnectsResult(TypedDict, total=False): - TransitGatewayConnects: Optional[TransitGatewayConnectList] - NextToken: Optional[String] + TransitGatewayConnects: TransitGatewayConnectList | None + NextToken: String | None + + +TransitGatewayMeteringPolicyIdStringList = list[TransitGatewayMeteringPolicyId] -TransitGatewayMulticastDomainIdStringList = List[TransitGatewayMulticastDomainId] +class DescribeTransitGatewayMeteringPoliciesRequest(ServiceRequest): + TransitGatewayMeteringPolicyIds: TransitGatewayMeteringPolicyIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None + + +TransitGatewayMeteringPolicyList = list[TransitGatewayMeteringPolicy] + + +class DescribeTransitGatewayMeteringPoliciesResult(TypedDict, total=False): + TransitGatewayMeteringPolicies: TransitGatewayMeteringPolicyList | None + NextToken: String | None + + +TransitGatewayMulticastDomainIdStringList = list[TransitGatewayMulticastDomainId] class DescribeTransitGatewayMulticastDomainsRequest(ServiceRequest): - TransitGatewayMulticastDomainIds: Optional[TransitGatewayMulticastDomainIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayMulticastDomainIds: TransitGatewayMulticastDomainIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayMulticastDomainList = List[TransitGatewayMulticastDomain] +TransitGatewayMulticastDomainList = list[TransitGatewayMulticastDomain] class DescribeTransitGatewayMulticastDomainsResult(TypedDict, total=False): - TransitGatewayMulticastDomains: Optional[TransitGatewayMulticastDomainList] - NextToken: Optional[String] + TransitGatewayMulticastDomains: TransitGatewayMulticastDomainList | None + NextToken: String | None class DescribeTransitGatewayPeeringAttachmentsRequest(ServiceRequest): - TransitGatewayAttachmentIds: Optional[TransitGatewayAttachmentIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayAttachmentIds: TransitGatewayAttachmentIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayPeeringAttachmentList = List[TransitGatewayPeeringAttachment] +TransitGatewayPeeringAttachmentList = list[TransitGatewayPeeringAttachment] class DescribeTransitGatewayPeeringAttachmentsResult(TypedDict, total=False): - TransitGatewayPeeringAttachments: Optional[TransitGatewayPeeringAttachmentList] - NextToken: Optional[String] + TransitGatewayPeeringAttachments: TransitGatewayPeeringAttachmentList | None + NextToken: String | None -TransitGatewayPolicyTableIdStringList = List[TransitGatewayPolicyTableId] +TransitGatewayPolicyTableIdStringList = list[TransitGatewayPolicyTableId] class DescribeTransitGatewayPolicyTablesRequest(ServiceRequest): - TransitGatewayPolicyTableIds: Optional[TransitGatewayPolicyTableIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayPolicyTableIds: TransitGatewayPolicyTableIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayPolicyTableList = List[TransitGatewayPolicyTable] +TransitGatewayPolicyTableList = list[TransitGatewayPolicyTable] class DescribeTransitGatewayPolicyTablesResult(TypedDict, total=False): - TransitGatewayPolicyTables: Optional[TransitGatewayPolicyTableList] - NextToken: Optional[String] + TransitGatewayPolicyTables: TransitGatewayPolicyTableList | None + NextToken: String | None -TransitGatewayRouteTableAnnouncementIdStringList = List[TransitGatewayRouteTableAnnouncementId] +TransitGatewayRouteTableAnnouncementIdStringList = list[TransitGatewayRouteTableAnnouncementId] class DescribeTransitGatewayRouteTableAnnouncementsRequest(ServiceRequest): - TransitGatewayRouteTableAnnouncementIds: Optional[ - TransitGatewayRouteTableAnnouncementIdStringList - ] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayRouteTableAnnouncementIds: TransitGatewayRouteTableAnnouncementIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayRouteTableAnnouncementList = List[TransitGatewayRouteTableAnnouncement] +TransitGatewayRouteTableAnnouncementList = list[TransitGatewayRouteTableAnnouncement] class DescribeTransitGatewayRouteTableAnnouncementsResult(TypedDict, total=False): - TransitGatewayRouteTableAnnouncements: Optional[TransitGatewayRouteTableAnnouncementList] - NextToken: Optional[String] + TransitGatewayRouteTableAnnouncements: TransitGatewayRouteTableAnnouncementList | None + NextToken: String | None -TransitGatewayRouteTableIdStringList = List[TransitGatewayRouteTableId] +TransitGatewayRouteTableIdStringList = list[TransitGatewayRouteTableId] class DescribeTransitGatewayRouteTablesRequest(ServiceRequest): - TransitGatewayRouteTableIds: Optional[TransitGatewayRouteTableIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayRouteTableIds: TransitGatewayRouteTableIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayRouteTableList = List[TransitGatewayRouteTable] +TransitGatewayRouteTableList = list[TransitGatewayRouteTable] class DescribeTransitGatewayRouteTablesResult(TypedDict, total=False): - TransitGatewayRouteTables: Optional[TransitGatewayRouteTableList] - NextToken: Optional[String] + TransitGatewayRouteTables: TransitGatewayRouteTableList | None + NextToken: String | None class DescribeTransitGatewayVpcAttachmentsRequest(ServiceRequest): - TransitGatewayAttachmentIds: Optional[TransitGatewayAttachmentIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayAttachmentIds: TransitGatewayAttachmentIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayVpcAttachmentList = List[TransitGatewayVpcAttachment] +TransitGatewayVpcAttachmentList = list[TransitGatewayVpcAttachment] class DescribeTransitGatewayVpcAttachmentsResult(TypedDict, total=False): - TransitGatewayVpcAttachments: Optional[TransitGatewayVpcAttachmentList] - NextToken: Optional[String] + TransitGatewayVpcAttachments: TransitGatewayVpcAttachmentList | None + NextToken: String | None -TransitGatewayIdStringList = List[TransitGatewayId] +TransitGatewayIdStringList = list[TransitGatewayId] class DescribeTransitGatewaysRequest(ServiceRequest): - TransitGatewayIds: Optional[TransitGatewayIdStringList] - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + TransitGatewayIds: TransitGatewayIdStringList | None + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayList = List[TransitGateway] +TransitGatewayList = list[TransitGateway] class DescribeTransitGatewaysResult(TypedDict, total=False): - TransitGateways: Optional[TransitGatewayList] - NextToken: Optional[String] + TransitGateways: TransitGatewayList | None + NextToken: String | None -TrunkInterfaceAssociationIdList = List[TrunkInterfaceAssociationId] +TrunkInterfaceAssociationIdList = list[TrunkInterfaceAssociationId] class DescribeTrunkInterfaceAssociationsRequest(ServiceRequest): - AssociationIds: Optional[TrunkInterfaceAssociationIdList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[DescribeTrunkInterfaceAssociationsMaxResults] + AssociationIds: TrunkInterfaceAssociationIdList | None + DryRun: Boolean | None + Filters: FilterList | None + NextToken: String | None + MaxResults: DescribeTrunkInterfaceAssociationsMaxResults | None -TrunkInterfaceAssociationList = List[TrunkInterfaceAssociation] +TrunkInterfaceAssociationList = list[TrunkInterfaceAssociation] class DescribeTrunkInterfaceAssociationsResult(TypedDict, total=False): - InterfaceAssociations: Optional[TrunkInterfaceAssociationList] - NextToken: Optional[String] + InterfaceAssociations: TrunkInterfaceAssociationList | None + NextToken: String | None -VerifiedAccessEndpointIdList = List[VerifiedAccessEndpointId] +VerifiedAccessEndpointIdList = list[VerifiedAccessEndpointId] class DescribeVerifiedAccessEndpointsRequest(ServiceRequest): - VerifiedAccessEndpointIds: Optional[VerifiedAccessEndpointIdList] - VerifiedAccessInstanceId: Optional[VerifiedAccessInstanceId] - VerifiedAccessGroupId: Optional[VerifiedAccessGroupId] - MaxResults: Optional[DescribeVerifiedAccessEndpointsMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + VerifiedAccessEndpointIds: VerifiedAccessEndpointIdList | None + VerifiedAccessInstanceId: VerifiedAccessInstanceId | None + VerifiedAccessGroupId: VerifiedAccessGroupId | None + MaxResults: DescribeVerifiedAccessEndpointsMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + DryRun: Boolean | None -VerifiedAccessEndpointList = List[VerifiedAccessEndpoint] +VerifiedAccessEndpointList = list[VerifiedAccessEndpoint] class DescribeVerifiedAccessEndpointsResult(TypedDict, total=False): - VerifiedAccessEndpoints: Optional[VerifiedAccessEndpointList] - NextToken: Optional[NextToken] + VerifiedAccessEndpoints: VerifiedAccessEndpointList | None + NextToken: NextToken | None -VerifiedAccessGroupIdList = List[VerifiedAccessGroupId] +VerifiedAccessGroupIdList = list[VerifiedAccessGroupId] class DescribeVerifiedAccessGroupsRequest(ServiceRequest): - VerifiedAccessGroupIds: Optional[VerifiedAccessGroupIdList] - VerifiedAccessInstanceId: Optional[VerifiedAccessInstanceId] - MaxResults: Optional[DescribeVerifiedAccessGroupMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + VerifiedAccessGroupIds: VerifiedAccessGroupIdList | None + VerifiedAccessInstanceId: VerifiedAccessInstanceId | None + MaxResults: DescribeVerifiedAccessGroupMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + DryRun: Boolean | None -VerifiedAccessGroupList = List[VerifiedAccessGroup] +VerifiedAccessGroupList = list[VerifiedAccessGroup] class DescribeVerifiedAccessGroupsResult(TypedDict, total=False): - VerifiedAccessGroups: Optional[VerifiedAccessGroupList] - NextToken: Optional[NextToken] + VerifiedAccessGroups: VerifiedAccessGroupList | None + NextToken: NextToken | None -VerifiedAccessInstanceIdList = List[VerifiedAccessInstanceId] +VerifiedAccessInstanceIdList = list[VerifiedAccessInstanceId] class DescribeVerifiedAccessInstanceLoggingConfigurationsRequest(ServiceRequest): - VerifiedAccessInstanceIds: Optional[VerifiedAccessInstanceIdList] - MaxResults: Optional[DescribeVerifiedAccessInstanceLoggingConfigurationsMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + VerifiedAccessInstanceIds: VerifiedAccessInstanceIdList | None + MaxResults: DescribeVerifiedAccessInstanceLoggingConfigurationsMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + DryRun: Boolean | None class VerifiedAccessLogDeliveryStatus(TypedDict, total=False): - Code: Optional[VerifiedAccessLogDeliveryStatusCode] - Message: Optional[String] + Code: VerifiedAccessLogDeliveryStatusCode | None + Message: String | None class VerifiedAccessLogKinesisDataFirehoseDestination(TypedDict, total=False): - Enabled: Optional[Boolean] - DeliveryStatus: Optional[VerifiedAccessLogDeliveryStatus] - DeliveryStream: Optional[String] + Enabled: Boolean | None + DeliveryStatus: VerifiedAccessLogDeliveryStatus | None + DeliveryStream: String | None class VerifiedAccessLogCloudWatchLogsDestination(TypedDict, total=False): - Enabled: Optional[Boolean] - DeliveryStatus: Optional[VerifiedAccessLogDeliveryStatus] - LogGroup: Optional[String] + Enabled: Boolean | None + DeliveryStatus: VerifiedAccessLogDeliveryStatus | None + LogGroup: String | None class VerifiedAccessLogS3Destination(TypedDict, total=False): - Enabled: Optional[Boolean] - DeliveryStatus: Optional[VerifiedAccessLogDeliveryStatus] - BucketName: Optional[String] - Prefix: Optional[String] - BucketOwner: Optional[String] + Enabled: Boolean | None + DeliveryStatus: VerifiedAccessLogDeliveryStatus | None + BucketName: String | None + Prefix: String | None + BucketOwner: String | None class VerifiedAccessLogs(TypedDict, total=False): - S3: Optional[VerifiedAccessLogS3Destination] - CloudWatchLogs: Optional[VerifiedAccessLogCloudWatchLogsDestination] - KinesisDataFirehose: Optional[VerifiedAccessLogKinesisDataFirehoseDestination] - LogVersion: Optional[String] - IncludeTrustContext: Optional[Boolean] + S3: VerifiedAccessLogS3Destination | None + CloudWatchLogs: VerifiedAccessLogCloudWatchLogsDestination | None + KinesisDataFirehose: VerifiedAccessLogKinesisDataFirehoseDestination | None + LogVersion: String | None + IncludeTrustContext: Boolean | None class VerifiedAccessInstanceLoggingConfiguration(TypedDict, total=False): - VerifiedAccessInstanceId: Optional[String] - AccessLogs: Optional[VerifiedAccessLogs] + VerifiedAccessInstanceId: String | None + AccessLogs: VerifiedAccessLogs | None -VerifiedAccessInstanceLoggingConfigurationList = List[VerifiedAccessInstanceLoggingConfiguration] +VerifiedAccessInstanceLoggingConfigurationList = list[VerifiedAccessInstanceLoggingConfiguration] class DescribeVerifiedAccessInstanceLoggingConfigurationsResult(TypedDict, total=False): - LoggingConfigurations: Optional[VerifiedAccessInstanceLoggingConfigurationList] - NextToken: Optional[NextToken] + LoggingConfigurations: VerifiedAccessInstanceLoggingConfigurationList | None + NextToken: NextToken | None class DescribeVerifiedAccessInstancesRequest(ServiceRequest): - VerifiedAccessInstanceIds: Optional[VerifiedAccessInstanceIdList] - MaxResults: Optional[DescribeVerifiedAccessInstancesMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + VerifiedAccessInstanceIds: VerifiedAccessInstanceIdList | None + MaxResults: DescribeVerifiedAccessInstancesMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + DryRun: Boolean | None -VerifiedAccessInstanceList = List[VerifiedAccessInstance] +VerifiedAccessInstanceList = list[VerifiedAccessInstance] class DescribeVerifiedAccessInstancesResult(TypedDict, total=False): - VerifiedAccessInstances: Optional[VerifiedAccessInstanceList] - NextToken: Optional[NextToken] + VerifiedAccessInstances: VerifiedAccessInstanceList | None + NextToken: NextToken | None -VerifiedAccessTrustProviderIdList = List[VerifiedAccessTrustProviderId] +VerifiedAccessTrustProviderIdList = list[VerifiedAccessTrustProviderId] class DescribeVerifiedAccessTrustProvidersRequest(ServiceRequest): - VerifiedAccessTrustProviderIds: Optional[VerifiedAccessTrustProviderIdList] - MaxResults: Optional[DescribeVerifiedAccessTrustProvidersMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + VerifiedAccessTrustProviderIds: VerifiedAccessTrustProviderIdList | None + MaxResults: DescribeVerifiedAccessTrustProvidersMaxResults | None + NextToken: NextToken | None + Filters: FilterList | None + DryRun: Boolean | None -VerifiedAccessTrustProviderList = List[VerifiedAccessTrustProvider] +VerifiedAccessTrustProviderList = list[VerifiedAccessTrustProvider] class DescribeVerifiedAccessTrustProvidersResult(TypedDict, total=False): - VerifiedAccessTrustProviders: Optional[VerifiedAccessTrustProviderList] - NextToken: Optional[NextToken] + VerifiedAccessTrustProviders: VerifiedAccessTrustProviderList | None + NextToken: NextToken | None class DescribeVolumeAttributeRequest(ServiceRequest): Attribute: VolumeAttributeName VolumeId: VolumeId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DescribeVolumeAttributeResult(TypedDict, total=False): - AutoEnableIO: Optional[AttributeBooleanValue] - ProductCodes: Optional[ProductCodeList] - VolumeId: Optional[String] + AutoEnableIO: AttributeBooleanValue | None + ProductCodes: ProductCodeList | None + VolumeId: String | None class DescribeVolumeStatusRequest(ServiceRequest): - MaxResults: Optional[Integer] - NextToken: Optional[String] - VolumeIds: Optional[VolumeIdStringList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + MaxResults: Integer | None + NextToken: String | None + VolumeIds: VolumeIdStringList | None + DryRun: Boolean | None + Filters: FilterList | None + + +class InitializationStatusDetails(TypedDict, total=False): + InitializationType: InitializationType | None + Progress: Long | None + EstimatedTimeToCompleteInSeconds: Long | None class VolumeStatusAttachmentStatus(TypedDict, total=False): - IoPerformance: Optional[String] - InstanceId: Optional[String] + IoPerformance: String | None + InstanceId: String | None -VolumeStatusAttachmentStatusList = List[VolumeStatusAttachmentStatus] +VolumeStatusAttachmentStatusList = list[VolumeStatusAttachmentStatus] class VolumeStatusDetails(TypedDict, total=False): - Name: Optional[VolumeStatusName] - Status: Optional[String] + Name: VolumeStatusName | None + Status: String | None -VolumeStatusDetailsList = List[VolumeStatusDetails] +VolumeStatusDetailsList = list[VolumeStatusDetails] class VolumeStatusInfo(TypedDict, total=False): - Details: Optional[VolumeStatusDetailsList] - Status: Optional[VolumeStatusInfoStatus] + Details: VolumeStatusDetailsList | None + Status: VolumeStatusInfoStatus | None class VolumeStatusEvent(TypedDict, total=False): - Description: Optional[String] - EventId: Optional[String] - EventType: Optional[String] - NotAfter: Optional[MillisecondDateTime] - NotBefore: Optional[MillisecondDateTime] - InstanceId: Optional[String] + Description: String | None + EventId: String | None + EventType: String | None + NotAfter: MillisecondDateTime | None + NotBefore: MillisecondDateTime | None + InstanceId: String | None -VolumeStatusEventsList = List[VolumeStatusEvent] +VolumeStatusEventsList = list[VolumeStatusEvent] class VolumeStatusAction(TypedDict, total=False): - Code: Optional[String] - Description: Optional[String] - EventId: Optional[String] - EventType: Optional[String] + Code: String | None + Description: String | None + EventId: String | None + EventType: String | None -VolumeStatusActionsList = List[VolumeStatusAction] +VolumeStatusActionsList = list[VolumeStatusAction] class VolumeStatusItem(TypedDict, total=False): - Actions: Optional[VolumeStatusActionsList] - AvailabilityZone: Optional[String] - OutpostArn: Optional[String] - Events: Optional[VolumeStatusEventsList] - VolumeId: Optional[String] - VolumeStatus: Optional[VolumeStatusInfo] - AttachmentStatuses: Optional[VolumeStatusAttachmentStatusList] - AvailabilityZoneId: Optional[String] + Actions: VolumeStatusActionsList | None + AvailabilityZone: String | None + OutpostArn: String | None + Events: VolumeStatusEventsList | None + VolumeId: String | None + VolumeStatus: VolumeStatusInfo | None + AttachmentStatuses: VolumeStatusAttachmentStatusList | None + InitializationStatusDetails: InitializationStatusDetails | None + AvailabilityZoneId: String | None -VolumeStatusList = List[VolumeStatusItem] +VolumeStatusList = list[VolumeStatusItem] class DescribeVolumeStatusResult(TypedDict, total=False): - NextToken: Optional[String] - VolumeStatuses: Optional[VolumeStatusList] + NextToken: String | None + VolumeStatuses: VolumeStatusList | None class DescribeVolumesModificationsRequest(ServiceRequest): - DryRun: Optional[Boolean] - VolumeIds: Optional[VolumeIdStringList] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[Integer] + DryRun: Boolean | None + VolumeIds: VolumeIdStringList | None + Filters: FilterList | None + NextToken: String | None + MaxResults: Integer | None class VolumeModification(TypedDict, total=False): - VolumeId: Optional[String] - ModificationState: Optional[VolumeModificationState] - StatusMessage: Optional[String] - TargetSize: Optional[Integer] - TargetIops: Optional[Integer] - TargetVolumeType: Optional[VolumeType] - TargetThroughput: Optional[Integer] - TargetMultiAttachEnabled: Optional[Boolean] - OriginalSize: Optional[Integer] - OriginalIops: Optional[Integer] - OriginalVolumeType: Optional[VolumeType] - OriginalThroughput: Optional[Integer] - OriginalMultiAttachEnabled: Optional[Boolean] - Progress: Optional[Long] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] - - -VolumeModificationList = List[VolumeModification] + VolumeId: String | None + ModificationState: VolumeModificationState | None + StatusMessage: String | None + TargetSize: Integer | None + TargetIops: Integer | None + TargetVolumeType: VolumeType | None + TargetThroughput: Integer | None + TargetMultiAttachEnabled: Boolean | None + OriginalSize: Integer | None + OriginalIops: Integer | None + OriginalVolumeType: VolumeType | None + OriginalThroughput: Integer | None + OriginalMultiAttachEnabled: Boolean | None + Progress: Long | None + StartTime: DateTime | None + EndTime: DateTime | None + + +VolumeModificationList = list[VolumeModification] class DescribeVolumesModificationsResult(TypedDict, total=False): - NextToken: Optional[String] - VolumesModifications: Optional[VolumeModificationList] + NextToken: String | None + VolumesModifications: VolumeModificationList | None class DescribeVolumesRequest(ServiceRequest): - VolumeIds: Optional[VolumeIdStringList] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - NextToken: Optional[String] - MaxResults: Optional[Integer] - - -class VolumeAttachment(TypedDict, total=False): - DeleteOnTermination: Optional[Boolean] - AssociatedResource: Optional[String] - InstanceOwningService: Optional[String] - VolumeId: Optional[String] - InstanceId: Optional[String] - Device: Optional[String] - State: Optional[VolumeAttachmentState] - AttachTime: Optional[DateTime] - - -VolumeAttachmentList = List[VolumeAttachment] - - -class Volume(TypedDict, total=False): - OutpostArn: Optional[String] - Iops: Optional[Integer] - Tags: Optional[TagList] - VolumeType: Optional[VolumeType] - FastRestored: Optional[Boolean] - MultiAttachEnabled: Optional[Boolean] - Throughput: Optional[Integer] - SseType: Optional[SSEType] - Operator: Optional[OperatorResponse] - VolumeInitializationRate: Optional[Integer] - VolumeId: Optional[String] - Size: Optional[Integer] - SnapshotId: Optional[String] - AvailabilityZone: Optional[String] - State: Optional[VolumeState] - CreateTime: Optional[DateTime] - Attachments: Optional[VolumeAttachmentList] - Encrypted: Optional[Boolean] - KmsKeyId: Optional[String] - - -VolumeList = List[Volume] + VolumeIds: VolumeIdStringList | None + DryRun: Boolean | None + Filters: FilterList | None + NextToken: String | None + MaxResults: Integer | None class DescribeVolumesResult(TypedDict, total=False): - NextToken: Optional[String] - Volumes: Optional[VolumeList] + NextToken: String | None + Volumes: VolumeList | None class DescribeVpcAttributeRequest(ServiceRequest): Attribute: VpcAttributeName VpcId: VpcId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DescribeVpcAttributeResult(TypedDict, total=False): - EnableDnsHostnames: Optional[AttributeBooleanValue] - EnableDnsSupport: Optional[AttributeBooleanValue] - EnableNetworkAddressUsageMetrics: Optional[AttributeBooleanValue] - VpcId: Optional[String] + EnableDnsHostnames: AttributeBooleanValue | None + EnableDnsSupport: AttributeBooleanValue | None + EnableNetworkAddressUsageMetrics: AttributeBooleanValue | None + VpcId: String | None -VpcBlockPublicAccessExclusionIdList = List[VpcBlockPublicAccessExclusionId] +VpcBlockPublicAccessExclusionIdList = list[VpcBlockPublicAccessExclusionId] class DescribeVpcBlockPublicAccessExclusionsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - ExclusionIds: Optional[VpcBlockPublicAccessExclusionIdList] - NextToken: Optional[String] - MaxResults: Optional[DescribeVpcBlockPublicAccessExclusionsMaxResults] + DryRun: Boolean | None + Filters: FilterList | None + ExclusionIds: VpcBlockPublicAccessExclusionIdList | None + NextToken: String | None + MaxResults: DescribeVpcBlockPublicAccessExclusionsMaxResults | None -VpcBlockPublicAccessExclusionList = List[VpcBlockPublicAccessExclusion] +VpcBlockPublicAccessExclusionList = list[VpcBlockPublicAccessExclusion] class DescribeVpcBlockPublicAccessExclusionsResult(TypedDict, total=False): - VpcBlockPublicAccessExclusions: Optional[VpcBlockPublicAccessExclusionList] - NextToken: Optional[String] + VpcBlockPublicAccessExclusions: VpcBlockPublicAccessExclusionList | None + NextToken: String | None class DescribeVpcBlockPublicAccessOptionsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class VpcBlockPublicAccessOptions(TypedDict, total=False): - AwsAccountId: Optional[String] - AwsRegion: Optional[String] - State: Optional[VpcBlockPublicAccessState] - InternetGatewayBlockMode: Optional[InternetGatewayBlockMode] - Reason: Optional[String] - LastUpdateTimestamp: Optional[MillisecondDateTime] - ManagedBy: Optional[ManagedBy] - ExclusionsAllowed: Optional[VpcBlockPublicAccessExclusionsAllowed] + AwsAccountId: String | None + AwsRegion: String | None + State: VpcBlockPublicAccessState | None + InternetGatewayBlockMode: InternetGatewayBlockMode | None + Reason: String | None + LastUpdateTimestamp: MillisecondDateTime | None + ManagedBy: ManagedBy | None + ExclusionsAllowed: VpcBlockPublicAccessExclusionsAllowed | None class DescribeVpcBlockPublicAccessOptionsResult(TypedDict, total=False): - VpcBlockPublicAccessOptions: Optional[VpcBlockPublicAccessOptions] + VpcBlockPublicAccessOptions: VpcBlockPublicAccessOptions | None -VpcClassicLinkIdList = List[VpcId] +VpcClassicLinkIdList = list[VpcId] class DescribeVpcClassicLinkDnsSupportRequest(ServiceRequest): - VpcIds: Optional[VpcClassicLinkIdList] - MaxResults: Optional[DescribeVpcClassicLinkDnsSupportMaxResults] - NextToken: Optional[DescribeVpcClassicLinkDnsSupportNextToken] + VpcIds: VpcClassicLinkIdList | None + MaxResults: DescribeVpcClassicLinkDnsSupportMaxResults | None + NextToken: DescribeVpcClassicLinkDnsSupportNextToken | None class DescribeVpcClassicLinkDnsSupportResult(TypedDict, total=False): - NextToken: Optional[DescribeVpcClassicLinkDnsSupportNextToken] - Vpcs: Optional[ClassicLinkDnsSupportList] + NextToken: DescribeVpcClassicLinkDnsSupportNextToken | None + Vpcs: ClassicLinkDnsSupportList | None class DescribeVpcClassicLinkRequest(ServiceRequest): - DryRun: Optional[Boolean] - VpcIds: Optional[VpcClassicLinkIdList] - Filters: Optional[FilterList] + DryRun: Boolean | None + VpcIds: VpcClassicLinkIdList | None + Filters: FilterList | None class VpcClassicLink(TypedDict, total=False): - ClassicLinkEnabled: Optional[Boolean] - Tags: Optional[TagList] - VpcId: Optional[String] + ClassicLinkEnabled: Boolean | None + Tags: TagList | None + VpcId: String | None -VpcClassicLinkList = List[VpcClassicLink] +VpcClassicLinkList = list[VpcClassicLink] class DescribeVpcClassicLinkResult(TypedDict, total=False): - Vpcs: Optional[VpcClassicLinkList] + Vpcs: VpcClassicLinkList | None + + +VpcIdStringList = list[VpcId] +VpcEncryptionControlIdList = list[VpcEncryptionControlId] + + +class DescribeVpcEncryptionControlsRequest(ServiceRequest): + DryRun: Boolean | None + Filters: FilterList | None + VpcEncryptionControlIds: VpcEncryptionControlIdList | None + VpcIds: VpcIdStringList | None + NextToken: String | None + MaxResults: DescribeVpcEncryptionControlsMaxResults | None + + +VpcEncryptionControlList = list[VpcEncryptionControl] + + +class DescribeVpcEncryptionControlsResult(TypedDict, total=False): + VpcEncryptionControls: VpcEncryptionControlList | None + NextToken: String | None class DescribeVpcEndpointAssociationsRequest(ServiceRequest): - DryRun: Optional[Boolean] - VpcEndpointIds: Optional[VpcEndpointIdList] - Filters: Optional[FilterList] - MaxResults: Optional[maxResults] - NextToken: Optional[String] + DryRun: Boolean | None + VpcEndpointIds: VpcEndpointIdList | None + Filters: FilterList | None + MaxResults: maxResults | None + NextToken: String | None class VpcEndpointAssociation(TypedDict, total=False): - Id: Optional[String] - VpcEndpointId: Optional[VpcEndpointId] - ServiceNetworkArn: Optional[ServiceNetworkArn] - ServiceNetworkName: Optional[String] - AssociatedResourceAccessibility: Optional[String] - FailureReason: Optional[String] - FailureCode: Optional[String] - DnsEntry: Optional[DnsEntry] - PrivateDnsEntry: Optional[DnsEntry] - AssociatedResourceArn: Optional[String] - ResourceConfigurationGroupArn: Optional[String] - Tags: Optional[TagList] + Id: String | None + VpcEndpointId: VpcEndpointId | None + ServiceNetworkArn: ServiceNetworkArn | None + ServiceNetworkName: String | None + AssociatedResourceAccessibility: String | None + FailureReason: String | None + FailureCode: String | None + DnsEntry: DnsEntry | None + PrivateDnsEntry: DnsEntry | None + AssociatedResourceArn: String | None + ResourceConfigurationGroupArn: String | None + Tags: TagList | None -VpcEndpointAssociationSet = List[VpcEndpointAssociation] +VpcEndpointAssociationSet = list[VpcEndpointAssociation] class DescribeVpcEndpointAssociationsResult(TypedDict, total=False): - VpcEndpointAssociations: Optional[VpcEndpointAssociationSet] - NextToken: Optional[String] + VpcEndpointAssociations: VpcEndpointAssociationSet | None + NextToken: String | None class DescribeVpcEndpointConnectionNotificationsRequest(ServiceRequest): - DryRun: Optional[Boolean] - ConnectionNotificationId: Optional[ConnectionNotificationId] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + ConnectionNotificationId: ConnectionNotificationId | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None class DescribeVpcEndpointConnectionNotificationsResult(TypedDict, total=False): - ConnectionNotificationSet: Optional[ConnectionNotificationSet] - NextToken: Optional[String] + ConnectionNotificationSet: ConnectionNotificationSet | None + NextToken: String | None class DescribeVpcEndpointConnectionsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None class VpcEndpointConnection(TypedDict, total=False): - ServiceId: Optional[String] - VpcEndpointId: Optional[String] - VpcEndpointOwner: Optional[String] - VpcEndpointState: Optional[State] - CreationTimestamp: Optional[MillisecondDateTime] - DnsEntries: Optional[DnsEntrySet] - NetworkLoadBalancerArns: Optional[ValueStringList] - GatewayLoadBalancerArns: Optional[ValueStringList] - IpAddressType: Optional[IpAddressType] - VpcEndpointConnectionId: Optional[String] - Tags: Optional[TagList] - VpcEndpointRegion: Optional[String] + ServiceId: String | None + VpcEndpointId: String | None + VpcEndpointOwner: String | None + VpcEndpointState: State | None + CreationTimestamp: MillisecondDateTime | None + DnsEntries: DnsEntrySet | None + NetworkLoadBalancerArns: ValueStringList | None + GatewayLoadBalancerArns: ValueStringList | None + IpAddressType: IpAddressType | None + VpcEndpointConnectionId: String | None + Tags: TagList | None + VpcEndpointRegion: String | None -VpcEndpointConnectionSet = List[VpcEndpointConnection] +VpcEndpointConnectionSet = list[VpcEndpointConnection] class DescribeVpcEndpointConnectionsResult(TypedDict, total=False): - VpcEndpointConnections: Optional[VpcEndpointConnectionSet] - NextToken: Optional[String] + VpcEndpointConnections: VpcEndpointConnectionSet | None + NextToken: String | None class DescribeVpcEndpointServiceConfigurationsRequest(ServiceRequest): - DryRun: Optional[Boolean] - ServiceIds: Optional[VpcEndpointServiceIdList] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + ServiceIds: VpcEndpointServiceIdList | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None -ServiceConfigurationSet = List[ServiceConfiguration] +ServiceConfigurationSet = list[ServiceConfiguration] class DescribeVpcEndpointServiceConfigurationsResult(TypedDict, total=False): - ServiceConfigurations: Optional[ServiceConfigurationSet] - NextToken: Optional[String] + ServiceConfigurations: ServiceConfigurationSet | None + NextToken: String | None class DescribeVpcEndpointServicePermissionsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceId: VpcEndpointServiceId - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None class DescribeVpcEndpointServicePermissionsResult(TypedDict, total=False): - AllowedPrincipals: Optional[AllowedPrincipalSet] - NextToken: Optional[String] + AllowedPrincipals: AllowedPrincipalSet | None + NextToken: String | None class DescribeVpcEndpointServicesRequest(ServiceRequest): - DryRun: Optional[Boolean] - ServiceNames: Optional[ValueStringList] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] - ServiceRegions: Optional[ValueStringList] + DryRun: Boolean | None + ServiceNames: ValueStringList | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None + ServiceRegions: ValueStringList | None class PrivateDnsDetails(TypedDict, total=False): - PrivateDnsName: Optional[String] + PrivateDnsName: String | None -PrivateDnsDetailsSet = List[PrivateDnsDetails] +PrivateDnsDetailsSet = list[PrivateDnsDetails] class ServiceDetail(TypedDict, total=False): - ServiceName: Optional[String] - ServiceId: Optional[String] - ServiceType: Optional[ServiceTypeDetailSet] - ServiceRegion: Optional[String] - AvailabilityZones: Optional[ValueStringList] - Owner: Optional[String] - BaseEndpointDnsNames: Optional[ValueStringList] - PrivateDnsName: Optional[String] - PrivateDnsNames: Optional[PrivateDnsDetailsSet] - VpcEndpointPolicySupported: Optional[Boolean] - AcceptanceRequired: Optional[Boolean] - ManagesVpcEndpoints: Optional[Boolean] - PayerResponsibility: Optional[PayerResponsibility] - Tags: Optional[TagList] - PrivateDnsNameVerificationState: Optional[DnsNameState] - SupportedIpAddressTypes: Optional[SupportedIpAddressTypes] - - -ServiceDetailSet = List[ServiceDetail] + ServiceName: String | None + ServiceId: String | None + ServiceType: ServiceTypeDetailSet | None + ServiceRegion: String | None + AvailabilityZoneIds: ValueStringList | None + AvailabilityZones: ValueStringList | None + Owner: String | None + BaseEndpointDnsNames: ValueStringList | None + PrivateDnsName: String | None + PrivateDnsNames: PrivateDnsDetailsSet | None + VpcEndpointPolicySupported: Boolean | None + AcceptanceRequired: Boolean | None + ManagesVpcEndpoints: Boolean | None + PayerResponsibility: PayerResponsibility | None + Tags: TagList | None + PrivateDnsNameVerificationState: DnsNameState | None + SupportedIpAddressTypes: SupportedIpAddressTypes | None + + +ServiceDetailSet = list[ServiceDetail] class DescribeVpcEndpointServicesResult(TypedDict, total=False): - ServiceNames: Optional[ValueStringList] - ServiceDetails: Optional[ServiceDetailSet] - NextToken: Optional[String] + ServiceNames: ValueStringList | None + ServiceDetails: ServiceDetailSet | None + NextToken: String | None class DescribeVpcEndpointsRequest(ServiceRequest): - DryRun: Optional[Boolean] - VpcEndpointIds: Optional[VpcEndpointIdList] - Filters: Optional[FilterList] - MaxResults: Optional[Integer] - NextToken: Optional[String] + DryRun: Boolean | None + VpcEndpointIds: VpcEndpointIdList | None + Filters: FilterList | None + MaxResults: Integer | None + NextToken: String | None -VpcEndpointSet = List[VpcEndpoint] +VpcEndpointSet = list[VpcEndpoint] class DescribeVpcEndpointsResult(TypedDict, total=False): - VpcEndpoints: Optional[VpcEndpointSet] - NextToken: Optional[String] + VpcEndpoints: VpcEndpointSet | None + NextToken: String | None -VpcPeeringConnectionIdList = List[VpcPeeringConnectionId] +VpcPeeringConnectionIdList = list[VpcPeeringConnectionId] class DescribeVpcPeeringConnectionsRequest(ServiceRequest): - NextToken: Optional[String] - MaxResults: Optional[DescribeVpcPeeringConnectionsMaxResults] - DryRun: Optional[Boolean] - VpcPeeringConnectionIds: Optional[VpcPeeringConnectionIdList] - Filters: Optional[FilterList] + NextToken: String | None + MaxResults: DescribeVpcPeeringConnectionsMaxResults | None + DryRun: Boolean | None + VpcPeeringConnectionIds: VpcPeeringConnectionIdList | None + Filters: FilterList | None -VpcPeeringConnectionList = List[VpcPeeringConnection] +VpcPeeringConnectionList = list[VpcPeeringConnection] class DescribeVpcPeeringConnectionsResult(TypedDict, total=False): - VpcPeeringConnections: Optional[VpcPeeringConnectionList] - NextToken: Optional[String] - - -VpcIdStringList = List[VpcId] + VpcPeeringConnections: VpcPeeringConnectionList | None + NextToken: String | None class DescribeVpcsRequest(ServiceRequest): - Filters: Optional[FilterList] - VpcIds: Optional[VpcIdStringList] - NextToken: Optional[String] - MaxResults: Optional[DescribeVpcsMaxResults] - DryRun: Optional[Boolean] + Filters: FilterList | None + VpcIds: VpcIdStringList | None + NextToken: String | None + MaxResults: DescribeVpcsMaxResults | None + DryRun: Boolean | None -VpcList = List[Vpc] +VpcList = list[Vpc] class DescribeVpcsResult(TypedDict, total=False): - NextToken: Optional[String] - Vpcs: Optional[VpcList] + NextToken: String | None + Vpcs: VpcList | None + + +VpnConcentratorIdStringList = list[VpnConcentratorId] + + +class DescribeVpnConcentratorsRequest(ServiceRequest): + VpnConcentratorIds: VpnConcentratorIdStringList | None + Filters: FilterList | None + MaxResults: GVCDMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None + +VpnConcentratorList = list[VpnConcentrator] -VpnConnectionIdStringList = List[VpnConnectionId] + +class DescribeVpnConcentratorsResult(TypedDict, total=False): + VpnConcentrators: VpnConcentratorList | None + NextToken: NextToken | None + + +VpnConnectionIdStringList = list[VpnConnectionId] class DescribeVpnConnectionsRequest(ServiceRequest): - Filters: Optional[FilterList] - VpnConnectionIds: Optional[VpnConnectionIdStringList] - DryRun: Optional[Boolean] + Filters: FilterList | None + VpnConnectionIds: VpnConnectionIdStringList | None + DryRun: Boolean | None -VpnConnectionList = List[VpnConnection] +VpnConnectionList = list[VpnConnection] class DescribeVpnConnectionsResult(TypedDict, total=False): - VpnConnections: Optional[VpnConnectionList] + VpnConnections: VpnConnectionList | None -VpnGatewayIdStringList = List[VpnGatewayId] +VpnGatewayIdStringList = list[VpnGatewayId] class DescribeVpnGatewaysRequest(ServiceRequest): - Filters: Optional[FilterList] - VpnGatewayIds: Optional[VpnGatewayIdStringList] - DryRun: Optional[Boolean] + Filters: FilterList | None + VpnGatewayIds: VpnGatewayIdStringList | None + DryRun: Boolean | None -VpnGatewayList = List[VpnGateway] +VpnGatewayList = list[VpnGateway] class DescribeVpnGatewaysResult(TypedDict, total=False): - VpnGateways: Optional[VpnGatewayList] + VpnGateways: VpnGatewayList | None class DetachClassicLinkVpcRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId VpcId: VpcId class DetachClassicLinkVpcResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DetachInternetGatewayRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InternetGatewayId: InternetGatewayId VpcId: VpcId class DetachNetworkInterfaceRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None AttachmentId: NetworkInterfaceAttachmentId - Force: Optional[Boolean] + Force: Boolean | None class DetachVerifiedAccessTrustProviderRequest(ServiceRequest): VerifiedAccessInstanceId: VerifiedAccessInstanceId VerifiedAccessTrustProviderId: VerifiedAccessTrustProviderId - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None class DetachVerifiedAccessTrustProviderResult(TypedDict, total=False): - VerifiedAccessTrustProvider: Optional[VerifiedAccessTrustProvider] - VerifiedAccessInstance: Optional[VerifiedAccessInstance] + VerifiedAccessTrustProvider: VerifiedAccessTrustProvider | None + VerifiedAccessInstance: VerifiedAccessInstance | None class DetachVolumeRequest(ServiceRequest): - Device: Optional[String] - Force: Optional[Boolean] - InstanceId: Optional[InstanceIdForResolver] + Device: String | None + Force: Boolean | None + InstanceId: InstanceIdForResolver | None VolumeId: VolumeIdWithResolver - DryRun: Optional[Boolean] + DryRun: Boolean | None class DetachVpnGatewayRequest(ServiceRequest): VpcId: VpcId VpnGatewayId: VpnGatewayId - DryRun: Optional[Boolean] + DryRun: Boolean | None -DeviceTrustProviderTypeList = List[DeviceTrustProviderType] +DeviceTrustProviderTypeList = list[DeviceTrustProviderType] class DisableAddressTransferRequest(ServiceRequest): AllocationId: AllocationId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableAddressTransferResult(TypedDict, total=False): - AddressTransfer: Optional[AddressTransfer] + AddressTransfer: AddressTransfer | None class DisableAllowedImagesSettingsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableAllowedImagesSettingsResult(TypedDict, total=False): - AllowedImagesSettingsState: Optional[AllowedImagesSettingsDisabledState] + AllowedImagesSettingsState: AllowedImagesSettingsDisabledState | None class DisableAwsNetworkPerformanceMetricSubscriptionRequest(ServiceRequest): - Source: Optional[String] - Destination: Optional[String] - Metric: Optional[MetricType] - Statistic: Optional[StatisticType] - DryRun: Optional[Boolean] + Source: String | None + Destination: String | None + Metric: MetricType | None + Statistic: StatisticType | None + DryRun: Boolean | None class DisableAwsNetworkPerformanceMetricSubscriptionResult(TypedDict, total=False): - Output: Optional[Boolean] + Output: Boolean | None + + +class DisableCapacityManagerRequest(ServiceRequest): + DryRun: Boolean | None + ClientToken: String | None + + +class DisableCapacityManagerResult(TypedDict, total=False): + CapacityManagerStatus: CapacityManagerStatus | None + OrganizationsAccess: Boolean | None class DisableEbsEncryptionByDefaultRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableEbsEncryptionByDefaultResult(TypedDict, total=False): - EbsEncryptionByDefault: Optional[Boolean] + EbsEncryptionByDefault: Boolean | None class DisableFastLaunchRequest(ServiceRequest): ImageId: ImageId - Force: Optional[Boolean] - DryRun: Optional[Boolean] + Force: Boolean | None + DryRun: Boolean | None class DisableFastLaunchResult(TypedDict, total=False): - ImageId: Optional[ImageId] - ResourceType: Optional[FastLaunchResourceType] - SnapshotConfiguration: Optional[FastLaunchSnapshotConfigurationResponse] - LaunchTemplate: Optional[FastLaunchLaunchTemplateSpecificationResponse] - MaxParallelLaunches: Optional[Integer] - OwnerId: Optional[String] - State: Optional[FastLaunchStateCode] - StateTransitionReason: Optional[String] - StateTransitionTime: Optional[MillisecondDateTime] + ImageId: ImageId | None + ResourceType: FastLaunchResourceType | None + SnapshotConfiguration: FastLaunchSnapshotConfigurationResponse | None + LaunchTemplate: FastLaunchLaunchTemplateSpecificationResponse | None + MaxParallelLaunches: Integer | None + OwnerId: String | None + State: FastLaunchStateCode | None + StateTransitionReason: String | None + StateTransitionTime: MillisecondDateTime | None class DisableFastSnapshotRestoreStateError(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None class DisableFastSnapshotRestoreStateErrorItem(TypedDict, total=False): - AvailabilityZone: Optional[String] - Error: Optional[DisableFastSnapshotRestoreStateError] + AvailabilityZone: String | None + AvailabilityZoneId: String | None + Error: DisableFastSnapshotRestoreStateError | None -DisableFastSnapshotRestoreStateErrorSet = List[DisableFastSnapshotRestoreStateErrorItem] +DisableFastSnapshotRestoreStateErrorSet = list[DisableFastSnapshotRestoreStateErrorItem] class DisableFastSnapshotRestoreErrorItem(TypedDict, total=False): - SnapshotId: Optional[String] - FastSnapshotRestoreStateErrors: Optional[DisableFastSnapshotRestoreStateErrorSet] + SnapshotId: String | None + FastSnapshotRestoreStateErrors: DisableFastSnapshotRestoreStateErrorSet | None -DisableFastSnapshotRestoreErrorSet = List[DisableFastSnapshotRestoreErrorItem] +DisableFastSnapshotRestoreErrorSet = list[DisableFastSnapshotRestoreErrorItem] class DisableFastSnapshotRestoreSuccessItem(TypedDict, total=False): - SnapshotId: Optional[String] - AvailabilityZone: Optional[String] - State: Optional[FastSnapshotRestoreStateCode] - StateTransitionReason: Optional[String] - OwnerId: Optional[String] - OwnerAlias: Optional[String] - EnablingTime: Optional[MillisecondDateTime] - OptimizingTime: Optional[MillisecondDateTime] - EnabledTime: Optional[MillisecondDateTime] - DisablingTime: Optional[MillisecondDateTime] - DisabledTime: Optional[MillisecondDateTime] + SnapshotId: String | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + State: FastSnapshotRestoreStateCode | None + StateTransitionReason: String | None + OwnerId: String | None + OwnerAlias: String | None + EnablingTime: MillisecondDateTime | None + OptimizingTime: MillisecondDateTime | None + EnabledTime: MillisecondDateTime | None + DisablingTime: MillisecondDateTime | None + DisabledTime: MillisecondDateTime | None -DisableFastSnapshotRestoreSuccessSet = List[DisableFastSnapshotRestoreSuccessItem] +DisableFastSnapshotRestoreSuccessSet = list[DisableFastSnapshotRestoreSuccessItem] class DisableFastSnapshotRestoresRequest(ServiceRequest): - AvailabilityZones: AvailabilityZoneStringList + AvailabilityZones: AvailabilityZoneStringList | None + AvailabilityZoneIds: AvailabilityZoneIdStringList | None SourceSnapshotIds: SnapshotIdStringList - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableFastSnapshotRestoresResult(TypedDict, total=False): - Successful: Optional[DisableFastSnapshotRestoreSuccessSet] - Unsuccessful: Optional[DisableFastSnapshotRestoreErrorSet] + Successful: DisableFastSnapshotRestoreSuccessSet | None + Unsuccessful: DisableFastSnapshotRestoreErrorSet | None class DisableImageBlockPublicAccessRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableImageBlockPublicAccessResult(TypedDict, total=False): - ImageBlockPublicAccessState: Optional[ImageBlockPublicAccessDisabledState] + ImageBlockPublicAccessState: ImageBlockPublicAccessDisabledState | None class DisableImageDeprecationRequest(ServiceRequest): ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableImageDeprecationResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DisableImageDeregistrationProtectionRequest(ServiceRequest): ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableImageDeregistrationProtectionResult(TypedDict, total=False): - Return: Optional[String] + Return: String | None class DisableImageRequest(ServiceRequest): ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableImageResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None + + +InstanceIdUpdateStringList = list[InstanceId] + + +class DisableInstanceSqlHaStandbyDetectionsRequest(ServiceRequest): + InstanceIds: InstanceIdUpdateStringList + DryRun: Boolean | None + + +class DisableInstanceSqlHaStandbyDetectionsResult(TypedDict, total=False): + Instances: RegisteredInstanceList | None class DisableIpamOrganizationAdminAccountRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None DelegatedAdminAccountId: String class DisableIpamOrganizationAdminAccountResult(TypedDict, total=False): - Success: Optional[Boolean] + Success: Boolean | None + + +class DisableIpamPolicyRequest(ServiceRequest): + DryRun: Boolean | None + IpamPolicyId: IpamPolicyId + OrganizationTargetId: String | None + + +class DisableIpamPolicyResult(TypedDict, total=False): + Return: Boolean | None class DisableRouteServerPropagationRequest(ServiceRequest): RouteServerId: RouteServerId RouteTableId: RouteTableId - DryRun: Optional[Boolean] + DryRun: Boolean | None class RouteServerPropagation(TypedDict, total=False): - RouteServerId: Optional[RouteServerId] - RouteTableId: Optional[RouteTableId] - State: Optional[RouteServerPropagationState] + RouteServerId: RouteServerId | None + RouteTableId: RouteTableId | None + State: RouteServerPropagationState | None class DisableRouteServerPropagationResult(TypedDict, total=False): - RouteServerPropagation: Optional[RouteServerPropagation] + RouteServerPropagation: RouteServerPropagation | None class DisableSerialConsoleAccessRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableSerialConsoleAccessResult(TypedDict, total=False): - SerialConsoleAccessEnabled: Optional[Boolean] + SerialConsoleAccessEnabled: Boolean | None class DisableSnapshotBlockPublicAccessRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableSnapshotBlockPublicAccessResult(TypedDict, total=False): - State: Optional[SnapshotBlockPublicAccessState] + State: SnapshotBlockPublicAccessState | None class DisableTransitGatewayRouteTablePropagationRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - DryRun: Optional[Boolean] - TransitGatewayRouteTableAnnouncementId: Optional[TransitGatewayRouteTableAnnouncementId] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + DryRun: Boolean | None + TransitGatewayRouteTableAnnouncementId: TransitGatewayRouteTableAnnouncementId | None class TransitGatewayPropagation(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - TransitGatewayRouteTableId: Optional[String] - State: Optional[TransitGatewayPropagationState] - TransitGatewayRouteTableAnnouncementId: Optional[TransitGatewayRouteTableAnnouncementId] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + TransitGatewayRouteTableId: String | None + State: TransitGatewayPropagationState | None + TransitGatewayRouteTableAnnouncementId: TransitGatewayRouteTableAnnouncementId | None class DisableTransitGatewayRouteTablePropagationResult(TypedDict, total=False): - Propagation: Optional[TransitGatewayPropagation] + Propagation: TransitGatewayPropagation | None class DisableVgwRoutePropagationRequest(ServiceRequest): GatewayId: VpnGatewayId RouteTableId: RouteTableId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisableVpcClassicLinkDnsSupportRequest(ServiceRequest): - VpcId: Optional[VpcId] + VpcId: VpcId | None class DisableVpcClassicLinkDnsSupportResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DisableVpcClassicLinkRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VpcId: VpcId class DisableVpcClassicLinkResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DisassociateAddressRequest(ServiceRequest): - AssociationId: Optional[ElasticIpAssociationId] - PublicIp: Optional[EipAllocationPublicIp] - DryRun: Optional[Boolean] + AssociationId: ElasticIpAssociationId | None + PublicIp: EipAllocationPublicIp | None + DryRun: Boolean | None class DisassociateCapacityReservationBillingOwnerRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None CapacityReservationId: CapacityReservationId UnusedReservationBillingOwnerId: AccountID class DisassociateCapacityReservationBillingOwnerResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DisassociateClientVpnTargetNetworkRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId AssociationId: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisassociateClientVpnTargetNetworkResult(TypedDict, total=False): - AssociationId: Optional[String] - Status: Optional[AssociationStatus] + AssociationId: String | None + Status: AssociationStatus | None class DisassociateEnclaveCertificateIamRoleRequest(ServiceRequest): CertificateArn: CertificateId RoleArn: RoleId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisassociateEnclaveCertificateIamRoleResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class DisassociateIamInstanceProfileRequest(ServiceRequest): @@ -16519,82 +18556,82 @@ class DisassociateIamInstanceProfileRequest(ServiceRequest): class DisassociateIamInstanceProfileResult(TypedDict, total=False): - IamInstanceProfileAssociation: Optional[IamInstanceProfileAssociation] + IamInstanceProfileAssociation: IamInstanceProfileAssociation | None class InstanceEventWindowDisassociationRequest(TypedDict, total=False): - InstanceIds: Optional[InstanceIdList] - InstanceTags: Optional[TagList] - DedicatedHostIds: Optional[DedicatedHostIdList] + InstanceIds: InstanceIdList | None + InstanceTags: TagList | None + DedicatedHostIds: DedicatedHostIdList | None class DisassociateInstanceEventWindowRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceEventWindowId: InstanceEventWindowId AssociationTarget: InstanceEventWindowDisassociationRequest class DisassociateInstanceEventWindowResult(TypedDict, total=False): - InstanceEventWindow: Optional[InstanceEventWindow] + InstanceEventWindow: InstanceEventWindow | None class DisassociateIpamByoasnRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Asn: String Cidr: String class DisassociateIpamByoasnResult(TypedDict, total=False): - AsnAssociation: Optional[AsnAssociation] + AsnAssociation: AsnAssociation | None class DisassociateIpamResourceDiscoveryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamResourceDiscoveryAssociationId: IpamResourceDiscoveryAssociationId class DisassociateIpamResourceDiscoveryResult(TypedDict, total=False): - IpamResourceDiscoveryAssociation: Optional[IpamResourceDiscoveryAssociation] + IpamResourceDiscoveryAssociation: IpamResourceDiscoveryAssociation | None -EipAssociationIdList = List[ElasticIpAssociationId] +EipAssociationIdList = list[ElasticIpAssociationId] class DisassociateNatGatewayAddressRequest(ServiceRequest): NatGatewayId: NatGatewayId AssociationIds: EipAssociationIdList - MaxDrainDurationSeconds: Optional[DrainSeconds] - DryRun: Optional[Boolean] + MaxDrainDurationSeconds: DrainSeconds | None + DryRun: Boolean | None class DisassociateNatGatewayAddressResult(TypedDict, total=False): - NatGatewayId: Optional[NatGatewayId] - NatGatewayAddresses: Optional[NatGatewayAddressList] + NatGatewayId: NatGatewayId | None + NatGatewayAddresses: NatGatewayAddressList | None class DisassociateRouteServerRequest(ServiceRequest): RouteServerId: RouteServerId VpcId: VpcId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisassociateRouteServerResult(TypedDict, total=False): - RouteServerAssociation: Optional[RouteServerAssociation] + RouteServerAssociation: RouteServerAssociation | None class DisassociateRouteTableRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None AssociationId: RouteTableAssociationId class DisassociateSecurityGroupVpcRequest(ServiceRequest): GroupId: DisassociateSecurityGroupVpcSecurityGroupId VpcId: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisassociateSecurityGroupVpcResult(TypedDict, total=False): - State: Optional[SecurityGroupVpcAssociationState] + State: SecurityGroupVpcAssociationState | None class DisassociateSubnetCidrBlockRequest(ServiceRequest): @@ -16602,50 +18639,50 @@ class DisassociateSubnetCidrBlockRequest(ServiceRequest): class DisassociateSubnetCidrBlockResult(TypedDict, total=False): - Ipv6CidrBlockAssociation: Optional[SubnetIpv6CidrBlockAssociation] - SubnetId: Optional[String] + Ipv6CidrBlockAssociation: SubnetIpv6CidrBlockAssociation | None + SubnetId: String | None class DisassociateTransitGatewayMulticastDomainRequest(ServiceRequest): TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId TransitGatewayAttachmentId: TransitGatewayAttachmentId SubnetIds: TransitGatewaySubnetIdList - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisassociateTransitGatewayMulticastDomainResult(TypedDict, total=False): - Associations: Optional[TransitGatewayMulticastDomainAssociations] + Associations: TransitGatewayMulticastDomainAssociations | None class DisassociateTransitGatewayPolicyTableRequest(ServiceRequest): TransitGatewayPolicyTableId: TransitGatewayPolicyTableId TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisassociateTransitGatewayPolicyTableResult(TypedDict, total=False): - Association: Optional[TransitGatewayPolicyTableAssociation] + Association: TransitGatewayPolicyTableAssociation | None class DisassociateTransitGatewayRouteTableRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class DisassociateTransitGatewayRouteTableResult(TypedDict, total=False): - Association: Optional[TransitGatewayAssociation] + Association: TransitGatewayAssociation | None class DisassociateTrunkInterfaceRequest(ServiceRequest): AssociationId: TrunkInterfaceAssociationId - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None class DisassociateTrunkInterfaceResult(TypedDict, total=False): - Return: Optional[Boolean] - ClientToken: Optional[String] + Return: Boolean | None + ClientToken: String | None class DisassociateVpcCidrBlockRequest(ServiceRequest): @@ -16653,9 +18690,9 @@ class DisassociateVpcCidrBlockRequest(ServiceRequest): class DisassociateVpcCidrBlockResult(TypedDict, total=False): - Ipv6CidrBlockAssociation: Optional[VpcIpv6CidrBlockAssociation] - CidrBlockAssociation: Optional[VpcCidrBlockAssociation] - VpcId: Optional[String] + Ipv6CidrBlockAssociation: VpcIpv6CidrBlockAssociation | None + CidrBlockAssociation: VpcCidrBlockAssociation | None + VpcId: String | None class VolumeDetail(TypedDict, total=False): @@ -16669,596 +18706,720 @@ class DiskImageDetail(TypedDict, total=False): class DiskImage(TypedDict, total=False): - Description: Optional[String] - Image: Optional[DiskImageDetail] - Volume: Optional[VolumeDetail] + Description: String | None + Image: DiskImageDetail | None + Volume: VolumeDetail | None -DiskImageList = List[DiskImage] +DiskImageList = list[DiskImage] class DnsServersOptionsModifyStructure(TypedDict, total=False): - CustomDnsServers: Optional[ValueStringList] - Enabled: Optional[Boolean] + CustomDnsServers: ValueStringList | None + Enabled: Boolean | None class EbsInstanceBlockDeviceSpecification(TypedDict, total=False): - VolumeId: Optional[VolumeId] - DeleteOnTermination: Optional[Boolean] + VolumeId: VolumeId | None + DeleteOnTermination: Boolean | None -ElasticGpuSpecifications = List[ElasticGpuSpecification] +ElasticGpuSpecifications = list[ElasticGpuSpecification] class ElasticInferenceAccelerator(TypedDict, total=False): Type: String - Count: Optional[ElasticInferenceAcceleratorCount] + Count: ElasticInferenceAcceleratorCount | None -ElasticInferenceAccelerators = List[ElasticInferenceAccelerator] +ElasticInferenceAccelerators = list[ElasticInferenceAccelerator] class EnableAddressTransferRequest(ServiceRequest): AllocationId: AllocationId TransferAccountId: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableAddressTransferResult(TypedDict, total=False): - AddressTransfer: Optional[AddressTransfer] + AddressTransfer: AddressTransfer | None class EnableAllowedImagesSettingsRequest(ServiceRequest): AllowedImagesSettingsState: AllowedImagesSettingsEnabledState - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableAllowedImagesSettingsResult(TypedDict, total=False): - AllowedImagesSettingsState: Optional[AllowedImagesSettingsEnabledState] + AllowedImagesSettingsState: AllowedImagesSettingsEnabledState | None class EnableAwsNetworkPerformanceMetricSubscriptionRequest(ServiceRequest): - Source: Optional[String] - Destination: Optional[String] - Metric: Optional[MetricType] - Statistic: Optional[StatisticType] - DryRun: Optional[Boolean] + Source: String | None + Destination: String | None + Metric: MetricType | None + Statistic: StatisticType | None + DryRun: Boolean | None class EnableAwsNetworkPerformanceMetricSubscriptionResult(TypedDict, total=False): - Output: Optional[Boolean] + Output: Boolean | None + + +class EnableCapacityManagerRequest(ServiceRequest): + OrganizationsAccess: Boolean | None + DryRun: Boolean | None + ClientToken: String | None + + +class EnableCapacityManagerResult(TypedDict, total=False): + CapacityManagerStatus: CapacityManagerStatus | None + OrganizationsAccess: Boolean | None class EnableEbsEncryptionByDefaultRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableEbsEncryptionByDefaultResult(TypedDict, total=False): - EbsEncryptionByDefault: Optional[Boolean] + EbsEncryptionByDefault: Boolean | None class FastLaunchLaunchTemplateSpecificationRequest(TypedDict, total=False): - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[String] + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: String | None Version: String class FastLaunchSnapshotConfigurationRequest(TypedDict, total=False): - TargetResourceCount: Optional[Integer] + TargetResourceCount: Integer | None class EnableFastLaunchRequest(ServiceRequest): ImageId: ImageId - ResourceType: Optional[String] - SnapshotConfiguration: Optional[FastLaunchSnapshotConfigurationRequest] - LaunchTemplate: Optional[FastLaunchLaunchTemplateSpecificationRequest] - MaxParallelLaunches: Optional[Integer] - DryRun: Optional[Boolean] + ResourceType: String | None + SnapshotConfiguration: FastLaunchSnapshotConfigurationRequest | None + LaunchTemplate: FastLaunchLaunchTemplateSpecificationRequest | None + MaxParallelLaunches: Integer | None + DryRun: Boolean | None class EnableFastLaunchResult(TypedDict, total=False): - ImageId: Optional[ImageId] - ResourceType: Optional[FastLaunchResourceType] - SnapshotConfiguration: Optional[FastLaunchSnapshotConfigurationResponse] - LaunchTemplate: Optional[FastLaunchLaunchTemplateSpecificationResponse] - MaxParallelLaunches: Optional[Integer] - OwnerId: Optional[String] - State: Optional[FastLaunchStateCode] - StateTransitionReason: Optional[String] - StateTransitionTime: Optional[MillisecondDateTime] + ImageId: ImageId | None + ResourceType: FastLaunchResourceType | None + SnapshotConfiguration: FastLaunchSnapshotConfigurationResponse | None + LaunchTemplate: FastLaunchLaunchTemplateSpecificationResponse | None + MaxParallelLaunches: Integer | None + OwnerId: String | None + State: FastLaunchStateCode | None + StateTransitionReason: String | None + StateTransitionTime: MillisecondDateTime | None class EnableFastSnapshotRestoreStateError(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None class EnableFastSnapshotRestoreStateErrorItem(TypedDict, total=False): - AvailabilityZone: Optional[String] - Error: Optional[EnableFastSnapshotRestoreStateError] + AvailabilityZone: String | None + AvailabilityZoneId: String | None + Error: EnableFastSnapshotRestoreStateError | None -EnableFastSnapshotRestoreStateErrorSet = List[EnableFastSnapshotRestoreStateErrorItem] +EnableFastSnapshotRestoreStateErrorSet = list[EnableFastSnapshotRestoreStateErrorItem] class EnableFastSnapshotRestoreErrorItem(TypedDict, total=False): - SnapshotId: Optional[String] - FastSnapshotRestoreStateErrors: Optional[EnableFastSnapshotRestoreStateErrorSet] + SnapshotId: String | None + FastSnapshotRestoreStateErrors: EnableFastSnapshotRestoreStateErrorSet | None -EnableFastSnapshotRestoreErrorSet = List[EnableFastSnapshotRestoreErrorItem] +EnableFastSnapshotRestoreErrorSet = list[EnableFastSnapshotRestoreErrorItem] class EnableFastSnapshotRestoreSuccessItem(TypedDict, total=False): - SnapshotId: Optional[String] - AvailabilityZone: Optional[String] - State: Optional[FastSnapshotRestoreStateCode] - StateTransitionReason: Optional[String] - OwnerId: Optional[String] - OwnerAlias: Optional[String] - EnablingTime: Optional[MillisecondDateTime] - OptimizingTime: Optional[MillisecondDateTime] - EnabledTime: Optional[MillisecondDateTime] - DisablingTime: Optional[MillisecondDateTime] - DisabledTime: Optional[MillisecondDateTime] + SnapshotId: String | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + State: FastSnapshotRestoreStateCode | None + StateTransitionReason: String | None + OwnerId: String | None + OwnerAlias: String | None + EnablingTime: MillisecondDateTime | None + OptimizingTime: MillisecondDateTime | None + EnabledTime: MillisecondDateTime | None + DisablingTime: MillisecondDateTime | None + DisabledTime: MillisecondDateTime | None -EnableFastSnapshotRestoreSuccessSet = List[EnableFastSnapshotRestoreSuccessItem] +EnableFastSnapshotRestoreSuccessSet = list[EnableFastSnapshotRestoreSuccessItem] class EnableFastSnapshotRestoresRequest(ServiceRequest): - AvailabilityZones: AvailabilityZoneStringList + AvailabilityZones: AvailabilityZoneStringList | None + AvailabilityZoneIds: AvailabilityZoneIdStringList | None SourceSnapshotIds: SnapshotIdStringList - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableFastSnapshotRestoresResult(TypedDict, total=False): - Successful: Optional[EnableFastSnapshotRestoreSuccessSet] - Unsuccessful: Optional[EnableFastSnapshotRestoreErrorSet] + Successful: EnableFastSnapshotRestoreSuccessSet | None + Unsuccessful: EnableFastSnapshotRestoreErrorSet | None class EnableImageBlockPublicAccessRequest(ServiceRequest): ImageBlockPublicAccessState: ImageBlockPublicAccessEnabledState - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableImageBlockPublicAccessResult(TypedDict, total=False): - ImageBlockPublicAccessState: Optional[ImageBlockPublicAccessEnabledState] + ImageBlockPublicAccessState: ImageBlockPublicAccessEnabledState | None class EnableImageDeprecationRequest(ServiceRequest): ImageId: ImageId DeprecateAt: MillisecondDateTime - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableImageDeprecationResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class EnableImageDeregistrationProtectionRequest(ServiceRequest): ImageId: ImageId - WithCooldown: Optional[Boolean] - DryRun: Optional[Boolean] + WithCooldown: Boolean | None + DryRun: Boolean | None class EnableImageDeregistrationProtectionResult(TypedDict, total=False): - Return: Optional[String] + Return: String | None class EnableImageRequest(ServiceRequest): ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableImageResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None + + +class EnableInstanceSqlHaStandbyDetectionsRequest(ServiceRequest): + InstanceIds: InstanceIdUpdateStringList + SqlServerCredentials: SecretArn | None + DryRun: Boolean | None + + +class EnableInstanceSqlHaStandbyDetectionsResult(TypedDict, total=False): + Instances: RegisteredInstanceList | None class EnableIpamOrganizationAdminAccountRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None DelegatedAdminAccountId: String class EnableIpamOrganizationAdminAccountResult(TypedDict, total=False): - Success: Optional[Boolean] + Success: Boolean | None + + +class EnableIpamPolicyRequest(ServiceRequest): + DryRun: Boolean | None + IpamPolicyId: IpamPolicyId + OrganizationTargetId: String | None + + +class EnableIpamPolicyResult(TypedDict, total=False): + IpamPolicyId: IpamPolicyId | None class EnableReachabilityAnalyzerOrganizationSharingRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableReachabilityAnalyzerOrganizationSharingResult(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class EnableRouteServerPropagationRequest(ServiceRequest): RouteServerId: RouteServerId RouteTableId: RouteTableId - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableRouteServerPropagationResult(TypedDict, total=False): - RouteServerPropagation: Optional[RouteServerPropagation] + RouteServerPropagation: RouteServerPropagation | None class EnableSerialConsoleAccessRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableSerialConsoleAccessResult(TypedDict, total=False): - SerialConsoleAccessEnabled: Optional[Boolean] + SerialConsoleAccessEnabled: Boolean | None class EnableSnapshotBlockPublicAccessRequest(ServiceRequest): State: SnapshotBlockPublicAccessState - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableSnapshotBlockPublicAccessResult(TypedDict, total=False): - State: Optional[SnapshotBlockPublicAccessState] + State: SnapshotBlockPublicAccessState | None class EnableTransitGatewayRouteTablePropagationRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - DryRun: Optional[Boolean] - TransitGatewayRouteTableAnnouncementId: Optional[TransitGatewayRouteTableAnnouncementId] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + DryRun: Boolean | None + TransitGatewayRouteTableAnnouncementId: TransitGatewayRouteTableAnnouncementId | None class EnableTransitGatewayRouteTablePropagationResult(TypedDict, total=False): - Propagation: Optional[TransitGatewayPropagation] + Propagation: TransitGatewayPropagation | None class EnableVgwRoutePropagationRequest(ServiceRequest): GatewayId: VpnGatewayId RouteTableId: RouteTableId - DryRun: Optional[Boolean] + DryRun: Boolean | None class EnableVolumeIORequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VolumeId: VolumeId class EnableVpcClassicLinkDnsSupportRequest(ServiceRequest): - VpcId: Optional[VpcId] + VpcId: VpcId | None class EnableVpcClassicLinkDnsSupportResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class EnableVpcClassicLinkRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VpcId: VpcId class EnableVpcClassicLinkResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class EnclaveOptionsRequest(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class ExportClientVpnClientCertificateRevocationListRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - DryRun: Optional[Boolean] + DryRun: Boolean | None class ExportClientVpnClientCertificateRevocationListResult(TypedDict, total=False): - CertificateRevocationList: Optional[String] - Status: Optional[ClientCertificateRevocationListStatus] + CertificateRevocationList: String | None + Status: ClientCertificateRevocationListStatus | None class ExportClientVpnClientConfigurationRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - DryRun: Optional[Boolean] + DryRun: Boolean | None class ExportClientVpnClientConfigurationResult(TypedDict, total=False): - ClientConfiguration: Optional[String] + ClientConfiguration: String | None class ExportTaskS3LocationRequest(TypedDict, total=False): S3Bucket: String - S3Prefix: Optional[String] + S3Prefix: String | None class ExportImageRequest(ServiceRequest): - ClientToken: Optional[String] - Description: Optional[String] + ClientToken: String | None + Description: String | None DiskImageFormat: DiskImageFormat - DryRun: Optional[Boolean] + DryRun: Boolean | None ImageId: ImageId S3ExportLocation: ExportTaskS3LocationRequest - RoleName: Optional[String] - TagSpecifications: Optional[TagSpecificationList] + RoleName: String | None + TagSpecifications: TagSpecificationList | None class ExportImageResult(TypedDict, total=False): - Description: Optional[String] - DiskImageFormat: Optional[DiskImageFormat] - ExportImageTaskId: Optional[String] - ImageId: Optional[String] - RoleName: Optional[String] - Progress: Optional[String] - S3ExportLocation: Optional[ExportTaskS3Location] - Status: Optional[String] - StatusMessage: Optional[String] - Tags: Optional[TagList] + Description: String | None + DiskImageFormat: DiskImageFormat | None + ExportImageTaskId: String | None + ImageId: String | None + RoleName: String | None + Progress: String | None + S3ExportLocation: ExportTaskS3Location | None + Status: String | None + StatusMessage: String | None + Tags: TagList | None class ExportTransitGatewayRoutesRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId - Filters: Optional[FilterList] + Filters: FilterList | None S3Bucket: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class ExportTransitGatewayRoutesResult(TypedDict, total=False): - S3Location: Optional[String] + S3Location: String | None class ExportVerifiedAccessInstanceClientConfigurationRequest(ServiceRequest): VerifiedAccessInstanceId: VerifiedAccessInstanceId - DryRun: Optional[Boolean] + DryRun: Boolean | None class VerifiedAccessInstanceOpenVpnClientConfigurationRoute(TypedDict, total=False): - Cidr: Optional[String] + Cidr: String | None -VerifiedAccessInstanceOpenVpnClientConfigurationRouteList = List[ +VerifiedAccessInstanceOpenVpnClientConfigurationRouteList = list[ VerifiedAccessInstanceOpenVpnClientConfigurationRoute ] class VerifiedAccessInstanceOpenVpnClientConfiguration(TypedDict, total=False): - Config: Optional[String] - Routes: Optional[VerifiedAccessInstanceOpenVpnClientConfigurationRouteList] + Config: String | None + Routes: VerifiedAccessInstanceOpenVpnClientConfigurationRouteList | None -VerifiedAccessInstanceOpenVpnClientConfigurationList = List[ +VerifiedAccessInstanceOpenVpnClientConfigurationList = list[ VerifiedAccessInstanceOpenVpnClientConfiguration ] class VerifiedAccessInstanceUserTrustProviderClientConfiguration(TypedDict, total=False): - Type: Optional[UserTrustProviderType] - Scopes: Optional[String] - Issuer: Optional[String] - AuthorizationEndpoint: Optional[String] - PublicSigningKeyEndpoint: Optional[String] - TokenEndpoint: Optional[String] - UserInfoEndpoint: Optional[String] - ClientId: Optional[String] - ClientSecret: Optional[ClientSecretType] - PkceEnabled: Optional[Boolean] + Type: UserTrustProviderType | None + Scopes: String | None + Issuer: String | None + AuthorizationEndpoint: String | None + PublicSigningKeyEndpoint: String | None + TokenEndpoint: String | None + UserInfoEndpoint: String | None + ClientId: String | None + ClientSecret: ClientSecretType | None + PkceEnabled: Boolean | None class ExportVerifiedAccessInstanceClientConfigurationResult(TypedDict, total=False): - Version: Optional[String] - VerifiedAccessInstanceId: Optional[String] - Region: Optional[String] - DeviceTrustProviders: Optional[DeviceTrustProviderTypeList] - UserTrustProvider: Optional[VerifiedAccessInstanceUserTrustProviderClientConfiguration] - OpenVpnConfigurations: Optional[VerifiedAccessInstanceOpenVpnClientConfigurationList] + Version: String | None + VerifiedAccessInstanceId: String | None + Region: String | None + DeviceTrustProviders: DeviceTrustProviderTypeList | None + UserTrustProvider: VerifiedAccessInstanceUserTrustProviderClientConfiguration | None + OpenVpnConfigurations: VerifiedAccessInstanceOpenVpnClientConfigurationList | None class GetActiveVpnTunnelStatusRequest(ServiceRequest): VpnConnectionId: VpnConnectionId VpnTunnelOutsideIpAddress: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetActiveVpnTunnelStatusResult(TypedDict, total=False): - ActiveVpnTunnelStatus: Optional[ActiveVpnTunnelStatus] + ActiveVpnTunnelStatus: ActiveVpnTunnelStatus | None class GetAllowedImagesSettingsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None -ImageProviderList = List[ImageProvider] +ImageNameList = list[ImageName] +MarketplaceProductCodeList = list[MarketplaceProductCode] +ImageProviderList = list[ImageProvider] class ImageCriterion(TypedDict, total=False): - ImageProviders: Optional[ImageProviderList] + ImageProviders: ImageProviderList | None + MarketplaceProductCodes: MarketplaceProductCodeList | None + ImageNames: ImageNameList | None + DeprecationTimeCondition: DeprecationTimeCondition | None + CreationDateCondition: CreationDateCondition | None -ImageCriterionList = List[ImageCriterion] +ImageCriterionList = list[ImageCriterion] class GetAllowedImagesSettingsResult(TypedDict, total=False): - State: Optional[String] - ImageCriteria: Optional[ImageCriterionList] - ManagedBy: Optional[ManagedBy] + State: String | None + ImageCriteria: ImageCriterionList | None + ManagedBy: ManagedBy | None class GetAssociatedEnclaveCertificateIamRolesRequest(ServiceRequest): CertificateArn: CertificateId - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetAssociatedEnclaveCertificateIamRolesResult(TypedDict, total=False): - AssociatedRoles: Optional[AssociatedRolesList] + AssociatedRoles: AssociatedRolesList | None class GetAssociatedIpv6PoolCidrsRequest(ServiceRequest): PoolId: Ipv6PoolEc2Id - NextToken: Optional[NextToken] - MaxResults: Optional[Ipv6PoolMaxResults] - DryRun: Optional[Boolean] + NextToken: NextToken | None + MaxResults: Ipv6PoolMaxResults | None + DryRun: Boolean | None class Ipv6CidrAssociation(TypedDict, total=False): - Ipv6Cidr: Optional[String] - AssociatedResource: Optional[String] + Ipv6Cidr: String | None + AssociatedResource: String | None -Ipv6CidrAssociationSet = List[Ipv6CidrAssociation] +Ipv6CidrAssociationSet = list[Ipv6CidrAssociation] class GetAssociatedIpv6PoolCidrsResult(TypedDict, total=False): - Ipv6CidrAssociations: Optional[Ipv6CidrAssociationSet] - NextToken: Optional[String] + Ipv6CidrAssociations: Ipv6CidrAssociationSet | None + NextToken: String | None class GetAwsNetworkPerformanceDataRequest(ServiceRequest): - DataQueries: Optional[DataQueries] - StartTime: Optional[MillisecondDateTime] - EndTime: Optional[MillisecondDateTime] - MaxResults: Optional[Integer] - NextToken: Optional[String] - DryRun: Optional[Boolean] + DataQueries: DataQueries | None + StartTime: MillisecondDateTime | None + EndTime: MillisecondDateTime | None + MaxResults: Integer | None + NextToken: String | None + DryRun: Boolean | None class GetAwsNetworkPerformanceDataResult(TypedDict, total=False): - DataResponses: Optional[DataResponses] - NextToken: Optional[String] + DataResponses: DataResponses | None + NextToken: String | None + + +class GetCapacityManagerAttributesRequest(ServiceRequest): + DryRun: Boolean | None + + +class GetCapacityManagerAttributesResult(TypedDict, total=False): + CapacityManagerStatus: CapacityManagerStatus | None + OrganizationsAccess: Boolean | None + DataExportCount: Integer | None + IngestionStatus: IngestionStatus | None + IngestionStatusMessage: String | None + EarliestDatapointTimestamp: MillisecondDateTime | None + LatestDatapointTimestamp: MillisecondDateTime | None + + +GroupBySet = list[GroupBy] +MetricSet = list[Metric] + + +class GetCapacityManagerMetricDataRequest(ServiceRequest): + MetricNames: MetricSet + StartTime: MillisecondDateTime + EndTime: MillisecondDateTime + Period: Period + GroupBy: GroupBySet | None + FilterBy: CapacityManagerConditionSet | None + MaxResults: MaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None + + +class MetricValue(TypedDict, total=False): + Metric: Metric | None + Value: Double | None + + +MetricValueSet = list[MetricValue] + + +class MetricDataResult(TypedDict, total=False): + Dimension: CapacityManagerDimension | None + Timestamp: MillisecondDateTime | None + MetricValues: MetricValueSet | None + + +MetricDataResultSet = list[MetricDataResult] + + +class GetCapacityManagerMetricDataResult(TypedDict, total=False): + MetricDataResults: MetricDataResultSet | None + NextToken: NextToken | None + + +class GetCapacityManagerMetricDimensionsRequest(ServiceRequest): + GroupBy: GroupBySet + FilterBy: CapacityManagerConditionSet | None + StartTime: MillisecondDateTime + EndTime: MillisecondDateTime + MetricNames: MetricSet + MaxResults: MaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None + + +MetricDimensionResultSet = list[CapacityManagerDimension] + + +class GetCapacityManagerMetricDimensionsResult(TypedDict, total=False): + MetricDimensionResults: MetricDimensionResultSet | None + NextToken: NextToken | None class GetCapacityReservationUsageRequest(ServiceRequest): CapacityReservationId: CapacityReservationId - NextToken: Optional[String] - MaxResults: Optional[GetCapacityReservationUsageRequestMaxResults] - DryRun: Optional[Boolean] + NextToken: String | None + MaxResults: GetCapacityReservationUsageRequestMaxResults | None + DryRun: Boolean | None class InstanceUsage(TypedDict, total=False): - AccountId: Optional[String] - UsedInstanceCount: Optional[Integer] + AccountId: String | None + UsedInstanceCount: Integer | None -InstanceUsageSet = List[InstanceUsage] +InstanceUsageSet = list[InstanceUsage] class GetCapacityReservationUsageResult(TypedDict, total=False): - NextToken: Optional[String] - CapacityReservationId: Optional[String] - InstanceType: Optional[String] - TotalInstanceCount: Optional[Integer] - AvailableInstanceCount: Optional[Integer] - State: Optional[CapacityReservationState] - InstanceUsages: Optional[InstanceUsageSet] + NextToken: String | None + CapacityReservationId: String | None + InstanceType: String | None + TotalInstanceCount: Integer | None + AvailableInstanceCount: Integer | None + State: CapacityReservationState | None + InstanceUsages: InstanceUsageSet | None + Interruptible: BoxedBoolean | None + InterruptibleCapacityAllocation: InterruptibleCapacityAllocation | None + InterruptionInfo: InterruptionInfo | None class GetCoipPoolUsageRequest(ServiceRequest): PoolId: Ipv4PoolCoipId - Filters: Optional[FilterList] - MaxResults: Optional[CoipPoolMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: CoipPoolMaxResults | None + NextToken: String | None + DryRun: Boolean | None class GetCoipPoolUsageResult(TypedDict, total=False): - CoipPoolId: Optional[String] - CoipAddressUsages: Optional[CoipAddressUsageSet] - LocalGatewayRouteTableId: Optional[String] - NextToken: Optional[String] + CoipPoolId: String | None + CoipAddressUsages: CoipAddressUsageSet | None + LocalGatewayRouteTableId: String | None + NextToken: String | None class GetConsoleOutputRequest(ServiceRequest): InstanceId: InstanceId - Latest: Optional[Boolean] - DryRun: Optional[Boolean] + Latest: Boolean | None + DryRun: Boolean | None class GetConsoleOutputResult(TypedDict, total=False): - InstanceId: Optional[String] - Timestamp: Optional[DateTime] - Output: Optional[String] + InstanceId: String | None + Timestamp: DateTime | None + Output: String | None class GetConsoleScreenshotRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId - WakeUp: Optional[Boolean] + WakeUp: Boolean | None class GetConsoleScreenshotResult(TypedDict, total=False): - ImageData: Optional[String] - InstanceId: Optional[String] + ImageData: String | None + InstanceId: String | None class GetDeclarativePoliciesReportSummaryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ReportId: DeclarativePoliciesReportId class GetDeclarativePoliciesReportSummaryResult(TypedDict, total=False): - ReportId: Optional[String] - S3Bucket: Optional[String] - S3Prefix: Optional[String] - TargetId: Optional[String] - StartTime: Optional[MillisecondDateTime] - EndTime: Optional[MillisecondDateTime] - NumberOfAccounts: Optional[Integer] - NumberOfFailedAccounts: Optional[Integer] - AttributeSummaries: Optional[AttributeSummaryList] + ReportId: String | None + S3Bucket: String | None + S3Prefix: String | None + TargetId: String | None + StartTime: MillisecondDateTime | None + EndTime: MillisecondDateTime | None + NumberOfAccounts: Integer | None + NumberOfFailedAccounts: Integer | None + AttributeSummaries: AttributeSummaryList | None class GetDefaultCreditSpecificationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceFamily: UnlimitedSupportedInstanceFamily class InstanceFamilyCreditSpecification(TypedDict, total=False): - InstanceFamily: Optional[UnlimitedSupportedInstanceFamily] - CpuCredits: Optional[String] + InstanceFamily: UnlimitedSupportedInstanceFamily | None + CpuCredits: String | None class GetDefaultCreditSpecificationResult(TypedDict, total=False): - InstanceFamilyCreditSpecification: Optional[InstanceFamilyCreditSpecification] + InstanceFamilyCreditSpecification: InstanceFamilyCreditSpecification | None class GetEbsDefaultKmsKeyIdRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetEbsDefaultKmsKeyIdResult(TypedDict, total=False): - KmsKeyId: Optional[String] + KmsKeyId: String | None class GetEbsEncryptionByDefaultRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetEbsEncryptionByDefaultResult(TypedDict, total=False): - EbsEncryptionByDefault: Optional[Boolean] - SseType: Optional[SSEType] + EbsEncryptionByDefault: Boolean | None + SseType: SSEType | None + + +class GetEnabledIpamPolicyRequest(ServiceRequest): + DryRun: Boolean | None + + +class GetEnabledIpamPolicyResult(TypedDict, total=False): + IpamPolicyEnabled: Boolean | None + IpamPolicyId: IpamPolicyId | None + ManagedBy: IpamPolicyManagedBy | None class IntegrateServices(TypedDict, total=False): - AthenaIntegrations: Optional[AthenaIntegrationsSet] + AthenaIntegrations: AthenaIntegrationsSet | None class GetFlowLogsIntegrationTemplateRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None FlowLogId: VpcFlowLogId ConfigDeliveryS3DestinationArn: String IntegrateServices: IntegrateServices class GetFlowLogsIntegrationTemplateResult(TypedDict, total=False): - Result: Optional[String] + Result: String | None class GetGroupsForCapacityReservationRequest(ServiceRequest): CapacityReservationId: CapacityReservationId - NextToken: Optional[String] - MaxResults: Optional[GetGroupsForCapacityReservationRequestMaxResults] - DryRun: Optional[Boolean] + NextToken: String | None + MaxResults: GetGroupsForCapacityReservationRequestMaxResults | None + DryRun: Boolean | None class GetGroupsForCapacityReservationResult(TypedDict, total=False): - NextToken: Optional[String] - CapacityReservationGroups: Optional[CapacityReservationGroupSet] + NextToken: String | None + CapacityReservationGroups: CapacityReservationGroupSet | None -RequestHostIdSet = List[DedicatedHostId] +RequestHostIdSet = list[DedicatedHostId] class GetHostReservationPurchasePreviewRequest(ServiceRequest): @@ -17267,1405 +19428,1668 @@ class GetHostReservationPurchasePreviewRequest(ServiceRequest): class Purchase(TypedDict, total=False): - CurrencyCode: Optional[CurrencyCodeValues] - Duration: Optional[Integer] - HostIdSet: Optional[ResponseHostIdSet] - HostReservationId: Optional[HostReservationId] - HourlyPrice: Optional[String] - InstanceFamily: Optional[String] - PaymentOption: Optional[PaymentOption] - UpfrontPrice: Optional[String] + CurrencyCode: CurrencyCodeValues | None + Duration: Integer | None + HostIdSet: ResponseHostIdSet | None + HostReservationId: HostReservationId | None + HourlyPrice: String | None + InstanceFamily: String | None + PaymentOption: PaymentOption | None + UpfrontPrice: String | None -PurchaseSet = List[Purchase] +PurchaseSet = list[Purchase] class GetHostReservationPurchasePreviewResult(TypedDict, total=False): - CurrencyCode: Optional[CurrencyCodeValues] - Purchase: Optional[PurchaseSet] - TotalHourlyPrice: Optional[String] - TotalUpfrontPrice: Optional[String] + CurrencyCode: CurrencyCodeValues | None + Purchase: PurchaseSet | None + TotalHourlyPrice: String | None + TotalUpfrontPrice: String | None + + +class GetImageAncestryRequest(ServiceRequest): + ImageId: ImageId + DryRun: Boolean | None + + +class ImageAncestryEntry(TypedDict, total=False): + CreationDate: MillisecondDateTime | None + ImageId: ImageId | None + ImageOwnerAlias: String | None + SourceImageId: ImageId | None + SourceImageRegion: String | None + + +ImageAncestryEntryList = list[ImageAncestryEntry] + + +class GetImageAncestryResult(TypedDict, total=False): + ImageAncestryEntries: ImageAncestryEntryList | None class GetImageBlockPublicAccessStateRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetImageBlockPublicAccessStateResult(TypedDict, total=False): - ImageBlockPublicAccessState: Optional[String] - ManagedBy: Optional[ManagedBy] + ImageBlockPublicAccessState: String | None + ManagedBy: ManagedBy | None class GetInstanceMetadataDefaultsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class InstanceMetadataDefaultsResponse(TypedDict, total=False): - HttpTokens: Optional[HttpTokensState] - HttpPutResponseHopLimit: Optional[BoxedInteger] - HttpEndpoint: Optional[InstanceMetadataEndpointState] - InstanceMetadataTags: Optional[InstanceMetadataTagsState] - ManagedBy: Optional[ManagedBy] - ManagedExceptionMessage: Optional[String] + HttpTokens: HttpTokensState | None + HttpPutResponseHopLimit: BoxedInteger | None + HttpEndpoint: InstanceMetadataEndpointState | None + InstanceMetadataTags: InstanceMetadataTagsState | None + ManagedBy: ManagedBy | None + ManagedExceptionMessage: String | None + HttpTokensEnforced: HttpTokensEnforcedState | None class GetInstanceMetadataDefaultsResult(TypedDict, total=False): - AccountLevel: Optional[InstanceMetadataDefaultsResponse] + AccountLevel: InstanceMetadataDefaultsResponse | None class GetInstanceTpmEkPubRequest(ServiceRequest): InstanceId: InstanceId KeyType: EkPubKeyType KeyFormat: EkPubKeyFormat - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetInstanceTpmEkPubResult(TypedDict, total=False): - InstanceId: Optional[InstanceId] - KeyType: Optional[EkPubKeyType] - KeyFormat: Optional[EkPubKeyFormat] - KeyValue: Optional[EkPubKeyValue] + InstanceId: InstanceId | None + KeyType: EkPubKeyType | None + KeyFormat: EkPubKeyFormat | None + KeyValue: EkPubKeyValue | None -VirtualizationTypeSet = List[VirtualizationType] +VirtualizationTypeSet = list[VirtualizationType] class GetInstanceTypesFromInstanceRequirementsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ArchitectureTypes: ArchitectureTypeSet VirtualizationTypes: VirtualizationTypeSet InstanceRequirements: InstanceRequirementsRequest - MaxResults: Optional[Integer] - NextToken: Optional[String] - Context: Optional[String] + MaxResults: Integer | None + NextToken: String | None + Context: String | None class InstanceTypeInfoFromInstanceRequirements(TypedDict, total=False): - InstanceType: Optional[String] + InstanceType: String | None -InstanceTypeInfoFromInstanceRequirementsSet = List[InstanceTypeInfoFromInstanceRequirements] +InstanceTypeInfoFromInstanceRequirementsSet = list[InstanceTypeInfoFromInstanceRequirements] class GetInstanceTypesFromInstanceRequirementsResult(TypedDict, total=False): - InstanceTypes: Optional[InstanceTypeInfoFromInstanceRequirementsSet] - NextToken: Optional[String] + InstanceTypes: InstanceTypeInfoFromInstanceRequirementsSet | None + NextToken: String | None class GetInstanceUefiDataRequest(ServiceRequest): InstanceId: InstanceId - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetInstanceUefiDataResult(TypedDict, total=False): - InstanceId: Optional[InstanceId] - UefiData: Optional[String] + InstanceId: InstanceId | None + UefiData: String | None class GetIpamAddressHistoryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Cidr: String IpamScopeId: IpamScopeId - VpcId: Optional[String] - StartTime: Optional[MillisecondDateTime] - EndTime: Optional[MillisecondDateTime] - MaxResults: Optional[IpamAddressHistoryMaxResults] - NextToken: Optional[NextToken] + VpcId: String | None + StartTime: MillisecondDateTime | None + EndTime: MillisecondDateTime | None + MaxResults: IpamAddressHistoryMaxResults | None + NextToken: NextToken | None class IpamAddressHistoryRecord(TypedDict, total=False): - ResourceOwnerId: Optional[String] - ResourceRegion: Optional[String] - ResourceType: Optional[IpamAddressHistoryResourceType] - ResourceId: Optional[String] - ResourceCidr: Optional[String] - ResourceName: Optional[String] - ResourceComplianceStatus: Optional[IpamComplianceStatus] - ResourceOverlapStatus: Optional[IpamOverlapStatus] - VpcId: Optional[String] - SampledStartTime: Optional[MillisecondDateTime] - SampledEndTime: Optional[MillisecondDateTime] + ResourceOwnerId: String | None + ResourceRegion: String | None + ResourceType: IpamAddressHistoryResourceType | None + ResourceId: String | None + ResourceCidr: String | None + ResourceName: String | None + ResourceComplianceStatus: IpamComplianceStatus | None + ResourceOverlapStatus: IpamOverlapStatus | None + VpcId: String | None + SampledStartTime: MillisecondDateTime | None + SampledEndTime: MillisecondDateTime | None -IpamAddressHistoryRecordSet = List[IpamAddressHistoryRecord] +IpamAddressHistoryRecordSet = list[IpamAddressHistoryRecord] class GetIpamAddressHistoryResult(TypedDict, total=False): - HistoryRecords: Optional[IpamAddressHistoryRecordSet] - NextToken: Optional[NextToken] + HistoryRecords: IpamAddressHistoryRecordSet | None + NextToken: NextToken | None class GetIpamDiscoveredAccountsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamResourceDiscoveryId: IpamResourceDiscoveryId DiscoveryRegion: String - Filters: Optional[FilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[IpamMaxResults] + Filters: FilterList | None + NextToken: NextToken | None + MaxResults: IpamMaxResults | None class IpamDiscoveryFailureReason(TypedDict, total=False): - Code: Optional[IpamDiscoveryFailureCode] - Message: Optional[String] + Code: IpamDiscoveryFailureCode | None + Message: String | None class IpamDiscoveredAccount(TypedDict, total=False): - AccountId: Optional[String] - DiscoveryRegion: Optional[String] - FailureReason: Optional[IpamDiscoveryFailureReason] - LastAttemptedDiscoveryTime: Optional[MillisecondDateTime] - LastSuccessfulDiscoveryTime: Optional[MillisecondDateTime] - OrganizationalUnitId: Optional[String] + AccountId: String | None + DiscoveryRegion: String | None + FailureReason: IpamDiscoveryFailureReason | None + LastAttemptedDiscoveryTime: MillisecondDateTime | None + LastSuccessfulDiscoveryTime: MillisecondDateTime | None + OrganizationalUnitId: String | None -IpamDiscoveredAccountSet = List[IpamDiscoveredAccount] +IpamDiscoveredAccountSet = list[IpamDiscoveredAccount] class GetIpamDiscoveredAccountsResult(TypedDict, total=False): - IpamDiscoveredAccounts: Optional[IpamDiscoveredAccountSet] - NextToken: Optional[NextToken] + IpamDiscoveredAccounts: IpamDiscoveredAccountSet | None + NextToken: NextToken | None class GetIpamDiscoveredPublicAddressesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamResourceDiscoveryId: IpamResourceDiscoveryId AddressRegion: String - Filters: Optional[FilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[IpamMaxResults] + Filters: FilterList | None + NextToken: NextToken | None + MaxResults: IpamMaxResults | None class IpamPublicAddressSecurityGroup(TypedDict, total=False): - GroupName: Optional[String] - GroupId: Optional[String] + GroupName: String | None + GroupId: String | None -IpamPublicAddressSecurityGroupList = List[IpamPublicAddressSecurityGroup] +IpamPublicAddressSecurityGroupList = list[IpamPublicAddressSecurityGroup] class IpamPublicAddressTag(TypedDict, total=False): - Key: Optional[String] - Value: Optional[String] + Key: String | None + Value: String | None -IpamPublicAddressTagList = List[IpamPublicAddressTag] +IpamPublicAddressTagList = list[IpamPublicAddressTag] class IpamPublicAddressTags(TypedDict, total=False): - EipTags: Optional[IpamPublicAddressTagList] + EipTags: IpamPublicAddressTagList | None class IpamDiscoveredPublicAddress(TypedDict, total=False): - IpamResourceDiscoveryId: Optional[IpamResourceDiscoveryId] - AddressRegion: Optional[String] - Address: Optional[String] - AddressOwnerId: Optional[String] - AddressAllocationId: Optional[String] - AssociationStatus: Optional[IpamPublicAddressAssociationStatus] - AddressType: Optional[IpamPublicAddressType] - Service: Optional[IpamPublicAddressAwsService] - ServiceResource: Optional[String] - VpcId: Optional[String] - SubnetId: Optional[String] - PublicIpv4PoolId: Optional[String] - NetworkInterfaceId: Optional[String] - NetworkInterfaceDescription: Optional[String] - InstanceId: Optional[String] - Tags: Optional[IpamPublicAddressTags] - NetworkBorderGroup: Optional[String] - SecurityGroups: Optional[IpamPublicAddressSecurityGroupList] - SampleTime: Optional[MillisecondDateTime] - - -IpamDiscoveredPublicAddressSet = List[IpamDiscoveredPublicAddress] + IpamResourceDiscoveryId: IpamResourceDiscoveryId | None + AddressRegion: String | None + Address: String | None + AddressOwnerId: String | None + AddressAllocationId: String | None + AssociationStatus: IpamPublicAddressAssociationStatus | None + AddressType: IpamPublicAddressType | None + Service: IpamPublicAddressAwsService | None + ServiceResource: String | None + VpcId: String | None + SubnetId: String | None + PublicIpv4PoolId: String | None + NetworkInterfaceId: String | None + NetworkInterfaceDescription: String | None + InstanceId: String | None + Tags: IpamPublicAddressTags | None + NetworkBorderGroup: String | None + SecurityGroups: IpamPublicAddressSecurityGroupList | None + SampleTime: MillisecondDateTime | None + + +IpamDiscoveredPublicAddressSet = list[IpamDiscoveredPublicAddress] class GetIpamDiscoveredPublicAddressesResult(TypedDict, total=False): - IpamDiscoveredPublicAddresses: Optional[IpamDiscoveredPublicAddressSet] - OldestSampleTime: Optional[MillisecondDateTime] - NextToken: Optional[NextToken] + IpamDiscoveredPublicAddresses: IpamDiscoveredPublicAddressSet | None + OldestSampleTime: MillisecondDateTime | None + NextToken: NextToken | None class GetIpamDiscoveredResourceCidrsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamResourceDiscoveryId: IpamResourceDiscoveryId ResourceRegion: String - Filters: Optional[FilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[IpamMaxResults] + Filters: FilterList | None + NextToken: NextToken | None + MaxResults: IpamMaxResults | None class IpamDiscoveredResourceCidr(TypedDict, total=False): - IpamResourceDiscoveryId: Optional[IpamResourceDiscoveryId] - ResourceRegion: Optional[String] - ResourceId: Optional[String] - ResourceOwnerId: Optional[String] - ResourceCidr: Optional[String] - IpSource: Optional[IpamResourceCidrIpSource] - ResourceType: Optional[IpamResourceType] - ResourceTags: Optional[IpamResourceTagList] - IpUsage: Optional[BoxedDouble] - VpcId: Optional[String] - SubnetId: Optional[String] - NetworkInterfaceAttachmentStatus: Optional[IpamNetworkInterfaceAttachmentStatus] - SampleTime: Optional[MillisecondDateTime] - AvailabilityZoneId: Optional[String] + IpamResourceDiscoveryId: IpamResourceDiscoveryId | None + ResourceRegion: String | None + ResourceId: String | None + ResourceOwnerId: String | None + ResourceCidr: String | None + IpSource: IpamResourceCidrIpSource | None + ResourceType: IpamResourceType | None + ResourceTags: IpamResourceTagList | None + IpUsage: BoxedDouble | None + VpcId: String | None + SubnetId: String | None + NetworkInterfaceAttachmentStatus: IpamNetworkInterfaceAttachmentStatus | None + SampleTime: MillisecondDateTime | None + AvailabilityZoneId: String | None -IpamDiscoveredResourceCidrSet = List[IpamDiscoveredResourceCidr] +IpamDiscoveredResourceCidrSet = list[IpamDiscoveredResourceCidr] class GetIpamDiscoveredResourceCidrsResult(TypedDict, total=False): - IpamDiscoveredResourceCidrs: Optional[IpamDiscoveredResourceCidrSet] - NextToken: Optional[NextToken] + IpamDiscoveredResourceCidrs: IpamDiscoveredResourceCidrSet | None + NextToken: NextToken | None + + +class GetIpamPolicyAllocationRulesRequest(ServiceRequest): + DryRun: Boolean | None + IpamPolicyId: IpamPolicyId + Filters: FilterList | None + Locale: String | None + ResourceType: IpamPolicyResourceType | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + + +class IpamPolicyAllocationRule(TypedDict, total=False): + SourceIpamPoolId: IpamPoolId | None + + +IpamPolicyAllocationRuleList = list[IpamPolicyAllocationRule] + + +class IpamPolicyDocument(TypedDict, total=False): + IpamPolicyId: IpamPolicyId | None + Locale: String | None + ResourceType: IpamPolicyResourceType | None + AllocationRules: IpamPolicyAllocationRuleList | None + + +IpamPolicyDocumentSet = list[IpamPolicyDocument] + + +class GetIpamPolicyAllocationRulesResult(TypedDict, total=False): + IpamPolicyDocuments: IpamPolicyDocumentSet | None + NextToken: NextToken | None + + +class GetIpamPolicyOrganizationTargetsRequest(ServiceRequest): + DryRun: Boolean | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + IpamPolicyId: IpamPolicyId + Filters: FilterList | None + + +class IpamPolicyOrganizationTarget(TypedDict, total=False): + OrganizationTargetId: String | None + + +IpamPolicyOrganizationTargetSet = list[IpamPolicyOrganizationTarget] + + +class GetIpamPolicyOrganizationTargetsResult(TypedDict, total=False): + OrganizationTargets: IpamPolicyOrganizationTargetSet | None + NextToken: NextToken | None class GetIpamPoolAllocationsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId - IpamPoolAllocationId: Optional[IpamPoolAllocationId] - Filters: Optional[FilterList] - MaxResults: Optional[GetIpamPoolAllocationsMaxResults] - NextToken: Optional[NextToken] + IpamPoolAllocationId: IpamPoolAllocationId | None + Filters: FilterList | None + MaxResults: GetIpamPoolAllocationsMaxResults | None + NextToken: NextToken | None -IpamPoolAllocationSet = List[IpamPoolAllocation] +IpamPoolAllocationSet = list[IpamPoolAllocation] class GetIpamPoolAllocationsResult(TypedDict, total=False): - IpamPoolAllocations: Optional[IpamPoolAllocationSet] - NextToken: Optional[NextToken] + IpamPoolAllocations: IpamPoolAllocationSet | None + NextToken: NextToken | None class GetIpamPoolCidrsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId - Filters: Optional[FilterList] - MaxResults: Optional[IpamMaxResults] - NextToken: Optional[NextToken] + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None -IpamPoolCidrSet = List[IpamPoolCidr] +IpamPoolCidrSet = list[IpamPoolCidr] class GetIpamPoolCidrsResult(TypedDict, total=False): - IpamPoolCidrs: Optional[IpamPoolCidrSet] - NextToken: Optional[NextToken] + IpamPoolCidrs: IpamPoolCidrSet | None + NextToken: NextToken | None + + +class GetIpamPrefixListResolverRulesRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverId: IpamPrefixListResolverId + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + + +class IpamPrefixListResolverRuleCondition(TypedDict, total=False): + Operation: IpamPrefixListResolverRuleConditionOperation | None + IpamPoolId: String | None + ResourceId: String | None + ResourceOwner: String | None + ResourceRegion: String | None + ResourceTag: IpamResourceTag | None + Cidr: String | None + + +IpamPrefixListResolverRuleConditionSet = list[IpamPrefixListResolverRuleCondition] + + +class IpamPrefixListResolverRule(TypedDict, total=False): + RuleType: IpamPrefixListResolverRuleType | None + StaticCidr: String | None + IpamScopeId: IpamScopeId | None + ResourceType: IpamResourceType | None + Conditions: IpamPrefixListResolverRuleConditionSet | None + + +IpamPrefixListResolverRuleSet = list[IpamPrefixListResolverRule] + + +class GetIpamPrefixListResolverRulesResult(TypedDict, total=False): + Rules: IpamPrefixListResolverRuleSet | None + NextToken: NextToken | None + + +class GetIpamPrefixListResolverVersionEntriesRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverId: IpamPrefixListResolverId + IpamPrefixListResolverVersion: Long + MaxResults: IpamMaxResults | None + NextToken: NextToken | None + + +class IpamPrefixListResolverVersionEntry(TypedDict, total=False): + Cidr: String | None + + +IpamPrefixListResolverVersionEntrySet = list[IpamPrefixListResolverVersionEntry] + + +class GetIpamPrefixListResolverVersionEntriesResult(TypedDict, total=False): + Entries: IpamPrefixListResolverVersionEntrySet | None + NextToken: NextToken | None + + +IpamPrefixListResolverVersionNumberSet = list[Long] + + +class GetIpamPrefixListResolverVersionsRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverId: IpamPrefixListResolverId + IpamPrefixListResolverVersions: IpamPrefixListResolverVersionNumberSet | None + MaxResults: IpamMaxResults | None + Filters: FilterList | None + NextToken: NextToken | None + + +class IpamPrefixListResolverVersion(TypedDict, total=False): + Version: Long | None + + +IpamPrefixListResolverVersionSet = list[IpamPrefixListResolverVersion] + + +class GetIpamPrefixListResolverVersionsResult(TypedDict, total=False): + IpamPrefixListResolverVersions: IpamPrefixListResolverVersionSet | None + NextToken: NextToken | None class GetIpamResourceCidrsRequest(ServiceRequest): - DryRun: Optional[Boolean] - Filters: Optional[FilterList] - MaxResults: Optional[IpamMaxResults] - NextToken: Optional[NextToken] + DryRun: Boolean | None + Filters: FilterList | None + MaxResults: IpamMaxResults | None + NextToken: NextToken | None IpamScopeId: IpamScopeId - IpamPoolId: Optional[IpamPoolId] - ResourceId: Optional[String] - ResourceType: Optional[IpamResourceType] - ResourceTag: Optional[RequestIpamResourceTag] - ResourceOwner: Optional[String] + IpamPoolId: IpamPoolId | None + ResourceId: String | None + ResourceType: IpamResourceType | None + ResourceTag: RequestIpamResourceTag | None + ResourceOwner: String | None class IpamResourceCidr(TypedDict, total=False): - IpamId: Optional[IpamId] - IpamScopeId: Optional[IpamScopeId] - IpamPoolId: Optional[IpamPoolId] - ResourceRegion: Optional[String] - ResourceOwnerId: Optional[String] - ResourceId: Optional[String] - ResourceName: Optional[String] - ResourceCidr: Optional[String] - ResourceType: Optional[IpamResourceType] - ResourceTags: Optional[IpamResourceTagList] - IpUsage: Optional[BoxedDouble] - ComplianceStatus: Optional[IpamComplianceStatus] - ManagementState: Optional[IpamManagementState] - OverlapStatus: Optional[IpamOverlapStatus] - VpcId: Optional[String] - AvailabilityZoneId: Optional[String] - - -IpamResourceCidrSet = List[IpamResourceCidr] + IpamId: IpamId | None + IpamScopeId: IpamScopeId | None + IpamPoolId: IpamPoolId | None + ResourceRegion: String | None + ResourceOwnerId: String | None + ResourceId: String | None + ResourceName: String | None + ResourceCidr: String | None + ResourceType: IpamResourceType | None + ResourceTags: IpamResourceTagList | None + IpUsage: BoxedDouble | None + ComplianceStatus: IpamComplianceStatus | None + ManagementState: IpamManagementState | None + OverlapStatus: IpamOverlapStatus | None + VpcId: String | None + AvailabilityZoneId: String | None + + +IpamResourceCidrSet = list[IpamResourceCidr] class GetIpamResourceCidrsResult(TypedDict, total=False): - NextToken: Optional[NextToken] - IpamResourceCidrs: Optional[IpamResourceCidrSet] + NextToken: NextToken | None + IpamResourceCidrs: IpamResourceCidrSet | None class GetLaunchTemplateDataRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId class GetLaunchTemplateDataResult(TypedDict, total=False): - LaunchTemplateData: Optional[ResponseLaunchTemplateData] + LaunchTemplateData: ResponseLaunchTemplateData | None class GetManagedPrefixListAssociationsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PrefixListId: PrefixListResourceId - MaxResults: Optional[GetManagedPrefixListAssociationsMaxResults] - NextToken: Optional[NextToken] + MaxResults: GetManagedPrefixListAssociationsMaxResults | None + NextToken: NextToken | None class PrefixListAssociation(TypedDict, total=False): - ResourceId: Optional[String] - ResourceOwner: Optional[String] + ResourceId: String | None + ResourceOwner: String | None -PrefixListAssociationSet = List[PrefixListAssociation] +PrefixListAssociationSet = list[PrefixListAssociation] class GetManagedPrefixListAssociationsResult(TypedDict, total=False): - PrefixListAssociations: Optional[PrefixListAssociationSet] - NextToken: Optional[String] + PrefixListAssociations: PrefixListAssociationSet | None + NextToken: String | None class GetManagedPrefixListEntriesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PrefixListId: PrefixListResourceId - TargetVersion: Optional[Long] - MaxResults: Optional[PrefixListMaxResults] - NextToken: Optional[NextToken] + TargetVersion: Long | None + MaxResults: PrefixListMaxResults | None + NextToken: NextToken | None class PrefixListEntry(TypedDict, total=False): - Cidr: Optional[String] - Description: Optional[String] + Cidr: String | None + Description: String | None -PrefixListEntrySet = List[PrefixListEntry] +PrefixListEntrySet = list[PrefixListEntry] class GetManagedPrefixListEntriesResult(TypedDict, total=False): - Entries: Optional[PrefixListEntrySet] - NextToken: Optional[NextToken] + Entries: PrefixListEntrySet | None + NextToken: NextToken | None class GetNetworkInsightsAccessScopeAnalysisFindingsRequest(ServiceRequest): NetworkInsightsAccessScopeAnalysisId: NetworkInsightsAccessScopeAnalysisId - MaxResults: Optional[GetNetworkInsightsAccessScopeAnalysisFindingsMaxResults] - NextToken: Optional[NextToken] - DryRun: Optional[Boolean] + MaxResults: GetNetworkInsightsAccessScopeAnalysisFindingsMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None class GetNetworkInsightsAccessScopeAnalysisFindingsResult(TypedDict, total=False): - NetworkInsightsAccessScopeAnalysisId: Optional[NetworkInsightsAccessScopeAnalysisId] - AnalysisStatus: Optional[AnalysisStatus] - AnalysisFindings: Optional[AccessScopeAnalysisFindingList] - NextToken: Optional[String] + NetworkInsightsAccessScopeAnalysisId: NetworkInsightsAccessScopeAnalysisId | None + AnalysisStatus: AnalysisStatus | None + AnalysisFindings: AccessScopeAnalysisFindingList | None + NextToken: String | None class GetNetworkInsightsAccessScopeContentRequest(ServiceRequest): NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetNetworkInsightsAccessScopeContentResult(TypedDict, total=False): - NetworkInsightsAccessScopeContent: Optional[NetworkInsightsAccessScopeContent] + NetworkInsightsAccessScopeContent: NetworkInsightsAccessScopeContent | None class GetPasswordDataRequest(ServiceRequest): InstanceId: InstanceId - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetPasswordDataResult(TypedDict, total=False): - InstanceId: Optional[String] - Timestamp: Optional[DateTime] - PasswordData: Optional[PasswordData] + InstanceId: String | None + Timestamp: DateTime | None + PasswordData: PasswordData | None class GetReservedInstancesExchangeQuoteRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ReservedInstanceIds: ReservedInstanceIdSet - TargetConfigurations: Optional[TargetConfigurationRequestSet] + TargetConfigurations: TargetConfigurationRequestSet | None class TargetConfiguration(TypedDict, total=False): - InstanceCount: Optional[Integer] - OfferingId: Optional[String] + InstanceCount: Integer | None + OfferingId: String | None class ReservationValue(TypedDict, total=False): - HourlyPrice: Optional[String] - RemainingTotalValue: Optional[String] - RemainingUpfrontValue: Optional[String] + HourlyPrice: String | None + RemainingTotalValue: String | None + RemainingUpfrontValue: String | None class TargetReservationValue(TypedDict, total=False): - ReservationValue: Optional[ReservationValue] - TargetConfiguration: Optional[TargetConfiguration] + ReservationValue: ReservationValue | None + TargetConfiguration: TargetConfiguration | None -TargetReservationValueSet = List[TargetReservationValue] +TargetReservationValueSet = list[TargetReservationValue] class ReservedInstanceReservationValue(TypedDict, total=False): - ReservationValue: Optional[ReservationValue] - ReservedInstanceId: Optional[String] + ReservationValue: ReservationValue | None + ReservedInstanceId: String | None -ReservedInstanceReservationValueSet = List[ReservedInstanceReservationValue] +ReservedInstanceReservationValueSet = list[ReservedInstanceReservationValue] class GetReservedInstancesExchangeQuoteResult(TypedDict, total=False): - CurrencyCode: Optional[String] - IsValidExchange: Optional[Boolean] - OutputReservedInstancesWillExpireAt: Optional[DateTime] - PaymentDue: Optional[String] - ReservedInstanceValueRollup: Optional[ReservationValue] - ReservedInstanceValueSet: Optional[ReservedInstanceReservationValueSet] - TargetConfigurationValueRollup: Optional[ReservationValue] - TargetConfigurationValueSet: Optional[TargetReservationValueSet] - ValidationFailureReason: Optional[String] + CurrencyCode: String | None + IsValidExchange: Boolean | None + OutputReservedInstancesWillExpireAt: DateTime | None + PaymentDue: String | None + ReservedInstanceValueRollup: ReservationValue | None + ReservedInstanceValueSet: ReservedInstanceReservationValueSet | None + TargetConfigurationValueRollup: ReservationValue | None + TargetConfigurationValueSet: TargetReservationValueSet | None + ValidationFailureReason: String | None class GetRouteServerAssociationsRequest(ServiceRequest): RouteServerId: RouteServerId - DryRun: Optional[Boolean] + DryRun: Boolean | None -RouteServerAssociationsList = List[RouteServerAssociation] +RouteServerAssociationsList = list[RouteServerAssociation] class GetRouteServerAssociationsResult(TypedDict, total=False): - RouteServerAssociations: Optional[RouteServerAssociationsList] + RouteServerAssociations: RouteServerAssociationsList | None class GetRouteServerPropagationsRequest(ServiceRequest): RouteServerId: RouteServerId - RouteTableId: Optional[RouteTableId] - DryRun: Optional[Boolean] + RouteTableId: RouteTableId | None + DryRun: Boolean | None -RouteServerPropagationsList = List[RouteServerPropagation] +RouteServerPropagationsList = list[RouteServerPropagation] class GetRouteServerPropagationsResult(TypedDict, total=False): - RouteServerPropagations: Optional[RouteServerPropagationsList] + RouteServerPropagations: RouteServerPropagationsList | None class GetRouteServerRoutingDatabaseRequest(ServiceRequest): RouteServerId: RouteServerId - NextToken: Optional[String] - MaxResults: Optional[RouteServerMaxResults] - DryRun: Optional[Boolean] - Filters: Optional[FilterList] + NextToken: String | None + MaxResults: RouteServerMaxResults | None + DryRun: Boolean | None + Filters: FilterList | None class RouteServerRouteInstallationDetail(TypedDict, total=False): - RouteTableId: Optional[RouteTableId] - RouteInstallationStatus: Optional[RouteServerRouteInstallationStatus] - RouteInstallationStatusReason: Optional[String] + RouteTableId: RouteTableId | None + RouteInstallationStatus: RouteServerRouteInstallationStatus | None + RouteInstallationStatusReason: String | None -RouteServerRouteInstallationDetails = List[RouteServerRouteInstallationDetail] +RouteServerRouteInstallationDetails = list[RouteServerRouteInstallationDetail] class RouteServerRoute(TypedDict, total=False): - RouteServerEndpointId: Optional[RouteServerEndpointId] - RouteServerPeerId: Optional[RouteServerPeerId] - RouteInstallationDetails: Optional[RouteServerRouteInstallationDetails] - RouteStatus: Optional[RouteServerRouteStatus] - Prefix: Optional[String] - AsPaths: Optional[AsPath] - Med: Optional[Integer] - NextHopIp: Optional[String] + RouteServerEndpointId: RouteServerEndpointId | None + RouteServerPeerId: RouteServerPeerId | None + RouteInstallationDetails: RouteServerRouteInstallationDetails | None + RouteStatus: RouteServerRouteStatus | None + Prefix: String | None + AsPaths: AsPath | None + Med: Integer | None + NextHopIp: String | None -RouteServerRouteList = List[RouteServerRoute] +RouteServerRouteList = list[RouteServerRoute] class GetRouteServerRoutingDatabaseResult(TypedDict, total=False): - AreRoutesPersisted: Optional[Boolean] - Routes: Optional[RouteServerRouteList] - NextToken: Optional[String] + AreRoutesPersisted: Boolean | None + Routes: RouteServerRouteList | None + NextToken: String | None class GetSecurityGroupsForVpcRequest(ServiceRequest): VpcId: VpcId - NextToken: Optional[String] - MaxResults: Optional[GetSecurityGroupsForVpcRequestMaxResults] - Filters: Optional[FilterList] - DryRun: Optional[Boolean] + NextToken: String | None + MaxResults: GetSecurityGroupsForVpcRequestMaxResults | None + Filters: FilterList | None + DryRun: Boolean | None class SecurityGroupForVpc(TypedDict, total=False): - Description: Optional[String] - GroupName: Optional[String] - OwnerId: Optional[String] - GroupId: Optional[String] - Tags: Optional[TagList] - PrimaryVpcId: Optional[String] + Description: String | None + GroupName: String | None + OwnerId: String | None + GroupId: String | None + Tags: TagList | None + PrimaryVpcId: String | None -SecurityGroupForVpcList = List[SecurityGroupForVpc] +SecurityGroupForVpcList = list[SecurityGroupForVpc] class GetSecurityGroupsForVpcResult(TypedDict, total=False): - NextToken: Optional[String] - SecurityGroupForVpcs: Optional[SecurityGroupForVpcList] + NextToken: String | None + SecurityGroupForVpcs: SecurityGroupForVpcList | None class GetSerialConsoleAccessStatusRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetSerialConsoleAccessStatusResult(TypedDict, total=False): - SerialConsoleAccessEnabled: Optional[Boolean] - ManagedBy: Optional[ManagedBy] + SerialConsoleAccessEnabled: Boolean | None + ManagedBy: ManagedBy | None class GetSnapshotBlockPublicAccessStateRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetSnapshotBlockPublicAccessStateResult(TypedDict, total=False): - State: Optional[SnapshotBlockPublicAccessState] - ManagedBy: Optional[ManagedBy] + State: SnapshotBlockPublicAccessState | None + ManagedBy: ManagedBy | None class InstanceRequirementsWithMetadataRequest(TypedDict, total=False): - ArchitectureTypes: Optional[ArchitectureTypeSet] - VirtualizationTypes: Optional[VirtualizationTypeSet] - InstanceRequirements: Optional[InstanceRequirementsRequest] + ArchitectureTypes: ArchitectureTypeSet | None + VirtualizationTypes: VirtualizationTypeSet | None + InstanceRequirements: InstanceRequirementsRequest | None -RegionNames = List[String] -InstanceTypes = List[String] +RegionNames = list[String] +InstanceTypes = list[String] class GetSpotPlacementScoresRequest(ServiceRequest): - InstanceTypes: Optional[InstanceTypes] + InstanceTypes: InstanceTypes | None TargetCapacity: SpotPlacementScoresTargetCapacity - TargetCapacityUnitType: Optional[TargetCapacityUnitType] - SingleAvailabilityZone: Optional[Boolean] - RegionNames: Optional[RegionNames] - InstanceRequirementsWithMetadata: Optional[InstanceRequirementsWithMetadataRequest] - DryRun: Optional[Boolean] - MaxResults: Optional[SpotPlacementScoresMaxResults] - NextToken: Optional[String] + TargetCapacityUnitType: TargetCapacityUnitType | None + SingleAvailabilityZone: Boolean | None + RegionNames: RegionNames | None + InstanceRequirementsWithMetadata: InstanceRequirementsWithMetadataRequest | None + DryRun: Boolean | None + MaxResults: SpotPlacementScoresMaxResults | None + NextToken: String | None class SpotPlacementScore(TypedDict, total=False): - Region: Optional[String] - AvailabilityZoneId: Optional[String] - Score: Optional[Integer] + Region: String | None + AvailabilityZoneId: String | None + Score: Integer | None -SpotPlacementScores = List[SpotPlacementScore] +SpotPlacementScores = list[SpotPlacementScore] class GetSpotPlacementScoresResult(TypedDict, total=False): - SpotPlacementScores: Optional[SpotPlacementScores] - NextToken: Optional[String] + SpotPlacementScores: SpotPlacementScores | None + NextToken: String | None class GetSubnetCidrReservationsRequest(ServiceRequest): - Filters: Optional[FilterList] + Filters: FilterList | None SubnetId: SubnetId - DryRun: Optional[Boolean] - NextToken: Optional[String] - MaxResults: Optional[GetSubnetCidrReservationsMaxResults] + DryRun: Boolean | None + NextToken: String | None + MaxResults: GetSubnetCidrReservationsMaxResults | None -SubnetCidrReservationList = List[SubnetCidrReservation] +SubnetCidrReservationList = list[SubnetCidrReservation] class GetSubnetCidrReservationsResult(TypedDict, total=False): - SubnetIpv4CidrReservations: Optional[SubnetCidrReservationList] - SubnetIpv6CidrReservations: Optional[SubnetCidrReservationList] - NextToken: Optional[String] + SubnetIpv4CidrReservations: SubnetCidrReservationList | None + SubnetIpv6CidrReservations: SubnetCidrReservationList | None + NextToken: String | None class GetTransitGatewayAttachmentPropagationsRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class TransitGatewayAttachmentPropagation(TypedDict, total=False): - TransitGatewayRouteTableId: Optional[String] - State: Optional[TransitGatewayPropagationState] + TransitGatewayRouteTableId: String | None + State: TransitGatewayPropagationState | None -TransitGatewayAttachmentPropagationList = List[TransitGatewayAttachmentPropagation] +TransitGatewayAttachmentPropagationList = list[TransitGatewayAttachmentPropagation] class GetTransitGatewayAttachmentPropagationsResult(TypedDict, total=False): - TransitGatewayAttachmentPropagations: Optional[TransitGatewayAttachmentPropagationList] - NextToken: Optional[String] + TransitGatewayAttachmentPropagations: TransitGatewayAttachmentPropagationList | None + NextToken: String | None + + +class GetTransitGatewayMeteringPolicyEntriesRequest(ServiceRequest): + TransitGatewayMeteringPolicyId: TransitGatewayMeteringPolicyId + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None + + +TransitGatewayMeteringPolicyEntryList = list[TransitGatewayMeteringPolicyEntry] + + +class GetTransitGatewayMeteringPolicyEntriesResult(TypedDict, total=False): + TransitGatewayMeteringPolicyEntries: TransitGatewayMeteringPolicyEntryList | None + NextToken: String | None class GetTransitGatewayMulticastDomainAssociationsRequest(ServiceRequest): TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class TransitGatewayMulticastDomainAssociation(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - ResourceOwnerId: Optional[String] - Subnet: Optional[SubnetAssociation] + TransitGatewayAttachmentId: String | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + ResourceOwnerId: String | None + Subnet: SubnetAssociation | None -TransitGatewayMulticastDomainAssociationList = List[TransitGatewayMulticastDomainAssociation] +TransitGatewayMulticastDomainAssociationList = list[TransitGatewayMulticastDomainAssociation] class GetTransitGatewayMulticastDomainAssociationsResult(TypedDict, total=False): - MulticastDomainAssociations: Optional[TransitGatewayMulticastDomainAssociationList] - NextToken: Optional[String] + MulticastDomainAssociations: TransitGatewayMulticastDomainAssociationList | None + NextToken: String | None class GetTransitGatewayPolicyTableAssociationsRequest(ServiceRequest): TransitGatewayPolicyTableId: TransitGatewayPolicyTableId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayPolicyTableAssociationList = List[TransitGatewayPolicyTableAssociation] +TransitGatewayPolicyTableAssociationList = list[TransitGatewayPolicyTableAssociation] class GetTransitGatewayPolicyTableAssociationsResult(TypedDict, total=False): - Associations: Optional[TransitGatewayPolicyTableAssociationList] - NextToken: Optional[String] + Associations: TransitGatewayPolicyTableAssociationList | None + NextToken: String | None class GetTransitGatewayPolicyTableEntriesRequest(ServiceRequest): TransitGatewayPolicyTableId: TransitGatewayPolicyTableId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class TransitGatewayPolicyRuleMetaData(TypedDict, total=False): - MetaDataKey: Optional[String] - MetaDataValue: Optional[String] + MetaDataKey: String | None + MetaDataValue: String | None class TransitGatewayPolicyRule(TypedDict, total=False): - SourceCidrBlock: Optional[String] - SourcePortRange: Optional[String] - DestinationCidrBlock: Optional[String] - DestinationPortRange: Optional[String] - Protocol: Optional[String] - MetaData: Optional[TransitGatewayPolicyRuleMetaData] + SourceCidrBlock: String | None + SourcePortRange: String | None + DestinationCidrBlock: String | None + DestinationPortRange: String | None + Protocol: String | None + MetaData: TransitGatewayPolicyRuleMetaData | None class TransitGatewayPolicyTableEntry(TypedDict, total=False): - PolicyRuleNumber: Optional[String] - PolicyRule: Optional[TransitGatewayPolicyRule] - TargetRouteTableId: Optional[TransitGatewayRouteTableId] + PolicyRuleNumber: String | None + PolicyRule: TransitGatewayPolicyRule | None + TargetRouteTableId: TransitGatewayRouteTableId | None -TransitGatewayPolicyTableEntryList = List[TransitGatewayPolicyTableEntry] +TransitGatewayPolicyTableEntryList = list[TransitGatewayPolicyTableEntry] class GetTransitGatewayPolicyTableEntriesResult(TypedDict, total=False): - TransitGatewayPolicyTableEntries: Optional[TransitGatewayPolicyTableEntryList] + TransitGatewayPolicyTableEntries: TransitGatewayPolicyTableEntryList | None class GetTransitGatewayPrefixListReferencesRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None -TransitGatewayPrefixListReferenceSet = List[TransitGatewayPrefixListReference] +TransitGatewayPrefixListReferenceSet = list[TransitGatewayPrefixListReference] class GetTransitGatewayPrefixListReferencesResult(TypedDict, total=False): - TransitGatewayPrefixListReferences: Optional[TransitGatewayPrefixListReferenceSet] - NextToken: Optional[String] + TransitGatewayPrefixListReferences: TransitGatewayPrefixListReferenceSet | None + NextToken: String | None class GetTransitGatewayRouteTableAssociationsRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class TransitGatewayRouteTableAssociation(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - State: Optional[TransitGatewayAssociationState] + TransitGatewayAttachmentId: String | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + State: TransitGatewayAssociationState | None -TransitGatewayRouteTableAssociationList = List[TransitGatewayRouteTableAssociation] +TransitGatewayRouteTableAssociationList = list[TransitGatewayRouteTableAssociation] class GetTransitGatewayRouteTableAssociationsResult(TypedDict, total=False): - Associations: Optional[TransitGatewayRouteTableAssociationList] - NextToken: Optional[String] + Associations: TransitGatewayRouteTableAssociationList | None + NextToken: String | None class GetTransitGatewayRouteTablePropagationsRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class TransitGatewayRouteTablePropagation(TypedDict, total=False): - TransitGatewayAttachmentId: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - State: Optional[TransitGatewayPropagationState] - TransitGatewayRouteTableAnnouncementId: Optional[TransitGatewayRouteTableAnnouncementId] + TransitGatewayAttachmentId: String | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + State: TransitGatewayPropagationState | None + TransitGatewayRouteTableAnnouncementId: TransitGatewayRouteTableAnnouncementId | None -TransitGatewayRouteTablePropagationList = List[TransitGatewayRouteTablePropagation] +TransitGatewayRouteTablePropagationList = list[TransitGatewayRouteTablePropagation] class GetTransitGatewayRouteTablePropagationsResult(TypedDict, total=False): - TransitGatewayRouteTablePropagations: Optional[TransitGatewayRouteTablePropagationList] - NextToken: Optional[String] + TransitGatewayRouteTablePropagations: TransitGatewayRouteTablePropagationList | None + NextToken: String | None class GetVerifiedAccessEndpointPolicyRequest(ServiceRequest): VerifiedAccessEndpointId: VerifiedAccessEndpointId - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetVerifiedAccessEndpointPolicyResult(TypedDict, total=False): - PolicyEnabled: Optional[Boolean] - PolicyDocument: Optional[String] + PolicyEnabled: Boolean | None + PolicyDocument: String | None class GetVerifiedAccessEndpointTargetsRequest(ServiceRequest): VerifiedAccessEndpointId: VerifiedAccessEndpointId - MaxResults: Optional[GetVerifiedAccessEndpointTargetsMaxResults] - NextToken: Optional[NextToken] - DryRun: Optional[Boolean] + MaxResults: GetVerifiedAccessEndpointTargetsMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None class VerifiedAccessEndpointTarget(TypedDict, total=False): - VerifiedAccessEndpointId: Optional[VerifiedAccessEndpointId] - VerifiedAccessEndpointTargetIpAddress: Optional[String] - VerifiedAccessEndpointTargetDns: Optional[String] + VerifiedAccessEndpointId: VerifiedAccessEndpointId | None + VerifiedAccessEndpointTargetIpAddress: String | None + VerifiedAccessEndpointTargetDns: String | None -VerifiedAccessEndpointTargetList = List[VerifiedAccessEndpointTarget] +VerifiedAccessEndpointTargetList = list[VerifiedAccessEndpointTarget] class GetVerifiedAccessEndpointTargetsResult(TypedDict, total=False): - VerifiedAccessEndpointTargets: Optional[VerifiedAccessEndpointTargetList] - NextToken: Optional[NextToken] + VerifiedAccessEndpointTargets: VerifiedAccessEndpointTargetList | None + NextToken: NextToken | None class GetVerifiedAccessGroupPolicyRequest(ServiceRequest): VerifiedAccessGroupId: VerifiedAccessGroupId - DryRun: Optional[Boolean] + DryRun: Boolean | None class GetVerifiedAccessGroupPolicyResult(TypedDict, total=False): - PolicyEnabled: Optional[Boolean] - PolicyDocument: Optional[String] + PolicyEnabled: Boolean | None + PolicyDocument: String | None + + +class GetVpcResourcesBlockingEncryptionEnforcementRequest(ServiceRequest): + VpcId: VpcId + MaxResults: GetVpcResourcesBlockingEncryptionEnforcementMaxResults | None + NextToken: String | None + DryRun: Boolean | None + + +class VpcEncryptionNonCompliantResource(TypedDict, total=False): + Id: String | None + Type: String | None + Description: String | None + IsExcludable: Boolean | None + + +VpcEncryptionNonCompliantResourceList = list[VpcEncryptionNonCompliantResource] + + +class GetVpcResourcesBlockingEncryptionEnforcementResult(TypedDict, total=False): + NonCompliantResources: VpcEncryptionNonCompliantResourceList | None + NextToken: String | None class GetVpnConnectionDeviceSampleConfigurationRequest(ServiceRequest): VpnConnectionId: VpnConnectionId VpnConnectionDeviceTypeId: VpnConnectionDeviceTypeId - InternetKeyExchangeVersion: Optional[String] - SampleType: Optional[String] - DryRun: Optional[Boolean] + InternetKeyExchangeVersion: String | None + SampleType: String | None + DryRun: Boolean | None class GetVpnConnectionDeviceSampleConfigurationResult(TypedDict, total=False): - VpnConnectionDeviceSampleConfiguration: Optional[VpnConnectionDeviceSampleConfiguration] + VpnConnectionDeviceSampleConfiguration: VpnConnectionDeviceSampleConfiguration | None class GetVpnConnectionDeviceTypesRequest(ServiceRequest): - MaxResults: Optional[GVCDMaxResults] - NextToken: Optional[NextToken] - DryRun: Optional[Boolean] + MaxResults: GVCDMaxResults | None + NextToken: NextToken | None + DryRun: Boolean | None class VpnConnectionDeviceType(TypedDict, total=False): - VpnConnectionDeviceTypeId: Optional[String] - Vendor: Optional[String] - Platform: Optional[String] - Software: Optional[String] + VpnConnectionDeviceTypeId: String | None + Vendor: String | None + Platform: String | None + Software: String | None -VpnConnectionDeviceTypeList = List[VpnConnectionDeviceType] +VpnConnectionDeviceTypeList = list[VpnConnectionDeviceType] class GetVpnConnectionDeviceTypesResult(TypedDict, total=False): - VpnConnectionDeviceTypes: Optional[VpnConnectionDeviceTypeList] - NextToken: Optional[NextToken] + VpnConnectionDeviceTypes: VpnConnectionDeviceTypeList | None + NextToken: NextToken | None class GetVpnTunnelReplacementStatusRequest(ServiceRequest): VpnConnectionId: VpnConnectionId VpnTunnelOutsideIpAddress: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class MaintenanceDetails(TypedDict, total=False): - PendingMaintenance: Optional[String] - MaintenanceAutoAppliedAfter: Optional[MillisecondDateTime] - LastMaintenanceApplied: Optional[MillisecondDateTime] + PendingMaintenance: String | None + MaintenanceAutoAppliedAfter: MillisecondDateTime | None + LastMaintenanceApplied: MillisecondDateTime | None class GetVpnTunnelReplacementStatusResult(TypedDict, total=False): - VpnConnectionId: Optional[VpnConnectionId] - TransitGatewayId: Optional[TransitGatewayId] - CustomerGatewayId: Optional[CustomerGatewayId] - VpnGatewayId: Optional[VpnGatewayId] - VpnTunnelOutsideIpAddress: Optional[String] - MaintenanceDetails: Optional[MaintenanceDetails] + VpnConnectionId: VpnConnectionId | None + TransitGatewayId: TransitGatewayId | None + CustomerGatewayId: CustomerGatewayId | None + VpnGatewayId: VpnGatewayId | None + VpnTunnelOutsideIpAddress: String | None + MaintenanceDetails: MaintenanceDetails | None class HibernationOptionsRequest(TypedDict, total=False): - Configured: Optional[Boolean] + Configured: Boolean | None class LaunchPermission(TypedDict, total=False): - OrganizationArn: Optional[String] - OrganizationalUnitArn: Optional[String] - UserId: Optional[String] - Group: Optional[PermissionGroup] + OrganizationArn: String | None + OrganizationalUnitArn: String | None + UserId: String | None + Group: PermissionGroup | None -LaunchPermissionList = List[LaunchPermission] +LaunchPermissionList = list[LaunchPermission] class ImageAttribute(TypedDict, total=False): - Description: Optional[AttributeValue] - KernelId: Optional[AttributeValue] - RamdiskId: Optional[AttributeValue] - SriovNetSupport: Optional[AttributeValue] - BootMode: Optional[AttributeValue] - TpmSupport: Optional[AttributeValue] - UefiData: Optional[AttributeValue] - LastLaunchedTime: Optional[AttributeValue] - ImdsSupport: Optional[AttributeValue] - DeregistrationProtection: Optional[AttributeValue] - ImageId: Optional[String] - LaunchPermissions: Optional[LaunchPermissionList] - ProductCodes: Optional[ProductCodeList] - BlockDeviceMappings: Optional[BlockDeviceMappingList] - - -ImageProviderRequestList = List[ImageProviderRequest] + Description: AttributeValue | None + KernelId: AttributeValue | None + RamdiskId: AttributeValue | None + SriovNetSupport: AttributeValue | None + BootMode: AttributeValue | None + TpmSupport: AttributeValue | None + UefiData: AttributeValue | None + LastLaunchedTime: AttributeValue | None + ImdsSupport: AttributeValue | None + DeregistrationProtection: AttributeValue | None + ImageId: String | None + LaunchPermissions: LaunchPermissionList | None + ProductCodes: ProductCodeList | None + BlockDeviceMappings: BlockDeviceMappingList | None + + +ImageNameRequestList = list[ImageNameRequest] +MarketplaceProductCodeRequestList = list[MarketplaceProductCodeRequest] +ImageProviderRequestList = list[ImageProviderRequest] class ImageCriterionRequest(TypedDict, total=False): - ImageProviders: Optional[ImageProviderRequestList] + ImageProviders: ImageProviderRequestList | None + MarketplaceProductCodes: MarketplaceProductCodeRequestList | None + ImageNames: ImageNameRequestList | None + DeprecationTimeCondition: DeprecationTimeConditionRequest | None + CreationDateCondition: CreationDateConditionRequest | None -ImageCriterionRequestList = List[ImageCriterionRequest] +ImageCriterionRequestList = list[ImageCriterionRequest] class UserBucket(TypedDict, total=False): - S3Bucket: Optional[String] - S3Key: Optional[String] + S3Bucket: String | None + S3Key: String | None class ImageDiskContainer(TypedDict, total=False): - Description: Optional[String] - DeviceName: Optional[String] - Format: Optional[String] - SnapshotId: Optional[SnapshotId] - Url: Optional[SensitiveUrl] - UserBucket: Optional[UserBucket] + Description: String | None + DeviceName: String | None + Format: String | None + SnapshotId: SnapshotId | None + Url: SensitiveUrl | None + UserBucket: UserBucket | None -ImageDiskContainerList = List[ImageDiskContainer] +ImageDiskContainerList = list[ImageDiskContainer] class ImageRecycleBinInfo(TypedDict, total=False): - ImageId: Optional[String] - Name: Optional[String] - Description: Optional[String] - RecycleBinEnterTime: Optional[MillisecondDateTime] - RecycleBinExitTime: Optional[MillisecondDateTime] + ImageId: String | None + Name: String | None + Description: String | None + RecycleBinEnterTime: MillisecondDateTime | None + RecycleBinExitTime: MillisecondDateTime | None -ImageRecycleBinInfoList = List[ImageRecycleBinInfo] +ImageRecycleBinInfoList = list[ImageRecycleBinInfo] class ImportClientVpnClientCertificateRevocationListRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId CertificateRevocationList: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class ImportClientVpnClientCertificateRevocationListResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ImportImageLicenseConfigurationRequest(TypedDict, total=False): - LicenseConfigurationArn: Optional[String] + LicenseConfigurationArn: String | None -ImportImageLicenseSpecificationListRequest = List[ImportImageLicenseConfigurationRequest] +ImportImageLicenseSpecificationListRequest = list[ImportImageLicenseConfigurationRequest] class ImportImageRequest(ServiceRequest): - Architecture: Optional[String] - ClientData: Optional[ClientData] - ClientToken: Optional[String] - Description: Optional[String] - DiskContainers: Optional[ImageDiskContainerList] - DryRun: Optional[Boolean] - Encrypted: Optional[Boolean] - Hypervisor: Optional[String] - KmsKeyId: Optional[KmsKeyId] - LicenseType: Optional[String] - Platform: Optional[String] - RoleName: Optional[String] - LicenseSpecifications: Optional[ImportImageLicenseSpecificationListRequest] - TagSpecifications: Optional[TagSpecificationList] - UsageOperation: Optional[String] - BootMode: Optional[BootModeValues] + Architecture: String | None + ClientData: ClientData | None + ClientToken: String | None + Description: String | None + DiskContainers: ImageDiskContainerList | None + DryRun: Boolean | None + Encrypted: Boolean | None + Hypervisor: String | None + KmsKeyId: KmsKeyId | None + LicenseType: String | None + Platform: String | None + RoleName: String | None + LicenseSpecifications: ImportImageLicenseSpecificationListRequest | None + TagSpecifications: TagSpecificationList | None + UsageOperation: String | None + BootMode: BootModeValues | None class ImportImageResult(TypedDict, total=False): - Architecture: Optional[String] - Description: Optional[String] - Encrypted: Optional[Boolean] - Hypervisor: Optional[String] - ImageId: Optional[String] - ImportTaskId: Optional[ImportImageTaskId] - KmsKeyId: Optional[KmsKeyId] - LicenseType: Optional[String] - Platform: Optional[String] - Progress: Optional[String] - SnapshotDetails: Optional[SnapshotDetailList] - Status: Optional[String] - StatusMessage: Optional[String] - LicenseSpecifications: Optional[ImportImageLicenseSpecificationListResponse] - Tags: Optional[TagList] - UsageOperation: Optional[String] + Architecture: String | None + Description: String | None + Encrypted: Boolean | None + Hypervisor: String | None + ImageId: String | None + ImportTaskId: ImportImageTaskId | None + KmsKeyId: KmsKeyId | None + LicenseType: String | None + Platform: String | None + Progress: String | None + SnapshotDetails: SnapshotDetailList | None + Status: String | None + StatusMessage: String | None + LicenseSpecifications: ImportImageLicenseSpecificationListResponse | None + Tags: TagList | None + UsageOperation: String | None class UserData(TypedDict, total=False): - Data: Optional[String] + Data: String | None class ImportInstanceLaunchSpecification(TypedDict, total=False): - Architecture: Optional[ArchitectureValues] - GroupNames: Optional[SecurityGroupStringList] - GroupIds: Optional[SecurityGroupIdStringList] - AdditionalInfo: Optional[String] - UserData: Optional[UserData] - InstanceType: Optional[InstanceType] - Placement: Optional[Placement] - Monitoring: Optional[Boolean] - SubnetId: Optional[SubnetId] - InstanceInitiatedShutdownBehavior: Optional[ShutdownBehavior] - PrivateIpAddress: Optional[String] + Architecture: ArchitectureValues | None + GroupNames: SecurityGroupStringList | None + GroupIds: SecurityGroupIdStringList | None + AdditionalInfo: String | None + UserData: UserData | None + InstanceType: InstanceType | None + Placement: Placement | None + Monitoring: Boolean | None + SubnetId: SubnetId | None + InstanceInitiatedShutdownBehavior: ShutdownBehavior | None + PrivateIpAddress: String | None class ImportInstanceRequest(ServiceRequest): - DryRun: Optional[Boolean] - Description: Optional[String] - LaunchSpecification: Optional[ImportInstanceLaunchSpecification] - DiskImages: Optional[DiskImageList] + DryRun: Boolean | None + Description: String | None + LaunchSpecification: ImportInstanceLaunchSpecification | None + DiskImages: DiskImageList | None Platform: PlatformValues class ImportInstanceResult(TypedDict, total=False): - ConversionTask: Optional[ConversionTask] + ConversionTask: ConversionTask | None class ImportKeyPairRequest(ServiceRequest): - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None KeyName: String PublicKeyMaterial: Blob class ImportKeyPairResult(TypedDict, total=False): - KeyFingerprint: Optional[String] - KeyName: Optional[String] - KeyPairId: Optional[String] - Tags: Optional[TagList] + KeyFingerprint: String | None + KeyName: String | None + KeyPairId: String | None + Tags: TagList | None class SnapshotDiskContainer(TypedDict, total=False): - Description: Optional[String] - Format: Optional[String] - Url: Optional[SensitiveUrl] - UserBucket: Optional[UserBucket] + Description: String | None + Format: String | None + Url: SensitiveUrl | None + UserBucket: UserBucket | None class ImportSnapshotRequest(ServiceRequest): - ClientData: Optional[ClientData] - ClientToken: Optional[String] - Description: Optional[String] - DiskContainer: Optional[SnapshotDiskContainer] - DryRun: Optional[Boolean] - Encrypted: Optional[Boolean] - KmsKeyId: Optional[KmsKeyId] - RoleName: Optional[String] - TagSpecifications: Optional[TagSpecificationList] + ClientData: ClientData | None + ClientToken: String | None + Description: String | None + DiskContainer: SnapshotDiskContainer | None + DryRun: Boolean | None + Encrypted: Boolean | None + KmsKeyId: KmsKeyId | None + RoleName: String | None + TagSpecifications: TagSpecificationList | None class ImportSnapshotResult(TypedDict, total=False): - Description: Optional[String] - ImportTaskId: Optional[String] - SnapshotTaskDetail: Optional[SnapshotTaskDetail] - Tags: Optional[TagList] + Description: String | None + ImportTaskId: String | None + SnapshotTaskDetail: SnapshotTaskDetail | None + Tags: TagList | None class ImportVolumeRequest(ServiceRequest): - DryRun: Optional[Boolean] - AvailabilityZone: String + AvailabilityZoneId: AvailabilityZoneId | None + DryRun: Boolean | None + AvailabilityZone: String | None Image: DiskImageDetail - Description: Optional[String] + Description: String | None Volume: VolumeDetail class ImportVolumeResult(TypedDict, total=False): - ConversionTask: Optional[ConversionTask] + ConversionTask: ConversionTask | None class InstanceAttribute(TypedDict, total=False): - BlockDeviceMappings: Optional[InstanceBlockDeviceMappingList] - DisableApiTermination: Optional[AttributeBooleanValue] - EnaSupport: Optional[AttributeBooleanValue] - EnclaveOptions: Optional[EnclaveOptions] - EbsOptimized: Optional[AttributeBooleanValue] - InstanceId: Optional[String] - InstanceInitiatedShutdownBehavior: Optional[AttributeValue] - InstanceType: Optional[AttributeValue] - KernelId: Optional[AttributeValue] - ProductCodes: Optional[ProductCodeList] - RamdiskId: Optional[AttributeValue] - RootDeviceName: Optional[AttributeValue] - SourceDestCheck: Optional[AttributeBooleanValue] - SriovNetSupport: Optional[AttributeValue] - UserData: Optional[AttributeValue] - DisableApiStop: Optional[AttributeBooleanValue] - Groups: Optional[GroupIdentifierList] + BlockDeviceMappings: InstanceBlockDeviceMappingList | None + DisableApiTermination: AttributeBooleanValue | None + EnaSupport: AttributeBooleanValue | None + EnclaveOptions: EnclaveOptions | None + EbsOptimized: AttributeBooleanValue | None + InstanceId: String | None + InstanceInitiatedShutdownBehavior: AttributeValue | None + InstanceType: AttributeValue | None + KernelId: AttributeValue | None + ProductCodes: ProductCodeList | None + RamdiskId: AttributeValue | None + RootDeviceName: AttributeValue | None + SourceDestCheck: AttributeBooleanValue | None + SriovNetSupport: AttributeValue | None + UserData: AttributeValue | None + DisableApiStop: AttributeBooleanValue | None + Groups: GroupIdentifierList | None class InstanceBlockDeviceMappingSpecification(TypedDict, total=False): - DeviceName: Optional[String] - Ebs: Optional[EbsInstanceBlockDeviceSpecification] - VirtualName: Optional[String] - NoDevice: Optional[String] + DeviceName: String | None + Ebs: EbsInstanceBlockDeviceSpecification | None + VirtualName: String | None + NoDevice: String | None -InstanceBlockDeviceMappingSpecificationList = List[InstanceBlockDeviceMappingSpecification] +InstanceBlockDeviceMappingSpecificationList = list[InstanceBlockDeviceMappingSpecification] class InstanceCreditSpecificationRequest(TypedDict, total=False): InstanceId: InstanceId - CpuCredits: Optional[String] + CpuCredits: String | None -InstanceCreditSpecificationListRequest = List[InstanceCreditSpecificationRequest] -InstanceIdSet = List[InstanceId] +InstanceCreditSpecificationListRequest = list[InstanceCreditSpecificationRequest] +InstanceIdSet = list[InstanceId] class InstanceMaintenanceOptionsRequest(TypedDict, total=False): - AutoRecovery: Optional[InstanceAutoRecoveryState] + AutoRecovery: InstanceAutoRecoveryState | None class SpotMarketOptions(TypedDict, total=False): - MaxPrice: Optional[String] - SpotInstanceType: Optional[SpotInstanceType] - BlockDurationMinutes: Optional[Integer] - ValidUntil: Optional[DateTime] - InstanceInterruptionBehavior: Optional[InstanceInterruptionBehavior] + MaxPrice: String | None + SpotInstanceType: SpotInstanceType | None + BlockDurationMinutes: Integer | None + ValidUntil: DateTime | None + InstanceInterruptionBehavior: InstanceInterruptionBehavior | None class InstanceMarketOptionsRequest(TypedDict, total=False): - MarketType: Optional[MarketType] - SpotOptions: Optional[SpotMarketOptions] + MarketType: MarketType | None + SpotOptions: SpotMarketOptions | None class InstanceMetadataOptionsRequest(TypedDict, total=False): - HttpTokens: Optional[HttpTokensState] - HttpPutResponseHopLimit: Optional[Integer] - HttpEndpoint: Optional[InstanceMetadataEndpointState] - HttpProtocolIpv6: Optional[InstanceMetadataProtocolState] - InstanceMetadataTags: Optional[InstanceMetadataTagsState] + HttpTokens: HttpTokensState | None + HttpPutResponseHopLimit: Integer | None + HttpEndpoint: InstanceMetadataEndpointState | None + HttpProtocolIpv6: InstanceMetadataProtocolState | None + InstanceMetadataTags: InstanceMetadataTagsState | None class InstanceMonitoring(TypedDict, total=False): - InstanceId: Optional[String] - Monitoring: Optional[Monitoring] + InstanceId: String | None + Monitoring: Monitoring | None -InstanceMonitoringList = List[InstanceMonitoring] +InstanceMonitoringList = list[InstanceMonitoring] class InstanceNetworkPerformanceOptionsRequest(TypedDict, total=False): - BandwidthWeighting: Optional[InstanceBandwidthWeighting] + BandwidthWeighting: InstanceBandwidthWeighting | None + + +class InstanceSecondaryInterfacePrivateIpAddressRequest(TypedDict, total=False): + PrivateIpAddress: String + + +InstanceSecondaryInterfacePrivateIpAddressListRequest = list[ + InstanceSecondaryInterfacePrivateIpAddressRequest +] + + +class InstanceSecondaryInterfaceSpecificationRequest(TypedDict, total=False): + DeleteOnTermination: Boolean | None + DeviceIndex: Integer | None + PrivateIpAddresses: InstanceSecondaryInterfacePrivateIpAddressListRequest | None + PrivateIpAddressCount: Integer | None + SecondarySubnetId: SecondarySubnetId | None + InterfaceType: SecondaryInterfaceType | None + NetworkCardIndex: Integer | None + + +InstanceSecondaryInterfaceSpecificationListRequest = list[ + InstanceSecondaryInterfaceSpecificationRequest +] class InstanceStateChange(TypedDict, total=False): - InstanceId: Optional[String] - CurrentState: Optional[InstanceState] - PreviousState: Optional[InstanceState] + InstanceId: String | None + CurrentState: InstanceState | None + PreviousState: InstanceState | None -InstanceStateChangeList = List[InstanceStateChange] +InstanceStateChangeList = list[InstanceStateChange] class IpamCidrAuthorizationContext(TypedDict, total=False): - Message: Optional[String] - Signature: Optional[String] + Message: String | None + Signature: String | None + + +class IpamPolicyAllocationRuleRequest(TypedDict, total=False): + SourceIpamPoolId: IpamPoolId | None + + +IpamPolicyAllocationRuleListRequest = list[IpamPolicyAllocationRuleRequest] class KeyPair(TypedDict, total=False): - KeyPairId: Optional[String] - Tags: Optional[TagList] - KeyName: Optional[String] - KeyFingerprint: Optional[String] - KeyMaterial: Optional[SensitiveUserData] + KeyPairId: String | None + Tags: TagList | None + KeyName: String | None + KeyFingerprint: String | None + KeyMaterial: SensitiveUserData | None class LaunchPermissionModifications(TypedDict, total=False): - Add: Optional[LaunchPermissionList] - Remove: Optional[LaunchPermissionList] + Add: LaunchPermissionList | None + Remove: LaunchPermissionList | None class LaunchTemplateSpecification(TypedDict, total=False): - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[String] - Version: Optional[String] + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: String | None + Version: String | None class LicenseConfigurationRequest(TypedDict, total=False): - LicenseConfigurationArn: Optional[String] + LicenseConfigurationArn: String | None -LicenseSpecificationListRequest = List[LicenseConfigurationRequest] +LicenseSpecificationListRequest = list[LicenseConfigurationRequest] class ListImagesInRecycleBinRequest(ServiceRequest): - ImageIds: Optional[ImageIdStringList] - NextToken: Optional[String] - MaxResults: Optional[ListImagesInRecycleBinMaxResults] - DryRun: Optional[Boolean] + ImageIds: ImageIdStringList | None + NextToken: String | None + MaxResults: ListImagesInRecycleBinMaxResults | None + DryRun: Boolean | None class ListImagesInRecycleBinResult(TypedDict, total=False): - Images: Optional[ImageRecycleBinInfoList] - NextToken: Optional[String] + Images: ImageRecycleBinInfoList | None + NextToken: String | None class ListSnapshotsInRecycleBinRequest(ServiceRequest): - MaxResults: Optional[ListSnapshotsInRecycleBinMaxResults] - NextToken: Optional[String] - SnapshotIds: Optional[SnapshotIdStringList] - DryRun: Optional[Boolean] + MaxResults: ListSnapshotsInRecycleBinMaxResults | None + NextToken: String | None + SnapshotIds: SnapshotIdStringList | None + DryRun: Boolean | None class SnapshotRecycleBinInfo(TypedDict, total=False): - SnapshotId: Optional[String] - RecycleBinEnterTime: Optional[MillisecondDateTime] - RecycleBinExitTime: Optional[MillisecondDateTime] - Description: Optional[String] - VolumeId: Optional[String] + SnapshotId: String | None + RecycleBinEnterTime: MillisecondDateTime | None + RecycleBinExitTime: MillisecondDateTime | None + Description: String | None + VolumeId: String | None -SnapshotRecycleBinInfoList = List[SnapshotRecycleBinInfo] +SnapshotRecycleBinInfoList = list[SnapshotRecycleBinInfo] class ListSnapshotsInRecycleBinResult(TypedDict, total=False): - Snapshots: Optional[SnapshotRecycleBinInfoList] - NextToken: Optional[String] + Snapshots: SnapshotRecycleBinInfoList | None + NextToken: String | None + + +class ListVolumesInRecycleBinRequest(ServiceRequest): + VolumeIds: VolumeIdStringList | None + DryRun: Boolean | None + MaxResults: Integer | None + NextToken: String | None + + +class VolumeRecycleBinInfo(TypedDict, total=False): + VolumeId: VolumeId | None + VolumeType: VolumeType | None + State: VolumeState | None + Size: Integer | None + Iops: Integer | None + Throughput: Integer | None + OutpostArn: String | None + AvailabilityZone: String | None + AvailabilityZoneId: String | None + SourceVolumeId: String | None + SnapshotId: String | None + Operator: OperatorResponse | None + CreateTime: DateTime | None + RecycleBinEnterTime: MillisecondDateTime | None + RecycleBinExitTime: MillisecondDateTime | None + + +VolumeRecycleBinInfoList = list[VolumeRecycleBinInfo] + + +class ListVolumesInRecycleBinResult(TypedDict, total=False): + Volumes: VolumeRecycleBinInfoList | None + NextToken: String | None class LoadPermissionRequest(TypedDict, total=False): - Group: Optional[PermissionGroup] - UserId: Optional[String] + Group: PermissionGroup | None + UserId: String | None -LoadPermissionListRequest = List[LoadPermissionRequest] +LoadPermissionListRequest = list[LoadPermissionRequest] class LoadPermissionModifications(TypedDict, total=False): - Add: Optional[LoadPermissionListRequest] - Remove: Optional[LoadPermissionListRequest] + Add: LoadPermissionListRequest | None + Remove: LoadPermissionListRequest | None -LocalGatewayRouteList = List[LocalGatewayRoute] +LocalGatewayRouteList = list[LocalGatewayRoute] class LockSnapshotRequest(ServiceRequest): SnapshotId: SnapshotId - DryRun: Optional[Boolean] + DryRun: Boolean | None LockMode: LockMode - CoolOffPeriod: Optional[CoolOffPeriodRequestHours] - LockDuration: Optional[RetentionPeriodRequestDays] - ExpirationDate: Optional[MillisecondDateTime] + CoolOffPeriod: CoolOffPeriodRequestHours | None + LockDuration: RetentionPeriodRequestDays | None + ExpirationDate: MillisecondDateTime | None class LockSnapshotResult(TypedDict, total=False): - SnapshotId: Optional[String] - LockState: Optional[LockState] - LockDuration: Optional[RetentionPeriodResponseDays] - CoolOffPeriod: Optional[CoolOffPeriodResponseHours] - CoolOffPeriodExpiresOn: Optional[MillisecondDateTime] - LockCreatedOn: Optional[MillisecondDateTime] - LockExpiresOn: Optional[MillisecondDateTime] - LockDurationStartTime: Optional[MillisecondDateTime] + SnapshotId: String | None + LockState: LockState | None + LockDuration: RetentionPeriodResponseDays | None + CoolOffPeriod: CoolOffPeriodResponseHours | None + CoolOffPeriodExpiresOn: MillisecondDateTime | None + LockCreatedOn: MillisecondDateTime | None + LockExpiresOn: MillisecondDateTime | None + LockDurationStartTime: MillisecondDateTime | None class ModifyAddressAttributeRequest(ServiceRequest): AllocationId: AllocationId - DomainName: Optional[String] - DryRun: Optional[Boolean] + DomainName: String | None + DryRun: Boolean | None class ModifyAddressAttributeResult(TypedDict, total=False): - Address: Optional[AddressAttribute] + Address: AddressAttribute | None class ModifyAvailabilityZoneGroupRequest(ServiceRequest): GroupName: String OptInStatus: ModifyAvailabilityZoneOptInStatus - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyAvailabilityZoneGroupResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyCapacityReservationFleetRequest(ServiceRequest): CapacityReservationFleetId: CapacityReservationFleetId - TotalTargetCapacity: Optional[Integer] - EndDate: Optional[MillisecondDateTime] - DryRun: Optional[Boolean] - RemoveEndDate: Optional[Boolean] + TotalTargetCapacity: Integer | None + EndDate: MillisecondDateTime | None + DryRun: Boolean | None + RemoveEndDate: Boolean | None class ModifyCapacityReservationFleetResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyCapacityReservationRequest(ServiceRequest): CapacityReservationId: CapacityReservationId - InstanceCount: Optional[Integer] - EndDate: Optional[DateTime] - EndDateType: Optional[EndDateType] - Accept: Optional[Boolean] - DryRun: Optional[Boolean] - AdditionalInfo: Optional[String] - InstanceMatchCriteria: Optional[InstanceMatchCriteria] + InstanceCount: Integer | None + EndDate: DateTime | None + EndDateType: EndDateType | None + Accept: Boolean | None + DryRun: Boolean | None + AdditionalInfo: String | None + InstanceMatchCriteria: InstanceMatchCriteria | None class ModifyCapacityReservationResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyClientVpnEndpointRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - ServerCertificateArn: Optional[String] - ConnectionLogOptions: Optional[ConnectionLogOptions] - DnsServers: Optional[DnsServersOptionsModifyStructure] - VpnPort: Optional[Integer] - Description: Optional[String] - SplitTunnel: Optional[Boolean] - DryRun: Optional[Boolean] - SecurityGroupIds: Optional[ClientVpnSecurityGroupIdSet] - VpcId: Optional[VpcId] - SelfServicePortal: Optional[SelfServicePortal] - ClientConnectOptions: Optional[ClientConnectOptions] - SessionTimeoutHours: Optional[Integer] - ClientLoginBannerOptions: Optional[ClientLoginBannerOptions] - ClientRouteEnforcementOptions: Optional[ClientRouteEnforcementOptions] - DisconnectOnSessionTimeout: Optional[Boolean] + ServerCertificateArn: String | None + ConnectionLogOptions: ConnectionLogOptions | None + DnsServers: DnsServersOptionsModifyStructure | None + VpnPort: Integer | None + Description: String | None + SplitTunnel: Boolean | None + DryRun: Boolean | None + SecurityGroupIds: ClientVpnSecurityGroupIdSet | None + VpcId: VpcId | None + SelfServicePortal: SelfServicePortal | None + ClientConnectOptions: ClientConnectOptions | None + SessionTimeoutHours: Integer | None + ClientLoginBannerOptions: ClientLoginBannerOptions | None + ClientRouteEnforcementOptions: ClientRouteEnforcementOptions | None + DisconnectOnSessionTimeout: Boolean | None class ModifyClientVpnEndpointResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyDefaultCreditSpecificationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceFamily: UnlimitedSupportedInstanceFamily CpuCredits: String class ModifyDefaultCreditSpecificationResult(TypedDict, total=False): - InstanceFamilyCreditSpecification: Optional[InstanceFamilyCreditSpecification] + InstanceFamilyCreditSpecification: InstanceFamilyCreditSpecification | None class ModifyEbsDefaultKmsKeyIdRequest(ServiceRequest): KmsKeyId: KmsKeyId - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyEbsDefaultKmsKeyIdResult(TypedDict, total=False): - KmsKeyId: Optional[String] + KmsKeyId: String | None class ModifyFleetRequest(ServiceRequest): - DryRun: Optional[Boolean] - ExcessCapacityTerminationPolicy: Optional[FleetExcessCapacityTerminationPolicy] - LaunchTemplateConfigs: Optional[FleetLaunchTemplateConfigListRequest] + DryRun: Boolean | None + ExcessCapacityTerminationPolicy: FleetExcessCapacityTerminationPolicy | None + LaunchTemplateConfigs: FleetLaunchTemplateConfigListRequest | None FleetId: FleetId - TargetCapacitySpecification: Optional[TargetCapacitySpecificationRequest] - Context: Optional[String] + TargetCapacitySpecification: TargetCapacitySpecificationRequest | None + Context: String | None class ModifyFleetResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None -ProductCodeStringList = List[String] -UserGroupStringList = List[String] -UserIdStringList = List[String] +ProductCodeStringList = list[String] +UserGroupStringList = list[String] +UserIdStringList = list[String] class ModifyFpgaImageAttributeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None FpgaImageId: FpgaImageId - Attribute: Optional[FpgaImageAttributeName] - OperationType: Optional[OperationType] - UserIds: Optional[UserIdStringList] - UserGroups: Optional[UserGroupStringList] - ProductCodes: Optional[ProductCodeStringList] - LoadPermission: Optional[LoadPermissionModifications] - Description: Optional[String] - Name: Optional[String] + Attribute: FpgaImageAttributeName | None + OperationType: OperationType | None + UserIds: UserIdStringList | None + UserGroups: UserGroupStringList | None + ProductCodes: ProductCodeStringList | None + LoadPermission: LoadPermissionModifications | None + Description: String | None + Name: String | None class ModifyFpgaImageAttributeResult(TypedDict, total=False): - FpgaImageAttribute: Optional[FpgaImageAttribute] + FpgaImageAttribute: FpgaImageAttribute | None class ModifyHostsRequest(ServiceRequest): - HostRecovery: Optional[HostRecovery] - InstanceType: Optional[String] - InstanceFamily: Optional[String] - HostMaintenance: Optional[HostMaintenance] + HostRecovery: HostRecovery | None + InstanceType: String | None + InstanceFamily: String | None + HostMaintenance: HostMaintenance | None HostIds: RequestHostIdList - AutoPlacement: Optional[AutoPlacement] + AutoPlacement: AutoPlacement | None -UnsuccessfulItemList = List[UnsuccessfulItem] +UnsuccessfulItemList = list[UnsuccessfulItem] class ModifyHostsResult(TypedDict, total=False): - Successful: Optional[ResponseHostIdList] - Unsuccessful: Optional[UnsuccessfulItemList] + Successful: ResponseHostIdList | None + Unsuccessful: UnsuccessfulItemList | None class ModifyIdFormatRequest(ServiceRequest): @@ -18679,1129 +21103,1212 @@ class ModifyIdentityIdFormatRequest(ServiceRequest): PrincipalArn: String -OrganizationalUnitArnStringList = List[String] -OrganizationArnStringList = List[String] +OrganizationalUnitArnStringList = list[String] +OrganizationArnStringList = list[String] class ModifyImageAttributeRequest(ServiceRequest): - Attribute: Optional[String] - Description: Optional[AttributeValue] + Attribute: String | None + Description: AttributeValue | None ImageId: ImageId - LaunchPermission: Optional[LaunchPermissionModifications] - OperationType: Optional[OperationType] - ProductCodes: Optional[ProductCodeStringList] - UserGroups: Optional[UserGroupStringList] - UserIds: Optional[UserIdStringList] - Value: Optional[String] - OrganizationArns: Optional[OrganizationArnStringList] - OrganizationalUnitArns: Optional[OrganizationalUnitArnStringList] - ImdsSupport: Optional[AttributeValue] - DryRun: Optional[Boolean] + LaunchPermission: LaunchPermissionModifications | None + OperationType: OperationType | None + ProductCodes: ProductCodeStringList | None + UserGroups: UserGroupStringList | None + UserIds: UserIdStringList | None + Value: String | None + OrganizationArns: OrganizationArnStringList | None + OrganizationalUnitArns: OrganizationalUnitArnStringList | None + ImdsSupport: AttributeValue | None + DryRun: Boolean | None class ModifyInstanceAttributeRequest(ServiceRequest): - SourceDestCheck: Optional[AttributeBooleanValue] - DisableApiStop: Optional[AttributeBooleanValue] - DryRun: Optional[Boolean] + SourceDestCheck: AttributeBooleanValue | None + DisableApiStop: AttributeBooleanValue | None + DryRun: Boolean | None InstanceId: InstanceId - Attribute: Optional[InstanceAttributeName] - Value: Optional[String] - BlockDeviceMappings: Optional[InstanceBlockDeviceMappingSpecificationList] - DisableApiTermination: Optional[AttributeBooleanValue] - InstanceType: Optional[AttributeValue] - Kernel: Optional[AttributeValue] - Ramdisk: Optional[AttributeValue] - UserData: Optional[BlobAttributeValue] - InstanceInitiatedShutdownBehavior: Optional[AttributeValue] - Groups: Optional[GroupIdStringList] - EbsOptimized: Optional[AttributeBooleanValue] - SriovNetSupport: Optional[AttributeValue] - EnaSupport: Optional[AttributeBooleanValue] + Attribute: InstanceAttributeName | None + Value: String | None + BlockDeviceMappings: InstanceBlockDeviceMappingSpecificationList | None + DisableApiTermination: AttributeBooleanValue | None + InstanceType: AttributeValue | None + Kernel: AttributeValue | None + Ramdisk: AttributeValue | None + UserData: BlobAttributeValue | None + InstanceInitiatedShutdownBehavior: AttributeValue | None + Groups: GroupIdStringList | None + EbsOptimized: AttributeBooleanValue | None + SriovNetSupport: AttributeValue | None + EnaSupport: AttributeBooleanValue | None class ModifyInstanceCapacityReservationAttributesRequest(ServiceRequest): InstanceId: InstanceId CapacityReservationSpecification: CapacityReservationSpecification - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyInstanceCapacityReservationAttributesResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None + + +class ModifyInstanceConnectEndpointRequest(ServiceRequest): + DryRun: Boolean | None + InstanceConnectEndpointId: InstanceConnectEndpointId + IpAddressType: IpAddressType | None + SecurityGroupIds: SecurityGroupIdStringListRequest | None + PreserveClientIp: Boolean | None + + +class ModifyInstanceConnectEndpointResult(TypedDict, total=False): + Return: Boolean | None class ModifyInstanceCpuOptionsRequest(ServiceRequest): InstanceId: InstanceId - CoreCount: Integer - ThreadsPerCore: Integer - DryRun: Optional[Boolean] + CoreCount: Integer | None + ThreadsPerCore: Integer | None + NestedVirtualization: NestedVirtualizationSpecification | None + DryRun: Boolean | None class ModifyInstanceCpuOptionsResult(TypedDict, total=False): - InstanceId: Optional[InstanceId] - CoreCount: Optional[Integer] - ThreadsPerCore: Optional[Integer] + InstanceId: InstanceId | None + CoreCount: Integer | None + ThreadsPerCore: Integer | None + NestedVirtualization: NestedVirtualizationSpecification | None class ModifyInstanceCreditSpecificationRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] + DryRun: Boolean | None + ClientToken: String | None InstanceCreditSpecifications: InstanceCreditSpecificationListRequest class UnsuccessfulInstanceCreditSpecificationItemError(TypedDict, total=False): - Code: Optional[UnsuccessfulInstanceCreditSpecificationErrorCode] - Message: Optional[String] + Code: UnsuccessfulInstanceCreditSpecificationErrorCode | None + Message: String | None class UnsuccessfulInstanceCreditSpecificationItem(TypedDict, total=False): - InstanceId: Optional[String] - Error: Optional[UnsuccessfulInstanceCreditSpecificationItemError] + InstanceId: String | None + Error: UnsuccessfulInstanceCreditSpecificationItemError | None -UnsuccessfulInstanceCreditSpecificationSet = List[UnsuccessfulInstanceCreditSpecificationItem] +UnsuccessfulInstanceCreditSpecificationSet = list[UnsuccessfulInstanceCreditSpecificationItem] class SuccessfulInstanceCreditSpecificationItem(TypedDict, total=False): - InstanceId: Optional[String] + InstanceId: String | None -SuccessfulInstanceCreditSpecificationSet = List[SuccessfulInstanceCreditSpecificationItem] +SuccessfulInstanceCreditSpecificationSet = list[SuccessfulInstanceCreditSpecificationItem] class ModifyInstanceCreditSpecificationResult(TypedDict, total=False): - SuccessfulInstanceCreditSpecifications: Optional[SuccessfulInstanceCreditSpecificationSet] - UnsuccessfulInstanceCreditSpecifications: Optional[UnsuccessfulInstanceCreditSpecificationSet] + SuccessfulInstanceCreditSpecifications: SuccessfulInstanceCreditSpecificationSet | None + UnsuccessfulInstanceCreditSpecifications: UnsuccessfulInstanceCreditSpecificationSet | None class ModifyInstanceEventStartTimeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId InstanceEventId: String NotBefore: DateTime class ModifyInstanceEventStartTimeResult(TypedDict, total=False): - Event: Optional[InstanceStatusEvent] + Event: InstanceStatusEvent | None class ModifyInstanceEventWindowRequest(ServiceRequest): - DryRun: Optional[Boolean] - Name: Optional[String] + DryRun: Boolean | None + Name: String | None InstanceEventWindowId: InstanceEventWindowId - TimeRanges: Optional[InstanceEventWindowTimeRangeRequestSet] - CronExpression: Optional[InstanceEventWindowCronExpression] + TimeRanges: InstanceEventWindowTimeRangeRequestSet | None + CronExpression: InstanceEventWindowCronExpression | None class ModifyInstanceEventWindowResult(TypedDict, total=False): - InstanceEventWindow: Optional[InstanceEventWindow] + InstanceEventWindow: InstanceEventWindow | None class ModifyInstanceMaintenanceOptionsRequest(ServiceRequest): InstanceId: InstanceId - AutoRecovery: Optional[InstanceAutoRecoveryState] - RebootMigration: Optional[InstanceRebootMigrationState] - DryRun: Optional[Boolean] + AutoRecovery: InstanceAutoRecoveryState | None + RebootMigration: InstanceRebootMigrationState | None + DryRun: Boolean | None class ModifyInstanceMaintenanceOptionsResult(TypedDict, total=False): - InstanceId: Optional[String] - AutoRecovery: Optional[InstanceAutoRecoveryState] - RebootMigration: Optional[InstanceRebootMigrationState] + InstanceId: String | None + AutoRecovery: InstanceAutoRecoveryState | None + RebootMigration: InstanceRebootMigrationState | None class ModifyInstanceMetadataDefaultsRequest(ServiceRequest): - HttpTokens: Optional[MetadataDefaultHttpTokensState] - HttpPutResponseHopLimit: Optional[BoxedInteger] - HttpEndpoint: Optional[DefaultInstanceMetadataEndpointState] - InstanceMetadataTags: Optional[DefaultInstanceMetadataTagsState] - DryRun: Optional[Boolean] + HttpTokens: MetadataDefaultHttpTokensState | None + HttpPutResponseHopLimit: BoxedInteger | None + HttpEndpoint: DefaultInstanceMetadataEndpointState | None + InstanceMetadataTags: DefaultInstanceMetadataTagsState | None + DryRun: Boolean | None + HttpTokensEnforced: DefaultHttpTokensEnforcedState | None class ModifyInstanceMetadataDefaultsResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyInstanceMetadataOptionsRequest(ServiceRequest): InstanceId: InstanceId - HttpTokens: Optional[HttpTokensState] - HttpPutResponseHopLimit: Optional[Integer] - HttpEndpoint: Optional[InstanceMetadataEndpointState] - DryRun: Optional[Boolean] - HttpProtocolIpv6: Optional[InstanceMetadataProtocolState] - InstanceMetadataTags: Optional[InstanceMetadataTagsState] + HttpTokens: HttpTokensState | None + HttpPutResponseHopLimit: Integer | None + HttpEndpoint: InstanceMetadataEndpointState | None + DryRun: Boolean | None + HttpProtocolIpv6: InstanceMetadataProtocolState | None + InstanceMetadataTags: InstanceMetadataTagsState | None class ModifyInstanceMetadataOptionsResult(TypedDict, total=False): - InstanceId: Optional[String] - InstanceMetadataOptions: Optional[InstanceMetadataOptionsResponse] + InstanceId: String | None + InstanceMetadataOptions: InstanceMetadataOptionsResponse | None class ModifyInstanceNetworkPerformanceRequest(ServiceRequest): InstanceId: InstanceId BandwidthWeighting: InstanceBandwidthWeighting - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyInstanceNetworkPerformanceResult(TypedDict, total=False): - InstanceId: Optional[InstanceId] - BandwidthWeighting: Optional[InstanceBandwidthWeighting] + InstanceId: InstanceId | None + BandwidthWeighting: InstanceBandwidthWeighting | None class ModifyInstancePlacementRequest(ServiceRequest): - GroupName: Optional[PlacementGroupName] - PartitionNumber: Optional[Integer] - HostResourceGroupArn: Optional[String] - GroupId: Optional[PlacementGroupId] + GroupName: PlacementGroupName | None + PartitionNumber: Integer | None + HostResourceGroupArn: String | None + GroupId: PlacementGroupId | None InstanceId: InstanceId - Tenancy: Optional[HostTenancy] - Affinity: Optional[Affinity] - HostId: Optional[DedicatedHostId] + Tenancy: HostTenancy | None + Affinity: Affinity | None + HostId: DedicatedHostId | None class ModifyInstancePlacementResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None + + +class ModifyIpamPolicyAllocationRulesRequest(ServiceRequest): + DryRun: Boolean | None + IpamPolicyId: IpamPolicyId + Locale: String + ResourceType: IpamPolicyResourceType + AllocationRules: IpamPolicyAllocationRuleListRequest | None + + +class ModifyIpamPolicyAllocationRulesResult(TypedDict, total=False): + IpamPolicyDocument: IpamPolicyDocument | None class ModifyIpamPoolRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId - Description: Optional[String] - AutoImport: Optional[Boolean] - AllocationMinNetmaskLength: Optional[IpamNetmaskLength] - AllocationMaxNetmaskLength: Optional[IpamNetmaskLength] - AllocationDefaultNetmaskLength: Optional[IpamNetmaskLength] - ClearAllocationDefaultNetmaskLength: Optional[Boolean] - AddAllocationResourceTags: Optional[RequestIpamResourceTagList] - RemoveAllocationResourceTags: Optional[RequestIpamResourceTagList] + Description: String | None + AutoImport: Boolean | None + AllocationMinNetmaskLength: IpamNetmaskLength | None + AllocationMaxNetmaskLength: IpamNetmaskLength | None + AllocationDefaultNetmaskLength: IpamNetmaskLength | None + ClearAllocationDefaultNetmaskLength: Boolean | None + AddAllocationResourceTags: RequestIpamResourceTagList | None + RemoveAllocationResourceTags: RequestIpamResourceTagList | None class ModifyIpamPoolResult(TypedDict, total=False): - IpamPool: Optional[IpamPool] + IpamPool: IpamPool | None + + +class ModifyIpamPrefixListResolverRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverId: IpamPrefixListResolverId + Description: String | None + Rules: IpamPrefixListResolverRuleRequestSet | None + + +class ModifyIpamPrefixListResolverResult(TypedDict, total=False): + IpamPrefixListResolver: IpamPrefixListResolver | None + + +class ModifyIpamPrefixListResolverTargetRequest(ServiceRequest): + DryRun: Boolean | None + IpamPrefixListResolverTargetId: IpamPrefixListResolverTargetId + DesiredVersion: BoxedLong | None + TrackLatestVersion: BoxedBoolean | None + ClientToken: String | None + + +class ModifyIpamPrefixListResolverTargetResult(TypedDict, total=False): + IpamPrefixListResolverTarget: IpamPrefixListResolverTarget | None class RemoveIpamOperatingRegion(TypedDict, total=False): - RegionName: Optional[String] + RegionName: String | None -RemoveIpamOperatingRegionSet = List[RemoveIpamOperatingRegion] +RemoveIpamOperatingRegionSet = list[RemoveIpamOperatingRegion] class ModifyIpamRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamId: IpamId - Description: Optional[String] - AddOperatingRegions: Optional[AddIpamOperatingRegionSet] - RemoveOperatingRegions: Optional[RemoveIpamOperatingRegionSet] - Tier: Optional[IpamTier] - EnablePrivateGua: Optional[Boolean] - MeteredAccount: Optional[IpamMeteredAccount] + Description: String | None + AddOperatingRegions: AddIpamOperatingRegionSet | None + RemoveOperatingRegions: RemoveIpamOperatingRegionSet | None + Tier: IpamTier | None + EnablePrivateGua: Boolean | None + MeteredAccount: IpamMeteredAccount | None class ModifyIpamResourceCidrRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ResourceId: String ResourceCidr: String ResourceRegion: String CurrentIpamScopeId: IpamScopeId - DestinationIpamScopeId: Optional[IpamScopeId] + DestinationIpamScopeId: IpamScopeId | None Monitored: Boolean class ModifyIpamResourceCidrResult(TypedDict, total=False): - IpamResourceCidr: Optional[IpamResourceCidr] + IpamResourceCidr: IpamResourceCidr | None class RemoveIpamOrganizationalUnitExclusion(TypedDict, total=False): - OrganizationsEntityPath: Optional[String] + OrganizationsEntityPath: String | None -RemoveIpamOrganizationalUnitExclusionSet = List[RemoveIpamOrganizationalUnitExclusion] +RemoveIpamOrganizationalUnitExclusionSet = list[RemoveIpamOrganizationalUnitExclusion] class ModifyIpamResourceDiscoveryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamResourceDiscoveryId: IpamResourceDiscoveryId - Description: Optional[String] - AddOperatingRegions: Optional[AddIpamOperatingRegionSet] - RemoveOperatingRegions: Optional[RemoveIpamOperatingRegionSet] - AddOrganizationalUnitExclusions: Optional[AddIpamOrganizationalUnitExclusionSet] - RemoveOrganizationalUnitExclusions: Optional[RemoveIpamOrganizationalUnitExclusionSet] + Description: String | None + AddOperatingRegions: AddIpamOperatingRegionSet | None + RemoveOperatingRegions: RemoveIpamOperatingRegionSet | None + AddOrganizationalUnitExclusions: AddIpamOrganizationalUnitExclusionSet | None + RemoveOrganizationalUnitExclusions: RemoveIpamOrganizationalUnitExclusionSet | None class ModifyIpamResourceDiscoveryResult(TypedDict, total=False): - IpamResourceDiscovery: Optional[IpamResourceDiscovery] + IpamResourceDiscovery: IpamResourceDiscovery | None class ModifyIpamResult(TypedDict, total=False): - Ipam: Optional[Ipam] + Ipam: Ipam | None class ModifyIpamScopeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamScopeId: IpamScopeId - Description: Optional[String] + Description: String | None + ExternalAuthorityConfiguration: ExternalAuthorityConfiguration | None + RemoveExternalAuthorityConfiguration: Boolean | None class ModifyIpamScopeResult(TypedDict, total=False): - IpamScope: Optional[IpamScope] + IpamScope: IpamScope | None class ModifyLaunchTemplateRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] - LaunchTemplateId: Optional[LaunchTemplateId] - LaunchTemplateName: Optional[LaunchTemplateName] - DefaultVersion: Optional[String] + DryRun: Boolean | None + ClientToken: String | None + LaunchTemplateId: LaunchTemplateId | None + LaunchTemplateName: LaunchTemplateName | None + DefaultVersion: String | None class ModifyLaunchTemplateResult(TypedDict, total=False): - LaunchTemplate: Optional[LaunchTemplate] + LaunchTemplate: LaunchTemplate | None class ModifyLocalGatewayRouteRequest(ServiceRequest): - DestinationCidrBlock: Optional[String] + DestinationCidrBlock: String | None LocalGatewayRouteTableId: LocalGatewayRoutetableId - LocalGatewayVirtualInterfaceGroupId: Optional[LocalGatewayVirtualInterfaceGroupId] - NetworkInterfaceId: Optional[NetworkInterfaceId] - DryRun: Optional[Boolean] - DestinationPrefixListId: Optional[PrefixListResourceId] + LocalGatewayVirtualInterfaceGroupId: LocalGatewayVirtualInterfaceGroupId | None + NetworkInterfaceId: NetworkInterfaceId | None + DryRun: Boolean | None + DestinationPrefixListId: PrefixListResourceId | None class ModifyLocalGatewayRouteResult(TypedDict, total=False): - Route: Optional[LocalGatewayRoute] + Route: LocalGatewayRoute | None class RemovePrefixListEntry(TypedDict, total=False): Cidr: String -RemovePrefixListEntries = List[RemovePrefixListEntry] +RemovePrefixListEntries = list[RemovePrefixListEntry] class ModifyManagedPrefixListRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PrefixListId: PrefixListResourceId - CurrentVersion: Optional[Long] - PrefixListName: Optional[String] - AddEntries: Optional[AddPrefixListEntries] - RemoveEntries: Optional[RemovePrefixListEntries] - MaxEntries: Optional[Integer] + CurrentVersion: Long | None + PrefixListName: String | None + AddEntries: AddPrefixListEntries | None + RemoveEntries: RemovePrefixListEntries | None + MaxEntries: Integer | None + IpamPrefixListResolverSyncEnabled: BoxedBoolean | None class ModifyManagedPrefixListResult(TypedDict, total=False): - PrefixList: Optional[ManagedPrefixList] + PrefixList: ManagedPrefixList | None class NetworkInterfaceAttachmentChanges(TypedDict, total=False): - DefaultEnaQueueCount: Optional[Boolean] - EnaQueueCount: Optional[Integer] - AttachmentId: Optional[NetworkInterfaceAttachmentId] - DeleteOnTermination: Optional[Boolean] + DefaultEnaQueueCount: Boolean | None + EnaQueueCount: Integer | None + AttachmentId: NetworkInterfaceAttachmentId | None + DeleteOnTermination: Boolean | None -SubnetIdList = List[SubnetId] +SubnetIdList = list[SubnetId] class ModifyNetworkInterfaceAttributeRequest(ServiceRequest): - EnaSrdSpecification: Optional[EnaSrdSpecification] - EnablePrimaryIpv6: Optional[Boolean] - ConnectionTrackingSpecification: Optional[ConnectionTrackingSpecificationRequest] - AssociatePublicIpAddress: Optional[Boolean] - AssociatedSubnetIds: Optional[SubnetIdList] - DryRun: Optional[Boolean] + EnaSrdSpecification: EnaSrdSpecification | None + EnablePrimaryIpv6: Boolean | None + ConnectionTrackingSpecification: ConnectionTrackingSpecificationRequest | None + AssociatePublicIpAddress: Boolean | None + AssociatedSubnetIds: SubnetIdList | None + DryRun: Boolean | None NetworkInterfaceId: NetworkInterfaceId - Description: Optional[AttributeValue] - SourceDestCheck: Optional[AttributeBooleanValue] - Groups: Optional[SecurityGroupIdStringList] - Attachment: Optional[NetworkInterfaceAttachmentChanges] + Description: AttributeValue | None + SourceDestCheck: AttributeBooleanValue | None + Groups: SecurityGroupIdStringList | None + Attachment: NetworkInterfaceAttachmentChanges | None class ModifyPrivateDnsNameOptionsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId - PrivateDnsHostnameType: Optional[HostnameType] - EnableResourceNameDnsARecord: Optional[Boolean] - EnableResourceNameDnsAAAARecord: Optional[Boolean] + PrivateDnsHostnameType: HostnameType | None + EnableResourceNameDnsARecord: Boolean | None + EnableResourceNameDnsAAAARecord: Boolean | None class ModifyPrivateDnsNameOptionsResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyPublicIpDnsNameOptionsRequest(ServiceRequest): NetworkInterfaceId: NetworkInterfaceId HostnameType: PublicIpDnsOption - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyPublicIpDnsNameOptionsResult(TypedDict, total=False): - Successful: Optional[Boolean] + Successful: Boolean | None -ReservedInstancesConfigurationList = List[ReservedInstancesConfiguration] +ReservedInstancesConfigurationList = list[ReservedInstancesConfiguration] class ModifyReservedInstancesRequest(ServiceRequest): ReservedInstancesIds: ReservedInstancesIdStringList - ClientToken: Optional[String] + ClientToken: String | None TargetConfigurations: ReservedInstancesConfigurationList class ModifyReservedInstancesResult(TypedDict, total=False): - ReservedInstancesModificationId: Optional[String] + ReservedInstancesModificationId: String | None class ModifyRouteServerRequest(ServiceRequest): RouteServerId: RouteServerId - PersistRoutes: Optional[RouteServerPersistRoutesAction] - PersistRoutesDuration: Optional[BoxedLong] - SnsNotificationsEnabled: Optional[Boolean] - DryRun: Optional[Boolean] + PersistRoutes: RouteServerPersistRoutesAction | None + PersistRoutesDuration: BoxedLong | None + SnsNotificationsEnabled: Boolean | None + DryRun: Boolean | None class ModifyRouteServerResult(TypedDict, total=False): - RouteServer: Optional[RouteServer] + RouteServer: RouteServer | None class SecurityGroupRuleRequest(TypedDict, total=False): - IpProtocol: Optional[String] - FromPort: Optional[Integer] - ToPort: Optional[Integer] - CidrIpv4: Optional[String] - CidrIpv6: Optional[String] - PrefixListId: Optional[PrefixListResourceId] - ReferencedGroupId: Optional[SecurityGroupId] - Description: Optional[String] + IpProtocol: String | None + FromPort: Integer | None + ToPort: Integer | None + CidrIpv4: String | None + CidrIpv6: String | None + PrefixListId: PrefixListResourceId | None + ReferencedGroupId: SecurityGroupId | None + Description: String | None class SecurityGroupRuleUpdate(TypedDict, total=False): SecurityGroupRuleId: SecurityGroupRuleId - SecurityGroupRule: Optional[SecurityGroupRuleRequest] + SecurityGroupRule: SecurityGroupRuleRequest | None -SecurityGroupRuleUpdateList = List[SecurityGroupRuleUpdate] +SecurityGroupRuleUpdateList = list[SecurityGroupRuleUpdate] class ModifySecurityGroupRulesRequest(ServiceRequest): GroupId: SecurityGroupId SecurityGroupRules: SecurityGroupRuleUpdateList - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifySecurityGroupRulesResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifySnapshotAttributeRequest(ServiceRequest): - Attribute: Optional[SnapshotAttributeName] - CreateVolumePermission: Optional[CreateVolumePermissionModifications] - GroupNames: Optional[GroupNameStringList] - OperationType: Optional[OperationType] + Attribute: SnapshotAttributeName | None + CreateVolumePermission: CreateVolumePermissionModifications | None + GroupNames: GroupNameStringList | None + OperationType: OperationType | None SnapshotId: SnapshotId - UserIds: Optional[UserIdStringList] - DryRun: Optional[Boolean] + UserIds: UserIdStringList | None + DryRun: Boolean | None class ModifySnapshotTierRequest(ServiceRequest): SnapshotId: SnapshotId - StorageTier: Optional[TargetStorageTier] - DryRun: Optional[Boolean] + StorageTier: TargetStorageTier | None + DryRun: Boolean | None class ModifySnapshotTierResult(TypedDict, total=False): - SnapshotId: Optional[String] - TieringStartTime: Optional[MillisecondDateTime] + SnapshotId: String | None + TieringStartTime: MillisecondDateTime | None class ModifySpotFleetRequestRequest(ServiceRequest): - LaunchTemplateConfigs: Optional[LaunchTemplateConfigList] - OnDemandTargetCapacity: Optional[Integer] - Context: Optional[String] + LaunchTemplateConfigs: LaunchTemplateConfigList | None + OnDemandTargetCapacity: Integer | None + Context: String | None SpotFleetRequestId: SpotFleetRequestId - TargetCapacity: Optional[Integer] - ExcessCapacityTerminationPolicy: Optional[ExcessCapacityTerminationPolicy] + TargetCapacity: Integer | None + ExcessCapacityTerminationPolicy: ExcessCapacityTerminationPolicy | None class ModifySpotFleetRequestResponse(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifySubnetAttributeRequest(ServiceRequest): - AssignIpv6AddressOnCreation: Optional[AttributeBooleanValue] - MapPublicIpOnLaunch: Optional[AttributeBooleanValue] + AssignIpv6AddressOnCreation: AttributeBooleanValue | None + MapPublicIpOnLaunch: AttributeBooleanValue | None SubnetId: SubnetId - MapCustomerOwnedIpOnLaunch: Optional[AttributeBooleanValue] - CustomerOwnedIpv4Pool: Optional[CoipPoolId] - EnableDns64: Optional[AttributeBooleanValue] - PrivateDnsHostnameTypeOnLaunch: Optional[HostnameType] - EnableResourceNameDnsARecordOnLaunch: Optional[AttributeBooleanValue] - EnableResourceNameDnsAAAARecordOnLaunch: Optional[AttributeBooleanValue] - EnableLniAtDeviceIndex: Optional[Integer] - DisableLniAtDeviceIndex: Optional[AttributeBooleanValue] + MapCustomerOwnedIpOnLaunch: AttributeBooleanValue | None + CustomerOwnedIpv4Pool: CoipPoolId | None + EnableDns64: AttributeBooleanValue | None + PrivateDnsHostnameTypeOnLaunch: HostnameType | None + EnableResourceNameDnsARecordOnLaunch: AttributeBooleanValue | None + EnableResourceNameDnsAAAARecordOnLaunch: AttributeBooleanValue | None + EnableLniAtDeviceIndex: Integer | None + DisableLniAtDeviceIndex: AttributeBooleanValue | None class ModifyTrafficMirrorFilterNetworkServicesRequest(ServiceRequest): TrafficMirrorFilterId: TrafficMirrorFilterId - AddNetworkServices: Optional[TrafficMirrorNetworkServiceList] - RemoveNetworkServices: Optional[TrafficMirrorNetworkServiceList] - DryRun: Optional[Boolean] + AddNetworkServices: TrafficMirrorNetworkServiceList | None + RemoveNetworkServices: TrafficMirrorNetworkServiceList | None + DryRun: Boolean | None class ModifyTrafficMirrorFilterNetworkServicesResult(TypedDict, total=False): - TrafficMirrorFilter: Optional[TrafficMirrorFilter] + TrafficMirrorFilter: TrafficMirrorFilter | None -TrafficMirrorFilterRuleFieldList = List[TrafficMirrorFilterRuleField] +TrafficMirrorFilterRuleFieldList = list[TrafficMirrorFilterRuleField] class ModifyTrafficMirrorFilterRuleRequest(ServiceRequest): TrafficMirrorFilterRuleId: TrafficMirrorFilterRuleIdWithResolver - TrafficDirection: Optional[TrafficDirection] - RuleNumber: Optional[Integer] - RuleAction: Optional[TrafficMirrorRuleAction] - DestinationPortRange: Optional[TrafficMirrorPortRangeRequest] - SourcePortRange: Optional[TrafficMirrorPortRangeRequest] - Protocol: Optional[Integer] - DestinationCidrBlock: Optional[String] - SourceCidrBlock: Optional[String] - Description: Optional[String] - RemoveFields: Optional[TrafficMirrorFilterRuleFieldList] - DryRun: Optional[Boolean] + TrafficDirection: TrafficDirection | None + RuleNumber: Integer | None + RuleAction: TrafficMirrorRuleAction | None + DestinationPortRange: TrafficMirrorPortRangeRequest | None + SourcePortRange: TrafficMirrorPortRangeRequest | None + Protocol: Integer | None + DestinationCidrBlock: String | None + SourceCidrBlock: String | None + Description: String | None + RemoveFields: TrafficMirrorFilterRuleFieldList | None + DryRun: Boolean | None class ModifyTrafficMirrorFilterRuleResult(TypedDict, total=False): - TrafficMirrorFilterRule: Optional[TrafficMirrorFilterRule] + TrafficMirrorFilterRule: TrafficMirrorFilterRule | None -TrafficMirrorSessionFieldList = List[TrafficMirrorSessionField] +TrafficMirrorSessionFieldList = list[TrafficMirrorSessionField] class ModifyTrafficMirrorSessionRequest(ServiceRequest): TrafficMirrorSessionId: TrafficMirrorSessionId - TrafficMirrorTargetId: Optional[TrafficMirrorTargetId] - TrafficMirrorFilterId: Optional[TrafficMirrorFilterId] - PacketLength: Optional[Integer] - SessionNumber: Optional[Integer] - VirtualNetworkId: Optional[Integer] - Description: Optional[String] - RemoveFields: Optional[TrafficMirrorSessionFieldList] - DryRun: Optional[Boolean] + TrafficMirrorTargetId: TrafficMirrorTargetId | None + TrafficMirrorFilterId: TrafficMirrorFilterId | None + PacketLength: Integer | None + SessionNumber: Integer | None + VirtualNetworkId: Integer | None + Description: String | None + RemoveFields: TrafficMirrorSessionFieldList | None + DryRun: Boolean | None class ModifyTrafficMirrorSessionResult(TypedDict, total=False): - TrafficMirrorSession: Optional[TrafficMirrorSession] + TrafficMirrorSession: TrafficMirrorSession | None + + +class ModifyTransitGatewayMeteringPolicyRequest(ServiceRequest): + TransitGatewayMeteringPolicyId: TransitGatewayMeteringPolicyId + AddMiddleboxAttachmentIds: TransitGatewayAttachmentIdStringList | None + RemoveMiddleboxAttachmentIds: TransitGatewayAttachmentIdStringList | None + DryRun: Boolean | None + + +class ModifyTransitGatewayMeteringPolicyResult(TypedDict, total=False): + TransitGatewayMeteringPolicy: TransitGatewayMeteringPolicy | None class ModifyTransitGatewayOptions(TypedDict, total=False): - AddTransitGatewayCidrBlocks: Optional[TransitGatewayCidrBlockStringList] - RemoveTransitGatewayCidrBlocks: Optional[TransitGatewayCidrBlockStringList] - VpnEcmpSupport: Optional[VpnEcmpSupportValue] - DnsSupport: Optional[DnsSupportValue] - SecurityGroupReferencingSupport: Optional[SecurityGroupReferencingSupportValue] - AutoAcceptSharedAttachments: Optional[AutoAcceptSharedAttachmentsValue] - DefaultRouteTableAssociation: Optional[DefaultRouteTableAssociationValue] - AssociationDefaultRouteTableId: Optional[TransitGatewayRouteTableId] - DefaultRouteTablePropagation: Optional[DefaultRouteTablePropagationValue] - PropagationDefaultRouteTableId: Optional[TransitGatewayRouteTableId] - AmazonSideAsn: Optional[Long] + AddTransitGatewayCidrBlocks: TransitGatewayCidrBlockStringList | None + RemoveTransitGatewayCidrBlocks: TransitGatewayCidrBlockStringList | None + VpnEcmpSupport: VpnEcmpSupportValue | None + DnsSupport: DnsSupportValue | None + SecurityGroupReferencingSupport: SecurityGroupReferencingSupportValue | None + AutoAcceptSharedAttachments: AutoAcceptSharedAttachmentsValue | None + DefaultRouteTableAssociation: DefaultRouteTableAssociationValue | None + AssociationDefaultRouteTableId: TransitGatewayRouteTableId | None + DefaultRouteTablePropagation: DefaultRouteTablePropagationValue | None + PropagationDefaultRouteTableId: TransitGatewayRouteTableId | None + AmazonSideAsn: Long | None + EncryptionSupport: EncryptionSupportOptionValue | None class ModifyTransitGatewayPrefixListReferenceRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId PrefixListId: PrefixListResourceId - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - Blackhole: Optional[Boolean] - DryRun: Optional[Boolean] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + Blackhole: Boolean | None + DryRun: Boolean | None class ModifyTransitGatewayPrefixListReferenceResult(TypedDict, total=False): - TransitGatewayPrefixListReference: Optional[TransitGatewayPrefixListReference] + TransitGatewayPrefixListReference: TransitGatewayPrefixListReference | None class ModifyTransitGatewayRequest(ServiceRequest): TransitGatewayId: TransitGatewayId - Description: Optional[String] - Options: Optional[ModifyTransitGatewayOptions] - DryRun: Optional[Boolean] + Description: String | None + Options: ModifyTransitGatewayOptions | None + DryRun: Boolean | None class ModifyTransitGatewayResult(TypedDict, total=False): - TransitGateway: Optional[TransitGateway] + TransitGateway: TransitGateway | None class ModifyTransitGatewayVpcAttachmentRequestOptions(TypedDict, total=False): - DnsSupport: Optional[DnsSupportValue] - SecurityGroupReferencingSupport: Optional[SecurityGroupReferencingSupportValue] - Ipv6Support: Optional[Ipv6SupportValue] - ApplianceModeSupport: Optional[ApplianceModeSupportValue] + DnsSupport: DnsSupportValue | None + SecurityGroupReferencingSupport: SecurityGroupReferencingSupportValue | None + Ipv6Support: Ipv6SupportValue | None + ApplianceModeSupport: ApplianceModeSupportValue | None class ModifyTransitGatewayVpcAttachmentRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - AddSubnetIds: Optional[TransitGatewaySubnetIdList] - RemoveSubnetIds: Optional[TransitGatewaySubnetIdList] - Options: Optional[ModifyTransitGatewayVpcAttachmentRequestOptions] - DryRun: Optional[Boolean] + AddSubnetIds: TransitGatewaySubnetIdList | None + RemoveSubnetIds: TransitGatewaySubnetIdList | None + Options: ModifyTransitGatewayVpcAttachmentRequestOptions | None + DryRun: Boolean | None class ModifyTransitGatewayVpcAttachmentResult(TypedDict, total=False): - TransitGatewayVpcAttachment: Optional[TransitGatewayVpcAttachment] + TransitGatewayVpcAttachment: TransitGatewayVpcAttachment | None class ModifyVerifiedAccessEndpointPortRange(TypedDict, total=False): - FromPort: Optional[VerifiedAccessEndpointPortNumber] - ToPort: Optional[VerifiedAccessEndpointPortNumber] + FromPort: VerifiedAccessEndpointPortNumber | None + ToPort: VerifiedAccessEndpointPortNumber | None -ModifyVerifiedAccessEndpointPortRangeList = List[ModifyVerifiedAccessEndpointPortRange] +ModifyVerifiedAccessEndpointPortRangeList = list[ModifyVerifiedAccessEndpointPortRange] class ModifyVerifiedAccessEndpointCidrOptions(TypedDict, total=False): - PortRanges: Optional[ModifyVerifiedAccessEndpointPortRangeList] + PortRanges: ModifyVerifiedAccessEndpointPortRangeList | None class ModifyVerifiedAccessEndpointEniOptions(TypedDict, total=False): - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - PortRanges: Optional[ModifyVerifiedAccessEndpointPortRangeList] + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + PortRanges: ModifyVerifiedAccessEndpointPortRangeList | None -ModifyVerifiedAccessEndpointSubnetIdList = List[SubnetId] +ModifyVerifiedAccessEndpointSubnetIdList = list[SubnetId] class ModifyVerifiedAccessEndpointLoadBalancerOptions(TypedDict, total=False): - SubnetIds: Optional[ModifyVerifiedAccessEndpointSubnetIdList] - Protocol: Optional[VerifiedAccessEndpointProtocol] - Port: Optional[VerifiedAccessEndpointPortNumber] - PortRanges: Optional[ModifyVerifiedAccessEndpointPortRangeList] + SubnetIds: ModifyVerifiedAccessEndpointSubnetIdList | None + Protocol: VerifiedAccessEndpointProtocol | None + Port: VerifiedAccessEndpointPortNumber | None + PortRanges: ModifyVerifiedAccessEndpointPortRangeList | None class ModifyVerifiedAccessEndpointPolicyRequest(ServiceRequest): VerifiedAccessEndpointId: VerifiedAccessEndpointId - PolicyEnabled: Optional[Boolean] - PolicyDocument: Optional[String] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - SseSpecification: Optional[VerifiedAccessSseSpecificationRequest] + PolicyEnabled: Boolean | None + PolicyDocument: String | None + ClientToken: String | None + DryRun: Boolean | None + SseSpecification: VerifiedAccessSseSpecificationRequest | None class ModifyVerifiedAccessEndpointPolicyResult(TypedDict, total=False): - PolicyEnabled: Optional[Boolean] - PolicyDocument: Optional[String] - SseSpecification: Optional[VerifiedAccessSseSpecificationResponse] + PolicyEnabled: Boolean | None + PolicyDocument: String | None + SseSpecification: VerifiedAccessSseSpecificationResponse | None class ModifyVerifiedAccessEndpointRdsOptions(TypedDict, total=False): - SubnetIds: Optional[ModifyVerifiedAccessEndpointSubnetIdList] - Port: Optional[VerifiedAccessEndpointPortNumber] - RdsEndpoint: Optional[String] + SubnetIds: ModifyVerifiedAccessEndpointSubnetIdList | None + Port: VerifiedAccessEndpointPortNumber | None + RdsEndpoint: String | None class ModifyVerifiedAccessEndpointRequest(ServiceRequest): VerifiedAccessEndpointId: VerifiedAccessEndpointId - VerifiedAccessGroupId: Optional[VerifiedAccessGroupId] - LoadBalancerOptions: Optional[ModifyVerifiedAccessEndpointLoadBalancerOptions] - NetworkInterfaceOptions: Optional[ModifyVerifiedAccessEndpointEniOptions] - Description: Optional[String] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - RdsOptions: Optional[ModifyVerifiedAccessEndpointRdsOptions] - CidrOptions: Optional[ModifyVerifiedAccessEndpointCidrOptions] + VerifiedAccessGroupId: VerifiedAccessGroupId | None + LoadBalancerOptions: ModifyVerifiedAccessEndpointLoadBalancerOptions | None + NetworkInterfaceOptions: ModifyVerifiedAccessEndpointEniOptions | None + Description: String | None + ClientToken: String | None + DryRun: Boolean | None + RdsOptions: ModifyVerifiedAccessEndpointRdsOptions | None + CidrOptions: ModifyVerifiedAccessEndpointCidrOptions | None class ModifyVerifiedAccessEndpointResult(TypedDict, total=False): - VerifiedAccessEndpoint: Optional[VerifiedAccessEndpoint] + VerifiedAccessEndpoint: VerifiedAccessEndpoint | None class ModifyVerifiedAccessGroupPolicyRequest(ServiceRequest): VerifiedAccessGroupId: VerifiedAccessGroupId - PolicyEnabled: Optional[Boolean] - PolicyDocument: Optional[String] - ClientToken: Optional[String] - DryRun: Optional[Boolean] - SseSpecification: Optional[VerifiedAccessSseSpecificationRequest] + PolicyEnabled: Boolean | None + PolicyDocument: String | None + ClientToken: String | None + DryRun: Boolean | None + SseSpecification: VerifiedAccessSseSpecificationRequest | None class ModifyVerifiedAccessGroupPolicyResult(TypedDict, total=False): - PolicyEnabled: Optional[Boolean] - PolicyDocument: Optional[String] - SseSpecification: Optional[VerifiedAccessSseSpecificationResponse] + PolicyEnabled: Boolean | None + PolicyDocument: String | None + SseSpecification: VerifiedAccessSseSpecificationResponse | None class ModifyVerifiedAccessGroupRequest(ServiceRequest): VerifiedAccessGroupId: VerifiedAccessGroupId - VerifiedAccessInstanceId: Optional[VerifiedAccessInstanceId] - Description: Optional[String] - ClientToken: Optional[String] - DryRun: Optional[Boolean] + VerifiedAccessInstanceId: VerifiedAccessInstanceId | None + Description: String | None + ClientToken: String | None + DryRun: Boolean | None class ModifyVerifiedAccessGroupResult(TypedDict, total=False): - VerifiedAccessGroup: Optional[VerifiedAccessGroup] + VerifiedAccessGroup: VerifiedAccessGroup | None class VerifiedAccessLogKinesisDataFirehoseDestinationOptions(TypedDict, total=False): Enabled: Boolean - DeliveryStream: Optional[String] + DeliveryStream: String | None class VerifiedAccessLogCloudWatchLogsDestinationOptions(TypedDict, total=False): Enabled: Boolean - LogGroup: Optional[String] + LogGroup: String | None class VerifiedAccessLogS3DestinationOptions(TypedDict, total=False): Enabled: Boolean - BucketName: Optional[String] - Prefix: Optional[String] - BucketOwner: Optional[String] + BucketName: String | None + Prefix: String | None + BucketOwner: String | None class VerifiedAccessLogOptions(TypedDict, total=False): - S3: Optional[VerifiedAccessLogS3DestinationOptions] - CloudWatchLogs: Optional[VerifiedAccessLogCloudWatchLogsDestinationOptions] - KinesisDataFirehose: Optional[VerifiedAccessLogKinesisDataFirehoseDestinationOptions] - LogVersion: Optional[String] - IncludeTrustContext: Optional[Boolean] + S3: VerifiedAccessLogS3DestinationOptions | None + CloudWatchLogs: VerifiedAccessLogCloudWatchLogsDestinationOptions | None + KinesisDataFirehose: VerifiedAccessLogKinesisDataFirehoseDestinationOptions | None + LogVersion: String | None + IncludeTrustContext: Boolean | None class ModifyVerifiedAccessInstanceLoggingConfigurationRequest(ServiceRequest): VerifiedAccessInstanceId: VerifiedAccessInstanceId AccessLogs: VerifiedAccessLogOptions - DryRun: Optional[Boolean] - ClientToken: Optional[String] + DryRun: Boolean | None + ClientToken: String | None class ModifyVerifiedAccessInstanceLoggingConfigurationResult(TypedDict, total=False): - LoggingConfiguration: Optional[VerifiedAccessInstanceLoggingConfiguration] + LoggingConfiguration: VerifiedAccessInstanceLoggingConfiguration | None class ModifyVerifiedAccessInstanceRequest(ServiceRequest): VerifiedAccessInstanceId: VerifiedAccessInstanceId - Description: Optional[String] - DryRun: Optional[Boolean] - ClientToken: Optional[String] - CidrEndpointsCustomSubDomain: Optional[String] + Description: String | None + DryRun: Boolean | None + ClientToken: String | None + CidrEndpointsCustomSubDomain: String | None class ModifyVerifiedAccessInstanceResult(TypedDict, total=False): - VerifiedAccessInstance: Optional[VerifiedAccessInstance] + VerifiedAccessInstance: VerifiedAccessInstance | None class ModifyVerifiedAccessNativeApplicationOidcOptions(TypedDict, total=False): - PublicSigningKeyEndpoint: Optional[String] - Issuer: Optional[String] - AuthorizationEndpoint: Optional[String] - TokenEndpoint: Optional[String] - UserInfoEndpoint: Optional[String] - ClientId: Optional[String] - ClientSecret: Optional[ClientSecretType] - Scope: Optional[String] + PublicSigningKeyEndpoint: String | None + Issuer: String | None + AuthorizationEndpoint: String | None + TokenEndpoint: String | None + UserInfoEndpoint: String | None + ClientId: String | None + ClientSecret: ClientSecretType | None + Scope: String | None class ModifyVerifiedAccessTrustProviderDeviceOptions(TypedDict, total=False): - PublicSigningKeyUrl: Optional[String] + PublicSigningKeyUrl: String | None class ModifyVerifiedAccessTrustProviderOidcOptions(TypedDict, total=False): - Issuer: Optional[String] - AuthorizationEndpoint: Optional[String] - TokenEndpoint: Optional[String] - UserInfoEndpoint: Optional[String] - ClientId: Optional[String] - ClientSecret: Optional[ClientSecretType] - Scope: Optional[String] + Issuer: String | None + AuthorizationEndpoint: String | None + TokenEndpoint: String | None + UserInfoEndpoint: String | None + ClientId: String | None + ClientSecret: ClientSecretType | None + Scope: String | None class ModifyVerifiedAccessTrustProviderRequest(ServiceRequest): VerifiedAccessTrustProviderId: VerifiedAccessTrustProviderId - OidcOptions: Optional[ModifyVerifiedAccessTrustProviderOidcOptions] - DeviceOptions: Optional[ModifyVerifiedAccessTrustProviderDeviceOptions] - Description: Optional[String] - DryRun: Optional[Boolean] - ClientToken: Optional[String] - SseSpecification: Optional[VerifiedAccessSseSpecificationRequest] - NativeApplicationOidcOptions: Optional[ModifyVerifiedAccessNativeApplicationOidcOptions] + OidcOptions: ModifyVerifiedAccessTrustProviderOidcOptions | None + DeviceOptions: ModifyVerifiedAccessTrustProviderDeviceOptions | None + Description: String | None + DryRun: Boolean | None + ClientToken: String | None + SseSpecification: VerifiedAccessSseSpecificationRequest | None + NativeApplicationOidcOptions: ModifyVerifiedAccessNativeApplicationOidcOptions | None class ModifyVerifiedAccessTrustProviderResult(TypedDict, total=False): - VerifiedAccessTrustProvider: Optional[VerifiedAccessTrustProvider] + VerifiedAccessTrustProvider: VerifiedAccessTrustProvider | None class ModifyVolumeAttributeRequest(ServiceRequest): - AutoEnableIO: Optional[AttributeBooleanValue] + AutoEnableIO: AttributeBooleanValue | None VolumeId: VolumeId - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyVolumeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VolumeId: VolumeId - Size: Optional[Integer] - VolumeType: Optional[VolumeType] - Iops: Optional[Integer] - Throughput: Optional[Integer] - MultiAttachEnabled: Optional[Boolean] + Size: Integer | None + VolumeType: VolumeType | None + Iops: Integer | None + Throughput: Integer | None + MultiAttachEnabled: Boolean | None class ModifyVolumeResult(TypedDict, total=False): - VolumeModification: Optional[VolumeModification] + VolumeModification: VolumeModification | None class ModifyVpcAttributeRequest(ServiceRequest): - EnableDnsHostnames: Optional[AttributeBooleanValue] - EnableDnsSupport: Optional[AttributeBooleanValue] + EnableDnsHostnames: AttributeBooleanValue | None + EnableDnsSupport: AttributeBooleanValue | None VpcId: VpcId - EnableNetworkAddressUsageMetrics: Optional[AttributeBooleanValue] + EnableNetworkAddressUsageMetrics: AttributeBooleanValue | None class ModifyVpcBlockPublicAccessExclusionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ExclusionId: VpcBlockPublicAccessExclusionId InternetGatewayExclusionMode: InternetGatewayExclusionMode class ModifyVpcBlockPublicAccessExclusionResult(TypedDict, total=False): - VpcBlockPublicAccessExclusion: Optional[VpcBlockPublicAccessExclusion] + VpcBlockPublicAccessExclusion: VpcBlockPublicAccessExclusion | None class ModifyVpcBlockPublicAccessOptionsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InternetGatewayBlockMode: InternetGatewayBlockMode class ModifyVpcBlockPublicAccessOptionsResult(TypedDict, total=False): - VpcBlockPublicAccessOptions: Optional[VpcBlockPublicAccessOptions] + VpcBlockPublicAccessOptions: VpcBlockPublicAccessOptions | None + + +class ModifyVpcEncryptionControlRequest(ServiceRequest): + DryRun: Boolean | None + VpcEncryptionControlId: VpcEncryptionControlId + Mode: VpcEncryptionControlMode | None + InternetGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + EgressOnlyInternetGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + NatGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + VirtualPrivateGatewayExclusion: VpcEncryptionControlExclusionStateInput | None + VpcPeeringExclusion: VpcEncryptionControlExclusionStateInput | None + LambdaExclusion: VpcEncryptionControlExclusionStateInput | None + VpcLatticeExclusion: VpcEncryptionControlExclusionStateInput | None + ElasticFileSystemExclusion: VpcEncryptionControlExclusionStateInput | None + + +class ModifyVpcEncryptionControlResult(TypedDict, total=False): + VpcEncryptionControl: VpcEncryptionControl | None class ModifyVpcEndpointConnectionNotificationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ConnectionNotificationId: ConnectionNotificationId - ConnectionNotificationArn: Optional[String] - ConnectionEvents: Optional[ValueStringList] + ConnectionNotificationArn: String | None + ConnectionEvents: ValueStringList | None class ModifyVpcEndpointConnectionNotificationResult(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class ModifyVpcEndpointRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VpcEndpointId: VpcEndpointId - ResetPolicy: Optional[Boolean] - PolicyDocument: Optional[String] - AddRouteTableIds: Optional[VpcEndpointRouteTableIdList] - RemoveRouteTableIds: Optional[VpcEndpointRouteTableIdList] - AddSubnetIds: Optional[VpcEndpointSubnetIdList] - RemoveSubnetIds: Optional[VpcEndpointSubnetIdList] - AddSecurityGroupIds: Optional[VpcEndpointSecurityGroupIdList] - RemoveSecurityGroupIds: Optional[VpcEndpointSecurityGroupIdList] - IpAddressType: Optional[IpAddressType] - DnsOptions: Optional[DnsOptionsSpecification] - PrivateDnsEnabled: Optional[Boolean] - SubnetConfigurations: Optional[SubnetConfigurationsList] + ResetPolicy: Boolean | None + PolicyDocument: String | None + AddRouteTableIds: VpcEndpointRouteTableIdList | None + RemoveRouteTableIds: VpcEndpointRouteTableIdList | None + AddSubnetIds: VpcEndpointSubnetIdList | None + RemoveSubnetIds: VpcEndpointSubnetIdList | None + AddSecurityGroupIds: VpcEndpointSecurityGroupIdList | None + RemoveSecurityGroupIds: VpcEndpointSecurityGroupIdList | None + IpAddressType: IpAddressType | None + DnsOptions: DnsOptionsSpecification | None + PrivateDnsEnabled: Boolean | None + SubnetConfigurations: SubnetConfigurationsList | None class ModifyVpcEndpointResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyVpcEndpointServiceConfigurationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceId: VpcEndpointServiceId - PrivateDnsName: Optional[String] - RemovePrivateDnsName: Optional[Boolean] - AcceptanceRequired: Optional[Boolean] - AddNetworkLoadBalancerArns: Optional[ValueStringList] - RemoveNetworkLoadBalancerArns: Optional[ValueStringList] - AddGatewayLoadBalancerArns: Optional[ValueStringList] - RemoveGatewayLoadBalancerArns: Optional[ValueStringList] - AddSupportedIpAddressTypes: Optional[ValueStringList] - RemoveSupportedIpAddressTypes: Optional[ValueStringList] - AddSupportedRegions: Optional[ValueStringList] - RemoveSupportedRegions: Optional[ValueStringList] + PrivateDnsName: String | None + RemovePrivateDnsName: Boolean | None + AcceptanceRequired: Boolean | None + AddNetworkLoadBalancerArns: ValueStringList | None + RemoveNetworkLoadBalancerArns: ValueStringList | None + AddGatewayLoadBalancerArns: ValueStringList | None + RemoveGatewayLoadBalancerArns: ValueStringList | None + AddSupportedIpAddressTypes: ValueStringList | None + RemoveSupportedIpAddressTypes: ValueStringList | None + AddSupportedRegions: ValueStringList | None + RemoveSupportedRegions: ValueStringList | None class ModifyVpcEndpointServiceConfigurationResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ModifyVpcEndpointServicePayerResponsibilityRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceId: VpcEndpointServiceId PayerResponsibility: PayerResponsibility class ModifyVpcEndpointServicePayerResponsibilityResult(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class ModifyVpcEndpointServicePermissionsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceId: VpcEndpointServiceId - AddAllowedPrincipals: Optional[ValueStringList] - RemoveAllowedPrincipals: Optional[ValueStringList] + AddAllowedPrincipals: ValueStringList | None + RemoveAllowedPrincipals: ValueStringList | None class ModifyVpcEndpointServicePermissionsResult(TypedDict, total=False): - AddedPrincipals: Optional[AddedPrincipalSet] - ReturnValue: Optional[Boolean] + AddedPrincipals: AddedPrincipalSet | None + ReturnValue: Boolean | None class PeeringConnectionOptionsRequest(TypedDict, total=False): - AllowDnsResolutionFromRemoteVpc: Optional[Boolean] - AllowEgressFromLocalClassicLinkToRemoteVpc: Optional[Boolean] - AllowEgressFromLocalVpcToRemoteClassicLink: Optional[Boolean] + AllowDnsResolutionFromRemoteVpc: Boolean | None + AllowEgressFromLocalClassicLinkToRemoteVpc: Boolean | None + AllowEgressFromLocalVpcToRemoteClassicLink: Boolean | None class ModifyVpcPeeringConnectionOptionsRequest(ServiceRequest): - AccepterPeeringConnectionOptions: Optional[PeeringConnectionOptionsRequest] - DryRun: Optional[Boolean] - RequesterPeeringConnectionOptions: Optional[PeeringConnectionOptionsRequest] + AccepterPeeringConnectionOptions: PeeringConnectionOptionsRequest | None + DryRun: Boolean | None + RequesterPeeringConnectionOptions: PeeringConnectionOptionsRequest | None VpcPeeringConnectionId: VpcPeeringConnectionId class PeeringConnectionOptions(TypedDict, total=False): - AllowDnsResolutionFromRemoteVpc: Optional[Boolean] - AllowEgressFromLocalClassicLinkToRemoteVpc: Optional[Boolean] - AllowEgressFromLocalVpcToRemoteClassicLink: Optional[Boolean] + AllowDnsResolutionFromRemoteVpc: Boolean | None + AllowEgressFromLocalClassicLinkToRemoteVpc: Boolean | None + AllowEgressFromLocalVpcToRemoteClassicLink: Boolean | None class ModifyVpcPeeringConnectionOptionsResult(TypedDict, total=False): - AccepterPeeringConnectionOptions: Optional[PeeringConnectionOptions] - RequesterPeeringConnectionOptions: Optional[PeeringConnectionOptions] + AccepterPeeringConnectionOptions: PeeringConnectionOptions | None + RequesterPeeringConnectionOptions: PeeringConnectionOptions | None class ModifyVpcTenancyRequest(ServiceRequest): VpcId: VpcId InstanceTenancy: VpcTenancy - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyVpcTenancyResult(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class ModifyVpnConnectionOptionsRequest(ServiceRequest): VpnConnectionId: VpnConnectionId - LocalIpv4NetworkCidr: Optional[String] - RemoteIpv4NetworkCidr: Optional[String] - LocalIpv6NetworkCidr: Optional[String] - RemoteIpv6NetworkCidr: Optional[String] - DryRun: Optional[Boolean] + LocalIpv4NetworkCidr: String | None + RemoteIpv4NetworkCidr: String | None + LocalIpv6NetworkCidr: String | None + RemoteIpv6NetworkCidr: String | None + DryRun: Boolean | None class ModifyVpnConnectionOptionsResult(TypedDict, total=False): - VpnConnection: Optional[VpnConnection] + VpnConnection: VpnConnection | None class ModifyVpnConnectionRequest(ServiceRequest): VpnConnectionId: VpnConnectionId - TransitGatewayId: Optional[TransitGatewayId] - CustomerGatewayId: Optional[CustomerGatewayId] - VpnGatewayId: Optional[VpnGatewayId] - DryRun: Optional[Boolean] + TransitGatewayId: TransitGatewayId | None + CustomerGatewayId: CustomerGatewayId | None + VpnGatewayId: VpnGatewayId | None + DryRun: Boolean | None class ModifyVpnConnectionResult(TypedDict, total=False): - VpnConnection: Optional[VpnConnection] + VpnConnection: VpnConnection | None class ModifyVpnTunnelCertificateRequest(ServiceRequest): VpnConnectionId: VpnConnectionId VpnTunnelOutsideIpAddress: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class ModifyVpnTunnelCertificateResult(TypedDict, total=False): - VpnConnection: Optional[VpnConnection] + VpnConnection: VpnConnection | None class ModifyVpnTunnelOptionsSpecification(TypedDict, total=False): - TunnelInsideCidr: Optional[String] - TunnelInsideIpv6Cidr: Optional[String] - PreSharedKey: Optional[preSharedKey] - Phase1LifetimeSeconds: Optional[Integer] - Phase2LifetimeSeconds: Optional[Integer] - RekeyMarginTimeSeconds: Optional[Integer] - RekeyFuzzPercentage: Optional[Integer] - ReplayWindowSize: Optional[Integer] - DPDTimeoutSeconds: Optional[Integer] - DPDTimeoutAction: Optional[String] - Phase1EncryptionAlgorithms: Optional[Phase1EncryptionAlgorithmsRequestList] - Phase2EncryptionAlgorithms: Optional[Phase2EncryptionAlgorithmsRequestList] - Phase1IntegrityAlgorithms: Optional[Phase1IntegrityAlgorithmsRequestList] - Phase2IntegrityAlgorithms: Optional[Phase2IntegrityAlgorithmsRequestList] - Phase1DHGroupNumbers: Optional[Phase1DHGroupNumbersRequestList] - Phase2DHGroupNumbers: Optional[Phase2DHGroupNumbersRequestList] - IKEVersions: Optional[IKEVersionsRequestList] - StartupAction: Optional[String] - LogOptions: Optional[VpnTunnelLogOptionsSpecification] - EnableTunnelLifecycleControl: Optional[Boolean] + TunnelInsideCidr: String | None + TunnelInsideIpv6Cidr: String | None + PreSharedKey: preSharedKey | None + Phase1LifetimeSeconds: Integer | None + Phase2LifetimeSeconds: Integer | None + RekeyMarginTimeSeconds: Integer | None + RekeyFuzzPercentage: Integer | None + ReplayWindowSize: Integer | None + DPDTimeoutSeconds: Integer | None + DPDTimeoutAction: String | None + Phase1EncryptionAlgorithms: Phase1EncryptionAlgorithmsRequestList | None + Phase2EncryptionAlgorithms: Phase2EncryptionAlgorithmsRequestList | None + Phase1IntegrityAlgorithms: Phase1IntegrityAlgorithmsRequestList | None + Phase2IntegrityAlgorithms: Phase2IntegrityAlgorithmsRequestList | None + Phase1DHGroupNumbers: Phase1DHGroupNumbersRequestList | None + Phase2DHGroupNumbers: Phase2DHGroupNumbersRequestList | None + IKEVersions: IKEVersionsRequestList | None + StartupAction: String | None + LogOptions: VpnTunnelLogOptionsSpecification | None + EnableTunnelLifecycleControl: Boolean | None class ModifyVpnTunnelOptionsRequest(ServiceRequest): VpnConnectionId: VpnConnectionId VpnTunnelOutsideIpAddress: String TunnelOptions: ModifyVpnTunnelOptionsSpecification - DryRun: Optional[Boolean] - SkipTunnelReplacement: Optional[Boolean] - PreSharedKeyStorage: Optional[String] + DryRun: Boolean | None + SkipTunnelReplacement: Boolean | None + PreSharedKeyStorage: String | None class ModifyVpnTunnelOptionsResult(TypedDict, total=False): - VpnConnection: Optional[VpnConnection] + VpnConnection: VpnConnection | None class MonitorInstancesRequest(ServiceRequest): InstanceIds: InstanceIdStringList - DryRun: Optional[Boolean] + DryRun: Boolean | None class MonitorInstancesResult(TypedDict, total=False): - InstanceMonitorings: Optional[InstanceMonitoringList] + InstanceMonitorings: InstanceMonitoringList | None class MoveAddressToVpcRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PublicIp: String class MoveAddressToVpcResult(TypedDict, total=False): - AllocationId: Optional[String] - Status: Optional[Status] + AllocationId: String | None + Status: Status | None class MoveByoipCidrToIpamRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Cidr: String IpamPoolId: IpamPoolId IpamPoolOwner: String class MoveByoipCidrToIpamResult(TypedDict, total=False): - ByoipCidr: Optional[ByoipCidr] + ByoipCidr: ByoipCidr | None class MoveCapacityReservationInstancesRequest(ServiceRequest): - DryRun: Optional[Boolean] - ClientToken: Optional[String] + DryRun: Boolean | None + ClientToken: String | None SourceCapacityReservationId: CapacityReservationId DestinationCapacityReservationId: CapacityReservationId InstanceCount: Integer class MoveCapacityReservationInstancesResult(TypedDict, total=False): - SourceCapacityReservation: Optional[CapacityReservation] - DestinationCapacityReservation: Optional[CapacityReservation] - InstanceCount: Optional[Integer] + SourceCapacityReservation: CapacityReservation | None + DestinationCapacityReservation: CapacityReservation | None + InstanceCount: Integer | None class PrivateDnsNameOptionsRequest(TypedDict, total=False): - HostnameType: Optional[HostnameType] - EnableResourceNameDnsARecord: Optional[Boolean] - EnableResourceNameDnsAAAARecord: Optional[Boolean] + HostnameType: HostnameType | None + EnableResourceNameDnsARecord: Boolean | None + EnableResourceNameDnsAAAARecord: Boolean | None class ScheduledInstancesPrivateIpAddressConfig(TypedDict, total=False): - Primary: Optional[Boolean] - PrivateIpAddress: Optional[String] + Primary: Boolean | None + PrivateIpAddress: String | None -PrivateIpAddressConfigSet = List[ScheduledInstancesPrivateIpAddressConfig] +PrivateIpAddressConfigSet = list[ScheduledInstancesPrivateIpAddressConfig] class ProvisionByoipCidrRequest(ServiceRequest): Cidr: String - CidrAuthorizationContext: Optional[CidrAuthorizationContext] - PubliclyAdvertisable: Optional[Boolean] - Description: Optional[String] - DryRun: Optional[Boolean] - PoolTagSpecifications: Optional[TagSpecificationList] - MultiRegion: Optional[Boolean] - NetworkBorderGroup: Optional[String] + CidrAuthorizationContext: CidrAuthorizationContext | None + PubliclyAdvertisable: Boolean | None + Description: String | None + DryRun: Boolean | None + PoolTagSpecifications: TagSpecificationList | None + MultiRegion: Boolean | None + NetworkBorderGroup: String | None class ProvisionByoipCidrResult(TypedDict, total=False): - ByoipCidr: Optional[ByoipCidr] + ByoipCidr: ByoipCidr | None class ProvisionIpamByoasnRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamId: IpamId Asn: String AsnAuthorizationContext: AsnAuthorizationContext class ProvisionIpamByoasnResult(TypedDict, total=False): - Byoasn: Optional[Byoasn] + Byoasn: Byoasn | None class ProvisionIpamPoolCidrRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId - Cidr: Optional[String] - CidrAuthorizationContext: Optional[IpamCidrAuthorizationContext] - NetmaskLength: Optional[Integer] - ClientToken: Optional[String] - VerificationMethod: Optional[VerificationMethod] - IpamExternalResourceVerificationTokenId: Optional[IpamExternalResourceVerificationTokenId] + Cidr: String | None + CidrAuthorizationContext: IpamCidrAuthorizationContext | None + NetmaskLength: Integer | None + ClientToken: String | None + VerificationMethod: VerificationMethod | None + IpamExternalResourceVerificationTokenId: IpamExternalResourceVerificationTokenId | None class ProvisionIpamPoolCidrResult(TypedDict, total=False): - IpamPoolCidr: Optional[IpamPoolCidr] + IpamPoolCidr: IpamPoolCidr | None class ProvisionPublicIpv4PoolCidrRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId PoolId: Ipv4PoolEc2Id NetmaskLength: Integer - NetworkBorderGroup: Optional[String] + NetworkBorderGroup: String | None class ProvisionPublicIpv4PoolCidrResult(TypedDict, total=False): - PoolId: Optional[Ipv4PoolEc2Id] - PoolAddressRange: Optional[PublicIpv4PoolRange] + PoolId: Ipv4PoolEc2Id | None + PoolAddressRange: PublicIpv4PoolRange | None class PurchaseCapacityBlockExtensionRequest(ServiceRequest): CapacityBlockExtensionOfferingId: OfferingId CapacityReservationId: CapacityReservationId - DryRun: Optional[Boolean] + DryRun: Boolean | None class PurchaseCapacityBlockExtensionResult(TypedDict, total=False): - CapacityBlockExtensions: Optional[CapacityBlockExtensionSet] + CapacityBlockExtensions: CapacityBlockExtensionSet | None class PurchaseCapacityBlockRequest(ServiceRequest): - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None CapacityBlockOfferingId: OfferingId InstancePlatform: CapacityReservationInstancePlatform class PurchaseCapacityBlockResult(TypedDict, total=False): - CapacityReservation: Optional[CapacityReservation] - CapacityBlocks: Optional[CapacityBlockSet] + CapacityReservation: CapacityReservation | None + CapacityBlocks: CapacityBlockSet | None class PurchaseHostReservationRequest(ServiceRequest): - ClientToken: Optional[String] - CurrencyCode: Optional[CurrencyCodeValues] + ClientToken: String | None + CurrencyCode: CurrencyCodeValues | None HostIdSet: RequestHostIdSet - LimitPrice: Optional[String] + LimitPrice: String | None OfferingId: OfferingId - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None class PurchaseHostReservationResult(TypedDict, total=False): - ClientToken: Optional[String] - CurrencyCode: Optional[CurrencyCodeValues] - Purchase: Optional[PurchaseSet] - TotalHourlyPrice: Optional[String] - TotalUpfrontPrice: Optional[String] + ClientToken: String | None + CurrencyCode: CurrencyCodeValues | None + Purchase: PurchaseSet | None + TotalHourlyPrice: String | None + TotalUpfrontPrice: String | None class PurchaseRequest(TypedDict, total=False): @@ -19809,182 +22316,182 @@ class PurchaseRequest(TypedDict, total=False): PurchaseToken: String -PurchaseRequestSet = List[PurchaseRequest] +PurchaseRequestSet = list[PurchaseRequest] class ReservedInstanceLimitPrice(TypedDict, total=False): - Amount: Optional[Double] - CurrencyCode: Optional[CurrencyCodeValues] + Amount: Double | None + CurrencyCode: CurrencyCodeValues | None class PurchaseReservedInstancesOfferingRequest(ServiceRequest): InstanceCount: Integer ReservedInstancesOfferingId: ReservedInstancesOfferingId - PurchaseTime: Optional[DateTime] - DryRun: Optional[Boolean] - LimitPrice: Optional[ReservedInstanceLimitPrice] + PurchaseTime: DateTime | None + DryRun: Boolean | None + LimitPrice: ReservedInstanceLimitPrice | None class PurchaseReservedInstancesOfferingResult(TypedDict, total=False): - ReservedInstancesId: Optional[String] + ReservedInstancesId: String | None class PurchaseScheduledInstancesRequest(ServiceRequest): - ClientToken: Optional[String] - DryRun: Optional[Boolean] + ClientToken: String | None + DryRun: Boolean | None PurchaseRequests: PurchaseRequestSet -PurchasedScheduledInstanceSet = List[ScheduledInstance] +PurchasedScheduledInstanceSet = list[ScheduledInstance] class PurchaseScheduledInstancesResult(TypedDict, total=False): - ScheduledInstanceSet: Optional[PurchasedScheduledInstanceSet] + ScheduledInstanceSet: PurchasedScheduledInstanceSet | None -ReasonCodesList = List[ReportInstanceReasonCodes] +ReasonCodesList = list[ReportInstanceReasonCodes] class RebootInstancesRequest(ServiceRequest): InstanceIds: InstanceIdStringList - DryRun: Optional[Boolean] + DryRun: Boolean | None class RegisterImageRequest(ServiceRequest): - ImageLocation: Optional[String] - BillingProducts: Optional[BillingProductList] - BootMode: Optional[BootModeValues] - TpmSupport: Optional[TpmSupportValues] - UefiData: Optional[StringType] - ImdsSupport: Optional[ImdsSupportValues] - TagSpecifications: Optional[TagSpecificationList] - DryRun: Optional[Boolean] + ImageLocation: String | None + BillingProducts: BillingProductList | None + BootMode: BootModeValues | None + TpmSupport: TpmSupportValues | None + UefiData: StringType | None + ImdsSupport: ImdsSupportValues | None + TagSpecifications: TagSpecificationList | None + DryRun: Boolean | None Name: String - Description: Optional[String] - Architecture: Optional[ArchitectureValues] - KernelId: Optional[KernelId] - RamdiskId: Optional[RamdiskId] - RootDeviceName: Optional[String] - BlockDeviceMappings: Optional[BlockDeviceMappingRequestList] - VirtualizationType: Optional[String] - SriovNetSupport: Optional[String] - EnaSupport: Optional[Boolean] + Description: String | None + Architecture: ArchitectureValues | None + KernelId: KernelId | None + RamdiskId: RamdiskId | None + RootDeviceName: String | None + BlockDeviceMappings: BlockDeviceMappingRequestList | None + VirtualizationType: String | None + SriovNetSupport: String | None + EnaSupport: Boolean | None class RegisterImageResult(TypedDict, total=False): - ImageId: Optional[String] + ImageId: String | None class RegisterInstanceTagAttributeRequest(TypedDict, total=False): - IncludeAllTagsOfInstance: Optional[Boolean] - InstanceTagKeys: Optional[InstanceTagKeySet] + IncludeAllTagsOfInstance: Boolean | None + InstanceTagKeys: InstanceTagKeySet | None class RegisterInstanceEventNotificationAttributesRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceTagAttribute: RegisterInstanceTagAttributeRequest class RegisterInstanceEventNotificationAttributesResult(TypedDict, total=False): - InstanceTagAttribute: Optional[InstanceTagNotificationAttribute] + InstanceTagAttribute: InstanceTagNotificationAttribute | None class RegisterTransitGatewayMulticastGroupMembersRequest(ServiceRequest): TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId - GroupIpAddress: Optional[String] + GroupIpAddress: String | None NetworkInterfaceIds: TransitGatewayNetworkInterfaceIdList - DryRun: Optional[Boolean] + DryRun: Boolean | None class TransitGatewayMulticastRegisteredGroupMembers(TypedDict, total=False): - TransitGatewayMulticastDomainId: Optional[String] - RegisteredNetworkInterfaceIds: Optional[ValueStringList] - GroupIpAddress: Optional[String] + TransitGatewayMulticastDomainId: String | None + RegisteredNetworkInterfaceIds: ValueStringList | None + GroupIpAddress: String | None class RegisterTransitGatewayMulticastGroupMembersResult(TypedDict, total=False): - RegisteredMulticastGroupMembers: Optional[TransitGatewayMulticastRegisteredGroupMembers] + RegisteredMulticastGroupMembers: TransitGatewayMulticastRegisteredGroupMembers | None class RegisterTransitGatewayMulticastGroupSourcesRequest(ServiceRequest): TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId - GroupIpAddress: Optional[String] + GroupIpAddress: String | None NetworkInterfaceIds: TransitGatewayNetworkInterfaceIdList - DryRun: Optional[Boolean] + DryRun: Boolean | None class TransitGatewayMulticastRegisteredGroupSources(TypedDict, total=False): - TransitGatewayMulticastDomainId: Optional[String] - RegisteredNetworkInterfaceIds: Optional[ValueStringList] - GroupIpAddress: Optional[String] + TransitGatewayMulticastDomainId: String | None + RegisteredNetworkInterfaceIds: ValueStringList | None + GroupIpAddress: String | None class RegisterTransitGatewayMulticastGroupSourcesResult(TypedDict, total=False): - RegisteredMulticastGroupSources: Optional[TransitGatewayMulticastRegisteredGroupSources] + RegisteredMulticastGroupSources: TransitGatewayMulticastRegisteredGroupSources | None class RejectCapacityReservationBillingOwnershipRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None CapacityReservationId: CapacityReservationId class RejectCapacityReservationBillingOwnershipResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class RejectTransitGatewayMulticastDomainAssociationsRequest(ServiceRequest): - TransitGatewayMulticastDomainId: Optional[TransitGatewayMulticastDomainId] - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - SubnetIds: Optional[ValueStringList] - DryRun: Optional[Boolean] + TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId | None + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + SubnetIds: ValueStringList | None + DryRun: Boolean | None class RejectTransitGatewayMulticastDomainAssociationsResult(TypedDict, total=False): - Associations: Optional[TransitGatewayMulticastDomainAssociations] + Associations: TransitGatewayMulticastDomainAssociations | None class RejectTransitGatewayPeeringAttachmentRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class RejectTransitGatewayPeeringAttachmentResult(TypedDict, total=False): - TransitGatewayPeeringAttachment: Optional[TransitGatewayPeeringAttachment] + TransitGatewayPeeringAttachment: TransitGatewayPeeringAttachment | None class RejectTransitGatewayVpcAttachmentRequest(ServiceRequest): TransitGatewayAttachmentId: TransitGatewayAttachmentId - DryRun: Optional[Boolean] + DryRun: Boolean | None class RejectTransitGatewayVpcAttachmentResult(TypedDict, total=False): - TransitGatewayVpcAttachment: Optional[TransitGatewayVpcAttachment] + TransitGatewayVpcAttachment: TransitGatewayVpcAttachment | None class RejectVpcEndpointConnectionsRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceId: VpcEndpointServiceId VpcEndpointIds: VpcEndpointIdList class RejectVpcEndpointConnectionsResult(TypedDict, total=False): - Unsuccessful: Optional[UnsuccessfulItemSet] + Unsuccessful: UnsuccessfulItemSet | None class RejectVpcPeeringConnectionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None VpcPeeringConnectionId: VpcPeeringConnectionId class RejectVpcPeeringConnectionResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ReleaseAddressRequest(ServiceRequest): - AllocationId: Optional[AllocationId] - PublicIp: Optional[String] - NetworkBorderGroup: Optional[String] - DryRun: Optional[Boolean] + AllocationId: AllocationId | None + PublicIp: String | None + NetworkBorderGroup: String | None + DryRun: Boolean | None class ReleaseHostsRequest(ServiceRequest): @@ -19992,19 +22499,19 @@ class ReleaseHostsRequest(ServiceRequest): class ReleaseHostsResult(TypedDict, total=False): - Successful: Optional[ResponseHostIdList] - Unsuccessful: Optional[UnsuccessfulItemList] + Successful: ResponseHostIdList | None + Unsuccessful: UnsuccessfulItemList | None class ReleaseIpamPoolAllocationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None IpamPoolId: IpamPoolId Cidr: String IpamPoolAllocationId: IpamPoolAllocationId class ReleaseIpamPoolAllocationResult(TypedDict, total=False): - Success: Optional[Boolean] + Success: Boolean | None class ReplaceIamInstanceProfileAssociationRequest(ServiceRequest): @@ -20013,730 +22520,771 @@ class ReplaceIamInstanceProfileAssociationRequest(ServiceRequest): class ReplaceIamInstanceProfileAssociationResult(TypedDict, total=False): - IamInstanceProfileAssociation: Optional[IamInstanceProfileAssociation] + IamInstanceProfileAssociation: IamInstanceProfileAssociation | None class ReplaceImageCriteriaInAllowedImagesSettingsRequest(ServiceRequest): - ImageCriteria: Optional[ImageCriterionRequestList] - DryRun: Optional[Boolean] + ImageCriteria: ImageCriterionRequestList | None + DryRun: Boolean | None class ReplaceImageCriteriaInAllowedImagesSettingsResult(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class ReplaceNetworkAclAssociationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None AssociationId: NetworkAclAssociationId NetworkAclId: NetworkAclId class ReplaceNetworkAclAssociationResult(TypedDict, total=False): - NewAssociationId: Optional[String] + NewAssociationId: String | None class ReplaceNetworkAclEntryRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkAclId: NetworkAclId RuleNumber: Integer Protocol: String RuleAction: RuleAction Egress: Boolean - CidrBlock: Optional[String] - Ipv6CidrBlock: Optional[String] - IcmpTypeCode: Optional[IcmpTypeCode] - PortRange: Optional[PortRange] + CidrBlock: String | None + Ipv6CidrBlock: String | None + IcmpTypeCode: IcmpTypeCode | None + PortRange: PortRange | None class ReplaceRouteRequest(ServiceRequest): - DestinationPrefixListId: Optional[PrefixListResourceId] - VpcEndpointId: Optional[VpcEndpointId] - LocalTarget: Optional[Boolean] - TransitGatewayId: Optional[TransitGatewayId] - LocalGatewayId: Optional[LocalGatewayId] - CarrierGatewayId: Optional[CarrierGatewayId] - CoreNetworkArn: Optional[CoreNetworkArn] - OdbNetworkArn: Optional[OdbNetworkArn] - DryRun: Optional[Boolean] + DestinationPrefixListId: PrefixListResourceId | None + VpcEndpointId: VpcEndpointId | None + LocalTarget: Boolean | None + TransitGatewayId: TransitGatewayId | None + LocalGatewayId: LocalGatewayId | None + CarrierGatewayId: CarrierGatewayId | None + CoreNetworkArn: CoreNetworkArn | None + OdbNetworkArn: OdbNetworkArn | None + DryRun: Boolean | None RouteTableId: RouteTableId - DestinationCidrBlock: Optional[String] - GatewayId: Optional[RouteGatewayId] - DestinationIpv6CidrBlock: Optional[String] - EgressOnlyInternetGatewayId: Optional[EgressOnlyInternetGatewayId] - InstanceId: Optional[InstanceId] - NetworkInterfaceId: Optional[NetworkInterfaceId] - VpcPeeringConnectionId: Optional[VpcPeeringConnectionId] - NatGatewayId: Optional[NatGatewayId] + DestinationCidrBlock: String | None + GatewayId: RouteGatewayId | None + DestinationIpv6CidrBlock: String | None + EgressOnlyInternetGatewayId: EgressOnlyInternetGatewayId | None + InstanceId: InstanceId | None + NetworkInterfaceId: NetworkInterfaceId | None + VpcPeeringConnectionId: VpcPeeringConnectionId | None + NatGatewayId: NatGatewayId | None class ReplaceRouteTableAssociationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None AssociationId: RouteTableAssociationId RouteTableId: RouteTableId class ReplaceRouteTableAssociationResult(TypedDict, total=False): - NewAssociationId: Optional[String] - AssociationState: Optional[RouteTableAssociationState] + NewAssociationId: String | None + AssociationState: RouteTableAssociationState | None class ReplaceTransitGatewayRouteRequest(ServiceRequest): DestinationCidrBlock: String TransitGatewayRouteTableId: TransitGatewayRouteTableId - TransitGatewayAttachmentId: Optional[TransitGatewayAttachmentId] - Blackhole: Optional[Boolean] - DryRun: Optional[Boolean] + TransitGatewayAttachmentId: TransitGatewayAttachmentId | None + Blackhole: Boolean | None + DryRun: Boolean | None class ReplaceTransitGatewayRouteResult(TypedDict, total=False): - Route: Optional[TransitGatewayRoute] + Route: TransitGatewayRoute | None class ReplaceVpnTunnelRequest(ServiceRequest): VpnConnectionId: VpnConnectionId VpnTunnelOutsideIpAddress: String - ApplyPendingMaintenance: Optional[Boolean] - DryRun: Optional[Boolean] + ApplyPendingMaintenance: Boolean | None + DryRun: Boolean | None class ReplaceVpnTunnelResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ReportInstanceStatusRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None Instances: InstanceIdStringList Status: ReportStatusType - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] + StartTime: DateTime | None + EndTime: DateTime | None ReasonCodes: ReasonCodesList - Description: Optional[ReportInstanceStatusRequestDescription] + Description: ReportInstanceStatusRequestDescription | None class RequestSpotFleetRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None SpotFleetRequestConfig: SpotFleetRequestConfigData class RequestSpotFleetResponse(TypedDict, total=False): - SpotFleetRequestId: Optional[String] + SpotFleetRequestId: String | None -RequestSpotLaunchSpecificationSecurityGroupList = List[String] -RequestSpotLaunchSpecificationSecurityGroupIdList = List[SecurityGroupId] +RequestSpotLaunchSpecificationSecurityGroupList = list[String] +RequestSpotLaunchSpecificationSecurityGroupIdList = list[SecurityGroupId] class RequestSpotLaunchSpecification(TypedDict, total=False): - SecurityGroupIds: Optional[RequestSpotLaunchSpecificationSecurityGroupIdList] - SecurityGroups: Optional[RequestSpotLaunchSpecificationSecurityGroupList] - AddressingType: Optional[String] - BlockDeviceMappings: Optional[BlockDeviceMappingList] - EbsOptimized: Optional[Boolean] - IamInstanceProfile: Optional[IamInstanceProfileSpecification] - ImageId: Optional[ImageId] - InstanceType: Optional[InstanceType] - KernelId: Optional[KernelId] - KeyName: Optional[KeyPairNameWithResolver] - Monitoring: Optional[RunInstancesMonitoringEnabled] - NetworkInterfaces: Optional[InstanceNetworkInterfaceSpecificationList] - Placement: Optional[SpotPlacement] - RamdiskId: Optional[RamdiskId] - SubnetId: Optional[SubnetId] - UserData: Optional[SensitiveUserData] + SecurityGroupIds: RequestSpotLaunchSpecificationSecurityGroupIdList | None + SecurityGroups: RequestSpotLaunchSpecificationSecurityGroupList | None + AddressingType: String | None + BlockDeviceMappings: BlockDeviceMappingList | None + EbsOptimized: Boolean | None + IamInstanceProfile: IamInstanceProfileSpecification | None + ImageId: ImageId | None + InstanceType: InstanceType | None + KernelId: KernelId | None + KeyName: KeyPairNameWithResolver | None + Monitoring: RunInstancesMonitoringEnabled | None + NetworkInterfaces: InstanceNetworkInterfaceSpecificationList | None + Placement: SpotPlacement | None + RamdiskId: RamdiskId | None + SubnetId: SubnetId | None + UserData: SensitiveUserData | None class RequestSpotInstancesRequest(ServiceRequest): - LaunchSpecification: Optional[RequestSpotLaunchSpecification] - TagSpecifications: Optional[TagSpecificationList] - InstanceInterruptionBehavior: Optional[InstanceInterruptionBehavior] - DryRun: Optional[Boolean] - SpotPrice: Optional[String] - ClientToken: Optional[String] - InstanceCount: Optional[Integer] - Type: Optional[SpotInstanceType] - ValidFrom: Optional[DateTime] - ValidUntil: Optional[DateTime] - LaunchGroup: Optional[String] - AvailabilityZoneGroup: Optional[String] - BlockDurationMinutes: Optional[Integer] + LaunchSpecification: RequestSpotLaunchSpecification | None + TagSpecifications: TagSpecificationList | None + InstanceInterruptionBehavior: InstanceInterruptionBehavior | None + DryRun: Boolean | None + SpotPrice: String | None + ClientToken: String | None + InstanceCount: Integer | None + Type: SpotInstanceType | None + ValidFrom: DateTime | None + ValidUntil: DateTime | None + LaunchGroup: String | None + AvailabilityZoneGroup: String | None + BlockDurationMinutes: Integer | None class RequestSpotInstancesResult(TypedDict, total=False): - SpotInstanceRequests: Optional[SpotInstanceRequestList] + SpotInstanceRequests: SpotInstanceRequestList | None class ResetAddressAttributeRequest(ServiceRequest): AllocationId: AllocationId Attribute: AddressAttributeName - DryRun: Optional[Boolean] + DryRun: Boolean | None class ResetAddressAttributeResult(TypedDict, total=False): - Address: Optional[AddressAttribute] + Address: AddressAttribute | None class ResetEbsDefaultKmsKeyIdRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None class ResetEbsDefaultKmsKeyIdResult(TypedDict, total=False): - KmsKeyId: Optional[String] + KmsKeyId: String | None class ResetFpgaImageAttributeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None FpgaImageId: FpgaImageId - Attribute: Optional[ResetFpgaImageAttributeName] + Attribute: ResetFpgaImageAttributeName | None class ResetFpgaImageAttributeResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class ResetImageAttributeRequest(ServiceRequest): Attribute: ResetImageAttributeName ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None class ResetInstanceAttributeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None InstanceId: InstanceId Attribute: InstanceAttributeName class ResetNetworkInterfaceAttributeRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None NetworkInterfaceId: NetworkInterfaceId - SourceDestCheck: Optional[String] + SourceDestCheck: String | None class ResetSnapshotAttributeRequest(ServiceRequest): Attribute: SnapshotAttributeName SnapshotId: SnapshotId - DryRun: Optional[Boolean] + DryRun: Boolean | None class RestoreAddressToClassicRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PublicIp: String class RestoreAddressToClassicResult(TypedDict, total=False): - PublicIp: Optional[String] - Status: Optional[Status] + PublicIp: String | None + Status: Status | None class RestoreImageFromRecycleBinRequest(ServiceRequest): ImageId: ImageId - DryRun: Optional[Boolean] + DryRun: Boolean | None class RestoreImageFromRecycleBinResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class RestoreManagedPrefixListVersionRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None PrefixListId: PrefixListResourceId PreviousVersion: Long CurrentVersion: Long class RestoreManagedPrefixListVersionResult(TypedDict, total=False): - PrefixList: Optional[ManagedPrefixList] + PrefixList: ManagedPrefixList | None class RestoreSnapshotFromRecycleBinRequest(ServiceRequest): SnapshotId: SnapshotId - DryRun: Optional[Boolean] + DryRun: Boolean | None class RestoreSnapshotFromRecycleBinResult(TypedDict, total=False): - SnapshotId: Optional[String] - OutpostArn: Optional[String] - Description: Optional[String] - Encrypted: Optional[Boolean] - OwnerId: Optional[String] - Progress: Optional[String] - StartTime: Optional[MillisecondDateTime] - State: Optional[SnapshotState] - VolumeId: Optional[String] - VolumeSize: Optional[Integer] - SseType: Optional[SSEType] + SnapshotId: String | None + OutpostArn: String | None + Description: String | None + Encrypted: Boolean | None + OwnerId: String | None + Progress: String | None + StartTime: MillisecondDateTime | None + State: SnapshotState | None + VolumeId: String | None + VolumeSize: Integer | None + SseType: SSEType | None class RestoreSnapshotTierRequest(ServiceRequest): SnapshotId: SnapshotId - TemporaryRestoreDays: Optional[RestoreSnapshotTierRequestTemporaryRestoreDays] - PermanentRestore: Optional[Boolean] - DryRun: Optional[Boolean] + TemporaryRestoreDays: RestoreSnapshotTierRequestTemporaryRestoreDays | None + PermanentRestore: Boolean | None + DryRun: Boolean | None class RestoreSnapshotTierResult(TypedDict, total=False): - SnapshotId: Optional[String] - RestoreStartTime: Optional[MillisecondDateTime] - RestoreDuration: Optional[Integer] - IsPermanentRestore: Optional[Boolean] + SnapshotId: String | None + RestoreStartTime: MillisecondDateTime | None + RestoreDuration: Integer | None + IsPermanentRestore: Boolean | None + + +class RestoreVolumeFromRecycleBinRequest(ServiceRequest): + VolumeId: VolumeId + DryRun: Boolean | None + + +class RestoreVolumeFromRecycleBinResult(TypedDict, total=False): + Return: Boolean | None class RevokeClientVpnIngressRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId TargetNetworkCidr: String - AccessGroupId: Optional[String] - RevokeAllGroups: Optional[Boolean] - DryRun: Optional[Boolean] + AccessGroupId: String | None + RevokeAllGroups: Boolean | None + DryRun: Boolean | None class RevokeClientVpnIngressResult(TypedDict, total=False): - Status: Optional[ClientVpnAuthorizationRuleStatus] + Status: ClientVpnAuthorizationRuleStatus | None class RevokeSecurityGroupEgressRequest(ServiceRequest): - SecurityGroupRuleIds: Optional[SecurityGroupRuleIdList] - DryRun: Optional[Boolean] + SecurityGroupRuleIds: SecurityGroupRuleIdList | None + DryRun: Boolean | None GroupId: SecurityGroupId - SourceSecurityGroupName: Optional[String] - SourceSecurityGroupOwnerId: Optional[String] - IpProtocol: Optional[String] - FromPort: Optional[Integer] - ToPort: Optional[Integer] - CidrIp: Optional[String] - IpPermissions: Optional[IpPermissionList] + SourceSecurityGroupName: String | None + SourceSecurityGroupOwnerId: String | None + IpProtocol: String | None + FromPort: Integer | None + ToPort: Integer | None + CidrIp: String | None + IpPermissions: IpPermissionList | None class RevokedSecurityGroupRule(TypedDict, total=False): - SecurityGroupRuleId: Optional[SecurityGroupRuleId] - GroupId: Optional[SecurityGroupId] - IsEgress: Optional[Boolean] - IpProtocol: Optional[String] - FromPort: Optional[Integer] - ToPort: Optional[Integer] - CidrIpv4: Optional[String] - CidrIpv6: Optional[String] - PrefixListId: Optional[PrefixListResourceId] - ReferencedGroupId: Optional[SecurityGroupId] - Description: Optional[String] + SecurityGroupRuleId: SecurityGroupRuleId | None + GroupId: SecurityGroupId | None + IsEgress: Boolean | None + IpProtocol: String | None + FromPort: Integer | None + ToPort: Integer | None + CidrIpv4: String | None + CidrIpv6: String | None + PrefixListId: PrefixListResourceId | None + ReferencedGroupId: SecurityGroupId | None + Description: String | None -RevokedSecurityGroupRuleList = List[RevokedSecurityGroupRule] +RevokedSecurityGroupRuleList = list[RevokedSecurityGroupRule] class RevokeSecurityGroupEgressResult(TypedDict, total=False): - Return: Optional[Boolean] - UnknownIpPermissions: Optional[IpPermissionList] - RevokedSecurityGroupRules: Optional[RevokedSecurityGroupRuleList] + Return: Boolean | None + UnknownIpPermissions: IpPermissionList | None + RevokedSecurityGroupRules: RevokedSecurityGroupRuleList | None class RevokeSecurityGroupIngressRequest(ServiceRequest): - CidrIp: Optional[String] - FromPort: Optional[Integer] - GroupId: Optional[SecurityGroupId] - GroupName: Optional[SecurityGroupName] - IpPermissions: Optional[IpPermissionList] - IpProtocol: Optional[String] - SourceSecurityGroupName: Optional[String] - SourceSecurityGroupOwnerId: Optional[String] - ToPort: Optional[Integer] - SecurityGroupRuleIds: Optional[SecurityGroupRuleIdList] - DryRun: Optional[Boolean] + CidrIp: String | None + FromPort: Integer | None + GroupId: SecurityGroupId | None + GroupName: SecurityGroupName | None + IpPermissions: IpPermissionList | None + IpProtocol: String | None + SourceSecurityGroupName: String | None + SourceSecurityGroupOwnerId: String | None + ToPort: Integer | None + SecurityGroupRuleIds: SecurityGroupRuleIdList | None + DryRun: Boolean | None class RevokeSecurityGroupIngressResult(TypedDict, total=False): - Return: Optional[Boolean] - UnknownIpPermissions: Optional[IpPermissionList] - RevokedSecurityGroupRules: Optional[RevokedSecurityGroupRuleList] + Return: Boolean | None + UnknownIpPermissions: IpPermissionList | None + RevokedSecurityGroupRules: RevokedSecurityGroupRuleList | None class RunInstancesRequest(ServiceRequest): - BlockDeviceMappings: Optional[BlockDeviceMappingRequestList] - ImageId: Optional[ImageId] - InstanceType: Optional[InstanceType] - Ipv6AddressCount: Optional[Integer] - Ipv6Addresses: Optional[InstanceIpv6AddressList] - KernelId: Optional[KernelId] - KeyName: Optional[KeyPairName] + BlockDeviceMappings: BlockDeviceMappingRequestList | None + ImageId: ImageId | None + InstanceType: InstanceType | None + Ipv6AddressCount: Integer | None + Ipv6Addresses: InstanceIpv6AddressList | None + KernelId: KernelId | None + KeyName: KeyPairName | None MaxCount: Integer MinCount: Integer - Monitoring: Optional[RunInstancesMonitoringEnabled] - Placement: Optional[Placement] - RamdiskId: Optional[RamdiskId] - SecurityGroupIds: Optional[SecurityGroupIdStringList] - SecurityGroups: Optional[SecurityGroupStringList] - SubnetId: Optional[SubnetId] - UserData: Optional[RunInstancesUserData] - ElasticGpuSpecification: Optional[ElasticGpuSpecifications] - ElasticInferenceAccelerators: Optional[ElasticInferenceAccelerators] - TagSpecifications: Optional[TagSpecificationList] - LaunchTemplate: Optional[LaunchTemplateSpecification] - InstanceMarketOptions: Optional[InstanceMarketOptionsRequest] - CreditSpecification: Optional[CreditSpecificationRequest] - CpuOptions: Optional[CpuOptionsRequest] - CapacityReservationSpecification: Optional[CapacityReservationSpecification] - HibernationOptions: Optional[HibernationOptionsRequest] - LicenseSpecifications: Optional[LicenseSpecificationListRequest] - MetadataOptions: Optional[InstanceMetadataOptionsRequest] - EnclaveOptions: Optional[EnclaveOptionsRequest] - PrivateDnsNameOptions: Optional[PrivateDnsNameOptionsRequest] - MaintenanceOptions: Optional[InstanceMaintenanceOptionsRequest] - DisableApiStop: Optional[Boolean] - EnablePrimaryIpv6: Optional[Boolean] - NetworkPerformanceOptions: Optional[InstanceNetworkPerformanceOptionsRequest] - Operator: Optional[OperatorRequest] - DryRun: Optional[Boolean] - DisableApiTermination: Optional[Boolean] - InstanceInitiatedShutdownBehavior: Optional[ShutdownBehavior] - PrivateIpAddress: Optional[String] - ClientToken: Optional[String] - AdditionalInfo: Optional[String] - NetworkInterfaces: Optional[InstanceNetworkInterfaceSpecificationList] - IamInstanceProfile: Optional[IamInstanceProfileSpecification] - EbsOptimized: Optional[Boolean] - - -ScheduledInstancesSecurityGroupIdSet = List[SecurityGroupId] + Monitoring: RunInstancesMonitoringEnabled | None + Placement: Placement | None + RamdiskId: RamdiskId | None + SecurityGroupIds: SecurityGroupIdStringList | None + SecurityGroups: SecurityGroupStringList | None + SubnetId: SubnetId | None + UserData: RunInstancesUserData | None + ElasticGpuSpecification: ElasticGpuSpecifications | None + ElasticInferenceAccelerators: ElasticInferenceAccelerators | None + TagSpecifications: TagSpecificationList | None + LaunchTemplate: LaunchTemplateSpecification | None + InstanceMarketOptions: InstanceMarketOptionsRequest | None + CreditSpecification: CreditSpecificationRequest | None + CpuOptions: CpuOptionsRequest | None + CapacityReservationSpecification: CapacityReservationSpecification | None + HibernationOptions: HibernationOptionsRequest | None + LicenseSpecifications: LicenseSpecificationListRequest | None + MetadataOptions: InstanceMetadataOptionsRequest | None + EnclaveOptions: EnclaveOptionsRequest | None + PrivateDnsNameOptions: PrivateDnsNameOptionsRequest | None + MaintenanceOptions: InstanceMaintenanceOptionsRequest | None + DisableApiStop: Boolean | None + EnablePrimaryIpv6: Boolean | None + NetworkPerformanceOptions: InstanceNetworkPerformanceOptionsRequest | None + Operator: OperatorRequest | None + SecondaryInterfaces: InstanceSecondaryInterfaceSpecificationListRequest | None + DryRun: Boolean | None + DisableApiTermination: Boolean | None + InstanceInitiatedShutdownBehavior: ShutdownBehavior | None + PrivateIpAddress: String | None + ClientToken: String | None + AdditionalInfo: String | None + NetworkInterfaces: InstanceNetworkInterfaceSpecificationList | None + IamInstanceProfile: IamInstanceProfileSpecification | None + EbsOptimized: Boolean | None + + +ScheduledInstancesSecurityGroupIdSet = list[SecurityGroupId] class ScheduledInstancesPlacement(TypedDict, total=False): - AvailabilityZone: Optional[String] - GroupName: Optional[PlacementGroupName] + AvailabilityZone: String | None + GroupName: PlacementGroupName | None class ScheduledInstancesIpv6Address(TypedDict, total=False): - Ipv6Address: Optional[Ipv6Address] + Ipv6Address: Ipv6Address | None -ScheduledInstancesIpv6AddressList = List[ScheduledInstancesIpv6Address] +ScheduledInstancesIpv6AddressList = list[ScheduledInstancesIpv6Address] class ScheduledInstancesNetworkInterface(TypedDict, total=False): - AssociatePublicIpAddress: Optional[Boolean] - DeleteOnTermination: Optional[Boolean] - Description: Optional[String] - DeviceIndex: Optional[Integer] - Groups: Optional[ScheduledInstancesSecurityGroupIdSet] - Ipv6AddressCount: Optional[Integer] - Ipv6Addresses: Optional[ScheduledInstancesIpv6AddressList] - NetworkInterfaceId: Optional[NetworkInterfaceId] - PrivateIpAddress: Optional[String] - PrivateIpAddressConfigs: Optional[PrivateIpAddressConfigSet] - SecondaryPrivateIpAddressCount: Optional[Integer] - SubnetId: Optional[SubnetId] + AssociatePublicIpAddress: Boolean | None + DeleteOnTermination: Boolean | None + Description: String | None + DeviceIndex: Integer | None + Groups: ScheduledInstancesSecurityGroupIdSet | None + Ipv6AddressCount: Integer | None + Ipv6Addresses: ScheduledInstancesIpv6AddressList | None + NetworkInterfaceId: NetworkInterfaceId | None + PrivateIpAddress: String | None + PrivateIpAddressConfigs: PrivateIpAddressConfigSet | None + SecondaryPrivateIpAddressCount: Integer | None + SubnetId: SubnetId | None -ScheduledInstancesNetworkInterfaceSet = List[ScheduledInstancesNetworkInterface] +ScheduledInstancesNetworkInterfaceSet = list[ScheduledInstancesNetworkInterface] class ScheduledInstancesMonitoring(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class ScheduledInstancesIamInstanceProfile(TypedDict, total=False): - Arn: Optional[String] - Name: Optional[String] + Arn: String | None + Name: String | None class ScheduledInstancesEbs(TypedDict, total=False): - DeleteOnTermination: Optional[Boolean] - Encrypted: Optional[Boolean] - Iops: Optional[Integer] - SnapshotId: Optional[SnapshotId] - VolumeSize: Optional[Integer] - VolumeType: Optional[String] + DeleteOnTermination: Boolean | None + Encrypted: Boolean | None + Iops: Integer | None + SnapshotId: SnapshotId | None + VolumeSize: Integer | None + VolumeType: String | None class ScheduledInstancesBlockDeviceMapping(TypedDict, total=False): - DeviceName: Optional[String] - Ebs: Optional[ScheduledInstancesEbs] - NoDevice: Optional[String] - VirtualName: Optional[String] + DeviceName: String | None + Ebs: ScheduledInstancesEbs | None + NoDevice: String | None + VirtualName: String | None -ScheduledInstancesBlockDeviceMappingSet = List[ScheduledInstancesBlockDeviceMapping] +ScheduledInstancesBlockDeviceMappingSet = list[ScheduledInstancesBlockDeviceMapping] class ScheduledInstancesLaunchSpecification(TypedDict, total=False): - BlockDeviceMappings: Optional[ScheduledInstancesBlockDeviceMappingSet] - EbsOptimized: Optional[Boolean] - IamInstanceProfile: Optional[ScheduledInstancesIamInstanceProfile] + BlockDeviceMappings: ScheduledInstancesBlockDeviceMappingSet | None + EbsOptimized: Boolean | None + IamInstanceProfile: ScheduledInstancesIamInstanceProfile | None ImageId: ImageId - InstanceType: Optional[String] - KernelId: Optional[KernelId] - KeyName: Optional[KeyPairName] - Monitoring: Optional[ScheduledInstancesMonitoring] - NetworkInterfaces: Optional[ScheduledInstancesNetworkInterfaceSet] - Placement: Optional[ScheduledInstancesPlacement] - RamdiskId: Optional[RamdiskId] - SecurityGroupIds: Optional[ScheduledInstancesSecurityGroupIdSet] - SubnetId: Optional[SubnetId] - UserData: Optional[String] + InstanceType: String | None + KernelId: KernelId | None + KeyName: KeyPairName | None + Monitoring: ScheduledInstancesMonitoring | None + NetworkInterfaces: ScheduledInstancesNetworkInterfaceSet | None + Placement: ScheduledInstancesPlacement | None + RamdiskId: RamdiskId | None + SecurityGroupIds: ScheduledInstancesSecurityGroupIdSet | None + SubnetId: SubnetId | None + UserData: String | None class RunScheduledInstancesRequest(ServiceRequest): - ClientToken: Optional[String] - DryRun: Optional[Boolean] - InstanceCount: Optional[Integer] + ClientToken: String | None + DryRun: Boolean | None + InstanceCount: Integer | None LaunchSpecification: ScheduledInstancesLaunchSpecification ScheduledInstanceId: ScheduledInstanceId class RunScheduledInstancesResult(TypedDict, total=False): - InstanceIdSet: Optional[InstanceIdSet] + InstanceIdSet: InstanceIdSet | None class SearchLocalGatewayRoutesRequest(ServiceRequest): LocalGatewayRouteTableId: LocalGatewayRoutetableId - Filters: Optional[FilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: MaxResults | None + NextToken: String | None + DryRun: Boolean | None class SearchLocalGatewayRoutesResult(TypedDict, total=False): - Routes: Optional[LocalGatewayRouteList] - NextToken: Optional[String] + Routes: LocalGatewayRouteList | None + NextToken: String | None class SearchTransitGatewayMulticastGroupsRequest(ServiceRequest): TransitGatewayMulticastDomainId: TransitGatewayMulticastDomainId - Filters: Optional[FilterList] - MaxResults: Optional[TransitGatewayMaxResults] - NextToken: Optional[String] - DryRun: Optional[Boolean] + Filters: FilterList | None + MaxResults: TransitGatewayMaxResults | None + NextToken: String | None + DryRun: Boolean | None class TransitGatewayMulticastGroup(TypedDict, total=False): - GroupIpAddress: Optional[String] - TransitGatewayAttachmentId: Optional[String] - SubnetId: Optional[String] - ResourceId: Optional[String] - ResourceType: Optional[TransitGatewayAttachmentResourceType] - ResourceOwnerId: Optional[String] - NetworkInterfaceId: Optional[String] - GroupMember: Optional[Boolean] - GroupSource: Optional[Boolean] - MemberType: Optional[MembershipType] - SourceType: Optional[MembershipType] + GroupIpAddress: String | None + TransitGatewayAttachmentId: String | None + SubnetId: String | None + ResourceId: String | None + ResourceType: TransitGatewayAttachmentResourceType | None + ResourceOwnerId: String | None + NetworkInterfaceId: String | None + GroupMember: Boolean | None + GroupSource: Boolean | None + MemberType: MembershipType | None + SourceType: MembershipType | None -TransitGatewayMulticastGroupList = List[TransitGatewayMulticastGroup] +TransitGatewayMulticastGroupList = list[TransitGatewayMulticastGroup] class SearchTransitGatewayMulticastGroupsResult(TypedDict, total=False): - MulticastGroups: Optional[TransitGatewayMulticastGroupList] - NextToken: Optional[String] + MulticastGroups: TransitGatewayMulticastGroupList | None + NextToken: String | None class SearchTransitGatewayRoutesRequest(ServiceRequest): TransitGatewayRouteTableId: TransitGatewayRouteTableId Filters: FilterList - MaxResults: Optional[TransitGatewayMaxResults] - DryRun: Optional[Boolean] + MaxResults: TransitGatewayMaxResults | None + DryRun: Boolean | None + NextToken: String | None -TransitGatewayRouteList = List[TransitGatewayRoute] +TransitGatewayRouteList = list[TransitGatewayRoute] class SearchTransitGatewayRoutesResult(TypedDict, total=False): - Routes: Optional[TransitGatewayRouteList] - AdditionalRoutesAvailable: Optional[Boolean] + Routes: TransitGatewayRouteList | None + AdditionalRoutesAvailable: Boolean | None + NextToken: String | None class SecurityGroupRuleDescription(TypedDict, total=False): - SecurityGroupRuleId: Optional[String] - Description: Optional[String] + SecurityGroupRuleId: String | None + Description: String | None -SecurityGroupRuleDescriptionList = List[SecurityGroupRuleDescription] +SecurityGroupRuleDescriptionList = list[SecurityGroupRuleDescription] class SendDiagnosticInterruptRequest(ServiceRequest): InstanceId: InstanceId - DryRun: Optional[Boolean] + DryRun: Boolean | None class StartDeclarativePoliciesReportRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None S3Bucket: String - S3Prefix: Optional[String] + S3Prefix: String | None TargetId: String - TagSpecifications: Optional[TagSpecificationList] + TagSpecifications: TagSpecificationList | None class StartDeclarativePoliciesReportResult(TypedDict, total=False): - ReportId: Optional[String] + ReportId: String | None class StartInstancesRequest(ServiceRequest): InstanceIds: InstanceIdStringList - AdditionalInfo: Optional[String] - DryRun: Optional[Boolean] + AdditionalInfo: String | None + DryRun: Boolean | None class StartInstancesResult(TypedDict, total=False): - StartingInstances: Optional[InstanceStateChangeList] + StartingInstances: InstanceStateChangeList | None class StartNetworkInsightsAccessScopeAnalysisRequest(ServiceRequest): NetworkInsightsAccessScopeId: NetworkInsightsAccessScopeId - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None ClientToken: String class StartNetworkInsightsAccessScopeAnalysisResult(TypedDict, total=False): - NetworkInsightsAccessScopeAnalysis: Optional[NetworkInsightsAccessScopeAnalysis] + NetworkInsightsAccessScopeAnalysis: NetworkInsightsAccessScopeAnalysis | None class StartNetworkInsightsAnalysisRequest(ServiceRequest): NetworkInsightsPathId: NetworkInsightsPathId - AdditionalAccounts: Optional[ValueStringList] - FilterInArns: Optional[ArnList] - FilterOutArns: Optional[ArnList] - DryRun: Optional[Boolean] - TagSpecifications: Optional[TagSpecificationList] + AdditionalAccounts: ValueStringList | None + FilterInArns: ArnList | None + FilterOutArns: ArnList | None + DryRun: Boolean | None + TagSpecifications: TagSpecificationList | None ClientToken: String class StartNetworkInsightsAnalysisResult(TypedDict, total=False): - NetworkInsightsAnalysis: Optional[NetworkInsightsAnalysis] + NetworkInsightsAnalysis: NetworkInsightsAnalysis | None class StartVpcEndpointServicePrivateDnsVerificationRequest(ServiceRequest): - DryRun: Optional[Boolean] + DryRun: Boolean | None ServiceId: VpcEndpointServiceId class StartVpcEndpointServicePrivateDnsVerificationResult(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class StopInstancesRequest(ServiceRequest): InstanceIds: InstanceIdStringList - Hibernate: Optional[Boolean] - DryRun: Optional[Boolean] - Force: Optional[Boolean] + Hibernate: Boolean | None + SkipOsShutdown: Boolean | None + DryRun: Boolean | None + Force: Boolean | None class StopInstancesResult(TypedDict, total=False): - StoppingInstances: Optional[InstanceStateChangeList] + StoppingInstances: InstanceStateChangeList | None class TerminateClientVpnConnectionsRequest(ServiceRequest): ClientVpnEndpointId: ClientVpnEndpointId - ConnectionId: Optional[String] - Username: Optional[String] - DryRun: Optional[Boolean] + ConnectionId: String | None + Username: String | None + DryRun: Boolean | None class TerminateConnectionStatus(TypedDict, total=False): - ConnectionId: Optional[String] - PreviousStatus: Optional[ClientVpnConnectionStatus] - CurrentStatus: Optional[ClientVpnConnectionStatus] + ConnectionId: String | None + PreviousStatus: ClientVpnConnectionStatus | None + CurrentStatus: ClientVpnConnectionStatus | None -TerminateConnectionStatusSet = List[TerminateConnectionStatus] +TerminateConnectionStatusSet = list[TerminateConnectionStatus] class TerminateClientVpnConnectionsResult(TypedDict, total=False): - ClientVpnEndpointId: Optional[String] - Username: Optional[String] - ConnectionStatuses: Optional[TerminateConnectionStatusSet] + ClientVpnEndpointId: String | None + Username: String | None + ConnectionStatuses: TerminateConnectionStatusSet | None class TerminateInstancesRequest(ServiceRequest): InstanceIds: InstanceIdStringList - DryRun: Optional[Boolean] + Force: Boolean | None + SkipOsShutdown: Boolean | None + DryRun: Boolean | None class TerminateInstancesResult(TypedDict, total=False): - TerminatingInstances: Optional[InstanceStateChangeList] + TerminatingInstances: InstanceStateChangeList | None class UnassignIpv6AddressesRequest(ServiceRequest): - Ipv6Prefixes: Optional[IpPrefixList] + Ipv6Prefixes: IpPrefixList | None NetworkInterfaceId: NetworkInterfaceId - Ipv6Addresses: Optional[Ipv6AddressList] + Ipv6Addresses: Ipv6AddressList | None class UnassignIpv6AddressesResult(TypedDict, total=False): - NetworkInterfaceId: Optional[String] - UnassignedIpv6Addresses: Optional[Ipv6AddressList] - UnassignedIpv6Prefixes: Optional[IpPrefixList] + NetworkInterfaceId: String | None + UnassignedIpv6Addresses: Ipv6AddressList | None + UnassignedIpv6Prefixes: IpPrefixList | None class UnassignPrivateIpAddressesRequest(ServiceRequest): - Ipv4Prefixes: Optional[IpPrefixList] + Ipv4Prefixes: IpPrefixList | None NetworkInterfaceId: NetworkInterfaceId - PrivateIpAddresses: Optional[PrivateIpAddressStringList] + PrivateIpAddresses: PrivateIpAddressStringList | None class UnassignPrivateNatGatewayAddressRequest(ServiceRequest): NatGatewayId: NatGatewayId PrivateIpAddresses: IpList - MaxDrainDurationSeconds: Optional[DrainSeconds] - DryRun: Optional[Boolean] + MaxDrainDurationSeconds: DrainSeconds | None + DryRun: Boolean | None class UnassignPrivateNatGatewayAddressResult(TypedDict, total=False): - NatGatewayId: Optional[NatGatewayId] - NatGatewayAddresses: Optional[NatGatewayAddressList] + NatGatewayId: NatGatewayId | None + NatGatewayAddresses: NatGatewayAddressList | None class UnlockSnapshotRequest(ServiceRequest): SnapshotId: SnapshotId - DryRun: Optional[Boolean] + DryRun: Boolean | None class UnlockSnapshotResult(TypedDict, total=False): - SnapshotId: Optional[String] + SnapshotId: String | None class UnmonitorInstancesRequest(ServiceRequest): InstanceIds: InstanceIdStringList - DryRun: Optional[Boolean] + DryRun: Boolean | None class UnmonitorInstancesResult(TypedDict, total=False): - InstanceMonitorings: Optional[InstanceMonitoringList] + InstanceMonitorings: InstanceMonitoringList | None + + +class UpdateCapacityManagerOrganizationsAccessRequest(ServiceRequest): + OrganizationsAccess: BoxedBoolean + DryRun: Boolean | None + ClientToken: String | None + + +class UpdateCapacityManagerOrganizationsAccessResult(TypedDict, total=False): + CapacityManagerStatus: CapacityManagerStatus | None + OrganizationsAccess: Boolean | None + + +class UpdateInterruptibleCapacityReservationAllocationRequest(ServiceRequest): + CapacityReservationId: CapacityReservationId + TargetInstanceCount: Integer + DryRun: Boolean | None + + +class UpdateInterruptibleCapacityReservationAllocationResult(TypedDict, total=False): + InterruptibleCapacityReservationId: CapacityReservationId | None + SourceCapacityReservationId: CapacityReservationId | None + InstanceCount: Integer | None + TargetInstanceCount: Integer | None + Status: InterruptibleCapacityReservationAllocationStatus | None + InterruptionType: InterruptionType | None class UpdateSecurityGroupRuleDescriptionsEgressRequest(ServiceRequest): - DryRun: Optional[Boolean] - GroupId: Optional[SecurityGroupId] - GroupName: Optional[SecurityGroupName] - IpPermissions: Optional[IpPermissionList] - SecurityGroupRuleDescriptions: Optional[SecurityGroupRuleDescriptionList] + DryRun: Boolean | None + GroupId: SecurityGroupId | None + GroupName: SecurityGroupName | None + IpPermissions: IpPermissionList | None + SecurityGroupRuleDescriptions: SecurityGroupRuleDescriptionList | None class UpdateSecurityGroupRuleDescriptionsEgressResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class UpdateSecurityGroupRuleDescriptionsIngressRequest(ServiceRequest): - DryRun: Optional[Boolean] - GroupId: Optional[SecurityGroupId] - GroupName: Optional[SecurityGroupName] - IpPermissions: Optional[IpPermissionList] - SecurityGroupRuleDescriptions: Optional[SecurityGroupRuleDescriptionList] + DryRun: Boolean | None + GroupId: SecurityGroupId | None + GroupName: SecurityGroupName | None + IpPermissions: IpPermissionList | None + SecurityGroupRuleDescriptions: SecurityGroupRuleDescriptionList | None class UpdateSecurityGroupRuleDescriptionsIngressResult(TypedDict, total=False): - Return: Optional[Boolean] + Return: Boolean | None class WithdrawByoipCidrRequest(ServiceRequest): Cidr: String - DryRun: Optional[Boolean] + DryRun: Boolean | None class WithdrawByoipCidrResult(TypedDict, total=False): - ByoipCidr: Optional[ByoipCidr] + ByoipCidr: ByoipCidr | None class Ec2Api: - service = "ec2" - version = "2016-11-15" + service: str = "ec2" + version: str = "2016-11-15" @handler("AcceptAddressTransfer") def accept_address_transfer( @@ -20866,7 +23414,7 @@ def allocate_hosts( client_token: String | None = None, instance_type: String | None = None, quantity: Integer | None = None, - availability_zone: String | None = None, + availability_zone: AvailabilityZoneName | None = None, **kwargs, ) -> AllocateHostsResult: raise NotImplementedError @@ -21052,6 +23600,8 @@ def associate_nat_gateway_address( allocation_ids: AllocationIdList, private_ip_addresses: IpList | None = None, dry_run: Boolean | None = None, + availability_zone: AvailabilityZoneName | None = None, + availability_zone_id: AvailabilityZoneId | None = None, **kwargs, ) -> AssociateNatGatewayAddressResult: raise NotImplementedError @@ -21073,6 +23623,7 @@ def associate_route_table( context: RequestContext, route_table_id: RouteTableId, gateway_id: RouteGatewayId | None = None, + public_ipv4_pool: Ipv4PoolEc2Id | None = None, dry_run: Boolean | None = None, subnet_id: SubnetId | None = None, **kwargs, @@ -21225,6 +23776,7 @@ def attach_volume( device: String, instance_id: InstanceId, volume_id: VolumeId, + ebs_card_index: BoxedInteger | None = None, dry_run: Boolean | None = None, **kwargs, ) -> VolumeAttachment: @@ -21444,6 +23996,8 @@ def copy_image( copy_image_tags: Boolean | None = None, tag_specifications: TagSpecificationList | None = None, snapshot_copy_completion_duration_minutes: Long | None = None, + destination_availability_zone: String | None = None, + destination_availability_zone_id: String | None = None, dry_run: Boolean | None = None, **kwargs, ) -> CopyImageResult: @@ -21463,11 +24017,44 @@ def copy_snapshot( presigned_url: CopySnapshotRequestPSU | None = None, tag_specifications: TagSpecificationList | None = None, completion_duration_minutes: SnapshotCompletionDurationMinutesRequest | None = None, + destination_availability_zone: String | None = None, dry_run: Boolean | None = None, **kwargs, ) -> CopySnapshotResult: raise NotImplementedError + @handler("CopyVolumes") + def copy_volumes( + self, + context: RequestContext, + source_volume_id: VolumeId, + iops: Integer | None = None, + size: Integer | None = None, + volume_type: VolumeType | None = None, + dry_run: Boolean | None = None, + tag_specifications: TagSpecificationList | None = None, + multi_attach_enabled: Boolean | None = None, + throughput: Integer | None = None, + client_token: String | None = None, + **kwargs, + ) -> CopyVolumesResult: + raise NotImplementedError + + @handler("CreateCapacityManagerDataExport") + def create_capacity_manager_data_export( + self, + context: RequestContext, + s3_bucket_name: String, + schedule: Schedule, + output_format: OutputFormat, + s3_bucket_prefix: String | None = None, + client_token: String | None = None, + dry_run: Boolean | None = None, + tag_specifications: TagSpecificationList | None = None, + **kwargs, + ) -> CreateCapacityManagerDataExportResult: + raise NotImplementedError + @handler("CreateCapacityReservation") def create_capacity_reservation( self, @@ -21541,10 +24128,10 @@ def create_carrier_gateway( def create_client_vpn_endpoint( self, context: RequestContext, - client_cidr_block: String, server_certificate_arn: String, authentication_options: ClientVpnAuthenticationRequestList, connection_log_options: ConnectionLogOptions, + client_cidr_block: String | None = None, dns_servers: ValueStringList | None = None, transport_protocol: TransportProtocol | None = None, vpn_port: Integer | None = None, @@ -21561,6 +24148,8 @@ def create_client_vpn_endpoint( client_login_banner_options: ClientLoginBannerOptions | None = None, client_route_enforcement_options: ClientRouteEnforcementOptions | None = None, disconnect_on_session_timeout: Boolean | None = None, + endpoint_ip_address_type: EndpointIpAddressType | None = None, + traffic_ip_address_type: TrafficIpAddressType | None = None, **kwargs, ) -> CreateClientVpnEndpointResult: raise NotImplementedError @@ -21611,9 +24200,10 @@ def create_customer_gateway( def create_default_subnet( self, context: RequestContext, - availability_zone: AvailabilityZoneName, + availability_zone: AvailabilityZoneName | None = None, dry_run: Boolean | None = None, ipv6_native: Boolean | None = None, + availability_zone_id: AvailabilityZoneId | None = None, **kwargs, ) -> CreateDefaultSubnetResult: raise NotImplementedError @@ -21712,11 +24302,25 @@ def create_image( tag_specifications: TagSpecificationList | None = None, snapshot_location: SnapshotLocationEnum | None = None, dry_run: Boolean | None = None, - description: String | None = None, - no_reboot: Boolean | None = None, - block_device_mappings: BlockDeviceMappingRequestList | None = None, + description: String | None = None, + no_reboot: Boolean | None = None, + block_device_mappings: BlockDeviceMappingRequestList | None = None, + **kwargs, + ) -> CreateImageResult: + raise NotImplementedError + + @handler("CreateImageUsageReport") + def create_image_usage_report( + self, + context: RequestContext, + image_id: ImageId, + resource_types: ImageUsageResourceTypeRequestList, + dry_run: Boolean | None = None, + account_ids: ImageUsageReportUserIdStringList | None = None, + client_token: String | None = None, + tag_specifications: TagSpecificationList | None = None, **kwargs, - ) -> CreateImageResult: + ) -> CreateImageUsageReportResult: raise NotImplementedError @handler("CreateInstanceConnectEndpoint") @@ -21729,6 +24333,7 @@ def create_instance_connect_endpoint( preserve_client_ip: Boolean | None = None, client_token: String | None = None, tag_specifications: TagSpecificationList | None = None, + ip_address_type: IpAddressType | None = None, **kwargs, ) -> CreateInstanceConnectEndpointResult: raise NotImplementedError @@ -21769,6 +24374,19 @@ def create_internet_gateway( ) -> CreateInternetGatewayResult: raise NotImplementedError + @handler("CreateInterruptibleCapacityReservationAllocation") + def create_interruptible_capacity_reservation_allocation( + self, + context: RequestContext, + capacity_reservation_id: CapacityReservationId, + instance_count: Integer, + client_token: String | None = None, + dry_run: Boolean | None = None, + tag_specifications: TagSpecificationList | None = None, + **kwargs, + ) -> CreateInterruptibleCapacityReservationAllocationResult: + raise NotImplementedError + @handler("CreateIpam") def create_ipam( self, @@ -21797,6 +24415,18 @@ def create_ipam_external_resource_verification_token( ) -> CreateIpamExternalResourceVerificationTokenResult: raise NotImplementedError + @handler("CreateIpamPolicy") + def create_ipam_policy( + self, + context: RequestContext, + ipam_id: IpamId, + dry_run: Boolean | None = None, + tag_specifications: TagSpecificationList | None = None, + client_token: String | None = None, + **kwargs, + ) -> CreateIpamPolicyResult: + raise NotImplementedError + @handler("CreateIpamPool") def create_ipam_pool( self, @@ -21822,6 +24452,37 @@ def create_ipam_pool( ) -> CreateIpamPoolResult: raise NotImplementedError + @handler("CreateIpamPrefixListResolver") + def create_ipam_prefix_list_resolver( + self, + context: RequestContext, + ipam_id: IpamId, + address_family: AddressFamily, + dry_run: Boolean | None = None, + description: String | None = None, + rules: IpamPrefixListResolverRuleRequestSet | None = None, + tag_specifications: TagSpecificationList | None = None, + client_token: String | None = None, + **kwargs, + ) -> CreateIpamPrefixListResolverResult: + raise NotImplementedError + + @handler("CreateIpamPrefixListResolverTarget") + def create_ipam_prefix_list_resolver_target( + self, + context: RequestContext, + ipam_prefix_list_resolver_id: IpamPrefixListResolverId, + prefix_list_id: String, + prefix_list_region: String, + track_latest_version: Boolean, + dry_run: Boolean | None = None, + desired_version: BoxedLong | None = None, + tag_specifications: TagSpecificationList | None = None, + client_token: String | None = None, + **kwargs, + ) -> CreateIpamPrefixListResolverTargetResult: + raise NotImplementedError + @handler("CreateIpamResourceDiscovery") def create_ipam_resource_discovery( self, @@ -21844,6 +24505,7 @@ def create_ipam_scope( description: String | None = None, tag_specifications: TagSpecificationList | None = None, client_token: String | None = None, + external_authority_configuration: ExternalAuthorityConfiguration | None = None, **kwargs, ) -> CreateIpamScopeResult: raise NotImplementedError @@ -22007,10 +24669,13 @@ def create_managed_prefix_list( def create_nat_gateway( self, context: RequestContext, - subnet_id: SubnetId, + availability_mode: AvailabilityMode | None = None, allocation_id: AllocationId | None = None, client_token: String | None = None, dry_run: Boolean | None = None, + subnet_id: SubnetId | None = None, + vpc_id: VpcId | None = None, + availability_zone_addresses: AvailabilityZoneAddresses | None = None, tag_specifications: TagSpecificationList | None = None, connectivity_type: ConnectivityType | None = None, private_ip_address: String | None = None, @@ -22130,6 +24795,8 @@ def create_placement_group( partition_count: Integer | None = None, tag_specifications: TagSpecificationList | None = None, spread_level: SpreadLevel | None = None, + linked_group_id: PlacementGroupId | None = None, + operator: OperatorRequest | None = None, dry_run: Boolean | None = None, group_name: String | None = None, strategy: PlacementStrategy | None = None, @@ -22267,6 +24934,34 @@ def create_route_table( ) -> CreateRouteTableResult: raise NotImplementedError + @handler("CreateSecondaryNetwork") + def create_secondary_network( + self, + context: RequestContext, + ipv4_cidr_block: String, + network_type: SecondaryNetworkType, + client_token: String | None = None, + dry_run: Boolean | None = None, + tag_specifications: TagSpecificationList | None = None, + **kwargs, + ) -> CreateSecondaryNetworkResult: + raise NotImplementedError + + @handler("CreateSecondarySubnet") + def create_secondary_subnet( + self, + context: RequestContext, + ipv4_cidr_block: String, + secondary_network_id: SecondaryNetworkId, + client_token: String | None = None, + availability_zone: AvailabilityZoneName | None = None, + availability_zone_id: AvailabilityZoneId | None = None, + dry_run: Boolean | None = None, + tag_specifications: TagSpecificationList | None = None, + **kwargs, + ) -> CreateSecondarySubnetResult: + raise NotImplementedError + @handler("CreateSecurityGroup") def create_security_group( self, @@ -22483,6 +25178,40 @@ def create_transit_gateway_connect_peer( ) -> CreateTransitGatewayConnectPeerResult: raise NotImplementedError + @handler("CreateTransitGatewayMeteringPolicy") + def create_transit_gateway_metering_policy( + self, + context: RequestContext, + transit_gateway_id: TransitGatewayId, + middlebox_attachment_ids: TransitGatewayAttachmentIdStringList | None = None, + tag_specifications: TagSpecificationList | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> CreateTransitGatewayMeteringPolicyResult: + raise NotImplementedError + + @handler("CreateTransitGatewayMeteringPolicyEntry") + def create_transit_gateway_metering_policy_entry( + self, + context: RequestContext, + transit_gateway_metering_policy_id: TransitGatewayMeteringPolicyId, + policy_rule_number: Integer, + metered_account: TransitGatewayMeteringPayerType, + source_transit_gateway_attachment_id: TransitGatewayAttachmentId | None = None, + source_transit_gateway_attachment_type: TransitGatewayAttachmentResourceType | None = None, + source_cidr_block: String | None = None, + source_port_range: String | None = None, + destination_transit_gateway_attachment_id: TransitGatewayAttachmentId | None = None, + destination_transit_gateway_attachment_type: TransitGatewayAttachmentResourceType + | None = None, + destination_cidr_block: String | None = None, + destination_port_range: String | None = None, + protocol: String | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> CreateTransitGatewayMeteringPolicyEntryResult: + raise NotImplementedError + @handler("CreateTransitGatewayMulticastDomain") def create_transit_gateway_multicast_domain( self, @@ -22663,7 +25392,8 @@ def create_verified_access_trust_provider( def create_volume( self, context: RequestContext, - availability_zone: AvailabilityZoneName, + availability_zone: AvailabilityZoneName | None = None, + availability_zone_id: AvailabilityZoneId | None = None, encrypted: Boolean | None = None, iops: Integer | None = None, kms_key_id: KmsKeyId | None = None, @@ -22694,6 +25424,7 @@ def create_vpc( ipv6_ipam_pool_id: IpamPoolId | None = None, ipv6_netmask_length: NetmaskLength | None = None, ipv6_cidr_block_network_border_group: String | None = None, + vpc_encryption_control: VpcEncryptionControlConfiguration | None = None, tag_specifications: TagSpecificationList | None = None, dry_run: Boolean | None = None, instance_tenancy: Tenancy | None = None, @@ -22715,6 +25446,17 @@ def create_vpc_block_public_access_exclusion( ) -> CreateVpcBlockPublicAccessExclusionResult: raise NotImplementedError + @handler("CreateVpcEncryptionControl") + def create_vpc_encryption_control( + self, + context: RequestContext, + vpc_id: VpcId, + dry_run: Boolean | None = None, + tag_specifications: TagSpecificationList | None = None, + **kwargs, + ) -> CreateVpcEncryptionControlResult: + raise NotImplementedError + @handler("CreateVpcEndpoint") def create_vpc_endpoint( self, @@ -22785,6 +25527,12 @@ def create_vpc_peering_connection( ) -> CreateVpcPeeringConnectionResult: raise NotImplementedError + @handler("CreateVpnConcentrator", expand=False) + def create_vpn_concentrator( + self, context: RequestContext, request: CreateVpnConcentratorRequest, **kwargs + ) -> CreateVpnConcentratorResult: + raise NotImplementedError + @handler("CreateVpnConnection", expand=False) def create_vpn_connection( self, context: RequestContext, request: CreateVpnConnectionRequest, **kwargs @@ -22807,6 +25555,16 @@ def create_vpn_gateway( ) -> CreateVpnGatewayResult: raise NotImplementedError + @handler("DeleteCapacityManagerDataExport") + def delete_capacity_manager_data_export( + self, + context: RequestContext, + capacity_manager_data_export_id: CapacityManagerDataExportId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteCapacityManagerDataExportResult: + raise NotImplementedError + @handler("DeleteCarrierGateway") def delete_carrier_gateway( self, @@ -22921,6 +25679,16 @@ def delete_fpga_image( ) -> DeleteFpgaImageResult: raise NotImplementedError + @handler("DeleteImageUsageReport") + def delete_image_usage_report( + self, + context: RequestContext, + report_id: ImageUsageReportId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteImageUsageReportResult: + raise NotImplementedError + @handler("DeleteInstanceConnectEndpoint") def delete_instance_connect_endpoint( self, @@ -22973,6 +25741,16 @@ def delete_ipam_external_resource_verification_token( ) -> DeleteIpamExternalResourceVerificationTokenResult: raise NotImplementedError + @handler("DeleteIpamPolicy") + def delete_ipam_policy( + self, + context: RequestContext, + ipam_policy_id: IpamPolicyId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteIpamPolicyResult: + raise NotImplementedError + @handler("DeleteIpamPool") def delete_ipam_pool( self, @@ -22984,6 +25762,26 @@ def delete_ipam_pool( ) -> DeleteIpamPoolResult: raise NotImplementedError + @handler("DeleteIpamPrefixListResolver") + def delete_ipam_prefix_list_resolver( + self, + context: RequestContext, + ipam_prefix_list_resolver_id: IpamPrefixListResolverId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteIpamPrefixListResolverResult: + raise NotImplementedError + + @handler("DeleteIpamPrefixListResolverTarget") + def delete_ipam_prefix_list_resolver_target( + self, + context: RequestContext, + ipam_prefix_list_resolver_target_id: IpamPrefixListResolverTargetId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteIpamPrefixListResolverTargetResult: + raise NotImplementedError + @handler("DeleteIpamResourceDiscovery") def delete_ipam_resource_discovery( self, @@ -23207,7 +26005,7 @@ def delete_network_interface_permission( def delete_placement_group( self, context: RequestContext, - group_name: PlacementGroupName, + group_name: PlacementGroupNameWithResolver, dry_run: Boolean | None = None, **kwargs, ) -> None: @@ -23287,6 +26085,28 @@ def delete_route_table( ) -> None: raise NotImplementedError + @handler("DeleteSecondaryNetwork") + def delete_secondary_network( + self, + context: RequestContext, + secondary_network_id: SecondaryNetworkId, + client_token: String | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteSecondaryNetworkResult: + raise NotImplementedError + + @handler("DeleteSecondarySubnet") + def delete_secondary_subnet( + self, + context: RequestContext, + secondary_subnet_id: SecondarySubnetId, + client_token: String | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteSecondarySubnetResult: + raise NotImplementedError + @handler("DeleteSecurityGroup") def delete_security_group( self, @@ -23411,6 +26231,27 @@ def delete_transit_gateway_connect_peer( ) -> DeleteTransitGatewayConnectPeerResult: raise NotImplementedError + @handler("DeleteTransitGatewayMeteringPolicy") + def delete_transit_gateway_metering_policy( + self, + context: RequestContext, + transit_gateway_metering_policy_id: TransitGatewayMeteringPolicyId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteTransitGatewayMeteringPolicyResult: + raise NotImplementedError + + @handler("DeleteTransitGatewayMeteringPolicyEntry") + def delete_transit_gateway_metering_policy_entry( + self, + context: RequestContext, + transit_gateway_metering_policy_id: TransitGatewayMeteringPolicyId, + policy_rule_number: Integer, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteTransitGatewayMeteringPolicyEntryResult: + raise NotImplementedError + @handler("DeleteTransitGatewayMulticastDomain") def delete_transit_gateway_multicast_domain( self, @@ -23559,6 +26400,16 @@ def delete_vpc_block_public_access_exclusion( ) -> DeleteVpcBlockPublicAccessExclusionResult: raise NotImplementedError + @handler("DeleteVpcEncryptionControl") + def delete_vpc_encryption_control( + self, + context: RequestContext, + vpc_encryption_control_id: VpcEncryptionControlId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteVpcEncryptionControlResult: + raise NotImplementedError + @handler("DeleteVpcEndpointConnectionNotifications") def delete_vpc_endpoint_connection_notifications( self, @@ -23599,6 +26450,16 @@ def delete_vpc_peering_connection( ) -> DeleteVpcPeeringConnectionResult: raise NotImplementedError + @handler("DeleteVpnConcentrator") + def delete_vpn_concentrator( + self, + context: RequestContext, + vpn_concentrator_id: VpnConcentratorId, + dry_run: Boolean | None = None, + **kwargs, + ) -> DeleteVpnConcentratorResult: + raise NotImplementedError + @handler("DeleteVpnConnection") def delete_vpn_connection( self, @@ -23853,6 +26714,7 @@ def describe_capacity_block_offerings( max_results: DescribeCapacityBlockOfferingsMaxResults | None = None, ultraserver_type: String | None = None, ultraserver_count: Integer | None = None, + all_availability_zones: Boolean | None = None, **kwargs, ) -> DescribeCapacityBlockOfferingsResult: raise NotImplementedError @@ -23883,6 +26745,19 @@ def describe_capacity_blocks( ) -> DescribeCapacityBlocksResult: raise NotImplementedError + @handler("DescribeCapacityManagerDataExports") + def describe_capacity_manager_data_exports( + self, + context: RequestContext, + capacity_manager_data_export_ids: CapacityManagerDataExportIdSet | None = None, + max_results: DescribeCapacityManagerDataExportsRequestMaxResults | None = None, + next_token: String | None = None, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + **kwargs, + ) -> DescribeCapacityManagerDataExportsResult: + raise NotImplementedError + @handler("DescribeCapacityReservationBillingRequests") def describe_capacity_reservation_billing_requests( self, @@ -23910,6 +26785,19 @@ def describe_capacity_reservation_fleets( ) -> DescribeCapacityReservationFleetsResult: raise NotImplementedError + @handler("DescribeCapacityReservationTopology") + def describe_capacity_reservation_topology( + self, + context: RequestContext, + dry_run: Boolean | None = None, + next_token: String | None = None, + max_results: DescribeCapacityReservationTopologyMaxResults | None = None, + capacity_reservation_ids: CapacityReservationIdSet | None = None, + filters: FilterList | None = None, + **kwargs, + ) -> DescribeCapacityReservationTopologyResult: + raise NotImplementedError + @handler("DescribeCapacityReservations") def describe_capacity_reservations( self, @@ -24303,6 +27191,48 @@ def describe_image_attribute( ) -> ImageAttribute: raise NotImplementedError + @handler("DescribeImageReferences") + def describe_image_references( + self, + context: RequestContext, + image_ids: DescribeImageReferencesImageIdStringList, + include_all_resource_types: Boolean | None = None, + resource_types: ResourceTypeRequestList | None = None, + next_token: String | None = None, + dry_run: Boolean | None = None, + max_results: DescribeImageReferencesMaxResults | None = None, + **kwargs, + ) -> DescribeImageReferencesResult: + raise NotImplementedError + + @handler("DescribeImageUsageReportEntries") + def describe_image_usage_report_entries( + self, + context: RequestContext, + image_ids: DescribeImageUsageReportsImageIdStringList | None = None, + report_ids: ImageUsageReportIdStringList | None = None, + next_token: String | None = None, + filters: FilterList | None = None, + dry_run: Boolean | None = None, + max_results: DescribeImageUsageReportEntriesMaxResults | None = None, + **kwargs, + ) -> DescribeImageUsageReportEntriesResult: + raise NotImplementedError + + @handler("DescribeImageUsageReports") + def describe_image_usage_reports( + self, + context: RequestContext, + image_ids: DescribeImageUsageReportsImageIdStringList | None = None, + report_ids: ImageUsageReportIdStringList | None = None, + next_token: String | None = None, + filters: FilterList | None = None, + dry_run: Boolean | None = None, + max_results: DescribeImageUsageReportsMaxResults | None = None, + **kwargs, + ) -> DescribeImageUsageReportsResult: + raise NotImplementedError + @handler("DescribeImages") def describe_images( self, @@ -24415,6 +27345,34 @@ def describe_instance_image_metadata( ) -> DescribeInstanceImageMetadataResult: raise NotImplementedError + @handler("DescribeInstanceSqlHaHistoryStates") + def describe_instance_sql_ha_history_states( + self, + context: RequestContext, + instance_ids: InstanceIdStringList | None = None, + start_time: MillisecondDateTime | None = None, + end_time: MillisecondDateTime | None = None, + next_token: NextToken | None = None, + max_results: DescribeInstanceSqlHaStatesRequestMaxResultsInteger | None = None, + filters: FilterList | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> DescribeInstanceSqlHaHistoryStatesResult: + raise NotImplementedError + + @handler("DescribeInstanceSqlHaStates") + def describe_instance_sql_ha_states( + self, + context: RequestContext, + instance_ids: InstanceIdStringList | None = None, + next_token: NextToken | None = None, + max_results: DescribeInstanceSqlHaStatesRequestMaxResultsInteger | None = None, + filters: FilterList | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> DescribeInstanceSqlHaStatesResult: + raise NotImplementedError + @handler("DescribeInstanceStatus") def describe_instance_status( self, @@ -24516,20 +27474,60 @@ def describe_ipam_external_resource_verification_tokens( max_results: IpamMaxResults | None = None, ipam_external_resource_verification_token_ids: ValueStringList | None = None, **kwargs, - ) -> DescribeIpamExternalResourceVerificationTokensResult: + ) -> DescribeIpamExternalResourceVerificationTokensResult: + raise NotImplementedError + + @handler("DescribeIpamPolicies") + def describe_ipam_policies( + self, + context: RequestContext, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + max_results: IpamMaxResults | None = None, + next_token: NextToken | None = None, + ipam_policy_ids: ValueStringList | None = None, + **kwargs, + ) -> DescribeIpamPoliciesResult: + raise NotImplementedError + + @handler("DescribeIpamPools") + def describe_ipam_pools( + self, + context: RequestContext, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + max_results: IpamMaxResults | None = None, + next_token: NextToken | None = None, + ipam_pool_ids: ValueStringList | None = None, + **kwargs, + ) -> DescribeIpamPoolsResult: + raise NotImplementedError + + @handler("DescribeIpamPrefixListResolverTargets") + def describe_ipam_prefix_list_resolver_targets( + self, + context: RequestContext, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + max_results: IpamMaxResults | None = None, + next_token: NextToken | None = None, + ipam_prefix_list_resolver_target_ids: ValueStringList | None = None, + ipam_prefix_list_resolver_id: IpamPrefixListResolverId | None = None, + **kwargs, + ) -> DescribeIpamPrefixListResolverTargetsResult: raise NotImplementedError - @handler("DescribeIpamPools") - def describe_ipam_pools( + @handler("DescribeIpamPrefixListResolvers") + def describe_ipam_prefix_list_resolvers( self, context: RequestContext, dry_run: Boolean | None = None, filters: FilterList | None = None, max_results: IpamMaxResults | None = None, next_token: NextToken | None = None, - ipam_pool_ids: ValueStringList | None = None, + ipam_prefix_list_resolver_ids: ValueStringList | None = None, **kwargs, - ) -> DescribeIpamPoolsResult: + ) -> DescribeIpamPrefixListResolversResult: raise NotImplementedError @handler("DescribeIpamResourceDiscoveries") @@ -25137,6 +28135,45 @@ def describe_scheduled_instances( ) -> DescribeScheduledInstancesResult: raise NotImplementedError + @handler("DescribeSecondaryInterfaces") + def describe_secondary_interfaces( + self, + context: RequestContext, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + max_results: DescribeSecondaryInterfacesMaxResults | None = None, + next_token: String | None = None, + secondary_interface_ids: SecondaryInterfaceIdList | None = None, + **kwargs, + ) -> DescribeSecondaryInterfacesResult: + raise NotImplementedError + + @handler("DescribeSecondaryNetworks") + def describe_secondary_networks( + self, + context: RequestContext, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + max_results: DescribeSecondaryNetworksMaxResults | None = None, + next_token: String | None = None, + secondary_network_ids: SecondaryNetworkIdList | None = None, + **kwargs, + ) -> DescribeSecondaryNetworksResult: + raise NotImplementedError + + @handler("DescribeSecondarySubnets") + def describe_secondary_subnets( + self, + context: RequestContext, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + max_results: DescribeSecondarySubnetsMaxResults | None = None, + next_token: String | None = None, + secondary_subnet_ids: SecondarySubnetIdList | None = None, + **kwargs, + ) -> DescribeSecondarySubnetsResult: + raise NotImplementedError + @handler("DescribeSecurityGroupReferences") def describe_security_group_references( self, context: RequestContext, group_id: GroupIds, dry_run: Boolean | None = None, **kwargs @@ -25294,6 +28331,7 @@ def describe_spot_instance_requests( def describe_spot_price_history( self, context: RequestContext, + availability_zone_id: AvailabilityZoneId | None = None, dry_run: Boolean | None = None, start_time: DateTime | None = None, end_time: DateTime | None = None, @@ -25449,6 +28487,19 @@ def describe_transit_gateway_connects( ) -> DescribeTransitGatewayConnectsResult: raise NotImplementedError + @handler("DescribeTransitGatewayMeteringPolicies") + def describe_transit_gateway_metering_policies( + self, + context: RequestContext, + transit_gateway_metering_policy_ids: TransitGatewayMeteringPolicyIdStringList | None = None, + filters: FilterList | None = None, + max_results: TransitGatewayMaxResults | None = None, + next_token: String | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> DescribeTransitGatewayMeteringPoliciesResult: + raise NotImplementedError + @handler("DescribeTransitGatewayMulticastDomains") def describe_transit_gateway_multicast_domains( self, @@ -25725,6 +28776,20 @@ def describe_vpc_classic_link_dns_support( ) -> DescribeVpcClassicLinkDnsSupportResult: raise NotImplementedError + @handler("DescribeVpcEncryptionControls") + def describe_vpc_encryption_controls( + self, + context: RequestContext, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + vpc_encryption_control_ids: VpcEncryptionControlIdList | None = None, + vpc_ids: VpcIdStringList | None = None, + next_token: String | None = None, + max_results: DescribeVpcEncryptionControlsMaxResults | None = None, + **kwargs, + ) -> DescribeVpcEncryptionControlsResult: + raise NotImplementedError + @handler("DescribeVpcEndpointAssociations") def describe_vpc_endpoint_associations( self, @@ -25842,6 +28907,19 @@ def describe_vpcs( ) -> DescribeVpcsResult: raise NotImplementedError + @handler("DescribeVpnConcentrators") + def describe_vpn_concentrators( + self, + context: RequestContext, + vpn_concentrator_ids: VpnConcentratorIdStringList | None = None, + filters: FilterList | None = None, + max_results: GVCDMaxResults | None = None, + next_token: NextToken | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> DescribeVpnConcentratorsResult: + raise NotImplementedError + @handler("DescribeVpnConnections") def describe_vpn_connections( self, @@ -25962,6 +29040,16 @@ def disable_aws_network_performance_metric_subscription( ) -> DisableAwsNetworkPerformanceMetricSubscriptionResult: raise NotImplementedError + @handler("DisableCapacityManager") + def disable_capacity_manager( + self, + context: RequestContext, + dry_run: Boolean | None = None, + client_token: String | None = None, + **kwargs, + ) -> DisableCapacityManagerResult: + raise NotImplementedError + @handler("DisableEbsEncryptionByDefault") def disable_ebs_encryption_by_default( self, context: RequestContext, dry_run: Boolean | None = None, **kwargs @@ -25983,8 +29071,9 @@ def disable_fast_launch( def disable_fast_snapshot_restores( self, context: RequestContext, - availability_zones: AvailabilityZoneStringList, source_snapshot_ids: SnapshotIdStringList, + availability_zones: AvailabilityZoneStringList | None = None, + availability_zone_ids: AvailabilityZoneIdStringList | None = None, dry_run: Boolean | None = None, **kwargs, ) -> DisableFastSnapshotRestoresResult: @@ -26014,6 +29103,16 @@ def disable_image_deregistration_protection( ) -> DisableImageDeregistrationProtectionResult: raise NotImplementedError + @handler("DisableInstanceSqlHaStandbyDetections") + def disable_instance_sql_ha_standby_detections( + self, + context: RequestContext, + instance_ids: InstanceIdUpdateStringList, + dry_run: Boolean | None = None, + **kwargs, + ) -> DisableInstanceSqlHaStandbyDetectionsResult: + raise NotImplementedError + @handler("DisableIpamOrganizationAdminAccount") def disable_ipam_organization_admin_account( self, @@ -26024,6 +29123,17 @@ def disable_ipam_organization_admin_account( ) -> DisableIpamOrganizationAdminAccountResult: raise NotImplementedError + @handler("DisableIpamPolicy") + def disable_ipam_policy( + self, + context: RequestContext, + ipam_policy_id: IpamPolicyId, + dry_run: Boolean | None = None, + organization_target_id: String | None = None, + **kwargs, + ) -> DisableIpamPolicyResult: + raise NotImplementedError + @handler("DisableRouteServerPropagation") def disable_route_server_propagation( self, @@ -26300,6 +29410,17 @@ def enable_aws_network_performance_metric_subscription( ) -> EnableAwsNetworkPerformanceMetricSubscriptionResult: raise NotImplementedError + @handler("EnableCapacityManager") + def enable_capacity_manager( + self, + context: RequestContext, + organizations_access: Boolean | None = None, + dry_run: Boolean | None = None, + client_token: String | None = None, + **kwargs, + ) -> EnableCapacityManagerResult: + raise NotImplementedError + @handler("EnableEbsEncryptionByDefault") def enable_ebs_encryption_by_default( self, context: RequestContext, dry_run: Boolean | None = None, **kwargs @@ -26324,8 +29445,9 @@ def enable_fast_launch( def enable_fast_snapshot_restores( self, context: RequestContext, - availability_zones: AvailabilityZoneStringList, source_snapshot_ids: SnapshotIdStringList, + availability_zones: AvailabilityZoneStringList | None = None, + availability_zone_ids: AvailabilityZoneIdStringList | None = None, dry_run: Boolean | None = None, **kwargs, ) -> EnableFastSnapshotRestoresResult: @@ -26369,6 +29491,17 @@ def enable_image_deregistration_protection( ) -> EnableImageDeregistrationProtectionResult: raise NotImplementedError + @handler("EnableInstanceSqlHaStandbyDetections") + def enable_instance_sql_ha_standby_detections( + self, + context: RequestContext, + instance_ids: InstanceIdUpdateStringList, + sql_server_credentials: SecretArn | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> EnableInstanceSqlHaStandbyDetectionsResult: + raise NotImplementedError + @handler("EnableIpamOrganizationAdminAccount") def enable_ipam_organization_admin_account( self, @@ -26379,6 +29512,17 @@ def enable_ipam_organization_admin_account( ) -> EnableIpamOrganizationAdminAccountResult: raise NotImplementedError + @handler("EnableIpamPolicy") + def enable_ipam_policy( + self, + context: RequestContext, + ipam_policy_id: IpamPolicyId, + dry_run: Boolean | None = None, + organization_target_id: String | None = None, + **kwargs, + ) -> EnableIpamPolicyResult: + raise NotImplementedError + @handler("EnableReachabilityAnalyzerOrganizationSharing") def enable_reachability_analyzer_organization_sharing( self, context: RequestContext, dry_run: Boolean | None = None, **kwargs @@ -26565,6 +29709,45 @@ def get_aws_network_performance_data( ) -> GetAwsNetworkPerformanceDataResult: raise NotImplementedError + @handler("GetCapacityManagerAttributes") + def get_capacity_manager_attributes( + self, context: RequestContext, dry_run: Boolean | None = None, **kwargs + ) -> GetCapacityManagerAttributesResult: + raise NotImplementedError + + @handler("GetCapacityManagerMetricData") + def get_capacity_manager_metric_data( + self, + context: RequestContext, + metric_names: MetricSet, + start_time: MillisecondDateTime, + end_time: MillisecondDateTime, + period: Period, + group_by: GroupBySet | None = None, + filter_by: CapacityManagerConditionSet | None = None, + max_results: MaxResults | None = None, + next_token: NextToken | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> GetCapacityManagerMetricDataResult: + raise NotImplementedError + + @handler("GetCapacityManagerMetricDimensions") + def get_capacity_manager_metric_dimensions( + self, + context: RequestContext, + group_by: GroupBySet, + start_time: MillisecondDateTime, + end_time: MillisecondDateTime, + metric_names: MetricSet, + filter_by: CapacityManagerConditionSet | None = None, + max_results: MaxResults | None = None, + next_token: NextToken | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> GetCapacityManagerMetricDimensionsResult: + raise NotImplementedError + @handler("GetCapacityReservationUsage") def get_capacity_reservation_usage( self, @@ -26644,6 +29827,12 @@ def get_ebs_encryption_by_default( ) -> GetEbsEncryptionByDefaultResult: raise NotImplementedError + @handler("GetEnabledIpamPolicy") + def get_enabled_ipam_policy( + self, context: RequestContext, dry_run: Boolean | None = None, **kwargs + ) -> GetEnabledIpamPolicyResult: + raise NotImplementedError + @handler("GetFlowLogsIntegrationTemplate") def get_flow_logs_integration_template( self, @@ -26678,6 +29867,12 @@ def get_host_reservation_purchase_preview( ) -> GetHostReservationPurchasePreviewResult: raise NotImplementedError + @handler("GetImageAncestry") + def get_image_ancestry( + self, context: RequestContext, image_id: ImageId, dry_run: Boolean | None = None, **kwargs + ) -> GetImageAncestryResult: + raise NotImplementedError + @handler("GetImageBlockPublicAccessState") def get_image_block_public_access_state( self, context: RequestContext, dry_run: Boolean | None = None, **kwargs @@ -26779,6 +29974,34 @@ def get_ipam_discovered_resource_cidrs( ) -> GetIpamDiscoveredResourceCidrsResult: raise NotImplementedError + @handler("GetIpamPolicyAllocationRules") + def get_ipam_policy_allocation_rules( + self, + context: RequestContext, + ipam_policy_id: IpamPolicyId, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + locale: String | None = None, + resource_type: IpamPolicyResourceType | None = None, + max_results: IpamMaxResults | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> GetIpamPolicyAllocationRulesResult: + raise NotImplementedError + + @handler("GetIpamPolicyOrganizationTargets") + def get_ipam_policy_organization_targets( + self, + context: RequestContext, + ipam_policy_id: IpamPolicyId, + dry_run: Boolean | None = None, + max_results: IpamMaxResults | None = None, + next_token: NextToken | None = None, + filters: FilterList | None = None, + **kwargs, + ) -> GetIpamPolicyOrganizationTargetsResult: + raise NotImplementedError + @handler("GetIpamPoolAllocations") def get_ipam_pool_allocations( self, @@ -26806,6 +30029,46 @@ def get_ipam_pool_cidrs( ) -> GetIpamPoolCidrsResult: raise NotImplementedError + @handler("GetIpamPrefixListResolverRules") + def get_ipam_prefix_list_resolver_rules( + self, + context: RequestContext, + ipam_prefix_list_resolver_id: IpamPrefixListResolverId, + dry_run: Boolean | None = None, + filters: FilterList | None = None, + max_results: IpamMaxResults | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> GetIpamPrefixListResolverRulesResult: + raise NotImplementedError + + @handler("GetIpamPrefixListResolverVersionEntries") + def get_ipam_prefix_list_resolver_version_entries( + self, + context: RequestContext, + ipam_prefix_list_resolver_id: IpamPrefixListResolverId, + ipam_prefix_list_resolver_version: Long, + dry_run: Boolean | None = None, + max_results: IpamMaxResults | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> GetIpamPrefixListResolverVersionEntriesResult: + raise NotImplementedError + + @handler("GetIpamPrefixListResolverVersions") + def get_ipam_prefix_list_resolver_versions( + self, + context: RequestContext, + ipam_prefix_list_resolver_id: IpamPrefixListResolverId, + dry_run: Boolean | None = None, + ipam_prefix_list_resolver_versions: IpamPrefixListResolverVersionNumberSet | None = None, + max_results: IpamMaxResults | None = None, + filters: FilterList | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> GetIpamPrefixListResolverVersionsResult: + raise NotImplementedError + @handler("GetIpamResourceCidrs") def get_ipam_resource_cidrs( self, @@ -27004,6 +30267,19 @@ def get_transit_gateway_attachment_propagations( ) -> GetTransitGatewayAttachmentPropagationsResult: raise NotImplementedError + @handler("GetTransitGatewayMeteringPolicyEntries") + def get_transit_gateway_metering_policy_entries( + self, + context: RequestContext, + transit_gateway_metering_policy_id: TransitGatewayMeteringPolicyId, + filters: FilterList | None = None, + max_results: TransitGatewayMaxResults | None = None, + next_token: String | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> GetTransitGatewayMeteringPolicyEntriesResult: + raise NotImplementedError + @handler("GetTransitGatewayMulticastDomainAssociations") def get_transit_gateway_multicast_domain_associations( self, @@ -27114,6 +30390,18 @@ def get_verified_access_group_policy( ) -> GetVerifiedAccessGroupPolicyResult: raise NotImplementedError + @handler("GetVpcResourcesBlockingEncryptionEnforcement") + def get_vpc_resources_blocking_encryption_enforcement( + self, + context: RequestContext, + vpc_id: VpcId, + max_results: GetVpcResourcesBlockingEncryptionEnforcementMaxResults | None = None, + next_token: String | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> GetVpcResourcesBlockingEncryptionEnforcementResult: + raise NotImplementedError + @handler("GetVpnConnectionDeviceSampleConfiguration") def get_vpn_connection_device_sample_configuration( self, @@ -27230,10 +30518,11 @@ def import_snapshot( def import_volume( self, context: RequestContext, - availability_zone: String, image: DiskImageDetail, volume: VolumeDetail, + availability_zone_id: AvailabilityZoneId | None = None, dry_run: Boolean | None = None, + availability_zone: String | None = None, description: String | None = None, **kwargs, ) -> ImportVolumeResult: @@ -27263,6 +30552,18 @@ def list_snapshots_in_recycle_bin( ) -> ListSnapshotsInRecycleBinResult: raise NotImplementedError + @handler("ListVolumesInRecycleBin") + def list_volumes_in_recycle_bin( + self, + context: RequestContext, + volume_ids: VolumeIdStringList | None = None, + dry_run: Boolean | None = None, + max_results: Integer | None = None, + next_token: String | None = None, + **kwargs, + ) -> ListVolumesInRecycleBinResult: + raise NotImplementedError + @handler("LockSnapshot") def lock_snapshot( self, @@ -27485,13 +30786,27 @@ def modify_instance_capacity_reservation_attributes( ) -> ModifyInstanceCapacityReservationAttributesResult: raise NotImplementedError + @handler("ModifyInstanceConnectEndpoint") + def modify_instance_connect_endpoint( + self, + context: RequestContext, + instance_connect_endpoint_id: InstanceConnectEndpointId, + dry_run: Boolean | None = None, + ip_address_type: IpAddressType | None = None, + security_group_ids: SecurityGroupIdStringListRequest | None = None, + preserve_client_ip: Boolean | None = None, + **kwargs, + ) -> ModifyInstanceConnectEndpointResult: + raise NotImplementedError + @handler("ModifyInstanceCpuOptions") def modify_instance_cpu_options( self, context: RequestContext, instance_id: InstanceId, - core_count: Integer, - threads_per_core: Integer, + core_count: Integer | None = None, + threads_per_core: Integer | None = None, + nested_virtualization: NestedVirtualizationSpecification | None = None, dry_run: Boolean | None = None, **kwargs, ) -> ModifyInstanceCpuOptionsResult: @@ -27554,6 +30869,7 @@ def modify_instance_metadata_defaults( http_endpoint: DefaultInstanceMetadataEndpointState | None = None, instance_metadata_tags: DefaultInstanceMetadataTagsState | None = None, dry_run: Boolean | None = None, + http_tokens_enforced: DefaultHttpTokensEnforcedState | None = None, **kwargs, ) -> ModifyInstanceMetadataDefaultsResult: raise NotImplementedError @@ -27616,6 +30932,19 @@ def modify_ipam( ) -> ModifyIpamResult: raise NotImplementedError + @handler("ModifyIpamPolicyAllocationRules") + def modify_ipam_policy_allocation_rules( + self, + context: RequestContext, + ipam_policy_id: IpamPolicyId, + locale: String, + resource_type: IpamPolicyResourceType, + dry_run: Boolean | None = None, + allocation_rules: IpamPolicyAllocationRuleListRequest | None = None, + **kwargs, + ) -> ModifyIpamPolicyAllocationRulesResult: + raise NotImplementedError + @handler("ModifyIpamPool") def modify_ipam_pool( self, @@ -27634,6 +30963,31 @@ def modify_ipam_pool( ) -> ModifyIpamPoolResult: raise NotImplementedError + @handler("ModifyIpamPrefixListResolver") + def modify_ipam_prefix_list_resolver( + self, + context: RequestContext, + ipam_prefix_list_resolver_id: IpamPrefixListResolverId, + dry_run: Boolean | None = None, + description: String | None = None, + rules: IpamPrefixListResolverRuleRequestSet | None = None, + **kwargs, + ) -> ModifyIpamPrefixListResolverResult: + raise NotImplementedError + + @handler("ModifyIpamPrefixListResolverTarget") + def modify_ipam_prefix_list_resolver_target( + self, + context: RequestContext, + ipam_prefix_list_resolver_target_id: IpamPrefixListResolverTargetId, + dry_run: Boolean | None = None, + desired_version: BoxedLong | None = None, + track_latest_version: BoxedBoolean | None = None, + client_token: String | None = None, + **kwargs, + ) -> ModifyIpamPrefixListResolverTargetResult: + raise NotImplementedError + @handler("ModifyIpamResourceCidr") def modify_ipam_resource_cidr( self, @@ -27672,6 +31026,8 @@ def modify_ipam_scope( ipam_scope_id: IpamScopeId, dry_run: Boolean | None = None, description: String | None = None, + external_authority_configuration: ExternalAuthorityConfiguration | None = None, + remove_external_authority_configuration: Boolean | None = None, **kwargs, ) -> ModifyIpamScopeResult: raise NotImplementedError @@ -27714,6 +31070,7 @@ def modify_managed_prefix_list( add_entries: AddPrefixListEntries | None = None, remove_entries: RemovePrefixListEntries | None = None, max_entries: Integer | None = None, + ipam_prefix_list_resolver_sync_enabled: BoxedBoolean | None = None, **kwargs, ) -> ModifyManagedPrefixListResult: raise NotImplementedError @@ -27908,6 +31265,18 @@ def modify_transit_gateway( ) -> ModifyTransitGatewayResult: raise NotImplementedError + @handler("ModifyTransitGatewayMeteringPolicy") + def modify_transit_gateway_metering_policy( + self, + context: RequestContext, + transit_gateway_metering_policy_id: TransitGatewayMeteringPolicyId, + add_middlebox_attachment_ids: TransitGatewayAttachmentIdStringList | None = None, + remove_middlebox_attachment_ids: TransitGatewayAttachmentIdStringList | None = None, + dry_run: Boolean | None = None, + **kwargs, + ) -> ModifyTransitGatewayMeteringPolicyResult: + raise NotImplementedError + @handler("ModifyTransitGatewayPrefixListReference") def modify_transit_gateway_prefix_list_reference( self, @@ -28093,6 +31462,26 @@ def modify_vpc_block_public_access_options( ) -> ModifyVpcBlockPublicAccessOptionsResult: raise NotImplementedError + @handler("ModifyVpcEncryptionControl") + def modify_vpc_encryption_control( + self, + context: RequestContext, + vpc_encryption_control_id: VpcEncryptionControlId, + dry_run: Boolean | None = None, + mode: VpcEncryptionControlMode | None = None, + internet_gateway_exclusion: VpcEncryptionControlExclusionStateInput | None = None, + egress_only_internet_gateway_exclusion: VpcEncryptionControlExclusionStateInput + | None = None, + nat_gateway_exclusion: VpcEncryptionControlExclusionStateInput | None = None, + virtual_private_gateway_exclusion: VpcEncryptionControlExclusionStateInput | None = None, + vpc_peering_exclusion: VpcEncryptionControlExclusionStateInput | None = None, + lambda_exclusion: VpcEncryptionControlExclusionStateInput | None = None, + vpc_lattice_exclusion: VpcEncryptionControlExclusionStateInput | None = None, + elastic_file_system_exclusion: VpcEncryptionControlExclusionStateInput | None = None, + **kwargs, + ) -> ModifyVpcEncryptionControlResult: + raise NotImplementedError + @handler("ModifyVpcEndpoint") def modify_vpc_endpoint( self, @@ -28829,6 +32218,12 @@ def restore_snapshot_tier( ) -> RestoreSnapshotTierResult: raise NotImplementedError + @handler("RestoreVolumeFromRecycleBin") + def restore_volume_from_recycle_bin( + self, context: RequestContext, volume_id: VolumeId, dry_run: Boolean | None = None, **kwargs + ) -> RestoreVolumeFromRecycleBinResult: + raise NotImplementedError + @handler("RevokeClientVpnIngress") def revoke_client_vpn_ingress( self, @@ -28917,6 +32312,7 @@ def run_instances( enable_primary_ipv6: Boolean | None = None, network_performance_options: InstanceNetworkPerformanceOptionsRequest | None = None, operator: OperatorRequest | None = None, + secondary_interfaces: InstanceSecondaryInterfaceSpecificationListRequest | None = None, dry_run: Boolean | None = None, disable_api_termination: Boolean | None = None, instance_initiated_shutdown_behavior: ShutdownBehavior | None = None, @@ -28977,6 +32373,7 @@ def search_transit_gateway_routes( filters: FilterList, max_results: TransitGatewayMaxResults | None = None, dry_run: Boolean | None = None, + next_token: String | None = None, **kwargs, ) -> SearchTransitGatewayRoutesResult: raise NotImplementedError @@ -29058,6 +32455,7 @@ def stop_instances( context: RequestContext, instance_ids: InstanceIdStringList, hibernate: Boolean | None = None, + skip_os_shutdown: Boolean | None = None, dry_run: Boolean | None = None, force: Boolean | None = None, **kwargs, @@ -29081,6 +32479,8 @@ def terminate_instances( self, context: RequestContext, instance_ids: InstanceIdStringList, + force: Boolean | None = None, + skip_os_shutdown: Boolean | None = None, dry_run: Boolean | None = None, **kwargs, ) -> TerminateInstancesResult: @@ -29140,6 +32540,28 @@ def unmonitor_instances( ) -> UnmonitorInstancesResult: raise NotImplementedError + @handler("UpdateCapacityManagerOrganizationsAccess") + def update_capacity_manager_organizations_access( + self, + context: RequestContext, + organizations_access: BoxedBoolean, + dry_run: Boolean | None = None, + client_token: String | None = None, + **kwargs, + ) -> UpdateCapacityManagerOrganizationsAccessResult: + raise NotImplementedError + + @handler("UpdateInterruptibleCapacityReservationAllocation") + def update_interruptible_capacity_reservation_allocation( + self, + context: RequestContext, + capacity_reservation_id: CapacityReservationId, + target_instance_count: Integer, + dry_run: Boolean | None = None, + **kwargs, + ) -> UpdateInterruptibleCapacityReservationAllocationResult: + raise NotImplementedError + @handler("UpdateSecurityGroupRuleDescriptionsEgress") def update_security_group_rule_descriptions_egress( self, diff --git a/localstack-core/localstack/aws/api/es/__init__.py b/localstack-core/localstack/aws/api/es/__init__.py index 4c5774cbd36fa..80f61f5f2081a 100644 --- a/localstack-core/localstack/aws/api/es/__init__.py +++ b/localstack-core/localstack/aws/api/es/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -418,25 +418,25 @@ class AcceptInboundCrossClusterSearchConnectionRequest(ServiceRequest): class InboundCrossClusterSearchConnectionStatus(TypedDict, total=False): - StatusCode: Optional[InboundCrossClusterSearchConnectionStatusCode] - Message: Optional[CrossClusterSearchConnectionStatusMessage] + StatusCode: InboundCrossClusterSearchConnectionStatusCode | None + Message: CrossClusterSearchConnectionStatusMessage | None class DomainInformation(TypedDict, total=False): - OwnerId: Optional[OwnerId] + OwnerId: OwnerId | None DomainName: DomainName - Region: Optional[Region] + Region: Region | None class InboundCrossClusterSearchConnection(TypedDict, total=False): - SourceDomainInfo: Optional[DomainInformation] - DestinationDomainInfo: Optional[DomainInformation] - CrossClusterSearchConnectionId: Optional[CrossClusterSearchConnectionId] - ConnectionStatus: Optional[InboundCrossClusterSearchConnectionStatus] + SourceDomainInfo: DomainInformation | None + DestinationDomainInfo: DomainInformation | None + CrossClusterSearchConnectionId: CrossClusterSearchConnectionId | None + ConnectionStatus: InboundCrossClusterSearchConnectionStatus | None class AcceptInboundCrossClusterSearchConnectionResponse(TypedDict, total=False): - CrossClusterSearchConnection: Optional[InboundCrossClusterSearchConnection] + CrossClusterSearchConnection: InboundCrossClusterSearchConnection | None UpdateTimestamp = datetime @@ -445,9 +445,9 @@ class AcceptInboundCrossClusterSearchConnectionResponse(TypedDict, total=False): class OptionStatus(TypedDict, total=False): CreationDate: UpdateTimestamp UpdateDate: UpdateTimestamp - UpdateVersion: Optional[UIntValue] + UpdateVersion: UIntValue | None State: OptionState - PendingDeletion: Optional[Boolean] + PendingDeletion: Boolean | None class AccessPoliciesStatus(TypedDict, total=False): @@ -460,7 +460,7 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] class AddTagsRequest(ServiceRequest): @@ -468,16 +468,16 @@ class AddTagsRequest(ServiceRequest): TagList: TagList -LimitValueList = List[LimitValue] +LimitValueList = list[LimitValue] class AdditionalLimit(TypedDict, total=False): - LimitName: Optional[LimitName] - LimitValues: Optional[LimitValueList] + LimitName: LimitName | None + LimitValues: LimitValueList | None -AdditionalLimitList = List[AdditionalLimit] -AdvancedOptions = Dict[String, String] +AdditionalLimitList = list[AdditionalLimit] +AdvancedOptions = dict[String, String] class AdvancedOptionsStatus(TypedDict, total=False): @@ -494,43 +494,43 @@ class SAMLIdp(TypedDict, total=False): class SAMLOptionsOutput(TypedDict, total=False): - Enabled: Optional[Boolean] - Idp: Optional[SAMLIdp] - SubjectKey: Optional[String] - RolesKey: Optional[String] - SessionTimeoutMinutes: Optional[IntegerClass] + Enabled: Boolean | None + Idp: SAMLIdp | None + SubjectKey: String | None + RolesKey: String | None + SessionTimeoutMinutes: IntegerClass | None class AdvancedSecurityOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - InternalUserDatabaseEnabled: Optional[Boolean] - SAMLOptions: Optional[SAMLOptionsOutput] - AnonymousAuthDisableDate: Optional[DisableTimestamp] - AnonymousAuthEnabled: Optional[Boolean] + Enabled: Boolean | None + InternalUserDatabaseEnabled: Boolean | None + SAMLOptions: SAMLOptionsOutput | None + AnonymousAuthDisableDate: DisableTimestamp | None + AnonymousAuthEnabled: Boolean | None class SAMLOptionsInput(TypedDict, total=False): - Enabled: Optional[Boolean] - Idp: Optional[SAMLIdp] - MasterUserName: Optional[Username] - MasterBackendRole: Optional[BackendRole] - SubjectKey: Optional[String] - RolesKey: Optional[String] - SessionTimeoutMinutes: Optional[IntegerClass] + Enabled: Boolean | None + Idp: SAMLIdp | None + MasterUserName: Username | None + MasterBackendRole: BackendRole | None + SubjectKey: String | None + RolesKey: String | None + SessionTimeoutMinutes: IntegerClass | None class MasterUserOptions(TypedDict, total=False): - MasterUserARN: Optional[ARN] - MasterUserName: Optional[Username] - MasterUserPassword: Optional[Password] + MasterUserARN: ARN | None + MasterUserName: Username | None + MasterUserPassword: Password | None class AdvancedSecurityOptionsInput(TypedDict, total=False): - Enabled: Optional[Boolean] - InternalUserDatabaseEnabled: Optional[Boolean] - MasterUserOptions: Optional[MasterUserOptions] - SAMLOptions: Optional[SAMLOptionsInput] - AnonymousAuthEnabled: Optional[Boolean] + Enabled: Boolean | None + InternalUserDatabaseEnabled: Boolean | None + MasterUserOptions: MasterUserOptions | None + SAMLOptions: SAMLOptionsInput | None + AnonymousAuthEnabled: Boolean | None class AdvancedSecurityOptionsStatus(TypedDict, total=False): @@ -544,27 +544,27 @@ class AssociatePackageRequest(ServiceRequest): class ErrorDetails(TypedDict, total=False): - ErrorType: Optional[ErrorType] - ErrorMessage: Optional[ErrorMessage] + ErrorType: ErrorType | None + ErrorMessage: ErrorMessage | None LastUpdated = datetime class DomainPackageDetails(TypedDict, total=False): - PackageID: Optional[PackageID] - PackageName: Optional[PackageName] - PackageType: Optional[PackageType] - LastUpdated: Optional[LastUpdated] - DomainName: Optional[DomainName] - DomainPackageStatus: Optional[DomainPackageStatus] - PackageVersion: Optional[PackageVersion] - ReferencePath: Optional[ReferencePath] - ErrorDetails: Optional[ErrorDetails] + PackageID: PackageID | None + PackageName: PackageName | None + PackageType: PackageType | None + LastUpdated: LastUpdated | None + DomainName: DomainName | None + DomainPackageStatus: DomainPackageStatus | None + PackageVersion: PackageVersion | None + ReferencePath: ReferencePath | None + ErrorDetails: ErrorDetails | None class AssociatePackageResponse(TypedDict, total=False): - DomainPackageDetails: Optional[DomainPackageDetails] + DomainPackageDetails: DomainPackageDetails | None class AuthorizeVpcEndpointAccessRequest(ServiceRequest): @@ -573,104 +573,104 @@ class AuthorizeVpcEndpointAccessRequest(ServiceRequest): class AuthorizedPrincipal(TypedDict, total=False): - PrincipalType: Optional[PrincipalType] - Principal: Optional[String] + PrincipalType: PrincipalType | None + Principal: String | None class AuthorizeVpcEndpointAccessResponse(TypedDict, total=False): AuthorizedPrincipal: AuthorizedPrincipal -AuthorizedPrincipalList = List[AuthorizedPrincipal] +AuthorizedPrincipalList = list[AuthorizedPrincipal] AutoTuneDate = datetime class ScheduledAutoTuneDetails(TypedDict, total=False): - Date: Optional[AutoTuneDate] - ActionType: Optional[ScheduledAutoTuneActionType] - Action: Optional[ScheduledAutoTuneDescription] - Severity: Optional[ScheduledAutoTuneSeverityType] + Date: AutoTuneDate | None + ActionType: ScheduledAutoTuneActionType | None + Action: ScheduledAutoTuneDescription | None + Severity: ScheduledAutoTuneSeverityType | None class AutoTuneDetails(TypedDict, total=False): - ScheduledAutoTuneDetails: Optional[ScheduledAutoTuneDetails] + ScheduledAutoTuneDetails: ScheduledAutoTuneDetails | None class AutoTune(TypedDict, total=False): - AutoTuneType: Optional[AutoTuneType] - AutoTuneDetails: Optional[AutoTuneDetails] + AutoTuneType: AutoTuneType | None + AutoTuneDetails: AutoTuneDetails | None -AutoTuneList = List[AutoTune] +AutoTuneList = list[AutoTune] DurationValue = int class Duration(TypedDict, total=False): - Value: Optional[DurationValue] - Unit: Optional[TimeUnit] + Value: DurationValue | None + Unit: TimeUnit | None StartAt = datetime class AutoTuneMaintenanceSchedule(TypedDict, total=False): - StartAt: Optional[StartAt] - Duration: Optional[Duration] - CronExpressionForRecurrence: Optional[String] + StartAt: StartAt | None + Duration: Duration | None + CronExpressionForRecurrence: String | None -AutoTuneMaintenanceScheduleList = List[AutoTuneMaintenanceSchedule] +AutoTuneMaintenanceScheduleList = list[AutoTuneMaintenanceSchedule] class AutoTuneOptions(TypedDict, total=False): - DesiredState: Optional[AutoTuneDesiredState] - RollbackOnDisable: Optional[RollbackOnDisable] - MaintenanceSchedules: Optional[AutoTuneMaintenanceScheduleList] + DesiredState: AutoTuneDesiredState | None + RollbackOnDisable: RollbackOnDisable | None + MaintenanceSchedules: AutoTuneMaintenanceScheduleList | None class AutoTuneOptionsInput(TypedDict, total=False): - DesiredState: Optional[AutoTuneDesiredState] - MaintenanceSchedules: Optional[AutoTuneMaintenanceScheduleList] + DesiredState: AutoTuneDesiredState | None + MaintenanceSchedules: AutoTuneMaintenanceScheduleList | None class AutoTuneOptionsOutput(TypedDict, total=False): - State: Optional[AutoTuneState] - ErrorMessage: Optional[String] + State: AutoTuneState | None + ErrorMessage: String | None class AutoTuneStatus(TypedDict, total=False): CreationDate: UpdateTimestamp UpdateDate: UpdateTimestamp - UpdateVersion: Optional[UIntValue] + UpdateVersion: UIntValue | None State: AutoTuneState - ErrorMessage: Optional[String] - PendingDeletion: Optional[Boolean] + ErrorMessage: String | None + PendingDeletion: Boolean | None class AutoTuneOptionsStatus(TypedDict, total=False): - Options: Optional[AutoTuneOptions] - Status: Optional[AutoTuneStatus] + Options: AutoTuneOptions | None + Status: AutoTuneStatus | None class CancelDomainConfigChangeRequest(ServiceRequest): DomainName: DomainName - DryRun: Optional[DryRun] + DryRun: DryRun | None class CancelledChangeProperty(TypedDict, total=False): - PropertyName: Optional[String] - CancelledValue: Optional[String] - ActiveValue: Optional[String] + PropertyName: String | None + CancelledValue: String | None + ActiveValue: String | None -CancelledChangePropertyList = List[CancelledChangeProperty] -GUIDList = List[GUID] +CancelledChangePropertyList = list[CancelledChangeProperty] +GUIDList = list[GUID] class CancelDomainConfigChangeResponse(TypedDict, total=False): - DryRun: Optional[DryRun] - CancelledChangeIds: Optional[GUIDList] - CancelledChangeProperties: Optional[CancelledChangePropertyList] + DryRun: DryRun | None + CancelledChangeIds: GUIDList | None + CancelledChangeProperties: CancelledChangePropertyList | None class CancelElasticsearchServiceSoftwareUpdateRequest(ServiceRequest): @@ -681,58 +681,58 @@ class CancelElasticsearchServiceSoftwareUpdateRequest(ServiceRequest): class ServiceSoftwareOptions(TypedDict, total=False): - CurrentVersion: Optional[String] - NewVersion: Optional[String] - UpdateAvailable: Optional[Boolean] - Cancellable: Optional[Boolean] - UpdateStatus: Optional[DeploymentStatus] - Description: Optional[String] - AutomatedUpdateDate: Optional[DeploymentCloseDateTimeStamp] - OptionalDeployment: Optional[Boolean] + CurrentVersion: String | None + NewVersion: String | None + UpdateAvailable: Boolean | None + Cancellable: Boolean | None + UpdateStatus: DeploymentStatus | None + Description: String | None + AutomatedUpdateDate: DeploymentCloseDateTimeStamp | None + OptionalDeployment: Boolean | None class CancelElasticsearchServiceSoftwareUpdateResponse(TypedDict, total=False): - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] + ServiceSoftwareOptions: ServiceSoftwareOptions | None class ChangeProgressDetails(TypedDict, total=False): - ChangeId: Optional[GUID] - Message: Optional[Message] - ConfigChangeStatus: Optional[ConfigChangeStatus] - StartTime: Optional[UpdateTimestamp] - LastUpdatedTime: Optional[UpdateTimestamp] - InitiatedBy: Optional[InitiatedBy] + ChangeId: GUID | None + Message: Message | None + ConfigChangeStatus: ConfigChangeStatus | None + StartTime: UpdateTimestamp | None + LastUpdatedTime: UpdateTimestamp | None + InitiatedBy: InitiatedBy | None class ChangeProgressStage(TypedDict, total=False): - Name: Optional[ChangeProgressStageName] - Status: Optional[ChangeProgressStageStatus] - Description: Optional[Description] - LastUpdated: Optional[LastUpdated] + Name: ChangeProgressStageName | None + Status: ChangeProgressStageStatus | None + Description: Description | None + LastUpdated: LastUpdated | None -ChangeProgressStageList = List[ChangeProgressStage] -StringList = List[String] +ChangeProgressStageList = list[ChangeProgressStage] +StringList = list[String] class ChangeProgressStatusDetails(TypedDict, total=False): - ChangeId: Optional[GUID] - StartTime: Optional[UpdateTimestamp] - Status: Optional[OverallChangeStatus] - PendingProperties: Optional[StringList] - CompletedProperties: Optional[StringList] - TotalNumberOfStages: Optional[TotalNumberOfStages] - ChangeProgressStages: Optional[ChangeProgressStageList] - ConfigChangeStatus: Optional[ConfigChangeStatus] - LastUpdatedTime: Optional[UpdateTimestamp] - InitiatedBy: Optional[InitiatedBy] + ChangeId: GUID | None + StartTime: UpdateTimestamp | None + Status: OverallChangeStatus | None + PendingProperties: StringList | None + CompletedProperties: StringList | None + TotalNumberOfStages: TotalNumberOfStages | None + ChangeProgressStages: ChangeProgressStageList | None + ConfigChangeStatus: ConfigChangeStatus | None + LastUpdatedTime: UpdateTimestamp | None + InitiatedBy: InitiatedBy | None class CognitoOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - UserPoolId: Optional[UserPoolId] - IdentityPoolId: Optional[IdentityPoolId] - RoleArn: Optional[RoleArn] + Enabled: Boolean | None + UserPoolId: UserPoolId | None + IdentityPoolId: IdentityPoolId | None + RoleArn: RoleArn | None class CognitoOptionsStatus(TypedDict, total=False): @@ -744,148 +744,148 @@ class ColdStorageOptions(TypedDict, total=False): Enabled: Boolean -ElasticsearchVersionList = List[ElasticsearchVersionString] +ElasticsearchVersionList = list[ElasticsearchVersionString] class CompatibleVersionsMap(TypedDict, total=False): - SourceVersion: Optional[ElasticsearchVersionString] - TargetVersions: Optional[ElasticsearchVersionList] + SourceVersion: ElasticsearchVersionString | None + TargetVersions: ElasticsearchVersionList | None -CompatibleElasticsearchVersionsList = List[CompatibleVersionsMap] +CompatibleElasticsearchVersionsList = list[CompatibleVersionsMap] class DomainEndpointOptions(TypedDict, total=False): - EnforceHTTPS: Optional[Boolean] - TLSSecurityPolicy: Optional[TLSSecurityPolicy] - CustomEndpointEnabled: Optional[Boolean] - CustomEndpoint: Optional[DomainNameFqdn] - CustomEndpointCertificateArn: Optional[ARN] + EnforceHTTPS: Boolean | None + TLSSecurityPolicy: TLSSecurityPolicy | None + CustomEndpointEnabled: Boolean | None + CustomEndpoint: DomainNameFqdn | None + CustomEndpointCertificateArn: ARN | None class LogPublishingOption(TypedDict, total=False): - CloudWatchLogsLogGroupArn: Optional[CloudWatchLogsLogGroupArn] - Enabled: Optional[Boolean] + CloudWatchLogsLogGroupArn: CloudWatchLogsLogGroupArn | None + Enabled: Boolean | None -LogPublishingOptions = Dict[LogType, LogPublishingOption] +LogPublishingOptions = dict[LogType, LogPublishingOption] class NodeToNodeEncryptionOptions(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class EncryptionAtRestOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - KmsKeyId: Optional[KmsKeyId] + Enabled: Boolean | None + KmsKeyId: KmsKeyId | None class VPCOptions(TypedDict, total=False): - SubnetIds: Optional[StringList] - SecurityGroupIds: Optional[StringList] + SubnetIds: StringList | None + SecurityGroupIds: StringList | None class SnapshotOptions(TypedDict, total=False): - AutomatedSnapshotStartHour: Optional[IntegerClass] + AutomatedSnapshotStartHour: IntegerClass | None class EBSOptions(TypedDict, total=False): - EBSEnabled: Optional[Boolean] - VolumeType: Optional[VolumeType] - VolumeSize: Optional[IntegerClass] - Iops: Optional[IntegerClass] - Throughput: Optional[IntegerClass] + EBSEnabled: Boolean | None + VolumeType: VolumeType | None + VolumeSize: IntegerClass | None + Iops: IntegerClass | None + Throughput: IntegerClass | None class ZoneAwarenessConfig(TypedDict, total=False): - AvailabilityZoneCount: Optional[IntegerClass] + AvailabilityZoneCount: IntegerClass | None class ElasticsearchClusterConfig(TypedDict, total=False): - InstanceType: Optional[ESPartitionInstanceType] - InstanceCount: Optional[IntegerClass] - DedicatedMasterEnabled: Optional[Boolean] - ZoneAwarenessEnabled: Optional[Boolean] - ZoneAwarenessConfig: Optional[ZoneAwarenessConfig] - DedicatedMasterType: Optional[ESPartitionInstanceType] - DedicatedMasterCount: Optional[IntegerClass] - WarmEnabled: Optional[Boolean] - WarmType: Optional[ESWarmPartitionInstanceType] - WarmCount: Optional[IntegerClass] - ColdStorageOptions: Optional[ColdStorageOptions] + InstanceType: ESPartitionInstanceType | None + InstanceCount: IntegerClass | None + DedicatedMasterEnabled: Boolean | None + ZoneAwarenessEnabled: Boolean | None + ZoneAwarenessConfig: ZoneAwarenessConfig | None + DedicatedMasterType: ESPartitionInstanceType | None + DedicatedMasterCount: IntegerClass | None + WarmEnabled: Boolean | None + WarmType: ESWarmPartitionInstanceType | None + WarmCount: IntegerClass | None + ColdStorageOptions: ColdStorageOptions | None class CreateElasticsearchDomainRequest(ServiceRequest): DomainName: DomainName - ElasticsearchVersion: Optional[ElasticsearchVersionString] - ElasticsearchClusterConfig: Optional[ElasticsearchClusterConfig] - EBSOptions: Optional[EBSOptions] - AccessPolicies: Optional[PolicyDocument] - SnapshotOptions: Optional[SnapshotOptions] - VPCOptions: Optional[VPCOptions] - CognitoOptions: Optional[CognitoOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - AdvancedOptions: Optional[AdvancedOptions] - LogPublishingOptions: Optional[LogPublishingOptions] - DomainEndpointOptions: Optional[DomainEndpointOptions] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - AutoTuneOptions: Optional[AutoTuneOptionsInput] - TagList: Optional[TagList] + ElasticsearchVersion: ElasticsearchVersionString | None + ElasticsearchClusterConfig: ElasticsearchClusterConfig | None + EBSOptions: EBSOptions | None + AccessPolicies: PolicyDocument | None + SnapshotOptions: SnapshotOptions | None + VPCOptions: VPCOptions | None + CognitoOptions: CognitoOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + AdvancedOptions: AdvancedOptions | None + LogPublishingOptions: LogPublishingOptions | None + DomainEndpointOptions: DomainEndpointOptions | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + AutoTuneOptions: AutoTuneOptionsInput | None + TagList: TagList | None class ModifyingProperties(TypedDict, total=False): - Name: Optional[String] - ActiveValue: Optional[String] - PendingValue: Optional[String] - ValueType: Optional[PropertyValueType] + Name: String | None + ActiveValue: String | None + PendingValue: String | None + ValueType: PropertyValueType | None -ModifyingPropertiesList = List[ModifyingProperties] +ModifyingPropertiesList = list[ModifyingProperties] class VPCDerivedInfo(TypedDict, total=False): - VPCId: Optional[String] - SubnetIds: Optional[StringList] - AvailabilityZones: Optional[StringList] - SecurityGroupIds: Optional[StringList] + VPCId: String | None + SubnetIds: StringList | None + AvailabilityZones: StringList | None + SecurityGroupIds: StringList | None -EndpointsMap = Dict[String, ServiceUrl] +EndpointsMap = dict[String, ServiceUrl] class ElasticsearchDomainStatus(TypedDict, total=False): DomainId: DomainId DomainName: DomainName ARN: ARN - Created: Optional[Boolean] - Deleted: Optional[Boolean] - Endpoint: Optional[ServiceUrl] - Endpoints: Optional[EndpointsMap] - Processing: Optional[Boolean] - UpgradeProcessing: Optional[Boolean] - ElasticsearchVersion: Optional[ElasticsearchVersionString] + Created: Boolean | None + Deleted: Boolean | None + Endpoint: ServiceUrl | None + Endpoints: EndpointsMap | None + Processing: Boolean | None + UpgradeProcessing: Boolean | None + ElasticsearchVersion: ElasticsearchVersionString | None ElasticsearchClusterConfig: ElasticsearchClusterConfig - EBSOptions: Optional[EBSOptions] - AccessPolicies: Optional[PolicyDocument] - SnapshotOptions: Optional[SnapshotOptions] - VPCOptions: Optional[VPCDerivedInfo] - CognitoOptions: Optional[CognitoOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - AdvancedOptions: Optional[AdvancedOptions] - LogPublishingOptions: Optional[LogPublishingOptions] - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] - DomainEndpointOptions: Optional[DomainEndpointOptions] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptions] - AutoTuneOptions: Optional[AutoTuneOptionsOutput] - ChangeProgressDetails: Optional[ChangeProgressDetails] - DomainProcessingStatus: Optional[DomainProcessingStatusType] - ModifyingProperties: Optional[ModifyingPropertiesList] + EBSOptions: EBSOptions | None + AccessPolicies: PolicyDocument | None + SnapshotOptions: SnapshotOptions | None + VPCOptions: VPCDerivedInfo | None + CognitoOptions: CognitoOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + AdvancedOptions: AdvancedOptions | None + LogPublishingOptions: LogPublishingOptions | None + ServiceSoftwareOptions: ServiceSoftwareOptions | None + DomainEndpointOptions: DomainEndpointOptions | None + AdvancedSecurityOptions: AdvancedSecurityOptions | None + AutoTuneOptions: AutoTuneOptionsOutput | None + ChangeProgressDetails: ChangeProgressDetails | None + DomainProcessingStatus: DomainProcessingStatusType | None + ModifyingProperties: ModifyingPropertiesList | None class CreateElasticsearchDomainResponse(TypedDict, total=False): - DomainStatus: Optional[ElasticsearchDomainStatus] + DomainStatus: ElasticsearchDomainStatus | None class CreateOutboundCrossClusterSearchConnectionRequest(ServiceRequest): @@ -895,27 +895,27 @@ class CreateOutboundCrossClusterSearchConnectionRequest(ServiceRequest): class OutboundCrossClusterSearchConnectionStatus(TypedDict, total=False): - StatusCode: Optional[OutboundCrossClusterSearchConnectionStatusCode] - Message: Optional[CrossClusterSearchConnectionStatusMessage] + StatusCode: OutboundCrossClusterSearchConnectionStatusCode | None + Message: CrossClusterSearchConnectionStatusMessage | None class CreateOutboundCrossClusterSearchConnectionResponse(TypedDict, total=False): - SourceDomainInfo: Optional[DomainInformation] - DestinationDomainInfo: Optional[DomainInformation] - ConnectionAlias: Optional[ConnectionAlias] - ConnectionStatus: Optional[OutboundCrossClusterSearchConnectionStatus] - CrossClusterSearchConnectionId: Optional[CrossClusterSearchConnectionId] + SourceDomainInfo: DomainInformation | None + DestinationDomainInfo: DomainInformation | None + ConnectionAlias: ConnectionAlias | None + ConnectionStatus: OutboundCrossClusterSearchConnectionStatus | None + CrossClusterSearchConnectionId: CrossClusterSearchConnectionId | None class PackageSource(TypedDict, total=False): - S3BucketName: Optional[S3BucketName] - S3Key: Optional[S3Key] + S3BucketName: S3BucketName | None + S3Key: S3Key | None class CreatePackageRequest(ServiceRequest): PackageName: PackageName PackageType: PackageType - PackageDescription: Optional[PackageDescription] + PackageDescription: PackageDescription | None PackageSource: PackageSource @@ -923,34 +923,34 @@ class CreatePackageRequest(ServiceRequest): class PackageDetails(TypedDict, total=False): - PackageID: Optional[PackageID] - PackageName: Optional[PackageName] - PackageType: Optional[PackageType] - PackageDescription: Optional[PackageDescription] - PackageStatus: Optional[PackageStatus] - CreatedAt: Optional[CreatedAt] - LastUpdatedAt: Optional[LastUpdated] - AvailablePackageVersion: Optional[PackageVersion] - ErrorDetails: Optional[ErrorDetails] + PackageID: PackageID | None + PackageName: PackageName | None + PackageType: PackageType | None + PackageDescription: PackageDescription | None + PackageStatus: PackageStatus | None + CreatedAt: CreatedAt | None + LastUpdatedAt: LastUpdated | None + AvailablePackageVersion: PackageVersion | None + ErrorDetails: ErrorDetails | None class CreatePackageResponse(TypedDict, total=False): - PackageDetails: Optional[PackageDetails] + PackageDetails: PackageDetails | None class CreateVpcEndpointRequest(ServiceRequest): DomainArn: DomainArn VpcOptions: VPCOptions - ClientToken: Optional[ClientToken] + ClientToken: ClientToken | None class VpcEndpoint(TypedDict, total=False): - VpcEndpointId: Optional[VpcEndpointId] - VpcEndpointOwner: Optional[AWSAccount] - DomainArn: Optional[DomainArn] - VpcOptions: Optional[VPCDerivedInfo] - Status: Optional[VpcEndpointStatus] - Endpoint: Optional[Endpoint] + VpcEndpointId: VpcEndpointId | None + VpcEndpointOwner: AWSAccount | None + DomainArn: DomainArn | None + VpcOptions: VPCDerivedInfo | None + Status: VpcEndpointStatus | None + Endpoint: Endpoint | None class CreateVpcEndpointResponse(TypedDict, total=False): @@ -962,7 +962,7 @@ class DeleteElasticsearchDomainRequest(ServiceRequest): class DeleteElasticsearchDomainResponse(TypedDict, total=False): - DomainStatus: Optional[ElasticsearchDomainStatus] + DomainStatus: ElasticsearchDomainStatus | None class DeleteInboundCrossClusterSearchConnectionRequest(ServiceRequest): @@ -970,7 +970,7 @@ class DeleteInboundCrossClusterSearchConnectionRequest(ServiceRequest): class DeleteInboundCrossClusterSearchConnectionResponse(TypedDict, total=False): - CrossClusterSearchConnection: Optional[InboundCrossClusterSearchConnection] + CrossClusterSearchConnection: InboundCrossClusterSearchConnection | None class DeleteOutboundCrossClusterSearchConnectionRequest(ServiceRequest): @@ -978,15 +978,15 @@ class DeleteOutboundCrossClusterSearchConnectionRequest(ServiceRequest): class OutboundCrossClusterSearchConnection(TypedDict, total=False): - SourceDomainInfo: Optional[DomainInformation] - DestinationDomainInfo: Optional[DomainInformation] - CrossClusterSearchConnectionId: Optional[CrossClusterSearchConnectionId] - ConnectionAlias: Optional[ConnectionAlias] - ConnectionStatus: Optional[OutboundCrossClusterSearchConnectionStatus] + SourceDomainInfo: DomainInformation | None + DestinationDomainInfo: DomainInformation | None + CrossClusterSearchConnectionId: CrossClusterSearchConnectionId | None + ConnectionAlias: ConnectionAlias | None + ConnectionStatus: OutboundCrossClusterSearchConnectionStatus | None class DeleteOutboundCrossClusterSearchConnectionResponse(TypedDict, total=False): - CrossClusterSearchConnection: Optional[OutboundCrossClusterSearchConnection] + CrossClusterSearchConnection: OutboundCrossClusterSearchConnection | None class DeletePackageRequest(ServiceRequest): @@ -994,7 +994,7 @@ class DeletePackageRequest(ServiceRequest): class DeletePackageResponse(TypedDict, total=False): - PackageDetails: Optional[PackageDetails] + PackageDetails: PackageDetails | None class DeleteVpcEndpointRequest(ServiceRequest): @@ -1002,10 +1002,10 @@ class DeleteVpcEndpointRequest(ServiceRequest): class VpcEndpointSummary(TypedDict, total=False): - VpcEndpointId: Optional[VpcEndpointId] - VpcEndpointOwner: Optional[String] - DomainArn: Optional[DomainArn] - Status: Optional[VpcEndpointStatus] + VpcEndpointId: VpcEndpointId | None + VpcEndpointOwner: String | None + DomainArn: DomainArn | None + Status: VpcEndpointStatus | None class DeleteVpcEndpointResponse(TypedDict, total=False): @@ -1014,22 +1014,22 @@ class DeleteVpcEndpointResponse(TypedDict, total=False): class DescribeDomainAutoTunesRequest(ServiceRequest): DomainName: DomainName - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class DescribeDomainAutoTunesResponse(TypedDict, total=False): - AutoTunes: Optional[AutoTuneList] - NextToken: Optional[NextToken] + AutoTunes: AutoTuneList | None + NextToken: NextToken | None class DescribeDomainChangeProgressRequest(ServiceRequest): DomainName: DomainName - ChangeId: Optional[GUID] + ChangeId: GUID | None class DescribeDomainChangeProgressResponse(TypedDict, total=False): - ChangeProgressStatus: Optional[ChangeProgressStatusDetails] + ChangeProgressStatus: ChangeProgressStatusDetails | None class DescribeElasticsearchDomainConfigRequest(ServiceRequest): @@ -1042,8 +1042,8 @@ class DomainEndpointOptionsStatus(TypedDict, total=False): class LogPublishingOptionsStatus(TypedDict, total=False): - Options: Optional[LogPublishingOptions] - Status: Optional[OptionStatus] + Options: LogPublishingOptions | None + Status: OptionStatus | None class NodeToNodeEncryptionOptionsStatus(TypedDict, total=False): @@ -1082,22 +1082,22 @@ class ElasticsearchVersionStatus(TypedDict, total=False): class ElasticsearchDomainConfig(TypedDict, total=False): - ElasticsearchVersion: Optional[ElasticsearchVersionStatus] - ElasticsearchClusterConfig: Optional[ElasticsearchClusterConfigStatus] - EBSOptions: Optional[EBSOptionsStatus] - AccessPolicies: Optional[AccessPoliciesStatus] - SnapshotOptions: Optional[SnapshotOptionsStatus] - VPCOptions: Optional[VPCDerivedInfoStatus] - CognitoOptions: Optional[CognitoOptionsStatus] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptionsStatus] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptionsStatus] - AdvancedOptions: Optional[AdvancedOptionsStatus] - LogPublishingOptions: Optional[LogPublishingOptionsStatus] - DomainEndpointOptions: Optional[DomainEndpointOptionsStatus] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsStatus] - AutoTuneOptions: Optional[AutoTuneOptionsStatus] - ChangeProgressDetails: Optional[ChangeProgressDetails] - ModifyingProperties: Optional[ModifyingPropertiesList] + ElasticsearchVersion: ElasticsearchVersionStatus | None + ElasticsearchClusterConfig: ElasticsearchClusterConfigStatus | None + EBSOptions: EBSOptionsStatus | None + AccessPolicies: AccessPoliciesStatus | None + SnapshotOptions: SnapshotOptionsStatus | None + VPCOptions: VPCDerivedInfoStatus | None + CognitoOptions: CognitoOptionsStatus | None + EncryptionAtRestOptions: EncryptionAtRestOptionsStatus | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptionsStatus | None + AdvancedOptions: AdvancedOptionsStatus | None + LogPublishingOptions: LogPublishingOptionsStatus | None + DomainEndpointOptions: DomainEndpointOptionsStatus | None + AdvancedSecurityOptions: AdvancedSecurityOptionsStatus | None + AutoTuneOptions: AutoTuneOptionsStatus | None + ChangeProgressDetails: ChangeProgressDetails | None + ModifyingProperties: ModifyingPropertiesList | None class DescribeElasticsearchDomainConfigResponse(TypedDict, total=False): @@ -1112,14 +1112,14 @@ class DescribeElasticsearchDomainResponse(TypedDict, total=False): DomainStatus: ElasticsearchDomainStatus -DomainNameList = List[DomainName] +DomainNameList = list[DomainName] class DescribeElasticsearchDomainsRequest(ServiceRequest): DomainNames: DomainNameList -ElasticsearchDomainStatusList = List[ElasticsearchDomainStatus] +ElasticsearchDomainStatusList = list[ElasticsearchDomainStatus] class DescribeElasticsearchDomainsResponse(TypedDict, total=False): @@ -1127,178 +1127,178 @@ class DescribeElasticsearchDomainsResponse(TypedDict, total=False): class DescribeElasticsearchInstanceTypeLimitsRequest(ServiceRequest): - DomainName: Optional[DomainName] + DomainName: DomainName | None InstanceType: ESPartitionInstanceType ElasticsearchVersion: ElasticsearchVersionString class InstanceCountLimits(TypedDict, total=False): - MinimumInstanceCount: Optional[MinimumInstanceCount] - MaximumInstanceCount: Optional[MaximumInstanceCount] + MinimumInstanceCount: MinimumInstanceCount | None + MaximumInstanceCount: MaximumInstanceCount | None class InstanceLimits(TypedDict, total=False): - InstanceCountLimits: Optional[InstanceCountLimits] + InstanceCountLimits: InstanceCountLimits | None class StorageTypeLimit(TypedDict, total=False): - LimitName: Optional[LimitName] - LimitValues: Optional[LimitValueList] + LimitName: LimitName | None + LimitValues: LimitValueList | None -StorageTypeLimitList = List[StorageTypeLimit] +StorageTypeLimitList = list[StorageTypeLimit] class StorageType(TypedDict, total=False): - StorageTypeName: Optional[StorageTypeName] - StorageSubTypeName: Optional[StorageSubTypeName] - StorageTypeLimits: Optional[StorageTypeLimitList] + StorageTypeName: StorageTypeName | None + StorageSubTypeName: StorageSubTypeName | None + StorageTypeLimits: StorageTypeLimitList | None -StorageTypeList = List[StorageType] +StorageTypeList = list[StorageType] class Limits(TypedDict, total=False): - StorageTypes: Optional[StorageTypeList] - InstanceLimits: Optional[InstanceLimits] - AdditionalLimits: Optional[AdditionalLimitList] + StorageTypes: StorageTypeList | None + InstanceLimits: InstanceLimits | None + AdditionalLimits: AdditionalLimitList | None -LimitsByRole = Dict[InstanceRole, Limits] +LimitsByRole = dict[InstanceRole, Limits] class DescribeElasticsearchInstanceTypeLimitsResponse(TypedDict, total=False): - LimitsByRole: Optional[LimitsByRole] + LimitsByRole: LimitsByRole | None -ValueStringList = List[NonEmptyString] +ValueStringList = list[NonEmptyString] class Filter(TypedDict, total=False): - Name: Optional[NonEmptyString] - Values: Optional[ValueStringList] + Name: NonEmptyString | None + Values: ValueStringList | None -FilterList = List[Filter] +FilterList = list[Filter] class DescribeInboundCrossClusterSearchConnectionsRequest(ServiceRequest): - Filters: Optional[FilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: FilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None -InboundCrossClusterSearchConnections = List[InboundCrossClusterSearchConnection] +InboundCrossClusterSearchConnections = list[InboundCrossClusterSearchConnection] class DescribeInboundCrossClusterSearchConnectionsResponse(TypedDict, total=False): - CrossClusterSearchConnections: Optional[InboundCrossClusterSearchConnections] - NextToken: Optional[NextToken] + CrossClusterSearchConnections: InboundCrossClusterSearchConnections | None + NextToken: NextToken | None class DescribeOutboundCrossClusterSearchConnectionsRequest(ServiceRequest): - Filters: Optional[FilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: FilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None -OutboundCrossClusterSearchConnections = List[OutboundCrossClusterSearchConnection] +OutboundCrossClusterSearchConnections = list[OutboundCrossClusterSearchConnection] class DescribeOutboundCrossClusterSearchConnectionsResponse(TypedDict, total=False): - CrossClusterSearchConnections: Optional[OutboundCrossClusterSearchConnections] - NextToken: Optional[NextToken] + CrossClusterSearchConnections: OutboundCrossClusterSearchConnections | None + NextToken: NextToken | None -DescribePackagesFilterValues = List[DescribePackagesFilterValue] +DescribePackagesFilterValues = list[DescribePackagesFilterValue] class DescribePackagesFilter(TypedDict, total=False): - Name: Optional[DescribePackagesFilterName] - Value: Optional[DescribePackagesFilterValues] + Name: DescribePackagesFilterName | None + Value: DescribePackagesFilterValues | None -DescribePackagesFilterList = List[DescribePackagesFilter] +DescribePackagesFilterList = list[DescribePackagesFilter] class DescribePackagesRequest(ServiceRequest): - Filters: Optional[DescribePackagesFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: DescribePackagesFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None -PackageDetailsList = List[PackageDetails] +PackageDetailsList = list[PackageDetails] class DescribePackagesResponse(TypedDict, total=False): - PackageDetailsList: Optional[PackageDetailsList] - NextToken: Optional[String] + PackageDetailsList: PackageDetailsList | None + NextToken: String | None class DescribeReservedElasticsearchInstanceOfferingsRequest(ServiceRequest): - ReservedElasticsearchInstanceOfferingId: Optional[GUID] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + ReservedElasticsearchInstanceOfferingId: GUID | None + MaxResults: MaxResults | None + NextToken: NextToken | None class RecurringCharge(TypedDict, total=False): - RecurringChargeAmount: Optional[Double] - RecurringChargeFrequency: Optional[String] + RecurringChargeAmount: Double | None + RecurringChargeFrequency: String | None -RecurringChargeList = List[RecurringCharge] +RecurringChargeList = list[RecurringCharge] class ReservedElasticsearchInstanceOffering(TypedDict, total=False): - ReservedElasticsearchInstanceOfferingId: Optional[GUID] - ElasticsearchInstanceType: Optional[ESPartitionInstanceType] - Duration: Optional[Integer] - FixedPrice: Optional[Double] - UsagePrice: Optional[Double] - CurrencyCode: Optional[String] - PaymentOption: Optional[ReservedElasticsearchInstancePaymentOption] - RecurringCharges: Optional[RecurringChargeList] + ReservedElasticsearchInstanceOfferingId: GUID | None + ElasticsearchInstanceType: ESPartitionInstanceType | None + Duration: Integer | None + FixedPrice: Double | None + UsagePrice: Double | None + CurrencyCode: String | None + PaymentOption: ReservedElasticsearchInstancePaymentOption | None + RecurringCharges: RecurringChargeList | None -ReservedElasticsearchInstanceOfferingList = List[ReservedElasticsearchInstanceOffering] +ReservedElasticsearchInstanceOfferingList = list[ReservedElasticsearchInstanceOffering] class DescribeReservedElasticsearchInstanceOfferingsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - ReservedElasticsearchInstanceOfferings: Optional[ReservedElasticsearchInstanceOfferingList] + NextToken: NextToken | None + ReservedElasticsearchInstanceOfferings: ReservedElasticsearchInstanceOfferingList | None class DescribeReservedElasticsearchInstancesRequest(ServiceRequest): - ReservedElasticsearchInstanceId: Optional[GUID] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + ReservedElasticsearchInstanceId: GUID | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ReservedElasticsearchInstance(TypedDict, total=False): - ReservationName: Optional[ReservationToken] - ReservedElasticsearchInstanceId: Optional[GUID] - ReservedElasticsearchInstanceOfferingId: Optional[String] - ElasticsearchInstanceType: Optional[ESPartitionInstanceType] - StartTime: Optional[UpdateTimestamp] - Duration: Optional[Integer] - FixedPrice: Optional[Double] - UsagePrice: Optional[Double] - CurrencyCode: Optional[String] - ElasticsearchInstanceCount: Optional[Integer] - State: Optional[String] - PaymentOption: Optional[ReservedElasticsearchInstancePaymentOption] - RecurringCharges: Optional[RecurringChargeList] + ReservationName: ReservationToken | None + ReservedElasticsearchInstanceId: GUID | None + ReservedElasticsearchInstanceOfferingId: String | None + ElasticsearchInstanceType: ESPartitionInstanceType | None + StartTime: UpdateTimestamp | None + Duration: Integer | None + FixedPrice: Double | None + UsagePrice: Double | None + CurrencyCode: String | None + ElasticsearchInstanceCount: Integer | None + State: String | None + PaymentOption: ReservedElasticsearchInstancePaymentOption | None + RecurringCharges: RecurringChargeList | None -ReservedElasticsearchInstanceList = List[ReservedElasticsearchInstance] +ReservedElasticsearchInstanceList = list[ReservedElasticsearchInstance] class DescribeReservedElasticsearchInstancesResponse(TypedDict, total=False): - NextToken: Optional[String] - ReservedElasticsearchInstances: Optional[ReservedElasticsearchInstanceList] + NextToken: String | None + ReservedElasticsearchInstances: ReservedElasticsearchInstanceList | None -VpcEndpointIdList = List[VpcEndpointId] +VpcEndpointIdList = list[VpcEndpointId] class DescribeVpcEndpointsRequest(ServiceRequest): @@ -1306,13 +1306,13 @@ class DescribeVpcEndpointsRequest(ServiceRequest): class VpcEndpointError(TypedDict, total=False): - VpcEndpointId: Optional[VpcEndpointId] - ErrorCode: Optional[VpcEndpointErrorCode] - ErrorMessage: Optional[String] + VpcEndpointId: VpcEndpointId | None + ErrorCode: VpcEndpointErrorCode | None + ErrorMessage: String | None -VpcEndpointErrorList = List[VpcEndpointError] -VpcEndpoints = List[VpcEndpoint] +VpcEndpointErrorList = list[VpcEndpointError] +VpcEndpoints = list[VpcEndpoint] class DescribeVpcEndpointsResponse(TypedDict, total=False): @@ -1326,88 +1326,88 @@ class DissociatePackageRequest(ServiceRequest): class DissociatePackageResponse(TypedDict, total=False): - DomainPackageDetails: Optional[DomainPackageDetails] + DomainPackageDetails: DomainPackageDetails | None class DomainInfo(TypedDict, total=False): - DomainName: Optional[DomainName] - EngineType: Optional[EngineType] + DomainName: DomainName | None + EngineType: EngineType | None -DomainInfoList = List[DomainInfo] -DomainPackageDetailsList = List[DomainPackageDetails] +DomainInfoList = list[DomainInfo] +DomainPackageDetailsList = list[DomainPackageDetails] class DryRunResults(TypedDict, total=False): - DeploymentType: Optional[DeploymentType] - Message: Optional[Message] + DeploymentType: DeploymentType | None + Message: Message | None -ElasticsearchInstanceTypeList = List[ESPartitionInstanceType] +ElasticsearchInstanceTypeList = list[ESPartitionInstanceType] class GetCompatibleElasticsearchVersionsRequest(ServiceRequest): - DomainName: Optional[DomainName] + DomainName: DomainName | None class GetCompatibleElasticsearchVersionsResponse(TypedDict, total=False): - CompatibleElasticsearchVersions: Optional[CompatibleElasticsearchVersionsList] + CompatibleElasticsearchVersions: CompatibleElasticsearchVersionsList | None class GetPackageVersionHistoryRequest(ServiceRequest): PackageID: PackageID - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class PackageVersionHistory(TypedDict, total=False): - PackageVersion: Optional[PackageVersion] - CommitMessage: Optional[CommitMessage] - CreatedAt: Optional[CreatedAt] + PackageVersion: PackageVersion | None + CommitMessage: CommitMessage | None + CreatedAt: CreatedAt | None -PackageVersionHistoryList = List[PackageVersionHistory] +PackageVersionHistoryList = list[PackageVersionHistory] class GetPackageVersionHistoryResponse(TypedDict, total=False): - PackageID: Optional[PackageID] - PackageVersionHistoryList: Optional[PackageVersionHistoryList] - NextToken: Optional[String] + PackageID: PackageID | None + PackageVersionHistoryList: PackageVersionHistoryList | None + NextToken: String | None class GetUpgradeHistoryRequest(ServiceRequest): DomainName: DomainName - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None -Issues = List[Issue] +Issues = list[Issue] class UpgradeStepItem(TypedDict, total=False): - UpgradeStep: Optional[UpgradeStep] - UpgradeStepStatus: Optional[UpgradeStatus] - Issues: Optional[Issues] - ProgressPercent: Optional[Double] + UpgradeStep: UpgradeStep | None + UpgradeStepStatus: UpgradeStatus | None + Issues: Issues | None + ProgressPercent: Double | None -UpgradeStepsList = List[UpgradeStepItem] +UpgradeStepsList = list[UpgradeStepItem] StartTimestamp = datetime class UpgradeHistory(TypedDict, total=False): - UpgradeName: Optional[UpgradeName] - StartTimestamp: Optional[StartTimestamp] - UpgradeStatus: Optional[UpgradeStatus] - StepsList: Optional[UpgradeStepsList] + UpgradeName: UpgradeName | None + StartTimestamp: StartTimestamp | None + UpgradeStatus: UpgradeStatus | None + StepsList: UpgradeStepsList | None -UpgradeHistoryList = List[UpgradeHistory] +UpgradeHistoryList = list[UpgradeHistory] class GetUpgradeHistoryResponse(TypedDict, total=False): - UpgradeHistories: Optional[UpgradeHistoryList] - NextToken: Optional[String] + UpgradeHistories: UpgradeHistoryList | None + NextToken: String | None class GetUpgradeStatusRequest(ServiceRequest): @@ -1415,61 +1415,61 @@ class GetUpgradeStatusRequest(ServiceRequest): class GetUpgradeStatusResponse(TypedDict, total=False): - UpgradeStep: Optional[UpgradeStep] - StepStatus: Optional[UpgradeStatus] - UpgradeName: Optional[UpgradeName] + UpgradeStep: UpgradeStep | None + StepStatus: UpgradeStatus | None + UpgradeName: UpgradeName | None class ListDomainNamesRequest(ServiceRequest): - EngineType: Optional[EngineType] + EngineType: EngineType | None class ListDomainNamesResponse(TypedDict, total=False): - DomainNames: Optional[DomainInfoList] + DomainNames: DomainInfoList | None class ListDomainsForPackageRequest(ServiceRequest): PackageID: PackageID - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListDomainsForPackageResponse(TypedDict, total=False): - DomainPackageDetailsList: Optional[DomainPackageDetailsList] - NextToken: Optional[String] + DomainPackageDetailsList: DomainPackageDetailsList | None + NextToken: String | None class ListElasticsearchInstanceTypesRequest(ServiceRequest): ElasticsearchVersion: ElasticsearchVersionString - DomainName: Optional[DomainName] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + DomainName: DomainName | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListElasticsearchInstanceTypesResponse(TypedDict, total=False): - ElasticsearchInstanceTypes: Optional[ElasticsearchInstanceTypeList] - NextToken: Optional[NextToken] + ElasticsearchInstanceTypes: ElasticsearchInstanceTypeList | None + NextToken: NextToken | None class ListElasticsearchVersionsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListElasticsearchVersionsResponse(TypedDict, total=False): - ElasticsearchVersions: Optional[ElasticsearchVersionList] - NextToken: Optional[NextToken] + ElasticsearchVersions: ElasticsearchVersionList | None + NextToken: NextToken | None class ListPackagesForDomainRequest(ServiceRequest): DomainName: DomainName - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListPackagesForDomainResponse(TypedDict, total=False): - DomainPackageDetailsList: Optional[DomainPackageDetailsList] - NextToken: Optional[String] + DomainPackageDetailsList: DomainPackageDetailsList | None + NextToken: String | None class ListTagsRequest(ServiceRequest): @@ -1477,12 +1477,12 @@ class ListTagsRequest(ServiceRequest): class ListTagsResponse(TypedDict, total=False): - TagList: Optional[TagList] + TagList: TagList | None class ListVpcEndpointAccessRequest(ServiceRequest): DomainName: DomainName - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListVpcEndpointAccessResponse(TypedDict, total=False): @@ -1492,10 +1492,10 @@ class ListVpcEndpointAccessResponse(TypedDict, total=False): class ListVpcEndpointsForDomainRequest(ServiceRequest): DomainName: DomainName - NextToken: Optional[NextToken] + NextToken: NextToken | None -VpcEndpointSummaryList = List[VpcEndpointSummary] +VpcEndpointSummaryList = list[VpcEndpointSummary] class ListVpcEndpointsForDomainResponse(TypedDict, total=False): @@ -1504,7 +1504,7 @@ class ListVpcEndpointsForDomainResponse(TypedDict, total=False): class ListVpcEndpointsRequest(ServiceRequest): - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListVpcEndpointsResponse(TypedDict, total=False): @@ -1515,12 +1515,12 @@ class ListVpcEndpointsResponse(TypedDict, total=False): class PurchaseReservedElasticsearchInstanceOfferingRequest(ServiceRequest): ReservedElasticsearchInstanceOfferingId: GUID ReservationName: ReservationToken - InstanceCount: Optional[InstanceCount] + InstanceCount: InstanceCount | None class PurchaseReservedElasticsearchInstanceOfferingResponse(TypedDict, total=False): - ReservedElasticsearchInstanceId: Optional[GUID] - ReservationName: Optional[ReservationToken] + ReservedElasticsearchInstanceId: GUID | None + ReservationName: ReservationToken | None class RejectInboundCrossClusterSearchConnectionRequest(ServiceRequest): @@ -1528,7 +1528,7 @@ class RejectInboundCrossClusterSearchConnectionRequest(ServiceRequest): class RejectInboundCrossClusterSearchConnectionResponse(TypedDict, total=False): - CrossClusterSearchConnection: Optional[InboundCrossClusterSearchConnection] + CrossClusterSearchConnection: InboundCrossClusterSearchConnection | None class RemoveTagsRequest(ServiceRequest): @@ -1550,41 +1550,41 @@ class StartElasticsearchServiceSoftwareUpdateRequest(ServiceRequest): class StartElasticsearchServiceSoftwareUpdateResponse(TypedDict, total=False): - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] + ServiceSoftwareOptions: ServiceSoftwareOptions | None class UpdateElasticsearchDomainConfigRequest(ServiceRequest): DomainName: DomainName - ElasticsearchClusterConfig: Optional[ElasticsearchClusterConfig] - EBSOptions: Optional[EBSOptions] - SnapshotOptions: Optional[SnapshotOptions] - VPCOptions: Optional[VPCOptions] - CognitoOptions: Optional[CognitoOptions] - AdvancedOptions: Optional[AdvancedOptions] - AccessPolicies: Optional[PolicyDocument] - LogPublishingOptions: Optional[LogPublishingOptions] - DomainEndpointOptions: Optional[DomainEndpointOptions] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - AutoTuneOptions: Optional[AutoTuneOptions] - DryRun: Optional[DryRun] + ElasticsearchClusterConfig: ElasticsearchClusterConfig | None + EBSOptions: EBSOptions | None + SnapshotOptions: SnapshotOptions | None + VPCOptions: VPCOptions | None + CognitoOptions: CognitoOptions | None + AdvancedOptions: AdvancedOptions | None + AccessPolicies: PolicyDocument | None + LogPublishingOptions: LogPublishingOptions | None + DomainEndpointOptions: DomainEndpointOptions | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + AutoTuneOptions: AutoTuneOptions | None + DryRun: DryRun | None class UpdateElasticsearchDomainConfigResponse(TypedDict, total=False): DomainConfig: ElasticsearchDomainConfig - DryRunResults: Optional[DryRunResults] + DryRunResults: DryRunResults | None class UpdatePackageRequest(ServiceRequest): PackageID: PackageID PackageSource: PackageSource - PackageDescription: Optional[PackageDescription] - CommitMessage: Optional[CommitMessage] + PackageDescription: PackageDescription | None + CommitMessage: CommitMessage | None class UpdatePackageResponse(TypedDict, total=False): - PackageDetails: Optional[PackageDetails] + PackageDetails: PackageDetails | None class UpdateVpcEndpointRequest(ServiceRequest): @@ -1599,19 +1599,19 @@ class UpdateVpcEndpointResponse(TypedDict, total=False): class UpgradeElasticsearchDomainRequest(ServiceRequest): DomainName: DomainName TargetVersion: ElasticsearchVersionString - PerformCheckOnly: Optional[Boolean] + PerformCheckOnly: Boolean | None class UpgradeElasticsearchDomainResponse(TypedDict, total=False): - DomainName: Optional[DomainName] - TargetVersion: Optional[ElasticsearchVersionString] - PerformCheckOnly: Optional[Boolean] - ChangeProgressDetails: Optional[ChangeProgressDetails] + DomainName: DomainName | None + TargetVersion: ElasticsearchVersionString | None + PerformCheckOnly: Boolean | None + ChangeProgressDetails: ChangeProgressDetails | None class EsApi: - service = "es" - version = "2015-01-01" + service: str = "es" + version: str = "2015-01-01" @handler("AcceptInboundCrossClusterSearchConnection") def accept_inbound_cross_cluster_search_connection( diff --git a/localstack-core/localstack/aws/api/events/__init__.py b/localstack-core/localstack/aws/api/events/__init__.py index 3ad5d9dcaaaf1..486f41bab31d2 100644 --- a/localstack-core/localstack/aws/api/events/__init__.py +++ b/localstack-core/localstack/aws/api/events/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -180,12 +180,24 @@ class EventSourceState(StrEnum): DELETED = "DELETED" +class IncludeDetail(StrEnum): + NONE = "NONE" + FULL = "FULL" + + class LaunchType(StrEnum): EC2 = "EC2" FARGATE = "FARGATE" EXTERNAL = "EXTERNAL" +class Level(StrEnum): + OFF = "OFF" + ERROR = "ERROR" + INFO = "INFO" + TRACE = "TRACE" + + class PlacementConstraintType(StrEnum): distinctInstance = "distinctInstance" memberOf = "memberOf" @@ -307,61 +319,61 @@ class ActivateEventSourceRequest(ServiceRequest): class ApiDestination(TypedDict, total=False): - ApiDestinationArn: Optional[ApiDestinationArn] - Name: Optional[ApiDestinationName] - ApiDestinationState: Optional[ApiDestinationState] - ConnectionArn: Optional[ConnectionArn] - InvocationEndpoint: Optional[HttpsEndpoint] - HttpMethod: Optional[ApiDestinationHttpMethod] - InvocationRateLimitPerSecond: Optional[ApiDestinationInvocationRateLimitPerSecond] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + ApiDestinationArn: ApiDestinationArn | None + Name: ApiDestinationName | None + ApiDestinationState: ApiDestinationState | None + ConnectionArn: ConnectionArn | None + InvocationEndpoint: HttpsEndpoint | None + HttpMethod: ApiDestinationHttpMethod | None + InvocationRateLimitPerSecond: ApiDestinationInvocationRateLimitPerSecond | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None -ApiDestinationResponseList = List[ApiDestination] +ApiDestinationResponseList = list[ApiDestination] class AppSyncParameters(TypedDict, total=False): - GraphQLOperation: Optional[GraphQLOperation] + GraphQLOperation: GraphQLOperation | None Long = int class Archive(TypedDict, total=False): - ArchiveName: Optional[ArchiveName] - EventSourceArn: Optional[EventBusArn] - State: Optional[ArchiveState] - StateReason: Optional[ArchiveStateReason] - RetentionDays: Optional[RetentionDays] - SizeBytes: Optional[Long] - EventCount: Optional[Long] - CreationTime: Optional[Timestamp] + ArchiveName: ArchiveName | None + EventSourceArn: EventBusArn | None + State: ArchiveState | None + StateReason: ArchiveStateReason | None + RetentionDays: RetentionDays | None + SizeBytes: Long | None + EventCount: Long | None + CreationTime: Timestamp | None -ArchiveResponseList = List[Archive] -StringList = List[String] +ArchiveResponseList = list[Archive] +StringList = list[String] class AwsVpcConfiguration(TypedDict, total=False): Subnets: StringList - SecurityGroups: Optional[StringList] - AssignPublicIp: Optional[AssignPublicIp] + SecurityGroups: StringList | None + AssignPublicIp: AssignPublicIp | None class BatchArrayProperties(TypedDict, total=False): - Size: Optional[Integer] + Size: Integer | None class BatchRetryStrategy(TypedDict, total=False): - Attempts: Optional[Integer] + Attempts: Integer | None class BatchParameters(TypedDict, total=False): JobDefinition: String JobName: String - ArrayProperties: Optional[BatchArrayProperties] - RetryStrategy: Optional[BatchRetryStrategy] + ArrayProperties: BatchArrayProperties | None + RetryStrategy: BatchRetryStrategy | None class CancelReplayRequest(ServiceRequest): @@ -369,18 +381,18 @@ class CancelReplayRequest(ServiceRequest): class CancelReplayResponse(TypedDict, total=False): - ReplayArn: Optional[ReplayArn] - State: Optional[ReplayState] - StateReason: Optional[ReplayStateReason] + ReplayArn: ReplayArn | None + State: ReplayState | None + StateReason: ReplayStateReason | None class CapacityProviderStrategyItem(TypedDict, total=False): capacityProvider: CapacityProvider - weight: Optional[CapacityProviderStrategyItemWeight] - base: Optional[CapacityProviderStrategyItemBase] + weight: CapacityProviderStrategyItemWeight | None + base: CapacityProviderStrategyItemBase | None -CapacityProviderStrategy = List[CapacityProviderStrategyItem] +CapacityProviderStrategy = list[CapacityProviderStrategyItem] class Condition(TypedDict, total=False): @@ -390,18 +402,18 @@ class Condition(TypedDict, total=False): class Connection(TypedDict, total=False): - ConnectionArn: Optional[ConnectionArn] - Name: Optional[ConnectionName] - ConnectionState: Optional[ConnectionState] - StateReason: Optional[ConnectionStateReason] - AuthorizationType: Optional[ConnectionAuthorizationType] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] - LastAuthorizedTime: Optional[Timestamp] + ConnectionArn: ConnectionArn | None + Name: ConnectionName | None + ConnectionState: ConnectionState | None + StateReason: ConnectionStateReason | None + AuthorizationType: ConnectionAuthorizationType | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None + LastAuthorizedTime: Timestamp | None class ConnectionApiKeyAuthResponseParameters(TypedDict, total=False): - ApiKeyName: Optional[AuthHeaderParameters] + ApiKeyName: AuthHeaderParameters | None class DescribeConnectionResourceParameters(TypedDict, total=False): @@ -414,62 +426,62 @@ class DescribeConnectionConnectivityParameters(TypedDict, total=False): class ConnectionBodyParameter(TypedDict, total=False): - Key: Optional[String] - Value: Optional[SensitiveString] - IsValueSecret: Optional[Boolean] + Key: String | None + Value: SensitiveString | None + IsValueSecret: Boolean | None -ConnectionBodyParametersList = List[ConnectionBodyParameter] +ConnectionBodyParametersList = list[ConnectionBodyParameter] class ConnectionQueryStringParameter(TypedDict, total=False): - Key: Optional[QueryStringKey] - Value: Optional[QueryStringValueSensitive] - IsValueSecret: Optional[Boolean] + Key: QueryStringKey | None + Value: QueryStringValueSensitive | None + IsValueSecret: Boolean | None -ConnectionQueryStringParametersList = List[ConnectionQueryStringParameter] +ConnectionQueryStringParametersList = list[ConnectionQueryStringParameter] class ConnectionHeaderParameter(TypedDict, total=False): - Key: Optional[HeaderKey] - Value: Optional[HeaderValueSensitive] - IsValueSecret: Optional[Boolean] + Key: HeaderKey | None + Value: HeaderValueSensitive | None + IsValueSecret: Boolean | None -ConnectionHeaderParametersList = List[ConnectionHeaderParameter] +ConnectionHeaderParametersList = list[ConnectionHeaderParameter] class ConnectionHttpParameters(TypedDict, total=False): - HeaderParameters: Optional[ConnectionHeaderParametersList] - QueryStringParameters: Optional[ConnectionQueryStringParametersList] - BodyParameters: Optional[ConnectionBodyParametersList] + HeaderParameters: ConnectionHeaderParametersList | None + QueryStringParameters: ConnectionQueryStringParametersList | None + BodyParameters: ConnectionBodyParametersList | None class ConnectionOAuthClientResponseParameters(TypedDict, total=False): - ClientID: Optional[AuthHeaderParameters] + ClientID: AuthHeaderParameters | None class ConnectionOAuthResponseParameters(TypedDict, total=False): - ClientParameters: Optional[ConnectionOAuthClientResponseParameters] - AuthorizationEndpoint: Optional[HttpsEndpoint] - HttpMethod: Optional[ConnectionOAuthHttpMethod] - OAuthHttpParameters: Optional[ConnectionHttpParameters] + ClientParameters: ConnectionOAuthClientResponseParameters | None + AuthorizationEndpoint: HttpsEndpoint | None + HttpMethod: ConnectionOAuthHttpMethod | None + OAuthHttpParameters: ConnectionHttpParameters | None class ConnectionBasicAuthResponseParameters(TypedDict, total=False): - Username: Optional[AuthHeaderParameters] + Username: AuthHeaderParameters | None class ConnectionAuthResponseParameters(TypedDict, total=False): - BasicAuthParameters: Optional[ConnectionBasicAuthResponseParameters] - OAuthParameters: Optional[ConnectionOAuthResponseParameters] - ApiKeyAuthParameters: Optional[ConnectionApiKeyAuthResponseParameters] - InvocationHttpParameters: Optional[ConnectionHttpParameters] - ConnectivityParameters: Optional[DescribeConnectionConnectivityParameters] + BasicAuthParameters: ConnectionBasicAuthResponseParameters | None + OAuthParameters: ConnectionOAuthResponseParameters | None + ApiKeyAuthParameters: ConnectionApiKeyAuthResponseParameters | None + InvocationHttpParameters: ConnectionHttpParameters | None + ConnectivityParameters: DescribeConnectionConnectivityParameters | None -ConnectionResponseList = List[Connection] +ConnectionResponseList = list[Connection] class ConnectivityResourceConfigurationArn(TypedDict, total=False): @@ -482,34 +494,34 @@ class ConnectivityResourceParameters(TypedDict, total=False): class CreateApiDestinationRequest(ServiceRequest): Name: ApiDestinationName - Description: Optional[ApiDestinationDescription] + Description: ApiDestinationDescription | None ConnectionArn: ConnectionArn InvocationEndpoint: HttpsEndpoint HttpMethod: ApiDestinationHttpMethod - InvocationRateLimitPerSecond: Optional[ApiDestinationInvocationRateLimitPerSecond] + InvocationRateLimitPerSecond: ApiDestinationInvocationRateLimitPerSecond | None class CreateApiDestinationResponse(TypedDict, total=False): - ApiDestinationArn: Optional[ApiDestinationArn] - ApiDestinationState: Optional[ApiDestinationState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + ApiDestinationArn: ApiDestinationArn | None + ApiDestinationState: ApiDestinationState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class CreateArchiveRequest(ServiceRequest): ArchiveName: ArchiveName EventSourceArn: EventBusArn - Description: Optional[ArchiveDescription] - EventPattern: Optional[EventPattern] - RetentionDays: Optional[RetentionDays] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] + Description: ArchiveDescription | None + EventPattern: EventPattern | None + RetentionDays: RetentionDays | None + KmsKeyIdentifier: KmsKeyIdentifier | None class CreateArchiveResponse(TypedDict, total=False): - ArchiveArn: Optional[ArchiveArn] - State: Optional[ArchiveState] - StateReason: Optional[ArchiveStateReason] - CreationTime: Optional[Timestamp] + ArchiveArn: ArchiveArn | None + State: ArchiveState | None + StateReason: ArchiveStateReason | None + CreationTime: Timestamp | None class CreateConnectionApiKeyAuthRequestParameters(TypedDict, total=False): @@ -526,7 +538,7 @@ class CreateConnectionOAuthRequestParameters(TypedDict, total=False): ClientParameters: CreateConnectionOAuthClientRequestParameters AuthorizationEndpoint: HttpsEndpoint HttpMethod: ConnectionOAuthHttpMethod - OAuthHttpParameters: Optional[ConnectionHttpParameters] + OAuthHttpParameters: ConnectionHttpParameters | None class CreateConnectionBasicAuthRequestParameters(TypedDict, total=False): @@ -535,38 +547,38 @@ class CreateConnectionBasicAuthRequestParameters(TypedDict, total=False): class CreateConnectionAuthRequestParameters(TypedDict, total=False): - BasicAuthParameters: Optional[CreateConnectionBasicAuthRequestParameters] - OAuthParameters: Optional[CreateConnectionOAuthRequestParameters] - ApiKeyAuthParameters: Optional[CreateConnectionApiKeyAuthRequestParameters] - InvocationHttpParameters: Optional[ConnectionHttpParameters] - ConnectivityParameters: Optional[ConnectivityResourceParameters] + BasicAuthParameters: CreateConnectionBasicAuthRequestParameters | None + OAuthParameters: CreateConnectionOAuthRequestParameters | None + ApiKeyAuthParameters: CreateConnectionApiKeyAuthRequestParameters | None + InvocationHttpParameters: ConnectionHttpParameters | None + ConnectivityParameters: ConnectivityResourceParameters | None class CreateConnectionRequest(ServiceRequest): Name: ConnectionName - Description: Optional[ConnectionDescription] + Description: ConnectionDescription | None AuthorizationType: ConnectionAuthorizationType AuthParameters: CreateConnectionAuthRequestParameters - InvocationConnectivityParameters: Optional[ConnectivityResourceParameters] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] + InvocationConnectivityParameters: ConnectivityResourceParameters | None + KmsKeyIdentifier: KmsKeyIdentifier | None class CreateConnectionResponse(TypedDict, total=False): - ConnectionArn: Optional[ConnectionArn] - ConnectionState: Optional[ConnectionState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + ConnectionArn: ConnectionArn | None + ConnectionState: ConnectionState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class EndpointEventBus(TypedDict, total=False): EventBusArn: NonPartnerEventBusArn -EndpointEventBusList = List[EndpointEventBus] +EndpointEventBusList = list[EndpointEventBus] class ReplicationConfig(TypedDict, total=False): - State: Optional[ReplicationState] + State: ReplicationState | None class Secondary(TypedDict, total=False): @@ -588,21 +600,21 @@ class RoutingConfig(TypedDict, total=False): class CreateEndpointRequest(ServiceRequest): Name: EndpointName - Description: Optional[EndpointDescription] + Description: EndpointDescription | None RoutingConfig: RoutingConfig - ReplicationConfig: Optional[ReplicationConfig] + ReplicationConfig: ReplicationConfig | None EventBuses: EndpointEventBusList - RoleArn: Optional[IamRoleArn] + RoleArn: IamRoleArn | None class CreateEndpointResponse(TypedDict, total=False): - Name: Optional[EndpointName] - Arn: Optional[EndpointArn] - RoutingConfig: Optional[RoutingConfig] - ReplicationConfig: Optional[ReplicationConfig] - EventBuses: Optional[EndpointEventBusList] - RoleArn: Optional[IamRoleArn] - State: Optional[EndpointState] + Name: EndpointName | None + Arn: EndpointArn | None + RoutingConfig: RoutingConfig | None + ReplicationConfig: ReplicationConfig | None + EventBuses: EndpointEventBusList | None + RoleArn: IamRoleArn | None + State: EndpointState | None class Tag(TypedDict, total=False): @@ -610,27 +622,34 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] + + +class LogConfig(TypedDict, total=False): + IncludeDetail: IncludeDetail | None + Level: Level | None class DeadLetterConfig(TypedDict, total=False): - Arn: Optional[ResourceArn] + Arn: ResourceArn | None class CreateEventBusRequest(ServiceRequest): Name: EventBusName - EventSourceName: Optional[EventSourceName] - Description: Optional[EventBusDescription] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] - DeadLetterConfig: Optional[DeadLetterConfig] - Tags: Optional[TagList] + EventSourceName: EventSourceName | None + Description: EventBusDescription | None + KmsKeyIdentifier: KmsKeyIdentifier | None + DeadLetterConfig: DeadLetterConfig | None + LogConfig: LogConfig | None + Tags: TagList | None class CreateEventBusResponse(TypedDict, total=False): - EventBusArn: Optional[String] - Description: Optional[EventBusDescription] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] - DeadLetterConfig: Optional[DeadLetterConfig] + EventBusArn: String | None + Description: EventBusDescription | None + KmsKeyIdentifier: KmsKeyIdentifier | None + DeadLetterConfig: DeadLetterConfig | None + LogConfig: LogConfig | None class CreatePartnerEventSourceRequest(ServiceRequest): @@ -639,7 +658,7 @@ class CreatePartnerEventSourceRequest(ServiceRequest): class CreatePartnerEventSourceResponse(TypedDict, total=False): - EventSourceArn: Optional[String] + EventSourceArn: String | None class DeactivateEventSourceRequest(ServiceRequest): @@ -651,11 +670,11 @@ class DeauthorizeConnectionRequest(ServiceRequest): class DeauthorizeConnectionResponse(TypedDict, total=False): - ConnectionArn: Optional[ConnectionArn] - ConnectionState: Optional[ConnectionState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] - LastAuthorizedTime: Optional[Timestamp] + ConnectionArn: ConnectionArn | None + ConnectionState: ConnectionState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None + LastAuthorizedTime: Timestamp | None class DeleteApiDestinationRequest(ServiceRequest): @@ -679,11 +698,11 @@ class DeleteConnectionRequest(ServiceRequest): class DeleteConnectionResponse(TypedDict, total=False): - ConnectionArn: Optional[ConnectionArn] - ConnectionState: Optional[ConnectionState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] - LastAuthorizedTime: Optional[Timestamp] + ConnectionArn: ConnectionArn | None + ConnectionState: ConnectionState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None + LastAuthorizedTime: Timestamp | None class DeleteEndpointRequest(ServiceRequest): @@ -705,8 +724,8 @@ class DeletePartnerEventSourceRequest(ServiceRequest): class DeleteRuleRequest(ServiceRequest): Name: RuleName - EventBusName: Optional[EventBusNameOrArn] - Force: Optional[Boolean] + EventBusName: EventBusNameOrArn | None + Force: Boolean | None class DescribeApiDestinationRequest(ServiceRequest): @@ -714,16 +733,16 @@ class DescribeApiDestinationRequest(ServiceRequest): class DescribeApiDestinationResponse(TypedDict, total=False): - ApiDestinationArn: Optional[ApiDestinationArn] - Name: Optional[ApiDestinationName] - Description: Optional[ApiDestinationDescription] - ApiDestinationState: Optional[ApiDestinationState] - ConnectionArn: Optional[ConnectionArn] - InvocationEndpoint: Optional[HttpsEndpoint] - HttpMethod: Optional[ApiDestinationHttpMethod] - InvocationRateLimitPerSecond: Optional[ApiDestinationInvocationRateLimitPerSecond] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + ApiDestinationArn: ApiDestinationArn | None + Name: ApiDestinationName | None + Description: ApiDestinationDescription | None + ApiDestinationState: ApiDestinationState | None + ConnectionArn: ConnectionArn | None + InvocationEndpoint: HttpsEndpoint | None + HttpMethod: ApiDestinationHttpMethod | None + InvocationRateLimitPerSecond: ApiDestinationInvocationRateLimitPerSecond | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class DescribeArchiveRequest(ServiceRequest): @@ -731,18 +750,18 @@ class DescribeArchiveRequest(ServiceRequest): class DescribeArchiveResponse(TypedDict, total=False): - ArchiveArn: Optional[ArchiveArn] - ArchiveName: Optional[ArchiveName] - EventSourceArn: Optional[EventBusArn] - Description: Optional[ArchiveDescription] - EventPattern: Optional[EventPattern] - State: Optional[ArchiveState] - StateReason: Optional[ArchiveStateReason] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] - RetentionDays: Optional[RetentionDays] - SizeBytes: Optional[Long] - EventCount: Optional[Long] - CreationTime: Optional[Timestamp] + ArchiveArn: ArchiveArn | None + ArchiveName: ArchiveName | None + EventSourceArn: EventBusArn | None + Description: ArchiveDescription | None + EventPattern: EventPattern | None + State: ArchiveState | None + StateReason: ArchiveStateReason | None + KmsKeyIdentifier: KmsKeyIdentifier | None + RetentionDays: RetentionDays | None + SizeBytes: Long | None + EventCount: Long | None + CreationTime: Timestamp | None class DescribeConnectionRequest(ServiceRequest): @@ -750,55 +769,56 @@ class DescribeConnectionRequest(ServiceRequest): class DescribeConnectionResponse(TypedDict, total=False): - ConnectionArn: Optional[ConnectionArn] - Name: Optional[ConnectionName] - Description: Optional[ConnectionDescription] - InvocationConnectivityParameters: Optional[DescribeConnectionConnectivityParameters] - ConnectionState: Optional[ConnectionState] - StateReason: Optional[ConnectionStateReason] - AuthorizationType: Optional[ConnectionAuthorizationType] - SecretArn: Optional[SecretsManagerSecretArn] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] - AuthParameters: Optional[ConnectionAuthResponseParameters] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] - LastAuthorizedTime: Optional[Timestamp] + ConnectionArn: ConnectionArn | None + Name: ConnectionName | None + Description: ConnectionDescription | None + InvocationConnectivityParameters: DescribeConnectionConnectivityParameters | None + ConnectionState: ConnectionState | None + StateReason: ConnectionStateReason | None + AuthorizationType: ConnectionAuthorizationType | None + SecretArn: SecretsManagerSecretArn | None + KmsKeyIdentifier: KmsKeyIdentifier | None + AuthParameters: ConnectionAuthResponseParameters | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None + LastAuthorizedTime: Timestamp | None class DescribeEndpointRequest(ServiceRequest): Name: EndpointName - HomeRegion: Optional[HomeRegion] + HomeRegion: HomeRegion | None class DescribeEndpointResponse(TypedDict, total=False): - Name: Optional[EndpointName] - Description: Optional[EndpointDescription] - Arn: Optional[EndpointArn] - RoutingConfig: Optional[RoutingConfig] - ReplicationConfig: Optional[ReplicationConfig] - EventBuses: Optional[EndpointEventBusList] - RoleArn: Optional[IamRoleArn] - EndpointId: Optional[EndpointId] - EndpointUrl: Optional[EndpointUrl] - State: Optional[EndpointState] - StateReason: Optional[EndpointStateReason] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Name: EndpointName | None + Description: EndpointDescription | None + Arn: EndpointArn | None + RoutingConfig: RoutingConfig | None + ReplicationConfig: ReplicationConfig | None + EventBuses: EndpointEventBusList | None + RoleArn: IamRoleArn | None + EndpointId: EndpointId | None + EndpointUrl: EndpointUrl | None + State: EndpointState | None + StateReason: EndpointStateReason | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class DescribeEventBusRequest(ServiceRequest): - Name: Optional[EventBusNameOrArn] + Name: EventBusNameOrArn | None class DescribeEventBusResponse(TypedDict, total=False): - Name: Optional[String] - Arn: Optional[String] - Description: Optional[EventBusDescription] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] - DeadLetterConfig: Optional[DeadLetterConfig] - Policy: Optional[String] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Name: String | None + Arn: String | None + Description: EventBusDescription | None + KmsKeyIdentifier: KmsKeyIdentifier | None + DeadLetterConfig: DeadLetterConfig | None + Policy: String | None + LogConfig: LogConfig | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class DescribeEventSourceRequest(ServiceRequest): @@ -806,12 +826,12 @@ class DescribeEventSourceRequest(ServiceRequest): class DescribeEventSourceResponse(TypedDict, total=False): - Arn: Optional[String] - CreatedBy: Optional[String] - CreationTime: Optional[Timestamp] - ExpirationTime: Optional[Timestamp] - Name: Optional[String] - State: Optional[EventSourceState] + Arn: String | None + CreatedBy: String | None + CreationTime: Timestamp | None + ExpirationTime: Timestamp | None + Name: String | None + State: EventSourceState | None class DescribePartnerEventSourceRequest(ServiceRequest): @@ -819,165 +839,161 @@ class DescribePartnerEventSourceRequest(ServiceRequest): class DescribePartnerEventSourceResponse(TypedDict, total=False): - Arn: Optional[String] - Name: Optional[String] + Arn: String | None + Name: String | None class DescribeReplayRequest(ServiceRequest): ReplayName: ReplayName -ReplayDestinationFilters = List[Arn] +ReplayDestinationFilters = list[Arn] class ReplayDestination(TypedDict, total=False): Arn: Arn - FilterArns: Optional[ReplayDestinationFilters] + FilterArns: ReplayDestinationFilters | None class DescribeReplayResponse(TypedDict, total=False): - ReplayName: Optional[ReplayName] - ReplayArn: Optional[ReplayArn] - Description: Optional[ReplayDescription] - State: Optional[ReplayState] - StateReason: Optional[ReplayStateReason] - EventSourceArn: Optional[ArchiveArn] - Destination: Optional[ReplayDestination] - EventStartTime: Optional[Timestamp] - EventEndTime: Optional[Timestamp] - EventLastReplayedTime: Optional[Timestamp] - ReplayStartTime: Optional[Timestamp] - ReplayEndTime: Optional[Timestamp] + ReplayName: ReplayName | None + ReplayArn: ReplayArn | None + Description: ReplayDescription | None + State: ReplayState | None + StateReason: ReplayStateReason | None + EventSourceArn: ArchiveArn | None + Destination: ReplayDestination | None + EventStartTime: Timestamp | None + EventEndTime: Timestamp | None + EventLastReplayedTime: Timestamp | None + ReplayStartTime: Timestamp | None + ReplayEndTime: Timestamp | None class DescribeRuleRequest(ServiceRequest): Name: RuleName - EventBusName: Optional[EventBusNameOrArn] + EventBusName: EventBusNameOrArn | None class DescribeRuleResponse(TypedDict, total=False): - Name: Optional[RuleName] - Arn: Optional[RuleArn] - EventPattern: Optional[EventPattern] - ScheduleExpression: Optional[ScheduleExpression] - State: Optional[RuleState] - Description: Optional[RuleDescription] - RoleArn: Optional[RoleArn] - ManagedBy: Optional[ManagedBy] - EventBusName: Optional[EventBusName] - CreatedBy: Optional[CreatedBy] + Name: RuleName | None + Arn: RuleArn | None + EventPattern: EventPattern | None + ScheduleExpression: ScheduleExpression | None + State: RuleState | None + Description: RuleDescription | None + RoleArn: RoleArn | None + ManagedBy: ManagedBy | None + EventBusName: EventBusName | None + CreatedBy: CreatedBy | None class DisableRuleRequest(ServiceRequest): Name: RuleName - EventBusName: Optional[EventBusNameOrArn] - - -PlacementStrategy = TypedDict( - "PlacementStrategy", - { - "type": Optional[PlacementStrategyType], - "field": Optional[PlacementStrategyField], - }, - total=False, -) -PlacementStrategies = List[PlacementStrategy] -PlacementConstraint = TypedDict( - "PlacementConstraint", - { - "type": Optional[PlacementConstraintType], - "expression": Optional[PlacementConstraintExpression], - }, - total=False, -) -PlacementConstraints = List[PlacementConstraint] + EventBusName: EventBusNameOrArn | None + + +class PlacementStrategy(TypedDict, total=False): + type: PlacementStrategyType | None + field: PlacementStrategyField | None + + +PlacementStrategies = list[PlacementStrategy] + + +class PlacementConstraint(TypedDict, total=False): + type: PlacementConstraintType | None + expression: PlacementConstraintExpression | None + + +PlacementConstraints = list[PlacementConstraint] class NetworkConfiguration(TypedDict, total=False): - awsvpcConfiguration: Optional[AwsVpcConfiguration] + awsvpcConfiguration: AwsVpcConfiguration | None class EcsParameters(TypedDict, total=False): TaskDefinitionArn: Arn - TaskCount: Optional[LimitMin1] - LaunchType: Optional[LaunchType] - NetworkConfiguration: Optional[NetworkConfiguration] - PlatformVersion: Optional[String] - Group: Optional[String] - CapacityProviderStrategy: Optional[CapacityProviderStrategy] - EnableECSManagedTags: Optional[Boolean] - EnableExecuteCommand: Optional[Boolean] - PlacementConstraints: Optional[PlacementConstraints] - PlacementStrategy: Optional[PlacementStrategies] - PropagateTags: Optional[PropagateTags] - ReferenceId: Optional[ReferenceId] - Tags: Optional[TagList] + TaskCount: LimitMin1 | None + LaunchType: LaunchType | None + NetworkConfiguration: NetworkConfiguration | None + PlatformVersion: String | None + Group: String | None + CapacityProviderStrategy: CapacityProviderStrategy | None + EnableECSManagedTags: Boolean | None + EnableExecuteCommand: Boolean | None + PlacementConstraints: PlacementConstraints | None + PlacementStrategy: PlacementStrategies | None + PropagateTags: PropagateTags | None + ReferenceId: ReferenceId | None + Tags: TagList | None class EnableRuleRequest(ServiceRequest): Name: RuleName - EventBusName: Optional[EventBusNameOrArn] + EventBusName: EventBusNameOrArn | None class Endpoint(TypedDict, total=False): - Name: Optional[EndpointName] - Description: Optional[EndpointDescription] - Arn: Optional[EndpointArn] - RoutingConfig: Optional[RoutingConfig] - ReplicationConfig: Optional[ReplicationConfig] - EventBuses: Optional[EndpointEventBusList] - RoleArn: Optional[IamRoleArn] - EndpointId: Optional[EndpointId] - EndpointUrl: Optional[EndpointUrl] - State: Optional[EndpointState] - StateReason: Optional[EndpointStateReason] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Name: EndpointName | None + Description: EndpointDescription | None + Arn: EndpointArn | None + RoutingConfig: RoutingConfig | None + ReplicationConfig: ReplicationConfig | None + EventBuses: EndpointEventBusList | None + RoleArn: IamRoleArn | None + EndpointId: EndpointId | None + EndpointUrl: EndpointUrl | None + State: EndpointState | None + StateReason: EndpointStateReason | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None -EndpointList = List[Endpoint] +EndpointList = list[Endpoint] class EventBus(TypedDict, total=False): - Name: Optional[String] - Arn: Optional[String] - Description: Optional[EventBusDescription] - Policy: Optional[String] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Name: String | None + Arn: String | None + Description: EventBusDescription | None + Policy: String | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None -EventBusList = List[EventBus] -EventResourceList = List[EventResource] +EventBusList = list[EventBus] +EventResourceList = list[EventResource] class EventSource(TypedDict, total=False): - Arn: Optional[String] - CreatedBy: Optional[String] - CreationTime: Optional[Timestamp] - ExpirationTime: Optional[Timestamp] - Name: Optional[String] - State: Optional[EventSourceState] + Arn: String | None + CreatedBy: String | None + CreationTime: Timestamp | None + ExpirationTime: Timestamp | None + Name: String | None + State: EventSourceState | None -EventSourceList = List[EventSource] +EventSourceList = list[EventSource] EventTime = datetime -HeaderParametersMap = Dict[HeaderKey, HeaderValue] -QueryStringParametersMap = Dict[QueryStringKey, QueryStringValue] -PathParameterList = List[PathParameter] +HeaderParametersMap = dict[HeaderKey, HeaderValue] +QueryStringParametersMap = dict[QueryStringKey, QueryStringValue] +PathParameterList = list[PathParameter] class HttpParameters(TypedDict, total=False): - PathParameterValues: Optional[PathParameterList] - HeaderParameters: Optional[HeaderParametersMap] - QueryStringParameters: Optional[QueryStringParametersMap] + PathParameterValues: PathParameterList | None + HeaderParameters: HeaderParametersMap | None + QueryStringParameters: QueryStringParametersMap | None -TransformerPaths = Dict[InputTransformerPathKey, TargetInputPath] +TransformerPaths = dict[InputTransformerPathKey, TargetInputPath] class InputTransformer(TypedDict, total=False): - InputPathsMap: Optional[TransformerPaths] + InputPathsMap: TransformerPaths | None InputTemplate: TransformerInput @@ -986,184 +1002,184 @@ class KinesisParameters(TypedDict, total=False): class ListApiDestinationsRequest(ServiceRequest): - NamePrefix: Optional[ApiDestinationName] - ConnectionArn: Optional[ConnectionArn] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: ApiDestinationName | None + ConnectionArn: ConnectionArn | None + NextToken: NextToken | None + Limit: LimitMax100 | None class ListApiDestinationsResponse(TypedDict, total=False): - ApiDestinations: Optional[ApiDestinationResponseList] - NextToken: Optional[NextToken] + ApiDestinations: ApiDestinationResponseList | None + NextToken: NextToken | None class ListArchivesRequest(ServiceRequest): - NamePrefix: Optional[ArchiveName] - EventSourceArn: Optional[EventBusArn] - State: Optional[ArchiveState] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: ArchiveName | None + EventSourceArn: EventBusArn | None + State: ArchiveState | None + NextToken: NextToken | None + Limit: LimitMax100 | None class ListArchivesResponse(TypedDict, total=False): - Archives: Optional[ArchiveResponseList] - NextToken: Optional[NextToken] + Archives: ArchiveResponseList | None + NextToken: NextToken | None class ListConnectionsRequest(ServiceRequest): - NamePrefix: Optional[ConnectionName] - ConnectionState: Optional[ConnectionState] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: ConnectionName | None + ConnectionState: ConnectionState | None + NextToken: NextToken | None + Limit: LimitMax100 | None class ListConnectionsResponse(TypedDict, total=False): - Connections: Optional[ConnectionResponseList] - NextToken: Optional[NextToken] + Connections: ConnectionResponseList | None + NextToken: NextToken | None class ListEndpointsRequest(ServiceRequest): - NamePrefix: Optional[EndpointName] - HomeRegion: Optional[HomeRegion] - NextToken: Optional[NextToken] - MaxResults: Optional[LimitMax100] + NamePrefix: EndpointName | None + HomeRegion: HomeRegion | None + NextToken: NextToken | None + MaxResults: LimitMax100 | None class ListEndpointsResponse(TypedDict, total=False): - Endpoints: Optional[EndpointList] - NextToken: Optional[NextToken] + Endpoints: EndpointList | None + NextToken: NextToken | None class ListEventBusesRequest(ServiceRequest): - NamePrefix: Optional[EventBusName] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: EventBusName | None + NextToken: NextToken | None + Limit: LimitMax100 | None class ListEventBusesResponse(TypedDict, total=False): - EventBuses: Optional[EventBusList] - NextToken: Optional[NextToken] + EventBuses: EventBusList | None + NextToken: NextToken | None class ListEventSourcesRequest(ServiceRequest): - NamePrefix: Optional[EventSourceNamePrefix] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: EventSourceNamePrefix | None + NextToken: NextToken | None + Limit: LimitMax100 | None class ListEventSourcesResponse(TypedDict, total=False): - EventSources: Optional[EventSourceList] - NextToken: Optional[NextToken] + EventSources: EventSourceList | None + NextToken: NextToken | None class ListPartnerEventSourceAccountsRequest(ServiceRequest): EventSourceName: EventSourceName - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NextToken: NextToken | None + Limit: LimitMax100 | None class PartnerEventSourceAccount(TypedDict, total=False): - Account: Optional[AccountId] - CreationTime: Optional[Timestamp] - ExpirationTime: Optional[Timestamp] - State: Optional[EventSourceState] + Account: AccountId | None + CreationTime: Timestamp | None + ExpirationTime: Timestamp | None + State: EventSourceState | None -PartnerEventSourceAccountList = List[PartnerEventSourceAccount] +PartnerEventSourceAccountList = list[PartnerEventSourceAccount] class ListPartnerEventSourceAccountsResponse(TypedDict, total=False): - PartnerEventSourceAccounts: Optional[PartnerEventSourceAccountList] - NextToken: Optional[NextToken] + PartnerEventSourceAccounts: PartnerEventSourceAccountList | None + NextToken: NextToken | None class ListPartnerEventSourcesRequest(ServiceRequest): NamePrefix: PartnerEventSourceNamePrefix - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NextToken: NextToken | None + Limit: LimitMax100 | None class PartnerEventSource(TypedDict, total=False): - Arn: Optional[String] - Name: Optional[String] + Arn: String | None + Name: String | None -PartnerEventSourceList = List[PartnerEventSource] +PartnerEventSourceList = list[PartnerEventSource] class ListPartnerEventSourcesResponse(TypedDict, total=False): - PartnerEventSources: Optional[PartnerEventSourceList] - NextToken: Optional[NextToken] + PartnerEventSources: PartnerEventSourceList | None + NextToken: NextToken | None class ListReplaysRequest(ServiceRequest): - NamePrefix: Optional[ReplayName] - State: Optional[ReplayState] - EventSourceArn: Optional[ArchiveArn] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: ReplayName | None + State: ReplayState | None + EventSourceArn: ArchiveArn | None + NextToken: NextToken | None + Limit: LimitMax100 | None class Replay(TypedDict, total=False): - ReplayName: Optional[ReplayName] - EventSourceArn: Optional[ArchiveArn] - State: Optional[ReplayState] - StateReason: Optional[ReplayStateReason] - EventStartTime: Optional[Timestamp] - EventEndTime: Optional[Timestamp] - EventLastReplayedTime: Optional[Timestamp] - ReplayStartTime: Optional[Timestamp] - ReplayEndTime: Optional[Timestamp] + ReplayName: ReplayName | None + EventSourceArn: ArchiveArn | None + State: ReplayState | None + StateReason: ReplayStateReason | None + EventStartTime: Timestamp | None + EventEndTime: Timestamp | None + EventLastReplayedTime: Timestamp | None + ReplayStartTime: Timestamp | None + ReplayEndTime: Timestamp | None -ReplayList = List[Replay] +ReplayList = list[Replay] class ListReplaysResponse(TypedDict, total=False): - Replays: Optional[ReplayList] - NextToken: Optional[NextToken] + Replays: ReplayList | None + NextToken: NextToken | None class ListRuleNamesByTargetRequest(ServiceRequest): TargetArn: TargetArn - EventBusName: Optional[EventBusNameOrArn] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + EventBusName: EventBusNameOrArn | None + NextToken: NextToken | None + Limit: LimitMax100 | None -RuleNameList = List[RuleName] +RuleNameList = list[RuleName] class ListRuleNamesByTargetResponse(TypedDict, total=False): - RuleNames: Optional[RuleNameList] - NextToken: Optional[NextToken] + RuleNames: RuleNameList | None + NextToken: NextToken | None class ListRulesRequest(ServiceRequest): - NamePrefix: Optional[RuleName] - EventBusName: Optional[EventBusNameOrArn] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: RuleName | None + EventBusName: EventBusNameOrArn | None + NextToken: NextToken | None + Limit: LimitMax100 | None class Rule(TypedDict, total=False): - Name: Optional[RuleName] - Arn: Optional[RuleArn] - EventPattern: Optional[EventPattern] - State: Optional[RuleState] - Description: Optional[RuleDescription] - ScheduleExpression: Optional[ScheduleExpression] - RoleArn: Optional[RoleArn] - ManagedBy: Optional[ManagedBy] - EventBusName: Optional[EventBusName] + Name: RuleName | None + Arn: RuleArn | None + EventPattern: EventPattern | None + State: RuleState | None + Description: RuleDescription | None + ScheduleExpression: ScheduleExpression | None + RoleArn: RoleArn | None + ManagedBy: ManagedBy | None + EventBusName: EventBusName | None -RuleResponseList = List[Rule] +RuleResponseList = list[Rule] class ListRulesResponse(TypedDict, total=False): - Rules: Optional[RuleResponseList] - NextToken: Optional[NextToken] + Rules: RuleResponseList | None + NextToken: NextToken | None class ListTagsForResourceRequest(ServiceRequest): @@ -1171,19 +1187,19 @@ class ListTagsForResourceRequest(ServiceRequest): class ListTagsForResourceResponse(TypedDict, total=False): - Tags: Optional[TagList] + Tags: TagList | None class ListTargetsByRuleRequest(ServiceRequest): Rule: RuleName - EventBusName: Optional[EventBusNameOrArn] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + EventBusName: EventBusNameOrArn | None + NextToken: NextToken | None + Limit: LimitMax100 | None class RetryPolicy(TypedDict, total=False): - MaximumRetryAttempts: Optional[MaximumRetryAttempts] - MaximumEventAgeInSeconds: Optional[MaximumEventAgeInSeconds] + MaximumRetryAttempts: MaximumRetryAttempts | None + MaximumEventAgeInSeconds: MaximumEventAgeInSeconds | None class SageMakerPipelineParameter(TypedDict, total=False): @@ -1191,31 +1207,31 @@ class SageMakerPipelineParameter(TypedDict, total=False): Value: SageMakerPipelineParameterValue -SageMakerPipelineParameterList = List[SageMakerPipelineParameter] +SageMakerPipelineParameterList = list[SageMakerPipelineParameter] class SageMakerPipelineParameters(TypedDict, total=False): - PipelineParameterList: Optional[SageMakerPipelineParameterList] + PipelineParameterList: SageMakerPipelineParameterList | None -Sqls = List[Sql] +Sqls = list[Sql] class RedshiftDataParameters(TypedDict, total=False): - SecretManagerArn: Optional[RedshiftSecretManagerArn] + SecretManagerArn: RedshiftSecretManagerArn | None Database: Database - DbUser: Optional[DbUser] - Sql: Optional[Sql] - StatementName: Optional[StatementName] - WithEvent: Optional[Boolean] - Sqls: Optional[Sqls] + DbUser: DbUser | None + Sql: Sql | None + StatementName: StatementName | None + WithEvent: Boolean | None + Sqls: Sqls | None class SqsParameters(TypedDict, total=False): - MessageGroupId: Optional[MessageGroupId] + MessageGroupId: MessageGroupId | None -RunCommandTargetValues = List[RunCommandTargetValue] +RunCommandTargetValues = list[RunCommandTargetValue] class RunCommandTarget(TypedDict, total=False): @@ -1223,7 +1239,7 @@ class RunCommandTarget(TypedDict, total=False): Values: RunCommandTargetValues -RunCommandTargets = List[RunCommandTarget] +RunCommandTargets = list[RunCommandTarget] class RunCommandParameters(TypedDict, total=False): @@ -1233,72 +1249,72 @@ class RunCommandParameters(TypedDict, total=False): class Target(TypedDict, total=False): Id: TargetId Arn: TargetArn - RoleArn: Optional[RoleArn] - Input: Optional[TargetInput] - InputPath: Optional[TargetInputPath] - InputTransformer: Optional[InputTransformer] - KinesisParameters: Optional[KinesisParameters] - RunCommandParameters: Optional[RunCommandParameters] - EcsParameters: Optional[EcsParameters] - BatchParameters: Optional[BatchParameters] - SqsParameters: Optional[SqsParameters] - HttpParameters: Optional[HttpParameters] - RedshiftDataParameters: Optional[RedshiftDataParameters] - SageMakerPipelineParameters: Optional[SageMakerPipelineParameters] - DeadLetterConfig: Optional[DeadLetterConfig] - RetryPolicy: Optional[RetryPolicy] - AppSyncParameters: Optional[AppSyncParameters] - - -TargetList = List[Target] + RoleArn: RoleArn | None + Input: TargetInput | None + InputPath: TargetInputPath | None + InputTransformer: InputTransformer | None + KinesisParameters: KinesisParameters | None + RunCommandParameters: RunCommandParameters | None + EcsParameters: EcsParameters | None + BatchParameters: BatchParameters | None + SqsParameters: SqsParameters | None + HttpParameters: HttpParameters | None + RedshiftDataParameters: RedshiftDataParameters | None + SageMakerPipelineParameters: SageMakerPipelineParameters | None + DeadLetterConfig: DeadLetterConfig | None + RetryPolicy: RetryPolicy | None + AppSyncParameters: AppSyncParameters | None + + +TargetList = list[Target] class ListTargetsByRuleResponse(TypedDict, total=False): - Targets: Optional[TargetList] - NextToken: Optional[NextToken] + Targets: TargetList | None + NextToken: NextToken | None class PutEventsRequestEntry(TypedDict, total=False): - Time: Optional[EventTime] - Source: Optional[String] - Resources: Optional[EventResourceList] - DetailType: Optional[String] - Detail: Optional[String] - EventBusName: Optional[NonPartnerEventBusNameOrArn] - TraceHeader: Optional[TraceHeader] + Time: EventTime | None + Source: String | None + Resources: EventResourceList | None + DetailType: String | None + Detail: String | None + EventBusName: NonPartnerEventBusNameOrArn | None + TraceHeader: TraceHeader | None -PutEventsRequestEntryList = List[PutEventsRequestEntry] +PutEventsRequestEntryList = list[PutEventsRequestEntry] class PutEventsRequest(ServiceRequest): Entries: PutEventsRequestEntryList - EndpointId: Optional[EndpointId] + EndpointId: EndpointId | None class PutEventsResultEntry(TypedDict, total=False): - EventId: Optional[EventId] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + EventId: EventId | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None -PutEventsResultEntryList = List[PutEventsResultEntry] +PutEventsResultEntryList = list[PutEventsResultEntry] class PutEventsResponse(TypedDict, total=False): - FailedEntryCount: Optional[Integer] - Entries: Optional[PutEventsResultEntryList] + FailedEntryCount: Integer | None + Entries: PutEventsResultEntryList | None class PutPartnerEventsRequestEntry(TypedDict, total=False): - Time: Optional[EventTime] - Source: Optional[EventSourceName] - Resources: Optional[EventResourceList] - DetailType: Optional[String] - Detail: Optional[String] + Time: EventTime | None + Source: EventSourceName | None + Resources: EventResourceList | None + DetailType: String | None + Detail: String | None -PutPartnerEventsRequestEntryList = List[PutPartnerEventsRequestEntry] +PutPartnerEventsRequestEntryList = list[PutPartnerEventsRequestEntry] class PutPartnerEventsRequest(ServiceRequest): @@ -1306,96 +1322,96 @@ class PutPartnerEventsRequest(ServiceRequest): class PutPartnerEventsResultEntry(TypedDict, total=False): - EventId: Optional[EventId] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + EventId: EventId | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None -PutPartnerEventsResultEntryList = List[PutPartnerEventsResultEntry] +PutPartnerEventsResultEntryList = list[PutPartnerEventsResultEntry] class PutPartnerEventsResponse(TypedDict, total=False): - FailedEntryCount: Optional[Integer] - Entries: Optional[PutPartnerEventsResultEntryList] + FailedEntryCount: Integer | None + Entries: PutPartnerEventsResultEntryList | None class PutPermissionRequest(ServiceRequest): - EventBusName: Optional[NonPartnerEventBusName] - Action: Optional[Action] - Principal: Optional[Principal] - StatementId: Optional[StatementId] - Condition: Optional[Condition] - Policy: Optional[String] + EventBusName: NonPartnerEventBusName | None + Action: Action | None + Principal: Principal | None + StatementId: StatementId | None + Condition: Condition | None + Policy: String | None class PutRuleRequest(ServiceRequest): Name: RuleName - ScheduleExpression: Optional[ScheduleExpression] - EventPattern: Optional[EventPattern] - State: Optional[RuleState] - Description: Optional[RuleDescription] - RoleArn: Optional[RoleArn] - Tags: Optional[TagList] - EventBusName: Optional[EventBusNameOrArn] + ScheduleExpression: ScheduleExpression | None + EventPattern: EventPattern | None + State: RuleState | None + Description: RuleDescription | None + RoleArn: RoleArn | None + Tags: TagList | None + EventBusName: EventBusNameOrArn | None class PutRuleResponse(TypedDict, total=False): - RuleArn: Optional[RuleArn] + RuleArn: RuleArn | None class PutTargetsRequest(ServiceRequest): Rule: RuleName - EventBusName: Optional[EventBusNameOrArn] + EventBusName: EventBusNameOrArn | None Targets: TargetList class PutTargetsResultEntry(TypedDict, total=False): - TargetId: Optional[TargetId] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + TargetId: TargetId | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None -PutTargetsResultEntryList = List[PutTargetsResultEntry] +PutTargetsResultEntryList = list[PutTargetsResultEntry] class PutTargetsResponse(TypedDict, total=False): - FailedEntryCount: Optional[Integer] - FailedEntries: Optional[PutTargetsResultEntryList] + FailedEntryCount: Integer | None + FailedEntries: PutTargetsResultEntryList | None class RemovePermissionRequest(ServiceRequest): - StatementId: Optional[StatementId] - RemoveAllPermissions: Optional[Boolean] - EventBusName: Optional[NonPartnerEventBusName] + StatementId: StatementId | None + RemoveAllPermissions: Boolean | None + EventBusName: NonPartnerEventBusName | None -TargetIdList = List[TargetId] +TargetIdList = list[TargetId] class RemoveTargetsRequest(ServiceRequest): Rule: RuleName - EventBusName: Optional[EventBusNameOrArn] + EventBusName: EventBusNameOrArn | None Ids: TargetIdList - Force: Optional[Boolean] + Force: Boolean | None class RemoveTargetsResultEntry(TypedDict, total=False): - TargetId: Optional[TargetId] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + TargetId: TargetId | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None -RemoveTargetsResultEntryList = List[RemoveTargetsResultEntry] +RemoveTargetsResultEntryList = list[RemoveTargetsResultEntry] class RemoveTargetsResponse(TypedDict, total=False): - FailedEntryCount: Optional[Integer] - FailedEntries: Optional[RemoveTargetsResultEntryList] + FailedEntryCount: Integer | None + FailedEntries: RemoveTargetsResultEntryList | None class StartReplayRequest(ServiceRequest): ReplayName: ReplayName - Description: Optional[ReplayDescription] + Description: ReplayDescription | None EventSourceArn: ArchiveArn EventStartTime: Timestamp EventEndTime: Timestamp @@ -1403,13 +1419,13 @@ class StartReplayRequest(ServiceRequest): class StartReplayResponse(TypedDict, total=False): - ReplayArn: Optional[ReplayArn] - State: Optional[ReplayState] - StateReason: Optional[ReplayStateReason] - ReplayStartTime: Optional[Timestamp] + ReplayArn: ReplayArn | None + State: ReplayState | None + StateReason: ReplayStateReason | None + ReplayStartTime: Timestamp | None -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceRequest(ServiceRequest): @@ -1427,7 +1443,7 @@ class TestEventPatternRequest(ServiceRequest): class TestEventPatternResponse(TypedDict, total=False): - Result: Optional[Boolean] + Result: Boolean | None class UntagResourceRequest(ServiceRequest): @@ -1441,121 +1457,123 @@ class UntagResourceResponse(TypedDict, total=False): class UpdateApiDestinationRequest(ServiceRequest): Name: ApiDestinationName - Description: Optional[ApiDestinationDescription] - ConnectionArn: Optional[ConnectionArn] - InvocationEndpoint: Optional[HttpsEndpoint] - HttpMethod: Optional[ApiDestinationHttpMethod] - InvocationRateLimitPerSecond: Optional[ApiDestinationInvocationRateLimitPerSecond] + Description: ApiDestinationDescription | None + ConnectionArn: ConnectionArn | None + InvocationEndpoint: HttpsEndpoint | None + HttpMethod: ApiDestinationHttpMethod | None + InvocationRateLimitPerSecond: ApiDestinationInvocationRateLimitPerSecond | None class UpdateApiDestinationResponse(TypedDict, total=False): - ApiDestinationArn: Optional[ApiDestinationArn] - ApiDestinationState: Optional[ApiDestinationState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + ApiDestinationArn: ApiDestinationArn | None + ApiDestinationState: ApiDestinationState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class UpdateArchiveRequest(ServiceRequest): ArchiveName: ArchiveName - Description: Optional[ArchiveDescription] - EventPattern: Optional[EventPattern] - RetentionDays: Optional[RetentionDays] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] + Description: ArchiveDescription | None + EventPattern: EventPattern | None + RetentionDays: RetentionDays | None + KmsKeyIdentifier: KmsKeyIdentifier | None class UpdateArchiveResponse(TypedDict, total=False): - ArchiveArn: Optional[ArchiveArn] - State: Optional[ArchiveState] - StateReason: Optional[ArchiveStateReason] - CreationTime: Optional[Timestamp] + ArchiveArn: ArchiveArn | None + State: ArchiveState | None + StateReason: ArchiveStateReason | None + CreationTime: Timestamp | None class UpdateConnectionApiKeyAuthRequestParameters(TypedDict, total=False): - ApiKeyName: Optional[AuthHeaderParameters] - ApiKeyValue: Optional[AuthHeaderParametersSensitive] + ApiKeyName: AuthHeaderParameters | None + ApiKeyValue: AuthHeaderParametersSensitive | None class UpdateConnectionOAuthClientRequestParameters(TypedDict, total=False): - ClientID: Optional[AuthHeaderParameters] - ClientSecret: Optional[AuthHeaderParametersSensitive] + ClientID: AuthHeaderParameters | None + ClientSecret: AuthHeaderParametersSensitive | None class UpdateConnectionOAuthRequestParameters(TypedDict, total=False): - ClientParameters: Optional[UpdateConnectionOAuthClientRequestParameters] - AuthorizationEndpoint: Optional[HttpsEndpoint] - HttpMethod: Optional[ConnectionOAuthHttpMethod] - OAuthHttpParameters: Optional[ConnectionHttpParameters] + ClientParameters: UpdateConnectionOAuthClientRequestParameters | None + AuthorizationEndpoint: HttpsEndpoint | None + HttpMethod: ConnectionOAuthHttpMethod | None + OAuthHttpParameters: ConnectionHttpParameters | None class UpdateConnectionBasicAuthRequestParameters(TypedDict, total=False): - Username: Optional[AuthHeaderParameters] - Password: Optional[AuthHeaderParametersSensitive] + Username: AuthHeaderParameters | None + Password: AuthHeaderParametersSensitive | None class UpdateConnectionAuthRequestParameters(TypedDict, total=False): - BasicAuthParameters: Optional[UpdateConnectionBasicAuthRequestParameters] - OAuthParameters: Optional[UpdateConnectionOAuthRequestParameters] - ApiKeyAuthParameters: Optional[UpdateConnectionApiKeyAuthRequestParameters] - InvocationHttpParameters: Optional[ConnectionHttpParameters] - ConnectivityParameters: Optional[ConnectivityResourceParameters] + BasicAuthParameters: UpdateConnectionBasicAuthRequestParameters | None + OAuthParameters: UpdateConnectionOAuthRequestParameters | None + ApiKeyAuthParameters: UpdateConnectionApiKeyAuthRequestParameters | None + InvocationHttpParameters: ConnectionHttpParameters | None + ConnectivityParameters: ConnectivityResourceParameters | None class UpdateConnectionRequest(ServiceRequest): Name: ConnectionName - Description: Optional[ConnectionDescription] - AuthorizationType: Optional[ConnectionAuthorizationType] - AuthParameters: Optional[UpdateConnectionAuthRequestParameters] - InvocationConnectivityParameters: Optional[ConnectivityResourceParameters] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] + Description: ConnectionDescription | None + AuthorizationType: ConnectionAuthorizationType | None + AuthParameters: UpdateConnectionAuthRequestParameters | None + InvocationConnectivityParameters: ConnectivityResourceParameters | None + KmsKeyIdentifier: KmsKeyIdentifier | None class UpdateConnectionResponse(TypedDict, total=False): - ConnectionArn: Optional[ConnectionArn] - ConnectionState: Optional[ConnectionState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] - LastAuthorizedTime: Optional[Timestamp] + ConnectionArn: ConnectionArn | None + ConnectionState: ConnectionState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None + LastAuthorizedTime: Timestamp | None class UpdateEndpointRequest(ServiceRequest): Name: EndpointName - Description: Optional[EndpointDescription] - RoutingConfig: Optional[RoutingConfig] - ReplicationConfig: Optional[ReplicationConfig] - EventBuses: Optional[EndpointEventBusList] - RoleArn: Optional[IamRoleArn] + Description: EndpointDescription | None + RoutingConfig: RoutingConfig | None + ReplicationConfig: ReplicationConfig | None + EventBuses: EndpointEventBusList | None + RoleArn: IamRoleArn | None class UpdateEndpointResponse(TypedDict, total=False): - Name: Optional[EndpointName] - Arn: Optional[EndpointArn] - RoutingConfig: Optional[RoutingConfig] - ReplicationConfig: Optional[ReplicationConfig] - EventBuses: Optional[EndpointEventBusList] - RoleArn: Optional[IamRoleArn] - EndpointId: Optional[EndpointId] - EndpointUrl: Optional[EndpointUrl] - State: Optional[EndpointState] + Name: EndpointName | None + Arn: EndpointArn | None + RoutingConfig: RoutingConfig | None + ReplicationConfig: ReplicationConfig | None + EventBuses: EndpointEventBusList | None + RoleArn: IamRoleArn | None + EndpointId: EndpointId | None + EndpointUrl: EndpointUrl | None + State: EndpointState | None class UpdateEventBusRequest(ServiceRequest): - Name: Optional[EventBusName] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] - Description: Optional[EventBusDescription] - DeadLetterConfig: Optional[DeadLetterConfig] + Name: EventBusName | None + KmsKeyIdentifier: KmsKeyIdentifier | None + Description: EventBusDescription | None + DeadLetterConfig: DeadLetterConfig | None + LogConfig: LogConfig | None class UpdateEventBusResponse(TypedDict, total=False): - Arn: Optional[String] - Name: Optional[EventBusName] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] - Description: Optional[EventBusDescription] - DeadLetterConfig: Optional[DeadLetterConfig] + Arn: String | None + Name: EventBusName | None + KmsKeyIdentifier: KmsKeyIdentifier | None + Description: EventBusDescription | None + DeadLetterConfig: DeadLetterConfig | None + LogConfig: LogConfig | None class EventsApi: - service = "events" - version = "2015-10-07" + service: str = "events" + version: str = "2015-10-07" @handler("ActivateEventSource") def activate_event_source( @@ -1634,6 +1652,7 @@ def create_event_bus( description: EventBusDescription | None = None, kms_key_identifier: KmsKeyIdentifier | None = None, dead_letter_config: DeadLetterConfig | None = None, + log_config: LogConfig | None = None, tags: TagList | None = None, **kwargs, ) -> CreateEventBusResponse: @@ -2107,6 +2126,7 @@ def update_event_bus( kms_key_identifier: KmsKeyIdentifier | None = None, description: EventBusDescription | None = None, dead_letter_config: DeadLetterConfig | None = None, + log_config: LogConfig | None = None, **kwargs, ) -> UpdateEventBusResponse: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/firehose/__init__.py b/localstack-core/localstack/aws/api/firehose/__init__.py index f1b3c79ac204d..902bfb916e45d 100644 --- a/localstack-core/localstack/aws/api/firehose/__init__.py +++ b/localstack-core/localstack/aws/api/firehose/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -371,12 +371,12 @@ class ServiceUnavailableException(ServiceException): class AmazonOpenSearchServerlessBufferingHints(TypedDict, total=False): - IntervalInSeconds: Optional[AmazonOpenSearchServerlessBufferingIntervalInSeconds] - SizeInMBs: Optional[AmazonOpenSearchServerlessBufferingSizeInMBs] + IntervalInSeconds: AmazonOpenSearchServerlessBufferingIntervalInSeconds | None + SizeInMBs: AmazonOpenSearchServerlessBufferingSizeInMBs | None -SecurityGroupIdList = List[NonEmptyStringWithoutWhitespace] -SubnetIdList = List[NonEmptyStringWithoutWhitespace] +SecurityGroupIdList = list[NonEmptyStringWithoutWhitespace] +SubnetIdList = list[NonEmptyStringWithoutWhitespace] class VpcConfiguration(TypedDict, total=False): @@ -386,9 +386,9 @@ class VpcConfiguration(TypedDict, total=False): class CloudWatchLoggingOptions(TypedDict, total=False): - Enabled: Optional[BooleanObject] - LogGroupName: Optional[LogGroupName] - LogStreamName: Optional[LogStreamName] + Enabled: BooleanObject | None + LogGroupName: LogGroupName | None + LogStreamName: LogStreamName | None class ProcessorParameter(TypedDict, total=False): @@ -396,20 +396,20 @@ class ProcessorParameter(TypedDict, total=False): ParameterValue: ProcessorParameterValue -ProcessorParameterList = List[ProcessorParameter] +ProcessorParameterList = list[ProcessorParameter] class Processor(TypedDict, total=False): Type: ProcessorType - Parameters: Optional[ProcessorParameterList] + Parameters: ProcessorParameterList | None -ProcessorList = List[Processor] +ProcessorList = list[Processor] class ProcessingConfiguration(TypedDict, total=False): - Enabled: Optional[BooleanObject] - Processors: Optional[ProcessorList] + Enabled: BooleanObject | None + Processors: ProcessorList | None class KMSEncryptionConfig(TypedDict, total=False): @@ -417,41 +417,41 @@ class KMSEncryptionConfig(TypedDict, total=False): class EncryptionConfiguration(TypedDict, total=False): - NoEncryptionConfig: Optional[NoEncryptionConfig] - KMSEncryptionConfig: Optional[KMSEncryptionConfig] + NoEncryptionConfig: NoEncryptionConfig | None + KMSEncryptionConfig: KMSEncryptionConfig | None class BufferingHints(TypedDict, total=False): - SizeInMBs: Optional[SizeInMBs] - IntervalInSeconds: Optional[IntervalInSeconds] + SizeInMBs: SizeInMBs | None + IntervalInSeconds: IntervalInSeconds | None class S3DestinationConfiguration(TypedDict, total=False): RoleARN: RoleARN BucketARN: BucketARN - Prefix: Optional[Prefix] - ErrorOutputPrefix: Optional[ErrorOutputPrefix] - BufferingHints: Optional[BufferingHints] - CompressionFormat: Optional[CompressionFormat] - EncryptionConfiguration: Optional[EncryptionConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] + Prefix: Prefix | None + ErrorOutputPrefix: ErrorOutputPrefix | None + BufferingHints: BufferingHints | None + CompressionFormat: CompressionFormat | None + EncryptionConfiguration: EncryptionConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None class AmazonOpenSearchServerlessRetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[AmazonOpenSearchServerlessRetryDurationInSeconds] + DurationInSeconds: AmazonOpenSearchServerlessRetryDurationInSeconds | None class AmazonOpenSearchServerlessDestinationConfiguration(TypedDict, total=False): RoleARN: RoleARN - CollectionEndpoint: Optional[AmazonOpenSearchServerlessCollectionEndpoint] + CollectionEndpoint: AmazonOpenSearchServerlessCollectionEndpoint | None IndexName: AmazonOpenSearchServerlessIndexName - BufferingHints: Optional[AmazonOpenSearchServerlessBufferingHints] - RetryOptions: Optional[AmazonOpenSearchServerlessRetryOptions] - S3BackupMode: Optional[AmazonOpenSearchServerlessS3BackupMode] + BufferingHints: AmazonOpenSearchServerlessBufferingHints | None + RetryOptions: AmazonOpenSearchServerlessRetryOptions | None + S3BackupMode: AmazonOpenSearchServerlessS3BackupMode | None S3Configuration: S3DestinationConfiguration - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - VpcConfiguration: Optional[VpcConfiguration] + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + VpcConfiguration: VpcConfiguration | None class VpcConfigurationDescription(TypedDict, total=False): @@ -464,52 +464,52 @@ class VpcConfigurationDescription(TypedDict, total=False): class S3DestinationDescription(TypedDict, total=False): RoleARN: RoleARN BucketARN: BucketARN - Prefix: Optional[Prefix] - ErrorOutputPrefix: Optional[ErrorOutputPrefix] + Prefix: Prefix | None + ErrorOutputPrefix: ErrorOutputPrefix | None BufferingHints: BufferingHints CompressionFormat: CompressionFormat EncryptionConfiguration: EncryptionConfiguration - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None class AmazonOpenSearchServerlessDestinationDescription(TypedDict, total=False): - RoleARN: Optional[RoleARN] - CollectionEndpoint: Optional[AmazonOpenSearchServerlessCollectionEndpoint] - IndexName: Optional[AmazonOpenSearchServerlessIndexName] - BufferingHints: Optional[AmazonOpenSearchServerlessBufferingHints] - RetryOptions: Optional[AmazonOpenSearchServerlessRetryOptions] - S3BackupMode: Optional[AmazonOpenSearchServerlessS3BackupMode] - S3DestinationDescription: Optional[S3DestinationDescription] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - VpcConfigurationDescription: Optional[VpcConfigurationDescription] + RoleARN: RoleARN | None + CollectionEndpoint: AmazonOpenSearchServerlessCollectionEndpoint | None + IndexName: AmazonOpenSearchServerlessIndexName | None + BufferingHints: AmazonOpenSearchServerlessBufferingHints | None + RetryOptions: AmazonOpenSearchServerlessRetryOptions | None + S3BackupMode: AmazonOpenSearchServerlessS3BackupMode | None + S3DestinationDescription: S3DestinationDescription | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + VpcConfigurationDescription: VpcConfigurationDescription | None class S3DestinationUpdate(TypedDict, total=False): - RoleARN: Optional[RoleARN] - BucketARN: Optional[BucketARN] - Prefix: Optional[Prefix] - ErrorOutputPrefix: Optional[ErrorOutputPrefix] - BufferingHints: Optional[BufferingHints] - CompressionFormat: Optional[CompressionFormat] - EncryptionConfiguration: Optional[EncryptionConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] + RoleARN: RoleARN | None + BucketARN: BucketARN | None + Prefix: Prefix | None + ErrorOutputPrefix: ErrorOutputPrefix | None + BufferingHints: BufferingHints | None + CompressionFormat: CompressionFormat | None + EncryptionConfiguration: EncryptionConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None class AmazonOpenSearchServerlessDestinationUpdate(TypedDict, total=False): - RoleARN: Optional[RoleARN] - CollectionEndpoint: Optional[AmazonOpenSearchServerlessCollectionEndpoint] - IndexName: Optional[AmazonOpenSearchServerlessIndexName] - BufferingHints: Optional[AmazonOpenSearchServerlessBufferingHints] - RetryOptions: Optional[AmazonOpenSearchServerlessRetryOptions] - S3Update: Optional[S3DestinationUpdate] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] + RoleARN: RoleARN | None + CollectionEndpoint: AmazonOpenSearchServerlessCollectionEndpoint | None + IndexName: AmazonOpenSearchServerlessIndexName | None + BufferingHints: AmazonOpenSearchServerlessBufferingHints | None + RetryOptions: AmazonOpenSearchServerlessRetryOptions | None + S3Update: S3DestinationUpdate | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None class AmazonopensearchserviceBufferingHints(TypedDict, total=False): - IntervalInSeconds: Optional[AmazonopensearchserviceBufferingIntervalInSeconds] - SizeInMBs: Optional[AmazonopensearchserviceBufferingSizeInMBs] + IntervalInSeconds: AmazonopensearchserviceBufferingIntervalInSeconds | None + SizeInMBs: AmazonopensearchserviceBufferingSizeInMBs | None class DocumentIdOptions(TypedDict, total=False): @@ -517,56 +517,56 @@ class DocumentIdOptions(TypedDict, total=False): class AmazonopensearchserviceRetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[AmazonopensearchserviceRetryDurationInSeconds] + DurationInSeconds: AmazonopensearchserviceRetryDurationInSeconds | None class AmazonopensearchserviceDestinationConfiguration(TypedDict, total=False): RoleARN: RoleARN - DomainARN: Optional[AmazonopensearchserviceDomainARN] - ClusterEndpoint: Optional[AmazonopensearchserviceClusterEndpoint] + DomainARN: AmazonopensearchserviceDomainARN | None + ClusterEndpoint: AmazonopensearchserviceClusterEndpoint | None IndexName: AmazonopensearchserviceIndexName - TypeName: Optional[AmazonopensearchserviceTypeName] - IndexRotationPeriod: Optional[AmazonopensearchserviceIndexRotationPeriod] - BufferingHints: Optional[AmazonopensearchserviceBufferingHints] - RetryOptions: Optional[AmazonopensearchserviceRetryOptions] - S3BackupMode: Optional[AmazonopensearchserviceS3BackupMode] + TypeName: AmazonopensearchserviceTypeName | None + IndexRotationPeriod: AmazonopensearchserviceIndexRotationPeriod | None + BufferingHints: AmazonopensearchserviceBufferingHints | None + RetryOptions: AmazonopensearchserviceRetryOptions | None + S3BackupMode: AmazonopensearchserviceS3BackupMode | None S3Configuration: S3DestinationConfiguration - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - VpcConfiguration: Optional[VpcConfiguration] - DocumentIdOptions: Optional[DocumentIdOptions] + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + VpcConfiguration: VpcConfiguration | None + DocumentIdOptions: DocumentIdOptions | None class AmazonopensearchserviceDestinationDescription(TypedDict, total=False): - RoleARN: Optional[RoleARN] - DomainARN: Optional[AmazonopensearchserviceDomainARN] - ClusterEndpoint: Optional[AmazonopensearchserviceClusterEndpoint] - IndexName: Optional[AmazonopensearchserviceIndexName] - TypeName: Optional[AmazonopensearchserviceTypeName] - IndexRotationPeriod: Optional[AmazonopensearchserviceIndexRotationPeriod] - BufferingHints: Optional[AmazonopensearchserviceBufferingHints] - RetryOptions: Optional[AmazonopensearchserviceRetryOptions] - S3BackupMode: Optional[AmazonopensearchserviceS3BackupMode] - S3DestinationDescription: Optional[S3DestinationDescription] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - VpcConfigurationDescription: Optional[VpcConfigurationDescription] - DocumentIdOptions: Optional[DocumentIdOptions] + RoleARN: RoleARN | None + DomainARN: AmazonopensearchserviceDomainARN | None + ClusterEndpoint: AmazonopensearchserviceClusterEndpoint | None + IndexName: AmazonopensearchserviceIndexName | None + TypeName: AmazonopensearchserviceTypeName | None + IndexRotationPeriod: AmazonopensearchserviceIndexRotationPeriod | None + BufferingHints: AmazonopensearchserviceBufferingHints | None + RetryOptions: AmazonopensearchserviceRetryOptions | None + S3BackupMode: AmazonopensearchserviceS3BackupMode | None + S3DestinationDescription: S3DestinationDescription | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + VpcConfigurationDescription: VpcConfigurationDescription | None + DocumentIdOptions: DocumentIdOptions | None class AmazonopensearchserviceDestinationUpdate(TypedDict, total=False): - RoleARN: Optional[RoleARN] - DomainARN: Optional[AmazonopensearchserviceDomainARN] - ClusterEndpoint: Optional[AmazonopensearchserviceClusterEndpoint] - IndexName: Optional[AmazonopensearchserviceIndexName] - TypeName: Optional[AmazonopensearchserviceTypeName] - IndexRotationPeriod: Optional[AmazonopensearchserviceIndexRotationPeriod] - BufferingHints: Optional[AmazonopensearchserviceBufferingHints] - RetryOptions: Optional[AmazonopensearchserviceRetryOptions] - S3Update: Optional[S3DestinationUpdate] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - DocumentIdOptions: Optional[DocumentIdOptions] + RoleARN: RoleARN | None + DomainARN: AmazonopensearchserviceDomainARN | None + ClusterEndpoint: AmazonopensearchserviceClusterEndpoint | None + IndexName: AmazonopensearchserviceIndexName | None + TypeName: AmazonopensearchserviceTypeName | None + IndexRotationPeriod: AmazonopensearchserviceIndexRotationPeriod | None + BufferingHints: AmazonopensearchserviceBufferingHints | None + RetryOptions: AmazonopensearchserviceRetryOptions | None + S3Update: S3DestinationUpdate | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + DocumentIdOptions: DocumentIdOptions | None class AuthenticationConfiguration(TypedDict, total=False): @@ -575,17 +575,17 @@ class AuthenticationConfiguration(TypedDict, total=False): class CatalogConfiguration(TypedDict, total=False): - CatalogARN: Optional[GlueDataCatalogARN] - WarehouseLocation: Optional[WarehouseLocation] + CatalogARN: GlueDataCatalogARN | None + WarehouseLocation: WarehouseLocation | None -ColumnToJsonKeyMappings = Dict[NonEmptyStringWithoutWhitespace, NonEmptyString] +ColumnToJsonKeyMappings = dict[NonEmptyStringWithoutWhitespace, NonEmptyString] class CopyCommand(TypedDict, total=False): DataTableName: DataTableName - DataTableColumns: Optional[DataTableColumns] - CopyOptions: Optional[CopyOptions] + DataTableColumns: DataTableColumns | None + CopyOptions: CopyOptions | None class DatabaseSourceVPCConfiguration(TypedDict, total=False): @@ -593,8 +593,8 @@ class DatabaseSourceVPCConfiguration(TypedDict, total=False): class SecretsManagerConfiguration(TypedDict, total=False): - SecretARN: Optional[SecretARN] - RoleARN: Optional[RoleARN] + SecretARN: SecretARN | None + RoleARN: RoleARN | None Enabled: BooleanObject @@ -602,47 +602,47 @@ class DatabaseSourceAuthenticationConfiguration(TypedDict, total=False): SecretsManagerConfiguration: SecretsManagerConfiguration -DatabaseSurrogateKeyList = List[NonEmptyStringWithoutWhitespace] -DatabaseColumnIncludeOrExcludeList = List[DatabaseColumnName] +DatabaseSurrogateKeyList = list[NonEmptyStringWithoutWhitespace] +DatabaseColumnIncludeOrExcludeList = list[DatabaseColumnName] class DatabaseColumnList(TypedDict, total=False): - Include: Optional[DatabaseColumnIncludeOrExcludeList] - Exclude: Optional[DatabaseColumnIncludeOrExcludeList] + Include: DatabaseColumnIncludeOrExcludeList | None + Exclude: DatabaseColumnIncludeOrExcludeList | None -DatabaseTableIncludeOrExcludeList = List[DatabaseTableName] +DatabaseTableIncludeOrExcludeList = list[DatabaseTableName] class DatabaseTableList(TypedDict, total=False): - Include: Optional[DatabaseTableIncludeOrExcludeList] - Exclude: Optional[DatabaseTableIncludeOrExcludeList] + Include: DatabaseTableIncludeOrExcludeList | None + Exclude: DatabaseTableIncludeOrExcludeList | None -DatabaseIncludeOrExcludeList = List[DatabaseName] +DatabaseIncludeOrExcludeList = list[DatabaseName] class DatabaseList(TypedDict, total=False): - Include: Optional[DatabaseIncludeOrExcludeList] - Exclude: Optional[DatabaseIncludeOrExcludeList] + Include: DatabaseIncludeOrExcludeList | None + Exclude: DatabaseIncludeOrExcludeList | None class DatabaseSourceConfiguration(TypedDict, total=False): Type: DatabaseType Endpoint: DatabaseEndpoint Port: DatabasePort - SSLMode: Optional[SSLMode] + SSLMode: SSLMode | None Databases: DatabaseList Tables: DatabaseTableList - Columns: Optional[DatabaseColumnList] - SurrogateKeys: Optional[DatabaseSurrogateKeyList] + Columns: DatabaseColumnList | None + SurrogateKeys: DatabaseSurrogateKeyList | None SnapshotWatermarkTable: DatabaseTableName DatabaseSourceAuthenticationConfiguration: DatabaseSourceAuthenticationConfiguration DatabaseSourceVPCConfiguration: DatabaseSourceVPCConfiguration class RetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[RetryDurationInSeconds] + DurationInSeconds: RetryDurationInSeconds | None class TableCreationConfiguration(TypedDict, total=False): @@ -657,49 +657,49 @@ class PartitionField(TypedDict, total=False): SourceName: NonEmptyStringWithoutWhitespace -PartitionFields = List[PartitionField] +PartitionFields = list[PartitionField] class PartitionSpec(TypedDict, total=False): - Identity: Optional[PartitionFields] + Identity: PartitionFields | None -ListOfNonEmptyStringsWithoutWhitespace = List[NonEmptyStringWithoutWhitespace] +ListOfNonEmptyStringsWithoutWhitespace = list[NonEmptyStringWithoutWhitespace] class DestinationTableConfiguration(TypedDict, total=False): DestinationTableName: StringWithLettersDigitsUnderscoresDots DestinationDatabaseName: StringWithLettersDigitsUnderscoresDots - UniqueKeys: Optional[ListOfNonEmptyStringsWithoutWhitespace] - PartitionSpec: Optional[PartitionSpec] - S3ErrorOutputPrefix: Optional[ErrorOutputPrefix] + UniqueKeys: ListOfNonEmptyStringsWithoutWhitespace | None + PartitionSpec: PartitionSpec | None + S3ErrorOutputPrefix: ErrorOutputPrefix | None -DestinationTableConfigurationList = List[DestinationTableConfiguration] +DestinationTableConfigurationList = list[DestinationTableConfiguration] class IcebergDestinationConfiguration(TypedDict, total=False): - DestinationTableConfigurationList: Optional[DestinationTableConfigurationList] - SchemaEvolutionConfiguration: Optional[SchemaEvolutionConfiguration] - TableCreationConfiguration: Optional[TableCreationConfiguration] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[IcebergS3BackupMode] - RetryOptions: Optional[RetryOptions] + DestinationTableConfigurationList: DestinationTableConfigurationList | None + SchemaEvolutionConfiguration: SchemaEvolutionConfiguration | None + TableCreationConfiguration: TableCreationConfiguration | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: IcebergS3BackupMode | None + RetryOptions: RetryOptions | None RoleARN: RoleARN - AppendOnly: Optional[BooleanObject] + AppendOnly: BooleanObject | None CatalogConfiguration: CatalogConfiguration S3Configuration: S3DestinationConfiguration class SnowflakeBufferingHints(TypedDict, total=False): - SizeInMBs: Optional[SnowflakeBufferingSizeInMBs] - IntervalInSeconds: Optional[SnowflakeBufferingIntervalInSeconds] + SizeInMBs: SnowflakeBufferingSizeInMBs | None + IntervalInSeconds: SnowflakeBufferingIntervalInSeconds | None class SnowflakeRetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[SnowflakeRetryDurationInSeconds] + DurationInSeconds: SnowflakeRetryDurationInSeconds | None class SnowflakeVpcConfiguration(TypedDict, total=False): @@ -707,31 +707,31 @@ class SnowflakeVpcConfiguration(TypedDict, total=False): class SnowflakeRoleConfiguration(TypedDict, total=False): - Enabled: Optional[BooleanObject] - SnowflakeRole: Optional[SnowflakeRole] + Enabled: BooleanObject | None + SnowflakeRole: SnowflakeRole | None class SnowflakeDestinationConfiguration(TypedDict, total=False): AccountUrl: SnowflakeAccountUrl - PrivateKey: Optional[SnowflakePrivateKey] - KeyPassphrase: Optional[SnowflakeKeyPassphrase] - User: Optional[SnowflakeUser] + PrivateKey: SnowflakePrivateKey | None + KeyPassphrase: SnowflakeKeyPassphrase | None + User: SnowflakeUser | None Database: SnowflakeDatabase Schema: SnowflakeSchema Table: SnowflakeTable - SnowflakeRoleConfiguration: Optional[SnowflakeRoleConfiguration] - DataLoadingOption: Optional[SnowflakeDataLoadingOption] - MetaDataColumnName: Optional[SnowflakeMetaDataColumnName] - ContentColumnName: Optional[SnowflakeContentColumnName] - SnowflakeVpcConfiguration: Optional[SnowflakeVpcConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] + SnowflakeRoleConfiguration: SnowflakeRoleConfiguration | None + DataLoadingOption: SnowflakeDataLoadingOption | None + MetaDataColumnName: SnowflakeMetaDataColumnName | None + ContentColumnName: SnowflakeContentColumnName | None + SnowflakeVpcConfiguration: SnowflakeVpcConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None RoleARN: RoleARN - RetryOptions: Optional[SnowflakeRetryOptions] - S3BackupMode: Optional[SnowflakeS3BackupMode] + RetryOptions: SnowflakeRetryOptions | None + S3BackupMode: SnowflakeS3BackupMode | None S3Configuration: S3DestinationConfiguration - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] - BufferingHints: Optional[SnowflakeBufferingHints] + SecretsManagerConfiguration: SecretsManagerConfiguration | None + BufferingHints: SnowflakeBufferingHints | None ReadFromTimestamp = datetime @@ -741,19 +741,19 @@ class MSKSourceConfiguration(TypedDict, total=False): MSKClusterARN: MSKClusterARN TopicName: TopicName AuthenticationConfiguration: AuthenticationConfiguration - ReadFromTimestamp: Optional[ReadFromTimestamp] + ReadFromTimestamp: ReadFromTimestamp | None class Tag(TypedDict, total=False): Key: TagKey - Value: Optional[TagValue] + Value: TagValue | None -TagDeliveryStreamInputTagList = List[Tag] +TagDeliveryStreamInputTagList = list[Tag] class HttpEndpointRetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[HttpEndpointRetryDurationInSeconds] + DurationInSeconds: HttpEndpointRetryDurationInSeconds | None class HttpEndpointCommonAttribute(TypedDict, total=False): @@ -761,200 +761,200 @@ class HttpEndpointCommonAttribute(TypedDict, total=False): AttributeValue: HttpEndpointAttributeValue -HttpEndpointCommonAttributesList = List[HttpEndpointCommonAttribute] +HttpEndpointCommonAttributesList = list[HttpEndpointCommonAttribute] class HttpEndpointRequestConfiguration(TypedDict, total=False): - ContentEncoding: Optional[ContentEncoding] - CommonAttributes: Optional[HttpEndpointCommonAttributesList] + ContentEncoding: ContentEncoding | None + CommonAttributes: HttpEndpointCommonAttributesList | None class HttpEndpointBufferingHints(TypedDict, total=False): - SizeInMBs: Optional[HttpEndpointBufferingSizeInMBs] - IntervalInSeconds: Optional[HttpEndpointBufferingIntervalInSeconds] + SizeInMBs: HttpEndpointBufferingSizeInMBs | None + IntervalInSeconds: HttpEndpointBufferingIntervalInSeconds | None class HttpEndpointConfiguration(TypedDict, total=False): Url: HttpEndpointUrl - Name: Optional[HttpEndpointName] - AccessKey: Optional[HttpEndpointAccessKey] + Name: HttpEndpointName | None + AccessKey: HttpEndpointAccessKey | None class HttpEndpointDestinationConfiguration(TypedDict, total=False): EndpointConfiguration: HttpEndpointConfiguration - BufferingHints: Optional[HttpEndpointBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - RequestConfiguration: Optional[HttpEndpointRequestConfiguration] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RoleARN: Optional[RoleARN] - RetryOptions: Optional[HttpEndpointRetryOptions] - S3BackupMode: Optional[HttpEndpointS3BackupMode] + BufferingHints: HttpEndpointBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + RequestConfiguration: HttpEndpointRequestConfiguration | None + ProcessingConfiguration: ProcessingConfiguration | None + RoleARN: RoleARN | None + RetryOptions: HttpEndpointRetryOptions | None + S3BackupMode: HttpEndpointS3BackupMode | None S3Configuration: S3DestinationConfiguration - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + SecretsManagerConfiguration: SecretsManagerConfiguration | None class SplunkBufferingHints(TypedDict, total=False): - IntervalInSeconds: Optional[SplunkBufferingIntervalInSeconds] - SizeInMBs: Optional[SplunkBufferingSizeInMBs] + IntervalInSeconds: SplunkBufferingIntervalInSeconds | None + SizeInMBs: SplunkBufferingSizeInMBs | None class SplunkRetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[SplunkRetryDurationInSeconds] + DurationInSeconds: SplunkRetryDurationInSeconds | None class SplunkDestinationConfiguration(TypedDict, total=False): HECEndpoint: HECEndpoint HECEndpointType: HECEndpointType - HECToken: Optional[HECToken] - HECAcknowledgmentTimeoutInSeconds: Optional[HECAcknowledgmentTimeoutInSeconds] - RetryOptions: Optional[SplunkRetryOptions] - S3BackupMode: Optional[SplunkS3BackupMode] + HECToken: HECToken | None + HECAcknowledgmentTimeoutInSeconds: HECAcknowledgmentTimeoutInSeconds | None + RetryOptions: SplunkRetryOptions | None + S3BackupMode: SplunkS3BackupMode | None S3Configuration: S3DestinationConfiguration - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - BufferingHints: Optional[SplunkBufferingHints] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + BufferingHints: SplunkBufferingHints | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class ElasticsearchRetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[ElasticsearchRetryDurationInSeconds] + DurationInSeconds: ElasticsearchRetryDurationInSeconds | None class ElasticsearchBufferingHints(TypedDict, total=False): - IntervalInSeconds: Optional[ElasticsearchBufferingIntervalInSeconds] - SizeInMBs: Optional[ElasticsearchBufferingSizeInMBs] + IntervalInSeconds: ElasticsearchBufferingIntervalInSeconds | None + SizeInMBs: ElasticsearchBufferingSizeInMBs | None class ElasticsearchDestinationConfiguration(TypedDict, total=False): RoleARN: RoleARN - DomainARN: Optional[ElasticsearchDomainARN] - ClusterEndpoint: Optional[ElasticsearchClusterEndpoint] + DomainARN: ElasticsearchDomainARN | None + ClusterEndpoint: ElasticsearchClusterEndpoint | None IndexName: ElasticsearchIndexName - TypeName: Optional[ElasticsearchTypeName] - IndexRotationPeriod: Optional[ElasticsearchIndexRotationPeriod] - BufferingHints: Optional[ElasticsearchBufferingHints] - RetryOptions: Optional[ElasticsearchRetryOptions] - S3BackupMode: Optional[ElasticsearchS3BackupMode] + TypeName: ElasticsearchTypeName | None + IndexRotationPeriod: ElasticsearchIndexRotationPeriod | None + BufferingHints: ElasticsearchBufferingHints | None + RetryOptions: ElasticsearchRetryOptions | None + S3BackupMode: ElasticsearchS3BackupMode | None S3Configuration: S3DestinationConfiguration - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - VpcConfiguration: Optional[VpcConfiguration] - DocumentIdOptions: Optional[DocumentIdOptions] + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + VpcConfiguration: VpcConfiguration | None + DocumentIdOptions: DocumentIdOptions | None class RedshiftRetryOptions(TypedDict, total=False): - DurationInSeconds: Optional[RedshiftRetryDurationInSeconds] + DurationInSeconds: RedshiftRetryDurationInSeconds | None class RedshiftDestinationConfiguration(TypedDict, total=False): RoleARN: RoleARN ClusterJDBCURL: ClusterJDBCURL CopyCommand: CopyCommand - Username: Optional[Username] - Password: Optional[Password] - RetryOptions: Optional[RedshiftRetryOptions] + Username: Username | None + Password: Password | None + RetryOptions: RedshiftRetryOptions | None S3Configuration: S3DestinationConfiguration - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[RedshiftS3BackupMode] - S3BackupConfiguration: Optional[S3DestinationConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: RedshiftS3BackupMode | None + S3BackupConfiguration: S3DestinationConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class DynamicPartitioningConfiguration(TypedDict, total=False): - RetryOptions: Optional[RetryOptions] - Enabled: Optional[BooleanObject] + RetryOptions: RetryOptions | None + Enabled: BooleanObject | None class OrcSerDe(TypedDict, total=False): - StripeSizeBytes: Optional[OrcStripeSizeBytes] - BlockSizeBytes: Optional[BlockSizeBytes] - RowIndexStride: Optional[OrcRowIndexStride] - EnablePadding: Optional[BooleanObject] - PaddingTolerance: Optional[Proportion] - Compression: Optional[OrcCompression] - BloomFilterColumns: Optional[ListOfNonEmptyStringsWithoutWhitespace] - BloomFilterFalsePositiveProbability: Optional[Proportion] - DictionaryKeyThreshold: Optional[Proportion] - FormatVersion: Optional[OrcFormatVersion] + StripeSizeBytes: OrcStripeSizeBytes | None + BlockSizeBytes: BlockSizeBytes | None + RowIndexStride: OrcRowIndexStride | None + EnablePadding: BooleanObject | None + PaddingTolerance: Proportion | None + Compression: OrcCompression | None + BloomFilterColumns: ListOfNonEmptyStringsWithoutWhitespace | None + BloomFilterFalsePositiveProbability: Proportion | None + DictionaryKeyThreshold: Proportion | None + FormatVersion: OrcFormatVersion | None class ParquetSerDe(TypedDict, total=False): - BlockSizeBytes: Optional[BlockSizeBytes] - PageSizeBytes: Optional[ParquetPageSizeBytes] - Compression: Optional[ParquetCompression] - EnableDictionaryCompression: Optional[BooleanObject] - MaxPaddingBytes: Optional[NonNegativeIntegerObject] - WriterVersion: Optional[ParquetWriterVersion] + BlockSizeBytes: BlockSizeBytes | None + PageSizeBytes: ParquetPageSizeBytes | None + Compression: ParquetCompression | None + EnableDictionaryCompression: BooleanObject | None + MaxPaddingBytes: NonNegativeIntegerObject | None + WriterVersion: ParquetWriterVersion | None class Serializer(TypedDict, total=False): - ParquetSerDe: Optional[ParquetSerDe] - OrcSerDe: Optional[OrcSerDe] + ParquetSerDe: ParquetSerDe | None + OrcSerDe: OrcSerDe | None class OutputFormatConfiguration(TypedDict, total=False): - Serializer: Optional[Serializer] + Serializer: Serializer | None -ListOfNonEmptyStrings = List[NonEmptyString] +ListOfNonEmptyStrings = list[NonEmptyString] class HiveJsonSerDe(TypedDict, total=False): - TimestampFormats: Optional[ListOfNonEmptyStrings] + TimestampFormats: ListOfNonEmptyStrings | None class OpenXJsonSerDe(TypedDict, total=False): - ConvertDotsInJsonKeysToUnderscores: Optional[BooleanObject] - CaseInsensitive: Optional[BooleanObject] - ColumnToJsonKeyMappings: Optional[ColumnToJsonKeyMappings] + ConvertDotsInJsonKeysToUnderscores: BooleanObject | None + CaseInsensitive: BooleanObject | None + ColumnToJsonKeyMappings: ColumnToJsonKeyMappings | None class Deserializer(TypedDict, total=False): - OpenXJsonSerDe: Optional[OpenXJsonSerDe] - HiveJsonSerDe: Optional[HiveJsonSerDe] + OpenXJsonSerDe: OpenXJsonSerDe | None + HiveJsonSerDe: HiveJsonSerDe | None class InputFormatConfiguration(TypedDict, total=False): - Deserializer: Optional[Deserializer] + Deserializer: Deserializer | None class SchemaConfiguration(TypedDict, total=False): - RoleARN: Optional[NonEmptyStringWithoutWhitespace] - CatalogId: Optional[NonEmptyStringWithoutWhitespace] - DatabaseName: Optional[NonEmptyStringWithoutWhitespace] - TableName: Optional[NonEmptyStringWithoutWhitespace] - Region: Optional[NonEmptyStringWithoutWhitespace] - VersionId: Optional[NonEmptyStringWithoutWhitespace] + RoleARN: NonEmptyStringWithoutWhitespace | None + CatalogId: NonEmptyStringWithoutWhitespace | None + DatabaseName: NonEmptyStringWithoutWhitespace | None + TableName: NonEmptyStringWithoutWhitespace | None + Region: NonEmptyStringWithoutWhitespace | None + VersionId: NonEmptyStringWithoutWhitespace | None class DataFormatConversionConfiguration(TypedDict, total=False): - SchemaConfiguration: Optional[SchemaConfiguration] - InputFormatConfiguration: Optional[InputFormatConfiguration] - OutputFormatConfiguration: Optional[OutputFormatConfiguration] - Enabled: Optional[BooleanObject] + SchemaConfiguration: SchemaConfiguration | None + InputFormatConfiguration: InputFormatConfiguration | None + OutputFormatConfiguration: OutputFormatConfiguration | None + Enabled: BooleanObject | None class ExtendedS3DestinationConfiguration(TypedDict, total=False): RoleARN: RoleARN BucketARN: BucketARN - Prefix: Optional[Prefix] - ErrorOutputPrefix: Optional[ErrorOutputPrefix] - BufferingHints: Optional[BufferingHints] - CompressionFormat: Optional[CompressionFormat] - EncryptionConfiguration: Optional[EncryptionConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[S3BackupMode] - S3BackupConfiguration: Optional[S3DestinationConfiguration] - DataFormatConversionConfiguration: Optional[DataFormatConversionConfiguration] - DynamicPartitioningConfiguration: Optional[DynamicPartitioningConfiguration] - FileExtension: Optional[FileExtension] - CustomTimeZone: Optional[CustomTimeZone] + Prefix: Prefix | None + ErrorOutputPrefix: ErrorOutputPrefix | None + BufferingHints: BufferingHints | None + CompressionFormat: CompressionFormat | None + EncryptionConfiguration: EncryptionConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: S3BackupMode | None + S3BackupConfiguration: S3DestinationConfiguration | None + DataFormatConversionConfiguration: DataFormatConversionConfiguration | None + DynamicPartitioningConfiguration: DynamicPartitioningConfiguration | None + FileExtension: FileExtension | None + CustomTimeZone: CustomTimeZone | None class DeliveryStreamEncryptionConfigurationInput(TypedDict, total=False): - KeyARN: Optional[AWSKMSKeyARN] + KeyARN: AWSKMSKeyARN | None KeyType: KeyType @@ -969,31 +969,31 @@ class DirectPutSourceConfiguration(TypedDict, total=False): class CreateDeliveryStreamInput(ServiceRequest): DeliveryStreamName: DeliveryStreamName - DeliveryStreamType: Optional[DeliveryStreamType] - DirectPutSourceConfiguration: Optional[DirectPutSourceConfiguration] - KinesisStreamSourceConfiguration: Optional[KinesisStreamSourceConfiguration] - DeliveryStreamEncryptionConfigurationInput: Optional[DeliveryStreamEncryptionConfigurationInput] - S3DestinationConfiguration: Optional[S3DestinationConfiguration] - ExtendedS3DestinationConfiguration: Optional[ExtendedS3DestinationConfiguration] - RedshiftDestinationConfiguration: Optional[RedshiftDestinationConfiguration] - ElasticsearchDestinationConfiguration: Optional[ElasticsearchDestinationConfiguration] - AmazonopensearchserviceDestinationConfiguration: Optional[ - AmazonopensearchserviceDestinationConfiguration - ] - SplunkDestinationConfiguration: Optional[SplunkDestinationConfiguration] - HttpEndpointDestinationConfiguration: Optional[HttpEndpointDestinationConfiguration] - Tags: Optional[TagDeliveryStreamInputTagList] - AmazonOpenSearchServerlessDestinationConfiguration: Optional[ - AmazonOpenSearchServerlessDestinationConfiguration - ] - MSKSourceConfiguration: Optional[MSKSourceConfiguration] - SnowflakeDestinationConfiguration: Optional[SnowflakeDestinationConfiguration] - IcebergDestinationConfiguration: Optional[IcebergDestinationConfiguration] - DatabaseSourceConfiguration: Optional[DatabaseSourceConfiguration] + DeliveryStreamType: DeliveryStreamType | None + DirectPutSourceConfiguration: DirectPutSourceConfiguration | None + KinesisStreamSourceConfiguration: KinesisStreamSourceConfiguration | None + DeliveryStreamEncryptionConfigurationInput: DeliveryStreamEncryptionConfigurationInput | None + S3DestinationConfiguration: S3DestinationConfiguration | None + ExtendedS3DestinationConfiguration: ExtendedS3DestinationConfiguration | None + RedshiftDestinationConfiguration: RedshiftDestinationConfiguration | None + ElasticsearchDestinationConfiguration: ElasticsearchDestinationConfiguration | None + AmazonopensearchserviceDestinationConfiguration: ( + AmazonopensearchserviceDestinationConfiguration | None + ) + SplunkDestinationConfiguration: SplunkDestinationConfiguration | None + HttpEndpointDestinationConfiguration: HttpEndpointDestinationConfiguration | None + Tags: TagDeliveryStreamInputTagList | None + AmazonOpenSearchServerlessDestinationConfiguration: ( + AmazonOpenSearchServerlessDestinationConfiguration | None + ) + MSKSourceConfiguration: MSKSourceConfiguration | None + SnowflakeDestinationConfiguration: SnowflakeDestinationConfiguration | None + IcebergDestinationConfiguration: IcebergDestinationConfiguration | None + DatabaseSourceConfiguration: DatabaseSourceConfiguration | None class CreateDeliveryStreamOutput(TypedDict, total=False): - DeliveryStreamARN: Optional[DeliveryStreamARN] + DeliveryStreamARN: DeliveryStreamARN | None Data = bytes @@ -1013,30 +1013,30 @@ class DatabaseSnapshotInfo(TypedDict, total=False): RequestTimestamp: Timestamp RequestedBy: SnapshotRequestedBy Status: SnapshotStatus - FailureDescription: Optional[FailureDescription] + FailureDescription: FailureDescription | None -DatabaseSnapshotInfoList = List[DatabaseSnapshotInfo] +DatabaseSnapshotInfoList = list[DatabaseSnapshotInfo] class DatabaseSourceDescription(TypedDict, total=False): - Type: Optional[DatabaseType] - Endpoint: Optional[DatabaseEndpoint] - Port: Optional[DatabasePort] - SSLMode: Optional[SSLMode] - Databases: Optional[DatabaseList] - Tables: Optional[DatabaseTableList] - Columns: Optional[DatabaseColumnList] - SurrogateKeys: Optional[DatabaseColumnIncludeOrExcludeList] - SnapshotWatermarkTable: Optional[DatabaseTableName] - SnapshotInfo: Optional[DatabaseSnapshotInfoList] - DatabaseSourceAuthenticationConfiguration: Optional[DatabaseSourceAuthenticationConfiguration] - DatabaseSourceVPCConfiguration: Optional[DatabaseSourceVPCConfiguration] + Type: DatabaseType | None + Endpoint: DatabaseEndpoint | None + Port: DatabasePort | None + SSLMode: SSLMode | None + Databases: DatabaseList | None + Tables: DatabaseTableList | None + Columns: DatabaseColumnList | None + SurrogateKeys: DatabaseColumnIncludeOrExcludeList | None + SnapshotWatermarkTable: DatabaseTableName | None + SnapshotInfo: DatabaseSnapshotInfoList | None + DatabaseSourceAuthenticationConfiguration: DatabaseSourceAuthenticationConfiguration | None + DatabaseSourceVPCConfiguration: DatabaseSourceVPCConfiguration | None class DeleteDeliveryStreamInput(ServiceRequest): DeliveryStreamName: DeliveryStreamName - AllowForceDelete: Optional[BooleanObject] + AllowForceDelete: BooleanObject | None class DeleteDeliveryStreamOutput(TypedDict, total=False): @@ -1047,197 +1047,197 @@ class DeleteDeliveryStreamOutput(TypedDict, total=False): class IcebergDestinationDescription(TypedDict, total=False): - DestinationTableConfigurationList: Optional[DestinationTableConfigurationList] - SchemaEvolutionConfiguration: Optional[SchemaEvolutionConfiguration] - TableCreationConfiguration: Optional[TableCreationConfiguration] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[IcebergS3BackupMode] - RetryOptions: Optional[RetryOptions] - RoleARN: Optional[RoleARN] - AppendOnly: Optional[BooleanObject] - CatalogConfiguration: Optional[CatalogConfiguration] - S3DestinationDescription: Optional[S3DestinationDescription] + DestinationTableConfigurationList: DestinationTableConfigurationList | None + SchemaEvolutionConfiguration: SchemaEvolutionConfiguration | None + TableCreationConfiguration: TableCreationConfiguration | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: IcebergS3BackupMode | None + RetryOptions: RetryOptions | None + RoleARN: RoleARN | None + AppendOnly: BooleanObject | None + CatalogConfiguration: CatalogConfiguration | None + S3DestinationDescription: S3DestinationDescription | None class SnowflakeDestinationDescription(TypedDict, total=False): - AccountUrl: Optional[SnowflakeAccountUrl] - User: Optional[SnowflakeUser] - Database: Optional[SnowflakeDatabase] - Schema: Optional[SnowflakeSchema] - Table: Optional[SnowflakeTable] - SnowflakeRoleConfiguration: Optional[SnowflakeRoleConfiguration] - DataLoadingOption: Optional[SnowflakeDataLoadingOption] - MetaDataColumnName: Optional[SnowflakeMetaDataColumnName] - ContentColumnName: Optional[SnowflakeContentColumnName] - SnowflakeVpcConfiguration: Optional[SnowflakeVpcConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RoleARN: Optional[RoleARN] - RetryOptions: Optional[SnowflakeRetryOptions] - S3BackupMode: Optional[SnowflakeS3BackupMode] - S3DestinationDescription: Optional[S3DestinationDescription] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] - BufferingHints: Optional[SnowflakeBufferingHints] + AccountUrl: SnowflakeAccountUrl | None + User: SnowflakeUser | None + Database: SnowflakeDatabase | None + Schema: SnowflakeSchema | None + Table: SnowflakeTable | None + SnowflakeRoleConfiguration: SnowflakeRoleConfiguration | None + DataLoadingOption: SnowflakeDataLoadingOption | None + MetaDataColumnName: SnowflakeMetaDataColumnName | None + ContentColumnName: SnowflakeContentColumnName | None + SnowflakeVpcConfiguration: SnowflakeVpcConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + RoleARN: RoleARN | None + RetryOptions: SnowflakeRetryOptions | None + S3BackupMode: SnowflakeS3BackupMode | None + S3DestinationDescription: S3DestinationDescription | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None + BufferingHints: SnowflakeBufferingHints | None class HttpEndpointDescription(TypedDict, total=False): - Url: Optional[HttpEndpointUrl] - Name: Optional[HttpEndpointName] + Url: HttpEndpointUrl | None + Name: HttpEndpointName | None class HttpEndpointDestinationDescription(TypedDict, total=False): - EndpointConfiguration: Optional[HttpEndpointDescription] - BufferingHints: Optional[HttpEndpointBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - RequestConfiguration: Optional[HttpEndpointRequestConfiguration] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RoleARN: Optional[RoleARN] - RetryOptions: Optional[HttpEndpointRetryOptions] - S3BackupMode: Optional[HttpEndpointS3BackupMode] - S3DestinationDescription: Optional[S3DestinationDescription] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + EndpointConfiguration: HttpEndpointDescription | None + BufferingHints: HttpEndpointBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + RequestConfiguration: HttpEndpointRequestConfiguration | None + ProcessingConfiguration: ProcessingConfiguration | None + RoleARN: RoleARN | None + RetryOptions: HttpEndpointRetryOptions | None + S3BackupMode: HttpEndpointS3BackupMode | None + S3DestinationDescription: S3DestinationDescription | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class SplunkDestinationDescription(TypedDict, total=False): - HECEndpoint: Optional[HECEndpoint] - HECEndpointType: Optional[HECEndpointType] - HECToken: Optional[HECToken] - HECAcknowledgmentTimeoutInSeconds: Optional[HECAcknowledgmentTimeoutInSeconds] - RetryOptions: Optional[SplunkRetryOptions] - S3BackupMode: Optional[SplunkS3BackupMode] - S3DestinationDescription: Optional[S3DestinationDescription] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - BufferingHints: Optional[SplunkBufferingHints] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + HECEndpoint: HECEndpoint | None + HECEndpointType: HECEndpointType | None + HECToken: HECToken | None + HECAcknowledgmentTimeoutInSeconds: HECAcknowledgmentTimeoutInSeconds | None + RetryOptions: SplunkRetryOptions | None + S3BackupMode: SplunkS3BackupMode | None + S3DestinationDescription: S3DestinationDescription | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + BufferingHints: SplunkBufferingHints | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class ElasticsearchDestinationDescription(TypedDict, total=False): - RoleARN: Optional[RoleARN] - DomainARN: Optional[ElasticsearchDomainARN] - ClusterEndpoint: Optional[ElasticsearchClusterEndpoint] - IndexName: Optional[ElasticsearchIndexName] - TypeName: Optional[ElasticsearchTypeName] - IndexRotationPeriod: Optional[ElasticsearchIndexRotationPeriod] - BufferingHints: Optional[ElasticsearchBufferingHints] - RetryOptions: Optional[ElasticsearchRetryOptions] - S3BackupMode: Optional[ElasticsearchS3BackupMode] - S3DestinationDescription: Optional[S3DestinationDescription] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - VpcConfigurationDescription: Optional[VpcConfigurationDescription] - DocumentIdOptions: Optional[DocumentIdOptions] + RoleARN: RoleARN | None + DomainARN: ElasticsearchDomainARN | None + ClusterEndpoint: ElasticsearchClusterEndpoint | None + IndexName: ElasticsearchIndexName | None + TypeName: ElasticsearchTypeName | None + IndexRotationPeriod: ElasticsearchIndexRotationPeriod | None + BufferingHints: ElasticsearchBufferingHints | None + RetryOptions: ElasticsearchRetryOptions | None + S3BackupMode: ElasticsearchS3BackupMode | None + S3DestinationDescription: S3DestinationDescription | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + VpcConfigurationDescription: VpcConfigurationDescription | None + DocumentIdOptions: DocumentIdOptions | None class RedshiftDestinationDescription(TypedDict, total=False): RoleARN: RoleARN ClusterJDBCURL: ClusterJDBCURL CopyCommand: CopyCommand - Username: Optional[Username] - RetryOptions: Optional[RedshiftRetryOptions] + Username: Username | None + RetryOptions: RedshiftRetryOptions | None S3DestinationDescription: S3DestinationDescription - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[RedshiftS3BackupMode] - S3BackupDescription: Optional[S3DestinationDescription] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: RedshiftS3BackupMode | None + S3BackupDescription: S3DestinationDescription | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class ExtendedS3DestinationDescription(TypedDict, total=False): RoleARN: RoleARN BucketARN: BucketARN - Prefix: Optional[Prefix] - ErrorOutputPrefix: Optional[ErrorOutputPrefix] + Prefix: Prefix | None + ErrorOutputPrefix: ErrorOutputPrefix | None BufferingHints: BufferingHints CompressionFormat: CompressionFormat EncryptionConfiguration: EncryptionConfiguration - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[S3BackupMode] - S3BackupDescription: Optional[S3DestinationDescription] - DataFormatConversionConfiguration: Optional[DataFormatConversionConfiguration] - DynamicPartitioningConfiguration: Optional[DynamicPartitioningConfiguration] - FileExtension: Optional[FileExtension] - CustomTimeZone: Optional[CustomTimeZone] + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: S3BackupMode | None + S3BackupDescription: S3DestinationDescription | None + DataFormatConversionConfiguration: DataFormatConversionConfiguration | None + DynamicPartitioningConfiguration: DynamicPartitioningConfiguration | None + FileExtension: FileExtension | None + CustomTimeZone: CustomTimeZone | None class DestinationDescription(TypedDict, total=False): DestinationId: DestinationId - S3DestinationDescription: Optional[S3DestinationDescription] - ExtendedS3DestinationDescription: Optional[ExtendedS3DestinationDescription] - RedshiftDestinationDescription: Optional[RedshiftDestinationDescription] - ElasticsearchDestinationDescription: Optional[ElasticsearchDestinationDescription] - AmazonopensearchserviceDestinationDescription: Optional[ - AmazonopensearchserviceDestinationDescription - ] - SplunkDestinationDescription: Optional[SplunkDestinationDescription] - HttpEndpointDestinationDescription: Optional[HttpEndpointDestinationDescription] - SnowflakeDestinationDescription: Optional[SnowflakeDestinationDescription] - AmazonOpenSearchServerlessDestinationDescription: Optional[ - AmazonOpenSearchServerlessDestinationDescription - ] - IcebergDestinationDescription: Optional[IcebergDestinationDescription] + S3DestinationDescription: S3DestinationDescription | None + ExtendedS3DestinationDescription: ExtendedS3DestinationDescription | None + RedshiftDestinationDescription: RedshiftDestinationDescription | None + ElasticsearchDestinationDescription: ElasticsearchDestinationDescription | None + AmazonopensearchserviceDestinationDescription: ( + AmazonopensearchserviceDestinationDescription | None + ) + SplunkDestinationDescription: SplunkDestinationDescription | None + HttpEndpointDestinationDescription: HttpEndpointDestinationDescription | None + SnowflakeDestinationDescription: SnowflakeDestinationDescription | None + AmazonOpenSearchServerlessDestinationDescription: ( + AmazonOpenSearchServerlessDestinationDescription | None + ) + IcebergDestinationDescription: IcebergDestinationDescription | None -DestinationDescriptionList = List[DestinationDescription] +DestinationDescriptionList = list[DestinationDescription] class MSKSourceDescription(TypedDict, total=False): - MSKClusterARN: Optional[MSKClusterARN] - TopicName: Optional[TopicName] - AuthenticationConfiguration: Optional[AuthenticationConfiguration] - DeliveryStartTimestamp: Optional[DeliveryStartTimestamp] - ReadFromTimestamp: Optional[ReadFromTimestamp] + MSKClusterARN: MSKClusterARN | None + TopicName: TopicName | None + AuthenticationConfiguration: AuthenticationConfiguration | None + DeliveryStartTimestamp: DeliveryStartTimestamp | None + ReadFromTimestamp: ReadFromTimestamp | None class KinesisStreamSourceDescription(TypedDict, total=False): - KinesisStreamARN: Optional[KinesisStreamARN] - RoleARN: Optional[RoleARN] - DeliveryStartTimestamp: Optional[DeliveryStartTimestamp] + KinesisStreamARN: KinesisStreamARN | None + RoleARN: RoleARN | None + DeliveryStartTimestamp: DeliveryStartTimestamp | None class DirectPutSourceDescription(TypedDict, total=False): - ThroughputHintInMBs: Optional[ThroughputHintInMBs] + ThroughputHintInMBs: ThroughputHintInMBs | None class SourceDescription(TypedDict, total=False): - DirectPutSourceDescription: Optional[DirectPutSourceDescription] - KinesisStreamSourceDescription: Optional[KinesisStreamSourceDescription] - MSKSourceDescription: Optional[MSKSourceDescription] - DatabaseSourceDescription: Optional[DatabaseSourceDescription] + DirectPutSourceDescription: DirectPutSourceDescription | None + KinesisStreamSourceDescription: KinesisStreamSourceDescription | None + MSKSourceDescription: MSKSourceDescription | None + DatabaseSourceDescription: DatabaseSourceDescription | None class DeliveryStreamEncryptionConfiguration(TypedDict, total=False): - KeyARN: Optional[AWSKMSKeyARN] - KeyType: Optional[KeyType] - Status: Optional[DeliveryStreamEncryptionStatus] - FailureDescription: Optional[FailureDescription] + KeyARN: AWSKMSKeyARN | None + KeyType: KeyType | None + Status: DeliveryStreamEncryptionStatus | None + FailureDescription: FailureDescription | None class DeliveryStreamDescription(TypedDict, total=False): DeliveryStreamName: DeliveryStreamName DeliveryStreamARN: DeliveryStreamARN DeliveryStreamStatus: DeliveryStreamStatus - FailureDescription: Optional[FailureDescription] - DeliveryStreamEncryptionConfiguration: Optional[DeliveryStreamEncryptionConfiguration] + FailureDescription: FailureDescription | None + DeliveryStreamEncryptionConfiguration: DeliveryStreamEncryptionConfiguration | None DeliveryStreamType: DeliveryStreamType VersionId: DeliveryStreamVersionId - CreateTimestamp: Optional[Timestamp] - LastUpdateTimestamp: Optional[Timestamp] - Source: Optional[SourceDescription] + CreateTimestamp: Timestamp | None + LastUpdateTimestamp: Timestamp | None + Source: SourceDescription | None Destinations: DestinationDescriptionList HasMoreDestinations: BooleanObject -DeliveryStreamNameList = List[DeliveryStreamName] +DeliveryStreamNameList = list[DeliveryStreamName] class DescribeDeliveryStreamInput(ServiceRequest): DeliveryStreamName: DeliveryStreamName - Limit: Optional[DescribeDeliveryStreamInputLimit] - ExclusiveStartDestinationId: Optional[DestinationId] + Limit: DescribeDeliveryStreamInputLimit | None + ExclusiveStartDestinationId: DestinationId | None class DescribeDeliveryStreamOutput(TypedDict, total=False): @@ -1245,70 +1245,70 @@ class DescribeDeliveryStreamOutput(TypedDict, total=False): class ElasticsearchDestinationUpdate(TypedDict, total=False): - RoleARN: Optional[RoleARN] - DomainARN: Optional[ElasticsearchDomainARN] - ClusterEndpoint: Optional[ElasticsearchClusterEndpoint] - IndexName: Optional[ElasticsearchIndexName] - TypeName: Optional[ElasticsearchTypeName] - IndexRotationPeriod: Optional[ElasticsearchIndexRotationPeriod] - BufferingHints: Optional[ElasticsearchBufferingHints] - RetryOptions: Optional[ElasticsearchRetryOptions] - S3Update: Optional[S3DestinationUpdate] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - DocumentIdOptions: Optional[DocumentIdOptions] + RoleARN: RoleARN | None + DomainARN: ElasticsearchDomainARN | None + ClusterEndpoint: ElasticsearchClusterEndpoint | None + IndexName: ElasticsearchIndexName | None + TypeName: ElasticsearchTypeName | None + IndexRotationPeriod: ElasticsearchIndexRotationPeriod | None + BufferingHints: ElasticsearchBufferingHints | None + RetryOptions: ElasticsearchRetryOptions | None + S3Update: S3DestinationUpdate | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + DocumentIdOptions: DocumentIdOptions | None class ExtendedS3DestinationUpdate(TypedDict, total=False): - RoleARN: Optional[RoleARN] - BucketARN: Optional[BucketARN] - Prefix: Optional[Prefix] - ErrorOutputPrefix: Optional[ErrorOutputPrefix] - BufferingHints: Optional[BufferingHints] - CompressionFormat: Optional[CompressionFormat] - EncryptionConfiguration: Optional[EncryptionConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[S3BackupMode] - S3BackupUpdate: Optional[S3DestinationUpdate] - DataFormatConversionConfiguration: Optional[DataFormatConversionConfiguration] - DynamicPartitioningConfiguration: Optional[DynamicPartitioningConfiguration] - FileExtension: Optional[FileExtension] - CustomTimeZone: Optional[CustomTimeZone] + RoleARN: RoleARN | None + BucketARN: BucketARN | None + Prefix: Prefix | None + ErrorOutputPrefix: ErrorOutputPrefix | None + BufferingHints: BufferingHints | None + CompressionFormat: CompressionFormat | None + EncryptionConfiguration: EncryptionConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: S3BackupMode | None + S3BackupUpdate: S3DestinationUpdate | None + DataFormatConversionConfiguration: DataFormatConversionConfiguration | None + DynamicPartitioningConfiguration: DynamicPartitioningConfiguration | None + FileExtension: FileExtension | None + CustomTimeZone: CustomTimeZone | None class HttpEndpointDestinationUpdate(TypedDict, total=False): - EndpointConfiguration: Optional[HttpEndpointConfiguration] - BufferingHints: Optional[HttpEndpointBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - RequestConfiguration: Optional[HttpEndpointRequestConfiguration] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RoleARN: Optional[RoleARN] - RetryOptions: Optional[HttpEndpointRetryOptions] - S3BackupMode: Optional[HttpEndpointS3BackupMode] - S3Update: Optional[S3DestinationUpdate] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + EndpointConfiguration: HttpEndpointConfiguration | None + BufferingHints: HttpEndpointBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + RequestConfiguration: HttpEndpointRequestConfiguration | None + ProcessingConfiguration: ProcessingConfiguration | None + RoleARN: RoleARN | None + RetryOptions: HttpEndpointRetryOptions | None + S3BackupMode: HttpEndpointS3BackupMode | None + S3Update: S3DestinationUpdate | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class IcebergDestinationUpdate(TypedDict, total=False): - DestinationTableConfigurationList: Optional[DestinationTableConfigurationList] - SchemaEvolutionConfiguration: Optional[SchemaEvolutionConfiguration] - TableCreationConfiguration: Optional[TableCreationConfiguration] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[IcebergS3BackupMode] - RetryOptions: Optional[RetryOptions] - RoleARN: Optional[RoleARN] - AppendOnly: Optional[BooleanObject] - CatalogConfiguration: Optional[CatalogConfiguration] - S3Configuration: Optional[S3DestinationConfiguration] + DestinationTableConfigurationList: DestinationTableConfigurationList | None + SchemaEvolutionConfiguration: SchemaEvolutionConfiguration | None + TableCreationConfiguration: TableCreationConfiguration | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: IcebergS3BackupMode | None + RetryOptions: RetryOptions | None + RoleARN: RoleARN | None + AppendOnly: BooleanObject | None + CatalogConfiguration: CatalogConfiguration | None + S3Configuration: S3DestinationConfiguration | None class ListDeliveryStreamsInput(ServiceRequest): - Limit: Optional[ListDeliveryStreamsInputLimit] - DeliveryStreamType: Optional[DeliveryStreamType] - ExclusiveStartDeliveryStreamName: Optional[DeliveryStreamName] + Limit: ListDeliveryStreamsInputLimit | None + DeliveryStreamType: DeliveryStreamType | None + ExclusiveStartDeliveryStreamName: DeliveryStreamName | None class ListDeliveryStreamsOutput(TypedDict, total=False): @@ -1318,11 +1318,11 @@ class ListDeliveryStreamsOutput(TypedDict, total=False): class ListTagsForDeliveryStreamInput(ServiceRequest): DeliveryStreamName: DeliveryStreamName - ExclusiveStartTagKey: Optional[TagKey] - Limit: Optional[ListTagsForDeliveryStreamInputLimit] + ExclusiveStartTagKey: TagKey | None + Limit: ListTagsForDeliveryStreamInputLimit | None -ListTagsForDeliveryStreamOutputTagList = List[Tag] +ListTagsForDeliveryStreamOutputTagList = list[Tag] class ListTagsForDeliveryStreamOutput(TypedDict, total=False): @@ -1334,7 +1334,7 @@ class Record(TypedDict, total=False): Data: Data -PutRecordBatchRequestEntryList = List[Record] +PutRecordBatchRequestEntryList = list[Record] class PutRecordBatchInput(ServiceRequest): @@ -1343,17 +1343,17 @@ class PutRecordBatchInput(ServiceRequest): class PutRecordBatchResponseEntry(TypedDict, total=False): - RecordId: Optional[PutResponseRecordId] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + RecordId: PutResponseRecordId | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None -PutRecordBatchResponseEntryList = List[PutRecordBatchResponseEntry] +PutRecordBatchResponseEntryList = list[PutRecordBatchResponseEntry] class PutRecordBatchOutput(TypedDict, total=False): FailedPutCount: NonNegativeIntegerObject - Encrypted: Optional[BooleanObject] + Encrypted: BooleanObject | None RequestResponses: PutRecordBatchResponseEntryList @@ -1364,63 +1364,63 @@ class PutRecordInput(ServiceRequest): class PutRecordOutput(TypedDict, total=False): RecordId: PutResponseRecordId - Encrypted: Optional[BooleanObject] + Encrypted: BooleanObject | None class RedshiftDestinationUpdate(TypedDict, total=False): - RoleARN: Optional[RoleARN] - ClusterJDBCURL: Optional[ClusterJDBCURL] - CopyCommand: Optional[CopyCommand] - Username: Optional[Username] - Password: Optional[Password] - RetryOptions: Optional[RedshiftRetryOptions] - S3Update: Optional[S3DestinationUpdate] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupMode: Optional[RedshiftS3BackupMode] - S3BackupUpdate: Optional[S3DestinationUpdate] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + RoleARN: RoleARN | None + ClusterJDBCURL: ClusterJDBCURL | None + CopyCommand: CopyCommand | None + Username: Username | None + Password: Password | None + RetryOptions: RedshiftRetryOptions | None + S3Update: S3DestinationUpdate | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupMode: RedshiftS3BackupMode | None + S3BackupUpdate: S3DestinationUpdate | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class SnowflakeDestinationUpdate(TypedDict, total=False): - AccountUrl: Optional[SnowflakeAccountUrl] - PrivateKey: Optional[SnowflakePrivateKey] - KeyPassphrase: Optional[SnowflakeKeyPassphrase] - User: Optional[SnowflakeUser] - Database: Optional[SnowflakeDatabase] - Schema: Optional[SnowflakeSchema] - Table: Optional[SnowflakeTable] - SnowflakeRoleConfiguration: Optional[SnowflakeRoleConfiguration] - DataLoadingOption: Optional[SnowflakeDataLoadingOption] - MetaDataColumnName: Optional[SnowflakeMetaDataColumnName] - ContentColumnName: Optional[SnowflakeContentColumnName] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RoleARN: Optional[RoleARN] - RetryOptions: Optional[SnowflakeRetryOptions] - S3BackupMode: Optional[SnowflakeS3BackupMode] - S3Update: Optional[S3DestinationUpdate] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] - BufferingHints: Optional[SnowflakeBufferingHints] + AccountUrl: SnowflakeAccountUrl | None + PrivateKey: SnowflakePrivateKey | None + KeyPassphrase: SnowflakeKeyPassphrase | None + User: SnowflakeUser | None + Database: SnowflakeDatabase | None + Schema: SnowflakeSchema | None + Table: SnowflakeTable | None + SnowflakeRoleConfiguration: SnowflakeRoleConfiguration | None + DataLoadingOption: SnowflakeDataLoadingOption | None + MetaDataColumnName: SnowflakeMetaDataColumnName | None + ContentColumnName: SnowflakeContentColumnName | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + RoleARN: RoleARN | None + RetryOptions: SnowflakeRetryOptions | None + S3BackupMode: SnowflakeS3BackupMode | None + S3Update: S3DestinationUpdate | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None + BufferingHints: SnowflakeBufferingHints | None class SplunkDestinationUpdate(TypedDict, total=False): - HECEndpoint: Optional[HECEndpoint] - HECEndpointType: Optional[HECEndpointType] - HECToken: Optional[HECToken] - HECAcknowledgmentTimeoutInSeconds: Optional[HECAcknowledgmentTimeoutInSeconds] - RetryOptions: Optional[SplunkRetryOptions] - S3BackupMode: Optional[SplunkS3BackupMode] - S3Update: Optional[S3DestinationUpdate] - ProcessingConfiguration: Optional[ProcessingConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - BufferingHints: Optional[SplunkBufferingHints] - SecretsManagerConfiguration: Optional[SecretsManagerConfiguration] + HECEndpoint: HECEndpoint | None + HECEndpointType: HECEndpointType | None + HECToken: HECToken | None + HECAcknowledgmentTimeoutInSeconds: HECAcknowledgmentTimeoutInSeconds | None + RetryOptions: SplunkRetryOptions | None + S3BackupMode: SplunkS3BackupMode | None + S3Update: S3DestinationUpdate | None + ProcessingConfiguration: ProcessingConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + BufferingHints: SplunkBufferingHints | None + SecretsManagerConfiguration: SecretsManagerConfiguration | None class StartDeliveryStreamEncryptionInput(ServiceRequest): DeliveryStreamName: DeliveryStreamName - DeliveryStreamEncryptionConfigurationInput: Optional[DeliveryStreamEncryptionConfigurationInput] + DeliveryStreamEncryptionConfigurationInput: DeliveryStreamEncryptionConfigurationInput | None class StartDeliveryStreamEncryptionOutput(TypedDict, total=False): @@ -1444,7 +1444,7 @@ class TagDeliveryStreamOutput(TypedDict, total=False): pass -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class UntagDeliveryStreamInput(ServiceRequest): @@ -1460,18 +1460,16 @@ class UpdateDestinationInput(ServiceRequest): DeliveryStreamName: DeliveryStreamName CurrentDeliveryStreamVersionId: DeliveryStreamVersionId DestinationId: DestinationId - S3DestinationUpdate: Optional[S3DestinationUpdate] - ExtendedS3DestinationUpdate: Optional[ExtendedS3DestinationUpdate] - RedshiftDestinationUpdate: Optional[RedshiftDestinationUpdate] - ElasticsearchDestinationUpdate: Optional[ElasticsearchDestinationUpdate] - AmazonopensearchserviceDestinationUpdate: Optional[AmazonopensearchserviceDestinationUpdate] - SplunkDestinationUpdate: Optional[SplunkDestinationUpdate] - HttpEndpointDestinationUpdate: Optional[HttpEndpointDestinationUpdate] - AmazonOpenSearchServerlessDestinationUpdate: Optional[ - AmazonOpenSearchServerlessDestinationUpdate - ] - SnowflakeDestinationUpdate: Optional[SnowflakeDestinationUpdate] - IcebergDestinationUpdate: Optional[IcebergDestinationUpdate] + S3DestinationUpdate: S3DestinationUpdate | None + ExtendedS3DestinationUpdate: ExtendedS3DestinationUpdate | None + RedshiftDestinationUpdate: RedshiftDestinationUpdate | None + ElasticsearchDestinationUpdate: ElasticsearchDestinationUpdate | None + AmazonopensearchserviceDestinationUpdate: AmazonopensearchserviceDestinationUpdate | None + SplunkDestinationUpdate: SplunkDestinationUpdate | None + HttpEndpointDestinationUpdate: HttpEndpointDestinationUpdate | None + AmazonOpenSearchServerlessDestinationUpdate: AmazonOpenSearchServerlessDestinationUpdate | None + SnowflakeDestinationUpdate: SnowflakeDestinationUpdate | None + IcebergDestinationUpdate: IcebergDestinationUpdate | None class UpdateDestinationOutput(TypedDict, total=False): @@ -1479,8 +1477,8 @@ class UpdateDestinationOutput(TypedDict, total=False): class FirehoseApi: - service = "firehose" - version = "2015-08-04" + service: str = "firehose" + version: str = "2015-08-04" @handler("CreateDeliveryStream") def create_delivery_stream( diff --git a/localstack-core/localstack/aws/api/iam/__init__.py b/localstack-core/localstack/aws/api/iam/__init__.py index 6967249f66bf8..cb76197128008 100644 --- a/localstack-core/localstack/aws/api/iam/__init__.py +++ b/localstack-core/localstack/aws/api/iam/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -13,6 +13,8 @@ ContextKeyValueType = str DeletionTaskIdType = str EvalDecisionSourceType = str +FeatureDisabledMessage = str +FeatureEnabledMessage = str LineNumber = int OpenIDConnectProviderUrlType = str OrganizationIdType = str @@ -27,6 +29,7 @@ accessKeyIdType = str accessKeySecretType = str accountAliasType = str +accountIdType = str allUsers = bool arnType = str attachmentCountType = int @@ -37,11 +40,14 @@ certificateChainType = str certificateIdType = str clientIDType = str +consoleDeepLinkType = str credentialAgeDays = int credentialReportExpiredExceptionMessage = str credentialReportNotPresentExceptionMessage = str credentialReportNotReadyExceptionMessage = str customSuffixType = str +delegationRequestDescriptionType = str +delegationRequestIdType = str deleteConflictMessage = str duplicateCertificateMessage = str duplicateSSHPublicKeyMessage = str @@ -61,6 +67,7 @@ jobIDType = str keyPairMismatchMessage = str limitExceededMessage = str +localeType = str malformedCertificateMessage = str malformedPolicyDocumentMessage = str markerType = str @@ -68,19 +75,25 @@ maxPasswordAgeType = int minimumPasswordLengthType = int noSuchEntityMessage = str +notesType = str +notificationChannelType = str openIdIdpCommunicationErrorExceptionMessage = str organizationsEntityPathType = str organizationsPolicyIdType = str +ownerIdType = str passwordPolicyViolationMessage = str passwordReusePreventionType = int passwordType = str pathPrefixType = str pathType = str +permissionType = str policyDescriptionType = str policyDocumentType = str policyEvaluationErrorMessage = str policyNameType = str policyNotAttachableMessage = str +policyParameterNameType = str +policyParameterValueType = str policyPathType = str policyVersionIdType = str privateKeyIdType = str @@ -88,7 +101,11 @@ publicKeyFingerprintType = str publicKeyIdType = str publicKeyMaterialType = str +redirectUrlType = str reportGenerationLimitExceededMessage = str +requestMessageType = str +requestorNameType = str +requestorWorkflowIdType = str responseMarkerType = str roleDescriptionType = str roleMaxSessionDurationType = int @@ -105,7 +122,9 @@ servicePassword = str serviceSpecificCredentialId = str serviceUserName = str +sessionDurationType = int stringType = str +summaryContentType = str summaryValueType = int tagKeyType = str tagValueType = str @@ -166,6 +185,11 @@ class PolicyEvaluationDecisionType(StrEnum): implicitDeny = "implicitDeny" +class PolicyParameterTypeEnum(StrEnum): + string = "string" + stringList = "stringList" + + class PolicySourceType(StrEnum): user = "user" group = "group" @@ -218,6 +242,18 @@ class jobStatusType(StrEnum): FAILED = "FAILED" +class permissionCheckResultType(StrEnum): + ALLOWED = "ALLOWED" + DENIED = "DENIED" + UNSURE = "UNSURE" + + +class permissionCheckStatusType(StrEnum): + COMPLETE = "COMPLETE" + IN_PROGRESS = "IN_PROGRESS" + FAILED = "FAILED" + + class policyOwnerEntityType(StrEnum): USER = "USER" ROLE = "ROLE" @@ -242,6 +278,16 @@ class sortKeyType(StrEnum): LAST_AUTHENTICATED_TIME_DESCENDING = "LAST_AUTHENTICATED_TIME_DESCENDING" +class stateType(StrEnum): + UNASSIGNED = "UNASSIGNED" + ASSIGNED = "ASSIGNED" + PENDING_APPROVAL = "PENDING_APPROVAL" + FINALIZED = "FINALIZED" + ACCEPTED = "ACCEPTED" + REJECTED = "REJECTED" + EXPIRED = "EXPIRED" + + class statusType(StrEnum): Active = "Active" Inactive = "Inactive" @@ -276,6 +322,20 @@ class summaryKeyType(StrEnum): PolicyVersionsInUseQuota = "PolicyVersionsInUseQuota" VersionsPerPolicyQuota = "VersionsPerPolicyQuota" GlobalEndpointTokenVersion = "GlobalEndpointTokenVersion" + AssumeRolePolicySizeQuota = "AssumeRolePolicySizeQuota" + InstanceProfiles = "InstanceProfiles" + InstanceProfilesQuota = "InstanceProfilesQuota" + Providers = "Providers" + RolePolicySizeQuota = "RolePolicySizeQuota" + Roles = "Roles" + RolesQuota = "RolesQuota" + + +class summaryStateType(StrEnum): + AVAILABLE = "AVAILABLE" + NOT_AVAILABLE = "NOT_AVAILABLE" + NOT_SUPPORTED = "NOT_SUPPORTED" + FAILED = "FAILED" class AccountNotManagementOrDelegatedAdministratorException(ServiceException): @@ -344,6 +404,18 @@ class EntityTemporarilyUnmodifiableException(ServiceException): status_code: int = 409 +class FeatureDisabledException(ServiceException): + code: str = "FeatureDisabled" + sender_fault: bool = True + status_code: int = 404 + + +class FeatureEnabledException(ServiceException): + code: str = "FeatureEnabled" + sender_fault: bool = True + status_code: int = 409 + + class InvalidAuthenticationCodeException(ServiceException): code: str = "InvalidAuthenticationCode" sender_fault: bool = True @@ -476,19 +548,23 @@ class UnrecognizedPublicKeyEncodingException(ServiceException): status_code: int = 400 +class AcceptDelegationRequestRequest(ServiceRequest): + DelegationRequestId: delegationRequestIdType + + dateType = datetime class AccessDetail(TypedDict, total=False): ServiceName: serviceNameType ServiceNamespace: serviceNamespaceType - Region: Optional[stringType] - EntityPath: Optional[organizationsEntityPathType] - LastAuthenticatedTime: Optional[dateType] - TotalAuthenticatedEntities: Optional[integerType] + Region: stringType | None + EntityPath: organizationsEntityPathType | None + LastAuthenticatedTime: dateType | None + TotalAuthenticatedEntities: integerType | None -AccessDetails = List[AccessDetail] +AccessDetails = list[AccessDetail] class AccessKey(TypedDict, total=False): @@ -496,23 +572,23 @@ class AccessKey(TypedDict, total=False): AccessKeyId: accessKeyIdType Status: statusType SecretAccessKey: accessKeySecretType - CreateDate: Optional[dateType] + CreateDate: dateType | None class AccessKeyLastUsed(TypedDict, total=False): - LastUsedDate: Optional[dateType] + LastUsedDate: dateType | None ServiceName: stringType Region: stringType class AccessKeyMetadata(TypedDict, total=False): - UserName: Optional[userNameType] - AccessKeyId: Optional[accessKeyIdType] - Status: Optional[statusType] - CreateDate: Optional[dateType] + UserName: userNameType | None + AccessKeyId: accessKeyIdType | None + Status: statusType | None + CreateDate: dateType | None -ActionNameListType = List[ActionNameType] +ActionNameListType = list[ActionNameType] class AddClientIDToOpenIDConnectProviderRequest(ServiceRequest): @@ -530,7 +606,11 @@ class AddUserToGroupRequest(ServiceRequest): UserName: existingUserNameType -ArnListType = List[arnType] +ArnListType = list[arnType] + + +class AssociateDelegationRequestRequest(ServiceRequest): + DelegationRequestId: delegationRequestIdType class AttachGroupPolicyRequest(ServiceRequest): @@ -549,17 +629,17 @@ class AttachUserPolicyRequest(ServiceRequest): class AttachedPermissionsBoundary(TypedDict, total=False): - PermissionsBoundaryType: Optional[PermissionsBoundaryAttachmentType] - PermissionsBoundaryArn: Optional[arnType] + PermissionsBoundaryType: PermissionsBoundaryAttachmentType | None + PermissionsBoundaryArn: arnType | None class AttachedPolicy(TypedDict, total=False): - PolicyName: Optional[policyNameType] - PolicyArn: Optional[arnType] + PolicyName: policyNameType | None + PolicyArn: arnType | None BootstrapDatum = bytes -CertificationMapType = Dict[CertificationKeyType, CertificationValueType] +CertificationMapType = dict[CertificationKeyType, CertificationValueType] class ChangePasswordRequest(ServiceRequest): @@ -567,21 +647,21 @@ class ChangePasswordRequest(ServiceRequest): NewPassword: passwordType -ContextKeyValueListType = List[ContextKeyValueType] +ContextKeyValueListType = list[ContextKeyValueType] class ContextEntry(TypedDict, total=False): - ContextKeyName: Optional[ContextKeyNameType] - ContextKeyValues: Optional[ContextKeyValueListType] - ContextKeyType: Optional[ContextKeyTypeEnum] + ContextKeyName: ContextKeyNameType | None + ContextKeyValues: ContextKeyValueListType | None + ContextKeyType: ContextKeyTypeEnum | None -ContextEntryListType = List[ContextEntry] -ContextKeyNamesResultListType = List[ContextKeyNameType] +ContextEntryListType = list[ContextEntry] +ContextKeyNamesResultListType = list[ContextKeyNameType] class CreateAccessKeyRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None class CreateAccessKeyResponse(TypedDict, total=False): @@ -592,8 +672,42 @@ class CreateAccountAliasRequest(ServiceRequest): AccountAlias: accountAliasType +policyParameterValuesListType = list[policyParameterValueType] + + +class PolicyParameter(TypedDict, total=False): + Name: policyParameterNameType | None + Values: policyParameterValuesListType | None + Type: PolicyParameterTypeEnum | None + + +policyParameterListType = list[PolicyParameter] + + +class DelegationPermission(TypedDict, total=False): + PolicyTemplateArn: arnType | None + Parameters: policyParameterListType | None + + +class CreateDelegationRequestRequest(ServiceRequest): + OwnerAccountId: accountIdType | None + Description: delegationRequestDescriptionType + Permissions: DelegationPermission + RequestMessage: requestMessageType | None + RequestorWorkflowId: requestorWorkflowIdType + RedirectUrl: redirectUrlType | None + NotificationChannel: notificationChannelType + SessionDuration: sessionDurationType + OnlySendByOwner: booleanType | None + + +class CreateDelegationRequestResponse(TypedDict, total=False): + ConsoleDeepLink: consoleDeepLinkType | None + DelegationRequestId: delegationRequestIdType | None + + class CreateGroupRequest(ServiceRequest): - Path: Optional[pathType] + Path: pathType | None GroupName: groupNameType @@ -614,18 +728,18 @@ class Tag(TypedDict, total=False): Value: tagValueType -tagListType = List[Tag] +tagListType = list[Tag] class CreateInstanceProfileRequest(ServiceRequest): InstanceProfileName: instanceProfileNameType - Path: Optional[pathType] - Tags: Optional[tagListType] + Path: pathType | None + Tags: tagListType | None class RoleLastUsed(TypedDict, total=False): - LastUsedDate: Optional[dateType] - Region: Optional[stringType] + LastUsedDate: dateType | None + Region: stringType | None class Role(TypedDict, total=False): @@ -634,15 +748,15 @@ class Role(TypedDict, total=False): RoleId: idType Arn: arnType CreateDate: dateType - AssumeRolePolicyDocument: Optional[policyDocumentType] - Description: Optional[roleDescriptionType] - MaxSessionDuration: Optional[roleMaxSessionDurationType] - PermissionsBoundary: Optional[AttachedPermissionsBoundary] - Tags: Optional[tagListType] - RoleLastUsed: Optional[RoleLastUsed] + AssumeRolePolicyDocument: policyDocumentType | None + Description: roleDescriptionType | None + MaxSessionDuration: roleMaxSessionDurationType | None + PermissionsBoundary: AttachedPermissionsBoundary | None + Tags: tagListType | None + RoleLastUsed: RoleLastUsed | None -roleListType = List[Role] +roleListType = list[Role] class InstanceProfile(TypedDict, total=False): @@ -652,7 +766,7 @@ class InstanceProfile(TypedDict, total=False): Arn: arnType CreateDate: dateType Roles: roleListType - Tags: Optional[tagListType] + Tags: tagListType | None class CreateInstanceProfileResponse(TypedDict, total=False): @@ -660,89 +774,89 @@ class CreateInstanceProfileResponse(TypedDict, total=False): class CreateLoginProfileRequest(ServiceRequest): - UserName: Optional[userNameType] - Password: Optional[passwordType] - PasswordResetRequired: Optional[booleanType] + UserName: userNameType | None + Password: passwordType | None + PasswordResetRequired: booleanType | None class LoginProfile(TypedDict, total=False): UserName: userNameType CreateDate: dateType - PasswordResetRequired: Optional[booleanType] + PasswordResetRequired: booleanType | None class CreateLoginProfileResponse(TypedDict, total=False): LoginProfile: LoginProfile -thumbprintListType = List[thumbprintType] -clientIDListType = List[clientIDType] +thumbprintListType = list[thumbprintType] +clientIDListType = list[clientIDType] class CreateOpenIDConnectProviderRequest(ServiceRequest): Url: OpenIDConnectProviderUrlType - ClientIDList: Optional[clientIDListType] - ThumbprintList: Optional[thumbprintListType] - Tags: Optional[tagListType] + ClientIDList: clientIDListType | None + ThumbprintList: thumbprintListType | None + Tags: tagListType | None class CreateOpenIDConnectProviderResponse(TypedDict, total=False): - OpenIDConnectProviderArn: Optional[arnType] - Tags: Optional[tagListType] + OpenIDConnectProviderArn: arnType | None + Tags: tagListType | None class CreatePolicyRequest(ServiceRequest): PolicyName: policyNameType - Path: Optional[policyPathType] + Path: policyPathType | None PolicyDocument: policyDocumentType - Description: Optional[policyDescriptionType] - Tags: Optional[tagListType] + Description: policyDescriptionType | None + Tags: tagListType | None class Policy(TypedDict, total=False): - PolicyName: Optional[policyNameType] - PolicyId: Optional[idType] - Arn: Optional[arnType] - Path: Optional[policyPathType] - DefaultVersionId: Optional[policyVersionIdType] - AttachmentCount: Optional[attachmentCountType] - PermissionsBoundaryUsageCount: Optional[attachmentCountType] - IsAttachable: Optional[booleanType] - Description: Optional[policyDescriptionType] - CreateDate: Optional[dateType] - UpdateDate: Optional[dateType] - Tags: Optional[tagListType] + PolicyName: policyNameType | None + PolicyId: idType | None + Arn: arnType | None + Path: policyPathType | None + DefaultVersionId: policyVersionIdType | None + AttachmentCount: attachmentCountType | None + PermissionsBoundaryUsageCount: attachmentCountType | None + IsAttachable: booleanType | None + Description: policyDescriptionType | None + CreateDate: dateType | None + UpdateDate: dateType | None + Tags: tagListType | None class CreatePolicyResponse(TypedDict, total=False): - Policy: Optional[Policy] + Policy: Policy | None class CreatePolicyVersionRequest(ServiceRequest): PolicyArn: arnType PolicyDocument: policyDocumentType - SetAsDefault: Optional[booleanType] + SetAsDefault: booleanType | None class PolicyVersion(TypedDict, total=False): - Document: Optional[policyDocumentType] - VersionId: Optional[policyVersionIdType] - IsDefaultVersion: Optional[booleanType] - CreateDate: Optional[dateType] + Document: policyDocumentType | None + VersionId: policyVersionIdType | None + IsDefaultVersion: booleanType | None + CreateDate: dateType | None class CreatePolicyVersionResponse(TypedDict, total=False): - PolicyVersion: Optional[PolicyVersion] + PolicyVersion: PolicyVersion | None class CreateRoleRequest(ServiceRequest): - Path: Optional[pathType] + Path: pathType | None RoleName: roleNameType AssumeRolePolicyDocument: policyDocumentType - Description: Optional[roleDescriptionType] - MaxSessionDuration: Optional[roleMaxSessionDurationType] - PermissionsBoundary: Optional[arnType] - Tags: Optional[tagListType] + Description: roleDescriptionType | None + MaxSessionDuration: roleMaxSessionDurationType | None + PermissionsBoundary: arnType | None + Tags: tagListType | None class CreateRoleResponse(TypedDict, total=False): @@ -752,54 +866,54 @@ class CreateRoleResponse(TypedDict, total=False): class CreateSAMLProviderRequest(ServiceRequest): SAMLMetadataDocument: SAMLMetadataDocumentType Name: SAMLProviderNameType - Tags: Optional[tagListType] - AssertionEncryptionMode: Optional[assertionEncryptionModeType] - AddPrivateKey: Optional[privateKeyType] + Tags: tagListType | None + AssertionEncryptionMode: assertionEncryptionModeType | None + AddPrivateKey: privateKeyType | None class CreateSAMLProviderResponse(TypedDict, total=False): - SAMLProviderArn: Optional[arnType] - Tags: Optional[tagListType] + SAMLProviderArn: arnType | None + Tags: tagListType | None class CreateServiceLinkedRoleRequest(ServiceRequest): AWSServiceName: groupNameType - Description: Optional[roleDescriptionType] - CustomSuffix: Optional[customSuffixType] + Description: roleDescriptionType | None + CustomSuffix: customSuffixType | None class CreateServiceLinkedRoleResponse(TypedDict, total=False): - Role: Optional[Role] + Role: Role | None class CreateServiceSpecificCredentialRequest(ServiceRequest): UserName: userNameType ServiceName: serviceName - CredentialAgeDays: Optional[credentialAgeDays] + CredentialAgeDays: credentialAgeDays | None class ServiceSpecificCredential(TypedDict, total=False): CreateDate: dateType - ExpirationDate: Optional[dateType] + ExpirationDate: dateType | None ServiceName: serviceName - ServiceUserName: Optional[serviceUserName] - ServicePassword: Optional[servicePassword] - ServiceCredentialAlias: Optional[serviceCredentialAlias] - ServiceCredentialSecret: Optional[serviceCredentialSecret] + ServiceUserName: serviceUserName | None + ServicePassword: servicePassword | None + ServiceCredentialAlias: serviceCredentialAlias | None + ServiceCredentialSecret: serviceCredentialSecret | None ServiceSpecificCredentialId: serviceSpecificCredentialId UserName: userNameType Status: statusType class CreateServiceSpecificCredentialResponse(TypedDict, total=False): - ServiceSpecificCredential: Optional[ServiceSpecificCredential] + ServiceSpecificCredential: ServiceSpecificCredential | None class CreateUserRequest(ServiceRequest): - Path: Optional[pathType] + Path: pathType | None UserName: userNameType - PermissionsBoundary: Optional[arnType] - Tags: Optional[tagListType] + PermissionsBoundary: arnType | None + Tags: tagListType | None class User(TypedDict, total=False): @@ -808,28 +922,28 @@ class User(TypedDict, total=False): UserId: idType Arn: arnType CreateDate: dateType - PasswordLastUsed: Optional[dateType] - PermissionsBoundary: Optional[AttachedPermissionsBoundary] - Tags: Optional[tagListType] + PasswordLastUsed: dateType | None + PermissionsBoundary: AttachedPermissionsBoundary | None + Tags: tagListType | None class CreateUserResponse(TypedDict, total=False): - User: Optional[User] + User: User | None class CreateVirtualMFADeviceRequest(ServiceRequest): - Path: Optional[pathType] + Path: pathType | None VirtualMFADeviceName: virtualMFADeviceName - Tags: Optional[tagListType] + Tags: tagListType | None class VirtualMFADevice(TypedDict, total=False): SerialNumber: serialNumberType - Base32StringSeed: Optional[BootstrapDatum] - QRCodePNG: Optional[BootstrapDatum] - User: Optional[User] - EnableDate: Optional[dateType] - Tags: Optional[tagListType] + Base32StringSeed: BootstrapDatum | None + QRCodePNG: BootstrapDatum | None + User: User | None + EnableDate: dateType | None + Tags: tagListType | None class CreateVirtualMFADeviceResponse(TypedDict, total=False): @@ -837,12 +951,38 @@ class CreateVirtualMFADeviceResponse(TypedDict, total=False): class DeactivateMFADeviceRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None SerialNumber: serialNumberType +rolePermissionRestrictionArnListType = list[arnType] + + +class DelegationRequest(TypedDict, total=False): + DelegationRequestId: delegationRequestIdType | None + OwnerAccountId: accountIdType | None + Description: delegationRequestDescriptionType | None + RequestMessage: requestMessageType | None + Permissions: DelegationPermission | None + PermissionPolicy: permissionType | None + RolePermissionRestrictionArns: rolePermissionRestrictionArnListType | None + OwnerId: ownerIdType | None + ApproverId: arnType | None + State: stateType | None + ExpirationTime: dateType | None + RequestorId: accountIdType | None + RequestorName: requestorNameType | None + CreateDate: dateType | None + SessionDuration: sessionDurationType | None + RedirectUrl: redirectUrlType | None + Notes: notesType | None + RejectionReason: notesType | None + OnlySendByOwner: booleanType | None + UpdatedTime: dateType | None + + class DeleteAccessKeyRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None AccessKeyId: accessKeyIdType @@ -864,7 +1004,7 @@ class DeleteInstanceProfileRequest(ServiceRequest): class DeleteLoginProfileRequest(ServiceRequest): - UserName: Optional[userNameType] + UserName: userNameType | None class DeleteOpenIDConnectProviderRequest(ServiceRequest): @@ -915,12 +1055,12 @@ class DeleteServiceLinkedRoleResponse(TypedDict, total=False): class DeleteServiceSpecificCredentialRequest(ServiceRequest): - UserName: Optional[userNameType] + UserName: userNameType | None ServiceSpecificCredentialId: serviceSpecificCredentialId class DeleteSigningCertificateRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None CertificateId: certificateIdType @@ -942,16 +1082,16 @@ class DeleteVirtualMFADeviceRequest(ServiceRequest): class RoleUsageType(TypedDict, total=False): - Region: Optional[RegionNameType] - Resources: Optional[ArnListType] + Region: RegionNameType | None + Resources: ArnListType | None -RoleUsageListType = List[RoleUsageType] +RoleUsageListType = list[RoleUsageType] class DeletionTaskFailureReasonType(TypedDict, total=False): - Reason: Optional[ReasonType] - RoleUsageList: Optional[RoleUsageListType] + Reason: ReasonType | None + RoleUsageList: RoleUsageListType | None class DetachGroupPolicyRequest(ServiceRequest): @@ -973,12 +1113,12 @@ class DisableOrganizationsRootCredentialsManagementRequest(ServiceRequest): pass -FeaturesListType = List[FeatureType] +FeaturesListType = list[FeatureType] class DisableOrganizationsRootCredentialsManagementResponse(TypedDict, total=False): - OrganizationId: Optional[OrganizationIdType] - EnabledFeatures: Optional[FeaturesListType] + OrganizationId: OrganizationIdType | None + EnabledFeatures: FeaturesListType | None class DisableOrganizationsRootSessionsRequest(ServiceRequest): @@ -986,8 +1126,8 @@ class DisableOrganizationsRootSessionsRequest(ServiceRequest): class DisableOrganizationsRootSessionsResponse(TypedDict, total=False): - OrganizationId: Optional[OrganizationIdType] - EnabledFeatures: Optional[FeaturesListType] + OrganizationId: OrganizationIdType | None + EnabledFeatures: FeaturesListType | None class EnableMFADeviceRequest(ServiceRequest): @@ -1002,8 +1142,8 @@ class EnableOrganizationsRootCredentialsManagementRequest(ServiceRequest): class EnableOrganizationsRootCredentialsManagementResponse(TypedDict, total=False): - OrganizationId: Optional[OrganizationIdType] - EnabledFeatures: Optional[FeaturesListType] + OrganizationId: OrganizationIdType | None + EnabledFeatures: FeaturesListType | None class EnableOrganizationsRootSessionsRequest(ServiceRequest): @@ -1011,8 +1151,12 @@ class EnableOrganizationsRootSessionsRequest(ServiceRequest): class EnableOrganizationsRootSessionsResponse(TypedDict, total=False): - OrganizationId: Optional[OrganizationIdType] - EnabledFeatures: Optional[FeaturesListType] + OrganizationId: OrganizationIdType | None + EnabledFeatures: FeaturesListType | None + + +class EnableOutboundWebIdentityFederationResponse(TypedDict, total=False): + IssuerIdentifier: stringType | None class EntityInfo(TypedDict, total=False): @@ -1020,12 +1164,12 @@ class EntityInfo(TypedDict, total=False): Name: userNameType Type: policyOwnerEntityType Id: idType - Path: Optional[pathType] + Path: pathType | None class EntityDetails(TypedDict, total=False): EntityInfo: EntityInfo - LastAuthenticated: Optional[dateType] + LastAuthenticated: dateType | None class ErrorDetails(TypedDict, total=False): @@ -1033,80 +1177,80 @@ class ErrorDetails(TypedDict, total=False): Code: stringType -EvalDecisionDetailsType = Dict[EvalDecisionSourceType, PolicyEvaluationDecisionType] +EvalDecisionDetailsType = dict[EvalDecisionSourceType, PolicyEvaluationDecisionType] class PermissionsBoundaryDecisionDetail(TypedDict, total=False): - AllowedByPermissionsBoundary: Optional[booleanType] + AllowedByPermissionsBoundary: booleanType | None class Position(TypedDict, total=False): - Line: Optional[LineNumber] - Column: Optional[ColumnNumber] + Line: LineNumber | None + Column: ColumnNumber | None class Statement(TypedDict, total=False): - SourcePolicyId: Optional[PolicyIdentifierType] - SourcePolicyType: Optional[PolicySourceType] - StartPosition: Optional[Position] - EndPosition: Optional[Position] + SourcePolicyId: PolicyIdentifierType | None + SourcePolicyType: PolicySourceType | None + StartPosition: Position | None + EndPosition: Position | None -StatementListType = List[Statement] +StatementListType = list[Statement] class ResourceSpecificResult(TypedDict, total=False): EvalResourceName: ResourceNameType EvalResourceDecision: PolicyEvaluationDecisionType - MatchedStatements: Optional[StatementListType] - MissingContextValues: Optional[ContextKeyNamesResultListType] - EvalDecisionDetails: Optional[EvalDecisionDetailsType] - PermissionsBoundaryDecisionDetail: Optional[PermissionsBoundaryDecisionDetail] + MatchedStatements: StatementListType | None + MissingContextValues: ContextKeyNamesResultListType | None + EvalDecisionDetails: EvalDecisionDetailsType | None + PermissionsBoundaryDecisionDetail: PermissionsBoundaryDecisionDetail | None -ResourceSpecificResultListType = List[ResourceSpecificResult] +ResourceSpecificResultListType = list[ResourceSpecificResult] class OrganizationsDecisionDetail(TypedDict, total=False): - AllowedByOrganizations: Optional[booleanType] + AllowedByOrganizations: booleanType | None class EvaluationResult(TypedDict, total=False): EvalActionName: ActionNameType - EvalResourceName: Optional[ResourceNameType] + EvalResourceName: ResourceNameType | None EvalDecision: PolicyEvaluationDecisionType - MatchedStatements: Optional[StatementListType] - MissingContextValues: Optional[ContextKeyNamesResultListType] - OrganizationsDecisionDetail: Optional[OrganizationsDecisionDetail] - PermissionsBoundaryDecisionDetail: Optional[PermissionsBoundaryDecisionDetail] - EvalDecisionDetails: Optional[EvalDecisionDetailsType] - ResourceSpecificResults: Optional[ResourceSpecificResultListType] + MatchedStatements: StatementListType | None + MissingContextValues: ContextKeyNamesResultListType | None + OrganizationsDecisionDetail: OrganizationsDecisionDetail | None + PermissionsBoundaryDecisionDetail: PermissionsBoundaryDecisionDetail | None + EvalDecisionDetails: EvalDecisionDetailsType | None + ResourceSpecificResults: ResourceSpecificResultListType | None -EvaluationResultsListType = List[EvaluationResult] +EvaluationResultsListType = list[EvaluationResult] class GenerateCredentialReportResponse(TypedDict, total=False): - State: Optional[ReportStateType] - Description: Optional[ReportStateDescriptionType] + State: ReportStateType | None + Description: ReportStateDescriptionType | None class GenerateOrganizationsAccessReportRequest(ServiceRequest): EntityPath: organizationsEntityPathType - OrganizationsPolicyId: Optional[organizationsPolicyIdType] + OrganizationsPolicyId: organizationsPolicyIdType | None class GenerateOrganizationsAccessReportResponse(TypedDict, total=False): - JobId: Optional[jobIDType] + JobId: jobIDType | None class GenerateServiceLastAccessedDetailsRequest(ServiceRequest): Arn: arnType - Granularity: Optional[AccessAdvisorUsageGranularityType] + Granularity: AccessAdvisorUsageGranularityType | None class GenerateServiceLastAccessedDetailsResponse(TypedDict, total=False): - JobId: Optional[jobIDType] + JobId: jobIDType | None class GetAccessKeyLastUsedRequest(ServiceRequest): @@ -1114,132 +1258,132 @@ class GetAccessKeyLastUsedRequest(ServiceRequest): class GetAccessKeyLastUsedResponse(TypedDict, total=False): - UserName: Optional[existingUserNameType] - AccessKeyLastUsed: Optional[AccessKeyLastUsed] + UserName: existingUserNameType | None + AccessKeyLastUsed: AccessKeyLastUsed | None -entityListType = List[EntityType] +entityListType = list[EntityType] class GetAccountAuthorizationDetailsRequest(ServiceRequest): - Filter: Optional[entityListType] - MaxItems: Optional[maxItemsType] - Marker: Optional[markerType] + Filter: entityListType | None + MaxItems: maxItemsType | None + Marker: markerType | None -policyDocumentVersionListType = List[PolicyVersion] +policyDocumentVersionListType = list[PolicyVersion] class ManagedPolicyDetail(TypedDict, total=False): - PolicyName: Optional[policyNameType] - PolicyId: Optional[idType] - Arn: Optional[arnType] - Path: Optional[policyPathType] - DefaultVersionId: Optional[policyVersionIdType] - AttachmentCount: Optional[attachmentCountType] - PermissionsBoundaryUsageCount: Optional[attachmentCountType] - IsAttachable: Optional[booleanType] - Description: Optional[policyDescriptionType] - CreateDate: Optional[dateType] - UpdateDate: Optional[dateType] - PolicyVersionList: Optional[policyDocumentVersionListType] + PolicyName: policyNameType | None + PolicyId: idType | None + Arn: arnType | None + Path: policyPathType | None + DefaultVersionId: policyVersionIdType | None + AttachmentCount: attachmentCountType | None + PermissionsBoundaryUsageCount: attachmentCountType | None + IsAttachable: booleanType | None + Description: policyDescriptionType | None + CreateDate: dateType | None + UpdateDate: dateType | None + PolicyVersionList: policyDocumentVersionListType | None -ManagedPolicyDetailListType = List[ManagedPolicyDetail] -attachedPoliciesListType = List[AttachedPolicy] +ManagedPolicyDetailListType = list[ManagedPolicyDetail] +attachedPoliciesListType = list[AttachedPolicy] class PolicyDetail(TypedDict, total=False): - PolicyName: Optional[policyNameType] - PolicyDocument: Optional[policyDocumentType] + PolicyName: policyNameType | None + PolicyDocument: policyDocumentType | None -policyDetailListType = List[PolicyDetail] -instanceProfileListType = List[InstanceProfile] +policyDetailListType = list[PolicyDetail] +instanceProfileListType = list[InstanceProfile] class RoleDetail(TypedDict, total=False): - Path: Optional[pathType] - RoleName: Optional[roleNameType] - RoleId: Optional[idType] - Arn: Optional[arnType] - CreateDate: Optional[dateType] - AssumeRolePolicyDocument: Optional[policyDocumentType] - InstanceProfileList: Optional[instanceProfileListType] - RolePolicyList: Optional[policyDetailListType] - AttachedManagedPolicies: Optional[attachedPoliciesListType] - PermissionsBoundary: Optional[AttachedPermissionsBoundary] - Tags: Optional[tagListType] - RoleLastUsed: Optional[RoleLastUsed] + Path: pathType | None + RoleName: roleNameType | None + RoleId: idType | None + Arn: arnType | None + CreateDate: dateType | None + AssumeRolePolicyDocument: policyDocumentType | None + InstanceProfileList: instanceProfileListType | None + RolePolicyList: policyDetailListType | None + AttachedManagedPolicies: attachedPoliciesListType | None + PermissionsBoundary: AttachedPermissionsBoundary | None + Tags: tagListType | None + RoleLastUsed: RoleLastUsed | None -roleDetailListType = List[RoleDetail] +roleDetailListType = list[RoleDetail] class GroupDetail(TypedDict, total=False): - Path: Optional[pathType] - GroupName: Optional[groupNameType] - GroupId: Optional[idType] - Arn: Optional[arnType] - CreateDate: Optional[dateType] - GroupPolicyList: Optional[policyDetailListType] - AttachedManagedPolicies: Optional[attachedPoliciesListType] + Path: pathType | None + GroupName: groupNameType | None + GroupId: idType | None + Arn: arnType | None + CreateDate: dateType | None + GroupPolicyList: policyDetailListType | None + AttachedManagedPolicies: attachedPoliciesListType | None -groupDetailListType = List[GroupDetail] -groupNameListType = List[groupNameType] +groupDetailListType = list[GroupDetail] +groupNameListType = list[groupNameType] class UserDetail(TypedDict, total=False): - Path: Optional[pathType] - UserName: Optional[userNameType] - UserId: Optional[idType] - Arn: Optional[arnType] - CreateDate: Optional[dateType] - UserPolicyList: Optional[policyDetailListType] - GroupList: Optional[groupNameListType] - AttachedManagedPolicies: Optional[attachedPoliciesListType] - PermissionsBoundary: Optional[AttachedPermissionsBoundary] - Tags: Optional[tagListType] + Path: pathType | None + UserName: userNameType | None + UserId: idType | None + Arn: arnType | None + CreateDate: dateType | None + UserPolicyList: policyDetailListType | None + GroupList: groupNameListType | None + AttachedManagedPolicies: attachedPoliciesListType | None + PermissionsBoundary: AttachedPermissionsBoundary | None + Tags: tagListType | None -userDetailListType = List[UserDetail] +userDetailListType = list[UserDetail] class GetAccountAuthorizationDetailsResponse(TypedDict, total=False): - UserDetailList: Optional[userDetailListType] - GroupDetailList: Optional[groupDetailListType] - RoleDetailList: Optional[roleDetailListType] - Policies: Optional[ManagedPolicyDetailListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + UserDetailList: userDetailListType | None + GroupDetailList: groupDetailListType | None + RoleDetailList: roleDetailListType | None + Policies: ManagedPolicyDetailListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class PasswordPolicy(TypedDict, total=False): - MinimumPasswordLength: Optional[minimumPasswordLengthType] - RequireSymbols: Optional[booleanType] - RequireNumbers: Optional[booleanType] - RequireUppercaseCharacters: Optional[booleanType] - RequireLowercaseCharacters: Optional[booleanType] - AllowUsersToChangePassword: Optional[booleanType] - ExpirePasswords: Optional[booleanType] - MaxPasswordAge: Optional[maxPasswordAgeType] - PasswordReusePrevention: Optional[passwordReusePreventionType] - HardExpiry: Optional[booleanObjectType] + MinimumPasswordLength: minimumPasswordLengthType | None + RequireSymbols: booleanType | None + RequireNumbers: booleanType | None + RequireUppercaseCharacters: booleanType | None + RequireLowercaseCharacters: booleanType | None + AllowUsersToChangePassword: booleanType | None + ExpirePasswords: booleanType | None + MaxPasswordAge: maxPasswordAgeType | None + PasswordReusePrevention: passwordReusePreventionType | None + HardExpiry: booleanObjectType | None class GetAccountPasswordPolicyResponse(TypedDict, total=False): PasswordPolicy: PasswordPolicy -summaryMapType = Dict[summaryKeyType, summaryValueType] +summaryMapType = dict[summaryKeyType, summaryValueType] class GetAccountSummaryResponse(TypedDict, total=False): - SummaryMap: Optional[summaryMapType] + SummaryMap: summaryMapType | None -SimulationPolicyListType = List[policyDocumentType] +SimulationPolicyListType = list[policyDocumentType] class GetContextKeysForCustomPolicyRequest(ServiceRequest): @@ -1247,21 +1391,32 @@ class GetContextKeysForCustomPolicyRequest(ServiceRequest): class GetContextKeysForPolicyResponse(TypedDict, total=False): - ContextKeyNames: Optional[ContextKeyNamesResultListType] + ContextKeyNames: ContextKeyNamesResultListType | None class GetContextKeysForPrincipalPolicyRequest(ServiceRequest): PolicySourceArn: arnType - PolicyInputList: Optional[SimulationPolicyListType] + PolicyInputList: SimulationPolicyListType | None ReportContentType = bytes class GetCredentialReportResponse(TypedDict, total=False): - Content: Optional[ReportContentType] - ReportFormat: Optional[ReportFormatType] - GeneratedTime: Optional[dateType] + Content: ReportContentType | None + ReportFormat: ReportFormatType | None + GeneratedTime: dateType | None + + +class GetDelegationRequestRequest(ServiceRequest): + DelegationRequestId: delegationRequestIdType + DelegationPermissionCheck: booleanType | None + + +class GetDelegationRequestResponse(TypedDict, total=False): + DelegationRequest: DelegationRequest | None + PermissionCheckStatus: permissionCheckStatusType | None + PermissionCheckResult: permissionCheckResultType | None class GetGroupPolicyRequest(ServiceRequest): @@ -1277,18 +1432,29 @@ class GetGroupPolicyResponse(TypedDict, total=False): class GetGroupRequest(ServiceRequest): GroupName: groupNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None -userListType = List[User] +userListType = list[User] class GetGroupResponse(TypedDict, total=False): Group: Group Users: userListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None + + +class GetHumanReadableSummaryRequest(ServiceRequest): + EntityArn: arnType + Locale: localeType | None + + +class GetHumanReadableSummaryResponse(TypedDict, total=False): + SummaryContent: summaryContentType | None + Locale: localeType | None + SummaryState: summaryStateType | None class GetInstanceProfileRequest(ServiceRequest): @@ -1300,7 +1466,7 @@ class GetInstanceProfileResponse(TypedDict, total=False): class GetLoginProfileRequest(ServiceRequest): - UserName: Optional[userNameType] + UserName: userNameType | None class GetLoginProfileResponse(TypedDict, total=False): @@ -1309,14 +1475,14 @@ class GetLoginProfileResponse(TypedDict, total=False): class GetMFADeviceRequest(ServiceRequest): SerialNumber: serialNumberType - UserName: Optional[userNameType] + UserName: userNameType | None class GetMFADeviceResponse(TypedDict, total=False): - UserName: Optional[userNameType] + UserName: userNameType | None SerialNumber: serialNumberType - EnableDate: Optional[dateType] - Certifications: Optional[CertificationMapType] + EnableDate: dateType | None + Certifications: CertificationMapType | None class GetOpenIDConnectProviderRequest(ServiceRequest): @@ -1324,30 +1490,35 @@ class GetOpenIDConnectProviderRequest(ServiceRequest): class GetOpenIDConnectProviderResponse(TypedDict, total=False): - Url: Optional[OpenIDConnectProviderUrlType] - ClientIDList: Optional[clientIDListType] - ThumbprintList: Optional[thumbprintListType] - CreateDate: Optional[dateType] - Tags: Optional[tagListType] + Url: OpenIDConnectProviderUrlType | None + ClientIDList: clientIDListType | None + ThumbprintList: thumbprintListType | None + CreateDate: dateType | None + Tags: tagListType | None class GetOrganizationsAccessReportRequest(ServiceRequest): JobId: jobIDType - MaxItems: Optional[maxItemsType] - Marker: Optional[markerType] - SortKey: Optional[sortKeyType] + MaxItems: maxItemsType | None + Marker: markerType | None + SortKey: sortKeyType | None class GetOrganizationsAccessReportResponse(TypedDict, total=False): JobStatus: jobStatusType JobCreationDate: dateType - JobCompletionDate: Optional[dateType] - NumberOfServicesAccessible: Optional[integerType] - NumberOfServicesNotAccessed: Optional[integerType] - AccessDetails: Optional[AccessDetails] - IsTruncated: Optional[booleanType] - Marker: Optional[markerType] - ErrorDetails: Optional[ErrorDetails] + JobCompletionDate: dateType | None + NumberOfServicesAccessible: integerType | None + NumberOfServicesNotAccessed: integerType | None + AccessDetails: AccessDetails | None + IsTruncated: booleanType | None + Marker: markerType | None + ErrorDetails: ErrorDetails | None + + +class GetOutboundWebIdentityFederationInfoResponse(TypedDict, total=False): + IssuerIdentifier: stringType | None + JwtVendingEnabled: booleanType | None class GetPolicyRequest(ServiceRequest): @@ -1355,7 +1526,7 @@ class GetPolicyRequest(ServiceRequest): class GetPolicyResponse(TypedDict, total=False): - Policy: Optional[Policy] + Policy: Policy | None class GetPolicyVersionRequest(ServiceRequest): @@ -1364,7 +1535,7 @@ class GetPolicyVersionRequest(ServiceRequest): class GetPolicyVersionResponse(TypedDict, total=False): - PolicyVersion: Optional[PolicyVersion] + PolicyVersion: PolicyVersion | None class GetRolePolicyRequest(ServiceRequest): @@ -1391,21 +1562,21 @@ class GetSAMLProviderRequest(ServiceRequest): class SAMLPrivateKey(TypedDict, total=False): - KeyId: Optional[privateKeyIdType] - Timestamp: Optional[dateType] + KeyId: privateKeyIdType | None + Timestamp: dateType | None -privateKeyList = List[SAMLPrivateKey] +privateKeyList = list[SAMLPrivateKey] class GetSAMLProviderResponse(TypedDict, total=False): - SAMLProviderUUID: Optional[privateKeyIdType] - SAMLMetadataDocument: Optional[SAMLMetadataDocumentType] - CreateDate: Optional[dateType] - ValidUntil: Optional[dateType] - Tags: Optional[tagListType] - AssertionEncryptionMode: Optional[assertionEncryptionModeType] - PrivateKeyList: Optional[privateKeyList] + SAMLProviderUUID: privateKeyIdType | None + SAMLMetadataDocument: SAMLMetadataDocumentType | None + CreateDate: dateType | None + ValidUntil: dateType | None + Tags: tagListType | None + AssertionEncryptionMode: assertionEncryptionModeType | None + PrivateKeyList: privateKeyList | None class GetSSHPublicKeyRequest(ServiceRequest): @@ -1420,11 +1591,11 @@ class SSHPublicKey(TypedDict, total=False): Fingerprint: publicKeyFingerprintType SSHPublicKeyBody: publicKeyMaterialType Status: statusType - UploadDate: Optional[dateType] + UploadDate: dateType | None class GetSSHPublicKeyResponse(TypedDict, total=False): - SSHPublicKey: Optional[SSHPublicKey] + SSHPublicKey: SSHPublicKey | None class GetServerCertificateRequest(ServiceRequest): @@ -1436,15 +1607,15 @@ class ServerCertificateMetadata(TypedDict, total=False): ServerCertificateName: serverCertificateNameType ServerCertificateId: idType Arn: arnType - UploadDate: Optional[dateType] - Expiration: Optional[dateType] + UploadDate: dateType | None + Expiration: dateType | None class ServerCertificate(TypedDict, total=False): ServerCertificateMetadata: ServerCertificateMetadata CertificateBody: certificateBodyType - CertificateChain: Optional[certificateChainType] - Tags: Optional[tagListType] + CertificateChain: certificateChainType | None + Tags: tagListType | None class GetServerCertificateResponse(TypedDict, total=False): @@ -1453,52 +1624,52 @@ class GetServerCertificateResponse(TypedDict, total=False): class GetServiceLastAccessedDetailsRequest(ServiceRequest): JobId: jobIDType - MaxItems: Optional[maxItemsType] - Marker: Optional[markerType] + MaxItems: maxItemsType | None + Marker: markerType | None class TrackedActionLastAccessed(TypedDict, total=False): - ActionName: Optional[stringType] - LastAccessedEntity: Optional[arnType] - LastAccessedTime: Optional[dateType] - LastAccessedRegion: Optional[stringType] + ActionName: stringType | None + LastAccessedEntity: arnType | None + LastAccessedTime: dateType | None + LastAccessedRegion: stringType | None -TrackedActionsLastAccessed = List[TrackedActionLastAccessed] +TrackedActionsLastAccessed = list[TrackedActionLastAccessed] class ServiceLastAccessed(TypedDict, total=False): ServiceName: serviceNameType - LastAuthenticated: Optional[dateType] + LastAuthenticated: dateType | None ServiceNamespace: serviceNamespaceType - LastAuthenticatedEntity: Optional[arnType] - LastAuthenticatedRegion: Optional[stringType] - TotalAuthenticatedEntities: Optional[integerType] - TrackedActionsLastAccessed: Optional[TrackedActionsLastAccessed] + LastAuthenticatedEntity: arnType | None + LastAuthenticatedRegion: stringType | None + TotalAuthenticatedEntities: integerType | None + TrackedActionsLastAccessed: TrackedActionsLastAccessed | None -ServicesLastAccessed = List[ServiceLastAccessed] +ServicesLastAccessed = list[ServiceLastAccessed] class GetServiceLastAccessedDetailsResponse(TypedDict, total=False): JobStatus: jobStatusType - JobType: Optional[AccessAdvisorUsageGranularityType] + JobType: AccessAdvisorUsageGranularityType | None JobCreationDate: dateType ServicesLastAccessed: ServicesLastAccessed JobCompletionDate: dateType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] - Error: Optional[ErrorDetails] + IsTruncated: booleanType | None + Marker: responseMarkerType | None + Error: ErrorDetails | None class GetServiceLastAccessedDetailsWithEntitiesRequest(ServiceRequest): JobId: jobIDType ServiceNamespace: serviceNamespaceType - MaxItems: Optional[maxItemsType] - Marker: Optional[markerType] + MaxItems: maxItemsType | None + Marker: markerType | None -entityDetailsListType = List[EntityDetails] +entityDetailsListType = list[EntityDetails] class GetServiceLastAccessedDetailsWithEntitiesResponse(TypedDict, total=False): @@ -1506,9 +1677,9 @@ class GetServiceLastAccessedDetailsWithEntitiesResponse(TypedDict, total=False): JobCreationDate: dateType JobCompletionDate: dateType EntityDetailsList: entityDetailsListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] - Error: Optional[ErrorDetails] + IsTruncated: booleanType | None + Marker: responseMarkerType | None + Error: ErrorDetails | None class GetServiceLinkedRoleDeletionStatusRequest(ServiceRequest): @@ -1517,7 +1688,7 @@ class GetServiceLinkedRoleDeletionStatusRequest(ServiceRequest): class GetServiceLinkedRoleDeletionStatusResponse(TypedDict, total=False): Status: DeletionTaskStatusType - Reason: Optional[DeletionTaskFailureReasonType] + Reason: DeletionTaskFailureReasonType | None class GetUserPolicyRequest(ServiceRequest): @@ -1532,7 +1703,7 @@ class GetUserPolicyResponse(TypedDict, total=False): class GetUserRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None class GetUserResponse(TypedDict, total=False): @@ -1540,208 +1711,223 @@ class GetUserResponse(TypedDict, total=False): class ListAccessKeysRequest(ServiceRequest): - UserName: Optional[existingUserNameType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + UserName: existingUserNameType | None + Marker: markerType | None + MaxItems: maxItemsType | None -accessKeyMetadataListType = List[AccessKeyMetadata] +accessKeyMetadataListType = list[AccessKeyMetadata] class ListAccessKeysResponse(TypedDict, total=False): AccessKeyMetadata: accessKeyMetadataListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListAccountAliasesRequest(ServiceRequest): - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None -accountAliasListType = List[accountAliasType] +accountAliasListType = list[accountAliasType] class ListAccountAliasesResponse(TypedDict, total=False): AccountAliases: accountAliasListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListAttachedGroupPoliciesRequest(ServiceRequest): GroupName: groupNameType - PathPrefix: Optional[policyPathType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: policyPathType | None + Marker: markerType | None + MaxItems: maxItemsType | None class ListAttachedGroupPoliciesResponse(TypedDict, total=False): - AttachedPolicies: Optional[attachedPoliciesListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + AttachedPolicies: attachedPoliciesListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListAttachedRolePoliciesRequest(ServiceRequest): RoleName: roleNameType - PathPrefix: Optional[policyPathType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: policyPathType | None + Marker: markerType | None + MaxItems: maxItemsType | None class ListAttachedRolePoliciesResponse(TypedDict, total=False): - AttachedPolicies: Optional[attachedPoliciesListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + AttachedPolicies: attachedPoliciesListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListAttachedUserPoliciesRequest(ServiceRequest): UserName: userNameType - PathPrefix: Optional[policyPathType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: policyPathType | None + Marker: markerType | None + MaxItems: maxItemsType | None class ListAttachedUserPoliciesResponse(TypedDict, total=False): - AttachedPolicies: Optional[attachedPoliciesListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + AttachedPolicies: attachedPoliciesListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None + + +class ListDelegationRequestsRequest(ServiceRequest): + OwnerId: ownerIdType | None + Marker: markerType | None + MaxItems: maxItemsType | None + + +delegationRequestsListType = list[DelegationRequest] + + +class ListDelegationRequestsResponse(TypedDict, total=False): + DelegationRequests: delegationRequestsListType | None + Marker: markerType | None + isTruncated: booleanType | None class ListEntitiesForPolicyRequest(ServiceRequest): PolicyArn: arnType - EntityFilter: Optional[EntityType] - PathPrefix: Optional[pathType] - PolicyUsageFilter: Optional[PolicyUsageType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + EntityFilter: EntityType | None + PathPrefix: pathType | None + PolicyUsageFilter: PolicyUsageType | None + Marker: markerType | None + MaxItems: maxItemsType | None class PolicyRole(TypedDict, total=False): - RoleName: Optional[roleNameType] - RoleId: Optional[idType] + RoleName: roleNameType | None + RoleId: idType | None -PolicyRoleListType = List[PolicyRole] +PolicyRoleListType = list[PolicyRole] class PolicyUser(TypedDict, total=False): - UserName: Optional[userNameType] - UserId: Optional[idType] + UserName: userNameType | None + UserId: idType | None -PolicyUserListType = List[PolicyUser] +PolicyUserListType = list[PolicyUser] class PolicyGroup(TypedDict, total=False): - GroupName: Optional[groupNameType] - GroupId: Optional[idType] + GroupName: groupNameType | None + GroupId: idType | None -PolicyGroupListType = List[PolicyGroup] +PolicyGroupListType = list[PolicyGroup] class ListEntitiesForPolicyResponse(TypedDict, total=False): - PolicyGroups: Optional[PolicyGroupListType] - PolicyUsers: Optional[PolicyUserListType] - PolicyRoles: Optional[PolicyRoleListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + PolicyGroups: PolicyGroupListType | None + PolicyUsers: PolicyUserListType | None + PolicyRoles: PolicyRoleListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListGroupPoliciesRequest(ServiceRequest): GroupName: groupNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None -policyNameListType = List[policyNameType] +policyNameListType = list[policyNameType] class ListGroupPoliciesResponse(TypedDict, total=False): PolicyNames: policyNameListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListGroupsForUserRequest(ServiceRequest): UserName: existingUserNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None -groupListType = List[Group] +groupListType = list[Group] class ListGroupsForUserResponse(TypedDict, total=False): Groups: groupListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListGroupsRequest(ServiceRequest): - PathPrefix: Optional[pathPrefixType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: pathPrefixType | None + Marker: markerType | None + MaxItems: maxItemsType | None class ListGroupsResponse(TypedDict, total=False): Groups: groupListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListInstanceProfileTagsRequest(ServiceRequest): InstanceProfileName: instanceProfileNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListInstanceProfileTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListInstanceProfilesForRoleRequest(ServiceRequest): RoleName: roleNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListInstanceProfilesForRoleResponse(TypedDict, total=False): InstanceProfiles: instanceProfileListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListInstanceProfilesRequest(ServiceRequest): - PathPrefix: Optional[pathPrefixType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: pathPrefixType | None + Marker: markerType | None + MaxItems: maxItemsType | None class ListInstanceProfilesResponse(TypedDict, total=False): InstanceProfiles: instanceProfileListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListMFADeviceTagsRequest(ServiceRequest): SerialNumber: serialNumberType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListMFADeviceTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListMFADevicesRequest(ServiceRequest): - UserName: Optional[existingUserNameType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + UserName: existingUserNameType | None + Marker: markerType | None + MaxItems: maxItemsType | None class MFADevice(TypedDict, total=False): @@ -1750,25 +1936,25 @@ class MFADevice(TypedDict, total=False): EnableDate: dateType -mfaDeviceListType = List[MFADevice] +mfaDeviceListType = list[MFADevice] class ListMFADevicesResponse(TypedDict, total=False): MFADevices: mfaDeviceListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListOpenIDConnectProviderTagsRequest(ServiceRequest): OpenIDConnectProviderArn: arnType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListOpenIDConnectProviderTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListOpenIDConnectProvidersRequest(ServiceRequest): @@ -1776,14 +1962,14 @@ class ListOpenIDConnectProvidersRequest(ServiceRequest): class OpenIDConnectProviderListEntry(TypedDict, total=False): - Arn: Optional[arnType] + Arn: arnType | None -OpenIDConnectProviderListType = List[OpenIDConnectProviderListEntry] +OpenIDConnectProviderListType = list[OpenIDConnectProviderListEntry] class ListOpenIDConnectProvidersResponse(TypedDict, total=False): - OpenIDConnectProviderList: Optional[OpenIDConnectProviderListType] + OpenIDConnectProviderList: OpenIDConnectProviderListType | None class ListOrganizationsFeaturesRequest(ServiceRequest): @@ -1791,132 +1977,132 @@ class ListOrganizationsFeaturesRequest(ServiceRequest): class ListOrganizationsFeaturesResponse(TypedDict, total=False): - OrganizationId: Optional[OrganizationIdType] - EnabledFeatures: Optional[FeaturesListType] + OrganizationId: OrganizationIdType | None + EnabledFeatures: FeaturesListType | None class PolicyGrantingServiceAccess(TypedDict, total=False): PolicyName: policyNameType PolicyType: policyType - PolicyArn: Optional[arnType] - EntityType: Optional[policyOwnerEntityType] - EntityName: Optional[entityNameType] + PolicyArn: arnType | None + EntityType: policyOwnerEntityType | None + EntityName: entityNameType | None -policyGrantingServiceAccessListType = List[PolicyGrantingServiceAccess] +policyGrantingServiceAccessListType = list[PolicyGrantingServiceAccess] class ListPoliciesGrantingServiceAccessEntry(TypedDict, total=False): - ServiceNamespace: Optional[serviceNamespaceType] - Policies: Optional[policyGrantingServiceAccessListType] + ServiceNamespace: serviceNamespaceType | None + Policies: policyGrantingServiceAccessListType | None -serviceNamespaceListType = List[serviceNamespaceType] +serviceNamespaceListType = list[serviceNamespaceType] class ListPoliciesGrantingServiceAccessRequest(ServiceRequest): - Marker: Optional[markerType] + Marker: markerType | None Arn: arnType ServiceNamespaces: serviceNamespaceListType -listPolicyGrantingServiceAccessResponseListType = List[ListPoliciesGrantingServiceAccessEntry] +listPolicyGrantingServiceAccessResponseListType = list[ListPoliciesGrantingServiceAccessEntry] class ListPoliciesGrantingServiceAccessResponse(TypedDict, total=False): PoliciesGrantingServiceAccess: listPolicyGrantingServiceAccessResponseListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListPoliciesRequest(ServiceRequest): - Scope: Optional[policyScopeType] - OnlyAttached: Optional[booleanType] - PathPrefix: Optional[policyPathType] - PolicyUsageFilter: Optional[PolicyUsageType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Scope: policyScopeType | None + OnlyAttached: booleanType | None + PathPrefix: policyPathType | None + PolicyUsageFilter: PolicyUsageType | None + Marker: markerType | None + MaxItems: maxItemsType | None -policyListType = List[Policy] +policyListType = list[Policy] class ListPoliciesResponse(TypedDict, total=False): - Policies: Optional[policyListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + Policies: policyListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListPolicyTagsRequest(ServiceRequest): PolicyArn: arnType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListPolicyTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListPolicyVersionsRequest(ServiceRequest): PolicyArn: arnType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListPolicyVersionsResponse(TypedDict, total=False): - Versions: Optional[policyDocumentVersionListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + Versions: policyDocumentVersionListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListRolePoliciesRequest(ServiceRequest): RoleName: roleNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListRolePoliciesResponse(TypedDict, total=False): PolicyNames: policyNameListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListRoleTagsRequest(ServiceRequest): RoleName: roleNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListRoleTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListRolesRequest(ServiceRequest): - PathPrefix: Optional[pathPrefixType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: pathPrefixType | None + Marker: markerType | None + MaxItems: maxItemsType | None class ListRolesResponse(TypedDict, total=False): Roles: roleListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListSAMLProviderTagsRequest(ServiceRequest): SAMLProviderArn: arnType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListSAMLProviderTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListSAMLProvidersRequest(ServiceRequest): @@ -1924,22 +2110,22 @@ class ListSAMLProvidersRequest(ServiceRequest): class SAMLProviderListEntry(TypedDict, total=False): - Arn: Optional[arnType] - ValidUntil: Optional[dateType] - CreateDate: Optional[dateType] + Arn: arnType | None + ValidUntil: dateType | None + CreateDate: dateType | None -SAMLProviderListType = List[SAMLProviderListEntry] +SAMLProviderListType = list[SAMLProviderListEntry] class ListSAMLProvidersResponse(TypedDict, total=False): - SAMLProviderList: Optional[SAMLProviderListType] + SAMLProviderList: SAMLProviderListType | None class ListSSHPublicKeysRequest(ServiceRequest): - UserName: Optional[userNameType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + UserName: userNameType | None + Marker: markerType | None + MaxItems: maxItemsType | None class SSHPublicKeyMetadata(TypedDict, total=False): @@ -1949,74 +2135,74 @@ class SSHPublicKeyMetadata(TypedDict, total=False): UploadDate: dateType -SSHPublicKeyListType = List[SSHPublicKeyMetadata] +SSHPublicKeyListType = list[SSHPublicKeyMetadata] class ListSSHPublicKeysResponse(TypedDict, total=False): - SSHPublicKeys: Optional[SSHPublicKeyListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + SSHPublicKeys: SSHPublicKeyListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListServerCertificateTagsRequest(ServiceRequest): ServerCertificateName: serverCertificateNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListServerCertificateTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListServerCertificatesRequest(ServiceRequest): - PathPrefix: Optional[pathPrefixType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: pathPrefixType | None + Marker: markerType | None + MaxItems: maxItemsType | None -serverCertificateMetadataListType = List[ServerCertificateMetadata] +serverCertificateMetadataListType = list[ServerCertificateMetadata] class ListServerCertificatesResponse(TypedDict, total=False): ServerCertificateMetadataList: serverCertificateMetadataListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListServiceSpecificCredentialsRequest(ServiceRequest): - UserName: Optional[userNameType] - ServiceName: Optional[serviceName] - AllUsers: Optional[allUsers] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + UserName: userNameType | None + ServiceName: serviceName | None + AllUsers: allUsers | None + Marker: markerType | None + MaxItems: maxItemsType | None class ServiceSpecificCredentialMetadata(TypedDict, total=False): UserName: userNameType Status: statusType - ServiceUserName: Optional[serviceUserName] - ServiceCredentialAlias: Optional[serviceCredentialAlias] + ServiceUserName: serviceUserName | None + ServiceCredentialAlias: serviceCredentialAlias | None CreateDate: dateType - ExpirationDate: Optional[dateType] + ExpirationDate: dateType | None ServiceSpecificCredentialId: serviceSpecificCredentialId ServiceName: serviceName -ServiceSpecificCredentialsListType = List[ServiceSpecificCredentialMetadata] +ServiceSpecificCredentialsListType = list[ServiceSpecificCredentialMetadata] class ListServiceSpecificCredentialsResponse(TypedDict, total=False): - ServiceSpecificCredentials: Optional[ServiceSpecificCredentialsListType] - Marker: Optional[responseMarkerType] - IsTruncated: Optional[booleanType] + ServiceSpecificCredentials: ServiceSpecificCredentialsListType | None + Marker: responseMarkerType | None + IsTruncated: booleanType | None class ListSigningCertificatesRequest(ServiceRequest): - UserName: Optional[existingUserNameType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + UserName: existingUserNameType | None + Marker: markerType | None + MaxItems: maxItemsType | None class SigningCertificate(TypedDict, total=False): @@ -2024,67 +2210,67 @@ class SigningCertificate(TypedDict, total=False): CertificateId: certificateIdType CertificateBody: certificateBodyType Status: statusType - UploadDate: Optional[dateType] + UploadDate: dateType | None -certificateListType = List[SigningCertificate] +certificateListType = list[SigningCertificate] class ListSigningCertificatesResponse(TypedDict, total=False): Certificates: certificateListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListUserPoliciesRequest(ServiceRequest): UserName: existingUserNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListUserPoliciesResponse(TypedDict, total=False): PolicyNames: policyNameListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListUserTagsRequest(ServiceRequest): UserName: existingUserNameType - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + Marker: markerType | None + MaxItems: maxItemsType | None class ListUserTagsResponse(TypedDict, total=False): Tags: tagListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListUsersRequest(ServiceRequest): - PathPrefix: Optional[pathPrefixType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + PathPrefix: pathPrefixType | None + Marker: markerType | None + MaxItems: maxItemsType | None class ListUsersResponse(TypedDict, total=False): Users: userListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class ListVirtualMFADevicesRequest(ServiceRequest): - AssignmentStatus: Optional[assignmentStatusType] - Marker: Optional[markerType] - MaxItems: Optional[maxItemsType] + AssignmentStatus: assignmentStatusType | None + Marker: markerType | None + MaxItems: maxItemsType | None -virtualMFADeviceListType = List[VirtualMFADevice] +virtualMFADeviceListType = list[VirtualMFADevice] class ListVirtualMFADevicesResponse(TypedDict, total=False): VirtualMFADevices: virtualMFADeviceListType - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + IsTruncated: booleanType | None + Marker: responseMarkerType | None class PutGroupPolicyRequest(ServiceRequest): @@ -2115,6 +2301,11 @@ class PutUserPolicyRequest(ServiceRequest): PolicyDocument: policyDocumentType +class RejectDelegationRequestRequest(ServiceRequest): + DelegationRequestId: delegationRequestIdType + Notes: notesType | None + + class RemoveClientIDFromOpenIDConnectProviderRequest(ServiceRequest): OpenIDConnectProviderArn: arnType ClientID: clientIDType @@ -2131,15 +2322,15 @@ class RemoveUserFromGroupRequest(ServiceRequest): class ResetServiceSpecificCredentialRequest(ServiceRequest): - UserName: Optional[userNameType] + UserName: userNameType | None ServiceSpecificCredentialId: serviceSpecificCredentialId class ResetServiceSpecificCredentialResponse(TypedDict, total=False): - ServiceSpecificCredential: Optional[ServiceSpecificCredential] + ServiceSpecificCredential: ServiceSpecificCredential | None -ResourceNameListType = List[ResourceNameType] +ResourceNameListType = list[ResourceNameType] class ResyncMFADeviceRequest(ServiceRequest): @@ -2149,6 +2340,10 @@ class ResyncMFADeviceRequest(ServiceRequest): AuthenticationCode2: authenticationCodeType +class SendDelegationTokenRequest(ServiceRequest): + DelegationRequestId: delegationRequestIdType + + class SetDefaultPolicyVersionRequest(ServiceRequest): PolicyArn: arnType VersionId: policyVersionIdType @@ -2160,37 +2355,37 @@ class SetSecurityTokenServicePreferencesRequest(ServiceRequest): class SimulateCustomPolicyRequest(ServiceRequest): PolicyInputList: SimulationPolicyListType - PermissionsBoundaryPolicyInputList: Optional[SimulationPolicyListType] + PermissionsBoundaryPolicyInputList: SimulationPolicyListType | None ActionNames: ActionNameListType - ResourceArns: Optional[ResourceNameListType] - ResourcePolicy: Optional[policyDocumentType] - ResourceOwner: Optional[ResourceNameType] - CallerArn: Optional[ResourceNameType] - ContextEntries: Optional[ContextEntryListType] - ResourceHandlingOption: Optional[ResourceHandlingOptionType] - MaxItems: Optional[maxItemsType] - Marker: Optional[markerType] + ResourceArns: ResourceNameListType | None + ResourcePolicy: policyDocumentType | None + ResourceOwner: ResourceNameType | None + CallerArn: ResourceNameType | None + ContextEntries: ContextEntryListType | None + ResourceHandlingOption: ResourceHandlingOptionType | None + MaxItems: maxItemsType | None + Marker: markerType | None class SimulatePolicyResponse(TypedDict, total=False): - EvaluationResults: Optional[EvaluationResultsListType] - IsTruncated: Optional[booleanType] - Marker: Optional[responseMarkerType] + EvaluationResults: EvaluationResultsListType | None + IsTruncated: booleanType | None + Marker: responseMarkerType | None class SimulatePrincipalPolicyRequest(ServiceRequest): PolicySourceArn: arnType - PolicyInputList: Optional[SimulationPolicyListType] - PermissionsBoundaryPolicyInputList: Optional[SimulationPolicyListType] + PolicyInputList: SimulationPolicyListType | None + PermissionsBoundaryPolicyInputList: SimulationPolicyListType | None ActionNames: ActionNameListType - ResourceArns: Optional[ResourceNameListType] - ResourcePolicy: Optional[policyDocumentType] - ResourceOwner: Optional[ResourceNameType] - CallerArn: Optional[ResourceNameType] - ContextEntries: Optional[ContextEntryListType] - ResourceHandlingOption: Optional[ResourceHandlingOptionType] - MaxItems: Optional[maxItemsType] - Marker: Optional[markerType] + ResourceArns: ResourceNameListType | None + ResourcePolicy: policyDocumentType | None + ResourceOwner: ResourceNameType | None + CallerArn: ResourceNameType | None + ContextEntries: ContextEntryListType | None + ResourceHandlingOption: ResourceHandlingOptionType | None + MaxItems: maxItemsType | None + Marker: markerType | None class TagInstanceProfileRequest(ServiceRequest): @@ -2233,7 +2428,7 @@ class TagUserRequest(ServiceRequest): Tags: tagListType -tagKeyListType = List[tagKeyType] +tagKeyListType = list[tagKeyType] class UntagInstanceProfileRequest(ServiceRequest): @@ -2277,21 +2472,21 @@ class UntagUserRequest(ServiceRequest): class UpdateAccessKeyRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None AccessKeyId: accessKeyIdType Status: statusType class UpdateAccountPasswordPolicyRequest(ServiceRequest): - MinimumPasswordLength: Optional[minimumPasswordLengthType] - RequireSymbols: Optional[booleanType] - RequireNumbers: Optional[booleanType] - RequireUppercaseCharacters: Optional[booleanType] - RequireLowercaseCharacters: Optional[booleanType] - AllowUsersToChangePassword: Optional[booleanType] - MaxPasswordAge: Optional[maxPasswordAgeType] - PasswordReusePrevention: Optional[passwordReusePreventionType] - HardExpiry: Optional[booleanObjectType] + MinimumPasswordLength: minimumPasswordLengthType | None + RequireSymbols: booleanType | None + RequireNumbers: booleanType | None + RequireUppercaseCharacters: booleanType | None + RequireLowercaseCharacters: booleanType | None + AllowUsersToChangePassword: booleanType | None + MaxPasswordAge: maxPasswordAgeType | None + PasswordReusePrevention: passwordReusePreventionType | None + HardExpiry: booleanObjectType | None class UpdateAssumeRolePolicyRequest(ServiceRequest): @@ -2299,16 +2494,21 @@ class UpdateAssumeRolePolicyRequest(ServiceRequest): PolicyDocument: policyDocumentType +class UpdateDelegationRequestRequest(ServiceRequest): + DelegationRequestId: delegationRequestIdType + Notes: notesType | None + + class UpdateGroupRequest(ServiceRequest): GroupName: groupNameType - NewPath: Optional[pathType] - NewGroupName: Optional[groupNameType] + NewPath: pathType | None + NewGroupName: groupNameType | None class UpdateLoginProfileRequest(ServiceRequest): UserName: userNameType - Password: Optional[passwordType] - PasswordResetRequired: Optional[booleanObjectType] + Password: passwordType | None + PasswordResetRequired: booleanObjectType | None class UpdateOpenIDConnectProviderThumbprintRequest(ServiceRequest): @@ -2322,13 +2522,13 @@ class UpdateRoleDescriptionRequest(ServiceRequest): class UpdateRoleDescriptionResponse(TypedDict, total=False): - Role: Optional[Role] + Role: Role | None class UpdateRoleRequest(ServiceRequest): RoleName: roleNameType - Description: Optional[roleDescriptionType] - MaxSessionDuration: Optional[roleMaxSessionDurationType] + Description: roleDescriptionType | None + MaxSessionDuration: roleMaxSessionDurationType | None class UpdateRoleResponse(TypedDict, total=False): @@ -2336,15 +2536,15 @@ class UpdateRoleResponse(TypedDict, total=False): class UpdateSAMLProviderRequest(ServiceRequest): - SAMLMetadataDocument: Optional[SAMLMetadataDocumentType] + SAMLMetadataDocument: SAMLMetadataDocumentType | None SAMLProviderArn: arnType - AssertionEncryptionMode: Optional[assertionEncryptionModeType] - AddPrivateKey: Optional[privateKeyType] - RemovePrivateKey: Optional[privateKeyIdType] + AssertionEncryptionMode: assertionEncryptionModeType | None + AddPrivateKey: privateKeyType | None + RemovePrivateKey: privateKeyIdType | None class UpdateSAMLProviderResponse(TypedDict, total=False): - SAMLProviderArn: Optional[arnType] + SAMLProviderArn: arnType | None class UpdateSSHPublicKeyRequest(ServiceRequest): @@ -2355,26 +2555,26 @@ class UpdateSSHPublicKeyRequest(ServiceRequest): class UpdateServerCertificateRequest(ServiceRequest): ServerCertificateName: serverCertificateNameType - NewPath: Optional[pathType] - NewServerCertificateName: Optional[serverCertificateNameType] + NewPath: pathType | None + NewServerCertificateName: serverCertificateNameType | None class UpdateServiceSpecificCredentialRequest(ServiceRequest): - UserName: Optional[userNameType] + UserName: userNameType | None ServiceSpecificCredentialId: serviceSpecificCredentialId Status: statusType class UpdateSigningCertificateRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None CertificateId: certificateIdType Status: statusType class UpdateUserRequest(ServiceRequest): UserName: existingUserNameType - NewPath: Optional[pathType] - NewUserName: Optional[userNameType] + NewPath: pathType | None + NewUserName: userNameType | None class UploadSSHPublicKeyRequest(ServiceRequest): @@ -2383,25 +2583,25 @@ class UploadSSHPublicKeyRequest(ServiceRequest): class UploadSSHPublicKeyResponse(TypedDict, total=False): - SSHPublicKey: Optional[SSHPublicKey] + SSHPublicKey: SSHPublicKey | None class UploadServerCertificateRequest(ServiceRequest): - Path: Optional[pathType] + Path: pathType | None ServerCertificateName: serverCertificateNameType CertificateBody: certificateBodyType PrivateKey: privateKeyType - CertificateChain: Optional[certificateChainType] - Tags: Optional[tagListType] + CertificateChain: certificateChainType | None + Tags: tagListType | None class UploadServerCertificateResponse(TypedDict, total=False): - ServerCertificateMetadata: Optional[ServerCertificateMetadata] - Tags: Optional[tagListType] + ServerCertificateMetadata: ServerCertificateMetadata | None + Tags: tagListType | None class UploadSigningCertificateRequest(ServiceRequest): - UserName: Optional[existingUserNameType] + UserName: existingUserNameType | None CertificateBody: certificateBodyType @@ -2410,8 +2610,14 @@ class UploadSigningCertificateResponse(TypedDict, total=False): class IamApi: - service = "iam" - version = "2010-05-08" + service: str = "iam" + version: str = "2010-05-08" + + @handler("AcceptDelegationRequest") + def accept_delegation_request( + self, context: RequestContext, delegation_request_id: delegationRequestIdType, **kwargs + ) -> None: + raise NotImplementedError @handler("AddClientIDToOpenIDConnectProvider") def add_client_id_to_open_id_connect_provider( @@ -2443,6 +2649,12 @@ def add_user_to_group( ) -> None: raise NotImplementedError + @handler("AssociateDelegationRequest") + def associate_delegation_request( + self, context: RequestContext, delegation_request_id: delegationRequestIdType, **kwargs + ) -> None: + raise NotImplementedError + @handler("AttachGroupPolicy") def attach_group_policy( self, context: RequestContext, group_name: groupNameType, policy_arn: arnType, **kwargs @@ -2483,6 +2695,23 @@ def create_account_alias( ) -> None: raise NotImplementedError + @handler("CreateDelegationRequest") + def create_delegation_request( + self, + context: RequestContext, + description: delegationRequestDescriptionType, + permissions: DelegationPermission, + requestor_workflow_id: requestorWorkflowIdType, + notification_channel: notificationChannelType, + session_duration: sessionDurationType, + owner_account_id: accountIdType | None = None, + request_message: requestMessageType | None = None, + redirect_url: redirectUrlType | None = None, + only_send_by_owner: booleanType | None = None, + **kwargs, + ) -> CreateDelegationRequestResponse: + raise NotImplementedError + @handler("CreateGroup") def create_group( self, @@ -2826,6 +3055,10 @@ def disable_organizations_root_sessions( ) -> DisableOrganizationsRootSessionsResponse: raise NotImplementedError + @handler("DisableOutboundWebIdentityFederation") + def disable_outbound_web_identity_federation(self, context: RequestContext, **kwargs) -> None: + raise NotImplementedError + @handler("EnableMFADevice") def enable_mfa_device( self, @@ -2850,6 +3083,12 @@ def enable_organizations_root_sessions( ) -> EnableOrganizationsRootSessionsResponse: raise NotImplementedError + @handler("EnableOutboundWebIdentityFederation") + def enable_outbound_web_identity_federation( + self, context: RequestContext, **kwargs + ) -> EnableOutboundWebIdentityFederationResponse: + raise NotImplementedError + @handler("GenerateCredentialReport") def generate_credential_report( self, context: RequestContext, **kwargs @@ -2925,6 +3164,16 @@ def get_credential_report( ) -> GetCredentialReportResponse: raise NotImplementedError + @handler("GetDelegationRequest") + def get_delegation_request( + self, + context: RequestContext, + delegation_request_id: delegationRequestIdType, + delegation_permission_check: booleanType | None = None, + **kwargs, + ) -> GetDelegationRequestResponse: + raise NotImplementedError + @handler("GetGroup") def get_group( self, @@ -2946,6 +3195,16 @@ def get_group_policy( ) -> GetGroupPolicyResponse: raise NotImplementedError + @handler("GetHumanReadableSummary") + def get_human_readable_summary( + self, + context: RequestContext, + entity_arn: arnType, + locale: localeType | None = None, + **kwargs, + ) -> GetHumanReadableSummaryResponse: + raise NotImplementedError + @handler("GetInstanceProfile") def get_instance_profile( self, context: RequestContext, instance_profile_name: instanceProfileNameType, **kwargs @@ -2986,6 +3245,12 @@ def get_organizations_access_report( ) -> GetOrganizationsAccessReportResponse: raise NotImplementedError + @handler("GetOutboundWebIdentityFederationInfo") + def get_outbound_web_identity_federation_info( + self, context: RequestContext, **kwargs + ) -> GetOutboundWebIdentityFederationInfoResponse: + raise NotImplementedError + @handler("GetPolicy") def get_policy( self, context: RequestContext, policy_arn: arnType, **kwargs @@ -3143,6 +3408,17 @@ def list_attached_user_policies( ) -> ListAttachedUserPoliciesResponse: raise NotImplementedError + @handler("ListDelegationRequests") + def list_delegation_requests( + self, + context: RequestContext, + owner_id: ownerIdType | None = None, + marker: markerType | None = None, + max_items: maxItemsType | None = None, + **kwargs, + ) -> ListDelegationRequestsResponse: + raise NotImplementedError + @handler("ListEntitiesForPolicy") def list_entities_for_policy( self, @@ -3517,6 +3793,16 @@ def put_user_policy( ) -> None: raise NotImplementedError + @handler("RejectDelegationRequest") + def reject_delegation_request( + self, + context: RequestContext, + delegation_request_id: delegationRequestIdType, + notes: notesType | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + @handler("RemoveClientIDFromOpenIDConnectProvider") def remove_client_id_from_open_id_connect_provider( self, @@ -3569,6 +3855,12 @@ def resync_mfa_device( ) -> None: raise NotImplementedError + @handler("SendDelegationToken") + def send_delegation_token( + self, context: RequestContext, delegation_request_id: delegationRequestIdType, **kwargs + ) -> None: + raise NotImplementedError + @handler("SetDefaultPolicyVersion") def set_default_policy_version( self, @@ -3797,6 +4089,16 @@ def update_assume_role_policy( ) -> None: raise NotImplementedError + @handler("UpdateDelegationRequest") + def update_delegation_request( + self, + context: RequestContext, + delegation_request_id: delegationRequestIdType, + notes: notesType | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + @handler("UpdateGroup") def update_group( self, diff --git a/localstack-core/localstack/aws/api/kinesis/__init__.py b/localstack-core/localstack/aws/api/kinesis/__init__.py index 61f6f105fac9c..578d33a79a342 100644 --- a/localstack-core/localstack/aws/api/kinesis/__init__.py +++ b/localstack-core/localstack/aws/api/kinesis/__init__.py @@ -1,6 +1,7 @@ +from collections.abc import Iterator from datetime import datetime from enum import StrEnum -from typing import Dict, Iterator, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -18,6 +19,8 @@ ListStreamConsumersInputLimit = int ListStreamsInputLimit = int ListTagsForStreamInputLimit = int +MaxRecordSizeInKiB = int +NaturalIntegerObject = int NextToken = str OnDemandStreamCountLimitObject = int OnDemandStreamCountObject = int @@ -31,6 +34,7 @@ ShardId = str ShardIterator = str StreamARN = str +StreamId = str StreamName = str TagKey = str TagValue = str @@ -58,6 +62,17 @@ class MetricsName(StrEnum): ALL = "ALL" +class MinimumThroughputBillingCommitmentInputStatus(StrEnum): + ENABLED = "ENABLED" + DISABLED = "DISABLED" + + +class MinimumThroughputBillingCommitmentOutputStatus(StrEnum): + ENABLED = "ENABLED" + DISABLED = "DISABLED" + ENABLED_UNTIL_EARLIEST_ALLOWED_END = "ENABLED_UNTIL_EARLIEST_ALLOWED_END" + + class ScalingType(StrEnum): UNIFORM_SCALING = "UNIFORM_SCALING" @@ -187,13 +202,14 @@ class ValidationException(ServiceException): status_code: int = 400 -TagMap = Dict[TagKey, TagValue] +TagMap = dict[TagKey, TagValue] class AddTagsToStreamInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None Tags: TagMap - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class HashKeyRange(TypedDict, total=False): @@ -201,7 +217,7 @@ class HashKeyRange(TypedDict, total=False): EndingHashKey: HashKey -ShardIdList = List[ShardId] +ShardIdList = list[ShardId] class ChildShard(TypedDict, total=False): @@ -210,7 +226,7 @@ class ChildShard(TypedDict, total=False): HashKeyRange: HashKeyRange -ChildShardList = List[ChildShard] +ChildShardList = list[ChildShard] Timestamp = datetime @@ -229,7 +245,7 @@ class ConsumerDescription(TypedDict, total=False): StreamARN: StreamARN -ConsumerList = List[Consumer] +ConsumerList = list[Consumer] class StreamModeDetails(TypedDict, total=False): @@ -238,34 +254,55 @@ class StreamModeDetails(TypedDict, total=False): class CreateStreamInput(ServiceRequest): StreamName: StreamName - ShardCount: Optional[PositiveIntegerObject] - StreamModeDetails: Optional[StreamModeDetails] - Tags: Optional[TagMap] + ShardCount: PositiveIntegerObject | None + StreamModeDetails: StreamModeDetails | None + Tags: TagMap | None + WarmThroughputMiBps: NaturalIntegerObject | None + MaxRecordSizeInKiB: MaxRecordSizeInKiB | None Data = bytes class DecreaseStreamRetentionPeriodInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None RetentionPeriodHours: RetentionPeriodHours - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class DeleteResourcePolicyInput(ServiceRequest): ResourceARN: ResourceARN + StreamId: StreamId | None class DeleteStreamInput(ServiceRequest): - StreamName: Optional[StreamName] - EnforceConsumerDeletion: Optional[BooleanObject] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + EnforceConsumerDeletion: BooleanObject | None + StreamARN: StreamARN | None + StreamId: StreamId | None class DeregisterStreamConsumerInput(ServiceRequest): - StreamARN: Optional[StreamARN] - ConsumerName: Optional[ConsumerName] - ConsumerARN: Optional[ConsumerARN] + StreamARN: StreamARN | None + ConsumerName: ConsumerName | None + ConsumerARN: ConsumerARN | None + StreamId: StreamId | None + + +class DescribeAccountSettingsInput(ServiceRequest): + pass + + +class MinimumThroughputBillingCommitmentOutput(TypedDict, total=False): + Status: MinimumThroughputBillingCommitmentOutputStatus + StartedAt: Timestamp | None + EndedAt: Timestamp | None + EarliestAllowedEndAt: Timestamp | None + + +class DescribeAccountSettingsOutput(TypedDict, total=False): + MinimumThroughputBillingCommitment: MinimumThroughputBillingCommitmentOutput | None class DescribeLimitsInput(ServiceRequest): @@ -280,9 +317,10 @@ class DescribeLimitsOutput(TypedDict, total=False): class DescribeStreamConsumerInput(ServiceRequest): - StreamARN: Optional[StreamARN] - ConsumerName: Optional[ConsumerName] - ConsumerARN: Optional[ConsumerARN] + StreamARN: StreamARN | None + ConsumerName: ConsumerName | None + ConsumerARN: ConsumerARN | None + StreamId: StreamId | None class DescribeStreamConsumerOutput(TypedDict, total=False): @@ -290,50 +328,51 @@ class DescribeStreamConsumerOutput(TypedDict, total=False): class DescribeStreamInput(ServiceRequest): - StreamName: Optional[StreamName] - Limit: Optional[DescribeStreamInputLimit] - ExclusiveStartShardId: Optional[ShardId] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + Limit: DescribeStreamInputLimit | None + ExclusiveStartShardId: ShardId | None + StreamARN: StreamARN | None + StreamId: StreamId | None -MetricsNameList = List[MetricsName] +MetricsNameList = list[MetricsName] class EnhancedMetrics(TypedDict, total=False): - ShardLevelMetrics: Optional[MetricsNameList] + ShardLevelMetrics: MetricsNameList | None -EnhancedMonitoringList = List[EnhancedMetrics] +EnhancedMonitoringList = list[EnhancedMetrics] class SequenceNumberRange(TypedDict, total=False): StartingSequenceNumber: SequenceNumber - EndingSequenceNumber: Optional[SequenceNumber] + EndingSequenceNumber: SequenceNumber | None class Shard(TypedDict, total=False): ShardId: ShardId - ParentShardId: Optional[ShardId] - AdjacentParentShardId: Optional[ShardId] + ParentShardId: ShardId | None + AdjacentParentShardId: ShardId | None HashKeyRange: HashKeyRange SequenceNumberRange: SequenceNumberRange -ShardList = List[Shard] +ShardList = list[Shard] class StreamDescription(TypedDict, total=False): StreamName: StreamName StreamARN: StreamARN StreamStatus: StreamStatus - StreamModeDetails: Optional[StreamModeDetails] + StreamModeDetails: StreamModeDetails | None Shards: ShardList HasMoreShards: BooleanObject RetentionPeriodHours: RetentionPeriodHours StreamCreationTimestamp: Timestamp EnhancedMonitoring: EnhancedMonitoringList - EncryptionType: Optional[EncryptionType] - KeyId: Optional[KeyId] + EncryptionType: EncryptionType | None + KeyId: KeyId | None class DescribeStreamOutput(TypedDict, total=False): @@ -341,22 +380,31 @@ class DescribeStreamOutput(TypedDict, total=False): class DescribeStreamSummaryInput(ServiceRequest): - StreamName: Optional[StreamName] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + StreamARN: StreamARN | None + StreamId: StreamId | None + + +class WarmThroughputObject(TypedDict, total=False): + TargetMiBps: NaturalIntegerObject | None + CurrentMiBps: NaturalIntegerObject | None class StreamDescriptionSummary(TypedDict, total=False): StreamName: StreamName StreamARN: StreamARN + StreamId: StreamId | None StreamStatus: StreamStatus - StreamModeDetails: Optional[StreamModeDetails] + StreamModeDetails: StreamModeDetails | None RetentionPeriodHours: RetentionPeriodHours StreamCreationTimestamp: Timestamp EnhancedMonitoring: EnhancedMonitoringList - EncryptionType: Optional[EncryptionType] - KeyId: Optional[KeyId] + EncryptionType: EncryptionType | None + KeyId: KeyId | None OpenShardCount: ShardCountObject - ConsumerCount: Optional[ConsumerCountObject] + ConsumerCount: ConsumerCountObject | None + WarmThroughput: WarmThroughputObject | None + MaxRecordSizeInKiB: MaxRecordSizeInKiB | None class DescribeStreamSummaryOutput(TypedDict, total=False): @@ -364,28 +412,31 @@ class DescribeStreamSummaryOutput(TypedDict, total=False): class DisableEnhancedMonitoringInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None ShardLevelMetrics: MetricsNameList - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class EnableEnhancedMonitoringInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None ShardLevelMetrics: MetricsNameList - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class EnhancedMonitoringOutput(TypedDict, total=False): - StreamName: Optional[StreamName] - CurrentShardLevelMetrics: Optional[MetricsNameList] - DesiredShardLevelMetrics: Optional[MetricsNameList] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + CurrentShardLevelMetrics: MetricsNameList | None + DesiredShardLevelMetrics: MetricsNameList | None + StreamARN: StreamARN | None class GetRecordsInput(ServiceRequest): ShardIterator: ShardIterator - Limit: Optional[GetRecordsInputLimit] - StreamARN: Optional[StreamARN] + Limit: GetRecordsInputLimit | None + StreamARN: StreamARN | None + StreamId: StreamId | None MillisBehindLatest = int @@ -393,24 +444,25 @@ class GetRecordsInput(ServiceRequest): class Record(TypedDict, total=False): SequenceNumber: SequenceNumber - ApproximateArrivalTimestamp: Optional[Timestamp] + ApproximateArrivalTimestamp: Timestamp | None Data: Data PartitionKey: PartitionKey - EncryptionType: Optional[EncryptionType] + EncryptionType: EncryptionType | None -RecordList = List[Record] +RecordList = list[Record] class GetRecordsOutput(TypedDict, total=False): Records: RecordList - NextShardIterator: Optional[ShardIterator] - MillisBehindLatest: Optional[MillisBehindLatest] - ChildShards: Optional[ChildShardList] + NextShardIterator: ShardIterator | None + MillisBehindLatest: MillisBehindLatest | None + ChildShards: ChildShardList | None class GetResourcePolicyInput(ServiceRequest): ResourceARN: ResourceARN + StreamId: StreamId | None class GetResourcePolicyOutput(TypedDict, total=False): @@ -418,103 +470,109 @@ class GetResourcePolicyOutput(TypedDict, total=False): class GetShardIteratorInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None ShardId: ShardId ShardIteratorType: ShardIteratorType - StartingSequenceNumber: Optional[SequenceNumber] - Timestamp: Optional[Timestamp] - StreamARN: Optional[StreamARN] + StartingSequenceNumber: SequenceNumber | None + Timestamp: Timestamp | None + StreamARN: StreamARN | None + StreamId: StreamId | None class GetShardIteratorOutput(TypedDict, total=False): - ShardIterator: Optional[ShardIterator] + ShardIterator: ShardIterator | None class IncreaseStreamRetentionPeriodInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None RetentionPeriodHours: RetentionPeriodHours - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class ShardFilter(TypedDict, total=False): Type: ShardFilterType - ShardId: Optional[ShardId] - Timestamp: Optional[Timestamp] + ShardId: ShardId | None + Timestamp: Timestamp | None class ListShardsInput(ServiceRequest): - StreamName: Optional[StreamName] - NextToken: Optional[NextToken] - ExclusiveStartShardId: Optional[ShardId] - MaxResults: Optional[ListShardsInputLimit] - StreamCreationTimestamp: Optional[Timestamp] - ShardFilter: Optional[ShardFilter] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + NextToken: NextToken | None + ExclusiveStartShardId: ShardId | None + MaxResults: ListShardsInputLimit | None + StreamCreationTimestamp: Timestamp | None + ShardFilter: ShardFilter | None + StreamARN: StreamARN | None + StreamId: StreamId | None class ListShardsOutput(TypedDict, total=False): - Shards: Optional[ShardList] - NextToken: Optional[NextToken] + Shards: ShardList | None + NextToken: NextToken | None class ListStreamConsumersInput(ServiceRequest): StreamARN: StreamARN - NextToken: Optional[NextToken] - MaxResults: Optional[ListStreamConsumersInputLimit] - StreamCreationTimestamp: Optional[Timestamp] + NextToken: NextToken | None + MaxResults: ListStreamConsumersInputLimit | None + StreamCreationTimestamp: Timestamp | None + StreamId: StreamId | None class ListStreamConsumersOutput(TypedDict, total=False): - Consumers: Optional[ConsumerList] - NextToken: Optional[NextToken] + Consumers: ConsumerList | None + NextToken: NextToken | None class ListStreamsInput(ServiceRequest): - Limit: Optional[ListStreamsInputLimit] - ExclusiveStartStreamName: Optional[StreamName] - NextToken: Optional[NextToken] + Limit: ListStreamsInputLimit | None + ExclusiveStartStreamName: StreamName | None + NextToken: NextToken | None class StreamSummary(TypedDict, total=False): StreamName: StreamName StreamARN: StreamARN StreamStatus: StreamStatus - StreamModeDetails: Optional[StreamModeDetails] - StreamCreationTimestamp: Optional[Timestamp] + StreamModeDetails: StreamModeDetails | None + StreamCreationTimestamp: Timestamp | None -StreamSummaryList = List[StreamSummary] -StreamNameList = List[StreamName] +StreamSummaryList = list[StreamSummary] +StreamNameList = list[StreamName] class ListStreamsOutput(TypedDict, total=False): StreamNames: StreamNameList HasMoreStreams: BooleanObject - NextToken: Optional[NextToken] - StreamSummaries: Optional[StreamSummaryList] + NextToken: NextToken | None + StreamSummaries: StreamSummaryList | None class ListTagsForResourceInput(ServiceRequest): ResourceARN: ResourceARN + StreamId: StreamId | None class Tag(TypedDict, total=False): Key: TagKey - Value: Optional[TagValue] + Value: TagValue | None -TagList = List[Tag] +TagList = list[Tag] class ListTagsForResourceOutput(TypedDict, total=False): - Tags: Optional[TagList] + Tags: TagList | None class ListTagsForStreamInput(ServiceRequest): - StreamName: Optional[StreamName] - ExclusiveStartTagKey: Optional[TagKey] - Limit: Optional[ListTagsForStreamInputLimit] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + ExclusiveStartTagKey: TagKey | None + Limit: ListTagsForStreamInputLimit | None + StreamARN: StreamARN | None + StreamId: StreamId | None class ListTagsForStreamOutput(TypedDict, total=False): @@ -523,131 +581,145 @@ class ListTagsForStreamOutput(TypedDict, total=False): class MergeShardsInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None ShardToMerge: ShardId AdjacentShardToMerge: ShardId - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None + + +class MinimumThroughputBillingCommitmentInput(TypedDict, total=False): + Status: MinimumThroughputBillingCommitmentInputStatus class PutRecordInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None Data: Data PartitionKey: PartitionKey - ExplicitHashKey: Optional[HashKey] - SequenceNumberForOrdering: Optional[SequenceNumber] - StreamARN: Optional[StreamARN] + ExplicitHashKey: HashKey | None + SequenceNumberForOrdering: SequenceNumber | None + StreamARN: StreamARN | None + StreamId: StreamId | None class PutRecordOutput(TypedDict, total=False): ShardId: ShardId SequenceNumber: SequenceNumber - EncryptionType: Optional[EncryptionType] + EncryptionType: EncryptionType | None class PutRecordsRequestEntry(TypedDict, total=False): Data: Data - ExplicitHashKey: Optional[HashKey] + ExplicitHashKey: HashKey | None PartitionKey: PartitionKey -PutRecordsRequestEntryList = List[PutRecordsRequestEntry] +PutRecordsRequestEntryList = list[PutRecordsRequestEntry] class PutRecordsInput(ServiceRequest): Records: PutRecordsRequestEntryList - StreamName: Optional[StreamName] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + StreamARN: StreamARN | None + StreamId: StreamId | None class PutRecordsResultEntry(TypedDict, total=False): - SequenceNumber: Optional[SequenceNumber] - ShardId: Optional[ShardId] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + SequenceNumber: SequenceNumber | None + ShardId: ShardId | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None -PutRecordsResultEntryList = List[PutRecordsResultEntry] +PutRecordsResultEntryList = list[PutRecordsResultEntry] class PutRecordsOutput(TypedDict, total=False): - FailedRecordCount: Optional[PositiveIntegerObject] + FailedRecordCount: PositiveIntegerObject | None Records: PutRecordsResultEntryList - EncryptionType: Optional[EncryptionType] + EncryptionType: EncryptionType | None class PutResourcePolicyInput(ServiceRequest): ResourceARN: ResourceARN + StreamId: StreamId | None Policy: Policy class RegisterStreamConsumerInput(ServiceRequest): StreamARN: StreamARN ConsumerName: ConsumerName - Tags: Optional[TagMap] + StreamId: StreamId | None + Tags: TagMap | None class RegisterStreamConsumerOutput(TypedDict, total=False): Consumer: Consumer -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class RemoveTagsFromStreamInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None TagKeys: TagKeyList - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class SplitShardInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None ShardToSplit: ShardId NewStartingHashKey: HashKey - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class StartStreamEncryptionInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None EncryptionType: EncryptionType KeyId: KeyId - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class StartingPosition(TypedDict, total=False): Type: ShardIteratorType - SequenceNumber: Optional[SequenceNumber] - Timestamp: Optional[Timestamp] + SequenceNumber: SequenceNumber | None + Timestamp: Timestamp | None class StopStreamEncryptionInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None EncryptionType: EncryptionType KeyId: KeyId - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class SubscribeToShardEvent(TypedDict, total=False): Records: RecordList ContinuationSequenceNumber: SequenceNumber MillisBehindLatest: MillisBehindLatest - ChildShards: Optional[ChildShardList] + ChildShards: ChildShardList | None class SubscribeToShardEventStream(TypedDict, total=False): SubscribeToShardEvent: SubscribeToShardEvent - ResourceNotFoundException: Optional[ResourceNotFoundException] - ResourceInUseException: Optional[ResourceInUseException] - KMSDisabledException: Optional[KMSDisabledException] - KMSInvalidStateException: Optional[KMSInvalidStateException] - KMSAccessDeniedException: Optional[KMSAccessDeniedException] - KMSNotFoundException: Optional[KMSNotFoundException] - KMSOptInRequired: Optional[KMSOptInRequired] - KMSThrottlingException: Optional[KMSThrottlingException] - InternalFailureException: Optional[InternalFailureException] + ResourceNotFoundException: ResourceNotFoundException | None + ResourceInUseException: ResourceInUseException | None + KMSDisabledException: KMSDisabledException | None + KMSInvalidStateException: KMSInvalidStateException | None + KMSAccessDeniedException: KMSAccessDeniedException | None + KMSNotFoundException: KMSNotFoundException | None + KMSOptInRequired: KMSOptInRequired | None + KMSThrottlingException: KMSThrottlingException | None + InternalFailureException: InternalFailureException | None class SubscribeToShardInput(ServiceRequest): ConsumerARN: ConsumerARN + StreamId: StreamId | None ShardId: ShardId StartingPosition: StartingPosition @@ -659,35 +731,67 @@ class SubscribeToShardOutput(TypedDict, total=False): class TagResourceInput(ServiceRequest): Tags: TagMap ResourceARN: ResourceARN + StreamId: StreamId | None class UntagResourceInput(ServiceRequest): TagKeys: TagKeyList ResourceARN: ResourceARN + StreamId: StreamId | None + + +class UpdateAccountSettingsInput(ServiceRequest): + MinimumThroughputBillingCommitment: MinimumThroughputBillingCommitmentInput + + +class UpdateAccountSettingsOutput(TypedDict, total=False): + MinimumThroughputBillingCommitment: MinimumThroughputBillingCommitmentOutput | None + + +class UpdateMaxRecordSizeInput(ServiceRequest): + StreamARN: StreamARN | None + StreamId: StreamId | None + MaxRecordSizeInKiB: MaxRecordSizeInKiB class UpdateShardCountInput(ServiceRequest): - StreamName: Optional[StreamName] + StreamName: StreamName | None TargetShardCount: PositiveIntegerObject ScalingType: ScalingType - StreamARN: Optional[StreamARN] + StreamARN: StreamARN | None + StreamId: StreamId | None class UpdateShardCountOutput(TypedDict, total=False): - StreamName: Optional[StreamName] - CurrentShardCount: Optional[PositiveIntegerObject] - TargetShardCount: Optional[PositiveIntegerObject] - StreamARN: Optional[StreamARN] + StreamName: StreamName | None + CurrentShardCount: PositiveIntegerObject | None + TargetShardCount: PositiveIntegerObject | None + StreamARN: StreamARN | None class UpdateStreamModeInput(ServiceRequest): StreamARN: StreamARN + StreamId: StreamId | None StreamModeDetails: StreamModeDetails + WarmThroughputMiBps: NaturalIntegerObject | None + + +class UpdateStreamWarmThroughputInput(ServiceRequest): + StreamARN: StreamARN | None + StreamName: StreamName | None + StreamId: StreamId | None + WarmThroughputMiBps: NaturalIntegerObject + + +class UpdateStreamWarmThroughputOutput(TypedDict, total=False): + StreamARN: StreamARN | None + StreamName: StreamName | None + WarmThroughput: WarmThroughputObject | None class KinesisApi: - service = "kinesis" - version = "2013-12-02" + service: str = "kinesis" + version: str = "2013-12-02" @handler("AddTagsToStream") def add_tags_to_stream( @@ -696,6 +800,7 @@ def add_tags_to_stream( tags: TagMap, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -708,6 +813,8 @@ def create_stream( shard_count: PositiveIntegerObject | None = None, stream_mode_details: StreamModeDetails | None = None, tags: TagMap | None = None, + warm_throughput_mi_bps: NaturalIntegerObject | None = None, + max_record_size_in_ki_b: MaxRecordSizeInKiB | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -719,13 +826,18 @@ def decrease_stream_retention_period( retention_period_hours: RetentionPeriodHours, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @handler("DeleteResourcePolicy") def delete_resource_policy( - self, context: RequestContext, resource_arn: ResourceARN, **kwargs + self, + context: RequestContext, + resource_arn: ResourceARN, + stream_id: StreamId | None = None, + **kwargs, ) -> None: raise NotImplementedError @@ -736,6 +848,7 @@ def delete_stream( stream_name: StreamName | None = None, enforce_consumer_deletion: BooleanObject | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -747,10 +860,17 @@ def deregister_stream_consumer( stream_arn: StreamARN | None = None, consumer_name: ConsumerName | None = None, consumer_arn: ConsumerARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError + @handler("DescribeAccountSettings") + def describe_account_settings( + self, context: RequestContext, **kwargs + ) -> DescribeAccountSettingsOutput: + raise NotImplementedError + @handler("DescribeLimits") def describe_limits(self, context: RequestContext, **kwargs) -> DescribeLimitsOutput: raise NotImplementedError @@ -763,6 +883,7 @@ def describe_stream( limit: DescribeStreamInputLimit | None = None, exclusive_start_shard_id: ShardId | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> DescribeStreamOutput: raise NotImplementedError @@ -774,6 +895,7 @@ def describe_stream_consumer( stream_arn: StreamARN | None = None, consumer_name: ConsumerName | None = None, consumer_arn: ConsumerARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> DescribeStreamConsumerOutput: raise NotImplementedError @@ -784,6 +906,7 @@ def describe_stream_summary( context: RequestContext, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> DescribeStreamSummaryOutput: raise NotImplementedError @@ -795,6 +918,7 @@ def disable_enhanced_monitoring( shard_level_metrics: MetricsNameList, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> EnhancedMonitoringOutput: raise NotImplementedError @@ -806,6 +930,7 @@ def enable_enhanced_monitoring( shard_level_metrics: MetricsNameList, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> EnhancedMonitoringOutput: raise NotImplementedError @@ -817,13 +942,18 @@ def get_records( shard_iterator: ShardIterator, limit: GetRecordsInputLimit | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> GetRecordsOutput: raise NotImplementedError @handler("GetResourcePolicy") def get_resource_policy( - self, context: RequestContext, resource_arn: ResourceARN, **kwargs + self, + context: RequestContext, + resource_arn: ResourceARN, + stream_id: StreamId | None = None, + **kwargs, ) -> GetResourcePolicyOutput: raise NotImplementedError @@ -837,6 +967,7 @@ def get_shard_iterator( starting_sequence_number: SequenceNumber | None = None, timestamp: Timestamp | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> GetShardIteratorOutput: raise NotImplementedError @@ -848,6 +979,7 @@ def increase_stream_retention_period( retention_period_hours: RetentionPeriodHours, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -863,6 +995,7 @@ def list_shards( stream_creation_timestamp: Timestamp | None = None, shard_filter: ShardFilter | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> ListShardsOutput: raise NotImplementedError @@ -875,6 +1008,7 @@ def list_stream_consumers( next_token: NextToken | None = None, max_results: ListStreamConsumersInputLimit | None = None, stream_creation_timestamp: Timestamp | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> ListStreamConsumersOutput: raise NotImplementedError @@ -892,7 +1026,11 @@ def list_streams( @handler("ListTagsForResource") def list_tags_for_resource( - self, context: RequestContext, resource_arn: ResourceARN, **kwargs + self, + context: RequestContext, + resource_arn: ResourceARN, + stream_id: StreamId | None = None, + **kwargs, ) -> ListTagsForResourceOutput: raise NotImplementedError @@ -904,6 +1042,7 @@ def list_tags_for_stream( exclusive_start_tag_key: TagKey | None = None, limit: ListTagsForStreamInputLimit | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> ListTagsForStreamOutput: raise NotImplementedError @@ -916,6 +1055,7 @@ def merge_shards( adjacent_shard_to_merge: ShardId, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -930,6 +1070,7 @@ def put_record( explicit_hash_key: HashKey | None = None, sequence_number_for_ordering: SequenceNumber | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> PutRecordOutput: raise NotImplementedError @@ -941,13 +1082,19 @@ def put_records( records: PutRecordsRequestEntryList, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> PutRecordsOutput: raise NotImplementedError @handler("PutResourcePolicy") def put_resource_policy( - self, context: RequestContext, resource_arn: ResourceARN, policy: Policy, **kwargs + self, + context: RequestContext, + resource_arn: ResourceARN, + policy: Policy, + stream_id: StreamId | None = None, + **kwargs, ) -> None: raise NotImplementedError @@ -957,6 +1104,7 @@ def register_stream_consumer( context: RequestContext, stream_arn: StreamARN, consumer_name: ConsumerName, + stream_id: StreamId | None = None, tags: TagMap | None = None, **kwargs, ) -> RegisterStreamConsumerOutput: @@ -969,6 +1117,7 @@ def remove_tags_from_stream( tag_keys: TagKeyList, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -981,6 +1130,7 @@ def split_shard( new_starting_hash_key: HashKey, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -993,6 +1143,7 @@ def start_stream_encryption( key_id: KeyId, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -1005,6 +1156,7 @@ def stop_stream_encryption( key_id: KeyId, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -1016,19 +1168,50 @@ def subscribe_to_shard( consumer_arn: ConsumerARN, shard_id: ShardId, starting_position: StartingPosition, + stream_id: StreamId | None = None, **kwargs, ) -> SubscribeToShardOutput: raise NotImplementedError @handler("TagResource") def tag_resource( - self, context: RequestContext, tags: TagMap, resource_arn: ResourceARN, **kwargs + self, + context: RequestContext, + tags: TagMap, + resource_arn: ResourceARN, + stream_id: StreamId | None = None, + **kwargs, ) -> None: raise NotImplementedError @handler("UntagResource") def untag_resource( - self, context: RequestContext, tag_keys: TagKeyList, resource_arn: ResourceARN, **kwargs + self, + context: RequestContext, + tag_keys: TagKeyList, + resource_arn: ResourceARN, + stream_id: StreamId | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + + @handler("UpdateAccountSettings") + def update_account_settings( + self, + context: RequestContext, + minimum_throughput_billing_commitment: MinimumThroughputBillingCommitmentInput, + **kwargs, + ) -> UpdateAccountSettingsOutput: + raise NotImplementedError + + @handler("UpdateMaxRecordSize") + def update_max_record_size( + self, + context: RequestContext, + max_record_size_in_ki_b: MaxRecordSizeInKiB, + stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, + **kwargs, ) -> None: raise NotImplementedError @@ -1040,6 +1223,7 @@ def update_shard_count( scaling_type: ScalingType, stream_name: StreamName | None = None, stream_arn: StreamARN | None = None, + stream_id: StreamId | None = None, **kwargs, ) -> UpdateShardCountOutput: raise NotImplementedError @@ -1050,6 +1234,20 @@ def update_stream_mode( context: RequestContext, stream_arn: StreamARN, stream_mode_details: StreamModeDetails, + stream_id: StreamId | None = None, + warm_throughput_mi_bps: NaturalIntegerObject | None = None, **kwargs, ) -> None: raise NotImplementedError + + @handler("UpdateStreamWarmThroughput") + def update_stream_warm_throughput( + self, + context: RequestContext, + warm_throughput_mi_bps: NaturalIntegerObject, + stream_arn: StreamARN | None = None, + stream_name: StreamName | None = None, + stream_id: StreamId | None = None, + **kwargs, + ) -> UpdateStreamWarmThroughputOutput: + raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/kms/__init__.py b/localstack-core/localstack/aws/api/kms/__init__.py index b5e0fec886732..d320caa179a35 100644 --- a/localstack-core/localstack/aws/api/kms/__init__.py +++ b/localstack-core/localstack/aws/api/kms/__init__.py @@ -1,10 +1,11 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler AWSAccountIdType = str +AccountIdType = str AliasNameType = str ArnType = str BackingKeyIdResponseType = str @@ -114,6 +115,7 @@ class DataKeyPairSpec(StrEnum): ECC_NIST_P521 = "ECC_NIST_P521" ECC_SECG_P256K1 = "ECC_SECG_P256K1" SM2 = "SM2" + ECC_NIST_EDWARDS25519 = "ECC_NIST_EDWARDS25519" class DataKeySpec(StrEnum): @@ -121,6 +123,10 @@ class DataKeySpec(StrEnum): AES_128 = "AES_128" +class DryRunModifierType(StrEnum): + IGNORE_CIPHERTEXT = "IGNORE_CIPHERTEXT" + + class EncryptionAlgorithmSpec(StrEnum): SYMMETRIC_DEFAULT = "SYMMETRIC_DEFAULT" RSAES_OAEP_SHA_1 = "RSAES_OAEP_SHA_1" @@ -185,6 +191,7 @@ class KeyMaterialState(StrEnum): NON_CURRENT = "NON_CURRENT" CURRENT = "CURRENT" PENDING_ROTATION = "PENDING_ROTATION" + PENDING_MULTI_REGION_IMPORT_AND_ROTATION = "PENDING_MULTI_REGION_IMPORT_AND_ROTATION" class KeySpec(StrEnum): @@ -204,6 +211,7 @@ class KeySpec(StrEnum): ML_DSA_44 = "ML_DSA_44" ML_DSA_65 = "ML_DSA_65" ML_DSA_87 = "ML_DSA_87" + ECC_NIST_EDWARDS25519 = "ECC_NIST_EDWARDS25519" class KeyState(StrEnum): @@ -266,6 +274,8 @@ class SigningAlgorithmSpec(StrEnum): ECDSA_SHA_512 = "ECDSA_SHA_512" SM2DSA = "SM2DSA" ML_DSA_SHAKE_256 = "ML_DSA_SHAKE_256" + ED25519_SHA_512 = "ED25519_SHA_512" + ED25519_PH_SHA_512 = "ED25519_PH_SHA_512" class WrappingKeySpec(StrEnum): @@ -572,14 +582,14 @@ class XksProxyVpcEndpointServiceNotFoundException(ServiceException): class AliasListEntry(TypedDict, total=False): - AliasName: Optional[AliasNameType] - AliasArn: Optional[ArnType] - TargetKeyId: Optional[KeyIdType] - CreationDate: Optional[DateType] - LastUpdatedDate: Optional[DateType] + AliasName: AliasNameType | None + AliasArn: ArnType | None + TargetKeyId: KeyIdType | None + CreationDate: DateType | None + LastUpdatedDate: DateType | None -AliasList = List[AliasListEntry] +AliasList = list[AliasListEntry] AttestationDocumentType = bytes @@ -588,7 +598,7 @@ class CancelKeyDeletionRequest(ServiceRequest): class CancelKeyDeletionResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] + KeyId: KeyIdType | None CiphertextType = bytes @@ -614,47 +624,48 @@ class XksProxyAuthenticationCredentialType(TypedDict, total=False): class CreateCustomKeyStoreRequest(ServiceRequest): CustomKeyStoreName: CustomKeyStoreNameType - CloudHsmClusterId: Optional[CloudHsmClusterIdType] - TrustAnchorCertificate: Optional[TrustAnchorCertificateType] - KeyStorePassword: Optional[KeyStorePasswordType] - CustomKeyStoreType: Optional[CustomKeyStoreType] - XksProxyUriEndpoint: Optional[XksProxyUriEndpointType] - XksProxyUriPath: Optional[XksProxyUriPathType] - XksProxyVpcEndpointServiceName: Optional[XksProxyVpcEndpointServiceNameType] - XksProxyAuthenticationCredential: Optional[XksProxyAuthenticationCredentialType] - XksProxyConnectivity: Optional[XksProxyConnectivityType] + CloudHsmClusterId: CloudHsmClusterIdType | None + TrustAnchorCertificate: TrustAnchorCertificateType | None + KeyStorePassword: KeyStorePasswordType | None + CustomKeyStoreType: CustomKeyStoreType | None + XksProxyUriEndpoint: XksProxyUriEndpointType | None + XksProxyUriPath: XksProxyUriPathType | None + XksProxyVpcEndpointServiceName: XksProxyVpcEndpointServiceNameType | None + XksProxyVpcEndpointServiceOwner: AccountIdType | None + XksProxyAuthenticationCredential: XksProxyAuthenticationCredentialType | None + XksProxyConnectivity: XksProxyConnectivityType | None class CreateCustomKeyStoreResponse(TypedDict, total=False): - CustomKeyStoreId: Optional[CustomKeyStoreIdType] + CustomKeyStoreId: CustomKeyStoreIdType | None -GrantTokenList = List[GrantTokenType] -EncryptionContextType = Dict[EncryptionContextKey, EncryptionContextValue] +GrantTokenList = list[GrantTokenType] +EncryptionContextType = dict[EncryptionContextKey, EncryptionContextValue] class GrantConstraints(TypedDict, total=False): - EncryptionContextSubset: Optional[EncryptionContextType] - EncryptionContextEquals: Optional[EncryptionContextType] + EncryptionContextSubset: EncryptionContextType | None + EncryptionContextEquals: EncryptionContextType | None -GrantOperationList = List[GrantOperation] +GrantOperationList = list[GrantOperation] class CreateGrantRequest(ServiceRequest): KeyId: KeyIdType GranteePrincipal: PrincipalIdType - RetiringPrincipal: Optional[PrincipalIdType] + RetiringPrincipal: PrincipalIdType | None Operations: GrantOperationList - Constraints: Optional[GrantConstraints] - GrantTokens: Optional[GrantTokenList] - Name: Optional[GrantNameType] - DryRun: Optional[NullableBooleanType] + Constraints: GrantConstraints | None + GrantTokens: GrantTokenList | None + Name: GrantNameType | None + DryRun: NullableBooleanType | None class CreateGrantResponse(TypedDict, total=False): - GrantToken: Optional[GrantTokenType] - GrantId: Optional[GrantIdType] + GrantToken: GrantTokenType | None + GrantId: GrantIdType | None class Tag(TypedDict, total=False): @@ -662,129 +673,132 @@ class Tag(TypedDict, total=False): TagValue: TagValueType -TagList = List[Tag] +TagList = list[Tag] class CreateKeyRequest(ServiceRequest): - Policy: Optional[PolicyType] - Description: Optional[DescriptionType] - KeyUsage: Optional[KeyUsageType] - CustomerMasterKeySpec: Optional[CustomerMasterKeySpec] - KeySpec: Optional[KeySpec] - Origin: Optional[OriginType] - CustomKeyStoreId: Optional[CustomKeyStoreIdType] - BypassPolicyLockoutSafetyCheck: Optional[BooleanType] - Tags: Optional[TagList] - MultiRegion: Optional[NullableBooleanType] - XksKeyId: Optional[XksKeyIdType] + Policy: PolicyType | None + Description: DescriptionType | None + KeyUsage: KeyUsageType | None + CustomerMasterKeySpec: CustomerMasterKeySpec | None + KeySpec: KeySpec | None + Origin: OriginType | None + CustomKeyStoreId: CustomKeyStoreIdType | None + BypassPolicyLockoutSafetyCheck: BooleanType | None + Tags: TagList | None + MultiRegion: NullableBooleanType | None + XksKeyId: XksKeyIdType | None class XksKeyConfigurationType(TypedDict, total=False): - Id: Optional[XksKeyIdType] + Id: XksKeyIdType | None -MacAlgorithmSpecList = List[MacAlgorithmSpec] +MacAlgorithmSpecList = list[MacAlgorithmSpec] class MultiRegionKey(TypedDict, total=False): - Arn: Optional[ArnType] - Region: Optional[RegionType] + Arn: ArnType | None + Region: RegionType | None -MultiRegionKeyList = List[MultiRegionKey] +MultiRegionKeyList = list[MultiRegionKey] class MultiRegionConfiguration(TypedDict, total=False): - MultiRegionKeyType: Optional[MultiRegionKeyType] - PrimaryKey: Optional[MultiRegionKey] - ReplicaKeys: Optional[MultiRegionKeyList] + MultiRegionKeyType: MultiRegionKeyType | None + PrimaryKey: MultiRegionKey | None + ReplicaKeys: MultiRegionKeyList | None -KeyAgreementAlgorithmSpecList = List[KeyAgreementAlgorithmSpec] -SigningAlgorithmSpecList = List[SigningAlgorithmSpec] -EncryptionAlgorithmSpecList = List[EncryptionAlgorithmSpec] +KeyAgreementAlgorithmSpecList = list[KeyAgreementAlgorithmSpec] +SigningAlgorithmSpecList = list[SigningAlgorithmSpec] +EncryptionAlgorithmSpecList = list[EncryptionAlgorithmSpec] class KeyMetadata(TypedDict, total=False): - AWSAccountId: Optional[AWSAccountIdType] + AWSAccountId: AWSAccountIdType | None KeyId: KeyIdType - Arn: Optional[ArnType] - CreationDate: Optional[DateType] - Enabled: Optional[BooleanType] - Description: Optional[DescriptionType] - KeyUsage: Optional[KeyUsageType] - KeyState: Optional[KeyState] - DeletionDate: Optional[DateType] - ValidTo: Optional[DateType] - Origin: Optional[OriginType] - CustomKeyStoreId: Optional[CustomKeyStoreIdType] - CloudHsmClusterId: Optional[CloudHsmClusterIdType] - ExpirationModel: Optional[ExpirationModelType] - KeyManager: Optional[KeyManagerType] - CustomerMasterKeySpec: Optional[CustomerMasterKeySpec] - KeySpec: Optional[KeySpec] - EncryptionAlgorithms: Optional[EncryptionAlgorithmSpecList] - SigningAlgorithms: Optional[SigningAlgorithmSpecList] - KeyAgreementAlgorithms: Optional[KeyAgreementAlgorithmSpecList] - MultiRegion: Optional[NullableBooleanType] - MultiRegionConfiguration: Optional[MultiRegionConfiguration] - PendingDeletionWindowInDays: Optional[PendingWindowInDaysType] - MacAlgorithms: Optional[MacAlgorithmSpecList] - XksKeyConfiguration: Optional[XksKeyConfigurationType] - CurrentKeyMaterialId: Optional[BackingKeyIdType] + Arn: ArnType | None + CreationDate: DateType | None + Enabled: BooleanType | None + Description: DescriptionType | None + KeyUsage: KeyUsageType | None + KeyState: KeyState | None + DeletionDate: DateType | None + ValidTo: DateType | None + Origin: OriginType | None + CustomKeyStoreId: CustomKeyStoreIdType | None + CloudHsmClusterId: CloudHsmClusterIdType | None + ExpirationModel: ExpirationModelType | None + KeyManager: KeyManagerType | None + CustomerMasterKeySpec: CustomerMasterKeySpec | None + KeySpec: KeySpec | None + EncryptionAlgorithms: EncryptionAlgorithmSpecList | None + SigningAlgorithms: SigningAlgorithmSpecList | None + KeyAgreementAlgorithms: KeyAgreementAlgorithmSpecList | None + MultiRegion: NullableBooleanType | None + MultiRegionConfiguration: MultiRegionConfiguration | None + PendingDeletionWindowInDays: PendingWindowInDaysType | None + MacAlgorithms: MacAlgorithmSpecList | None + XksKeyConfiguration: XksKeyConfigurationType | None + CurrentKeyMaterialId: BackingKeyIdType | None class CreateKeyResponse(TypedDict, total=False): - KeyMetadata: Optional[KeyMetadata] + KeyMetadata: KeyMetadata | None class XksProxyConfigurationType(TypedDict, total=False): - Connectivity: Optional[XksProxyConnectivityType] - AccessKeyId: Optional[XksProxyAuthenticationAccessKeyIdType] - UriEndpoint: Optional[XksProxyUriEndpointType] - UriPath: Optional[XksProxyUriPathType] - VpcEndpointServiceName: Optional[XksProxyVpcEndpointServiceNameType] + Connectivity: XksProxyConnectivityType | None + AccessKeyId: XksProxyAuthenticationAccessKeyIdType | None + UriEndpoint: XksProxyUriEndpointType | None + UriPath: XksProxyUriPathType | None + VpcEndpointServiceName: XksProxyVpcEndpointServiceNameType | None + VpcEndpointServiceOwner: AccountIdType | None class CustomKeyStoresListEntry(TypedDict, total=False): - CustomKeyStoreId: Optional[CustomKeyStoreIdType] - CustomKeyStoreName: Optional[CustomKeyStoreNameType] - CloudHsmClusterId: Optional[CloudHsmClusterIdType] - TrustAnchorCertificate: Optional[TrustAnchorCertificateType] - ConnectionState: Optional[ConnectionStateType] - ConnectionErrorCode: Optional[ConnectionErrorCodeType] - CreationDate: Optional[DateType] - CustomKeyStoreType: Optional[CustomKeyStoreType] - XksProxyConfiguration: Optional[XksProxyConfigurationType] + CustomKeyStoreId: CustomKeyStoreIdType | None + CustomKeyStoreName: CustomKeyStoreNameType | None + CloudHsmClusterId: CloudHsmClusterIdType | None + TrustAnchorCertificate: TrustAnchorCertificateType | None + ConnectionState: ConnectionStateType | None + ConnectionErrorCode: ConnectionErrorCodeType | None + CreationDate: DateType | None + CustomKeyStoreType: CustomKeyStoreType | None + XksProxyConfiguration: XksProxyConfigurationType | None -CustomKeyStoresList = List[CustomKeyStoresListEntry] +CustomKeyStoresList = list[CustomKeyStoresListEntry] +DryRunModifierList = list[DryRunModifierType] class RecipientInfo(TypedDict, total=False): - KeyEncryptionAlgorithm: Optional[KeyEncryptionMechanism] - AttestationDocument: Optional[AttestationDocumentType] + KeyEncryptionAlgorithm: KeyEncryptionMechanism | None + AttestationDocument: AttestationDocumentType | None class DecryptRequest(ServiceRequest): - CiphertextBlob: CiphertextType - EncryptionContext: Optional[EncryptionContextType] - GrantTokens: Optional[GrantTokenList] - KeyId: Optional[KeyIdType] - EncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] - Recipient: Optional[RecipientInfo] - DryRun: Optional[NullableBooleanType] + CiphertextBlob: CiphertextType | None + EncryptionContext: EncryptionContextType | None + GrantTokens: GrantTokenList | None + KeyId: KeyIdType | None + EncryptionAlgorithm: EncryptionAlgorithmSpec | None + Recipient: RecipientInfo | None + DryRun: NullableBooleanType | None + DryRunModifiers: DryRunModifierList | None PlaintextType = bytes class DecryptResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - Plaintext: Optional[PlaintextType] - EncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] - CiphertextForRecipient: Optional[CiphertextType] - KeyMaterialId: Optional[BackingKeyIdType] + KeyId: KeyIdType | None + Plaintext: PlaintextType | None + EncryptionAlgorithm: EncryptionAlgorithmSpec | None + CiphertextForRecipient: CiphertextType | None + KeyMaterialId: BackingKeyIdType | None class DeleteAliasRequest(ServiceRequest): @@ -801,12 +815,12 @@ class DeleteCustomKeyStoreResponse(TypedDict, total=False): class DeleteImportedKeyMaterialRequest(ServiceRequest): KeyId: KeyIdType - KeyMaterialId: Optional[BackingKeyIdType] + KeyMaterialId: BackingKeyIdType | None class DeleteImportedKeyMaterialResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - KeyMaterialId: Optional[BackingKeyIdResponseType] + KeyId: KeyIdType | None + KeyMaterialId: BackingKeyIdResponseType | None PublicKeyType = bytes @@ -816,39 +830,39 @@ class DeriveSharedSecretRequest(ServiceRequest): KeyId: KeyIdType KeyAgreementAlgorithm: KeyAgreementAlgorithmSpec PublicKey: PublicKeyType - GrantTokens: Optional[GrantTokenList] - DryRun: Optional[NullableBooleanType] - Recipient: Optional[RecipientInfo] + GrantTokens: GrantTokenList | None + DryRun: NullableBooleanType | None + Recipient: RecipientInfo | None class DeriveSharedSecretResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - SharedSecret: Optional[PlaintextType] - CiphertextForRecipient: Optional[CiphertextType] - KeyAgreementAlgorithm: Optional[KeyAgreementAlgorithmSpec] - KeyOrigin: Optional[OriginType] + KeyId: KeyIdType | None + SharedSecret: PlaintextType | None + CiphertextForRecipient: CiphertextType | None + KeyAgreementAlgorithm: KeyAgreementAlgorithmSpec | None + KeyOrigin: OriginType | None class DescribeCustomKeyStoresRequest(ServiceRequest): - CustomKeyStoreId: Optional[CustomKeyStoreIdType] - CustomKeyStoreName: Optional[CustomKeyStoreNameType] - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + CustomKeyStoreId: CustomKeyStoreIdType | None + CustomKeyStoreName: CustomKeyStoreNameType | None + Limit: LimitType | None + Marker: MarkerType | None class DescribeCustomKeyStoresResponse(TypedDict, total=False): - CustomKeyStores: Optional[CustomKeyStoresList] - NextMarker: Optional[MarkerType] - Truncated: Optional[BooleanType] + CustomKeyStores: CustomKeyStoresList | None + NextMarker: MarkerType | None + Truncated: BooleanType | None class DescribeKeyRequest(ServiceRequest): KeyId: KeyIdType - GrantTokens: Optional[GrantTokenList] + GrantTokens: GrantTokenList | None class DescribeKeyResponse(TypedDict, total=False): - KeyMetadata: Optional[KeyMetadata] + KeyMetadata: KeyMetadata | None class DisableKeyRequest(ServiceRequest): @@ -873,125 +887,125 @@ class EnableKeyRequest(ServiceRequest): class EnableKeyRotationRequest(ServiceRequest): KeyId: KeyIdType - RotationPeriodInDays: Optional[RotationPeriodInDaysType] + RotationPeriodInDays: RotationPeriodInDaysType | None class EncryptRequest(ServiceRequest): KeyId: KeyIdType Plaintext: PlaintextType - EncryptionContext: Optional[EncryptionContextType] - GrantTokens: Optional[GrantTokenList] - EncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] - DryRun: Optional[NullableBooleanType] + EncryptionContext: EncryptionContextType | None + GrantTokens: GrantTokenList | None + EncryptionAlgorithm: EncryptionAlgorithmSpec | None + DryRun: NullableBooleanType | None class EncryptResponse(TypedDict, total=False): - CiphertextBlob: Optional[CiphertextType] - KeyId: Optional[KeyIdType] - EncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] + CiphertextBlob: CiphertextType | None + KeyId: KeyIdType | None + EncryptionAlgorithm: EncryptionAlgorithmSpec | None class GenerateDataKeyPairRequest(ServiceRequest): - EncryptionContext: Optional[EncryptionContextType] + EncryptionContext: EncryptionContextType | None KeyId: KeyIdType KeyPairSpec: DataKeyPairSpec - GrantTokens: Optional[GrantTokenList] - Recipient: Optional[RecipientInfo] - DryRun: Optional[NullableBooleanType] + GrantTokens: GrantTokenList | None + Recipient: RecipientInfo | None + DryRun: NullableBooleanType | None class GenerateDataKeyPairResponse(TypedDict, total=False): - PrivateKeyCiphertextBlob: Optional[CiphertextType] - PrivateKeyPlaintext: Optional[PlaintextType] - PublicKey: Optional[PublicKeyType] - KeyId: Optional[KeyIdType] - KeyPairSpec: Optional[DataKeyPairSpec] - CiphertextForRecipient: Optional[CiphertextType] - KeyMaterialId: Optional[BackingKeyIdType] + PrivateKeyCiphertextBlob: CiphertextType | None + PrivateKeyPlaintext: PlaintextType | None + PublicKey: PublicKeyType | None + KeyId: KeyIdType | None + KeyPairSpec: DataKeyPairSpec | None + CiphertextForRecipient: CiphertextType | None + KeyMaterialId: BackingKeyIdType | None class GenerateDataKeyPairWithoutPlaintextRequest(ServiceRequest): - EncryptionContext: Optional[EncryptionContextType] + EncryptionContext: EncryptionContextType | None KeyId: KeyIdType KeyPairSpec: DataKeyPairSpec - GrantTokens: Optional[GrantTokenList] - DryRun: Optional[NullableBooleanType] + GrantTokens: GrantTokenList | None + DryRun: NullableBooleanType | None class GenerateDataKeyPairWithoutPlaintextResponse(TypedDict, total=False): - PrivateKeyCiphertextBlob: Optional[CiphertextType] - PublicKey: Optional[PublicKeyType] - KeyId: Optional[KeyIdType] - KeyPairSpec: Optional[DataKeyPairSpec] - KeyMaterialId: Optional[BackingKeyIdType] + PrivateKeyCiphertextBlob: CiphertextType | None + PublicKey: PublicKeyType | None + KeyId: KeyIdType | None + KeyPairSpec: DataKeyPairSpec | None + KeyMaterialId: BackingKeyIdType | None class GenerateDataKeyRequest(ServiceRequest): KeyId: KeyIdType - EncryptionContext: Optional[EncryptionContextType] - NumberOfBytes: Optional[NumberOfBytesType] - KeySpec: Optional[DataKeySpec] - GrantTokens: Optional[GrantTokenList] - Recipient: Optional[RecipientInfo] - DryRun: Optional[NullableBooleanType] + EncryptionContext: EncryptionContextType | None + NumberOfBytes: NumberOfBytesType | None + KeySpec: DataKeySpec | None + GrantTokens: GrantTokenList | None + Recipient: RecipientInfo | None + DryRun: NullableBooleanType | None class GenerateDataKeyResponse(TypedDict, total=False): - CiphertextBlob: Optional[CiphertextType] - Plaintext: Optional[PlaintextType] - KeyId: Optional[KeyIdType] - CiphertextForRecipient: Optional[CiphertextType] - KeyMaterialId: Optional[BackingKeyIdType] + CiphertextBlob: CiphertextType | None + Plaintext: PlaintextType | None + KeyId: KeyIdType | None + CiphertextForRecipient: CiphertextType | None + KeyMaterialId: BackingKeyIdType | None class GenerateDataKeyWithoutPlaintextRequest(ServiceRequest): KeyId: KeyIdType - EncryptionContext: Optional[EncryptionContextType] - KeySpec: Optional[DataKeySpec] - NumberOfBytes: Optional[NumberOfBytesType] - GrantTokens: Optional[GrantTokenList] - DryRun: Optional[NullableBooleanType] + EncryptionContext: EncryptionContextType | None + KeySpec: DataKeySpec | None + NumberOfBytes: NumberOfBytesType | None + GrantTokens: GrantTokenList | None + DryRun: NullableBooleanType | None class GenerateDataKeyWithoutPlaintextResponse(TypedDict, total=False): - CiphertextBlob: Optional[CiphertextType] - KeyId: Optional[KeyIdType] - KeyMaterialId: Optional[BackingKeyIdType] + CiphertextBlob: CiphertextType | None + KeyId: KeyIdType | None + KeyMaterialId: BackingKeyIdType | None class GenerateMacRequest(ServiceRequest): Message: PlaintextType KeyId: KeyIdType MacAlgorithm: MacAlgorithmSpec - GrantTokens: Optional[GrantTokenList] - DryRun: Optional[NullableBooleanType] + GrantTokens: GrantTokenList | None + DryRun: NullableBooleanType | None class GenerateMacResponse(TypedDict, total=False): - Mac: Optional[CiphertextType] - MacAlgorithm: Optional[MacAlgorithmSpec] - KeyId: Optional[KeyIdType] + Mac: CiphertextType | None + MacAlgorithm: MacAlgorithmSpec | None + KeyId: KeyIdType | None class GenerateRandomRequest(ServiceRequest): - NumberOfBytes: Optional[NumberOfBytesType] - CustomKeyStoreId: Optional[CustomKeyStoreIdType] - Recipient: Optional[RecipientInfo] + NumberOfBytes: NumberOfBytesType | None + CustomKeyStoreId: CustomKeyStoreIdType | None + Recipient: RecipientInfo | None class GenerateRandomResponse(TypedDict, total=False): - Plaintext: Optional[PlaintextType] - CiphertextForRecipient: Optional[CiphertextType] + Plaintext: PlaintextType | None + CiphertextForRecipient: CiphertextType | None class GetKeyPolicyRequest(ServiceRequest): KeyId: KeyIdType - PolicyName: Optional[PolicyNameType] + PolicyName: PolicyNameType | None class GetKeyPolicyResponse(TypedDict, total=False): - Policy: Optional[PolicyType] - PolicyName: Optional[PolicyNameType] + Policy: PolicyType | None + PolicyName: PolicyNameType | None class GetKeyRotationStatusRequest(ServiceRequest): @@ -999,11 +1013,11 @@ class GetKeyRotationStatusRequest(ServiceRequest): class GetKeyRotationStatusResponse(TypedDict, total=False): - KeyRotationEnabled: Optional[BooleanType] - KeyId: Optional[KeyIdType] - RotationPeriodInDays: Optional[RotationPeriodInDaysType] - NextRotationDate: Optional[DateType] - OnDemandRotationStartDate: Optional[DateType] + KeyRotationEnabled: BooleanType | None + KeyId: KeyIdType | None + RotationPeriodInDays: RotationPeriodInDaysType | None + NextRotationDate: DateType | None + OnDemandRotationStartDate: DateType | None class GetParametersForImportRequest(ServiceRequest): @@ -1013,220 +1027,221 @@ class GetParametersForImportRequest(ServiceRequest): class GetParametersForImportResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - ImportToken: Optional[CiphertextType] - PublicKey: Optional[PlaintextType] - ParametersValidTo: Optional[DateType] + KeyId: KeyIdType | None + ImportToken: CiphertextType | None + PublicKey: PlaintextType | None + ParametersValidTo: DateType | None class GetPublicKeyRequest(ServiceRequest): KeyId: KeyIdType - GrantTokens: Optional[GrantTokenList] + GrantTokens: GrantTokenList | None class GetPublicKeyResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - PublicKey: Optional[PublicKeyType] - CustomerMasterKeySpec: Optional[CustomerMasterKeySpec] - KeySpec: Optional[KeySpec] - KeyUsage: Optional[KeyUsageType] - EncryptionAlgorithms: Optional[EncryptionAlgorithmSpecList] - SigningAlgorithms: Optional[SigningAlgorithmSpecList] - KeyAgreementAlgorithms: Optional[KeyAgreementAlgorithmSpecList] + KeyId: KeyIdType | None + PublicKey: PublicKeyType | None + CustomerMasterKeySpec: CustomerMasterKeySpec | None + KeySpec: KeySpec | None + KeyUsage: KeyUsageType | None + EncryptionAlgorithms: EncryptionAlgorithmSpecList | None + SigningAlgorithms: SigningAlgorithmSpecList | None + KeyAgreementAlgorithms: KeyAgreementAlgorithmSpecList | None class GrantListEntry(TypedDict, total=False): - KeyId: Optional[KeyIdType] - GrantId: Optional[GrantIdType] - Name: Optional[GrantNameType] - CreationDate: Optional[DateType] - GranteePrincipal: Optional[PrincipalIdType] - RetiringPrincipal: Optional[PrincipalIdType] - IssuingAccount: Optional[PrincipalIdType] - Operations: Optional[GrantOperationList] - Constraints: Optional[GrantConstraints] + KeyId: KeyIdType | None + GrantId: GrantIdType | None + Name: GrantNameType | None + CreationDate: DateType | None + GranteePrincipal: PrincipalIdType | None + RetiringPrincipal: PrincipalIdType | None + IssuingAccount: PrincipalIdType | None + Operations: GrantOperationList | None + Constraints: GrantConstraints | None -GrantList = List[GrantListEntry] +GrantList = list[GrantListEntry] class ImportKeyMaterialRequest(ServiceRequest): KeyId: KeyIdType ImportToken: CiphertextType EncryptedKeyMaterial: CiphertextType - ValidTo: Optional[DateType] - ExpirationModel: Optional[ExpirationModelType] - ImportType: Optional[ImportType] - KeyMaterialDescription: Optional[KeyMaterialDescriptionType] - KeyMaterialId: Optional[BackingKeyIdType] + ValidTo: DateType | None + ExpirationModel: ExpirationModelType | None + ImportType: ImportType | None + KeyMaterialDescription: KeyMaterialDescriptionType | None + KeyMaterialId: BackingKeyIdType | None class ImportKeyMaterialResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - KeyMaterialId: Optional[BackingKeyIdType] + KeyId: KeyIdType | None + KeyMaterialId: BackingKeyIdType | None class KeyListEntry(TypedDict, total=False): - KeyId: Optional[KeyIdType] - KeyArn: Optional[ArnType] + KeyId: KeyIdType | None + KeyArn: ArnType | None -KeyList = List[KeyListEntry] +KeyList = list[KeyListEntry] class ListAliasesRequest(ServiceRequest): - KeyId: Optional[KeyIdType] - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + KeyId: KeyIdType | None + Limit: LimitType | None + Marker: MarkerType | None class ListAliasesResponse(TypedDict, total=False): - Aliases: Optional[AliasList] - NextMarker: Optional[MarkerType] - Truncated: Optional[BooleanType] + Aliases: AliasList | None + NextMarker: MarkerType | None + Truncated: BooleanType | None class ListGrantsRequest(ServiceRequest): - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + Limit: LimitType | None + Marker: MarkerType | None KeyId: KeyIdType - GrantId: Optional[GrantIdType] - GranteePrincipal: Optional[PrincipalIdType] + GrantId: GrantIdType | None + GranteePrincipal: PrincipalIdType | None class ListGrantsResponse(TypedDict, total=False): - Grants: Optional[GrantList] - NextMarker: Optional[MarkerType] - Truncated: Optional[BooleanType] + Grants: GrantList | None + NextMarker: MarkerType | None + Truncated: BooleanType | None class ListKeyPoliciesRequest(ServiceRequest): KeyId: KeyIdType - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + Limit: LimitType | None + Marker: MarkerType | None -PolicyNameList = List[PolicyNameType] +PolicyNameList = list[PolicyNameType] class ListKeyPoliciesResponse(TypedDict, total=False): - PolicyNames: Optional[PolicyNameList] - NextMarker: Optional[MarkerType] - Truncated: Optional[BooleanType] + PolicyNames: PolicyNameList | None + NextMarker: MarkerType | None + Truncated: BooleanType | None class ListKeyRotationsRequest(ServiceRequest): KeyId: KeyIdType - IncludeKeyMaterial: Optional[IncludeKeyMaterial] - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + IncludeKeyMaterial: IncludeKeyMaterial | None + Limit: LimitType | None + Marker: MarkerType | None class RotationsListEntry(TypedDict, total=False): - KeyId: Optional[KeyIdType] - KeyMaterialId: Optional[BackingKeyIdType] - KeyMaterialDescription: Optional[KeyMaterialDescriptionType] - ImportState: Optional[ImportState] - KeyMaterialState: Optional[KeyMaterialState] - ExpirationModel: Optional[ExpirationModelType] - ValidTo: Optional[DateType] - RotationDate: Optional[DateType] - RotationType: Optional[RotationType] + KeyId: KeyIdType | None + KeyMaterialId: BackingKeyIdType | None + KeyMaterialDescription: KeyMaterialDescriptionType | None + ImportState: ImportState | None + KeyMaterialState: KeyMaterialState | None + ExpirationModel: ExpirationModelType | None + ValidTo: DateType | None + RotationDate: DateType | None + RotationType: RotationType | None -RotationsList = List[RotationsListEntry] +RotationsList = list[RotationsListEntry] class ListKeyRotationsResponse(TypedDict, total=False): - Rotations: Optional[RotationsList] - NextMarker: Optional[MarkerType] - Truncated: Optional[BooleanType] + Rotations: RotationsList | None + NextMarker: MarkerType | None + Truncated: BooleanType | None class ListKeysRequest(ServiceRequest): - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + Limit: LimitType | None + Marker: MarkerType | None class ListKeysResponse(TypedDict, total=False): - Keys: Optional[KeyList] - NextMarker: Optional[MarkerType] - Truncated: Optional[BooleanType] + Keys: KeyList | None + NextMarker: MarkerType | None + Truncated: BooleanType | None class ListResourceTagsRequest(ServiceRequest): KeyId: KeyIdType - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + Limit: LimitType | None + Marker: MarkerType | None class ListResourceTagsResponse(TypedDict, total=False): - Tags: Optional[TagList] - NextMarker: Optional[MarkerType] - Truncated: Optional[BooleanType] + Tags: TagList | None + NextMarker: MarkerType | None + Truncated: BooleanType | None class ListRetirableGrantsRequest(ServiceRequest): - Limit: Optional[LimitType] - Marker: Optional[MarkerType] + Limit: LimitType | None + Marker: MarkerType | None RetiringPrincipal: PrincipalIdType class PutKeyPolicyRequest(ServiceRequest): KeyId: KeyIdType - PolicyName: Optional[PolicyNameType] + PolicyName: PolicyNameType | None Policy: PolicyType - BypassPolicyLockoutSafetyCheck: Optional[BooleanType] + BypassPolicyLockoutSafetyCheck: BooleanType | None class ReEncryptRequest(ServiceRequest): - CiphertextBlob: CiphertextType - SourceEncryptionContext: Optional[EncryptionContextType] - SourceKeyId: Optional[KeyIdType] + CiphertextBlob: CiphertextType | None + SourceEncryptionContext: EncryptionContextType | None + SourceKeyId: KeyIdType | None DestinationKeyId: KeyIdType - DestinationEncryptionContext: Optional[EncryptionContextType] - SourceEncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] - DestinationEncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] - GrantTokens: Optional[GrantTokenList] - DryRun: Optional[NullableBooleanType] + DestinationEncryptionContext: EncryptionContextType | None + SourceEncryptionAlgorithm: EncryptionAlgorithmSpec | None + DestinationEncryptionAlgorithm: EncryptionAlgorithmSpec | None + GrantTokens: GrantTokenList | None + DryRun: NullableBooleanType | None + DryRunModifiers: DryRunModifierList | None class ReEncryptResponse(TypedDict, total=False): - CiphertextBlob: Optional[CiphertextType] - SourceKeyId: Optional[KeyIdType] - KeyId: Optional[KeyIdType] - SourceEncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] - DestinationEncryptionAlgorithm: Optional[EncryptionAlgorithmSpec] - SourceKeyMaterialId: Optional[BackingKeyIdType] - DestinationKeyMaterialId: Optional[BackingKeyIdType] + CiphertextBlob: CiphertextType | None + SourceKeyId: KeyIdType | None + KeyId: KeyIdType | None + SourceEncryptionAlgorithm: EncryptionAlgorithmSpec | None + DestinationEncryptionAlgorithm: EncryptionAlgorithmSpec | None + SourceKeyMaterialId: BackingKeyIdType | None + DestinationKeyMaterialId: BackingKeyIdType | None class ReplicateKeyRequest(ServiceRequest): KeyId: KeyIdType ReplicaRegion: RegionType - Policy: Optional[PolicyType] - BypassPolicyLockoutSafetyCheck: Optional[BooleanType] - Description: Optional[DescriptionType] - Tags: Optional[TagList] + Policy: PolicyType | None + BypassPolicyLockoutSafetyCheck: BooleanType | None + Description: DescriptionType | None + Tags: TagList | None class ReplicateKeyResponse(TypedDict, total=False): - ReplicaKeyMetadata: Optional[KeyMetadata] - ReplicaPolicy: Optional[PolicyType] - ReplicaTags: Optional[TagList] + ReplicaKeyMetadata: KeyMetadata | None + ReplicaPolicy: PolicyType | None + ReplicaTags: TagList | None class RetireGrantRequest(ServiceRequest): - GrantToken: Optional[GrantTokenType] - KeyId: Optional[KeyIdType] - GrantId: Optional[GrantIdType] - DryRun: Optional[NullableBooleanType] + GrantToken: GrantTokenType | None + KeyId: KeyIdType | None + GrantId: GrantIdType | None + DryRun: NullableBooleanType | None class RevokeGrantRequest(ServiceRequest): KeyId: KeyIdType GrantId: GrantIdType - DryRun: Optional[NullableBooleanType] + DryRun: NullableBooleanType | None class RotateKeyOnDemandRequest(ServiceRequest): @@ -1234,37 +1249,37 @@ class RotateKeyOnDemandRequest(ServiceRequest): class RotateKeyOnDemandResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] + KeyId: KeyIdType | None class ScheduleKeyDeletionRequest(ServiceRequest): KeyId: KeyIdType - PendingWindowInDays: Optional[PendingWindowInDaysType] + PendingWindowInDays: PendingWindowInDaysType | None class ScheduleKeyDeletionResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - DeletionDate: Optional[DateType] - KeyState: Optional[KeyState] - PendingWindowInDays: Optional[PendingWindowInDaysType] + KeyId: KeyIdType | None + DeletionDate: DateType | None + KeyState: KeyState | None + PendingWindowInDays: PendingWindowInDaysType | None class SignRequest(ServiceRequest): KeyId: KeyIdType Message: PlaintextType - MessageType: Optional[MessageType] - GrantTokens: Optional[GrantTokenList] + MessageType: MessageType | None + GrantTokens: GrantTokenList | None SigningAlgorithm: SigningAlgorithmSpec - DryRun: Optional[NullableBooleanType] + DryRun: NullableBooleanType | None class SignResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - Signature: Optional[CiphertextType] - SigningAlgorithm: Optional[SigningAlgorithmSpec] + KeyId: KeyIdType | None + Signature: CiphertextType | None + SigningAlgorithm: SigningAlgorithmSpec | None -TagKeyList = List[TagKeyType] +TagKeyList = list[TagKeyType] class TagResourceRequest(ServiceRequest): @@ -1284,14 +1299,15 @@ class UpdateAliasRequest(ServiceRequest): class UpdateCustomKeyStoreRequest(ServiceRequest): CustomKeyStoreId: CustomKeyStoreIdType - NewCustomKeyStoreName: Optional[CustomKeyStoreNameType] - KeyStorePassword: Optional[KeyStorePasswordType] - CloudHsmClusterId: Optional[CloudHsmClusterIdType] - XksProxyUriEndpoint: Optional[XksProxyUriEndpointType] - XksProxyUriPath: Optional[XksProxyUriPathType] - XksProxyVpcEndpointServiceName: Optional[XksProxyVpcEndpointServiceNameType] - XksProxyAuthenticationCredential: Optional[XksProxyAuthenticationCredentialType] - XksProxyConnectivity: Optional[XksProxyConnectivityType] + NewCustomKeyStoreName: CustomKeyStoreNameType | None + KeyStorePassword: KeyStorePasswordType | None + CloudHsmClusterId: CloudHsmClusterIdType | None + XksProxyUriEndpoint: XksProxyUriEndpointType | None + XksProxyUriPath: XksProxyUriPathType | None + XksProxyVpcEndpointServiceName: XksProxyVpcEndpointServiceNameType | None + XksProxyVpcEndpointServiceOwner: AccountIdType | None + XksProxyAuthenticationCredential: XksProxyAuthenticationCredentialType | None + XksProxyConnectivity: XksProxyConnectivityType | None class UpdateCustomKeyStoreResponse(TypedDict, total=False): @@ -1313,35 +1329,35 @@ class VerifyMacRequest(ServiceRequest): KeyId: KeyIdType MacAlgorithm: MacAlgorithmSpec Mac: CiphertextType - GrantTokens: Optional[GrantTokenList] - DryRun: Optional[NullableBooleanType] + GrantTokens: GrantTokenList | None + DryRun: NullableBooleanType | None class VerifyMacResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - MacValid: Optional[BooleanType] - MacAlgorithm: Optional[MacAlgorithmSpec] + KeyId: KeyIdType | None + MacValid: BooleanType | None + MacAlgorithm: MacAlgorithmSpec | None class VerifyRequest(ServiceRequest): KeyId: KeyIdType Message: PlaintextType - MessageType: Optional[MessageType] + MessageType: MessageType | None Signature: CiphertextType SigningAlgorithm: SigningAlgorithmSpec - GrantTokens: Optional[GrantTokenList] - DryRun: Optional[NullableBooleanType] + GrantTokens: GrantTokenList | None + DryRun: NullableBooleanType | None class VerifyResponse(TypedDict, total=False): - KeyId: Optional[KeyIdType] - SignatureValid: Optional[BooleanType] - SigningAlgorithm: Optional[SigningAlgorithmSpec] + KeyId: KeyIdType | None + SignatureValid: BooleanType | None + SigningAlgorithm: SigningAlgorithmSpec | None class KmsApi: - service = "kms" - version = "2014-11-01" + service: str = "kms" + version: str = "2014-11-01" @handler("CancelKeyDeletion") def cancel_key_deletion( @@ -1373,6 +1389,7 @@ def create_custom_key_store( xks_proxy_uri_endpoint: XksProxyUriEndpointType | None = None, xks_proxy_uri_path: XksProxyUriPathType | None = None, xks_proxy_vpc_endpoint_service_name: XksProxyVpcEndpointServiceNameType | None = None, + xks_proxy_vpc_endpoint_service_owner: AccountIdType | None = None, xks_proxy_authentication_credential: XksProxyAuthenticationCredentialType | None = None, xks_proxy_connectivity: XksProxyConnectivityType | None = None, **kwargs, @@ -1418,13 +1435,14 @@ def create_key( def decrypt( self, context: RequestContext, - ciphertext_blob: CiphertextType, + ciphertext_blob: CiphertextType | None = None, encryption_context: EncryptionContextType | None = None, grant_tokens: GrantTokenList | None = None, key_id: KeyIdType | None = None, encryption_algorithm: EncryptionAlgorithmSpec | None = None, recipient: RecipientInfo | None = None, dry_run: NullableBooleanType | None = None, + dry_run_modifiers: DryRunModifierList | None = None, **kwargs, ) -> DecryptResponse: raise NotImplementedError @@ -1755,8 +1773,8 @@ def put_key_policy( def re_encrypt( self, context: RequestContext, - ciphertext_blob: CiphertextType, destination_key_id: KeyIdType, + ciphertext_blob: CiphertextType | None = None, source_encryption_context: EncryptionContextType | None = None, source_key_id: KeyIdType | None = None, destination_encryption_context: EncryptionContextType | None = None, @@ -1764,6 +1782,7 @@ def re_encrypt( destination_encryption_algorithm: EncryptionAlgorithmSpec | None = None, grant_tokens: GrantTokenList | None = None, dry_run: NullableBooleanType | None = None, + dry_run_modifiers: DryRunModifierList | None = None, **kwargs, ) -> ReEncryptResponse: raise NotImplementedError @@ -1864,6 +1883,7 @@ def update_custom_key_store( xks_proxy_uri_endpoint: XksProxyUriEndpointType | None = None, xks_proxy_uri_path: XksProxyUriPathType | None = None, xks_proxy_vpc_endpoint_service_name: XksProxyVpcEndpointServiceNameType | None = None, + xks_proxy_vpc_endpoint_service_owner: AccountIdType | None = None, xks_proxy_authentication_credential: XksProxyAuthenticationCredentialType | None = None, xks_proxy_connectivity: XksProxyConnectivityType | None = None, **kwargs, diff --git a/localstack-core/localstack/aws/api/lambda_/__init__.py b/localstack-core/localstack/aws/api/lambda_/__init__.py index 0f1e716980e9e..78cd7159eb7f0 100644 --- a/localstack-core/localstack/aws/api/lambda_/__init__.py +++ b/localstack-core/localstack/aws/api/lambda_/__init__.py @@ -1,6 +1,7 @@ +from collections.abc import Iterable, Iterator from datetime import datetime from enum import StrEnum -from typing import IO, Dict, Iterable, Iterator, List, Optional, TypedDict, Union +from typing import IO, TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -9,34 +10,57 @@ Alias = str AllowCredentials = bool Arn = str +AttemptCount = int BatchSize = int BisectBatchOnFunctionError = bool Boolean = bool +CallbackId = str +CapacityProviderArn = str +CapacityProviderMaxVCpuCount = int +CapacityProviderName = str +CheckpointToken = str +ClientToken = str CodeSigningConfigArn = str CodeSigningConfigId = str CollectionName = str DatabaseName = str Description = str DestinationArn = str +DurableExecutionArn = str +DurableExecutionName = str +DurationSeconds = int Enabled = bool Endpoint = str EnvironmentVariableName = str EnvironmentVariableValue = str EphemeralStorageSize = int +ErrorData = str +ErrorMessage = str +ErrorType = str +EventId = int EventSourceMappingArn = str EventSourceToken = str +ExecutionEnvironmentMemoryGiBPerVCpu = float +ExecutionTimeout = int FileSystemArn = str FilterCriteriaErrorCode = str FilterCriteriaErrorMessage = str FunctionArn = str FunctionName = str +FunctionScalingConfigExecutionEnvironments = int FunctionUrl = str FunctionUrlQualifier = str Handler = str Header = str HttpStatus = int +IncludeExecutionData = bool +InputPayload = str +InstanceType = str Integer = int +InvokedViaFunctionUrl = bool +ItemCount = int KMSKeyArn = str +KMSKeyArnNonEmpty = str LastUpdateStatusReason = str LayerArn = str LayerName = str @@ -48,6 +72,7 @@ LogGroup = str MasterRegion = str MaxAge = int +MaxFiftyListItems = int MaxFunctionEventInvokeConfigListItems = int MaxItems = int MaxLayerListItems = int @@ -62,23 +87,36 @@ MaximumRetryAttemptsEventSourceMapping = int MemorySize = int Method = str +MetricTargetValue = float MinimumNumberOfPollers = int NameSpacedFunctionArn = str NamespacedFunctionName = str NamespacedStatementId = str NonNegativeInteger = int NullableBoolean = bool +NumericLatestPublishedOrAliasQualifier = str +OperationId = str +OperationName = str +OperationPayload = str +OperationSubType = str OrganizationId = str Origin = str +OutputPayload = str ParallelizationFactor = int Pattern = str +PerExecutionEnvironmentMaxConcurrency = int PositiveInteger = int Principal = str PrincipalOrgID = str +ProvisionedPollerGroupName = str +PublishedFunctionQualifier = str Qualifier = str Queue = str +ReplayChildren = bool ReservedConcurrentExecutions = int ResourceArn = str +RetentionPeriodInDays = int +ReverseOrder = bool RoleArn = str RuntimeVersionArn = str S3Bucket = str @@ -88,8 +126,10 @@ SecurityGroupId = str SensitiveString = str SourceOwner = str +StackTraceEntry = str StateReason = str StatementId = str +StepOptionsNextAttemptDelaySecondsInteger = int String = str SubnetId = str TagKey = str @@ -97,17 +137,22 @@ TaggableResource = str TagsErrorCode = str TagsErrorMessage = str +TenantId = str Timeout = int Timestamp = str Topic = str +Truncated = bool TumblingWindowInSeconds = int URI = str UnqualifiedFunctionName = str UnreservedConcurrentExecutions = int Version = str +VersionWithLatestPublished = str VpcId = str +WaitOptionsWaitSecondsInteger = int Weight = float WorkingDirectory = str +XAmznTraceId = str class ApplicationLogLevel(StrEnum): @@ -124,6 +169,22 @@ class Architecture(StrEnum): arm64 = "arm64" +class CapacityProviderPredefinedMetricType(StrEnum): + LambdaCapacityProviderAverageCPUUtilization = "LambdaCapacityProviderAverageCPUUtilization" + + +class CapacityProviderScalingMode(StrEnum): + Auto = "Auto" + Manual = "Manual" + + +class CapacityProviderState(StrEnum): + Pending = "Pending" + Active = "Active" + Failed = "Failed" + Deleting = "Deleting" + + class CodeSigningPolicy(StrEnum): Warn = "Warn" Enforce = "Enforce" @@ -135,6 +196,14 @@ class EndPointType(StrEnum): class EventSourceMappingMetric(StrEnum): EventCount = "EventCount" + ErrorCount = "ErrorCount" + KafkaMetrics = "KafkaMetrics" + + +class EventSourceMappingSystemLogLevel(StrEnum): + DEBUG = "DEBUG" + INFO = "INFO" + WARN = "WARN" class EventSourcePosition(StrEnum): @@ -143,6 +212,41 @@ class EventSourcePosition(StrEnum): AT_TIMESTAMP = "AT_TIMESTAMP" +class EventType(StrEnum): + ExecutionStarted = "ExecutionStarted" + ExecutionSucceeded = "ExecutionSucceeded" + ExecutionFailed = "ExecutionFailed" + ExecutionTimedOut = "ExecutionTimedOut" + ExecutionStopped = "ExecutionStopped" + ContextStarted = "ContextStarted" + ContextSucceeded = "ContextSucceeded" + ContextFailed = "ContextFailed" + WaitStarted = "WaitStarted" + WaitSucceeded = "WaitSucceeded" + WaitCancelled = "WaitCancelled" + StepStarted = "StepStarted" + StepSucceeded = "StepSucceeded" + StepFailed = "StepFailed" + ChainedInvokeStarted = "ChainedInvokeStarted" + ChainedInvokeSucceeded = "ChainedInvokeSucceeded" + ChainedInvokeFailed = "ChainedInvokeFailed" + ChainedInvokeTimedOut = "ChainedInvokeTimedOut" + ChainedInvokeStopped = "ChainedInvokeStopped" + CallbackStarted = "CallbackStarted" + CallbackSucceeded = "CallbackSucceeded" + CallbackFailed = "CallbackFailed" + CallbackTimedOut = "CallbackTimedOut" + InvocationCompleted = "InvocationCompleted" + + +class ExecutionStatus(StrEnum): + RUNNING = "RUNNING" + SUCCEEDED = "SUCCEEDED" + FAILED = "FAILED" + TIMED_OUT = "TIMED_OUT" + STOPPED = "STOPPED" + + class FullDocument(StrEnum): UpdateLookup = "UpdateLookup" Default = "Default" @@ -161,6 +265,10 @@ class FunctionVersion(StrEnum): ALL = "ALL" +class FunctionVersionLatestPublished(StrEnum): + LATEST_PUBLISHED = "LATEST_PUBLISHED" + + class InvocationType(StrEnum): Event = "Event" RequestResponse = "RequestResponse" @@ -211,6 +319,19 @@ class LastUpdateStatusReasonCode(StrEnum): InvalidRuntime = "InvalidRuntime" InvalidZipFileException = "InvalidZipFileException" FunctionError = "FunctionError" + VcpuLimitExceeded = "VcpuLimitExceeded" + CapacityProviderScalingLimitExceeded = "CapacityProviderScalingLimitExceeded" + InsufficientCapacity = "InsufficientCapacity" + EC2RequestLimitExceeded = "EC2RequestLimitExceeded" + FunctionError_InitTimeout = "FunctionError.InitTimeout" + FunctionError_RuntimeInitError = "FunctionError.RuntimeInitError" + FunctionError_ExtensionInitError = "FunctionError.ExtensionInitError" + FunctionError_InvalidEntryPoint = "FunctionError.InvalidEntryPoint" + FunctionError_InvalidWorkingDirectory = "FunctionError.InvalidWorkingDirectory" + FunctionError_PermissionDenied = "FunctionError.PermissionDenied" + FunctionError_TooManyExtensions = "FunctionError.TooManyExtensions" + FunctionError_InitResourceExhausted = "FunctionError.InitResourceExhausted" + DisallowedByVpcEncryptionControl = "DisallowedByVpcEncryptionControl" class LogFormat(StrEnum): @@ -223,6 +344,34 @@ class LogType(StrEnum): Tail = "Tail" +class OperationAction(StrEnum): + START = "START" + SUCCEED = "SUCCEED" + FAIL = "FAIL" + RETRY = "RETRY" + CANCEL = "CANCEL" + + +class OperationStatus(StrEnum): + STARTED = "STARTED" + PENDING = "PENDING" + READY = "READY" + SUCCEEDED = "SUCCEEDED" + FAILED = "FAILED" + CANCELLED = "CANCELLED" + TIMED_OUT = "TIMED_OUT" + STOPPED = "STOPPED" + + +class OperationType(StrEnum): + EXECUTION = "EXECUTION" + CONTEXT = "CONTEXT" + STEP = "STEP" + WAIT = "WAIT" + CALLBACK = "CALLBACK" + CHAINED_INVOKE = "CHAINED_INVOKE" + + class PackageType(StrEnum): Zip = "Zip" Image = "Image" @@ -286,6 +435,10 @@ class Runtime(StrEnum): java21 = "java21" python3_13 = "python3.13" nodejs22_x = "nodejs22.x" + nodejs24_x = "nodejs24.x" + python3_14 = "python3.14" + java25 = "java25" + dotnet10 = "dotnet10" class SchemaRegistryEventRecordFormat(StrEnum): @@ -319,6 +472,10 @@ class State(StrEnum): Active = "Active" Inactive = "Inactive" Failed = "Failed" + Deactivating = "Deactivating" + Deactivated = "Deactivated" + ActiveNonInvocable = "ActiveNonInvocable" + Deleting = "Deleting" class StateReasonCode(StrEnum): @@ -346,6 +503,20 @@ class StateReasonCode(StrEnum): InvalidRuntime = "InvalidRuntime" InvalidZipFileException = "InvalidZipFileException" FunctionError = "FunctionError" + DrainingDurableExecutions = "DrainingDurableExecutions" + VcpuLimitExceeded = "VcpuLimitExceeded" + CapacityProviderScalingLimitExceeded = "CapacityProviderScalingLimitExceeded" + InsufficientCapacity = "InsufficientCapacity" + EC2RequestLimitExceeded = "EC2RequestLimitExceeded" + FunctionError_InitTimeout = "FunctionError.InitTimeout" + FunctionError_RuntimeInitError = "FunctionError.RuntimeInitError" + FunctionError_ExtensionInitError = "FunctionError.ExtensionInitError" + FunctionError_InvalidEntryPoint = "FunctionError.InvalidEntryPoint" + FunctionError_InvalidWorkingDirectory = "FunctionError.InvalidWorkingDirectory" + FunctionError_PermissionDenied = "FunctionError.PermissionDenied" + FunctionError_TooManyExtensions = "FunctionError.TooManyExtensions" + FunctionError_InitResourceExhausted = "FunctionError.InitResourceExhausted" + DisallowedByVpcEncryptionControl = "DisallowedByVpcEncryptionControl" class SystemLogLevel(StrEnum): @@ -354,6 +525,10 @@ class SystemLogLevel(StrEnum): WARN = "WARN" +class TenantIsolationMode(StrEnum): + PER_TENANT = "PER_TENANT" + + class ThrottleReason(StrEnum): ConcurrentInvocationLimitExceeded = "ConcurrentInvocationLimitExceeded" FunctionInvocationRateLimitExceeded = "FunctionInvocationRateLimitExceeded" @@ -376,289 +551,331 @@ class UpdateRuntimeOn(StrEnum): FunctionUpdate = "FunctionUpdate" +class CallbackTimeoutException(ServiceException): + code: str = "CallbackTimeoutException" + sender_fault: bool = True + status_code: int = 400 + Type: String | None + + +class CapacityProviderLimitExceededException(ServiceException): + code: str = "CapacityProviderLimitExceededException" + sender_fault: bool = True + status_code: int = 400 + Type: String | None + + class CodeSigningConfigNotFoundException(ServiceException): code: str = "CodeSigningConfigNotFoundException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 404 - Type: Optional[String] + Type: String | None class CodeStorageExceededException(ServiceException): code: str = "CodeStorageExceededException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class CodeVerificationFailedException(ServiceException): code: str = "CodeVerificationFailedException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None + + +class DurableExecutionAlreadyStartedException(ServiceException): + code: str = "DurableExecutionAlreadyStartedException" + sender_fault: bool = True + status_code: int = 409 + Type: String | None class EC2AccessDeniedException(ServiceException): code: str = "EC2AccessDeniedException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class EC2ThrottledException(ServiceException): code: str = "EC2ThrottledException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class EC2UnexpectedException(ServiceException): code: str = "EC2UnexpectedException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] - EC2ErrorCode: Optional[String] + Type: String | None + EC2ErrorCode: String | None class EFSIOException(ServiceException): code: str = "EFSIOException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 410 - Type: Optional[String] + Type: String | None class EFSMountConnectivityException(ServiceException): code: str = "EFSMountConnectivityException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 408 - Type: Optional[String] + Type: String | None class EFSMountFailureException(ServiceException): code: str = "EFSMountFailureException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 403 - Type: Optional[String] + Type: String | None class EFSMountTimeoutException(ServiceException): code: str = "EFSMountTimeoutException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 408 - Type: Optional[String] + Type: String | None class ENILimitReachedException(ServiceException): code: str = "ENILimitReachedException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None + + +class FunctionVersionsPerCapacityProviderLimitExceededException(ServiceException): + code: str = "FunctionVersionsPerCapacityProviderLimitExceededException" + sender_fault: bool = True + status_code: int = 400 + Type: String | None class InvalidCodeSignatureException(ServiceException): code: str = "InvalidCodeSignatureException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class InvalidParameterValueException(ServiceException): code: str = "InvalidParameterValueException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class InvalidRequestContentException(ServiceException): code: str = "InvalidRequestContentException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class InvalidRuntimeException(ServiceException): code: str = "InvalidRuntimeException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class InvalidSecurityGroupIDException(ServiceException): code: str = "InvalidSecurityGroupIDException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class InvalidSubnetIDException(ServiceException): code: str = "InvalidSubnetIDException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class InvalidZipFileException(ServiceException): code: str = "InvalidZipFileException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class KMSAccessDeniedException(ServiceException): code: str = "KMSAccessDeniedException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class KMSDisabledException(ServiceException): code: str = "KMSDisabledException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class KMSInvalidStateException(ServiceException): code: str = "KMSInvalidStateException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class KMSNotFoundException(ServiceException): code: str = "KMSNotFoundException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None + + +class NoPublishedVersionException(ServiceException): + code: str = "NoPublishedVersionException" + sender_fault: bool = True + status_code: int = 400 + Type: String | None class PolicyLengthExceededException(ServiceException): code: str = "PolicyLengthExceededException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class PreconditionFailedException(ServiceException): code: str = "PreconditionFailedException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 412 - Type: Optional[String] + Type: String | None class ProvisionedConcurrencyConfigNotFoundException(ServiceException): code: str = "ProvisionedConcurrencyConfigNotFoundException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 404 - Type: Optional[String] + Type: String | None class RecursiveInvocationException(ServiceException): code: str = "RecursiveInvocationException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class RequestTooLargeException(ServiceException): code: str = "RequestTooLargeException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 413 - Type: Optional[String] + Type: String | None class ResourceConflictException(ServiceException): code: str = "ResourceConflictException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 409 - Type: Optional[String] + Type: String | None class ResourceInUseException(ServiceException): code: str = "ResourceInUseException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class ResourceNotFoundException(ServiceException): code: str = "ResourceNotFoundException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 404 - Type: Optional[String] + Type: String | None class ResourceNotReadyException(ServiceException): code: str = "ResourceNotReadyException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None + + +class SerializedRequestEntityTooLargeException(ServiceException): + code: str = "SerializedRequestEntityTooLargeException" + sender_fault: bool = True + status_code: int = 413 + Type: String | None class ServiceException(ServiceException): code: str = "ServiceException" sender_fault: bool = False status_code: int = 500 - Type: Optional[String] + Type: String | None class SnapStartException(ServiceException): code: str = "SnapStartException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 400 - Type: Optional[String] + Type: String | None class SnapStartNotReadyException(ServiceException): code: str = "SnapStartNotReadyException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 409 - Type: Optional[String] + Type: String | None class SnapStartTimeoutException(ServiceException): code: str = "SnapStartTimeoutException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 408 - Type: Optional[String] + Type: String | None class SubnetIPAddressLimitReachedException(ServiceException): code: str = "SubnetIPAddressLimitReachedException" sender_fault: bool = False status_code: int = 502 - Type: Optional[String] + Type: String | None class TooManyRequestsException(ServiceException): code: str = "TooManyRequestsException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 429 - retryAfterSeconds: Optional[String] - Type: Optional[String] - Reason: Optional[ThrottleReason] + retryAfterSeconds: String | None + Type: String | None + Reason: ThrottleReason | None class UnsupportedMediaTypeException(ServiceException): code: str = "UnsupportedMediaTypeException" - sender_fault: bool = False + sender_fault: bool = True status_code: int = 415 - Type: Optional[String] + Type: String | None Long = int class AccountLimit(TypedDict, total=False): - TotalCodeSize: Optional[Long] - CodeSizeUnzipped: Optional[Long] - CodeSizeZipped: Optional[Long] - ConcurrentExecutions: Optional[Integer] - UnreservedConcurrentExecutions: Optional[UnreservedConcurrentExecutions] + TotalCodeSize: Long | None + CodeSizeUnzipped: Long | None + CodeSizeZipped: Long | None + ConcurrentExecutions: Integer | None + UnreservedConcurrentExecutions: UnreservedConcurrentExecutions | None class AccountUsage(TypedDict, total=False): - TotalCodeSize: Optional[Long] - FunctionCount: Optional[Long] + TotalCodeSize: Long | None + FunctionCount: Long | None LayerVersionNumber = int @@ -670,53 +887,54 @@ class AddLayerVersionPermissionRequest(ServiceRequest): StatementId: StatementId Action: LayerPermissionAllowedAction Principal: LayerPermissionAllowedPrincipal - OrganizationId: Optional[OrganizationId] - RevisionId: Optional[String] + OrganizationId: OrganizationId | None + RevisionId: String | None class AddLayerVersionPermissionResponse(TypedDict, total=False): - Statement: Optional[String] - RevisionId: Optional[String] + Statement: String | None + RevisionId: String | None class AddPermissionRequest(ServiceRequest): - FunctionName: FunctionName + FunctionName: NamespacedFunctionName StatementId: StatementId Action: Action Principal: Principal - SourceArn: Optional[Arn] - SourceAccount: Optional[SourceOwner] - EventSourceToken: Optional[EventSourceToken] - Qualifier: Optional[Qualifier] - RevisionId: Optional[String] - PrincipalOrgID: Optional[PrincipalOrgID] - FunctionUrlAuthType: Optional[FunctionUrlAuthType] + SourceArn: Arn | None + SourceAccount: SourceOwner | None + EventSourceToken: EventSourceToken | None + Qualifier: NumericLatestPublishedOrAliasQualifier | None + RevisionId: String | None + PrincipalOrgID: PrincipalOrgID | None + FunctionUrlAuthType: FunctionUrlAuthType | None + InvokedViaFunctionUrl: InvokedViaFunctionUrl | None class AddPermissionResponse(TypedDict, total=False): - Statement: Optional[String] + Statement: String | None -AdditionalVersionWeights = Dict[AdditionalVersion, Weight] +AdditionalVersionWeights = dict[AdditionalVersion, Weight] class AliasRoutingConfiguration(TypedDict, total=False): - AdditionalVersionWeights: Optional[AdditionalVersionWeights] + AdditionalVersionWeights: AdditionalVersionWeights | None class AliasConfiguration(TypedDict, total=False): - AliasArn: Optional[FunctionArn] - Name: Optional[Alias] - FunctionVersion: Optional[Version] - Description: Optional[Description] - RoutingConfig: Optional[AliasRoutingConfiguration] - RevisionId: Optional[String] + AliasArn: FunctionArn | None + Name: Alias | None + FunctionVersion: Version | None + Description: Description | None + RoutingConfig: AliasRoutingConfiguration | None + RevisionId: String | None -AliasList = List[AliasConfiguration] -AllowMethodsList = List[Method] -AllowOriginsList = List[Origin] -SigningProfileVersionArns = List[Arn] +AliasList = list[AliasConfiguration] +AllowMethodsList = list[Method] +AllowOriginsList = list[Origin] +SigningProfileVersionArns = list[Arn] class AllowedPublishers(TypedDict, total=False): @@ -724,87 +942,352 @@ class AllowedPublishers(TypedDict, total=False): class KafkaSchemaValidationConfig(TypedDict, total=False): - Attribute: Optional[KafkaSchemaValidationAttribute] + Attribute: KafkaSchemaValidationAttribute | None -KafkaSchemaValidationConfigList = List[KafkaSchemaValidationConfig] +KafkaSchemaValidationConfigList = list[KafkaSchemaValidationConfig] class KafkaSchemaRegistryAccessConfig(TypedDict, total=False): - Type: Optional[KafkaSchemaRegistryAuthType] - URI: Optional[Arn] + Type: KafkaSchemaRegistryAuthType | None + URI: Arn | None -KafkaSchemaRegistryAccessConfigList = List[KafkaSchemaRegistryAccessConfig] +KafkaSchemaRegistryAccessConfigList = list[KafkaSchemaRegistryAccessConfig] class KafkaSchemaRegistryConfig(TypedDict, total=False): - SchemaRegistryURI: Optional[SchemaRegistryUri] - EventRecordFormat: Optional[SchemaRegistryEventRecordFormat] - AccessConfigs: Optional[KafkaSchemaRegistryAccessConfigList] - SchemaValidationConfigs: Optional[KafkaSchemaValidationConfigList] + SchemaRegistryURI: SchemaRegistryUri | None + EventRecordFormat: SchemaRegistryEventRecordFormat | None + AccessConfigs: KafkaSchemaRegistryAccessConfigList | None + SchemaValidationConfigs: KafkaSchemaValidationConfigList | None class AmazonManagedKafkaEventSourceConfig(TypedDict, total=False): - ConsumerGroupId: Optional[URI] - SchemaRegistryConfig: Optional[KafkaSchemaRegistryConfig] + ConsumerGroupId: URI | None + SchemaRegistryConfig: KafkaSchemaRegistryConfig | None -ArchitecturesList = List[Architecture] +ArchitecturesList = list[Architecture] +BinaryOperationPayload = bytes Blob = bytes BlobStream = bytes +StackTraceEntries = list[StackTraceEntry] + + +class ErrorObject(TypedDict, total=False): + ErrorMessage: ErrorMessage | None + ErrorType: ErrorType | None + ErrorData: ErrorData | None + StackTrace: StackTraceEntries | None + + +class CallbackDetails(TypedDict, total=False): + CallbackId: CallbackId | None + Result: OperationPayload | None + Error: ErrorObject | None + + +class EventError(TypedDict, total=False): + Payload: ErrorObject | None + Truncated: Truncated | None + + +class CallbackFailedDetails(TypedDict, total=False): + Error: EventError + + +class CallbackOptions(TypedDict, total=False): + TimeoutSeconds: DurationSeconds | None + HeartbeatTimeoutSeconds: DurationSeconds | None + + +class CallbackStartedDetails(TypedDict, total=False): + CallbackId: CallbackId + HeartbeatTimeout: DurationSeconds | None + Timeout: DurationSeconds | None + + +class EventResult(TypedDict, total=False): + Payload: OperationPayload | None + Truncated: Truncated | None + + +class CallbackSucceededDetails(TypedDict, total=False): + Result: EventResult + + +class CallbackTimedOutDetails(TypedDict, total=False): + Error: EventError + + +class TargetTrackingScalingPolicy(TypedDict, total=False): + PredefinedMetricType: CapacityProviderPredefinedMetricType + TargetValue: MetricTargetValue + + +CapacityProviderScalingPoliciesList = list[TargetTrackingScalingPolicy] + + +class CapacityProviderScalingConfig(TypedDict, total=False): + MaxVCpuCount: CapacityProviderMaxVCpuCount | None + ScalingMode: CapacityProviderScalingMode | None + ScalingPolicies: CapacityProviderScalingPoliciesList | None + + +InstanceTypeSet = list[InstanceType] + + +class InstanceRequirements(TypedDict, total=False): + Architectures: ArchitecturesList | None + AllowedInstanceTypes: InstanceTypeSet | None + ExcludedInstanceTypes: InstanceTypeSet | None + + +class CapacityProviderPermissionsConfig(TypedDict, total=False): + CapacityProviderOperatorRoleArn: RoleArn + + +CapacityProviderSecurityGroupIds = list[SecurityGroupId] +CapacityProviderSubnetIds = list[SubnetId] + + +class CapacityProviderVpcConfig(TypedDict, total=False): + SubnetIds: CapacityProviderSubnetIds + SecurityGroupIds: CapacityProviderSecurityGroupIds + + +class CapacityProvider(TypedDict, total=False): + CapacityProviderArn: CapacityProviderArn + State: CapacityProviderState + VpcConfig: CapacityProviderVpcConfig + PermissionsConfig: CapacityProviderPermissionsConfig + InstanceRequirements: InstanceRequirements | None + CapacityProviderScalingConfig: CapacityProviderScalingConfig | None + KmsKeyArn: KMSKeyArn | None + LastModified: Timestamp | None + + +class LambdaManagedInstancesCapacityProviderConfig(TypedDict, total=False): + CapacityProviderArn: CapacityProviderArn + PerExecutionEnvironmentMaxConcurrency: PerExecutionEnvironmentMaxConcurrency | None + ExecutionEnvironmentMemoryGiBPerVCpu: ExecutionEnvironmentMemoryGiBPerVCpu | None + + +class CapacityProviderConfig(TypedDict, total=False): + LambdaManagedInstancesCapacityProviderConfig: LambdaManagedInstancesCapacityProviderConfig + + +CapacityProvidersList = list[CapacityProvider] + + +class ChainedInvokeDetails(TypedDict, total=False): + Result: OperationPayload | None + Error: ErrorObject | None + + +class ChainedInvokeFailedDetails(TypedDict, total=False): + Error: EventError + + +class ChainedInvokeOptions(TypedDict, total=False): + FunctionName: NamespacedFunctionName + TenantId: TenantId | None + + +class EventInput(TypedDict, total=False): + Payload: InputPayload | None + Truncated: Truncated | None + + +class ChainedInvokeStartedDetails(TypedDict, total=False): + FunctionName: NamespacedFunctionName + TenantId: TenantId | None + Input: EventInput | None + ExecutedVersion: VersionWithLatestPublished | None + DurableExecutionArn: DurableExecutionArn | None + + +class ChainedInvokeStoppedDetails(TypedDict, total=False): + Error: EventError + + +class ChainedInvokeSucceededDetails(TypedDict, total=False): + Result: EventResult + + +class ChainedInvokeTimedOutDetails(TypedDict, total=False): + Error: EventError + + +class WaitOptions(TypedDict, total=False): + WaitSeconds: WaitOptionsWaitSecondsInteger | None + + +class StepOptions(TypedDict, total=False): + NextAttemptDelaySeconds: StepOptionsNextAttemptDelaySecondsInteger | None + + +class ContextOptions(TypedDict, total=False): + ReplayChildren: ReplayChildren | None + + +class OperationUpdate(TypedDict, total=False): + Id: OperationId + ParentId: OperationId | None + Name: OperationName | None + Type: OperationType + SubType: OperationSubType | None + Action: OperationAction + Payload: OperationPayload | None + Error: ErrorObject | None + ContextOptions: ContextOptions | None + StepOptions: StepOptions | None + WaitOptions: WaitOptions | None + CallbackOptions: CallbackOptions | None + ChainedInvokeOptions: ChainedInvokeOptions | None + + +OperationUpdates = list[OperationUpdate] + + +class CheckpointDurableExecutionRequest(ServiceRequest): + DurableExecutionArn: DurableExecutionArn + CheckpointToken: CheckpointToken + Updates: OperationUpdates | None + ClientToken: ClientToken | None + + +ExecutionTimestamp = datetime + + +class WaitDetails(TypedDict, total=False): + ScheduledEndTimestamp: ExecutionTimestamp | None + + +class StepDetails(TypedDict, total=False): + Attempt: AttemptCount | None + NextAttemptTimestamp: ExecutionTimestamp | None + Result: OperationPayload | None + Error: ErrorObject | None + + +class ContextDetails(TypedDict, total=False): + ReplayChildren: ReplayChildren | None + Result: OperationPayload | None + Error: ErrorObject | None + + +class ExecutionDetails(TypedDict, total=False): + InputPayload: InputPayload | None + + +class Operation(TypedDict, total=False): + Id: OperationId + ParentId: OperationId | None + Name: OperationName | None + Type: OperationType + SubType: OperationSubType | None + StartTimestamp: ExecutionTimestamp + EndTimestamp: ExecutionTimestamp | None + Status: OperationStatus + ExecutionDetails: ExecutionDetails | None + ContextDetails: ContextDetails | None + StepDetails: StepDetails | None + WaitDetails: WaitDetails | None + CallbackDetails: CallbackDetails | None + ChainedInvokeDetails: ChainedInvokeDetails | None + + +Operations = list[Operation] + + +class CheckpointUpdatedExecutionState(TypedDict, total=False): + Operations: Operations | None + NextMarker: String | None + + +class CheckpointDurableExecutionResponse(TypedDict, total=False): + CheckpointToken: CheckpointToken | None + NewExecutionState: CheckpointUpdatedExecutionState class CodeSigningPolicies(TypedDict, total=False): - UntrustedArtifactOnDeployment: Optional[CodeSigningPolicy] + UntrustedArtifactOnDeployment: CodeSigningPolicy | None class CodeSigningConfig(TypedDict, total=False): CodeSigningConfigId: CodeSigningConfigId CodeSigningConfigArn: CodeSigningConfigArn - Description: Optional[Description] + Description: Description | None AllowedPublishers: AllowedPublishers CodeSigningPolicies: CodeSigningPolicies LastModified: Timestamp -CodeSigningConfigList = List[CodeSigningConfig] -CompatibleArchitectures = List[Architecture] -CompatibleRuntimes = List[Runtime] +CodeSigningConfigList = list[CodeSigningConfig] +CompatibleArchitectures = list[Architecture] +CompatibleRuntimes = list[Runtime] class Concurrency(TypedDict, total=False): - ReservedConcurrentExecutions: Optional[ReservedConcurrentExecutions] + ReservedConcurrentExecutions: ReservedConcurrentExecutions | None + + +class ContextFailedDetails(TypedDict, total=False): + Error: EventError + + +class ContextStartedDetails(TypedDict, total=False): + pass + +class ContextSucceededDetails(TypedDict, total=False): + Result: EventResult -HeadersList = List[Header] + +HeadersList = list[Header] class Cors(TypedDict, total=False): - AllowCredentials: Optional[AllowCredentials] - AllowHeaders: Optional[HeadersList] - AllowMethods: Optional[AllowMethodsList] - AllowOrigins: Optional[AllowOriginsList] - ExposeHeaders: Optional[HeadersList] - MaxAge: Optional[MaxAge] + AllowCredentials: AllowCredentials | None + AllowHeaders: HeadersList | None + AllowMethods: AllowMethodsList | None + AllowOrigins: AllowOriginsList | None + ExposeHeaders: HeadersList | None + MaxAge: MaxAge | None class CreateAliasRequest(ServiceRequest): FunctionName: FunctionName Name: Alias - FunctionVersion: Version - Description: Optional[Description] - RoutingConfig: Optional[AliasRoutingConfiguration] + FunctionVersion: VersionWithLatestPublished + Description: Description | None + RoutingConfig: AliasRoutingConfiguration | None + + +Tags = dict[TagKey, TagValue] + +class CreateCapacityProviderRequest(ServiceRequest): + CapacityProviderName: CapacityProviderName + VpcConfig: CapacityProviderVpcConfig + PermissionsConfig: CapacityProviderPermissionsConfig + InstanceRequirements: InstanceRequirements | None + CapacityProviderScalingConfig: CapacityProviderScalingConfig | None + KmsKeyArn: KMSKeyArnNonEmpty | None + Tags: Tags | None -Tags = Dict[TagKey, TagValue] + +class CreateCapacityProviderResponse(TypedDict, total=False): + CapacityProvider: CapacityProvider class CreateCodeSigningConfigRequest(ServiceRequest): - Description: Optional[Description] + Description: Description | None AllowedPublishers: AllowedPublishers - CodeSigningPolicies: Optional[CodeSigningPolicies] - Tags: Optional[Tags] + CodeSigningPolicies: CodeSigningPolicies | None + Tags: Tags | None class CreateCodeSigningConfigResponse(TypedDict, total=False): @@ -812,130 +1295,145 @@ class CreateCodeSigningConfigResponse(TypedDict, total=False): class ProvisionedPollerConfig(TypedDict, total=False): - MinimumPollers: Optional[MinimumNumberOfPollers] - MaximumPollers: Optional[MaximumNumberOfPollers] + MinimumPollers: MinimumNumberOfPollers | None + MaximumPollers: MaximumNumberOfPollers | None + PollerGroupName: ProvisionedPollerGroupName | None + + +class EventSourceMappingLoggingConfig(TypedDict, total=False): + SystemLogLevel: EventSourceMappingSystemLogLevel | None -EventSourceMappingMetricList = List[EventSourceMappingMetric] +EventSourceMappingMetricList = list[EventSourceMappingMetric] class EventSourceMappingMetricsConfig(TypedDict, total=False): - Metrics: Optional[EventSourceMappingMetricList] + Metrics: EventSourceMappingMetricList | None class DocumentDBEventSourceConfig(TypedDict, total=False): - DatabaseName: Optional[DatabaseName] - CollectionName: Optional[CollectionName] - FullDocument: Optional[FullDocument] + DatabaseName: DatabaseName | None + CollectionName: CollectionName | None + FullDocument: FullDocument | None class ScalingConfig(TypedDict, total=False): - MaximumConcurrency: Optional[MaximumConcurrency] + MaximumConcurrency: MaximumConcurrency | None class SelfManagedKafkaEventSourceConfig(TypedDict, total=False): - ConsumerGroupId: Optional[URI] - SchemaRegistryConfig: Optional[KafkaSchemaRegistryConfig] + ConsumerGroupId: URI | None + SchemaRegistryConfig: KafkaSchemaRegistryConfig | None -FunctionResponseTypeList = List[FunctionResponseType] -EndpointLists = List[Endpoint] -Endpoints = Dict[EndPointType, EndpointLists] +FunctionResponseTypeList = list[FunctionResponseType] +EndpointLists = list[Endpoint] +Endpoints = dict[EndPointType, EndpointLists] class SelfManagedEventSource(TypedDict, total=False): - Endpoints: Optional[Endpoints] + Endpoints: Endpoints | None class SourceAccessConfiguration(TypedDict, total=False): - Type: Optional[SourceAccessType] - URI: Optional[URI] + Type: SourceAccessType | None + URI: URI | None -SourceAccessConfigurations = List[SourceAccessConfiguration] -Queues = List[Queue] -Topics = List[Topic] +SourceAccessConfigurations = list[SourceAccessConfiguration] +Queues = list[Queue] +Topics = list[Topic] class OnFailure(TypedDict, total=False): - Destination: Optional[DestinationArn] + Destination: DestinationArn | None class OnSuccess(TypedDict, total=False): - Destination: Optional[DestinationArn] + Destination: DestinationArn | None class DestinationConfig(TypedDict, total=False): - OnSuccess: Optional[OnSuccess] - OnFailure: Optional[OnFailure] + OnSuccess: OnSuccess | None + OnFailure: OnFailure | None Date = datetime class Filter(TypedDict, total=False): - Pattern: Optional[Pattern] + Pattern: Pattern | None -FilterList = List[Filter] +FilterList = list[Filter] class FilterCriteria(TypedDict, total=False): - Filters: Optional[FilterList] + Filters: FilterList | None class CreateEventSourceMappingRequest(ServiceRequest): - EventSourceArn: Optional[Arn] - FunctionName: FunctionName - Enabled: Optional[Enabled] - BatchSize: Optional[BatchSize] - FilterCriteria: Optional[FilterCriteria] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - ParallelizationFactor: Optional[ParallelizationFactor] - StartingPosition: Optional[EventSourcePosition] - StartingPositionTimestamp: Optional[Date] - DestinationConfig: Optional[DestinationConfig] - MaximumRecordAgeInSeconds: Optional[MaximumRecordAgeInSeconds] - BisectBatchOnFunctionError: Optional[BisectBatchOnFunctionError] - MaximumRetryAttempts: Optional[MaximumRetryAttemptsEventSourceMapping] - Tags: Optional[Tags] - TumblingWindowInSeconds: Optional[TumblingWindowInSeconds] - Topics: Optional[Topics] - Queues: Optional[Queues] - SourceAccessConfigurations: Optional[SourceAccessConfigurations] - SelfManagedEventSource: Optional[SelfManagedEventSource] - FunctionResponseTypes: Optional[FunctionResponseTypeList] - AmazonManagedKafkaEventSourceConfig: Optional[AmazonManagedKafkaEventSourceConfig] - SelfManagedKafkaEventSourceConfig: Optional[SelfManagedKafkaEventSourceConfig] - ScalingConfig: Optional[ScalingConfig] - DocumentDBEventSourceConfig: Optional[DocumentDBEventSourceConfig] - KMSKeyArn: Optional[KMSKeyArn] - MetricsConfig: Optional[EventSourceMappingMetricsConfig] - ProvisionedPollerConfig: Optional[ProvisionedPollerConfig] + EventSourceArn: Arn | None + FunctionName: NamespacedFunctionName + Enabled: Enabled | None + BatchSize: BatchSize | None + FilterCriteria: FilterCriteria | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + ParallelizationFactor: ParallelizationFactor | None + StartingPosition: EventSourcePosition | None + StartingPositionTimestamp: Date | None + DestinationConfig: DestinationConfig | None + MaximumRecordAgeInSeconds: MaximumRecordAgeInSeconds | None + BisectBatchOnFunctionError: BisectBatchOnFunctionError | None + MaximumRetryAttempts: MaximumRetryAttemptsEventSourceMapping | None + Tags: Tags | None + TumblingWindowInSeconds: TumblingWindowInSeconds | None + Topics: Topics | None + Queues: Queues | None + SourceAccessConfigurations: SourceAccessConfigurations | None + SelfManagedEventSource: SelfManagedEventSource | None + FunctionResponseTypes: FunctionResponseTypeList | None + AmazonManagedKafkaEventSourceConfig: AmazonManagedKafkaEventSourceConfig | None + SelfManagedKafkaEventSourceConfig: SelfManagedKafkaEventSourceConfig | None + ScalingConfig: ScalingConfig | None + DocumentDBEventSourceConfig: DocumentDBEventSourceConfig | None + KMSKeyArn: KMSKeyArn | None + MetricsConfig: EventSourceMappingMetricsConfig | None + LoggingConfig: EventSourceMappingLoggingConfig | None + ProvisionedPollerConfig: ProvisionedPollerConfig | None + + +class TenancyConfig(TypedDict, total=False): + TenantIsolationMode: TenantIsolationMode + + +class DurableConfig(TypedDict, total=False): + RetentionPeriodInDays: RetentionPeriodInDays | None + ExecutionTimeout: ExecutionTimeout | None class LoggingConfig(TypedDict, total=False): - LogFormat: Optional[LogFormat] - ApplicationLogLevel: Optional[ApplicationLogLevel] - SystemLogLevel: Optional[SystemLogLevel] - LogGroup: Optional[LogGroup] + LogFormat: LogFormat | None + ApplicationLogLevel: ApplicationLogLevel | None + SystemLogLevel: SystemLogLevel | None + LogGroup: LogGroup | None class SnapStart(TypedDict, total=False): - ApplyOn: Optional[SnapStartApplyOn] + ApplyOn: SnapStartApplyOn | None class EphemeralStorage(TypedDict, total=False): Size: EphemeralStorageSize -StringList = List[String] +StringList = list[String] class ImageConfig(TypedDict, total=False): - EntryPoint: Optional[StringList] - Command: Optional[StringList] - WorkingDirectory: Optional[WorkingDirectory] + EntryPoint: StringList | None + Command: StringList | None + WorkingDirectory: WorkingDirectory | None class FileSystemConfig(TypedDict, total=False): @@ -943,86 +1441,90 @@ class FileSystemConfig(TypedDict, total=False): LocalMountPath: LocalMountPath -FileSystemConfigList = List[FileSystemConfig] -LayerList = List[LayerVersionArn] +FileSystemConfigList = list[FileSystemConfig] +LayerList = list[LayerVersionArn] class TracingConfig(TypedDict, total=False): - Mode: Optional[TracingMode] + Mode: TracingMode | None -EnvironmentVariables = Dict[EnvironmentVariableName, EnvironmentVariableValue] +EnvironmentVariables = dict[EnvironmentVariableName, EnvironmentVariableValue] class Environment(TypedDict, total=False): - Variables: Optional[EnvironmentVariables] + Variables: EnvironmentVariables | None class DeadLetterConfig(TypedDict, total=False): - TargetArn: Optional[ResourceArn] + TargetArn: ResourceArn | None -SecurityGroupIds = List[SecurityGroupId] -SubnetIds = List[SubnetId] +SecurityGroupIds = list[SecurityGroupId] +SubnetIds = list[SubnetId] class VpcConfig(TypedDict, total=False): - SubnetIds: Optional[SubnetIds] - SecurityGroupIds: Optional[SecurityGroupIds] - Ipv6AllowedForDualStack: Optional[NullableBoolean] + SubnetIds: SubnetIds | None + SecurityGroupIds: SecurityGroupIds | None + Ipv6AllowedForDualStack: NullableBoolean | None class FunctionCode(TypedDict, total=False): - ZipFile: Optional[Blob] - S3Bucket: Optional[S3Bucket] - S3Key: Optional[S3Key] - S3ObjectVersion: Optional[S3ObjectVersion] - ImageUri: Optional[String] - SourceKMSKeyArn: Optional[KMSKeyArn] + ZipFile: Blob | None + S3Bucket: S3Bucket | None + S3Key: S3Key | None + S3ObjectVersion: S3ObjectVersion | None + ImageUri: String | None + SourceKMSKeyArn: KMSKeyArn | None class CreateFunctionRequest(ServiceRequest): FunctionName: FunctionName - Runtime: Optional[Runtime] + Runtime: Runtime | None Role: RoleArn - Handler: Optional[Handler] + Handler: Handler | None Code: FunctionCode - Description: Optional[Description] - Timeout: Optional[Timeout] - MemorySize: Optional[MemorySize] - Publish: Optional[Boolean] - VpcConfig: Optional[VpcConfig] - PackageType: Optional[PackageType] - DeadLetterConfig: Optional[DeadLetterConfig] - Environment: Optional[Environment] - KMSKeyArn: Optional[KMSKeyArn] - TracingConfig: Optional[TracingConfig] - Tags: Optional[Tags] - Layers: Optional[LayerList] - FileSystemConfigs: Optional[FileSystemConfigList] - ImageConfig: Optional[ImageConfig] - CodeSigningConfigArn: Optional[CodeSigningConfigArn] - Architectures: Optional[ArchitecturesList] - EphemeralStorage: Optional[EphemeralStorage] - SnapStart: Optional[SnapStart] - LoggingConfig: Optional[LoggingConfig] + Description: Description | None + Timeout: Timeout | None + MemorySize: MemorySize | None + Publish: Boolean | None + VpcConfig: VpcConfig | None + PackageType: PackageType | None + DeadLetterConfig: DeadLetterConfig | None + Environment: Environment | None + KMSKeyArn: KMSKeyArn | None + TracingConfig: TracingConfig | None + Tags: Tags | None + Layers: LayerList | None + FileSystemConfigs: FileSystemConfigList | None + ImageConfig: ImageConfig | None + CodeSigningConfigArn: CodeSigningConfigArn | None + Architectures: ArchitecturesList | None + EphemeralStorage: EphemeralStorage | None + SnapStart: SnapStart | None + LoggingConfig: LoggingConfig | None + CapacityProviderConfig: CapacityProviderConfig | None + PublishTo: FunctionVersionLatestPublished | None + DurableConfig: DurableConfig | None + TenancyConfig: TenancyConfig | None class CreateFunctionUrlConfigRequest(ServiceRequest): FunctionName: FunctionName - Qualifier: Optional[FunctionUrlQualifier] + Qualifier: FunctionUrlQualifier | None AuthType: FunctionUrlAuthType - Cors: Optional[Cors] - InvokeMode: Optional[InvokeMode] + Cors: Cors | None + InvokeMode: InvokeMode | None class CreateFunctionUrlConfigResponse(TypedDict, total=False): FunctionUrl: FunctionUrl FunctionArn: FunctionArn AuthType: FunctionUrlAuthType - Cors: Optional[Cors] + Cors: Cors | None CreationTime: Timestamp - InvokeMode: Optional[InvokeMode] + InvokeMode: InvokeMode | None class DeleteAliasRequest(ServiceRequest): @@ -1030,6 +1532,14 @@ class DeleteAliasRequest(ServiceRequest): Name: Alias +class DeleteCapacityProviderRequest(ServiceRequest): + CapacityProviderName: CapacityProviderName + + +class DeleteCapacityProviderResponse(TypedDict, total=False): + CapacityProvider: CapacityProvider + + class DeleteCodeSigningConfigRequest(ServiceRequest): CodeSigningConfigArn: CodeSigningConfigArn @@ -1043,7 +1553,7 @@ class DeleteEventSourceMappingRequest(ServiceRequest): class DeleteFunctionCodeSigningConfigRequest(ServiceRequest): - FunctionName: FunctionName + FunctionName: NamespacedFunctionName class DeleteFunctionConcurrencyRequest(ServiceRequest): @@ -1051,18 +1561,22 @@ class DeleteFunctionConcurrencyRequest(ServiceRequest): class DeleteFunctionEventInvokeConfigRequest(ServiceRequest): - FunctionName: FunctionName - Qualifier: Optional[Qualifier] + FunctionName: NamespacedFunctionName + Qualifier: NumericLatestPublishedOrAliasQualifier | None class DeleteFunctionRequest(ServiceRequest): - FunctionName: FunctionName - Qualifier: Optional[Qualifier] + FunctionName: NamespacedFunctionName + Qualifier: NumericLatestPublishedOrAliasQualifier | None + + +class DeleteFunctionResponse(TypedDict, total=False): + StatusCode: Integer | None class DeleteFunctionUrlConfigRequest(ServiceRequest): FunctionName: FunctionName - Qualifier: Optional[FunctionUrlQualifier] + Qualifier: FunctionUrlQualifier | None class DeleteLayerVersionRequest(ServiceRequest): @@ -1075,163 +1589,281 @@ class DeleteProvisionedConcurrencyConfigRequest(ServiceRequest): Qualifier: Qualifier +class Execution(TypedDict, total=False): + DurableExecutionArn: DurableExecutionArn + DurableExecutionName: DurableExecutionName + FunctionArn: NameSpacedFunctionArn + Status: ExecutionStatus + StartTimestamp: ExecutionTimestamp + EndTimestamp: ExecutionTimestamp | None + + +DurableExecutions = list[Execution] + + class EnvironmentError(TypedDict, total=False): - ErrorCode: Optional[String] - Message: Optional[SensitiveString] + ErrorCode: String | None + Message: SensitiveString | None class EnvironmentResponse(TypedDict, total=False): - Variables: Optional[EnvironmentVariables] - Error: Optional[EnvironmentError] + Variables: EnvironmentVariables | None + Error: EnvironmentError | None + + +class InvocationCompletedDetails(TypedDict, total=False): + StartTimestamp: ExecutionTimestamp + EndTimestamp: ExecutionTimestamp + RequestId: String + Error: EventError | None + + +class RetryDetails(TypedDict, total=False): + CurrentAttempt: AttemptCount | None + NextAttemptDelaySeconds: DurationSeconds | None + + +class StepFailedDetails(TypedDict, total=False): + Error: EventError + RetryDetails: RetryDetails + + +class StepSucceededDetails(TypedDict, total=False): + Result: EventResult + RetryDetails: RetryDetails + + +class StepStartedDetails(TypedDict, total=False): + pass + + +class WaitCancelledDetails(TypedDict, total=False): + Error: EventError | None + + +class WaitSucceededDetails(TypedDict, total=False): + Duration: DurationSeconds | None + + +class WaitStartedDetails(TypedDict, total=False): + Duration: DurationSeconds + ScheduledEndTimestamp: ExecutionTimestamp + + +class ExecutionStoppedDetails(TypedDict, total=False): + Error: EventError + + +class ExecutionTimedOutDetails(TypedDict, total=False): + Error: EventError | None + + +class ExecutionFailedDetails(TypedDict, total=False): + Error: EventError + + +class ExecutionSucceededDetails(TypedDict, total=False): + Result: EventResult + + +class ExecutionStartedDetails(TypedDict, total=False): + Input: EventInput + ExecutionTimeout: DurationSeconds + + +class Event(TypedDict, total=False): + EventType: EventType | None + SubType: OperationSubType | None + EventId: EventId | None + Id: OperationId | None + Name: OperationName | None + EventTimestamp: ExecutionTimestamp | None + ParentId: OperationId | None + ExecutionStartedDetails: ExecutionStartedDetails | None + ExecutionSucceededDetails: ExecutionSucceededDetails | None + ExecutionFailedDetails: ExecutionFailedDetails | None + ExecutionTimedOutDetails: ExecutionTimedOutDetails | None + ExecutionStoppedDetails: ExecutionStoppedDetails | None + ContextStartedDetails: ContextStartedDetails | None + ContextSucceededDetails: ContextSucceededDetails | None + ContextFailedDetails: ContextFailedDetails | None + WaitStartedDetails: WaitStartedDetails | None + WaitSucceededDetails: WaitSucceededDetails | None + WaitCancelledDetails: WaitCancelledDetails | None + StepStartedDetails: StepStartedDetails | None + StepSucceededDetails: StepSucceededDetails | None + StepFailedDetails: StepFailedDetails | None + ChainedInvokeStartedDetails: ChainedInvokeStartedDetails | None + ChainedInvokeSucceededDetails: ChainedInvokeSucceededDetails | None + ChainedInvokeFailedDetails: ChainedInvokeFailedDetails | None + ChainedInvokeTimedOutDetails: ChainedInvokeTimedOutDetails | None + ChainedInvokeStoppedDetails: ChainedInvokeStoppedDetails | None + CallbackStartedDetails: CallbackStartedDetails | None + CallbackSucceededDetails: CallbackSucceededDetails | None + CallbackFailedDetails: CallbackFailedDetails | None + CallbackTimedOutDetails: CallbackTimedOutDetails | None + InvocationCompletedDetails: InvocationCompletedDetails | None class FilterCriteriaError(TypedDict, total=False): - ErrorCode: Optional[FilterCriteriaErrorCode] - Message: Optional[FilterCriteriaErrorMessage] + ErrorCode: FilterCriteriaErrorCode | None + Message: FilterCriteriaErrorMessage | None class EventSourceMappingConfiguration(TypedDict, total=False): - UUID: Optional[String] - StartingPosition: Optional[EventSourcePosition] - StartingPositionTimestamp: Optional[Date] - BatchSize: Optional[BatchSize] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - ParallelizationFactor: Optional[ParallelizationFactor] - EventSourceArn: Optional[Arn] - FilterCriteria: Optional[FilterCriteria] - FunctionArn: Optional[FunctionArn] - LastModified: Optional[Date] - LastProcessingResult: Optional[String] - State: Optional[String] - StateTransitionReason: Optional[String] - DestinationConfig: Optional[DestinationConfig] - Topics: Optional[Topics] - Queues: Optional[Queues] - SourceAccessConfigurations: Optional[SourceAccessConfigurations] - SelfManagedEventSource: Optional[SelfManagedEventSource] - MaximumRecordAgeInSeconds: Optional[MaximumRecordAgeInSeconds] - BisectBatchOnFunctionError: Optional[BisectBatchOnFunctionError] - MaximumRetryAttempts: Optional[MaximumRetryAttemptsEventSourceMapping] - TumblingWindowInSeconds: Optional[TumblingWindowInSeconds] - FunctionResponseTypes: Optional[FunctionResponseTypeList] - AmazonManagedKafkaEventSourceConfig: Optional[AmazonManagedKafkaEventSourceConfig] - SelfManagedKafkaEventSourceConfig: Optional[SelfManagedKafkaEventSourceConfig] - ScalingConfig: Optional[ScalingConfig] - DocumentDBEventSourceConfig: Optional[DocumentDBEventSourceConfig] - KMSKeyArn: Optional[KMSKeyArn] - FilterCriteriaError: Optional[FilterCriteriaError] - EventSourceMappingArn: Optional[EventSourceMappingArn] - MetricsConfig: Optional[EventSourceMappingMetricsConfig] - ProvisionedPollerConfig: Optional[ProvisionedPollerConfig] - - -EventSourceMappingsList = List[EventSourceMappingConfiguration] -FunctionArnList = List[FunctionArn] + UUID: String | None + StartingPosition: EventSourcePosition | None + StartingPositionTimestamp: Date | None + BatchSize: BatchSize | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + ParallelizationFactor: ParallelizationFactor | None + EventSourceArn: Arn | None + FilterCriteria: FilterCriteria | None + FunctionArn: FunctionArn | None + LastModified: Date | None + LastProcessingResult: String | None + State: String | None + StateTransitionReason: String | None + DestinationConfig: DestinationConfig | None + Topics: Topics | None + Queues: Queues | None + SourceAccessConfigurations: SourceAccessConfigurations | None + SelfManagedEventSource: SelfManagedEventSource | None + MaximumRecordAgeInSeconds: MaximumRecordAgeInSeconds | None + BisectBatchOnFunctionError: BisectBatchOnFunctionError | None + MaximumRetryAttempts: MaximumRetryAttemptsEventSourceMapping | None + TumblingWindowInSeconds: TumblingWindowInSeconds | None + FunctionResponseTypes: FunctionResponseTypeList | None + AmazonManagedKafkaEventSourceConfig: AmazonManagedKafkaEventSourceConfig | None + SelfManagedKafkaEventSourceConfig: SelfManagedKafkaEventSourceConfig | None + ScalingConfig: ScalingConfig | None + DocumentDBEventSourceConfig: DocumentDBEventSourceConfig | None + KMSKeyArn: KMSKeyArn | None + FilterCriteriaError: FilterCriteriaError | None + EventSourceMappingArn: EventSourceMappingArn | None + MetricsConfig: EventSourceMappingMetricsConfig | None + LoggingConfig: EventSourceMappingLoggingConfig | None + ProvisionedPollerConfig: ProvisionedPollerConfig | None + + +EventSourceMappingsList = list[EventSourceMappingConfiguration] +Events = list[Event] +ExecutionStatusList = list[ExecutionStatus] +FunctionArnList = list[FunctionArn] class FunctionCodeLocation(TypedDict, total=False): - RepositoryType: Optional[String] - Location: Optional[String] - ImageUri: Optional[String] - ResolvedImageUri: Optional[String] - SourceKMSKeyArn: Optional[String] + RepositoryType: String | None + Location: String | None + ImageUri: String | None + ResolvedImageUri: String | None + SourceKMSKeyArn: String | None class RuntimeVersionError(TypedDict, total=False): - ErrorCode: Optional[String] - Message: Optional[SensitiveString] + ErrorCode: String | None + Message: SensitiveString | None class RuntimeVersionConfig(TypedDict, total=False): - RuntimeVersionArn: Optional[RuntimeVersionArn] - Error: Optional[RuntimeVersionError] + RuntimeVersionArn: RuntimeVersionArn | None + Error: RuntimeVersionError | None class SnapStartResponse(TypedDict, total=False): - ApplyOn: Optional[SnapStartApplyOn] - OptimizationStatus: Optional[SnapStartOptimizationStatus] + ApplyOn: SnapStartApplyOn | None + OptimizationStatus: SnapStartOptimizationStatus | None class ImageConfigError(TypedDict, total=False): - ErrorCode: Optional[String] - Message: Optional[SensitiveString] + ErrorCode: String | None + Message: SensitiveString | None class ImageConfigResponse(TypedDict, total=False): - ImageConfig: Optional[ImageConfig] - Error: Optional[ImageConfigError] + ImageConfig: ImageConfig | None + Error: ImageConfigError | None class Layer(TypedDict, total=False): - Arn: Optional[LayerVersionArn] - CodeSize: Optional[Long] - SigningProfileVersionArn: Optional[Arn] - SigningJobArn: Optional[Arn] + Arn: LayerVersionArn | None + CodeSize: Long | None + SigningProfileVersionArn: Arn | None + SigningJobArn: Arn | None -LayersReferenceList = List[Layer] +LayersReferenceList = list[Layer] class TracingConfigResponse(TypedDict, total=False): - Mode: Optional[TracingMode] + Mode: TracingMode | None class VpcConfigResponse(TypedDict, total=False): - SubnetIds: Optional[SubnetIds] - SecurityGroupIds: Optional[SecurityGroupIds] - VpcId: Optional[VpcId] - Ipv6AllowedForDualStack: Optional[NullableBoolean] + SubnetIds: SubnetIds | None + SecurityGroupIds: SecurityGroupIds | None + VpcId: VpcId | None + Ipv6AllowedForDualStack: NullableBoolean | None class FunctionConfiguration(TypedDict, total=False): - FunctionName: Optional[NamespacedFunctionName] - FunctionArn: Optional[NameSpacedFunctionArn] - Runtime: Optional[Runtime] - Role: Optional[RoleArn] - Handler: Optional[Handler] - CodeSize: Optional[Long] - Description: Optional[Description] - Timeout: Optional[Timeout] - MemorySize: Optional[MemorySize] - LastModified: Optional[Timestamp] - CodeSha256: Optional[String] - Version: Optional[Version] - VpcConfig: Optional[VpcConfigResponse] - DeadLetterConfig: Optional[DeadLetterConfig] - Environment: Optional[EnvironmentResponse] - KMSKeyArn: Optional[KMSKeyArn] - TracingConfig: Optional[TracingConfigResponse] - MasterArn: Optional[FunctionArn] - RevisionId: Optional[String] - Layers: Optional[LayersReferenceList] - State: Optional[State] - StateReason: Optional[StateReason] - StateReasonCode: Optional[StateReasonCode] - LastUpdateStatus: Optional[LastUpdateStatus] - LastUpdateStatusReason: Optional[LastUpdateStatusReason] - LastUpdateStatusReasonCode: Optional[LastUpdateStatusReasonCode] - FileSystemConfigs: Optional[FileSystemConfigList] - PackageType: Optional[PackageType] - ImageConfigResponse: Optional[ImageConfigResponse] - SigningProfileVersionArn: Optional[Arn] - SigningJobArn: Optional[Arn] - Architectures: Optional[ArchitecturesList] - EphemeralStorage: Optional[EphemeralStorage] - SnapStart: Optional[SnapStartResponse] - RuntimeVersionConfig: Optional[RuntimeVersionConfig] - LoggingConfig: Optional[LoggingConfig] + FunctionName: NamespacedFunctionName | None + FunctionArn: NameSpacedFunctionArn | None + Runtime: Runtime | None + Role: RoleArn | None + Handler: Handler | None + CodeSize: Long | None + Description: Description | None + Timeout: Timeout | None + MemorySize: MemorySize | None + LastModified: Timestamp | None + CodeSha256: String | None + Version: Version | None + VpcConfig: VpcConfigResponse | None + DeadLetterConfig: DeadLetterConfig | None + Environment: EnvironmentResponse | None + KMSKeyArn: KMSKeyArn | None + TracingConfig: TracingConfigResponse | None + MasterArn: FunctionArn | None + RevisionId: String | None + Layers: LayersReferenceList | None + State: State | None + StateReason: StateReason | None + StateReasonCode: StateReasonCode | None + LastUpdateStatus: LastUpdateStatus | None + LastUpdateStatusReason: LastUpdateStatusReason | None + LastUpdateStatusReasonCode: LastUpdateStatusReasonCode | None + FileSystemConfigs: FileSystemConfigList | None + PackageType: PackageType | None + ImageConfigResponse: ImageConfigResponse | None + SigningProfileVersionArn: Arn | None + SigningJobArn: Arn | None + Architectures: ArchitecturesList | None + EphemeralStorage: EphemeralStorage | None + SnapStart: SnapStartResponse | None + RuntimeVersionConfig: RuntimeVersionConfig | None + LoggingConfig: LoggingConfig | None + CapacityProviderConfig: CapacityProviderConfig | None + ConfigSha256: String | None + DurableConfig: DurableConfig | None + TenancyConfig: TenancyConfig | None class FunctionEventInvokeConfig(TypedDict, total=False): - LastModified: Optional[Date] - FunctionArn: Optional[FunctionArn] - MaximumRetryAttempts: Optional[MaximumRetryAttempts] - MaximumEventAgeInSeconds: Optional[MaximumEventAgeInSeconds] - DestinationConfig: Optional[DestinationConfig] + LastModified: Date | None + FunctionArn: FunctionArn | None + MaximumRetryAttempts: MaximumRetryAttempts | None + MaximumEventAgeInSeconds: MaximumEventAgeInSeconds | None + DestinationConfig: DestinationConfig | None + + +FunctionEventInvokeConfigList = list[FunctionEventInvokeConfig] +FunctionList = list[FunctionConfiguration] -FunctionEventInvokeConfigList = List[FunctionEventInvokeConfig] -FunctionList = List[FunctionConfiguration] +class FunctionScalingConfig(TypedDict, total=False): + MinExecutionEnvironments: FunctionScalingConfigExecutionEnvironments | None + MaxExecutionEnvironments: FunctionScalingConfigExecutionEnvironments | None class FunctionUrlConfig(TypedDict, total=False): @@ -1239,12 +1871,20 @@ class FunctionUrlConfig(TypedDict, total=False): FunctionArn: FunctionArn CreationTime: Timestamp LastModifiedTime: Timestamp - Cors: Optional[Cors] + Cors: Cors | None AuthType: FunctionUrlAuthType - InvokeMode: Optional[InvokeMode] + InvokeMode: InvokeMode | None + + +FunctionUrlConfigList = list[FunctionUrlConfig] + +class FunctionVersionsByCapacityProviderListItem(TypedDict, total=False): + FunctionArn: NameSpacedFunctionArn + State: State -FunctionUrlConfigList = List[FunctionUrlConfig] + +FunctionVersionsByCapacityProviderList = list[FunctionVersionsByCapacityProviderListItem] class GetAccountSettingsRequest(ServiceRequest): @@ -1252,8 +1892,8 @@ class GetAccountSettingsRequest(ServiceRequest): class GetAccountSettingsResponse(TypedDict, total=False): - AccountLimit: Optional[AccountLimit] - AccountUsage: Optional[AccountUsage] + AccountLimit: AccountLimit | None + AccountUsage: AccountUsage | None class GetAliasRequest(ServiceRequest): @@ -1261,6 +1901,14 @@ class GetAliasRequest(ServiceRequest): Name: Alias +class GetCapacityProviderRequest(ServiceRequest): + CapacityProviderName: CapacityProviderName + + +class GetCapacityProviderResponse(TypedDict, total=False): + CapacityProvider: CapacityProvider + + class GetCodeSigningConfigRequest(ServiceRequest): CodeSigningConfigArn: CodeSigningConfigArn @@ -1269,12 +1917,59 @@ class GetCodeSigningConfigResponse(TypedDict, total=False): CodeSigningConfig: CodeSigningConfig +class GetDurableExecutionHistoryRequest(ServiceRequest): + DurableExecutionArn: DurableExecutionArn + IncludeExecutionData: IncludeExecutionData | None + MaxItems: ItemCount | None + Marker: String | None + ReverseOrder: ReverseOrder | None + + +class GetDurableExecutionHistoryResponse(TypedDict, total=False): + Events: Events + NextMarker: String | None + + +class GetDurableExecutionRequest(ServiceRequest): + DurableExecutionArn: DurableExecutionArn + + +class TraceHeader(TypedDict, total=False): + XAmznTraceId: XAmznTraceId | None + + +class GetDurableExecutionResponse(TypedDict, total=False): + DurableExecutionArn: DurableExecutionArn + DurableExecutionName: DurableExecutionName + FunctionArn: NameSpacedFunctionArn + InputPayload: InputPayload | None + Result: OutputPayload | None + Error: ErrorObject | None + StartTimestamp: ExecutionTimestamp + Status: ExecutionStatus + EndTimestamp: ExecutionTimestamp | None + Version: VersionWithLatestPublished | None + TraceHeader: TraceHeader | None + + +class GetDurableExecutionStateRequest(ServiceRequest): + DurableExecutionArn: DurableExecutionArn + CheckpointToken: CheckpointToken + Marker: String | None + MaxItems: ItemCount | None + + +class GetDurableExecutionStateResponse(TypedDict, total=False): + Operations: Operations + NextMarker: String | None + + class GetEventSourceMappingRequest(ServiceRequest): UUID: String class GetFunctionCodeSigningConfigRequest(ServiceRequest): - FunctionName: FunctionName + FunctionName: NamespacedFunctionName class GetFunctionCodeSigningConfigResponse(TypedDict, total=False): @@ -1287,17 +1982,17 @@ class GetFunctionConcurrencyRequest(ServiceRequest): class GetFunctionConcurrencyResponse(TypedDict, total=False): - ReservedConcurrentExecutions: Optional[ReservedConcurrentExecutions] + ReservedConcurrentExecutions: ReservedConcurrentExecutions | None class GetFunctionConfigurationRequest(ServiceRequest): FunctionName: NamespacedFunctionName - Qualifier: Optional[Qualifier] + Qualifier: NumericLatestPublishedOrAliasQualifier | None class GetFunctionEventInvokeConfigRequest(ServiceRequest): - FunctionName: FunctionName - Qualifier: Optional[Qualifier] + FunctionName: NamespacedFunctionName + Qualifier: NumericLatestPublishedOrAliasQualifier | None class GetFunctionRecursionConfigRequest(ServiceRequest): @@ -1305,12 +2000,12 @@ class GetFunctionRecursionConfigRequest(ServiceRequest): class GetFunctionRecursionConfigResponse(TypedDict, total=False): - RecursiveLoop: Optional[RecursiveLoop] + RecursiveLoop: RecursiveLoop | None class GetFunctionRequest(ServiceRequest): FunctionName: NamespacedFunctionName - Qualifier: Optional[Qualifier] + Qualifier: NumericLatestPublishedOrAliasQualifier | None class TagsError(TypedDict, total=False): @@ -1319,26 +2014,37 @@ class TagsError(TypedDict, total=False): class GetFunctionResponse(TypedDict, total=False): - Configuration: Optional[FunctionConfiguration] - Code: Optional[FunctionCodeLocation] - Tags: Optional[Tags] - TagsError: Optional[TagsError] - Concurrency: Optional[Concurrency] + Configuration: FunctionConfiguration | None + Code: FunctionCodeLocation | None + Tags: Tags | None + TagsError: TagsError | None + Concurrency: Concurrency | None + + +class GetFunctionScalingConfigRequest(ServiceRequest): + FunctionName: UnqualifiedFunctionName + Qualifier: PublishedFunctionQualifier + + +class GetFunctionScalingConfigResponse(TypedDict, total=False): + FunctionArn: FunctionArn | None + AppliedFunctionScalingConfig: FunctionScalingConfig | None + RequestedFunctionScalingConfig: FunctionScalingConfig | None class GetFunctionUrlConfigRequest(ServiceRequest): FunctionName: FunctionName - Qualifier: Optional[FunctionUrlQualifier] + Qualifier: FunctionUrlQualifier | None class GetFunctionUrlConfigResponse(TypedDict, total=False): FunctionUrl: FunctionUrl FunctionArn: FunctionArn AuthType: FunctionUrlAuthType - Cors: Optional[Cors] + Cors: Cors | None CreationTime: Timestamp LastModifiedTime: Timestamp - InvokeMode: Optional[InvokeMode] + InvokeMode: InvokeMode | None class GetLayerVersionByArnRequest(ServiceRequest): @@ -1351,8 +2057,8 @@ class GetLayerVersionPolicyRequest(ServiceRequest): class GetLayerVersionPolicyResponse(TypedDict, total=False): - Policy: Optional[String] - RevisionId: Optional[String] + Policy: String | None + RevisionId: String | None class GetLayerVersionRequest(ServiceRequest): @@ -1361,33 +2067,33 @@ class GetLayerVersionRequest(ServiceRequest): class LayerVersionContentOutput(TypedDict, total=False): - Location: Optional[String] - CodeSha256: Optional[String] - CodeSize: Optional[Long] - SigningProfileVersionArn: Optional[String] - SigningJobArn: Optional[String] + Location: String | None + CodeSha256: String | None + CodeSize: Long | None + SigningProfileVersionArn: String | None + SigningJobArn: String | None class GetLayerVersionResponse(TypedDict, total=False): - Content: Optional[LayerVersionContentOutput] - LayerArn: Optional[LayerArn] - LayerVersionArn: Optional[LayerVersionArn] - Description: Optional[Description] - CreatedDate: Optional[Timestamp] - Version: Optional[LayerVersionNumber] - CompatibleRuntimes: Optional[CompatibleRuntimes] - LicenseInfo: Optional[LicenseInfo] - CompatibleArchitectures: Optional[CompatibleArchitectures] + Content: LayerVersionContentOutput | None + LayerArn: LayerArn | None + LayerVersionArn: LayerVersionArn | None + Description: Description | None + CreatedDate: Timestamp | None + Version: LayerVersionNumber | None + CompatibleRuntimes: CompatibleRuntimes | None + LicenseInfo: LicenseInfo | None + CompatibleArchitectures: CompatibleArchitectures | None class GetPolicyRequest(ServiceRequest): FunctionName: NamespacedFunctionName - Qualifier: Optional[Qualifier] + Qualifier: NumericLatestPublishedOrAliasQualifier | None class GetPolicyResponse(TypedDict, total=False): - Policy: Optional[String] - RevisionId: Optional[String] + Policy: String | None + RevisionId: String | None class GetProvisionedConcurrencyConfigRequest(ServiceRequest): @@ -1396,40 +2102,43 @@ class GetProvisionedConcurrencyConfigRequest(ServiceRequest): class GetProvisionedConcurrencyConfigResponse(TypedDict, total=False): - RequestedProvisionedConcurrentExecutions: Optional[PositiveInteger] - AvailableProvisionedConcurrentExecutions: Optional[NonNegativeInteger] - AllocatedProvisionedConcurrentExecutions: Optional[NonNegativeInteger] - Status: Optional[ProvisionedConcurrencyStatusEnum] - StatusReason: Optional[String] - LastModified: Optional[Timestamp] + RequestedProvisionedConcurrentExecutions: PositiveInteger | None + AvailableProvisionedConcurrentExecutions: NonNegativeInteger | None + AllocatedProvisionedConcurrentExecutions: NonNegativeInteger | None + Status: ProvisionedConcurrencyStatusEnum | None + StatusReason: String | None + LastModified: Timestamp | None class GetRuntimeManagementConfigRequest(ServiceRequest): FunctionName: NamespacedFunctionName - Qualifier: Optional[Qualifier] + Qualifier: NumericLatestPublishedOrAliasQualifier | None class GetRuntimeManagementConfigResponse(TypedDict, total=False): - UpdateRuntimeOn: Optional[UpdateRuntimeOn] - RuntimeVersionArn: Optional[RuntimeVersionArn] - FunctionArn: Optional[NameSpacedFunctionArn] + UpdateRuntimeOn: UpdateRuntimeOn | None + RuntimeVersionArn: RuntimeVersionArn | None + FunctionArn: NameSpacedFunctionArn | None class InvocationRequest(ServiceRequest): - Payload: Optional[IO[Blob]] + Payload: IO[Blob] | None FunctionName: NamespacedFunctionName - InvocationType: Optional[InvocationType] - LogType: Optional[LogType] - ClientContext: Optional[String] - Qualifier: Optional[Qualifier] + InvocationType: InvocationType | None + LogType: LogType | None + ClientContext: String | None + DurableExecutionName: DurableExecutionName | None + Qualifier: NumericLatestPublishedOrAliasQualifier | None + TenantId: TenantId | None class InvocationResponse(TypedDict, total=False): - Payload: Optional[Union[Blob, IO[Blob], Iterable[Blob]]] - StatusCode: Optional[Integer] - FunctionError: Optional[String] - LogResult: Optional[String] - ExecutedVersion: Optional[Version] + Payload: Blob | IO[Blob] | Iterable[Blob] | None + StatusCode: Integer | None + FunctionError: String | None + LogResult: String | None + ExecutedVersion: Version | None + DurableExecutionArn: DurableExecutionArn | None class InvokeAsyncRequest(ServiceRequest): @@ -1438,195 +2147,236 @@ class InvokeAsyncRequest(ServiceRequest): class InvokeAsyncResponse(TypedDict, total=False): - Status: Optional[HttpStatus] + Status: HttpStatus | None class InvokeResponseStreamUpdate(TypedDict, total=False): - Payload: Optional[Blob] + Payload: Blob | None class InvokeWithResponseStreamCompleteEvent(TypedDict, total=False): - ErrorCode: Optional[String] - ErrorDetails: Optional[String] - LogResult: Optional[String] + ErrorCode: String | None + ErrorDetails: String | None + LogResult: String | None class InvokeWithResponseStreamRequest(ServiceRequest): - Payload: Optional[IO[Blob]] + Payload: IO[Blob] | None FunctionName: NamespacedFunctionName - InvocationType: Optional[ResponseStreamingInvocationType] - LogType: Optional[LogType] - ClientContext: Optional[String] - Qualifier: Optional[Qualifier] + InvocationType: ResponseStreamingInvocationType | None + LogType: LogType | None + ClientContext: String | None + Qualifier: NumericLatestPublishedOrAliasQualifier | None + TenantId: TenantId | None class InvokeWithResponseStreamResponseEvent(TypedDict, total=False): - PayloadChunk: Optional[InvokeResponseStreamUpdate] - InvokeComplete: Optional[InvokeWithResponseStreamCompleteEvent] + PayloadChunk: InvokeResponseStreamUpdate | None + InvokeComplete: InvokeWithResponseStreamCompleteEvent | None class InvokeWithResponseStreamResponse(TypedDict, total=False): - StatusCode: Optional[Integer] - ExecutedVersion: Optional[Version] + StatusCode: Integer | None + ExecutedVersion: Version | None EventStream: Iterator[InvokeWithResponseStreamResponseEvent] - ResponseStreamContentType: Optional[String] + ResponseStreamContentType: String | None class LayerVersionContentInput(TypedDict, total=False): - S3Bucket: Optional[S3Bucket] - S3Key: Optional[S3Key] - S3ObjectVersion: Optional[S3ObjectVersion] - ZipFile: Optional[Blob] + S3Bucket: S3Bucket | None + S3Key: S3Key | None + S3ObjectVersion: S3ObjectVersion | None + ZipFile: Blob | None class LayerVersionsListItem(TypedDict, total=False): - LayerVersionArn: Optional[LayerVersionArn] - Version: Optional[LayerVersionNumber] - Description: Optional[Description] - CreatedDate: Optional[Timestamp] - CompatibleRuntimes: Optional[CompatibleRuntimes] - LicenseInfo: Optional[LicenseInfo] - CompatibleArchitectures: Optional[CompatibleArchitectures] + LayerVersionArn: LayerVersionArn | None + Version: LayerVersionNumber | None + Description: Description | None + CreatedDate: Timestamp | None + CompatibleRuntimes: CompatibleRuntimes | None + LicenseInfo: LicenseInfo | None + CompatibleArchitectures: CompatibleArchitectures | None -LayerVersionsList = List[LayerVersionsListItem] +LayerVersionsList = list[LayerVersionsListItem] class LayersListItem(TypedDict, total=False): - LayerName: Optional[LayerName] - LayerArn: Optional[LayerArn] - LatestMatchingVersion: Optional[LayerVersionsListItem] + LayerName: LayerName | None + LayerArn: LayerArn | None + LatestMatchingVersion: LayerVersionsListItem | None -LayersList = List[LayersListItem] +LayersList = list[LayersListItem] class ListAliasesRequest(ServiceRequest): FunctionName: FunctionName - FunctionVersion: Optional[Version] - Marker: Optional[String] - MaxItems: Optional[MaxListItems] + FunctionVersion: VersionWithLatestPublished | None + Marker: String | None + MaxItems: MaxListItems | None class ListAliasesResponse(TypedDict, total=False): - NextMarker: Optional[String] - Aliases: Optional[AliasList] + NextMarker: String | None + Aliases: AliasList | None + + +class ListCapacityProvidersRequest(ServiceRequest): + State: CapacityProviderState | None + Marker: String | None + MaxItems: MaxFiftyListItems | None + + +class ListCapacityProvidersResponse(TypedDict, total=False): + CapacityProviders: CapacityProvidersList + NextMarker: String | None class ListCodeSigningConfigsRequest(ServiceRequest): - Marker: Optional[String] - MaxItems: Optional[MaxListItems] + Marker: String | None + MaxItems: MaxListItems | None class ListCodeSigningConfigsResponse(TypedDict, total=False): - NextMarker: Optional[String] - CodeSigningConfigs: Optional[CodeSigningConfigList] + NextMarker: String | None + CodeSigningConfigs: CodeSigningConfigList | None + + +class ListDurableExecutionsByFunctionRequest(ServiceRequest): + FunctionName: NamespacedFunctionName + Qualifier: NumericLatestPublishedOrAliasQualifier | None + DurableExecutionName: DurableExecutionName | None + Statuses: ExecutionStatusList | None + StartedAfter: ExecutionTimestamp | None + StartedBefore: ExecutionTimestamp | None + ReverseOrder: ReverseOrder | None + Marker: String | None + MaxItems: ItemCount | None + + +class ListDurableExecutionsByFunctionResponse(TypedDict, total=False): + DurableExecutions: DurableExecutions | None + NextMarker: String | None class ListEventSourceMappingsRequest(ServiceRequest): - EventSourceArn: Optional[Arn] - FunctionName: Optional[FunctionName] - Marker: Optional[String] - MaxItems: Optional[MaxListItems] + EventSourceArn: Arn | None + FunctionName: NamespacedFunctionName | None + Marker: String | None + MaxItems: MaxListItems | None class ListEventSourceMappingsResponse(TypedDict, total=False): - NextMarker: Optional[String] - EventSourceMappings: Optional[EventSourceMappingsList] + NextMarker: String | None + EventSourceMappings: EventSourceMappingsList | None class ListFunctionEventInvokeConfigsRequest(ServiceRequest): - FunctionName: FunctionName - Marker: Optional[String] - MaxItems: Optional[MaxFunctionEventInvokeConfigListItems] + FunctionName: NamespacedFunctionName + Marker: String | None + MaxItems: MaxFunctionEventInvokeConfigListItems | None class ListFunctionEventInvokeConfigsResponse(TypedDict, total=False): - FunctionEventInvokeConfigs: Optional[FunctionEventInvokeConfigList] - NextMarker: Optional[String] + FunctionEventInvokeConfigs: FunctionEventInvokeConfigList | None + NextMarker: String | None class ListFunctionUrlConfigsRequest(ServiceRequest): FunctionName: FunctionName - Marker: Optional[String] - MaxItems: Optional[MaxItems] + Marker: String | None + MaxItems: MaxItems | None class ListFunctionUrlConfigsResponse(TypedDict, total=False): FunctionUrlConfigs: FunctionUrlConfigList - NextMarker: Optional[String] + NextMarker: String | None + + +class ListFunctionVersionsByCapacityProviderRequest(ServiceRequest): + CapacityProviderName: CapacityProviderName + Marker: String | None + MaxItems: MaxFiftyListItems | None + + +class ListFunctionVersionsByCapacityProviderResponse(TypedDict, total=False): + CapacityProviderArn: CapacityProviderArn + FunctionVersions: FunctionVersionsByCapacityProviderList + NextMarker: String | None class ListFunctionsByCodeSigningConfigRequest(ServiceRequest): CodeSigningConfigArn: CodeSigningConfigArn - Marker: Optional[String] - MaxItems: Optional[MaxListItems] + Marker: String | None + MaxItems: MaxListItems | None class ListFunctionsByCodeSigningConfigResponse(TypedDict, total=False): - NextMarker: Optional[String] - FunctionArns: Optional[FunctionArnList] + NextMarker: String | None + FunctionArns: FunctionArnList | None class ListFunctionsRequest(ServiceRequest): - MasterRegion: Optional[MasterRegion] - FunctionVersion: Optional[FunctionVersion] - Marker: Optional[String] - MaxItems: Optional[MaxListItems] + MasterRegion: MasterRegion | None + FunctionVersion: FunctionVersion | None + Marker: String | None + MaxItems: MaxListItems | None class ListFunctionsResponse(TypedDict, total=False): - NextMarker: Optional[String] - Functions: Optional[FunctionList] + NextMarker: String | None + Functions: FunctionList | None class ListLayerVersionsRequest(ServiceRequest): - CompatibleRuntime: Optional[Runtime] + CompatibleRuntime: Runtime | None LayerName: LayerName - Marker: Optional[String] - MaxItems: Optional[MaxLayerListItems] - CompatibleArchitecture: Optional[Architecture] + Marker: String | None + MaxItems: MaxLayerListItems | None + CompatibleArchitecture: Architecture | None class ListLayerVersionsResponse(TypedDict, total=False): - NextMarker: Optional[String] - LayerVersions: Optional[LayerVersionsList] + NextMarker: String | None + LayerVersions: LayerVersionsList | None class ListLayersRequest(ServiceRequest): - CompatibleRuntime: Optional[Runtime] - Marker: Optional[String] - MaxItems: Optional[MaxLayerListItems] - CompatibleArchitecture: Optional[Architecture] + CompatibleRuntime: Runtime | None + Marker: String | None + MaxItems: MaxLayerListItems | None + CompatibleArchitecture: Architecture | None class ListLayersResponse(TypedDict, total=False): - NextMarker: Optional[String] - Layers: Optional[LayersList] + NextMarker: String | None + Layers: LayersList | None class ListProvisionedConcurrencyConfigsRequest(ServiceRequest): FunctionName: FunctionName - Marker: Optional[String] - MaxItems: Optional[MaxProvisionedConcurrencyConfigListItems] + Marker: String | None + MaxItems: MaxProvisionedConcurrencyConfigListItems | None class ProvisionedConcurrencyConfigListItem(TypedDict, total=False): - FunctionArn: Optional[FunctionArn] - RequestedProvisionedConcurrentExecutions: Optional[PositiveInteger] - AvailableProvisionedConcurrentExecutions: Optional[NonNegativeInteger] - AllocatedProvisionedConcurrentExecutions: Optional[NonNegativeInteger] - Status: Optional[ProvisionedConcurrencyStatusEnum] - StatusReason: Optional[String] - LastModified: Optional[Timestamp] + FunctionArn: FunctionArn | None + RequestedProvisionedConcurrentExecutions: PositiveInteger | None + AvailableProvisionedConcurrentExecutions: NonNegativeInteger | None + AllocatedProvisionedConcurrentExecutions: NonNegativeInteger | None + Status: ProvisionedConcurrencyStatusEnum | None + StatusReason: String | None + LastModified: Timestamp | None -ProvisionedConcurrencyConfigList = List[ProvisionedConcurrencyConfigListItem] +ProvisionedConcurrencyConfigList = list[ProvisionedConcurrencyConfigListItem] class ListProvisionedConcurrencyConfigsResponse(TypedDict, total=False): - ProvisionedConcurrencyConfigs: Optional[ProvisionedConcurrencyConfigList] - NextMarker: Optional[String] + ProvisionedConcurrencyConfigs: ProvisionedConcurrencyConfigList | None + NextMarker: String | None class ListTagsRequest(ServiceRequest): @@ -1634,51 +2384,52 @@ class ListTagsRequest(ServiceRequest): class ListTagsResponse(TypedDict, total=False): - Tags: Optional[Tags] + Tags: Tags | None class ListVersionsByFunctionRequest(ServiceRequest): FunctionName: NamespacedFunctionName - Marker: Optional[String] - MaxItems: Optional[MaxListItems] + Marker: String | None + MaxItems: MaxListItems | None class ListVersionsByFunctionResponse(TypedDict, total=False): - NextMarker: Optional[String] - Versions: Optional[FunctionList] + NextMarker: String | None + Versions: FunctionList | None class PublishLayerVersionRequest(ServiceRequest): LayerName: LayerName - Description: Optional[Description] + Description: Description | None Content: LayerVersionContentInput - CompatibleRuntimes: Optional[CompatibleRuntimes] - LicenseInfo: Optional[LicenseInfo] - CompatibleArchitectures: Optional[CompatibleArchitectures] + CompatibleRuntimes: CompatibleRuntimes | None + LicenseInfo: LicenseInfo | None + CompatibleArchitectures: CompatibleArchitectures | None class PublishLayerVersionResponse(TypedDict, total=False): - Content: Optional[LayerVersionContentOutput] - LayerArn: Optional[LayerArn] - LayerVersionArn: Optional[LayerVersionArn] - Description: Optional[Description] - CreatedDate: Optional[Timestamp] - Version: Optional[LayerVersionNumber] - CompatibleRuntimes: Optional[CompatibleRuntimes] - LicenseInfo: Optional[LicenseInfo] - CompatibleArchitectures: Optional[CompatibleArchitectures] + Content: LayerVersionContentOutput | None + LayerArn: LayerArn | None + LayerVersionArn: LayerVersionArn | None + Description: Description | None + CreatedDate: Timestamp | None + Version: LayerVersionNumber | None + CompatibleRuntimes: CompatibleRuntimes | None + LicenseInfo: LicenseInfo | None + CompatibleArchitectures: CompatibleArchitectures | None class PublishVersionRequest(ServiceRequest): FunctionName: FunctionName - CodeSha256: Optional[String] - Description: Optional[Description] - RevisionId: Optional[String] + CodeSha256: String | None + Description: Description | None + RevisionId: String | None + PublishTo: FunctionVersionLatestPublished | None class PutFunctionCodeSigningConfigRequest(ServiceRequest): CodeSigningConfigArn: CodeSigningConfigArn - FunctionName: FunctionName + FunctionName: NamespacedFunctionName class PutFunctionCodeSigningConfigResponse(TypedDict, total=False): @@ -1692,11 +2443,11 @@ class PutFunctionConcurrencyRequest(ServiceRequest): class PutFunctionEventInvokeConfigRequest(ServiceRequest): - FunctionName: FunctionName - Qualifier: Optional[Qualifier] - MaximumRetryAttempts: Optional[MaximumRetryAttempts] - MaximumEventAgeInSeconds: Optional[MaximumEventAgeInSeconds] - DestinationConfig: Optional[DestinationConfig] + FunctionName: NamespacedFunctionName + Qualifier: NumericLatestPublishedOrAliasQualifier | None + MaximumRetryAttempts: MaximumRetryAttempts | None + MaximumEventAgeInSeconds: MaximumEventAgeInSeconds | None + DestinationConfig: DestinationConfig | None class PutFunctionRecursionConfigRequest(ServiceRequest): @@ -1705,7 +2456,17 @@ class PutFunctionRecursionConfigRequest(ServiceRequest): class PutFunctionRecursionConfigResponse(TypedDict, total=False): - RecursiveLoop: Optional[RecursiveLoop] + RecursiveLoop: RecursiveLoop | None + + +class PutFunctionScalingConfigRequest(ServiceRequest): + FunctionName: UnqualifiedFunctionName + Qualifier: PublishedFunctionQualifier + FunctionScalingConfig: FunctionScalingConfig | None + + +class PutFunctionScalingConfigResponse(TypedDict, total=False): + FunctionState: State | None class PutProvisionedConcurrencyConfigRequest(ServiceRequest): @@ -1715,42 +2476,77 @@ class PutProvisionedConcurrencyConfigRequest(ServiceRequest): class PutProvisionedConcurrencyConfigResponse(TypedDict, total=False): - RequestedProvisionedConcurrentExecutions: Optional[PositiveInteger] - AvailableProvisionedConcurrentExecutions: Optional[NonNegativeInteger] - AllocatedProvisionedConcurrentExecutions: Optional[NonNegativeInteger] - Status: Optional[ProvisionedConcurrencyStatusEnum] - StatusReason: Optional[String] - LastModified: Optional[Timestamp] + RequestedProvisionedConcurrentExecutions: PositiveInteger | None + AvailableProvisionedConcurrentExecutions: NonNegativeInteger | None + AllocatedProvisionedConcurrentExecutions: NonNegativeInteger | None + Status: ProvisionedConcurrencyStatusEnum | None + StatusReason: String | None + LastModified: Timestamp | None class PutRuntimeManagementConfigRequest(ServiceRequest): - FunctionName: FunctionName - Qualifier: Optional[Qualifier] + FunctionName: NamespacedFunctionName + Qualifier: NumericLatestPublishedOrAliasQualifier | None UpdateRuntimeOn: UpdateRuntimeOn - RuntimeVersionArn: Optional[RuntimeVersionArn] + RuntimeVersionArn: RuntimeVersionArn | None class PutRuntimeManagementConfigResponse(TypedDict, total=False): UpdateRuntimeOn: UpdateRuntimeOn FunctionArn: FunctionArn - RuntimeVersionArn: Optional[RuntimeVersionArn] + RuntimeVersionArn: RuntimeVersionArn | None class RemoveLayerVersionPermissionRequest(ServiceRequest): LayerName: LayerName VersionNumber: LayerVersionNumber StatementId: StatementId - RevisionId: Optional[String] + RevisionId: String | None class RemovePermissionRequest(ServiceRequest): - FunctionName: FunctionName + FunctionName: NamespacedFunctionName StatementId: NamespacedStatementId - Qualifier: Optional[Qualifier] - RevisionId: Optional[String] + Qualifier: NumericLatestPublishedOrAliasQualifier | None + RevisionId: String | None + + +class SendDurableExecutionCallbackFailureRequest(ServiceRequest): + CallbackId: CallbackId + Error: ErrorObject | None -TagKeyList = List[TagKey] +class SendDurableExecutionCallbackFailureResponse(TypedDict, total=False): + pass + + +class SendDurableExecutionCallbackHeartbeatRequest(ServiceRequest): + CallbackId: CallbackId + + +class SendDurableExecutionCallbackHeartbeatResponse(TypedDict, total=False): + pass + + +class SendDurableExecutionCallbackSuccessRequest(ServiceRequest): + Result: IO[BinaryOperationPayload] | None + CallbackId: CallbackId + + +class SendDurableExecutionCallbackSuccessResponse(TypedDict, total=False): + pass + + +class StopDurableExecutionRequest(ServiceRequest): + DurableExecutionArn: DurableExecutionArn + Error: ErrorObject | None + + +class StopDurableExecutionResponse(TypedDict, total=False): + StopTimestamp: ExecutionTimestamp + + +TagKeyList = list[TagKey] class TagResourceRequest(ServiceRequest): @@ -1766,17 +2562,26 @@ class UntagResourceRequest(ServiceRequest): class UpdateAliasRequest(ServiceRequest): FunctionName: FunctionName Name: Alias - FunctionVersion: Optional[Version] - Description: Optional[Description] - RoutingConfig: Optional[AliasRoutingConfiguration] - RevisionId: Optional[String] + FunctionVersion: VersionWithLatestPublished | None + Description: Description | None + RoutingConfig: AliasRoutingConfiguration | None + RevisionId: String | None + + +class UpdateCapacityProviderRequest(ServiceRequest): + CapacityProviderName: CapacityProviderName + CapacityProviderScalingConfig: CapacityProviderScalingConfig | None + + +class UpdateCapacityProviderResponse(TypedDict, total=False): + CapacityProvider: CapacityProvider class UpdateCodeSigningConfigRequest(ServiceRequest): CodeSigningConfigArn: CodeSigningConfigArn - Description: Optional[Description] - AllowedPublishers: Optional[AllowedPublishers] - CodeSigningPolicies: Optional[CodeSigningPolicies] + Description: Description | None + AllowedPublishers: AllowedPublishers | None + CodeSigningPolicies: CodeSigningPolicies | None class UpdateCodeSigningConfigResponse(TypedDict, total=False): @@ -1785,93 +2590,97 @@ class UpdateCodeSigningConfigResponse(TypedDict, total=False): class UpdateEventSourceMappingRequest(ServiceRequest): UUID: String - FunctionName: Optional[FunctionName] - Enabled: Optional[Enabled] - BatchSize: Optional[BatchSize] - FilterCriteria: Optional[FilterCriteria] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - DestinationConfig: Optional[DestinationConfig] - MaximumRecordAgeInSeconds: Optional[MaximumRecordAgeInSeconds] - BisectBatchOnFunctionError: Optional[BisectBatchOnFunctionError] - MaximumRetryAttempts: Optional[MaximumRetryAttemptsEventSourceMapping] - ParallelizationFactor: Optional[ParallelizationFactor] - SourceAccessConfigurations: Optional[SourceAccessConfigurations] - TumblingWindowInSeconds: Optional[TumblingWindowInSeconds] - FunctionResponseTypes: Optional[FunctionResponseTypeList] - ScalingConfig: Optional[ScalingConfig] - AmazonManagedKafkaEventSourceConfig: Optional[AmazonManagedKafkaEventSourceConfig] - SelfManagedKafkaEventSourceConfig: Optional[SelfManagedKafkaEventSourceConfig] - DocumentDBEventSourceConfig: Optional[DocumentDBEventSourceConfig] - KMSKeyArn: Optional[KMSKeyArn] - MetricsConfig: Optional[EventSourceMappingMetricsConfig] - ProvisionedPollerConfig: Optional[ProvisionedPollerConfig] + FunctionName: NamespacedFunctionName | None + Enabled: Enabled | None + BatchSize: BatchSize | None + FilterCriteria: FilterCriteria | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + DestinationConfig: DestinationConfig | None + MaximumRecordAgeInSeconds: MaximumRecordAgeInSeconds | None + BisectBatchOnFunctionError: BisectBatchOnFunctionError | None + MaximumRetryAttempts: MaximumRetryAttemptsEventSourceMapping | None + ParallelizationFactor: ParallelizationFactor | None + SourceAccessConfigurations: SourceAccessConfigurations | None + TumblingWindowInSeconds: TumblingWindowInSeconds | None + FunctionResponseTypes: FunctionResponseTypeList | None + ScalingConfig: ScalingConfig | None + AmazonManagedKafkaEventSourceConfig: AmazonManagedKafkaEventSourceConfig | None + SelfManagedKafkaEventSourceConfig: SelfManagedKafkaEventSourceConfig | None + DocumentDBEventSourceConfig: DocumentDBEventSourceConfig | None + KMSKeyArn: KMSKeyArn | None + MetricsConfig: EventSourceMappingMetricsConfig | None + LoggingConfig: EventSourceMappingLoggingConfig | None + ProvisionedPollerConfig: ProvisionedPollerConfig | None class UpdateFunctionCodeRequest(ServiceRequest): FunctionName: FunctionName - ZipFile: Optional[Blob] - S3Bucket: Optional[S3Bucket] - S3Key: Optional[S3Key] - S3ObjectVersion: Optional[S3ObjectVersion] - ImageUri: Optional[String] - Publish: Optional[Boolean] - DryRun: Optional[Boolean] - RevisionId: Optional[String] - Architectures: Optional[ArchitecturesList] - SourceKMSKeyArn: Optional[KMSKeyArn] + ZipFile: Blob | None + S3Bucket: S3Bucket | None + S3Key: S3Key | None + S3ObjectVersion: S3ObjectVersion | None + ImageUri: String | None + Publish: Boolean | None + DryRun: Boolean | None + RevisionId: String | None + Architectures: ArchitecturesList | None + SourceKMSKeyArn: KMSKeyArn | None + PublishTo: FunctionVersionLatestPublished | None class UpdateFunctionConfigurationRequest(ServiceRequest): FunctionName: FunctionName - Role: Optional[RoleArn] - Handler: Optional[Handler] - Description: Optional[Description] - Timeout: Optional[Timeout] - MemorySize: Optional[MemorySize] - VpcConfig: Optional[VpcConfig] - Environment: Optional[Environment] - Runtime: Optional[Runtime] - DeadLetterConfig: Optional[DeadLetterConfig] - KMSKeyArn: Optional[KMSKeyArn] - TracingConfig: Optional[TracingConfig] - RevisionId: Optional[String] - Layers: Optional[LayerList] - FileSystemConfigs: Optional[FileSystemConfigList] - ImageConfig: Optional[ImageConfig] - EphemeralStorage: Optional[EphemeralStorage] - SnapStart: Optional[SnapStart] - LoggingConfig: Optional[LoggingConfig] + Role: RoleArn | None + Handler: Handler | None + Description: Description | None + Timeout: Timeout | None + MemorySize: MemorySize | None + VpcConfig: VpcConfig | None + Environment: Environment | None + Runtime: Runtime | None + DeadLetterConfig: DeadLetterConfig | None + KMSKeyArn: KMSKeyArn | None + TracingConfig: TracingConfig | None + RevisionId: String | None + Layers: LayerList | None + FileSystemConfigs: FileSystemConfigList | None + ImageConfig: ImageConfig | None + EphemeralStorage: EphemeralStorage | None + SnapStart: SnapStart | None + LoggingConfig: LoggingConfig | None + CapacityProviderConfig: CapacityProviderConfig | None + DurableConfig: DurableConfig | None class UpdateFunctionEventInvokeConfigRequest(ServiceRequest): - FunctionName: FunctionName - Qualifier: Optional[Qualifier] - MaximumRetryAttempts: Optional[MaximumRetryAttempts] - MaximumEventAgeInSeconds: Optional[MaximumEventAgeInSeconds] - DestinationConfig: Optional[DestinationConfig] + FunctionName: NamespacedFunctionName + Qualifier: NumericLatestPublishedOrAliasQualifier | None + MaximumRetryAttempts: MaximumRetryAttempts | None + MaximumEventAgeInSeconds: MaximumEventAgeInSeconds | None + DestinationConfig: DestinationConfig | None class UpdateFunctionUrlConfigRequest(ServiceRequest): FunctionName: FunctionName - Qualifier: Optional[FunctionUrlQualifier] - AuthType: Optional[FunctionUrlAuthType] - Cors: Optional[Cors] - InvokeMode: Optional[InvokeMode] + Qualifier: FunctionUrlQualifier | None + AuthType: FunctionUrlAuthType | None + Cors: Cors | None + InvokeMode: InvokeMode | None class UpdateFunctionUrlConfigResponse(TypedDict, total=False): FunctionUrl: FunctionUrl FunctionArn: FunctionArn AuthType: FunctionUrlAuthType - Cors: Optional[Cors] + Cors: Cors | None CreationTime: Timestamp LastModifiedTime: Timestamp - InvokeMode: Optional[InvokeMode] + InvokeMode: InvokeMode | None class LambdaApi: - service = "lambda" - version = "2015-03-31" + service: str = "lambda" + version: str = "2015-03-31" @handler("AddLayerVersionPermission") def add_layer_version_permission( @@ -1892,34 +2701,62 @@ def add_layer_version_permission( def add_permission( self, context: RequestContext, - function_name: FunctionName, + function_name: NamespacedFunctionName, statement_id: StatementId, action: Action, principal: Principal, source_arn: Arn | None = None, source_account: SourceOwner | None = None, event_source_token: EventSourceToken | None = None, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, revision_id: String | None = None, principal_org_id: PrincipalOrgID | None = None, function_url_auth_type: FunctionUrlAuthType | None = None, + invoked_via_function_url: InvokedViaFunctionUrl | None = None, **kwargs, ) -> AddPermissionResponse: raise NotImplementedError + @handler("CheckpointDurableExecution") + def checkpoint_durable_execution( + self, + context: RequestContext, + durable_execution_arn: DurableExecutionArn, + checkpoint_token: CheckpointToken, + updates: OperationUpdates | None = None, + client_token: ClientToken | None = None, + **kwargs, + ) -> CheckpointDurableExecutionResponse: + raise NotImplementedError + @handler("CreateAlias") def create_alias( self, context: RequestContext, function_name: FunctionName, name: Alias, - function_version: Version, + function_version: VersionWithLatestPublished, description: Description | None = None, routing_config: AliasRoutingConfiguration | None = None, **kwargs, ) -> AliasConfiguration: raise NotImplementedError + @handler("CreateCapacityProvider") + def create_capacity_provider( + self, + context: RequestContext, + capacity_provider_name: CapacityProviderName, + vpc_config: CapacityProviderVpcConfig, + permissions_config: CapacityProviderPermissionsConfig, + instance_requirements: InstanceRequirements | None = None, + capacity_provider_scaling_config: CapacityProviderScalingConfig | None = None, + kms_key_arn: KMSKeyArnNonEmpty | None = None, + tags: Tags | None = None, + **kwargs, + ) -> CreateCapacityProviderResponse: + raise NotImplementedError + @handler("CreateCodeSigningConfig") def create_code_signing_config( self, @@ -1936,7 +2773,7 @@ def create_code_signing_config( def create_event_source_mapping( self, context: RequestContext, - function_name: FunctionName, + function_name: NamespacedFunctionName, event_source_arn: Arn | None = None, enabled: Enabled | None = None, batch_size: BatchSize | None = None, @@ -1962,6 +2799,7 @@ def create_event_source_mapping( document_db_event_source_config: DocumentDBEventSourceConfig | None = None, kms_key_arn: KMSKeyArn | None = None, metrics_config: EventSourceMappingMetricsConfig | None = None, + logging_config: EventSourceMappingLoggingConfig | None = None, provisioned_poller_config: ProvisionedPollerConfig | None = None, **kwargs, ) -> EventSourceMappingConfiguration: @@ -1995,6 +2833,10 @@ def create_function( ephemeral_storage: EphemeralStorage | None = None, snap_start: SnapStart | None = None, logging_config: LoggingConfig | None = None, + capacity_provider_config: CapacityProviderConfig | None = None, + publish_to: FunctionVersionLatestPublished | None = None, + durable_config: DurableConfig | None = None, + tenancy_config: TenancyConfig | None = None, **kwargs, ) -> FunctionConfiguration: raise NotImplementedError @@ -2018,6 +2860,12 @@ def delete_alias( ) -> None: raise NotImplementedError + @handler("DeleteCapacityProvider") + def delete_capacity_provider( + self, context: RequestContext, capacity_provider_name: CapacityProviderName, **kwargs + ) -> DeleteCapacityProviderResponse: + raise NotImplementedError + @handler("DeleteCodeSigningConfig") def delete_code_signing_config( self, context: RequestContext, code_signing_config_arn: CodeSigningConfigArn, **kwargs @@ -2034,15 +2882,15 @@ def delete_event_source_mapping( def delete_function( self, context: RequestContext, - function_name: FunctionName, - qualifier: Qualifier | None = None, + function_name: NamespacedFunctionName, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, - ) -> None: + ) -> DeleteFunctionResponse: raise NotImplementedError @handler("DeleteFunctionCodeSigningConfig") def delete_function_code_signing_config( - self, context: RequestContext, function_name: FunctionName, **kwargs + self, context: RequestContext, function_name: NamespacedFunctionName, **kwargs ) -> None: raise NotImplementedError @@ -2056,8 +2904,8 @@ def delete_function_concurrency( def delete_function_event_invoke_config( self, context: RequestContext, - function_name: FunctionName, - qualifier: Qualifier | None = None, + function_name: NamespacedFunctionName, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -2098,12 +2946,49 @@ def get_alias( ) -> AliasConfiguration: raise NotImplementedError + @handler("GetCapacityProvider") + def get_capacity_provider( + self, context: RequestContext, capacity_provider_name: CapacityProviderName, **kwargs + ) -> GetCapacityProviderResponse: + raise NotImplementedError + @handler("GetCodeSigningConfig") def get_code_signing_config( self, context: RequestContext, code_signing_config_arn: CodeSigningConfigArn, **kwargs ) -> GetCodeSigningConfigResponse: raise NotImplementedError + @handler("GetDurableExecution") + def get_durable_execution( + self, context: RequestContext, durable_execution_arn: DurableExecutionArn, **kwargs + ) -> GetDurableExecutionResponse: + raise NotImplementedError + + @handler("GetDurableExecutionHistory") + def get_durable_execution_history( + self, + context: RequestContext, + durable_execution_arn: DurableExecutionArn, + include_execution_data: IncludeExecutionData | None = None, + max_items: ItemCount | None = None, + marker: String | None = None, + reverse_order: ReverseOrder | None = None, + **kwargs, + ) -> GetDurableExecutionHistoryResponse: + raise NotImplementedError + + @handler("GetDurableExecutionState") + def get_durable_execution_state( + self, + context: RequestContext, + durable_execution_arn: DurableExecutionArn, + checkpoint_token: CheckpointToken, + marker: String | None = None, + max_items: ItemCount | None = None, + **kwargs, + ) -> GetDurableExecutionStateResponse: + raise NotImplementedError + @handler("GetEventSourceMapping") def get_event_source_mapping( self, context: RequestContext, uuid: String, **kwargs @@ -2115,14 +3000,14 @@ def get_function( self, context: RequestContext, function_name: NamespacedFunctionName, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> GetFunctionResponse: raise NotImplementedError @handler("GetFunctionCodeSigningConfig") def get_function_code_signing_config( - self, context: RequestContext, function_name: FunctionName, **kwargs + self, context: RequestContext, function_name: NamespacedFunctionName, **kwargs ) -> GetFunctionCodeSigningConfigResponse: raise NotImplementedError @@ -2137,7 +3022,7 @@ def get_function_configuration( self, context: RequestContext, function_name: NamespacedFunctionName, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> FunctionConfiguration: raise NotImplementedError @@ -2146,8 +3031,8 @@ def get_function_configuration( def get_function_event_invoke_config( self, context: RequestContext, - function_name: FunctionName, - qualifier: Qualifier | None = None, + function_name: NamespacedFunctionName, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> FunctionEventInvokeConfig: raise NotImplementedError @@ -2158,6 +3043,16 @@ def get_function_recursion_config( ) -> GetFunctionRecursionConfigResponse: raise NotImplementedError + @handler("GetFunctionScalingConfig") + def get_function_scaling_config( + self, + context: RequestContext, + function_name: UnqualifiedFunctionName, + qualifier: PublishedFunctionQualifier, + **kwargs, + ) -> GetFunctionScalingConfigResponse: + raise NotImplementedError + @handler("GetFunctionUrlConfig") def get_function_url_config( self, @@ -2199,7 +3094,7 @@ def get_policy( self, context: RequestContext, function_name: NamespacedFunctionName, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> GetPolicyResponse: raise NotImplementedError @@ -2215,7 +3110,7 @@ def get_runtime_management_config( self, context: RequestContext, function_name: NamespacedFunctionName, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> GetRuntimeManagementConfigResponse: raise NotImplementedError @@ -2228,8 +3123,10 @@ def invoke( invocation_type: InvocationType | None = None, log_type: LogType | None = None, client_context: String | None = None, + durable_execution_name: DurableExecutionName | None = None, payload: IO[Blob] | None = None, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, + tenant_id: TenantId | None = None, **kwargs, ) -> InvocationResponse: raise NotImplementedError @@ -2252,8 +3149,9 @@ def invoke_with_response_stream( invocation_type: ResponseStreamingInvocationType | None = None, log_type: LogType | None = None, client_context: String | None = None, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, payload: IO[Blob] | None = None, + tenant_id: TenantId | None = None, **kwargs, ) -> InvokeWithResponseStreamResponse: raise NotImplementedError @@ -2263,13 +3161,24 @@ def list_aliases( self, context: RequestContext, function_name: FunctionName, - function_version: Version | None = None, + function_version: VersionWithLatestPublished | None = None, marker: String | None = None, max_items: MaxListItems | None = None, **kwargs, ) -> ListAliasesResponse: raise NotImplementedError + @handler("ListCapacityProviders") + def list_capacity_providers( + self, + context: RequestContext, + state: CapacityProviderState | None = None, + marker: String | None = None, + max_items: MaxFiftyListItems | None = None, + **kwargs, + ) -> ListCapacityProvidersResponse: + raise NotImplementedError + @handler("ListCodeSigningConfigs") def list_code_signing_configs( self, @@ -2280,12 +3189,29 @@ def list_code_signing_configs( ) -> ListCodeSigningConfigsResponse: raise NotImplementedError + @handler("ListDurableExecutionsByFunction") + def list_durable_executions_by_function( + self, + context: RequestContext, + function_name: NamespacedFunctionName, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, + durable_execution_name: DurableExecutionName | None = None, + statuses: ExecutionStatusList | None = None, + started_after: ExecutionTimestamp | None = None, + started_before: ExecutionTimestamp | None = None, + reverse_order: ReverseOrder | None = None, + marker: String | None = None, + max_items: ItemCount | None = None, + **kwargs, + ) -> ListDurableExecutionsByFunctionResponse: + raise NotImplementedError + @handler("ListEventSourceMappings") def list_event_source_mappings( self, context: RequestContext, event_source_arn: Arn | None = None, - function_name: FunctionName | None = None, + function_name: NamespacedFunctionName | None = None, marker: String | None = None, max_items: MaxListItems | None = None, **kwargs, @@ -2296,7 +3222,7 @@ def list_event_source_mappings( def list_function_event_invoke_configs( self, context: RequestContext, - function_name: FunctionName, + function_name: NamespacedFunctionName, marker: String | None = None, max_items: MaxFunctionEventInvokeConfigListItems | None = None, **kwargs, @@ -2314,6 +3240,17 @@ def list_function_url_configs( ) -> ListFunctionUrlConfigsResponse: raise NotImplementedError + @handler("ListFunctionVersionsByCapacityProvider") + def list_function_versions_by_capacity_provider( + self, + context: RequestContext, + capacity_provider_name: CapacityProviderName, + marker: String | None = None, + max_items: MaxFiftyListItems | None = None, + **kwargs, + ) -> ListFunctionVersionsByCapacityProviderResponse: + raise NotImplementedError + @handler("ListFunctions") def list_functions( self, @@ -2412,6 +3349,7 @@ def publish_version( code_sha256: String | None = None, description: Description | None = None, revision_id: String | None = None, + publish_to: FunctionVersionLatestPublished | None = None, **kwargs, ) -> FunctionConfiguration: raise NotImplementedError @@ -2421,7 +3359,7 @@ def put_function_code_signing_config( self, context: RequestContext, code_signing_config_arn: CodeSigningConfigArn, - function_name: FunctionName, + function_name: NamespacedFunctionName, **kwargs, ) -> PutFunctionCodeSigningConfigResponse: raise NotImplementedError @@ -2440,8 +3378,8 @@ def put_function_concurrency( def put_function_event_invoke_config( self, context: RequestContext, - function_name: FunctionName, - qualifier: Qualifier | None = None, + function_name: NamespacedFunctionName, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, maximum_retry_attempts: MaximumRetryAttempts | None = None, maximum_event_age_in_seconds: MaximumEventAgeInSeconds | None = None, destination_config: DestinationConfig | None = None, @@ -2459,6 +3397,17 @@ def put_function_recursion_config( ) -> PutFunctionRecursionConfigResponse: raise NotImplementedError + @handler("PutFunctionScalingConfig") + def put_function_scaling_config( + self, + context: RequestContext, + function_name: UnqualifiedFunctionName, + qualifier: PublishedFunctionQualifier, + function_scaling_config: FunctionScalingConfig | None = None, + **kwargs, + ) -> PutFunctionScalingConfigResponse: + raise NotImplementedError + @handler("PutProvisionedConcurrencyConfig") def put_provisioned_concurrency_config( self, @@ -2474,9 +3423,9 @@ def put_provisioned_concurrency_config( def put_runtime_management_config( self, context: RequestContext, - function_name: FunctionName, + function_name: NamespacedFunctionName, update_runtime_on: UpdateRuntimeOn, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, runtime_version_arn: RuntimeVersionArn | None = None, **kwargs, ) -> PutRuntimeManagementConfigResponse: @@ -2498,14 +3447,50 @@ def remove_layer_version_permission( def remove_permission( self, context: RequestContext, - function_name: FunctionName, + function_name: NamespacedFunctionName, statement_id: NamespacedStatementId, - qualifier: Qualifier | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, revision_id: String | None = None, **kwargs, ) -> None: raise NotImplementedError + @handler("SendDurableExecutionCallbackFailure") + def send_durable_execution_callback_failure( + self, + context: RequestContext, + callback_id: CallbackId, + error: ErrorObject | None = None, + **kwargs, + ) -> SendDurableExecutionCallbackFailureResponse: + raise NotImplementedError + + @handler("SendDurableExecutionCallbackHeartbeat") + def send_durable_execution_callback_heartbeat( + self, context: RequestContext, callback_id: CallbackId, **kwargs + ) -> SendDurableExecutionCallbackHeartbeatResponse: + raise NotImplementedError + + @handler("SendDurableExecutionCallbackSuccess") + def send_durable_execution_callback_success( + self, + context: RequestContext, + callback_id: CallbackId, + result: IO[BinaryOperationPayload] | None = None, + **kwargs, + ) -> SendDurableExecutionCallbackSuccessResponse: + raise NotImplementedError + + @handler("StopDurableExecution") + def stop_durable_execution( + self, + context: RequestContext, + durable_execution_arn: DurableExecutionArn, + error: ErrorObject | None = None, + **kwargs, + ) -> StopDurableExecutionResponse: + raise NotImplementedError + @handler("TagResource") def tag_resource( self, context: RequestContext, resource: TaggableResource, tags: Tags, **kwargs @@ -2524,7 +3509,7 @@ def update_alias( context: RequestContext, function_name: FunctionName, name: Alias, - function_version: Version | None = None, + function_version: VersionWithLatestPublished | None = None, description: Description | None = None, routing_config: AliasRoutingConfiguration | None = None, revision_id: String | None = None, @@ -2532,6 +3517,16 @@ def update_alias( ) -> AliasConfiguration: raise NotImplementedError + @handler("UpdateCapacityProvider") + def update_capacity_provider( + self, + context: RequestContext, + capacity_provider_name: CapacityProviderName, + capacity_provider_scaling_config: CapacityProviderScalingConfig | None = None, + **kwargs, + ) -> UpdateCapacityProviderResponse: + raise NotImplementedError + @handler("UpdateCodeSigningConfig") def update_code_signing_config( self, @@ -2549,7 +3544,7 @@ def update_event_source_mapping( self, context: RequestContext, uuid: String, - function_name: FunctionName | None = None, + function_name: NamespacedFunctionName | None = None, enabled: Enabled | None = None, batch_size: BatchSize | None = None, filter_criteria: FilterCriteria | None = None, @@ -2568,6 +3563,7 @@ def update_event_source_mapping( document_db_event_source_config: DocumentDBEventSourceConfig | None = None, kms_key_arn: KMSKeyArn | None = None, metrics_config: EventSourceMappingMetricsConfig | None = None, + logging_config: EventSourceMappingLoggingConfig | None = None, provisioned_poller_config: ProvisionedPollerConfig | None = None, **kwargs, ) -> EventSourceMappingConfiguration: @@ -2588,6 +3584,7 @@ def update_function_code( revision_id: String | None = None, architectures: ArchitecturesList | None = None, source_kms_key_arn: KMSKeyArn | None = None, + publish_to: FunctionVersionLatestPublished | None = None, **kwargs, ) -> FunctionConfiguration: raise NotImplementedError @@ -2615,6 +3612,8 @@ def update_function_configuration( ephemeral_storage: EphemeralStorage | None = None, snap_start: SnapStart | None = None, logging_config: LoggingConfig | None = None, + capacity_provider_config: CapacityProviderConfig | None = None, + durable_config: DurableConfig | None = None, **kwargs, ) -> FunctionConfiguration: raise NotImplementedError @@ -2623,8 +3622,8 @@ def update_function_configuration( def update_function_event_invoke_config( self, context: RequestContext, - function_name: FunctionName, - qualifier: Qualifier | None = None, + function_name: NamespacedFunctionName, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, maximum_retry_attempts: MaximumRetryAttempts | None = None, maximum_event_age_in_seconds: MaximumEventAgeInSeconds | None = None, destination_config: DestinationConfig | None = None, diff --git a/localstack-core/localstack/aws/api/logs/__init__.py b/localstack-core/localstack/aws/api/logs/__init__.py index 1d93648315038..aa641e6167225 100644 --- a/localstack-core/localstack/aws/api/logs/__init__.py +++ b/localstack-core/localstack/aws/api/logs/__init__.py @@ -1,5 +1,6 @@ +from collections.abc import Iterator from enum import StrEnum -from typing import Dict, Iterator, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -14,13 +15,18 @@ ApplyOnTransformedLogs = bool Arn = str Baseline = bool +BatchId = str Boolean = bool ClientToken = str CollectionRetentionDays = int Column = str DataProtectionPolicyDocument = str +DataSourceName = str +DataSourceType = str +DataType = str Days = int DefaultValue = float +DeletionProtectionEnabled = bool Delimiter = str DeliveryDestinationName = str DeliveryDestinationPolicy = str @@ -44,9 +50,11 @@ EntityAttributesValue = str EntityKeyAttributesKey = str EntityKeyAttributesValue = str +ErrorMessage = str EventId = str EventMessage = str EventsLimit = int +ExpectedRevisionId = str ExportDestinationBucket = str ExportDestinationPrefix = str ExportTaskId = str @@ -56,6 +64,7 @@ FieldDelimiter = str FieldHeader = str FieldIndexName = str +FieldSelectionCriteria = str FilterCount = int FilterName = str FilterPattern = str @@ -63,7 +72,11 @@ Force = bool ForceUpdate = bool FromKey = str +GetScheduledQueryHistoryMaxResults = int GrokMatch = str +GroupingIdentifierKey = str +GroupingIdentifierValue = str +ImportId = str IncludeLinkedAccounts = bool InferredTokenName = str Integer = int @@ -80,17 +93,24 @@ ListLimit = int ListLogAnomalyDetectorsLimit = int ListLogGroupsForQueryMaxResults = int +ListLogGroupsRequestLimit = int +ListScheduledQueriesMaxResults = int +ListSourcesForS3TableIntegrationMaxResults = int Locale = str LogEventIndex = int +LogFieldName = str LogGroupArn = str +LogGroupCount = int LogGroupIdentifier = str LogGroupName = str LogGroupNamePattern = str LogGroupNameRegexPattern = str +LogObjectPointer = str LogRecordPointer = str LogStreamName = str LogStreamSearchedCompletely = bool LogType = str +MappingVersion = str MatchPattern = str Message = str MetricName = str @@ -125,6 +145,14 @@ ResourceIdentifier = str ResourceType = str RoleArn = str +S3TableIntegrationSourceIdentifier = str +S3TableIntegrationSourceStatusReason = str +S3Uri = str +ScheduleExpression = str +ScheduleTimezone = str +ScheduledQueryDescription = str +ScheduledQueryIdentifier = str +ScheduledQueryName = str SelectionCriteria = str SequenceToken = str Service = str @@ -134,7 +162,9 @@ SplitStringDelimiter = str StartFromHead = bool StatsValue = float +String = str Success = bool +SystemField = str TagKey = str TagValue = str Target = str @@ -152,6 +182,13 @@ WithKey = str +class ActionStatus(StrEnum): + IN_PROGRESS = "IN_PROGRESS" + CLIENT_ERROR = "CLIENT_ERROR" + FAILED = "FAILED" + COMPLETE = "COMPLETE" + + class AnomalyDetectorStatus(StrEnum): INITIALIZING = "INITIALIZING" TRAINING = "TRAINING" @@ -172,6 +209,7 @@ class DeliveryDestinationType(StrEnum): S3 = "S3" CWL = "CWL" FH = "FH" + XRAY = "XRAY" class Distribution(StrEnum): @@ -206,6 +244,14 @@ class EventSource(StrEnum): AWSWAF = "AWSWAF" +class ExecutionStatus(StrEnum): + Running = "Running" + InvalidQuery = "InvalidQuery" + Complete = "Complete" + Failed = "Failed" + Timeout = "Timeout" + + class ExportTaskStatusCode(StrEnum): CANCELLED = "CANCELLED" COMPLETED = "COMPLETED" @@ -220,11 +266,23 @@ class FlattenedElement(StrEnum): last = "last" +class ImportStatus(StrEnum): + IN_PROGRESS = "IN_PROGRESS" + CANCELLED = "CANCELLED" + COMPLETED = "COMPLETED" + FAILED = "FAILED" + + class IndexSource(StrEnum): ACCOUNT = "ACCOUNT" LOG_GROUP = "LOG_GROUP" +class IndexType(StrEnum): + FACET = "FACET" + FIELD_INDEX = "FIELD_INDEX" + + class InheritedProperty(StrEnum): ACCOUNT_DATA_PROTECTION = "ACCOUNT_DATA_PROTECTION" @@ -239,6 +297,11 @@ class IntegrationType(StrEnum): OPENSEARCH = "OPENSEARCH" +class ListAggregateLogGroupSummariesGroupBy(StrEnum): + DATA_SOURCE_NAME_TYPE_AND_FORMAT = "DATA_SOURCE_NAME_TYPE_AND_FORMAT" + DATA_SOURCE_NAME_AND_TYPE = "DATA_SOURCE_NAME_AND_TYPE" + + class LogGroupClass(StrEnum): STANDARD = "STANDARD" INFREQUENT_ACCESS = "INFREQUENT_ACCESS" @@ -247,6 +310,7 @@ class LogGroupClass(StrEnum): class OCSFVersion(StrEnum): V1_1 = "V1.1" + V1_5 = "V1.5" class OpenSearchResourceStatusType(StrEnum): @@ -268,11 +332,17 @@ class OutputFormat(StrEnum): parquet = "parquet" +class PolicyScope(StrEnum): + ACCOUNT = "ACCOUNT" + RESOURCE = "RESOURCE" + + class PolicyType(StrEnum): DATA_PROTECTION_POLICY = "DATA_PROTECTION_POLICY" SUBSCRIPTION_FILTER_POLICY = "SUBSCRIPTION_FILTER_POLICY" FIELD_INDEX_POLICY = "FIELD_INDEX_POLICY" TRANSFORMER_POLICY = "TRANSFORMER_POLICY" + METRIC_EXTRACTION_POLICY = "METRIC_EXTRACTION_POLICY" class QueryLanguage(StrEnum): @@ -291,6 +361,22 @@ class QueryStatus(StrEnum): Unknown = "Unknown" +class S3TableIntegrationSourceStatus(StrEnum): + ACTIVE = "ACTIVE" + UNHEALTHY = "UNHEALTHY" + FAILED = "FAILED" + DATA_SOURCE_DELETE_IN_PROGRESS = "DATA_SOURCE_DELETE_IN_PROGRESS" + + +class ScheduledQueryDestinationType(StrEnum): + S3 = "S3" + + +class ScheduledQueryState(StrEnum): + ENABLED = "ENABLED" + DISABLED = "DISABLED" + + class Scope(StrEnum): ALL = "ALL" @@ -370,7 +456,19 @@ class DataAlreadyAcceptedException(ServiceException): code: str = "DataAlreadyAcceptedException" sender_fault: bool = False status_code: int = 400 - expectedSequenceToken: Optional[SequenceToken] + expectedSequenceToken: SequenceToken | None + + +class InternalServerException(ServiceException): + code: str = "InternalServerException" + sender_fault: bool = False + status_code: int = 400 + + +class InternalStreamingException(ServiceException): + code: str = "InternalStreamingException" + sender_fault: bool = False + status_code: int = 400 class InvalidOperationException(ServiceException): @@ -389,7 +487,7 @@ class InvalidSequenceTokenException(ServiceException): code: str = "InvalidSequenceTokenException" sender_fault: bool = False status_code: int = 400 - expectedSequenceToken: Optional[SequenceToken] + expectedSequenceToken: SequenceToken | None class LimitExceededException(ServiceException): @@ -399,20 +497,20 @@ class LimitExceededException(ServiceException): class QueryCompileErrorLocation(TypedDict, total=False): - startCharOffset: Optional[QueryCharOffset] - endCharOffset: Optional[QueryCharOffset] + startCharOffset: QueryCharOffset | None + endCharOffset: QueryCharOffset | None class QueryCompileError(TypedDict, total=False): - location: Optional[QueryCompileErrorLocation] - message: Optional[Message] + location: QueryCompileErrorLocation | None + message: Message | None class MalformedQueryException(ServiceException): code: str = "MalformedQueryException" sender_fault: bool = False status_code: int = 400 - queryCompileError: Optional[QueryCompileError] + queryCompileError: QueryCompileError | None class OperationAbortedException(ServiceException): @@ -467,7 +565,7 @@ class TooManyTagsException(ServiceException): code: str = "TooManyTagsException" sender_fault: bool = False status_code: int = 400 - resourceName: Optional[AmazonResourceName] + resourceName: AmazonResourceName | None class UnrecognizedClientException(ServiceException): @@ -482,70 +580,84 @@ class ValidationException(ServiceException): status_code: int = 400 -AccountIds = List[AccountId] +AccountIds = list[AccountId] Timestamp = int class AccountPolicy(TypedDict, total=False): - policyName: Optional[PolicyName] - policyDocument: Optional[AccountPolicyDocument] - lastUpdatedTime: Optional[Timestamp] - policyType: Optional[PolicyType] - scope: Optional[Scope] - selectionCriteria: Optional[SelectionCriteria] - accountId: Optional[AccountId] + policyName: PolicyName | None + policyDocument: AccountPolicyDocument | None + lastUpdatedTime: Timestamp | None + policyType: PolicyType | None + scope: Scope | None + selectionCriteria: SelectionCriteria | None + accountId: AccountId | None -AccountPolicies = List[AccountPolicy] +AccountPolicies = list[AccountPolicy] class AddKeyEntry(TypedDict, total=False): key: Key value: AddKeyValue - overwriteIfExists: Optional[OverwriteIfExists] + overwriteIfExists: OverwriteIfExists | None -AddKeyEntries = List[AddKeyEntry] +AddKeyEntries = list[AddKeyEntry] class AddKeys(TypedDict, total=False): entries: AddKeyEntries -AllowedFieldDelimiters = List[FieldDelimiter] +class GroupingIdentifier(TypedDict, total=False): + key: GroupingIdentifierKey | None + value: GroupingIdentifierValue | None + + +GroupingIdentifiers = list[GroupingIdentifier] + + +class AggregateLogGroupSummary(TypedDict, total=False): + logGroupCount: LogGroupCount | None + groupingIdentifiers: GroupingIdentifiers | None + + +AggregateLogGroupSummaries = list[AggregateLogGroupSummary] +AllowedFieldDelimiters = list[FieldDelimiter] class RecordField(TypedDict, total=False): - name: Optional[FieldHeader] - mandatory: Optional[Boolean] + name: FieldHeader | None + mandatory: Boolean | None -AllowedFields = List[RecordField] +AllowedFields = list[RecordField] EpochMillis = int -LogGroupArnList = List[LogGroupArn] +LogGroupArnList = list[LogGroupArn] TokenValue = int -Enumerations = Dict[TokenString, TokenValue] +Enumerations = dict[TokenString, TokenValue] class PatternToken(TypedDict, total=False): - dynamicTokenPosition: Optional[DynamicTokenPosition] - isDynamic: Optional[Boolean] - tokenString: Optional[TokenString] - enumerations: Optional[Enumerations] - inferredTokenName: Optional[InferredTokenName] + dynamicTokenPosition: DynamicTokenPosition | None + isDynamic: Boolean | None + tokenString: TokenString | None + enumerations: Enumerations | None + inferredTokenName: InferredTokenName | None -PatternTokens = List[PatternToken] +PatternTokens = list[PatternToken] class LogEvent(TypedDict, total=False): - timestamp: Optional[Timestamp] - message: Optional[EventMessage] + timestamp: Timestamp | None + message: EventMessage | None -LogSamples = List[LogEvent] +LogSamples = list[LogEvent] Count = int -Histogram = Dict[Time, Count] +Histogram = dict[Time, Count] class Anomaly(TypedDict, total=False): @@ -553,8 +665,8 @@ class Anomaly(TypedDict, total=False): patternId: PatternId anomalyDetectorArn: AnomalyDetectorArn patternString: PatternString - patternRegex: Optional[PatternRegex] - priority: Optional[Priority] + patternRegex: PatternRegex | None + priority: Priority | None firstSeen: EpochMillis lastSeen: EpochMillis description: Description @@ -564,164 +676,215 @@ class Anomaly(TypedDict, total=False): logSamples: LogSamples patternTokens: PatternTokens logGroupArnList: LogGroupArnList - suppressed: Optional[Boolean] - suppressedDate: Optional[EpochMillis] - suppressedUntil: Optional[EpochMillis] - isPatternLevelSuppression: Optional[Boolean] + suppressed: Boolean | None + suppressedDate: EpochMillis | None + suppressedUntil: EpochMillis | None + isPatternLevelSuppression: Boolean | None -Anomalies = List[Anomaly] +Anomalies = list[Anomaly] AnomalyVisibilityTime = int class AnomalyDetector(TypedDict, total=False): - anomalyDetectorArn: Optional[AnomalyDetectorArn] - detectorName: Optional[DetectorName] - logGroupArnList: Optional[LogGroupArnList] - evaluationFrequency: Optional[EvaluationFrequency] - filterPattern: Optional[FilterPattern] - anomalyDetectorStatus: Optional[AnomalyDetectorStatus] - kmsKeyId: Optional[KmsKeyId] - creationTimeStamp: Optional[EpochMillis] - lastModifiedTimeStamp: Optional[EpochMillis] - anomalyVisibilityTime: Optional[AnomalyVisibilityTime] + anomalyDetectorArn: AnomalyDetectorArn | None + detectorName: DetectorName | None + logGroupArnList: LogGroupArnList | None + evaluationFrequency: EvaluationFrequency | None + filterPattern: FilterPattern | None + anomalyDetectorStatus: AnomalyDetectorStatus | None + kmsKeyId: KmsKeyId | None + creationTimeStamp: EpochMillis | None + lastModifiedTimeStamp: EpochMillis | None + anomalyVisibilityTime: AnomalyVisibilityTime | None -AnomalyDetectors = List[AnomalyDetector] +AnomalyDetectors = list[AnomalyDetector] class AssociateKmsKeyRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] + logGroupName: LogGroupName | None kmsKeyId: KmsKeyId - resourceIdentifier: Optional[ResourceIdentifier] + resourceIdentifier: ResourceIdentifier | None + + +class DataSource(TypedDict, total=False): + name: DataSourceName + type: DataSourceType | None + +class AssociateSourceToS3TableIntegrationRequest(ServiceRequest): + integrationArn: Arn + dataSource: DataSource -Columns = List[Column] + +class AssociateSourceToS3TableIntegrationResponse(TypedDict, total=False): + identifier: S3TableIntegrationSourceIdentifier | None + + +Columns = list[Column] class CSV(TypedDict, total=False): - quoteCharacter: Optional[QuoteCharacter] - delimiter: Optional[Delimiter] - columns: Optional[Columns] - source: Optional[Source] + quoteCharacter: QuoteCharacter | None + delimiter: Delimiter | None + columns: Columns | None + source: Source | None class CancelExportTaskRequest(ServiceRequest): taskId: ExportTaskId -RecordFields = List[FieldHeader] -OutputFormats = List[OutputFormat] +class CancelImportTaskRequest(ServiceRequest): + importId: ImportId + + +StoredBytes = int + + +class ImportStatistics(TypedDict, total=False): + bytesImported: StoredBytes | None + + +class CancelImportTaskResponse(TypedDict, total=False): + importId: ImportId | None + importStatistics: ImportStatistics | None + importStatus: ImportStatus | None + creationTime: Timestamp | None + lastUpdatedTime: Timestamp | None + + +RecordFields = list[FieldHeader] +OutputFormats = list[OutputFormat] class S3DeliveryConfiguration(TypedDict, total=False): - suffixPath: Optional[DeliverySuffixPath] - enableHiveCompatiblePath: Optional[Boolean] + suffixPath: DeliverySuffixPath | None + enableHiveCompatiblePath: Boolean | None class ConfigurationTemplateDeliveryConfigValues(TypedDict, total=False): - recordFields: Optional[RecordFields] - fieldDelimiter: Optional[FieldDelimiter] - s3DeliveryConfiguration: Optional[S3DeliveryConfiguration] + recordFields: RecordFields | None + fieldDelimiter: FieldDelimiter | None + s3DeliveryConfiguration: S3DeliveryConfiguration | None class ConfigurationTemplate(TypedDict, total=False): - service: Optional[Service] - logType: Optional[LogType] - resourceType: Optional[ResourceType] - deliveryDestinationType: Optional[DeliveryDestinationType] - defaultDeliveryConfigValues: Optional[ConfigurationTemplateDeliveryConfigValues] - allowedFields: Optional[AllowedFields] - allowedOutputFormats: Optional[OutputFormats] - allowedActionForAllowVendedLogsDeliveryForResource: Optional[ - AllowedActionForAllowVendedLogsDeliveryForResource - ] - allowedFieldDelimiters: Optional[AllowedFieldDelimiters] - allowedSuffixPathFields: Optional[RecordFields] + service: Service | None + logType: LogType | None + resourceType: ResourceType | None + deliveryDestinationType: DeliveryDestinationType | None + defaultDeliveryConfigValues: ConfigurationTemplateDeliveryConfigValues | None + allowedFields: AllowedFields | None + allowedOutputFormats: OutputFormats | None + allowedActionForAllowVendedLogsDeliveryForResource: ( + AllowedActionForAllowVendedLogsDeliveryForResource | None + ) + allowedFieldDelimiters: AllowedFieldDelimiters | None + allowedSuffixPathFields: RecordFields | None -ConfigurationTemplates = List[ConfigurationTemplate] +ConfigurationTemplates = list[ConfigurationTemplate] class CopyValueEntry(TypedDict, total=False): source: Source target: Target - overwriteIfExists: Optional[OverwriteIfExists] + overwriteIfExists: OverwriteIfExists | None -CopyValueEntries = List[CopyValueEntry] +CopyValueEntries = list[CopyValueEntry] class CopyValue(TypedDict, total=False): entries: CopyValueEntries -Tags = Dict[TagKey, TagValue] +Tags = dict[TagKey, TagValue] class CreateDeliveryRequest(ServiceRequest): deliverySourceName: DeliverySourceName deliveryDestinationArn: Arn - recordFields: Optional[RecordFields] - fieldDelimiter: Optional[FieldDelimiter] - s3DeliveryConfiguration: Optional[S3DeliveryConfiguration] - tags: Optional[Tags] + recordFields: RecordFields | None + fieldDelimiter: FieldDelimiter | None + s3DeliveryConfiguration: S3DeliveryConfiguration | None + tags: Tags | None class Delivery(TypedDict, total=False): - id: Optional[DeliveryId] - arn: Optional[Arn] - deliverySourceName: Optional[DeliverySourceName] - deliveryDestinationArn: Optional[Arn] - deliveryDestinationType: Optional[DeliveryDestinationType] - recordFields: Optional[RecordFields] - fieldDelimiter: Optional[FieldDelimiter] - s3DeliveryConfiguration: Optional[S3DeliveryConfiguration] - tags: Optional[Tags] + id: DeliveryId | None + arn: Arn | None + deliverySourceName: DeliverySourceName | None + deliveryDestinationArn: Arn | None + deliveryDestinationType: DeliveryDestinationType | None + recordFields: RecordFields | None + fieldDelimiter: FieldDelimiter | None + s3DeliveryConfiguration: S3DeliveryConfiguration | None + tags: Tags | None class CreateDeliveryResponse(TypedDict, total=False): - delivery: Optional[Delivery] + delivery: Delivery | None CreateExportTaskRequest = TypedDict( "CreateExportTaskRequest", { - "taskName": Optional[ExportTaskName], + "taskName": ExportTaskName | None, "logGroupName": LogGroupName, - "logStreamNamePrefix": Optional[LogStreamName], + "logStreamNamePrefix": LogStreamName | None, "from": Timestamp, "to": Timestamp, "destination": ExportDestinationBucket, - "destinationPrefix": Optional[ExportDestinationPrefix], + "destinationPrefix": ExportDestinationPrefix | None, }, total=False, ) class CreateExportTaskResponse(TypedDict, total=False): - taskId: Optional[ExportTaskId] + taskId: ExportTaskId | None + + +class ImportFilter(TypedDict, total=False): + startEventTime: Timestamp | None + endEventTime: Timestamp | None + + +class CreateImportTaskRequest(ServiceRequest): + importSourceArn: Arn + importRoleArn: RoleArn + importFilter: ImportFilter | None + + +class CreateImportTaskResponse(TypedDict, total=False): + importId: ImportId | None + importDestinationArn: Arn | None + creationTime: Timestamp | None class CreateLogAnomalyDetectorRequest(ServiceRequest): logGroupArnList: LogGroupArnList - detectorName: Optional[DetectorName] - evaluationFrequency: Optional[EvaluationFrequency] - filterPattern: Optional[FilterPattern] - kmsKeyId: Optional[DetectorKmsKeyArn] - anomalyVisibilityTime: Optional[AnomalyVisibilityTime] - tags: Optional[Tags] + detectorName: DetectorName | None + evaluationFrequency: EvaluationFrequency | None + filterPattern: FilterPattern | None + kmsKeyId: DetectorKmsKeyArn | None + anomalyVisibilityTime: AnomalyVisibilityTime | None + tags: Tags | None class CreateLogAnomalyDetectorResponse(TypedDict, total=False): - anomalyDetectorArn: Optional[AnomalyDetectorArn] + anomalyDetectorArn: AnomalyDetectorArn | None class CreateLogGroupRequest(ServiceRequest): logGroupName: LogGroupName - kmsKeyId: Optional[KmsKeyId] - tags: Optional[Tags] - logGroupClass: Optional[LogGroupClass] + kmsKeyId: KmsKeyId | None + tags: Tags | None + logGroupClass: LogGroupClass | None + deletionProtectionEnabled: DeletionProtectionEnabled | None class CreateLogStreamRequest(ServiceRequest): @@ -729,18 +892,62 @@ class CreateLogStreamRequest(ServiceRequest): logStreamName: LogStreamName -DashboardViewerPrincipals = List[Arn] -MatchPatterns = List[MatchPattern] +class S3Configuration(TypedDict, total=False): + destinationIdentifier: S3Uri + roleArn: RoleArn + + +class DestinationConfiguration(TypedDict, total=False): + s3Configuration: S3Configuration + + +StartTimeOffset = int +ScheduledQueryLogGroupIdentifiers = list[LogGroupIdentifier] + + +class CreateScheduledQueryRequest(ServiceRequest): + name: ScheduledQueryName + description: ScheduledQueryDescription | None + queryLanguage: QueryLanguage + queryString: QueryString + logGroupIdentifiers: ScheduledQueryLogGroupIdentifiers | None + scheduleExpression: ScheduleExpression + timezone: ScheduleTimezone | None + startTimeOffset: StartTimeOffset | None + destinationConfiguration: DestinationConfiguration | None + scheduleStartTime: Timestamp | None + scheduleEndTime: Timestamp | None + executionRoleArn: RoleArn + state: ScheduledQueryState | None + tags: Tags | None + + +class CreateScheduledQueryResponse(TypedDict, total=False): + scheduledQueryArn: Arn | None + state: ScheduledQueryState | None + + +DashboardViewerPrincipals = list[Arn] +Data = bytes + + +class DataSourceFilter(TypedDict, total=False): + name: DataSourceName + type: DataSourceType | None + + +DataSourceFilters = list[DataSourceFilter] +MatchPatterns = list[MatchPattern] class DateTimeConverter(TypedDict, total=False): source: Source target: Target - targetFormat: Optional[TargetFormat] + targetFormat: TargetFormat | None matchPatterns: MatchPatterns - sourceTimezone: Optional[SourceTimezone] - targetTimezone: Optional[TargetTimezone] - locale: Optional[Locale] + sourceTimezone: SourceTimezone | None + targetTimezone: TargetTimezone | None + locale: Locale | None class DeleteAccountPolicyRequest(ServiceRequest): @@ -782,14 +989,14 @@ class DeleteIndexPolicyResponse(TypedDict, total=False): class DeleteIntegrationRequest(ServiceRequest): integrationName: IntegrationName - force: Optional[Force] + force: Force | None class DeleteIntegrationResponse(TypedDict, total=False): pass -DeleteWithKeys = List[WithKey] +DeleteWithKeys = list[WithKey] class DeleteKeys(TypedDict, total=False): @@ -819,17 +1026,27 @@ class DeleteQueryDefinitionRequest(ServiceRequest): class DeleteQueryDefinitionResponse(TypedDict, total=False): - success: Optional[Success] + success: Success | None class DeleteResourcePolicyRequest(ServiceRequest): - policyName: Optional[PolicyName] + policyName: PolicyName | None + resourceArn: Arn | None + expectedRevisionId: ExpectedRevisionId | None class DeleteRetentionPolicyRequest(ServiceRequest): logGroupName: LogGroupName +class DeleteScheduledQueryRequest(ServiceRequest): + identifier: ScheduledQueryIdentifier + + +class DeleteScheduledQueryResponse(TypedDict, total=False): + pass + + class DeleteSubscriptionFilterRequest(ServiceRequest): logGroupName: LogGroupName filterName: FilterName @@ -839,7 +1056,7 @@ class DeleteTransformerRequest(ServiceRequest): logGroupIdentifier: LogGroupIdentifier -Deliveries = List[Delivery] +Deliveries = list[Delivery] class DeliveryDestinationConfiguration(TypedDict, total=False): @@ -847,465 +1064,546 @@ class DeliveryDestinationConfiguration(TypedDict, total=False): class DeliveryDestination(TypedDict, total=False): - name: Optional[DeliveryDestinationName] - arn: Optional[Arn] - deliveryDestinationType: Optional[DeliveryDestinationType] - outputFormat: Optional[OutputFormat] - deliveryDestinationConfiguration: Optional[DeliveryDestinationConfiguration] - tags: Optional[Tags] + name: DeliveryDestinationName | None + arn: Arn | None + deliveryDestinationType: DeliveryDestinationType | None + outputFormat: OutputFormat | None + deliveryDestinationConfiguration: DeliveryDestinationConfiguration | None + tags: Tags | None -DeliveryDestinationTypes = List[DeliveryDestinationType] -DeliveryDestinations = List[DeliveryDestination] -ResourceArns = List[Arn] +DeliveryDestinationTypes = list[DeliveryDestinationType] +DeliveryDestinations = list[DeliveryDestination] +ResourceArns = list[Arn] class DeliverySource(TypedDict, total=False): - name: Optional[DeliverySourceName] - arn: Optional[Arn] - resourceArns: Optional[ResourceArns] - service: Optional[Service] - logType: Optional[LogType] - tags: Optional[Tags] + name: DeliverySourceName | None + arn: Arn | None + resourceArns: ResourceArns | None + service: Service | None + logType: LogType | None + tags: Tags | None -DeliverySources = List[DeliverySource] +DeliverySources = list[DeliverySource] class DescribeAccountPoliciesRequest(ServiceRequest): policyType: PolicyType - policyName: Optional[PolicyName] - accountIdentifiers: Optional[AccountIds] - nextToken: Optional[NextToken] + policyName: PolicyName | None + accountIdentifiers: AccountIds | None + nextToken: NextToken | None class DescribeAccountPoliciesResponse(TypedDict, total=False): - accountPolicies: Optional[AccountPolicies] - nextToken: Optional[NextToken] + accountPolicies: AccountPolicies | None + nextToken: NextToken | None -ResourceTypes = List[ResourceType] -LogTypes = List[LogType] +ResourceTypes = list[ResourceType] +LogTypes = list[LogType] class DescribeConfigurationTemplatesRequest(ServiceRequest): - service: Optional[Service] - logTypes: Optional[LogTypes] - resourceTypes: Optional[ResourceTypes] - deliveryDestinationTypes: Optional[DeliveryDestinationTypes] - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + service: Service | None + logTypes: LogTypes | None + resourceTypes: ResourceTypes | None + deliveryDestinationTypes: DeliveryDestinationTypes | None + nextToken: NextToken | None + limit: DescribeLimit | None class DescribeConfigurationTemplatesResponse(TypedDict, total=False): - configurationTemplates: Optional[ConfigurationTemplates] - nextToken: Optional[NextToken] + configurationTemplates: ConfigurationTemplates | None + nextToken: NextToken | None class DescribeDeliveriesRequest(ServiceRequest): - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + nextToken: NextToken | None + limit: DescribeLimit | None class DescribeDeliveriesResponse(TypedDict, total=False): - deliveries: Optional[Deliveries] - nextToken: Optional[NextToken] + deliveries: Deliveries | None + nextToken: NextToken | None class DescribeDeliveryDestinationsRequest(ServiceRequest): - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + nextToken: NextToken | None + limit: DescribeLimit | None class DescribeDeliveryDestinationsResponse(TypedDict, total=False): - deliveryDestinations: Optional[DeliveryDestinations] - nextToken: Optional[NextToken] + deliveryDestinations: DeliveryDestinations | None + nextToken: NextToken | None class DescribeDeliverySourcesRequest(ServiceRequest): - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + nextToken: NextToken | None + limit: DescribeLimit | None class DescribeDeliverySourcesResponse(TypedDict, total=False): - deliverySources: Optional[DeliverySources] - nextToken: Optional[NextToken] + deliverySources: DeliverySources | None + nextToken: NextToken | None class DescribeDestinationsRequest(ServiceRequest): - DestinationNamePrefix: Optional[DestinationName] - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + DestinationNamePrefix: DestinationName | None + nextToken: NextToken | None + limit: DescribeLimit | None class Destination(TypedDict, total=False): - destinationName: Optional[DestinationName] - targetArn: Optional[TargetArn] - roleArn: Optional[RoleArn] - accessPolicy: Optional[AccessPolicy] - arn: Optional[Arn] - creationTime: Optional[Timestamp] + destinationName: DestinationName | None + targetArn: TargetArn | None + roleArn: RoleArn | None + accessPolicy: AccessPolicy | None + arn: Arn | None + creationTime: Timestamp | None -Destinations = List[Destination] +Destinations = list[Destination] class DescribeDestinationsResponse(TypedDict, total=False): - destinations: Optional[Destinations] - nextToken: Optional[NextToken] + destinations: Destinations | None + nextToken: NextToken | None class DescribeExportTasksRequest(ServiceRequest): - taskId: Optional[ExportTaskId] - statusCode: Optional[ExportTaskStatusCode] - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + taskId: ExportTaskId | None + statusCode: ExportTaskStatusCode | None + nextToken: NextToken | None + limit: DescribeLimit | None class ExportTaskExecutionInfo(TypedDict, total=False): - creationTime: Optional[Timestamp] - completionTime: Optional[Timestamp] + creationTime: Timestamp | None + completionTime: Timestamp | None class ExportTaskStatus(TypedDict, total=False): - code: Optional[ExportTaskStatusCode] - message: Optional[ExportTaskStatusMessage] + code: ExportTaskStatusCode | None + message: ExportTaskStatusMessage | None ExportTask = TypedDict( "ExportTask", { - "taskId": Optional[ExportTaskId], - "taskName": Optional[ExportTaskName], - "logGroupName": Optional[LogGroupName], - "from": Optional[Timestamp], - "to": Optional[Timestamp], - "destination": Optional[ExportDestinationBucket], - "destinationPrefix": Optional[ExportDestinationPrefix], - "status": Optional[ExportTaskStatus], - "executionInfo": Optional[ExportTaskExecutionInfo], + "taskId": ExportTaskId | None, + "taskName": ExportTaskName | None, + "logGroupName": LogGroupName | None, + "from": Timestamp | None, + "to": Timestamp | None, + "destination": ExportDestinationBucket | None, + "destinationPrefix": ExportDestinationPrefix | None, + "status": ExportTaskStatus | None, + "executionInfo": ExportTaskExecutionInfo | None, }, total=False, ) -ExportTasks = List[ExportTask] +ExportTasks = list[ExportTask] class DescribeExportTasksResponse(TypedDict, total=False): - exportTasks: Optional[ExportTasks] - nextToken: Optional[NextToken] + exportTasks: ExportTasks | None + nextToken: NextToken | None -DescribeFieldIndexesLogGroupIdentifiers = List[LogGroupIdentifier] +DescribeFieldIndexesLogGroupIdentifiers = list[LogGroupIdentifier] class DescribeFieldIndexesRequest(ServiceRequest): logGroupIdentifiers: DescribeFieldIndexesLogGroupIdentifiers - nextToken: Optional[NextToken] + nextToken: NextToken | None class FieldIndex(TypedDict, total=False): - logGroupIdentifier: Optional[LogGroupIdentifier] - fieldIndexName: Optional[FieldIndexName] - lastScanTime: Optional[Timestamp] - firstEventTime: Optional[Timestamp] - lastEventTime: Optional[Timestamp] + logGroupIdentifier: LogGroupIdentifier | None + fieldIndexName: FieldIndexName | None + lastScanTime: Timestamp | None + firstEventTime: Timestamp | None + lastEventTime: Timestamp | None + type: IndexType | None -FieldIndexes = List[FieldIndex] +FieldIndexes = list[FieldIndex] class DescribeFieldIndexesResponse(TypedDict, total=False): - fieldIndexes: Optional[FieldIndexes] - nextToken: Optional[NextToken] + fieldIndexes: FieldIndexes | None + nextToken: NextToken | None + + +ImportStatusList = list[ImportStatus] + + +class DescribeImportTaskBatchesRequest(ServiceRequest): + importId: ImportId + batchImportStatus: ImportStatusList | None + limit: DescribeLimit | None + nextToken: NextToken | None + + +class ImportBatch(TypedDict, total=False): + batchId: BatchId + status: ImportStatus + errorMessage: ErrorMessage | None + + +ImportBatchList = list[ImportBatch] + + +class DescribeImportTaskBatchesResponse(TypedDict, total=False): + importSourceArn: Arn | None + importId: ImportId | None + importBatches: ImportBatchList | None + nextToken: NextToken | None + + +class DescribeImportTasksRequest(ServiceRequest): + importId: ImportId | None + importStatus: ImportStatus | None + importSourceArn: Arn | None + limit: DescribeLimit | None + nextToken: NextToken | None + + +class Import(TypedDict, total=False): + importId: ImportId | None + importSourceArn: Arn | None + importStatus: ImportStatus | None + importDestinationArn: Arn | None + importStatistics: ImportStatistics | None + importFilter: ImportFilter | None + creationTime: Timestamp | None + lastUpdatedTime: Timestamp | None + errorMessage: ErrorMessage | None + + +ImportList = list[Import] + +class DescribeImportTasksResponse(TypedDict, total=False): + imports: ImportList | None + nextToken: NextToken | None -DescribeIndexPoliciesLogGroupIdentifiers = List[LogGroupIdentifier] + +DescribeIndexPoliciesLogGroupIdentifiers = list[LogGroupIdentifier] class DescribeIndexPoliciesRequest(ServiceRequest): logGroupIdentifiers: DescribeIndexPoliciesLogGroupIdentifiers - nextToken: Optional[NextToken] + nextToken: NextToken | None class IndexPolicy(TypedDict, total=False): - logGroupIdentifier: Optional[LogGroupIdentifier] - lastUpdateTime: Optional[Timestamp] - policyDocument: Optional[PolicyDocument] - policyName: Optional[PolicyName] - source: Optional[IndexSource] + logGroupIdentifier: LogGroupIdentifier | None + lastUpdateTime: Timestamp | None + policyDocument: PolicyDocument | None + policyName: PolicyName | None + source: IndexSource | None -IndexPolicies = List[IndexPolicy] +IndexPolicies = list[IndexPolicy] class DescribeIndexPoliciesResponse(TypedDict, total=False): - indexPolicies: Optional[IndexPolicies] - nextToken: Optional[NextToken] + indexPolicies: IndexPolicies | None + nextToken: NextToken | None -DescribeLogGroupsLogGroupIdentifiers = List[LogGroupIdentifier] +DescribeLogGroupsLogGroupIdentifiers = list[LogGroupIdentifier] class DescribeLogGroupsRequest(ServiceRequest): - accountIdentifiers: Optional[AccountIds] - logGroupNamePrefix: Optional[LogGroupName] - logGroupNamePattern: Optional[LogGroupNamePattern] - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] - includeLinkedAccounts: Optional[IncludeLinkedAccounts] - logGroupClass: Optional[LogGroupClass] - logGroupIdentifiers: Optional[DescribeLogGroupsLogGroupIdentifiers] + accountIdentifiers: AccountIds | None + logGroupNamePrefix: LogGroupName | None + logGroupNamePattern: LogGroupNamePattern | None + nextToken: NextToken | None + limit: DescribeLimit | None + includeLinkedAccounts: IncludeLinkedAccounts | None + logGroupClass: LogGroupClass | None + logGroupIdentifiers: DescribeLogGroupsLogGroupIdentifiers | None -InheritedProperties = List[InheritedProperty] -StoredBytes = int +InheritedProperties = list[InheritedProperty] class LogGroup(TypedDict, total=False): - logGroupName: Optional[LogGroupName] - creationTime: Optional[Timestamp] - retentionInDays: Optional[Days] - metricFilterCount: Optional[FilterCount] - arn: Optional[Arn] - storedBytes: Optional[StoredBytes] - kmsKeyId: Optional[KmsKeyId] - dataProtectionStatus: Optional[DataProtectionStatus] - inheritedProperties: Optional[InheritedProperties] - logGroupClass: Optional[LogGroupClass] - logGroupArn: Optional[Arn] + logGroupName: LogGroupName | None + creationTime: Timestamp | None + retentionInDays: Days | None + metricFilterCount: FilterCount | None + arn: Arn | None + storedBytes: StoredBytes | None + kmsKeyId: KmsKeyId | None + dataProtectionStatus: DataProtectionStatus | None + inheritedProperties: InheritedProperties | None + logGroupClass: LogGroupClass | None + logGroupArn: Arn | None + deletionProtectionEnabled: DeletionProtectionEnabled | None -LogGroups = List[LogGroup] +LogGroups = list[LogGroup] class DescribeLogGroupsResponse(TypedDict, total=False): - logGroups: Optional[LogGroups] - nextToken: Optional[NextToken] + logGroups: LogGroups | None + nextToken: NextToken | None class DescribeLogStreamsRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] - logGroupIdentifier: Optional[LogGroupIdentifier] - logStreamNamePrefix: Optional[LogStreamName] - orderBy: Optional[OrderBy] - descending: Optional[Descending] - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + logGroupName: LogGroupName | None + logGroupIdentifier: LogGroupIdentifier | None + logStreamNamePrefix: LogStreamName | None + orderBy: OrderBy | None + descending: Descending | None + nextToken: NextToken | None + limit: DescribeLimit | None class LogStream(TypedDict, total=False): - logStreamName: Optional[LogStreamName] - creationTime: Optional[Timestamp] - firstEventTimestamp: Optional[Timestamp] - lastEventTimestamp: Optional[Timestamp] - lastIngestionTime: Optional[Timestamp] - uploadSequenceToken: Optional[SequenceToken] - arn: Optional[Arn] - storedBytes: Optional[StoredBytes] + logStreamName: LogStreamName | None + creationTime: Timestamp | None + firstEventTimestamp: Timestamp | None + lastEventTimestamp: Timestamp | None + lastIngestionTime: Timestamp | None + uploadSequenceToken: SequenceToken | None + arn: Arn | None + storedBytes: StoredBytes | None -LogStreams = List[LogStream] +LogStreams = list[LogStream] class DescribeLogStreamsResponse(TypedDict, total=False): - logStreams: Optional[LogStreams] - nextToken: Optional[NextToken] + logStreams: LogStreams | None + nextToken: NextToken | None class DescribeMetricFiltersRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] - filterNamePrefix: Optional[FilterName] - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] - metricName: Optional[MetricName] - metricNamespace: Optional[MetricNamespace] + logGroupName: LogGroupName | None + filterNamePrefix: FilterName | None + nextToken: NextToken | None + limit: DescribeLimit | None + metricName: MetricName | None + metricNamespace: MetricNamespace | None -Dimensions = Dict[DimensionsKey, DimensionsValue] +EmitSystemFields = list[SystemField] +Dimensions = dict[DimensionsKey, DimensionsValue] class MetricTransformation(TypedDict, total=False): metricName: MetricName metricNamespace: MetricNamespace metricValue: MetricValue - defaultValue: Optional[DefaultValue] - dimensions: Optional[Dimensions] - unit: Optional[StandardUnit] + defaultValue: DefaultValue | None + dimensions: Dimensions | None + unit: StandardUnit | None -MetricTransformations = List[MetricTransformation] +MetricTransformations = list[MetricTransformation] class MetricFilter(TypedDict, total=False): - filterName: Optional[FilterName] - filterPattern: Optional[FilterPattern] - metricTransformations: Optional[MetricTransformations] - creationTime: Optional[Timestamp] - logGroupName: Optional[LogGroupName] - applyOnTransformedLogs: Optional[ApplyOnTransformedLogs] + filterName: FilterName | None + filterPattern: FilterPattern | None + metricTransformations: MetricTransformations | None + creationTime: Timestamp | None + logGroupName: LogGroupName | None + applyOnTransformedLogs: ApplyOnTransformedLogs | None + fieldSelectionCriteria: FieldSelectionCriteria | None + emitSystemFieldDimensions: EmitSystemFields | None -MetricFilters = List[MetricFilter] +MetricFilters = list[MetricFilter] class DescribeMetricFiltersResponse(TypedDict, total=False): - metricFilters: Optional[MetricFilters] - nextToken: Optional[NextToken] + metricFilters: MetricFilters | None + nextToken: NextToken | None class DescribeQueriesRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] - status: Optional[QueryStatus] - maxResults: Optional[DescribeQueriesMaxResults] - nextToken: Optional[NextToken] - queryLanguage: Optional[QueryLanguage] + logGroupName: LogGroupName | None + status: QueryStatus | None + maxResults: DescribeQueriesMaxResults | None + nextToken: NextToken | None + queryLanguage: QueryLanguage | None class QueryInfo(TypedDict, total=False): - queryLanguage: Optional[QueryLanguage] - queryId: Optional[QueryId] - queryString: Optional[QueryString] - status: Optional[QueryStatus] - createTime: Optional[Timestamp] - logGroupName: Optional[LogGroupName] + queryLanguage: QueryLanguage | None + queryId: QueryId | None + queryString: QueryString | None + status: QueryStatus | None + createTime: Timestamp | None + logGroupName: LogGroupName | None -QueryInfoList = List[QueryInfo] +QueryInfoList = list[QueryInfo] class DescribeQueriesResponse(TypedDict, total=False): - queries: Optional[QueryInfoList] - nextToken: Optional[NextToken] + queries: QueryInfoList | None + nextToken: NextToken | None class DescribeQueryDefinitionsRequest(ServiceRequest): - queryLanguage: Optional[QueryLanguage] - queryDefinitionNamePrefix: Optional[QueryDefinitionName] - maxResults: Optional[QueryListMaxResults] - nextToken: Optional[NextToken] + queryLanguage: QueryLanguage | None + queryDefinitionNamePrefix: QueryDefinitionName | None + maxResults: QueryListMaxResults | None + nextToken: NextToken | None -LogGroupNames = List[LogGroupName] +LogGroupNames = list[LogGroupName] class QueryDefinition(TypedDict, total=False): - queryLanguage: Optional[QueryLanguage] - queryDefinitionId: Optional[QueryId] - name: Optional[QueryDefinitionName] - queryString: Optional[QueryDefinitionString] - lastModified: Optional[Timestamp] - logGroupNames: Optional[LogGroupNames] + queryLanguage: QueryLanguage | None + queryDefinitionId: QueryId | None + name: QueryDefinitionName | None + queryString: QueryDefinitionString | None + lastModified: Timestamp | None + logGroupNames: LogGroupNames | None -QueryDefinitionList = List[QueryDefinition] +QueryDefinitionList = list[QueryDefinition] class DescribeQueryDefinitionsResponse(TypedDict, total=False): - queryDefinitions: Optional[QueryDefinitionList] - nextToken: Optional[NextToken] + queryDefinitions: QueryDefinitionList | None + nextToken: NextToken | None class DescribeResourcePoliciesRequest(ServiceRequest): - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + nextToken: NextToken | None + limit: DescribeLimit | None + resourceArn: Arn | None + policyScope: PolicyScope | None class ResourcePolicy(TypedDict, total=False): - policyName: Optional[PolicyName] - policyDocument: Optional[PolicyDocument] - lastUpdatedTime: Optional[Timestamp] + policyName: PolicyName | None + policyDocument: PolicyDocument | None + lastUpdatedTime: Timestamp | None + policyScope: PolicyScope | None + resourceArn: Arn | None + revisionId: ExpectedRevisionId | None -ResourcePolicies = List[ResourcePolicy] +ResourcePolicies = list[ResourcePolicy] class DescribeResourcePoliciesResponse(TypedDict, total=False): - resourcePolicies: Optional[ResourcePolicies] - nextToken: Optional[NextToken] + resourcePolicies: ResourcePolicies | None + nextToken: NextToken | None class DescribeSubscriptionFiltersRequest(ServiceRequest): logGroupName: LogGroupName - filterNamePrefix: Optional[FilterName] - nextToken: Optional[NextToken] - limit: Optional[DescribeLimit] + filterNamePrefix: FilterName | None + nextToken: NextToken | None + limit: DescribeLimit | None class SubscriptionFilter(TypedDict, total=False): - filterName: Optional[FilterName] - logGroupName: Optional[LogGroupName] - filterPattern: Optional[FilterPattern] - destinationArn: Optional[DestinationArn] - roleArn: Optional[RoleArn] - distribution: Optional[Distribution] - applyOnTransformedLogs: Optional[ApplyOnTransformedLogs] - creationTime: Optional[Timestamp] + filterName: FilterName | None + logGroupName: LogGroupName | None + filterPattern: FilterPattern | None + destinationArn: DestinationArn | None + roleArn: RoleArn | None + distribution: Distribution | None + applyOnTransformedLogs: ApplyOnTransformedLogs | None + creationTime: Timestamp | None + fieldSelectionCriteria: FieldSelectionCriteria | None + emitSystemFields: EmitSystemFields | None -SubscriptionFilters = List[SubscriptionFilter] +SubscriptionFilters = list[SubscriptionFilter] class DescribeSubscriptionFiltersResponse(TypedDict, total=False): - subscriptionFilters: Optional[SubscriptionFilters] - nextToken: Optional[NextToken] + subscriptionFilters: SubscriptionFilters | None + nextToken: NextToken | None class DisassociateKmsKeyRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] - resourceIdentifier: Optional[ResourceIdentifier] + logGroupName: LogGroupName | None + resourceIdentifier: ResourceIdentifier | None + + +class DisassociateSourceFromS3TableIntegrationRequest(ServiceRequest): + identifier: S3TableIntegrationSourceIdentifier + +class DisassociateSourceFromS3TableIntegrationResponse(TypedDict, total=False): + identifier: S3TableIntegrationSourceIdentifier | None -EntityAttributes = Dict[EntityAttributesKey, EntityAttributesValue] -EntityKeyAttributes = Dict[EntityKeyAttributesKey, EntityKeyAttributesValue] + +EntityAttributes = dict[EntityAttributesKey, EntityAttributesValue] +EntityKeyAttributes = dict[EntityKeyAttributesKey, EntityKeyAttributesValue] class Entity(TypedDict, total=False): - keyAttributes: Optional[EntityKeyAttributes] - attributes: Optional[EntityAttributes] + keyAttributes: EntityKeyAttributes | None + attributes: EntityAttributes | None EventNumber = int -ExtractedValues = Dict[Token, Value] -InputLogStreamNames = List[LogStreamName] +ExecutionStatusList = list[ExecutionStatus] +ExtractedValues = dict[Token, Value] +FieldIndexNames = list[FieldIndexName] + + +class FieldsData(TypedDict, total=False): + data: Data | None + + +InputLogStreamNames = list[LogStreamName] class FilterLogEventsRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] - logGroupIdentifier: Optional[LogGroupIdentifier] - logStreamNames: Optional[InputLogStreamNames] - logStreamNamePrefix: Optional[LogStreamName] - startTime: Optional[Timestamp] - endTime: Optional[Timestamp] - filterPattern: Optional[FilterPattern] - nextToken: Optional[NextToken] - limit: Optional[EventsLimit] - interleaved: Optional[Interleaved] - unmask: Optional[Unmask] + logGroupName: LogGroupName | None + logGroupIdentifier: LogGroupIdentifier | None + logStreamNames: InputLogStreamNames | None + logStreamNamePrefix: LogStreamName | None + startTime: Timestamp | None + endTime: Timestamp | None + filterPattern: FilterPattern | None + nextToken: NextToken | None + limit: EventsLimit | None + interleaved: Interleaved | None + unmask: Unmask | None class SearchedLogStream(TypedDict, total=False): - logStreamName: Optional[LogStreamName] - searchedCompletely: Optional[LogStreamSearchedCompletely] + logStreamName: LogStreamName | None + searchedCompletely: LogStreamSearchedCompletely | None -SearchedLogStreams = List[SearchedLogStream] +SearchedLogStreams = list[SearchedLogStream] class FilteredLogEvent(TypedDict, total=False): - logStreamName: Optional[LogStreamName] - timestamp: Optional[Timestamp] - message: Optional[EventMessage] - ingestionTime: Optional[Timestamp] - eventId: Optional[EventId] + logStreamName: LogStreamName | None + timestamp: Timestamp | None + message: EventMessage | None + ingestionTime: Timestamp | None + eventId: EventId | None -FilteredLogEvents = List[FilteredLogEvent] +FilteredLogEvents = list[FilteredLogEvent] class FilterLogEventsResponse(TypedDict, total=False): - events: Optional[FilteredLogEvents] - searchedLogStreams: Optional[SearchedLogStreams] - nextToken: Optional[NextToken] + events: FilteredLogEvents | None + searchedLogStreams: SearchedLogStreams | None + nextToken: NextToken | None class GetDataProtectionPolicyRequest(ServiceRequest): @@ -1313,9 +1611,9 @@ class GetDataProtectionPolicyRequest(ServiceRequest): class GetDataProtectionPolicyResponse(TypedDict, total=False): - logGroupIdentifier: Optional[LogGroupIdentifier] - policyDocument: Optional[DataProtectionPolicyDocument] - lastUpdatedTime: Optional[Timestamp] + logGroupIdentifier: LogGroupIdentifier | None + policyDocument: DataProtectionPolicyDocument | None + lastUpdatedTime: Timestamp | None class GetDeliveryDestinationPolicyRequest(ServiceRequest): @@ -1323,11 +1621,11 @@ class GetDeliveryDestinationPolicyRequest(ServiceRequest): class Policy(TypedDict, total=False): - deliveryDestinationPolicy: Optional[DeliveryDestinationPolicy] + deliveryDestinationPolicy: DeliveryDestinationPolicy | None class GetDeliveryDestinationPolicyResponse(TypedDict, total=False): - policy: Optional[Policy] + policy: Policy | None class GetDeliveryDestinationRequest(ServiceRequest): @@ -1335,7 +1633,7 @@ class GetDeliveryDestinationRequest(ServiceRequest): class GetDeliveryDestinationResponse(TypedDict, total=False): - deliveryDestination: Optional[DeliveryDestination] + deliveryDestination: DeliveryDestination | None class GetDeliveryRequest(ServiceRequest): @@ -1343,7 +1641,7 @@ class GetDeliveryRequest(ServiceRequest): class GetDeliveryResponse(TypedDict, total=False): - delivery: Optional[Delivery] + delivery: Delivery | None class GetDeliverySourceRequest(ServiceRequest): @@ -1351,7 +1649,7 @@ class GetDeliverySourceRequest(ServiceRequest): class GetDeliverySourceResponse(TypedDict, total=False): - deliverySource: Optional[DeliverySource] + deliverySource: DeliverySource | None class GetIntegrationRequest(ServiceRequest): @@ -1359,73 +1657,73 @@ class GetIntegrationRequest(ServiceRequest): class OpenSearchResourceStatus(TypedDict, total=False): - status: Optional[OpenSearchResourceStatusType] - statusMessage: Optional[IntegrationStatusMessage] + status: OpenSearchResourceStatusType | None + statusMessage: IntegrationStatusMessage | None class OpenSearchLifecyclePolicy(TypedDict, total=False): - policyName: Optional[OpenSearchPolicyName] - status: Optional[OpenSearchResourceStatus] + policyName: OpenSearchPolicyName | None + status: OpenSearchResourceStatus | None class OpenSearchDataAccessPolicy(TypedDict, total=False): - policyName: Optional[OpenSearchPolicyName] - status: Optional[OpenSearchResourceStatus] + policyName: OpenSearchPolicyName | None + status: OpenSearchResourceStatus | None class OpenSearchNetworkPolicy(TypedDict, total=False): - policyName: Optional[OpenSearchPolicyName] - status: Optional[OpenSearchResourceStatus] + policyName: OpenSearchPolicyName | None + status: OpenSearchResourceStatus | None class OpenSearchEncryptionPolicy(TypedDict, total=False): - policyName: Optional[OpenSearchPolicyName] - status: Optional[OpenSearchResourceStatus] + policyName: OpenSearchPolicyName | None + status: OpenSearchResourceStatus | None class OpenSearchWorkspace(TypedDict, total=False): - workspaceId: Optional[OpenSearchWorkspaceId] - status: Optional[OpenSearchResourceStatus] + workspaceId: OpenSearchWorkspaceId | None + status: OpenSearchResourceStatus | None class OpenSearchCollection(TypedDict, total=False): - collectionEndpoint: Optional[OpenSearchCollectionEndpoint] - collectionArn: Optional[Arn] - status: Optional[OpenSearchResourceStatus] + collectionEndpoint: OpenSearchCollectionEndpoint | None + collectionArn: Arn | None + status: OpenSearchResourceStatus | None class OpenSearchApplication(TypedDict, total=False): - applicationEndpoint: Optional[OpenSearchApplicationEndpoint] - applicationArn: Optional[Arn] - applicationId: Optional[OpenSearchApplicationId] - status: Optional[OpenSearchResourceStatus] + applicationEndpoint: OpenSearchApplicationEndpoint | None + applicationArn: Arn | None + applicationId: OpenSearchApplicationId | None + status: OpenSearchResourceStatus | None class OpenSearchDataSource(TypedDict, total=False): - dataSourceName: Optional[OpenSearchDataSourceName] - status: Optional[OpenSearchResourceStatus] + dataSourceName: OpenSearchDataSourceName | None + status: OpenSearchResourceStatus | None class OpenSearchIntegrationDetails(TypedDict, total=False): - dataSource: Optional[OpenSearchDataSource] - application: Optional[OpenSearchApplication] - collection: Optional[OpenSearchCollection] - workspace: Optional[OpenSearchWorkspace] - encryptionPolicy: Optional[OpenSearchEncryptionPolicy] - networkPolicy: Optional[OpenSearchNetworkPolicy] - accessPolicy: Optional[OpenSearchDataAccessPolicy] - lifecyclePolicy: Optional[OpenSearchLifecyclePolicy] + dataSource: OpenSearchDataSource | None + application: OpenSearchApplication | None + collection: OpenSearchCollection | None + workspace: OpenSearchWorkspace | None + encryptionPolicy: OpenSearchEncryptionPolicy | None + networkPolicy: OpenSearchNetworkPolicy | None + accessPolicy: OpenSearchDataAccessPolicy | None + lifecyclePolicy: OpenSearchLifecyclePolicy | None class IntegrationDetails(TypedDict, total=False): - openSearchIntegrationDetails: Optional[OpenSearchIntegrationDetails] + openSearchIntegrationDetails: OpenSearchIntegrationDetails | None class GetIntegrationResponse(TypedDict, total=False): - integrationName: Optional[IntegrationName] - integrationType: Optional[IntegrationType] - integrationStatus: Optional[IntegrationStatus] - integrationDetails: Optional[IntegrationDetails] + integrationName: IntegrationName | None + integrationType: IntegrationType | None + integrationStatus: IntegrationStatus | None + integrationDetails: IntegrationDetails | None class GetLogAnomalyDetectorRequest(ServiceRequest): @@ -1433,72 +1731,109 @@ class GetLogAnomalyDetectorRequest(ServiceRequest): class GetLogAnomalyDetectorResponse(TypedDict, total=False): - detectorName: Optional[DetectorName] - logGroupArnList: Optional[LogGroupArnList] - evaluationFrequency: Optional[EvaluationFrequency] - filterPattern: Optional[FilterPattern] - anomalyDetectorStatus: Optional[AnomalyDetectorStatus] - kmsKeyId: Optional[KmsKeyId] - creationTimeStamp: Optional[EpochMillis] - lastModifiedTimeStamp: Optional[EpochMillis] - anomalyVisibilityTime: Optional[AnomalyVisibilityTime] + detectorName: DetectorName | None + logGroupArnList: LogGroupArnList | None + evaluationFrequency: EvaluationFrequency | None + filterPattern: FilterPattern | None + anomalyDetectorStatus: AnomalyDetectorStatus | None + kmsKeyId: KmsKeyId | None + creationTimeStamp: EpochMillis | None + lastModifiedTimeStamp: EpochMillis | None + anomalyVisibilityTime: AnomalyVisibilityTime | None class GetLogEventsRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] - logGroupIdentifier: Optional[LogGroupIdentifier] + logGroupName: LogGroupName | None + logGroupIdentifier: LogGroupIdentifier | None logStreamName: LogStreamName - startTime: Optional[Timestamp] - endTime: Optional[Timestamp] - nextToken: Optional[NextToken] - limit: Optional[EventsLimit] - startFromHead: Optional[StartFromHead] - unmask: Optional[Unmask] + startTime: Timestamp | None + endTime: Timestamp | None + nextToken: NextToken | None + limit: EventsLimit | None + startFromHead: StartFromHead | None + unmask: Unmask | None class OutputLogEvent(TypedDict, total=False): - timestamp: Optional[Timestamp] - message: Optional[EventMessage] - ingestionTime: Optional[Timestamp] + timestamp: Timestamp | None + message: EventMessage | None + ingestionTime: Timestamp | None -OutputLogEvents = List[OutputLogEvent] +OutputLogEvents = list[OutputLogEvent] class GetLogEventsResponse(TypedDict, total=False): - events: Optional[OutputLogEvents] - nextForwardToken: Optional[NextToken] - nextBackwardToken: Optional[NextToken] + events: OutputLogEvents | None + nextForwardToken: NextToken | None + nextBackwardToken: NextToken | None + + +class GetLogFieldsRequest(ServiceRequest): + dataSourceName: DataSourceName + dataSourceType: DataSourceType + + +LogFieldsList = list["LogFieldsListItem"] + + +class LogFieldType(TypedDict, total=False): + type: "DataType | None" + element: "LogFieldType | None" + fields: "LogFieldsList | None" + + +class LogFieldsListItem(TypedDict, total=False): + logFieldName: LogFieldName | None + logFieldType: LogFieldType | None + + +class GetLogFieldsResponse(TypedDict, total=False): + logFields: LogFieldsList | None class GetLogGroupFieldsRequest(ServiceRequest): - logGroupName: Optional[LogGroupName] - time: Optional[Timestamp] - logGroupIdentifier: Optional[LogGroupIdentifier] + logGroupName: LogGroupName | None + time: Timestamp | None + logGroupIdentifier: LogGroupIdentifier | None class LogGroupField(TypedDict, total=False): - name: Optional[Field] - percent: Optional[Percentage] + name: Field | None + percent: Percentage | None -LogGroupFieldList = List[LogGroupField] +LogGroupFieldList = list[LogGroupField] class GetLogGroupFieldsResponse(TypedDict, total=False): - logGroupFields: Optional[LogGroupFieldList] + logGroupFields: LogGroupFieldList | None + + +class GetLogObjectRequest(ServiceRequest): + unmask: Unmask | None + logObjectPointer: LogObjectPointer + + +class GetLogObjectResponseStream(TypedDict, total=False): + fields: FieldsData | None + InternalStreamingException: InternalStreamingException | None + + +class GetLogObjectResponse(TypedDict, total=False): + fieldStream: Iterator[GetLogObjectResponseStream] class GetLogRecordRequest(ServiceRequest): logRecordPointer: LogRecordPointer - unmask: Optional[Unmask] + unmask: Unmask | None -LogRecord = Dict[Field, Value] +LogRecord = dict[Field, Value] class GetLogRecordResponse(TypedDict, total=False): - logRecord: Optional[LogRecord] + logRecord: LogRecord | None class GetQueryResultsRequest(ServiceRequest): @@ -1506,58 +1841,118 @@ class GetQueryResultsRequest(ServiceRequest): class QueryStatistics(TypedDict, total=False): - recordsMatched: Optional[StatsValue] - recordsScanned: Optional[StatsValue] - estimatedRecordsSkipped: Optional[StatsValue] - bytesScanned: Optional[StatsValue] - estimatedBytesSkipped: Optional[StatsValue] - logGroupsScanned: Optional[StatsValue] + recordsMatched: StatsValue | None + recordsScanned: StatsValue | None + estimatedRecordsSkipped: StatsValue | None + bytesScanned: StatsValue | None + estimatedBytesSkipped: StatsValue | None + logGroupsScanned: StatsValue | None class ResultField(TypedDict, total=False): - field: Optional[Field] - value: Optional[Value] + field: Field | None + value: Value | None -ResultRows = List[ResultField] -QueryResults = List[ResultRows] +ResultRows = list[ResultField] +QueryResults = list[ResultRows] class GetQueryResultsResponse(TypedDict, total=False): - queryLanguage: Optional[QueryLanguage] - results: Optional[QueryResults] - statistics: Optional[QueryStatistics] - status: Optional[QueryStatus] - encryptionKey: Optional[EncryptionKey] + queryLanguage: QueryLanguage | None + results: QueryResults | None + statistics: QueryStatistics | None + status: QueryStatus | None + encryptionKey: EncryptionKey | None + + +class GetScheduledQueryHistoryRequest(ServiceRequest): + identifier: ScheduledQueryIdentifier + startTime: Timestamp + endTime: Timestamp + executionStatuses: ExecutionStatusList | None + maxResults: GetScheduledQueryHistoryMaxResults | None + nextToken: NextToken | None + + +class ScheduledQueryDestination(TypedDict, total=False): + destinationType: ScheduledQueryDestinationType | None + destinationIdentifier: String | None + status: ActionStatus | None + processedIdentifier: String | None + errorMessage: String | None + + +ScheduledQueryDestinationList = list[ScheduledQueryDestination] + + +class TriggerHistoryRecord(TypedDict, total=False): + queryId: QueryId | None + executionStatus: ExecutionStatus | None + triggeredTimestamp: Timestamp | None + errorMessage: String | None + destinations: ScheduledQueryDestinationList | None + + +TriggerHistoryRecordList = list[TriggerHistoryRecord] + + +class GetScheduledQueryHistoryResponse(TypedDict, total=False): + name: ScheduledQueryName | None + scheduledQueryArn: Arn | None + triggerHistory: TriggerHistoryRecordList | None + nextToken: NextToken | None + + +class GetScheduledQueryRequest(ServiceRequest): + identifier: ScheduledQueryIdentifier + + +class GetScheduledQueryResponse(TypedDict, total=False): + scheduledQueryArn: Arn | None + name: ScheduledQueryName | None + description: ScheduledQueryDescription | None + queryLanguage: QueryLanguage | None + queryString: QueryString | None + logGroupIdentifiers: ScheduledQueryLogGroupIdentifiers | None + scheduleExpression: ScheduleExpression | None + timezone: ScheduleTimezone | None + startTimeOffset: StartTimeOffset | None + destinationConfiguration: DestinationConfiguration | None + state: ScheduledQueryState | None + lastTriggeredTime: Timestamp | None + lastExecutionStatus: ExecutionStatus | None + scheduleStartTime: Timestamp | None + scheduleEndTime: Timestamp | None + executionRoleArn: RoleArn | None + creationTime: Timestamp | None + lastUpdatedTime: Timestamp | None class GetTransformerRequest(ServiceRequest): logGroupIdentifier: LogGroupIdentifier -UpperCaseStringWithKeys = List[WithKey] +UpperCaseStringWithKeys = list[WithKey] class UpperCaseString(TypedDict, total=False): withKeys: UpperCaseStringWithKeys -TypeConverterEntry = TypedDict( - "TypeConverterEntry", - { - "key": Key, - "type": Type, - }, - total=False, -) -TypeConverterEntries = List[TypeConverterEntry] +class TypeConverterEntry(TypedDict, total=False): + key: Key + type: Type + + +TypeConverterEntries = list[TypeConverterEntry] class TypeConverter(TypedDict, total=False): entries: TypeConverterEntries -TrimStringWithKeys = List[WithKey] +TrimStringWithKeys = list[WithKey] class TrimString(TypedDict, total=False): @@ -1573,7 +1968,7 @@ class TrimString(TypedDict, total=False): }, total=False, ) -SubstituteStringEntries = List[SubstituteStringEntry] +SubstituteStringEntries = list[SubstituteStringEntry] class SubstituteString(TypedDict, total=False): @@ -1585,7 +1980,7 @@ class SplitStringEntry(TypedDict, total=False): delimiter: SplitStringDelimiter -SplitStringEntries = List[SplitStringEntry] +SplitStringEntries = list[SplitStringEntry] class SplitString(TypedDict, total=False): @@ -1595,10 +1990,10 @@ class SplitString(TypedDict, total=False): class RenameKeyEntry(TypedDict, total=False): key: Key renameTo: RenameTo - overwriteIfExists: Optional[OverwriteIfExists] + overwriteIfExists: OverwriteIfExists | None -RenameKeyEntries = List[RenameKeyEntry] +RenameKeyEntries = list[RenameKeyEntry] class RenameKeys(TypedDict, total=False): @@ -1606,60 +2001,61 @@ class RenameKeys(TypedDict, total=False): class ParseWAF(TypedDict, total=False): - source: Optional[Source] + source: Source | None class ParseVPC(TypedDict, total=False): - source: Optional[Source] + source: Source | None class ParsePostgres(TypedDict, total=False): - source: Optional[Source] + source: Source | None class ParseToOCSF(TypedDict, total=False): - source: Optional[Source] + source: Source | None eventSource: EventSource ocsfVersion: OCSFVersion + mappingVersion: MappingVersion | None class ParseRoute53(TypedDict, total=False): - source: Optional[Source] + source: Source | None class ParseKeyValue(TypedDict, total=False): - source: Optional[Source] - destination: Optional[DestinationField] - fieldDelimiter: Optional[ParserFieldDelimiter] - keyValueDelimiter: Optional[KeyValueDelimiter] - keyPrefix: Optional[KeyPrefix] - nonMatchValue: Optional[NonMatchValue] - overwriteIfExists: Optional[OverwriteIfExists] + source: Source | None + destination: DestinationField | None + fieldDelimiter: ParserFieldDelimiter | None + keyValueDelimiter: KeyValueDelimiter | None + keyPrefix: KeyPrefix | None + nonMatchValue: NonMatchValue | None + overwriteIfExists: OverwriteIfExists | None class ParseJSON(TypedDict, total=False): - source: Optional[Source] - destination: Optional[DestinationField] + source: Source | None + destination: DestinationField | None class ParseCloudfront(TypedDict, total=False): - source: Optional[Source] + source: Source | None class MoveKeyEntry(TypedDict, total=False): source: Source target: Target - overwriteIfExists: Optional[OverwriteIfExists] + overwriteIfExists: OverwriteIfExists | None -MoveKeyEntries = List[MoveKeyEntry] +MoveKeyEntries = list[MoveKeyEntry] class MoveKeys(TypedDict, total=False): entries: MoveKeyEntries -LowerCaseStringWithKeys = List[WithKey] +LowerCaseStringWithKeys = list[WithKey] class LowerCaseString(TypedDict, total=False): @@ -1669,51 +2065,51 @@ class LowerCaseString(TypedDict, total=False): class ListToMap(TypedDict, total=False): source: Source key: Key - valueKey: Optional[ValueKey] - target: Optional[Target] - flatten: Optional[Flatten] - flattenedElement: Optional[FlattenedElement] + valueKey: ValueKey | None + target: Target | None + flatten: Flatten | None + flattenedElement: FlattenedElement | None class Grok(TypedDict, total=False): - source: Optional[Source] + source: Source | None match: GrokMatch class Processor(TypedDict, total=False): - addKeys: Optional[AddKeys] - copyValue: Optional[CopyValue] - csv: Optional[CSV] - dateTimeConverter: Optional[DateTimeConverter] - deleteKeys: Optional[DeleteKeys] - grok: Optional[Grok] - listToMap: Optional[ListToMap] - lowerCaseString: Optional[LowerCaseString] - moveKeys: Optional[MoveKeys] - parseCloudfront: Optional[ParseCloudfront] - parseJSON: Optional[ParseJSON] - parseKeyValue: Optional[ParseKeyValue] - parseRoute53: Optional[ParseRoute53] - parseToOCSF: Optional[ParseToOCSF] - parsePostgres: Optional[ParsePostgres] - parseVPC: Optional[ParseVPC] - parseWAF: Optional[ParseWAF] - renameKeys: Optional[RenameKeys] - splitString: Optional[SplitString] - substituteString: Optional[SubstituteString] - trimString: Optional[TrimString] - typeConverter: Optional[TypeConverter] - upperCaseString: Optional[UpperCaseString] - - -Processors = List[Processor] + addKeys: AddKeys | None + copyValue: CopyValue | None + csv: CSV | None + dateTimeConverter: DateTimeConverter | None + deleteKeys: DeleteKeys | None + grok: Grok | None + listToMap: ListToMap | None + lowerCaseString: LowerCaseString | None + moveKeys: MoveKeys | None + parseCloudfront: ParseCloudfront | None + parseJSON: ParseJSON | None + parseKeyValue: ParseKeyValue | None + parseRoute53: ParseRoute53 | None + parseToOCSF: ParseToOCSF | None + parsePostgres: ParsePostgres | None + parseVPC: ParseVPC | None + parseWAF: ParseWAF | None + renameKeys: RenameKeys | None + splitString: SplitString | None + substituteString: SubstituteString | None + trimString: TrimString | None + typeConverter: TypeConverter | None + upperCaseString: UpperCaseString | None + + +Processors = list[Processor] class GetTransformerResponse(TypedDict, total=False): - logGroupIdentifier: Optional[LogGroupIdentifier] - creationTime: Optional[Timestamp] - lastModifiedTime: Optional[Timestamp] - transformerConfig: Optional[Processors] + logGroupIdentifier: LogGroupIdentifier | None + creationTime: Timestamp | None + lastModifiedTime: Timestamp | None + transformerConfig: Processors | None class InputLogEvent(TypedDict, total=False): @@ -1721,86 +2117,153 @@ class InputLogEvent(TypedDict, total=False): message: EventMessage -InputLogEvents = List[InputLogEvent] +InputLogEvents = list[InputLogEvent] class IntegrationSummary(TypedDict, total=False): - integrationName: Optional[IntegrationName] - integrationType: Optional[IntegrationType] - integrationStatus: Optional[IntegrationStatus] + integrationName: IntegrationName | None + integrationType: IntegrationType | None + integrationStatus: IntegrationStatus | None + +IntegrationSummaries = list[IntegrationSummary] -IntegrationSummaries = List[IntegrationSummary] + +class ListAggregateLogGroupSummariesRequest(ServiceRequest): + accountIdentifiers: AccountIds | None + includeLinkedAccounts: IncludeLinkedAccounts | None + logGroupClass: LogGroupClass | None + logGroupNamePattern: LogGroupNameRegexPattern | None + dataSources: DataSourceFilters | None + groupBy: ListAggregateLogGroupSummariesGroupBy + nextToken: NextToken | None + limit: ListLogGroupsRequestLimit | None + + +class ListAggregateLogGroupSummariesResponse(TypedDict, total=False): + aggregateLogGroupSummaries: AggregateLogGroupSummaries | None + nextToken: NextToken | None class ListAnomaliesRequest(ServiceRequest): - anomalyDetectorArn: Optional[AnomalyDetectorArn] - suppressionState: Optional[SuppressionState] - limit: Optional[ListAnomaliesLimit] - nextToken: Optional[NextToken] + anomalyDetectorArn: AnomalyDetectorArn | None + suppressionState: SuppressionState | None + limit: ListAnomaliesLimit | None + nextToken: NextToken | None class ListAnomaliesResponse(TypedDict, total=False): - anomalies: Optional[Anomalies] - nextToken: Optional[NextToken] + anomalies: Anomalies | None + nextToken: NextToken | None class ListIntegrationsRequest(ServiceRequest): - integrationNamePrefix: Optional[IntegrationNamePrefix] - integrationType: Optional[IntegrationType] - integrationStatus: Optional[IntegrationStatus] + integrationNamePrefix: IntegrationNamePrefix | None + integrationType: IntegrationType | None + integrationStatus: IntegrationStatus | None class ListIntegrationsResponse(TypedDict, total=False): - integrationSummaries: Optional[IntegrationSummaries] + integrationSummaries: IntegrationSummaries | None class ListLogAnomalyDetectorsRequest(ServiceRequest): - filterLogGroupArn: Optional[LogGroupArn] - limit: Optional[ListLogAnomalyDetectorsLimit] - nextToken: Optional[NextToken] + filterLogGroupArn: LogGroupArn | None + limit: ListLogAnomalyDetectorsLimit | None + nextToken: NextToken | None class ListLogAnomalyDetectorsResponse(TypedDict, total=False): - anomalyDetectors: Optional[AnomalyDetectors] - nextToken: Optional[NextToken] + anomalyDetectors: AnomalyDetectors | None + nextToken: NextToken | None class ListLogGroupsForQueryRequest(ServiceRequest): queryId: QueryId - nextToken: Optional[NextToken] - maxResults: Optional[ListLogGroupsForQueryMaxResults] + nextToken: NextToken | None + maxResults: ListLogGroupsForQueryMaxResults | None -LogGroupIdentifiers = List[LogGroupIdentifier] +LogGroupIdentifiers = list[LogGroupIdentifier] class ListLogGroupsForQueryResponse(TypedDict, total=False): - logGroupIdentifiers: Optional[LogGroupIdentifiers] - nextToken: Optional[NextToken] + logGroupIdentifiers: LogGroupIdentifiers | None + nextToken: NextToken | None class ListLogGroupsRequest(ServiceRequest): - logGroupNamePattern: Optional[LogGroupNameRegexPattern] - logGroupClass: Optional[LogGroupClass] - includeLinkedAccounts: Optional[IncludeLinkedAccounts] - accountIdentifiers: Optional[AccountIds] - nextToken: Optional[NextToken] - limit: Optional[ListLimit] + logGroupNamePattern: LogGroupNameRegexPattern | None + logGroupClass: LogGroupClass | None + includeLinkedAccounts: IncludeLinkedAccounts | None + accountIdentifiers: AccountIds | None + nextToken: NextToken | None + limit: ListLimit | None + dataSources: DataSourceFilters | None + fieldIndexNames: FieldIndexNames | None class LogGroupSummary(TypedDict, total=False): - logGroupName: Optional[LogGroupName] - logGroupArn: Optional[Arn] - logGroupClass: Optional[LogGroupClass] + logGroupName: LogGroupName | None + logGroupArn: Arn | None + logGroupClass: LogGroupClass | None -LogGroupSummaries = List[LogGroupSummary] +LogGroupSummaries = list[LogGroupSummary] class ListLogGroupsResponse(TypedDict, total=False): - logGroups: Optional[LogGroupSummaries] - nextToken: Optional[NextToken] + logGroups: LogGroupSummaries | None + nextToken: NextToken | None + + +class ListScheduledQueriesRequest(ServiceRequest): + maxResults: ListScheduledQueriesMaxResults | None + nextToken: NextToken | None + state: ScheduledQueryState | None + + +class ScheduledQuerySummary(TypedDict, total=False): + scheduledQueryArn: Arn | None + name: ScheduledQueryName | None + state: ScheduledQueryState | None + lastTriggeredTime: Timestamp | None + lastExecutionStatus: ExecutionStatus | None + scheduleExpression: ScheduleExpression | None + timezone: ScheduleTimezone | None + destinationConfiguration: DestinationConfiguration | None + creationTime: Timestamp | None + lastUpdatedTime: Timestamp | None + + +ScheduledQuerySummaryList = list[ScheduledQuerySummary] + + +class ListScheduledQueriesResponse(TypedDict, total=False): + nextToken: NextToken | None + scheduledQueries: ScheduledQuerySummaryList | None + + +class ListSourcesForS3TableIntegrationRequest(ServiceRequest): + integrationArn: Arn + maxResults: ListSourcesForS3TableIntegrationMaxResults | None + nextToken: NextToken | None + + +class S3TableIntegrationSource(TypedDict, total=False): + identifier: S3TableIntegrationSourceIdentifier | None + dataSource: DataSource | None + status: S3TableIntegrationSourceStatus | None + statusReason: S3TableIntegrationSourceStatusReason | None + createdTimeStamp: Timestamp | None + + +S3TableIntegrationSources = list[S3TableIntegrationSource] + + +class ListSourcesForS3TableIntegrationResponse(TypedDict, total=False): + sources: S3TableIntegrationSources | None + nextToken: NextToken | None class ListTagsForResourceRequest(ServiceRequest): @@ -1808,7 +2271,7 @@ class ListTagsForResourceRequest(ServiceRequest): class ListTagsForResourceResponse(TypedDict, total=False): - tags: Optional[Tags] + tags: Tags | None class ListTagsLogGroupRequest(ServiceRequest): @@ -1816,53 +2279,53 @@ class ListTagsLogGroupRequest(ServiceRequest): class ListTagsLogGroupResponse(TypedDict, total=False): - tags: Optional[Tags] + tags: Tags | None class LiveTailSessionLogEvent(TypedDict, total=False): - logStreamName: Optional[LogStreamName] - logGroupIdentifier: Optional[LogGroupIdentifier] - message: Optional[EventMessage] - timestamp: Optional[Timestamp] - ingestionTime: Optional[Timestamp] + logStreamName: LogStreamName | None + logGroupIdentifier: LogGroupIdentifier | None + message: EventMessage | None + timestamp: Timestamp | None + ingestionTime: Timestamp | None class LiveTailSessionMetadata(TypedDict, total=False): - sampled: Optional[IsSampled] + sampled: IsSampled | None -LiveTailSessionResults = List[LiveTailSessionLogEvent] -StartLiveTailLogGroupIdentifiers = List[LogGroupIdentifier] +LiveTailSessionResults = list[LiveTailSessionLogEvent] +StartLiveTailLogGroupIdentifiers = list[LogGroupIdentifier] class LiveTailSessionStart(TypedDict, total=False): - requestId: Optional[RequestId] - sessionId: Optional[SessionId] - logGroupIdentifiers: Optional[StartLiveTailLogGroupIdentifiers] - logStreamNames: Optional[InputLogStreamNames] - logStreamNamePrefixes: Optional[InputLogStreamNames] - logEventFilterPattern: Optional[FilterPattern] + requestId: RequestId | None + sessionId: SessionId | None + logGroupIdentifiers: StartLiveTailLogGroupIdentifiers | None + logStreamNames: InputLogStreamNames | None + logStreamNamePrefixes: InputLogStreamNames | None + logEventFilterPattern: FilterPattern | None class LiveTailSessionUpdate(TypedDict, total=False): - sessionMetadata: Optional[LiveTailSessionMetadata] - sessionResults: Optional[LiveTailSessionResults] + sessionMetadata: LiveTailSessionMetadata | None + sessionResults: LiveTailSessionResults | None class MetricFilterMatchRecord(TypedDict, total=False): - eventNumber: Optional[EventNumber] - eventMessage: Optional[EventMessage] - extractedValues: Optional[ExtractedValues] + eventNumber: EventNumber | None + eventMessage: EventMessage | None + extractedValues: ExtractedValues | None -MetricFilterMatches = List[MetricFilterMatchRecord] +MetricFilterMatches = list[MetricFilterMatchRecord] class OpenSearchResourceConfig(TypedDict, total=False): - kmsKeyArn: Optional[Arn] + kmsKeyArn: Arn | None dataSourceRoleArn: Arn dashboardViewerPrincipals: DashboardViewerPrincipals - applicationArn: Optional[Arn] + applicationArn: Arn | None retentionDays: CollectionRetentionDays @@ -1870,12 +2333,12 @@ class PutAccountPolicyRequest(ServiceRequest): policyName: PolicyName policyDocument: AccountPolicyDocument policyType: PolicyType - scope: Optional[Scope] - selectionCriteria: Optional[SelectionCriteria] + scope: Scope | None + selectionCriteria: SelectionCriteria | None class PutAccountPolicyResponse(TypedDict, total=False): - accountPolicy: Optional[AccountPolicy] + accountPolicy: AccountPolicy | None class PutDataProtectionPolicyRequest(ServiceRequest): @@ -1884,9 +2347,9 @@ class PutDataProtectionPolicyRequest(ServiceRequest): class PutDataProtectionPolicyResponse(TypedDict, total=False): - logGroupIdentifier: Optional[LogGroupIdentifier] - policyDocument: Optional[DataProtectionPolicyDocument] - lastUpdatedTime: Optional[Timestamp] + logGroupIdentifier: LogGroupIdentifier | None + policyDocument: DataProtectionPolicyDocument | None + lastUpdatedTime: Timestamp | None class PutDeliveryDestinationPolicyRequest(ServiceRequest): @@ -1895,46 +2358,47 @@ class PutDeliveryDestinationPolicyRequest(ServiceRequest): class PutDeliveryDestinationPolicyResponse(TypedDict, total=False): - policy: Optional[Policy] + policy: Policy | None class PutDeliveryDestinationRequest(ServiceRequest): name: DeliveryDestinationName - outputFormat: Optional[OutputFormat] - deliveryDestinationConfiguration: DeliveryDestinationConfiguration - tags: Optional[Tags] + outputFormat: OutputFormat | None + deliveryDestinationConfiguration: DeliveryDestinationConfiguration | None + deliveryDestinationType: DeliveryDestinationType | None + tags: Tags | None class PutDeliveryDestinationResponse(TypedDict, total=False): - deliveryDestination: Optional[DeliveryDestination] + deliveryDestination: DeliveryDestination | None class PutDeliverySourceRequest(ServiceRequest): name: DeliverySourceName resourceArn: Arn logType: LogType - tags: Optional[Tags] + tags: Tags | None class PutDeliverySourceResponse(TypedDict, total=False): - deliverySource: Optional[DeliverySource] + deliverySource: DeliverySource | None class PutDestinationPolicyRequest(ServiceRequest): destinationName: DestinationName accessPolicy: AccessPolicy - forceUpdate: Optional[ForceUpdate] + forceUpdate: ForceUpdate | None class PutDestinationRequest(ServiceRequest): destinationName: DestinationName targetArn: TargetArn roleArn: RoleArn - tags: Optional[Tags] + tags: Tags | None class PutDestinationResponse(TypedDict, total=False): - destination: Optional[Destination] + destination: Destination | None class PutIndexPolicyRequest(ServiceRequest): @@ -1943,11 +2407,11 @@ class PutIndexPolicyRequest(ServiceRequest): class PutIndexPolicyResponse(TypedDict, total=False): - indexPolicy: Optional[IndexPolicy] + indexPolicy: IndexPolicy | None class ResourceConfig(TypedDict, total=False): - openSearchResourceConfig: Optional[OpenSearchResourceConfig] + openSearchResourceConfig: OpenSearchResourceConfig | None class PutIntegrationRequest(ServiceRequest): @@ -1957,16 +2421,16 @@ class PutIntegrationRequest(ServiceRequest): class PutIntegrationResponse(TypedDict, total=False): - integrationName: Optional[IntegrationName] - integrationStatus: Optional[IntegrationStatus] + integrationName: IntegrationName | None + integrationStatus: IntegrationStatus | None class PutLogEventsRequest(ServiceRequest): logGroupName: LogGroupName logStreamName: LogStreamName logEvents: InputLogEvents - sequenceToken: Optional[SequenceToken] - entity: Optional[Entity] + sequenceToken: SequenceToken | None + entity: Entity | None class RejectedEntityInfo(TypedDict, total=False): @@ -1974,15 +2438,20 @@ class RejectedEntityInfo(TypedDict, total=False): class RejectedLogEventsInfo(TypedDict, total=False): - tooNewLogEventStartIndex: Optional[LogEventIndex] - tooOldLogEventEndIndex: Optional[LogEventIndex] - expiredLogEventEndIndex: Optional[LogEventIndex] + tooNewLogEventStartIndex: LogEventIndex | None + tooOldLogEventEndIndex: LogEventIndex | None + expiredLogEventEndIndex: LogEventIndex | None class PutLogEventsResponse(TypedDict, total=False): - nextSequenceToken: Optional[SequenceToken] - rejectedLogEventsInfo: Optional[RejectedLogEventsInfo] - rejectedEntityInfo: Optional[RejectedEntityInfo] + nextSequenceToken: SequenceToken | None + rejectedLogEventsInfo: RejectedLogEventsInfo | None + rejectedEntityInfo: RejectedEntityInfo | None + + +class PutLogGroupDeletionProtectionRequest(ServiceRequest): + logGroupIdentifier: LogGroupIdentifier + deletionProtectionEnabled: DeletionProtectionEnabled class PutMetricFilterRequest(ServiceRequest): @@ -1990,29 +2459,34 @@ class PutMetricFilterRequest(ServiceRequest): filterName: FilterName filterPattern: FilterPattern metricTransformations: MetricTransformations - applyOnTransformedLogs: Optional[ApplyOnTransformedLogs] + applyOnTransformedLogs: ApplyOnTransformedLogs | None + fieldSelectionCriteria: FieldSelectionCriteria | None + emitSystemFieldDimensions: EmitSystemFields | None class PutQueryDefinitionRequest(ServiceRequest): - queryLanguage: Optional[QueryLanguage] + queryLanguage: QueryLanguage | None name: QueryDefinitionName - queryDefinitionId: Optional[QueryId] - logGroupNames: Optional[LogGroupNames] + queryDefinitionId: QueryId | None + logGroupNames: LogGroupNames | None queryString: QueryDefinitionString - clientToken: Optional[ClientToken] + clientToken: ClientToken | None class PutQueryDefinitionResponse(TypedDict, total=False): - queryDefinitionId: Optional[QueryId] + queryDefinitionId: QueryId | None class PutResourcePolicyRequest(ServiceRequest): - policyName: Optional[PolicyName] - policyDocument: Optional[PolicyDocument] + policyName: PolicyName | None + policyDocument: PolicyDocument | None + resourceArn: Arn | None + expectedRevisionId: ExpectedRevisionId | None class PutResourcePolicyResponse(TypedDict, total=False): - resourcePolicy: Optional[ResourcePolicy] + resourcePolicy: ResourcePolicy | None + revisionId: ExpectedRevisionId | None class PutRetentionPolicyRequest(ServiceRequest): @@ -2025,9 +2499,11 @@ class PutSubscriptionFilterRequest(ServiceRequest): filterName: FilterName filterPattern: FilterPattern destinationArn: DestinationArn - roleArn: Optional[RoleArn] - distribution: Optional[Distribution] - applyOnTransformedLogs: Optional[ApplyOnTransformedLogs] + roleArn: RoleArn | None + distribution: Distribution | None + applyOnTransformedLogs: ApplyOnTransformedLogs | None + fieldSelectionCriteria: FieldSelectionCriteria | None + emitSystemFields: EmitSystemFields | None class PutTransformerRequest(ServiceRequest): @@ -2037,16 +2513,16 @@ class PutTransformerRequest(ServiceRequest): class StartLiveTailRequest(ServiceRequest): logGroupIdentifiers: StartLiveTailLogGroupIdentifiers - logStreamNames: Optional[InputLogStreamNames] - logStreamNamePrefixes: Optional[InputLogStreamNames] - logEventFilterPattern: Optional[FilterPattern] + logStreamNames: InputLogStreamNames | None + logStreamNamePrefixes: InputLogStreamNames | None + logEventFilterPattern: FilterPattern | None class StartLiveTailResponseStream(TypedDict, total=False): - sessionStart: Optional[LiveTailSessionStart] - sessionUpdate: Optional[LiveTailSessionUpdate] - SessionTimeoutException: Optional[SessionTimeoutException] - SessionStreamingException: Optional[SessionStreamingException] + sessionStart: LiveTailSessionStart | None + sessionUpdate: LiveTailSessionUpdate | None + SessionTimeoutException: SessionTimeoutException | None + SessionStreamingException: SessionStreamingException | None class StartLiveTailResponse(TypedDict, total=False): @@ -2054,18 +2530,18 @@ class StartLiveTailResponse(TypedDict, total=False): class StartQueryRequest(ServiceRequest): - queryLanguage: Optional[QueryLanguage] - logGroupName: Optional[LogGroupName] - logGroupNames: Optional[LogGroupNames] - logGroupIdentifiers: Optional[LogGroupIdentifiers] + queryLanguage: QueryLanguage | None + logGroupName: LogGroupName | None + logGroupNames: LogGroupNames | None + logGroupIdentifiers: LogGroupIdentifiers | None startTime: Timestamp endTime: Timestamp queryString: QueryString - limit: Optional[EventsLimit] + limit: EventsLimit | None class StartQueryResponse(TypedDict, total=False): - queryId: Optional[QueryId] + queryId: QueryId | None class StopQueryRequest(ServiceRequest): @@ -2073,16 +2549,16 @@ class StopQueryRequest(ServiceRequest): class StopQueryResponse(TypedDict, total=False): - success: Optional[Success] + success: Success | None class SuppressionPeriod(TypedDict, total=False): - value: Optional[Integer] - suppressionUnit: Optional[SuppressionUnit] + value: Integer | None + suppressionUnit: SuppressionUnit | None -TagKeyList = List[TagKey] -TagList = List[TagKey] +TagKeyList = list[TagKey] +TagList = list[TagKey] class TagLogGroupRequest(ServiceRequest): @@ -2095,7 +2571,7 @@ class TagResourceRequest(ServiceRequest): tags: Tags -TestEventMessages = List[EventMessage] +TestEventMessages = list[EventMessage] class TestMetricFilterRequest(ServiceRequest): @@ -2104,7 +2580,7 @@ class TestMetricFilterRequest(ServiceRequest): class TestMetricFilterResponse(TypedDict, total=False): - matches: Optional[MetricFilterMatches] + matches: MetricFilterMatches | None class TestTransformerRequest(ServiceRequest): @@ -2113,16 +2589,16 @@ class TestTransformerRequest(ServiceRequest): class TransformedLogRecord(TypedDict, total=False): - eventNumber: Optional[EventNumber] - eventMessage: Optional[EventMessage] - transformedEventMessage: Optional[TransformedEventMessage] + eventNumber: EventNumber | None + eventMessage: EventMessage | None + transformedEventMessage: TransformedEventMessage | None -TransformedLogs = List[TransformedLogRecord] +TransformedLogs = list[TransformedLogRecord] class TestTransformerResponse(TypedDict, total=False): - transformedLogs: Optional[TransformedLogs] + transformedLogs: TransformedLogs | None class UntagLogGroupRequest(ServiceRequest): @@ -2136,19 +2612,19 @@ class UntagResourceRequest(ServiceRequest): class UpdateAnomalyRequest(ServiceRequest): - anomalyId: Optional[AnomalyId] - patternId: Optional[PatternId] + anomalyId: AnomalyId | None + patternId: PatternId | None anomalyDetectorArn: AnomalyDetectorArn - suppressionType: Optional[SuppressionType] - suppressionPeriod: Optional[SuppressionPeriod] - baseline: Optional[Baseline] + suppressionType: SuppressionType | None + suppressionPeriod: SuppressionPeriod | None + baseline: Baseline | None class UpdateDeliveryConfigurationRequest(ServiceRequest): id: DeliveryId - recordFields: Optional[RecordFields] - fieldDelimiter: Optional[FieldDelimiter] - s3DeliveryConfiguration: Optional[S3DeliveryConfiguration] + recordFields: RecordFields | None + fieldDelimiter: FieldDelimiter | None + s3DeliveryConfiguration: S3DeliveryConfiguration | None class UpdateDeliveryConfigurationResponse(TypedDict, total=False): @@ -2157,15 +2633,52 @@ class UpdateDeliveryConfigurationResponse(TypedDict, total=False): class UpdateLogAnomalyDetectorRequest(ServiceRequest): anomalyDetectorArn: AnomalyDetectorArn - evaluationFrequency: Optional[EvaluationFrequency] - filterPattern: Optional[FilterPattern] - anomalyVisibilityTime: Optional[AnomalyVisibilityTime] + evaluationFrequency: EvaluationFrequency | None + filterPattern: FilterPattern | None + anomalyVisibilityTime: AnomalyVisibilityTime | None enabled: Boolean +class UpdateScheduledQueryRequest(ServiceRequest): + identifier: ScheduledQueryIdentifier + description: ScheduledQueryDescription | None + queryLanguage: QueryLanguage + queryString: QueryString + logGroupIdentifiers: ScheduledQueryLogGroupIdentifiers | None + scheduleExpression: ScheduleExpression + timezone: ScheduleTimezone | None + startTimeOffset: StartTimeOffset | None + destinationConfiguration: DestinationConfiguration | None + scheduleStartTime: Timestamp | None + scheduleEndTime: Timestamp | None + executionRoleArn: RoleArn + state: ScheduledQueryState | None + + +class UpdateScheduledQueryResponse(TypedDict, total=False): + scheduledQueryArn: Arn | None + name: ScheduledQueryName | None + description: ScheduledQueryDescription | None + queryLanguage: QueryLanguage | None + queryString: QueryString | None + logGroupIdentifiers: ScheduledQueryLogGroupIdentifiers | None + scheduleExpression: ScheduleExpression | None + timezone: ScheduleTimezone | None + startTimeOffset: StartTimeOffset | None + destinationConfiguration: DestinationConfiguration | None + state: ScheduledQueryState | None + lastTriggeredTime: Timestamp | None + lastExecutionStatus: ExecutionStatus | None + scheduleStartTime: Timestamp | None + scheduleEndTime: Timestamp | None + executionRoleArn: RoleArn | None + creationTime: Timestamp | None + lastUpdatedTime: Timestamp | None + + class LogsApi: - service = "logs" - version = "2014-03-28" + service: str = "logs" + version: str = "2014-03-28" @handler("AssociateKmsKey") def associate_kms_key( @@ -2178,10 +2691,22 @@ def associate_kms_key( ) -> None: raise NotImplementedError + @handler("AssociateSourceToS3TableIntegration") + def associate_source_to_s3_table_integration( + self, context: RequestContext, integration_arn: Arn, data_source: DataSource, **kwargs + ) -> AssociateSourceToS3TableIntegrationResponse: + raise NotImplementedError + @handler("CancelExportTask") def cancel_export_task(self, context: RequestContext, task_id: ExportTaskId, **kwargs) -> None: raise NotImplementedError + @handler("CancelImportTask") + def cancel_import_task( + self, context: RequestContext, import_id: ImportId, **kwargs + ) -> CancelImportTaskResponse: + raise NotImplementedError + @handler("CreateDelivery") def create_delivery( self, @@ -2202,6 +2727,17 @@ def create_export_task( ) -> CreateExportTaskResponse: raise NotImplementedError + @handler("CreateImportTask") + def create_import_task( + self, + context: RequestContext, + import_source_arn: Arn, + import_role_arn: RoleArn, + import_filter: ImportFilter | None = None, + **kwargs, + ) -> CreateImportTaskResponse: + raise NotImplementedError + @handler("CreateLogAnomalyDetector") def create_log_anomaly_detector( self, @@ -2225,6 +2761,7 @@ def create_log_group( kms_key_id: KmsKeyId | None = None, tags: Tags | None = None, log_group_class: LogGroupClass | None = None, + deletion_protection_enabled: DeletionProtectionEnabled | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -2239,6 +2776,28 @@ def create_log_stream( ) -> None: raise NotImplementedError + @handler("CreateScheduledQuery") + def create_scheduled_query( + self, + context: RequestContext, + name: ScheduledQueryName, + query_language: QueryLanguage, + query_string: QueryString, + schedule_expression: ScheduleExpression, + execution_role_arn: RoleArn, + description: ScheduledQueryDescription | None = None, + log_group_identifiers: ScheduledQueryLogGroupIdentifiers | None = None, + timezone: ScheduleTimezone | None = None, + start_time_offset: StartTimeOffset | None = None, + destination_configuration: DestinationConfiguration | None = None, + schedule_start_time: Timestamp | None = None, + schedule_end_time: Timestamp | None = None, + state: ScheduledQueryState | None = None, + tags: Tags | None = None, + **kwargs, + ) -> CreateScheduledQueryResponse: + raise NotImplementedError + @handler("DeleteAccountPolicy") def delete_account_policy( self, context: RequestContext, policy_name: PolicyName, policy_type: PolicyType, **kwargs @@ -2335,7 +2894,12 @@ def delete_query_definition( @handler("DeleteResourcePolicy") def delete_resource_policy( - self, context: RequestContext, policy_name: PolicyName | None = None, **kwargs + self, + context: RequestContext, + policy_name: PolicyName | None = None, + resource_arn: Arn | None = None, + expected_revision_id: ExpectedRevisionId | None = None, + **kwargs, ) -> None: raise NotImplementedError @@ -2345,6 +2909,12 @@ def delete_retention_policy( ) -> None: raise NotImplementedError + @handler("DeleteScheduledQuery") + def delete_scheduled_query( + self, context: RequestContext, identifier: ScheduledQueryIdentifier, **kwargs + ) -> DeleteScheduledQueryResponse: + raise NotImplementedError + @handler("DeleteSubscriptionFilter") def delete_subscription_filter( self, @@ -2450,6 +3020,31 @@ def describe_field_indexes( ) -> DescribeFieldIndexesResponse: raise NotImplementedError + @handler("DescribeImportTaskBatches") + def describe_import_task_batches( + self, + context: RequestContext, + import_id: ImportId, + batch_import_status: ImportStatusList | None = None, + limit: DescribeLimit | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> DescribeImportTaskBatchesResponse: + raise NotImplementedError + + @handler("DescribeImportTasks") + def describe_import_tasks( + self, + context: RequestContext, + import_id: ImportId | None = None, + import_status: ImportStatus | None = None, + import_source_arn: Arn | None = None, + limit: DescribeLimit | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> DescribeImportTasksResponse: + raise NotImplementedError + @handler("DescribeIndexPolicies") def describe_index_policies( self, @@ -2536,6 +3131,8 @@ def describe_resource_policies( context: RequestContext, next_token: NextToken | None = None, limit: DescribeLimit | None = None, + resource_arn: Arn | None = None, + policy_scope: PolicyScope | None = None, **kwargs, ) -> DescribeResourcePoliciesResponse: raise NotImplementedError @@ -2562,6 +3159,12 @@ def disassociate_kms_key( ) -> None: raise NotImplementedError + @handler("DisassociateSourceFromS3TableIntegration") + def disassociate_source_from_s3_table_integration( + self, context: RequestContext, identifier: S3TableIntegrationSourceIdentifier, **kwargs + ) -> DisassociateSourceFromS3TableIntegrationResponse: + raise NotImplementedError + @handler("FilterLogEvents") def filter_log_events( self, @@ -2640,6 +3243,16 @@ def get_log_events( ) -> GetLogEventsResponse: raise NotImplementedError + @handler("GetLogFields") + def get_log_fields( + self, + context: RequestContext, + data_source_name: DataSourceName, + data_source_type: DataSourceType, + **kwargs, + ) -> GetLogFieldsResponse: + raise NotImplementedError + @handler("GetLogGroupFields") def get_log_group_fields( self, @@ -2651,6 +3264,16 @@ def get_log_group_fields( ) -> GetLogGroupFieldsResponse: raise NotImplementedError + @handler("GetLogObject") + def get_log_object( + self, + context: RequestContext, + log_object_pointer: LogObjectPointer, + unmask: Unmask | None = None, + **kwargs, + ) -> GetLogObjectResponse: + raise NotImplementedError + @handler("GetLogRecord") def get_log_record( self, @@ -2667,12 +3290,48 @@ def get_query_results( ) -> GetQueryResultsResponse: raise NotImplementedError + @handler("GetScheduledQuery") + def get_scheduled_query( + self, context: RequestContext, identifier: ScheduledQueryIdentifier, **kwargs + ) -> GetScheduledQueryResponse: + raise NotImplementedError + + @handler("GetScheduledQueryHistory") + def get_scheduled_query_history( + self, + context: RequestContext, + identifier: ScheduledQueryIdentifier, + start_time: Timestamp, + end_time: Timestamp, + execution_statuses: ExecutionStatusList | None = None, + max_results: GetScheduledQueryHistoryMaxResults | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> GetScheduledQueryHistoryResponse: + raise NotImplementedError + @handler("GetTransformer") def get_transformer( self, context: RequestContext, log_group_identifier: LogGroupIdentifier, **kwargs ) -> GetTransformerResponse: raise NotImplementedError + @handler("ListAggregateLogGroupSummaries") + def list_aggregate_log_group_summaries( + self, + context: RequestContext, + group_by: ListAggregateLogGroupSummariesGroupBy, + account_identifiers: AccountIds | None = None, + include_linked_accounts: IncludeLinkedAccounts | None = None, + log_group_class: LogGroupClass | None = None, + log_group_name_pattern: LogGroupNameRegexPattern | None = None, + data_sources: DataSourceFilters | None = None, + next_token: NextToken | None = None, + limit: ListLogGroupsRequestLimit | None = None, + **kwargs, + ) -> ListAggregateLogGroupSummariesResponse: + raise NotImplementedError + @handler("ListAnomalies") def list_anomalies( self, @@ -2717,6 +3376,8 @@ def list_log_groups( account_identifiers: AccountIds | None = None, next_token: NextToken | None = None, limit: ListLimit | None = None, + data_sources: DataSourceFilters | None = None, + field_index_names: FieldIndexNames | None = None, **kwargs, ) -> ListLogGroupsResponse: raise NotImplementedError @@ -2732,6 +3393,28 @@ def list_log_groups_for_query( ) -> ListLogGroupsForQueryResponse: raise NotImplementedError + @handler("ListScheduledQueries") + def list_scheduled_queries( + self, + context: RequestContext, + max_results: ListScheduledQueriesMaxResults | None = None, + next_token: NextToken | None = None, + state: ScheduledQueryState | None = None, + **kwargs, + ) -> ListScheduledQueriesResponse: + raise NotImplementedError + + @handler("ListSourcesForS3TableIntegration") + def list_sources_for_s3_table_integration( + self, + context: RequestContext, + integration_arn: Arn, + max_results: ListSourcesForS3TableIntegrationMaxResults | None = None, + next_token: NextToken | None = None, + **kwargs, + ) -> ListSourcesForS3TableIntegrationResponse: + raise NotImplementedError + @handler("ListTagsForResource") def list_tags_for_resource( self, context: RequestContext, resource_arn: AmazonResourceName, **kwargs @@ -2772,8 +3455,9 @@ def put_delivery_destination( self, context: RequestContext, name: DeliveryDestinationName, - delivery_destination_configuration: DeliveryDestinationConfiguration, output_format: OutputFormat | None = None, + delivery_destination_configuration: DeliveryDestinationConfiguration | None = None, + delivery_destination_type: DeliveryDestinationType | None = None, tags: Tags | None = None, **kwargs, ) -> PutDeliveryDestinationResponse: @@ -2858,6 +3542,16 @@ def put_log_events( ) -> PutLogEventsResponse: raise NotImplementedError + @handler("PutLogGroupDeletionProtection") + def put_log_group_deletion_protection( + self, + context: RequestContext, + log_group_identifier: LogGroupIdentifier, + deletion_protection_enabled: DeletionProtectionEnabled, + **kwargs, + ) -> None: + raise NotImplementedError + @handler("PutMetricFilter") def put_metric_filter( self, @@ -2867,6 +3561,8 @@ def put_metric_filter( filter_pattern: FilterPattern, metric_transformations: MetricTransformations, apply_on_transformed_logs: ApplyOnTransformedLogs | None = None, + field_selection_criteria: FieldSelectionCriteria | None = None, + emit_system_field_dimensions: EmitSystemFields | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -2891,6 +3587,8 @@ def put_resource_policy( context: RequestContext, policy_name: PolicyName | None = None, policy_document: PolicyDocument | None = None, + resource_arn: Arn | None = None, + expected_revision_id: ExpectedRevisionId | None = None, **kwargs, ) -> PutResourcePolicyResponse: raise NotImplementedError @@ -2916,6 +3614,8 @@ def put_subscription_filter( role_arn: RoleArn | None = None, distribution: Distribution | None = None, apply_on_transformed_logs: ApplyOnTransformedLogs | None = None, + field_selection_criteria: FieldSelectionCriteria | None = None, + emit_system_fields: EmitSystemFields | None = None, **kwargs, ) -> None: raise NotImplementedError @@ -3048,3 +3748,24 @@ def update_log_anomaly_detector( **kwargs, ) -> None: raise NotImplementedError + + @handler("UpdateScheduledQuery") + def update_scheduled_query( + self, + context: RequestContext, + identifier: ScheduledQueryIdentifier, + query_language: QueryLanguage, + query_string: QueryString, + schedule_expression: ScheduleExpression, + execution_role_arn: RoleArn, + description: ScheduledQueryDescription | None = None, + log_group_identifiers: ScheduledQueryLogGroupIdentifiers | None = None, + timezone: ScheduleTimezone | None = None, + start_time_offset: StartTimeOffset | None = None, + destination_configuration: DestinationConfiguration | None = None, + schedule_start_time: Timestamp | None = None, + schedule_end_time: Timestamp | None = None, + state: ScheduledQueryState | None = None, + **kwargs, + ) -> UpdateScheduledQueryResponse: + raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/opensearch/__init__.py b/localstack-core/localstack/aws/api/opensearch/__init__.py index 73c9074d0a619..40778ff3cb322 100644 --- a/localstack-core/localstack/aws/api/opensearch/__init__.py +++ b/localstack-core/localstack/aws/api/opensearch/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -39,17 +39,21 @@ ErrorType = str GUID = str HostedZoneId = str +IAMFederationRolesKey = str +IAMFederationSubjectKey = str Id = str IdentityCenterApplicationARN = str IdentityCenterInstanceARN = str IdentityPoolId = str IdentityStoreId = str +IndexName = str InstanceCount = int InstanceRole = str InstanceTypeString = str Integer = int IntegerClass = int Issue = str +KmsKeyArn = str KmsKeyId = str LicenseFilepath = str LimitName = str @@ -260,6 +264,12 @@ class InboundConnectionStatusCode(StrEnum): DELETED = "DELETED" +class IndexStatus(StrEnum): + CREATED = "CREATED" + UPDATED = "UPDATED" + DELETED = "DELETED" + + class InitiatedBy(StrEnum): CUSTOMER = "CUSTOMER" SERVICE = "SERVICE" @@ -320,6 +330,7 @@ class NodeType(StrEnum): Data = "Data" Ultrawarm = "Ultrawarm" Master = "Master" + Warm = "Warm" class OpenSearchPartitionInstanceType(StrEnum): @@ -667,14 +678,20 @@ class ResourceNotFoundException(ServiceException): Long = int -SlotList = List[Long] +SlotList = list[Long] class SlotNotAvailableException(ServiceException): code: str = "SlotNotAvailableException" sender_fault: bool = False status_code: int = 409 - SlotSuggestions: Optional[SlotList] + SlotSuggestions: SlotList | None + + +class ThrottlingException(ServiceException): + code: str = "ThrottlingException" + sender_fault: bool = False + status_code: int = 429 class ValidationException(ServiceException): @@ -683,21 +700,33 @@ class ValidationException(ServiceException): status_code: int = 400 +class ServerlessVectorAcceleration(TypedDict, total=False): + Enabled: Boolean | None + + +class S3VectorsEngine(TypedDict, total=False): + Enabled: Boolean | None + + class NaturalLanguageQueryGenerationOptionsInput(TypedDict, total=False): - DesiredState: Optional[NaturalLanguageQueryGenerationDesiredState] + DesiredState: NaturalLanguageQueryGenerationDesiredState | None class AIMLOptionsInput(TypedDict, total=False): - NaturalLanguageQueryGenerationOptions: Optional[NaturalLanguageQueryGenerationOptionsInput] + NaturalLanguageQueryGenerationOptions: NaturalLanguageQueryGenerationOptionsInput | None + S3VectorsEngine: S3VectorsEngine | None + ServerlessVectorAcceleration: ServerlessVectorAcceleration | None class NaturalLanguageQueryGenerationOptionsOutput(TypedDict, total=False): - DesiredState: Optional[NaturalLanguageQueryGenerationDesiredState] - CurrentState: Optional[NaturalLanguageQueryGenerationCurrentState] + DesiredState: NaturalLanguageQueryGenerationDesiredState | None + CurrentState: NaturalLanguageQueryGenerationCurrentState | None class AIMLOptionsOutput(TypedDict, total=False): - NaturalLanguageQueryGenerationOptions: Optional[NaturalLanguageQueryGenerationOptionsOutput] + NaturalLanguageQueryGenerationOptions: NaturalLanguageQueryGenerationOptionsOutput | None + S3VectorsEngine: S3VectorsEngine | None + ServerlessVectorAcceleration: ServerlessVectorAcceleration | None UpdateTimestamp = datetime @@ -706,20 +735,20 @@ class AIMLOptionsOutput(TypedDict, total=False): class OptionStatus(TypedDict, total=False): CreationDate: UpdateTimestamp UpdateDate: UpdateTimestamp - UpdateVersion: Optional[UIntValue] + UpdateVersion: UIntValue | None State: OptionState - PendingDeletion: Optional[Boolean] + PendingDeletion: Boolean | None class AIMLOptionsStatus(TypedDict, total=False): - Options: Optional[AIMLOptionsOutput] - Status: Optional[OptionStatus] + Options: AIMLOptionsOutput | None + Status: OptionStatus | None class AWSDomainInformation(TypedDict, total=False): - OwnerId: Optional[OwnerId] + OwnerId: OwnerId | None DomainName: DomainName - Region: Optional[Region] + Region: Region | None class AcceptInboundConnectionRequest(ServiceRequest): @@ -727,24 +756,24 @@ class AcceptInboundConnectionRequest(ServiceRequest): class InboundConnectionStatus(TypedDict, total=False): - StatusCode: Optional[InboundConnectionStatusCode] - Message: Optional[ConnectionStatusMessage] + StatusCode: InboundConnectionStatusCode | None + Message: ConnectionStatusMessage | None class DomainInformationContainer(TypedDict, total=False): - AWSDomainInformation: Optional[AWSDomainInformation] + AWSDomainInformation: AWSDomainInformation | None class InboundConnection(TypedDict, total=False): - LocalDomainInfo: Optional[DomainInformationContainer] - RemoteDomainInfo: Optional[DomainInformationContainer] - ConnectionId: Optional[ConnectionId] - ConnectionStatus: Optional[InboundConnectionStatus] - ConnectionMode: Optional[ConnectionMode] + LocalDomainInfo: DomainInformationContainer | None + RemoteDomainInfo: DomainInformationContainer | None + ConnectionId: ConnectionId | None + ConnectionStatus: InboundConnectionStatus | None + ConnectionMode: ConnectionMode | None class AcceptInboundConnectionResponse(TypedDict, total=False): - Connection: Optional[InboundConnection] + Connection: InboundConnection | None class AccessPoliciesStatus(TypedDict, total=False): @@ -753,22 +782,22 @@ class AccessPoliciesStatus(TypedDict, total=False): class S3GlueDataCatalog(TypedDict, total=False): - RoleArn: Optional[RoleArn] + RoleArn: RoleArn | None class DataSourceType(TypedDict, total=False): - S3GlueDataCatalog: Optional[S3GlueDataCatalog] + S3GlueDataCatalog: S3GlueDataCatalog | None class AddDataSourceRequest(ServiceRequest): DomainName: DomainName Name: DataSourceName DataSourceType: DataSourceType - Description: Optional[DataSourceDescription] + Description: DataSourceDescription | None class AddDataSourceResponse(TypedDict, total=False): - Message: Optional[String] + Message: String | None class Tag(TypedDict, total=False): @@ -776,8 +805,8 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] -DirectQueryOpenSearchARNList = List[ARN] +TagList = list[Tag] +DirectQueryOpenSearchARNList = list[ARN] class SecurityLakeDirectQueryDataSource(TypedDict, total=False): @@ -789,20 +818,20 @@ class CloudWatchDirectQueryDataSource(TypedDict, total=False): class DirectQueryDataSourceType(TypedDict, total=False): - CloudWatchLog: Optional[CloudWatchDirectQueryDataSource] - SecurityLake: Optional[SecurityLakeDirectQueryDataSource] + CloudWatchLog: CloudWatchDirectQueryDataSource | None + SecurityLake: SecurityLakeDirectQueryDataSource | None class AddDirectQueryDataSourceRequest(ServiceRequest): DataSourceName: DirectQueryDataSourceName DataSourceType: DirectQueryDataSourceType - Description: Optional[DirectQueryDataSourceDescription] + Description: DirectQueryDataSourceDescription | None OpenSearchArns: DirectQueryOpenSearchARNList - TagList: Optional[TagList] + TagList: TagList | None class AddDirectQueryDataSourceResponse(TypedDict, total=False): - DataSourceArn: Optional[String] + DataSourceArn: String | None class AddTagsRequest(ServiceRequest): @@ -810,16 +839,16 @@ class AddTagsRequest(ServiceRequest): TagList: TagList -LimitValueList = List[LimitValue] +LimitValueList = list[LimitValue] class AdditionalLimit(TypedDict, total=False): - LimitName: Optional[LimitName] - LimitValues: Optional[LimitValueList] + LimitName: LimitName | None + LimitValues: LimitValueList | None -AdditionalLimitList = List[AdditionalLimit] -AdvancedOptions = Dict[String, String] +AdditionalLimitList = list[AdditionalLimit] +AdvancedOptions = dict[String, String] class AdvancedOptionsStatus(TypedDict, total=False): @@ -830,11 +859,17 @@ class AdvancedOptionsStatus(TypedDict, total=False): DisableTimestamp = datetime +class IAMFederationOptionsOutput(TypedDict, total=False): + Enabled: Boolean | None + SubjectKey: IAMFederationSubjectKey | None + RolesKey: IAMFederationRolesKey | None + + class JWTOptionsOutput(TypedDict, total=False): - Enabled: Optional[Boolean] - SubjectKey: Optional[String] - RolesKey: Optional[String] - PublicKey: Optional[String] + Enabled: Boolean | None + SubjectKey: String | None + RolesKey: String | None + PublicKey: String | None class SAMLIdp(TypedDict, total=False): @@ -843,52 +878,60 @@ class SAMLIdp(TypedDict, total=False): class SAMLOptionsOutput(TypedDict, total=False): - Enabled: Optional[Boolean] - Idp: Optional[SAMLIdp] - SubjectKey: Optional[String] - RolesKey: Optional[String] - SessionTimeoutMinutes: Optional[IntegerClass] + Enabled: Boolean | None + Idp: SAMLIdp | None + SubjectKey: String | None + RolesKey: String | None + SessionTimeoutMinutes: IntegerClass | None class AdvancedSecurityOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - InternalUserDatabaseEnabled: Optional[Boolean] - SAMLOptions: Optional[SAMLOptionsOutput] - JWTOptions: Optional[JWTOptionsOutput] - AnonymousAuthDisableDate: Optional[DisableTimestamp] - AnonymousAuthEnabled: Optional[Boolean] + Enabled: Boolean | None + InternalUserDatabaseEnabled: Boolean | None + SAMLOptions: SAMLOptionsOutput | None + JWTOptions: JWTOptionsOutput | None + IAMFederationOptions: IAMFederationOptionsOutput | None + AnonymousAuthDisableDate: DisableTimestamp | None + AnonymousAuthEnabled: Boolean | None + + +class IAMFederationOptionsInput(TypedDict, total=False): + Enabled: Boolean | None + SubjectKey: IAMFederationSubjectKey | None + RolesKey: IAMFederationRolesKey | None class JWTOptionsInput(TypedDict, total=False): - Enabled: Optional[Boolean] - SubjectKey: Optional[SubjectKey] - RolesKey: Optional[RolesKey] - PublicKey: Optional[String] + Enabled: Boolean | None + SubjectKey: SubjectKey | None + RolesKey: RolesKey | None + PublicKey: String | None class SAMLOptionsInput(TypedDict, total=False): - Enabled: Optional[Boolean] - Idp: Optional[SAMLIdp] - MasterUserName: Optional[Username] - MasterBackendRole: Optional[BackendRole] - SubjectKey: Optional[String] - RolesKey: Optional[String] - SessionTimeoutMinutes: Optional[IntegerClass] + Enabled: Boolean | None + Idp: SAMLIdp | None + MasterUserName: Username | None + MasterBackendRole: BackendRole | None + SubjectKey: String | None + RolesKey: String | None + SessionTimeoutMinutes: IntegerClass | None class MasterUserOptions(TypedDict, total=False): - MasterUserARN: Optional[ARN] - MasterUserName: Optional[Username] - MasterUserPassword: Optional[Password] + MasterUserARN: ARN | None + MasterUserName: Username | None + MasterUserPassword: Password | None class AdvancedSecurityOptionsInput(TypedDict, total=False): - Enabled: Optional[Boolean] - InternalUserDatabaseEnabled: Optional[Boolean] - MasterUserOptions: Optional[MasterUserOptions] - SAMLOptions: Optional[SAMLOptionsInput] - JWTOptions: Optional[JWTOptionsInput] - AnonymousAuthEnabled: Optional[Boolean] + Enabled: Boolean | None + InternalUserDatabaseEnabled: Boolean | None + MasterUserOptions: MasterUserOptions | None + SAMLOptions: SAMLOptionsInput | None + JWTOptions: JWTOptionsInput | None + IAMFederationOptions: IAMFederationOptionsInput | None + AnonymousAuthEnabled: Boolean | None class AdvancedSecurityOptionsStatus(TypedDict, total=False): @@ -897,80 +940,80 @@ class AdvancedSecurityOptionsStatus(TypedDict, total=False): class AppConfig(TypedDict, total=False): - key: Optional[AppConfigType] - value: Optional[AppConfigValue] + key: AppConfigType | None + value: AppConfigValue | None -AppConfigs = List[AppConfig] -ApplicationStatuses = List[ApplicationStatus] +AppConfigs = list[AppConfig] +ApplicationStatuses = list[ApplicationStatus] Timestamp = datetime class ApplicationSummary(TypedDict, total=False): - id: Optional[Id] - arn: Optional[ARN] - name: Optional[ApplicationName] - endpoint: Optional[String] - status: Optional[ApplicationStatus] - createdAt: Optional[Timestamp] - lastUpdatedAt: Optional[Timestamp] + id: Id | None + arn: ARN | None + name: ApplicationName | None + endpoint: String | None + status: ApplicationStatus | None + createdAt: Timestamp | None + lastUpdatedAt: Timestamp | None -ApplicationSummaries = List[ApplicationSummary] +ApplicationSummaries = list[ApplicationSummary] class KeyStoreAccessOption(TypedDict, total=False): - KeyAccessRoleArn: Optional[RoleArn] + KeyAccessRoleArn: RoleArn | None KeyStoreAccessEnabled: Boolean class PackageAssociationConfiguration(TypedDict, total=False): - KeyStoreAccessOption: Optional[KeyStoreAccessOption] + KeyStoreAccessOption: KeyStoreAccessOption | None -PackageIDList = List[PackageID] +PackageIDList = list[PackageID] class AssociatePackageRequest(ServiceRequest): PackageID: PackageID DomainName: DomainName - PrerequisitePackageIDList: Optional[PackageIDList] - AssociationConfiguration: Optional[PackageAssociationConfiguration] + PrerequisitePackageIDList: PackageIDList | None + AssociationConfiguration: PackageAssociationConfiguration | None class ErrorDetails(TypedDict, total=False): - ErrorType: Optional[ErrorType] - ErrorMessage: Optional[ErrorMessage] + ErrorType: ErrorType | None + ErrorMessage: ErrorMessage | None LastUpdated = datetime class DomainPackageDetails(TypedDict, total=False): - PackageID: Optional[PackageID] - PackageName: Optional[PackageName] - PackageType: Optional[PackageType] - LastUpdated: Optional[LastUpdated] - DomainName: Optional[DomainName] - DomainPackageStatus: Optional[DomainPackageStatus] - PackageVersion: Optional[PackageVersion] - PrerequisitePackageIDList: Optional[PackageIDList] - ReferencePath: Optional[ReferencePath] - ErrorDetails: Optional[ErrorDetails] - AssociationConfiguration: Optional[PackageAssociationConfiguration] + PackageID: PackageID | None + PackageName: PackageName | None + PackageType: PackageType | None + LastUpdated: LastUpdated | None + DomainName: DomainName | None + DomainPackageStatus: DomainPackageStatus | None + PackageVersion: PackageVersion | None + PrerequisitePackageIDList: PackageIDList | None + ReferencePath: ReferencePath | None + ErrorDetails: ErrorDetails | None + AssociationConfiguration: PackageAssociationConfiguration | None class AssociatePackageResponse(TypedDict, total=False): - DomainPackageDetails: Optional[DomainPackageDetails] + DomainPackageDetails: DomainPackageDetails | None class PackageDetailsForAssociation(TypedDict, total=False): PackageID: PackageID - PrerequisitePackageIDList: Optional[PackageIDList] - AssociationConfiguration: Optional[PackageAssociationConfiguration] + PrerequisitePackageIDList: PackageIDList | None + AssociationConfiguration: PackageAssociationConfiguration | None -PackageDetailsForAssociationList = List[PackageDetailsForAssociation] +PackageDetailsForAssociationList = list[PackageDetailsForAssociation] class AssociatePackagesRequest(ServiceRequest): @@ -978,134 +1021,134 @@ class AssociatePackagesRequest(ServiceRequest): DomainName: DomainName -DomainPackageDetailsList = List[DomainPackageDetails] +DomainPackageDetailsList = list[DomainPackageDetails] class AssociatePackagesResponse(TypedDict, total=False): - DomainPackageDetailsList: Optional[DomainPackageDetailsList] + DomainPackageDetailsList: DomainPackageDetailsList | None class AuthorizeVpcEndpointAccessRequest(ServiceRequest): DomainName: DomainName - Account: Optional[AWSAccount] - Service: Optional[AWSServicePrincipal] + Account: AWSAccount | None + Service: AWSServicePrincipal | None class AuthorizedPrincipal(TypedDict, total=False): - PrincipalType: Optional[PrincipalType] - Principal: Optional[String] + PrincipalType: PrincipalType | None + Principal: String | None class AuthorizeVpcEndpointAccessResponse(TypedDict, total=False): AuthorizedPrincipal: AuthorizedPrincipal -AuthorizedPrincipalList = List[AuthorizedPrincipal] +AuthorizedPrincipalList = list[AuthorizedPrincipal] AutoTuneDate = datetime class ScheduledAutoTuneDetails(TypedDict, total=False): - Date: Optional[AutoTuneDate] - ActionType: Optional[ScheduledAutoTuneActionType] - Action: Optional[ScheduledAutoTuneDescription] - Severity: Optional[ScheduledAutoTuneSeverityType] + Date: AutoTuneDate | None + ActionType: ScheduledAutoTuneActionType | None + Action: ScheduledAutoTuneDescription | None + Severity: ScheduledAutoTuneSeverityType | None class AutoTuneDetails(TypedDict, total=False): - ScheduledAutoTuneDetails: Optional[ScheduledAutoTuneDetails] + ScheduledAutoTuneDetails: ScheduledAutoTuneDetails | None class AutoTune(TypedDict, total=False): - AutoTuneType: Optional[AutoTuneType] - AutoTuneDetails: Optional[AutoTuneDetails] + AutoTuneType: AutoTuneType | None + AutoTuneDetails: AutoTuneDetails | None -AutoTuneList = List[AutoTune] +AutoTuneList = list[AutoTune] DurationValue = int class Duration(TypedDict, total=False): - Value: Optional[DurationValue] - Unit: Optional[TimeUnit] + Value: DurationValue | None + Unit: TimeUnit | None StartAt = datetime class AutoTuneMaintenanceSchedule(TypedDict, total=False): - StartAt: Optional[StartAt] - Duration: Optional[Duration] - CronExpressionForRecurrence: Optional[String] + StartAt: StartAt | None + Duration: Duration | None + CronExpressionForRecurrence: String | None -AutoTuneMaintenanceScheduleList = List[AutoTuneMaintenanceSchedule] +AutoTuneMaintenanceScheduleList = list[AutoTuneMaintenanceSchedule] class AutoTuneOptions(TypedDict, total=False): - DesiredState: Optional[AutoTuneDesiredState] - RollbackOnDisable: Optional[RollbackOnDisable] - MaintenanceSchedules: Optional[AutoTuneMaintenanceScheduleList] - UseOffPeakWindow: Optional[Boolean] + DesiredState: AutoTuneDesiredState | None + RollbackOnDisable: RollbackOnDisable | None + MaintenanceSchedules: AutoTuneMaintenanceScheduleList | None + UseOffPeakWindow: Boolean | None class AutoTuneOptionsInput(TypedDict, total=False): - DesiredState: Optional[AutoTuneDesiredState] - MaintenanceSchedules: Optional[AutoTuneMaintenanceScheduleList] - UseOffPeakWindow: Optional[Boolean] + DesiredState: AutoTuneDesiredState | None + MaintenanceSchedules: AutoTuneMaintenanceScheduleList | None + UseOffPeakWindow: Boolean | None class AutoTuneOptionsOutput(TypedDict, total=False): - State: Optional[AutoTuneState] - ErrorMessage: Optional[String] - UseOffPeakWindow: Optional[Boolean] + State: AutoTuneState | None + ErrorMessage: String | None + UseOffPeakWindow: Boolean | None class AutoTuneStatus(TypedDict, total=False): CreationDate: UpdateTimestamp UpdateDate: UpdateTimestamp - UpdateVersion: Optional[UIntValue] + UpdateVersion: UIntValue | None State: AutoTuneState - ErrorMessage: Optional[String] - PendingDeletion: Optional[Boolean] + ErrorMessage: String | None + PendingDeletion: Boolean | None class AutoTuneOptionsStatus(TypedDict, total=False): - Options: Optional[AutoTuneOptions] - Status: Optional[AutoTuneStatus] + Options: AutoTuneOptions | None + Status: AutoTuneStatus | None class AvailabilityZoneInfo(TypedDict, total=False): - AvailabilityZoneName: Optional[AvailabilityZone] - ZoneStatus: Optional[ZoneStatus] - ConfiguredDataNodeCount: Optional[NumberOfNodes] - AvailableDataNodeCount: Optional[NumberOfNodes] - TotalShards: Optional[NumberOfShards] - TotalUnAssignedShards: Optional[NumberOfShards] + AvailabilityZoneName: AvailabilityZone | None + ZoneStatus: ZoneStatus | None + ConfiguredDataNodeCount: NumberOfNodes | None + AvailableDataNodeCount: NumberOfNodes | None + TotalShards: NumberOfShards | None + TotalUnAssignedShards: NumberOfShards | None -AvailabilityZoneInfoList = List[AvailabilityZoneInfo] -AvailabilityZoneList = List[AvailabilityZone] +AvailabilityZoneInfoList = list[AvailabilityZoneInfo] +AvailabilityZoneList = list[AvailabilityZone] class CancelDomainConfigChangeRequest(ServiceRequest): DomainName: DomainName - DryRun: Optional[DryRun] + DryRun: DryRun | None class CancelledChangeProperty(TypedDict, total=False): - PropertyName: Optional[String] - CancelledValue: Optional[String] - ActiveValue: Optional[String] + PropertyName: String | None + CancelledValue: String | None + ActiveValue: String | None -CancelledChangePropertyList = List[CancelledChangeProperty] -GUIDList = List[GUID] +CancelledChangePropertyList = list[CancelledChangeProperty] +GUIDList = list[GUID] class CancelDomainConfigChangeResponse(TypedDict, total=False): - CancelledChangeIds: Optional[GUIDList] - CancelledChangeProperties: Optional[CancelledChangePropertyList] - DryRun: Optional[DryRun] + CancelledChangeIds: GUIDList | None + CancelledChangeProperties: CancelledChangePropertyList | None + DryRun: DryRun | None class CancelServiceSoftwareUpdateRequest(ServiceRequest): @@ -1116,65 +1159,65 @@ class CancelServiceSoftwareUpdateRequest(ServiceRequest): class ServiceSoftwareOptions(TypedDict, total=False): - CurrentVersion: Optional[String] - NewVersion: Optional[String] - UpdateAvailable: Optional[Boolean] - Cancellable: Optional[Boolean] - UpdateStatus: Optional[DeploymentStatus] - Description: Optional[String] - AutomatedUpdateDate: Optional[DeploymentCloseDateTimeStamp] - OptionalDeployment: Optional[Boolean] + CurrentVersion: String | None + NewVersion: String | None + UpdateAvailable: Boolean | None + Cancellable: Boolean | None + UpdateStatus: DeploymentStatus | None + Description: String | None + AutomatedUpdateDate: DeploymentCloseDateTimeStamp | None + OptionalDeployment: Boolean | None class CancelServiceSoftwareUpdateResponse(TypedDict, total=False): - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] + ServiceSoftwareOptions: ServiceSoftwareOptions | None class ChangeProgressDetails(TypedDict, total=False): - ChangeId: Optional[GUID] - Message: Optional[Message] - ConfigChangeStatus: Optional[ConfigChangeStatus] - InitiatedBy: Optional[InitiatedBy] - StartTime: Optional[UpdateTimestamp] - LastUpdatedTime: Optional[UpdateTimestamp] + ChangeId: GUID | None + Message: Message | None + ConfigChangeStatus: ConfigChangeStatus | None + InitiatedBy: InitiatedBy | None + StartTime: UpdateTimestamp | None + LastUpdatedTime: UpdateTimestamp | None class ChangeProgressStage(TypedDict, total=False): - Name: Optional[ChangeProgressStageName] - Status: Optional[ChangeProgressStageStatus] - Description: Optional[Description] - LastUpdated: Optional[LastUpdated] + Name: ChangeProgressStageName | None + Status: ChangeProgressStageStatus | None + Description: Description | None + LastUpdated: LastUpdated | None -ChangeProgressStageList = List[ChangeProgressStage] -StringList = List[String] +ChangeProgressStageList = list[ChangeProgressStage] +StringList = list[String] class ChangeProgressStatusDetails(TypedDict, total=False): - ChangeId: Optional[GUID] - StartTime: Optional[UpdateTimestamp] - Status: Optional[OverallChangeStatus] - PendingProperties: Optional[StringList] - CompletedProperties: Optional[StringList] - TotalNumberOfStages: Optional[TotalNumberOfStages] - ChangeProgressStages: Optional[ChangeProgressStageList] - LastUpdatedTime: Optional[UpdateTimestamp] - ConfigChangeStatus: Optional[ConfigChangeStatus] - InitiatedBy: Optional[InitiatedBy] + ChangeId: GUID | None + StartTime: UpdateTimestamp | None + Status: OverallChangeStatus | None + PendingProperties: StringList | None + CompletedProperties: StringList | None + TotalNumberOfStages: TotalNumberOfStages | None + ChangeProgressStages: ChangeProgressStageList | None + LastUpdatedTime: UpdateTimestamp | None + ConfigChangeStatus: ConfigChangeStatus | None + InitiatedBy: InitiatedBy | None class NodeConfig(TypedDict, total=False): - Enabled: Optional[Boolean] - Type: Optional[OpenSearchPartitionInstanceType] - Count: Optional[IntegerClass] + Enabled: Boolean | None + Type: OpenSearchPartitionInstanceType | None + Count: IntegerClass | None class NodeOption(TypedDict, total=False): - NodeType: Optional[NodeOptionsNodeType] - NodeConfig: Optional[NodeConfig] + NodeType: NodeOptionsNodeType | None + NodeConfig: NodeConfig | None -NodeOptionsList = List[NodeOption] +NodeOptionsList = list[NodeOption] class ColdStorageOptions(TypedDict, total=False): @@ -1182,23 +1225,23 @@ class ColdStorageOptions(TypedDict, total=False): class ZoneAwarenessConfig(TypedDict, total=False): - AvailabilityZoneCount: Optional[IntegerClass] + AvailabilityZoneCount: IntegerClass | None class ClusterConfig(TypedDict, total=False): - InstanceType: Optional[OpenSearchPartitionInstanceType] - InstanceCount: Optional[IntegerClass] - DedicatedMasterEnabled: Optional[Boolean] - ZoneAwarenessEnabled: Optional[Boolean] - ZoneAwarenessConfig: Optional[ZoneAwarenessConfig] - DedicatedMasterType: Optional[OpenSearchPartitionInstanceType] - DedicatedMasterCount: Optional[IntegerClass] - WarmEnabled: Optional[Boolean] - WarmType: Optional[OpenSearchWarmPartitionInstanceType] - WarmCount: Optional[IntegerClass] - ColdStorageOptions: Optional[ColdStorageOptions] - MultiAZWithStandbyEnabled: Optional[Boolean] - NodeOptions: Optional[NodeOptionsList] + InstanceType: OpenSearchPartitionInstanceType | None + InstanceCount: IntegerClass | None + DedicatedMasterEnabled: Boolean | None + ZoneAwarenessEnabled: Boolean | None + ZoneAwarenessConfig: ZoneAwarenessConfig | None + DedicatedMasterType: OpenSearchPartitionInstanceType | None + DedicatedMasterCount: IntegerClass | None + WarmEnabled: Boolean | None + WarmType: OpenSearchWarmPartitionInstanceType | None + WarmCount: IntegerClass | None + ColdStorageOptions: ColdStorageOptions | None + MultiAZWithStandbyEnabled: Boolean | None + NodeOptions: NodeOptionsList | None class ClusterConfigStatus(TypedDict, total=False): @@ -1207,10 +1250,10 @@ class ClusterConfigStatus(TypedDict, total=False): class CognitoOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - UserPoolId: Optional[UserPoolId] - IdentityPoolId: Optional[IdentityPoolId] - RoleArn: Optional[RoleArn] + Enabled: Boolean | None + UserPoolId: UserPoolId | None + IdentityPoolId: IdentityPoolId | None + RoleArn: RoleArn | None class CognitoOptionsStatus(TypedDict, total=False): @@ -1218,69 +1261,71 @@ class CognitoOptionsStatus(TypedDict, total=False): Status: OptionStatus -VersionList = List[VersionString] +VersionList = list[VersionString] class CompatibleVersionsMap(TypedDict, total=False): - SourceVersion: Optional[VersionString] - TargetVersions: Optional[VersionList] + SourceVersion: VersionString | None + TargetVersions: VersionList | None -CompatibleVersionsList = List[CompatibleVersionsMap] +CompatibleVersionsList = list[CompatibleVersionsMap] class CrossClusterSearchConnectionProperties(TypedDict, total=False): - SkipUnavailable: Optional[SkipUnavailableStatus] + SkipUnavailable: SkipUnavailableStatus | None class ConnectionProperties(TypedDict, total=False): - Endpoint: Optional[Endpoint] - CrossClusterSearch: Optional[CrossClusterSearchConnectionProperties] + Endpoint: Endpoint | None + CrossClusterSearch: CrossClusterSearchConnectionProperties | None class IamIdentityCenterOptionsInput(TypedDict, total=False): - enabled: Optional[Boolean] - iamIdentityCenterInstanceArn: Optional[ARN] - iamRoleForIdentityCenterApplicationArn: Optional[RoleArn] + enabled: Boolean | None + iamIdentityCenterInstanceArn: ARN | None + iamRoleForIdentityCenterApplicationArn: RoleArn | None class DataSource(TypedDict, total=False): - dataSourceArn: Optional[ARN] - dataSourceDescription: Optional[DataSourceDescription] + dataSourceArn: ARN | None + dataSourceDescription: DataSourceDescription | None -DataSources = List[DataSource] +DataSources = list[DataSource] class CreateApplicationRequest(ServiceRequest): - clientToken: Optional[ClientToken] + clientToken: ClientToken | None name: ApplicationName - dataSources: Optional[DataSources] - iamIdentityCenterOptions: Optional[IamIdentityCenterOptionsInput] - appConfigs: Optional[AppConfigs] - tagList: Optional[TagList] + dataSources: DataSources | None + iamIdentityCenterOptions: IamIdentityCenterOptionsInput | None + appConfigs: AppConfigs | None + tagList: TagList | None + kmsKeyArn: KmsKeyArn | None class IamIdentityCenterOptions(TypedDict, total=False): - enabled: Optional[Boolean] - iamIdentityCenterInstanceArn: Optional[ARN] - iamRoleForIdentityCenterApplicationArn: Optional[RoleArn] - iamIdentityCenterApplicationArn: Optional[ARN] + enabled: Boolean | None + iamIdentityCenterInstanceArn: ARN | None + iamRoleForIdentityCenterApplicationArn: RoleArn | None + iamIdentityCenterApplicationArn: ARN | None class CreateApplicationResponse(TypedDict, total=False): - id: Optional[Id] - name: Optional[ApplicationName] - arn: Optional[ARN] - dataSources: Optional[DataSources] - iamIdentityCenterOptions: Optional[IamIdentityCenterOptions] - appConfigs: Optional[AppConfigs] - tagList: Optional[TagList] - createdAt: Optional[Timestamp] + id: Id | None + name: ApplicationName | None + arn: ARN | None + dataSources: DataSources | None + iamIdentityCenterOptions: IamIdentityCenterOptions | None + appConfigs: AppConfigs | None + tagList: TagList | None + createdAt: Timestamp | None + kmsKeyArn: KmsKeyArn | None class SoftwareUpdateOptions(TypedDict, total=False): - AutoSoftwareUpdateEnabled: Optional[Boolean] + AutoSoftwareUpdateEnabled: Boolean | None StartTimeMinutes = int @@ -1293,182 +1338,196 @@ class WindowStartTime(TypedDict, total=False): class OffPeakWindow(TypedDict, total=False): - WindowStartTime: Optional[WindowStartTime] + WindowStartTime: WindowStartTime | None class OffPeakWindowOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - OffPeakWindow: Optional[OffPeakWindow] + Enabled: Boolean | None + OffPeakWindow: OffPeakWindow | None class IdentityCenterOptionsInput(TypedDict, total=False): - EnabledAPIAccess: Optional[Boolean] - IdentityCenterInstanceARN: Optional[IdentityCenterInstanceARN] - SubjectKey: Optional[SubjectKeyIdCOption] - RolesKey: Optional[RolesKeyIdCOption] + EnabledAPIAccess: Boolean | None + IdentityCenterInstanceARN: IdentityCenterInstanceARN | None + SubjectKey: SubjectKeyIdCOption | None + RolesKey: RolesKeyIdCOption | None class DomainEndpointOptions(TypedDict, total=False): - EnforceHTTPS: Optional[Boolean] - TLSSecurityPolicy: Optional[TLSSecurityPolicy] - CustomEndpointEnabled: Optional[Boolean] - CustomEndpoint: Optional[DomainNameFqdn] - CustomEndpointCertificateArn: Optional[ARN] + EnforceHTTPS: Boolean | None + TLSSecurityPolicy: TLSSecurityPolicy | None + CustomEndpointEnabled: Boolean | None + CustomEndpoint: DomainNameFqdn | None + CustomEndpointCertificateArn: ARN | None class LogPublishingOption(TypedDict, total=False): - CloudWatchLogsLogGroupArn: Optional[CloudWatchLogsLogGroupArn] - Enabled: Optional[Boolean] + CloudWatchLogsLogGroupArn: CloudWatchLogsLogGroupArn | None + Enabled: Boolean | None -LogPublishingOptions = Dict[LogType, LogPublishingOption] +LogPublishingOptions = dict[LogType, LogPublishingOption] class NodeToNodeEncryptionOptions(TypedDict, total=False): - Enabled: Optional[Boolean] + Enabled: Boolean | None class EncryptionAtRestOptions(TypedDict, total=False): - Enabled: Optional[Boolean] - KmsKeyId: Optional[KmsKeyId] + Enabled: Boolean | None + KmsKeyId: KmsKeyId | None class VPCOptions(TypedDict, total=False): - SubnetIds: Optional[StringList] - SecurityGroupIds: Optional[StringList] + SubnetIds: StringList | None + SecurityGroupIds: StringList | None class SnapshotOptions(TypedDict, total=False): - AutomatedSnapshotStartHour: Optional[IntegerClass] + AutomatedSnapshotStartHour: IntegerClass | None class EBSOptions(TypedDict, total=False): - EBSEnabled: Optional[Boolean] - VolumeType: Optional[VolumeType] - VolumeSize: Optional[IntegerClass] - Iops: Optional[IntegerClass] - Throughput: Optional[IntegerClass] + EBSEnabled: Boolean | None + VolumeType: VolumeType | None + VolumeSize: IntegerClass | None + Iops: IntegerClass | None + Throughput: IntegerClass | None class CreateDomainRequest(ServiceRequest): DomainName: DomainName - EngineVersion: Optional[VersionString] - ClusterConfig: Optional[ClusterConfig] - EBSOptions: Optional[EBSOptions] - AccessPolicies: Optional[PolicyDocument] - IPAddressType: Optional[IPAddressType] - SnapshotOptions: Optional[SnapshotOptions] - VPCOptions: Optional[VPCOptions] - CognitoOptions: Optional[CognitoOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - AdvancedOptions: Optional[AdvancedOptions] - LogPublishingOptions: Optional[LogPublishingOptions] - DomainEndpointOptions: Optional[DomainEndpointOptions] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - IdentityCenterOptions: Optional[IdentityCenterOptionsInput] - TagList: Optional[TagList] - AutoTuneOptions: Optional[AutoTuneOptionsInput] - OffPeakWindowOptions: Optional[OffPeakWindowOptions] - SoftwareUpdateOptions: Optional[SoftwareUpdateOptions] - AIMLOptions: Optional[AIMLOptionsInput] + EngineVersion: VersionString | None + ClusterConfig: ClusterConfig | None + EBSOptions: EBSOptions | None + AccessPolicies: PolicyDocument | None + IPAddressType: IPAddressType | None + SnapshotOptions: SnapshotOptions | None + VPCOptions: VPCOptions | None + CognitoOptions: CognitoOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + AdvancedOptions: AdvancedOptions | None + LogPublishingOptions: LogPublishingOptions | None + DomainEndpointOptions: DomainEndpointOptions | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + IdentityCenterOptions: IdentityCenterOptionsInput | None + TagList: TagList | None + AutoTuneOptions: AutoTuneOptionsInput | None + OffPeakWindowOptions: OffPeakWindowOptions | None + SoftwareUpdateOptions: SoftwareUpdateOptions | None + AIMLOptions: AIMLOptionsInput | None class ModifyingProperties(TypedDict, total=False): - Name: Optional[String] - ActiveValue: Optional[String] - PendingValue: Optional[String] - ValueType: Optional[PropertyValueType] + Name: String | None + ActiveValue: String | None + PendingValue: String | None + ValueType: PropertyValueType | None -ModifyingPropertiesList = List[ModifyingProperties] +ModifyingPropertiesList = list[ModifyingProperties] class IdentityCenterOptions(TypedDict, total=False): - EnabledAPIAccess: Optional[Boolean] - IdentityCenterInstanceARN: Optional[IdentityCenterInstanceARN] - SubjectKey: Optional[SubjectKeyIdCOption] - RolesKey: Optional[RolesKeyIdCOption] - IdentityCenterApplicationARN: Optional[IdentityCenterApplicationARN] - IdentityStoreId: Optional[IdentityStoreId] + EnabledAPIAccess: Boolean | None + IdentityCenterInstanceARN: IdentityCenterInstanceARN | None + SubjectKey: SubjectKeyIdCOption | None + RolesKey: RolesKeyIdCOption | None + IdentityCenterApplicationARN: IdentityCenterApplicationARN | None + IdentityStoreId: IdentityStoreId | None class VPCDerivedInfo(TypedDict, total=False): - VPCId: Optional[String] - SubnetIds: Optional[StringList] - AvailabilityZones: Optional[StringList] - SecurityGroupIds: Optional[StringList] + VPCId: String | None + SubnetIds: StringList | None + AvailabilityZones: StringList | None + SecurityGroupIds: StringList | None -EndpointsMap = Dict[String, ServiceUrl] +EndpointsMap = dict[String, ServiceUrl] class DomainStatus(TypedDict, total=False): DomainId: DomainId DomainName: DomainName ARN: ARN - Created: Optional[Boolean] - Deleted: Optional[Boolean] - Endpoint: Optional[ServiceUrl] - EndpointV2: Optional[ServiceUrl] - Endpoints: Optional[EndpointsMap] - DomainEndpointV2HostedZoneId: Optional[HostedZoneId] - Processing: Optional[Boolean] - UpgradeProcessing: Optional[Boolean] - EngineVersion: Optional[VersionString] + Created: Boolean | None + Deleted: Boolean | None + Endpoint: ServiceUrl | None + EndpointV2: ServiceUrl | None + Endpoints: EndpointsMap | None + DomainEndpointV2HostedZoneId: HostedZoneId | None + Processing: Boolean | None + UpgradeProcessing: Boolean | None + EngineVersion: VersionString | None ClusterConfig: ClusterConfig - EBSOptions: Optional[EBSOptions] - AccessPolicies: Optional[PolicyDocument] - IPAddressType: Optional[IPAddressType] - SnapshotOptions: Optional[SnapshotOptions] - VPCOptions: Optional[VPCDerivedInfo] - CognitoOptions: Optional[CognitoOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - AdvancedOptions: Optional[AdvancedOptions] - LogPublishingOptions: Optional[LogPublishingOptions] - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] - DomainEndpointOptions: Optional[DomainEndpointOptions] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptions] - IdentityCenterOptions: Optional[IdentityCenterOptions] - AutoTuneOptions: Optional[AutoTuneOptionsOutput] - ChangeProgressDetails: Optional[ChangeProgressDetails] - OffPeakWindowOptions: Optional[OffPeakWindowOptions] - SoftwareUpdateOptions: Optional[SoftwareUpdateOptions] - DomainProcessingStatus: Optional[DomainProcessingStatusType] - ModifyingProperties: Optional[ModifyingPropertiesList] - AIMLOptions: Optional[AIMLOptionsOutput] + EBSOptions: EBSOptions | None + AccessPolicies: PolicyDocument | None + IPAddressType: IPAddressType | None + SnapshotOptions: SnapshotOptions | None + VPCOptions: VPCDerivedInfo | None + CognitoOptions: CognitoOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + AdvancedOptions: AdvancedOptions | None + LogPublishingOptions: LogPublishingOptions | None + ServiceSoftwareOptions: ServiceSoftwareOptions | None + DomainEndpointOptions: DomainEndpointOptions | None + AdvancedSecurityOptions: AdvancedSecurityOptions | None + IdentityCenterOptions: IdentityCenterOptions | None + AutoTuneOptions: AutoTuneOptionsOutput | None + ChangeProgressDetails: ChangeProgressDetails | None + OffPeakWindowOptions: OffPeakWindowOptions | None + SoftwareUpdateOptions: SoftwareUpdateOptions | None + DomainProcessingStatus: DomainProcessingStatusType | None + ModifyingProperties: ModifyingPropertiesList | None + AIMLOptions: AIMLOptionsOutput | None class CreateDomainResponse(TypedDict, total=False): - DomainStatus: Optional[DomainStatus] + DomainStatus: DomainStatus | None + + +class IndexSchema(TypedDict, total=False): + pass + + +class CreateIndexRequest(ServiceRequest): + DomainName: DomainName + IndexName: IndexName + IndexSchema: IndexSchema + + +class CreateIndexResponse(TypedDict, total=False): + Status: IndexStatus class CreateOutboundConnectionRequest(ServiceRequest): LocalDomainInfo: DomainInformationContainer RemoteDomainInfo: DomainInformationContainer ConnectionAlias: ConnectionAlias - ConnectionMode: Optional[ConnectionMode] - ConnectionProperties: Optional[ConnectionProperties] + ConnectionMode: ConnectionMode | None + ConnectionProperties: ConnectionProperties | None class OutboundConnectionStatus(TypedDict, total=False): - StatusCode: Optional[OutboundConnectionStatusCode] - Message: Optional[ConnectionStatusMessage] + StatusCode: OutboundConnectionStatusCode | None + Message: ConnectionStatusMessage | None class CreateOutboundConnectionResponse(TypedDict, total=False): - LocalDomainInfo: Optional[DomainInformationContainer] - RemoteDomainInfo: Optional[DomainInformationContainer] - ConnectionAlias: Optional[ConnectionAlias] - ConnectionStatus: Optional[OutboundConnectionStatus] - ConnectionId: Optional[ConnectionId] - ConnectionMode: Optional[ConnectionMode] - ConnectionProperties: Optional[ConnectionProperties] + LocalDomainInfo: DomainInformationContainer | None + RemoteDomainInfo: DomainInformationContainer | None + ConnectionAlias: ConnectionAlias | None + ConnectionStatus: OutboundConnectionStatus | None + ConnectionId: ConnectionId | None + ConnectionMode: ConnectionMode | None + ConnectionProperties: ConnectionProperties | None class PackageEncryptionOptions(TypedDict, total=False): - KmsKeyIdentifier: Optional[KmsKeyId] + KmsKeyIdentifier: KmsKeyId | None EncryptionEnabled: Boolean @@ -1478,78 +1537,78 @@ class PackageVendingOptions(TypedDict, total=False): class PackageConfiguration(TypedDict, total=False): LicenseRequirement: RequirementLevel - LicenseFilepath: Optional[LicenseFilepath] + LicenseFilepath: LicenseFilepath | None ConfigurationRequirement: RequirementLevel - RequiresRestartForConfigurationUpdate: Optional[Boolean] + RequiresRestartForConfigurationUpdate: Boolean | None class PackageSource(TypedDict, total=False): - S3BucketName: Optional[S3BucketName] - S3Key: Optional[S3Key] + S3BucketName: S3BucketName | None + S3Key: S3Key | None class CreatePackageRequest(ServiceRequest): PackageName: PackageName PackageType: PackageType - PackageDescription: Optional[PackageDescription] + PackageDescription: PackageDescription | None PackageSource: PackageSource - PackageConfiguration: Optional[PackageConfiguration] - EngineVersion: Optional[EngineVersion] - PackageVendingOptions: Optional[PackageVendingOptions] - PackageEncryptionOptions: Optional[PackageEncryptionOptions] + PackageConfiguration: PackageConfiguration | None + EngineVersion: EngineVersion | None + PackageVendingOptions: PackageVendingOptions | None + PackageEncryptionOptions: PackageEncryptionOptions | None -PackageUserList = List[PackageUser] +PackageUserList = list[PackageUser] UncompressedPluginSizeInBytes = int class PluginProperties(TypedDict, total=False): - Name: Optional[PluginName] - Description: Optional[PluginDescription] - Version: Optional[PluginVersion] - ClassName: Optional[PluginClassName] - UncompressedSizeInBytes: Optional[UncompressedPluginSizeInBytes] + Name: PluginName | None + Description: PluginDescription | None + Version: PluginVersion | None + ClassName: PluginClassName | None + UncompressedSizeInBytes: UncompressedPluginSizeInBytes | None CreatedAt = datetime class PackageDetails(TypedDict, total=False): - PackageID: Optional[PackageID] - PackageName: Optional[PackageName] - PackageType: Optional[PackageType] - PackageDescription: Optional[PackageDescription] - PackageStatus: Optional[PackageStatus] - CreatedAt: Optional[CreatedAt] - LastUpdatedAt: Optional[LastUpdated] - AvailablePackageVersion: Optional[PackageVersion] - ErrorDetails: Optional[ErrorDetails] - EngineVersion: Optional[EngineVersion] - AvailablePluginProperties: Optional[PluginProperties] - AvailablePackageConfiguration: Optional[PackageConfiguration] - AllowListedUserList: Optional[PackageUserList] - PackageOwner: Optional[PackageOwner] - PackageVendingOptions: Optional[PackageVendingOptions] - PackageEncryptionOptions: Optional[PackageEncryptionOptions] + PackageID: PackageID | None + PackageName: PackageName | None + PackageType: PackageType | None + PackageDescription: PackageDescription | None + PackageStatus: PackageStatus | None + CreatedAt: CreatedAt | None + LastUpdatedAt: LastUpdated | None + AvailablePackageVersion: PackageVersion | None + ErrorDetails: ErrorDetails | None + EngineVersion: EngineVersion | None + AvailablePluginProperties: PluginProperties | None + AvailablePackageConfiguration: PackageConfiguration | None + AllowListedUserList: PackageUserList | None + PackageOwner: PackageOwner | None + PackageVendingOptions: PackageVendingOptions | None + PackageEncryptionOptions: PackageEncryptionOptions | None class CreatePackageResponse(TypedDict, total=False): - PackageDetails: Optional[PackageDetails] + PackageDetails: PackageDetails | None class CreateVpcEndpointRequest(ServiceRequest): DomainArn: DomainArn VpcOptions: VPCOptions - ClientToken: Optional[ClientToken] + ClientToken: ClientToken | None class VpcEndpoint(TypedDict, total=False): - VpcEndpointId: Optional[VpcEndpointId] - VpcEndpointOwner: Optional[AWSAccount] - DomainArn: Optional[DomainArn] - VpcOptions: Optional[VPCDerivedInfo] - Status: Optional[VpcEndpointStatus] - Endpoint: Optional[Endpoint] + VpcEndpointId: VpcEndpointId | None + VpcEndpointOwner: AWSAccount | None + DomainArn: DomainArn | None + VpcOptions: VPCDerivedInfo | None + Status: VpcEndpointStatus | None + Endpoint: Endpoint | None class CreateVpcEndpointResponse(TypedDict, total=False): @@ -1557,13 +1616,13 @@ class CreateVpcEndpointResponse(TypedDict, total=False): class DataSourceDetails(TypedDict, total=False): - DataSourceType: Optional[DataSourceType] - Name: Optional[DataSourceName] - Description: Optional[DataSourceDescription] - Status: Optional[DataSourceStatus] + DataSourceType: DataSourceType | None + Name: DataSourceName | None + Description: DataSourceDescription | None + Status: DataSourceStatus | None -DataSourceList = List[DataSourceDetails] +DataSourceList = list[DataSourceDetails] class DeleteApplicationRequest(ServiceRequest): @@ -1580,7 +1639,7 @@ class DeleteDataSourceRequest(ServiceRequest): class DeleteDataSourceResponse(TypedDict, total=False): - Message: Optional[String] + Message: String | None class DeleteDirectQueryDataSourceRequest(ServiceRequest): @@ -1592,7 +1651,7 @@ class DeleteDomainRequest(ServiceRequest): class DeleteDomainResponse(TypedDict, total=False): - DomainStatus: Optional[DomainStatus] + DomainStatus: DomainStatus | None class DeleteInboundConnectionRequest(ServiceRequest): @@ -1600,7 +1659,16 @@ class DeleteInboundConnectionRequest(ServiceRequest): class DeleteInboundConnectionResponse(TypedDict, total=False): - Connection: Optional[InboundConnection] + Connection: InboundConnection | None + + +class DeleteIndexRequest(ServiceRequest): + DomainName: DomainName + IndexName: IndexName + + +class DeleteIndexResponse(TypedDict, total=False): + Status: IndexStatus class DeleteOutboundConnectionRequest(ServiceRequest): @@ -1608,17 +1676,17 @@ class DeleteOutboundConnectionRequest(ServiceRequest): class OutboundConnection(TypedDict, total=False): - LocalDomainInfo: Optional[DomainInformationContainer] - RemoteDomainInfo: Optional[DomainInformationContainer] - ConnectionId: Optional[ConnectionId] - ConnectionAlias: Optional[ConnectionAlias] - ConnectionStatus: Optional[OutboundConnectionStatus] - ConnectionMode: Optional[ConnectionMode] - ConnectionProperties: Optional[ConnectionProperties] + LocalDomainInfo: DomainInformationContainer | None + RemoteDomainInfo: DomainInformationContainer | None + ConnectionId: ConnectionId | None + ConnectionAlias: ConnectionAlias | None + ConnectionStatus: OutboundConnectionStatus | None + ConnectionMode: ConnectionMode | None + ConnectionProperties: ConnectionProperties | None class DeleteOutboundConnectionResponse(TypedDict, total=False): - Connection: Optional[OutboundConnection] + Connection: OutboundConnection | None class DeletePackageRequest(ServiceRequest): @@ -1626,7 +1694,7 @@ class DeletePackageRequest(ServiceRequest): class DeletePackageResponse(TypedDict, total=False): - PackageDetails: Optional[PackageDetails] + PackageDetails: PackageDetails | None class DeleteVpcEndpointRequest(ServiceRequest): @@ -1634,10 +1702,10 @@ class DeleteVpcEndpointRequest(ServiceRequest): class VpcEndpointSummary(TypedDict, total=False): - VpcEndpointId: Optional[VpcEndpointId] - VpcEndpointOwner: Optional[String] - DomainArn: Optional[DomainArn] - Status: Optional[VpcEndpointStatus] + VpcEndpointId: VpcEndpointId | None + VpcEndpointOwner: String | None + DomainArn: DomainArn | None + Status: VpcEndpointStatus | None class DeleteVpcEndpointResponse(TypedDict, total=False): @@ -1646,22 +1714,22 @@ class DeleteVpcEndpointResponse(TypedDict, total=False): class DescribeDomainAutoTunesRequest(ServiceRequest): DomainName: DomainName - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class DescribeDomainAutoTunesResponse(TypedDict, total=False): - AutoTunes: Optional[AutoTuneList] - NextToken: Optional[NextToken] + AutoTunes: AutoTuneList | None + NextToken: NextToken | None class DescribeDomainChangeProgressRequest(ServiceRequest): DomainName: DomainName - ChangeId: Optional[GUID] + ChangeId: GUID | None class DescribeDomainChangeProgressResponse(TypedDict, total=False): - ChangeProgressStatus: Optional[ChangeProgressStatusDetails] + ChangeProgressStatus: ChangeProgressStatusDetails | None class DescribeDomainConfigRequest(ServiceRequest): @@ -1669,13 +1737,13 @@ class DescribeDomainConfigRequest(ServiceRequest): class SoftwareUpdateOptionsStatus(TypedDict, total=False): - Options: Optional[SoftwareUpdateOptions] - Status: Optional[OptionStatus] + Options: SoftwareUpdateOptions | None + Status: OptionStatus | None class OffPeakWindowOptionsStatus(TypedDict, total=False): - Options: Optional[OffPeakWindowOptions] - Status: Optional[OptionStatus] + Options: OffPeakWindowOptions | None + Status: OptionStatus | None class IdentityCenterOptionsStatus(TypedDict, total=False): @@ -1689,8 +1757,8 @@ class DomainEndpointOptionsStatus(TypedDict, total=False): class LogPublishingOptionsStatus(TypedDict, total=False): - Options: Optional[LogPublishingOptions] - Status: Optional[OptionStatus] + Options: LogPublishingOptions | None + Status: OptionStatus | None class NodeToNodeEncryptionOptionsStatus(TypedDict, total=False): @@ -1729,27 +1797,27 @@ class VersionStatus(TypedDict, total=False): class DomainConfig(TypedDict, total=False): - EngineVersion: Optional[VersionStatus] - ClusterConfig: Optional[ClusterConfigStatus] - EBSOptions: Optional[EBSOptionsStatus] - AccessPolicies: Optional[AccessPoliciesStatus] - IPAddressType: Optional[IPAddressTypeStatus] - SnapshotOptions: Optional[SnapshotOptionsStatus] - VPCOptions: Optional[VPCDerivedInfoStatus] - CognitoOptions: Optional[CognitoOptionsStatus] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptionsStatus] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptionsStatus] - AdvancedOptions: Optional[AdvancedOptionsStatus] - LogPublishingOptions: Optional[LogPublishingOptionsStatus] - DomainEndpointOptions: Optional[DomainEndpointOptionsStatus] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsStatus] - IdentityCenterOptions: Optional[IdentityCenterOptionsStatus] - AutoTuneOptions: Optional[AutoTuneOptionsStatus] - ChangeProgressDetails: Optional[ChangeProgressDetails] - OffPeakWindowOptions: Optional[OffPeakWindowOptionsStatus] - SoftwareUpdateOptions: Optional[SoftwareUpdateOptionsStatus] - ModifyingProperties: Optional[ModifyingPropertiesList] - AIMLOptions: Optional[AIMLOptionsStatus] + EngineVersion: VersionStatus | None + ClusterConfig: ClusterConfigStatus | None + EBSOptions: EBSOptionsStatus | None + AccessPolicies: AccessPoliciesStatus | None + IPAddressType: IPAddressTypeStatus | None + SnapshotOptions: SnapshotOptionsStatus | None + VPCOptions: VPCDerivedInfoStatus | None + CognitoOptions: CognitoOptionsStatus | None + EncryptionAtRestOptions: EncryptionAtRestOptionsStatus | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptionsStatus | None + AdvancedOptions: AdvancedOptionsStatus | None + LogPublishingOptions: LogPublishingOptionsStatus | None + DomainEndpointOptions: DomainEndpointOptionsStatus | None + AdvancedSecurityOptions: AdvancedSecurityOptionsStatus | None + IdentityCenterOptions: IdentityCenterOptionsStatus | None + AutoTuneOptions: AutoTuneOptionsStatus | None + ChangeProgressDetails: ChangeProgressDetails | None + OffPeakWindowOptions: OffPeakWindowOptionsStatus | None + SoftwareUpdateOptions: SoftwareUpdateOptionsStatus | None + ModifyingProperties: ModifyingPropertiesList | None + AIMLOptions: AIMLOptionsStatus | None class DescribeDomainConfigResponse(TypedDict, total=False): @@ -1761,26 +1829,26 @@ class DescribeDomainHealthRequest(ServiceRequest): class EnvironmentInfo(TypedDict, total=False): - AvailabilityZoneInformation: Optional[AvailabilityZoneInfoList] + AvailabilityZoneInformation: AvailabilityZoneInfoList | None -EnvironmentInfoList = List[EnvironmentInfo] +EnvironmentInfoList = list[EnvironmentInfo] class DescribeDomainHealthResponse(TypedDict, total=False): - DomainState: Optional[DomainState] - AvailabilityZoneCount: Optional[NumberOfAZs] - ActiveAvailabilityZoneCount: Optional[NumberOfAZs] - StandByAvailabilityZoneCount: Optional[NumberOfAZs] - DataNodeCount: Optional[NumberOfNodes] - DedicatedMaster: Optional[Boolean] - MasterEligibleNodeCount: Optional[NumberOfNodes] - WarmNodeCount: Optional[NumberOfNodes] - MasterNode: Optional[MasterNodeStatus] - ClusterHealth: Optional[DomainHealth] - TotalShards: Optional[NumberOfShards] - TotalUnAssignedShards: Optional[NumberOfShards] - EnvironmentInformation: Optional[EnvironmentInfoList] + DomainState: DomainState | None + AvailabilityZoneCount: NumberOfAZs | None + ActiveAvailabilityZoneCount: NumberOfAZs | None + StandByAvailabilityZoneCount: NumberOfAZs | None + DataNodeCount: NumberOfNodes | None + DedicatedMaster: Boolean | None + MasterEligibleNodeCount: NumberOfNodes | None + WarmNodeCount: NumberOfNodes | None + MasterNode: MasterNodeStatus | None + ClusterHealth: DomainHealth | None + TotalShards: NumberOfShards | None + TotalUnAssignedShards: NumberOfShards | None + EnvironmentInformation: EnvironmentInfoList | None class DescribeDomainNodesRequest(ServiceRequest): @@ -1788,21 +1856,21 @@ class DescribeDomainNodesRequest(ServiceRequest): class DomainNodesStatus(TypedDict, total=False): - NodeId: Optional[NodeId] - NodeType: Optional[NodeType] - AvailabilityZone: Optional[AvailabilityZone] - InstanceType: Optional[OpenSearchPartitionInstanceType] - NodeStatus: Optional[NodeStatus] - StorageType: Optional[StorageTypeName] - StorageVolumeType: Optional[VolumeType] - StorageSize: Optional[VolumeSize] + NodeId: NodeId | None + NodeType: NodeType | None + AvailabilityZone: AvailabilityZone | None + InstanceType: OpenSearchPartitionInstanceType | None + NodeStatus: NodeStatus | None + StorageType: StorageTypeName | None + StorageVolumeType: VolumeType | None + StorageSize: VolumeSize | None -DomainNodesStatusList = List[DomainNodesStatus] +DomainNodesStatusList = list[DomainNodesStatus] class DescribeDomainNodesResponse(TypedDict, total=False): - DomainNodesStatusList: Optional[DomainNodesStatusList] + DomainNodesStatusList: DomainNodesStatusList | None class DescribeDomainRequest(ServiceRequest): @@ -1813,14 +1881,14 @@ class DescribeDomainResponse(TypedDict, total=False): DomainStatus: DomainStatus -DomainNameList = List[DomainName] +DomainNameList = list[DomainName] class DescribeDomainsRequest(ServiceRequest): DomainNames: DomainNameList -DomainStatusList = List[DomainStatus] +DomainStatusList = list[DomainStatus] class DescribeDomainsResponse(TypedDict, total=False): @@ -1829,21 +1897,21 @@ class DescribeDomainsResponse(TypedDict, total=False): class DescribeDryRunProgressRequest(ServiceRequest): DomainName: DomainName - DryRunId: Optional[GUID] - LoadDryRunConfig: Optional[Boolean] + DryRunId: GUID | None + LoadDryRunConfig: Boolean | None class DryRunResults(TypedDict, total=False): - DeploymentType: Optional[DeploymentType] - Message: Optional[Message] + DeploymentType: DeploymentType | None + Message: Message | None class ValidationFailure(TypedDict, total=False): - Code: Optional[String] - Message: Optional[String] + Code: String | None + Message: String | None -ValidationFailures = List[ValidationFailure] +ValidationFailures = list[ValidationFailure] class DryRunProgressStatus(TypedDict, total=False): @@ -1851,189 +1919,189 @@ class DryRunProgressStatus(TypedDict, total=False): DryRunStatus: String CreationDate: String UpdateDate: String - ValidationFailures: Optional[ValidationFailures] + ValidationFailures: ValidationFailures | None class DescribeDryRunProgressResponse(TypedDict, total=False): - DryRunProgressStatus: Optional[DryRunProgressStatus] - DryRunConfig: Optional[DomainStatus] - DryRunResults: Optional[DryRunResults] + DryRunProgressStatus: DryRunProgressStatus | None + DryRunConfig: DomainStatus | None + DryRunResults: DryRunResults | None -ValueStringList = List[NonEmptyString] +ValueStringList = list[NonEmptyString] class Filter(TypedDict, total=False): - Name: Optional[NonEmptyString] - Values: Optional[ValueStringList] + Name: NonEmptyString | None + Values: ValueStringList | None -FilterList = List[Filter] +FilterList = list[Filter] class DescribeInboundConnectionsRequest(ServiceRequest): - Filters: Optional[FilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: FilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None -InboundConnections = List[InboundConnection] +InboundConnections = list[InboundConnection] class DescribeInboundConnectionsResponse(TypedDict, total=False): - Connections: Optional[InboundConnections] - NextToken: Optional[NextToken] + Connections: InboundConnections | None + NextToken: NextToken | None class DescribeInstanceTypeLimitsRequest(ServiceRequest): - DomainName: Optional[DomainName] + DomainName: DomainName | None InstanceType: OpenSearchPartitionInstanceType EngineVersion: VersionString class InstanceCountLimits(TypedDict, total=False): - MinimumInstanceCount: Optional[MinimumInstanceCount] - MaximumInstanceCount: Optional[MaximumInstanceCount] + MinimumInstanceCount: MinimumInstanceCount | None + MaximumInstanceCount: MaximumInstanceCount | None class InstanceLimits(TypedDict, total=False): - InstanceCountLimits: Optional[InstanceCountLimits] + InstanceCountLimits: InstanceCountLimits | None class StorageTypeLimit(TypedDict, total=False): - LimitName: Optional[LimitName] - LimitValues: Optional[LimitValueList] + LimitName: LimitName | None + LimitValues: LimitValueList | None -StorageTypeLimitList = List[StorageTypeLimit] +StorageTypeLimitList = list[StorageTypeLimit] class StorageType(TypedDict, total=False): - StorageTypeName: Optional[StorageTypeName] - StorageSubTypeName: Optional[StorageSubTypeName] - StorageTypeLimits: Optional[StorageTypeLimitList] + StorageTypeName: StorageTypeName | None + StorageSubTypeName: StorageSubTypeName | None + StorageTypeLimits: StorageTypeLimitList | None -StorageTypeList = List[StorageType] +StorageTypeList = list[StorageType] class Limits(TypedDict, total=False): - StorageTypes: Optional[StorageTypeList] - InstanceLimits: Optional[InstanceLimits] - AdditionalLimits: Optional[AdditionalLimitList] + StorageTypes: StorageTypeList | None + InstanceLimits: InstanceLimits | None + AdditionalLimits: AdditionalLimitList | None -LimitsByRole = Dict[InstanceRole, Limits] +LimitsByRole = dict[InstanceRole, Limits] class DescribeInstanceTypeLimitsResponse(TypedDict, total=False): - LimitsByRole: Optional[LimitsByRole] + LimitsByRole: LimitsByRole | None class DescribeOutboundConnectionsRequest(ServiceRequest): - Filters: Optional[FilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: FilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None -OutboundConnections = List[OutboundConnection] +OutboundConnections = list[OutboundConnection] class DescribeOutboundConnectionsResponse(TypedDict, total=False): - Connections: Optional[OutboundConnections] - NextToken: Optional[NextToken] + Connections: OutboundConnections | None + NextToken: NextToken | None -DescribePackagesFilterValues = List[DescribePackagesFilterValue] +DescribePackagesFilterValues = list[DescribePackagesFilterValue] class DescribePackagesFilter(TypedDict, total=False): - Name: Optional[DescribePackagesFilterName] - Value: Optional[DescribePackagesFilterValues] + Name: DescribePackagesFilterName | None + Value: DescribePackagesFilterValues | None -DescribePackagesFilterList = List[DescribePackagesFilter] +DescribePackagesFilterList = list[DescribePackagesFilter] class DescribePackagesRequest(ServiceRequest): - Filters: Optional[DescribePackagesFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: DescribePackagesFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None -PackageDetailsList = List[PackageDetails] +PackageDetailsList = list[PackageDetails] class DescribePackagesResponse(TypedDict, total=False): - PackageDetailsList: Optional[PackageDetailsList] - NextToken: Optional[String] + PackageDetailsList: PackageDetailsList | None + NextToken: String | None class DescribeReservedInstanceOfferingsRequest(ServiceRequest): - ReservedInstanceOfferingId: Optional[GUID] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + ReservedInstanceOfferingId: GUID | None + MaxResults: MaxResults | None + NextToken: NextToken | None class RecurringCharge(TypedDict, total=False): - RecurringChargeAmount: Optional[Double] - RecurringChargeFrequency: Optional[String] + RecurringChargeAmount: Double | None + RecurringChargeFrequency: String | None -RecurringChargeList = List[RecurringCharge] +RecurringChargeList = list[RecurringCharge] class ReservedInstanceOffering(TypedDict, total=False): - ReservedInstanceOfferingId: Optional[GUID] - InstanceType: Optional[OpenSearchPartitionInstanceType] - Duration: Optional[Integer] - FixedPrice: Optional[Double] - UsagePrice: Optional[Double] - CurrencyCode: Optional[String] - PaymentOption: Optional[ReservedInstancePaymentOption] - RecurringCharges: Optional[RecurringChargeList] + ReservedInstanceOfferingId: GUID | None + InstanceType: OpenSearchPartitionInstanceType | None + Duration: Integer | None + FixedPrice: Double | None + UsagePrice: Double | None + CurrencyCode: String | None + PaymentOption: ReservedInstancePaymentOption | None + RecurringCharges: RecurringChargeList | None -ReservedInstanceOfferingList = List[ReservedInstanceOffering] +ReservedInstanceOfferingList = list[ReservedInstanceOffering] class DescribeReservedInstanceOfferingsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - ReservedInstanceOfferings: Optional[ReservedInstanceOfferingList] + NextToken: NextToken | None + ReservedInstanceOfferings: ReservedInstanceOfferingList | None class DescribeReservedInstancesRequest(ServiceRequest): - ReservedInstanceId: Optional[GUID] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + ReservedInstanceId: GUID | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ReservedInstance(TypedDict, total=False): - ReservationName: Optional[ReservationToken] - ReservedInstanceId: Optional[GUID] - BillingSubscriptionId: Optional[Long] - ReservedInstanceOfferingId: Optional[String] - InstanceType: Optional[OpenSearchPartitionInstanceType] - StartTime: Optional[UpdateTimestamp] - Duration: Optional[Integer] - FixedPrice: Optional[Double] - UsagePrice: Optional[Double] - CurrencyCode: Optional[String] - InstanceCount: Optional[Integer] - State: Optional[String] - PaymentOption: Optional[ReservedInstancePaymentOption] - RecurringCharges: Optional[RecurringChargeList] + ReservationName: ReservationToken | None + ReservedInstanceId: GUID | None + BillingSubscriptionId: Long | None + ReservedInstanceOfferingId: String | None + InstanceType: OpenSearchPartitionInstanceType | None + StartTime: UpdateTimestamp | None + Duration: Integer | None + FixedPrice: Double | None + UsagePrice: Double | None + CurrencyCode: String | None + InstanceCount: Integer | None + State: String | None + PaymentOption: ReservedInstancePaymentOption | None + RecurringCharges: RecurringChargeList | None -ReservedInstanceList = List[ReservedInstance] +ReservedInstanceList = list[ReservedInstance] class DescribeReservedInstancesResponse(TypedDict, total=False): - NextToken: Optional[String] - ReservedInstances: Optional[ReservedInstanceList] + NextToken: String | None + ReservedInstances: ReservedInstanceList | None -VpcEndpointIdList = List[VpcEndpointId] +VpcEndpointIdList = list[VpcEndpointId] class DescribeVpcEndpointsRequest(ServiceRequest): @@ -2041,13 +2109,13 @@ class DescribeVpcEndpointsRequest(ServiceRequest): class VpcEndpointError(TypedDict, total=False): - VpcEndpointId: Optional[VpcEndpointId] - ErrorCode: Optional[VpcEndpointErrorCode] - ErrorMessage: Optional[String] + VpcEndpointId: VpcEndpointId | None + ErrorCode: VpcEndpointErrorCode | None + ErrorMessage: String | None -VpcEndpointErrorList = List[VpcEndpointError] -VpcEndpoints = List[VpcEndpoint] +VpcEndpointErrorList = list[VpcEndpointError] +VpcEndpoints = list[VpcEndpoint] class DescribeVpcEndpointsResponse(TypedDict, total=False): @@ -2056,15 +2124,15 @@ class DescribeVpcEndpointsResponse(TypedDict, total=False): class DirectQueryDataSource(TypedDict, total=False): - DataSourceName: Optional[DirectQueryDataSourceName] - DataSourceType: Optional[DirectQueryDataSourceType] - Description: Optional[DirectQueryDataSourceDescription] - OpenSearchArns: Optional[DirectQueryOpenSearchARNList] - DataSourceArn: Optional[String] - TagList: Optional[TagList] + DataSourceName: DirectQueryDataSourceName | None + DataSourceType: DirectQueryDataSourceType | None + Description: DirectQueryDataSourceDescription | None + OpenSearchArns: DirectQueryOpenSearchARNList | None + DataSourceArn: String | None + TagList: TagList | None -DirectQueryDataSourceList = List[DirectQueryDataSource] +DirectQueryDataSourceList = list[DirectQueryDataSource] class DissociatePackageRequest(ServiceRequest): @@ -2073,7 +2141,7 @@ class DissociatePackageRequest(ServiceRequest): class DissociatePackageResponse(TypedDict, total=False): - DomainPackageDetails: Optional[DomainPackageDetails] + DomainPackageDetails: DomainPackageDetails | None class DissociatePackagesRequest(ServiceRequest): @@ -2082,29 +2150,29 @@ class DissociatePackagesRequest(ServiceRequest): class DissociatePackagesResponse(TypedDict, total=False): - DomainPackageDetailsList: Optional[DomainPackageDetailsList] + DomainPackageDetailsList: DomainPackageDetailsList | None class DomainInfo(TypedDict, total=False): - DomainName: Optional[DomainName] - EngineType: Optional[EngineType] + DomainName: DomainName | None + EngineType: EngineType | None -DomainInfoList = List[DomainInfo] +DomainInfoList = list[DomainInfo] class DomainMaintenanceDetails(TypedDict, total=False): - MaintenanceId: Optional[RequestId] - DomainName: Optional[DomainName] - Action: Optional[MaintenanceType] - NodeId: Optional[NodeId] - Status: Optional[MaintenanceStatus] - StatusMessage: Optional[MaintenanceStatusMessage] - CreatedAt: Optional[UpdateTimestamp] - UpdatedAt: Optional[UpdateTimestamp] + MaintenanceId: RequestId | None + DomainName: DomainName | None + Action: MaintenanceType | None + NodeId: NodeId | None + Status: MaintenanceStatus | None + StatusMessage: MaintenanceStatusMessage | None + CreatedAt: UpdateTimestamp | None + UpdatedAt: UpdateTimestamp | None -DomainMaintenanceList = List[DomainMaintenanceDetails] +DomainMaintenanceList = list[DomainMaintenanceDetails] class GetApplicationRequest(ServiceRequest): @@ -2112,24 +2180,25 @@ class GetApplicationRequest(ServiceRequest): class GetApplicationResponse(TypedDict, total=False): - id: Optional[Id] - arn: Optional[ARN] - name: Optional[ApplicationName] - endpoint: Optional[String] - status: Optional[ApplicationStatus] - iamIdentityCenterOptions: Optional[IamIdentityCenterOptions] - dataSources: Optional[DataSources] - appConfigs: Optional[AppConfigs] - createdAt: Optional[Timestamp] - lastUpdatedAt: Optional[Timestamp] + id: Id | None + arn: ARN | None + name: ApplicationName | None + endpoint: String | None + status: ApplicationStatus | None + iamIdentityCenterOptions: IamIdentityCenterOptions | None + dataSources: DataSources | None + appConfigs: AppConfigs | None + createdAt: Timestamp | None + lastUpdatedAt: Timestamp | None + kmsKeyArn: KmsKeyArn | None class GetCompatibleVersionsRequest(ServiceRequest): - DomainName: Optional[DomainName] + DomainName: DomainName | None class GetCompatibleVersionsResponse(TypedDict, total=False): - CompatibleVersions: Optional[CompatibleVersionsList] + CompatibleVersions: CompatibleVersionsList | None class GetDataSourceRequest(ServiceRequest): @@ -2138,10 +2207,18 @@ class GetDataSourceRequest(ServiceRequest): class GetDataSourceResponse(TypedDict, total=False): - DataSourceType: Optional[DataSourceType] - Name: Optional[DataSourceName] - Description: Optional[DataSourceDescription] - Status: Optional[DataSourceStatus] + DataSourceType: DataSourceType | None + Name: DataSourceName | None + Description: DataSourceDescription | None + Status: DataSourceStatus | None + + +class GetDefaultApplicationSettingRequest(ServiceRequest): + pass + + +class GetDefaultApplicationSettingResponse(TypedDict, total=False): + applicationArn: ARN | None class GetDirectQueryDataSourceRequest(ServiceRequest): @@ -2149,11 +2226,11 @@ class GetDirectQueryDataSourceRequest(ServiceRequest): class GetDirectQueryDataSourceResponse(TypedDict, total=False): - DataSourceName: Optional[DirectQueryDataSourceName] - DataSourceType: Optional[DirectQueryDataSourceType] - Description: Optional[DirectQueryDataSourceDescription] - OpenSearchArns: Optional[DirectQueryOpenSearchARNList] - DataSourceArn: Optional[String] + DataSourceName: DirectQueryDataSourceName | None + DataSourceType: DirectQueryDataSourceType | None + Description: DirectQueryDataSourceDescription | None + OpenSearchArns: DirectQueryOpenSearchARNList | None + DataSourceArn: String | None class GetDomainMaintenanceStatusRequest(ServiceRequest): @@ -2162,70 +2239,79 @@ class GetDomainMaintenanceStatusRequest(ServiceRequest): class GetDomainMaintenanceStatusResponse(TypedDict, total=False): - Status: Optional[MaintenanceStatus] - StatusMessage: Optional[MaintenanceStatusMessage] - NodeId: Optional[NodeId] - Action: Optional[MaintenanceType] - CreatedAt: Optional[UpdateTimestamp] - UpdatedAt: Optional[UpdateTimestamp] + Status: MaintenanceStatus | None + StatusMessage: MaintenanceStatusMessage | None + NodeId: NodeId | None + Action: MaintenanceType | None + CreatedAt: UpdateTimestamp | None + UpdatedAt: UpdateTimestamp | None + + +class GetIndexRequest(ServiceRequest): + DomainName: DomainName + IndexName: IndexName + + +class GetIndexResponse(TypedDict, total=False): + IndexSchema: IndexSchema class GetPackageVersionHistoryRequest(ServiceRequest): PackageID: PackageID - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class PackageVersionHistory(TypedDict, total=False): - PackageVersion: Optional[PackageVersion] - CommitMessage: Optional[CommitMessage] - CreatedAt: Optional[CreatedAt] - PluginProperties: Optional[PluginProperties] - PackageConfiguration: Optional[PackageConfiguration] + PackageVersion: PackageVersion | None + CommitMessage: CommitMessage | None + CreatedAt: CreatedAt | None + PluginProperties: PluginProperties | None + PackageConfiguration: PackageConfiguration | None -PackageVersionHistoryList = List[PackageVersionHistory] +PackageVersionHistoryList = list[PackageVersionHistory] class GetPackageVersionHistoryResponse(TypedDict, total=False): - PackageID: Optional[PackageID] - PackageVersionHistoryList: Optional[PackageVersionHistoryList] - NextToken: Optional[String] + PackageID: PackageID | None + PackageVersionHistoryList: PackageVersionHistoryList | None + NextToken: String | None class GetUpgradeHistoryRequest(ServiceRequest): DomainName: DomainName - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None -Issues = List[Issue] +Issues = list[Issue] class UpgradeStepItem(TypedDict, total=False): - UpgradeStep: Optional[UpgradeStep] - UpgradeStepStatus: Optional[UpgradeStatus] - Issues: Optional[Issues] - ProgressPercent: Optional[Double] + UpgradeStep: UpgradeStep | None + UpgradeStepStatus: UpgradeStatus | None + Issues: Issues | None + ProgressPercent: Double | None -UpgradeStepsList = List[UpgradeStepItem] +UpgradeStepsList = list[UpgradeStepItem] StartTimestamp = datetime class UpgradeHistory(TypedDict, total=False): - UpgradeName: Optional[UpgradeName] - StartTimestamp: Optional[StartTimestamp] - UpgradeStatus: Optional[UpgradeStatus] - StepsList: Optional[UpgradeStepsList] + UpgradeName: UpgradeName | None + StartTimestamp: StartTimestamp | None + UpgradeStatus: UpgradeStatus | None + StepsList: UpgradeStepsList | None -UpgradeHistoryList = List[UpgradeHistory] +UpgradeHistoryList = list[UpgradeHistory] class GetUpgradeHistoryResponse(TypedDict, total=False): - UpgradeHistories: Optional[UpgradeHistoryList] - NextToken: Optional[String] + UpgradeHistories: UpgradeHistoryList | None + NextToken: String | None class GetUpgradeStatusRequest(ServiceRequest): @@ -2233,37 +2319,37 @@ class GetUpgradeStatusRequest(ServiceRequest): class GetUpgradeStatusResponse(TypedDict, total=False): - UpgradeStep: Optional[UpgradeStep] - StepStatus: Optional[UpgradeStatus] - UpgradeName: Optional[UpgradeName] + UpgradeStep: UpgradeStep | None + StepStatus: UpgradeStatus | None + UpgradeName: UpgradeName | None -InstanceRoleList = List[InstanceRole] +InstanceRoleList = list[InstanceRole] class InstanceTypeDetails(TypedDict, total=False): - InstanceType: Optional[OpenSearchPartitionInstanceType] - EncryptionEnabled: Optional[Boolean] - CognitoEnabled: Optional[Boolean] - AppLogsEnabled: Optional[Boolean] - AdvancedSecurityEnabled: Optional[Boolean] - WarmEnabled: Optional[Boolean] - InstanceRole: Optional[InstanceRoleList] - AvailabilityZones: Optional[AvailabilityZoneList] + InstanceType: OpenSearchPartitionInstanceType | None + EncryptionEnabled: Boolean | None + CognitoEnabled: Boolean | None + AppLogsEnabled: Boolean | None + AdvancedSecurityEnabled: Boolean | None + WarmEnabled: Boolean | None + InstanceRole: InstanceRoleList | None + AvailabilityZones: AvailabilityZoneList | None -InstanceTypeDetailsList = List[InstanceTypeDetails] +InstanceTypeDetailsList = list[InstanceTypeDetails] class ListApplicationsRequest(ServiceRequest): - nextToken: Optional[NextToken] - statuses: Optional[ApplicationStatuses] - maxResults: Optional[MaxResults] + nextToken: NextToken | None + statuses: ApplicationStatuses | None + maxResults: MaxResults | None class ListApplicationsResponse(TypedDict, total=False): - ApplicationSummaries: Optional[ApplicationSummaries] - nextToken: Optional[NextToken] + ApplicationSummaries: ApplicationSummaries | None + nextToken: NextToken | None class ListDataSourcesRequest(ServiceRequest): @@ -2271,79 +2357,79 @@ class ListDataSourcesRequest(ServiceRequest): class ListDataSourcesResponse(TypedDict, total=False): - DataSources: Optional[DataSourceList] + DataSources: DataSourceList | None class ListDirectQueryDataSourcesRequest(ServiceRequest): - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListDirectQueryDataSourcesResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - DirectQueryDataSources: Optional[DirectQueryDataSourceList] + NextToken: NextToken | None + DirectQueryDataSources: DirectQueryDataSourceList | None class ListDomainMaintenancesRequest(ServiceRequest): DomainName: DomainName - Action: Optional[MaintenanceType] - Status: Optional[MaintenanceStatus] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Action: MaintenanceType | None + Status: MaintenanceStatus | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListDomainMaintenancesResponse(TypedDict, total=False): - DomainMaintenances: Optional[DomainMaintenanceList] - NextToken: Optional[NextToken] + DomainMaintenances: DomainMaintenanceList | None + NextToken: NextToken | None class ListDomainNamesRequest(ServiceRequest): - EngineType: Optional[EngineType] + EngineType: EngineType | None class ListDomainNamesResponse(TypedDict, total=False): - DomainNames: Optional[DomainInfoList] + DomainNames: DomainInfoList | None class ListDomainsForPackageRequest(ServiceRequest): PackageID: PackageID - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListDomainsForPackageResponse(TypedDict, total=False): - DomainPackageDetailsList: Optional[DomainPackageDetailsList] - NextToken: Optional[String] + DomainPackageDetailsList: DomainPackageDetailsList | None + NextToken: String | None class ListInstanceTypeDetailsRequest(ServiceRequest): EngineVersion: VersionString - DomainName: Optional[DomainName] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - RetrieveAZs: Optional[Boolean] - InstanceType: Optional[InstanceTypeString] + DomainName: DomainName | None + MaxResults: MaxResults | None + NextToken: NextToken | None + RetrieveAZs: Boolean | None + InstanceType: InstanceTypeString | None class ListInstanceTypeDetailsResponse(TypedDict, total=False): - InstanceTypeDetails: Optional[InstanceTypeDetailsList] - NextToken: Optional[NextToken] + InstanceTypeDetails: InstanceTypeDetailsList | None + NextToken: NextToken | None class ListPackagesForDomainRequest(ServiceRequest): DomainName: DomainName - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListPackagesForDomainResponse(TypedDict, total=False): - DomainPackageDetailsList: Optional[DomainPackageDetailsList] - NextToken: Optional[String] + DomainPackageDetailsList: DomainPackageDetailsList | None + NextToken: String | None class ListScheduledActionsRequest(ServiceRequest): DomainName: DomainName - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ScheduledAction(TypedDict, total=False): @@ -2351,19 +2437,19 @@ class ScheduledAction(TypedDict, total=False): Type: ActionType Severity: ActionSeverity ScheduledTime: Long - Description: Optional[String] - ScheduledBy: Optional[ScheduledBy] - Status: Optional[ActionStatus] - Mandatory: Optional[Boolean] - Cancellable: Optional[Boolean] + Description: String | None + ScheduledBy: ScheduledBy | None + Status: ActionStatus | None + Mandatory: Boolean | None + Cancellable: Boolean | None -ScheduledActionsList = List[ScheduledAction] +ScheduledActionsList = list[ScheduledAction] class ListScheduledActionsResponse(TypedDict, total=False): - ScheduledActions: Optional[ScheduledActionsList] - NextToken: Optional[NextToken] + ScheduledActions: ScheduledActionsList | None + NextToken: NextToken | None class ListTagsRequest(ServiceRequest): @@ -2371,22 +2457,22 @@ class ListTagsRequest(ServiceRequest): class ListTagsResponse(TypedDict, total=False): - TagList: Optional[TagList] + TagList: TagList | None class ListVersionsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListVersionsResponse(TypedDict, total=False): - Versions: Optional[VersionList] - NextToken: Optional[NextToken] + Versions: VersionList | None + NextToken: NextToken | None class ListVpcEndpointAccessRequest(ServiceRequest): DomainName: DomainName - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListVpcEndpointAccessResponse(TypedDict, total=False): @@ -2396,10 +2482,10 @@ class ListVpcEndpointAccessResponse(TypedDict, total=False): class ListVpcEndpointsForDomainRequest(ServiceRequest): DomainName: DomainName - NextToken: Optional[NextToken] + NextToken: NextToken | None -VpcEndpointSummaryList = List[VpcEndpointSummary] +VpcEndpointSummaryList = list[VpcEndpointSummary] class ListVpcEndpointsForDomainResponse(TypedDict, total=False): @@ -2408,7 +2494,7 @@ class ListVpcEndpointsForDomainResponse(TypedDict, total=False): class ListVpcEndpointsRequest(ServiceRequest): - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListVpcEndpointsResponse(TypedDict, total=False): @@ -2419,12 +2505,21 @@ class ListVpcEndpointsResponse(TypedDict, total=False): class PurchaseReservedInstanceOfferingRequest(ServiceRequest): ReservedInstanceOfferingId: GUID ReservationName: ReservationToken - InstanceCount: Optional[InstanceCount] + InstanceCount: InstanceCount | None class PurchaseReservedInstanceOfferingResponse(TypedDict, total=False): - ReservedInstanceId: Optional[GUID] - ReservationName: Optional[ReservationToken] + ReservedInstanceId: GUID | None + ReservationName: ReservationToken | None + + +class PutDefaultApplicationSettingRequest(ServiceRequest): + applicationArn: ARN + setAsDefault: Boolean + + +class PutDefaultApplicationSettingResponse(TypedDict, total=False): + applicationArn: ARN | None class RejectInboundConnectionRequest(ServiceRequest): @@ -2432,7 +2527,7 @@ class RejectInboundConnectionRequest(ServiceRequest): class RejectInboundConnectionResponse(TypedDict, total=False): - Connection: Optional[InboundConnection] + Connection: InboundConnection | None class RemoveTagsRequest(ServiceRequest): @@ -2442,8 +2537,8 @@ class RemoveTagsRequest(ServiceRequest): class RevokeVpcEndpointAccessRequest(ServiceRequest): DomainName: DomainName - Account: Optional[AWSAccount] - Service: Optional[AWSServicePrincipal] + Account: AWSAccount | None + Service: AWSServicePrincipal | None class RevokeVpcEndpointAccessResponse(TypedDict, total=False): @@ -2453,104 +2548,114 @@ class RevokeVpcEndpointAccessResponse(TypedDict, total=False): class StartDomainMaintenanceRequest(ServiceRequest): DomainName: DomainName Action: MaintenanceType - NodeId: Optional[NodeId] + NodeId: NodeId | None class StartDomainMaintenanceResponse(TypedDict, total=False): - MaintenanceId: Optional[RequestId] + MaintenanceId: RequestId | None class StartServiceSoftwareUpdateRequest(ServiceRequest): DomainName: DomainName - ScheduleAt: Optional[ScheduleAt] - DesiredStartTime: Optional[Long] + ScheduleAt: ScheduleAt | None + DesiredStartTime: Long | None class StartServiceSoftwareUpdateResponse(TypedDict, total=False): - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] + ServiceSoftwareOptions: ServiceSoftwareOptions | None class UpdateApplicationRequest(ServiceRequest): id: Id - dataSources: Optional[DataSources] - appConfigs: Optional[AppConfigs] + dataSources: DataSources | None + appConfigs: AppConfigs | None class UpdateApplicationResponse(TypedDict, total=False): - id: Optional[Id] - name: Optional[ApplicationName] - arn: Optional[ARN] - dataSources: Optional[DataSources] - iamIdentityCenterOptions: Optional[IamIdentityCenterOptions] - appConfigs: Optional[AppConfigs] - createdAt: Optional[Timestamp] - lastUpdatedAt: Optional[Timestamp] + id: Id | None + name: ApplicationName | None + arn: ARN | None + dataSources: DataSources | None + iamIdentityCenterOptions: IamIdentityCenterOptions | None + appConfigs: AppConfigs | None + createdAt: Timestamp | None + lastUpdatedAt: Timestamp | None class UpdateDataSourceRequest(ServiceRequest): DomainName: DomainName Name: DataSourceName DataSourceType: DataSourceType - Description: Optional[DataSourceDescription] - Status: Optional[DataSourceStatus] + Description: DataSourceDescription | None + Status: DataSourceStatus | None class UpdateDataSourceResponse(TypedDict, total=False): - Message: Optional[String] + Message: String | None class UpdateDirectQueryDataSourceRequest(ServiceRequest): DataSourceName: DirectQueryDataSourceName DataSourceType: DirectQueryDataSourceType - Description: Optional[DirectQueryDataSourceDescription] + Description: DirectQueryDataSourceDescription | None OpenSearchArns: DirectQueryOpenSearchARNList class UpdateDirectQueryDataSourceResponse(TypedDict, total=False): - DataSourceArn: Optional[String] + DataSourceArn: String | None class UpdateDomainConfigRequest(ServiceRequest): DomainName: DomainName - ClusterConfig: Optional[ClusterConfig] - EBSOptions: Optional[EBSOptions] - SnapshotOptions: Optional[SnapshotOptions] - VPCOptions: Optional[VPCOptions] - CognitoOptions: Optional[CognitoOptions] - AdvancedOptions: Optional[AdvancedOptions] - AccessPolicies: Optional[PolicyDocument] - IPAddressType: Optional[IPAddressType] - LogPublishingOptions: Optional[LogPublishingOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - DomainEndpointOptions: Optional[DomainEndpointOptions] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - IdentityCenterOptions: Optional[IdentityCenterOptionsInput] - AutoTuneOptions: Optional[AutoTuneOptions] - DryRun: Optional[DryRun] - DryRunMode: Optional[DryRunMode] - OffPeakWindowOptions: Optional[OffPeakWindowOptions] - SoftwareUpdateOptions: Optional[SoftwareUpdateOptions] - AIMLOptions: Optional[AIMLOptionsInput] + ClusterConfig: ClusterConfig | None + EBSOptions: EBSOptions | None + SnapshotOptions: SnapshotOptions | None + VPCOptions: VPCOptions | None + CognitoOptions: CognitoOptions | None + AdvancedOptions: AdvancedOptions | None + AccessPolicies: PolicyDocument | None + IPAddressType: IPAddressType | None + LogPublishingOptions: LogPublishingOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + DomainEndpointOptions: DomainEndpointOptions | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + IdentityCenterOptions: IdentityCenterOptionsInput | None + AutoTuneOptions: AutoTuneOptions | None + DryRun: DryRun | None + DryRunMode: DryRunMode | None + OffPeakWindowOptions: OffPeakWindowOptions | None + SoftwareUpdateOptions: SoftwareUpdateOptions | None + AIMLOptions: AIMLOptionsInput | None class UpdateDomainConfigResponse(TypedDict, total=False): DomainConfig: DomainConfig - DryRunResults: Optional[DryRunResults] - DryRunProgressStatus: Optional[DryRunProgressStatus] + DryRunResults: DryRunResults | None + DryRunProgressStatus: DryRunProgressStatus | None + + +class UpdateIndexRequest(ServiceRequest): + DomainName: DomainName + IndexName: IndexName + IndexSchema: IndexSchema + + +class UpdateIndexResponse(TypedDict, total=False): + Status: IndexStatus class UpdatePackageRequest(ServiceRequest): PackageID: PackageID PackageSource: PackageSource - PackageDescription: Optional[PackageDescription] - CommitMessage: Optional[CommitMessage] - PackageConfiguration: Optional[PackageConfiguration] - PackageEncryptionOptions: Optional[PackageEncryptionOptions] + PackageDescription: PackageDescription | None + CommitMessage: CommitMessage | None + PackageConfiguration: PackageConfiguration | None + PackageEncryptionOptions: PackageEncryptionOptions | None class UpdatePackageResponse(TypedDict, total=False): - PackageDetails: Optional[PackageDetails] + PackageDetails: PackageDetails | None class UpdatePackageScopeRequest(ServiceRequest): @@ -2560,9 +2665,9 @@ class UpdatePackageScopeRequest(ServiceRequest): class UpdatePackageScopeResponse(TypedDict, total=False): - PackageID: Optional[PackageID] - Operation: Optional[PackageScopeOperationEnum] - PackageUserList: Optional[PackageUserList] + PackageID: PackageID | None + Operation: PackageScopeOperationEnum | None + PackageUserList: PackageUserList | None class UpdateScheduledActionRequest(ServiceRequest): @@ -2570,11 +2675,11 @@ class UpdateScheduledActionRequest(ServiceRequest): ActionID: String ActionType: ActionType ScheduleAt: ScheduleAt - DesiredStartTime: Optional[Long] + DesiredStartTime: Long | None class UpdateScheduledActionResponse(TypedDict, total=False): - ScheduledAction: Optional[ScheduledAction] + ScheduledAction: ScheduledAction | None class UpdateVpcEndpointRequest(ServiceRequest): @@ -2589,22 +2694,22 @@ class UpdateVpcEndpointResponse(TypedDict, total=False): class UpgradeDomainRequest(ServiceRequest): DomainName: DomainName TargetVersion: VersionString - PerformCheckOnly: Optional[Boolean] - AdvancedOptions: Optional[AdvancedOptions] + PerformCheckOnly: Boolean | None + AdvancedOptions: AdvancedOptions | None class UpgradeDomainResponse(TypedDict, total=False): - UpgradeId: Optional[String] - DomainName: Optional[DomainName] - TargetVersion: Optional[VersionString] - PerformCheckOnly: Optional[Boolean] - AdvancedOptions: Optional[AdvancedOptions] - ChangeProgressDetails: Optional[ChangeProgressDetails] + UpgradeId: String | None + DomainName: DomainName | None + TargetVersion: VersionString | None + PerformCheckOnly: Boolean | None + AdvancedOptions: AdvancedOptions | None + ChangeProgressDetails: ChangeProgressDetails | None class OpensearchApi: - service = "opensearch" - version = "2021-01-01" + service: str = "opensearch" + version: str = "2021-01-01" @handler("AcceptInboundConnection") def accept_inbound_connection( @@ -2700,6 +2805,7 @@ def create_application( iam_identity_center_options: IamIdentityCenterOptionsInput | None = None, app_configs: AppConfigs | None = None, tag_list: TagList | None = None, + kms_key_arn: KmsKeyArn | None = None, **kwargs, ) -> CreateApplicationResponse: raise NotImplementedError @@ -2733,6 +2839,17 @@ def create_domain( ) -> CreateDomainResponse: raise NotImplementedError + @handler("CreateIndex") + def create_index( + self, + context: RequestContext, + domain_name: DomainName, + index_name: IndexName, + index_schema: IndexSchema, + **kwargs, + ) -> CreateIndexResponse: + raise NotImplementedError + @handler("CreateOutboundConnection") def create_outbound_connection( self, @@ -2803,6 +2920,12 @@ def delete_inbound_connection( ) -> DeleteInboundConnectionResponse: raise NotImplementedError + @handler("DeleteIndex") + def delete_index( + self, context: RequestContext, domain_name: DomainName, index_name: IndexName, **kwargs + ) -> DeleteIndexResponse: + raise NotImplementedError + @handler("DeleteOutboundConnection") def delete_outbound_connection( self, context: RequestContext, connection_id: ConnectionId, **kwargs @@ -2987,6 +3110,12 @@ def get_data_source( ) -> GetDataSourceResponse: raise NotImplementedError + @handler("GetDefaultApplicationSetting") + def get_default_application_setting( + self, context: RequestContext, **kwargs + ) -> GetDefaultApplicationSettingResponse: + raise NotImplementedError + @handler("GetDirectQueryDataSource") def get_direct_query_data_source( self, context: RequestContext, data_source_name: DirectQueryDataSourceName, **kwargs @@ -2999,6 +3128,12 @@ def get_domain_maintenance_status( ) -> GetDomainMaintenanceStatusResponse: raise NotImplementedError + @handler("GetIndex") + def get_index( + self, context: RequestContext, domain_name: DomainName, index_name: IndexName, **kwargs + ) -> GetIndexResponse: + raise NotImplementedError + @handler("GetPackageVersionHistory") def get_package_version_history( self, @@ -3167,6 +3302,12 @@ def purchase_reserved_instance_offering( ) -> PurchaseReservedInstanceOfferingResponse: raise NotImplementedError + @handler("PutDefaultApplicationSetting") + def put_default_application_setting( + self, context: RequestContext, application_arn: ARN, set_as_default: Boolean, **kwargs + ) -> PutDefaultApplicationSettingResponse: + raise NotImplementedError + @handler("RejectInboundConnection") def reject_inbound_connection( self, context: RequestContext, connection_id: ConnectionId, **kwargs @@ -3277,6 +3418,17 @@ def update_domain_config( ) -> UpdateDomainConfigResponse: raise NotImplementedError + @handler("UpdateIndex") + def update_index( + self, + context: RequestContext, + domain_name: DomainName, + index_name: IndexName, + index_schema: IndexSchema, + **kwargs, + ) -> UpdateIndexResponse: + raise NotImplementedError + @handler("UpdatePackage") def update_package( self, diff --git a/localstack-core/localstack/aws/api/pipes/__init__.py b/localstack-core/localstack/aws/api/pipes/__init__.py index 6fe68d846fa23..9ed5cf8467091 100644 --- a/localstack-core/localstack/aws/api/pipes/__init__.py +++ b/localstack-core/localstack/aws/api/pipes/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -243,7 +243,7 @@ class InternalException(ServiceException): code: str = "InternalException" sender_fault: bool = False status_code: int = 500 - retryAfterSeconds: Optional[Integer] + retryAfterSeconds: Integer | None class NotFoundException(ServiceException): @@ -266,9 +266,9 @@ class ThrottlingException(ServiceException): code: str = "ThrottlingException" sender_fault: bool = True status_code: int = 429 - serviceCode: Optional[String] - quotaCode: Optional[String] - retryAfterSeconds: Optional[Integer] + serviceCode: String | None + quotaCode: String | None + retryAfterSeconds: Integer | None class ValidationExceptionField(TypedDict, total=False): @@ -276,28 +276,28 @@ class ValidationExceptionField(TypedDict, total=False): message: ErrorMessage -ValidationExceptionFieldList = List[ValidationExceptionField] +ValidationExceptionFieldList = list[ValidationExceptionField] class ValidationException(ServiceException): code: str = "ValidationException" sender_fault: bool = True status_code: int = 400 - fieldList: Optional[ValidationExceptionFieldList] + fieldList: ValidationExceptionFieldList | None -SecurityGroups = List[SecurityGroup] -Subnets = List[Subnet] +SecurityGroups = list[SecurityGroup] +Subnets = list[Subnet] class AwsVpcConfiguration(TypedDict, total=False): Subnets: Subnets - SecurityGroups: Optional[SecurityGroups] - AssignPublicIp: Optional[AssignPublicIp] + SecurityGroups: SecurityGroups | None + AssignPublicIp: AssignPublicIp | None class BatchArrayProperties(TypedDict, total=False): - Size: Optional[BatchArraySize] + Size: BatchArraySize | None class BatchResourceRequirement(TypedDict, total=False): @@ -305,56 +305,56 @@ class BatchResourceRequirement(TypedDict, total=False): Value: String -BatchResourceRequirementsList = List[BatchResourceRequirement] +BatchResourceRequirementsList = list[BatchResourceRequirement] class BatchEnvironmentVariable(TypedDict, total=False): - Name: Optional[String] - Value: Optional[String] + Name: String | None + Value: String | None -BatchEnvironmentVariableList = List[BatchEnvironmentVariable] -StringList = List[String] +BatchEnvironmentVariableList = list[BatchEnvironmentVariable] +StringList = list[String] class BatchContainerOverrides(TypedDict, total=False): - Command: Optional[StringList] - Environment: Optional[BatchEnvironmentVariableList] - InstanceType: Optional[String] - ResourceRequirements: Optional[BatchResourceRequirementsList] + Command: StringList | None + Environment: BatchEnvironmentVariableList | None + InstanceType: String | None + ResourceRequirements: BatchResourceRequirementsList | None class BatchJobDependency(TypedDict, total=False): - JobId: Optional[String] - Type: Optional[BatchJobDependencyType] + JobId: String | None + Type: BatchJobDependencyType | None -BatchDependsOn = List[BatchJobDependency] -BatchParametersMap = Dict[String, String] +BatchDependsOn = list[BatchJobDependency] +BatchParametersMap = dict[String, String] class BatchRetryStrategy(TypedDict, total=False): - Attempts: Optional[BatchRetryAttempts] + Attempts: BatchRetryAttempts | None class CapacityProviderStrategyItem(TypedDict, total=False): capacityProvider: CapacityProvider - weight: Optional[CapacityProviderStrategyItemWeight] - base: Optional[CapacityProviderStrategyItemBase] + weight: CapacityProviderStrategyItemWeight | None + base: CapacityProviderStrategyItemBase | None -CapacityProviderStrategy = List[CapacityProviderStrategyItem] +CapacityProviderStrategy = list[CapacityProviderStrategyItem] class CloudwatchLogsLogDestination(TypedDict, total=False): - LogGroupArn: Optional[CloudwatchLogGroupArn] + LogGroupArn: CloudwatchLogGroupArn | None class CloudwatchLogsLogDestinationParameters(TypedDict, total=False): LogGroupArn: CloudwatchLogGroupArn -IncludeExecutionData = List[IncludeExecutionDataOption] +IncludeExecutionData = list[IncludeExecutionDataOption] class FirehoseLogDestinationParameters(TypedDict, total=False): @@ -364,19 +364,19 @@ class FirehoseLogDestinationParameters(TypedDict, total=False): class S3LogDestinationParameters(TypedDict, total=False): BucketName: S3LogDestinationParametersBucketNameString BucketOwner: S3LogDestinationParametersBucketOwnerString - OutputFormat: Optional[S3OutputFormat] - Prefix: Optional[S3LogDestinationParametersPrefixString] + OutputFormat: S3OutputFormat | None + Prefix: S3LogDestinationParametersPrefixString | None class PipeLogConfigurationParameters(TypedDict, total=False): - S3LogDestination: Optional[S3LogDestinationParameters] - FirehoseLogDestination: Optional[FirehoseLogDestinationParameters] - CloudwatchLogsLogDestination: Optional[CloudwatchLogsLogDestinationParameters] + S3LogDestination: S3LogDestinationParameters | None + FirehoseLogDestination: FirehoseLogDestinationParameters | None + CloudwatchLogsLogDestination: CloudwatchLogsLogDestinationParameters | None Level: LogLevel - IncludeExecutionData: Optional[IncludeExecutionData] + IncludeExecutionData: IncludeExecutionData | None -TagMap = Dict[TagKey, TagValue] +TagMap = dict[TagKey, TagValue] class MultiMeasureAttributeMapping(TypedDict, total=False): @@ -385,7 +385,7 @@ class MultiMeasureAttributeMapping(TypedDict, total=False): MultiMeasureAttributeName: MultiMeasureAttributeName -MultiMeasureAttributeMappings = List[MultiMeasureAttributeMapping] +MultiMeasureAttributeMappings = list[MultiMeasureAttributeMapping] class MultiMeasureMapping(TypedDict, total=False): @@ -393,7 +393,7 @@ class MultiMeasureMapping(TypedDict, total=False): MultiMeasureAttributeMappings: MultiMeasureAttributeMappings -MultiMeasureMappings = List[MultiMeasureMapping] +MultiMeasureMappings = list[MultiMeasureMapping] class SingleMeasureMapping(TypedDict, total=False): @@ -402,7 +402,7 @@ class SingleMeasureMapping(TypedDict, total=False): MeasureName: MeasureName -SingleMeasureMappings = List[SingleMeasureMapping] +SingleMeasureMappings = list[SingleMeasureMapping] class DimensionMapping(TypedDict, total=False): @@ -411,34 +411,34 @@ class DimensionMapping(TypedDict, total=False): DimensionName: DimensionName -DimensionMappings = List[DimensionMapping] +DimensionMappings = list[DimensionMapping] class PipeTargetTimestreamParameters(TypedDict, total=False): TimeValue: TimeValue - EpochTimeUnit: Optional[EpochTimeUnit] - TimeFieldType: Optional[TimeFieldType] - TimestampFormat: Optional[TimestampFormat] + EpochTimeUnit: EpochTimeUnit | None + TimeFieldType: TimeFieldType | None + TimestampFormat: TimestampFormat | None VersionValue: VersionValue DimensionMappings: DimensionMappings - SingleMeasureMappings: Optional[SingleMeasureMappings] - MultiMeasureMappings: Optional[MultiMeasureMappings] + SingleMeasureMappings: SingleMeasureMappings | None + MultiMeasureMappings: MultiMeasureMappings | None class PipeTargetCloudWatchLogsParameters(TypedDict, total=False): - LogStreamName: Optional[LogStreamName] - Timestamp: Optional[JsonPath] + LogStreamName: LogStreamName | None + Timestamp: JsonPath | None -EventBridgeEventResourceList = List[ArnOrJsonPath] +EventBridgeEventResourceList = list[ArnOrJsonPath] class PipeTargetEventBridgeEventBusParameters(TypedDict, total=False): - EndpointId: Optional[EventBridgeEndpointId] - DetailType: Optional[EventBridgeDetailType] - Source: Optional[EventBridgeEventSource] - Resources: Optional[EventBridgeEventResourceList] - Time: Optional[JsonPath] + EndpointId: EventBridgeEndpointId | None + DetailType: EventBridgeDetailType | None + Source: EventBridgeEventSource | None + Resources: EventBridgeEventResourceList | None + Time: JsonPath | None class SageMakerPipelineParameter(TypedDict, total=False): @@ -446,49 +446,49 @@ class SageMakerPipelineParameter(TypedDict, total=False): Value: SageMakerPipelineParameterValue -SageMakerPipelineParameterList = List[SageMakerPipelineParameter] +SageMakerPipelineParameterList = list[SageMakerPipelineParameter] class PipeTargetSageMakerPipelineParameters(TypedDict, total=False): - PipelineParameterList: Optional[SageMakerPipelineParameterList] + PipelineParameterList: SageMakerPipelineParameterList | None -Sqls = List[Sql] +Sqls = list[Sql] class PipeTargetRedshiftDataParameters(TypedDict, total=False): - SecretManagerArn: Optional[SecretManagerArnOrJsonPath] + SecretManagerArn: SecretManagerArnOrJsonPath | None Database: Database - DbUser: Optional[DbUser] - StatementName: Optional[StatementName] - WithEvent: Optional[Boolean] + DbUser: DbUser | None + StatementName: StatementName | None + WithEvent: Boolean | None Sqls: Sqls -QueryStringParametersMap = Dict[QueryStringKey, QueryStringValue] -HeaderParametersMap = Dict[HeaderKey, HeaderValue] -PathParameterList = List[PathParameter] +QueryStringParametersMap = dict[QueryStringKey, QueryStringValue] +HeaderParametersMap = dict[HeaderKey, HeaderValue] +PathParameterList = list[PathParameter] class PipeTargetHttpParameters(TypedDict, total=False): - PathParameterValues: Optional[PathParameterList] - HeaderParameters: Optional[HeaderParametersMap] - QueryStringParameters: Optional[QueryStringParametersMap] + PathParameterValues: PathParameterList | None + HeaderParameters: HeaderParametersMap | None + QueryStringParameters: QueryStringParametersMap | None class PipeTargetSqsQueueParameters(TypedDict, total=False): - MessageGroupId: Optional[MessageGroupId] - MessageDeduplicationId: Optional[MessageDeduplicationId] + MessageGroupId: MessageGroupId | None + MessageDeduplicationId: MessageDeduplicationId | None class PipeTargetBatchJobParameters(TypedDict, total=False): JobDefinition: String JobName: String - ArrayProperties: Optional[BatchArrayProperties] - RetryStrategy: Optional[BatchRetryStrategy] - ContainerOverrides: Optional[BatchContainerOverrides] - DependsOn: Optional[BatchDependsOn] - Parameters: Optional[BatchParametersMap] + ArrayProperties: BatchArrayProperties | None + RetryStrategy: BatchRetryStrategy | None + ContainerOverrides: BatchContainerOverrides | None + DependsOn: BatchDependsOn | None + Parameters: BatchParametersMap | None class Tag(TypedDict, total=False): @@ -496,113 +496,105 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] class EcsInferenceAcceleratorOverride(TypedDict, total=False): - deviceName: Optional[String] - deviceType: Optional[String] + deviceName: String | None + deviceType: String | None -EcsInferenceAcceleratorOverrideList = List[EcsInferenceAcceleratorOverride] +EcsInferenceAcceleratorOverrideList = list[EcsInferenceAcceleratorOverride] class EcsEphemeralStorage(TypedDict, total=False): sizeInGiB: EphemeralStorageSize -EcsResourceRequirement = TypedDict( - "EcsResourceRequirement", - { - "type": EcsResourceRequirementType, - "value": String, - }, - total=False, -) -EcsResourceRequirementsList = List[EcsResourceRequirement] -EcsEnvironmentFile = TypedDict( - "EcsEnvironmentFile", - { - "type": EcsEnvironmentFileType, - "value": String, - }, - total=False, -) -EcsEnvironmentFileList = List[EcsEnvironmentFile] +class EcsResourceRequirement(TypedDict, total=False): + type: EcsResourceRequirementType + value: String + + +EcsResourceRequirementsList = list[EcsResourceRequirement] + + +class EcsEnvironmentFile(TypedDict, total=False): + type: EcsEnvironmentFileType + value: String + + +EcsEnvironmentFileList = list[EcsEnvironmentFile] class EcsEnvironmentVariable(TypedDict, total=False): - name: Optional[String] - value: Optional[String] + name: String | None + value: String | None -EcsEnvironmentVariableList = List[EcsEnvironmentVariable] +EcsEnvironmentVariableList = list[EcsEnvironmentVariable] class EcsContainerOverride(TypedDict, total=False): - Command: Optional[StringList] - Cpu: Optional[Integer] - Environment: Optional[EcsEnvironmentVariableList] - EnvironmentFiles: Optional[EcsEnvironmentFileList] - Memory: Optional[Integer] - MemoryReservation: Optional[Integer] - Name: Optional[String] - ResourceRequirements: Optional[EcsResourceRequirementsList] + Command: StringList | None + Cpu: Integer | None + Environment: EcsEnvironmentVariableList | None + EnvironmentFiles: EcsEnvironmentFileList | None + Memory: Integer | None + MemoryReservation: Integer | None + Name: String | None + ResourceRequirements: EcsResourceRequirementsList | None -EcsContainerOverrideList = List[EcsContainerOverride] +EcsContainerOverrideList = list[EcsContainerOverride] class EcsTaskOverride(TypedDict, total=False): - ContainerOverrides: Optional[EcsContainerOverrideList] - Cpu: Optional[String] - EphemeralStorage: Optional[EcsEphemeralStorage] - ExecutionRoleArn: Optional[ArnOrJsonPath] - InferenceAcceleratorOverrides: Optional[EcsInferenceAcceleratorOverrideList] - Memory: Optional[String] - TaskRoleArn: Optional[ArnOrJsonPath] - - -PlacementStrategy = TypedDict( - "PlacementStrategy", - { - "type": Optional[PlacementStrategyType], - "field": Optional[PlacementStrategyField], - }, - total=False, -) -PlacementStrategies = List[PlacementStrategy] -PlacementConstraint = TypedDict( - "PlacementConstraint", - { - "type": Optional[PlacementConstraintType], - "expression": Optional[PlacementConstraintExpression], - }, - total=False, -) -PlacementConstraints = List[PlacementConstraint] + ContainerOverrides: EcsContainerOverrideList | None + Cpu: String | None + EphemeralStorage: EcsEphemeralStorage | None + ExecutionRoleArn: ArnOrJsonPath | None + InferenceAcceleratorOverrides: EcsInferenceAcceleratorOverrideList | None + Memory: String | None + TaskRoleArn: ArnOrJsonPath | None + + +class PlacementStrategy(TypedDict, total=False): + type: PlacementStrategyType | None + field: PlacementStrategyField | None + + +PlacementStrategies = list[PlacementStrategy] + + +class PlacementConstraint(TypedDict, total=False): + type: PlacementConstraintType | None + expression: PlacementConstraintExpression | None + + +PlacementConstraints = list[PlacementConstraint] class NetworkConfiguration(TypedDict, total=False): - awsvpcConfiguration: Optional[AwsVpcConfiguration] + awsvpcConfiguration: AwsVpcConfiguration | None class PipeTargetEcsTaskParameters(TypedDict, total=False): TaskDefinitionArn: ArnOrJsonPath - TaskCount: Optional[LimitMin1] - LaunchType: Optional[LaunchType] - NetworkConfiguration: Optional[NetworkConfiguration] - PlatformVersion: Optional[String] - Group: Optional[String] - CapacityProviderStrategy: Optional[CapacityProviderStrategy] - EnableECSManagedTags: Optional[Boolean] - EnableExecuteCommand: Optional[Boolean] - PlacementConstraints: Optional[PlacementConstraints] - PlacementStrategy: Optional[PlacementStrategies] - PropagateTags: Optional[PropagateTags] - ReferenceId: Optional[ReferenceId] - Overrides: Optional[EcsTaskOverride] - Tags: Optional[TagList] + TaskCount: LimitMin1 | None + LaunchType: LaunchType | None + NetworkConfiguration: NetworkConfiguration | None + PlatformVersion: String | None + Group: String | None + CapacityProviderStrategy: CapacityProviderStrategy | None + EnableECSManagedTags: Boolean | None + EnableExecuteCommand: Boolean | None + PlacementConstraints: PlacementConstraints | None + PlacementStrategy: PlacementStrategies | None + PropagateTags: PropagateTags | None + ReferenceId: ReferenceId | None + Overrides: EcsTaskOverride | None + Tags: TagList | None class PipeTargetKinesisStreamParameters(TypedDict, total=False): @@ -610,121 +602,121 @@ class PipeTargetKinesisStreamParameters(TypedDict, total=False): class PipeTargetStateMachineParameters(TypedDict, total=False): - InvocationType: Optional[PipeTargetInvocationType] + InvocationType: PipeTargetInvocationType | None class PipeTargetLambdaFunctionParameters(TypedDict, total=False): - InvocationType: Optional[PipeTargetInvocationType] + InvocationType: PipeTargetInvocationType | None class PipeTargetParameters(TypedDict, total=False): - InputTemplate: Optional[InputTemplate] - LambdaFunctionParameters: Optional[PipeTargetLambdaFunctionParameters] - StepFunctionStateMachineParameters: Optional[PipeTargetStateMachineParameters] - KinesisStreamParameters: Optional[PipeTargetKinesisStreamParameters] - EcsTaskParameters: Optional[PipeTargetEcsTaskParameters] - BatchJobParameters: Optional[PipeTargetBatchJobParameters] - SqsQueueParameters: Optional[PipeTargetSqsQueueParameters] - HttpParameters: Optional[PipeTargetHttpParameters] - RedshiftDataParameters: Optional[PipeTargetRedshiftDataParameters] - SageMakerPipelineParameters: Optional[PipeTargetSageMakerPipelineParameters] - EventBridgeEventBusParameters: Optional[PipeTargetEventBridgeEventBusParameters] - CloudWatchLogsParameters: Optional[PipeTargetCloudWatchLogsParameters] - TimestreamParameters: Optional[PipeTargetTimestreamParameters] + InputTemplate: InputTemplate | None + LambdaFunctionParameters: PipeTargetLambdaFunctionParameters | None + StepFunctionStateMachineParameters: PipeTargetStateMachineParameters | None + KinesisStreamParameters: PipeTargetKinesisStreamParameters | None + EcsTaskParameters: PipeTargetEcsTaskParameters | None + BatchJobParameters: PipeTargetBatchJobParameters | None + SqsQueueParameters: PipeTargetSqsQueueParameters | None + HttpParameters: PipeTargetHttpParameters | None + RedshiftDataParameters: PipeTargetRedshiftDataParameters | None + SageMakerPipelineParameters: PipeTargetSageMakerPipelineParameters | None + EventBridgeEventBusParameters: PipeTargetEventBridgeEventBusParameters | None + CloudWatchLogsParameters: PipeTargetCloudWatchLogsParameters | None + TimestreamParameters: PipeTargetTimestreamParameters | None class PipeEnrichmentHttpParameters(TypedDict, total=False): - PathParameterValues: Optional[PathParameterList] - HeaderParameters: Optional[HeaderParametersMap] - QueryStringParameters: Optional[QueryStringParametersMap] + PathParameterValues: PathParameterList | None + HeaderParameters: HeaderParametersMap | None + QueryStringParameters: QueryStringParametersMap | None class PipeEnrichmentParameters(TypedDict, total=False): - InputTemplate: Optional[InputTemplate] - HttpParameters: Optional[PipeEnrichmentHttpParameters] + InputTemplate: InputTemplate | None + HttpParameters: PipeEnrichmentHttpParameters | None -SecurityGroupIds = List[SecurityGroupId] -SubnetIds = List[SubnetId] +SecurityGroupIds = list[SecurityGroupId] +SubnetIds = list[SubnetId] class SelfManagedKafkaAccessConfigurationVpc(TypedDict, total=False): - Subnets: Optional[SubnetIds] - SecurityGroup: Optional[SecurityGroupIds] + Subnets: SubnetIds | None + SecurityGroup: SecurityGroupIds | None class SelfManagedKafkaAccessConfigurationCredentials(TypedDict, total=False): - BasicAuth: Optional[SecretManagerArn] - SaslScram512Auth: Optional[SecretManagerArn] - SaslScram256Auth: Optional[SecretManagerArn] - ClientCertificateTlsAuth: Optional[SecretManagerArn] + BasicAuth: SecretManagerArn | None + SaslScram512Auth: SecretManagerArn | None + SaslScram256Auth: SecretManagerArn | None + ClientCertificateTlsAuth: SecretManagerArn | None -KafkaBootstrapServers = List[EndpointString] +KafkaBootstrapServers = list[EndpointString] class PipeSourceSelfManagedKafkaParameters(TypedDict, total=False): TopicName: KafkaTopicName - StartingPosition: Optional[SelfManagedKafkaStartPosition] - AdditionalBootstrapServers: Optional[KafkaBootstrapServers] - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - ConsumerGroupID: Optional[URI] - Credentials: Optional[SelfManagedKafkaAccessConfigurationCredentials] - ServerRootCaCertificate: Optional[SecretManagerArn] - Vpc: Optional[SelfManagedKafkaAccessConfigurationVpc] + StartingPosition: SelfManagedKafkaStartPosition | None + AdditionalBootstrapServers: KafkaBootstrapServers | None + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + ConsumerGroupID: URI | None + Credentials: SelfManagedKafkaAccessConfigurationCredentials | None + ServerRootCaCertificate: SecretManagerArn | None + Vpc: SelfManagedKafkaAccessConfigurationVpc | None class MSKAccessCredentials(TypedDict, total=False): - SaslScram512Auth: Optional[SecretManagerArn] - ClientCertificateTlsAuth: Optional[SecretManagerArn] + SaslScram512Auth: SecretManagerArn | None + ClientCertificateTlsAuth: SecretManagerArn | None class PipeSourceManagedStreamingKafkaParameters(TypedDict, total=False): TopicName: KafkaTopicName - StartingPosition: Optional[MSKStartPosition] - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - ConsumerGroupID: Optional[URI] - Credentials: Optional[MSKAccessCredentials] + StartingPosition: MSKStartPosition | None + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + ConsumerGroupID: URI | None + Credentials: MSKAccessCredentials | None class MQBrokerAccessCredentials(TypedDict, total=False): - BasicAuth: Optional[SecretManagerArn] + BasicAuth: SecretManagerArn | None class PipeSourceRabbitMQBrokerParameters(TypedDict, total=False): Credentials: MQBrokerAccessCredentials QueueName: MQBrokerQueueName - VirtualHost: Optional[URI] - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] + VirtualHost: URI | None + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None class PipeSourceActiveMQBrokerParameters(TypedDict, total=False): Credentials: MQBrokerAccessCredentials QueueName: MQBrokerQueueName - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None class PipeSourceSqsQueueParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None class DeadLetterConfig(TypedDict, total=False): - Arn: Optional[Arn] + Arn: Arn | None class PipeSourceDynamoDBStreamParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - DeadLetterConfig: Optional[DeadLetterConfig] - OnPartialBatchItemFailure: Optional[OnPartialBatchItemFailureStreams] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - MaximumRecordAgeInSeconds: Optional[MaximumRecordAgeInSeconds] - MaximumRetryAttempts: Optional[MaximumRetryAttemptsESM] - ParallelizationFactor: Optional[LimitMax10] + BatchSize: LimitMax10000 | None + DeadLetterConfig: DeadLetterConfig | None + OnPartialBatchItemFailure: OnPartialBatchItemFailureStreams | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + MaximumRecordAgeInSeconds: MaximumRecordAgeInSeconds | None + MaximumRetryAttempts: MaximumRetryAttemptsESM | None + ParallelizationFactor: LimitMax10 | None StartingPosition: DynamoDBStreamStartPosition @@ -732,62 +724,62 @@ class PipeSourceDynamoDBStreamParameters(TypedDict, total=False): class PipeSourceKinesisStreamParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - DeadLetterConfig: Optional[DeadLetterConfig] - OnPartialBatchItemFailure: Optional[OnPartialBatchItemFailureStreams] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - MaximumRecordAgeInSeconds: Optional[MaximumRecordAgeInSeconds] - MaximumRetryAttempts: Optional[MaximumRetryAttemptsESM] - ParallelizationFactor: Optional[LimitMax10] + BatchSize: LimitMax10000 | None + DeadLetterConfig: DeadLetterConfig | None + OnPartialBatchItemFailure: OnPartialBatchItemFailureStreams | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + MaximumRecordAgeInSeconds: MaximumRecordAgeInSeconds | None + MaximumRetryAttempts: MaximumRetryAttemptsESM | None + ParallelizationFactor: LimitMax10 | None StartingPosition: KinesisStreamStartPosition - StartingPositionTimestamp: Optional[Timestamp] + StartingPositionTimestamp: Timestamp | None class Filter(TypedDict, total=False): - Pattern: Optional[EventPattern] + Pattern: EventPattern | None -FilterList = List[Filter] +FilterList = list[Filter] class FilterCriteria(TypedDict, total=False): - Filters: Optional[FilterList] + Filters: FilterList | None class PipeSourceParameters(TypedDict, total=False): - FilterCriteria: Optional[FilterCriteria] - KinesisStreamParameters: Optional[PipeSourceKinesisStreamParameters] - DynamoDBStreamParameters: Optional[PipeSourceDynamoDBStreamParameters] - SqsQueueParameters: Optional[PipeSourceSqsQueueParameters] - ActiveMQBrokerParameters: Optional[PipeSourceActiveMQBrokerParameters] - RabbitMQBrokerParameters: Optional[PipeSourceRabbitMQBrokerParameters] - ManagedStreamingKafkaParameters: Optional[PipeSourceManagedStreamingKafkaParameters] - SelfManagedKafkaParameters: Optional[PipeSourceSelfManagedKafkaParameters] + FilterCriteria: FilterCriteria | None + KinesisStreamParameters: PipeSourceKinesisStreamParameters | None + DynamoDBStreamParameters: PipeSourceDynamoDBStreamParameters | None + SqsQueueParameters: PipeSourceSqsQueueParameters | None + ActiveMQBrokerParameters: PipeSourceActiveMQBrokerParameters | None + RabbitMQBrokerParameters: PipeSourceRabbitMQBrokerParameters | None + ManagedStreamingKafkaParameters: PipeSourceManagedStreamingKafkaParameters | None + SelfManagedKafkaParameters: PipeSourceSelfManagedKafkaParameters | None class CreatePipeRequest(ServiceRequest): Name: PipeName - Description: Optional[PipeDescription] - DesiredState: Optional[RequestedPipeState] + Description: PipeDescription | None + DesiredState: RequestedPipeState | None Source: ArnOrUrl - SourceParameters: Optional[PipeSourceParameters] - Enrichment: Optional[OptionalArn] - EnrichmentParameters: Optional[PipeEnrichmentParameters] + SourceParameters: PipeSourceParameters | None + Enrichment: OptionalArn | None + EnrichmentParameters: PipeEnrichmentParameters | None Target: Arn - TargetParameters: Optional[PipeTargetParameters] + TargetParameters: PipeTargetParameters | None RoleArn: RoleArn - Tags: Optional[TagMap] - LogConfiguration: Optional[PipeLogConfigurationParameters] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] + Tags: TagMap | None + LogConfiguration: PipeLogConfigurationParameters | None + KmsKeyIdentifier: KmsKeyIdentifier | None class CreatePipeResponse(TypedDict, total=False): - Arn: Optional[PipeArn] - Name: Optional[PipeName] - DesiredState: Optional[RequestedPipeState] - CurrentState: Optional[PipeState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Arn: PipeArn | None + Name: PipeName | None + DesiredState: RequestedPipeState | None + CurrentState: PipeState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class DeletePipeRequest(ServiceRequest): @@ -795,12 +787,12 @@ class DeletePipeRequest(ServiceRequest): class DeletePipeResponse(TypedDict, total=False): - Arn: Optional[PipeArn] - Name: Optional[PipeName] - DesiredState: Optional[RequestedPipeStateDescribeResponse] - CurrentState: Optional[PipeState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Arn: PipeArn | None + Name: PipeName | None + DesiredState: RequestedPipeStateDescribeResponse | None + CurrentState: PipeState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class DescribePipeRequest(ServiceRequest): @@ -808,74 +800,74 @@ class DescribePipeRequest(ServiceRequest): class FirehoseLogDestination(TypedDict, total=False): - DeliveryStreamArn: Optional[FirehoseArn] + DeliveryStreamArn: FirehoseArn | None class S3LogDestination(TypedDict, total=False): - BucketName: Optional[String] - Prefix: Optional[String] - BucketOwner: Optional[String] - OutputFormat: Optional[S3OutputFormat] + BucketName: String | None + Prefix: String | None + BucketOwner: String | None + OutputFormat: S3OutputFormat | None class PipeLogConfiguration(TypedDict, total=False): - S3LogDestination: Optional[S3LogDestination] - FirehoseLogDestination: Optional[FirehoseLogDestination] - CloudwatchLogsLogDestination: Optional[CloudwatchLogsLogDestination] - Level: Optional[LogLevel] - IncludeExecutionData: Optional[IncludeExecutionData] + S3LogDestination: S3LogDestination | None + FirehoseLogDestination: FirehoseLogDestination | None + CloudwatchLogsLogDestination: CloudwatchLogsLogDestination | None + Level: LogLevel | None + IncludeExecutionData: IncludeExecutionData | None class DescribePipeResponse(TypedDict, total=False): - Arn: Optional[PipeArn] - Name: Optional[PipeName] - Description: Optional[PipeDescription] - DesiredState: Optional[RequestedPipeStateDescribeResponse] - CurrentState: Optional[PipeState] - StateReason: Optional[PipeStateReason] - Source: Optional[ArnOrUrl] - SourceParameters: Optional[PipeSourceParameters] - Enrichment: Optional[OptionalArn] - EnrichmentParameters: Optional[PipeEnrichmentParameters] - Target: Optional[Arn] - TargetParameters: Optional[PipeTargetParameters] - RoleArn: Optional[RoleArn] - Tags: Optional[TagMap] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] - LogConfiguration: Optional[PipeLogConfiguration] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] + Arn: PipeArn | None + Name: PipeName | None + Description: PipeDescription | None + DesiredState: RequestedPipeStateDescribeResponse | None + CurrentState: PipeState | None + StateReason: PipeStateReason | None + Source: ArnOrUrl | None + SourceParameters: PipeSourceParameters | None + Enrichment: OptionalArn | None + EnrichmentParameters: PipeEnrichmentParameters | None + Target: Arn | None + TargetParameters: PipeTargetParameters | None + RoleArn: RoleArn | None + Tags: TagMap | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None + LogConfiguration: PipeLogConfiguration | None + KmsKeyIdentifier: KmsKeyIdentifier | None class ListPipesRequest(ServiceRequest): - NamePrefix: Optional[PipeName] - DesiredState: Optional[RequestedPipeState] - CurrentState: Optional[PipeState] - SourcePrefix: Optional[ResourceArn] - TargetPrefix: Optional[ResourceArn] - NextToken: Optional[NextToken] - Limit: Optional[LimitMax100] + NamePrefix: PipeName | None + DesiredState: RequestedPipeState | None + CurrentState: PipeState | None + SourcePrefix: ResourceArn | None + TargetPrefix: ResourceArn | None + NextToken: NextToken | None + Limit: LimitMax100 | None class Pipe(TypedDict, total=False): - Name: Optional[PipeName] - Arn: Optional[PipeArn] - DesiredState: Optional[RequestedPipeState] - CurrentState: Optional[PipeState] - StateReason: Optional[PipeStateReason] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] - Source: Optional[ArnOrUrl] - Target: Optional[Arn] - Enrichment: Optional[OptionalArn] + Name: PipeName | None + Arn: PipeArn | None + DesiredState: RequestedPipeState | None + CurrentState: PipeState | None + StateReason: PipeStateReason | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None + Source: ArnOrUrl | None + Target: Arn | None + Enrichment: OptionalArn | None -PipeList = List[Pipe] +PipeList = list[Pipe] class ListPipesResponse(TypedDict, total=False): - Pipes: Optional[PipeList] - NextToken: Optional[NextToken] + Pipes: PipeList | None + NextToken: NextToken | None class ListTagsForResourceRequest(ServiceRequest): @@ -883,7 +875,7 @@ class ListTagsForResourceRequest(ServiceRequest): class ListTagsForResourceResponse(TypedDict, total=False): - tags: Optional[TagMap] + tags: TagMap | None class StartPipeRequest(ServiceRequest): @@ -891,12 +883,12 @@ class StartPipeRequest(ServiceRequest): class StartPipeResponse(TypedDict, total=False): - Arn: Optional[PipeArn] - Name: Optional[PipeName] - DesiredState: Optional[RequestedPipeState] - CurrentState: Optional[PipeState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Arn: PipeArn | None + Name: PipeName | None + DesiredState: RequestedPipeState | None + CurrentState: PipeState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class StopPipeRequest(ServiceRequest): @@ -904,15 +896,15 @@ class StopPipeRequest(ServiceRequest): class StopPipeResponse(TypedDict, total=False): - Arn: Optional[PipeArn] - Name: Optional[PipeName] - DesiredState: Optional[RequestedPipeState] - CurrentState: Optional[PipeState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Arn: PipeArn | None + Name: PipeName | None + DesiredState: RequestedPipeState | None + CurrentState: PipeState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceRequest(ServiceRequest): @@ -934,93 +926,93 @@ class UntagResourceResponse(TypedDict, total=False): class UpdatePipeSourceSelfManagedKafkaParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - Credentials: Optional[SelfManagedKafkaAccessConfigurationCredentials] - ServerRootCaCertificate: Optional[SecretManagerArn] - Vpc: Optional[SelfManagedKafkaAccessConfigurationVpc] + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + Credentials: SelfManagedKafkaAccessConfigurationCredentials | None + ServerRootCaCertificate: SecretManagerArn | None + Vpc: SelfManagedKafkaAccessConfigurationVpc | None class UpdatePipeSourceManagedStreamingKafkaParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - Credentials: Optional[MSKAccessCredentials] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] + BatchSize: LimitMax10000 | None + Credentials: MSKAccessCredentials | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None class UpdatePipeSourceRabbitMQBrokerParameters(TypedDict, total=False): Credentials: MQBrokerAccessCredentials - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None class UpdatePipeSourceActiveMQBrokerParameters(TypedDict, total=False): Credentials: MQBrokerAccessCredentials - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None class UpdatePipeSourceSqsQueueParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] + BatchSize: LimitMax10000 | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None class UpdatePipeSourceDynamoDBStreamParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - DeadLetterConfig: Optional[DeadLetterConfig] - OnPartialBatchItemFailure: Optional[OnPartialBatchItemFailureStreams] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - MaximumRecordAgeInSeconds: Optional[MaximumRecordAgeInSeconds] - MaximumRetryAttempts: Optional[MaximumRetryAttemptsESM] - ParallelizationFactor: Optional[LimitMax10] + BatchSize: LimitMax10000 | None + DeadLetterConfig: DeadLetterConfig | None + OnPartialBatchItemFailure: OnPartialBatchItemFailureStreams | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + MaximumRecordAgeInSeconds: MaximumRecordAgeInSeconds | None + MaximumRetryAttempts: MaximumRetryAttemptsESM | None + ParallelizationFactor: LimitMax10 | None class UpdatePipeSourceKinesisStreamParameters(TypedDict, total=False): - BatchSize: Optional[LimitMax10000] - DeadLetterConfig: Optional[DeadLetterConfig] - OnPartialBatchItemFailure: Optional[OnPartialBatchItemFailureStreams] - MaximumBatchingWindowInSeconds: Optional[MaximumBatchingWindowInSeconds] - MaximumRecordAgeInSeconds: Optional[MaximumRecordAgeInSeconds] - MaximumRetryAttempts: Optional[MaximumRetryAttemptsESM] - ParallelizationFactor: Optional[LimitMax10] + BatchSize: LimitMax10000 | None + DeadLetterConfig: DeadLetterConfig | None + OnPartialBatchItemFailure: OnPartialBatchItemFailureStreams | None + MaximumBatchingWindowInSeconds: MaximumBatchingWindowInSeconds | None + MaximumRecordAgeInSeconds: MaximumRecordAgeInSeconds | None + MaximumRetryAttempts: MaximumRetryAttemptsESM | None + ParallelizationFactor: LimitMax10 | None class UpdatePipeSourceParameters(TypedDict, total=False): - FilterCriteria: Optional[FilterCriteria] - KinesisStreamParameters: Optional[UpdatePipeSourceKinesisStreamParameters] - DynamoDBStreamParameters: Optional[UpdatePipeSourceDynamoDBStreamParameters] - SqsQueueParameters: Optional[UpdatePipeSourceSqsQueueParameters] - ActiveMQBrokerParameters: Optional[UpdatePipeSourceActiveMQBrokerParameters] - RabbitMQBrokerParameters: Optional[UpdatePipeSourceRabbitMQBrokerParameters] - ManagedStreamingKafkaParameters: Optional[UpdatePipeSourceManagedStreamingKafkaParameters] - SelfManagedKafkaParameters: Optional[UpdatePipeSourceSelfManagedKafkaParameters] + FilterCriteria: FilterCriteria | None + KinesisStreamParameters: UpdatePipeSourceKinesisStreamParameters | None + DynamoDBStreamParameters: UpdatePipeSourceDynamoDBStreamParameters | None + SqsQueueParameters: UpdatePipeSourceSqsQueueParameters | None + ActiveMQBrokerParameters: UpdatePipeSourceActiveMQBrokerParameters | None + RabbitMQBrokerParameters: UpdatePipeSourceRabbitMQBrokerParameters | None + ManagedStreamingKafkaParameters: UpdatePipeSourceManagedStreamingKafkaParameters | None + SelfManagedKafkaParameters: UpdatePipeSourceSelfManagedKafkaParameters | None class UpdatePipeRequest(ServiceRequest): Name: PipeName - Description: Optional[PipeDescription] - DesiredState: Optional[RequestedPipeState] - SourceParameters: Optional[UpdatePipeSourceParameters] - Enrichment: Optional[OptionalArn] - EnrichmentParameters: Optional[PipeEnrichmentParameters] - Target: Optional[Arn] - TargetParameters: Optional[PipeTargetParameters] + Description: PipeDescription | None + DesiredState: RequestedPipeState | None + SourceParameters: UpdatePipeSourceParameters | None + Enrichment: OptionalArn | None + EnrichmentParameters: PipeEnrichmentParameters | None + Target: Arn | None + TargetParameters: PipeTargetParameters | None RoleArn: RoleArn - LogConfiguration: Optional[PipeLogConfigurationParameters] - KmsKeyIdentifier: Optional[KmsKeyIdentifier] + LogConfiguration: PipeLogConfigurationParameters | None + KmsKeyIdentifier: KmsKeyIdentifier | None class UpdatePipeResponse(TypedDict, total=False): - Arn: Optional[PipeArn] - Name: Optional[PipeName] - DesiredState: Optional[RequestedPipeState] - CurrentState: Optional[PipeState] - CreationTime: Optional[Timestamp] - LastModifiedTime: Optional[Timestamp] + Arn: PipeArn | None + Name: PipeName | None + DesiredState: RequestedPipeState | None + CurrentState: PipeState | None + CreationTime: Timestamp | None + LastModifiedTime: Timestamp | None class PipesApi: - service = "pipes" - version = "2015-10-07" + service: str = "pipes" + version: str = "2015-10-07" @handler("CreatePipe") def create_pipe( diff --git a/localstack-core/localstack/aws/api/redshift/__init__.py b/localstack-core/localstack/aws/api/redshift/__init__.py index 1bcc3ad7816ad..46576038b6aec 100644 --- a/localstack-core/localstack/aws/api/redshift/__init__.py +++ b/localstack-core/localstack/aws/api/redshift/__init__.py @@ -1,12 +1,13 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler AuthenticationProfileNameString = str Boolean = bool BooleanOptional = bool +CatalogNameString = str CustomDomainCertificateArnString = str CustomDomainNameString = str Description = str @@ -39,6 +40,11 @@ class ActionType(StrEnum): resize_cluster = "resize-cluster" +class ApplicationType(StrEnum): + None_ = "None" + Lakehouse = "Lakehouse" + + class AquaConfigurationStatus(StrEnum): enabled = "enabled" disabled = "disabled" @@ -95,6 +101,16 @@ class ImpactRankingType(StrEnum): LOW = "LOW" +class LakehouseIdcRegistration(StrEnum): + Associate = "Associate" + Disassociate = "Disassociate" + + +class LakehouseRegistration(StrEnum): + Register = "Register" + Deregister = "Deregister" + + class LogDestinationType(StrEnum): s3 = "s3" cloudwatch = "cloudwatch" @@ -227,6 +243,7 @@ class UsageLimitFeatureType(StrEnum): spectrum = "spectrum" concurrency_scaling = "concurrency-scaling" cross_region_datasharing = "cross-region-datasharing" + extra_compute_for_automatic_optimization = "extra-compute-for-automatic-optimization" class UsageLimitLimitType(StrEnum): @@ -844,6 +861,12 @@ class RedshiftIdcApplicationQuotaExceededFault(ServiceException): status_code: int = 400 +class RedshiftInvalidParameterFault(ServiceException): + code: str = "RedshiftInvalidParameter" + sender_fault: bool = True + status_code: int = 400 + + class ReservedNodeAlreadyExistsFault(ServiceException): code: str = "ReservedNodeAlreadyExists" sender_fault: bool = True @@ -1108,256 +1131,256 @@ class AcceptReservedNodeExchangeInputMessage(ServiceRequest): class RecurringCharge(TypedDict, total=False): - RecurringChargeAmount: Optional[Double] - RecurringChargeFrequency: Optional[String] + RecurringChargeAmount: Double | None + RecurringChargeFrequency: String | None -RecurringChargeList = List[RecurringCharge] +RecurringChargeList = list[RecurringCharge] TStamp = datetime class ReservedNode(TypedDict, total=False): - ReservedNodeId: Optional[String] - ReservedNodeOfferingId: Optional[String] - NodeType: Optional[String] - StartTime: Optional[TStamp] - Duration: Optional[Integer] - FixedPrice: Optional[Double] - UsagePrice: Optional[Double] - CurrencyCode: Optional[String] - NodeCount: Optional[Integer] - State: Optional[String] - OfferingType: Optional[String] - RecurringCharges: Optional[RecurringChargeList] - ReservedNodeOfferingType: Optional[ReservedNodeOfferingType] + ReservedNodeId: String | None + ReservedNodeOfferingId: String | None + NodeType: String | None + StartTime: TStamp | None + Duration: Integer | None + FixedPrice: Double | None + UsagePrice: Double | None + CurrencyCode: String | None + NodeCount: Integer | None + State: String | None + OfferingType: String | None + RecurringCharges: RecurringChargeList | None + ReservedNodeOfferingType: ReservedNodeOfferingType | None class AcceptReservedNodeExchangeOutputMessage(TypedDict, total=False): - ExchangedReservedNode: Optional[ReservedNode] + ExchangedReservedNode: ReservedNode | None class AttributeValueTarget(TypedDict, total=False): - AttributeValue: Optional[String] + AttributeValue: String | None -AttributeValueList = List[AttributeValueTarget] +AttributeValueList = list[AttributeValueTarget] class AccountAttribute(TypedDict, total=False): - AttributeName: Optional[String] - AttributeValues: Optional[AttributeValueList] + AttributeName: String | None + AttributeValues: AttributeValueList | None -AttributeList = List[AccountAttribute] +AttributeList = list[AccountAttribute] class AccountAttributeList(TypedDict, total=False): - AccountAttributes: Optional[AttributeList] + AccountAttributes: AttributeList | None class AccountWithRestoreAccess(TypedDict, total=False): - AccountId: Optional[String] - AccountAlias: Optional[String] + AccountId: String | None + AccountAlias: String | None -AccountsWithRestoreAccessList = List[AccountWithRestoreAccess] +AccountsWithRestoreAccessList = list[AccountWithRestoreAccess] class AquaConfiguration(TypedDict, total=False): - AquaStatus: Optional[AquaStatus] - AquaConfigurationStatus: Optional[AquaConfigurationStatus] + AquaStatus: AquaStatus | None + AquaConfigurationStatus: AquaConfigurationStatus | None class AssociateDataShareConsumerMessage(ServiceRequest): DataShareArn: String - AssociateEntireAccount: Optional[BooleanOptional] - ConsumerArn: Optional[String] - ConsumerRegion: Optional[String] - AllowWrites: Optional[BooleanOptional] + AssociateEntireAccount: BooleanOptional | None + ConsumerArn: String | None + ConsumerRegion: String | None + AllowWrites: BooleanOptional | None class ClusterAssociatedToSchedule(TypedDict, total=False): - ClusterIdentifier: Optional[String] - ScheduleAssociationState: Optional[ScheduleState] + ClusterIdentifier: String | None + ScheduleAssociationState: ScheduleState | None -AssociatedClusterList = List[ClusterAssociatedToSchedule] +AssociatedClusterList = list[ClusterAssociatedToSchedule] class CertificateAssociation(TypedDict, total=False): - CustomDomainName: Optional[String] - ClusterIdentifier: Optional[String] + CustomDomainName: String | None + ClusterIdentifier: String | None -CertificateAssociationList = List[CertificateAssociation] +CertificateAssociationList = list[CertificateAssociation] class Association(TypedDict, total=False): - CustomDomainCertificateArn: Optional[String] - CustomDomainCertificateExpiryDate: Optional[TStamp] - CertificateAssociations: Optional[CertificateAssociationList] + CustomDomainCertificateArn: String | None + CustomDomainCertificateExpiryDate: TStamp | None + CertificateAssociations: CertificateAssociationList | None -AssociationList = List[Association] -AttributeNameList = List[String] +AssociationList = list[Association] +AttributeNameList = list[String] class AuthenticationProfile(TypedDict, total=False): - AuthenticationProfileName: Optional[AuthenticationProfileNameString] - AuthenticationProfileContent: Optional[String] + AuthenticationProfileName: AuthenticationProfileNameString | None + AuthenticationProfileContent: String | None -AuthenticationProfileList = List[AuthenticationProfile] +AuthenticationProfileList = list[AuthenticationProfile] class AuthorizeClusterSecurityGroupIngressMessage(ServiceRequest): ClusterSecurityGroupName: String - CIDRIP: Optional[String] - EC2SecurityGroupName: Optional[String] - EC2SecurityGroupOwnerId: Optional[String] + CIDRIP: String | None + EC2SecurityGroupName: String | None + EC2SecurityGroupOwnerId: String | None class Tag(TypedDict, total=False): - Key: Optional[String] - Value: Optional[String] + Key: String | None + Value: String | None -TagList = List[Tag] +TagList = list[Tag] class IPRange(TypedDict, total=False): - Status: Optional[String] - CIDRIP: Optional[String] - Tags: Optional[TagList] + Status: String | None + CIDRIP: String | None + Tags: TagList | None -IPRangeList = List[IPRange] +IPRangeList = list[IPRange] class EC2SecurityGroup(TypedDict, total=False): - Status: Optional[String] - EC2SecurityGroupName: Optional[String] - EC2SecurityGroupOwnerId: Optional[String] - Tags: Optional[TagList] + Status: String | None + EC2SecurityGroupName: String | None + EC2SecurityGroupOwnerId: String | None + Tags: TagList | None -EC2SecurityGroupList = List[EC2SecurityGroup] +EC2SecurityGroupList = list[EC2SecurityGroup] class ClusterSecurityGroup(TypedDict, total=False): - ClusterSecurityGroupName: Optional[String] - Description: Optional[String] - EC2SecurityGroups: Optional[EC2SecurityGroupList] - IPRanges: Optional[IPRangeList] - Tags: Optional[TagList] + ClusterSecurityGroupName: String | None + Description: String | None + EC2SecurityGroups: EC2SecurityGroupList | None + IPRanges: IPRangeList | None + Tags: TagList | None class AuthorizeClusterSecurityGroupIngressResult(TypedDict, total=False): - ClusterSecurityGroup: Optional[ClusterSecurityGroup] + ClusterSecurityGroup: ClusterSecurityGroup | None class AuthorizeDataShareMessage(ServiceRequest): DataShareArn: String ConsumerIdentifier: String - AllowWrites: Optional[BooleanOptional] + AllowWrites: BooleanOptional | None -VpcIdentifierList = List[String] +VpcIdentifierList = list[String] class AuthorizeEndpointAccessMessage(ServiceRequest): - ClusterIdentifier: Optional[String] + ClusterIdentifier: String | None Account: String - VpcIds: Optional[VpcIdentifierList] + VpcIds: VpcIdentifierList | None class AuthorizeSnapshotAccessMessage(ServiceRequest): - SnapshotIdentifier: Optional[String] - SnapshotArn: Optional[String] - SnapshotClusterIdentifier: Optional[String] + SnapshotIdentifier: String | None + SnapshotArn: String | None + SnapshotClusterIdentifier: String | None AccountWithRestoreAccess: String -RestorableNodeTypeList = List[String] +RestorableNodeTypeList = list[String] Long = int class Snapshot(TypedDict, total=False): - SnapshotIdentifier: Optional[String] - ClusterIdentifier: Optional[String] - SnapshotCreateTime: Optional[TStamp] - Status: Optional[String] - Port: Optional[Integer] - AvailabilityZone: Optional[String] - ClusterCreateTime: Optional[TStamp] - MasterUsername: Optional[String] - ClusterVersion: Optional[String] - EngineFullVersion: Optional[String] - SnapshotType: Optional[String] - NodeType: Optional[String] - NumberOfNodes: Optional[Integer] - DBName: Optional[String] - VpcId: Optional[String] - Encrypted: Optional[Boolean] - KmsKeyId: Optional[String] - EncryptedWithHSM: Optional[Boolean] - AccountsWithRestoreAccess: Optional[AccountsWithRestoreAccessList] - OwnerAccount: Optional[String] - TotalBackupSizeInMegaBytes: Optional[Double] - ActualIncrementalBackupSizeInMegaBytes: Optional[Double] - BackupProgressInMegaBytes: Optional[Double] - CurrentBackupRateInMegaBytesPerSecond: Optional[Double] - EstimatedSecondsToCompletion: Optional[Long] - ElapsedTimeInSeconds: Optional[Long] - SourceRegion: Optional[String] - Tags: Optional[TagList] - RestorableNodeTypes: Optional[RestorableNodeTypeList] - EnhancedVpcRouting: Optional[Boolean] - MaintenanceTrackName: Optional[String] - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] - ManualSnapshotRemainingDays: Optional[IntegerOptional] - SnapshotRetentionStartTime: Optional[TStamp] - MasterPasswordSecretArn: Optional[String] - MasterPasswordSecretKmsKeyId: Optional[String] - SnapshotArn: Optional[String] + SnapshotIdentifier: String | None + ClusterIdentifier: String | None + SnapshotCreateTime: TStamp | None + Status: String | None + Port: Integer | None + AvailabilityZone: String | None + ClusterCreateTime: TStamp | None + MasterUsername: String | None + ClusterVersion: String | None + EngineFullVersion: String | None + SnapshotType: String | None + NodeType: String | None + NumberOfNodes: Integer | None + DBName: String | None + VpcId: String | None + Encrypted: Boolean | None + KmsKeyId: String | None + EncryptedWithHSM: Boolean | None + AccountsWithRestoreAccess: AccountsWithRestoreAccessList | None + OwnerAccount: String | None + TotalBackupSizeInMegaBytes: Double | None + ActualIncrementalBackupSizeInMegaBytes: Double | None + BackupProgressInMegaBytes: Double | None + CurrentBackupRateInMegaBytesPerSecond: Double | None + EstimatedSecondsToCompletion: Long | None + ElapsedTimeInSeconds: Long | None + SourceRegion: String | None + Tags: TagList | None + RestorableNodeTypes: RestorableNodeTypeList | None + EnhancedVpcRouting: Boolean | None + MaintenanceTrackName: String | None + ManualSnapshotRetentionPeriod: IntegerOptional | None + ManualSnapshotRemainingDays: IntegerOptional | None + SnapshotRetentionStartTime: TStamp | None + MasterPasswordSecretArn: String | None + MasterPasswordSecretKmsKeyId: String | None + SnapshotArn: String | None class AuthorizeSnapshotAccessResult(TypedDict, total=False): - Snapshot: Optional[Snapshot] + Snapshot: Snapshot | None -AuthorizedAudienceList = List[String] +AuthorizedAudienceList = list[String] class AuthorizedTokenIssuer(TypedDict, total=False): - TrustedTokenIssuerArn: Optional[String] - AuthorizedAudiencesList: Optional[AuthorizedAudienceList] + TrustedTokenIssuerArn: String | None + AuthorizedAudiencesList: AuthorizedAudienceList | None -AuthorizedTokenIssuerList = List[AuthorizedTokenIssuer] +AuthorizedTokenIssuerList = list[AuthorizedTokenIssuer] class SupportedPlatform(TypedDict, total=False): - Name: Optional[String] + Name: String | None -SupportedPlatformsList = List[SupportedPlatform] +SupportedPlatformsList = list[SupportedPlatform] class AvailabilityZone(TypedDict, total=False): - Name: Optional[String] - SupportedPlatforms: Optional[SupportedPlatformsList] + Name: String | None + SupportedPlatforms: SupportedPlatformsList | None -AvailabilityZoneList = List[AvailabilityZone] +AvailabilityZoneList = list[AvailabilityZone] class DeleteClusterSnapshotMessage(ServiceRequest): SnapshotIdentifier: String - SnapshotClusterIdentifier: Optional[String] + SnapshotClusterIdentifier: String | None -DeleteClusterSnapshotMessageList = List[DeleteClusterSnapshotMessage] +DeleteClusterSnapshotMessageList = list[DeleteClusterSnapshotMessage] class BatchDeleteClusterSnapshotsRequest(ServiceRequest): @@ -1365,33 +1388,33 @@ class BatchDeleteClusterSnapshotsRequest(ServiceRequest): class SnapshotErrorMessage(TypedDict, total=False): - SnapshotIdentifier: Optional[String] - SnapshotClusterIdentifier: Optional[String] - FailureCode: Optional[String] - FailureReason: Optional[String] + SnapshotIdentifier: String | None + SnapshotClusterIdentifier: String | None + FailureCode: String | None + FailureReason: String | None -BatchSnapshotOperationErrorList = List[SnapshotErrorMessage] -SnapshotIdentifierList = List[String] +BatchSnapshotOperationErrorList = list[SnapshotErrorMessage] +SnapshotIdentifierList = list[String] class BatchDeleteClusterSnapshotsResult(TypedDict, total=False): - Resources: Optional[SnapshotIdentifierList] - Errors: Optional[BatchSnapshotOperationErrorList] + Resources: SnapshotIdentifierList | None + Errors: BatchSnapshotOperationErrorList | None class BatchModifyClusterSnapshotsMessage(ServiceRequest): SnapshotIdentifierList: SnapshotIdentifierList - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] - Force: Optional[Boolean] + ManualSnapshotRetentionPeriod: IntegerOptional | None + Force: Boolean | None -BatchSnapshotOperationErrors = List[SnapshotErrorMessage] +BatchSnapshotOperationErrors = list[SnapshotErrorMessage] class BatchModifyClusterSnapshotsOutputMessage(TypedDict, total=False): - Resources: Optional[SnapshotIdentifierList] - Errors: Optional[BatchSnapshotOperationErrors] + Resources: SnapshotIdentifierList | None + Errors: BatchSnapshotOperationErrors | None class CancelResizeMessage(ServiceRequest): @@ -1399,380 +1422,388 @@ class CancelResizeMessage(ServiceRequest): class ClusterNode(TypedDict, total=False): - NodeRole: Optional[String] - PrivateIPAddress: Optional[String] - PublicIPAddress: Optional[String] + NodeRole: String | None + PrivateIPAddress: String | None + PublicIPAddress: String | None -ClusterNodesList = List[ClusterNode] +ClusterNodesList = list[ClusterNode] class SecondaryClusterInfo(TypedDict, total=False): - AvailabilityZone: Optional[String] - ClusterNodes: Optional[ClusterNodesList] + AvailabilityZone: String | None + ClusterNodes: ClusterNodesList | None class ReservedNodeExchangeStatus(TypedDict, total=False): - ReservedNodeExchangeRequestId: Optional[String] - Status: Optional[ReservedNodeExchangeStatusType] - RequestTime: Optional[TStamp] - SourceReservedNodeId: Optional[String] - SourceReservedNodeType: Optional[String] - SourceReservedNodeCount: Optional[Integer] - TargetReservedNodeOfferingId: Optional[String] - TargetReservedNodeType: Optional[String] - TargetReservedNodeCount: Optional[Integer] + ReservedNodeExchangeRequestId: String | None + Status: ReservedNodeExchangeStatusType | None + RequestTime: TStamp | None + SourceReservedNodeId: String | None + SourceReservedNodeType: String | None + SourceReservedNodeCount: Integer | None + TargetReservedNodeOfferingId: String | None + TargetReservedNodeType: String | None + TargetReservedNodeCount: Integer | None LongOptional = int class ResizeInfo(TypedDict, total=False): - ResizeType: Optional[String] - AllowCancelResize: Optional[Boolean] + ResizeType: String | None + AllowCancelResize: Boolean | None class DeferredMaintenanceWindow(TypedDict, total=False): - DeferMaintenanceIdentifier: Optional[String] - DeferMaintenanceStartTime: Optional[TStamp] - DeferMaintenanceEndTime: Optional[TStamp] + DeferMaintenanceIdentifier: String | None + DeferMaintenanceStartTime: TStamp | None + DeferMaintenanceEndTime: TStamp | None -DeferredMaintenanceWindowsList = List[DeferredMaintenanceWindow] -PendingActionsList = List[String] +DeferredMaintenanceWindowsList = list[DeferredMaintenanceWindow] +PendingActionsList = list[String] class ClusterIamRole(TypedDict, total=False): - IamRoleArn: Optional[String] - ApplyStatus: Optional[String] + IamRoleArn: String | None + ApplyStatus: String | None -ClusterIamRoleList = List[ClusterIamRole] +ClusterIamRoleList = list[ClusterIamRole] class ElasticIpStatus(TypedDict, total=False): - ElasticIp: Optional[String] - Status: Optional[String] + ElasticIp: String | None + Status: String | None class ClusterSnapshotCopyStatus(TypedDict, total=False): - DestinationRegion: Optional[String] - RetentionPeriod: Optional[Long] - ManualSnapshotRetentionPeriod: Optional[Integer] - SnapshotCopyGrantName: Optional[String] + DestinationRegion: String | None + RetentionPeriod: Long | None + ManualSnapshotRetentionPeriod: Integer | None + SnapshotCopyGrantName: String | None class HsmStatus(TypedDict, total=False): - HsmClientCertificateIdentifier: Optional[String] - HsmConfigurationIdentifier: Optional[String] - Status: Optional[String] + HsmClientCertificateIdentifier: String | None + HsmConfigurationIdentifier: String | None + Status: String | None class DataTransferProgress(TypedDict, total=False): - Status: Optional[String] - CurrentRateInMegaBytesPerSecond: Optional[DoubleOptional] - TotalDataInMegaBytes: Optional[Long] - DataTransferredInMegaBytes: Optional[Long] - EstimatedTimeToCompletionInSeconds: Optional[LongOptional] - ElapsedTimeInSeconds: Optional[LongOptional] + Status: String | None + CurrentRateInMegaBytesPerSecond: DoubleOptional | None + TotalDataInMegaBytes: Long | None + DataTransferredInMegaBytes: Long | None + EstimatedTimeToCompletionInSeconds: LongOptional | None + ElapsedTimeInSeconds: LongOptional | None class RestoreStatus(TypedDict, total=False): - Status: Optional[String] - CurrentRestoreRateInMegaBytesPerSecond: Optional[Double] - SnapshotSizeInMegaBytes: Optional[Long] - ProgressInMegaBytes: Optional[Long] - ElapsedTimeInSeconds: Optional[Long] - EstimatedTimeToCompletionInSeconds: Optional[Long] + Status: String | None + CurrentRestoreRateInMegaBytesPerSecond: Double | None + SnapshotSizeInMegaBytes: Long | None + ProgressInMegaBytes: Long | None + ElapsedTimeInSeconds: Long | None + EstimatedTimeToCompletionInSeconds: Long | None class PendingModifiedValues(TypedDict, total=False): - MasterUserPassword: Optional[SensitiveString] - NodeType: Optional[String] - NumberOfNodes: Optional[IntegerOptional] - ClusterType: Optional[String] - ClusterVersion: Optional[String] - AutomatedSnapshotRetentionPeriod: Optional[IntegerOptional] - ClusterIdentifier: Optional[String] - PubliclyAccessible: Optional[BooleanOptional] - EnhancedVpcRouting: Optional[BooleanOptional] - MaintenanceTrackName: Optional[String] - EncryptionType: Optional[String] + MasterUserPassword: SensitiveString | None + NodeType: String | None + NumberOfNodes: IntegerOptional | None + ClusterType: String | None + ClusterVersion: String | None + AutomatedSnapshotRetentionPeriod: IntegerOptional | None + ClusterIdentifier: String | None + PubliclyAccessible: BooleanOptional | None + EnhancedVpcRouting: BooleanOptional | None + MaintenanceTrackName: String | None + EncryptionType: String | None class ClusterParameterStatus(TypedDict, total=False): - ParameterName: Optional[String] - ParameterApplyStatus: Optional[String] - ParameterApplyErrorDescription: Optional[String] + ParameterName: String | None + ParameterApplyStatus: String | None + ParameterApplyErrorDescription: String | None -ClusterParameterStatusList = List[ClusterParameterStatus] +ClusterParameterStatusList = list[ClusterParameterStatus] class ClusterParameterGroupStatus(TypedDict, total=False): - ParameterGroupName: Optional[String] - ParameterApplyStatus: Optional[String] - ClusterParameterStatusList: Optional[ClusterParameterStatusList] + ParameterGroupName: String | None + ParameterApplyStatus: String | None + ClusterParameterStatusList: ClusterParameterStatusList | None -ClusterParameterGroupStatusList = List[ClusterParameterGroupStatus] +ClusterParameterGroupStatusList = list[ClusterParameterGroupStatus] class VpcSecurityGroupMembership(TypedDict, total=False): - VpcSecurityGroupId: Optional[String] - Status: Optional[String] + VpcSecurityGroupId: String | None + Status: String | None -VpcSecurityGroupMembershipList = List[VpcSecurityGroupMembership] +VpcSecurityGroupMembershipList = list[VpcSecurityGroupMembership] class ClusterSecurityGroupMembership(TypedDict, total=False): - ClusterSecurityGroupName: Optional[String] - Status: Optional[String] + ClusterSecurityGroupName: String | None + Status: String | None -ClusterSecurityGroupMembershipList = List[ClusterSecurityGroupMembership] +ClusterSecurityGroupMembershipList = list[ClusterSecurityGroupMembership] class NetworkInterface(TypedDict, total=False): - NetworkInterfaceId: Optional[String] - SubnetId: Optional[String] - PrivateIpAddress: Optional[String] - AvailabilityZone: Optional[String] - Ipv6Address: Optional[String] + NetworkInterfaceId: String | None + SubnetId: String | None + PrivateIpAddress: String | None + AvailabilityZone: String | None + Ipv6Address: String | None -NetworkInterfaceList = List[NetworkInterface] +NetworkInterfaceList = list[NetworkInterface] class VpcEndpoint(TypedDict, total=False): - VpcEndpointId: Optional[String] - VpcId: Optional[String] - NetworkInterfaces: Optional[NetworkInterfaceList] + VpcEndpointId: String | None + VpcId: String | None + NetworkInterfaces: NetworkInterfaceList | None -VpcEndpointsList = List[VpcEndpoint] +VpcEndpointsList = list[VpcEndpoint] class Endpoint(TypedDict, total=False): - Address: Optional[String] - Port: Optional[Integer] - VpcEndpoints: Optional[VpcEndpointsList] + Address: String | None + Port: Integer | None + VpcEndpoints: VpcEndpointsList | None class Cluster(TypedDict, total=False): - ClusterIdentifier: Optional[String] - NodeType: Optional[String] - ClusterStatus: Optional[String] - ClusterAvailabilityStatus: Optional[String] - ModifyStatus: Optional[String] - MasterUsername: Optional[String] - DBName: Optional[String] - Endpoint: Optional[Endpoint] - ClusterCreateTime: Optional[TStamp] - AutomatedSnapshotRetentionPeriod: Optional[Integer] - ManualSnapshotRetentionPeriod: Optional[Integer] - ClusterSecurityGroups: Optional[ClusterSecurityGroupMembershipList] - VpcSecurityGroups: Optional[VpcSecurityGroupMembershipList] - ClusterParameterGroups: Optional[ClusterParameterGroupStatusList] - ClusterSubnetGroupName: Optional[String] - VpcId: Optional[String] - AvailabilityZone: Optional[String] - PreferredMaintenanceWindow: Optional[String] - PendingModifiedValues: Optional[PendingModifiedValues] - ClusterVersion: Optional[String] - AllowVersionUpgrade: Optional[Boolean] - NumberOfNodes: Optional[Integer] - PubliclyAccessible: Optional[Boolean] - Encrypted: Optional[Boolean] - RestoreStatus: Optional[RestoreStatus] - DataTransferProgress: Optional[DataTransferProgress] - HsmStatus: Optional[HsmStatus] - ClusterSnapshotCopyStatus: Optional[ClusterSnapshotCopyStatus] - ClusterPublicKey: Optional[String] - ClusterNodes: Optional[ClusterNodesList] - ElasticIpStatus: Optional[ElasticIpStatus] - ClusterRevisionNumber: Optional[String] - Tags: Optional[TagList] - KmsKeyId: Optional[String] - EnhancedVpcRouting: Optional[Boolean] - IamRoles: Optional[ClusterIamRoleList] - PendingActions: Optional[PendingActionsList] - MaintenanceTrackName: Optional[String] - ElasticResizeNumberOfNodeOptions: Optional[String] - DeferredMaintenanceWindows: Optional[DeferredMaintenanceWindowsList] - SnapshotScheduleIdentifier: Optional[String] - SnapshotScheduleState: Optional[ScheduleState] - ExpectedNextSnapshotScheduleTime: Optional[TStamp] - ExpectedNextSnapshotScheduleTimeStatus: Optional[String] - NextMaintenanceWindowStartTime: Optional[TStamp] - ResizeInfo: Optional[ResizeInfo] - AvailabilityZoneRelocationStatus: Optional[String] - ClusterNamespaceArn: Optional[String] - TotalStorageCapacityInMegaBytes: Optional[LongOptional] - AquaConfiguration: Optional[AquaConfiguration] - DefaultIamRoleArn: Optional[String] - ReservedNodeExchangeStatus: Optional[ReservedNodeExchangeStatus] - CustomDomainName: Optional[String] - CustomDomainCertificateArn: Optional[String] - CustomDomainCertificateExpiryDate: Optional[TStamp] - MasterPasswordSecretArn: Optional[String] - MasterPasswordSecretKmsKeyId: Optional[String] - IpAddressType: Optional[String] - MultiAZ: Optional[String] - MultiAZSecondary: Optional[SecondaryClusterInfo] + ClusterIdentifier: String | None + NodeType: String | None + ClusterStatus: String | None + ClusterAvailabilityStatus: String | None + ModifyStatus: String | None + MasterUsername: String | None + DBName: String | None + Endpoint: Endpoint | None + ClusterCreateTime: TStamp | None + AutomatedSnapshotRetentionPeriod: Integer | None + ManualSnapshotRetentionPeriod: Integer | None + ClusterSecurityGroups: ClusterSecurityGroupMembershipList | None + VpcSecurityGroups: VpcSecurityGroupMembershipList | None + ClusterParameterGroups: ClusterParameterGroupStatusList | None + ClusterSubnetGroupName: String | None + VpcId: String | None + AvailabilityZone: String | None + PreferredMaintenanceWindow: String | None + PendingModifiedValues: PendingModifiedValues | None + ClusterVersion: String | None + AllowVersionUpgrade: Boolean | None + NumberOfNodes: Integer | None + PubliclyAccessible: Boolean | None + Encrypted: Boolean | None + RestoreStatus: RestoreStatus | None + DataTransferProgress: DataTransferProgress | None + HsmStatus: HsmStatus | None + ClusterSnapshotCopyStatus: ClusterSnapshotCopyStatus | None + ClusterPublicKey: String | None + ClusterNodes: ClusterNodesList | None + ElasticIpStatus: ElasticIpStatus | None + ClusterRevisionNumber: String | None + Tags: TagList | None + KmsKeyId: String | None + EnhancedVpcRouting: Boolean | None + IamRoles: ClusterIamRoleList | None + PendingActions: PendingActionsList | None + MaintenanceTrackName: String | None + ElasticResizeNumberOfNodeOptions: String | None + DeferredMaintenanceWindows: DeferredMaintenanceWindowsList | None + SnapshotScheduleIdentifier: String | None + SnapshotScheduleState: ScheduleState | None + ExpectedNextSnapshotScheduleTime: TStamp | None + ExpectedNextSnapshotScheduleTimeStatus: String | None + NextMaintenanceWindowStartTime: TStamp | None + ResizeInfo: ResizeInfo | None + AvailabilityZoneRelocationStatus: String | None + ClusterNamespaceArn: String | None + TotalStorageCapacityInMegaBytes: LongOptional | None + AquaConfiguration: AquaConfiguration | None + DefaultIamRoleArn: String | None + ReservedNodeExchangeStatus: ReservedNodeExchangeStatus | None + CustomDomainName: String | None + CustomDomainCertificateArn: String | None + CustomDomainCertificateExpiryDate: TStamp | None + MasterPasswordSecretArn: String | None + MasterPasswordSecretKmsKeyId: String | None + IpAddressType: String | None + MultiAZ: String | None + MultiAZSecondary: SecondaryClusterInfo | None + LakehouseRegistrationStatus: String | None + CatalogArn: String | None + ExtraComputeForAutomaticOptimization: String | None class ClusterCredentials(TypedDict, total=False): - DbUser: Optional[String] - DbPassword: Optional[SensitiveString] - Expiration: Optional[TStamp] + DbUser: String | None + DbPassword: SensitiveString | None + Expiration: TStamp | None class RevisionTarget(TypedDict, total=False): - DatabaseRevision: Optional[String] - Description: Optional[String] - DatabaseRevisionReleaseDate: Optional[TStamp] + DatabaseRevision: String | None + Description: String | None + DatabaseRevisionReleaseDate: TStamp | None -RevisionTargetsList = List[RevisionTarget] +RevisionTargetsList = list[RevisionTarget] class ClusterDbRevision(TypedDict, total=False): - ClusterIdentifier: Optional[String] - CurrentDatabaseRevision: Optional[String] - DatabaseRevisionReleaseDate: Optional[TStamp] - RevisionTargets: Optional[RevisionTargetsList] + ClusterIdentifier: String | None + CurrentDatabaseRevision: String | None + DatabaseRevisionReleaseDate: TStamp | None + RevisionTargets: RevisionTargetsList | None -ClusterDbRevisionsList = List[ClusterDbRevision] +ClusterDbRevisionsList = list[ClusterDbRevision] class ClusterDbRevisionsMessage(TypedDict, total=False): - Marker: Optional[String] - ClusterDbRevisions: Optional[ClusterDbRevisionsList] + Marker: String | None + ClusterDbRevisions: ClusterDbRevisionsList | None class ClusterExtendedCredentials(TypedDict, total=False): - DbUser: Optional[String] - DbPassword: Optional[SensitiveString] - Expiration: Optional[TStamp] - NextRefreshTime: Optional[TStamp] + DbUser: String | None + DbPassword: SensitiveString | None + Expiration: TStamp | None + NextRefreshTime: TStamp | None -ClusterList = List[Cluster] +ClusterIdentifierList = list[String] +ClusterList = list[Cluster] class ClusterParameterGroup(TypedDict, total=False): - ParameterGroupName: Optional[String] - ParameterGroupFamily: Optional[String] - Description: Optional[String] - Tags: Optional[TagList] + ParameterGroupName: String | None + ParameterGroupFamily: String | None + Description: String | None + Tags: TagList | None class Parameter(TypedDict, total=False): - ParameterName: Optional[String] - ParameterValue: Optional[String] - Description: Optional[String] - Source: Optional[String] - DataType: Optional[String] - AllowedValues: Optional[String] - ApplyType: Optional[ParameterApplyType] - IsModifiable: Optional[Boolean] - MinimumEngineVersion: Optional[String] + ParameterName: String | None + ParameterValue: String | None + Description: String | None + Source: String | None + DataType: String | None + AllowedValues: String | None + ApplyType: ParameterApplyType | None + IsModifiable: Boolean | None + MinimumEngineVersion: String | None -ParametersList = List[Parameter] +ParametersList = list[Parameter] class ClusterParameterGroupDetails(TypedDict, total=False): - Parameters: Optional[ParametersList] - Marker: Optional[String] + Parameters: ParametersList | None + Marker: String | None class ClusterParameterGroupNameMessage(TypedDict, total=False): - ParameterGroupName: Optional[String] - ParameterGroupStatus: Optional[String] + ParameterGroupName: String | None + ParameterGroupStatus: String | None -ParameterGroupList = List[ClusterParameterGroup] +ParameterGroupList = list[ClusterParameterGroup] class ClusterParameterGroupsMessage(TypedDict, total=False): - Marker: Optional[String] - ParameterGroups: Optional[ParameterGroupList] + Marker: String | None + ParameterGroups: ParameterGroupList | None -ClusterSecurityGroups = List[ClusterSecurityGroup] +ClusterSecurityGroups = list[ClusterSecurityGroup] class ClusterSecurityGroupMessage(TypedDict, total=False): - Marker: Optional[String] - ClusterSecurityGroups: Optional[ClusterSecurityGroups] + Marker: String | None + ClusterSecurityGroups: ClusterSecurityGroups | None -ClusterSecurityGroupNameList = List[String] -ValueStringList = List[String] +ClusterSecurityGroupNameList = list[String] +ValueStringList = list[String] class Subnet(TypedDict, total=False): - SubnetIdentifier: Optional[String] - SubnetAvailabilityZone: Optional[AvailabilityZone] - SubnetStatus: Optional[String] + SubnetIdentifier: String | None + SubnetAvailabilityZone: AvailabilityZone | None + SubnetStatus: String | None -SubnetList = List[Subnet] +SubnetList = list[Subnet] class ClusterSubnetGroup(TypedDict, total=False): - ClusterSubnetGroupName: Optional[String] - Description: Optional[String] - VpcId: Optional[String] - SubnetGroupStatus: Optional[String] - Subnets: Optional[SubnetList] - Tags: Optional[TagList] - SupportedClusterIpAddressTypes: Optional[ValueStringList] + ClusterSubnetGroupName: String | None + Description: String | None + VpcId: String | None + SubnetGroupStatus: String | None + Subnets: SubnetList | None + Tags: TagList | None + SupportedClusterIpAddressTypes: ValueStringList | None -ClusterSubnetGroups = List[ClusterSubnetGroup] +ClusterSubnetGroups = list[ClusterSubnetGroup] class ClusterSubnetGroupMessage(TypedDict, total=False): - Marker: Optional[String] - ClusterSubnetGroups: Optional[ClusterSubnetGroups] + Marker: String | None + ClusterSubnetGroups: ClusterSubnetGroups | None class ClusterVersion(TypedDict, total=False): - ClusterVersion: Optional[String] - ClusterParameterGroupFamily: Optional[String] - Description: Optional[String] + ClusterVersion: String | None + ClusterParameterGroupFamily: String | None + Description: String | None -ClusterVersionList = List[ClusterVersion] +ClusterVersionList = list[ClusterVersion] class ClusterVersionsMessage(TypedDict, total=False): - Marker: Optional[String] - ClusterVersions: Optional[ClusterVersionList] + Marker: String | None + ClusterVersions: ClusterVersionList | None class ClustersMessage(TypedDict, total=False): - Marker: Optional[String] - Clusters: Optional[ClusterList] + Marker: String | None + Clusters: ClusterList | None -ConsumerIdentifierList = List[String] +class Connect(TypedDict, total=False): + Authorization: ServiceAuthorization + + +ConsumerIdentifierList = list[String] class CopyClusterSnapshotMessage(ServiceRequest): SourceSnapshotIdentifier: String - SourceSnapshotClusterIdentifier: Optional[String] + SourceSnapshotClusterIdentifier: String | None TargetSnapshotIdentifier: String - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] + ManualSnapshotRetentionPeriod: IntegerOptional | None class CopyClusterSnapshotResult(TypedDict, total=False): - Snapshot: Optional[Snapshot] + Snapshot: Snapshot | None class CreateAuthenticationProfileMessage(ServiceRequest): @@ -1781,104 +1812,106 @@ class CreateAuthenticationProfileMessage(ServiceRequest): class CreateAuthenticationProfileResult(TypedDict, total=False): - AuthenticationProfileName: Optional[AuthenticationProfileNameString] - AuthenticationProfileContent: Optional[String] + AuthenticationProfileName: AuthenticationProfileNameString | None + AuthenticationProfileContent: String | None -IamRoleArnList = List[String] -VpcSecurityGroupIdList = List[String] +IamRoleArnList = list[String] +VpcSecurityGroupIdList = list[String] class CreateClusterMessage(ServiceRequest): - DBName: Optional[String] + DBName: String | None ClusterIdentifier: String - ClusterType: Optional[String] + ClusterType: String | None NodeType: String MasterUsername: String - MasterUserPassword: Optional[SensitiveString] - ClusterSecurityGroups: Optional[ClusterSecurityGroupNameList] - VpcSecurityGroupIds: Optional[VpcSecurityGroupIdList] - ClusterSubnetGroupName: Optional[String] - AvailabilityZone: Optional[String] - PreferredMaintenanceWindow: Optional[String] - ClusterParameterGroupName: Optional[String] - AutomatedSnapshotRetentionPeriod: Optional[IntegerOptional] - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] - Port: Optional[IntegerOptional] - ClusterVersion: Optional[String] - AllowVersionUpgrade: Optional[BooleanOptional] - NumberOfNodes: Optional[IntegerOptional] - PubliclyAccessible: Optional[BooleanOptional] - Encrypted: Optional[BooleanOptional] - HsmClientCertificateIdentifier: Optional[String] - HsmConfigurationIdentifier: Optional[String] - ElasticIp: Optional[String] - Tags: Optional[TagList] - KmsKeyId: Optional[String] - EnhancedVpcRouting: Optional[BooleanOptional] - AdditionalInfo: Optional[String] - IamRoles: Optional[IamRoleArnList] - MaintenanceTrackName: Optional[String] - SnapshotScheduleIdentifier: Optional[String] - AvailabilityZoneRelocation: Optional[BooleanOptional] - AquaConfigurationStatus: Optional[AquaConfigurationStatus] - DefaultIamRoleArn: Optional[String] - LoadSampleData: Optional[String] - ManageMasterPassword: Optional[BooleanOptional] - MasterPasswordSecretKmsKeyId: Optional[String] - IpAddressType: Optional[String] - MultiAZ: Optional[BooleanOptional] - RedshiftIdcApplicationArn: Optional[String] + MasterUserPassword: SensitiveString | None + ClusterSecurityGroups: ClusterSecurityGroupNameList | None + VpcSecurityGroupIds: VpcSecurityGroupIdList | None + ClusterSubnetGroupName: String | None + AvailabilityZone: String | None + PreferredMaintenanceWindow: String | None + ClusterParameterGroupName: String | None + AutomatedSnapshotRetentionPeriod: IntegerOptional | None + ManualSnapshotRetentionPeriod: IntegerOptional | None + Port: IntegerOptional | None + ClusterVersion: String | None + AllowVersionUpgrade: BooleanOptional | None + NumberOfNodes: IntegerOptional | None + PubliclyAccessible: BooleanOptional | None + Encrypted: BooleanOptional | None + HsmClientCertificateIdentifier: String | None + HsmConfigurationIdentifier: String | None + ElasticIp: String | None + Tags: TagList | None + KmsKeyId: String | None + EnhancedVpcRouting: BooleanOptional | None + AdditionalInfo: String | None + IamRoles: IamRoleArnList | None + MaintenanceTrackName: String | None + SnapshotScheduleIdentifier: String | None + AvailabilityZoneRelocation: BooleanOptional | None + AquaConfigurationStatus: AquaConfigurationStatus | None + DefaultIamRoleArn: String | None + LoadSampleData: String | None + ManageMasterPassword: BooleanOptional | None + MasterPasswordSecretKmsKeyId: String | None + IpAddressType: String | None + MultiAZ: BooleanOptional | None + RedshiftIdcApplicationArn: String | None + CatalogName: CatalogNameString | None + ExtraComputeForAutomaticOptimization: BooleanOptional | None class CreateClusterParameterGroupMessage(ServiceRequest): ParameterGroupName: String ParameterGroupFamily: String Description: String - Tags: Optional[TagList] + Tags: TagList | None class CreateClusterParameterGroupResult(TypedDict, total=False): - ClusterParameterGroup: Optional[ClusterParameterGroup] + ClusterParameterGroup: ClusterParameterGroup | None class CreateClusterResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class CreateClusterSecurityGroupMessage(ServiceRequest): ClusterSecurityGroupName: String Description: String - Tags: Optional[TagList] + Tags: TagList | None class CreateClusterSecurityGroupResult(TypedDict, total=False): - ClusterSecurityGroup: Optional[ClusterSecurityGroup] + ClusterSecurityGroup: ClusterSecurityGroup | None class CreateClusterSnapshotMessage(ServiceRequest): SnapshotIdentifier: String ClusterIdentifier: String - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] - Tags: Optional[TagList] + ManualSnapshotRetentionPeriod: IntegerOptional | None + Tags: TagList | None class CreateClusterSnapshotResult(TypedDict, total=False): - Snapshot: Optional[Snapshot] + Snapshot: Snapshot | None -SubnetIdentifierList = List[String] +SubnetIdentifierList = list[String] class CreateClusterSubnetGroupMessage(ServiceRequest): ClusterSubnetGroupName: String Description: String SubnetIds: SubnetIdentifierList - Tags: Optional[TagList] + Tags: TagList | None class CreateClusterSubnetGroupResult(TypedDict, total=False): - ClusterSubnetGroup: Optional[ClusterSubnetGroup] + ClusterSubnetGroup: ClusterSubnetGroup | None class CreateCustomDomainAssociationMessage(ServiceRequest): @@ -1888,66 +1921,66 @@ class CreateCustomDomainAssociationMessage(ServiceRequest): class CreateCustomDomainAssociationResult(TypedDict, total=False): - CustomDomainName: Optional[CustomDomainNameString] - CustomDomainCertificateArn: Optional[CustomDomainCertificateArnString] - ClusterIdentifier: Optional[String] - CustomDomainCertExpiryTime: Optional[String] + CustomDomainName: CustomDomainNameString | None + CustomDomainCertificateArn: CustomDomainCertificateArnString | None + ClusterIdentifier: String | None + CustomDomainCertExpiryTime: String | None class CreateEndpointAccessMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - ResourceOwner: Optional[String] + ClusterIdentifier: String | None + ResourceOwner: String | None EndpointName: String SubnetGroupName: String - VpcSecurityGroupIds: Optional[VpcSecurityGroupIdList] + VpcSecurityGroupIds: VpcSecurityGroupIdList | None -EventCategoriesList = List[String] -SourceIdsList = List[String] +EventCategoriesList = list[String] +SourceIdsList = list[String] class CreateEventSubscriptionMessage(ServiceRequest): SubscriptionName: String SnsTopicArn: String - SourceType: Optional[String] - SourceIds: Optional[SourceIdsList] - EventCategories: Optional[EventCategoriesList] - Severity: Optional[String] - Enabled: Optional[BooleanOptional] - Tags: Optional[TagList] + SourceType: String | None + SourceIds: SourceIdsList | None + EventCategories: EventCategoriesList | None + Severity: String | None + Enabled: BooleanOptional | None + Tags: TagList | None class EventSubscription(TypedDict, total=False): - CustomerAwsId: Optional[String] - CustSubscriptionId: Optional[String] - SnsTopicArn: Optional[String] - Status: Optional[String] - SubscriptionCreationTime: Optional[TStamp] - SourceType: Optional[String] - SourceIdsList: Optional[SourceIdsList] - EventCategoriesList: Optional[EventCategoriesList] - Severity: Optional[String] - Enabled: Optional[Boolean] - Tags: Optional[TagList] + CustomerAwsId: String | None + CustSubscriptionId: String | None + SnsTopicArn: String | None + Status: String | None + SubscriptionCreationTime: TStamp | None + SourceType: String | None + SourceIdsList: SourceIdsList | None + EventCategoriesList: EventCategoriesList | None + Severity: String | None + Enabled: Boolean | None + Tags: TagList | None class CreateEventSubscriptionResult(TypedDict, total=False): - EventSubscription: Optional[EventSubscription] + EventSubscription: EventSubscription | None class CreateHsmClientCertificateMessage(ServiceRequest): HsmClientCertificateIdentifier: String - Tags: Optional[TagList] + Tags: TagList | None class HsmClientCertificate(TypedDict, total=False): - HsmClientCertificateIdentifier: Optional[String] - HsmClientCertificatePublicKey: Optional[String] - Tags: Optional[TagList] + HsmClientCertificateIdentifier: String | None + HsmClientCertificatePublicKey: String | None + Tags: TagList | None class CreateHsmClientCertificateResult(TypedDict, total=False): - HsmClientCertificate: Optional[HsmClientCertificate] + HsmClientCertificate: HsmClientCertificate | None class CreateHsmConfigurationMessage(ServiceRequest): @@ -1957,32 +1990,42 @@ class CreateHsmConfigurationMessage(ServiceRequest): HsmPartitionName: String HsmPartitionPassword: String HsmServerPublicCertificate: String - Tags: Optional[TagList] + Tags: TagList | None class HsmConfiguration(TypedDict, total=False): - HsmConfigurationIdentifier: Optional[String] - Description: Optional[String] - HsmIpAddress: Optional[String] - HsmPartitionName: Optional[String] - Tags: Optional[TagList] + HsmConfigurationIdentifier: String | None + Description: String | None + HsmIpAddress: String | None + HsmPartitionName: String | None + Tags: TagList | None class CreateHsmConfigurationResult(TypedDict, total=False): - HsmConfiguration: Optional[HsmConfiguration] + HsmConfiguration: HsmConfiguration | None -EncryptionContextMap = Dict[String, String] +EncryptionContextMap = dict[String, String] class CreateIntegrationMessage(ServiceRequest): SourceArn: SourceArn TargetArn: TargetArn IntegrationName: IntegrationName - KMSKeyId: Optional[String] - TagList: Optional[TagList] - AdditionalEncryptionContext: Optional[EncryptionContextMap] - Description: Optional[IntegrationDescription] + KMSKeyId: String | None + TagList: TagList | None + AdditionalEncryptionContext: EncryptionContextMap | None + Description: IntegrationDescription | None + + +TagKeyList = list[String] + + +class RedshiftScopeUnion(TypedDict, total=False): + Connect: Connect | None + + +RedshiftServiceIntegrations = list[RedshiftScopeUnion] class ReadWriteAccess(TypedDict, total=False): @@ -1990,10 +2033,10 @@ class ReadWriteAccess(TypedDict, total=False): class S3AccessGrantsScopeUnion(TypedDict, total=False): - ReadWriteAccess: Optional[ReadWriteAccess] + ReadWriteAccess: ReadWriteAccess | None -S3AccessGrantsServiceIntegrations = List[S3AccessGrantsScopeUnion] +S3AccessGrantsServiceIntegrations = list[S3AccessGrantsScopeUnion] class LakeFormationQuery(TypedDict, total=False): @@ -2001,45 +2044,52 @@ class LakeFormationQuery(TypedDict, total=False): class LakeFormationScopeUnion(TypedDict, total=False): - LakeFormationQuery: Optional[LakeFormationQuery] + LakeFormationQuery: LakeFormationQuery | None -LakeFormationServiceIntegrations = List[LakeFormationScopeUnion] +LakeFormationServiceIntegrations = list[LakeFormationScopeUnion] class ServiceIntegrationsUnion(TypedDict, total=False): - LakeFormation: Optional[LakeFormationServiceIntegrations] - S3AccessGrants: Optional[S3AccessGrantsServiceIntegrations] + LakeFormation: LakeFormationServiceIntegrations | None + S3AccessGrants: S3AccessGrantsServiceIntegrations | None + Redshift: RedshiftServiceIntegrations | None -ServiceIntegrationList = List[ServiceIntegrationsUnion] +ServiceIntegrationList = list[ServiceIntegrationsUnion] class CreateRedshiftIdcApplicationMessage(ServiceRequest): IdcInstanceArn: String RedshiftIdcApplicationName: RedshiftIdcApplicationName - IdentityNamespace: Optional[IdentityNamespaceString] + IdentityNamespace: IdentityNamespaceString | None IdcDisplayName: IdcDisplayNameString IamRoleArn: String - AuthorizedTokenIssuerList: Optional[AuthorizedTokenIssuerList] - ServiceIntegrations: Optional[ServiceIntegrationList] + AuthorizedTokenIssuerList: AuthorizedTokenIssuerList | None + ServiceIntegrations: ServiceIntegrationList | None + ApplicationType: ApplicationType | None + Tags: TagList | None + SsoTagKeys: TagKeyList | None class RedshiftIdcApplication(TypedDict, total=False): - IdcInstanceArn: Optional[String] - RedshiftIdcApplicationName: Optional[RedshiftIdcApplicationName] - RedshiftIdcApplicationArn: Optional[String] - IdentityNamespace: Optional[IdentityNamespaceString] - IdcDisplayName: Optional[IdcDisplayNameString] - IamRoleArn: Optional[String] - IdcManagedApplicationArn: Optional[String] - IdcOnboardStatus: Optional[String] - AuthorizedTokenIssuerList: Optional[AuthorizedTokenIssuerList] - ServiceIntegrations: Optional[ServiceIntegrationList] + IdcInstanceArn: String | None + RedshiftIdcApplicationName: RedshiftIdcApplicationName | None + RedshiftIdcApplicationArn: String | None + IdentityNamespace: IdentityNamespaceString | None + IdcDisplayName: IdcDisplayNameString | None + IamRoleArn: String | None + IdcManagedApplicationArn: String | None + IdcOnboardStatus: String | None + AuthorizedTokenIssuerList: AuthorizedTokenIssuerList | None + ServiceIntegrations: ServiceIntegrationList | None + ApplicationType: ApplicationType | None + Tags: TagList | None + SsoTagKeys: TagKeyList | None class CreateRedshiftIdcApplicationResult(TypedDict, total=False): - RedshiftIdcApplication: Optional[RedshiftIdcApplication] + RedshiftIdcApplication: RedshiftIdcApplication | None class ResumeClusterMessage(ServiceRequest): @@ -2052,18 +2102,18 @@ class PauseClusterMessage(ServiceRequest): class ResizeClusterMessage(ServiceRequest): ClusterIdentifier: String - ClusterType: Optional[String] - NodeType: Optional[String] - NumberOfNodes: Optional[IntegerOptional] - Classic: Optional[BooleanOptional] - ReservedNodeId: Optional[String] - TargetReservedNodeOfferingId: Optional[String] + ClusterType: String | None + NodeType: String | None + NumberOfNodes: IntegerOptional | None + Classic: BooleanOptional | None + ReservedNodeId: String | None + TargetReservedNodeOfferingId: String | None class ScheduledActionType(TypedDict, total=False): - ResizeCluster: Optional[ResizeClusterMessage] - PauseCluster: Optional[PauseClusterMessage] - ResumeCluster: Optional[ResumeClusterMessage] + ResizeCluster: ResizeClusterMessage | None + PauseCluster: PauseClusterMessage | None + ResumeCluster: ResumeClusterMessage | None class CreateScheduledActionMessage(ServiceRequest): @@ -2071,38 +2121,38 @@ class CreateScheduledActionMessage(ServiceRequest): TargetAction: ScheduledActionType Schedule: String IamRole: String - ScheduledActionDescription: Optional[String] - StartTime: Optional[TStamp] - EndTime: Optional[TStamp] - Enable: Optional[BooleanOptional] + ScheduledActionDescription: String | None + StartTime: TStamp | None + EndTime: TStamp | None + Enable: BooleanOptional | None class CreateSnapshotCopyGrantMessage(ServiceRequest): SnapshotCopyGrantName: String - KmsKeyId: Optional[String] - Tags: Optional[TagList] + KmsKeyId: String | None + Tags: TagList | None class SnapshotCopyGrant(TypedDict, total=False): - SnapshotCopyGrantName: Optional[String] - KmsKeyId: Optional[String] - Tags: Optional[TagList] + SnapshotCopyGrantName: String | None + KmsKeyId: String | None + Tags: TagList | None class CreateSnapshotCopyGrantResult(TypedDict, total=False): - SnapshotCopyGrant: Optional[SnapshotCopyGrant] + SnapshotCopyGrant: SnapshotCopyGrant | None -ScheduleDefinitionList = List[String] +ScheduleDefinitionList = list[String] class CreateSnapshotScheduleMessage(ServiceRequest): - ScheduleDefinitions: Optional[ScheduleDefinitionList] - ScheduleIdentifier: Optional[String] - ScheduleDescription: Optional[String] - Tags: Optional[TagList] - DryRun: Optional[BooleanOptional] - NextInvocations: Optional[IntegerOptional] + ScheduleDefinitions: ScheduleDefinitionList | None + ScheduleIdentifier: String | None + ScheduleDescription: String | None + Tags: TagList | None + DryRun: BooleanOptional | None + NextInvocations: IntegerOptional | None class CreateTagsMessage(ServiceRequest): @@ -2115,45 +2165,45 @@ class CreateUsageLimitMessage(ServiceRequest): FeatureType: UsageLimitFeatureType LimitType: UsageLimitLimitType Amount: Long - Period: Optional[UsageLimitPeriod] - BreachAction: Optional[UsageLimitBreachAction] - Tags: Optional[TagList] + Period: UsageLimitPeriod | None + BreachAction: UsageLimitBreachAction | None + Tags: TagList | None class CustomDomainAssociationsMessage(TypedDict, total=False): - Marker: Optional[String] - Associations: Optional[AssociationList] + Marker: String | None + Associations: AssociationList | None class CustomerStorageMessage(TypedDict, total=False): - TotalBackupSizeInMegaBytes: Optional[Double] - TotalProvisionedStorageInMegaBytes: Optional[Double] + TotalBackupSizeInMegaBytes: Double | None + TotalProvisionedStorageInMegaBytes: Double | None class DataShareAssociation(TypedDict, total=False): - ConsumerIdentifier: Optional[String] - Status: Optional[DataShareStatus] - ConsumerRegion: Optional[String] - CreatedDate: Optional[TStamp] - StatusChangeDate: Optional[TStamp] - ProducerAllowedWrites: Optional[BooleanOptional] - ConsumerAcceptedWrites: Optional[BooleanOptional] + ConsumerIdentifier: String | None + Status: DataShareStatus | None + ConsumerRegion: String | None + CreatedDate: TStamp | None + StatusChangeDate: TStamp | None + ProducerAllowedWrites: BooleanOptional | None + ConsumerAcceptedWrites: BooleanOptional | None -DataShareAssociationList = List[DataShareAssociation] +DataShareAssociationList = list[DataShareAssociation] class DataShare(TypedDict, total=False): - DataShareArn: Optional[String] - ProducerArn: Optional[String] - AllowPubliclyAccessibleConsumers: Optional[Boolean] - DataShareAssociations: Optional[DataShareAssociationList] - ManagedBy: Optional[String] - DataShareType: Optional[DataShareType] + DataShareArn: String | None + ProducerArn: String | None + AllowPubliclyAccessibleConsumers: Boolean | None + DataShareAssociations: DataShareAssociationList | None + ManagedBy: String | None + DataShareType: DataShareType | None -DataShareList = List[DataShare] -DbGroupList = List[String] +DataShareList = list[DataShare] +DbGroupList = list[String] class DeauthorizeDataShareMessage(ServiceRequest): @@ -2162,9 +2212,9 @@ class DeauthorizeDataShareMessage(ServiceRequest): class DefaultClusterParameters(TypedDict, total=False): - ParameterGroupFamily: Optional[String] - Marker: Optional[String] - Parameters: Optional[ParametersList] + ParameterGroupFamily: String | None + Marker: String | None + Parameters: ParametersList | None class DeleteAuthenticationProfileMessage(ServiceRequest): @@ -2172,14 +2222,14 @@ class DeleteAuthenticationProfileMessage(ServiceRequest): class DeleteAuthenticationProfileResult(TypedDict, total=False): - AuthenticationProfileName: Optional[AuthenticationProfileNameString] + AuthenticationProfileName: AuthenticationProfileNameString | None class DeleteClusterMessage(ServiceRequest): ClusterIdentifier: String - SkipFinalClusterSnapshot: Optional[Boolean] - FinalClusterSnapshotIdentifier: Optional[String] - FinalClusterSnapshotRetentionPeriod: Optional[IntegerOptional] + SkipFinalClusterSnapshot: Boolean | None + FinalClusterSnapshotIdentifier: String | None + FinalClusterSnapshotRetentionPeriod: IntegerOptional | None class DeleteClusterParameterGroupMessage(ServiceRequest): @@ -2187,7 +2237,7 @@ class DeleteClusterParameterGroupMessage(ServiceRequest): class DeleteClusterResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class DeleteClusterSecurityGroupMessage(ServiceRequest): @@ -2195,7 +2245,7 @@ class DeleteClusterSecurityGroupMessage(ServiceRequest): class DeleteClusterSnapshotResult(TypedDict, total=False): - Snapshot: Optional[Snapshot] + Snapshot: Snapshot | None class DeleteClusterSubnetGroupMessage(ServiceRequest): @@ -2247,9 +2297,6 @@ class DeleteSnapshotScheduleMessage(ServiceRequest): ScheduleIdentifier: String -TagKeyList = List[String] - - class DeleteTagsMessage(ServiceRequest): ResourceName: String TagKeys: TagKeyList @@ -2269,8 +2316,8 @@ class ServerlessIdentifier(TypedDict, total=False): class NamespaceIdentifierUnion(TypedDict, total=False): - ServerlessIdentifier: Optional[ServerlessIdentifier] - ProvisionedIdentifier: Optional[ProvisionedIdentifier] + ServerlessIdentifier: ServerlessIdentifier | None + ProvisionedIdentifier: ProvisionedIdentifier | None class DeregisterNamespaceInputMessage(ServiceRequest): @@ -2279,221 +2326,221 @@ class DeregisterNamespaceInputMessage(ServiceRequest): class DeregisterNamespaceOutputMessage(TypedDict, total=False): - Status: Optional[NamespaceRegistrationStatus] + Status: NamespaceRegistrationStatus | None class DescribeAccountAttributesMessage(ServiceRequest): - AttributeNames: Optional[AttributeNameList] + AttributeNames: AttributeNameList | None class DescribeAuthenticationProfilesMessage(ServiceRequest): - AuthenticationProfileName: Optional[AuthenticationProfileNameString] + AuthenticationProfileName: AuthenticationProfileNameString | None class DescribeAuthenticationProfilesResult(TypedDict, total=False): - AuthenticationProfiles: Optional[AuthenticationProfileList] + AuthenticationProfiles: AuthenticationProfileList | None class DescribeClusterDbRevisionsMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterIdentifier: String | None + MaxRecords: IntegerOptional | None + Marker: String | None -TagValueList = List[String] +TagValueList = list[String] class DescribeClusterParameterGroupsMessage(ServiceRequest): - ParameterGroupName: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + ParameterGroupName: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeClusterParametersMessage(ServiceRequest): ParameterGroupName: String - Source: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + Source: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeClusterSecurityGroupsMessage(ServiceRequest): - ClusterSecurityGroupName: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + ClusterSecurityGroupName: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class SnapshotSortingEntity(TypedDict, total=False): Attribute: SnapshotAttributeToSortBy - SortOrder: Optional[SortByOrder] + SortOrder: SortByOrder | None -SnapshotSortingEntityList = List[SnapshotSortingEntity] +SnapshotSortingEntityList = list[SnapshotSortingEntity] class DescribeClusterSnapshotsMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - SnapshotIdentifier: Optional[String] - SnapshotArn: Optional[String] - SnapshotType: Optional[String] - StartTime: Optional[TStamp] - EndTime: Optional[TStamp] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - OwnerAccount: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] - ClusterExists: Optional[BooleanOptional] - SortingEntities: Optional[SnapshotSortingEntityList] + ClusterIdentifier: String | None + SnapshotIdentifier: String | None + SnapshotArn: String | None + SnapshotType: String | None + StartTime: TStamp | None + EndTime: TStamp | None + MaxRecords: IntegerOptional | None + Marker: String | None + OwnerAccount: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None + ClusterExists: BooleanOptional | None + SortingEntities: SnapshotSortingEntityList | None class DescribeClusterSubnetGroupsMessage(ServiceRequest): - ClusterSubnetGroupName: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + ClusterSubnetGroupName: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeClusterTracksMessage(ServiceRequest): - MaintenanceTrackName: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + MaintenanceTrackName: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeClusterVersionsMessage(ServiceRequest): - ClusterVersion: Optional[String] - ClusterParameterGroupFamily: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterVersion: String | None + ClusterParameterGroupFamily: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeClustersMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + ClusterIdentifier: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeCustomDomainAssociationsMessage(ServiceRequest): - CustomDomainName: Optional[CustomDomainNameString] - CustomDomainCertificateArn: Optional[CustomDomainCertificateArnString] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + CustomDomainName: CustomDomainNameString | None + CustomDomainCertificateArn: CustomDomainCertificateArnString | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeDataSharesForConsumerMessage(ServiceRequest): - ConsumerArn: Optional[String] - Status: Optional[DataShareStatusForConsumer] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ConsumerArn: String | None + Status: DataShareStatusForConsumer | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeDataSharesForConsumerResult(TypedDict, total=False): - DataShares: Optional[DataShareList] - Marker: Optional[String] + DataShares: DataShareList | None + Marker: String | None class DescribeDataSharesForProducerMessage(ServiceRequest): - ProducerArn: Optional[String] - Status: Optional[DataShareStatusForProducer] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ProducerArn: String | None + Status: DataShareStatusForProducer | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeDataSharesForProducerResult(TypedDict, total=False): - DataShares: Optional[DataShareList] - Marker: Optional[String] + DataShares: DataShareList | None + Marker: String | None class DescribeDataSharesMessage(ServiceRequest): - DataShareArn: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + DataShareArn: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeDataSharesResult(TypedDict, total=False): - DataShares: Optional[DataShareList] - Marker: Optional[String] + DataShares: DataShareList | None + Marker: String | None class DescribeDefaultClusterParametersMessage(ServiceRequest): ParameterGroupFamily: String - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeDefaultClusterParametersResult(TypedDict, total=False): - DefaultClusterParameters: Optional[DefaultClusterParameters] + DefaultClusterParameters: DefaultClusterParameters | None class DescribeEndpointAccessMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - ResourceOwner: Optional[String] - EndpointName: Optional[String] - VpcId: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterIdentifier: String | None + ResourceOwner: String | None + EndpointName: String | None + VpcId: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeEndpointAuthorizationMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - Account: Optional[String] - Grantee: Optional[BooleanOptional] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterIdentifier: String | None + Account: String | None + Grantee: BooleanOptional | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeEventCategoriesMessage(ServiceRequest): - SourceType: Optional[String] + SourceType: String | None class DescribeEventSubscriptionsMessage(ServiceRequest): - SubscriptionName: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + SubscriptionName: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeEventsMessage(ServiceRequest): - SourceIdentifier: Optional[String] - SourceType: Optional[SourceType] - StartTime: Optional[TStamp] - EndTime: Optional[TStamp] - Duration: Optional[IntegerOptional] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + SourceIdentifier: String | None + SourceType: SourceType | None + StartTime: TStamp | None + EndTime: TStamp | None + Duration: IntegerOptional | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeHsmClientCertificatesMessage(ServiceRequest): - HsmClientCertificateIdentifier: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + HsmClientCertificateIdentifier: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeHsmConfigurationsMessage(ServiceRequest): - HsmConfigurationIdentifier: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + HsmConfigurationIdentifier: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeInboundIntegrationsMessage(ServiceRequest): - IntegrationArn: Optional[InboundIntegrationArn] - TargetArn: Optional[TargetArn] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + IntegrationArn: InboundIntegrationArn | None + TargetArn: TargetArn | None + MaxRecords: IntegerOptional | None + Marker: String | None -DescribeIntegrationsFilterValueList = List[String] +DescribeIntegrationsFilterValueList = list[String] class DescribeIntegrationsFilter(TypedDict, total=False): @@ -2501,14 +2548,14 @@ class DescribeIntegrationsFilter(TypedDict, total=False): Values: DescribeIntegrationsFilterValueList -DescribeIntegrationsFilterList = List[DescribeIntegrationsFilter] +DescribeIntegrationsFilterList = list[DescribeIntegrationsFilter] class DescribeIntegrationsMessage(ServiceRequest): - IntegrationArn: Optional[IntegrationArn] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - Filters: Optional[DescribeIntegrationsFilterList] + IntegrationArn: IntegrationArn | None + MaxRecords: IntegerOptional | None + Marker: String | None + Filters: DescribeIntegrationsFilterList | None class DescribeLoggingStatusMessage(ServiceRequest): @@ -2516,94 +2563,94 @@ class DescribeLoggingStatusMessage(ServiceRequest): class NodeConfigurationOptionsFilter(TypedDict, total=False): - Name: Optional[NodeConfigurationOptionsFilterName] - Operator: Optional[OperatorType] - Values: Optional[ValueStringList] + Name: NodeConfigurationOptionsFilterName | None + Operator: OperatorType | None + Values: ValueStringList | None -NodeConfigurationOptionsFilterList = List[NodeConfigurationOptionsFilter] +NodeConfigurationOptionsFilterList = list[NodeConfigurationOptionsFilter] class DescribeNodeConfigurationOptionsMessage(ServiceRequest): ActionType: ActionType - ClusterIdentifier: Optional[String] - SnapshotIdentifier: Optional[String] - SnapshotArn: Optional[String] - OwnerAccount: Optional[String] - Filters: Optional[NodeConfigurationOptionsFilterList] - Marker: Optional[String] - MaxRecords: Optional[IntegerOptional] + ClusterIdentifier: String | None + SnapshotIdentifier: String | None + SnapshotArn: String | None + OwnerAccount: String | None + Filters: NodeConfigurationOptionsFilterList | None + Marker: String | None + MaxRecords: IntegerOptional | None class DescribeOrderableClusterOptionsMessage(ServiceRequest): - ClusterVersion: Optional[String] - NodeType: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterVersion: String | None + NodeType: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribePartnersInputMessage(ServiceRequest): AccountId: PartnerIntegrationAccountId ClusterIdentifier: PartnerIntegrationClusterIdentifier - DatabaseName: Optional[PartnerIntegrationDatabaseName] - PartnerName: Optional[PartnerIntegrationPartnerName] + DatabaseName: PartnerIntegrationDatabaseName | None + PartnerName: PartnerIntegrationPartnerName | None class PartnerIntegrationInfo(TypedDict, total=False): - DatabaseName: Optional[PartnerIntegrationDatabaseName] - PartnerName: Optional[PartnerIntegrationPartnerName] - Status: Optional[PartnerIntegrationStatus] - StatusMessage: Optional[PartnerIntegrationStatusMessage] - CreatedAt: Optional[TStamp] - UpdatedAt: Optional[TStamp] + DatabaseName: PartnerIntegrationDatabaseName | None + PartnerName: PartnerIntegrationPartnerName | None + Status: PartnerIntegrationStatus | None + StatusMessage: PartnerIntegrationStatusMessage | None + CreatedAt: TStamp | None + UpdatedAt: TStamp | None -PartnerIntegrationInfoList = List[PartnerIntegrationInfo] +PartnerIntegrationInfoList = list[PartnerIntegrationInfo] class DescribePartnersOutputMessage(TypedDict, total=False): - PartnerIntegrationInfoList: Optional[PartnerIntegrationInfoList] + PartnerIntegrationInfoList: PartnerIntegrationInfoList | None class DescribeRedshiftIdcApplicationsMessage(ServiceRequest): - RedshiftIdcApplicationArn: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + RedshiftIdcApplicationArn: String | None + MaxRecords: IntegerOptional | None + Marker: String | None -RedshiftIdcApplicationList = List[RedshiftIdcApplication] +RedshiftIdcApplicationList = list[RedshiftIdcApplication] class DescribeRedshiftIdcApplicationsResult(TypedDict, total=False): - RedshiftIdcApplications: Optional[RedshiftIdcApplicationList] - Marker: Optional[String] + RedshiftIdcApplications: RedshiftIdcApplicationList | None + Marker: String | None class DescribeReservedNodeExchangeStatusInputMessage(ServiceRequest): - ReservedNodeId: Optional[String] - ReservedNodeExchangeRequestId: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ReservedNodeId: String | None + ReservedNodeExchangeRequestId: String | None + MaxRecords: IntegerOptional | None + Marker: String | None -ReservedNodeExchangeStatusList = List[ReservedNodeExchangeStatus] +ReservedNodeExchangeStatusList = list[ReservedNodeExchangeStatus] class DescribeReservedNodeExchangeStatusOutputMessage(TypedDict, total=False): - ReservedNodeExchangeStatusDetails: Optional[ReservedNodeExchangeStatusList] - Marker: Optional[String] + ReservedNodeExchangeStatusDetails: ReservedNodeExchangeStatusList | None + Marker: String | None class DescribeReservedNodeOfferingsMessage(ServiceRequest): - ReservedNodeOfferingId: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ReservedNodeOfferingId: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeReservedNodesMessage(ServiceRequest): - ReservedNodeId: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ReservedNodeId: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeResizeMessage(ServiceRequest): @@ -2615,82 +2662,82 @@ class ScheduledActionFilter(TypedDict, total=False): Values: ValueStringList -ScheduledActionFilterList = List[ScheduledActionFilter] +ScheduledActionFilterList = list[ScheduledActionFilter] class DescribeScheduledActionsMessage(ServiceRequest): - ScheduledActionName: Optional[String] - TargetActionType: Optional[ScheduledActionTypeValues] - StartTime: Optional[TStamp] - EndTime: Optional[TStamp] - Active: Optional[BooleanOptional] - Filters: Optional[ScheduledActionFilterList] - Marker: Optional[String] - MaxRecords: Optional[IntegerOptional] + ScheduledActionName: String | None + TargetActionType: ScheduledActionTypeValues | None + StartTime: TStamp | None + EndTime: TStamp | None + Active: BooleanOptional | None + Filters: ScheduledActionFilterList | None + Marker: String | None + MaxRecords: IntegerOptional | None class DescribeSnapshotCopyGrantsMessage(ServiceRequest): - SnapshotCopyGrantName: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + SnapshotCopyGrantName: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeSnapshotSchedulesMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - ScheduleIdentifier: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] - Marker: Optional[String] - MaxRecords: Optional[IntegerOptional] + ClusterIdentifier: String | None + ScheduleIdentifier: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None + Marker: String | None + MaxRecords: IntegerOptional | None -ScheduledSnapshotTimeList = List[TStamp] +ScheduledSnapshotTimeList = list[TStamp] class SnapshotSchedule(TypedDict, total=False): - ScheduleDefinitions: Optional[ScheduleDefinitionList] - ScheduleIdentifier: Optional[String] - ScheduleDescription: Optional[String] - Tags: Optional[TagList] - NextInvocations: Optional[ScheduledSnapshotTimeList] - AssociatedClusterCount: Optional[IntegerOptional] - AssociatedClusters: Optional[AssociatedClusterList] + ScheduleDefinitions: ScheduleDefinitionList | None + ScheduleIdentifier: String | None + ScheduleDescription: String | None + Tags: TagList | None + NextInvocations: ScheduledSnapshotTimeList | None + AssociatedClusterCount: IntegerOptional | None + AssociatedClusters: AssociatedClusterList | None -SnapshotScheduleList = List[SnapshotSchedule] +SnapshotScheduleList = list[SnapshotSchedule] class DescribeSnapshotSchedulesOutputMessage(TypedDict, total=False): - SnapshotSchedules: Optional[SnapshotScheduleList] - Marker: Optional[String] + SnapshotSchedules: SnapshotScheduleList | None + Marker: String | None class DescribeTableRestoreStatusMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - TableRestoreRequestId: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterIdentifier: String | None + TableRestoreRequestId: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class DescribeTagsMessage(ServiceRequest): - ResourceName: Optional[String] - ResourceType: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + ResourceName: String | None + ResourceType: String | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DescribeUsageLimitsMessage(ServiceRequest): - UsageLimitId: Optional[String] - ClusterIdentifier: Optional[String] - FeatureType: Optional[UsageLimitFeatureType] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] - TagKeys: Optional[TagKeyList] - TagValues: Optional[TagValueList] + UsageLimitId: String | None + ClusterIdentifier: String | None + FeatureType: UsageLimitFeatureType | None + MaxRecords: IntegerOptional | None + Marker: String | None + TagKeys: TagKeyList | None + TagValues: TagValueList | None class DisableLoggingMessage(ServiceRequest): @@ -2702,138 +2749,138 @@ class DisableSnapshotCopyMessage(ServiceRequest): class DisableSnapshotCopyResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class DisassociateDataShareConsumerMessage(ServiceRequest): DataShareArn: String - DisassociateEntireAccount: Optional[BooleanOptional] - ConsumerArn: Optional[String] - ConsumerRegion: Optional[String] + DisassociateEntireAccount: BooleanOptional | None + ConsumerArn: String | None + ConsumerRegion: String | None class SupportedOperation(TypedDict, total=False): - OperationName: Optional[String] + OperationName: String | None -SupportedOperationList = List[SupportedOperation] +SupportedOperationList = list[SupportedOperation] class UpdateTarget(TypedDict, total=False): - MaintenanceTrackName: Optional[String] - DatabaseVersion: Optional[String] - SupportedOperations: Optional[SupportedOperationList] + MaintenanceTrackName: String | None + DatabaseVersion: String | None + SupportedOperations: SupportedOperationList | None -EligibleTracksToUpdateList = List[UpdateTarget] -LogTypeList = List[String] +EligibleTracksToUpdateList = list[UpdateTarget] +LogTypeList = list[String] class EnableLoggingMessage(ServiceRequest): ClusterIdentifier: String - BucketName: Optional[String] - S3KeyPrefix: Optional[S3KeyPrefixValue] - LogDestinationType: Optional[LogDestinationType] - LogExports: Optional[LogTypeList] + BucketName: String | None + S3KeyPrefix: S3KeyPrefixValue | None + LogDestinationType: LogDestinationType | None + LogExports: LogTypeList | None class EnableSnapshotCopyMessage(ServiceRequest): ClusterIdentifier: String DestinationRegion: String - RetentionPeriod: Optional[IntegerOptional] - SnapshotCopyGrantName: Optional[String] - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] + RetentionPeriod: IntegerOptional | None + SnapshotCopyGrantName: String | None + ManualSnapshotRetentionPeriod: IntegerOptional | None class EnableSnapshotCopyResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class EndpointAccess(TypedDict, total=False): - ClusterIdentifier: Optional[String] - ResourceOwner: Optional[String] - SubnetGroupName: Optional[String] - EndpointStatus: Optional[String] - EndpointName: Optional[String] - EndpointCreateTime: Optional[TStamp] - Port: Optional[Integer] - Address: Optional[String] - VpcSecurityGroups: Optional[VpcSecurityGroupMembershipList] - VpcEndpoint: Optional[VpcEndpoint] + ClusterIdentifier: String | None + ResourceOwner: String | None + SubnetGroupName: String | None + EndpointStatus: String | None + EndpointName: String | None + EndpointCreateTime: TStamp | None + Port: Integer | None + Address: String | None + VpcSecurityGroups: VpcSecurityGroupMembershipList | None + VpcEndpoint: VpcEndpoint | None -EndpointAccesses = List[EndpointAccess] +EndpointAccesses = list[EndpointAccess] class EndpointAccessList(TypedDict, total=False): - EndpointAccessList: Optional[EndpointAccesses] - Marker: Optional[String] + EndpointAccessList: EndpointAccesses | None + Marker: String | None class EndpointAuthorization(TypedDict, total=False): - Grantor: Optional[String] - Grantee: Optional[String] - ClusterIdentifier: Optional[String] - AuthorizeTime: Optional[TStamp] - ClusterStatus: Optional[String] - Status: Optional[AuthorizationStatus] - AllowedAllVPCs: Optional[Boolean] - AllowedVPCs: Optional[VpcIdentifierList] - EndpointCount: Optional[Integer] + Grantor: String | None + Grantee: String | None + ClusterIdentifier: String | None + AuthorizeTime: TStamp | None + ClusterStatus: String | None + Status: AuthorizationStatus | None + AllowedAllVPCs: Boolean | None + AllowedVPCs: VpcIdentifierList | None + EndpointCount: Integer | None -EndpointAuthorizations = List[EndpointAuthorization] +EndpointAuthorizations = list[EndpointAuthorization] class EndpointAuthorizationList(TypedDict, total=False): - EndpointAuthorizationList: Optional[EndpointAuthorizations] - Marker: Optional[String] + EndpointAuthorizationList: EndpointAuthorizations | None + Marker: String | None class Event(TypedDict, total=False): - SourceIdentifier: Optional[String] - SourceType: Optional[SourceType] - Message: Optional[String] - EventCategories: Optional[EventCategoriesList] - Severity: Optional[String] - Date: Optional[TStamp] - EventId: Optional[String] + SourceIdentifier: String | None + SourceType: SourceType | None + Message: String | None + EventCategories: EventCategoriesList | None + Severity: String | None + Date: TStamp | None + EventId: String | None class EventInfoMap(TypedDict, total=False): - EventId: Optional[String] - EventCategories: Optional[EventCategoriesList] - EventDescription: Optional[String] - Severity: Optional[String] + EventId: String | None + EventCategories: EventCategoriesList | None + EventDescription: String | None + Severity: String | None -EventInfoMapList = List[EventInfoMap] +EventInfoMapList = list[EventInfoMap] class EventCategoriesMap(TypedDict, total=False): - SourceType: Optional[String] - Events: Optional[EventInfoMapList] + SourceType: String | None + Events: EventInfoMapList | None -EventCategoriesMapList = List[EventCategoriesMap] +EventCategoriesMapList = list[EventCategoriesMap] class EventCategoriesMessage(TypedDict, total=False): - EventCategoriesMapList: Optional[EventCategoriesMapList] + EventCategoriesMapList: EventCategoriesMapList | None -EventList = List[Event] -EventSubscriptionsList = List[EventSubscription] +EventList = list[Event] +EventSubscriptionsList = list[EventSubscription] class EventSubscriptionsMessage(TypedDict, total=False): - Marker: Optional[String] - EventSubscriptionsList: Optional[EventSubscriptionsList] + Marker: String | None + EventSubscriptionsList: EventSubscriptionsList | None class EventsMessage(TypedDict, total=False): - Marker: Optional[String] - Events: Optional[EventList] + Marker: String | None + Events: EventList | None class FailoverPrimaryComputeInputMessage(ServiceRequest): @@ -2841,72 +2888,81 @@ class FailoverPrimaryComputeInputMessage(ServiceRequest): class FailoverPrimaryComputeResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class GetClusterCredentialsMessage(ServiceRequest): DbUser: String - DbName: Optional[String] - ClusterIdentifier: Optional[String] - DurationSeconds: Optional[IntegerOptional] - AutoCreate: Optional[BooleanOptional] - DbGroups: Optional[DbGroupList] - CustomDomainName: Optional[String] + DbName: String | None + ClusterIdentifier: String | None + DurationSeconds: IntegerOptional | None + AutoCreate: BooleanOptional | None + DbGroups: DbGroupList | None + CustomDomainName: String | None class GetClusterCredentialsWithIAMMessage(ServiceRequest): - DbName: Optional[String] - ClusterIdentifier: Optional[String] - DurationSeconds: Optional[IntegerOptional] - CustomDomainName: Optional[String] + DbName: String | None + ClusterIdentifier: String | None + DurationSeconds: IntegerOptional | None + CustomDomainName: String | None + + +class GetIdentityCenterAuthTokenRequest(ServiceRequest): + ClusterIds: ClusterIdentifierList + + +class GetIdentityCenterAuthTokenResponse(TypedDict, total=False): + Token: SensitiveString | None + ExpirationTime: TStamp | None class GetReservedNodeExchangeConfigurationOptionsInputMessage(ServiceRequest): ActionType: ReservedNodeExchangeActionType - ClusterIdentifier: Optional[String] - SnapshotIdentifier: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterIdentifier: String | None + SnapshotIdentifier: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class ReservedNodeOffering(TypedDict, total=False): - ReservedNodeOfferingId: Optional[String] - NodeType: Optional[String] - Duration: Optional[Integer] - FixedPrice: Optional[Double] - UsagePrice: Optional[Double] - CurrencyCode: Optional[String] - OfferingType: Optional[String] - RecurringCharges: Optional[RecurringChargeList] - ReservedNodeOfferingType: Optional[ReservedNodeOfferingType] + ReservedNodeOfferingId: String | None + NodeType: String | None + Duration: Integer | None + FixedPrice: Double | None + UsagePrice: Double | None + CurrencyCode: String | None + OfferingType: String | None + RecurringCharges: RecurringChargeList | None + ReservedNodeOfferingType: ReservedNodeOfferingType | None class ReservedNodeConfigurationOption(TypedDict, total=False): - SourceReservedNode: Optional[ReservedNode] - TargetReservedNodeCount: Optional[Integer] - TargetReservedNodeOffering: Optional[ReservedNodeOffering] + SourceReservedNode: ReservedNode | None + TargetReservedNodeCount: Integer | None + TargetReservedNodeOffering: ReservedNodeOffering | None -ReservedNodeConfigurationOptionList = List[ReservedNodeConfigurationOption] +ReservedNodeConfigurationOptionList = list[ReservedNodeConfigurationOption] class GetReservedNodeExchangeConfigurationOptionsOutputMessage(TypedDict, total=False): - Marker: Optional[String] - ReservedNodeConfigurationOptionList: Optional[ReservedNodeConfigurationOptionList] + Marker: String | None + ReservedNodeConfigurationOptionList: ReservedNodeConfigurationOptionList | None class GetReservedNodeExchangeOfferingsInputMessage(ServiceRequest): ReservedNodeId: String - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + MaxRecords: IntegerOptional | None + Marker: String | None -ReservedNodeOfferingList = List[ReservedNodeOffering] +ReservedNodeOfferingList = list[ReservedNodeOffering] class GetReservedNodeExchangeOfferingsOutputMessage(TypedDict, total=False): - Marker: Optional[String] - ReservedNodeOfferings: Optional[ReservedNodeOfferingList] + Marker: String | None + ReservedNodeOfferings: ReservedNodeOfferingList | None class GetResourcePolicyMessage(ServiceRequest): @@ -2914,154 +2970,161 @@ class GetResourcePolicyMessage(ServiceRequest): class ResourcePolicy(TypedDict, total=False): - ResourceArn: Optional[String] - Policy: Optional[String] + ResourceArn: String | None + Policy: String | None class GetResourcePolicyResult(TypedDict, total=False): - ResourcePolicy: Optional[ResourcePolicy] + ResourcePolicy: ResourcePolicy | None -HsmClientCertificateList = List[HsmClientCertificate] +HsmClientCertificateList = list[HsmClientCertificate] class HsmClientCertificateMessage(TypedDict, total=False): - Marker: Optional[String] - HsmClientCertificates: Optional[HsmClientCertificateList] + Marker: String | None + HsmClientCertificates: HsmClientCertificateList | None -HsmConfigurationList = List[HsmConfiguration] +HsmConfigurationList = list[HsmConfiguration] class HsmConfigurationMessage(TypedDict, total=False): - Marker: Optional[String] - HsmConfigurations: Optional[HsmConfigurationList] + Marker: String | None + HsmConfigurations: HsmConfigurationList | None -ImportTablesCompleted = List[String] -ImportTablesInProgress = List[String] -ImportTablesNotStarted = List[String] +ImportTablesCompleted = list[String] +ImportTablesInProgress = list[String] +ImportTablesNotStarted = list[String] class IntegrationError(TypedDict, total=False): ErrorCode: String - ErrorMessage: Optional[String] + ErrorMessage: String | None -IntegrationErrorList = List[IntegrationError] +IntegrationErrorList = list[IntegrationError] class InboundIntegration(TypedDict, total=False): - IntegrationArn: Optional[InboundIntegrationArn] - SourceArn: Optional[String] - TargetArn: Optional[TargetArn] - Status: Optional[ZeroETLIntegrationStatus] - Errors: Optional[IntegrationErrorList] - CreateTime: Optional[TStamp] + IntegrationArn: InboundIntegrationArn | None + SourceArn: String | None + TargetArn: TargetArn | None + Status: ZeroETLIntegrationStatus | None + Errors: IntegrationErrorList | None + CreateTime: TStamp | None -InboundIntegrationList = List[InboundIntegration] +InboundIntegrationList = list[InboundIntegration] class InboundIntegrationsMessage(TypedDict, total=False): - Marker: Optional[String] - InboundIntegrations: Optional[InboundIntegrationList] + Marker: String | None + InboundIntegrations: InboundIntegrationList | None class Integration(TypedDict, total=False): - IntegrationArn: Optional[IntegrationArn] - IntegrationName: Optional[IntegrationName] - SourceArn: Optional[SourceArn] - TargetArn: Optional[TargetArn] - Status: Optional[ZeroETLIntegrationStatus] - Errors: Optional[IntegrationErrorList] - CreateTime: Optional[TStamp] - Description: Optional[Description] - KMSKeyId: Optional[String] - AdditionalEncryptionContext: Optional[EncryptionContextMap] - Tags: Optional[TagList] + IntegrationArn: IntegrationArn | None + IntegrationName: IntegrationName | None + SourceArn: SourceArn | None + TargetArn: TargetArn | None + Status: ZeroETLIntegrationStatus | None + Errors: IntegrationErrorList | None + CreateTime: TStamp | None + Description: Description | None + KMSKeyId: String | None + AdditionalEncryptionContext: EncryptionContextMap | None + Tags: TagList | None -IntegrationList = List[Integration] +IntegrationList = list[Integration] class IntegrationsMessage(TypedDict, total=False): - Marker: Optional[String] - Integrations: Optional[IntegrationList] + Marker: String | None + Integrations: IntegrationList | None + + +class LakehouseConfiguration(TypedDict, total=False): + ClusterIdentifier: String | None + LakehouseIdcApplicationArn: String | None + LakehouseRegistrationStatus: String | None + CatalogArn: String | None class ListRecommendationsMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - NamespaceArn: Optional[String] - MaxRecords: Optional[IntegerOptional] - Marker: Optional[String] + ClusterIdentifier: String | None + NamespaceArn: String | None + MaxRecords: IntegerOptional | None + Marker: String | None class ReferenceLink(TypedDict, total=False): - Text: Optional[String] - Link: Optional[String] + Text: String | None + Link: String | None -ReferenceLinkList = List[ReferenceLink] +ReferenceLinkList = list[ReferenceLink] class RecommendedAction(TypedDict, total=False): - Text: Optional[String] - Database: Optional[String] - Command: Optional[String] - Type: Optional[RecommendedActionType] + Text: String | None + Database: String | None + Command: String | None + Type: RecommendedActionType | None -RecommendedActionList = List[RecommendedAction] +RecommendedActionList = list[RecommendedAction] class Recommendation(TypedDict, total=False): - Id: Optional[String] - ClusterIdentifier: Optional[String] - NamespaceArn: Optional[String] - CreatedAt: Optional[TStamp] - RecommendationType: Optional[String] - Title: Optional[String] - Description: Optional[String] - Observation: Optional[String] - ImpactRanking: Optional[ImpactRankingType] - RecommendationText: Optional[String] - RecommendedActions: Optional[RecommendedActionList] - ReferenceLinks: Optional[ReferenceLinkList] + Id: String | None + ClusterIdentifier: String | None + NamespaceArn: String | None + CreatedAt: TStamp | None + RecommendationType: String | None + Title: String | None + Description: String | None + Observation: String | None + ImpactRanking: ImpactRankingType | None + RecommendationText: String | None + RecommendedActions: RecommendedActionList | None + ReferenceLinks: ReferenceLinkList | None -RecommendationList = List[Recommendation] +RecommendationList = list[Recommendation] class ListRecommendationsResult(TypedDict, total=False): - Recommendations: Optional[RecommendationList] - Marker: Optional[String] + Recommendations: RecommendationList | None + Marker: String | None class LoggingStatus(TypedDict, total=False): - LoggingEnabled: Optional[Boolean] - BucketName: Optional[String] - S3KeyPrefix: Optional[S3KeyPrefixValue] - LastSuccessfulDeliveryTime: Optional[TStamp] - LastFailureTime: Optional[TStamp] - LastFailureMessage: Optional[String] - LogDestinationType: Optional[LogDestinationType] - LogExports: Optional[LogTypeList] + LoggingEnabled: Boolean | None + BucketName: String | None + S3KeyPrefix: S3KeyPrefixValue | None + LastSuccessfulDeliveryTime: TStamp | None + LastFailureTime: TStamp | None + LastFailureMessage: String | None + LogDestinationType: LogDestinationType | None + LogExports: LogTypeList | None class MaintenanceTrack(TypedDict, total=False): - MaintenanceTrackName: Optional[String] - DatabaseVersion: Optional[String] - UpdateTargets: Optional[EligibleTracksToUpdateList] + MaintenanceTrackName: String | None + DatabaseVersion: String | None + UpdateTargets: EligibleTracksToUpdateList | None class ModifyAquaInputMessage(ServiceRequest): ClusterIdentifier: String - AquaConfigurationStatus: Optional[AquaConfigurationStatus] + AquaConfigurationStatus: AquaConfigurationStatus | None class ModifyAquaOutputMessage(TypedDict, total=False): - AquaConfiguration: Optional[AquaConfiguration] + AquaConfiguration: AquaConfiguration | None class ModifyAuthenticationProfileMessage(ServiceRequest): @@ -3070,8 +3133,8 @@ class ModifyAuthenticationProfileMessage(ServiceRequest): class ModifyAuthenticationProfileResult(TypedDict, total=False): - AuthenticationProfileName: Optional[AuthenticationProfileNameString] - AuthenticationProfileContent: Optional[String] + AuthenticationProfileName: AuthenticationProfileNameString | None + AuthenticationProfileContent: String | None class ModifyClusterDbRevisionMessage(ServiceRequest): @@ -3080,63 +3143,64 @@ class ModifyClusterDbRevisionMessage(ServiceRequest): class ModifyClusterDbRevisionResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class ModifyClusterIamRolesMessage(ServiceRequest): ClusterIdentifier: String - AddIamRoles: Optional[IamRoleArnList] - RemoveIamRoles: Optional[IamRoleArnList] - DefaultIamRoleArn: Optional[String] + AddIamRoles: IamRoleArnList | None + RemoveIamRoles: IamRoleArnList | None + DefaultIamRoleArn: String | None class ModifyClusterIamRolesResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class ModifyClusterMaintenanceMessage(ServiceRequest): ClusterIdentifier: String - DeferMaintenance: Optional[BooleanOptional] - DeferMaintenanceIdentifier: Optional[String] - DeferMaintenanceStartTime: Optional[TStamp] - DeferMaintenanceEndTime: Optional[TStamp] - DeferMaintenanceDuration: Optional[IntegerOptional] + DeferMaintenance: BooleanOptional | None + DeferMaintenanceIdentifier: String | None + DeferMaintenanceStartTime: TStamp | None + DeferMaintenanceEndTime: TStamp | None + DeferMaintenanceDuration: IntegerOptional | None class ModifyClusterMaintenanceResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class ModifyClusterMessage(ServiceRequest): ClusterIdentifier: String - ClusterType: Optional[String] - NodeType: Optional[String] - NumberOfNodes: Optional[IntegerOptional] - ClusterSecurityGroups: Optional[ClusterSecurityGroupNameList] - VpcSecurityGroupIds: Optional[VpcSecurityGroupIdList] - MasterUserPassword: Optional[SensitiveString] - ClusterParameterGroupName: Optional[String] - AutomatedSnapshotRetentionPeriod: Optional[IntegerOptional] - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] - PreferredMaintenanceWindow: Optional[String] - ClusterVersion: Optional[String] - AllowVersionUpgrade: Optional[BooleanOptional] - HsmClientCertificateIdentifier: Optional[String] - HsmConfigurationIdentifier: Optional[String] - NewClusterIdentifier: Optional[String] - PubliclyAccessible: Optional[BooleanOptional] - ElasticIp: Optional[String] - EnhancedVpcRouting: Optional[BooleanOptional] - MaintenanceTrackName: Optional[String] - Encrypted: Optional[BooleanOptional] - KmsKeyId: Optional[String] - AvailabilityZoneRelocation: Optional[BooleanOptional] - AvailabilityZone: Optional[String] - Port: Optional[IntegerOptional] - ManageMasterPassword: Optional[BooleanOptional] - MasterPasswordSecretKmsKeyId: Optional[String] - IpAddressType: Optional[String] - MultiAZ: Optional[BooleanOptional] + ClusterType: String | None + NodeType: String | None + NumberOfNodes: IntegerOptional | None + ClusterSecurityGroups: ClusterSecurityGroupNameList | None + VpcSecurityGroupIds: VpcSecurityGroupIdList | None + MasterUserPassword: SensitiveString | None + ClusterParameterGroupName: String | None + AutomatedSnapshotRetentionPeriod: IntegerOptional | None + ManualSnapshotRetentionPeriod: IntegerOptional | None + PreferredMaintenanceWindow: String | None + ClusterVersion: String | None + AllowVersionUpgrade: BooleanOptional | None + HsmClientCertificateIdentifier: String | None + HsmConfigurationIdentifier: String | None + NewClusterIdentifier: String | None + PubliclyAccessible: BooleanOptional | None + ElasticIp: String | None + EnhancedVpcRouting: BooleanOptional | None + MaintenanceTrackName: String | None + Encrypted: BooleanOptional | None + KmsKeyId: String | None + AvailabilityZoneRelocation: BooleanOptional | None + AvailabilityZone: String | None + Port: IntegerOptional | None + ManageMasterPassword: BooleanOptional | None + MasterPasswordSecretKmsKeyId: String | None + IpAddressType: String | None + MultiAZ: BooleanOptional | None + ExtraComputeForAutomaticOptimization: BooleanOptional | None class ModifyClusterParameterGroupMessage(ServiceRequest): @@ -3145,33 +3209,33 @@ class ModifyClusterParameterGroupMessage(ServiceRequest): class ModifyClusterResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class ModifyClusterSnapshotMessage(ServiceRequest): SnapshotIdentifier: String - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] - Force: Optional[Boolean] + ManualSnapshotRetentionPeriod: IntegerOptional | None + Force: Boolean | None class ModifyClusterSnapshotResult(TypedDict, total=False): - Snapshot: Optional[Snapshot] + Snapshot: Snapshot | None class ModifyClusterSnapshotScheduleMessage(ServiceRequest): ClusterIdentifier: String - ScheduleIdentifier: Optional[String] - DisassociateSchedule: Optional[BooleanOptional] + ScheduleIdentifier: String | None + DisassociateSchedule: BooleanOptional | None class ModifyClusterSubnetGroupMessage(ServiceRequest): ClusterSubnetGroupName: String - Description: Optional[String] + Description: String | None SubnetIds: SubnetIdentifierList class ModifyClusterSubnetGroupResult(TypedDict, total=False): - ClusterSubnetGroup: Optional[ClusterSubnetGroup] + ClusterSubnetGroup: ClusterSubnetGroup | None class ModifyCustomDomainAssociationMessage(ServiceRequest): @@ -3181,69 +3245,78 @@ class ModifyCustomDomainAssociationMessage(ServiceRequest): class ModifyCustomDomainAssociationResult(TypedDict, total=False): - CustomDomainName: Optional[CustomDomainNameString] - CustomDomainCertificateArn: Optional[CustomDomainCertificateArnString] - ClusterIdentifier: Optional[String] - CustomDomainCertExpiryTime: Optional[String] + CustomDomainName: CustomDomainNameString | None + CustomDomainCertificateArn: CustomDomainCertificateArnString | None + ClusterIdentifier: String | None + CustomDomainCertExpiryTime: String | None class ModifyEndpointAccessMessage(ServiceRequest): EndpointName: String - VpcSecurityGroupIds: Optional[VpcSecurityGroupIdList] + VpcSecurityGroupIds: VpcSecurityGroupIdList | None class ModifyEventSubscriptionMessage(ServiceRequest): SubscriptionName: String - SnsTopicArn: Optional[String] - SourceType: Optional[String] - SourceIds: Optional[SourceIdsList] - EventCategories: Optional[EventCategoriesList] - Severity: Optional[String] - Enabled: Optional[BooleanOptional] + SnsTopicArn: String | None + SourceType: String | None + SourceIds: SourceIdsList | None + EventCategories: EventCategoriesList | None + Severity: String | None + Enabled: BooleanOptional | None class ModifyEventSubscriptionResult(TypedDict, total=False): - EventSubscription: Optional[EventSubscription] + EventSubscription: EventSubscription | None class ModifyIntegrationMessage(ServiceRequest): IntegrationArn: IntegrationArn - Description: Optional[IntegrationDescription] - IntegrationName: Optional[IntegrationName] + Description: IntegrationDescription | None + IntegrationName: IntegrationName | None + + +class ModifyLakehouseConfigurationMessage(ServiceRequest): + ClusterIdentifier: String + LakehouseRegistration: LakehouseRegistration | None + CatalogName: CatalogNameString | None + LakehouseIdcRegistration: LakehouseIdcRegistration | None + LakehouseIdcApplicationArn: String | None + DryRun: BooleanOptional | None class ModifyRedshiftIdcApplicationMessage(ServiceRequest): RedshiftIdcApplicationArn: String - IdentityNamespace: Optional[IdentityNamespaceString] - IamRoleArn: Optional[String] - IdcDisplayName: Optional[IdcDisplayNameString] - AuthorizedTokenIssuerList: Optional[AuthorizedTokenIssuerList] - ServiceIntegrations: Optional[ServiceIntegrationList] + IdentityNamespace: IdentityNamespaceString | None + IamRoleArn: String | None + IdcDisplayName: IdcDisplayNameString | None + AuthorizedTokenIssuerList: AuthorizedTokenIssuerList | None + ServiceIntegrations: ServiceIntegrationList | None class ModifyRedshiftIdcApplicationResult(TypedDict, total=False): - RedshiftIdcApplication: Optional[RedshiftIdcApplication] + RedshiftIdcApplication: RedshiftIdcApplication | None class ModifyScheduledActionMessage(ServiceRequest): ScheduledActionName: String - TargetAction: Optional[ScheduledActionType] - Schedule: Optional[String] - IamRole: Optional[String] - ScheduledActionDescription: Optional[String] - StartTime: Optional[TStamp] - EndTime: Optional[TStamp] - Enable: Optional[BooleanOptional] + TargetAction: ScheduledActionType | None + Schedule: String | None + IamRole: String | None + ScheduledActionDescription: String | None + StartTime: TStamp | None + EndTime: TStamp | None + Enable: BooleanOptional | None class ModifySnapshotCopyRetentionPeriodMessage(ServiceRequest): ClusterIdentifier: String RetentionPeriod: Integer - Manual: Optional[Boolean] + Manual: Boolean | None class ModifySnapshotCopyRetentionPeriodResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class ModifySnapshotScheduleMessage(ServiceRequest): @@ -3253,38 +3326,38 @@ class ModifySnapshotScheduleMessage(ServiceRequest): class ModifyUsageLimitMessage(ServiceRequest): UsageLimitId: String - Amount: Optional[LongOptional] - BreachAction: Optional[UsageLimitBreachAction] + Amount: LongOptional | None + BreachAction: UsageLimitBreachAction | None class NodeConfigurationOption(TypedDict, total=False): - NodeType: Optional[String] - NumberOfNodes: Optional[Integer] - EstimatedDiskUtilizationPercent: Optional[DoubleOptional] - Mode: Optional[Mode] + NodeType: String | None + NumberOfNodes: Integer | None + EstimatedDiskUtilizationPercent: DoubleOptional | None + Mode: Mode | None -NodeConfigurationOptionList = List[NodeConfigurationOption] +NodeConfigurationOptionList = list[NodeConfigurationOption] class NodeConfigurationOptionsMessage(TypedDict, total=False): - NodeConfigurationOptionList: Optional[NodeConfigurationOptionList] - Marker: Optional[String] + NodeConfigurationOptionList: NodeConfigurationOptionList | None + Marker: String | None class OrderableClusterOption(TypedDict, total=False): - ClusterVersion: Optional[String] - ClusterType: Optional[String] - NodeType: Optional[String] - AvailabilityZones: Optional[AvailabilityZoneList] + ClusterVersion: String | None + ClusterType: String | None + NodeType: String | None + AvailabilityZones: AvailabilityZoneList | None -OrderableClusterOptionsList = List[OrderableClusterOption] +OrderableClusterOptionsList = list[OrderableClusterOption] class OrderableClusterOptionsMessage(TypedDict, total=False): - OrderableClusterOptions: Optional[OrderableClusterOptionsList] - Marker: Optional[String] + OrderableClusterOptions: OrderableClusterOptionsList | None + Marker: String | None class PartnerIntegrationInputMessage(ServiceRequest): @@ -3295,21 +3368,21 @@ class PartnerIntegrationInputMessage(ServiceRequest): class PartnerIntegrationOutputMessage(TypedDict, total=False): - DatabaseName: Optional[PartnerIntegrationDatabaseName] - PartnerName: Optional[PartnerIntegrationPartnerName] + DatabaseName: PartnerIntegrationDatabaseName | None + PartnerName: PartnerIntegrationPartnerName | None class PauseClusterResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class PurchaseReservedNodeOfferingMessage(ServiceRequest): ReservedNodeOfferingId: String - NodeCount: Optional[IntegerOptional] + NodeCount: IntegerOptional | None class PurchaseReservedNodeOfferingResult(TypedDict, total=False): - ReservedNode: Optional[ReservedNode] + ReservedNode: ReservedNode | None class PutResourcePolicyMessage(ServiceRequest): @@ -3318,7 +3391,7 @@ class PutResourcePolicyMessage(ServiceRequest): class PutResourcePolicyResult(TypedDict, total=False): - ResourcePolicy: Optional[ResourcePolicy] + ResourcePolicy: ResourcePolicy | None class RebootClusterMessage(ServiceRequest): @@ -3326,7 +3399,7 @@ class RebootClusterMessage(ServiceRequest): class RebootClusterResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class RegisterNamespaceInputMessage(ServiceRequest): @@ -3335,163 +3408,165 @@ class RegisterNamespaceInputMessage(ServiceRequest): class RegisterNamespaceOutputMessage(TypedDict, total=False): - Status: Optional[NamespaceRegistrationStatus] + Status: NamespaceRegistrationStatus | None class RejectDataShareMessage(ServiceRequest): DataShareArn: String -ReservedNodeList = List[ReservedNode] +ReservedNodeList = list[ReservedNode] class ReservedNodeOfferingsMessage(TypedDict, total=False): - Marker: Optional[String] - ReservedNodeOfferings: Optional[ReservedNodeOfferingList] + Marker: String | None + ReservedNodeOfferings: ReservedNodeOfferingList | None class ReservedNodesMessage(TypedDict, total=False): - Marker: Optional[String] - ReservedNodes: Optional[ReservedNodeList] + Marker: String | None + ReservedNodes: ReservedNodeList | None class ResetClusterParameterGroupMessage(ServiceRequest): ParameterGroupName: String - ResetAllParameters: Optional[Boolean] - Parameters: Optional[ParametersList] + ResetAllParameters: Boolean | None + Parameters: ParametersList | None class ResizeClusterResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class ResizeProgressMessage(TypedDict, total=False): - TargetNodeType: Optional[String] - TargetNumberOfNodes: Optional[IntegerOptional] - TargetClusterType: Optional[String] - Status: Optional[String] - ImportTablesCompleted: Optional[ImportTablesCompleted] - ImportTablesInProgress: Optional[ImportTablesInProgress] - ImportTablesNotStarted: Optional[ImportTablesNotStarted] - AvgResizeRateInMegaBytesPerSecond: Optional[DoubleOptional] - TotalResizeDataInMegaBytes: Optional[LongOptional] - ProgressInMegaBytes: Optional[LongOptional] - ElapsedTimeInSeconds: Optional[LongOptional] - EstimatedTimeToCompletionInSeconds: Optional[LongOptional] - ResizeType: Optional[String] - Message: Optional[String] - TargetEncryptionType: Optional[String] - DataTransferProgressPercent: Optional[DoubleOptional] + TargetNodeType: String | None + TargetNumberOfNodes: IntegerOptional | None + TargetClusterType: String | None + Status: String | None + ImportTablesCompleted: ImportTablesCompleted | None + ImportTablesInProgress: ImportTablesInProgress | None + ImportTablesNotStarted: ImportTablesNotStarted | None + AvgResizeRateInMegaBytesPerSecond: DoubleOptional | None + TotalResizeDataInMegaBytes: LongOptional | None + ProgressInMegaBytes: LongOptional | None + ElapsedTimeInSeconds: LongOptional | None + EstimatedTimeToCompletionInSeconds: LongOptional | None + ResizeType: String | None + Message: String | None + TargetEncryptionType: String | None + DataTransferProgressPercent: DoubleOptional | None class RestoreFromClusterSnapshotMessage(ServiceRequest): ClusterIdentifier: String - SnapshotIdentifier: Optional[String] - SnapshotArn: Optional[String] - SnapshotClusterIdentifier: Optional[String] - Port: Optional[IntegerOptional] - AvailabilityZone: Optional[String] - AllowVersionUpgrade: Optional[BooleanOptional] - ClusterSubnetGroupName: Optional[String] - PubliclyAccessible: Optional[BooleanOptional] - OwnerAccount: Optional[String] - HsmClientCertificateIdentifier: Optional[String] - HsmConfigurationIdentifier: Optional[String] - ElasticIp: Optional[String] - ClusterParameterGroupName: Optional[String] - ClusterSecurityGroups: Optional[ClusterSecurityGroupNameList] - VpcSecurityGroupIds: Optional[VpcSecurityGroupIdList] - PreferredMaintenanceWindow: Optional[String] - AutomatedSnapshotRetentionPeriod: Optional[IntegerOptional] - ManualSnapshotRetentionPeriod: Optional[IntegerOptional] - KmsKeyId: Optional[String] - NodeType: Optional[String] - EnhancedVpcRouting: Optional[BooleanOptional] - AdditionalInfo: Optional[String] - IamRoles: Optional[IamRoleArnList] - MaintenanceTrackName: Optional[String] - SnapshotScheduleIdentifier: Optional[String] - NumberOfNodes: Optional[IntegerOptional] - AvailabilityZoneRelocation: Optional[BooleanOptional] - AquaConfigurationStatus: Optional[AquaConfigurationStatus] - DefaultIamRoleArn: Optional[String] - ReservedNodeId: Optional[String] - TargetReservedNodeOfferingId: Optional[String] - Encrypted: Optional[BooleanOptional] - ManageMasterPassword: Optional[BooleanOptional] - MasterPasswordSecretKmsKeyId: Optional[String] - IpAddressType: Optional[String] - MultiAZ: Optional[BooleanOptional] + SnapshotIdentifier: String | None + SnapshotArn: String | None + SnapshotClusterIdentifier: String | None + Port: IntegerOptional | None + AvailabilityZone: String | None + AllowVersionUpgrade: BooleanOptional | None + ClusterSubnetGroupName: String | None + PubliclyAccessible: BooleanOptional | None + OwnerAccount: String | None + HsmClientCertificateIdentifier: String | None + HsmConfigurationIdentifier: String | None + ElasticIp: String | None + ClusterParameterGroupName: String | None + ClusterSecurityGroups: ClusterSecurityGroupNameList | None + VpcSecurityGroupIds: VpcSecurityGroupIdList | None + PreferredMaintenanceWindow: String | None + AutomatedSnapshotRetentionPeriod: IntegerOptional | None + ManualSnapshotRetentionPeriod: IntegerOptional | None + KmsKeyId: String | None + NodeType: String | None + EnhancedVpcRouting: BooleanOptional | None + AdditionalInfo: String | None + IamRoles: IamRoleArnList | None + MaintenanceTrackName: String | None + SnapshotScheduleIdentifier: String | None + NumberOfNodes: IntegerOptional | None + AvailabilityZoneRelocation: BooleanOptional | None + AquaConfigurationStatus: AquaConfigurationStatus | None + DefaultIamRoleArn: String | None + ReservedNodeId: String | None + TargetReservedNodeOfferingId: String | None + Encrypted: BooleanOptional | None + ManageMasterPassword: BooleanOptional | None + MasterPasswordSecretKmsKeyId: String | None + IpAddressType: String | None + MultiAZ: BooleanOptional | None + CatalogName: CatalogNameString | None + RedshiftIdcApplicationArn: String | None class RestoreFromClusterSnapshotResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class RestoreTableFromClusterSnapshotMessage(ServiceRequest): ClusterIdentifier: String SnapshotIdentifier: String SourceDatabaseName: String - SourceSchemaName: Optional[String] + SourceSchemaName: String | None SourceTableName: String - TargetDatabaseName: Optional[String] - TargetSchemaName: Optional[String] + TargetDatabaseName: String | None + TargetSchemaName: String | None NewTableName: String - EnableCaseSensitiveIdentifier: Optional[BooleanOptional] + EnableCaseSensitiveIdentifier: BooleanOptional | None class TableRestoreStatus(TypedDict, total=False): - TableRestoreRequestId: Optional[String] - Status: Optional[TableRestoreStatusType] - Message: Optional[String] - RequestTime: Optional[TStamp] - ProgressInMegaBytes: Optional[LongOptional] - TotalDataInMegaBytes: Optional[LongOptional] - ClusterIdentifier: Optional[String] - SnapshotIdentifier: Optional[String] - SourceDatabaseName: Optional[String] - SourceSchemaName: Optional[String] - SourceTableName: Optional[String] - TargetDatabaseName: Optional[String] - TargetSchemaName: Optional[String] - NewTableName: Optional[String] + TableRestoreRequestId: String | None + Status: TableRestoreStatusType | None + Message: String | None + RequestTime: TStamp | None + ProgressInMegaBytes: LongOptional | None + TotalDataInMegaBytes: LongOptional | None + ClusterIdentifier: String | None + SnapshotIdentifier: String | None + SourceDatabaseName: String | None + SourceSchemaName: String | None + SourceTableName: String | None + TargetDatabaseName: String | None + TargetSchemaName: String | None + NewTableName: String | None class RestoreTableFromClusterSnapshotResult(TypedDict, total=False): - TableRestoreStatus: Optional[TableRestoreStatus] + TableRestoreStatus: TableRestoreStatus | None class ResumeClusterResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None class RevokeClusterSecurityGroupIngressMessage(ServiceRequest): ClusterSecurityGroupName: String - CIDRIP: Optional[String] - EC2SecurityGroupName: Optional[String] - EC2SecurityGroupOwnerId: Optional[String] + CIDRIP: String | None + EC2SecurityGroupName: String | None + EC2SecurityGroupOwnerId: String | None class RevokeClusterSecurityGroupIngressResult(TypedDict, total=False): - ClusterSecurityGroup: Optional[ClusterSecurityGroup] + ClusterSecurityGroup: ClusterSecurityGroup | None class RevokeEndpointAccessMessage(ServiceRequest): - ClusterIdentifier: Optional[String] - Account: Optional[String] - VpcIds: Optional[VpcIdentifierList] - Force: Optional[Boolean] + ClusterIdentifier: String | None + Account: String | None + VpcIds: VpcIdentifierList | None + Force: Boolean | None class RevokeSnapshotAccessMessage(ServiceRequest): - SnapshotIdentifier: Optional[String] - SnapshotArn: Optional[String] - SnapshotClusterIdentifier: Optional[String] + SnapshotIdentifier: String | None + SnapshotArn: String | None + SnapshotClusterIdentifier: String | None AccountWithRestoreAccess: String class RevokeSnapshotAccessResult(TypedDict, total=False): - Snapshot: Optional[Snapshot] + Snapshot: Snapshot | None class RotateEncryptionKeyMessage(ServiceRequest): @@ -3499,76 +3574,76 @@ class RotateEncryptionKeyMessage(ServiceRequest): class RotateEncryptionKeyResult(TypedDict, total=False): - Cluster: Optional[Cluster] + Cluster: Cluster | None -ScheduledActionTimeList = List[TStamp] +ScheduledActionTimeList = list[TStamp] class ScheduledAction(TypedDict, total=False): - ScheduledActionName: Optional[String] - TargetAction: Optional[ScheduledActionType] - Schedule: Optional[String] - IamRole: Optional[String] - ScheduledActionDescription: Optional[String] - State: Optional[ScheduledActionState] - NextInvocations: Optional[ScheduledActionTimeList] - StartTime: Optional[TStamp] - EndTime: Optional[TStamp] + ScheduledActionName: String | None + TargetAction: ScheduledActionType | None + Schedule: String | None + IamRole: String | None + ScheduledActionDescription: String | None + State: ScheduledActionState | None + NextInvocations: ScheduledActionTimeList | None + StartTime: TStamp | None + EndTime: TStamp | None -ScheduledActionList = List[ScheduledAction] +ScheduledActionList = list[ScheduledAction] class ScheduledActionsMessage(TypedDict, total=False): - Marker: Optional[String] - ScheduledActions: Optional[ScheduledActionList] + Marker: String | None + ScheduledActions: ScheduledActionList | None -SnapshotCopyGrantList = List[SnapshotCopyGrant] +SnapshotCopyGrantList = list[SnapshotCopyGrant] class SnapshotCopyGrantMessage(TypedDict, total=False): - Marker: Optional[String] - SnapshotCopyGrants: Optional[SnapshotCopyGrantList] + Marker: String | None + SnapshotCopyGrants: SnapshotCopyGrantList | None -SnapshotList = List[Snapshot] +SnapshotList = list[Snapshot] class SnapshotMessage(TypedDict, total=False): - Marker: Optional[String] - Snapshots: Optional[SnapshotList] + Marker: String | None + Snapshots: SnapshotList | None -TableRestoreStatusList = List[TableRestoreStatus] +TableRestoreStatusList = list[TableRestoreStatus] class TableRestoreStatusMessage(TypedDict, total=False): - TableRestoreStatusDetails: Optional[TableRestoreStatusList] - Marker: Optional[String] + TableRestoreStatusDetails: TableRestoreStatusList | None + Marker: String | None class TaggedResource(TypedDict, total=False): - Tag: Optional[Tag] - ResourceName: Optional[String] - ResourceType: Optional[String] + Tag: Tag | None + ResourceName: String | None + ResourceType: String | None -TaggedResourceList = List[TaggedResource] +TaggedResourceList = list[TaggedResource] class TaggedResourceListMessage(TypedDict, total=False): - TaggedResources: Optional[TaggedResourceList] - Marker: Optional[String] + TaggedResources: TaggedResourceList | None + Marker: String | None -TrackList = List[MaintenanceTrack] +TrackList = list[MaintenanceTrack] class TrackListMessage(TypedDict, total=False): - MaintenanceTracks: Optional[TrackList] - Marker: Optional[String] + MaintenanceTracks: TrackList | None + Marker: String | None class UpdatePartnerStatusInputMessage(ServiceRequest): @@ -3577,31 +3652,31 @@ class UpdatePartnerStatusInputMessage(ServiceRequest): DatabaseName: PartnerIntegrationDatabaseName PartnerName: PartnerIntegrationPartnerName Status: PartnerIntegrationStatus - StatusMessage: Optional[PartnerIntegrationStatusMessage] + StatusMessage: PartnerIntegrationStatusMessage | None class UsageLimit(TypedDict, total=False): - UsageLimitId: Optional[String] - ClusterIdentifier: Optional[String] - FeatureType: Optional[UsageLimitFeatureType] - LimitType: Optional[UsageLimitLimitType] - Amount: Optional[Long] - Period: Optional[UsageLimitPeriod] - BreachAction: Optional[UsageLimitBreachAction] - Tags: Optional[TagList] + UsageLimitId: String | None + ClusterIdentifier: String | None + FeatureType: UsageLimitFeatureType | None + LimitType: UsageLimitLimitType | None + Amount: Long | None + Period: UsageLimitPeriod | None + BreachAction: UsageLimitBreachAction | None + Tags: TagList | None -UsageLimits = List[UsageLimit] +UsageLimits = list[UsageLimit] class UsageLimitList(TypedDict, total=False): - UsageLimits: Optional[UsageLimits] - Marker: Optional[String] + UsageLimits: UsageLimits | None + Marker: String | None class RedshiftApi: - service = "redshift" - version = "2012-12-01" + service: str = "redshift" + version: str = "2012-12-01" @handler("AcceptReservedNodeExchange") def accept_reserved_node_exchange( @@ -3772,6 +3847,8 @@ def create_cluster( ip_address_type: String | None = None, multi_az: BooleanOptional | None = None, redshift_idc_application_arn: String | None = None, + catalog_name: CatalogNameString | None = None, + extra_compute_for_automatic_optimization: BooleanOptional | None = None, **kwargs, ) -> CreateClusterResult: raise NotImplementedError @@ -3914,6 +3991,9 @@ def create_redshift_idc_application( identity_namespace: IdentityNamespaceString | None = None, authorized_token_issuer_list: AuthorizedTokenIssuerList | None = None, service_integrations: ServiceIntegrationList | None = None, + application_type: ApplicationType | None = None, + tags: TagList | None = None, + sso_tag_keys: TagKeyList | None = None, **kwargs, ) -> CreateRedshiftIdcApplicationResult: raise NotImplementedError @@ -4708,6 +4788,12 @@ def get_cluster_credentials_with_iam( ) -> ClusterExtendedCredentials: raise NotImplementedError + @handler("GetIdentityCenterAuthToken") + def get_identity_center_auth_token( + self, context: RequestContext, cluster_ids: ClusterIdentifierList, **kwargs + ) -> GetIdentityCenterAuthTokenResponse: + raise NotImplementedError + @handler("GetReservedNodeExchangeConfigurationOptions") def get_reserved_node_exchange_configuration_options( self, @@ -4803,6 +4889,7 @@ def modify_cluster( master_password_secret_kms_key_id: String | None = None, ip_address_type: String | None = None, multi_az: BooleanOptional | None = None, + extra_compute_for_automatic_optimization: BooleanOptional | None = None, **kwargs, ) -> ModifyClusterResult: raise NotImplementedError @@ -4929,6 +5016,20 @@ def modify_integration( ) -> Integration: raise NotImplementedError + @handler("ModifyLakehouseConfiguration") + def modify_lakehouse_configuration( + self, + context: RequestContext, + cluster_identifier: String, + lakehouse_registration: LakehouseRegistration | None = None, + catalog_name: CatalogNameString | None = None, + lakehouse_idc_registration: LakehouseIdcRegistration | None = None, + lakehouse_idc_application_arn: String | None = None, + dry_run: BooleanOptional | None = None, + **kwargs, + ) -> LakehouseConfiguration: + raise NotImplementedError + @handler("ModifyRedshiftIdcApplication") def modify_redshift_idc_application( self, @@ -5102,6 +5203,8 @@ def restore_from_cluster_snapshot( master_password_secret_kms_key_id: String | None = None, ip_address_type: String | None = None, multi_az: BooleanOptional | None = None, + catalog_name: CatalogNameString | None = None, + redshift_idc_application_arn: String | None = None, **kwargs, ) -> RestoreFromClusterSnapshotResult: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/resource_groups/__init__.py b/localstack-core/localstack/aws/api/resource_groups/__init__.py index b7511726ef579..efcc162678feb 100644 --- a/localstack-core/localstack/aws/api/resource_groups/__init__.py +++ b/localstack-core/localstack/aws/api/resource_groups/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -149,36 +149,36 @@ class UnauthorizedException(ServiceException): class AccountSettings(TypedDict, total=False): - GroupLifecycleEventsDesiredStatus: Optional[GroupLifecycleEventsDesiredStatus] - GroupLifecycleEventsStatus: Optional[GroupLifecycleEventsStatus] - GroupLifecycleEventsStatusMessage: Optional[GroupLifecycleEventsStatusMessage] + GroupLifecycleEventsDesiredStatus: GroupLifecycleEventsDesiredStatus | None + GroupLifecycleEventsStatus: GroupLifecycleEventsStatus | None + GroupLifecycleEventsStatusMessage: GroupLifecycleEventsStatusMessage | None -ApplicationTag = Dict[ApplicationTagKey, ApplicationArn] +ApplicationTag = dict[ApplicationTagKey, ApplicationArn] class CancelTagSyncTaskInput(ServiceRequest): TaskArn: TagSyncTaskArn -GroupConfigurationParameterValueList = List[GroupConfigurationParameterValue] +GroupConfigurationParameterValueList = list[GroupConfigurationParameterValue] class GroupConfigurationParameter(TypedDict, total=False): Name: GroupConfigurationParameterName - Values: Optional[GroupConfigurationParameterValueList] + Values: GroupConfigurationParameterValueList | None -GroupParameterList = List[GroupConfigurationParameter] +GroupParameterList = list[GroupConfigurationParameter] class GroupConfigurationItem(TypedDict, total=False): Type: GroupConfigurationType - Parameters: Optional[GroupParameterList] + Parameters: GroupParameterList | None -GroupConfigurationList = List[GroupConfigurationItem] -Tags = Dict[TagKey, TagValue] +GroupConfigurationList = list[GroupConfigurationItem] +Tags = dict[TagKey, TagValue] class ResourceQuery(TypedDict, total=False): @@ -188,81 +188,81 @@ class ResourceQuery(TypedDict, total=False): class CreateGroupInput(ServiceRequest): Name: CreateGroupName - Description: Optional[Description] - ResourceQuery: Optional[ResourceQuery] - Tags: Optional[Tags] - Configuration: Optional[GroupConfigurationList] - Criticality: Optional[Criticality] - Owner: Optional[Owner] - DisplayName: Optional[DisplayName] + Description: Description | None + ResourceQuery: ResourceQuery | None + Tags: Tags | None + Configuration: GroupConfigurationList | None + Criticality: Criticality | None + Owner: Owner | None + DisplayName: DisplayName | None class GroupConfiguration(TypedDict, total=False): - Configuration: Optional[GroupConfigurationList] - ProposedConfiguration: Optional[GroupConfigurationList] - Status: Optional[GroupConfigurationStatus] - FailureReason: Optional[GroupConfigurationFailureReason] + Configuration: GroupConfigurationList | None + ProposedConfiguration: GroupConfigurationList | None + Status: GroupConfigurationStatus | None + FailureReason: GroupConfigurationFailureReason | None class Group(TypedDict, total=False): GroupArn: GroupArnV2 Name: GroupName - Description: Optional[Description] - Criticality: Optional[Criticality] - Owner: Optional[Owner] - DisplayName: Optional[DisplayName] - ApplicationTag: Optional[ApplicationTag] + Description: Description | None + Criticality: Criticality | None + Owner: Owner | None + DisplayName: DisplayName | None + ApplicationTag: ApplicationTag | None class CreateGroupOutput(TypedDict, total=False): - Group: Optional[Group] - ResourceQuery: Optional[ResourceQuery] - Tags: Optional[Tags] - GroupConfiguration: Optional[GroupConfiguration] + Group: Group | None + ResourceQuery: ResourceQuery | None + Tags: Tags | None + GroupConfiguration: GroupConfiguration | None class DeleteGroupInput(ServiceRequest): - GroupName: Optional[GroupName] - Group: Optional[GroupStringV2] + GroupName: GroupName | None + Group: GroupStringV2 | None class DeleteGroupOutput(TypedDict, total=False): - Group: Optional[Group] + Group: Group | None class FailedResource(TypedDict, total=False): - ResourceArn: Optional[ResourceArn] - ErrorMessage: Optional[ErrorMessage] - ErrorCode: Optional[ErrorCode] + ResourceArn: ResourceArn | None + ErrorMessage: ErrorMessage | None + ErrorCode: ErrorCode | None -FailedResourceList = List[FailedResource] +FailedResourceList = list[FailedResource] class GetAccountSettingsOutput(TypedDict, total=False): - AccountSettings: Optional[AccountSettings] + AccountSettings: AccountSettings | None class GetGroupConfigurationInput(ServiceRequest): - Group: Optional[GroupString] + Group: GroupString | None class GetGroupConfigurationOutput(TypedDict, total=False): - GroupConfiguration: Optional[GroupConfiguration] + GroupConfiguration: GroupConfiguration | None class GetGroupInput(ServiceRequest): - GroupName: Optional[GroupName] - Group: Optional[GroupStringV2] + GroupName: GroupName | None + Group: GroupStringV2 | None class GetGroupOutput(TypedDict, total=False): - Group: Optional[Group] + Group: Group | None class GetGroupQueryInput(ServiceRequest): - GroupName: Optional[GroupName] - Group: Optional[GroupString] + GroupName: GroupName | None + Group: GroupString | None class GroupQuery(TypedDict, total=False): @@ -271,7 +271,7 @@ class GroupQuery(TypedDict, total=False): class GetGroupQueryOutput(TypedDict, total=False): - GroupQuery: Optional[GroupQuery] + GroupQuery: GroupQuery | None class GetTagSyncTaskInput(ServiceRequest): @@ -282,16 +282,16 @@ class GetTagSyncTaskInput(ServiceRequest): class GetTagSyncTaskOutput(TypedDict, total=False): - GroupArn: Optional[GroupArnV2] - GroupName: Optional[GroupName] - TaskArn: Optional[TagSyncTaskArn] - TagKey: Optional[TagKey] - TagValue: Optional[TagValue] - ResourceQuery: Optional[ResourceQuery] - RoleArn: Optional[RoleArn] - Status: Optional[TagSyncTaskStatus] - ErrorMessage: Optional[ErrorMessage] - CreatedAt: Optional[timestamp] + GroupArn: GroupArnV2 | None + GroupName: GroupName | None + TaskArn: TagSyncTaskArn | None + TagKey: TagKey | None + TagValue: TagValue | None + ResourceQuery: ResourceQuery | None + RoleArn: RoleArn | None + Status: TagSyncTaskStatus | None + ErrorMessage: ErrorMessage | None + CreatedAt: timestamp | None class GetTagsInput(ServiceRequest): @@ -299,11 +299,11 @@ class GetTagsInput(ServiceRequest): class GetTagsOutput(TypedDict, total=False): - Arn: Optional[GroupArnV2] - Tags: Optional[Tags] + Arn: GroupArnV2 | None + Tags: Tags | None -GroupFilterValues = List[GroupFilterValue] +GroupFilterValues = list[GroupFilterValue] class GroupFilter(TypedDict, total=False): @@ -311,21 +311,21 @@ class GroupFilter(TypedDict, total=False): Values: GroupFilterValues -GroupFilterList = List[GroupFilter] +GroupFilterList = list[GroupFilter] class GroupIdentifier(TypedDict, total=False): - GroupName: Optional[GroupName] - GroupArn: Optional[GroupArn] - Description: Optional[Description] - Criticality: Optional[Criticality] - Owner: Optional[Owner] - DisplayName: Optional[DisplayName] + GroupName: GroupName | None + GroupArn: GroupArn | None + Description: Description | None + Criticality: Criticality | None + Owner: Owner | None + DisplayName: DisplayName | None -GroupIdentifierList = List[GroupIdentifier] -GroupList = List[Group] -ResourceArnList = List[ResourceArn] +GroupIdentifierList = list[GroupIdentifier] +GroupList = list[Group] +ResourceArnList = list[ResourceArn] class GroupResourcesInput(ServiceRequest): @@ -334,29 +334,29 @@ class GroupResourcesInput(ServiceRequest): class PendingResource(TypedDict, total=False): - ResourceArn: Optional[ResourceArn] + ResourceArn: ResourceArn | None -PendingResourceList = List[PendingResource] +PendingResourceList = list[PendingResource] class GroupResourcesOutput(TypedDict, total=False): - Succeeded: Optional[ResourceArnList] - Failed: Optional[FailedResourceList] - Pending: Optional[PendingResourceList] + Succeeded: ResourceArnList | None + Failed: FailedResourceList | None + Pending: PendingResourceList | None class GroupingStatusesItem(TypedDict, total=False): - ResourceArn: Optional[ResourceArn] - Action: Optional[GroupingType] - Status: Optional[GroupingStatus] - ErrorMessage: Optional[ErrorMessage] - ErrorCode: Optional[ErrorCode] - UpdatedAt: Optional[timestamp] + ResourceArn: ResourceArn | None + Action: GroupingType | None + Status: GroupingStatus | None + ErrorMessage: ErrorMessage | None + ErrorCode: ErrorCode | None + UpdatedAt: timestamp | None -GroupingStatusesList = List[GroupingStatusesItem] -ResourceFilterValues = List[ResourceFilterValue] +GroupingStatusesList = list[GroupingStatusesItem] +ResourceFilterValues = list[ResourceFilterValue] class ResourceFilter(TypedDict, total=False): @@ -364,51 +364,51 @@ class ResourceFilter(TypedDict, total=False): Values: ResourceFilterValues -ResourceFilterList = List[ResourceFilter] +ResourceFilterList = list[ResourceFilter] class ListGroupResourcesInput(ServiceRequest): - GroupName: Optional[GroupName] - Group: Optional[GroupStringV2] - Filters: Optional[ResourceFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + GroupName: GroupName | None + Group: GroupStringV2 | None + Filters: ResourceFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ResourceStatus(TypedDict, total=False): - Name: Optional[ResourceStatusValue] + Name: ResourceStatusValue | None class ResourceIdentifier(TypedDict, total=False): - ResourceArn: Optional[ResourceArn] - ResourceType: Optional[ResourceType] + ResourceArn: ResourceArn | None + ResourceType: ResourceType | None class ListGroupResourcesItem(TypedDict, total=False): - Identifier: Optional[ResourceIdentifier] - Status: Optional[ResourceStatus] + Identifier: ResourceIdentifier | None + Status: ResourceStatus | None -ListGroupResourcesItemList = List[ListGroupResourcesItem] +ListGroupResourcesItemList = list[ListGroupResourcesItem] class QueryError(TypedDict, total=False): - ErrorCode: Optional[QueryErrorCode] - Message: Optional[QueryErrorMessage] + ErrorCode: QueryErrorCode | None + Message: QueryErrorMessage | None -QueryErrorList = List[QueryError] -ResourceIdentifierList = List[ResourceIdentifier] +QueryErrorList = list[QueryError] +ResourceIdentifierList = list[ResourceIdentifier] class ListGroupResourcesOutput(TypedDict, total=False): - Resources: Optional[ListGroupResourcesItemList] - ResourceIdentifiers: Optional[ResourceIdentifierList] - NextToken: Optional[NextToken] - QueryErrors: Optional[QueryErrorList] + Resources: ListGroupResourcesItemList | None + ResourceIdentifiers: ResourceIdentifierList | None + NextToken: NextToken | None + QueryErrors: QueryErrorList | None -ListGroupingStatusesFilterValues = List[ListGroupingStatusesFilterValue] +ListGroupingStatusesFilterValues = list[ListGroupingStatusesFilterValue] class ListGroupingStatusesFilter(TypedDict, total=False): @@ -416,72 +416,72 @@ class ListGroupingStatusesFilter(TypedDict, total=False): Values: ListGroupingStatusesFilterValues -ListGroupingStatusesFilterList = List[ListGroupingStatusesFilter] +ListGroupingStatusesFilterList = list[ListGroupingStatusesFilter] class ListGroupingStatusesInput(ServiceRequest): Group: GroupStringV2 - MaxResults: Optional[MaxResults] - Filters: Optional[ListGroupingStatusesFilterList] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + Filters: ListGroupingStatusesFilterList | None + NextToken: NextToken | None class ListGroupingStatusesOutput(TypedDict, total=False): - Group: Optional[GroupStringV2] - GroupingStatuses: Optional[GroupingStatusesList] - NextToken: Optional[NextToken] + Group: GroupStringV2 | None + GroupingStatuses: GroupingStatusesList | None + NextToken: NextToken | None class ListGroupsInput(ServiceRequest): - Filters: Optional[GroupFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: GroupFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListGroupsOutput(TypedDict, total=False): - GroupIdentifiers: Optional[GroupIdentifierList] - Groups: Optional[GroupList] - NextToken: Optional[NextToken] + GroupIdentifiers: GroupIdentifierList | None + Groups: GroupList | None + NextToken: NextToken | None class ListTagSyncTasksFilter(TypedDict, total=False): - GroupArn: Optional[GroupArnV2] - GroupName: Optional[GroupName] + GroupArn: GroupArnV2 | None + GroupName: GroupName | None -ListTagSyncTasksFilterList = List[ListTagSyncTasksFilter] +ListTagSyncTasksFilterList = list[ListTagSyncTasksFilter] class ListTagSyncTasksInput(ServiceRequest): - Filters: Optional[ListTagSyncTasksFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: ListTagSyncTasksFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class TagSyncTaskItem(TypedDict, total=False): - GroupArn: Optional[GroupArnV2] - GroupName: Optional[GroupName] - TaskArn: Optional[TagSyncTaskArn] - TagKey: Optional[TagKey] - TagValue: Optional[TagValue] - ResourceQuery: Optional[ResourceQuery] - RoleArn: Optional[RoleArn] - Status: Optional[TagSyncTaskStatus] - ErrorMessage: Optional[ErrorMessage] - CreatedAt: Optional[timestamp] + GroupArn: GroupArnV2 | None + GroupName: GroupName | None + TaskArn: TagSyncTaskArn | None + TagKey: TagKey | None + TagValue: TagValue | None + ResourceQuery: ResourceQuery | None + RoleArn: RoleArn | None + Status: TagSyncTaskStatus | None + ErrorMessage: ErrorMessage | None + CreatedAt: timestamp | None -TagSyncTaskList = List[TagSyncTaskItem] +TagSyncTaskList = list[TagSyncTaskItem] class ListTagSyncTasksOutput(TypedDict, total=False): - TagSyncTasks: Optional[TagSyncTaskList] - NextToken: Optional[NextToken] + TagSyncTasks: TagSyncTaskList | None + NextToken: NextToken | None class PutGroupConfigurationInput(ServiceRequest): - Group: Optional[GroupString] - Configuration: Optional[GroupConfigurationList] + Group: GroupString | None + Configuration: GroupConfigurationList | None class PutGroupConfigurationOutput(TypedDict, total=False): @@ -490,32 +490,32 @@ class PutGroupConfigurationOutput(TypedDict, total=False): class SearchResourcesInput(ServiceRequest): ResourceQuery: ResourceQuery - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class SearchResourcesOutput(TypedDict, total=False): - ResourceIdentifiers: Optional[ResourceIdentifierList] - NextToken: Optional[NextToken] - QueryErrors: Optional[QueryErrorList] + ResourceIdentifiers: ResourceIdentifierList | None + NextToken: NextToken | None + QueryErrors: QueryErrorList | None class StartTagSyncTaskInput(ServiceRequest): Group: GroupStringV2 - TagKey: Optional[TagKey] - TagValue: Optional[TagValue] - ResourceQuery: Optional[ResourceQuery] + TagKey: TagKey | None + TagValue: TagValue | None + ResourceQuery: ResourceQuery | None RoleArn: RoleArn class StartTagSyncTaskOutput(TypedDict, total=False): - GroupArn: Optional[GroupArnV2] - GroupName: Optional[GroupName] - TaskArn: Optional[TagSyncTaskArn] - TagKey: Optional[TagKey] - TagValue: Optional[TagValue] - ResourceQuery: Optional[ResourceQuery] - RoleArn: Optional[RoleArn] + GroupArn: GroupArnV2 | None + GroupName: GroupName | None + TaskArn: TagSyncTaskArn | None + TagKey: TagKey | None + TagValue: TagValue | None + ResourceQuery: ResourceQuery | None + RoleArn: RoleArn | None class TagInput(ServiceRequest): @@ -523,12 +523,12 @@ class TagInput(ServiceRequest): Tags: Tags -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagOutput(TypedDict, total=False): - Arn: Optional[GroupArnV2] - Tags: Optional[Tags] + Arn: GroupArnV2 | None + Tags: Tags | None class UngroupResourcesInput(ServiceRequest): @@ -537,9 +537,9 @@ class UngroupResourcesInput(ServiceRequest): class UngroupResourcesOutput(TypedDict, total=False): - Succeeded: Optional[ResourceArnList] - Failed: Optional[FailedResourceList] - Pending: Optional[PendingResourceList] + Succeeded: ResourceArnList | None + Failed: FailedResourceList | None + Pending: PendingResourceList | None class UntagInput(ServiceRequest): @@ -548,44 +548,44 @@ class UntagInput(ServiceRequest): class UntagOutput(TypedDict, total=False): - Arn: Optional[GroupArnV2] - Keys: Optional[TagKeyList] + Arn: GroupArnV2 | None + Keys: TagKeyList | None class UpdateAccountSettingsInput(ServiceRequest): - GroupLifecycleEventsDesiredStatus: Optional[GroupLifecycleEventsDesiredStatus] + GroupLifecycleEventsDesiredStatus: GroupLifecycleEventsDesiredStatus | None class UpdateAccountSettingsOutput(TypedDict, total=False): - AccountSettings: Optional[AccountSettings] + AccountSettings: AccountSettings | None class UpdateGroupInput(ServiceRequest): - GroupName: Optional[GroupName] - Group: Optional[GroupStringV2] - Description: Optional[Description] - Criticality: Optional[Criticality] - Owner: Optional[Owner] - DisplayName: Optional[DisplayName] + GroupName: GroupName | None + Group: GroupStringV2 | None + Description: Description | None + Criticality: Criticality | None + Owner: Owner | None + DisplayName: DisplayName | None class UpdateGroupOutput(TypedDict, total=False): - Group: Optional[Group] + Group: Group | None class UpdateGroupQueryInput(ServiceRequest): - GroupName: Optional[GroupName] - Group: Optional[GroupString] + GroupName: GroupName | None + Group: GroupString | None ResourceQuery: ResourceQuery class UpdateGroupQueryOutput(TypedDict, total=False): - GroupQuery: Optional[GroupQuery] + GroupQuery: GroupQuery | None class ResourceGroupsApi: - service = "resource-groups" - version = "2017-11-27" + service: str = "resource-groups" + version: str = "2017-11-27" @handler("CancelTagSyncTask") def cancel_tag_sync_task( diff --git a/localstack-core/localstack/aws/api/resourcegroupstaggingapi/__init__.py b/localstack-core/localstack/aws/api/resourcegroupstaggingapi/__init__.py index cc496818d3120..9c6df7705bd36 100644 --- a/localstack-core/localstack/aws/api/resourcegroupstaggingapi/__init__.py +++ b/localstack-core/localstack/aws/api/resourcegroupstaggingapi/__init__.py @@ -1,19 +1,22 @@ from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler AmazonResourceType = str +CloudFormationResourceType = str ComplianceStatus = bool ErrorMessage = str ExceptionMessage = str ExcludeCompliantResources = bool IncludeComplianceDetails = bool LastUpdated = str +MaxResultsForListRequiredTags = int MaxResultsGetComplianceSummary = int PaginationToken = str Region = str ResourceARN = str +ResourceType = str ResourcesPerPage = int S3Bucket = str S3Location = str @@ -78,13 +81,14 @@ class ThrottledException(ServiceException): status_code: int = 400 -TagKeyList = List[TagKey] +CloudFormationResourceTypes = list[CloudFormationResourceType] +TagKeyList = list[TagKey] class ComplianceDetails(TypedDict, total=False): - NoncompliantKeys: Optional[TagKeyList] - KeysWithNoncompliantValues: Optional[TagKeyList] - ComplianceStatus: Optional[ComplianceStatus] + NoncompliantKeys: TagKeyList | None + KeysWithNoncompliantValues: TagKeyList | None + ComplianceStatus: ComplianceStatus | None class DescribeReportCreationInput(ServiceRequest): @@ -92,76 +96,76 @@ class DescribeReportCreationInput(ServiceRequest): class DescribeReportCreationOutput(TypedDict, total=False): - Status: Optional[Status] - S3Location: Optional[S3Location] - ErrorMessage: Optional[ErrorMessage] + Status: Status | None + S3Location: S3Location | None + ErrorMessage: ErrorMessage | None class FailureInfo(TypedDict, total=False): - StatusCode: Optional[StatusCode] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + StatusCode: StatusCode | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None -FailedResourcesMap = Dict[ResourceARN, FailureInfo] -GroupBy = List[GroupByAttribute] -TagKeyFilterList = List[TagKey] -ResourceTypeFilterList = List[AmazonResourceType] -RegionFilterList = List[Region] -TargetIdFilterList = List[TargetId] +FailedResourcesMap = dict[ResourceARN, FailureInfo] +GroupBy = list[GroupByAttribute] +TagKeyFilterList = list[TagKey] +ResourceTypeFilterList = list[AmazonResourceType] +RegionFilterList = list[Region] +TargetIdFilterList = list[TargetId] class GetComplianceSummaryInput(ServiceRequest): - TargetIdFilters: Optional[TargetIdFilterList] - RegionFilters: Optional[RegionFilterList] - ResourceTypeFilters: Optional[ResourceTypeFilterList] - TagKeyFilters: Optional[TagKeyFilterList] - GroupBy: Optional[GroupBy] - MaxResults: Optional[MaxResultsGetComplianceSummary] - PaginationToken: Optional[PaginationToken] + TargetIdFilters: TargetIdFilterList | None + RegionFilters: RegionFilterList | None + ResourceTypeFilters: ResourceTypeFilterList | None + TagKeyFilters: TagKeyFilterList | None + GroupBy: GroupBy | None + MaxResults: MaxResultsGetComplianceSummary | None + PaginationToken: PaginationToken | None NonCompliantResources = int class Summary(TypedDict, total=False): - LastUpdated: Optional[LastUpdated] - TargetId: Optional[TargetId] - TargetIdType: Optional[TargetIdType] - Region: Optional[Region] - ResourceType: Optional[AmazonResourceType] - NonCompliantResources: Optional[NonCompliantResources] + LastUpdated: LastUpdated | None + TargetId: TargetId | None + TargetIdType: TargetIdType | None + Region: Region | None + ResourceType: AmazonResourceType | None + NonCompliantResources: NonCompliantResources | None -SummaryList = List[Summary] +SummaryList = list[Summary] class GetComplianceSummaryOutput(TypedDict, total=False): - SummaryList: Optional[SummaryList] - PaginationToken: Optional[PaginationToken] + SummaryList: SummaryList | None + PaginationToken: PaginationToken | None -ResourceARNListForGet = List[ResourceARN] -TagValueList = List[TagValue] +ResourceARNListForGet = list[ResourceARN] +TagValueList = list[TagValue] class TagFilter(TypedDict, total=False): - Key: Optional[TagKey] - Values: Optional[TagValueList] + Key: TagKey | None + Values: TagValueList | None -TagFilterList = List[TagFilter] +TagFilterList = list[TagFilter] class GetResourcesInput(ServiceRequest): - PaginationToken: Optional[PaginationToken] - TagFilters: Optional[TagFilterList] - ResourcesPerPage: Optional[ResourcesPerPage] - TagsPerPage: Optional[TagsPerPage] - ResourceTypeFilters: Optional[ResourceTypeFilterList] - IncludeComplianceDetails: Optional[IncludeComplianceDetails] - ExcludeCompliantResources: Optional[ExcludeCompliantResources] - ResourceARNList: Optional[ResourceARNListForGet] + PaginationToken: PaginationToken | None + TagFilters: TagFilterList | None + ResourcesPerPage: ResourcesPerPage | None + TagsPerPage: TagsPerPage | None + ResourceTypeFilters: ResourceTypeFilterList | None + IncludeComplianceDetails: IncludeComplianceDetails | None + ExcludeCompliantResources: ExcludeCompliantResources | None + ResourceARNList: ResourceARNListForGet | None class Tag(TypedDict, total=False): @@ -169,46 +173,68 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] class ResourceTagMapping(TypedDict, total=False): - ResourceARN: Optional[ResourceARN] - Tags: Optional[TagList] - ComplianceDetails: Optional[ComplianceDetails] + ResourceARN: ResourceARN | None + Tags: TagList | None + ComplianceDetails: ComplianceDetails | None -ResourceTagMappingList = List[ResourceTagMapping] +ResourceTagMappingList = list[ResourceTagMapping] class GetResourcesOutput(TypedDict, total=False): - PaginationToken: Optional[PaginationToken] - ResourceTagMappingList: Optional[ResourceTagMappingList] + PaginationToken: PaginationToken | None + ResourceTagMappingList: ResourceTagMappingList | None class GetTagKeysInput(ServiceRequest): - PaginationToken: Optional[PaginationToken] + PaginationToken: PaginationToken | None class GetTagKeysOutput(TypedDict, total=False): - PaginationToken: Optional[PaginationToken] - TagKeys: Optional[TagKeyList] + PaginationToken: PaginationToken | None + TagKeys: TagKeyList | None class GetTagValuesInput(ServiceRequest): - PaginationToken: Optional[PaginationToken] + PaginationToken: PaginationToken | None Key: TagKey -TagValuesOutputList = List[TagValue] +TagValuesOutputList = list[TagValue] class GetTagValuesOutput(TypedDict, total=False): - PaginationToken: Optional[PaginationToken] - TagValues: Optional[TagValuesOutputList] + PaginationToken: PaginationToken | None + TagValues: TagValuesOutputList | None -ResourceARNListForTagUntag = List[ResourceARN] +class ListRequiredTagsInput(ServiceRequest): + NextToken: PaginationToken | None + MaxResults: MaxResultsForListRequiredTags | None + + +ReportingTagKeys = list[TagKey] + + +class RequiredTag(TypedDict, total=False): + ResourceType: ResourceType | None + CloudFormationResourceTypes: CloudFormationResourceTypes | None + ReportingTagKeys: ReportingTagKeys | None + + +RequiredTagsForListRequiredTags = list[RequiredTag] + + +class ListRequiredTagsOutput(TypedDict, total=False): + RequiredTags: RequiredTagsForListRequiredTags | None + NextToken: PaginationToken | None + + +ResourceARNListForTagUntag = list[ResourceARN] class StartReportCreationInput(ServiceRequest): @@ -219,8 +245,8 @@ class StartReportCreationOutput(TypedDict, total=False): pass -TagKeyListForUntag = List[TagKey] -TagMap = Dict[TagKey, TagValue] +TagKeyListForUntag = list[TagKey] +TagMap = dict[TagKey, TagValue] class TagResourcesInput(ServiceRequest): @@ -229,7 +255,7 @@ class TagResourcesInput(ServiceRequest): class TagResourcesOutput(TypedDict, total=False): - FailedResourcesMap: Optional[FailedResourcesMap] + FailedResourcesMap: FailedResourcesMap | None class UntagResourcesInput(ServiceRequest): @@ -238,12 +264,12 @@ class UntagResourcesInput(ServiceRequest): class UntagResourcesOutput(TypedDict, total=False): - FailedResourcesMap: Optional[FailedResourcesMap] + FailedResourcesMap: FailedResourcesMap | None class ResourcegroupstaggingapiApi: - service = "resourcegroupstaggingapi" - version = "2017-01-26" + service: str = "resourcegroupstaggingapi" + version: str = "2017-01-26" @handler("DescribeReportCreation") def describe_report_creation( @@ -298,6 +324,16 @@ def get_tag_values( ) -> GetTagValuesOutput: raise NotImplementedError + @handler("ListRequiredTags") + def list_required_tags( + self, + context: RequestContext, + next_token: PaginationToken | None = None, + max_results: MaxResultsForListRequiredTags | None = None, + **kwargs, + ) -> ListRequiredTagsOutput: + raise NotImplementedError + @handler("StartReportCreation") def start_report_creation( self, context: RequestContext, s3_bucket: S3Bucket, **kwargs diff --git a/localstack-core/localstack/aws/api/route53/__init__.py b/localstack-core/localstack/aws/api/route53/__init__.py index cc139c41afd03..f00029280b2e6 100644 --- a/localstack-core/localstack/aws/api/route53/__init__.py +++ b/localstack-core/localstack/aws/api/route53/__init__.py @@ -1,12 +1,13 @@ from datetime import datetime from enum import StrEnum -from typing import List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler ARN = str AWSAccountID = str AWSRegion = str +AcceleratedRecoveryEnabled = bool AlarmName = str AliasHealthEnabled = bool AssociateVPCComment = str @@ -26,6 +27,7 @@ EnableSNI = bool ErrorMessage = str EvaluationPeriods = int +FailureReason = str FailureThreshold = int FullyQualifiedDomainName = str GeoLocationContinentCode = str @@ -98,6 +100,17 @@ VPCId = str +class AcceleratedRecoveryStatus(StrEnum): + ENABLING = "ENABLING" + ENABLE_FAILED = "ENABLE_FAILED" + ENABLING_HOSTED_ZONE_LOCKED = "ENABLING_HOSTED_ZONE_LOCKED" + ENABLED = "ENABLED" + DISABLING = "DISABLING" + DISABLE_FAILED = "DISABLE_FAILED" + DISABLED = "DISABLED" + DISABLING_HOSTED_ZONE_LOCKED = "DISABLING_HOSTED_ZONE_LOCKED" + + class AccountLimitType(StrEnum): MAX_HEALTH_CHECKS_BY_OWNER = "MAX_HEALTH_CHECKS_BY_OWNER" MAX_HOSTED_ZONES_BY_OWNER = "MAX_HOSTED_ZONES_BY_OWNER" @@ -166,6 +179,9 @@ class CloudWatchRegion(StrEnum): ap_southeast_7 = "ap-southeast-7" ap_east_2 = "ap-east-2" eu_isoe_west_1 = "eu-isoe-west-1" + ap_southeast_6 = "ap-southeast-6" + us_isob_west_1 = "us-isob-west-1" + eusc_de_east_1 = "eusc-de-east-1" class ComparisonOperator(StrEnum): @@ -282,6 +298,8 @@ class ResourceRecordSetRegion(StrEnum): us_gov_east_1 = "us-gov-east-1" us_gov_west_1 = "us-gov-west-1" ap_east_2 = "ap-east-2" + ap_southeast_6 = "ap-southeast-6" + eusc_de_east_1 = "eusc-de-east-1" class ReusableDelegationSetLimitType(StrEnum): @@ -345,6 +363,9 @@ class VPCRegion(StrEnum): ap_southeast_7 = "ap-southeast-7" ap_east_2 = "ap-east-2" eu_isoe_west_1 = "eu-isoe-west-1" + ap_southeast_6 = "ap-southeast-6" + us_isob_west_1 = "us-isob-west-1" + eusc_de_east_1 = "eusc-de-east-1" class CidrBlockInUseException(ServiceException): @@ -491,14 +512,14 @@ class InvalidArgument(ServiceException): status_code: int = 400 -ErrorMessages = List[ErrorMessage] +ErrorMessages = list[ErrorMessage] class InvalidChangeBatch(ServiceException): code: str = "InvalidChangeBatch" sender_fault: bool = False status_code: int = 400 - messages: Optional[ErrorMessages] + messages: ErrorMessages | None class InvalidDomainName(ServiceException): @@ -785,7 +806,7 @@ class ChangeInfo(TypedDict, total=False): Id: ResourceId Status: ChangeStatus SubmittedAt: TimeStamp - Comment: Optional[ResourceDescription] + Comment: ResourceDescription | None class ActivateKeySigningKeyResponse(TypedDict, total=False): @@ -804,14 +825,14 @@ class AliasTarget(TypedDict, total=False): class VPC(TypedDict, total=False): - VPCRegion: Optional[VPCRegion] - VPCId: Optional[VPCId] + VPCRegion: VPCRegion | None + VPCId: VPCId | None class AssociateVPCWithHostedZoneRequest(ServiceRequest): HostedZoneId: ResourceId VPC: VPC - Comment: Optional[AssociateVPCComment] + Comment: AssociateVPCComment | None class AssociateVPCWithHostedZoneResponse(TypedDict, total=False): @@ -824,10 +845,10 @@ class Coordinates(TypedDict, total=False): class GeoProximityLocation(TypedDict, total=False): - AWSRegion: Optional[AWSRegion] - LocalZoneGroup: Optional[LocalZoneGroup] - Coordinates: Optional[Coordinates] - Bias: Optional[Bias] + AWSRegion: AWSRegion | None + LocalZoneGroup: LocalZoneGroup | None + Coordinates: Coordinates | None + Bias: Bias | None class CidrRoutingConfig(TypedDict, total=False): @@ -839,14 +860,14 @@ class ResourceRecord(TypedDict, total=False): Value: RData -ResourceRecords = List[ResourceRecord] +ResourceRecords = list[ResourceRecord] TTL = int class GeoLocation(TypedDict, total=False): - ContinentCode: Optional[GeoLocationContinentCode] - CountryCode: Optional[GeoLocationCountryCode] - SubdivisionCode: Optional[GeoLocationSubdivisionCode] + ContinentCode: GeoLocationContinentCode | None + CountryCode: GeoLocationCountryCode | None + SubdivisionCode: GeoLocationSubdivisionCode | None ResourceRecordSetWeight = int @@ -855,19 +876,19 @@ class GeoLocation(TypedDict, total=False): class ResourceRecordSet(TypedDict, total=False): Name: DNSName Type: RRType - SetIdentifier: Optional[ResourceRecordSetIdentifier] - Weight: Optional[ResourceRecordSetWeight] - Region: Optional[ResourceRecordSetRegion] - GeoLocation: Optional[GeoLocation] - Failover: Optional[ResourceRecordSetFailover] - MultiValueAnswer: Optional[ResourceRecordSetMultiValueAnswer] - TTL: Optional[TTL] - ResourceRecords: Optional[ResourceRecords] - AliasTarget: Optional[AliasTarget] - HealthCheckId: Optional[HealthCheckId] - TrafficPolicyInstanceId: Optional[TrafficPolicyInstanceId] - CidrRoutingConfig: Optional[CidrRoutingConfig] - GeoProximityLocation: Optional[GeoProximityLocation] + SetIdentifier: ResourceRecordSetIdentifier | None + Weight: ResourceRecordSetWeight | None + Region: ResourceRecordSetRegion | None + GeoLocation: GeoLocation | None + Failover: ResourceRecordSetFailover | None + MultiValueAnswer: ResourceRecordSetMultiValueAnswer | None + TTL: TTL | None + ResourceRecords: ResourceRecords | None + AliasTarget: AliasTarget | None + HealthCheckId: HealthCheckId | None + TrafficPolicyInstanceId: TrafficPolicyInstanceId | None + CidrRoutingConfig: CidrRoutingConfig | None + GeoProximityLocation: GeoProximityLocation | None class Change(TypedDict, total=False): @@ -875,15 +896,15 @@ class Change(TypedDict, total=False): ResourceRecordSet: ResourceRecordSet -Changes = List[Change] +Changes = list[Change] class ChangeBatch(TypedDict, total=False): - Comment: Optional[ResourceDescription] + Comment: ResourceDescription | None Changes: Changes -CidrList = List[Cidr] +CidrList = list[Cidr] class CidrCollectionChange(TypedDict, total=False): @@ -892,13 +913,13 @@ class CidrCollectionChange(TypedDict, total=False): CidrList: CidrList -CidrCollectionChanges = List[CidrCollectionChange] +CidrCollectionChanges = list[CidrCollectionChange] CollectionVersion = int class ChangeCidrCollectionRequest(ServiceRequest): Id: UUID - CollectionVersion: Optional[CollectionVersion] + CollectionVersion: CollectionVersion | None Changes: CidrCollectionChanges @@ -915,45 +936,45 @@ class ChangeResourceRecordSetsResponse(TypedDict, total=False): ChangeInfo: ChangeInfo -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class Tag(TypedDict, total=False): - Key: Optional[TagKey] - Value: Optional[TagValue] + Key: TagKey | None + Value: TagValue | None -TagList = List[Tag] +TagList = list[Tag] class ChangeTagsForResourceRequest(ServiceRequest): ResourceType: TagResourceType ResourceId: TagResourceId - AddTags: Optional[TagList] - RemoveTagKeys: Optional[TagKeyList] + AddTags: TagList | None + RemoveTagKeys: TagKeyList | None class ChangeTagsForResourceResponse(TypedDict, total=False): pass -CheckerIpRanges = List[IPAddressCidr] -ChildHealthCheckList = List[HealthCheckId] +CheckerIpRanges = list[IPAddressCidr] +ChildHealthCheckList = list[HealthCheckId] class CidrBlockSummary(TypedDict, total=False): - CidrBlock: Optional[Cidr] - LocationName: Optional[CidrLocationNameDefaultNotAllowed] + CidrBlock: Cidr | None + LocationName: CidrLocationNameDefaultNotAllowed | None -CidrBlockSummaries = List[CidrBlockSummary] +CidrBlockSummaries = list[CidrBlockSummary] class CidrCollection(TypedDict, total=False): - Arn: Optional[ARN] - Id: Optional[UUID] - Name: Optional[CollectionName] - Version: Optional[CollectionVersion] + Arn: ARN | None + Id: UUID | None + Name: CollectionName | None + Version: CollectionVersion | None class Dimension(TypedDict, total=False): @@ -961,7 +982,7 @@ class Dimension(TypedDict, total=False): Value: DimensionField -DimensionList = List[Dimension] +DimensionList = list[Dimension] class CloudWatchAlarmConfiguration(TypedDict, total=False): @@ -972,17 +993,17 @@ class CloudWatchAlarmConfiguration(TypedDict, total=False): MetricName: MetricName Namespace: Namespace Statistic: Statistic - Dimensions: Optional[DimensionList] + Dimensions: DimensionList | None class CollectionSummary(TypedDict, total=False): - Arn: Optional[ARN] - Id: Optional[UUID] - Name: Optional[CollectionName] - Version: Optional[CollectionVersion] + Arn: ARN | None + Id: UUID | None + Name: CollectionName | None + Version: CollectionVersion | None -CollectionSummaries = List[CollectionSummary] +CollectionSummaries = list[CollectionSummary] class CreateCidrCollectionRequest(ServiceRequest): @@ -991,32 +1012,32 @@ class CreateCidrCollectionRequest(ServiceRequest): class CreateCidrCollectionResponse(TypedDict, total=False): - Collection: Optional[CidrCollection] - Location: Optional[ResourceURI] + Collection: CidrCollection | None + Location: ResourceURI | None -HealthCheckRegionList = List[HealthCheckRegion] +HealthCheckRegionList = list[HealthCheckRegion] class HealthCheckConfig(TypedDict, total=False): - IPAddress: Optional[IPAddress] - Port: Optional[Port] + IPAddress: IPAddress | None + Port: Port | None Type: HealthCheckType - ResourcePath: Optional[ResourcePath] - FullyQualifiedDomainName: Optional[FullyQualifiedDomainName] - SearchString: Optional[SearchString] - RequestInterval: Optional[RequestInterval] - FailureThreshold: Optional[FailureThreshold] - MeasureLatency: Optional[MeasureLatency] - Inverted: Optional[Inverted] - Disabled: Optional[Disabled] - HealthThreshold: Optional[HealthThreshold] - ChildHealthChecks: Optional[ChildHealthCheckList] - EnableSNI: Optional[EnableSNI] - Regions: Optional[HealthCheckRegionList] - AlarmIdentifier: Optional[AlarmIdentifier] - InsufficientDataHealthStatus: Optional[InsufficientDataHealthStatus] - RoutingControlArn: Optional[RoutingControlArn] + ResourcePath: ResourcePath | None + FullyQualifiedDomainName: FullyQualifiedDomainName | None + SearchString: SearchString | None + RequestInterval: RequestInterval | None + FailureThreshold: FailureThreshold | None + MeasureLatency: MeasureLatency | None + Inverted: Inverted | None + Disabled: Disabled | None + HealthThreshold: HealthThreshold | None + ChildHealthChecks: ChildHealthCheckList | None + EnableSNI: EnableSNI | None + Regions: HealthCheckRegionList | None + AlarmIdentifier: AlarmIdentifier | None + InsufficientDataHealthStatus: InsufficientDataHealthStatus | None + RoutingControlArn: RoutingControlArn | None class CreateHealthCheckRequest(ServiceRequest): @@ -1028,17 +1049,17 @@ class CreateHealthCheckRequest(ServiceRequest): class LinkedService(TypedDict, total=False): - ServicePrincipal: Optional[ServicePrincipal] - Description: Optional[ResourceDescription] + ServicePrincipal: ServicePrincipal | None + Description: ResourceDescription | None class HealthCheck(TypedDict, total=False): Id: HealthCheckId CallerReference: HealthCheckNonce - LinkedService: Optional[LinkedService] + LinkedService: LinkedService | None HealthCheckConfig: HealthCheckConfig HealthCheckVersion: HealthCheckVersion - CloudWatchAlarmConfiguration: Optional[CloudWatchAlarmConfiguration] + CloudWatchAlarmConfiguration: CloudWatchAlarmConfiguration | None class CreateHealthCheckResponse(TypedDict, total=False): @@ -1047,27 +1068,36 @@ class CreateHealthCheckResponse(TypedDict, total=False): class HostedZoneConfig(TypedDict, total=False): - Comment: Optional[ResourceDescription] - PrivateZone: Optional[IsPrivateZone] + Comment: ResourceDescription | None + PrivateZone: IsPrivateZone | None class CreateHostedZoneRequest(ServiceRequest): Name: DNSName - VPC: Optional[VPC] + VPC: VPC | None CallerReference: Nonce - HostedZoneConfig: Optional[HostedZoneConfig] - DelegationSetId: Optional[ResourceId] + HostedZoneConfig: HostedZoneConfig | None + DelegationSetId: ResourceId | None -DelegationSetNameServers = List[DNSName] +DelegationSetNameServers = list[DNSName] class DelegationSet(TypedDict, total=False): - Id: Optional[ResourceId] - CallerReference: Optional[Nonce] + Id: ResourceId | None + CallerReference: Nonce | None NameServers: DelegationSetNameServers +class HostedZoneFailureReasons(TypedDict, total=False): + AcceleratedRecovery: FailureReason | None + + +class HostedZoneFeatures(TypedDict, total=False): + AcceleratedRecoveryStatus: AcceleratedRecoveryStatus | None + FailureReasons: HostedZoneFailureReasons | None + + HostedZoneRRSetCount = int @@ -1075,16 +1105,17 @@ class HostedZone(TypedDict, total=False): Id: ResourceId Name: DNSName CallerReference: Nonce - Config: Optional[HostedZoneConfig] - ResourceRecordSetCount: Optional[HostedZoneRRSetCount] - LinkedService: Optional[LinkedService] + Config: HostedZoneConfig | None + ResourceRecordSetCount: HostedZoneRRSetCount | None + LinkedService: LinkedService | None + Features: HostedZoneFeatures | None class CreateHostedZoneResponse(TypedDict, total=False): HostedZone: HostedZone ChangeInfo: ChangeInfo DelegationSet: DelegationSet - VPC: Optional[VPC] + VPC: VPC | None Location: ResourceURI @@ -1097,22 +1128,22 @@ class CreateKeySigningKeyRequest(ServiceRequest): class KeySigningKey(TypedDict, total=False): - Name: Optional[SigningKeyName] - KmsArn: Optional[SigningKeyString] - Flag: Optional[SigningKeyInteger] - SigningAlgorithmMnemonic: Optional[SigningKeyString] - SigningAlgorithmType: Optional[SigningKeyInteger] - DigestAlgorithmMnemonic: Optional[SigningKeyString] - DigestAlgorithmType: Optional[SigningKeyInteger] - KeyTag: Optional[SigningKeyTag] - DigestValue: Optional[SigningKeyString] - PublicKey: Optional[SigningKeyString] - DSRecord: Optional[SigningKeyString] - DNSKEYRecord: Optional[SigningKeyString] - Status: Optional[SigningKeyStatus] - StatusMessage: Optional[SigningKeyStatusMessage] - CreatedDate: Optional[TimeStamp] - LastModifiedDate: Optional[TimeStamp] + Name: SigningKeyName | None + KmsArn: SigningKeyString | None + Flag: SigningKeyInteger | None + SigningAlgorithmMnemonic: SigningKeyString | None + SigningAlgorithmType: SigningKeyInteger | None + DigestAlgorithmMnemonic: SigningKeyString | None + DigestAlgorithmType: SigningKeyInteger | None + KeyTag: SigningKeyTag | None + DigestValue: SigningKeyString | None + PublicKey: SigningKeyString | None + DSRecord: SigningKeyString | None + DNSKEYRecord: SigningKeyString | None + Status: SigningKeyStatus | None + StatusMessage: SigningKeyStatusMessage | None + CreatedDate: TimeStamp | None + LastModifiedDate: TimeStamp | None class CreateKeySigningKeyResponse(TypedDict, total=False): @@ -1139,7 +1170,7 @@ class CreateQueryLoggingConfigResponse(TypedDict, total=False): class CreateReusableDelegationSetRequest(ServiceRequest): CallerReference: Nonce - HostedZoneId: Optional[ResourceId] + HostedZoneId: ResourceId | None class CreateReusableDelegationSetResponse(TypedDict, total=False): @@ -1175,7 +1206,7 @@ class CreateTrafficPolicyInstanceResponse(TypedDict, total=False): class CreateTrafficPolicyRequest(ServiceRequest): Name: TrafficPolicyName Document: TrafficPolicyDocument - Comment: Optional[TrafficPolicyComment] + Comment: TrafficPolicyComment | None class TrafficPolicy(TypedDict, total=False): @@ -1184,7 +1215,7 @@ class TrafficPolicy(TypedDict, total=False): Name: TrafficPolicyName Type: RRType Document: TrafficPolicyDocument - Comment: Optional[TrafficPolicyComment] + Comment: TrafficPolicyComment | None class CreateTrafficPolicyResponse(TypedDict, total=False): @@ -1195,7 +1226,7 @@ class CreateTrafficPolicyResponse(TypedDict, total=False): class CreateTrafficPolicyVersionRequest(ServiceRequest): Id: TrafficPolicyId Document: TrafficPolicyDocument - Comment: Optional[TrafficPolicyComment] + Comment: TrafficPolicyComment | None class CreateTrafficPolicyVersionResponse(TypedDict, total=False): @@ -1214,8 +1245,8 @@ class CreateVPCAssociationAuthorizationResponse(TypedDict, total=False): class DNSSECStatus(TypedDict, total=False): - ServeSignature: Optional[ServeSignature] - StatusMessage: Optional[SigningKeyStatusMessage] + ServeSignature: ServeSignature | None + StatusMessage: SigningKeyStatusMessage | None class DeactivateKeySigningKeyRequest(ServiceRequest): @@ -1227,7 +1258,7 @@ class DeactivateKeySigningKeyResponse(TypedDict, total=False): ChangeInfo: ChangeInfo -DelegationSets = List[DelegationSet] +DelegationSets = list[DelegationSet] class DeleteCidrCollectionRequest(ServiceRequest): @@ -1316,7 +1347,7 @@ class DisableHostedZoneDNSSECResponse(TypedDict, total=False): class DisassociateVPCFromHostedZoneRequest(ServiceRequest): HostedZoneId: ResourceId VPC: VPC - Comment: Optional[DisassociateVPCComment] + Comment: DisassociateVPCComment | None class DisassociateVPCFromHostedZoneResponse(TypedDict, total=False): @@ -1332,15 +1363,15 @@ class EnableHostedZoneDNSSECResponse(TypedDict, total=False): class GeoLocationDetails(TypedDict, total=False): - ContinentCode: Optional[GeoLocationContinentCode] - ContinentName: Optional[GeoLocationContinentName] - CountryCode: Optional[GeoLocationCountryCode] - CountryName: Optional[GeoLocationCountryName] - SubdivisionCode: Optional[GeoLocationSubdivisionCode] - SubdivisionName: Optional[GeoLocationSubdivisionName] + ContinentCode: GeoLocationContinentCode | None + ContinentName: GeoLocationContinentName | None + CountryCode: GeoLocationCountryCode | None + CountryName: GeoLocationCountryName | None + SubdivisionCode: GeoLocationSubdivisionCode | None + SubdivisionName: GeoLocationSubdivisionName | None -GeoLocationDetailsList = List[GeoLocationDetails] +GeoLocationDetailsList = list[GeoLocationDetails] class GetAccountLimitRequest(ServiceRequest): @@ -1375,7 +1406,7 @@ class GetDNSSECRequest(ServiceRequest): HostedZoneId: ResourceId -KeySigningKeys = List[KeySigningKey] +KeySigningKeys = list[KeySigningKey] class GetDNSSECResponse(TypedDict, total=False): @@ -1384,9 +1415,9 @@ class GetDNSSECResponse(TypedDict, total=False): class GetGeoLocationRequest(ServiceRequest): - ContinentCode: Optional[GeoLocationContinentCode] - CountryCode: Optional[GeoLocationCountryCode] - SubdivisionCode: Optional[GeoLocationSubdivisionCode] + ContinentCode: GeoLocationContinentCode | None + CountryCode: GeoLocationCountryCode | None + SubdivisionCode: GeoLocationSubdivisionCode | None class GetGeoLocationResponse(TypedDict, total=False): @@ -1409,17 +1440,17 @@ class GetHealthCheckLastFailureReasonRequest(ServiceRequest): class StatusReport(TypedDict, total=False): - Status: Optional[Status] - CheckedTime: Optional[TimeStamp] + Status: Status | None + CheckedTime: TimeStamp | None class HealthCheckObservation(TypedDict, total=False): - Region: Optional[HealthCheckRegion] - IPAddress: Optional[IPAddress] - StatusReport: Optional[StatusReport] + Region: HealthCheckRegion | None + IPAddress: IPAddress | None + StatusReport: StatusReport | None -HealthCheckObservations = List[HealthCheckObservation] +HealthCheckObservations = list[HealthCheckObservation] class GetHealthCheckLastFailureReasonResponse(TypedDict, total=False): @@ -1472,13 +1503,13 @@ class GetHostedZoneRequest(ServiceRequest): Id: ResourceId -VPCs = List[VPC] +VPCs = list[VPC] class GetHostedZoneResponse(TypedDict, total=False): HostedZone: HostedZone - DelegationSet: Optional[DelegationSet] - VPCs: Optional[VPCs] + DelegationSet: DelegationSet | None + VPCs: VPCs | None class GetQueryLoggingConfigRequest(ServiceRequest): @@ -1537,12 +1568,12 @@ class GetTrafficPolicyResponse(TypedDict, total=False): TrafficPolicy: TrafficPolicy -HealthChecks = List[HealthCheck] +HealthChecks = list[HealthCheck] class HostedZoneOwner(TypedDict, total=False): - OwningAccount: Optional[AWSAccountID] - OwningService: Optional[HostedZoneOwningService] + OwningAccount: AWSAccountID | None + OwningService: HostedZoneOwningService | None class HostedZoneSummary(TypedDict, total=False): @@ -1551,167 +1582,167 @@ class HostedZoneSummary(TypedDict, total=False): Owner: HostedZoneOwner -HostedZoneSummaries = List[HostedZoneSummary] -HostedZones = List[HostedZone] +HostedZoneSummaries = list[HostedZoneSummary] +HostedZones = list[HostedZone] class ListCidrBlocksRequest(ServiceRequest): CollectionId: UUID - LocationName: Optional[CidrLocationNameDefaultNotAllowed] - NextToken: Optional[PaginationToken] - MaxResults: Optional[MaxResults] + LocationName: CidrLocationNameDefaultNotAllowed | None + NextToken: PaginationToken | None + MaxResults: MaxResults | None class ListCidrBlocksResponse(TypedDict, total=False): - NextToken: Optional[PaginationToken] - CidrBlocks: Optional[CidrBlockSummaries] + NextToken: PaginationToken | None + CidrBlocks: CidrBlockSummaries | None class ListCidrCollectionsRequest(ServiceRequest): - NextToken: Optional[PaginationToken] - MaxResults: Optional[MaxResults] + NextToken: PaginationToken | None + MaxResults: MaxResults | None class ListCidrCollectionsResponse(TypedDict, total=False): - NextToken: Optional[PaginationToken] - CidrCollections: Optional[CollectionSummaries] + NextToken: PaginationToken | None + CidrCollections: CollectionSummaries | None class ListCidrLocationsRequest(ServiceRequest): CollectionId: UUID - NextToken: Optional[PaginationToken] - MaxResults: Optional[MaxResults] + NextToken: PaginationToken | None + MaxResults: MaxResults | None class LocationSummary(TypedDict, total=False): - LocationName: Optional[CidrLocationNameDefaultAllowed] + LocationName: CidrLocationNameDefaultAllowed | None -LocationSummaries = List[LocationSummary] +LocationSummaries = list[LocationSummary] class ListCidrLocationsResponse(TypedDict, total=False): - NextToken: Optional[PaginationToken] - CidrLocations: Optional[LocationSummaries] + NextToken: PaginationToken | None + CidrLocations: LocationSummaries | None class ListGeoLocationsRequest(ServiceRequest): - StartContinentCode: Optional[GeoLocationContinentCode] - StartCountryCode: Optional[GeoLocationCountryCode] - StartSubdivisionCode: Optional[GeoLocationSubdivisionCode] - MaxItems: Optional[PageMaxItems] + StartContinentCode: GeoLocationContinentCode | None + StartCountryCode: GeoLocationCountryCode | None + StartSubdivisionCode: GeoLocationSubdivisionCode | None + MaxItems: PageMaxItems | None class ListGeoLocationsResponse(TypedDict, total=False): GeoLocationDetailsList: GeoLocationDetailsList IsTruncated: PageTruncated - NextContinentCode: Optional[GeoLocationContinentCode] - NextCountryCode: Optional[GeoLocationCountryCode] - NextSubdivisionCode: Optional[GeoLocationSubdivisionCode] + NextContinentCode: GeoLocationContinentCode | None + NextCountryCode: GeoLocationCountryCode | None + NextSubdivisionCode: GeoLocationSubdivisionCode | None MaxItems: PageMaxItems class ListHealthChecksRequest(ServiceRequest): - Marker: Optional[PageMarker] - MaxItems: Optional[PageMaxItems] + Marker: PageMarker | None + MaxItems: PageMaxItems | None class ListHealthChecksResponse(TypedDict, total=False): HealthChecks: HealthChecks Marker: PageMarker IsTruncated: PageTruncated - NextMarker: Optional[PageMarker] + NextMarker: PageMarker | None MaxItems: PageMaxItems class ListHostedZonesByNameRequest(ServiceRequest): - DNSName: Optional[DNSName] - HostedZoneId: Optional[ResourceId] - MaxItems: Optional[PageMaxItems] + DNSName: DNSName | None + HostedZoneId: ResourceId | None + MaxItems: PageMaxItems | None class ListHostedZonesByNameResponse(TypedDict, total=False): HostedZones: HostedZones - DNSName: Optional[DNSName] - HostedZoneId: Optional[ResourceId] + DNSName: DNSName | None + HostedZoneId: ResourceId | None IsTruncated: PageTruncated - NextDNSName: Optional[DNSName] - NextHostedZoneId: Optional[ResourceId] + NextDNSName: DNSName | None + NextHostedZoneId: ResourceId | None MaxItems: PageMaxItems class ListHostedZonesByVPCRequest(ServiceRequest): VPCId: VPCId VPCRegion: VPCRegion - MaxItems: Optional[PageMaxItems] - NextToken: Optional[PaginationToken] + MaxItems: PageMaxItems | None + NextToken: PaginationToken | None class ListHostedZonesByVPCResponse(TypedDict, total=False): HostedZoneSummaries: HostedZoneSummaries MaxItems: PageMaxItems - NextToken: Optional[PaginationToken] + NextToken: PaginationToken | None class ListHostedZonesRequest(ServiceRequest): - Marker: Optional[PageMarker] - MaxItems: Optional[PageMaxItems] - DelegationSetId: Optional[ResourceId] - HostedZoneType: Optional[HostedZoneType] + Marker: PageMarker | None + MaxItems: PageMaxItems | None + DelegationSetId: ResourceId | None + HostedZoneType: HostedZoneType | None class ListHostedZonesResponse(TypedDict, total=False): HostedZones: HostedZones Marker: PageMarker IsTruncated: PageTruncated - NextMarker: Optional[PageMarker] + NextMarker: PageMarker | None MaxItems: PageMaxItems class ListQueryLoggingConfigsRequest(ServiceRequest): - HostedZoneId: Optional[ResourceId] - NextToken: Optional[PaginationToken] - MaxResults: Optional[MaxResults] + HostedZoneId: ResourceId | None + NextToken: PaginationToken | None + MaxResults: MaxResults | None -QueryLoggingConfigs = List[QueryLoggingConfig] +QueryLoggingConfigs = list[QueryLoggingConfig] class ListQueryLoggingConfigsResponse(TypedDict, total=False): QueryLoggingConfigs: QueryLoggingConfigs - NextToken: Optional[PaginationToken] + NextToken: PaginationToken | None class ListResourceRecordSetsRequest(ServiceRequest): HostedZoneId: ResourceId - StartRecordName: Optional[DNSName] - StartRecordType: Optional[RRType] - StartRecordIdentifier: Optional[ResourceRecordSetIdentifier] - MaxItems: Optional[PageMaxItems] + StartRecordName: DNSName | None + StartRecordType: RRType | None + StartRecordIdentifier: ResourceRecordSetIdentifier | None + MaxItems: PageMaxItems | None -ResourceRecordSets = List[ResourceRecordSet] +ResourceRecordSets = list[ResourceRecordSet] class ListResourceRecordSetsResponse(TypedDict, total=False): ResourceRecordSets: ResourceRecordSets IsTruncated: PageTruncated - NextRecordName: Optional[DNSName] - NextRecordType: Optional[RRType] - NextRecordIdentifier: Optional[ResourceRecordSetIdentifier] + NextRecordName: DNSName | None + NextRecordType: RRType | None + NextRecordIdentifier: ResourceRecordSetIdentifier | None MaxItems: PageMaxItems class ListReusableDelegationSetsRequest(ServiceRequest): - Marker: Optional[PageMarker] - MaxItems: Optional[PageMaxItems] + Marker: PageMarker | None + MaxItems: PageMaxItems | None class ListReusableDelegationSetsResponse(TypedDict, total=False): DelegationSets: DelegationSets Marker: PageMarker IsTruncated: PageTruncated - NextMarker: Optional[PageMarker] + NextMarker: PageMarker | None MaxItems: PageMaxItems @@ -1721,16 +1752,16 @@ class ListTagsForResourceRequest(ServiceRequest): class ResourceTagSet(TypedDict, total=False): - ResourceType: Optional[TagResourceType] - ResourceId: Optional[TagResourceId] - Tags: Optional[TagList] + ResourceType: TagResourceType | None + ResourceId: TagResourceId | None + Tags: TagList | None class ListTagsForResourceResponse(TypedDict, total=False): ResourceTagSet: ResourceTagSet -TagResourceIdList = List[TagResourceId] +TagResourceIdList = list[TagResourceId] class ListTagsForResourcesRequest(ServiceRequest): @@ -1738,7 +1769,7 @@ class ListTagsForResourcesRequest(ServiceRequest): ResourceIds: TagResourceIdList -ResourceTagSetList = List[ResourceTagSet] +ResourceTagSetList = list[ResourceTagSet] class ListTagsForResourcesResponse(TypedDict, total=False): @@ -1746,8 +1777,8 @@ class ListTagsForResourcesResponse(TypedDict, total=False): class ListTrafficPoliciesRequest(ServiceRequest): - TrafficPolicyIdMarker: Optional[TrafficPolicyId] - MaxItems: Optional[PageMaxItems] + TrafficPolicyIdMarker: TrafficPolicyId | None + MaxItems: PageMaxItems | None class TrafficPolicySummary(TypedDict, total=False): @@ -1758,7 +1789,7 @@ class TrafficPolicySummary(TypedDict, total=False): TrafficPolicyCount: TrafficPolicyVersion -TrafficPolicySummaries = List[TrafficPolicySummary] +TrafficPolicySummaries = list[TrafficPolicySummary] class ListTrafficPoliciesResponse(TypedDict, total=False): @@ -1770,18 +1801,18 @@ class ListTrafficPoliciesResponse(TypedDict, total=False): class ListTrafficPolicyInstancesByHostedZoneRequest(ServiceRequest): HostedZoneId: ResourceId - TrafficPolicyInstanceNameMarker: Optional[DNSName] - TrafficPolicyInstanceTypeMarker: Optional[RRType] - MaxItems: Optional[PageMaxItems] + TrafficPolicyInstanceNameMarker: DNSName | None + TrafficPolicyInstanceTypeMarker: RRType | None + MaxItems: PageMaxItems | None -TrafficPolicyInstances = List[TrafficPolicyInstance] +TrafficPolicyInstances = list[TrafficPolicyInstance] class ListTrafficPolicyInstancesByHostedZoneResponse(TypedDict, total=False): TrafficPolicyInstances: TrafficPolicyInstances - TrafficPolicyInstanceNameMarker: Optional[DNSName] - TrafficPolicyInstanceTypeMarker: Optional[RRType] + TrafficPolicyInstanceNameMarker: DNSName | None + TrafficPolicyInstanceTypeMarker: RRType | None IsTruncated: PageTruncated MaxItems: PageMaxItems @@ -1789,44 +1820,44 @@ class ListTrafficPolicyInstancesByHostedZoneResponse(TypedDict, total=False): class ListTrafficPolicyInstancesByPolicyRequest(ServiceRequest): TrafficPolicyId: TrafficPolicyId TrafficPolicyVersion: TrafficPolicyVersion - HostedZoneIdMarker: Optional[ResourceId] - TrafficPolicyInstanceNameMarker: Optional[DNSName] - TrafficPolicyInstanceTypeMarker: Optional[RRType] - MaxItems: Optional[PageMaxItems] + HostedZoneIdMarker: ResourceId | None + TrafficPolicyInstanceNameMarker: DNSName | None + TrafficPolicyInstanceTypeMarker: RRType | None + MaxItems: PageMaxItems | None class ListTrafficPolicyInstancesByPolicyResponse(TypedDict, total=False): TrafficPolicyInstances: TrafficPolicyInstances - HostedZoneIdMarker: Optional[ResourceId] - TrafficPolicyInstanceNameMarker: Optional[DNSName] - TrafficPolicyInstanceTypeMarker: Optional[RRType] + HostedZoneIdMarker: ResourceId | None + TrafficPolicyInstanceNameMarker: DNSName | None + TrafficPolicyInstanceTypeMarker: RRType | None IsTruncated: PageTruncated MaxItems: PageMaxItems class ListTrafficPolicyInstancesRequest(ServiceRequest): - HostedZoneIdMarker: Optional[ResourceId] - TrafficPolicyInstanceNameMarker: Optional[DNSName] - TrafficPolicyInstanceTypeMarker: Optional[RRType] - MaxItems: Optional[PageMaxItems] + HostedZoneIdMarker: ResourceId | None + TrafficPolicyInstanceNameMarker: DNSName | None + TrafficPolicyInstanceTypeMarker: RRType | None + MaxItems: PageMaxItems | None class ListTrafficPolicyInstancesResponse(TypedDict, total=False): TrafficPolicyInstances: TrafficPolicyInstances - HostedZoneIdMarker: Optional[ResourceId] - TrafficPolicyInstanceNameMarker: Optional[DNSName] - TrafficPolicyInstanceTypeMarker: Optional[RRType] + HostedZoneIdMarker: ResourceId | None + TrafficPolicyInstanceNameMarker: DNSName | None + TrafficPolicyInstanceTypeMarker: RRType | None IsTruncated: PageTruncated MaxItems: PageMaxItems class ListTrafficPolicyVersionsRequest(ServiceRequest): Id: TrafficPolicyId - TrafficPolicyVersionMarker: Optional[TrafficPolicyVersionMarker] - MaxItems: Optional[PageMaxItems] + TrafficPolicyVersionMarker: TrafficPolicyVersionMarker | None + MaxItems: PageMaxItems | None -TrafficPolicies = List[TrafficPolicy] +TrafficPolicies = list[TrafficPolicy] class ListTrafficPolicyVersionsResponse(TypedDict, total=False): @@ -1838,27 +1869,27 @@ class ListTrafficPolicyVersionsResponse(TypedDict, total=False): class ListVPCAssociationAuthorizationsRequest(ServiceRequest): HostedZoneId: ResourceId - NextToken: Optional[PaginationToken] - MaxResults: Optional[MaxResults] + NextToken: PaginationToken | None + MaxResults: MaxResults | None class ListVPCAssociationAuthorizationsResponse(TypedDict, total=False): HostedZoneId: ResourceId - NextToken: Optional[PaginationToken] + NextToken: PaginationToken | None VPCs: VPCs -RecordData = List[RecordDataEntry] -ResettableElementNameList = List[ResettableElementName] +RecordData = list[RecordDataEntry] +ResettableElementNameList = list[ResettableElementName] class TestDNSAnswerRequest(ServiceRequest): HostedZoneId: ResourceId RecordName: DNSName RecordType: RRType - ResolverIP: Optional[IPAddress] - EDNS0ClientSubnetIP: Optional[IPAddress] - EDNS0ClientSubnetMask: Optional[SubnetMask] + ResolverIP: IPAddress | None + EDNS0ClientSubnetIP: IPAddress | None + EDNS0ClientSubnetMask: SubnetMask | None class TestDNSAnswerResponse(TypedDict, total=False): @@ -1872,22 +1903,22 @@ class TestDNSAnswerResponse(TypedDict, total=False): class UpdateHealthCheckRequest(ServiceRequest): HealthCheckId: HealthCheckId - HealthCheckVersion: Optional[HealthCheckVersion] - IPAddress: Optional[IPAddress] - Port: Optional[Port] - ResourcePath: Optional[ResourcePath] - FullyQualifiedDomainName: Optional[FullyQualifiedDomainName] - SearchString: Optional[SearchString] - FailureThreshold: Optional[FailureThreshold] - Inverted: Optional[Inverted] - Disabled: Optional[Disabled] - HealthThreshold: Optional[HealthThreshold] - ChildHealthChecks: Optional[ChildHealthCheckList] - EnableSNI: Optional[EnableSNI] - Regions: Optional[HealthCheckRegionList] - AlarmIdentifier: Optional[AlarmIdentifier] - InsufficientDataHealthStatus: Optional[InsufficientDataHealthStatus] - ResetElements: Optional[ResettableElementNameList] + HealthCheckVersion: HealthCheckVersion | None + IPAddress: IPAddress | None + Port: Port | None + ResourcePath: ResourcePath | None + FullyQualifiedDomainName: FullyQualifiedDomainName | None + SearchString: SearchString | None + FailureThreshold: FailureThreshold | None + Inverted: Inverted | None + Disabled: Disabled | None + HealthThreshold: HealthThreshold | None + ChildHealthChecks: ChildHealthCheckList | None + EnableSNI: EnableSNI | None + Regions: HealthCheckRegionList | None + AlarmIdentifier: AlarmIdentifier | None + InsufficientDataHealthStatus: InsufficientDataHealthStatus | None + ResetElements: ResettableElementNameList | None class UpdateHealthCheckResponse(TypedDict, total=False): @@ -1896,13 +1927,22 @@ class UpdateHealthCheckResponse(TypedDict, total=False): class UpdateHostedZoneCommentRequest(ServiceRequest): Id: ResourceId - Comment: Optional[ResourceDescription] + Comment: ResourceDescription | None class UpdateHostedZoneCommentResponse(TypedDict, total=False): HostedZone: HostedZone +class UpdateHostedZoneFeaturesRequest(ServiceRequest): + HostedZoneId: ResourceId + EnableAcceleratedRecovery: AcceleratedRecoveryEnabled | None + + +class UpdateHostedZoneFeaturesResponse(TypedDict, total=False): + pass + + class UpdateTrafficPolicyCommentRequest(ServiceRequest): Id: TrafficPolicyId Version: TrafficPolicyVersion @@ -1925,8 +1965,8 @@ class UpdateTrafficPolicyInstanceResponse(TypedDict, total=False): class Route53Api: - service = "route53" - version = "2013-04-01" + service: str = "route53" + version: str = "2013-04-01" @handler("ActivateKeySigningKey") def activate_key_signing_key( @@ -2538,6 +2578,16 @@ def update_hosted_zone_comment( ) -> UpdateHostedZoneCommentResponse: raise NotImplementedError + @handler("UpdateHostedZoneFeatures") + def update_hosted_zone_features( + self, + context: RequestContext, + hosted_zone_id: ResourceId, + enable_accelerated_recovery: AcceleratedRecoveryEnabled | None = None, + **kwargs, + ) -> UpdateHostedZoneFeaturesResponse: + raise NotImplementedError + @handler("UpdateTrafficPolicyComment") def update_traffic_policy_comment( self, diff --git a/localstack-core/localstack/aws/api/route53resolver/__init__.py b/localstack-core/localstack/aws/api/route53resolver/__init__.py index 35e718630ef4b..947399623f555 100644 --- a/localstack-core/localstack/aws/api/route53resolver/__init__.py +++ b/localstack-core/localstack/aws/api/route53resolver/__init__.py @@ -1,5 +1,5 @@ from enum import StrEnum -from typing import List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -42,6 +42,7 @@ ResolverRulePolicy = str ResourceId = str Rfc3339TimeString = str +RniEnhancedMetricsEnabled = bool ServerNameIndication = str ServicePrinciple = str SortByKey = str @@ -50,6 +51,7 @@ SubnetId = str TagKey = str TagValue = str +TargetNameServerMetricsEnabled = bool Unsigned = int @@ -84,6 +86,7 @@ class ConfidenceThreshold(StrEnum): class DnsThreatProtection(StrEnum): DGA = "DGA" DNS_TUNNELING = "DNS_TUNNELING" + DICTIONARY_DGA = "DICTIONARY_DGA" class FirewallDomainImportOperation(StrEnum): @@ -292,7 +295,7 @@ class InvalidParameterException(ServiceException): code: str = "InvalidParameterException" sender_fault: bool = False status_code: int = 400 - FieldName: Optional[String] + FieldName: String | None class InvalidPolicyDocument(ServiceException): @@ -317,35 +320,35 @@ class LimitExceededException(ServiceException): code: str = "LimitExceededException" sender_fault: bool = False status_code: int = 400 - ResourceType: Optional[String] + ResourceType: String | None class ResourceExistsException(ServiceException): code: str = "ResourceExistsException" sender_fault: bool = False status_code: int = 400 - ResourceType: Optional[String] + ResourceType: String | None class ResourceInUseException(ServiceException): code: str = "ResourceInUseException" sender_fault: bool = False status_code: int = 400 - ResourceType: Optional[String] + ResourceType: String | None class ResourceNotFoundException(ServiceException): code: str = "ResourceNotFoundException" sender_fault: bool = False status_code: int = 400 - ResourceType: Optional[String] + ResourceType: String | None class ResourceUnavailableException(ServiceException): code: str = "ResourceUnavailableException" sender_fault: bool = False status_code: int = 400 - ResourceType: Optional[String] + ResourceType: String | None class ServiceQuotaExceededException(ServiceException): @@ -377,7 +380,7 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] class AssociateFirewallRuleGroupRequest(ServiceRequest): @@ -386,35 +389,35 @@ class AssociateFirewallRuleGroupRequest(ServiceRequest): VpcId: ResourceId Priority: Priority Name: Name - MutationProtection: Optional[MutationProtectionStatus] - Tags: Optional[TagList] + MutationProtection: MutationProtectionStatus | None + Tags: TagList | None class FirewallRuleGroupAssociation(TypedDict, total=False): - Id: Optional[ResourceId] - Arn: Optional[Arn] - FirewallRuleGroupId: Optional[ResourceId] - VpcId: Optional[ResourceId] - Name: Optional[Name] - Priority: Optional[Priority] - MutationProtection: Optional[MutationProtectionStatus] - ManagedOwnerName: Optional[ServicePrinciple] - Status: Optional[FirewallRuleGroupAssociationStatus] - StatusMessage: Optional[StatusMessage] - CreatorRequestId: Optional[CreatorRequestId] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] + Id: ResourceId | None + Arn: Arn | None + FirewallRuleGroupId: ResourceId | None + VpcId: ResourceId | None + Name: Name | None + Priority: Priority | None + MutationProtection: MutationProtectionStatus | None + ManagedOwnerName: ServicePrinciple | None + Status: FirewallRuleGroupAssociationStatus | None + StatusMessage: StatusMessage | None + CreatorRequestId: CreatorRequestId | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None class AssociateFirewallRuleGroupResponse(TypedDict, total=False): - FirewallRuleGroupAssociation: Optional[FirewallRuleGroupAssociation] + FirewallRuleGroupAssociation: FirewallRuleGroupAssociation | None class IpAddressUpdate(TypedDict, total=False): - IpId: Optional[ResourceId] - SubnetId: Optional[SubnetId] - Ip: Optional[Ip] - Ipv6: Optional[Ipv6] + IpId: ResourceId | None + SubnetId: SubnetId | None + Ip: Ip | None + Ipv6: Ipv6 | None class AssociateResolverEndpointIpAddressRequest(ServiceRequest): @@ -422,31 +425,33 @@ class AssociateResolverEndpointIpAddressRequest(ServiceRequest): IpAddress: IpAddressUpdate -ProtocolList = List[Protocol] -SecurityGroupIds = List[ResourceId] +ProtocolList = list[Protocol] +SecurityGroupIds = list[ResourceId] class ResolverEndpoint(TypedDict, total=False): - Id: Optional[ResourceId] - CreatorRequestId: Optional[CreatorRequestId] - Arn: Optional[Arn] - Name: Optional[Name] - SecurityGroupIds: Optional[SecurityGroupIds] - Direction: Optional[ResolverEndpointDirection] - IpAddressCount: Optional[IpAddressCount] - HostVPCId: Optional[ResourceId] - Status: Optional[ResolverEndpointStatus] - StatusMessage: Optional[StatusMessage] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] - OutpostArn: Optional[OutpostArn] - PreferredInstanceType: Optional[OutpostInstanceType] - ResolverEndpointType: Optional[ResolverEndpointType] - Protocols: Optional[ProtocolList] + Id: ResourceId | None + CreatorRequestId: CreatorRequestId | None + Arn: Arn | None + Name: Name | None + SecurityGroupIds: SecurityGroupIds | None + Direction: ResolverEndpointDirection | None + IpAddressCount: IpAddressCount | None + HostVPCId: ResourceId | None + Status: ResolverEndpointStatus | None + StatusMessage: StatusMessage | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None + OutpostArn: OutpostArn | None + PreferredInstanceType: OutpostInstanceType | None + ResolverEndpointType: ResolverEndpointType | None + Protocols: ProtocolList | None + RniEnhancedMetricsEnabled: RniEnhancedMetricsEnabled | None + TargetNameServerMetricsEnabled: TargetNameServerMetricsEnabled | None class AssociateResolverEndpointIpAddressResponse(TypedDict, total=False): - ResolverEndpoint: Optional[ResolverEndpoint] + ResolverEndpoint: ResolverEndpoint | None class AssociateResolverQueryLogConfigRequest(ServiceRequest): @@ -455,245 +460,247 @@ class AssociateResolverQueryLogConfigRequest(ServiceRequest): class ResolverQueryLogConfigAssociation(TypedDict, total=False): - Id: Optional[ResourceId] - ResolverQueryLogConfigId: Optional[ResourceId] - ResourceId: Optional[ResourceId] - Status: Optional[ResolverQueryLogConfigAssociationStatus] - Error: Optional[ResolverQueryLogConfigAssociationError] - ErrorMessage: Optional[ResolverQueryLogConfigAssociationErrorMessage] - CreationTime: Optional[Rfc3339TimeString] + Id: ResourceId | None + ResolverQueryLogConfigId: ResourceId | None + ResourceId: ResourceId | None + Status: ResolverQueryLogConfigAssociationStatus | None + Error: ResolverQueryLogConfigAssociationError | None + ErrorMessage: ResolverQueryLogConfigAssociationErrorMessage | None + CreationTime: Rfc3339TimeString | None class AssociateResolverQueryLogConfigResponse(TypedDict, total=False): - ResolverQueryLogConfigAssociation: Optional[ResolverQueryLogConfigAssociation] + ResolverQueryLogConfigAssociation: ResolverQueryLogConfigAssociation | None class AssociateResolverRuleRequest(ServiceRequest): ResolverRuleId: ResourceId - Name: Optional[Name] + Name: Name | None VPCId: ResourceId class ResolverRuleAssociation(TypedDict, total=False): - Id: Optional[ResourceId] - ResolverRuleId: Optional[ResourceId] - Name: Optional[Name] - VPCId: Optional[ResourceId] - Status: Optional[ResolverRuleAssociationStatus] - StatusMessage: Optional[StatusMessage] + Id: ResourceId | None + ResolverRuleId: ResourceId | None + Name: Name | None + VPCId: ResourceId | None + Status: ResolverRuleAssociationStatus | None + StatusMessage: StatusMessage | None class AssociateResolverRuleResponse(TypedDict, total=False): - ResolverRuleAssociation: Optional[ResolverRuleAssociation] + ResolverRuleAssociation: ResolverRuleAssociation | None class CreateFirewallDomainListRequest(ServiceRequest): CreatorRequestId: CreatorRequestId Name: Name - Tags: Optional[TagList] + Tags: TagList | None class FirewallDomainList(TypedDict, total=False): - Id: Optional[ResourceId] - Arn: Optional[Arn] - Name: Optional[Name] - DomainCount: Optional[Unsigned] - Status: Optional[FirewallDomainListStatus] - StatusMessage: Optional[StatusMessage] - ManagedOwnerName: Optional[ServicePrinciple] - CreatorRequestId: Optional[CreatorRequestId] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] + Id: ResourceId | None + Arn: Arn | None + Name: Name | None + DomainCount: Unsigned | None + Status: FirewallDomainListStatus | None + StatusMessage: StatusMessage | None + ManagedOwnerName: ServicePrinciple | None + CreatorRequestId: CreatorRequestId | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None class CreateFirewallDomainListResponse(TypedDict, total=False): - FirewallDomainList: Optional[FirewallDomainList] + FirewallDomainList: FirewallDomainList | None class CreateFirewallRuleGroupRequest(ServiceRequest): CreatorRequestId: CreatorRequestId Name: Name - Tags: Optional[TagList] + Tags: TagList | None class FirewallRuleGroup(TypedDict, total=False): - Id: Optional[ResourceId] - Arn: Optional[Arn] - Name: Optional[Name] - RuleCount: Optional[Unsigned] - Status: Optional[FirewallRuleGroupStatus] - StatusMessage: Optional[StatusMessage] - OwnerId: Optional[AccountId] - CreatorRequestId: Optional[CreatorRequestId] - ShareStatus: Optional[ShareStatus] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] + Id: ResourceId | None + Arn: Arn | None + Name: Name | None + RuleCount: Unsigned | None + Status: FirewallRuleGroupStatus | None + StatusMessage: StatusMessage | None + OwnerId: AccountId | None + CreatorRequestId: CreatorRequestId | None + ShareStatus: ShareStatus | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None class CreateFirewallRuleGroupResponse(TypedDict, total=False): - FirewallRuleGroup: Optional[FirewallRuleGroup] + FirewallRuleGroup: FirewallRuleGroup | None class CreateFirewallRuleRequest(ServiceRequest): CreatorRequestId: CreatorRequestId FirewallRuleGroupId: ResourceId - FirewallDomainListId: Optional[ResourceId] + FirewallDomainListId: ResourceId | None Priority: Priority Action: Action - BlockResponse: Optional[BlockResponse] - BlockOverrideDomain: Optional[BlockOverrideDomain] - BlockOverrideDnsType: Optional[BlockOverrideDnsType] - BlockOverrideTtl: Optional[BlockOverrideTtl] + BlockResponse: BlockResponse | None + BlockOverrideDomain: BlockOverrideDomain | None + BlockOverrideDnsType: BlockOverrideDnsType | None + BlockOverrideTtl: BlockOverrideTtl | None Name: Name - FirewallDomainRedirectionAction: Optional[FirewallDomainRedirectionAction] - Qtype: Optional[Qtype] - DnsThreatProtection: Optional[DnsThreatProtection] - ConfidenceThreshold: Optional[ConfidenceThreshold] + FirewallDomainRedirectionAction: FirewallDomainRedirectionAction | None + Qtype: Qtype | None + DnsThreatProtection: DnsThreatProtection | None + ConfidenceThreshold: ConfidenceThreshold | None class FirewallRule(TypedDict, total=False): - FirewallRuleGroupId: Optional[ResourceId] - FirewallDomainListId: Optional[ResourceId] - FirewallThreatProtectionId: Optional[ResourceId] - Name: Optional[Name] - Priority: Optional[Priority] - Action: Optional[Action] - BlockResponse: Optional[BlockResponse] - BlockOverrideDomain: Optional[BlockOverrideDomain] - BlockOverrideDnsType: Optional[BlockOverrideDnsType] - BlockOverrideTtl: Optional[Unsigned] - CreatorRequestId: Optional[CreatorRequestId] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] - FirewallDomainRedirectionAction: Optional[FirewallDomainRedirectionAction] - Qtype: Optional[Qtype] - DnsThreatProtection: Optional[DnsThreatProtection] - ConfidenceThreshold: Optional[ConfidenceThreshold] + FirewallRuleGroupId: ResourceId | None + FirewallDomainListId: ResourceId | None + FirewallThreatProtectionId: ResourceId | None + Name: Name | None + Priority: Priority | None + Action: Action | None + BlockResponse: BlockResponse | None + BlockOverrideDomain: BlockOverrideDomain | None + BlockOverrideDnsType: BlockOverrideDnsType | None + BlockOverrideTtl: Unsigned | None + CreatorRequestId: CreatorRequestId | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None + FirewallDomainRedirectionAction: FirewallDomainRedirectionAction | None + Qtype: Qtype | None + DnsThreatProtection: DnsThreatProtection | None + ConfidenceThreshold: ConfidenceThreshold | None class CreateFirewallRuleResponse(TypedDict, total=False): - FirewallRule: Optional[FirewallRule] + FirewallRule: FirewallRule | None class CreateOutpostResolverRequest(ServiceRequest): CreatorRequestId: CreatorRequestId Name: OutpostResolverName - InstanceCount: Optional[InstanceCount] + InstanceCount: InstanceCount | None PreferredInstanceType: OutpostInstanceType OutpostArn: OutpostArn - Tags: Optional[TagList] + Tags: TagList | None class OutpostResolver(TypedDict, total=False): - Arn: Optional[Arn] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] - CreatorRequestId: Optional[CreatorRequestId] - Id: Optional[ResourceId] - InstanceCount: Optional[InstanceCount] - PreferredInstanceType: Optional[OutpostInstanceType] - Name: Optional[OutpostResolverName] - Status: Optional[OutpostResolverStatus] - StatusMessage: Optional[OutpostResolverStatusMessage] - OutpostArn: Optional[OutpostArn] + Arn: Arn | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None + CreatorRequestId: CreatorRequestId | None + Id: ResourceId | None + InstanceCount: InstanceCount | None + PreferredInstanceType: OutpostInstanceType | None + Name: OutpostResolverName | None + Status: OutpostResolverStatus | None + StatusMessage: OutpostResolverStatusMessage | None + OutpostArn: OutpostArn | None class CreateOutpostResolverResponse(TypedDict, total=False): - OutpostResolver: Optional[OutpostResolver] + OutpostResolver: OutpostResolver | None class IpAddressRequest(TypedDict, total=False): SubnetId: SubnetId - Ip: Optional[Ip] - Ipv6: Optional[Ipv6] + Ip: Ip | None + Ipv6: Ipv6 | None -IpAddressesRequest = List[IpAddressRequest] +IpAddressesRequest = list[IpAddressRequest] class CreateResolverEndpointRequest(ServiceRequest): CreatorRequestId: CreatorRequestId - Name: Optional[Name] + Name: Name | None SecurityGroupIds: SecurityGroupIds Direction: ResolverEndpointDirection IpAddresses: IpAddressesRequest - OutpostArn: Optional[OutpostArn] - PreferredInstanceType: Optional[OutpostInstanceType] - Tags: Optional[TagList] - ResolverEndpointType: Optional[ResolverEndpointType] - Protocols: Optional[ProtocolList] + OutpostArn: OutpostArn | None + PreferredInstanceType: OutpostInstanceType | None + Tags: TagList | None + ResolverEndpointType: ResolverEndpointType | None + Protocols: ProtocolList | None + RniEnhancedMetricsEnabled: RniEnhancedMetricsEnabled | None + TargetNameServerMetricsEnabled: TargetNameServerMetricsEnabled | None class CreateResolverEndpointResponse(TypedDict, total=False): - ResolverEndpoint: Optional[ResolverEndpoint] + ResolverEndpoint: ResolverEndpoint | None class CreateResolverQueryLogConfigRequest(ServiceRequest): Name: ResolverQueryLogConfigName DestinationArn: DestinationArn CreatorRequestId: CreatorRequestId - Tags: Optional[TagList] + Tags: TagList | None class ResolverQueryLogConfig(TypedDict, total=False): - Id: Optional[ResourceId] - OwnerId: Optional[AccountId] - Status: Optional[ResolverQueryLogConfigStatus] - ShareStatus: Optional[ShareStatus] - AssociationCount: Optional[Count] - Arn: Optional[Arn] - Name: Optional[ResolverQueryLogConfigName] - DestinationArn: Optional[DestinationArn] - CreatorRequestId: Optional[CreatorRequestId] - CreationTime: Optional[Rfc3339TimeString] + Id: ResourceId | None + OwnerId: AccountId | None + Status: ResolverQueryLogConfigStatus | None + ShareStatus: ShareStatus | None + AssociationCount: Count | None + Arn: Arn | None + Name: ResolverQueryLogConfigName | None + DestinationArn: DestinationArn | None + CreatorRequestId: CreatorRequestId | None + CreationTime: Rfc3339TimeString | None class CreateResolverQueryLogConfigResponse(TypedDict, total=False): - ResolverQueryLogConfig: Optional[ResolverQueryLogConfig] + ResolverQueryLogConfig: ResolverQueryLogConfig | None class TargetAddress(TypedDict, total=False): - Ip: Optional[Ip] - Port: Optional[Port] - Ipv6: Optional[Ipv6] - Protocol: Optional[Protocol] - ServerNameIndication: Optional[ServerNameIndication] + Ip: Ip | None + Port: Port | None + Ipv6: Ipv6 | None + Protocol: Protocol | None + ServerNameIndication: ServerNameIndication | None -TargetList = List[TargetAddress] +TargetList = list[TargetAddress] class CreateResolverRuleRequest(ServiceRequest): CreatorRequestId: CreatorRequestId - Name: Optional[Name] + Name: Name | None RuleType: RuleTypeOption - DomainName: Optional[DomainName] - TargetIps: Optional[TargetList] - ResolverEndpointId: Optional[ResourceId] - Tags: Optional[TagList] - DelegationRecord: Optional[DelegationRecord] + DomainName: DomainName | None + TargetIps: TargetList | None + ResolverEndpointId: ResourceId | None + Tags: TagList | None + DelegationRecord: DelegationRecord | None class ResolverRule(TypedDict, total=False): - Id: Optional[ResourceId] - CreatorRequestId: Optional[CreatorRequestId] - Arn: Optional[Arn] - DomainName: Optional[DomainName] - Status: Optional[ResolverRuleStatus] - StatusMessage: Optional[StatusMessage] - RuleType: Optional[RuleTypeOption] - Name: Optional[Name] - TargetIps: Optional[TargetList] - ResolverEndpointId: Optional[ResourceId] - OwnerId: Optional[AccountId] - ShareStatus: Optional[ShareStatus] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] - DelegationRecord: Optional[DelegationRecord] + Id: ResourceId | None + CreatorRequestId: CreatorRequestId | None + Arn: Arn | None + DomainName: DomainName | None + Status: ResolverRuleStatus | None + StatusMessage: StatusMessage | None + RuleType: RuleTypeOption | None + Name: Name | None + TargetIps: TargetList | None + ResolverEndpointId: ResourceId | None + OwnerId: AccountId | None + ShareStatus: ShareStatus | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None + DelegationRecord: DelegationRecord | None class CreateResolverRuleResponse(TypedDict, total=False): - ResolverRule: Optional[ResolverRule] + ResolverRule: ResolverRule | None class DeleteFirewallDomainListRequest(ServiceRequest): @@ -701,7 +708,7 @@ class DeleteFirewallDomainListRequest(ServiceRequest): class DeleteFirewallDomainListResponse(TypedDict, total=False): - FirewallDomainList: Optional[FirewallDomainList] + FirewallDomainList: FirewallDomainList | None class DeleteFirewallRuleGroupRequest(ServiceRequest): @@ -709,18 +716,18 @@ class DeleteFirewallRuleGroupRequest(ServiceRequest): class DeleteFirewallRuleGroupResponse(TypedDict, total=False): - FirewallRuleGroup: Optional[FirewallRuleGroup] + FirewallRuleGroup: FirewallRuleGroup | None class DeleteFirewallRuleRequest(ServiceRequest): FirewallRuleGroupId: ResourceId - FirewallDomainListId: Optional[ResourceId] - FirewallThreatProtectionId: Optional[ResourceId] - Qtype: Optional[Qtype] + FirewallDomainListId: ResourceId | None + FirewallThreatProtectionId: ResourceId | None + Qtype: Qtype | None class DeleteFirewallRuleResponse(TypedDict, total=False): - FirewallRule: Optional[FirewallRule] + FirewallRule: FirewallRule | None class DeleteOutpostResolverRequest(ServiceRequest): @@ -728,7 +735,7 @@ class DeleteOutpostResolverRequest(ServiceRequest): class DeleteOutpostResolverResponse(TypedDict, total=False): - OutpostResolver: Optional[OutpostResolver] + OutpostResolver: OutpostResolver | None class DeleteResolverEndpointRequest(ServiceRequest): @@ -736,7 +743,7 @@ class DeleteResolverEndpointRequest(ServiceRequest): class DeleteResolverEndpointResponse(TypedDict, total=False): - ResolverEndpoint: Optional[ResolverEndpoint] + ResolverEndpoint: ResolverEndpoint | None class DeleteResolverQueryLogConfigRequest(ServiceRequest): @@ -744,7 +751,7 @@ class DeleteResolverQueryLogConfigRequest(ServiceRequest): class DeleteResolverQueryLogConfigResponse(TypedDict, total=False): - ResolverQueryLogConfig: Optional[ResolverQueryLogConfig] + ResolverQueryLogConfig: ResolverQueryLogConfig | None class DeleteResolverRuleRequest(ServiceRequest): @@ -752,7 +759,7 @@ class DeleteResolverRuleRequest(ServiceRequest): class DeleteResolverRuleResponse(TypedDict, total=False): - ResolverRule: Optional[ResolverRule] + ResolverRule: ResolverRule | None class DisassociateFirewallRuleGroupRequest(ServiceRequest): @@ -760,7 +767,7 @@ class DisassociateFirewallRuleGroupRequest(ServiceRequest): class DisassociateFirewallRuleGroupResponse(TypedDict, total=False): - FirewallRuleGroupAssociation: Optional[FirewallRuleGroupAssociation] + FirewallRuleGroupAssociation: FirewallRuleGroupAssociation | None class DisassociateResolverEndpointIpAddressRequest(ServiceRequest): @@ -769,7 +776,7 @@ class DisassociateResolverEndpointIpAddressRequest(ServiceRequest): class DisassociateResolverEndpointIpAddressResponse(TypedDict, total=False): - ResolverEndpoint: Optional[ResolverEndpoint] + ResolverEndpoint: ResolverEndpoint | None class DisassociateResolverQueryLogConfigRequest(ServiceRequest): @@ -778,7 +785,7 @@ class DisassociateResolverQueryLogConfigRequest(ServiceRequest): class DisassociateResolverQueryLogConfigResponse(TypedDict, total=False): - ResolverQueryLogConfigAssociation: Optional[ResolverQueryLogConfigAssociation] + ResolverQueryLogConfigAssociation: ResolverQueryLogConfigAssociation | None class DisassociateResolverRuleRequest(ServiceRequest): @@ -787,54 +794,54 @@ class DisassociateResolverRuleRequest(ServiceRequest): class DisassociateResolverRuleResponse(TypedDict, total=False): - ResolverRuleAssociation: Optional[ResolverRuleAssociation] + ResolverRuleAssociation: ResolverRuleAssociation | None -FilterValues = List[FilterValue] +FilterValues = list[FilterValue] class Filter(TypedDict, total=False): - Name: Optional[FilterName] - Values: Optional[FilterValues] + Name: FilterName | None + Values: FilterValues | None -Filters = List[Filter] +Filters = list[Filter] class FirewallConfig(TypedDict, total=False): - Id: Optional[ResourceId] - ResourceId: Optional[ResourceId] - OwnerId: Optional[AccountId] - FirewallFailOpen: Optional[FirewallFailOpenStatus] + Id: ResourceId | None + ResourceId: ResourceId | None + OwnerId: AccountId | None + FirewallFailOpen: FirewallFailOpenStatus | None -FirewallConfigList = List[FirewallConfig] +FirewallConfigList = list[FirewallConfig] class FirewallDomainListMetadata(TypedDict, total=False): - Id: Optional[ResourceId] - Arn: Optional[Arn] - Name: Optional[Name] - CreatorRequestId: Optional[CreatorRequestId] - ManagedOwnerName: Optional[ServicePrinciple] + Id: ResourceId | None + Arn: Arn | None + Name: Name | None + CreatorRequestId: CreatorRequestId | None + ManagedOwnerName: ServicePrinciple | None -FirewallDomainListMetadataList = List[FirewallDomainListMetadata] -FirewallDomains = List[FirewallDomainName] -FirewallRuleGroupAssociations = List[FirewallRuleGroupAssociation] +FirewallDomainListMetadataList = list[FirewallDomainListMetadata] +FirewallDomains = list[FirewallDomainName] +FirewallRuleGroupAssociations = list[FirewallRuleGroupAssociation] class FirewallRuleGroupMetadata(TypedDict, total=False): - Id: Optional[ResourceId] - Arn: Optional[Arn] - Name: Optional[Name] - OwnerId: Optional[AccountId] - CreatorRequestId: Optional[CreatorRequestId] - ShareStatus: Optional[ShareStatus] + Id: ResourceId | None + Arn: Arn | None + Name: Name | None + OwnerId: AccountId | None + CreatorRequestId: CreatorRequestId | None + ShareStatus: ShareStatus | None -FirewallRuleGroupMetadataList = List[FirewallRuleGroupMetadata] -FirewallRules = List[FirewallRule] +FirewallRuleGroupMetadataList = list[FirewallRuleGroupMetadata] +FirewallRules = list[FirewallRule] class GetFirewallConfigRequest(ServiceRequest): @@ -842,7 +849,7 @@ class GetFirewallConfigRequest(ServiceRequest): class GetFirewallConfigResponse(TypedDict, total=False): - FirewallConfig: Optional[FirewallConfig] + FirewallConfig: FirewallConfig | None class GetFirewallDomainListRequest(ServiceRequest): @@ -850,7 +857,7 @@ class GetFirewallDomainListRequest(ServiceRequest): class GetFirewallDomainListResponse(TypedDict, total=False): - FirewallDomainList: Optional[FirewallDomainList] + FirewallDomainList: FirewallDomainList | None class GetFirewallRuleGroupAssociationRequest(ServiceRequest): @@ -858,7 +865,7 @@ class GetFirewallRuleGroupAssociationRequest(ServiceRequest): class GetFirewallRuleGroupAssociationResponse(TypedDict, total=False): - FirewallRuleGroupAssociation: Optional[FirewallRuleGroupAssociation] + FirewallRuleGroupAssociation: FirewallRuleGroupAssociation | None class GetFirewallRuleGroupPolicyRequest(ServiceRequest): @@ -866,7 +873,7 @@ class GetFirewallRuleGroupPolicyRequest(ServiceRequest): class GetFirewallRuleGroupPolicyResponse(TypedDict, total=False): - FirewallRuleGroupPolicy: Optional[FirewallRuleGroupPolicy] + FirewallRuleGroupPolicy: FirewallRuleGroupPolicy | None class GetFirewallRuleGroupRequest(ServiceRequest): @@ -874,7 +881,7 @@ class GetFirewallRuleGroupRequest(ServiceRequest): class GetFirewallRuleGroupResponse(TypedDict, total=False): - FirewallRuleGroup: Optional[FirewallRuleGroup] + FirewallRuleGroup: FirewallRuleGroup | None class GetOutpostResolverRequest(ServiceRequest): @@ -882,7 +889,7 @@ class GetOutpostResolverRequest(ServiceRequest): class GetOutpostResolverResponse(TypedDict, total=False): - OutpostResolver: Optional[OutpostResolver] + OutpostResolver: OutpostResolver | None class GetResolverConfigRequest(ServiceRequest): @@ -890,14 +897,14 @@ class GetResolverConfigRequest(ServiceRequest): class ResolverConfig(TypedDict, total=False): - Id: Optional[ResourceId] - ResourceId: Optional[ResourceId] - OwnerId: Optional[AccountId] - AutodefinedReverse: Optional[ResolverAutodefinedReverseStatus] + Id: ResourceId | None + ResourceId: ResourceId | None + OwnerId: AccountId | None + AutodefinedReverse: ResolverAutodefinedReverseStatus | None class GetResolverConfigResponse(TypedDict, total=False): - ResolverConfig: Optional[ResolverConfig] + ResolverConfig: ResolverConfig | None class GetResolverDnssecConfigRequest(ServiceRequest): @@ -905,14 +912,14 @@ class GetResolverDnssecConfigRequest(ServiceRequest): class ResolverDnssecConfig(TypedDict, total=False): - Id: Optional[ResourceId] - OwnerId: Optional[AccountId] - ResourceId: Optional[ResourceId] - ValidationStatus: Optional[ResolverDNSSECValidationStatus] + Id: ResourceId | None + OwnerId: AccountId | None + ResourceId: ResourceId | None + ValidationStatus: ResolverDNSSECValidationStatus | None class GetResolverDnssecConfigResponse(TypedDict, total=False): - ResolverDNSSECConfig: Optional[ResolverDnssecConfig] + ResolverDNSSECConfig: ResolverDnssecConfig | None class GetResolverEndpointRequest(ServiceRequest): @@ -920,7 +927,7 @@ class GetResolverEndpointRequest(ServiceRequest): class GetResolverEndpointResponse(TypedDict, total=False): - ResolverEndpoint: Optional[ResolverEndpoint] + ResolverEndpoint: ResolverEndpoint | None class GetResolverQueryLogConfigAssociationRequest(ServiceRequest): @@ -928,7 +935,7 @@ class GetResolverQueryLogConfigAssociationRequest(ServiceRequest): class GetResolverQueryLogConfigAssociationResponse(TypedDict, total=False): - ResolverQueryLogConfigAssociation: Optional[ResolverQueryLogConfigAssociation] + ResolverQueryLogConfigAssociation: ResolverQueryLogConfigAssociation | None class GetResolverQueryLogConfigPolicyRequest(ServiceRequest): @@ -936,7 +943,7 @@ class GetResolverQueryLogConfigPolicyRequest(ServiceRequest): class GetResolverQueryLogConfigPolicyResponse(TypedDict, total=False): - ResolverQueryLogConfigPolicy: Optional[ResolverQueryLogConfigPolicy] + ResolverQueryLogConfigPolicy: ResolverQueryLogConfigPolicy | None class GetResolverQueryLogConfigRequest(ServiceRequest): @@ -944,7 +951,7 @@ class GetResolverQueryLogConfigRequest(ServiceRequest): class GetResolverQueryLogConfigResponse(TypedDict, total=False): - ResolverQueryLogConfig: Optional[ResolverQueryLogConfig] + ResolverQueryLogConfig: ResolverQueryLogConfig | None class GetResolverRuleAssociationRequest(ServiceRequest): @@ -952,7 +959,7 @@ class GetResolverRuleAssociationRequest(ServiceRequest): class GetResolverRuleAssociationResponse(TypedDict, total=False): - ResolverRuleAssociation: Optional[ResolverRuleAssociation] + ResolverRuleAssociation: ResolverRuleAssociation | None class GetResolverRulePolicyRequest(ServiceRequest): @@ -960,7 +967,7 @@ class GetResolverRulePolicyRequest(ServiceRequest): class GetResolverRulePolicyResponse(TypedDict, total=False): - ResolverRulePolicy: Optional[ResolverRulePolicy] + ResolverRulePolicy: ResolverRulePolicy | None class GetResolverRuleRequest(ServiceRequest): @@ -968,7 +975,7 @@ class GetResolverRuleRequest(ServiceRequest): class GetResolverRuleResponse(TypedDict, total=False): - ResolverRule: Optional[ResolverRule] + ResolverRule: ResolverRule | None class ImportFirewallDomainsRequest(ServiceRequest): @@ -978,237 +985,237 @@ class ImportFirewallDomainsRequest(ServiceRequest): class ImportFirewallDomainsResponse(TypedDict, total=False): - Id: Optional[ResourceId] - Name: Optional[Name] - Status: Optional[FirewallDomainListStatus] - StatusMessage: Optional[StatusMessage] + Id: ResourceId | None + Name: Name | None + Status: FirewallDomainListStatus | None + StatusMessage: StatusMessage | None class IpAddressResponse(TypedDict, total=False): - IpId: Optional[ResourceId] - SubnetId: Optional[SubnetId] - Ip: Optional[Ip] - Ipv6: Optional[Ipv6] - Status: Optional[IpAddressStatus] - StatusMessage: Optional[StatusMessage] - CreationTime: Optional[Rfc3339TimeString] - ModificationTime: Optional[Rfc3339TimeString] + IpId: ResourceId | None + SubnetId: SubnetId | None + Ip: Ip | None + Ipv6: Ipv6 | None + Status: IpAddressStatus | None + StatusMessage: StatusMessage | None + CreationTime: Rfc3339TimeString | None + ModificationTime: Rfc3339TimeString | None -IpAddressesResponse = List[IpAddressResponse] +IpAddressesResponse = list[IpAddressResponse] class ListFirewallConfigsRequest(ServiceRequest): - MaxResults: Optional[ListFirewallConfigsMaxResult] - NextToken: Optional[NextToken] + MaxResults: ListFirewallConfigsMaxResult | None + NextToken: NextToken | None class ListFirewallConfigsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - FirewallConfigs: Optional[FirewallConfigList] + NextToken: NextToken | None + FirewallConfigs: FirewallConfigList | None class ListFirewallDomainListsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListFirewallDomainListsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - FirewallDomainLists: Optional[FirewallDomainListMetadataList] + NextToken: NextToken | None + FirewallDomainLists: FirewallDomainListMetadataList | None class ListFirewallDomainsRequest(ServiceRequest): FirewallDomainListId: ResourceId - MaxResults: Optional[ListDomainMaxResults] - NextToken: Optional[NextToken] + MaxResults: ListDomainMaxResults | None + NextToken: NextToken | None class ListFirewallDomainsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - Domains: Optional[FirewallDomains] + NextToken: NextToken | None + Domains: FirewallDomains | None class ListFirewallRuleGroupAssociationsRequest(ServiceRequest): - FirewallRuleGroupId: Optional[ResourceId] - VpcId: Optional[ResourceId] - Priority: Optional[Priority] - Status: Optional[FirewallRuleGroupAssociationStatus] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + FirewallRuleGroupId: ResourceId | None + VpcId: ResourceId | None + Priority: Priority | None + Status: FirewallRuleGroupAssociationStatus | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListFirewallRuleGroupAssociationsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - FirewallRuleGroupAssociations: Optional[FirewallRuleGroupAssociations] + NextToken: NextToken | None + FirewallRuleGroupAssociations: FirewallRuleGroupAssociations | None class ListFirewallRuleGroupsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListFirewallRuleGroupsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - FirewallRuleGroups: Optional[FirewallRuleGroupMetadataList] + NextToken: NextToken | None + FirewallRuleGroups: FirewallRuleGroupMetadataList | None class ListFirewallRulesRequest(ServiceRequest): FirewallRuleGroupId: ResourceId - Priority: Optional[Priority] - Action: Optional[Action] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Priority: Priority | None + Action: Action | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListFirewallRulesResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - FirewallRules: Optional[FirewallRules] + NextToken: NextToken | None + FirewallRules: FirewallRules | None class ListOutpostResolversRequest(ServiceRequest): - OutpostArn: Optional[OutpostArn] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + OutpostArn: OutpostArn | None + MaxResults: MaxResults | None + NextToken: NextToken | None -OutpostResolverList = List[OutpostResolver] +OutpostResolverList = list[OutpostResolver] class ListOutpostResolversResponse(TypedDict, total=False): - OutpostResolvers: Optional[OutpostResolverList] - NextToken: Optional[NextToken] + OutpostResolvers: OutpostResolverList | None + NextToken: NextToken | None class ListResolverConfigsRequest(ServiceRequest): - MaxResults: Optional[ListResolverConfigsMaxResult] - NextToken: Optional[NextToken] + MaxResults: ListResolverConfigsMaxResult | None + NextToken: NextToken | None -ResolverConfigList = List[ResolverConfig] +ResolverConfigList = list[ResolverConfig] class ListResolverConfigsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - ResolverConfigs: Optional[ResolverConfigList] + NextToken: NextToken | None + ResolverConfigs: ResolverConfigList | None class ListResolverDnssecConfigsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - Filters: Optional[Filters] + MaxResults: MaxResults | None + NextToken: NextToken | None + Filters: Filters | None -ResolverDnssecConfigList = List[ResolverDnssecConfig] +ResolverDnssecConfigList = list[ResolverDnssecConfig] class ListResolverDnssecConfigsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - ResolverDnssecConfigs: Optional[ResolverDnssecConfigList] + NextToken: NextToken | None + ResolverDnssecConfigs: ResolverDnssecConfigList | None class ListResolverEndpointIpAddressesRequest(ServiceRequest): ResolverEndpointId: ResourceId - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListResolverEndpointIpAddressesResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - IpAddresses: Optional[IpAddressesResponse] + NextToken: NextToken | None + MaxResults: MaxResults | None + IpAddresses: IpAddressesResponse | None class ListResolverEndpointsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - Filters: Optional[Filters] + MaxResults: MaxResults | None + NextToken: NextToken | None + Filters: Filters | None -ResolverEndpoints = List[ResolverEndpoint] +ResolverEndpoints = list[ResolverEndpoint] class ListResolverEndpointsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - ResolverEndpoints: Optional[ResolverEndpoints] + NextToken: NextToken | None + MaxResults: MaxResults | None + ResolverEndpoints: ResolverEndpoints | None class ListResolverQueryLogConfigAssociationsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - Filters: Optional[Filters] - SortBy: Optional[SortByKey] - SortOrder: Optional[SortOrder] + MaxResults: MaxResults | None + NextToken: NextToken | None + Filters: Filters | None + SortBy: SortByKey | None + SortOrder: SortOrder | None -ResolverQueryLogConfigAssociationList = List[ResolverQueryLogConfigAssociation] +ResolverQueryLogConfigAssociationList = list[ResolverQueryLogConfigAssociation] class ListResolverQueryLogConfigAssociationsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - TotalCount: Optional[Count] - TotalFilteredCount: Optional[Count] - ResolverQueryLogConfigAssociations: Optional[ResolverQueryLogConfigAssociationList] + NextToken: NextToken | None + TotalCount: Count | None + TotalFilteredCount: Count | None + ResolverQueryLogConfigAssociations: ResolverQueryLogConfigAssociationList | None class ListResolverQueryLogConfigsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - Filters: Optional[Filters] - SortBy: Optional[SortByKey] - SortOrder: Optional[SortOrder] + MaxResults: MaxResults | None + NextToken: NextToken | None + Filters: Filters | None + SortBy: SortByKey | None + SortOrder: SortOrder | None -ResolverQueryLogConfigList = List[ResolverQueryLogConfig] +ResolverQueryLogConfigList = list[ResolverQueryLogConfig] class ListResolverQueryLogConfigsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - TotalCount: Optional[Count] - TotalFilteredCount: Optional[Count] - ResolverQueryLogConfigs: Optional[ResolverQueryLogConfigList] + NextToken: NextToken | None + TotalCount: Count | None + TotalFilteredCount: Count | None + ResolverQueryLogConfigs: ResolverQueryLogConfigList | None class ListResolverRuleAssociationsRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - Filters: Optional[Filters] + MaxResults: MaxResults | None + NextToken: NextToken | None + Filters: Filters | None -ResolverRuleAssociations = List[ResolverRuleAssociation] +ResolverRuleAssociations = list[ResolverRuleAssociation] class ListResolverRuleAssociationsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - ResolverRuleAssociations: Optional[ResolverRuleAssociations] + NextToken: NextToken | None + MaxResults: MaxResults | None + ResolverRuleAssociations: ResolverRuleAssociations | None class ListResolverRulesRequest(ServiceRequest): - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - Filters: Optional[Filters] + MaxResults: MaxResults | None + NextToken: NextToken | None + Filters: Filters | None -ResolverRules = List[ResolverRule] +ResolverRules = list[ResolverRule] class ListResolverRulesResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - ResolverRules: Optional[ResolverRules] + NextToken: NextToken | None + MaxResults: MaxResults | None + ResolverRules: ResolverRules | None class ListTagsForResourceRequest(ServiceRequest): ResourceArn: Arn - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListTagsForResourceResponse(TypedDict, total=False): - Tags: Optional[TagList] - NextToken: Optional[NextToken] + Tags: TagList | None + NextToken: NextToken | None class PutFirewallRuleGroupPolicyRequest(ServiceRequest): @@ -1217,7 +1224,7 @@ class PutFirewallRuleGroupPolicyRequest(ServiceRequest): class PutFirewallRuleGroupPolicyResponse(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class PutResolverQueryLogConfigPolicyRequest(ServiceRequest): @@ -1226,7 +1233,7 @@ class PutResolverQueryLogConfigPolicyRequest(ServiceRequest): class PutResolverQueryLogConfigPolicyResponse(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class PutResolverRulePolicyRequest(ServiceRequest): @@ -1235,16 +1242,16 @@ class PutResolverRulePolicyRequest(ServiceRequest): class PutResolverRulePolicyResponse(TypedDict, total=False): - ReturnValue: Optional[Boolean] + ReturnValue: Boolean | None class ResolverRuleConfig(TypedDict, total=False): - Name: Optional[Name] - TargetIps: Optional[TargetList] - ResolverEndpointId: Optional[ResourceId] + Name: Name | None + TargetIps: TargetList | None + ResolverEndpointId: ResourceId | None -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceRequest(ServiceRequest): @@ -1271,7 +1278,7 @@ class UpdateFirewallConfigRequest(ServiceRequest): class UpdateFirewallConfigResponse(TypedDict, total=False): - FirewallConfig: Optional[FirewallConfig] + FirewallConfig: FirewallConfig | None class UpdateFirewallDomainsRequest(ServiceRequest): @@ -1281,42 +1288,42 @@ class UpdateFirewallDomainsRequest(ServiceRequest): class UpdateFirewallDomainsResponse(TypedDict, total=False): - Id: Optional[ResourceId] - Name: Optional[Name] - Status: Optional[FirewallDomainListStatus] - StatusMessage: Optional[StatusMessage] + Id: ResourceId | None + Name: Name | None + Status: FirewallDomainListStatus | None + StatusMessage: StatusMessage | None class UpdateFirewallRuleGroupAssociationRequest(ServiceRequest): FirewallRuleGroupAssociationId: ResourceId - Priority: Optional[Priority] - MutationProtection: Optional[MutationProtectionStatus] - Name: Optional[Name] + Priority: Priority | None + MutationProtection: MutationProtectionStatus | None + Name: Name | None class UpdateFirewallRuleGroupAssociationResponse(TypedDict, total=False): - FirewallRuleGroupAssociation: Optional[FirewallRuleGroupAssociation] + FirewallRuleGroupAssociation: FirewallRuleGroupAssociation | None class UpdateFirewallRuleRequest(ServiceRequest): FirewallRuleGroupId: ResourceId - FirewallDomainListId: Optional[ResourceId] - FirewallThreatProtectionId: Optional[ResourceId] - Priority: Optional[Priority] - Action: Optional[Action] - BlockResponse: Optional[BlockResponse] - BlockOverrideDomain: Optional[BlockOverrideDomain] - BlockOverrideDnsType: Optional[BlockOverrideDnsType] - BlockOverrideTtl: Optional[BlockOverrideTtl] - Name: Optional[Name] - FirewallDomainRedirectionAction: Optional[FirewallDomainRedirectionAction] - Qtype: Optional[Qtype] - DnsThreatProtection: Optional[DnsThreatProtection] - ConfidenceThreshold: Optional[ConfidenceThreshold] + FirewallDomainListId: ResourceId | None + FirewallThreatProtectionId: ResourceId | None + Priority: Priority | None + Action: Action | None + BlockResponse: BlockResponse | None + BlockOverrideDomain: BlockOverrideDomain | None + BlockOverrideDnsType: BlockOverrideDnsType | None + BlockOverrideTtl: BlockOverrideTtl | None + Name: Name | None + FirewallDomainRedirectionAction: FirewallDomainRedirectionAction | None + Qtype: Qtype | None + DnsThreatProtection: DnsThreatProtection | None + ConfidenceThreshold: ConfidenceThreshold | None class UpdateFirewallRuleResponse(TypedDict, total=False): - FirewallRule: Optional[FirewallRule] + FirewallRule: FirewallRule | None class UpdateIpAddress(TypedDict, total=False): @@ -1324,18 +1331,18 @@ class UpdateIpAddress(TypedDict, total=False): Ipv6: Ipv6 -UpdateIpAddresses = List[UpdateIpAddress] +UpdateIpAddresses = list[UpdateIpAddress] class UpdateOutpostResolverRequest(ServiceRequest): Id: ResourceId - Name: Optional[OutpostResolverName] - InstanceCount: Optional[InstanceCount] - PreferredInstanceType: Optional[OutpostInstanceType] + Name: OutpostResolverName | None + InstanceCount: InstanceCount | None + PreferredInstanceType: OutpostInstanceType | None class UpdateOutpostResolverResponse(TypedDict, total=False): - OutpostResolver: Optional[OutpostResolver] + OutpostResolver: OutpostResolver | None class UpdateResolverConfigRequest(ServiceRequest): @@ -1344,7 +1351,7 @@ class UpdateResolverConfigRequest(ServiceRequest): class UpdateResolverConfigResponse(TypedDict, total=False): - ResolverConfig: Optional[ResolverConfig] + ResolverConfig: ResolverConfig | None class UpdateResolverDnssecConfigRequest(ServiceRequest): @@ -1353,19 +1360,21 @@ class UpdateResolverDnssecConfigRequest(ServiceRequest): class UpdateResolverDnssecConfigResponse(TypedDict, total=False): - ResolverDNSSECConfig: Optional[ResolverDnssecConfig] + ResolverDNSSECConfig: ResolverDnssecConfig | None class UpdateResolverEndpointRequest(ServiceRequest): ResolverEndpointId: ResourceId - Name: Optional[Name] - ResolverEndpointType: Optional[ResolverEndpointType] - UpdateIpAddresses: Optional[UpdateIpAddresses] - Protocols: Optional[ProtocolList] + Name: Name | None + ResolverEndpointType: ResolverEndpointType | None + UpdateIpAddresses: UpdateIpAddresses | None + Protocols: ProtocolList | None + RniEnhancedMetricsEnabled: RniEnhancedMetricsEnabled | None + TargetNameServerMetricsEnabled: TargetNameServerMetricsEnabled | None class UpdateResolverEndpointResponse(TypedDict, total=False): - ResolverEndpoint: Optional[ResolverEndpoint] + ResolverEndpoint: ResolverEndpoint | None class UpdateResolverRuleRequest(ServiceRequest): @@ -1374,12 +1383,12 @@ class UpdateResolverRuleRequest(ServiceRequest): class UpdateResolverRuleResponse(TypedDict, total=False): - ResolverRule: Optional[ResolverRule] + ResolverRule: ResolverRule | None class Route53ResolverApi: - service = "route53resolver" - version = "2018-04-01" + service: str = "route53resolver" + version: str = "2018-04-01" @handler("AssociateFirewallRuleGroup") def associate_firewall_rule_group( @@ -1499,6 +1508,8 @@ def create_resolver_endpoint( tags: TagList | None = None, resolver_endpoint_type: ResolverEndpointType | None = None, protocols: ProtocolList | None = None, + rni_enhanced_metrics_enabled: RniEnhancedMetricsEnabled | None = None, + target_name_server_metrics_enabled: TargetNameServerMetricsEnabled | None = None, **kwargs, ) -> CreateResolverEndpointResponse: raise NotImplementedError @@ -2026,6 +2037,8 @@ def update_resolver_endpoint( resolver_endpoint_type: ResolverEndpointType | None = None, update_ip_addresses: UpdateIpAddresses | None = None, protocols: ProtocolList | None = None, + rni_enhanced_metrics_enabled: RniEnhancedMetricsEnabled | None = None, + target_name_server_metrics_enabled: TargetNameServerMetricsEnabled | None = None, **kwargs, ) -> UpdateResolverEndpointResponse: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/s3/__init__.py b/localstack-core/localstack/aws/api/s3/__init__.py index 85a31139d1dcb..36a6133339162 100644 --- a/localstack-core/localstack/aws/api/s3/__init__.py +++ b/localstack-core/localstack/aws/api/s3/__init__.py @@ -1,6 +1,7 @@ +from collections.abc import Iterable, Iterator from datetime import datetime from enum import StrEnum -from typing import IO, Dict, Iterable, Iterator, List, Optional, TypedDict, Union +from typing import IO, TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -90,6 +91,7 @@ KeyCount = int KeyMarker = str KeyPrefixEquals = str +KmsKeyArn = str LambdaFunctionArn = str Location = str LocationNameAsString = str @@ -116,6 +118,7 @@ NextToken = str NextUploadIdMarker = str NextVersionIdMarker = str +NonEmptyKmsKeyArnString = str NotificationId = str ObjectKey = str ObjectLockEnabledForBucket = bool @@ -133,6 +136,7 @@ QuoteEscapeCharacter = str Range = str RecordDelimiter = str +RecordExpirationDays = int Region = str RenameSource = str RenameSourceIfMatch = str @@ -209,6 +213,11 @@ class ArchiveStatus(StrEnum): DEEP_ARCHIVE_ACCESS = "DEEP_ARCHIVE_ACCESS" +class BucketAbacStatus(StrEnum): + Enabled = "Enabled" + Disabled = "Disabled" + + class BucketAccelerateStatus(StrEnum): Enabled = "Enabled" Suspended = "Suspended" @@ -310,6 +319,11 @@ class EncodingType(StrEnum): url = "url" +class EncryptionType(StrEnum): + NONE = "NONE" + SSE_C = "SSE-C" + + class Event(StrEnum): s3_ReducedRedundancyLostObject = "s3:ReducedRedundancyLostObject" s3_ObjectCreated_ = "s3:ObjectCreated:*" @@ -347,6 +361,11 @@ class ExistingObjectReplicationStatus(StrEnum): Disabled = "Disabled" +class ExpirationState(StrEnum): + ENABLED = "ENABLED" + DISABLED = "DISABLED" + + class ExpirationStatus(StrEnum): Enabled = "Enabled" Disabled = "Disabled" @@ -377,6 +396,11 @@ class IntelligentTieringStatus(StrEnum): Disabled = "Disabled" +class InventoryConfigurationState(StrEnum): + ENABLED = "ENABLED" + DISABLED = "DISABLED" + + class InventoryFormat(StrEnum): CSV = "CSV" ORC = "ORC" @@ -409,6 +433,7 @@ class InventoryOptionalField(StrEnum): ChecksumAlgorithm = "ChecksumAlgorithm" ObjectAccessControlList = "ObjectAccessControlList" ObjectOwner = "ObjectOwner" + LifecycleExpirationDate = "LifecycleExpirationDate" class JSONType(StrEnum): @@ -497,6 +522,7 @@ class ObjectStorageClass(StrEnum): SNOW = "SNOW" EXPRESS_ONEZONE = "EXPRESS_ONEZONE" FSX_OPENZFS = "FSX_OPENZFS" + FSX_ONTAP = "FSX_ONTAP" class ObjectVersionStorageClass(StrEnum): @@ -574,6 +600,11 @@ class RestoreRequestType(StrEnum): SELECT = "SELECT" +class S3TablesBucketType(StrEnum): + aws = "aws" + customer = "customer" + + class ServerSideEncryption(StrEnum): AES256 = "AES256" aws_fsx = "aws:fsx" @@ -604,12 +635,18 @@ class StorageClass(StrEnum): SNOW = "SNOW" EXPRESS_ONEZONE = "EXPRESS_ONEZONE" FSX_OPENZFS = "FSX_OPENZFS" + FSX_ONTAP = "FSX_ONTAP" class StorageClassAnalysisSchemaVersion(StrEnum): V_1 = "V_1" +class TableSseAlgorithm(StrEnum): + aws_kms = "aws:kms" + AES256 = "AES256" + + class TaggingDirective(StrEnum): COPY = "COPY" REPLACE = "REPLACE" @@ -641,6 +678,21 @@ class Type(StrEnum): Group = "Group" +ServerTime = datetime +Expires = datetime + + +class AccessDenied(ServiceException): + code: str = "AccessDenied" + sender_fault: bool = False + status_code: int = 403 + Expires: Expires | None + ServerTime: ServerTime | None + X_Amz_Expires: X_Amz_Expires | None + HostId: HostId | None + HeadersNotSigned: HeadersNotSigned | None + + class BucketAlreadyExists(ServiceException): code: str = "BucketAlreadyExists" sender_fault: bool = False @@ -651,7 +703,7 @@ class BucketAlreadyOwnedByYou(ServiceException): code: str = "BucketAlreadyOwnedByYou" sender_fault: bool = False status_code: int = 409 - BucketName: Optional[BucketName] + BucketName: BucketName | None class EncryptionTypeMismatch(ServiceException): @@ -670,8 +722,8 @@ class InvalidObjectState(ServiceException): code: str = "InvalidObjectState" sender_fault: bool = False status_code: int = 403 - StorageClass: Optional[StorageClass] - AccessTier: Optional[IntelligentTieringAccessTier] + StorageClass: StorageClass | None + AccessTier: IntelligentTieringAccessTier | None class InvalidRequest(ServiceException): @@ -690,23 +742,23 @@ class NoSuchBucket(ServiceException): code: str = "NoSuchBucket" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class NoSuchKey(ServiceException): code: str = "NoSuchKey" sender_fault: bool = False status_code: int = 404 - Key: Optional[ObjectKey] - DeleteMarker: Optional[DeleteMarker] - VersionId: Optional[ObjectVersionId] + Key: ObjectKey | None + DeleteMarker: DeleteMarker | None + VersionId: ObjectVersionId | None class NoSuchUpload(ServiceException): code: str = "NoSuchUpload" sender_fault: bool = False status_code: int = 404 - UploadId: Optional[MultipartUploadId] + UploadId: MultipartUploadId | None class ObjectAlreadyInActiveTierError(ServiceException): @@ -731,29 +783,29 @@ class NoSuchLifecycleConfiguration(ServiceException): code: str = "NoSuchLifecycleConfiguration" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class InvalidBucketName(ServiceException): code: str = "InvalidBucketName" sender_fault: bool = False status_code: int = 400 - BucketName: Optional[BucketName] + BucketName: BucketName | None class NoSuchVersion(ServiceException): code: str = "NoSuchVersion" sender_fault: bool = False status_code: int = 404 - VersionId: Optional[ObjectVersionId] - Key: Optional[ObjectKey] + VersionId: ObjectVersionId | None + Key: ObjectKey | None class PreconditionFailed(ServiceException): code: str = "PreconditionFailed" sender_fault: bool = False status_code: int = 412 - Condition: Optional[IfCondition] + Condition: IfCondition | None ObjectSize = int @@ -763,143 +815,128 @@ class InvalidRange(ServiceException): code: str = "InvalidRange" sender_fault: bool = False status_code: int = 416 - ActualObjectSize: Optional[ObjectSize] - RangeRequested: Optional[ContentRange] + ActualObjectSize: ObjectSize | None + RangeRequested: ContentRange | None class InvalidArgument(ServiceException): code: str = "InvalidArgument" sender_fault: bool = False status_code: int = 400 - ArgumentName: Optional[ArgumentName] - ArgumentValue: Optional[ArgumentValue] - HostId: Optional[HostId] + ArgumentName: ArgumentName | None + ArgumentValue: ArgumentValue | None + HostId: HostId | None class SignatureDoesNotMatch(ServiceException): code: str = "SignatureDoesNotMatch" sender_fault: bool = False status_code: int = 403 - AWSAccessKeyId: Optional[AWSAccessKeyId] - CanonicalRequest: Optional[CanonicalRequest] - CanonicalRequestBytes: Optional[CanonicalRequestBytes] - HostId: Optional[HostId] - SignatureProvided: Optional[SignatureProvided] - StringToSign: Optional[StringToSign] - StringToSignBytes: Optional[StringToSignBytes] - - -ServerTime = datetime -Expires = datetime - - -class AccessDenied(ServiceException): - code: str = "AccessDenied" - sender_fault: bool = False - status_code: int = 403 - Expires: Optional[Expires] - ServerTime: Optional[ServerTime] - X_Amz_Expires: Optional[X_Amz_Expires] - HostId: Optional[HostId] - HeadersNotSigned: Optional[HeadersNotSigned] + AWSAccessKeyId: AWSAccessKeyId | None + CanonicalRequest: CanonicalRequest | None + CanonicalRequestBytes: CanonicalRequestBytes | None + HostId: HostId | None + SignatureProvided: SignatureProvided | None + StringToSign: StringToSign | None + StringToSignBytes: StringToSignBytes | None class AuthorizationQueryParametersError(ServiceException): code: str = "AuthorizationQueryParametersError" sender_fault: bool = False status_code: int = 400 - HostId: Optional[HostId] + HostId: HostId | None class NoSuchWebsiteConfiguration(ServiceException): code: str = "NoSuchWebsiteConfiguration" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class ReplicationConfigurationNotFoundError(ServiceException): code: str = "ReplicationConfigurationNotFoundError" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class BadRequest(ServiceException): code: str = "BadRequest" sender_fault: bool = False status_code: int = 400 - HostId: Optional[HostId] + HostId: HostId | None class AccessForbidden(ServiceException): code: str = "AccessForbidden" sender_fault: bool = False status_code: int = 403 - HostId: Optional[HostId] - Method: Optional[HttpMethod] - ResourceType: Optional[ResourceType] + HostId: HostId | None + Method: HttpMethod | None + ResourceType: ResourceType | None class NoSuchCORSConfiguration(ServiceException): code: str = "NoSuchCORSConfiguration" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class MissingSecurityHeader(ServiceException): code: str = "MissingSecurityHeader" sender_fault: bool = False status_code: int = 400 - MissingHeaderName: Optional[MissingHeaderName] + MissingHeaderName: MissingHeaderName | None class InvalidPartOrder(ServiceException): code: str = "InvalidPartOrder" sender_fault: bool = False status_code: int = 400 - UploadId: Optional[MultipartUploadId] + UploadId: MultipartUploadId | None class InvalidStorageClass(ServiceException): code: str = "InvalidStorageClass" sender_fault: bool = False status_code: int = 400 - StorageClassRequested: Optional[StorageClass] + StorageClassRequested: StorageClass | None class MethodNotAllowed(ServiceException): code: str = "MethodNotAllowed" sender_fault: bool = False status_code: int = 405 - Method: Optional[HttpMethod] - ResourceType: Optional[ResourceType] - DeleteMarker: Optional[DeleteMarker] - VersionId: Optional[ObjectVersionId] - Allow: Optional[HttpMethod] + Method: HttpMethod | None + ResourceType: ResourceType | None + DeleteMarker: DeleteMarker | None + VersionId: ObjectVersionId | None + Allow: HttpMethod | None class CrossLocationLoggingProhibitted(ServiceException): code: str = "CrossLocationLoggingProhibitted" sender_fault: bool = False status_code: int = 403 - TargetBucketLocation: Optional[BucketRegion] - SourceBucketLocation: Optional[BucketRegion] + TargetBucketLocation: BucketRegion | None + SourceBucketLocation: BucketRegion | None class InvalidTargetBucketForLogging(ServiceException): code: str = "InvalidTargetBucketForLogging" sender_fault: bool = False status_code: int = 400 - TargetBucket: Optional[BucketName] + TargetBucket: BucketName | None class BucketNotEmpty(ServiceException): code: str = "BucketNotEmpty" sender_fault: bool = False status_code: int = 409 - BucketName: Optional[BucketName] + BucketName: BucketName | None ProposedSize = int @@ -910,144 +947,156 @@ class EntityTooSmall(ServiceException): code: str = "EntityTooSmall" sender_fault: bool = False status_code: int = 400 - ETag: Optional[ETag] - MinSizeAllowed: Optional[MinSizeAllowed] - PartNumber: Optional[PartNumber] - ProposedSize: Optional[ProposedSize] + ETag: ETag | None + MinSizeAllowed: MinSizeAllowed | None + PartNumber: PartNumber | None + ProposedSize: ProposedSize | None class InvalidPart(ServiceException): code: str = "InvalidPart" sender_fault: bool = False status_code: int = 400 - ETag: Optional[ETag] - UploadId: Optional[MultipartUploadId] - PartNumber: Optional[PartNumber] + ETag: ETag | None + UploadId: MultipartUploadId | None + PartNumber: PartNumber | None class NoSuchTagSet(ServiceException): code: str = "NoSuchTagSet" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class InvalidTag(ServiceException): code: str = "InvalidTag" sender_fault: bool = False status_code: int = 400 - TagKey: Optional[ObjectKey] - TagValue: Optional[Value] + TagKey: ObjectKey | None + TagValue: Value | None class ObjectLockConfigurationNotFoundError(ServiceException): code: str = "ObjectLockConfigurationNotFoundError" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class InvalidPartNumber(ServiceException): code: str = "InvalidPartNumber" sender_fault: bool = False status_code: int = 416 - PartNumberRequested: Optional[PartNumber] - ActualPartCount: Optional[PartNumber] + PartNumberRequested: PartNumber | None + ActualPartCount: PartNumber | None class OwnershipControlsNotFoundError(ServiceException): code: str = "OwnershipControlsNotFoundError" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class NoSuchPublicAccessBlockConfiguration(ServiceException): code: str = "NoSuchPublicAccessBlockConfiguration" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class NoSuchBucketPolicy(ServiceException): code: str = "NoSuchBucketPolicy" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[BucketName] + BucketName: BucketName | None class InvalidDigest(ServiceException): code: str = "InvalidDigest" sender_fault: bool = False status_code: int = 400 - Content_MD5: Optional[ContentMD5] + Content_MD5: ContentMD5 | None class KeyTooLongError(ServiceException): code: str = "KeyTooLongError" sender_fault: bool = False status_code: int = 400 - MaxSizeAllowed: Optional[KeyLength] - Size: Optional[KeyLength] + MaxSizeAllowed: KeyLength | None + Size: KeyLength | None class InvalidLocationConstraint(ServiceException): code: str = "InvalidLocationConstraint" sender_fault: bool = False status_code: int = 400 - LocationConstraint: Optional[BucketRegion] + LocationConstraint: BucketRegion | None class EntityTooLarge(ServiceException): code: str = "EntityTooLarge" sender_fault: bool = False status_code: int = 400 - MaxSizeAllowed: Optional[KeyLength] - HostId: Optional[HostId] - ProposedSize: Optional[ProposedSize] + MaxSizeAllowed: KeyLength | None + HostId: HostId | None + ProposedSize: ProposedSize | None class InvalidEncryptionAlgorithmError(ServiceException): code: str = "InvalidEncryptionAlgorithmError" sender_fault: bool = False status_code: int = 400 - ArgumentName: Optional[ArgumentName] - ArgumentValue: Optional[ArgumentValue] + ArgumentName: ArgumentName | None + ArgumentValue: ArgumentValue | None class NotImplemented(ServiceException): code: str = "NotImplemented" sender_fault: bool = False status_code: int = 501 - Header: Optional[Header] - additionalMessage: Optional[additionalMessage] + Header: Header | None + additionalMessage: additionalMessage | None class ConditionalRequestConflict(ServiceException): code: str = "ConditionalRequestConflict" sender_fault: bool = False status_code: int = 409 - Condition: Optional[IfCondition] - Key: Optional[ObjectKey] + Condition: IfCondition | None + Key: ObjectKey | None class BadDigest(ServiceException): code: str = "BadDigest" sender_fault: bool = False status_code: int = 400 - ExpectedDigest: Optional[ContentMD5] - CalculatedDigest: Optional[ContentMD5] + ExpectedDigest: ContentMD5 | None + CalculatedDigest: ContentMD5 | None + + +class AuthorizationHeaderMalformed(ServiceException): + code: str = "AuthorizationHeaderMalformed" + sender_fault: bool = False + status_code: int = 400 + Region: BucketRegion | None + HostId: HostId | None + + +class AbacStatus(TypedDict, total=False): + Status: BucketAbacStatus | None AbortDate = datetime class AbortIncompleteMultipartUpload(TypedDict, total=False): - DaysAfterInitiation: Optional[DaysAfterInitiation] + DaysAfterInitiation: DaysAfterInitiation | None class AbortMultipartUploadOutput(TypedDict, total=False): - RequestCharged: Optional[RequestCharged] + RequestCharged: RequestCharged | None IfMatchInitiatedTime = datetime @@ -1057,48 +1106,48 @@ class AbortMultipartUploadRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey UploadId: MultipartUploadId - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] - IfMatchInitiatedTime: Optional[IfMatchInitiatedTime] + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None + IfMatchInitiatedTime: IfMatchInitiatedTime | None class AccelerateConfiguration(TypedDict, total=False): - Status: Optional[BucketAccelerateStatus] + Status: BucketAccelerateStatus | None class Owner(TypedDict, total=False): - DisplayName: Optional[DisplayName] - ID: Optional[ID] + DisplayName: DisplayName | None + ID: ID | None class Grantee(TypedDict, total=False): - DisplayName: Optional[DisplayName] - EmailAddress: Optional[EmailAddress] - ID: Optional[ID] + DisplayName: DisplayName | None + EmailAddress: EmailAddress | None + ID: ID | None Type: Type - URI: Optional[URI] + URI: URI | None class Grant(TypedDict, total=False): - Grantee: Optional[Grantee] - Permission: Optional[Permission] + Grantee: Grantee | None + Permission: Permission | None -Grants = List[Grant] +Grants = list[Grant] class AccessControlPolicy(TypedDict, total=False): - Grants: Optional[Grants] - Owner: Optional[Owner] + Grants: Grants | None + Owner: Owner | None class AccessControlTranslation(TypedDict, total=False): Owner: OwnerOverride -AllowedHeaders = List[AllowedHeader] -AllowedMethods = List[AllowedMethod] -AllowedOrigins = List[AllowedOrigin] +AllowedHeaders = list[AllowedHeader] +AllowedMethods = list[AllowedMethod] +AllowedOrigins = list[AllowedOrigin] class Tag(TypedDict, total=False): @@ -1106,19 +1155,19 @@ class Tag(TypedDict, total=False): Value: Value -TagSet = List[Tag] +TagSet = list[Tag] class AnalyticsAndOperator(TypedDict, total=False): - Prefix: Optional[Prefix] - Tags: Optional[TagSet] + Prefix: Prefix | None + Tags: TagSet | None class AnalyticsS3BucketDestination(TypedDict, total=False): Format: AnalyticsS3ExportFileFormat - BucketAccountId: Optional[AccountId] + BucketAccountId: AccountId | None Bucket: BucketName - Prefix: Optional[Prefix] + Prefix: Prefix | None class AnalyticsExportDestination(TypedDict, total=False): @@ -1131,98 +1180,105 @@ class StorageClassAnalysisDataExport(TypedDict, total=False): class StorageClassAnalysis(TypedDict, total=False): - DataExport: Optional[StorageClassAnalysisDataExport] + DataExport: StorageClassAnalysisDataExport | None class AnalyticsFilter(TypedDict, total=False): - Prefix: Optional[Prefix] - Tag: Optional[Tag] - And: Optional[AnalyticsAndOperator] + Prefix: Prefix | None + Tag: Tag | None + And: AnalyticsAndOperator | None class AnalyticsConfiguration(TypedDict, total=False): Id: AnalyticsId - Filter: Optional[AnalyticsFilter] + Filter: AnalyticsFilter | None StorageClassAnalysis: StorageClassAnalysis -AnalyticsConfigurationList = List[AnalyticsConfiguration] +AnalyticsConfigurationList = list[AnalyticsConfiguration] +EncryptionTypeList = list[EncryptionType] + + +class BlockedEncryptionTypes(TypedDict, total=False): + EncryptionType: EncryptionTypeList | None + + Body = bytes CreationDate = datetime class Bucket(TypedDict, total=False): - Name: Optional[BucketName] - CreationDate: Optional[CreationDate] - BucketRegion: Optional[BucketRegion] - BucketArn: Optional[S3RegionalOrS3ExpressBucketArnString] + Name: BucketName | None + CreationDate: CreationDate | None + BucketRegion: BucketRegion | None + BucketArn: S3RegionalOrS3ExpressBucketArnString | None class BucketInfo(TypedDict, total=False): - DataRedundancy: Optional[DataRedundancy] - Type: Optional[BucketType] + DataRedundancy: DataRedundancy | None + Type: BucketType | None class NoncurrentVersionExpiration(TypedDict, total=False): - NoncurrentDays: Optional[Days] - NewerNoncurrentVersions: Optional[VersionCount] + NoncurrentDays: Days | None + NewerNoncurrentVersions: VersionCount | None class NoncurrentVersionTransition(TypedDict, total=False): - NoncurrentDays: Optional[Days] - StorageClass: Optional[TransitionStorageClass] - NewerNoncurrentVersions: Optional[VersionCount] + NoncurrentDays: Days | None + StorageClass: TransitionStorageClass | None + NewerNoncurrentVersions: VersionCount | None -NoncurrentVersionTransitionList = List[NoncurrentVersionTransition] +NoncurrentVersionTransitionList = list[NoncurrentVersionTransition] Date = datetime class Transition(TypedDict, total=False): - Date: Optional[Date] - Days: Optional[Days] - StorageClass: Optional[TransitionStorageClass] + Date: Date | None + Days: Days | None + StorageClass: TransitionStorageClass | None -TransitionList = List[Transition] +TransitionList = list[Transition] ObjectSizeLessThanBytes = int ObjectSizeGreaterThanBytes = int class LifecycleRuleAndOperator(TypedDict, total=False): - Prefix: Optional[Prefix] - Tags: Optional[TagSet] - ObjectSizeGreaterThan: Optional[ObjectSizeGreaterThanBytes] - ObjectSizeLessThan: Optional[ObjectSizeLessThanBytes] + Prefix: Prefix | None + Tags: TagSet | None + ObjectSizeGreaterThan: ObjectSizeGreaterThanBytes | None + ObjectSizeLessThan: ObjectSizeLessThanBytes | None class LifecycleRuleFilter(TypedDict, total=False): - Prefix: Optional[Prefix] - Tag: Optional[Tag] - ObjectSizeGreaterThan: Optional[ObjectSizeGreaterThanBytes] - ObjectSizeLessThan: Optional[ObjectSizeLessThanBytes] - And: Optional[LifecycleRuleAndOperator] + Prefix: Prefix | None + Tag: Tag | None + ObjectSizeGreaterThan: ObjectSizeGreaterThanBytes | None + ObjectSizeLessThan: ObjectSizeLessThanBytes | None + And: LifecycleRuleAndOperator | None class LifecycleExpiration(TypedDict, total=False): - Date: Optional[Date] - Days: Optional[Days] - ExpiredObjectDeleteMarker: Optional[ExpiredObjectDeleteMarker] + Date: Date | None + Days: Days | None + ExpiredObjectDeleteMarker: ExpiredObjectDeleteMarker | None class LifecycleRule(TypedDict, total=False): - Expiration: Optional[LifecycleExpiration] - ID: Optional[ID] - Prefix: Optional[Prefix] - Filter: Optional[LifecycleRuleFilter] + Expiration: LifecycleExpiration | None + ID: ID | None + Prefix: Prefix | None + Filter: LifecycleRuleFilter | None Status: ExpirationStatus - Transitions: Optional[TransitionList] - NoncurrentVersionTransitions: Optional[NoncurrentVersionTransitionList] - NoncurrentVersionExpiration: Optional[NoncurrentVersionExpiration] - AbortIncompleteMultipartUpload: Optional[AbortIncompleteMultipartUpload] + Transitions: TransitionList | None + NoncurrentVersionTransitions: NoncurrentVersionTransitionList | None + NoncurrentVersionExpiration: NoncurrentVersionExpiration | None + AbortIncompleteMultipartUpload: AbortIncompleteMultipartUpload | None -LifecycleRules = List[LifecycleRule] +LifecycleRules = list[LifecycleRule] class BucketLifecycleConfiguration(TypedDict, total=False): @@ -1230,7 +1286,7 @@ class BucketLifecycleConfiguration(TypedDict, total=False): class PartitionedPrefix(TypedDict, total=False): - PartitionDateSource: Optional[PartitionDateSource] + PartitionDateSource: PartitionDateSource | None class SimplePrefix(TypedDict, total=False): @@ -1238,46 +1294,46 @@ class SimplePrefix(TypedDict, total=False): class TargetObjectKeyFormat(TypedDict, total=False): - SimplePrefix: Optional[SimplePrefix] - PartitionedPrefix: Optional[PartitionedPrefix] + SimplePrefix: SimplePrefix | None + PartitionedPrefix: PartitionedPrefix | None class TargetGrant(TypedDict, total=False): - Grantee: Optional[Grantee] - Permission: Optional[BucketLogsPermission] + Grantee: Grantee | None + Permission: BucketLogsPermission | None -TargetGrants = List[TargetGrant] +TargetGrants = list[TargetGrant] class LoggingEnabled(TypedDict, total=False): TargetBucket: TargetBucket - TargetGrants: Optional[TargetGrants] + TargetGrants: TargetGrants | None TargetPrefix: TargetPrefix - TargetObjectKeyFormat: Optional[TargetObjectKeyFormat] + TargetObjectKeyFormat: TargetObjectKeyFormat | None class BucketLoggingStatus(TypedDict, total=False): - LoggingEnabled: Optional[LoggingEnabled] + LoggingEnabled: LoggingEnabled | None -Buckets = List[Bucket] +Buckets = list[Bucket] BytesProcessed = int BytesReturned = int BytesScanned = int -ExposeHeaders = List[ExposeHeader] +ExposeHeaders = list[ExposeHeader] class CORSRule(TypedDict, total=False): - ID: Optional[ID] - AllowedHeaders: Optional[AllowedHeaders] + ID: ID | None + AllowedHeaders: AllowedHeaders | None AllowedMethods: AllowedMethods AllowedOrigins: AllowedOrigins - ExposeHeaders: Optional[ExposeHeaders] - MaxAgeSeconds: Optional[MaxAgeSeconds] + ExposeHeaders: ExposeHeaders | None + MaxAgeSeconds: MaxAgeSeconds | None -CORSRules = List[CORSRule] +CORSRules = list[CORSRule] class CORSConfiguration(TypedDict, total=False): @@ -1285,114 +1341,114 @@ class CORSConfiguration(TypedDict, total=False): class CSVInput(TypedDict, total=False): - FileHeaderInfo: Optional[FileHeaderInfo] - Comments: Optional[Comments] - QuoteEscapeCharacter: Optional[QuoteEscapeCharacter] - RecordDelimiter: Optional[RecordDelimiter] - FieldDelimiter: Optional[FieldDelimiter] - QuoteCharacter: Optional[QuoteCharacter] - AllowQuotedRecordDelimiter: Optional[AllowQuotedRecordDelimiter] + FileHeaderInfo: FileHeaderInfo | None + Comments: Comments | None + QuoteEscapeCharacter: QuoteEscapeCharacter | None + RecordDelimiter: RecordDelimiter | None + FieldDelimiter: FieldDelimiter | None + QuoteCharacter: QuoteCharacter | None + AllowQuotedRecordDelimiter: AllowQuotedRecordDelimiter | None class CSVOutput(TypedDict, total=False): - QuoteFields: Optional[QuoteFields] - QuoteEscapeCharacter: Optional[QuoteEscapeCharacter] - RecordDelimiter: Optional[RecordDelimiter] - FieldDelimiter: Optional[FieldDelimiter] - QuoteCharacter: Optional[QuoteCharacter] + QuoteFields: QuoteFields | None + QuoteEscapeCharacter: QuoteEscapeCharacter | None + RecordDelimiter: RecordDelimiter | None + FieldDelimiter: FieldDelimiter | None + QuoteCharacter: QuoteCharacter | None class Checksum(TypedDict, total=False): - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - ChecksumType: Optional[ChecksumType] + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + ChecksumType: ChecksumType | None -ChecksumAlgorithmList = List[ChecksumAlgorithm] -EventList = List[Event] +ChecksumAlgorithmList = list[ChecksumAlgorithm] +EventList = list[Event] class CloudFunctionConfiguration(TypedDict, total=False): - Id: Optional[NotificationId] - Event: Optional[Event] - Events: Optional[EventList] - CloudFunction: Optional[CloudFunction] - InvocationRole: Optional[CloudFunctionInvocationRole] + Id: NotificationId | None + Event: Event | None + Events: EventList | None + CloudFunction: CloudFunction | None + InvocationRole: CloudFunctionInvocationRole | None class CommonPrefix(TypedDict, total=False): - Prefix: Optional[Prefix] + Prefix: Prefix | None -CommonPrefixList = List[CommonPrefix] +CommonPrefixList = list[CommonPrefix] class CompleteMultipartUploadOutput(TypedDict, total=False): - Location: Optional[Location] - Bucket: Optional[BucketName] - Key: Optional[ObjectKey] - Expiration: Optional[Expiration] - ETag: Optional[ETag] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - ChecksumType: Optional[ChecksumType] - ServerSideEncryption: Optional[ServerSideEncryption] - VersionId: Optional[ObjectVersionId] - SSEKMSKeyId: Optional[SSEKMSKeyId] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestCharged: Optional[RequestCharged] + Location: Location | None + Bucket: BucketName | None + Key: ObjectKey | None + Expiration: Expiration | None + ETag: ETag | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + ChecksumType: ChecksumType | None + ServerSideEncryption: ServerSideEncryption | None + VersionId: ObjectVersionId | None + SSEKMSKeyId: SSEKMSKeyId | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestCharged: RequestCharged | None MpuObjectSize = int class CompletedPart(TypedDict, total=False): - ETag: Optional[ETag] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - PartNumber: Optional[PartNumber] + ETag: ETag | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + PartNumber: PartNumber | None -CompletedPartList = List[CompletedPart] +CompletedPartList = list[CompletedPart] class CompletedMultipartUpload(TypedDict, total=False): - Parts: Optional[CompletedPartList] + Parts: CompletedPartList | None class CompleteMultipartUploadRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - MultipartUpload: Optional[CompletedMultipartUpload] + MultipartUpload: CompletedMultipartUpload | None UploadId: MultipartUploadId - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - ChecksumType: Optional[ChecksumType] - MpuObjectSize: Optional[MpuObjectSize] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] - IfMatch: Optional[IfMatch] - IfNoneMatch: Optional[IfNoneMatch] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + ChecksumType: ChecksumType | None + MpuObjectSize: MpuObjectSize | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None + IfMatch: IfMatch | None + IfNoneMatch: IfNoneMatch | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None class Condition(TypedDict, total=False): - HttpErrorCodeReturnedEquals: Optional[HttpErrorCodeReturnedEquals] - KeyPrefixEquals: Optional[KeyPrefixEquals] + HttpErrorCodeReturnedEquals: HttpErrorCodeReturnedEquals | None + KeyPrefixEquals: KeyPrefixEquals | None ContentLength = int @@ -1406,100 +1462,135 @@ class ContinuationEvent(TypedDict, total=False): class CopyObjectResult(TypedDict, total=False): - ETag: Optional[ETag] - LastModified: Optional[LastModified] - ChecksumType: Optional[ChecksumType] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] + ETag: ETag | None + LastModified: LastModified | None + ChecksumType: ChecksumType | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None class CopyObjectOutput(TypedDict, total=False): - CopyObjectResult: Optional[CopyObjectResult] - Expiration: Optional[Expiration] - CopySourceVersionId: Optional[CopySourceVersionId] - VersionId: Optional[ObjectVersionId] - ServerSideEncryption: Optional[ServerSideEncryption] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestCharged: Optional[RequestCharged] + CopyObjectResult: CopyObjectResult | None + Expiration: Expiration | None + CopySourceVersionId: CopySourceVersionId | None + VersionId: ObjectVersionId | None + ServerSideEncryption: ServerSideEncryption | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestCharged: RequestCharged | None ObjectLockRetainUntilDate = datetime -Metadata = Dict[MetadataKey, MetadataValue] +Metadata = dict[MetadataKey, MetadataValue] CopySourceIfUnmodifiedSince = datetime CopySourceIfModifiedSince = datetime class CopyObjectRequest(ServiceRequest): - ACL: Optional[ObjectCannedACL] + ACL: ObjectCannedACL | None Bucket: BucketName - CacheControl: Optional[CacheControl] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ContentDisposition: Optional[ContentDisposition] - ContentEncoding: Optional[ContentEncoding] - ContentLanguage: Optional[ContentLanguage] - ContentType: Optional[ContentType] + CacheControl: CacheControl | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ContentDisposition: ContentDisposition | None + ContentEncoding: ContentEncoding | None + ContentLanguage: ContentLanguage | None + ContentType: ContentType | None CopySource: CopySource - CopySourceIfMatch: Optional[CopySourceIfMatch] - CopySourceIfModifiedSince: Optional[CopySourceIfModifiedSince] - CopySourceIfNoneMatch: Optional[CopySourceIfNoneMatch] - CopySourceIfUnmodifiedSince: Optional[CopySourceIfUnmodifiedSince] - Expires: Optional[Expires] - GrantFullControl: Optional[GrantFullControl] - GrantRead: Optional[GrantRead] - GrantReadACP: Optional[GrantReadACP] - GrantWriteACP: Optional[GrantWriteACP] + CopySourceIfMatch: CopySourceIfMatch | None + CopySourceIfModifiedSince: CopySourceIfModifiedSince | None + CopySourceIfNoneMatch: CopySourceIfNoneMatch | None + CopySourceIfUnmodifiedSince: CopySourceIfUnmodifiedSince | None + Expires: Expires | None + GrantFullControl: GrantFullControl | None + GrantRead: GrantRead | None + GrantReadACP: GrantReadACP | None + GrantWriteACP: GrantWriteACP | None + IfMatch: IfMatch | None + IfNoneMatch: IfNoneMatch | None Key: ObjectKey - Metadata: Optional[Metadata] - MetadataDirective: Optional[MetadataDirective] - TaggingDirective: Optional[TaggingDirective] - ServerSideEncryption: Optional[ServerSideEncryption] - StorageClass: Optional[StorageClass] - WebsiteRedirectLocation: Optional[WebsiteRedirectLocation] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] - CopySourceSSECustomerAlgorithm: Optional[CopySourceSSECustomerAlgorithm] - CopySourceSSECustomerKey: Optional[CopySourceSSECustomerKey] - CopySourceSSECustomerKeyMD5: Optional[CopySourceSSECustomerKeyMD5] - RequestPayer: Optional[RequestPayer] - Tagging: Optional[TaggingHeader] - ObjectLockMode: Optional[ObjectLockMode] - ObjectLockRetainUntilDate: Optional[ObjectLockRetainUntilDate] - ObjectLockLegalHoldStatus: Optional[ObjectLockLegalHoldStatus] - ExpectedBucketOwner: Optional[AccountId] - ExpectedSourceBucketOwner: Optional[AccountId] + Metadata: Metadata | None + MetadataDirective: MetadataDirective | None + TaggingDirective: TaggingDirective | None + ServerSideEncryption: ServerSideEncryption | None + StorageClass: StorageClass | None + WebsiteRedirectLocation: WebsiteRedirectLocation | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None + CopySourceSSECustomerAlgorithm: CopySourceSSECustomerAlgorithm | None + CopySourceSSECustomerKey: CopySourceSSECustomerKey | None + CopySourceSSECustomerKeyMD5: CopySourceSSECustomerKeyMD5 | None + RequestPayer: RequestPayer | None + Tagging: TaggingHeader | None + ObjectLockMode: ObjectLockMode | None + ObjectLockRetainUntilDate: ObjectLockRetainUntilDate | None + ObjectLockLegalHoldStatus: ObjectLockLegalHoldStatus | None + ExpectedBucketOwner: AccountId | None + ExpectedSourceBucketOwner: AccountId | None class CopyPartResult(TypedDict, total=False): - ETag: Optional[ETag] - LastModified: Optional[LastModified] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] + ETag: ETag | None + LastModified: LastModified | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None class LocationInfo(TypedDict, total=False): - Type: Optional[LocationType] - Name: Optional[LocationNameAsString] + Type: LocationType | None + Name: LocationNameAsString | None class CreateBucketConfiguration(TypedDict, total=False): - LocationConstraint: Optional[BucketLocationConstraint] - Location: Optional[LocationInfo] - Bucket: Optional[BucketInfo] - Tags: Optional[TagSet] + LocationConstraint: BucketLocationConstraint | None + Location: LocationInfo | None + Bucket: BucketInfo | None + Tags: TagSet | None + + +class MetadataTableEncryptionConfiguration(TypedDict, total=False): + SseAlgorithm: TableSseAlgorithm + KmsKeyArn: KmsKeyArn | None + + +class InventoryTableConfiguration(TypedDict, total=False): + ConfigurationState: InventoryConfigurationState + EncryptionConfiguration: MetadataTableEncryptionConfiguration | None + + +class RecordExpiration(TypedDict, total=False): + Expiration: ExpirationState + Days: RecordExpirationDays | None + + +class JournalTableConfiguration(TypedDict, total=False): + RecordExpiration: RecordExpiration + EncryptionConfiguration: MetadataTableEncryptionConfiguration | None + + +class MetadataConfiguration(TypedDict, total=False): + JournalTableConfiguration: JournalTableConfiguration + InventoryTableConfiguration: InventoryTableConfiguration | None + + +class CreateBucketMetadataConfigurationRequest(ServiceRequest): + Bucket: BucketName + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + MetadataConfiguration: MetadataConfiguration + ExpectedBucketOwner: AccountId | None class S3TablesDestination(TypedDict, total=False): @@ -1513,79 +1604,79 @@ class MetadataTableConfiguration(TypedDict, total=False): class CreateBucketMetadataTableConfigurationRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None MetadataTableConfiguration: MetadataTableConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class CreateBucketOutput(TypedDict, total=False): - Location: Optional[Location] - BucketArn: Optional[S3RegionalOrS3ExpressBucketArnString] + Location: Location | None + BucketArn: S3RegionalOrS3ExpressBucketArnString | None class CreateBucketRequest(ServiceRequest): - ACL: Optional[BucketCannedACL] + ACL: BucketCannedACL | None Bucket: BucketName - CreateBucketConfiguration: Optional[CreateBucketConfiguration] - GrantFullControl: Optional[GrantFullControl] - GrantRead: Optional[GrantRead] - GrantReadACP: Optional[GrantReadACP] - GrantWrite: Optional[GrantWrite] - GrantWriteACP: Optional[GrantWriteACP] - ObjectLockEnabledForBucket: Optional[ObjectLockEnabledForBucket] - ObjectOwnership: Optional[ObjectOwnership] + CreateBucketConfiguration: CreateBucketConfiguration | None + GrantFullControl: GrantFullControl | None + GrantRead: GrantRead | None + GrantReadACP: GrantReadACP | None + GrantWrite: GrantWrite | None + GrantWriteACP: GrantWriteACP | None + ObjectLockEnabledForBucket: ObjectLockEnabledForBucket | None + ObjectOwnership: ObjectOwnership | None class CreateMultipartUploadOutput(TypedDict, total=False): - AbortDate: Optional[AbortDate] - AbortRuleId: Optional[AbortRuleId] - Bucket: Optional[BucketName] - Key: Optional[ObjectKey] - UploadId: Optional[MultipartUploadId] - ServerSideEncryption: Optional[ServerSideEncryption] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestCharged: Optional[RequestCharged] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ChecksumType: Optional[ChecksumType] + AbortDate: AbortDate | None + AbortRuleId: AbortRuleId | None + Bucket: BucketName | None + Key: ObjectKey | None + UploadId: MultipartUploadId | None + ServerSideEncryption: ServerSideEncryption | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestCharged: RequestCharged | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ChecksumType: ChecksumType | None class CreateMultipartUploadRequest(ServiceRequest): - ACL: Optional[ObjectCannedACL] + ACL: ObjectCannedACL | None Bucket: BucketName - CacheControl: Optional[CacheControl] - ContentDisposition: Optional[ContentDisposition] - ContentEncoding: Optional[ContentEncoding] - ContentLanguage: Optional[ContentLanguage] - ContentType: Optional[ContentType] - Expires: Optional[Expires] - GrantFullControl: Optional[GrantFullControl] - GrantRead: Optional[GrantRead] - GrantReadACP: Optional[GrantReadACP] - GrantWriteACP: Optional[GrantWriteACP] + CacheControl: CacheControl | None + ContentDisposition: ContentDisposition | None + ContentEncoding: ContentEncoding | None + ContentLanguage: ContentLanguage | None + ContentType: ContentType | None + Expires: Expires | None + GrantFullControl: GrantFullControl | None + GrantRead: GrantRead | None + GrantReadACP: GrantReadACP | None + GrantWriteACP: GrantWriteACP | None Key: ObjectKey - Metadata: Optional[Metadata] - ServerSideEncryption: Optional[ServerSideEncryption] - StorageClass: Optional[StorageClass] - WebsiteRedirectLocation: Optional[WebsiteRedirectLocation] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestPayer: Optional[RequestPayer] - Tagging: Optional[TaggingHeader] - ObjectLockMode: Optional[ObjectLockMode] - ObjectLockRetainUntilDate: Optional[ObjectLockRetainUntilDate] - ObjectLockLegalHoldStatus: Optional[ObjectLockLegalHoldStatus] - ExpectedBucketOwner: Optional[AccountId] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ChecksumType: Optional[ChecksumType] + Metadata: Metadata | None + ServerSideEncryption: ServerSideEncryption | None + StorageClass: StorageClass | None + WebsiteRedirectLocation: WebsiteRedirectLocation | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestPayer: RequestPayer | None + Tagging: TaggingHeader | None + ObjectLockMode: ObjectLockMode | None + ObjectLockRetainUntilDate: ObjectLockRetainUntilDate | None + ObjectLockLegalHoldStatus: ObjectLockLegalHoldStatus | None + ExpectedBucketOwner: AccountId | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ChecksumType: ChecksumType | None SessionExpiration = datetime @@ -1599,26 +1690,26 @@ class SessionCredentials(TypedDict, total=False): class CreateSessionOutput(TypedDict, total=False): - ServerSideEncryption: Optional[ServerSideEncryption] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] + ServerSideEncryption: ServerSideEncryption | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None Credentials: SessionCredentials class CreateSessionRequest(ServiceRequest): - SessionMode: Optional[SessionMode] + SessionMode: SessionMode | None Bucket: BucketName - ServerSideEncryption: Optional[ServerSideEncryption] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] + ServerSideEncryption: ServerSideEncryption | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None class DefaultRetention(TypedDict, total=False): - Mode: Optional[ObjectLockRetentionMode] - Days: Optional[Days] - Years: Optional[Years] + Mode: ObjectLockRetentionMode | None + Days: Days | None + Years: Years | None Size = int @@ -1627,113 +1718,118 @@ class DefaultRetention(TypedDict, total=False): class ObjectIdentifier(TypedDict, total=False): Key: ObjectKey - VersionId: Optional[ObjectVersionId] - ETag: Optional[ETag] - LastModifiedTime: Optional[LastModifiedTime] - Size: Optional[Size] + VersionId: ObjectVersionId | None + ETag: ETag | None + LastModifiedTime: LastModifiedTime | None + Size: Size | None -ObjectIdentifierList = List[ObjectIdentifier] +ObjectIdentifierList = list[ObjectIdentifier] class Delete(TypedDict, total=False): Objects: ObjectIdentifierList - Quiet: Optional[Quiet] + Quiet: Quiet | None class DeleteBucketAnalyticsConfigurationRequest(ServiceRequest): Bucket: BucketName Id: AnalyticsId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketCorsRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketEncryptionRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketIntelligentTieringConfigurationRequest(ServiceRequest): Bucket: BucketName Id: IntelligentTieringId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketInventoryConfigurationRequest(ServiceRequest): Bucket: BucketName Id: InventoryId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketLifecycleRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None + + +class DeleteBucketMetadataConfigurationRequest(ServiceRequest): + Bucket: BucketName + ExpectedBucketOwner: AccountId | None class DeleteBucketMetadataTableConfigurationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketMetricsConfigurationRequest(ServiceRequest): Bucket: BucketName Id: MetricsId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketOwnershipControlsRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketPolicyRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketReplicationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketTaggingRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteBucketWebsiteRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class DeleteMarkerEntry(TypedDict, total=False): - Owner: Optional[Owner] - Key: Optional[ObjectKey] - VersionId: Optional[ObjectVersionId] - IsLatest: Optional[IsLatest] - LastModified: Optional[LastModified] + Owner: Owner | None + Key: ObjectKey | None + VersionId: ObjectVersionId | None + IsLatest: IsLatest | None + LastModified: LastModified | None class DeleteMarkerReplication(TypedDict, total=False): - Status: Optional[DeleteMarkerReplicationStatus] + Status: DeleteMarkerReplicationStatus | None -DeleteMarkers = List[DeleteMarkerEntry] +DeleteMarkers = list[DeleteMarkerEntry] class DeleteObjectOutput(TypedDict, total=False): - DeleteMarker: Optional[DeleteMarker] - VersionId: Optional[ObjectVersionId] - RequestCharged: Optional[RequestCharged] + DeleteMarker: DeleteMarker | None + VersionId: ObjectVersionId | None + RequestCharged: RequestCharged | None IfMatchSize = int @@ -1743,75 +1839,75 @@ class DeleteObjectOutput(TypedDict, total=False): class DeleteObjectRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - MFA: Optional[MFA] - VersionId: Optional[ObjectVersionId] - RequestPayer: Optional[RequestPayer] - BypassGovernanceRetention: Optional[BypassGovernanceRetention] - ExpectedBucketOwner: Optional[AccountId] - IfMatch: Optional[IfMatch] - IfMatchLastModifiedTime: Optional[IfMatchLastModifiedTime] - IfMatchSize: Optional[IfMatchSize] + MFA: MFA | None + VersionId: ObjectVersionId | None + RequestPayer: RequestPayer | None + BypassGovernanceRetention: BypassGovernanceRetention | None + ExpectedBucketOwner: AccountId | None + IfMatch: IfMatch | None + IfMatchLastModifiedTime: IfMatchLastModifiedTime | None + IfMatchSize: IfMatchSize | None class DeleteObjectTaggingOutput(TypedDict, total=False): - VersionId: Optional[ObjectVersionId] + VersionId: ObjectVersionId | None class DeleteObjectTaggingRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - ExpectedBucketOwner: Optional[AccountId] + VersionId: ObjectVersionId | None + ExpectedBucketOwner: AccountId | None class Error(TypedDict, total=False): - Key: Optional[ObjectKey] - VersionId: Optional[ObjectVersionId] - Code: Optional[Code] - Message: Optional[Message] + Key: ObjectKey | None + VersionId: ObjectVersionId | None + Code: Code | None + Message: Message | None -Errors = List[Error] +Errors = list[Error] class DeletedObject(TypedDict, total=False): - Key: Optional[ObjectKey] - VersionId: Optional[ObjectVersionId] - DeleteMarker: Optional[DeleteMarker] - DeleteMarkerVersionId: Optional[DeleteMarkerVersionId] + Key: ObjectKey | None + VersionId: ObjectVersionId | None + DeleteMarker: DeleteMarker | None + DeleteMarkerVersionId: DeleteMarkerVersionId | None -DeletedObjects = List[DeletedObject] +DeletedObjects = list[DeletedObject] class DeleteObjectsOutput(TypedDict, total=False): - Deleted: Optional[DeletedObjects] - RequestCharged: Optional[RequestCharged] - Errors: Optional[Errors] + Deleted: DeletedObjects | None + RequestCharged: RequestCharged | None + Errors: Errors | None class DeleteObjectsRequest(ServiceRequest): Bucket: BucketName Delete: Delete - MFA: Optional[MFA] - RequestPayer: Optional[RequestPayer] - BypassGovernanceRetention: Optional[BypassGovernanceRetention] - ExpectedBucketOwner: Optional[AccountId] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + MFA: MFA | None + RequestPayer: RequestPayer | None + BypassGovernanceRetention: BypassGovernanceRetention | None + ExpectedBucketOwner: AccountId | None + ChecksumAlgorithm: ChecksumAlgorithm | None class DeletePublicAccessBlockRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class ReplicationTimeValue(TypedDict, total=False): - Minutes: Optional[Minutes] + Minutes: Minutes | None class Metrics(TypedDict, total=False): Status: MetricsStatus - EventThreshold: Optional[ReplicationTimeValue] + EventThreshold: ReplicationTimeValue | None class ReplicationTime(TypedDict, total=False): @@ -1820,23 +1916,29 @@ class ReplicationTime(TypedDict, total=False): class EncryptionConfiguration(TypedDict, total=False): - ReplicaKmsKeyID: Optional[ReplicaKmsKeyID] + ReplicaKmsKeyID: ReplicaKmsKeyID | None class Destination(TypedDict, total=False): Bucket: BucketName - Account: Optional[AccountId] - StorageClass: Optional[StorageClass] - AccessControlTranslation: Optional[AccessControlTranslation] - EncryptionConfiguration: Optional[EncryptionConfiguration] - ReplicationTime: Optional[ReplicationTime] - Metrics: Optional[Metrics] + Account: AccountId | None + StorageClass: StorageClass | None + AccessControlTranslation: AccessControlTranslation | None + EncryptionConfiguration: EncryptionConfiguration | None + ReplicationTime: ReplicationTime | None + Metrics: Metrics | None + + +class DestinationResult(TypedDict, total=False): + TableBucketType: S3TablesBucketType | None + TableBucketArn: S3TablesBucketArn | None + TableNamespace: S3TablesNamespace | None class Encryption(TypedDict, total=False): EncryptionType: ServerSideEncryption - KMSKeyId: Optional[SSEKMSKeyId] - KMSContext: Optional[KMSContext] + KMSKeyId: SSEKMSKeyId | None + KMSContext: KMSContext | None End = int @@ -1847,8 +1949,8 @@ class EndEvent(TypedDict, total=False): class ErrorDetails(TypedDict, total=False): - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None class ErrorDocument(TypedDict, total=False): @@ -1864,64 +1966,74 @@ class ExistingObjectReplication(TypedDict, total=False): class FilterRule(TypedDict, total=False): - Name: Optional[FilterRuleName] - Value: Optional[FilterRuleValue] + Name: FilterRuleName | None + Value: FilterRuleValue | None + + +FilterRuleList = list[FilterRule] + +class GetBucketAbacOutput(TypedDict, total=False): + AbacStatus: AbacStatus | None -FilterRuleList = List[FilterRule] + +class GetBucketAbacRequest(ServiceRequest): + Bucket: BucketName + ExpectedBucketOwner: AccountId | None class GetBucketAccelerateConfigurationOutput(TypedDict, total=False): - Status: Optional[BucketAccelerateStatus] - RequestCharged: Optional[RequestCharged] + Status: BucketAccelerateStatus | None + RequestCharged: RequestCharged | None class GetBucketAccelerateConfigurationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] - RequestPayer: Optional[RequestPayer] + ExpectedBucketOwner: AccountId | None + RequestPayer: RequestPayer | None class GetBucketAclOutput(TypedDict, total=False): - Owner: Optional[Owner] - Grants: Optional[Grants] + Owner: Owner | None + Grants: Grants | None class GetBucketAclRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketAnalyticsConfigurationOutput(TypedDict, total=False): - AnalyticsConfiguration: Optional[AnalyticsConfiguration] + AnalyticsConfiguration: AnalyticsConfiguration | None class GetBucketAnalyticsConfigurationRequest(ServiceRequest): Bucket: BucketName Id: AnalyticsId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketCorsOutput(TypedDict, total=False): - CORSRules: Optional[CORSRules] + CORSRules: CORSRules | None class GetBucketCorsRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class ServerSideEncryptionByDefault(TypedDict, total=False): SSEAlgorithm: ServerSideEncryption - KMSMasterKeyID: Optional[SSEKMSKeyId] + KMSMasterKeyID: SSEKMSKeyId | None class ServerSideEncryptionRule(TypedDict, total=False): - ApplyServerSideEncryptionByDefault: Optional[ServerSideEncryptionByDefault] - BucketKeyEnabled: Optional[BucketKeyEnabled] + ApplyServerSideEncryptionByDefault: ServerSideEncryptionByDefault | None + BucketKeyEnabled: BucketKeyEnabled | None + BlockedEncryptionTypes: BlockedEncryptionTypes | None -ServerSideEncryptionRules = List[ServerSideEncryptionRule] +ServerSideEncryptionRules = list[ServerSideEncryptionRule] class ServerSideEncryptionConfiguration(TypedDict, total=False): @@ -1929,12 +2041,12 @@ class ServerSideEncryptionConfiguration(TypedDict, total=False): class GetBucketEncryptionOutput(TypedDict, total=False): - ServerSideEncryptionConfiguration: Optional[ServerSideEncryptionConfiguration] + ServerSideEncryptionConfiguration: ServerSideEncryptionConfiguration | None class GetBucketEncryptionRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class Tiering(TypedDict, total=False): @@ -1942,42 +2054,42 @@ class Tiering(TypedDict, total=False): AccessTier: IntelligentTieringAccessTier -TieringList = List[Tiering] +TieringList = list[Tiering] class IntelligentTieringAndOperator(TypedDict, total=False): - Prefix: Optional[Prefix] - Tags: Optional[TagSet] + Prefix: Prefix | None + Tags: TagSet | None class IntelligentTieringFilter(TypedDict, total=False): - Prefix: Optional[Prefix] - Tag: Optional[Tag] - And: Optional[IntelligentTieringAndOperator] + Prefix: Prefix | None + Tag: Tag | None + And: IntelligentTieringAndOperator | None class IntelligentTieringConfiguration(TypedDict, total=False): Id: IntelligentTieringId - Filter: Optional[IntelligentTieringFilter] + Filter: IntelligentTieringFilter | None Status: IntelligentTieringStatus Tierings: TieringList class GetBucketIntelligentTieringConfigurationOutput(TypedDict, total=False): - IntelligentTieringConfiguration: Optional[IntelligentTieringConfiguration] + IntelligentTieringConfiguration: IntelligentTieringConfiguration | None class GetBucketIntelligentTieringConfigurationRequest(ServiceRequest): Bucket: BucketName Id: IntelligentTieringId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class InventorySchedule(TypedDict, total=False): Frequency: InventoryFrequency -InventoryOptionalFields = List[InventoryOptionalField] +InventoryOptionalFields = list[InventoryOptionalField] class InventoryFilter(TypedDict, total=False): @@ -1993,16 +2105,16 @@ class SSES3(TypedDict, total=False): class InventoryEncryption(TypedDict, total=False): - SSES3: Optional[SSES3] - SSEKMS: Optional[SSEKMS] + SSES3: SSES3 | None + SSEKMS: SSEKMS | None class InventoryS3BucketDestination(TypedDict, total=False): - AccountId: Optional[AccountId] + AccountId: AccountId | None Bucket: BucketName Format: InventoryFormat - Prefix: Optional[Prefix] - Encryption: Optional[InventoryEncryption] + Prefix: Prefix | None + Encryption: InventoryEncryption | None class InventoryDestination(TypedDict, total=False): @@ -2012,72 +2124,107 @@ class InventoryDestination(TypedDict, total=False): class InventoryConfiguration(TypedDict, total=False): Destination: InventoryDestination IsEnabled: IsEnabled - Filter: Optional[InventoryFilter] + Filter: InventoryFilter | None Id: InventoryId IncludedObjectVersions: InventoryIncludedObjectVersions - OptionalFields: Optional[InventoryOptionalFields] + OptionalFields: InventoryOptionalFields | None Schedule: InventorySchedule class GetBucketInventoryConfigurationOutput(TypedDict, total=False): - InventoryConfiguration: Optional[InventoryConfiguration] + InventoryConfiguration: InventoryConfiguration | None class GetBucketInventoryConfigurationRequest(ServiceRequest): Bucket: BucketName Id: InventoryId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketLifecycleConfigurationOutput(TypedDict, total=False): - Rules: Optional[LifecycleRules] - TransitionDefaultMinimumObjectSize: Optional[TransitionDefaultMinimumObjectSize] + Rules: LifecycleRules | None + TransitionDefaultMinimumObjectSize: TransitionDefaultMinimumObjectSize | None class GetBucketLifecycleConfigurationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class Rule(TypedDict, total=False): - Expiration: Optional[LifecycleExpiration] - ID: Optional[ID] + Expiration: LifecycleExpiration | None + ID: ID | None Prefix: Prefix Status: ExpirationStatus - Transition: Optional[Transition] - NoncurrentVersionTransition: Optional[NoncurrentVersionTransition] - NoncurrentVersionExpiration: Optional[NoncurrentVersionExpiration] - AbortIncompleteMultipartUpload: Optional[AbortIncompleteMultipartUpload] + Transition: Transition | None + NoncurrentVersionTransition: NoncurrentVersionTransition | None + NoncurrentVersionExpiration: NoncurrentVersionExpiration | None + AbortIncompleteMultipartUpload: AbortIncompleteMultipartUpload | None -Rules = List[Rule] +Rules = list[Rule] class GetBucketLifecycleOutput(TypedDict, total=False): - Rules: Optional[Rules] + Rules: Rules | None class GetBucketLifecycleRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketLocationOutput(TypedDict, total=False): - LocationConstraint: Optional[BucketLocationConstraint] + LocationConstraint: BucketLocationConstraint | None class GetBucketLocationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketLoggingOutput(TypedDict, total=False): - LoggingEnabled: Optional[LoggingEnabled] + LoggingEnabled: LoggingEnabled | None class GetBucketLoggingRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None + + +class InventoryTableConfigurationResult(TypedDict, total=False): + ConfigurationState: InventoryConfigurationState + TableStatus: MetadataTableStatus | None + Error: ErrorDetails | None + TableName: S3TablesName | None + TableArn: S3TablesArn | None + + +class JournalTableConfigurationResult(TypedDict, total=False): + TableStatus: MetadataTableStatus + Error: ErrorDetails | None + TableName: S3TablesName + TableArn: S3TablesArn | None + RecordExpiration: RecordExpiration + + +class MetadataConfigurationResult(TypedDict, total=False): + DestinationResult: DestinationResult + JournalTableConfigurationResult: JournalTableConfigurationResult | None + InventoryTableConfigurationResult: InventoryTableConfigurationResult | None + + +class GetBucketMetadataConfigurationResult(TypedDict, total=False): + MetadataConfigurationResult: MetadataConfigurationResult + + +class GetBucketMetadataConfigurationOutput(TypedDict, total=False): + GetBucketMetadataConfigurationResult: GetBucketMetadataConfigurationResult | None + + +class GetBucketMetadataConfigurationRequest(ServiceRequest): + Bucket: BucketName + ExpectedBucketOwner: AccountId | None class S3TablesDestinationResult(TypedDict, total=False): @@ -2094,56 +2241,56 @@ class MetadataTableConfigurationResult(TypedDict, total=False): class GetBucketMetadataTableConfigurationResult(TypedDict, total=False): MetadataTableConfigurationResult: MetadataTableConfigurationResult Status: MetadataTableStatus - Error: Optional[ErrorDetails] + Error: ErrorDetails | None class GetBucketMetadataTableConfigurationOutput(TypedDict, total=False): - GetBucketMetadataTableConfigurationResult: Optional[GetBucketMetadataTableConfigurationResult] + GetBucketMetadataTableConfigurationResult: GetBucketMetadataTableConfigurationResult | None class GetBucketMetadataTableConfigurationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class MetricsAndOperator(TypedDict, total=False): - Prefix: Optional[Prefix] - Tags: Optional[TagSet] - AccessPointArn: Optional[AccessPointArn] + Prefix: Prefix | None + Tags: TagSet | None + AccessPointArn: AccessPointArn | None class MetricsFilter(TypedDict, total=False): - Prefix: Optional[Prefix] - Tag: Optional[Tag] - AccessPointArn: Optional[AccessPointArn] - And: Optional[MetricsAndOperator] + Prefix: Prefix | None + Tag: Tag | None + AccessPointArn: AccessPointArn | None + And: MetricsAndOperator | None class MetricsConfiguration(TypedDict, total=False): Id: MetricsId - Filter: Optional[MetricsFilter] + Filter: MetricsFilter | None class GetBucketMetricsConfigurationOutput(TypedDict, total=False): - MetricsConfiguration: Optional[MetricsConfiguration] + MetricsConfiguration: MetricsConfiguration | None class GetBucketMetricsConfigurationRequest(ServiceRequest): Bucket: BucketName Id: MetricsId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketNotificationConfigurationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class OwnershipControlsRule(TypedDict, total=False): ObjectOwnership: ObjectOwnership -OwnershipControlsRules = List[OwnershipControlsRule] +OwnershipControlsRules = list[OwnershipControlsRule] class OwnershipControls(TypedDict, total=False): @@ -2151,34 +2298,34 @@ class OwnershipControls(TypedDict, total=False): class GetBucketOwnershipControlsOutput(TypedDict, total=False): - OwnershipControls: Optional[OwnershipControls] + OwnershipControls: OwnershipControls | None class GetBucketOwnershipControlsRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketPolicyOutput(TypedDict, total=False): - Policy: Optional[Policy] + Policy: Policy | None class GetBucketPolicyRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PolicyStatus(TypedDict, total=False): - IsPublic: Optional[IsPublic] + IsPublic: IsPublic | None class GetBucketPolicyStatusOutput(TypedDict, total=False): - PolicyStatus: Optional[PolicyStatus] + PolicyStatus: PolicyStatus | None class GetBucketPolicyStatusRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class ReplicaModifications(TypedDict, total=False): @@ -2190,34 +2337,34 @@ class SseKmsEncryptedObjects(TypedDict, total=False): class SourceSelectionCriteria(TypedDict, total=False): - SseKmsEncryptedObjects: Optional[SseKmsEncryptedObjects] - ReplicaModifications: Optional[ReplicaModifications] + SseKmsEncryptedObjects: SseKmsEncryptedObjects | None + ReplicaModifications: ReplicaModifications | None class ReplicationRuleAndOperator(TypedDict, total=False): - Prefix: Optional[Prefix] - Tags: Optional[TagSet] + Prefix: Prefix | None + Tags: TagSet | None class ReplicationRuleFilter(TypedDict, total=False): - Prefix: Optional[Prefix] - Tag: Optional[Tag] - And: Optional[ReplicationRuleAndOperator] + Prefix: Prefix | None + Tag: Tag | None + And: ReplicationRuleAndOperator | None class ReplicationRule(TypedDict, total=False): - ID: Optional[ID] - Priority: Optional[Priority] - Prefix: Optional[Prefix] - Filter: Optional[ReplicationRuleFilter] + ID: ID | None + Priority: Priority | None + Prefix: Prefix | None + Filter: ReplicationRuleFilter | None Status: ReplicationRuleStatus - SourceSelectionCriteria: Optional[SourceSelectionCriteria] - ExistingObjectReplication: Optional[ExistingObjectReplication] + SourceSelectionCriteria: SourceSelectionCriteria | None + ExistingObjectReplication: ExistingObjectReplication | None Destination: Destination - DeleteMarkerReplication: Optional[DeleteMarkerReplication] + DeleteMarkerReplication: DeleteMarkerReplication | None -ReplicationRules = List[ReplicationRule] +ReplicationRules = list[ReplicationRule] class ReplicationConfiguration(TypedDict, total=False): @@ -2226,21 +2373,21 @@ class ReplicationConfiguration(TypedDict, total=False): class GetBucketReplicationOutput(TypedDict, total=False): - ReplicationConfiguration: Optional[ReplicationConfiguration] + ReplicationConfiguration: ReplicationConfiguration | None class GetBucketReplicationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketRequestPaymentOutput(TypedDict, total=False): - Payer: Optional[Payer] + Payer: Payer | None class GetBucketRequestPaymentRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketTaggingOutput(TypedDict, total=False): @@ -2249,33 +2396,33 @@ class GetBucketTaggingOutput(TypedDict, total=False): class GetBucketTaggingRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetBucketVersioningOutput(TypedDict, total=False): - Status: Optional[BucketVersioningStatus] - MFADelete: Optional[MFADeleteStatus] + Status: BucketVersioningStatus | None + MFADelete: MFADeleteStatus | None class GetBucketVersioningRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class Redirect(TypedDict, total=False): - HostName: Optional[HostName] - HttpRedirectCode: Optional[HttpRedirectCode] - Protocol: Optional[Protocol] - ReplaceKeyPrefixWith: Optional[ReplaceKeyPrefixWith] - ReplaceKeyWith: Optional[ReplaceKeyWith] + HostName: HostName | None + HttpRedirectCode: HttpRedirectCode | None + Protocol: Protocol | None + ReplaceKeyPrefixWith: ReplaceKeyPrefixWith | None + ReplaceKeyWith: ReplaceKeyWith | None class RoutingRule(TypedDict, total=False): - Condition: Optional[Condition] + Condition: Condition | None Redirect: Redirect -RoutingRules = List[RoutingRule] +RoutingRules = list[RoutingRule] class IndexDocument(TypedDict, total=False): @@ -2284,160 +2431,160 @@ class IndexDocument(TypedDict, total=False): class RedirectAllRequestsTo(TypedDict, total=False): HostName: HostName - Protocol: Optional[Protocol] + Protocol: Protocol | None class GetBucketWebsiteOutput(TypedDict, total=False): - RedirectAllRequestsTo: Optional[RedirectAllRequestsTo] - IndexDocument: Optional[IndexDocument] - ErrorDocument: Optional[ErrorDocument] - RoutingRules: Optional[RoutingRules] + RedirectAllRequestsTo: RedirectAllRequestsTo | None + IndexDocument: IndexDocument | None + ErrorDocument: ErrorDocument | None + RoutingRules: RoutingRules | None class GetBucketWebsiteRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetObjectAclOutput(TypedDict, total=False): - Owner: Optional[Owner] - Grants: Optional[Grants] - RequestCharged: Optional[RequestCharged] + Owner: Owner | None + Grants: Grants | None + RequestCharged: RequestCharged | None class GetObjectAclRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] + VersionId: ObjectVersionId | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None class ObjectPart(TypedDict, total=False): - PartNumber: Optional[PartNumber] - Size: Optional[Size] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] + PartNumber: PartNumber | None + Size: Size | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None -PartsList = List[ObjectPart] +PartsList = list[ObjectPart] class GetObjectAttributesParts(TypedDict, total=False): - TotalPartsCount: Optional[PartsCount] - PartNumberMarker: Optional[PartNumberMarker] - NextPartNumberMarker: Optional[NextPartNumberMarker] - MaxParts: Optional[MaxParts] - IsTruncated: Optional[IsTruncated] - Parts: Optional[PartsList] + TotalPartsCount: PartsCount | None + PartNumberMarker: PartNumberMarker | None + NextPartNumberMarker: NextPartNumberMarker | None + MaxParts: MaxParts | None + IsTruncated: IsTruncated | None + Parts: PartsList | None class GetObjectAttributesOutput(TypedDict, total=False): - DeleteMarker: Optional[DeleteMarker] - LastModified: Optional[LastModified] - VersionId: Optional[ObjectVersionId] - RequestCharged: Optional[RequestCharged] - ETag: Optional[ETag] - Checksum: Optional[Checksum] - ObjectParts: Optional[GetObjectAttributesParts] - StorageClass: Optional[StorageClass] - ObjectSize: Optional[ObjectSize] + DeleteMarker: DeleteMarker | None + LastModified: LastModified | None + VersionId: ObjectVersionId | None + RequestCharged: RequestCharged | None + ETag: ETag | None + Checksum: Checksum | None + ObjectParts: GetObjectAttributesParts | None + StorageClass: StorageClass | None + ObjectSize: ObjectSize | None -ObjectAttributesList = List[ObjectAttributes] +ObjectAttributesList = list[ObjectAttributes] class GetObjectAttributesRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - MaxParts: Optional[MaxParts] - PartNumberMarker: Optional[PartNumberMarker] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] + VersionId: ObjectVersionId | None + MaxParts: MaxParts | None + PartNumberMarker: PartNumberMarker | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None ObjectAttributes: ObjectAttributesList class ObjectLockLegalHold(TypedDict, total=False): - Status: Optional[ObjectLockLegalHoldStatus] + Status: ObjectLockLegalHoldStatus | None class GetObjectLegalHoldOutput(TypedDict, total=False): - LegalHold: Optional[ObjectLockLegalHold] + LegalHold: ObjectLockLegalHold | None class GetObjectLegalHoldRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] + VersionId: ObjectVersionId | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None class ObjectLockRule(TypedDict, total=False): - DefaultRetention: Optional[DefaultRetention] + DefaultRetention: DefaultRetention | None class ObjectLockConfiguration(TypedDict, total=False): - ObjectLockEnabled: Optional[ObjectLockEnabled] - Rule: Optional[ObjectLockRule] + ObjectLockEnabled: ObjectLockEnabled | None + Rule: ObjectLockRule | None class GetObjectLockConfigurationOutput(TypedDict, total=False): - ObjectLockConfiguration: Optional[ObjectLockConfiguration] + ObjectLockConfiguration: ObjectLockConfiguration | None class GetObjectLockConfigurationRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GetObjectOutput(TypedDict, total=False): - Body: Optional[Union[Body, IO[Body], Iterable[Body]]] - DeleteMarker: Optional[DeleteMarker] - AcceptRanges: Optional[AcceptRanges] - Expiration: Optional[Expiration] - Restore: Optional[Restore] - LastModified: Optional[LastModified] - ContentLength: Optional[ContentLength] - ETag: Optional[ETag] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - ChecksumType: Optional[ChecksumType] - MissingMeta: Optional[MissingMeta] - VersionId: Optional[ObjectVersionId] - CacheControl: Optional[CacheControl] - ContentDisposition: Optional[ContentDisposition] - ContentEncoding: Optional[ContentEncoding] - ContentLanguage: Optional[ContentLanguage] - ContentRange: Optional[ContentRange] - ContentType: Optional[ContentType] - Expires: Optional[Expires] - WebsiteRedirectLocation: Optional[WebsiteRedirectLocation] - ServerSideEncryption: Optional[ServerSideEncryption] - Metadata: Optional[Metadata] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - BucketKeyEnabled: Optional[BucketKeyEnabled] - StorageClass: Optional[StorageClass] - RequestCharged: Optional[RequestCharged] - ReplicationStatus: Optional[ReplicationStatus] - PartsCount: Optional[PartsCount] - TagCount: Optional[TagCount] - ObjectLockMode: Optional[ObjectLockMode] - ObjectLockRetainUntilDate: Optional[ObjectLockRetainUntilDate] - ObjectLockLegalHoldStatus: Optional[ObjectLockLegalHoldStatus] - StatusCode: Optional[GetObjectResponseStatusCode] + Body: Body | IO[Body] | Iterable[Body] | None + DeleteMarker: DeleteMarker | None + AcceptRanges: AcceptRanges | None + Expiration: Expiration | None + Restore: Restore | None + LastModified: LastModified | None + ContentLength: ContentLength | None + ETag: ETag | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + ChecksumType: ChecksumType | None + MissingMeta: MissingMeta | None + VersionId: ObjectVersionId | None + CacheControl: CacheControl | None + ContentDisposition: ContentDisposition | None + ContentEncoding: ContentEncoding | None + ContentLanguage: ContentLanguage | None + ContentRange: ContentRange | None + ContentType: ContentType | None + Expires: Expires | None + WebsiteRedirectLocation: WebsiteRedirectLocation | None + ServerSideEncryption: ServerSideEncryption | None + Metadata: Metadata | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + BucketKeyEnabled: BucketKeyEnabled | None + StorageClass: StorageClass | None + RequestCharged: RequestCharged | None + ReplicationStatus: ReplicationStatus | None + PartsCount: PartsCount | None + TagCount: TagCount | None + ObjectLockMode: ObjectLockMode | None + ObjectLockRetainUntilDate: ObjectLockRetainUntilDate | None + ObjectLockLegalHoldStatus: ObjectLockLegalHoldStatus | None + StatusCode: GetObjectResponseStatusCode | None ResponseExpires = datetime @@ -2447,84 +2594,84 @@ class GetObjectOutput(TypedDict, total=False): class GetObjectRequest(ServiceRequest): Bucket: BucketName - IfMatch: Optional[IfMatch] - IfModifiedSince: Optional[IfModifiedSince] - IfNoneMatch: Optional[IfNoneMatch] - IfUnmodifiedSince: Optional[IfUnmodifiedSince] + IfMatch: IfMatch | None + IfModifiedSince: IfModifiedSince | None + IfNoneMatch: IfNoneMatch | None + IfUnmodifiedSince: IfUnmodifiedSince | None Key: ObjectKey - Range: Optional[Range] - ResponseCacheControl: Optional[ResponseCacheControl] - ResponseContentDisposition: Optional[ResponseContentDisposition] - ResponseContentEncoding: Optional[ResponseContentEncoding] - ResponseContentLanguage: Optional[ResponseContentLanguage] - ResponseContentType: Optional[ResponseContentType] - ResponseExpires: Optional[ResponseExpires] - VersionId: Optional[ObjectVersionId] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - RequestPayer: Optional[RequestPayer] - PartNumber: Optional[PartNumber] - ExpectedBucketOwner: Optional[AccountId] - ChecksumMode: Optional[ChecksumMode] + Range: Range | None + ResponseCacheControl: ResponseCacheControl | None + ResponseContentDisposition: ResponseContentDisposition | None + ResponseContentEncoding: ResponseContentEncoding | None + ResponseContentLanguage: ResponseContentLanguage | None + ResponseContentType: ResponseContentType | None + ResponseExpires: ResponseExpires | None + VersionId: ObjectVersionId | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + RequestPayer: RequestPayer | None + PartNumber: PartNumber | None + ExpectedBucketOwner: AccountId | None + ChecksumMode: ChecksumMode | None class ObjectLockRetention(TypedDict, total=False): - Mode: Optional[ObjectLockRetentionMode] - RetainUntilDate: Optional[Date] + Mode: ObjectLockRetentionMode | None + RetainUntilDate: Date | None class GetObjectRetentionOutput(TypedDict, total=False): - Retention: Optional[ObjectLockRetention] + Retention: ObjectLockRetention | None class GetObjectRetentionRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] + VersionId: ObjectVersionId | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None class GetObjectTaggingOutput(TypedDict, total=False): - VersionId: Optional[ObjectVersionId] + VersionId: ObjectVersionId | None TagSet: TagSet class GetObjectTaggingRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - ExpectedBucketOwner: Optional[AccountId] - RequestPayer: Optional[RequestPayer] + VersionId: ObjectVersionId | None + ExpectedBucketOwner: AccountId | None + RequestPayer: RequestPayer | None class GetObjectTorrentOutput(TypedDict, total=False): - Body: Optional[Union[Body, IO[Body], Iterable[Body]]] - RequestCharged: Optional[RequestCharged] + Body: Body | IO[Body] | Iterable[Body] | None + RequestCharged: RequestCharged | None class GetObjectTorrentRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None class PublicAccessBlockConfiguration(TypedDict, total=False): - BlockPublicAcls: Optional[Setting] - IgnorePublicAcls: Optional[Setting] - BlockPublicPolicy: Optional[Setting] - RestrictPublicBuckets: Optional[Setting] + BlockPublicAcls: Setting | None + IgnorePublicAcls: Setting | None + BlockPublicPolicy: Setting | None + RestrictPublicBuckets: Setting | None class GetPublicAccessBlockOutput(TypedDict, total=False): - PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] + PublicAccessBlockConfiguration: PublicAccessBlockConfiguration | None class GetPublicAccessBlockRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class GlacierJobParameters(TypedDict, total=False): @@ -2532,87 +2679,90 @@ class GlacierJobParameters(TypedDict, total=False): class HeadBucketOutput(TypedDict, total=False): - BucketRegion: Optional[BucketRegion] - BucketContentType: Optional[BucketContentType] + BucketArn: S3RegionalOrS3ExpressBucketArnString | None + BucketLocationType: LocationType | None + BucketLocationName: BucketLocationName | None + BucketRegion: Region | None + AccessPointAlias: AccessPointAlias | None class HeadBucketRequest(ServiceRequest): Bucket: BucketName - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class HeadObjectOutput(TypedDict, total=False): - DeleteMarker: Optional[DeleteMarker] - AcceptRanges: Optional[AcceptRanges] - Expiration: Optional[Expiration] - Restore: Optional[Restore] - ArchiveStatus: Optional[ArchiveStatus] - LastModified: Optional[LastModified] - ContentLength: Optional[ContentLength] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - ChecksumType: Optional[ChecksumType] - ETag: Optional[ETag] - MissingMeta: Optional[MissingMeta] - VersionId: Optional[ObjectVersionId] - CacheControl: Optional[CacheControl] - ContentDisposition: Optional[ContentDisposition] - ContentEncoding: Optional[ContentEncoding] - ContentLanguage: Optional[ContentLanguage] - ContentType: Optional[ContentType] - ContentRange: Optional[ContentRange] - Expires: Optional[Expires] - WebsiteRedirectLocation: Optional[WebsiteRedirectLocation] - ServerSideEncryption: Optional[ServerSideEncryption] - Metadata: Optional[Metadata] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - BucketKeyEnabled: Optional[BucketKeyEnabled] - StorageClass: Optional[StorageClass] - RequestCharged: Optional[RequestCharged] - ReplicationStatus: Optional[ReplicationStatus] - PartsCount: Optional[PartsCount] - TagCount: Optional[TagCount] - ObjectLockMode: Optional[ObjectLockMode] - ObjectLockRetainUntilDate: Optional[ObjectLockRetainUntilDate] - ObjectLockLegalHoldStatus: Optional[ObjectLockLegalHoldStatus] - StatusCode: Optional[GetObjectResponseStatusCode] + DeleteMarker: DeleteMarker | None + AcceptRanges: AcceptRanges | None + Expiration: Expiration | None + Restore: Restore | None + ArchiveStatus: ArchiveStatus | None + LastModified: LastModified | None + ContentLength: ContentLength | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + ChecksumType: ChecksumType | None + ETag: ETag | None + MissingMeta: MissingMeta | None + VersionId: ObjectVersionId | None + CacheControl: CacheControl | None + ContentDisposition: ContentDisposition | None + ContentEncoding: ContentEncoding | None + ContentLanguage: ContentLanguage | None + ContentType: ContentType | None + ContentRange: ContentRange | None + Expires: Expires | None + WebsiteRedirectLocation: WebsiteRedirectLocation | None + ServerSideEncryption: ServerSideEncryption | None + Metadata: Metadata | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + BucketKeyEnabled: BucketKeyEnabled | None + StorageClass: StorageClass | None + RequestCharged: RequestCharged | None + ReplicationStatus: ReplicationStatus | None + PartsCount: PartsCount | None + TagCount: TagCount | None + ObjectLockMode: ObjectLockMode | None + ObjectLockRetainUntilDate: ObjectLockRetainUntilDate | None + ObjectLockLegalHoldStatus: ObjectLockLegalHoldStatus | None + StatusCode: GetObjectResponseStatusCode | None class HeadObjectRequest(ServiceRequest): Bucket: BucketName - IfMatch: Optional[IfMatch] - IfModifiedSince: Optional[IfModifiedSince] - IfNoneMatch: Optional[IfNoneMatch] - IfUnmodifiedSince: Optional[IfUnmodifiedSince] + IfMatch: IfMatch | None + IfModifiedSince: IfModifiedSince | None + IfNoneMatch: IfNoneMatch | None + IfUnmodifiedSince: IfUnmodifiedSince | None Key: ObjectKey - Range: Optional[Range] - ResponseCacheControl: Optional[ResponseCacheControl] - ResponseContentDisposition: Optional[ResponseContentDisposition] - ResponseContentEncoding: Optional[ResponseContentEncoding] - ResponseContentLanguage: Optional[ResponseContentLanguage] - ResponseContentType: Optional[ResponseContentType] - ResponseExpires: Optional[ResponseExpires] - VersionId: Optional[ObjectVersionId] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - RequestPayer: Optional[RequestPayer] - PartNumber: Optional[PartNumber] - ExpectedBucketOwner: Optional[AccountId] - ChecksumMode: Optional[ChecksumMode] + Range: Range | None + ResponseCacheControl: ResponseCacheControl | None + ResponseContentDisposition: ResponseContentDisposition | None + ResponseContentEncoding: ResponseContentEncoding | None + ResponseContentLanguage: ResponseContentLanguage | None + ResponseContentType: ResponseContentType | None + ResponseExpires: ResponseExpires | None + VersionId: ObjectVersionId | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + RequestPayer: RequestPayer | None + PartNumber: PartNumber | None + ExpectedBucketOwner: AccountId | None + ChecksumMode: ChecksumMode | None Initiated = datetime class Initiator(TypedDict, total=False): - ID: Optional[ID] - DisplayName: Optional[DisplayName] + ID: ID | None + DisplayName: DisplayName | None class ParquetInput(TypedDict, total=False): @@ -2620,40 +2770,49 @@ class ParquetInput(TypedDict, total=False): class JSONInput(TypedDict, total=False): - Type: Optional[JSONType] + Type: JSONType | None class InputSerialization(TypedDict, total=False): - CSV: Optional[CSVInput] - CompressionType: Optional[CompressionType] - JSON: Optional[JSONInput] - Parquet: Optional[ParquetInput] + CSV: CSVInput | None + CompressionType: CompressionType | None + JSON: JSONInput | None + Parquet: ParquetInput | None + + +IntelligentTieringConfigurationList = list[IntelligentTieringConfiguration] +InventoryConfigurationList = list[InventoryConfiguration] -IntelligentTieringConfigurationList = List[IntelligentTieringConfiguration] -InventoryConfigurationList = List[InventoryConfiguration] +class InventoryTableConfigurationUpdates(TypedDict, total=False): + ConfigurationState: InventoryConfigurationState + EncryptionConfiguration: MetadataTableEncryptionConfiguration | None class JSONOutput(TypedDict, total=False): - RecordDelimiter: Optional[RecordDelimiter] + RecordDelimiter: RecordDelimiter | None + + +class JournalTableConfigurationUpdates(TypedDict, total=False): + RecordExpiration: RecordExpiration class S3KeyFilter(TypedDict, total=False): - FilterRules: Optional[FilterRuleList] + FilterRules: FilterRuleList | None class NotificationConfigurationFilter(TypedDict, total=False): - Key: Optional[S3KeyFilter] + Key: S3KeyFilter | None class LambdaFunctionConfiguration(TypedDict, total=False): - Id: Optional[NotificationId] + Id: NotificationId | None LambdaFunctionArn: LambdaFunctionArn Events: EventList - Filter: Optional[NotificationConfigurationFilter] + Filter: NotificationConfigurationFilter | None -LambdaFunctionConfigurationList = List[LambdaFunctionConfiguration] +LambdaFunctionConfigurationList = list[LambdaFunctionConfiguration] class LifecycleConfiguration(TypedDict, total=False): @@ -2661,357 +2820,366 @@ class LifecycleConfiguration(TypedDict, total=False): class ListBucketAnalyticsConfigurationsOutput(TypedDict, total=False): - IsTruncated: Optional[IsTruncated] - ContinuationToken: Optional[Token] - NextContinuationToken: Optional[NextToken] - AnalyticsConfigurationList: Optional[AnalyticsConfigurationList] + IsTruncated: IsTruncated | None + ContinuationToken: Token | None + NextContinuationToken: NextToken | None + AnalyticsConfigurationList: AnalyticsConfigurationList | None class ListBucketAnalyticsConfigurationsRequest(ServiceRequest): Bucket: BucketName - ContinuationToken: Optional[Token] - ExpectedBucketOwner: Optional[AccountId] + ContinuationToken: Token | None + ExpectedBucketOwner: AccountId | None class ListBucketIntelligentTieringConfigurationsOutput(TypedDict, total=False): - IsTruncated: Optional[IsTruncated] - ContinuationToken: Optional[Token] - NextContinuationToken: Optional[NextToken] - IntelligentTieringConfigurationList: Optional[IntelligentTieringConfigurationList] + IsTruncated: IsTruncated | None + ContinuationToken: Token | None + NextContinuationToken: NextToken | None + IntelligentTieringConfigurationList: IntelligentTieringConfigurationList | None class ListBucketIntelligentTieringConfigurationsRequest(ServiceRequest): Bucket: BucketName - ContinuationToken: Optional[Token] - ExpectedBucketOwner: Optional[AccountId] + ContinuationToken: Token | None + ExpectedBucketOwner: AccountId | None class ListBucketInventoryConfigurationsOutput(TypedDict, total=False): - ContinuationToken: Optional[Token] - InventoryConfigurationList: Optional[InventoryConfigurationList] - IsTruncated: Optional[IsTruncated] - NextContinuationToken: Optional[NextToken] + ContinuationToken: Token | None + InventoryConfigurationList: InventoryConfigurationList | None + IsTruncated: IsTruncated | None + NextContinuationToken: NextToken | None class ListBucketInventoryConfigurationsRequest(ServiceRequest): Bucket: BucketName - ContinuationToken: Optional[Token] - ExpectedBucketOwner: Optional[AccountId] + ContinuationToken: Token | None + ExpectedBucketOwner: AccountId | None -MetricsConfigurationList = List[MetricsConfiguration] +MetricsConfigurationList = list[MetricsConfiguration] class ListBucketMetricsConfigurationsOutput(TypedDict, total=False): - IsTruncated: Optional[IsTruncated] - ContinuationToken: Optional[Token] - NextContinuationToken: Optional[NextToken] - MetricsConfigurationList: Optional[MetricsConfigurationList] + IsTruncated: IsTruncated | None + ContinuationToken: Token | None + NextContinuationToken: NextToken | None + MetricsConfigurationList: MetricsConfigurationList | None class ListBucketMetricsConfigurationsRequest(ServiceRequest): Bucket: BucketName - ContinuationToken: Optional[Token] - ExpectedBucketOwner: Optional[AccountId] + ContinuationToken: Token | None + ExpectedBucketOwner: AccountId | None class ListBucketsOutput(TypedDict, total=False): - Owner: Optional[Owner] - ContinuationToken: Optional[NextToken] - Prefix: Optional[Prefix] - Buckets: Optional[Buckets] + Owner: Owner | None + ContinuationToken: NextToken | None + Prefix: Prefix | None + Buckets: Buckets | None class ListBucketsRequest(ServiceRequest): - MaxBuckets: Optional[MaxBuckets] - ContinuationToken: Optional[Token] - Prefix: Optional[Prefix] - BucketRegion: Optional[BucketRegion] + MaxBuckets: MaxBuckets | None + ContinuationToken: Token | None + Prefix: Prefix | None + BucketRegion: BucketRegion | None class ListDirectoryBucketsOutput(TypedDict, total=False): - Buckets: Optional[Buckets] - ContinuationToken: Optional[DirectoryBucketToken] + Buckets: Buckets | None + ContinuationToken: DirectoryBucketToken | None class ListDirectoryBucketsRequest(ServiceRequest): - ContinuationToken: Optional[DirectoryBucketToken] - MaxDirectoryBuckets: Optional[MaxDirectoryBuckets] + ContinuationToken: DirectoryBucketToken | None + MaxDirectoryBuckets: MaxDirectoryBuckets | None class MultipartUpload(TypedDict, total=False): - UploadId: Optional[MultipartUploadId] - Key: Optional[ObjectKey] - Initiated: Optional[Initiated] - StorageClass: Optional[StorageClass] - Owner: Optional[Owner] - Initiator: Optional[Initiator] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ChecksumType: Optional[ChecksumType] + UploadId: MultipartUploadId | None + Key: ObjectKey | None + Initiated: Initiated | None + StorageClass: StorageClass | None + Owner: Owner | None + Initiator: Initiator | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ChecksumType: ChecksumType | None -MultipartUploadList = List[MultipartUpload] +MultipartUploadList = list[MultipartUpload] class ListMultipartUploadsOutput(TypedDict, total=False): - Bucket: Optional[BucketName] - KeyMarker: Optional[KeyMarker] - UploadIdMarker: Optional[UploadIdMarker] - NextKeyMarker: Optional[NextKeyMarker] - Prefix: Optional[Prefix] - Delimiter: Optional[Delimiter] - NextUploadIdMarker: Optional[NextUploadIdMarker] - MaxUploads: Optional[MaxUploads] - IsTruncated: Optional[IsTruncated] - Uploads: Optional[MultipartUploadList] - CommonPrefixes: Optional[CommonPrefixList] - EncodingType: Optional[EncodingType] - RequestCharged: Optional[RequestCharged] + Bucket: BucketName | None + KeyMarker: KeyMarker | None + UploadIdMarker: UploadIdMarker | None + NextKeyMarker: NextKeyMarker | None + Prefix: Prefix | None + Delimiter: Delimiter | None + NextUploadIdMarker: NextUploadIdMarker | None + MaxUploads: MaxUploads | None + IsTruncated: IsTruncated | None + Uploads: MultipartUploadList | None + CommonPrefixes: CommonPrefixList | None + EncodingType: EncodingType | None + RequestCharged: RequestCharged | None class ListMultipartUploadsRequest(ServiceRequest): Bucket: BucketName - Delimiter: Optional[Delimiter] - EncodingType: Optional[EncodingType] - KeyMarker: Optional[KeyMarker] - MaxUploads: Optional[MaxUploads] - Prefix: Optional[Prefix] - UploadIdMarker: Optional[UploadIdMarker] - ExpectedBucketOwner: Optional[AccountId] - RequestPayer: Optional[RequestPayer] + Delimiter: Delimiter | None + EncodingType: EncodingType | None + KeyMarker: KeyMarker | None + MaxUploads: MaxUploads | None + Prefix: Prefix | None + UploadIdMarker: UploadIdMarker | None + ExpectedBucketOwner: AccountId | None + RequestPayer: RequestPayer | None RestoreExpiryDate = datetime class RestoreStatus(TypedDict, total=False): - IsRestoreInProgress: Optional[IsRestoreInProgress] - RestoreExpiryDate: Optional[RestoreExpiryDate] + IsRestoreInProgress: IsRestoreInProgress | None + RestoreExpiryDate: RestoreExpiryDate | None class ObjectVersion(TypedDict, total=False): - ETag: Optional[ETag] - ChecksumAlgorithm: Optional[ChecksumAlgorithmList] - ChecksumType: Optional[ChecksumType] - Size: Optional[Size] - StorageClass: Optional[ObjectVersionStorageClass] - Key: Optional[ObjectKey] - VersionId: Optional[ObjectVersionId] - IsLatest: Optional[IsLatest] - LastModified: Optional[LastModified] - Owner: Optional[Owner] - RestoreStatus: Optional[RestoreStatus] + ETag: ETag | None + ChecksumAlgorithm: ChecksumAlgorithmList | None + ChecksumType: ChecksumType | None + Size: Size | None + StorageClass: ObjectVersionStorageClass | None + Key: ObjectKey | None + VersionId: ObjectVersionId | None + IsLatest: IsLatest | None + LastModified: LastModified | None + Owner: Owner | None + RestoreStatus: RestoreStatus | None -ObjectVersionList = List[ObjectVersion] +ObjectVersionList = list[ObjectVersion] class ListObjectVersionsOutput(TypedDict, total=False): - IsTruncated: Optional[IsTruncated] - KeyMarker: Optional[KeyMarker] - VersionIdMarker: Optional[VersionIdMarker] - NextKeyMarker: Optional[NextKeyMarker] - NextVersionIdMarker: Optional[NextVersionIdMarker] - DeleteMarkers: Optional[DeleteMarkers] - Name: Optional[BucketName] - Prefix: Optional[Prefix] - Delimiter: Optional[Delimiter] - MaxKeys: Optional[MaxKeys] - CommonPrefixes: Optional[CommonPrefixList] - EncodingType: Optional[EncodingType] - RequestCharged: Optional[RequestCharged] - Versions: Optional[ObjectVersionList] + IsTruncated: IsTruncated | None + KeyMarker: KeyMarker | None + VersionIdMarker: VersionIdMarker | None + NextKeyMarker: NextKeyMarker | None + NextVersionIdMarker: NextVersionIdMarker | None + DeleteMarkers: DeleteMarkers | None + Name: BucketName | None + Prefix: Prefix | None + Delimiter: Delimiter | None + MaxKeys: MaxKeys | None + CommonPrefixes: CommonPrefixList | None + EncodingType: EncodingType | None + RequestCharged: RequestCharged | None + Versions: ObjectVersionList | None -OptionalObjectAttributesList = List[OptionalObjectAttributes] +OptionalObjectAttributesList = list[OptionalObjectAttributes] class ListObjectVersionsRequest(ServiceRequest): Bucket: BucketName - Delimiter: Optional[Delimiter] - EncodingType: Optional[EncodingType] - KeyMarker: Optional[KeyMarker] - MaxKeys: Optional[MaxKeys] - Prefix: Optional[Prefix] - VersionIdMarker: Optional[VersionIdMarker] - ExpectedBucketOwner: Optional[AccountId] - RequestPayer: Optional[RequestPayer] - OptionalObjectAttributes: Optional[OptionalObjectAttributesList] + Delimiter: Delimiter | None + EncodingType: EncodingType | None + KeyMarker: KeyMarker | None + MaxKeys: MaxKeys | None + Prefix: Prefix | None + VersionIdMarker: VersionIdMarker | None + ExpectedBucketOwner: AccountId | None + RequestPayer: RequestPayer | None + OptionalObjectAttributes: OptionalObjectAttributesList | None class Object(TypedDict, total=False): - Key: Optional[ObjectKey] - LastModified: Optional[LastModified] - ETag: Optional[ETag] - ChecksumAlgorithm: Optional[ChecksumAlgorithmList] - ChecksumType: Optional[ChecksumType] - Size: Optional[Size] - StorageClass: Optional[ObjectStorageClass] - Owner: Optional[Owner] - RestoreStatus: Optional[RestoreStatus] + Key: ObjectKey | None + LastModified: LastModified | None + ETag: ETag | None + ChecksumAlgorithm: ChecksumAlgorithmList | None + ChecksumType: ChecksumType | None + Size: Size | None + StorageClass: ObjectStorageClass | None + Owner: Owner | None + RestoreStatus: RestoreStatus | None -ObjectList = List[Object] +ObjectList = list[Object] class ListObjectsOutput(TypedDict, total=False): - IsTruncated: Optional[IsTruncated] - Marker: Optional[Marker] - NextMarker: Optional[NextMarker] - Name: Optional[BucketName] - Prefix: Optional[Prefix] - Delimiter: Optional[Delimiter] - MaxKeys: Optional[MaxKeys] - CommonPrefixes: Optional[CommonPrefixList] - EncodingType: Optional[EncodingType] - RequestCharged: Optional[RequestCharged] - BucketRegion: Optional[BucketRegion] - Contents: Optional[ObjectList] + IsTruncated: IsTruncated | None + Marker: Marker | None + NextMarker: NextMarker | None + Name: BucketName | None + Prefix: Prefix | None + Delimiter: Delimiter | None + MaxKeys: MaxKeys | None + CommonPrefixes: CommonPrefixList | None + EncodingType: EncodingType | None + RequestCharged: RequestCharged | None + BucketRegion: BucketRegion | None + Contents: ObjectList | None class ListObjectsRequest(ServiceRequest): Bucket: BucketName - Delimiter: Optional[Delimiter] - EncodingType: Optional[EncodingType] - Marker: Optional[Marker] - MaxKeys: Optional[MaxKeys] - Prefix: Optional[Prefix] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] - OptionalObjectAttributes: Optional[OptionalObjectAttributesList] + Delimiter: Delimiter | None + EncodingType: EncodingType | None + Marker: Marker | None + MaxKeys: MaxKeys | None + Prefix: Prefix | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None + OptionalObjectAttributes: OptionalObjectAttributesList | None class ListObjectsV2Output(TypedDict, total=False): - IsTruncated: Optional[IsTruncated] - Name: Optional[BucketName] - Prefix: Optional[Prefix] - Delimiter: Optional[Delimiter] - MaxKeys: Optional[MaxKeys] - CommonPrefixes: Optional[CommonPrefixList] - EncodingType: Optional[EncodingType] - KeyCount: Optional[KeyCount] - ContinuationToken: Optional[Token] - NextContinuationToken: Optional[NextToken] - StartAfter: Optional[StartAfter] - RequestCharged: Optional[RequestCharged] - BucketRegion: Optional[BucketRegion] - Contents: Optional[ObjectList] + IsTruncated: IsTruncated | None + Name: BucketName | None + Prefix: Prefix | None + Delimiter: Delimiter | None + MaxKeys: MaxKeys | None + CommonPrefixes: CommonPrefixList | None + EncodingType: EncodingType | None + KeyCount: KeyCount | None + ContinuationToken: Token | None + NextContinuationToken: NextToken | None + StartAfter: StartAfter | None + RequestCharged: RequestCharged | None + BucketRegion: BucketRegion | None + Contents: ObjectList | None class ListObjectsV2Request(ServiceRequest): Bucket: BucketName - Delimiter: Optional[Delimiter] - EncodingType: Optional[EncodingType] - MaxKeys: Optional[MaxKeys] - Prefix: Optional[Prefix] - ContinuationToken: Optional[Token] - FetchOwner: Optional[FetchOwner] - StartAfter: Optional[StartAfter] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] - OptionalObjectAttributes: Optional[OptionalObjectAttributesList] + Delimiter: Delimiter | None + EncodingType: EncodingType | None + MaxKeys: MaxKeys | None + Prefix: Prefix | None + ContinuationToken: Token | None + FetchOwner: FetchOwner | None + StartAfter: StartAfter | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None + OptionalObjectAttributes: OptionalObjectAttributesList | None class Part(TypedDict, total=False): - PartNumber: Optional[PartNumber] - LastModified: Optional[LastModified] - ETag: Optional[ETag] - Size: Optional[Size] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] + PartNumber: PartNumber | None + LastModified: LastModified | None + ETag: ETag | None + Size: Size | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None -Parts = List[Part] +Parts = list[Part] class ListPartsOutput(TypedDict, total=False): - AbortDate: Optional[AbortDate] - AbortRuleId: Optional[AbortRuleId] - Bucket: Optional[BucketName] - Key: Optional[ObjectKey] - UploadId: Optional[MultipartUploadId] - PartNumberMarker: Optional[PartNumberMarker] - NextPartNumberMarker: Optional[NextPartNumberMarker] - MaxParts: Optional[MaxParts] - IsTruncated: Optional[IsTruncated] - Parts: Optional[Parts] - Initiator: Optional[Initiator] - Owner: Optional[Owner] - StorageClass: Optional[StorageClass] - RequestCharged: Optional[RequestCharged] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ChecksumType: Optional[ChecksumType] + AbortDate: AbortDate | None + AbortRuleId: AbortRuleId | None + Bucket: BucketName | None + Key: ObjectKey | None + UploadId: MultipartUploadId | None + PartNumberMarker: PartNumberMarker | None + NextPartNumberMarker: NextPartNumberMarker | None + MaxParts: MaxParts | None + IsTruncated: IsTruncated | None + Parts: Parts | None + Initiator: Initiator | None + Owner: Owner | None + StorageClass: StorageClass | None + RequestCharged: RequestCharged | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ChecksumType: ChecksumType | None class ListPartsRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - MaxParts: Optional[MaxParts] - PartNumberMarker: Optional[PartNumberMarker] + MaxParts: MaxParts | None + PartNumberMarker: PartNumberMarker | None UploadId: MultipartUploadId - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None class MetadataEntry(TypedDict, total=False): - Name: Optional[MetadataKey] - Value: Optional[MetadataValue] + Name: MetadataKey | None + Value: MetadataValue | None class QueueConfiguration(TypedDict, total=False): - Id: Optional[NotificationId] + Id: NotificationId | None QueueArn: QueueArn Events: EventList - Filter: Optional[NotificationConfigurationFilter] + Filter: NotificationConfigurationFilter | None -QueueConfigurationList = List[QueueConfiguration] +QueueConfigurationList = list[QueueConfiguration] class TopicConfiguration(TypedDict, total=False): - Id: Optional[NotificationId] + Id: NotificationId | None TopicArn: TopicArn Events: EventList - Filter: Optional[NotificationConfigurationFilter] + Filter: NotificationConfigurationFilter | None -TopicConfigurationList = List[TopicConfiguration] +TopicConfigurationList = list[TopicConfiguration] class NotificationConfiguration(TypedDict, total=False): - TopicConfigurations: Optional[TopicConfigurationList] - QueueConfigurations: Optional[QueueConfigurationList] - LambdaFunctionConfigurations: Optional[LambdaFunctionConfigurationList] - EventBridgeConfiguration: Optional[EventBridgeConfiguration] + TopicConfigurations: TopicConfigurationList | None + QueueConfigurations: QueueConfigurationList | None + LambdaFunctionConfigurations: LambdaFunctionConfigurationList | None + EventBridgeConfiguration: EventBridgeConfiguration | None class QueueConfigurationDeprecated(TypedDict, total=False): - Id: Optional[NotificationId] - Event: Optional[Event] - Events: Optional[EventList] - Queue: Optional[QueueArn] + Id: NotificationId | None + Event: Event | None + Events: EventList | None + Queue: QueueArn | None class TopicConfigurationDeprecated(TypedDict, total=False): - Id: Optional[NotificationId] - Events: Optional[EventList] - Event: Optional[Event] - Topic: Optional[TopicArn] + Id: NotificationId | None + Events: EventList | None + Event: Event | None + Topic: TopicArn | None class NotificationConfigurationDeprecated(TypedDict, total=False): - TopicConfiguration: Optional[TopicConfigurationDeprecated] - QueueConfiguration: Optional[QueueConfigurationDeprecated] - CloudFunctionConfiguration: Optional[CloudFunctionConfiguration] + TopicConfiguration: TopicConfigurationDeprecated | None + QueueConfiguration: QueueConfigurationDeprecated | None + CloudFunctionConfiguration: CloudFunctionConfiguration | None + + +class SSEKMSEncryption(TypedDict, total=False): + KMSKeyArn: NonEmptyKmsKeyArnString + BucketKeyEnabled: BucketKeyEnabled | None + +class ObjectEncryption(TypedDict, total=False): + SSEKMS: SSEKMSEncryption | None -UserMetadata = List[MetadataEntry] + +UserMetadata = list[MetadataEntry] class Tagging(TypedDict, total=False): @@ -3021,81 +3189,89 @@ class Tagging(TypedDict, total=False): class S3Location(TypedDict, total=False): BucketName: BucketName Prefix: LocationPrefix - Encryption: Optional[Encryption] - CannedACL: Optional[ObjectCannedACL] - AccessControlList: Optional[Grants] - Tagging: Optional[Tagging] - UserMetadata: Optional[UserMetadata] - StorageClass: Optional[StorageClass] + Encryption: Encryption | None + CannedACL: ObjectCannedACL | None + AccessControlList: Grants | None + Tagging: Tagging | None + UserMetadata: UserMetadata | None + StorageClass: StorageClass | None class OutputLocation(TypedDict, total=False): - S3: Optional[S3Location] + S3: S3Location | None class OutputSerialization(TypedDict, total=False): - CSV: Optional[CSVOutput] - JSON: Optional[JSONOutput] + CSV: CSVOutput | None + JSON: JSONOutput | None class Progress(TypedDict, total=False): - BytesScanned: Optional[BytesScanned] - BytesProcessed: Optional[BytesProcessed] - BytesReturned: Optional[BytesReturned] + BytesScanned: BytesScanned | None + BytesProcessed: BytesProcessed | None + BytesReturned: BytesReturned | None class ProgressEvent(TypedDict, total=False): - Details: Optional[Progress] + Details: Progress | None + + +class PutBucketAbacRequest(ServiceRequest): + Bucket: BucketName + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ExpectedBucketOwner: AccountId | None + AbacStatus: AbacStatus class PutBucketAccelerateConfigurationRequest(ServiceRequest): Bucket: BucketName AccelerateConfiguration: AccelerateConfiguration - ExpectedBucketOwner: Optional[AccountId] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ExpectedBucketOwner: AccountId | None + ChecksumAlgorithm: ChecksumAlgorithm | None class PutBucketAclRequest(ServiceRequest): - ACL: Optional[BucketCannedACL] - AccessControlPolicy: Optional[AccessControlPolicy] + ACL: BucketCannedACL | None + AccessControlPolicy: AccessControlPolicy | None Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - GrantFullControl: Optional[GrantFullControl] - GrantRead: Optional[GrantRead] - GrantReadACP: Optional[GrantReadACP] - GrantWrite: Optional[GrantWrite] - GrantWriteACP: Optional[GrantWriteACP] - ExpectedBucketOwner: Optional[AccountId] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + GrantFullControl: GrantFullControl | None + GrantRead: GrantRead | None + GrantReadACP: GrantReadACP | None + GrantWrite: GrantWrite | None + GrantWriteACP: GrantWriteACP | None + ExpectedBucketOwner: AccountId | None class PutBucketAnalyticsConfigurationRequest(ServiceRequest): Bucket: BucketName Id: AnalyticsId AnalyticsConfiguration: AnalyticsConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutBucketCorsRequest(ServiceRequest): Bucket: BucketName CORSConfiguration: CORSConfiguration - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ExpectedBucketOwner: Optional[AccountId] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ExpectedBucketOwner: AccountId | None class PutBucketEncryptionRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None ServerSideEncryptionConfiguration: ServerSideEncryptionConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutBucketIntelligentTieringConfigurationRequest(ServiceRequest): Bucket: BucketName Id: IntelligentTieringId - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None IntelligentTieringConfiguration: IntelligentTieringConfiguration @@ -3103,83 +3279,83 @@ class PutBucketInventoryConfigurationRequest(ServiceRequest): Bucket: BucketName Id: InventoryId InventoryConfiguration: InventoryConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutBucketLifecycleConfigurationOutput(TypedDict, total=False): - TransitionDefaultMinimumObjectSize: Optional[TransitionDefaultMinimumObjectSize] + TransitionDefaultMinimumObjectSize: TransitionDefaultMinimumObjectSize | None class PutBucketLifecycleConfigurationRequest(ServiceRequest): Bucket: BucketName - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - LifecycleConfiguration: Optional[BucketLifecycleConfiguration] - ExpectedBucketOwner: Optional[AccountId] - TransitionDefaultMinimumObjectSize: Optional[TransitionDefaultMinimumObjectSize] + ChecksumAlgorithm: ChecksumAlgorithm | None + LifecycleConfiguration: BucketLifecycleConfiguration | None + ExpectedBucketOwner: AccountId | None + TransitionDefaultMinimumObjectSize: TransitionDefaultMinimumObjectSize | None class PutBucketLifecycleRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - LifecycleConfiguration: Optional[LifecycleConfiguration] - ExpectedBucketOwner: Optional[AccountId] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + LifecycleConfiguration: LifecycleConfiguration | None + ExpectedBucketOwner: AccountId | None class PutBucketLoggingRequest(ServiceRequest): Bucket: BucketName BucketLoggingStatus: BucketLoggingStatus - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ExpectedBucketOwner: Optional[AccountId] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ExpectedBucketOwner: AccountId | None class PutBucketMetricsConfigurationRequest(ServiceRequest): Bucket: BucketName Id: MetricsId MetricsConfiguration: MetricsConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutBucketNotificationConfigurationRequest(ServiceRequest): Bucket: BucketName NotificationConfiguration: NotificationConfiguration - ExpectedBucketOwner: Optional[AccountId] - SkipDestinationValidation: Optional[SkipValidation] + ExpectedBucketOwner: AccountId | None + SkipDestinationValidation: SkipValidation | None class PutBucketNotificationRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None NotificationConfiguration: NotificationConfigurationDeprecated - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutBucketOwnershipControlsRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ExpectedBucketOwner: Optional[AccountId] + ContentMD5: ContentMD5 | None + ExpectedBucketOwner: AccountId | None OwnershipControls: OwnershipControls - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ChecksumAlgorithm: ChecksumAlgorithm | None class PutBucketPolicyRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ConfirmRemoveSelfBucketAccess: Optional[ConfirmRemoveSelfBucketAccess] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ConfirmRemoveSelfBucketAccess: ConfirmRemoveSelfBucketAccess | None Policy: Policy - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutBucketReplicationRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None ReplicationConfiguration: ReplicationConfiguration - Token: Optional[ObjectLockToken] - ExpectedBucketOwner: Optional[AccountId] + Token: ObjectLockToken | None + ExpectedBucketOwner: AccountId | None class RequestPaymentConfiguration(TypedDict, total=False): @@ -3188,207 +3364,207 @@ class RequestPaymentConfiguration(TypedDict, total=False): class PutBucketRequestPaymentRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None RequestPaymentConfiguration: RequestPaymentConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutBucketTaggingRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None Tagging: Tagging - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class VersioningConfiguration(TypedDict, total=False): - MFADelete: Optional[MFADelete] - Status: Optional[BucketVersioningStatus] + MFADelete: MFADelete | None + Status: BucketVersioningStatus | None class PutBucketVersioningRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - MFA: Optional[MFA] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + MFA: MFA | None VersioningConfiguration: VersioningConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class WebsiteConfiguration(TypedDict, total=False): - ErrorDocument: Optional[ErrorDocument] - IndexDocument: Optional[IndexDocument] - RedirectAllRequestsTo: Optional[RedirectAllRequestsTo] - RoutingRules: Optional[RoutingRules] + ErrorDocument: ErrorDocument | None + IndexDocument: IndexDocument | None + RedirectAllRequestsTo: RedirectAllRequestsTo | None + RoutingRules: RoutingRules | None class PutBucketWebsiteRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None WebsiteConfiguration: WebsiteConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class PutObjectAclOutput(TypedDict, total=False): - RequestCharged: Optional[RequestCharged] + RequestCharged: RequestCharged | None class PutObjectAclRequest(ServiceRequest): - ACL: Optional[ObjectCannedACL] - AccessControlPolicy: Optional[AccessControlPolicy] + ACL: ObjectCannedACL | None + AccessControlPolicy: AccessControlPolicy | None Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - GrantFullControl: Optional[GrantFullControl] - GrantRead: Optional[GrantRead] - GrantReadACP: Optional[GrantReadACP] - GrantWrite: Optional[GrantWrite] - GrantWriteACP: Optional[GrantWriteACP] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + GrantFullControl: GrantFullControl | None + GrantRead: GrantRead | None + GrantReadACP: GrantReadACP | None + GrantWrite: GrantWrite | None + GrantWriteACP: GrantWriteACP | None Key: ObjectKey - RequestPayer: Optional[RequestPayer] - VersionId: Optional[ObjectVersionId] - ExpectedBucketOwner: Optional[AccountId] + RequestPayer: RequestPayer | None + VersionId: ObjectVersionId | None + ExpectedBucketOwner: AccountId | None class PutObjectLegalHoldOutput(TypedDict, total=False): - RequestCharged: Optional[RequestCharged] + RequestCharged: RequestCharged | None class PutObjectLegalHoldRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - LegalHold: Optional[ObjectLockLegalHold] - RequestPayer: Optional[RequestPayer] - VersionId: Optional[ObjectVersionId] - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ExpectedBucketOwner: Optional[AccountId] + LegalHold: ObjectLockLegalHold | None + RequestPayer: RequestPayer | None + VersionId: ObjectVersionId | None + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ExpectedBucketOwner: AccountId | None class PutObjectLockConfigurationOutput(TypedDict, total=False): - RequestCharged: Optional[RequestCharged] + RequestCharged: RequestCharged | None class PutObjectLockConfigurationRequest(ServiceRequest): Bucket: BucketName - ObjectLockConfiguration: Optional[ObjectLockConfiguration] - RequestPayer: Optional[RequestPayer] - Token: Optional[ObjectLockToken] - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ExpectedBucketOwner: Optional[AccountId] + ObjectLockConfiguration: ObjectLockConfiguration | None + RequestPayer: RequestPayer | None + Token: ObjectLockToken | None + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ExpectedBucketOwner: AccountId | None class PutObjectOutput(TypedDict, total=False): - Expiration: Optional[Expiration] - ETag: Optional[ETag] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - ChecksumType: Optional[ChecksumType] - ServerSideEncryption: Optional[ServerSideEncryption] - VersionId: Optional[ObjectVersionId] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] - Size: Optional[Size] - RequestCharged: Optional[RequestCharged] + Expiration: Expiration | None + ETag: ETag | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + ChecksumType: ChecksumType | None + ServerSideEncryption: ServerSideEncryption | None + VersionId: ObjectVersionId | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None + Size: Size | None + RequestCharged: RequestCharged | None WriteOffsetBytes = int class PutObjectRequest(ServiceRequest): - Body: Optional[IO[Body]] - ACL: Optional[ObjectCannedACL] + Body: IO[Body] | None + ACL: ObjectCannedACL | None Bucket: BucketName - CacheControl: Optional[CacheControl] - ContentDisposition: Optional[ContentDisposition] - ContentEncoding: Optional[ContentEncoding] - ContentLanguage: Optional[ContentLanguage] - ContentLength: Optional[ContentLength] - ContentMD5: Optional[ContentMD5] - ContentType: Optional[ContentType] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - Expires: Optional[Expires] - IfMatch: Optional[IfMatch] - IfNoneMatch: Optional[IfNoneMatch] - GrantFullControl: Optional[GrantFullControl] - GrantRead: Optional[GrantRead] - GrantReadACP: Optional[GrantReadACP] - GrantWriteACP: Optional[GrantWriteACP] + CacheControl: CacheControl | None + ContentDisposition: ContentDisposition | None + ContentEncoding: ContentEncoding | None + ContentLanguage: ContentLanguage | None + ContentLength: ContentLength | None + ContentMD5: ContentMD5 | None + ContentType: ContentType | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + Expires: Expires | None + IfMatch: IfMatch | None + IfNoneMatch: IfNoneMatch | None + GrantFullControl: GrantFullControl | None + GrantRead: GrantRead | None + GrantReadACP: GrantReadACP | None + GrantWriteACP: GrantWriteACP | None Key: ObjectKey - WriteOffsetBytes: Optional[WriteOffsetBytes] - Metadata: Optional[Metadata] - ServerSideEncryption: Optional[ServerSideEncryption] - StorageClass: Optional[StorageClass] - WebsiteRedirectLocation: Optional[WebsiteRedirectLocation] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestPayer: Optional[RequestPayer] - Tagging: Optional[TaggingHeader] - ObjectLockMode: Optional[ObjectLockMode] - ObjectLockRetainUntilDate: Optional[ObjectLockRetainUntilDate] - ObjectLockLegalHoldStatus: Optional[ObjectLockLegalHoldStatus] - ExpectedBucketOwner: Optional[AccountId] + WriteOffsetBytes: WriteOffsetBytes | None + Metadata: Metadata | None + ServerSideEncryption: ServerSideEncryption | None + StorageClass: StorageClass | None + WebsiteRedirectLocation: WebsiteRedirectLocation | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestPayer: RequestPayer | None + Tagging: TaggingHeader | None + ObjectLockMode: ObjectLockMode | None + ObjectLockRetainUntilDate: ObjectLockRetainUntilDate | None + ObjectLockLegalHoldStatus: ObjectLockLegalHoldStatus | None + ExpectedBucketOwner: AccountId | None class PutObjectRetentionOutput(TypedDict, total=False): - RequestCharged: Optional[RequestCharged] + RequestCharged: RequestCharged | None class PutObjectRetentionRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - Retention: Optional[ObjectLockRetention] - RequestPayer: Optional[RequestPayer] - VersionId: Optional[ObjectVersionId] - BypassGovernanceRetention: Optional[BypassGovernanceRetention] - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ExpectedBucketOwner: Optional[AccountId] + Retention: ObjectLockRetention | None + RequestPayer: RequestPayer | None + VersionId: ObjectVersionId | None + BypassGovernanceRetention: BypassGovernanceRetention | None + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ExpectedBucketOwner: AccountId | None class PutObjectTaggingOutput(TypedDict, total=False): - VersionId: Optional[ObjectVersionId] + VersionId: ObjectVersionId | None class PutObjectTaggingRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + VersionId: ObjectVersionId | None + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None Tagging: Tagging - ExpectedBucketOwner: Optional[AccountId] - RequestPayer: Optional[RequestPayer] + ExpectedBucketOwner: AccountId | None + RequestPayer: RequestPayer | None class PutPublicAccessBlockRequest(ServiceRequest): Bucket: BucketName - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None PublicAccessBlockConfiguration: PublicAccessBlockConfiguration - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None class RecordsEvent(TypedDict, total=False): - Payload: Optional[Body] + Payload: Body | None class RenameObjectOutput(TypedDict, total=False): @@ -3403,25 +3579,25 @@ class RenameObjectRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey RenameSource: RenameSource - DestinationIfMatch: Optional[IfMatch] - DestinationIfNoneMatch: Optional[IfNoneMatch] - DestinationIfModifiedSince: Optional[IfModifiedSince] - DestinationIfUnmodifiedSince: Optional[IfUnmodifiedSince] - SourceIfMatch: Optional[RenameSourceIfMatch] - SourceIfNoneMatch: Optional[RenameSourceIfNoneMatch] - SourceIfModifiedSince: Optional[RenameSourceIfModifiedSince] - SourceIfUnmodifiedSince: Optional[RenameSourceIfUnmodifiedSince] - ClientToken: Optional[ClientToken] + DestinationIfMatch: IfMatch | None + DestinationIfNoneMatch: IfNoneMatch | None + DestinationIfModifiedSince: IfModifiedSince | None + DestinationIfUnmodifiedSince: IfUnmodifiedSince | None + SourceIfMatch: RenameSourceIfMatch | None + SourceIfNoneMatch: RenameSourceIfNoneMatch | None + SourceIfModifiedSince: RenameSourceIfModifiedSince | None + SourceIfUnmodifiedSince: RenameSourceIfUnmodifiedSince | None + ClientToken: ClientToken | None class RequestProgress(TypedDict, total=False): - Enabled: Optional[EnableRequestProgress] + Enabled: EnableRequestProgress | None class RestoreObjectOutput(TypedDict, total=False): - RequestCharged: Optional[RequestCharged] - RestoreOutputPath: Optional[RestoreOutputPath] - StatusCode: Optional[RestoreObjectOutputStatusCode] + RequestCharged: RequestCharged | None + RestoreOutputPath: RestoreOutputPath | None + StatusCode: RestoreObjectOutputStatusCode | None class SelectParameters(TypedDict, total=False): @@ -3432,49 +3608,49 @@ class SelectParameters(TypedDict, total=False): class RestoreRequest(TypedDict, total=False): - Days: Optional[Days] - GlacierJobParameters: Optional[GlacierJobParameters] - Type: Optional[RestoreRequestType] - Tier: Optional[Tier] - Description: Optional[Description] - SelectParameters: Optional[SelectParameters] - OutputLocation: Optional[OutputLocation] + Days: Days | None + GlacierJobParameters: GlacierJobParameters | None + Type: RestoreRequestType | None + Tier: Tier | None + Description: Description | None + SelectParameters: SelectParameters | None + OutputLocation: OutputLocation | None class RestoreObjectRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - VersionId: Optional[ObjectVersionId] - RestoreRequest: Optional[RestoreRequest] - RequestPayer: Optional[RequestPayer] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ExpectedBucketOwner: Optional[AccountId] + VersionId: ObjectVersionId | None + RestoreRequest: RestoreRequest | None + RequestPayer: RequestPayer | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ExpectedBucketOwner: AccountId | None Start = int class ScanRange(TypedDict, total=False): - Start: Optional[Start] - End: Optional[End] + Start: Start | None + End: End | None class Stats(TypedDict, total=False): - BytesScanned: Optional[BytesScanned] - BytesProcessed: Optional[BytesProcessed] - BytesReturned: Optional[BytesReturned] + BytesScanned: BytesScanned | None + BytesProcessed: BytesProcessed | None + BytesReturned: BytesReturned | None class StatsEvent(TypedDict, total=False): - Details: Optional[Stats] + Details: Stats | None class SelectObjectContentEventStream(TypedDict, total=False): - Records: Optional[RecordsEvent] - Stats: Optional[StatsEvent] - Progress: Optional[ProgressEvent] - Cont: Optional[ContinuationEvent] - End: Optional[EndEvent] + Records: RecordsEvent | None + Stats: StatsEvent | None + Progress: ProgressEvent | None + Cont: ContinuationEvent | None + End: EndEvent | None class SelectObjectContentOutput(TypedDict, total=False): @@ -3484,164 +3660,195 @@ class SelectObjectContentOutput(TypedDict, total=False): class SelectObjectContentRequest(ServiceRequest): Bucket: BucketName Key: ObjectKey - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None Expression: Expression ExpressionType: ExpressionType - RequestProgress: Optional[RequestProgress] + RequestProgress: RequestProgress | None InputSerialization: InputSerialization OutputSerialization: OutputSerialization - ScanRange: Optional[ScanRange] - ExpectedBucketOwner: Optional[AccountId] + ScanRange: ScanRange | None + ExpectedBucketOwner: AccountId | None + + +class UpdateBucketMetadataInventoryTableConfigurationRequest(ServiceRequest): + Bucket: BucketName + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + InventoryTableConfiguration: InventoryTableConfigurationUpdates + ExpectedBucketOwner: AccountId | None + + +class UpdateBucketMetadataJournalTableConfigurationRequest(ServiceRequest): + Bucket: BucketName + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + JournalTableConfiguration: JournalTableConfigurationUpdates + ExpectedBucketOwner: AccountId | None + + +class UpdateObjectEncryptionRequest(ServiceRequest): + Bucket: BucketName + Key: ObjectKey + VersionId: ObjectVersionId | None + ObjectEncryption: ObjectEncryption + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + + +class UpdateObjectEncryptionResponse(TypedDict, total=False): + RequestCharged: RequestCharged | None class UploadPartCopyOutput(TypedDict, total=False): - CopySourceVersionId: Optional[CopySourceVersionId] - CopyPartResult: Optional[CopyPartResult] - ServerSideEncryption: Optional[ServerSideEncryption] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestCharged: Optional[RequestCharged] + CopySourceVersionId: CopySourceVersionId | None + CopyPartResult: CopyPartResult | None + ServerSideEncryption: ServerSideEncryption | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestCharged: RequestCharged | None class UploadPartCopyRequest(ServiceRequest): Bucket: BucketName CopySource: CopySource - CopySourceIfMatch: Optional[CopySourceIfMatch] - CopySourceIfModifiedSince: Optional[CopySourceIfModifiedSince] - CopySourceIfNoneMatch: Optional[CopySourceIfNoneMatch] - CopySourceIfUnmodifiedSince: Optional[CopySourceIfUnmodifiedSince] - CopySourceRange: Optional[CopySourceRange] + CopySourceIfMatch: CopySourceIfMatch | None + CopySourceIfModifiedSince: CopySourceIfModifiedSince | None + CopySourceIfNoneMatch: CopySourceIfNoneMatch | None + CopySourceIfUnmodifiedSince: CopySourceIfUnmodifiedSince | None + CopySourceRange: CopySourceRange | None Key: ObjectKey PartNumber: PartNumber UploadId: MultipartUploadId - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - CopySourceSSECustomerAlgorithm: Optional[CopySourceSSECustomerAlgorithm] - CopySourceSSECustomerKey: Optional[CopySourceSSECustomerKey] - CopySourceSSECustomerKeyMD5: Optional[CopySourceSSECustomerKeyMD5] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] - ExpectedSourceBucketOwner: Optional[AccountId] + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + CopySourceSSECustomerAlgorithm: CopySourceSSECustomerAlgorithm | None + CopySourceSSECustomerKey: CopySourceSSECustomerKey | None + CopySourceSSECustomerKeyMD5: CopySourceSSECustomerKeyMD5 | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None + ExpectedSourceBucketOwner: AccountId | None class UploadPartOutput(TypedDict, total=False): - ServerSideEncryption: Optional[ServerSideEncryption] - ETag: Optional[ETag] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestCharged: Optional[RequestCharged] + ServerSideEncryption: ServerSideEncryption | None + ETag: ETag | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestCharged: RequestCharged | None class UploadPartRequest(ServiceRequest): - Body: Optional[IO[Body]] + Body: IO[Body] | None Bucket: BucketName - ContentLength: Optional[ContentLength] - ContentMD5: Optional[ContentMD5] - ChecksumAlgorithm: Optional[ChecksumAlgorithm] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] + ContentLength: ContentLength | None + ContentMD5: ContentMD5 | None + ChecksumAlgorithm: ChecksumAlgorithm | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None Key: ObjectKey PartNumber: PartNumber UploadId: MultipartUploadId - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKey: Optional[SSECustomerKey] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - RequestPayer: Optional[RequestPayer] - ExpectedBucketOwner: Optional[AccountId] + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKey: SSECustomerKey | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + RequestPayer: RequestPayer | None + ExpectedBucketOwner: AccountId | None class WriteGetObjectResponseRequest(ServiceRequest): - Body: Optional[IO[Body]] + Body: IO[Body] | None RequestRoute: RequestRoute RequestToken: RequestToken - StatusCode: Optional[GetObjectResponseStatusCode] - ErrorCode: Optional[ErrorCode] - ErrorMessage: Optional[ErrorMessage] - AcceptRanges: Optional[AcceptRanges] - CacheControl: Optional[CacheControl] - ContentDisposition: Optional[ContentDisposition] - ContentEncoding: Optional[ContentEncoding] - ContentLanguage: Optional[ContentLanguage] - ContentLength: Optional[ContentLength] - ContentRange: Optional[ContentRange] - ContentType: Optional[ContentType] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - DeleteMarker: Optional[DeleteMarker] - ETag: Optional[ETag] - Expires: Optional[Expires] - Expiration: Optional[Expiration] - LastModified: Optional[LastModified] - MissingMeta: Optional[MissingMeta] - Metadata: Optional[Metadata] - ObjectLockMode: Optional[ObjectLockMode] - ObjectLockLegalHoldStatus: Optional[ObjectLockLegalHoldStatus] - ObjectLockRetainUntilDate: Optional[ObjectLockRetainUntilDate] - PartsCount: Optional[PartsCount] - ReplicationStatus: Optional[ReplicationStatus] - RequestCharged: Optional[RequestCharged] - Restore: Optional[Restore] - ServerSideEncryption: Optional[ServerSideEncryption] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - StorageClass: Optional[StorageClass] - TagCount: Optional[TagCount] - VersionId: Optional[ObjectVersionId] - BucketKeyEnabled: Optional[BucketKeyEnabled] + StatusCode: GetObjectResponseStatusCode | None + ErrorCode: ErrorCode | None + ErrorMessage: ErrorMessage | None + AcceptRanges: AcceptRanges | None + CacheControl: CacheControl | None + ContentDisposition: ContentDisposition | None + ContentEncoding: ContentEncoding | None + ContentLanguage: ContentLanguage | None + ContentLength: ContentLength | None + ContentRange: ContentRange | None + ContentType: ContentType | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + DeleteMarker: DeleteMarker | None + ETag: ETag | None + Expires: Expires | None + Expiration: Expiration | None + LastModified: LastModified | None + MissingMeta: MissingMeta | None + Metadata: Metadata | None + ObjectLockMode: ObjectLockMode | None + ObjectLockLegalHoldStatus: ObjectLockLegalHoldStatus | None + ObjectLockRetainUntilDate: ObjectLockRetainUntilDate | None + PartsCount: PartsCount | None + ReplicationStatus: ReplicationStatus | None + RequestCharged: RequestCharged | None + Restore: Restore | None + ServerSideEncryption: ServerSideEncryption | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSEKMSKeyId: SSEKMSKeyId | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + StorageClass: StorageClass | None + TagCount: TagCount | None + VersionId: ObjectVersionId | None + BucketKeyEnabled: BucketKeyEnabled | None class PostObjectRequest(ServiceRequest): - Body: Optional[IO[Body]] + Body: IO[Body] | None Bucket: BucketName class PostResponse(TypedDict, total=False): - StatusCode: Optional[GetObjectResponseStatusCode] - Location: Optional[Location] - LocationHeader: Optional[Location] - Bucket: Optional[BucketName] - Key: Optional[ObjectKey] - Expiration: Optional[Expiration] - ETag: Optional[ETag] - ETagHeader: Optional[ETag] - ChecksumCRC32: Optional[ChecksumCRC32] - ChecksumCRC32C: Optional[ChecksumCRC32C] - ChecksumCRC64NVME: Optional[ChecksumCRC64NVME] - ChecksumSHA1: Optional[ChecksumSHA1] - ChecksumSHA256: Optional[ChecksumSHA256] - ChecksumType: Optional[ChecksumType] - ServerSideEncryption: Optional[ServerSideEncryption] - VersionId: Optional[ObjectVersionId] - SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] - SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] - SSEKMSKeyId: Optional[SSEKMSKeyId] - SSEKMSEncryptionContext: Optional[SSEKMSEncryptionContext] - BucketKeyEnabled: Optional[BucketKeyEnabled] - RequestCharged: Optional[RequestCharged] + StatusCode: GetObjectResponseStatusCode | None + Location: Location | None + LocationHeader: Location | None + Bucket: BucketName | None + Key: ObjectKey | None + Expiration: Expiration | None + ETag: ETag | None + ETagHeader: ETag | None + ChecksumCRC32: ChecksumCRC32 | None + ChecksumCRC32C: ChecksumCRC32C | None + ChecksumCRC64NVME: ChecksumCRC64NVME | None + ChecksumSHA1: ChecksumSHA1 | None + ChecksumSHA256: ChecksumSHA256 | None + ChecksumType: ChecksumType | None + ServerSideEncryption: ServerSideEncryption | None + VersionId: ObjectVersionId | None + SSECustomerAlgorithm: SSECustomerAlgorithm | None + SSECustomerKeyMD5: SSECustomerKeyMD5 | None + SSEKMSKeyId: SSEKMSKeyId | None + SSEKMSEncryptionContext: SSEKMSEncryptionContext | None + BucketKeyEnabled: BucketKeyEnabled | None + RequestCharged: RequestCharged | None class S3Api: - service = "s3" - version = "2006-03-01" + service: str = "s3" + version: str = "2006-03-01" @handler("AbortMultipartUpload") def abort_multipart_upload( @@ -3706,6 +3913,8 @@ def copy_object( grant_read: GrantRead | None = None, grant_read_acp: GrantReadACP | None = None, grant_write_acp: GrantWriteACP | None = None, + if_match: IfMatch | None = None, + if_none_match: IfNoneMatch | None = None, metadata: Metadata | None = None, metadata_directive: MetadataDirective | None = None, tagging_directive: TaggingDirective | None = None, @@ -3750,6 +3959,19 @@ def create_bucket( ) -> CreateBucketOutput: raise NotImplementedError + @handler("CreateBucketMetadataConfiguration") + def create_bucket_metadata_configuration( + self, + context: RequestContext, + bucket: BucketName, + metadata_configuration: MetadataConfiguration, + content_md5: ContentMD5 | None = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + expected_bucket_owner: AccountId | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + @handler("CreateBucketMetadataTableConfiguration") def create_bucket_metadata_table_configuration( self, @@ -3889,6 +4111,16 @@ def delete_bucket_lifecycle( ) -> None: raise NotImplementedError + @handler("DeleteBucketMetadataConfiguration") + def delete_bucket_metadata_configuration( + self, + context: RequestContext, + bucket: BucketName, + expected_bucket_owner: AccountId | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + @handler("DeleteBucketMetadataTableConfiguration") def delete_bucket_metadata_table_configuration( self, @@ -4015,6 +4247,16 @@ def delete_public_access_block( ) -> None: raise NotImplementedError + @handler("GetBucketAbac") + def get_bucket_abac( + self, + context: RequestContext, + bucket: BucketName, + expected_bucket_owner: AccountId | None = None, + **kwargs, + ) -> GetBucketAbacOutput: + raise NotImplementedError + @handler("GetBucketAccelerateConfiguration") def get_bucket_accelerate_configuration( self, @@ -4129,6 +4371,16 @@ def get_bucket_logging( ) -> GetBucketLoggingOutput: raise NotImplementedError + @handler("GetBucketMetadataConfiguration") + def get_bucket_metadata_configuration( + self, + context: RequestContext, + bucket: BucketName, + expected_bucket_owner: AccountId | None = None, + **kwargs, + ) -> GetBucketMetadataConfigurationOutput: + raise NotImplementedError + @handler("GetBucketMetadataTableConfiguration") def get_bucket_metadata_table_configuration( self, @@ -4576,6 +4828,19 @@ def list_parts( ) -> ListPartsOutput: raise NotImplementedError + @handler("PutBucketAbac") + def put_bucket_abac( + self, + context: RequestContext, + bucket: BucketName, + abac_status: AbacStatus, + content_md5: ContentMD5 | None = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + expected_bucket_owner: AccountId | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + @handler("PutBucketAccelerateConfiguration") def put_bucket_accelerate_configuration( self, @@ -5042,6 +5307,48 @@ def select_object_content( ) -> SelectObjectContentOutput: raise NotImplementedError + @handler("UpdateBucketMetadataInventoryTableConfiguration") + def update_bucket_metadata_inventory_table_configuration( + self, + context: RequestContext, + bucket: BucketName, + inventory_table_configuration: InventoryTableConfigurationUpdates, + content_md5: ContentMD5 | None = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + expected_bucket_owner: AccountId | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + + @handler("UpdateBucketMetadataJournalTableConfiguration") + def update_bucket_metadata_journal_table_configuration( + self, + context: RequestContext, + bucket: BucketName, + journal_table_configuration: JournalTableConfigurationUpdates, + content_md5: ContentMD5 | None = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + expected_bucket_owner: AccountId | None = None, + **kwargs, + ) -> None: + raise NotImplementedError + + @handler("UpdateObjectEncryption") + def update_object_encryption( + self, + context: RequestContext, + bucket: BucketName, + key: ObjectKey, + object_encryption: ObjectEncryption, + version_id: ObjectVersionId | None = None, + request_payer: RequestPayer | None = None, + expected_bucket_owner: AccountId | None = None, + content_md5: ContentMD5 | None = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + **kwargs, + ) -> UpdateObjectEncryptionResponse: + raise NotImplementedError + @handler("UploadPart") def upload_part( self, diff --git a/localstack-core/localstack/aws/api/s3control/__init__.py b/localstack-core/localstack/aws/api/s3control/__init__.py index 429e3219630d2..b5b1972471567 100644 --- a/localstack-core/localstack/aws/api/s3control/__init__.py +++ b/localstack-core/localstack/aws/api/s3control/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -65,6 +65,7 @@ MultiRegionAccessPointId = str MultiRegionAccessPointName = str NoSuchPublicAccessBlockConfigurationMessage = str +NonEmptyKmsKeyArnString = str NonEmptyMaxLength1024String = str NonEmptyMaxLength2048String = str NonEmptyMaxLength256String = str @@ -147,6 +148,20 @@ class BucketVersioningStatus(StrEnum): Suspended = "Suspended" +class ComputeObjectChecksumAlgorithm(StrEnum): + CRC32 = "CRC32" + CRC32C = "CRC32C" + CRC64NVME = "CRC64NVME" + MD5 = "MD5" + SHA1 = "SHA1" + SHA256 = "SHA256" + + +class ComputeObjectChecksumType(StrEnum): + FULL_OBJECT = "FULL_OBJECT" + COMPOSITE = "COMPOSITE" + + class DeleteMarkerReplicationStatus(StrEnum): Enabled = "Enabled" Disabled = "Disabled" @@ -272,6 +287,8 @@ class OperationName(StrEnum): S3PutObjectLegalHold = "S3PutObjectLegalHold" S3PutObjectRetention = "S3PutObjectRetention" S3ReplicateObject = "S3ReplicateObject" + S3ComputeObjectChecksum = "S3ComputeObjectChecksum" + S3UpdateObjectEncryption = "S3UpdateObjectEncryption" class OutputSchemaVersion(StrEnum): @@ -505,7 +522,7 @@ class TooManyTagsException(ServiceException): class AbortIncompleteMultipartUpload(TypedDict, total=False): - DaysAfterInitiation: Optional[DaysAfterInitiation] + DaysAfterInitiation: DaysAfterInitiation | None class AccessControlTranslation(TypedDict, total=False): @@ -516,50 +533,50 @@ class AccessControlTranslation(TypedDict, total=False): class ListAccessGrantsInstanceEntry(TypedDict, total=False): - AccessGrantsInstanceId: Optional[AccessGrantsInstanceId] - AccessGrantsInstanceArn: Optional[AccessGrantsInstanceArn] - CreatedAt: Optional[CreationTimestamp] - IdentityCenterArn: Optional[IdentityCenterArn] - IdentityCenterInstanceArn: Optional[IdentityCenterArn] - IdentityCenterApplicationArn: Optional[IdentityCenterApplicationArn] + AccessGrantsInstanceId: AccessGrantsInstanceId | None + AccessGrantsInstanceArn: AccessGrantsInstanceArn | None + CreatedAt: CreationTimestamp | None + IdentityCenterArn: IdentityCenterArn | None + IdentityCenterInstanceArn: IdentityCenterArn | None + IdentityCenterApplicationArn: IdentityCenterApplicationArn | None -AccessGrantsInstancesList = List[ListAccessGrantsInstanceEntry] +AccessGrantsInstancesList = list[ListAccessGrantsInstanceEntry] class AccessGrantsLocationConfiguration(TypedDict, total=False): - S3SubPrefix: Optional[S3Prefix] + S3SubPrefix: S3Prefix | None class Grantee(TypedDict, total=False): - GranteeType: Optional[GranteeType] - GranteeIdentifier: Optional[GranteeIdentifier] + GranteeType: GranteeType | None + GranteeIdentifier: GranteeIdentifier | None class ListAccessGrantEntry(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantId: Optional[AccessGrantId] - AccessGrantArn: Optional[AccessGrantArn] - Grantee: Optional[Grantee] - Permission: Optional[Permission] - AccessGrantsLocationId: Optional[AccessGrantsLocationId] - AccessGrantsLocationConfiguration: Optional[AccessGrantsLocationConfiguration] - GrantScope: Optional[S3Prefix] - ApplicationArn: Optional[IdentityCenterApplicationArn] + CreatedAt: CreationTimestamp | None + AccessGrantId: AccessGrantId | None + AccessGrantArn: AccessGrantArn | None + Grantee: Grantee | None + Permission: Permission | None + AccessGrantsLocationId: AccessGrantsLocationId | None + AccessGrantsLocationConfiguration: AccessGrantsLocationConfiguration | None + GrantScope: S3Prefix | None + ApplicationArn: IdentityCenterApplicationArn | None -AccessGrantsList = List[ListAccessGrantEntry] +AccessGrantsList = list[ListAccessGrantEntry] class ListAccessGrantsLocationsEntry(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantsLocationId: Optional[AccessGrantsLocationId] - AccessGrantsLocationArn: Optional[AccessGrantsLocationArn] - LocationScope: Optional[S3Prefix] - IAMRoleArn: Optional[IAMRoleArn] + CreatedAt: CreationTimestamp | None + AccessGrantsLocationId: AccessGrantsLocationId | None + AccessGrantsLocationArn: AccessGrantsLocationArn | None + LocationScope: S3Prefix | None + IAMRoleArn: IAMRoleArn | None -AccessGrantsLocationsList = List[ListAccessGrantsLocationsEntry] +AccessGrantsLocationsList = list[ListAccessGrantsLocationsEntry] class VpcConfiguration(TypedDict, total=False): @@ -569,50 +586,54 @@ class VpcConfiguration(TypedDict, total=False): class AccessPoint(TypedDict, total=False): Name: AccessPointName NetworkOrigin: NetworkOrigin - VpcConfiguration: Optional[VpcConfiguration] + VpcConfiguration: VpcConfiguration | None Bucket: AccessPointBucketName - AccessPointArn: Optional[S3AccessPointArn] - Alias: Optional[Alias] - BucketAccountId: Optional[AccountId] - DataSourceId: Optional[DataSourceId] - DataSourceType: Optional[DataSourceType] + AccessPointArn: S3AccessPointArn | None + Alias: Alias | None + BucketAccountId: AccountId | None + DataSourceId: DataSourceId | None + DataSourceType: DataSourceType | None -AccessPointList = List[AccessPoint] -StorageLensGroupLevelExclude = List[StorageLensGroupArn] -StorageLensGroupLevelInclude = List[StorageLensGroupArn] +AccessPointList = list[AccessPoint] +StorageLensGroupLevelExclude = list[StorageLensGroupArn] +StorageLensGroupLevelInclude = list[StorageLensGroupArn] class StorageLensGroupLevelSelectionCriteria(TypedDict, total=False): - Include: Optional[StorageLensGroupLevelInclude] - Exclude: Optional[StorageLensGroupLevelExclude] + Include: StorageLensGroupLevelInclude | None + Exclude: StorageLensGroupLevelExclude | None class StorageLensGroupLevel(TypedDict, total=False): - SelectionCriteria: Optional[StorageLensGroupLevelSelectionCriteria] + SelectionCriteria: StorageLensGroupLevelSelectionCriteria | None + + +class AdvancedPerformanceMetrics(TypedDict, total=False): + IsEnabled: IsEnabled | None class DetailedStatusCodesMetrics(TypedDict, total=False): - IsEnabled: Optional[IsEnabled] + IsEnabled: IsEnabled | None class AdvancedDataProtectionMetrics(TypedDict, total=False): - IsEnabled: Optional[IsEnabled] + IsEnabled: IsEnabled | None class AdvancedCostOptimizationMetrics(TypedDict, total=False): - IsEnabled: Optional[IsEnabled] + IsEnabled: IsEnabled | None class SelectionCriteria(TypedDict, total=False): - Delimiter: Optional[StorageLensPrefixLevelDelimiter] - MaxDepth: Optional[StorageLensPrefixLevelMaxDepth] - MinStorageBytesPercentage: Optional[MinStorageBytesPercentage] + Delimiter: StorageLensPrefixLevelDelimiter | None + MaxDepth: StorageLensPrefixLevelMaxDepth | None + MinStorageBytesPercentage: MinStorageBytesPercentage | None class PrefixLevelStorageMetrics(TypedDict, total=False): - IsEnabled: Optional[IsEnabled] - SelectionCriteria: Optional[SelectionCriteria] + IsEnabled: IsEnabled | None + SelectionCriteria: SelectionCriteria | None class PrefixLevel(TypedDict, total=False): @@ -620,24 +641,26 @@ class PrefixLevel(TypedDict, total=False): class ActivityMetrics(TypedDict, total=False): - IsEnabled: Optional[IsEnabled] + IsEnabled: IsEnabled | None class BucketLevel(TypedDict, total=False): - ActivityMetrics: Optional[ActivityMetrics] - PrefixLevel: Optional[PrefixLevel] - AdvancedCostOptimizationMetrics: Optional[AdvancedCostOptimizationMetrics] - AdvancedDataProtectionMetrics: Optional[AdvancedDataProtectionMetrics] - DetailedStatusCodesMetrics: Optional[DetailedStatusCodesMetrics] + ActivityMetrics: ActivityMetrics | None + PrefixLevel: PrefixLevel | None + AdvancedCostOptimizationMetrics: AdvancedCostOptimizationMetrics | None + AdvancedDataProtectionMetrics: AdvancedDataProtectionMetrics | None + DetailedStatusCodesMetrics: DetailedStatusCodesMetrics | None + AdvancedPerformanceMetrics: AdvancedPerformanceMetrics | None class AccountLevel(TypedDict, total=False): - ActivityMetrics: Optional[ActivityMetrics] + ActivityMetrics: ActivityMetrics | None BucketLevel: BucketLevel - AdvancedCostOptimizationMetrics: Optional[AdvancedCostOptimizationMetrics] - AdvancedDataProtectionMetrics: Optional[AdvancedDataProtectionMetrics] - DetailedStatusCodesMetrics: Optional[DetailedStatusCodesMetrics] - StorageLensGroupLevel: Optional[StorageLensGroupLevel] + AdvancedCostOptimizationMetrics: AdvancedCostOptimizationMetrics | None + AdvancedDataProtectionMetrics: AdvancedDataProtectionMetrics | None + DetailedStatusCodesMetrics: DetailedStatusCodesMetrics | None + AdvancedPerformanceMetrics: AdvancedPerformanceMetrics | None + StorageLensGroupLevel: StorageLensGroupLevel | None class AssociateAccessGrantsIdentityCenterRequest(ServiceRequest): @@ -649,27 +672,27 @@ class AssociateAccessGrantsIdentityCenterRequest(ServiceRequest): class AsyncErrorDetails(TypedDict, total=False): - Code: Optional[MaxLength1024String] - Message: Optional[MaxLength1024String] - Resource: Optional[MaxLength1024String] - RequestId: Optional[MaxLength1024String] + Code: MaxLength1024String | None + Message: MaxLength1024String | None + Resource: MaxLength1024String | None + RequestId: MaxLength1024String | None class MultiRegionAccessPointRegionalResponse(TypedDict, total=False): - Name: Optional[RegionName] - RequestStatus: Optional[AsyncRequestStatus] + Name: RegionName | None + RequestStatus: AsyncRequestStatus | None -MultiRegionAccessPointRegionalResponseList = List[MultiRegionAccessPointRegionalResponse] +MultiRegionAccessPointRegionalResponseList = list[MultiRegionAccessPointRegionalResponse] class MultiRegionAccessPointsAsyncResponse(TypedDict, total=False): - Regions: Optional[MultiRegionAccessPointRegionalResponseList] + Regions: MultiRegionAccessPointRegionalResponseList | None class AsyncResponseDetails(TypedDict, total=False): - MultiRegionAccessPointDetails: Optional[MultiRegionAccessPointsAsyncResponse] - ErrorDetails: Optional[AsyncErrorDetails] + MultiRegionAccessPointDetails: MultiRegionAccessPointsAsyncResponse | None + ErrorDetails: AsyncErrorDetails | None class PutMultiRegionAccessPointPolicyInput(TypedDict, total=False): @@ -683,55 +706,55 @@ class DeleteMultiRegionAccessPointInput(TypedDict, total=False): class Region(TypedDict, total=False): Bucket: BucketName - BucketAccountId: Optional[AccountId] + BucketAccountId: AccountId | None -RegionCreationList = List[Region] +RegionCreationList = list[Region] class PublicAccessBlockConfiguration(TypedDict, total=False): - BlockPublicAcls: Optional[Setting] - IgnorePublicAcls: Optional[Setting] - BlockPublicPolicy: Optional[Setting] - RestrictPublicBuckets: Optional[Setting] + BlockPublicAcls: Setting | None + IgnorePublicAcls: Setting | None + BlockPublicPolicy: Setting | None + RestrictPublicBuckets: Setting | None class CreateMultiRegionAccessPointInput(TypedDict, total=False): Name: MultiRegionAccessPointName - PublicAccessBlock: Optional[PublicAccessBlockConfiguration] + PublicAccessBlock: PublicAccessBlockConfiguration | None Regions: RegionCreationList class AsyncRequestParameters(TypedDict, total=False): - CreateMultiRegionAccessPointRequest: Optional[CreateMultiRegionAccessPointInput] - DeleteMultiRegionAccessPointRequest: Optional[DeleteMultiRegionAccessPointInput] - PutMultiRegionAccessPointPolicyRequest: Optional[PutMultiRegionAccessPointPolicyInput] + CreateMultiRegionAccessPointRequest: CreateMultiRegionAccessPointInput | None + DeleteMultiRegionAccessPointRequest: DeleteMultiRegionAccessPointInput | None + PutMultiRegionAccessPointPolicyRequest: PutMultiRegionAccessPointPolicyInput | None class AsyncOperation(TypedDict, total=False): - CreationTime: Optional[AsyncCreationTimestamp] - Operation: Optional[AsyncOperationName] - RequestTokenARN: Optional[AsyncRequestTokenARN] - RequestParameters: Optional[AsyncRequestParameters] - RequestStatus: Optional[AsyncRequestStatus] - ResponseDetails: Optional[AsyncResponseDetails] + CreationTime: AsyncCreationTimestamp | None + Operation: AsyncOperationName | None + RequestTokenARN: AsyncRequestTokenARN | None + RequestParameters: AsyncRequestParameters | None + RequestStatus: AsyncRequestStatus | None + ResponseDetails: AsyncResponseDetails | None class AwsLambdaTransformation(TypedDict, total=False): FunctionArn: FunctionArnString - FunctionPayload: Optional[AwsLambdaTransformationPayload] + FunctionPayload: AwsLambdaTransformationPayload | None -Buckets = List[S3BucketArnString] +Buckets = list[S3BucketArnString] class ListCallerAccessGrantsEntry(TypedDict, total=False): - Permission: Optional[Permission] - GrantScope: Optional[S3Prefix] - ApplicationArn: Optional[IdentityCenterApplicationArn] + Permission: Permission | None + GrantScope: S3Prefix | None + ApplicationArn: IdentityCenterApplicationArn | None -CallerAccessGrantsList = List[ListCallerAccessGrantsEntry] +CallerAccessGrantsList = list[ListCallerAccessGrantsEntry] class CloudWatchMetrics(TypedDict, total=False): @@ -743,67 +766,67 @@ class Tag(TypedDict, total=False): Value: TagValueString -TagList = List[Tag] +TagList = list[Tag] class CreateAccessGrantRequest(ServiceRequest): AccountId: AccountId AccessGrantsLocationId: AccessGrantsLocationId - AccessGrantsLocationConfiguration: Optional[AccessGrantsLocationConfiguration] + AccessGrantsLocationConfiguration: AccessGrantsLocationConfiguration | None Grantee: Grantee Permission: Permission - ApplicationArn: Optional[IdentityCenterApplicationArn] - S3PrefixType: Optional[S3PrefixType] - Tags: Optional[TagList] + ApplicationArn: IdentityCenterApplicationArn | None + S3PrefixType: S3PrefixType | None + Tags: TagList | None class CreateAccessGrantResult(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantId: Optional[AccessGrantId] - AccessGrantArn: Optional[AccessGrantArn] - Grantee: Optional[Grantee] - AccessGrantsLocationId: Optional[AccessGrantsLocationId] - AccessGrantsLocationConfiguration: Optional[AccessGrantsLocationConfiguration] - Permission: Optional[Permission] - ApplicationArn: Optional[IdentityCenterApplicationArn] - GrantScope: Optional[S3Prefix] + CreatedAt: CreationTimestamp | None + AccessGrantId: AccessGrantId | None + AccessGrantArn: AccessGrantArn | None + Grantee: Grantee | None + AccessGrantsLocationId: AccessGrantsLocationId | None + AccessGrantsLocationConfiguration: AccessGrantsLocationConfiguration | None + Permission: Permission | None + ApplicationArn: IdentityCenterApplicationArn | None + GrantScope: S3Prefix | None class CreateAccessGrantsInstanceRequest(ServiceRequest): AccountId: AccountId - IdentityCenterArn: Optional[IdentityCenterArn] - Tags: Optional[TagList] + IdentityCenterArn: IdentityCenterArn | None + Tags: TagList | None class CreateAccessGrantsInstanceResult(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantsInstanceId: Optional[AccessGrantsInstanceId] - AccessGrantsInstanceArn: Optional[AccessGrantsInstanceArn] - IdentityCenterArn: Optional[IdentityCenterArn] - IdentityCenterInstanceArn: Optional[IdentityCenterArn] - IdentityCenterApplicationArn: Optional[IdentityCenterApplicationArn] + CreatedAt: CreationTimestamp | None + AccessGrantsInstanceId: AccessGrantsInstanceId | None + AccessGrantsInstanceArn: AccessGrantsInstanceArn | None + IdentityCenterArn: IdentityCenterArn | None + IdentityCenterInstanceArn: IdentityCenterArn | None + IdentityCenterApplicationArn: IdentityCenterApplicationArn | None class CreateAccessGrantsLocationRequest(ServiceRequest): AccountId: AccountId LocationScope: S3Prefix IAMRoleArn: IAMRoleArn - Tags: Optional[TagList] + Tags: TagList | None class CreateAccessGrantsLocationResult(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantsLocationId: Optional[AccessGrantsLocationId] - AccessGrantsLocationArn: Optional[AccessGrantsLocationArn] - LocationScope: Optional[S3Prefix] - IAMRoleArn: Optional[IAMRoleArn] + CreatedAt: CreationTimestamp | None + AccessGrantsLocationId: AccessGrantsLocationId | None + AccessGrantsLocationArn: AccessGrantsLocationArn | None + LocationScope: S3Prefix | None + IAMRoleArn: IAMRoleArn | None class ObjectLambdaContentTransformation(TypedDict, total=False): - AwsLambda: Optional[AwsLambdaTransformation] + AwsLambda: AwsLambdaTransformation | None -ObjectLambdaTransformationConfigurationActionsList = List[ +ObjectLambdaTransformationConfigurationActionsList = list[ ObjectLambdaTransformationConfigurationAction ] @@ -813,14 +836,14 @@ class ObjectLambdaTransformationConfiguration(TypedDict, total=False): ContentTransformation: ObjectLambdaContentTransformation -ObjectLambdaTransformationConfigurationsList = List[ObjectLambdaTransformationConfiguration] -ObjectLambdaAllowedFeaturesList = List[ObjectLambdaAllowedFeature] +ObjectLambdaTransformationConfigurationsList = list[ObjectLambdaTransformationConfiguration] +ObjectLambdaAllowedFeaturesList = list[ObjectLambdaAllowedFeature] class ObjectLambdaConfiguration(TypedDict, total=False): SupportingAccessPoint: ObjectLambdaSupportingAccessPointArn - CloudWatchMetricsEnabled: Optional[Boolean] - AllowedFeatures: Optional[ObjectLambdaAllowedFeaturesList] + CloudWatchMetricsEnabled: Boolean | None + AllowedFeatures: ObjectLambdaAllowedFeaturesList | None TransformationConfigurations: ObjectLambdaTransformationConfigurationsList @@ -831,86 +854,118 @@ class CreateAccessPointForObjectLambdaRequest(ServiceRequest): class ObjectLambdaAccessPointAlias(TypedDict, total=False): - Value: Optional[ObjectLambdaAccessPointAliasValue] - Status: Optional[ObjectLambdaAccessPointAliasStatus] + Value: ObjectLambdaAccessPointAliasValue | None + Status: ObjectLambdaAccessPointAliasStatus | None class CreateAccessPointForObjectLambdaResult(TypedDict, total=False): - ObjectLambdaAccessPointArn: Optional[ObjectLambdaAccessPointArn] - Alias: Optional[ObjectLambdaAccessPointAlias] + ObjectLambdaAccessPointArn: ObjectLambdaAccessPointArn | None + Alias: ObjectLambdaAccessPointAlias | None -ScopePermissionList = List[ScopePermission] -PrefixesList = List[Prefix] +ScopePermissionList = list[ScopePermission] +PrefixesList = list[Prefix] class Scope(TypedDict, total=False): - Prefixes: Optional[PrefixesList] - Permissions: Optional[ScopePermissionList] + Prefixes: PrefixesList | None + Permissions: ScopePermissionList | None class CreateAccessPointRequest(ServiceRequest): AccountId: AccountId Name: AccessPointName Bucket: BucketName - VpcConfiguration: Optional[VpcConfiguration] - PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] - BucketAccountId: Optional[AccountId] - Scope: Optional[Scope] + VpcConfiguration: VpcConfiguration | None + PublicAccessBlockConfiguration: PublicAccessBlockConfiguration | None + BucketAccountId: AccountId | None + Scope: Scope | None + Tags: TagList | None class CreateAccessPointResult(TypedDict, total=False): - AccessPointArn: Optional[S3AccessPointArn] - Alias: Optional[Alias] + AccessPointArn: S3AccessPointArn | None + Alias: Alias | None class CreateBucketConfiguration(TypedDict, total=False): - LocationConstraint: Optional[BucketLocationConstraint] + LocationConstraint: BucketLocationConstraint | None class CreateBucketRequest(ServiceRequest): - ACL: Optional[BucketCannedACL] + ACL: BucketCannedACL | None Bucket: BucketName - CreateBucketConfiguration: Optional[CreateBucketConfiguration] - GrantFullControl: Optional[GrantFullControl] - GrantRead: Optional[GrantRead] - GrantReadACP: Optional[GrantReadACP] - GrantWrite: Optional[GrantWrite] - GrantWriteACP: Optional[GrantWriteACP] - ObjectLockEnabledForBucket: Optional[ObjectLockEnabledForBucket] - OutpostId: Optional[NonEmptyMaxLength64String] + CreateBucketConfiguration: CreateBucketConfiguration | None + GrantFullControl: GrantFullControl | None + GrantRead: GrantRead | None + GrantReadACP: GrantReadACP | None + GrantWrite: GrantWrite | None + GrantWriteACP: GrantWriteACP | None + ObjectLockEnabledForBucket: ObjectLockEnabledForBucket | None + OutpostId: NonEmptyMaxLength64String | None class CreateBucketResult(TypedDict, total=False): - Location: Optional[Location] - BucketArn: Optional[S3RegionalBucketArn] + Location: Location | None + BucketArn: S3RegionalBucketArn | None + + +class NotSSEFilter(TypedDict, total=False): + pass + + +class SSECFilter(TypedDict, total=False): + pass + + +class DSSEKMSFilter(TypedDict, total=False): + KmsKeyArn: NonEmptyKmsKeyArnString | None + +class SSEKMSFilter(TypedDict, total=False): + KmsKeyArn: NonEmptyKmsKeyArnString | None + BucketKeyEnabled: Boolean | None -StorageClassList = List[S3StorageClass] + +class SSES3Filter(TypedDict, total=False): + pass + + +class ObjectEncryptionFilter(TypedDict, total=False): + SSES3: SSES3Filter | None + SSEKMS: SSEKMSFilter | None + DSSEKMS: DSSEKMSFilter | None + SSEC: SSECFilter | None + NOTSSE: NotSSEFilter | None + + +ObjectEncryptionFilterList = list[ObjectEncryptionFilter] +StorageClassList = list[S3StorageClass] ObjectSizeLessThanBytes = int ObjectSizeGreaterThanBytes = int -NonEmptyMaxLength1024StringList = List[NonEmptyMaxLength1024String] +NonEmptyMaxLength1024StringList = list[NonEmptyMaxLength1024String] class KeyNameConstraint(TypedDict, total=False): - MatchAnyPrefix: Optional[NonEmptyMaxLength1024StringList] - MatchAnySuffix: Optional[NonEmptyMaxLength1024StringList] - MatchAnySubstring: Optional[NonEmptyMaxLength1024StringList] + MatchAnyPrefix: NonEmptyMaxLength1024StringList | None + MatchAnySuffix: NonEmptyMaxLength1024StringList | None + MatchAnySubstring: NonEmptyMaxLength1024StringList | None -ReplicationStatusFilterList = List[ReplicationStatus] +ReplicationStatusFilterList = list[ReplicationStatus] ObjectCreationTime = datetime class JobManifestGeneratorFilter(TypedDict, total=False): - EligibleForReplication: Optional[Boolean] - CreatedAfter: Optional[ObjectCreationTime] - CreatedBefore: Optional[ObjectCreationTime] - ObjectReplicationStatuses: Optional[ReplicationStatusFilterList] - KeyNameConstraint: Optional[KeyNameConstraint] - ObjectSizeGreaterThanBytes: Optional[ObjectSizeGreaterThanBytes] - ObjectSizeLessThanBytes: Optional[ObjectSizeLessThanBytes] - MatchAnyStorageClass: Optional[StorageClassList] + EligibleForReplication: Boolean | None + CreatedAfter: ObjectCreationTime | None + CreatedBefore: ObjectCreationTime | None + ObjectReplicationStatuses: ReplicationStatusFilterList | None + KeyNameConstraint: KeyNameConstraint | None + ObjectSizeGreaterThanBytes: ObjectSizeGreaterThanBytes | None + ObjectSizeLessThanBytes: ObjectSizeLessThanBytes | None + MatchAnyStorageClass: StorageClassList | None + MatchAnyObjectEncryption: ObjectEncryptionFilterList | None class SSEKMSEncryption(TypedDict, total=False): @@ -922,28 +977,28 @@ class SSES3Encryption(TypedDict, total=False): class GeneratedManifestEncryption(TypedDict, total=False): - SSES3: Optional[SSES3Encryption] - SSEKMS: Optional[SSEKMSEncryption] + SSES3: SSES3Encryption | None + SSEKMS: SSEKMSEncryption | None class S3ManifestOutputLocation(TypedDict, total=False): - ExpectedManifestBucketOwner: Optional[AccountId] + ExpectedManifestBucketOwner: AccountId | None Bucket: S3BucketArnString - ManifestPrefix: Optional[ManifestPrefixString] - ManifestEncryption: Optional[GeneratedManifestEncryption] + ManifestPrefix: ManifestPrefixString | None + ManifestEncryption: GeneratedManifestEncryption | None ManifestFormat: GeneratedManifestFormat class S3JobManifestGenerator(TypedDict, total=False): - ExpectedBucketOwner: Optional[AccountId] + ExpectedBucketOwner: AccountId | None SourceBucket: S3BucketArnString - ManifestOutputLocation: Optional[S3ManifestOutputLocation] - Filter: Optional[JobManifestGeneratorFilter] + ManifestOutputLocation: S3ManifestOutputLocation | None + Filter: JobManifestGeneratorFilter | None EnableManifestOutput: Boolean class JobManifestGenerator(TypedDict, total=False): - S3JobManifestGenerator: Optional[S3JobManifestGenerator] + S3JobManifestGenerator: S3JobManifestGenerator | None class S3Tag(TypedDict, total=False): @@ -951,21 +1006,21 @@ class S3Tag(TypedDict, total=False): Value: TagValueString -S3TagSet = List[S3Tag] +S3TagSet = list[S3Tag] class JobManifestLocation(TypedDict, total=False): ObjectArn: S3KeyArnString - ObjectVersionId: Optional[S3ObjectVersionId] + ObjectVersionId: S3ObjectVersionId | None ETag: NonEmptyMaxLength1024String -JobManifestFieldList = List[JobManifestFieldName] +JobManifestFieldList = list[JobManifestFieldName] class JobManifestSpec(TypedDict, total=False): Format: JobManifestFormat - Fields: Optional[JobManifestFieldList] + Fields: JobManifestFieldList | None class JobManifest(TypedDict, total=False): @@ -974,11 +1029,30 @@ class JobManifest(TypedDict, total=False): class JobReport(TypedDict, total=False): - Bucket: Optional[S3BucketArnString] - Format: Optional[JobReportFormat] + Bucket: S3BucketArnString | None + Format: JobReportFormat | None Enabled: Boolean - Prefix: Optional[ReportPrefixString] - ReportScope: Optional[JobReportScope] + Prefix: ReportPrefixString | None + ReportScope: JobReportScope | None + ExpectedBucketOwner: AccountId | None + + +class S3UpdateObjectEncryptionSSEKMS(TypedDict, total=False): + KMSKeyArn: NonEmptyKmsKeyArnString + BucketKeyEnabled: Boolean | None + + +class ObjectEncryption(TypedDict, total=False): + SSEKMS: S3UpdateObjectEncryptionSSEKMS | None + + +class S3UpdateObjectEncryptionOperation(TypedDict, total=False): + ObjectEncryption: ObjectEncryption | None + + +class S3ComputeObjectChecksumOperation(TypedDict, total=False): + ChecksumAlgorithm: ComputeObjectChecksumAlgorithm | None + ChecksumType: ComputeObjectChecksumType | None class S3ReplicateObjectOperation(TypedDict, total=False): @@ -989,12 +1063,12 @@ class S3ReplicateObjectOperation(TypedDict, total=False): class S3Retention(TypedDict, total=False): - RetainUntilDate: Optional[TimeStamp] - Mode: Optional[S3ObjectLockRetentionMode] + RetainUntilDate: TimeStamp | None + Mode: S3ObjectLockRetentionMode | None class S3SetObjectRetentionOperation(TypedDict, total=False): - BypassGovernanceRetention: Optional[Boolean] + BypassGovernanceRetention: Boolean | None Retention: S3Retention @@ -1007,8 +1081,8 @@ class S3SetObjectLegalHoldOperation(TypedDict, total=False): class S3InitiateRestoreObjectOperation(TypedDict, total=False): - ExpirationInDays: Optional[S3ExpirationInDays] - GlacierJobTier: Optional[S3GlacierJobTier] + ExpirationInDays: S3ExpirationInDays | None + GlacierJobTier: S3GlacierJobTier | None class S3DeleteObjectTaggingOperation(TypedDict, total=False): @@ -1016,118 +1090,120 @@ class S3DeleteObjectTaggingOperation(TypedDict, total=False): class S3SetObjectTaggingOperation(TypedDict, total=False): - TagSet: Optional[S3TagSet] + TagSet: S3TagSet | None class S3Grantee(TypedDict, total=False): - TypeIdentifier: Optional[S3GranteeTypeIdentifier] - Identifier: Optional[NonEmptyMaxLength1024String] - DisplayName: Optional[NonEmptyMaxLength1024String] + TypeIdentifier: S3GranteeTypeIdentifier | None + Identifier: NonEmptyMaxLength1024String | None + DisplayName: NonEmptyMaxLength1024String | None class S3Grant(TypedDict, total=False): - Grantee: Optional[S3Grantee] - Permission: Optional[S3Permission] + Grantee: S3Grantee | None + Permission: S3Permission | None -S3GrantList = List[S3Grant] +S3GrantList = list[S3Grant] class S3ObjectOwner(TypedDict, total=False): - ID: Optional[NonEmptyMaxLength1024String] - DisplayName: Optional[NonEmptyMaxLength1024String] + ID: NonEmptyMaxLength1024String | None + DisplayName: NonEmptyMaxLength1024String | None class S3AccessControlList(TypedDict, total=False): Owner: S3ObjectOwner - Grants: Optional[S3GrantList] + Grants: S3GrantList | None class S3AccessControlPolicy(TypedDict, total=False): - AccessControlList: Optional[S3AccessControlList] - CannedAccessControlList: Optional[S3CannedAccessControlList] + AccessControlList: S3AccessControlList | None + CannedAccessControlList: S3CannedAccessControlList | None class S3SetObjectAclOperation(TypedDict, total=False): - AccessControlPolicy: Optional[S3AccessControlPolicy] + AccessControlPolicy: S3AccessControlPolicy | None S3ContentLength = int -S3UserMetadata = Dict[NonEmptyMaxLength1024String, MaxLength1024String] +S3UserMetadata = dict[NonEmptyMaxLength1024String, MaxLength1024String] class S3ObjectMetadata(TypedDict, total=False): - CacheControl: Optional[NonEmptyMaxLength1024String] - ContentDisposition: Optional[NonEmptyMaxLength1024String] - ContentEncoding: Optional[NonEmptyMaxLength1024String] - ContentLanguage: Optional[NonEmptyMaxLength1024String] - UserMetadata: Optional[S3UserMetadata] - ContentLength: Optional[S3ContentLength] - ContentMD5: Optional[NonEmptyMaxLength1024String] - ContentType: Optional[NonEmptyMaxLength1024String] - HttpExpiresDate: Optional[TimeStamp] - RequesterCharged: Optional[Boolean] - SSEAlgorithm: Optional[S3SSEAlgorithm] + CacheControl: NonEmptyMaxLength1024String | None + ContentDisposition: NonEmptyMaxLength1024String | None + ContentEncoding: NonEmptyMaxLength1024String | None + ContentLanguage: NonEmptyMaxLength1024String | None + UserMetadata: S3UserMetadata | None + ContentLength: S3ContentLength | None + ContentMD5: NonEmptyMaxLength1024String | None + ContentType: NonEmptyMaxLength1024String | None + HttpExpiresDate: TimeStamp | None + RequesterCharged: Boolean | None + SSEAlgorithm: S3SSEAlgorithm | None class S3CopyObjectOperation(TypedDict, total=False): - TargetResource: Optional[S3RegionalOrS3ExpressBucketArnString] - CannedAccessControlList: Optional[S3CannedAccessControlList] - AccessControlGrants: Optional[S3GrantList] - MetadataDirective: Optional[S3MetadataDirective] - ModifiedSinceConstraint: Optional[TimeStamp] - NewObjectMetadata: Optional[S3ObjectMetadata] - NewObjectTagging: Optional[S3TagSet] - RedirectLocation: Optional[NonEmptyMaxLength2048String] - RequesterPays: Optional[Boolean] - StorageClass: Optional[S3StorageClass] - UnModifiedSinceConstraint: Optional[TimeStamp] - SSEAwsKmsKeyId: Optional[KmsKeyArnString] - TargetKeyPrefix: Optional[NonEmptyMaxLength1024String] - ObjectLockLegalHoldStatus: Optional[S3ObjectLockLegalHoldStatus] - ObjectLockMode: Optional[S3ObjectLockMode] - ObjectLockRetainUntilDate: Optional[TimeStamp] - BucketKeyEnabled: Optional[Boolean] - ChecksumAlgorithm: Optional[S3ChecksumAlgorithm] - - -UserArguments = Dict[NonEmptyMaxLength64String, MaxLength1024String] + TargetResource: S3RegionalOrS3ExpressBucketArnString | None + CannedAccessControlList: S3CannedAccessControlList | None + AccessControlGrants: S3GrantList | None + MetadataDirective: S3MetadataDirective | None + ModifiedSinceConstraint: TimeStamp | None + NewObjectMetadata: S3ObjectMetadata | None + NewObjectTagging: S3TagSet | None + RedirectLocation: NonEmptyMaxLength2048String | None + RequesterPays: Boolean | None + StorageClass: S3StorageClass | None + UnModifiedSinceConstraint: TimeStamp | None + SSEAwsKmsKeyId: KmsKeyArnString | None + TargetKeyPrefix: NonEmptyMaxLength1024String | None + ObjectLockLegalHoldStatus: S3ObjectLockLegalHoldStatus | None + ObjectLockMode: S3ObjectLockMode | None + ObjectLockRetainUntilDate: TimeStamp | None + BucketKeyEnabled: Boolean | None + ChecksumAlgorithm: S3ChecksumAlgorithm | None + + +UserArguments = dict[NonEmptyMaxLength64String, MaxLength1024String] class LambdaInvokeOperation(TypedDict, total=False): - FunctionArn: Optional[FunctionArnString] - InvocationSchemaVersion: Optional[NonEmptyMaxLength64String] - UserArguments: Optional[UserArguments] + FunctionArn: FunctionArnString | None + InvocationSchemaVersion: NonEmptyMaxLength64String | None + UserArguments: UserArguments | None class JobOperation(TypedDict, total=False): - LambdaInvoke: Optional[LambdaInvokeOperation] - S3PutObjectCopy: Optional[S3CopyObjectOperation] - S3PutObjectAcl: Optional[S3SetObjectAclOperation] - S3PutObjectTagging: Optional[S3SetObjectTaggingOperation] - S3DeleteObjectTagging: Optional[S3DeleteObjectTaggingOperation] - S3InitiateRestoreObject: Optional[S3InitiateRestoreObjectOperation] - S3PutObjectLegalHold: Optional[S3SetObjectLegalHoldOperation] - S3PutObjectRetention: Optional[S3SetObjectRetentionOperation] - S3ReplicateObject: Optional[S3ReplicateObjectOperation] + LambdaInvoke: LambdaInvokeOperation | None + S3PutObjectCopy: S3CopyObjectOperation | None + S3PutObjectAcl: S3SetObjectAclOperation | None + S3PutObjectTagging: S3SetObjectTaggingOperation | None + S3DeleteObjectTagging: S3DeleteObjectTaggingOperation | None + S3InitiateRestoreObject: S3InitiateRestoreObjectOperation | None + S3PutObjectLegalHold: S3SetObjectLegalHoldOperation | None + S3PutObjectRetention: S3SetObjectRetentionOperation | None + S3ReplicateObject: S3ReplicateObjectOperation | None + S3ComputeObjectChecksum: S3ComputeObjectChecksumOperation | None + S3UpdateObjectEncryption: S3UpdateObjectEncryptionOperation | None class CreateJobRequest(ServiceRequest): AccountId: AccountId - ConfirmationRequired: Optional[ConfirmationRequired] + ConfirmationRequired: ConfirmationRequired | None Operation: JobOperation Report: JobReport ClientRequestToken: NonEmptyMaxLength64String - Manifest: Optional[JobManifest] - Description: Optional[NonEmptyMaxLength256String] + Manifest: JobManifest | None + Description: NonEmptyMaxLength256String | None Priority: JobPriority RoleArn: IAMRoleArn - Tags: Optional[S3TagSet] - ManifestGenerator: Optional[JobManifestGenerator] + Tags: S3TagSet | None + ManifestGenerator: JobManifestGenerator | None class CreateJobResult(TypedDict, total=False): - JobId: Optional[JobId] + JobId: JobId | None class CreateMultiRegionAccessPointRequest(ServiceRequest): @@ -1137,63 +1213,63 @@ class CreateMultiRegionAccessPointRequest(ServiceRequest): class CreateMultiRegionAccessPointResult(TypedDict, total=False): - RequestTokenARN: Optional[AsyncRequestTokenARN] + RequestTokenARN: AsyncRequestTokenARN | None ObjectSizeValue = int class MatchObjectSize(TypedDict, total=False): - BytesGreaterThan: Optional[ObjectSizeValue] - BytesLessThan: Optional[ObjectSizeValue] + BytesGreaterThan: ObjectSizeValue | None + BytesLessThan: ObjectSizeValue | None class MatchObjectAge(TypedDict, total=False): - DaysGreaterThan: Optional[ObjectAgeValue] - DaysLessThan: Optional[ObjectAgeValue] + DaysGreaterThan: ObjectAgeValue | None + DaysLessThan: ObjectAgeValue | None -MatchAnyTag = List[S3Tag] -MatchAnySuffix = List[Suffix] -MatchAnyPrefix = List[Prefix] +MatchAnyTag = list[S3Tag] +MatchAnySuffix = list[Suffix] +MatchAnyPrefix = list[Prefix] class StorageLensGroupOrOperator(TypedDict, total=False): - MatchAnyPrefix: Optional[MatchAnyPrefix] - MatchAnySuffix: Optional[MatchAnySuffix] - MatchAnyTag: Optional[MatchAnyTag] - MatchObjectAge: Optional[MatchObjectAge] - MatchObjectSize: Optional[MatchObjectSize] + MatchAnyPrefix: MatchAnyPrefix | None + MatchAnySuffix: MatchAnySuffix | None + MatchAnyTag: MatchAnyTag | None + MatchObjectAge: MatchObjectAge | None + MatchObjectSize: MatchObjectSize | None class StorageLensGroupAndOperator(TypedDict, total=False): - MatchAnyPrefix: Optional[MatchAnyPrefix] - MatchAnySuffix: Optional[MatchAnySuffix] - MatchAnyTag: Optional[MatchAnyTag] - MatchObjectAge: Optional[MatchObjectAge] - MatchObjectSize: Optional[MatchObjectSize] + MatchAnyPrefix: MatchAnyPrefix | None + MatchAnySuffix: MatchAnySuffix | None + MatchAnyTag: MatchAnyTag | None + MatchObjectAge: MatchObjectAge | None + MatchObjectSize: MatchObjectSize | None class StorageLensGroupFilter(TypedDict, total=False): - MatchAnyPrefix: Optional[MatchAnyPrefix] - MatchAnySuffix: Optional[MatchAnySuffix] - MatchAnyTag: Optional[MatchAnyTag] - MatchObjectAge: Optional[MatchObjectAge] - MatchObjectSize: Optional[MatchObjectSize] - And: Optional[StorageLensGroupAndOperator] - Or: Optional[StorageLensGroupOrOperator] + MatchAnyPrefix: MatchAnyPrefix | None + MatchAnySuffix: MatchAnySuffix | None + MatchAnyTag: MatchAnyTag | None + MatchObjectAge: MatchObjectAge | None + MatchObjectSize: MatchObjectSize | None + And: StorageLensGroupAndOperator | None + Or: StorageLensGroupOrOperator | None class StorageLensGroup(TypedDict, total=False): Name: StorageLensGroupName Filter: StorageLensGroupFilter - StorageLensGroupArn: Optional[StorageLensGroupArn] + StorageLensGroupArn: StorageLensGroupArn | None class CreateStorageLensGroupRequest(ServiceRequest): AccountId: AccountId StorageLensGroup: StorageLensGroup - Tags: Optional[TagList] + Tags: TagList | None CreationDate = datetime @@ -1201,10 +1277,10 @@ class CreateStorageLensGroupRequest(ServiceRequest): class Credentials(TypedDict, total=False): - AccessKeyId: Optional[AccessKeyId] - SecretAccessKey: Optional[SecretAccessKey] - SessionToken: Optional[SessionToken] - Expiration: Optional[Expiration] + AccessKeyId: AccessKeyId | None + SecretAccessKey: SecretAccessKey | None + SessionToken: SessionToken | None + Expiration: Expiration | None Date = datetime @@ -1298,7 +1374,7 @@ class DeleteMultiRegionAccessPointRequest(ServiceRequest): class DeleteMultiRegionAccessPointResult(TypedDict, total=False): - RequestTokenARN: Optional[AsyncRequestTokenARN] + RequestTokenARN: AsyncRequestTokenARN | None class DeletePublicAccessBlockRequest(ServiceRequest): @@ -1330,8 +1406,8 @@ class DescribeJobRequest(ServiceRequest): class S3GeneratedManifestDescriptor(TypedDict, total=False): - Format: Optional[GeneratedManifestFormat] - Location: Optional[JobManifestLocation] + Format: GeneratedManifestFormat | None + Location: JobManifestLocation | None SuspendedDate = datetime @@ -1340,16 +1416,16 @@ class S3GeneratedManifestDescriptor(TypedDict, total=False): class JobFailure(TypedDict, total=False): - FailureCode: Optional[JobFailureCode] - FailureReason: Optional[JobFailureReason] + FailureCode: JobFailureCode | None + FailureReason: JobFailureReason | None -JobFailureList = List[JobFailure] +JobFailureList = list[JobFailure] JobTimeInStateSeconds = int class JobTimers(TypedDict, total=False): - ElapsedTimeInActiveSeconds: Optional[JobTimeInStateSeconds] + ElapsedTimeInActiveSeconds: JobTimeInStateSeconds | None JobNumberOfTasksFailed = int @@ -1358,36 +1434,36 @@ class JobTimers(TypedDict, total=False): class JobProgressSummary(TypedDict, total=False): - TotalNumberOfTasks: Optional[JobTotalNumberOfTasks] - NumberOfTasksSucceeded: Optional[JobNumberOfTasksSucceeded] - NumberOfTasksFailed: Optional[JobNumberOfTasksFailed] - Timers: Optional[JobTimers] + TotalNumberOfTasks: JobTotalNumberOfTasks | None + NumberOfTasksSucceeded: JobNumberOfTasksSucceeded | None + NumberOfTasksFailed: JobNumberOfTasksFailed | None + Timers: JobTimers | None class JobDescriptor(TypedDict, total=False): - JobId: Optional[JobId] - ConfirmationRequired: Optional[ConfirmationRequired] - Description: Optional[NonEmptyMaxLength256String] - JobArn: Optional[JobArn] - Status: Optional[JobStatus] - Manifest: Optional[JobManifest] - Operation: Optional[JobOperation] - Priority: Optional[JobPriority] - ProgressSummary: Optional[JobProgressSummary] - StatusUpdateReason: Optional[JobStatusUpdateReason] - FailureReasons: Optional[JobFailureList] - Report: Optional[JobReport] - CreationTime: Optional[JobCreationTime] - TerminationDate: Optional[JobTerminationDate] - RoleArn: Optional[IAMRoleArn] - SuspendedDate: Optional[SuspendedDate] - SuspendedCause: Optional[SuspendedCause] - ManifestGenerator: Optional[JobManifestGenerator] - GeneratedManifestDescriptor: Optional[S3GeneratedManifestDescriptor] + JobId: JobId | None + ConfirmationRequired: ConfirmationRequired | None + Description: NonEmptyMaxLength256String | None + JobArn: JobArn | None + Status: JobStatus | None + Manifest: JobManifest | None + Operation: JobOperation | None + Priority: JobPriority | None + ProgressSummary: JobProgressSummary | None + StatusUpdateReason: JobStatusUpdateReason | None + FailureReasons: JobFailureList | None + Report: JobReport | None + CreationTime: JobCreationTime | None + TerminationDate: JobTerminationDate | None + RoleArn: IAMRoleArn | None + SuspendedDate: SuspendedDate | None + SuspendedCause: SuspendedCause | None + ManifestGenerator: JobManifestGenerator | None + GeneratedManifestDescriptor: S3GeneratedManifestDescriptor | None class DescribeJobResult(TypedDict, total=False): - Job: Optional[JobDescriptor] + Job: JobDescriptor | None class DescribeMultiRegionAccessPointOperationRequest(ServiceRequest): @@ -1396,20 +1472,20 @@ class DescribeMultiRegionAccessPointOperationRequest(ServiceRequest): class DescribeMultiRegionAccessPointOperationResult(TypedDict, total=False): - AsyncOperation: Optional[AsyncOperation] + AsyncOperation: AsyncOperation | None class ReplicationTimeValue(TypedDict, total=False): - Minutes: Optional[Minutes] + Minutes: Minutes | None class Metrics(TypedDict, total=False): Status: MetricsStatus - EventThreshold: Optional[ReplicationTimeValue] + EventThreshold: ReplicationTimeValue | None class EncryptionConfiguration(TypedDict, total=False): - ReplicaKmsKeyID: Optional[ReplicaKmsKeyID] + ReplicaKmsKeyID: ReplicaKmsKeyID | None class ReplicationTime(TypedDict, total=False): @@ -1418,32 +1494,32 @@ class ReplicationTime(TypedDict, total=False): class Destination(TypedDict, total=False): - Account: Optional[AccountId] + Account: AccountId | None Bucket: BucketIdentifierString - ReplicationTime: Optional[ReplicationTime] - AccessControlTranslation: Optional[AccessControlTranslation] - EncryptionConfiguration: Optional[EncryptionConfiguration] - Metrics: Optional[Metrics] - StorageClass: Optional[ReplicationStorageClass] + ReplicationTime: ReplicationTime | None + AccessControlTranslation: AccessControlTranslation | None + EncryptionConfiguration: EncryptionConfiguration | None + Metrics: Metrics | None + StorageClass: ReplicationStorageClass | None class DissociateAccessGrantsIdentityCenterRequest(ServiceRequest): AccountId: AccountId -Endpoints = Dict[NonEmptyMaxLength64String, NonEmptyMaxLength1024String] +Endpoints = dict[NonEmptyMaxLength64String, NonEmptyMaxLength1024String] class EstablishedMultiRegionAccessPointPolicy(TypedDict, total=False): - Policy: Optional[Policy] + Policy: Policy | None -Regions = List[S3AWSRegion] +Regions = list[S3AWSRegion] class Exclude(TypedDict, total=False): - Buckets: Optional[Buckets] - Regions: Optional[Regions] + Buckets: Buckets | None + Regions: Regions | None class ExistingObjectReplication(TypedDict, total=False): @@ -1456,15 +1532,15 @@ class GetAccessGrantRequest(ServiceRequest): class GetAccessGrantResult(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantId: Optional[AccessGrantId] - AccessGrantArn: Optional[AccessGrantArn] - Grantee: Optional[Grantee] - Permission: Optional[Permission] - AccessGrantsLocationId: Optional[AccessGrantsLocationId] - AccessGrantsLocationConfiguration: Optional[AccessGrantsLocationConfiguration] - GrantScope: Optional[S3Prefix] - ApplicationArn: Optional[IdentityCenterApplicationArn] + CreatedAt: CreationTimestamp | None + AccessGrantId: AccessGrantId | None + AccessGrantArn: AccessGrantArn | None + Grantee: Grantee | None + Permission: Permission | None + AccessGrantsLocationId: AccessGrantsLocationId | None + AccessGrantsLocationConfiguration: AccessGrantsLocationConfiguration | None + GrantScope: S3Prefix | None + ApplicationArn: IdentityCenterApplicationArn | None class GetAccessGrantsInstanceForPrefixRequest(ServiceRequest): @@ -1473,8 +1549,8 @@ class GetAccessGrantsInstanceForPrefixRequest(ServiceRequest): class GetAccessGrantsInstanceForPrefixResult(TypedDict, total=False): - AccessGrantsInstanceArn: Optional[AccessGrantsInstanceArn] - AccessGrantsInstanceId: Optional[AccessGrantsInstanceId] + AccessGrantsInstanceArn: AccessGrantsInstanceArn | None + AccessGrantsInstanceId: AccessGrantsInstanceId | None class GetAccessGrantsInstanceRequest(ServiceRequest): @@ -1486,18 +1562,18 @@ class GetAccessGrantsInstanceResourcePolicyRequest(ServiceRequest): class GetAccessGrantsInstanceResourcePolicyResult(TypedDict, total=False): - Policy: Optional[PolicyDocument] - Organization: Optional[Organization] - CreatedAt: Optional[CreationTimestamp] + Policy: PolicyDocument | None + Organization: Organization | None + CreatedAt: CreationTimestamp | None class GetAccessGrantsInstanceResult(TypedDict, total=False): - AccessGrantsInstanceArn: Optional[AccessGrantsInstanceArn] - AccessGrantsInstanceId: Optional[AccessGrantsInstanceId] - IdentityCenterArn: Optional[IdentityCenterArn] - IdentityCenterInstanceArn: Optional[IdentityCenterArn] - IdentityCenterApplicationArn: Optional[IdentityCenterApplicationArn] - CreatedAt: Optional[CreationTimestamp] + AccessGrantsInstanceArn: AccessGrantsInstanceArn | None + AccessGrantsInstanceId: AccessGrantsInstanceId | None + IdentityCenterArn: IdentityCenterArn | None + IdentityCenterInstanceArn: IdentityCenterArn | None + IdentityCenterApplicationArn: IdentityCenterApplicationArn | None + CreatedAt: CreationTimestamp | None class GetAccessGrantsLocationRequest(ServiceRequest): @@ -1506,11 +1582,11 @@ class GetAccessGrantsLocationRequest(ServiceRequest): class GetAccessGrantsLocationResult(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantsLocationId: Optional[AccessGrantsLocationId] - AccessGrantsLocationArn: Optional[AccessGrantsLocationArn] - LocationScope: Optional[S3Prefix] - IAMRoleArn: Optional[IAMRoleArn] + CreatedAt: CreationTimestamp | None + AccessGrantsLocationId: AccessGrantsLocationId | None + AccessGrantsLocationArn: AccessGrantsLocationArn | None + LocationScope: S3Prefix | None + IAMRoleArn: IAMRoleArn | None class GetAccessPointConfigurationForObjectLambdaRequest(ServiceRequest): @@ -1519,7 +1595,7 @@ class GetAccessPointConfigurationForObjectLambdaRequest(ServiceRequest): class GetAccessPointConfigurationForObjectLambdaResult(TypedDict, total=False): - Configuration: Optional[ObjectLambdaConfiguration] + Configuration: ObjectLambdaConfiguration | None class GetAccessPointForObjectLambdaRequest(ServiceRequest): @@ -1528,10 +1604,10 @@ class GetAccessPointForObjectLambdaRequest(ServiceRequest): class GetAccessPointForObjectLambdaResult(TypedDict, total=False): - Name: Optional[ObjectLambdaAccessPointName] - PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] - CreationDate: Optional[CreationDate] - Alias: Optional[ObjectLambdaAccessPointAlias] + Name: ObjectLambdaAccessPointName | None + PublicAccessBlockConfiguration: PublicAccessBlockConfiguration | None + CreationDate: CreationDate | None + Alias: ObjectLambdaAccessPointAlias | None class GetAccessPointPolicyForObjectLambdaRequest(ServiceRequest): @@ -1540,7 +1616,7 @@ class GetAccessPointPolicyForObjectLambdaRequest(ServiceRequest): class GetAccessPointPolicyForObjectLambdaResult(TypedDict, total=False): - Policy: Optional[ObjectLambdaPolicy] + Policy: ObjectLambdaPolicy | None class GetAccessPointPolicyRequest(ServiceRequest): @@ -1549,7 +1625,7 @@ class GetAccessPointPolicyRequest(ServiceRequest): class GetAccessPointPolicyResult(TypedDict, total=False): - Policy: Optional[Policy] + Policy: Policy | None class GetAccessPointPolicyStatusForObjectLambdaRequest(ServiceRequest): @@ -1558,11 +1634,11 @@ class GetAccessPointPolicyStatusForObjectLambdaRequest(ServiceRequest): class PolicyStatus(TypedDict, total=False): - IsPublic: Optional[IsPublic] + IsPublic: IsPublic | None class GetAccessPointPolicyStatusForObjectLambdaResult(TypedDict, total=False): - PolicyStatus: Optional[PolicyStatus] + PolicyStatus: PolicyStatus | None class GetAccessPointPolicyStatusRequest(ServiceRequest): @@ -1571,7 +1647,7 @@ class GetAccessPointPolicyStatusRequest(ServiceRequest): class GetAccessPointPolicyStatusResult(TypedDict, total=False): - PolicyStatus: Optional[PolicyStatus] + PolicyStatus: PolicyStatus | None class GetAccessPointRequest(ServiceRequest): @@ -1580,18 +1656,18 @@ class GetAccessPointRequest(ServiceRequest): class GetAccessPointResult(TypedDict, total=False): - Name: Optional[AccessPointName] - Bucket: Optional[AccessPointBucketName] - NetworkOrigin: Optional[NetworkOrigin] - VpcConfiguration: Optional[VpcConfiguration] - PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] - CreationDate: Optional[CreationDate] - Alias: Optional[Alias] - AccessPointArn: Optional[S3AccessPointArn] - Endpoints: Optional[Endpoints] - BucketAccountId: Optional[AccountId] - DataSourceId: Optional[DataSourceId] - DataSourceType: Optional[DataSourceType] + Name: AccessPointName | None + Bucket: AccessPointBucketName | None + NetworkOrigin: NetworkOrigin | None + VpcConfiguration: VpcConfiguration | None + PublicAccessBlockConfiguration: PublicAccessBlockConfiguration | None + CreationDate: CreationDate | None + Alias: Alias | None + AccessPointArn: S3AccessPointArn | None + Endpoints: Endpoints | None + BucketAccountId: AccountId | None + DataSourceId: DataSourceId | None + DataSourceType: DataSourceType | None class GetAccessPointScopeRequest(ServiceRequest): @@ -1600,7 +1676,7 @@ class GetAccessPointScopeRequest(ServiceRequest): class GetAccessPointScopeResult(TypedDict, total=False): - Scope: Optional[Scope] + Scope: Scope | None class GetBucketLifecycleConfigurationRequest(ServiceRequest): @@ -1609,64 +1685,64 @@ class GetBucketLifecycleConfigurationRequest(ServiceRequest): class NoncurrentVersionExpiration(TypedDict, total=False): - NoncurrentDays: Optional[Days] - NewerNoncurrentVersions: Optional[NoncurrentVersionCount] + NoncurrentDays: Days | None + NewerNoncurrentVersions: NoncurrentVersionCount | None class NoncurrentVersionTransition(TypedDict, total=False): - NoncurrentDays: Optional[Days] - StorageClass: Optional[TransitionStorageClass] + NoncurrentDays: Days | None + StorageClass: TransitionStorageClass | None -NoncurrentVersionTransitionList = List[NoncurrentVersionTransition] +NoncurrentVersionTransitionList = list[NoncurrentVersionTransition] class Transition(TypedDict, total=False): - Date: Optional[Date] - Days: Optional[Days] - StorageClass: Optional[TransitionStorageClass] + Date: Date | None + Days: Days | None + StorageClass: TransitionStorageClass | None -TransitionList = List[Transition] +TransitionList = list[Transition] class LifecycleRuleAndOperator(TypedDict, total=False): - Prefix: Optional[Prefix] - Tags: Optional[S3TagSet] - ObjectSizeGreaterThan: Optional[ObjectSizeGreaterThanBytes] - ObjectSizeLessThan: Optional[ObjectSizeLessThanBytes] + Prefix: Prefix | None + Tags: S3TagSet | None + ObjectSizeGreaterThan: ObjectSizeGreaterThanBytes | None + ObjectSizeLessThan: ObjectSizeLessThanBytes | None class LifecycleRuleFilter(TypedDict, total=False): - Prefix: Optional[Prefix] - Tag: Optional[S3Tag] - And: Optional[LifecycleRuleAndOperator] - ObjectSizeGreaterThan: Optional[ObjectSizeGreaterThanBytes] - ObjectSizeLessThan: Optional[ObjectSizeLessThanBytes] + Prefix: Prefix | None + Tag: S3Tag | None + And: LifecycleRuleAndOperator | None + ObjectSizeGreaterThan: ObjectSizeGreaterThanBytes | None + ObjectSizeLessThan: ObjectSizeLessThanBytes | None class LifecycleExpiration(TypedDict, total=False): - Date: Optional[Date] - Days: Optional[Days] - ExpiredObjectDeleteMarker: Optional[ExpiredObjectDeleteMarker] + Date: Date | None + Days: Days | None + ExpiredObjectDeleteMarker: ExpiredObjectDeleteMarker | None class LifecycleRule(TypedDict, total=False): - Expiration: Optional[LifecycleExpiration] - ID: Optional[ID] - Filter: Optional[LifecycleRuleFilter] + Expiration: LifecycleExpiration | None + ID: ID | None + Filter: LifecycleRuleFilter | None Status: ExpirationStatus - Transitions: Optional[TransitionList] - NoncurrentVersionTransitions: Optional[NoncurrentVersionTransitionList] - NoncurrentVersionExpiration: Optional[NoncurrentVersionExpiration] - AbortIncompleteMultipartUpload: Optional[AbortIncompleteMultipartUpload] + Transitions: TransitionList | None + NoncurrentVersionTransitions: NoncurrentVersionTransitionList | None + NoncurrentVersionExpiration: NoncurrentVersionExpiration | None + AbortIncompleteMultipartUpload: AbortIncompleteMultipartUpload | None -LifecycleRules = List[LifecycleRule] +LifecycleRules = list[LifecycleRule] class GetBucketLifecycleConfigurationResult(TypedDict, total=False): - Rules: Optional[LifecycleRules] + Rules: LifecycleRules | None class GetBucketPolicyRequest(ServiceRequest): @@ -1675,7 +1751,7 @@ class GetBucketPolicyRequest(ServiceRequest): class GetBucketPolicyResult(TypedDict, total=False): - Policy: Optional[Policy] + Policy: Policy | None class GetBucketReplicationRequest(ServiceRequest): @@ -1692,35 +1768,35 @@ class SseKmsEncryptedObjects(TypedDict, total=False): class SourceSelectionCriteria(TypedDict, total=False): - SseKmsEncryptedObjects: Optional[SseKmsEncryptedObjects] - ReplicaModifications: Optional[ReplicaModifications] + SseKmsEncryptedObjects: SseKmsEncryptedObjects | None + ReplicaModifications: ReplicaModifications | None class ReplicationRuleAndOperator(TypedDict, total=False): - Prefix: Optional[Prefix] - Tags: Optional[S3TagSet] + Prefix: Prefix | None + Tags: S3TagSet | None class ReplicationRuleFilter(TypedDict, total=False): - Prefix: Optional[Prefix] - Tag: Optional[S3Tag] - And: Optional[ReplicationRuleAndOperator] + Prefix: Prefix | None + Tag: S3Tag | None + And: ReplicationRuleAndOperator | None class ReplicationRule(TypedDict, total=False): - ID: Optional[ID] - Priority: Optional[Priority] - Prefix: Optional[Prefix] - Filter: Optional[ReplicationRuleFilter] + ID: ID | None + Priority: Priority | None + Prefix: Prefix | None + Filter: ReplicationRuleFilter | None Status: ReplicationRuleStatus - SourceSelectionCriteria: Optional[SourceSelectionCriteria] - ExistingObjectReplication: Optional[ExistingObjectReplication] + SourceSelectionCriteria: SourceSelectionCriteria | None + ExistingObjectReplication: ExistingObjectReplication | None Destination: Destination - DeleteMarkerReplication: Optional[DeleteMarkerReplication] + DeleteMarkerReplication: DeleteMarkerReplication | None Bucket: BucketIdentifierString -ReplicationRules = List[ReplicationRule] +ReplicationRules = list[ReplicationRule] class ReplicationConfiguration(TypedDict, total=False): @@ -1729,7 +1805,7 @@ class ReplicationConfiguration(TypedDict, total=False): class GetBucketReplicationResult(TypedDict, total=False): - ReplicationConfiguration: Optional[ReplicationConfiguration] + ReplicationConfiguration: ReplicationConfiguration | None class GetBucketRequest(ServiceRequest): @@ -1738,9 +1814,9 @@ class GetBucketRequest(ServiceRequest): class GetBucketResult(TypedDict, total=False): - Bucket: Optional[BucketName] - PublicAccessBlockEnabled: Optional[PublicAccessBlockEnabled] - CreationDate: Optional[CreationDate] + Bucket: BucketName | None + PublicAccessBlockEnabled: PublicAccessBlockEnabled | None + CreationDate: CreationDate | None class GetBucketTaggingRequest(ServiceRequest): @@ -1758,23 +1834,23 @@ class GetBucketVersioningRequest(ServiceRequest): class GetBucketVersioningResult(TypedDict, total=False): - Status: Optional[BucketVersioningStatus] - MFADelete: Optional[MFADeleteStatus] + Status: BucketVersioningStatus | None + MFADelete: MFADeleteStatus | None class GetDataAccessRequest(ServiceRequest): AccountId: AccountId Target: S3Prefix Permission: Permission - DurationSeconds: Optional[DurationSeconds] - Privilege: Optional[Privilege] - TargetType: Optional[S3PrefixType] + DurationSeconds: DurationSeconds | None + Privilege: Privilege | None + TargetType: S3PrefixType | None class GetDataAccessResult(TypedDict, total=False): - Credentials: Optional[Credentials] - MatchedGrantTarget: Optional[S3Prefix] - Grantee: Optional[Grantee] + Credentials: Credentials | None + MatchedGrantTarget: S3Prefix | None + Grantee: Grantee | None class GetJobTaggingRequest(ServiceRequest): @@ -1783,7 +1859,7 @@ class GetJobTaggingRequest(ServiceRequest): class GetJobTaggingResult(TypedDict, total=False): - Tags: Optional[S3TagSet] + Tags: S3TagSet | None class GetMultiRegionAccessPointPolicyRequest(ServiceRequest): @@ -1792,16 +1868,16 @@ class GetMultiRegionAccessPointPolicyRequest(ServiceRequest): class ProposedMultiRegionAccessPointPolicy(TypedDict, total=False): - Policy: Optional[Policy] + Policy: Policy | None class MultiRegionAccessPointPolicyDocument(TypedDict, total=False): - Established: Optional[EstablishedMultiRegionAccessPointPolicy] - Proposed: Optional[ProposedMultiRegionAccessPointPolicy] + Established: EstablishedMultiRegionAccessPointPolicy | None + Proposed: ProposedMultiRegionAccessPointPolicy | None class GetMultiRegionAccessPointPolicyResult(TypedDict, total=False): - Policy: Optional[MultiRegionAccessPointPolicyDocument] + Policy: MultiRegionAccessPointPolicyDocument | None class GetMultiRegionAccessPointPolicyStatusRequest(ServiceRequest): @@ -1810,7 +1886,7 @@ class GetMultiRegionAccessPointPolicyStatusRequest(ServiceRequest): class GetMultiRegionAccessPointPolicyStatusResult(TypedDict, total=False): - Established: Optional[PolicyStatus] + Established: PolicyStatus | None class GetMultiRegionAccessPointRequest(ServiceRequest): @@ -1819,25 +1895,25 @@ class GetMultiRegionAccessPointRequest(ServiceRequest): class RegionReport(TypedDict, total=False): - Bucket: Optional[BucketName] - Region: Optional[RegionName] - BucketAccountId: Optional[AccountId] + Bucket: BucketName | None + Region: RegionName | None + BucketAccountId: AccountId | None -RegionReportList = List[RegionReport] +RegionReportList = list[RegionReport] class MultiRegionAccessPointReport(TypedDict, total=False): - Name: Optional[MultiRegionAccessPointName] - Alias: Optional[MultiRegionAccessPointAlias] - CreatedAt: Optional[CreationTimestamp] - PublicAccessBlock: Optional[PublicAccessBlockConfiguration] - Status: Optional[MultiRegionAccessPointStatus] - Regions: Optional[RegionReportList] + Name: MultiRegionAccessPointName | None + Alias: MultiRegionAccessPointAlias | None + CreatedAt: CreationTimestamp | None + PublicAccessBlock: PublicAccessBlockConfiguration | None + Status: MultiRegionAccessPointStatus | None + Regions: RegionReportList | None class GetMultiRegionAccessPointResult(TypedDict, total=False): - AccessPoint: Optional[MultiRegionAccessPointReport] + AccessPoint: MultiRegionAccessPointReport | None class GetMultiRegionAccessPointRoutesRequest(ServiceRequest): @@ -1846,21 +1922,21 @@ class GetMultiRegionAccessPointRoutesRequest(ServiceRequest): class MultiRegionAccessPointRoute(TypedDict, total=False): - Bucket: Optional[BucketName] - Region: Optional[RegionName] + Bucket: BucketName | None + Region: RegionName | None TrafficDialPercentage: TrafficDialPercentage -RouteList = List[MultiRegionAccessPointRoute] +RouteList = list[MultiRegionAccessPointRoute] class GetMultiRegionAccessPointRoutesResult(TypedDict, total=False): - Mrap: Optional[MultiRegionAccessPointId] - Routes: Optional[RouteList] + Mrap: MultiRegionAccessPointId | None + Routes: RouteList | None class GetPublicAccessBlockOutput(TypedDict, total=False): - PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] + PublicAccessBlockConfiguration: PublicAccessBlockConfiguration | None class GetPublicAccessBlockRequest(ServiceRequest): @@ -1885,8 +1961,13 @@ class SSES3(TypedDict, total=False): class StorageLensDataExportEncryption(TypedDict, total=False): - SSES3: Optional[SSES3] - SSEKMS: Optional[SSEKMS] + SSES3: SSES3 | None + SSEKMS: SSEKMS | None + + +class StorageLensTableDestination(TypedDict, total=False): + IsEnabled: IsEnabled + Encryption: StorageLensDataExportEncryption | None class S3BucketDestination(TypedDict, total=False): @@ -1894,33 +1975,41 @@ class S3BucketDestination(TypedDict, total=False): OutputSchemaVersion: OutputSchemaVersion AccountId: AccountId Arn: S3BucketArnString - Prefix: Optional[Prefix] - Encryption: Optional[StorageLensDataExportEncryption] + Prefix: Prefix | None + Encryption: StorageLensDataExportEncryption | None + + +class StorageLensExpandedPrefixesDataExport(TypedDict, total=False): + S3BucketDestination: S3BucketDestination | None + StorageLensTableDestination: StorageLensTableDestination | None class StorageLensDataExport(TypedDict, total=False): - S3BucketDestination: Optional[S3BucketDestination] - CloudWatchMetrics: Optional[CloudWatchMetrics] + S3BucketDestination: S3BucketDestination | None + CloudWatchMetrics: CloudWatchMetrics | None + StorageLensTableDestination: StorageLensTableDestination | None class Include(TypedDict, total=False): - Buckets: Optional[Buckets] - Regions: Optional[Regions] + Buckets: Buckets | None + Regions: Regions | None class StorageLensConfiguration(TypedDict, total=False): Id: ConfigId AccountLevel: AccountLevel - Include: Optional[Include] - Exclude: Optional[Exclude] - DataExport: Optional[StorageLensDataExport] + Include: Include | None + Exclude: Exclude | None + DataExport: StorageLensDataExport | None + ExpandedPrefixesDataExport: StorageLensExpandedPrefixesDataExport | None IsEnabled: IsEnabled - AwsOrg: Optional[StorageLensAwsOrg] - StorageLensArn: Optional[StorageLensArn] + AwsOrg: StorageLensAwsOrg | None + StorageLensArn: StorageLensArn | None + PrefixDelimiter: StorageLensPrefixLevelDelimiter | None class GetStorageLensConfigurationResult(TypedDict, total=False): - StorageLensConfiguration: Optional[StorageLensConfiguration] + StorageLensConfiguration: StorageLensConfiguration | None class GetStorageLensConfigurationTaggingRequest(ServiceRequest): @@ -1933,11 +2022,11 @@ class StorageLensTag(TypedDict, total=False): Value: TagValueString -StorageLensTags = List[StorageLensTag] +StorageLensTags = list[StorageLensTag] class GetStorageLensConfigurationTaggingResult(TypedDict, total=False): - Tags: Optional[StorageLensTags] + Tags: StorageLensTags | None class GetStorageLensGroupRequest(ServiceRequest): @@ -1946,193 +2035,193 @@ class GetStorageLensGroupRequest(ServiceRequest): class GetStorageLensGroupResult(TypedDict, total=False): - StorageLensGroup: Optional[StorageLensGroup] + StorageLensGroup: StorageLensGroup | None class JobListDescriptor(TypedDict, total=False): - JobId: Optional[JobId] - Description: Optional[NonEmptyMaxLength256String] - Operation: Optional[OperationName] - Priority: Optional[JobPriority] - Status: Optional[JobStatus] - CreationTime: Optional[JobCreationTime] - TerminationDate: Optional[JobTerminationDate] - ProgressSummary: Optional[JobProgressSummary] + JobId: JobId | None + Description: NonEmptyMaxLength256String | None + Operation: OperationName | None + Priority: JobPriority | None + Status: JobStatus | None + CreationTime: JobCreationTime | None + TerminationDate: JobTerminationDate | None + ProgressSummary: JobProgressSummary | None -JobListDescriptorList = List[JobListDescriptor] -JobStatusList = List[JobStatus] +JobListDescriptorList = list[JobListDescriptor] +JobStatusList = list[JobStatus] class LifecycleConfiguration(TypedDict, total=False): - Rules: Optional[LifecycleRules] + Rules: LifecycleRules | None class ListAccessGrantsInstancesRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[ContinuationToken] - MaxResults: Optional[MaxResults] + NextToken: ContinuationToken | None + MaxResults: MaxResults | None class ListAccessGrantsInstancesResult(TypedDict, total=False): - NextToken: Optional[ContinuationToken] - AccessGrantsInstancesList: Optional[AccessGrantsInstancesList] + NextToken: ContinuationToken | None + AccessGrantsInstancesList: AccessGrantsInstancesList | None class ListAccessGrantsLocationsRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[ContinuationToken] - MaxResults: Optional[MaxResults] - LocationScope: Optional[S3Prefix] + NextToken: ContinuationToken | None + MaxResults: MaxResults | None + LocationScope: S3Prefix | None class ListAccessGrantsLocationsResult(TypedDict, total=False): - NextToken: Optional[ContinuationToken] - AccessGrantsLocationsList: Optional[AccessGrantsLocationsList] + NextToken: ContinuationToken | None + AccessGrantsLocationsList: AccessGrantsLocationsList | None class ListAccessGrantsRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[ContinuationToken] - MaxResults: Optional[MaxResults] - GranteeType: Optional[GranteeType] - GranteeIdentifier: Optional[GranteeIdentifier] - Permission: Optional[Permission] - GrantScope: Optional[S3Prefix] - ApplicationArn: Optional[IdentityCenterApplicationArn] + NextToken: ContinuationToken | None + MaxResults: MaxResults | None + GranteeType: GranteeType | None + GranteeIdentifier: GranteeIdentifier | None + Permission: Permission | None + GrantScope: S3Prefix | None + ApplicationArn: IdentityCenterApplicationArn | None class ListAccessGrantsResult(TypedDict, total=False): - NextToken: Optional[ContinuationToken] - AccessGrantsList: Optional[AccessGrantsList] + NextToken: ContinuationToken | None + AccessGrantsList: AccessGrantsList | None class ListAccessPointsForDirectoryBucketsRequest(ServiceRequest): AccountId: AccountId - DirectoryBucket: Optional[BucketName] - NextToken: Optional[NonEmptyMaxLength1024String] - MaxResults: Optional[MaxResults] + DirectoryBucket: BucketName | None + NextToken: NonEmptyMaxLength1024String | None + MaxResults: MaxResults | None class ListAccessPointsForDirectoryBucketsResult(TypedDict, total=False): - AccessPointList: Optional[AccessPointList] - NextToken: Optional[NonEmptyMaxLength1024String] + AccessPointList: AccessPointList | None + NextToken: NonEmptyMaxLength1024String | None class ListAccessPointsForObjectLambdaRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[NonEmptyMaxLength1024String] - MaxResults: Optional[MaxResults] + NextToken: NonEmptyMaxLength1024String | None + MaxResults: MaxResults | None class ObjectLambdaAccessPoint(TypedDict, total=False): Name: ObjectLambdaAccessPointName - ObjectLambdaAccessPointArn: Optional[ObjectLambdaAccessPointArn] - Alias: Optional[ObjectLambdaAccessPointAlias] + ObjectLambdaAccessPointArn: ObjectLambdaAccessPointArn | None + Alias: ObjectLambdaAccessPointAlias | None -ObjectLambdaAccessPointList = List[ObjectLambdaAccessPoint] +ObjectLambdaAccessPointList = list[ObjectLambdaAccessPoint] class ListAccessPointsForObjectLambdaResult(TypedDict, total=False): - ObjectLambdaAccessPointList: Optional[ObjectLambdaAccessPointList] - NextToken: Optional[NonEmptyMaxLength1024String] + ObjectLambdaAccessPointList: ObjectLambdaAccessPointList | None + NextToken: NonEmptyMaxLength1024String | None class ListAccessPointsRequest(ServiceRequest): AccountId: AccountId - Bucket: Optional[BucketName] - NextToken: Optional[NonEmptyMaxLength1024String] - MaxResults: Optional[MaxResults] - DataSourceId: Optional[DataSourceId] - DataSourceType: Optional[DataSourceType] + Bucket: BucketName | None + NextToken: NonEmptyMaxLength1024String | None + MaxResults: MaxResults | None + DataSourceId: DataSourceId | None + DataSourceType: DataSourceType | None class ListAccessPointsResult(TypedDict, total=False): - AccessPointList: Optional[AccessPointList] - NextToken: Optional[NonEmptyMaxLength1024String] + AccessPointList: AccessPointList | None + NextToken: NonEmptyMaxLength1024String | None class ListCallerAccessGrantsRequest(ServiceRequest): AccountId: AccountId - GrantScope: Optional[S3Prefix] - NextToken: Optional[ContinuationToken] - MaxResults: Optional[MaxResults] - AllowedByApplication: Optional[Boolean] + GrantScope: S3Prefix | None + NextToken: ContinuationToken | None + MaxResults: MaxResults | None + AllowedByApplication: Boolean | None class ListCallerAccessGrantsResult(TypedDict, total=False): - NextToken: Optional[ContinuationToken] - CallerAccessGrantsList: Optional[CallerAccessGrantsList] + NextToken: ContinuationToken | None + CallerAccessGrantsList: CallerAccessGrantsList | None class ListJobsRequest(ServiceRequest): AccountId: AccountId - JobStatuses: Optional[JobStatusList] - NextToken: Optional[StringForNextToken] - MaxResults: Optional[MaxResults] + JobStatuses: JobStatusList | None + NextToken: StringForNextToken | None + MaxResults: MaxResults | None class ListJobsResult(TypedDict, total=False): - NextToken: Optional[StringForNextToken] - Jobs: Optional[JobListDescriptorList] + NextToken: StringForNextToken | None + Jobs: JobListDescriptorList | None class ListMultiRegionAccessPointsRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[NonEmptyMaxLength1024String] - MaxResults: Optional[MaxResults] + NextToken: NonEmptyMaxLength1024String | None + MaxResults: MaxResults | None -MultiRegionAccessPointReportList = List[MultiRegionAccessPointReport] +MultiRegionAccessPointReportList = list[MultiRegionAccessPointReport] class ListMultiRegionAccessPointsResult(TypedDict, total=False): - AccessPoints: Optional[MultiRegionAccessPointReportList] - NextToken: Optional[NonEmptyMaxLength1024String] + AccessPoints: MultiRegionAccessPointReportList | None + NextToken: NonEmptyMaxLength1024String | None class ListRegionalBucketsRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[NonEmptyMaxLength1024String] - MaxResults: Optional[MaxResults] - OutpostId: Optional[NonEmptyMaxLength64String] + NextToken: NonEmptyMaxLength1024String | None + MaxResults: MaxResults | None + OutpostId: NonEmptyMaxLength64String | None class RegionalBucket(TypedDict, total=False): Bucket: BucketName - BucketArn: Optional[S3RegionalBucketArn] + BucketArn: S3RegionalBucketArn | None PublicAccessBlockEnabled: PublicAccessBlockEnabled CreationDate: CreationDate - OutpostId: Optional[NonEmptyMaxLength64String] + OutpostId: NonEmptyMaxLength64String | None -RegionalBucketList = List[RegionalBucket] +RegionalBucketList = list[RegionalBucket] class ListRegionalBucketsResult(TypedDict, total=False): - RegionalBucketList: Optional[RegionalBucketList] - NextToken: Optional[NonEmptyMaxLength1024String] + RegionalBucketList: RegionalBucketList | None + NextToken: NonEmptyMaxLength1024String | None class ListStorageLensConfigurationEntry(TypedDict, total=False): Id: ConfigId StorageLensArn: StorageLensArn HomeRegion: S3AWSRegion - IsEnabled: Optional[IsEnabled] + IsEnabled: IsEnabled | None class ListStorageLensConfigurationsRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[ContinuationToken] + NextToken: ContinuationToken | None -StorageLensConfigurationList = List[ListStorageLensConfigurationEntry] +StorageLensConfigurationList = list[ListStorageLensConfigurationEntry] class ListStorageLensConfigurationsResult(TypedDict, total=False): - NextToken: Optional[ContinuationToken] - StorageLensConfigurationList: Optional[StorageLensConfigurationList] + NextToken: ContinuationToken | None + StorageLensConfigurationList: StorageLensConfigurationList | None class ListStorageLensGroupEntry(TypedDict, total=False): @@ -2143,15 +2232,15 @@ class ListStorageLensGroupEntry(TypedDict, total=False): class ListStorageLensGroupsRequest(ServiceRequest): AccountId: AccountId - NextToken: Optional[ContinuationToken] + NextToken: ContinuationToken | None -StorageLensGroupList = List[ListStorageLensGroupEntry] +StorageLensGroupList = list[ListStorageLensGroupEntry] class ListStorageLensGroupsResult(TypedDict, total=False): - NextToken: Optional[ContinuationToken] - StorageLensGroupList: Optional[StorageLensGroupList] + NextToken: ContinuationToken | None + StorageLensGroupList: StorageLensGroupList | None class ListTagsForResourceRequest(ServiceRequest): @@ -2160,19 +2249,19 @@ class ListTagsForResourceRequest(ServiceRequest): class ListTagsForResourceResult(TypedDict, total=False): - Tags: Optional[TagList] + Tags: TagList | None class PutAccessGrantsInstanceResourcePolicyRequest(ServiceRequest): AccountId: AccountId Policy: PolicyDocument - Organization: Optional[Organization] + Organization: Organization | None class PutAccessGrantsInstanceResourcePolicyResult(TypedDict, total=False): - Policy: Optional[PolicyDocument] - Organization: Optional[Organization] - CreatedAt: Optional[CreationTimestamp] + Policy: PolicyDocument | None + Organization: Organization | None + CreatedAt: CreationTimestamp | None class PutAccessPointConfigurationForObjectLambdaRequest(ServiceRequest): @@ -2202,13 +2291,13 @@ class PutAccessPointScopeRequest(ServiceRequest): class PutBucketLifecycleConfigurationRequest(ServiceRequest): AccountId: AccountId Bucket: BucketName - LifecycleConfiguration: Optional[LifecycleConfiguration] + LifecycleConfiguration: LifecycleConfiguration | None class PutBucketPolicyRequest(ServiceRequest): AccountId: AccountId Bucket: BucketName - ConfirmRemoveSelfBucketAccess: Optional[ConfirmRemoveSelfBucketAccess] + ConfirmRemoveSelfBucketAccess: ConfirmRemoveSelfBucketAccess | None Policy: Policy @@ -2229,14 +2318,14 @@ class PutBucketTaggingRequest(ServiceRequest): class VersioningConfiguration(TypedDict, total=False): - MFADelete: Optional[MFADelete] - Status: Optional[BucketVersioningStatus] + MFADelete: MFADelete | None + Status: BucketVersioningStatus | None class PutBucketVersioningRequest(ServiceRequest): AccountId: AccountId Bucket: BucketName - MFA: Optional[MFA] + MFA: MFA | None VersioningConfiguration: VersioningConfiguration @@ -2257,7 +2346,7 @@ class PutMultiRegionAccessPointPolicyRequest(ServiceRequest): class PutMultiRegionAccessPointPolicyResult(TypedDict, total=False): - RequestTokenARN: Optional[AsyncRequestTokenARN] + RequestTokenARN: AsyncRequestTokenARN | None class PutPublicAccessBlockRequest(ServiceRequest): @@ -2269,7 +2358,7 @@ class PutStorageLensConfigurationRequest(ServiceRequest): ConfigId: ConfigId AccountId: AccountId StorageLensConfiguration: StorageLensConfiguration - Tags: Optional[StorageLensTags] + Tags: StorageLensTags | None class PutStorageLensConfigurationTaggingRequest(ServiceRequest): @@ -2292,7 +2381,7 @@ class SubmitMultiRegionAccessPointRoutesResult(TypedDict, total=False): pass -TagKeyList = List[TagKeyString] +TagKeyList = list[TagKeyString] class TagResourceRequest(ServiceRequest): @@ -2322,11 +2411,11 @@ class UpdateAccessGrantsLocationRequest(ServiceRequest): class UpdateAccessGrantsLocationResult(TypedDict, total=False): - CreatedAt: Optional[CreationTimestamp] - AccessGrantsLocationId: Optional[AccessGrantsLocationId] - AccessGrantsLocationArn: Optional[AccessGrantsLocationArn] - LocationScope: Optional[S3Prefix] - IAMRoleArn: Optional[IAMRoleArn] + CreatedAt: CreationTimestamp | None + AccessGrantsLocationId: AccessGrantsLocationId | None + AccessGrantsLocationArn: AccessGrantsLocationArn | None + LocationScope: S3Prefix | None + IAMRoleArn: IAMRoleArn | None class UpdateJobPriorityRequest(ServiceRequest): @@ -2344,13 +2433,13 @@ class UpdateJobStatusRequest(ServiceRequest): AccountId: AccountId JobId: JobId RequestedJobStatus: RequestedJobStatus - StatusUpdateReason: Optional[JobStatusUpdateReason] + StatusUpdateReason: JobStatusUpdateReason | None class UpdateJobStatusResult(TypedDict, total=False): - JobId: Optional[JobId] - Status: Optional[JobStatus] - StatusUpdateReason: Optional[JobStatusUpdateReason] + JobId: JobId | None + Status: JobStatus | None + StatusUpdateReason: JobStatusUpdateReason | None class UpdateStorageLensGroupRequest(ServiceRequest): @@ -2360,8 +2449,8 @@ class UpdateStorageLensGroupRequest(ServiceRequest): class S3ControlApi: - service = "s3control" - version = "2018-08-20" + service: str = "s3control" + version: str = "2018-08-20" @handler("AssociateAccessGrantsIdentityCenter") def associate_access_grants_identity_center( @@ -2423,6 +2512,7 @@ def create_access_point( public_access_block_configuration: PublicAccessBlockConfiguration | None = None, bucket_account_id: AccountId | None = None, scope: Scope | None = None, + tags: TagList | None = None, **kwargs, ) -> CreateAccessPointResult: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/scheduler/__init__.py b/localstack-core/localstack/aws/api/scheduler/__init__.py index 696814447cd11..d1695b15f6ff3 100644 --- a/localstack-core/localstack/aws/api/scheduler/__init__.py +++ b/localstack-core/localstack/aws/api/scheduler/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -132,23 +132,23 @@ class ValidationException(ServiceException): status_code: int = 400 -Subnets = List[Subnet] -SecurityGroups = List[SecurityGroup] +Subnets = list[Subnet] +SecurityGroups = list[SecurityGroup] class AwsVpcConfiguration(TypedDict, total=False): - AssignPublicIp: Optional[AssignPublicIp] - SecurityGroups: Optional[SecurityGroups] + AssignPublicIp: AssignPublicIp | None + SecurityGroups: SecurityGroups | None Subnets: Subnets class CapacityProviderStrategyItem(TypedDict, total=False): - base: Optional[CapacityProviderStrategyItemBase] + base: CapacityProviderStrategyItemBase | None capacityProvider: CapacityProvider - weight: Optional[CapacityProviderStrategyItemWeight] + weight: CapacityProviderStrategyItemWeight | None -CapacityProviderStrategy = List[CapacityProviderStrategyItem] +CapacityProviderStrategy = list[CapacityProviderStrategyItem] class Tag(TypedDict, total=False): @@ -156,13 +156,13 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] class CreateScheduleGroupInput(ServiceRequest): - ClientToken: Optional[ClientToken] + ClientToken: ClientToken | None Name: ScheduleGroupName - Tags: Optional[TagList] + Tags: TagList | None class CreateScheduleGroupOutput(TypedDict, total=False): @@ -170,7 +170,7 @@ class CreateScheduleGroupOutput(TypedDict, total=False): class SqsParameters(TypedDict, total=False): - MessageGroupId: Optional[MessageGroupId] + MessageGroupId: MessageGroupId | None class SageMakerPipelineParameter(TypedDict, total=False): @@ -178,16 +178,16 @@ class SageMakerPipelineParameter(TypedDict, total=False): Value: SageMakerPipelineParameterValue -SageMakerPipelineParameterList = List[SageMakerPipelineParameter] +SageMakerPipelineParameterList = list[SageMakerPipelineParameter] class SageMakerPipelineParameters(TypedDict, total=False): - PipelineParameterList: Optional[SageMakerPipelineParameterList] + PipelineParameterList: SageMakerPipelineParameterList | None class RetryPolicy(TypedDict, total=False): - MaximumEventAgeInSeconds: Optional[MaximumEventAgeInSeconds] - MaximumRetryAttempts: Optional[MaximumRetryAttempts] + MaximumEventAgeInSeconds: MaximumEventAgeInSeconds | None + MaximumRetryAttempts: MaximumRetryAttempts | None class KinesisParameters(TypedDict, total=False): @@ -199,71 +199,69 @@ class EventBridgeParameters(TypedDict, total=False): Source: Source -TagMap = Dict[TagKey, TagValue] -Tags = List[TagMap] -PlacementStrategy = TypedDict( - "PlacementStrategy", - { - "field": Optional[PlacementStrategyField], - "type": Optional[PlacementStrategyType], - }, - total=False, -) -PlacementStrategies = List[PlacementStrategy] -PlacementConstraint = TypedDict( - "PlacementConstraint", - { - "expression": Optional[PlacementConstraintExpression], - "type": Optional[PlacementConstraintType], - }, - total=False, -) -PlacementConstraints = List[PlacementConstraint] +TagMap = dict[TagKey, TagValue] +Tags = list[TagMap] + + +class PlacementStrategy(TypedDict, total=False): + field: PlacementStrategyField | None + type: PlacementStrategyType | None + + +PlacementStrategies = list[PlacementStrategy] + + +class PlacementConstraint(TypedDict, total=False): + expression: PlacementConstraintExpression | None + type: PlacementConstraintType | None + + +PlacementConstraints = list[PlacementConstraint] class NetworkConfiguration(TypedDict, total=False): - awsvpcConfiguration: Optional[AwsVpcConfiguration] + awsvpcConfiguration: AwsVpcConfiguration | None class EcsParameters(TypedDict, total=False): - CapacityProviderStrategy: Optional[CapacityProviderStrategy] - EnableECSManagedTags: Optional[EnableECSManagedTags] - EnableExecuteCommand: Optional[EnableExecuteCommand] - Group: Optional[Group] - LaunchType: Optional[LaunchType] - NetworkConfiguration: Optional[NetworkConfiguration] - PlacementConstraints: Optional[PlacementConstraints] - PlacementStrategy: Optional[PlacementStrategies] - PlatformVersion: Optional[PlatformVersion] - PropagateTags: Optional[PropagateTags] - ReferenceId: Optional[ReferenceId] - Tags: Optional[Tags] - TaskCount: Optional[TaskCount] + CapacityProviderStrategy: CapacityProviderStrategy | None + EnableECSManagedTags: EnableECSManagedTags | None + EnableExecuteCommand: EnableExecuteCommand | None + Group: Group | None + LaunchType: LaunchType | None + NetworkConfiguration: NetworkConfiguration | None + PlacementConstraints: PlacementConstraints | None + PlacementStrategy: PlacementStrategies | None + PlatformVersion: PlatformVersion | None + PropagateTags: PropagateTags | None + ReferenceId: ReferenceId | None + Tags: Tags | None + TaskCount: TaskCount | None TaskDefinitionArn: TaskDefinitionArn class DeadLetterConfig(TypedDict, total=False): - Arn: Optional[DeadLetterConfigArnString] + Arn: DeadLetterConfigArnString | None class Target(TypedDict, total=False): Arn: TargetArn - DeadLetterConfig: Optional[DeadLetterConfig] - EcsParameters: Optional[EcsParameters] - EventBridgeParameters: Optional[EventBridgeParameters] - Input: Optional[TargetInput] - KinesisParameters: Optional[KinesisParameters] - RetryPolicy: Optional[RetryPolicy] + DeadLetterConfig: DeadLetterConfig | None + EcsParameters: EcsParameters | None + EventBridgeParameters: EventBridgeParameters | None + Input: TargetInput | None + KinesisParameters: KinesisParameters | None + RetryPolicy: RetryPolicy | None RoleArn: RoleArn - SageMakerPipelineParameters: Optional[SageMakerPipelineParameters] - SqsParameters: Optional[SqsParameters] + SageMakerPipelineParameters: SageMakerPipelineParameters | None + SqsParameters: SqsParameters | None StartDate = datetime class FlexibleTimeWindow(TypedDict, total=False): - MaximumWindowInMinutes: Optional[MaximumWindowInMinutes] + MaximumWindowInMinutes: MaximumWindowInMinutes | None Mode: FlexibleTimeWindowMode @@ -271,18 +269,18 @@ class FlexibleTimeWindow(TypedDict, total=False): class CreateScheduleInput(ServiceRequest): - ActionAfterCompletion: Optional[ActionAfterCompletion] - ClientToken: Optional[ClientToken] - Description: Optional[Description] - EndDate: Optional[EndDate] + ActionAfterCompletion: ActionAfterCompletion | None + ClientToken: ClientToken | None + Description: Description | None + EndDate: EndDate | None FlexibleTimeWindow: FlexibleTimeWindow - GroupName: Optional[ScheduleGroupName] - KmsKeyArn: Optional[KmsKeyArn] + GroupName: ScheduleGroupName | None + KmsKeyArn: KmsKeyArn | None Name: Name ScheduleExpression: ScheduleExpression - ScheduleExpressionTimezone: Optional[ScheduleExpressionTimezone] - StartDate: Optional[StartDate] - State: Optional[ScheduleState] + ScheduleExpressionTimezone: ScheduleExpressionTimezone | None + StartDate: StartDate | None + State: ScheduleState | None Target: Target @@ -294,7 +292,7 @@ class CreateScheduleOutput(TypedDict, total=False): class DeleteScheduleGroupInput(ServiceRequest): - ClientToken: Optional[ClientToken] + ClientToken: ClientToken | None Name: ScheduleGroupName @@ -303,8 +301,8 @@ class DeleteScheduleGroupOutput(TypedDict, total=False): class DeleteScheduleInput(ServiceRequest): - ClientToken: Optional[ClientToken] - GroupName: Optional[ScheduleGroupName] + ClientToken: ClientToken | None + GroupName: ScheduleGroupName | None Name: Name @@ -320,64 +318,64 @@ class GetScheduleGroupInput(ServiceRequest): class GetScheduleGroupOutput(TypedDict, total=False): - Arn: Optional[ScheduleGroupArn] - CreationDate: Optional[CreationDate] - LastModificationDate: Optional[LastModificationDate] - Name: Optional[ScheduleGroupName] - State: Optional[ScheduleGroupState] + Arn: ScheduleGroupArn | None + CreationDate: CreationDate | None + LastModificationDate: LastModificationDate | None + Name: ScheduleGroupName | None + State: ScheduleGroupState | None class GetScheduleInput(ServiceRequest): - GroupName: Optional[ScheduleGroupName] + GroupName: ScheduleGroupName | None Name: Name class GetScheduleOutput(TypedDict, total=False): - ActionAfterCompletion: Optional[ActionAfterCompletion] - Arn: Optional[ScheduleArn] - CreationDate: Optional[CreationDate] - Description: Optional[Description] - EndDate: Optional[EndDate] - FlexibleTimeWindow: Optional[FlexibleTimeWindow] - GroupName: Optional[ScheduleGroupName] - KmsKeyArn: Optional[KmsKeyArn] - LastModificationDate: Optional[LastModificationDate] - Name: Optional[Name] - ScheduleExpression: Optional[ScheduleExpression] - ScheduleExpressionTimezone: Optional[ScheduleExpressionTimezone] - StartDate: Optional[StartDate] - State: Optional[ScheduleState] - Target: Optional[Target] + ActionAfterCompletion: ActionAfterCompletion | None + Arn: ScheduleArn | None + CreationDate: CreationDate | None + Description: Description | None + EndDate: EndDate | None + FlexibleTimeWindow: FlexibleTimeWindow | None + GroupName: ScheduleGroupName | None + KmsKeyArn: KmsKeyArn | None + LastModificationDate: LastModificationDate | None + Name: Name | None + ScheduleExpression: ScheduleExpression | None + ScheduleExpressionTimezone: ScheduleExpressionTimezone | None + StartDate: StartDate | None + State: ScheduleState | None + Target: Target | None class ListScheduleGroupsInput(ServiceRequest): - MaxResults: Optional[MaxResults] - NamePrefix: Optional[ScheduleGroupNamePrefix] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NamePrefix: ScheduleGroupNamePrefix | None + NextToken: NextToken | None class ScheduleGroupSummary(TypedDict, total=False): - Arn: Optional[ScheduleGroupArn] - CreationDate: Optional[CreationDate] - LastModificationDate: Optional[LastModificationDate] - Name: Optional[ScheduleGroupName] - State: Optional[ScheduleGroupState] + Arn: ScheduleGroupArn | None + CreationDate: CreationDate | None + LastModificationDate: LastModificationDate | None + Name: ScheduleGroupName | None + State: ScheduleGroupState | None -ScheduleGroupList = List[ScheduleGroupSummary] +ScheduleGroupList = list[ScheduleGroupSummary] class ListScheduleGroupsOutput(TypedDict, total=False): - NextToken: Optional[NextToken] + NextToken: NextToken | None ScheduleGroups: ScheduleGroupList class ListSchedulesInput(ServiceRequest): - GroupName: Optional[ScheduleGroupName] - MaxResults: Optional[MaxResults] - NamePrefix: Optional[NamePrefix] - NextToken: Optional[NextToken] - State: Optional[ScheduleState] + GroupName: ScheduleGroupName | None + MaxResults: MaxResults | None + NamePrefix: NamePrefix | None + NextToken: NextToken | None + State: ScheduleState | None class TargetSummary(TypedDict, total=False): @@ -385,20 +383,20 @@ class TargetSummary(TypedDict, total=False): class ScheduleSummary(TypedDict, total=False): - Arn: Optional[ScheduleArn] - CreationDate: Optional[CreationDate] - GroupName: Optional[ScheduleGroupName] - LastModificationDate: Optional[LastModificationDate] - Name: Optional[Name] - State: Optional[ScheduleState] - Target: Optional[TargetSummary] + Arn: ScheduleArn | None + CreationDate: CreationDate | None + GroupName: ScheduleGroupName | None + LastModificationDate: LastModificationDate | None + Name: Name | None + State: ScheduleState | None + Target: TargetSummary | None -ScheduleList = List[ScheduleSummary] +ScheduleList = list[ScheduleSummary] class ListSchedulesOutput(TypedDict, total=False): - NextToken: Optional[NextToken] + NextToken: NextToken | None Schedules: ScheduleList @@ -407,10 +405,10 @@ class ListTagsForResourceInput(ServiceRequest): class ListTagsForResourceOutput(TypedDict, total=False): - Tags: Optional[TagList] + Tags: TagList | None -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceInput(ServiceRequest): @@ -432,18 +430,18 @@ class UntagResourceOutput(TypedDict, total=False): class UpdateScheduleInput(ServiceRequest): - ActionAfterCompletion: Optional[ActionAfterCompletion] - ClientToken: Optional[ClientToken] - Description: Optional[Description] - EndDate: Optional[EndDate] + ActionAfterCompletion: ActionAfterCompletion | None + ClientToken: ClientToken | None + Description: Description | None + EndDate: EndDate | None FlexibleTimeWindow: FlexibleTimeWindow - GroupName: Optional[ScheduleGroupName] - KmsKeyArn: Optional[KmsKeyArn] + GroupName: ScheduleGroupName | None + KmsKeyArn: KmsKeyArn | None Name: Name ScheduleExpression: ScheduleExpression - ScheduleExpressionTimezone: Optional[ScheduleExpressionTimezone] - StartDate: Optional[StartDate] - State: Optional[ScheduleState] + ScheduleExpressionTimezone: ScheduleExpressionTimezone | None + StartDate: StartDate | None + State: ScheduleState | None Target: Target @@ -452,8 +450,8 @@ class UpdateScheduleOutput(TypedDict, total=False): class SchedulerApi: - service = "scheduler" - version = "2021-06-30" + service: str = "scheduler" + version: str = "2021-06-30" @handler("CreateSchedule") def create_schedule( diff --git a/localstack-core/localstack/aws/api/secretsmanager/__init__.py b/localstack-core/localstack/aws/api/secretsmanager/__init__.py index 7e4704d8f34ac..296220683a614 100644 --- a/localstack-core/localstack/aws/api/secretsmanager/__init__.py +++ b/localstack-core/localstack/aws/api/secretsmanager/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -15,11 +15,14 @@ ExcludeNumbersType = bool ExcludePunctuationType = bool ExcludeUppercaseType = bool +ExternalSecretRotationMetadataItemKeyType = str +ExternalSecretRotationMetadataItemValueType = str FilterValueStringType = str IncludeSpaceType = bool KmsKeyIdType = str MaxResultsBatchType = int MaxResultsType = int +MedeaTypeType = str NameType = str NextTokenType = str NonEmptyResourcePolicyType = str @@ -27,6 +30,7 @@ RandomPasswordType = str RegionType = str RequireEachIncludedTypeType = bool +RoleARNType = str RotationEnabledType = bool RotationLambdaARNType = str RotationTokenType = str @@ -52,6 +56,13 @@ class FilterNameStringType(StrEnum): all = "all" +class SortByType(StrEnum): + created_date = "created-date" + last_accessed_date = "last-accessed-date" + last_changed_date = "last-changed-date" + name = "name" + + class SortOrderType(StrEnum): asc = "asc" desc = "desc" @@ -136,62 +147,62 @@ class ResourceNotFoundException(ServiceException): class APIErrorType(TypedDict, total=False): - SecretId: Optional[SecretIdType] - ErrorCode: Optional[ErrorCode] - Message: Optional[ErrorMessage] + SecretId: SecretIdType | None + ErrorCode: ErrorCode | None + Message: ErrorMessage | None -APIErrorListType = List[APIErrorType] +APIErrorListType = list[APIErrorType] class ReplicaRegionType(TypedDict, total=False): - Region: Optional[RegionType] - KmsKeyId: Optional[KmsKeyIdType] + Region: RegionType | None + KmsKeyId: KmsKeyIdType | None -AddReplicaRegionListType = List[ReplicaRegionType] +AddReplicaRegionListType = list[ReplicaRegionType] AutomaticallyRotateAfterDaysType = int -FilterValuesStringList = List[FilterValueStringType] +FilterValuesStringList = list[FilterValueStringType] class Filter(TypedDict, total=False): - Key: Optional[FilterNameStringType] - Values: Optional[FilterValuesStringList] + Key: FilterNameStringType | None + Values: FilterValuesStringList | None -FiltersListType = List[Filter] -SecretIdListType = List[SecretIdType] +FiltersListType = list[Filter] +SecretIdListType = list[SecretIdType] class BatchGetSecretValueRequest(ServiceRequest): - SecretIdList: Optional[SecretIdListType] - Filters: Optional[FiltersListType] - MaxResults: Optional[MaxResultsBatchType] - NextToken: Optional[NextTokenType] + SecretIdList: SecretIdListType | None + Filters: FiltersListType | None + MaxResults: MaxResultsBatchType | None + NextToken: NextTokenType | None CreatedDateType = datetime -SecretVersionStagesType = List[SecretVersionStageType] +SecretVersionStagesType = list[SecretVersionStageType] SecretBinaryType = bytes class SecretValueEntry(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - VersionId: Optional[SecretVersionIdType] - SecretBinary: Optional[SecretBinaryType] - SecretString: Optional[SecretStringType] - VersionStages: Optional[SecretVersionStagesType] - CreatedDate: Optional[CreatedDateType] + ARN: SecretARNType | None + Name: SecretNameType | None + VersionId: SecretVersionIdType | None + SecretBinary: SecretBinaryType | None + SecretString: SecretStringType | None + VersionStages: SecretVersionStagesType | None + CreatedDate: CreatedDateType | None -SecretValuesType = List[SecretValueEntry] +SecretValuesType = list[SecretValueEntry] class BatchGetSecretValueResponse(TypedDict, total=False): - SecretValues: Optional[SecretValuesType] - NextToken: Optional[NextTokenType] - Errors: Optional[APIErrorListType] + SecretValues: SecretValuesType | None + NextToken: NextTokenType | None + Errors: APIErrorListType | None class CancelRotateSecretRequest(ServiceRequest): @@ -199,50 +210,51 @@ class CancelRotateSecretRequest(ServiceRequest): class CancelRotateSecretResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - VersionId: Optional[SecretVersionIdType] + ARN: SecretARNType | None + Name: SecretNameType | None + VersionId: SecretVersionIdType | None class Tag(TypedDict, total=False): - Key: Optional[TagKeyType] - Value: Optional[TagValueType] + Key: TagKeyType | None + Value: TagValueType | None -TagListType = List[Tag] +TagListType = list[Tag] class CreateSecretRequest(ServiceRequest): Name: NameType - ClientRequestToken: Optional[ClientRequestTokenType] - Description: Optional[DescriptionType] - KmsKeyId: Optional[KmsKeyIdType] - SecretBinary: Optional[SecretBinaryType] - SecretString: Optional[SecretStringType] - Tags: Optional[TagListType] - AddReplicaRegions: Optional[AddReplicaRegionListType] - ForceOverwriteReplicaSecret: Optional[BooleanType] + ClientRequestToken: ClientRequestTokenType | None + Description: DescriptionType | None + KmsKeyId: KmsKeyIdType | None + SecretBinary: SecretBinaryType | None + SecretString: SecretStringType | None + Tags: TagListType | None + AddReplicaRegions: AddReplicaRegionListType | None + ForceOverwriteReplicaSecret: BooleanType | None + Type: MedeaTypeType | None LastAccessedDateType = datetime class ReplicationStatusType(TypedDict, total=False): - Region: Optional[RegionType] - KmsKeyId: Optional[KmsKeyIdType] - Status: Optional[StatusType] - StatusMessage: Optional[StatusMessageType] - LastAccessedDate: Optional[LastAccessedDateType] + Region: RegionType | None + KmsKeyId: KmsKeyIdType | None + Status: StatusType | None + StatusMessage: StatusMessageType | None + LastAccessedDate: LastAccessedDateType | None -ReplicationStatusListType = List[ReplicationStatusType] +ReplicationStatusListType = list[ReplicationStatusType] class CreateSecretResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - VersionId: Optional[SecretVersionIdType] - ReplicationStatus: Optional[ReplicationStatusListType] + ARN: SecretARNType | None + Name: SecretNameType | None + VersionId: SecretVersionIdType | None + ReplicationStatus: ReplicationStatusListType | None class DeleteResourcePolicyRequest(ServiceRequest): @@ -250,8 +262,8 @@ class DeleteResourcePolicyRequest(ServiceRequest): class DeleteResourcePolicyResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[NameType] + ARN: SecretARNType | None + Name: NameType | None RecoveryWindowInDaysType = int @@ -259,17 +271,17 @@ class DeleteResourcePolicyResponse(TypedDict, total=False): class DeleteSecretRequest(ServiceRequest): SecretId: SecretIdType - RecoveryWindowInDays: Optional[RecoveryWindowInDaysType] - ForceDeleteWithoutRecovery: Optional[BooleanType] + RecoveryWindowInDays: RecoveryWindowInDaysType | None + ForceDeleteWithoutRecovery: BooleanType | None DeletionDateType = datetime class DeleteSecretResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - DeletionDate: Optional[DeletionDateType] + ARN: SecretARNType | None + Name: SecretNameType | None + DeletionDate: DeletionDateType | None DeletedDateType = datetime @@ -280,55 +292,66 @@ class DescribeSecretRequest(ServiceRequest): TimestampType = datetime -SecretVersionsToStagesMapType = Dict[SecretVersionIdType, SecretVersionStagesType] +SecretVersionsToStagesMapType = dict[SecretVersionIdType, SecretVersionStagesType] NextRotationDateType = datetime LastChangedDateType = datetime LastRotatedDateType = datetime +class ExternalSecretRotationMetadataItem(TypedDict, total=False): + Key: ExternalSecretRotationMetadataItemKeyType | None + Value: ExternalSecretRotationMetadataItemValueType | None + + +ExternalSecretRotationMetadataType = list[ExternalSecretRotationMetadataItem] + + class RotationRulesType(TypedDict, total=False): - AutomaticallyAfterDays: Optional[AutomaticallyRotateAfterDaysType] - Duration: Optional[DurationType] - ScheduleExpression: Optional[ScheduleExpressionType] + AutomaticallyAfterDays: AutomaticallyRotateAfterDaysType | None + Duration: DurationType | None + ScheduleExpression: ScheduleExpressionType | None class DescribeSecretResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - Description: Optional[DescriptionType] - KmsKeyId: Optional[KmsKeyIdType] - RotationEnabled: Optional[RotationEnabledType] - RotationLambdaARN: Optional[RotationLambdaARNType] - RotationRules: Optional[RotationRulesType] - LastRotatedDate: Optional[LastRotatedDateType] - LastChangedDate: Optional[LastChangedDateType] - LastAccessedDate: Optional[LastAccessedDateType] - DeletedDate: Optional[DeletedDateType] - NextRotationDate: Optional[NextRotationDateType] - Tags: Optional[TagListType] - VersionIdsToStages: Optional[SecretVersionsToStagesMapType] - OwningService: Optional[OwningServiceType] - CreatedDate: Optional[TimestampType] - PrimaryRegion: Optional[RegionType] - ReplicationStatus: Optional[ReplicationStatusListType] + ARN: SecretARNType | None + Name: SecretNameType | None + Type: MedeaTypeType | None + Description: DescriptionType | None + KmsKeyId: KmsKeyIdType | None + RotationEnabled: RotationEnabledType | None + RotationLambdaARN: RotationLambdaARNType | None + RotationRules: RotationRulesType | None + ExternalSecretRotationMetadata: ExternalSecretRotationMetadataType | None + ExternalSecretRotationRoleArn: RoleARNType | None + LastRotatedDate: LastRotatedDateType | None + LastChangedDate: LastChangedDateType | None + LastAccessedDate: LastAccessedDateType | None + DeletedDate: DeletedDateType | None + NextRotationDate: NextRotationDateType | None + Tags: TagListType | None + VersionIdsToStages: SecretVersionsToStagesMapType | None + OwningService: OwningServiceType | None + CreatedDate: TimestampType | None + PrimaryRegion: RegionType | None + ReplicationStatus: ReplicationStatusListType | None PasswordLengthType = int class GetRandomPasswordRequest(ServiceRequest): - PasswordLength: Optional[PasswordLengthType] - ExcludeCharacters: Optional[ExcludeCharactersType] - ExcludeNumbers: Optional[ExcludeNumbersType] - ExcludePunctuation: Optional[ExcludePunctuationType] - ExcludeUppercase: Optional[ExcludeUppercaseType] - ExcludeLowercase: Optional[ExcludeLowercaseType] - IncludeSpace: Optional[IncludeSpaceType] - RequireEachIncludedType: Optional[RequireEachIncludedTypeType] + PasswordLength: PasswordLengthType | None + ExcludeCharacters: ExcludeCharactersType | None + ExcludeNumbers: ExcludeNumbersType | None + ExcludePunctuation: ExcludePunctuationType | None + ExcludeUppercase: ExcludeUppercaseType | None + ExcludeLowercase: ExcludeLowercaseType | None + IncludeSpace: IncludeSpaceType | None + RequireEachIncludedType: RequireEachIncludedTypeType | None class GetRandomPasswordResponse(TypedDict, total=False): - RandomPassword: Optional[RandomPasswordType] + RandomPassword: RandomPasswordType | None class GetResourcePolicyRequest(ServiceRequest): @@ -336,119 +359,123 @@ class GetResourcePolicyRequest(ServiceRequest): class GetResourcePolicyResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[NameType] - ResourcePolicy: Optional[NonEmptyResourcePolicyType] + ARN: SecretARNType | None + Name: NameType | None + ResourcePolicy: NonEmptyResourcePolicyType | None class GetSecretValueRequest(ServiceRequest): SecretId: SecretIdType - VersionId: Optional[SecretVersionIdType] - VersionStage: Optional[SecretVersionStageType] + VersionId: SecretVersionIdType | None + VersionStage: SecretVersionStageType | None class GetSecretValueResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - VersionId: Optional[SecretVersionIdType] - SecretBinary: Optional[SecretBinaryType] - SecretString: Optional[SecretStringType] - VersionStages: Optional[SecretVersionStagesType] - CreatedDate: Optional[CreatedDateType] + ARN: SecretARNType | None + Name: SecretNameType | None + VersionId: SecretVersionIdType | None + SecretBinary: SecretBinaryType | None + SecretString: SecretStringType | None + VersionStages: SecretVersionStagesType | None + CreatedDate: CreatedDateType | None -KmsKeyIdListType = List[KmsKeyIdType] +KmsKeyIdListType = list[KmsKeyIdType] class ListSecretVersionIdsRequest(ServiceRequest): SecretId: SecretIdType - MaxResults: Optional[MaxResultsType] - NextToken: Optional[NextTokenType] - IncludeDeprecated: Optional[BooleanType] + MaxResults: MaxResultsType | None + NextToken: NextTokenType | None + IncludeDeprecated: BooleanType | None class SecretVersionsListEntry(TypedDict, total=False): - VersionId: Optional[SecretVersionIdType] - VersionStages: Optional[SecretVersionStagesType] - LastAccessedDate: Optional[LastAccessedDateType] - CreatedDate: Optional[CreatedDateType] - KmsKeyIds: Optional[KmsKeyIdListType] + VersionId: SecretVersionIdType | None + VersionStages: SecretVersionStagesType | None + LastAccessedDate: LastAccessedDateType | None + CreatedDate: CreatedDateType | None + KmsKeyIds: KmsKeyIdListType | None -SecretVersionsListType = List[SecretVersionsListEntry] +SecretVersionsListType = list[SecretVersionsListEntry] class ListSecretVersionIdsResponse(TypedDict, total=False): - Versions: Optional[SecretVersionsListType] - NextToken: Optional[NextTokenType] - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] + Versions: SecretVersionsListType | None + NextToken: NextTokenType | None + ARN: SecretARNType | None + Name: SecretNameType | None class ListSecretsRequest(ServiceRequest): - IncludePlannedDeletion: Optional[BooleanType] - MaxResults: Optional[MaxResultsType] - NextToken: Optional[NextTokenType] - Filters: Optional[FiltersListType] - SortOrder: Optional[SortOrderType] + IncludePlannedDeletion: BooleanType | None + MaxResults: MaxResultsType | None + NextToken: NextTokenType | None + Filters: FiltersListType | None + SortOrder: SortOrderType | None + SortBy: SortByType | None class SecretListEntry(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - Description: Optional[DescriptionType] - KmsKeyId: Optional[KmsKeyIdType] - RotationEnabled: Optional[RotationEnabledType] - RotationLambdaARN: Optional[RotationLambdaARNType] - RotationRules: Optional[RotationRulesType] - LastRotatedDate: Optional[LastRotatedDateType] - LastChangedDate: Optional[LastChangedDateType] - LastAccessedDate: Optional[LastAccessedDateType] - DeletedDate: Optional[DeletedDateType] - NextRotationDate: Optional[NextRotationDateType] - Tags: Optional[TagListType] - SecretVersionsToStages: Optional[SecretVersionsToStagesMapType] - OwningService: Optional[OwningServiceType] - CreatedDate: Optional[TimestampType] - PrimaryRegion: Optional[RegionType] - - -SecretListType = List[SecretListEntry] + ARN: SecretARNType | None + Name: SecretNameType | None + Type: MedeaTypeType | None + Description: DescriptionType | None + KmsKeyId: KmsKeyIdType | None + RotationEnabled: RotationEnabledType | None + RotationLambdaARN: RotationLambdaARNType | None + RotationRules: RotationRulesType | None + ExternalSecretRotationMetadata: ExternalSecretRotationMetadataType | None + ExternalSecretRotationRoleArn: RoleARNType | None + LastRotatedDate: LastRotatedDateType | None + LastChangedDate: LastChangedDateType | None + LastAccessedDate: LastAccessedDateType | None + DeletedDate: DeletedDateType | None + NextRotationDate: NextRotationDateType | None + Tags: TagListType | None + SecretVersionsToStages: SecretVersionsToStagesMapType | None + OwningService: OwningServiceType | None + CreatedDate: TimestampType | None + PrimaryRegion: RegionType | None + + +SecretListType = list[SecretListEntry] class ListSecretsResponse(TypedDict, total=False): - SecretList: Optional[SecretListType] - NextToken: Optional[NextTokenType] + SecretList: SecretListType | None + NextToken: NextTokenType | None class PutResourcePolicyRequest(ServiceRequest): SecretId: SecretIdType ResourcePolicy: NonEmptyResourcePolicyType - BlockPublicPolicy: Optional[BooleanType] + BlockPublicPolicy: BooleanType | None class PutResourcePolicyResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[NameType] + ARN: SecretARNType | None + Name: NameType | None class PutSecretValueRequest(ServiceRequest): SecretId: SecretIdType - ClientRequestToken: Optional[ClientRequestTokenType] - SecretBinary: Optional[SecretBinaryType] - SecretString: Optional[SecretStringType] - VersionStages: Optional[SecretVersionStagesType] - RotationToken: Optional[RotationTokenType] + ClientRequestToken: ClientRequestTokenType | None + SecretBinary: SecretBinaryType | None + SecretString: SecretStringType | None + VersionStages: SecretVersionStagesType | None + RotationToken: RotationTokenType | None class PutSecretValueResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - VersionId: Optional[SecretVersionIdType] - VersionStages: Optional[SecretVersionStagesType] + ARN: SecretARNType | None + Name: SecretNameType | None + VersionId: SecretVersionIdType | None + VersionStages: SecretVersionStagesType | None -RemoveReplicaRegionListType = List[RegionType] +RemoveReplicaRegionListType = list[RegionType] class RemoveRegionsFromReplicationRequest(ServiceRequest): @@ -457,19 +484,19 @@ class RemoveRegionsFromReplicationRequest(ServiceRequest): class RemoveRegionsFromReplicationResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - ReplicationStatus: Optional[ReplicationStatusListType] + ARN: SecretARNType | None + ReplicationStatus: ReplicationStatusListType | None class ReplicateSecretToRegionsRequest(ServiceRequest): SecretId: SecretIdType AddReplicaRegions: AddReplicaRegionListType - ForceOverwriteReplicaSecret: Optional[BooleanType] + ForceOverwriteReplicaSecret: BooleanType | None class ReplicateSecretToRegionsResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - ReplicationStatus: Optional[ReplicationStatusListType] + ARN: SecretARNType | None + ReplicationStatus: ReplicationStatusListType | None class RestoreSecretRequest(ServiceRequest): @@ -477,22 +504,24 @@ class RestoreSecretRequest(ServiceRequest): class RestoreSecretResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] + ARN: SecretARNType | None + Name: SecretNameType | None class RotateSecretRequest(ServiceRequest): SecretId: SecretIdType - ClientRequestToken: Optional[ClientRequestTokenType] - RotationLambdaARN: Optional[RotationLambdaARNType] - RotationRules: Optional[RotationRulesType] - RotateImmediately: Optional[BooleanType] + ClientRequestToken: ClientRequestTokenType | None + RotationLambdaARN: RotationLambdaARNType | None + RotationRules: RotationRulesType | None + ExternalSecretRotationMetadata: ExternalSecretRotationMetadataType | None + ExternalSecretRotationRoleArn: RoleARNType | None + RotateImmediately: BooleanType | None class RotateSecretResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - VersionId: Optional[SecretVersionIdType] + ARN: SecretARNType | None + Name: SecretNameType | None + VersionId: SecretVersionIdType | None class StopReplicationToReplicaRequest(ServiceRequest): @@ -500,10 +529,10 @@ class StopReplicationToReplicaRequest(ServiceRequest): class StopReplicationToReplicaResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] + ARN: SecretARNType | None -TagKeyListType = List[TagKeyType] +TagKeyListType = list[TagKeyType] class TagResourceRequest(ServiceRequest): @@ -518,52 +547,53 @@ class UntagResourceRequest(ServiceRequest): class UpdateSecretRequest(ServiceRequest): SecretId: SecretIdType - ClientRequestToken: Optional[ClientRequestTokenType] - Description: Optional[DescriptionType] - KmsKeyId: Optional[KmsKeyIdType] - SecretBinary: Optional[SecretBinaryType] - SecretString: Optional[SecretStringType] + ClientRequestToken: ClientRequestTokenType | None + Description: DescriptionType | None + KmsKeyId: KmsKeyIdType | None + SecretBinary: SecretBinaryType | None + SecretString: SecretStringType | None + Type: MedeaTypeType | None class UpdateSecretResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] - VersionId: Optional[SecretVersionIdType] + ARN: SecretARNType | None + Name: SecretNameType | None + VersionId: SecretVersionIdType | None class UpdateSecretVersionStageRequest(ServiceRequest): SecretId: SecretIdType VersionStage: SecretVersionStageType - RemoveFromVersionId: Optional[SecretVersionIdType] - MoveToVersionId: Optional[SecretVersionIdType] + RemoveFromVersionId: SecretVersionIdType | None + MoveToVersionId: SecretVersionIdType | None class UpdateSecretVersionStageResponse(TypedDict, total=False): - ARN: Optional[SecretARNType] - Name: Optional[SecretNameType] + ARN: SecretARNType | None + Name: SecretNameType | None class ValidateResourcePolicyRequest(ServiceRequest): - SecretId: Optional[SecretIdType] + SecretId: SecretIdType | None ResourcePolicy: NonEmptyResourcePolicyType class ValidationErrorsEntry(TypedDict, total=False): - CheckName: Optional[NameType] - ErrorMessage: Optional[ErrorMessage] + CheckName: NameType | None + ErrorMessage: ErrorMessage | None -ValidationErrorsType = List[ValidationErrorsEntry] +ValidationErrorsType = list[ValidationErrorsEntry] class ValidateResourcePolicyResponse(TypedDict, total=False): - PolicyValidationPassed: Optional[BooleanType] - ValidationErrors: Optional[ValidationErrorsType] + PolicyValidationPassed: BooleanType | None + ValidationErrors: ValidationErrorsType | None class SecretsmanagerApi: - service = "secretsmanager" - version = "2017-10-17" + service: str = "secretsmanager" + version: str = "2017-10-17" @handler("BatchGetSecretValue") def batch_get_secret_value( @@ -583,20 +613,9 @@ def cancel_rotate_secret( ) -> CancelRotateSecretResponse: raise NotImplementedError - @handler("CreateSecret") + @handler("CreateSecret", expand=False) def create_secret( - self, - context: RequestContext, - name: NameType, - client_request_token: ClientRequestTokenType | None = None, - description: DescriptionType | None = None, - kms_key_id: KmsKeyIdType | None = None, - secret_binary: SecretBinaryType | None = None, - secret_string: SecretStringType | None = None, - tags: TagListType | None = None, - add_replica_regions: AddReplicaRegionListType | None = None, - force_overwrite_replica_secret: BooleanType | None = None, - **kwargs, + self, context: RequestContext, request: CreateSecretRequest, **kwargs ) -> CreateSecretResponse: raise NotImplementedError @@ -677,6 +696,7 @@ def list_secrets( next_token: NextTokenType | None = None, filters: FiltersListType | None = None, sort_order: SortOrderType | None = None, + sort_by: SortByType | None = None, **kwargs, ) -> ListSecretsResponse: raise NotImplementedError @@ -741,6 +761,8 @@ def rotate_secret( client_request_token: ClientRequestTokenType | None = None, rotation_lambda_arn: RotationLambdaARNType | None = None, rotation_rules: RotationRulesType | None = None, + external_secret_rotation_metadata: ExternalSecretRotationMetadataType | None = None, + external_secret_rotation_role_arn: RoleARNType | None = None, rotate_immediately: BooleanType | None = None, **kwargs, ) -> RotateSecretResponse: @@ -764,17 +786,9 @@ def untag_resource( ) -> None: raise NotImplementedError - @handler("UpdateSecret") + @handler("UpdateSecret", expand=False) def update_secret( - self, - context: RequestContext, - secret_id: SecretIdType, - client_request_token: ClientRequestTokenType | None = None, - description: DescriptionType | None = None, - kms_key_id: KmsKeyIdType | None = None, - secret_binary: SecretBinaryType | None = None, - secret_string: SecretStringType | None = None, - **kwargs, + self, context: RequestContext, request: UpdateSecretRequest, **kwargs ) -> UpdateSecretResponse: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/ses/__init__.py b/localstack-core/localstack/aws/api/ses/__init__.py index 26e3b38f45cf1..6c62e8045f15e 100644 --- a/localstack-core/localstack/aws/api/ses/__init__.py +++ b/localstack-core/localstack/aws/api/ses/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -189,35 +189,35 @@ class AlreadyExistsException(ServiceException): code: str = "AlreadyExists" sender_fault: bool = True status_code: int = 400 - Name: Optional[RuleOrRuleSetName] + Name: RuleOrRuleSetName | None class CannotDeleteException(ServiceException): code: str = "CannotDelete" sender_fault: bool = True status_code: int = 400 - Name: Optional[RuleOrRuleSetName] + Name: RuleOrRuleSetName | None class ConfigurationSetAlreadyExistsException(ServiceException): code: str = "ConfigurationSetAlreadyExists" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] + ConfigurationSetName: ConfigurationSetName | None class ConfigurationSetDoesNotExistException(ServiceException): code: str = "ConfigurationSetDoesNotExist" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] + ConfigurationSetName: ConfigurationSetName | None class ConfigurationSetSendingPausedException(ServiceException): code: str = "ConfigurationSetSendingPausedException" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] + ConfigurationSetName: ConfigurationSetName | None class CustomVerificationEmailInvalidContentException(ServiceException): @@ -230,45 +230,45 @@ class CustomVerificationEmailTemplateAlreadyExistsException(ServiceException): code: str = "CustomVerificationEmailTemplateAlreadyExists" sender_fault: bool = True status_code: int = 400 - CustomVerificationEmailTemplateName: Optional[TemplateName] + CustomVerificationEmailTemplateName: TemplateName | None class CustomVerificationEmailTemplateDoesNotExistException(ServiceException): code: str = "CustomVerificationEmailTemplateDoesNotExist" sender_fault: bool = True status_code: int = 400 - CustomVerificationEmailTemplateName: Optional[TemplateName] + CustomVerificationEmailTemplateName: TemplateName | None class EventDestinationAlreadyExistsException(ServiceException): code: str = "EventDestinationAlreadyExists" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] - EventDestinationName: Optional[EventDestinationName] + ConfigurationSetName: ConfigurationSetName | None + EventDestinationName: EventDestinationName | None class EventDestinationDoesNotExistException(ServiceException): code: str = "EventDestinationDoesNotExist" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] - EventDestinationName: Optional[EventDestinationName] + ConfigurationSetName: ConfigurationSetName | None + EventDestinationName: EventDestinationName | None class FromEmailAddressNotVerifiedException(ServiceException): code: str = "FromEmailAddressNotVerified" sender_fault: bool = True status_code: int = 400 - FromEmailAddress: Optional[FromAddress] + FromEmailAddress: FromAddress | None class InvalidCloudWatchDestinationException(ServiceException): code: str = "InvalidCloudWatchDestination" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] - EventDestinationName: Optional[EventDestinationName] + ConfigurationSetName: ConfigurationSetName | None + EventDestinationName: EventDestinationName | None class InvalidConfigurationSetException(ServiceException): @@ -287,15 +287,15 @@ class InvalidFirehoseDestinationException(ServiceException): code: str = "InvalidFirehoseDestination" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] - EventDestinationName: Optional[EventDestinationName] + ConfigurationSetName: ConfigurationSetName | None + EventDestinationName: EventDestinationName | None class InvalidLambdaFunctionException(ServiceException): code: str = "InvalidLambdaFunction" sender_fault: bool = True status_code: int = 400 - FunctionArn: Optional[AmazonResourceName] + FunctionArn: AmazonResourceName | None class InvalidPolicyException(ServiceException): @@ -308,36 +308,36 @@ class InvalidRenderingParameterException(ServiceException): code: str = "InvalidRenderingParameter" sender_fault: bool = True status_code: int = 400 - TemplateName: Optional[TemplateName] + TemplateName: TemplateName | None class InvalidS3ConfigurationException(ServiceException): code: str = "InvalidS3Configuration" sender_fault: bool = True status_code: int = 400 - Bucket: Optional[S3BucketName] + Bucket: S3BucketName | None class InvalidSNSDestinationException(ServiceException): code: str = "InvalidSNSDestination" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] - EventDestinationName: Optional[EventDestinationName] + ConfigurationSetName: ConfigurationSetName | None + EventDestinationName: EventDestinationName | None class InvalidSnsTopicException(ServiceException): code: str = "InvalidSnsTopic" sender_fault: bool = True status_code: int = 400 - Topic: Optional[AmazonResourceName] + Topic: AmazonResourceName | None class InvalidTemplateException(ServiceException): code: str = "InvalidTemplate" sender_fault: bool = True status_code: int = 400 - TemplateName: Optional[TemplateName] + TemplateName: TemplateName | None class InvalidTrackingOptionsException(ServiceException): @@ -368,7 +368,7 @@ class MissingRenderingAttributeException(ServiceException): code: str = "MissingRenderingAttribute" sender_fault: bool = True status_code: int = 400 - TemplateName: Optional[TemplateName] + TemplateName: TemplateName | None class ProductionAccessNotGrantedException(ServiceException): @@ -381,35 +381,35 @@ class RuleDoesNotExistException(ServiceException): code: str = "RuleDoesNotExist" sender_fault: bool = True status_code: int = 400 - Name: Optional[RuleOrRuleSetName] + Name: RuleOrRuleSetName | None class RuleSetDoesNotExistException(ServiceException): code: str = "RuleSetDoesNotExist" sender_fault: bool = True status_code: int = 400 - Name: Optional[RuleOrRuleSetName] + Name: RuleOrRuleSetName | None class TemplateDoesNotExistException(ServiceException): code: str = "TemplateDoesNotExist" sender_fault: bool = True status_code: int = 400 - TemplateName: Optional[TemplateName] + TemplateName: TemplateName | None class TrackingOptionsAlreadyExistsException(ServiceException): code: str = "TrackingOptionsAlreadyExistsException" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] + ConfigurationSetName: ConfigurationSetName | None class TrackingOptionsDoesNotExistException(ServiceException): code: str = "TrackingOptionsDoesNotExistException" sender_fault: bool = True status_code: int = 400 - ConfigurationSetName: Optional[ConfigurationSetName] + ConfigurationSetName: ConfigurationSetName | None class AddHeaderAction(TypedDict, total=False): @@ -417,24 +417,24 @@ class AddHeaderAction(TypedDict, total=False): HeaderValue: HeaderValue -AddressList = List[Address] +AddressList = list[Address] ArrivalDate = datetime class Content(TypedDict, total=False): Data: MessageData - Charset: Optional[Charset] + Charset: Charset | None class Body(TypedDict, total=False): - Text: Optional[Content] - Html: Optional[Content] + Text: Content | None + Html: Content | None class BounceAction(TypedDict, total=False): - TopicArn: Optional[AmazonResourceName] + TopicArn: AmazonResourceName | None SmtpReplyCode: BounceSmtpReplyCode - StatusCode: Optional[BounceStatusCode] + StatusCode: BounceStatusCode | None Message: BounceMessage Sender: Address @@ -444,28 +444,28 @@ class ExtensionField(TypedDict, total=False): Value: ExtensionFieldValue -ExtensionFieldList = List[ExtensionField] +ExtensionFieldList = list[ExtensionField] LastAttemptDate = datetime class RecipientDsnFields(TypedDict, total=False): - FinalRecipient: Optional[Address] + FinalRecipient: Address | None Action: DsnAction - RemoteMta: Optional[RemoteMta] + RemoteMta: RemoteMta | None Status: DsnStatus - DiagnosticCode: Optional[DiagnosticCode] - LastAttemptDate: Optional[LastAttemptDate] - ExtensionFields: Optional[ExtensionFieldList] + DiagnosticCode: DiagnosticCode | None + LastAttemptDate: LastAttemptDate | None + ExtensionFields: ExtensionFieldList | None class BouncedRecipientInfo(TypedDict, total=False): Recipient: Address - RecipientArn: Optional[AmazonResourceName] - BounceType: Optional[BounceType] - RecipientDsnFields: Optional[RecipientDsnFields] + RecipientArn: AmazonResourceName | None + BounceType: BounceType | None + RecipientDsnFields: RecipientDsnFields | None -BouncedRecipientInfoList = List[BouncedRecipientInfo] +BouncedRecipientInfoList = list[BouncedRecipientInfo] class MessageTag(TypedDict, total=False): @@ -473,31 +473,31 @@ class MessageTag(TypedDict, total=False): Value: MessageTagValue -MessageTagList = List[MessageTag] +MessageTagList = list[MessageTag] class Destination(TypedDict, total=False): - ToAddresses: Optional[AddressList] - CcAddresses: Optional[AddressList] - BccAddresses: Optional[AddressList] + ToAddresses: AddressList | None + CcAddresses: AddressList | None + BccAddresses: AddressList | None class BulkEmailDestination(TypedDict, total=False): Destination: Destination - ReplacementTags: Optional[MessageTagList] - ReplacementTemplateData: Optional[TemplateData] + ReplacementTags: MessageTagList | None + ReplacementTemplateData: TemplateData | None -BulkEmailDestinationList = List[BulkEmailDestination] +BulkEmailDestinationList = list[BulkEmailDestination] class BulkEmailDestinationStatus(TypedDict, total=False): - Status: Optional[BulkEmailStatus] - Error: Optional[Error] - MessageId: Optional[MessageId] + Status: BulkEmailStatus | None + Error: Error | None + MessageId: MessageId | None -BulkEmailDestinationStatusList = List[BulkEmailDestinationStatus] +BulkEmailDestinationStatusList = list[BulkEmailDestinationStatus] class CloneReceiptRuleSetRequest(ServiceRequest): @@ -515,7 +515,7 @@ class CloudWatchDimensionConfiguration(TypedDict, total=False): DefaultDimensionValue: DefaultDimensionValue -CloudWatchDimensionConfigurations = List[CloudWatchDimensionConfiguration] +CloudWatchDimensionConfigurations = list[CloudWatchDimensionConfiguration] class CloudWatchDestination(TypedDict, total=False): @@ -526,8 +526,8 @@ class ConfigurationSet(TypedDict, total=False): Name: ConfigurationSetName -ConfigurationSetAttributeList = List[ConfigurationSetAttribute] -ConfigurationSets = List[ConfigurationSet] +ConfigurationSetAttributeList = list[ConfigurationSetAttribute] +ConfigurationSets = list[ConfigurationSet] class ConnectAction(TypedDict, total=False): @@ -547,16 +547,16 @@ class KinesisFirehoseDestination(TypedDict, total=False): DeliveryStreamARN: AmazonResourceName -EventTypes = List[EventType] +EventTypes = list[EventType] class EventDestination(TypedDict, total=False): Name: EventDestinationName - Enabled: Optional[Enabled] + Enabled: Enabled | None MatchingEventTypes: EventTypes - KinesisFirehoseDestination: Optional[KinesisFirehoseDestination] - CloudWatchDestination: Optional[CloudWatchDestination] - SNSDestination: Optional[SNSDestination] + KinesisFirehoseDestination: KinesisFirehoseDestination | None + CloudWatchDestination: CloudWatchDestination | None + SNSDestination: SNSDestination | None class CreateConfigurationSetEventDestinationRequest(ServiceRequest): @@ -577,7 +577,7 @@ class CreateConfigurationSetResponse(TypedDict, total=False): class TrackingOptions(TypedDict, total=False): - CustomRedirectDomain: Optional[CustomRedirectDomain] + CustomRedirectDomain: CustomRedirectDomain | None class CreateConfigurationSetTrackingOptionsRequest(ServiceRequest): @@ -618,60 +618,60 @@ class CreateReceiptFilterResponse(TypedDict, total=False): class SNSAction(TypedDict, total=False): TopicArn: AmazonResourceName - Encoding: Optional[SNSActionEncoding] + Encoding: SNSActionEncoding | None class StopAction(TypedDict, total=False): Scope: StopScope - TopicArn: Optional[AmazonResourceName] + TopicArn: AmazonResourceName | None class LambdaAction(TypedDict, total=False): - TopicArn: Optional[AmazonResourceName] + TopicArn: AmazonResourceName | None FunctionArn: AmazonResourceName - InvocationType: Optional[InvocationType] + InvocationType: InvocationType | None class WorkmailAction(TypedDict, total=False): - TopicArn: Optional[AmazonResourceName] + TopicArn: AmazonResourceName | None OrganizationArn: AmazonResourceName class S3Action(TypedDict, total=False): - TopicArn: Optional[AmazonResourceName] + TopicArn: AmazonResourceName | None BucketName: S3BucketName - ObjectKeyPrefix: Optional[S3KeyPrefix] - KmsKeyArn: Optional[AmazonResourceName] - IamRoleArn: Optional[IAMRoleARN] + ObjectKeyPrefix: S3KeyPrefix | None + KmsKeyArn: AmazonResourceName | None + IamRoleArn: IAMRoleARN | None class ReceiptAction(TypedDict, total=False): - S3Action: Optional[S3Action] - BounceAction: Optional[BounceAction] - WorkmailAction: Optional[WorkmailAction] - LambdaAction: Optional[LambdaAction] - StopAction: Optional[StopAction] - AddHeaderAction: Optional[AddHeaderAction] - SNSAction: Optional[SNSAction] - ConnectAction: Optional[ConnectAction] + S3Action: S3Action | None + BounceAction: BounceAction | None + WorkmailAction: WorkmailAction | None + LambdaAction: LambdaAction | None + StopAction: StopAction | None + AddHeaderAction: AddHeaderAction | None + SNSAction: SNSAction | None + ConnectAction: ConnectAction | None -ReceiptActionsList = List[ReceiptAction] -RecipientsList = List[Recipient] +ReceiptActionsList = list[ReceiptAction] +RecipientsList = list[Recipient] class ReceiptRule(TypedDict, total=False): Name: ReceiptRuleName - Enabled: Optional[Enabled] - TlsPolicy: Optional[TlsPolicy] - Recipients: Optional[RecipientsList] - Actions: Optional[ReceiptActionsList] - ScanEnabled: Optional[Enabled] + Enabled: Enabled | None + TlsPolicy: TlsPolicy | None + Recipients: RecipientsList | None + Actions: ReceiptActionsList | None + ScanEnabled: Enabled | None class CreateReceiptRuleRequest(ServiceRequest): RuleSetName: ReceiptRuleSetName - After: Optional[ReceiptRuleName] + After: ReceiptRuleName | None Rule: ReceiptRule @@ -689,9 +689,9 @@ class CreateReceiptRuleSetResponse(TypedDict, total=False): class Template(TypedDict, total=False): TemplateName: TemplateName - SubjectPart: Optional[SubjectPart] - TextPart: Optional[TextPart] - HtmlPart: Optional[HtmlPart] + SubjectPart: SubjectPart | None + TextPart: TextPart | None + HtmlPart: HtmlPart | None class CreateTemplateRequest(ServiceRequest): @@ -703,14 +703,14 @@ class CreateTemplateResponse(TypedDict, total=False): class CustomVerificationEmailTemplate(TypedDict, total=False): - TemplateName: Optional[TemplateName] - FromEmailAddress: Optional[FromAddress] - TemplateSubject: Optional[Subject] - SuccessRedirectionURL: Optional[SuccessRedirectionURL] - FailureRedirectionURL: Optional[FailureRedirectionURL] + TemplateName: TemplateName | None + FromEmailAddress: FromAddress | None + TemplateSubject: Subject | None + SuccessRedirectionURL: SuccessRedirectionURL | None + FailureRedirectionURL: FailureRedirectionURL | None -CustomVerificationEmailTemplates = List[CustomVerificationEmailTemplate] +CustomVerificationEmailTemplates = list[CustomVerificationEmailTemplate] class DeleteConfigurationSetEventDestinationRequest(ServiceRequest): @@ -797,50 +797,50 @@ class DeleteVerifiedEmailAddressRequest(ServiceRequest): class DeliveryOptions(TypedDict, total=False): - TlsPolicy: Optional[TlsPolicy] + TlsPolicy: TlsPolicy | None class DescribeActiveReceiptRuleSetRequest(ServiceRequest): pass -ReceiptRulesList = List[ReceiptRule] +ReceiptRulesList = list[ReceiptRule] Timestamp = datetime class ReceiptRuleSetMetadata(TypedDict, total=False): - Name: Optional[ReceiptRuleSetName] - CreatedTimestamp: Optional[Timestamp] + Name: ReceiptRuleSetName | None + CreatedTimestamp: Timestamp | None class DescribeActiveReceiptRuleSetResponse(TypedDict, total=False): - Metadata: Optional[ReceiptRuleSetMetadata] - Rules: Optional[ReceiptRulesList] + Metadata: ReceiptRuleSetMetadata | None + Rules: ReceiptRulesList | None class DescribeConfigurationSetRequest(ServiceRequest): ConfigurationSetName: ConfigurationSetName - ConfigurationSetAttributeNames: Optional[ConfigurationSetAttributeList] + ConfigurationSetAttributeNames: ConfigurationSetAttributeList | None LastFreshStart = datetime class ReputationOptions(TypedDict, total=False): - SendingEnabled: Optional[Enabled] - ReputationMetricsEnabled: Optional[Enabled] - LastFreshStart: Optional[LastFreshStart] + SendingEnabled: Enabled | None + ReputationMetricsEnabled: Enabled | None + LastFreshStart: LastFreshStart | None -EventDestinations = List[EventDestination] +EventDestinations = list[EventDestination] class DescribeConfigurationSetResponse(TypedDict, total=False): - ConfigurationSet: Optional[ConfigurationSet] - EventDestinations: Optional[EventDestinations] - TrackingOptions: Optional[TrackingOptions] - DeliveryOptions: Optional[DeliveryOptions] - ReputationOptions: Optional[ReputationOptions] + ConfigurationSet: ConfigurationSet | None + EventDestinations: EventDestinations | None + TrackingOptions: TrackingOptions | None + DeliveryOptions: DeliveryOptions | None + ReputationOptions: ReputationOptions | None class DescribeReceiptRuleRequest(ServiceRequest): @@ -849,7 +849,7 @@ class DescribeReceiptRuleRequest(ServiceRequest): class DescribeReceiptRuleResponse(TypedDict, total=False): - Rule: Optional[ReceiptRule] + Rule: ReceiptRule | None class DescribeReceiptRuleSetRequest(ServiceRequest): @@ -857,24 +857,24 @@ class DescribeReceiptRuleSetRequest(ServiceRequest): class DescribeReceiptRuleSetResponse(TypedDict, total=False): - Metadata: Optional[ReceiptRuleSetMetadata] - Rules: Optional[ReceiptRulesList] + Metadata: ReceiptRuleSetMetadata | None + Rules: ReceiptRulesList | None -VerificationTokenList = List[VerificationToken] +VerificationTokenList = list[VerificationToken] class IdentityDkimAttributes(TypedDict, total=False): DkimEnabled: Enabled DkimVerificationStatus: VerificationStatus - DkimTokens: Optional[VerificationTokenList] + DkimTokens: VerificationTokenList | None -DkimAttributes = Dict[Identity, IdentityDkimAttributes] +DkimAttributes = dict[Identity, IdentityDkimAttributes] class GetAccountSendingEnabledResponse(TypedDict, total=False): - Enabled: Optional[Enabled] + Enabled: Enabled | None class GetCustomVerificationEmailTemplateRequest(ServiceRequest): @@ -882,15 +882,15 @@ class GetCustomVerificationEmailTemplateRequest(ServiceRequest): class GetCustomVerificationEmailTemplateResponse(TypedDict, total=False): - TemplateName: Optional[TemplateName] - FromEmailAddress: Optional[FromAddress] - TemplateSubject: Optional[Subject] - TemplateContent: Optional[TemplateContent] - SuccessRedirectionURL: Optional[SuccessRedirectionURL] - FailureRedirectionURL: Optional[FailureRedirectionURL] + TemplateName: TemplateName | None + FromEmailAddress: FromAddress | None + TemplateSubject: Subject | None + TemplateContent: TemplateContent | None + SuccessRedirectionURL: SuccessRedirectionURL | None + FailureRedirectionURL: FailureRedirectionURL | None -IdentityList = List[Identity] +IdentityList = list[Identity] class GetIdentityDkimAttributesRequest(ServiceRequest): @@ -911,7 +911,7 @@ class IdentityMailFromDomainAttributes(TypedDict, total=False): BehaviorOnMXFailure: BehaviorOnMXFailure -MailFromDomainAttributes = Dict[Identity, IdentityMailFromDomainAttributes] +MailFromDomainAttributes = dict[Identity, IdentityMailFromDomainAttributes] class GetIdentityMailFromDomainAttributesResponse(TypedDict, total=False): @@ -927,19 +927,19 @@ class IdentityNotificationAttributes(TypedDict, total=False): ComplaintTopic: NotificationTopic DeliveryTopic: NotificationTopic ForwardingEnabled: Enabled - HeadersInBounceNotificationsEnabled: Optional[Enabled] - HeadersInComplaintNotificationsEnabled: Optional[Enabled] - HeadersInDeliveryNotificationsEnabled: Optional[Enabled] + HeadersInBounceNotificationsEnabled: Enabled | None + HeadersInComplaintNotificationsEnabled: Enabled | None + HeadersInDeliveryNotificationsEnabled: Enabled | None -NotificationAttributes = Dict[Identity, IdentityNotificationAttributes] +NotificationAttributes = dict[Identity, IdentityNotificationAttributes] class GetIdentityNotificationAttributesResponse(TypedDict, total=False): NotificationAttributes: NotificationAttributes -PolicyNameList = List[PolicyName] +PolicyNameList = list[PolicyName] class GetIdentityPoliciesRequest(ServiceRequest): @@ -947,7 +947,7 @@ class GetIdentityPoliciesRequest(ServiceRequest): PolicyNames: PolicyNameList -PolicyMap = Dict[PolicyName, Policy] +PolicyMap = dict[PolicyName, Policy] class GetIdentityPoliciesResponse(TypedDict, total=False): @@ -960,10 +960,10 @@ class GetIdentityVerificationAttributesRequest(ServiceRequest): class IdentityVerificationAttributes(TypedDict, total=False): VerificationStatus: VerificationStatus - VerificationToken: Optional[VerificationToken] + VerificationToken: VerificationToken | None -VerificationAttributes = Dict[Identity, IdentityVerificationAttributes] +VerificationAttributes = dict[Identity, IdentityVerificationAttributes] class GetIdentityVerificationAttributesResponse(TypedDict, total=False): @@ -971,24 +971,24 @@ class GetIdentityVerificationAttributesResponse(TypedDict, total=False): class GetSendQuotaResponse(TypedDict, total=False): - Max24HourSend: Optional[Max24HourSend] - MaxSendRate: Optional[MaxSendRate] - SentLast24Hours: Optional[SentLast24Hours] + Max24HourSend: Max24HourSend | None + MaxSendRate: MaxSendRate | None + SentLast24Hours: SentLast24Hours | None class SendDataPoint(TypedDict, total=False): - Timestamp: Optional[Timestamp] - DeliveryAttempts: Optional[Counter] - Bounces: Optional[Counter] - Complaints: Optional[Counter] - Rejects: Optional[Counter] + Timestamp: Timestamp | None + DeliveryAttempts: Counter | None + Bounces: Counter | None + Complaints: Counter | None + Rejects: Counter | None -SendDataPointList = List[SendDataPoint] +SendDataPointList = list[SendDataPoint] class GetSendStatisticsResponse(TypedDict, total=False): - SendDataPoints: Optional[SendDataPointList] + SendDataPoints: SendDataPointList | None class GetTemplateRequest(ServiceRequest): @@ -996,38 +996,38 @@ class GetTemplateRequest(ServiceRequest): class GetTemplateResponse(TypedDict, total=False): - Template: Optional[Template] + Template: Template | None class ListConfigurationSetsRequest(ServiceRequest): - NextToken: Optional[NextToken] - MaxItems: Optional[MaxItems] + NextToken: NextToken | None + MaxItems: MaxItems | None class ListConfigurationSetsResponse(TypedDict, total=False): - ConfigurationSets: Optional[ConfigurationSets] - NextToken: Optional[NextToken] + ConfigurationSets: ConfigurationSets | None + NextToken: NextToken | None class ListCustomVerificationEmailTemplatesRequest(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + NextToken: NextToken | None + MaxResults: MaxResults | None class ListCustomVerificationEmailTemplatesResponse(TypedDict, total=False): - CustomVerificationEmailTemplates: Optional[CustomVerificationEmailTemplates] - NextToken: Optional[NextToken] + CustomVerificationEmailTemplates: CustomVerificationEmailTemplates | None + NextToken: NextToken | None class ListIdentitiesRequest(ServiceRequest): - IdentityType: Optional[IdentityType] - NextToken: Optional[NextToken] - MaxItems: Optional[MaxItems] + IdentityType: IdentityType | None + NextToken: NextToken | None + MaxItems: MaxItems | None class ListIdentitiesResponse(TypedDict, total=False): Identities: IdentityList - NextToken: Optional[NextToken] + NextToken: NextToken | None class ListIdentityPoliciesRequest(ServiceRequest): @@ -1042,45 +1042,45 @@ class ListReceiptFiltersRequest(ServiceRequest): pass -ReceiptFilterList = List[ReceiptFilter] +ReceiptFilterList = list[ReceiptFilter] class ListReceiptFiltersResponse(TypedDict, total=False): - Filters: Optional[ReceiptFilterList] + Filters: ReceiptFilterList | None class ListReceiptRuleSetsRequest(ServiceRequest): - NextToken: Optional[NextToken] + NextToken: NextToken | None -ReceiptRuleSetsLists = List[ReceiptRuleSetMetadata] +ReceiptRuleSetsLists = list[ReceiptRuleSetMetadata] class ListReceiptRuleSetsResponse(TypedDict, total=False): - RuleSets: Optional[ReceiptRuleSetsLists] - NextToken: Optional[NextToken] + RuleSets: ReceiptRuleSetsLists | None + NextToken: NextToken | None class ListTemplatesRequest(ServiceRequest): - NextToken: Optional[NextToken] - MaxItems: Optional[MaxItems] + NextToken: NextToken | None + MaxItems: MaxItems | None class TemplateMetadata(TypedDict, total=False): - Name: Optional[TemplateName] - CreatedTimestamp: Optional[Timestamp] + Name: TemplateName | None + CreatedTimestamp: Timestamp | None -TemplateMetadataList = List[TemplateMetadata] +TemplateMetadataList = list[TemplateMetadata] class ListTemplatesResponse(TypedDict, total=False): - TemplatesMetadata: Optional[TemplateMetadataList] - NextToken: Optional[NextToken] + TemplatesMetadata: TemplateMetadataList | None + NextToken: NextToken | None class ListVerifiedEmailAddressesResponse(TypedDict, total=False): - VerifiedEmailAddresses: Optional[AddressList] + VerifiedEmailAddresses: AddressList | None class Message(TypedDict, total=False): @@ -1090,13 +1090,13 @@ class Message(TypedDict, total=False): class MessageDsn(TypedDict, total=False): ReportingMta: ReportingMta - ArrivalDate: Optional[ArrivalDate] - ExtensionFields: Optional[ExtensionFieldList] + ArrivalDate: ArrivalDate | None + ExtensionFields: ExtensionFieldList | None class PutConfigurationSetDeliveryOptionsRequest(ServiceRequest): ConfigurationSetName: ConfigurationSetName - DeliveryOptions: Optional[DeliveryOptions] + DeliveryOptions: DeliveryOptions | None class PutConfigurationSetDeliveryOptionsResponse(TypedDict, total=False): @@ -1120,7 +1120,7 @@ class RawMessage(TypedDict, total=False): Data: RawMessageData -ReceiptRuleNamesList = List[ReceiptRuleName] +ReceiptRuleNamesList = list[ReceiptRuleName] class ReorderReceiptRuleSetRequest(ServiceRequest): @@ -1135,26 +1135,26 @@ class ReorderReceiptRuleSetResponse(TypedDict, total=False): class SendBounceRequest(ServiceRequest): OriginalMessageId: MessageId BounceSender: Address - Explanation: Optional[Explanation] - MessageDsn: Optional[MessageDsn] + Explanation: Explanation | None + MessageDsn: MessageDsn | None BouncedRecipientInfoList: BouncedRecipientInfoList - BounceSenderArn: Optional[AmazonResourceName] + BounceSenderArn: AmazonResourceName | None class SendBounceResponse(TypedDict, total=False): - MessageId: Optional[MessageId] + MessageId: MessageId | None class SendBulkTemplatedEmailRequest(ServiceRequest): Source: Address - SourceArn: Optional[AmazonResourceName] - ReplyToAddresses: Optional[AddressList] - ReturnPath: Optional[Address] - ReturnPathArn: Optional[AmazonResourceName] - ConfigurationSetName: Optional[ConfigurationSetName] - DefaultTags: Optional[MessageTagList] + SourceArn: AmazonResourceName | None + ReplyToAddresses: AddressList | None + ReturnPath: Address | None + ReturnPathArn: AmazonResourceName | None + ConfigurationSetName: ConfigurationSetName | None + DefaultTags: MessageTagList | None Template: TemplateName - TemplateArn: Optional[AmazonResourceName] + TemplateArn: AmazonResourceName | None DefaultTemplateData: TemplateData Destinations: BulkEmailDestinationList @@ -1166,23 +1166,23 @@ class SendBulkTemplatedEmailResponse(TypedDict, total=False): class SendCustomVerificationEmailRequest(ServiceRequest): EmailAddress: Address TemplateName: TemplateName - ConfigurationSetName: Optional[ConfigurationSetName] + ConfigurationSetName: ConfigurationSetName | None class SendCustomVerificationEmailResponse(TypedDict, total=False): - MessageId: Optional[MessageId] + MessageId: MessageId | None class SendEmailRequest(ServiceRequest): Source: Address Destination: Destination Message: Message - ReplyToAddresses: Optional[AddressList] - ReturnPath: Optional[Address] - SourceArn: Optional[AmazonResourceName] - ReturnPathArn: Optional[AmazonResourceName] - Tags: Optional[MessageTagList] - ConfigurationSetName: Optional[ConfigurationSetName] + ReplyToAddresses: AddressList | None + ReturnPath: Address | None + SourceArn: AmazonResourceName | None + ReturnPathArn: AmazonResourceName | None + Tags: MessageTagList | None + ConfigurationSetName: ConfigurationSetName | None class SendEmailResponse(TypedDict, total=False): @@ -1190,14 +1190,14 @@ class SendEmailResponse(TypedDict, total=False): class SendRawEmailRequest(ServiceRequest): - Source: Optional[Address] - Destinations: Optional[AddressList] + Source: Address | None + Destinations: AddressList | None RawMessage: RawMessage - FromArn: Optional[AmazonResourceName] - SourceArn: Optional[AmazonResourceName] - ReturnPathArn: Optional[AmazonResourceName] - Tags: Optional[MessageTagList] - ConfigurationSetName: Optional[ConfigurationSetName] + FromArn: AmazonResourceName | None + SourceArn: AmazonResourceName | None + ReturnPathArn: AmazonResourceName | None + Tags: MessageTagList | None + ConfigurationSetName: ConfigurationSetName | None class SendRawEmailResponse(TypedDict, total=False): @@ -1207,14 +1207,14 @@ class SendRawEmailResponse(TypedDict, total=False): class SendTemplatedEmailRequest(ServiceRequest): Source: Address Destination: Destination - ReplyToAddresses: Optional[AddressList] - ReturnPath: Optional[Address] - SourceArn: Optional[AmazonResourceName] - ReturnPathArn: Optional[AmazonResourceName] - Tags: Optional[MessageTagList] - ConfigurationSetName: Optional[ConfigurationSetName] + ReplyToAddresses: AddressList | None + ReturnPath: Address | None + SourceArn: AmazonResourceName | None + ReturnPathArn: AmazonResourceName | None + Tags: MessageTagList | None + ConfigurationSetName: ConfigurationSetName | None Template: TemplateName - TemplateArn: Optional[AmazonResourceName] + TemplateArn: AmazonResourceName | None TemplateData: TemplateData @@ -1223,7 +1223,7 @@ class SendTemplatedEmailResponse(TypedDict, total=False): class SetActiveReceiptRuleSetRequest(ServiceRequest): - RuleSetName: Optional[ReceiptRuleSetName] + RuleSetName: ReceiptRuleSetName | None class SetActiveReceiptRuleSetResponse(TypedDict, total=False): @@ -1260,8 +1260,8 @@ class SetIdentityHeadersInNotificationsEnabledResponse(TypedDict, total=False): class SetIdentityMailFromDomainRequest(ServiceRequest): Identity: Identity - MailFromDomain: Optional[MailFromDomainName] - BehaviorOnMXFailure: Optional[BehaviorOnMXFailure] + MailFromDomain: MailFromDomainName | None + BehaviorOnMXFailure: BehaviorOnMXFailure | None class SetIdentityMailFromDomainResponse(TypedDict, total=False): @@ -1271,7 +1271,7 @@ class SetIdentityMailFromDomainResponse(TypedDict, total=False): class SetIdentityNotificationTopicRequest(ServiceRequest): Identity: Identity NotificationType: NotificationType - SnsTopic: Optional[NotificationTopic] + SnsTopic: NotificationTopic | None class SetIdentityNotificationTopicResponse(TypedDict, total=False): @@ -1281,7 +1281,7 @@ class SetIdentityNotificationTopicResponse(TypedDict, total=False): class SetReceiptRulePositionRequest(ServiceRequest): RuleSetName: ReceiptRuleSetName RuleName: ReceiptRuleName - After: Optional[ReceiptRuleName] + After: ReceiptRuleName | None class SetReceiptRulePositionResponse(TypedDict, total=False): @@ -1294,11 +1294,11 @@ class TestRenderTemplateRequest(ServiceRequest): class TestRenderTemplateResponse(TypedDict, total=False): - RenderedTemplate: Optional[RenderedTemplate] + RenderedTemplate: RenderedTemplate | None class UpdateAccountSendingEnabledRequest(ServiceRequest): - Enabled: Optional[Enabled] + Enabled: Enabled | None class UpdateConfigurationSetEventDestinationRequest(ServiceRequest): @@ -1331,11 +1331,11 @@ class UpdateConfigurationSetTrackingOptionsResponse(TypedDict, total=False): class UpdateCustomVerificationEmailTemplateRequest(ServiceRequest): TemplateName: TemplateName - FromEmailAddress: Optional[FromAddress] - TemplateSubject: Optional[Subject] - TemplateContent: Optional[TemplateContent] - SuccessRedirectionURL: Optional[SuccessRedirectionURL] - FailureRedirectionURL: Optional[FailureRedirectionURL] + FromEmailAddress: FromAddress | None + TemplateSubject: Subject | None + TemplateContent: TemplateContent | None + SuccessRedirectionURL: SuccessRedirectionURL | None + FailureRedirectionURL: FailureRedirectionURL | None class UpdateReceiptRuleRequest(ServiceRequest): @@ -1384,8 +1384,8 @@ class VerifyEmailIdentityResponse(TypedDict, total=False): class SesApi: - service = "ses" - version = "2010-12-01" + service: str = "ses" + version: str = "2010-12-01" @handler("CloneReceiptRuleSet") def clone_receipt_rule_set( diff --git a/localstack-core/localstack/aws/api/sns/__init__.py b/localstack-core/localstack/aws/api/sns/__init__.py index df5f5618138b5..d09e8f8259848 100644 --- a/localstack-core/localstack/aws/api/sns/__init__.py +++ b/localstack-core/localstack/aws/api/sns/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -274,8 +274,8 @@ class VerificationException(ServiceException): Status: string -ActionsList = List[action] -DelegatesList = List[delegate] +ActionsList = list[action] +DelegatesList = list[delegate] class AddPermissionInput(ServiceRequest): @@ -288,11 +288,11 @@ class AddPermissionInput(ServiceRequest): class BatchResultErrorEntry(TypedDict, total=False): Id: String Code: String - Message: Optional[String] + Message: String | None SenderFault: boolean -BatchResultErrorEntryList = List[BatchResultErrorEntry] +BatchResultErrorEntryList = list[BatchResultErrorEntry] Binary = bytes @@ -301,24 +301,24 @@ class CheckIfPhoneNumberIsOptedOutInput(ServiceRequest): class CheckIfPhoneNumberIsOptedOutResponse(TypedDict, total=False): - isOptedOut: Optional[boolean] + isOptedOut: boolean | None class ConfirmSubscriptionInput(ServiceRequest): TopicArn: topicARN Token: token - AuthenticateOnUnsubscribe: Optional[authenticateOnUnsubscribe] + AuthenticateOnUnsubscribe: authenticateOnUnsubscribe | None class ConfirmSubscriptionResponse(TypedDict, total=False): - SubscriptionArn: Optional[subscriptionARN] + SubscriptionArn: subscriptionARN | None class CreateEndpointResponse(TypedDict, total=False): - EndpointArn: Optional[String] + EndpointArn: String | None -MapStringToString = Dict[String, String] +MapStringToString = dict[String, String] class CreatePlatformApplicationInput(ServiceRequest): @@ -328,19 +328,19 @@ class CreatePlatformApplicationInput(ServiceRequest): class CreatePlatformApplicationResponse(TypedDict, total=False): - PlatformApplicationArn: Optional[String] + PlatformApplicationArn: String | None class CreatePlatformEndpointInput(ServiceRequest): PlatformApplicationArn: String Token: String - CustomUserData: Optional[String] - Attributes: Optional[MapStringToString] + CustomUserData: String | None + Attributes: MapStringToString | None class CreateSMSSandboxPhoneNumberInput(ServiceRequest): PhoneNumber: PhoneNumberString - LanguageCode: Optional[LanguageCodeString] + LanguageCode: LanguageCodeString | None class CreateSMSSandboxPhoneNumberResult(TypedDict, total=False): @@ -352,19 +352,19 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] -TopicAttributesMap = Dict[attributeName, attributeValue] +TagList = list[Tag] +TopicAttributesMap = dict[attributeName, attributeValue] class CreateTopicInput(ServiceRequest): Name: topicName - Attributes: Optional[TopicAttributesMap] - Tags: Optional[TagList] - DataProtectionPolicy: Optional[attributeValue] + Attributes: TopicAttributesMap | None + Tags: TagList | None + DataProtectionPolicy: attributeValue | None class CreateTopicResponse(TypedDict, total=False): - TopicArn: Optional[topicARN] + TopicArn: topicARN | None class DeleteEndpointInput(ServiceRequest): @@ -388,8 +388,8 @@ class DeleteTopicInput(ServiceRequest): class Endpoint(TypedDict, total=False): - EndpointArn: Optional[String] - Attributes: Optional[MapStringToString] + EndpointArn: String | None + Attributes: MapStringToString | None class GetDataProtectionPolicyInput(ServiceRequest): @@ -397,7 +397,7 @@ class GetDataProtectionPolicyInput(ServiceRequest): class GetDataProtectionPolicyResponse(TypedDict, total=False): - DataProtectionPolicy: Optional[attributeValue] + DataProtectionPolicy: attributeValue | None class GetEndpointAttributesInput(ServiceRequest): @@ -405,7 +405,7 @@ class GetEndpointAttributesInput(ServiceRequest): class GetEndpointAttributesResponse(TypedDict, total=False): - Attributes: Optional[MapStringToString] + Attributes: MapStringToString | None class GetPlatformApplicationAttributesInput(ServiceRequest): @@ -413,18 +413,18 @@ class GetPlatformApplicationAttributesInput(ServiceRequest): class GetPlatformApplicationAttributesResponse(TypedDict, total=False): - Attributes: Optional[MapStringToString] + Attributes: MapStringToString | None -ListString = List[String] +ListString = list[String] class GetSMSAttributesInput(ServiceRequest): - attributes: Optional[ListString] + attributes: ListString | None class GetSMSAttributesResponse(TypedDict, total=False): - attributes: Optional[MapStringToString] + attributes: MapStringToString | None class GetSMSSandboxAccountStatusInput(ServiceRequest): @@ -439,11 +439,11 @@ class GetSubscriptionAttributesInput(ServiceRequest): SubscriptionArn: subscriptionARN -SubscriptionAttributesMap = Dict[attributeName, attributeValue] +SubscriptionAttributesMap = dict[attributeName, attributeValue] class GetSubscriptionAttributesResponse(TypedDict, total=False): - Attributes: Optional[SubscriptionAttributesMap] + Attributes: SubscriptionAttributesMap | None class GetTopicAttributesInput(ServiceRequest): @@ -451,123 +451,123 @@ class GetTopicAttributesInput(ServiceRequest): class GetTopicAttributesResponse(TypedDict, total=False): - Attributes: Optional[TopicAttributesMap] + Attributes: TopicAttributesMap | None class ListEndpointsByPlatformApplicationInput(ServiceRequest): PlatformApplicationArn: String - NextToken: Optional[String] + NextToken: String | None -ListOfEndpoints = List[Endpoint] +ListOfEndpoints = list[Endpoint] class ListEndpointsByPlatformApplicationResponse(TypedDict, total=False): - Endpoints: Optional[ListOfEndpoints] - NextToken: Optional[String] + Endpoints: ListOfEndpoints | None + NextToken: String | None class PlatformApplication(TypedDict, total=False): - PlatformApplicationArn: Optional[String] - Attributes: Optional[MapStringToString] + PlatformApplicationArn: String | None + Attributes: MapStringToString | None -ListOfPlatformApplications = List[PlatformApplication] +ListOfPlatformApplications = list[PlatformApplication] class ListOriginationNumbersRequest(ServiceRequest): - NextToken: Optional[nextToken] - MaxResults: Optional[MaxItemsListOriginationNumbers] + NextToken: nextToken | None + MaxResults: MaxItemsListOriginationNumbers | None -NumberCapabilityList = List[NumberCapability] +NumberCapabilityList = list[NumberCapability] Timestamp = datetime class PhoneNumberInformation(TypedDict, total=False): - CreatedAt: Optional[Timestamp] - PhoneNumber: Optional[PhoneNumber] - Status: Optional[String] - Iso2CountryCode: Optional[Iso2CountryCode] - RouteType: Optional[RouteType] - NumberCapabilities: Optional[NumberCapabilityList] + CreatedAt: Timestamp | None + PhoneNumber: PhoneNumber | None + Status: String | None + Iso2CountryCode: Iso2CountryCode | None + RouteType: RouteType | None + NumberCapabilities: NumberCapabilityList | None -PhoneNumberInformationList = List[PhoneNumberInformation] +PhoneNumberInformationList = list[PhoneNumberInformation] class ListOriginationNumbersResult(TypedDict, total=False): - NextToken: Optional[nextToken] - PhoneNumbers: Optional[PhoneNumberInformationList] + NextToken: nextToken | None + PhoneNumbers: PhoneNumberInformationList | None class ListPhoneNumbersOptedOutInput(ServiceRequest): - nextToken: Optional[string] + nextToken: string | None -PhoneNumberList = List[PhoneNumber] +PhoneNumberList = list[PhoneNumber] class ListPhoneNumbersOptedOutResponse(TypedDict, total=False): - phoneNumbers: Optional[PhoneNumberList] - nextToken: Optional[string] + phoneNumbers: PhoneNumberList | None + nextToken: string | None class ListPlatformApplicationsInput(ServiceRequest): - NextToken: Optional[String] + NextToken: String | None class ListPlatformApplicationsResponse(TypedDict, total=False): - PlatformApplications: Optional[ListOfPlatformApplications] - NextToken: Optional[String] + PlatformApplications: ListOfPlatformApplications | None + NextToken: String | None class ListSMSSandboxPhoneNumbersInput(ServiceRequest): - NextToken: Optional[nextToken] - MaxResults: Optional[MaxItems] + NextToken: nextToken | None + MaxResults: MaxItems | None class SMSSandboxPhoneNumber(TypedDict, total=False): - PhoneNumber: Optional[PhoneNumberString] - Status: Optional[SMSSandboxPhoneNumberVerificationStatus] + PhoneNumber: PhoneNumberString | None + Status: SMSSandboxPhoneNumberVerificationStatus | None -SMSSandboxPhoneNumberList = List[SMSSandboxPhoneNumber] +SMSSandboxPhoneNumberList = list[SMSSandboxPhoneNumber] class ListSMSSandboxPhoneNumbersResult(TypedDict, total=False): PhoneNumbers: SMSSandboxPhoneNumberList - NextToken: Optional[string] + NextToken: string | None class ListSubscriptionsByTopicInput(ServiceRequest): TopicArn: topicARN - NextToken: Optional[nextToken] + NextToken: nextToken | None class Subscription(TypedDict, total=False): - SubscriptionArn: Optional[subscriptionARN] - Owner: Optional[account] - Protocol: Optional[protocol] - Endpoint: Optional[endpoint] - TopicArn: Optional[topicARN] + SubscriptionArn: subscriptionARN | None + Owner: account | None + Protocol: protocol | None + Endpoint: endpoint | None + TopicArn: topicARN | None -SubscriptionsList = List[Subscription] +SubscriptionsList = list[Subscription] class ListSubscriptionsByTopicResponse(TypedDict, total=False): - Subscriptions: Optional[SubscriptionsList] - NextToken: Optional[nextToken] + Subscriptions: SubscriptionsList | None + NextToken: nextToken | None class ListSubscriptionsInput(ServiceRequest): - NextToken: Optional[nextToken] + NextToken: nextToken | None class ListSubscriptionsResponse(TypedDict, total=False): - Subscriptions: Optional[SubscriptionsList] - NextToken: Optional[nextToken] + Subscriptions: SubscriptionsList | None + NextToken: nextToken | None class ListTagsForResourceRequest(ServiceRequest): @@ -575,32 +575,32 @@ class ListTagsForResourceRequest(ServiceRequest): class ListTagsForResourceResponse(TypedDict, total=False): - Tags: Optional[TagList] + Tags: TagList | None class ListTopicsInput(ServiceRequest): - NextToken: Optional[nextToken] + NextToken: nextToken | None class Topic(TypedDict, total=False): - TopicArn: Optional[topicARN] + TopicArn: topicARN | None -TopicsList = List[Topic] +TopicsList = list[Topic] class ListTopicsResponse(TypedDict, total=False): - Topics: Optional[TopicsList] - NextToken: Optional[nextToken] + Topics: TopicsList | None + NextToken: nextToken | None class MessageAttributeValue(TypedDict, total=False): DataType: String - StringValue: Optional[String] - BinaryValue: Optional[Binary] + StringValue: String | None + BinaryValue: Binary | None -MessageAttributeMap = Dict[String, MessageAttributeValue] +MessageAttributeMap = dict[String, MessageAttributeValue] class OptInPhoneNumberInput(ServiceRequest): @@ -614,14 +614,14 @@ class OptInPhoneNumberResponse(TypedDict, total=False): class PublishBatchRequestEntry(TypedDict, total=False): Id: String Message: message - Subject: Optional[subject] - MessageStructure: Optional[messageStructure] - MessageAttributes: Optional[MessageAttributeMap] - MessageDeduplicationId: Optional[String] - MessageGroupId: Optional[String] + Subject: subject | None + MessageStructure: messageStructure | None + MessageAttributes: MessageAttributeMap | None + MessageDeduplicationId: String | None + MessageGroupId: String | None -PublishBatchRequestEntryList = List[PublishBatchRequestEntry] +PublishBatchRequestEntryList = list[PublishBatchRequestEntry] class PublishBatchInput(ServiceRequest): @@ -630,34 +630,34 @@ class PublishBatchInput(ServiceRequest): class PublishBatchResultEntry(TypedDict, total=False): - Id: Optional[String] - MessageId: Optional[messageId] - SequenceNumber: Optional[String] + Id: String | None + MessageId: messageId | None + SequenceNumber: String | None -PublishBatchResultEntryList = List[PublishBatchResultEntry] +PublishBatchResultEntryList = list[PublishBatchResultEntry] class PublishBatchResponse(TypedDict, total=False): - Successful: Optional[PublishBatchResultEntryList] - Failed: Optional[BatchResultErrorEntryList] + Successful: PublishBatchResultEntryList | None + Failed: BatchResultErrorEntryList | None class PublishInput(ServiceRequest): - TopicArn: Optional[topicARN] - TargetArn: Optional[String] - PhoneNumber: Optional[PhoneNumber] + TopicArn: topicARN | None + TargetArn: String | None + PhoneNumber: PhoneNumber | None Message: message - Subject: Optional[subject] - MessageStructure: Optional[messageStructure] - MessageAttributes: Optional[MessageAttributeMap] - MessageDeduplicationId: Optional[String] - MessageGroupId: Optional[String] + Subject: subject | None + MessageStructure: messageStructure | None + MessageAttributes: MessageAttributeMap | None + MessageDeduplicationId: String | None + MessageGroupId: String | None class PublishResponse(TypedDict, total=False): - MessageId: Optional[messageId] - SequenceNumber: Optional[String] + MessageId: messageId | None + SequenceNumber: String | None class PutDataProtectionPolicyInput(ServiceRequest): @@ -691,28 +691,28 @@ class SetSMSAttributesResponse(TypedDict, total=False): class SetSubscriptionAttributesInput(ServiceRequest): SubscriptionArn: subscriptionARN AttributeName: attributeName - AttributeValue: Optional[attributeValue] + AttributeValue: attributeValue | None class SetTopicAttributesInput(ServiceRequest): TopicArn: topicARN AttributeName: attributeName - AttributeValue: Optional[attributeValue] + AttributeValue: attributeValue | None class SubscribeInput(ServiceRequest): TopicArn: topicARN Protocol: protocol - Endpoint: Optional[endpoint] - Attributes: Optional[SubscriptionAttributesMap] - ReturnSubscriptionArn: Optional[boolean] + Endpoint: endpoint | None + Attributes: SubscriptionAttributesMap | None + ReturnSubscriptionArn: boolean | None class SubscribeResponse(TypedDict, total=False): - SubscriptionArn: Optional[subscriptionARN] + SubscriptionArn: subscriptionARN | None -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceRequest(ServiceRequest): @@ -747,8 +747,8 @@ class VerifySMSSandboxPhoneNumberResult(TypedDict, total=False): class SnsApi: - service = "sns" - version = "2010-03-31" + service: str = "sns" + version: str = "2010-03-31" @handler("AddPermission") def add_permission( diff --git a/localstack-core/localstack/aws/api/sqs/__init__.py b/localstack-core/localstack/aws/api/sqs/__init__.py index a09978ffe8046..23580370cfb90 100644 --- a/localstack-core/localstack/aws/api/sqs/__init__.py +++ b/localstack-core/localstack/aws/api/sqs/__init__.py @@ -1,5 +1,5 @@ from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -224,8 +224,8 @@ class UnsupportedOperation(ServiceException): status_code: int = 400 -AWSAccountIdList = List[String] -ActionNameList = List[String] +AWSAccountIdList = list[String] +ActionNameList = list[String] class AddPermissionRequest(ServiceRequest): @@ -235,19 +235,19 @@ class AddPermissionRequest(ServiceRequest): Actions: ActionNameList -AttributeNameList = List[QueueAttributeName] +AttributeNameList = list[QueueAttributeName] class BatchResultErrorEntry(TypedDict, total=False): Id: String SenderFault: Boolean Code: String - Message: Optional[String] + Message: String | None -BatchResultErrorEntryList = List[BatchResultErrorEntry] +BatchResultErrorEntryList = list[BatchResultErrorEntry] Binary = bytes -BinaryList = List[Binary] +BinaryList = list[Binary] class CancelMessageMoveTaskRequest(ServiceRequest): @@ -258,16 +258,16 @@ class CancelMessageMoveTaskRequest(ServiceRequest): class CancelMessageMoveTaskResult(TypedDict, total=False): - ApproximateNumberOfMessagesMoved: Optional[Long] + ApproximateNumberOfMessagesMoved: Long | None class ChangeMessageVisibilityBatchRequestEntry(TypedDict, total=False): Id: String ReceiptHandle: String - VisibilityTimeout: Optional[NullableInteger] + VisibilityTimeout: NullableInteger | None -ChangeMessageVisibilityBatchRequestEntryList = List[ChangeMessageVisibilityBatchRequestEntry] +ChangeMessageVisibilityBatchRequestEntryList = list[ChangeMessageVisibilityBatchRequestEntry] class ChangeMessageVisibilityBatchRequest(ServiceRequest): @@ -279,7 +279,7 @@ class ChangeMessageVisibilityBatchResultEntry(TypedDict, total=False): Id: String -ChangeMessageVisibilityBatchResultEntryList = List[ChangeMessageVisibilityBatchResultEntry] +ChangeMessageVisibilityBatchResultEntryList = list[ChangeMessageVisibilityBatchResultEntry] class ChangeMessageVisibilityBatchResult(TypedDict, total=False): @@ -293,18 +293,18 @@ class ChangeMessageVisibilityRequest(ServiceRequest): VisibilityTimeout: NullableInteger -TagMap = Dict[TagKey, TagValue] -QueueAttributeMap = Dict[QueueAttributeName, String] +TagMap = dict[TagKey, TagValue] +QueueAttributeMap = dict[QueueAttributeName, String] class CreateQueueRequest(ServiceRequest): QueueName: String - Attributes: Optional[QueueAttributeMap] - tags: Optional[TagMap] + Attributes: QueueAttributeMap | None + tags: TagMap | None class CreateQueueResult(TypedDict, total=False): - QueueUrl: Optional[String] + QueueUrl: String | None class DeleteMessageBatchRequestEntry(TypedDict, total=False): @@ -312,7 +312,7 @@ class DeleteMessageBatchRequestEntry(TypedDict, total=False): ReceiptHandle: String -DeleteMessageBatchRequestEntryList = List[DeleteMessageBatchRequestEntry] +DeleteMessageBatchRequestEntryList = list[DeleteMessageBatchRequestEntry] class DeleteMessageBatchRequest(ServiceRequest): @@ -324,7 +324,7 @@ class DeleteMessageBatchResultEntry(TypedDict, total=False): Id: String -DeleteMessageBatchResultEntryList = List[DeleteMessageBatchResultEntry] +DeleteMessageBatchResultEntryList = list[DeleteMessageBatchResultEntry] class DeleteMessageBatchResult(TypedDict, total=False): @@ -343,61 +343,61 @@ class DeleteQueueRequest(ServiceRequest): class GetQueueAttributesRequest(ServiceRequest): QueueUrl: String - AttributeNames: Optional[AttributeNameList] + AttributeNames: AttributeNameList | None class GetQueueAttributesResult(TypedDict, total=False): - Attributes: Optional[QueueAttributeMap] + Attributes: QueueAttributeMap | None class GetQueueUrlRequest(ServiceRequest): QueueName: String - QueueOwnerAWSAccountId: Optional[String] + QueueOwnerAWSAccountId: String | None class GetQueueUrlResult(TypedDict, total=False): - QueueUrl: Optional[String] + QueueUrl: String | None class ListDeadLetterSourceQueuesRequest(ServiceRequest): QueueUrl: String - NextToken: Optional[Token] - MaxResults: Optional[BoxedInteger] + NextToken: Token | None + MaxResults: BoxedInteger | None -QueueUrlList = List[String] +QueueUrlList = list[String] class ListDeadLetterSourceQueuesResult(TypedDict, total=False): queueUrls: QueueUrlList - NextToken: Optional[Token] + NextToken: Token | None class ListMessageMoveTasksRequest(ServiceRequest): SourceArn: String - MaxResults: Optional[NullableInteger] + MaxResults: NullableInteger | None NullableLong = int class ListMessageMoveTasksResultEntry(TypedDict, total=False): - TaskHandle: Optional[String] - Status: Optional[String] - SourceArn: Optional[String] - DestinationArn: Optional[String] - MaxNumberOfMessagesPerSecond: Optional[NullableInteger] - ApproximateNumberOfMessagesMoved: Optional[Long] - ApproximateNumberOfMessagesToMove: Optional[NullableLong] - FailureReason: Optional[String] - StartedTimestamp: Optional[Long] + TaskHandle: String | None + Status: String | None + SourceArn: String | None + DestinationArn: String | None + MaxNumberOfMessagesPerSecond: NullableInteger | None + ApproximateNumberOfMessagesMoved: Long | None + ApproximateNumberOfMessagesToMove: NullableLong | None + FailureReason: String | None + StartedTimestamp: Long | None -ListMessageMoveTasksResultEntryList = List[ListMessageMoveTasksResultEntry] +ListMessageMoveTasksResultEntryList = list[ListMessageMoveTasksResultEntry] class ListMessageMoveTasksResult(TypedDict, total=False): - Results: Optional[ListMessageMoveTasksResultEntryList] + Results: ListMessageMoveTasksResultEntryList | None class ListQueueTagsRequest(ServiceRequest): @@ -405,61 +405,61 @@ class ListQueueTagsRequest(ServiceRequest): class ListQueueTagsResult(TypedDict, total=False): - Tags: Optional[TagMap] + Tags: TagMap | None class ListQueuesRequest(ServiceRequest): - QueueNamePrefix: Optional[String] - NextToken: Optional[Token] - MaxResults: Optional[BoxedInteger] + QueueNamePrefix: String | None + NextToken: Token | None + MaxResults: BoxedInteger | None class ListQueuesResult(TypedDict, total=False): - QueueUrls: Optional[QueueUrlList] - NextToken: Optional[Token] + QueueUrls: QueueUrlList | None + NextToken: Token | None -StringList = List[String] +StringList = list[String] class MessageAttributeValue(TypedDict, total=False): - StringValue: Optional[String] - BinaryValue: Optional[Binary] - StringListValues: Optional[StringList] - BinaryListValues: Optional[BinaryList] + StringValue: String | None + BinaryValue: Binary | None + StringListValues: StringList | None + BinaryListValues: BinaryList | None DataType: String -MessageBodyAttributeMap = Dict[String, MessageAttributeValue] -MessageSystemAttributeMap = Dict[MessageSystemAttributeName, String] +MessageBodyAttributeMap = dict[String, MessageAttributeValue] +MessageSystemAttributeMap = dict[MessageSystemAttributeName, String] class Message(TypedDict, total=False): - MessageId: Optional[String] - ReceiptHandle: Optional[String] - MD5OfBody: Optional[String] - Body: Optional[String] - Attributes: Optional[MessageSystemAttributeMap] - MD5OfMessageAttributes: Optional[String] - MessageAttributes: Optional[MessageBodyAttributeMap] + MessageId: String | None + ReceiptHandle: String | None + MD5OfBody: String | None + Body: String | None + Attributes: MessageSystemAttributeMap | None + MD5OfMessageAttributes: String | None + MessageAttributes: MessageBodyAttributeMap | None -MessageAttributeNameList = List[MessageAttributeName] +MessageAttributeNameList = list[MessageAttributeName] class MessageSystemAttributeValue(TypedDict, total=False): - StringValue: Optional[String] - BinaryValue: Optional[Binary] - StringListValues: Optional[StringList] - BinaryListValues: Optional[BinaryList] + StringValue: String | None + BinaryValue: Binary | None + StringListValues: StringList | None + BinaryListValues: BinaryList | None DataType: String -MessageBodySystemAttributeMap = Dict[ +MessageBodySystemAttributeMap = dict[ MessageSystemAttributeNameForSends, MessageSystemAttributeValue ] -MessageList = List[Message] -MessageSystemAttributeList = List[MessageSystemAttributeName] +MessageList = list[Message] +MessageSystemAttributeList = list[MessageSystemAttributeName] class PurgeQueueRequest(ServiceRequest): @@ -468,17 +468,17 @@ class PurgeQueueRequest(ServiceRequest): class ReceiveMessageRequest(ServiceRequest): QueueUrl: String - AttributeNames: Optional[AttributeNameList] - MessageSystemAttributeNames: Optional[MessageSystemAttributeList] - MessageAttributeNames: Optional[MessageAttributeNameList] - MaxNumberOfMessages: Optional[NullableInteger] - VisibilityTimeout: Optional[NullableInteger] - WaitTimeSeconds: Optional[NullableInteger] - ReceiveRequestAttemptId: Optional[String] + AttributeNames: AttributeNameList | None + MessageSystemAttributeNames: MessageSystemAttributeList | None + MessageAttributeNames: MessageAttributeNameList | None + MaxNumberOfMessages: NullableInteger | None + VisibilityTimeout: NullableInteger | None + WaitTimeSeconds: NullableInteger | None + ReceiveRequestAttemptId: String | None class ReceiveMessageResult(TypedDict, total=False): - Messages: Optional[MessageList] + Messages: MessageList | None class RemovePermissionRequest(ServiceRequest): @@ -489,14 +489,14 @@ class RemovePermissionRequest(ServiceRequest): class SendMessageBatchRequestEntry(TypedDict, total=False): Id: String MessageBody: String - DelaySeconds: Optional[NullableInteger] - MessageAttributes: Optional[MessageBodyAttributeMap] - MessageSystemAttributes: Optional[MessageBodySystemAttributeMap] - MessageDeduplicationId: Optional[String] - MessageGroupId: Optional[String] + DelaySeconds: NullableInteger | None + MessageAttributes: MessageBodyAttributeMap | None + MessageSystemAttributes: MessageBodySystemAttributeMap | None + MessageDeduplicationId: String | None + MessageGroupId: String | None -SendMessageBatchRequestEntryList = List[SendMessageBatchRequestEntry] +SendMessageBatchRequestEntryList = list[SendMessageBatchRequestEntry] class SendMessageBatchRequest(ServiceRequest): @@ -508,12 +508,12 @@ class SendMessageBatchResultEntry(TypedDict, total=False): Id: String MessageId: String MD5OfMessageBody: String - MD5OfMessageAttributes: Optional[String] - MD5OfMessageSystemAttributes: Optional[String] - SequenceNumber: Optional[String] + MD5OfMessageAttributes: String | None + MD5OfMessageSystemAttributes: String | None + SequenceNumber: String | None -SendMessageBatchResultEntryList = List[SendMessageBatchResultEntry] +SendMessageBatchResultEntryList = list[SendMessageBatchResultEntry] class SendMessageBatchResult(TypedDict, total=False): @@ -524,19 +524,19 @@ class SendMessageBatchResult(TypedDict, total=False): class SendMessageRequest(ServiceRequest): QueueUrl: String MessageBody: String - DelaySeconds: Optional[NullableInteger] - MessageAttributes: Optional[MessageBodyAttributeMap] - MessageSystemAttributes: Optional[MessageBodySystemAttributeMap] - MessageDeduplicationId: Optional[String] - MessageGroupId: Optional[String] + DelaySeconds: NullableInteger | None + MessageAttributes: MessageBodyAttributeMap | None + MessageSystemAttributes: MessageBodySystemAttributeMap | None + MessageDeduplicationId: String | None + MessageGroupId: String | None class SendMessageResult(TypedDict, total=False): - MD5OfMessageBody: Optional[String] - MD5OfMessageAttributes: Optional[String] - MD5OfMessageSystemAttributes: Optional[String] - MessageId: Optional[String] - SequenceNumber: Optional[String] + MD5OfMessageBody: String | None + MD5OfMessageAttributes: String | None + MD5OfMessageSystemAttributes: String | None + MessageId: String | None + SequenceNumber: String | None class SetQueueAttributesRequest(ServiceRequest): @@ -546,15 +546,15 @@ class SetQueueAttributesRequest(ServiceRequest): class StartMessageMoveTaskRequest(ServiceRequest): SourceArn: String - DestinationArn: Optional[String] - MaxNumberOfMessagesPerSecond: Optional[NullableInteger] + DestinationArn: String | None + MaxNumberOfMessagesPerSecond: NullableInteger | None class StartMessageMoveTaskResult(TypedDict, total=False): - TaskHandle: Optional[String] + TaskHandle: String | None -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagQueueRequest(ServiceRequest): @@ -568,8 +568,8 @@ class UntagQueueRequest(ServiceRequest): class SqsApi: - service = "sqs" - version = "2012-11-05" + service: str = "sqs" + version: str = "2012-11-05" @handler("AddPermission") def add_permission( diff --git a/localstack-core/localstack/aws/api/ssm/__init__.py b/localstack-core/localstack/aws/api/ssm/__init__.py index bf32cd2834bc2..6ff21b9f728ae 100644 --- a/localstack-core/localstack/aws/api/ssm/__init__.py +++ b/localstack-core/localstack/aws/api/ssm/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -21,6 +21,7 @@ ApplyOnlyAtCronInterval = bool ApproveAfterDays = int Architecture = str +AssociationDispatchAssumeRoleArn = str AssociationExecutionFilterValue = str AssociationExecutionId = str AssociationExecutionTargetsFilterValue = str @@ -1510,7 +1511,7 @@ class InvalidItemContentException(ServiceException): code: str = "InvalidItemContentException" sender_fault: bool = False status_code: int = 400 - TypeName: Optional[InventoryItemTypeName] + TypeName: InventoryItemTypeName | None class InvalidKeyId(ServiceException): @@ -1649,14 +1650,14 @@ class ItemContentMismatchException(ServiceException): code: str = "ItemContentMismatchException" sender_fault: bool = False status_code: int = 400 - TypeName: Optional[InventoryItemTypeName] + TypeName: InventoryItemTypeName | None class ItemSizeLimitExceededException(ServiceException): code: str = "ItemSizeLimitExceededException" sender_fault: bool = False status_code: int = 400 - TypeName: Optional[InventoryItemTypeName] + TypeName: InventoryItemTypeName | None class MalformedResourcePolicyDocumentException(ServiceException): @@ -1671,6 +1672,12 @@ class MaxDocumentSizeExceeded(ServiceException): status_code: int = 400 +class NoLongerSupportedException(ServiceException): + code: str = "NoLongerSupportedException" + sender_fault: bool = False + status_code: int = 400 + + class OpsItemAccessDeniedException(ServiceException): code: str = "OpsItemAccessDeniedException" sender_fault: bool = False @@ -1681,7 +1688,7 @@ class OpsItemAlreadyExistsException(ServiceException): code: str = "OpsItemAlreadyExistsException" sender_fault: bool = False status_code: int = 400 - OpsItemId: Optional[String] + OpsItemId: String | None class OpsItemConflictException(ServiceException): @@ -1690,23 +1697,23 @@ class OpsItemConflictException(ServiceException): status_code: int = 400 -OpsItemParameterNamesList = List[String] +OpsItemParameterNamesList = list[String] class OpsItemInvalidParameterException(ServiceException): code: str = "OpsItemInvalidParameterException" sender_fault: bool = False status_code: int = 400 - ParameterNames: Optional[OpsItemParameterNamesList] + ParameterNames: OpsItemParameterNamesList | None class OpsItemLimitExceededException(ServiceException): code: str = "OpsItemLimitExceededException" sender_fault: bool = False status_code: int = 400 - ResourceTypes: Optional[OpsItemParameterNamesList] - Limit: Optional[Integer] - LimitType: Optional[String] + ResourceTypes: OpsItemParameterNamesList | None + Limit: Integer | None + LimitType: String | None class OpsItemNotFoundException(ServiceException): @@ -1719,8 +1726,8 @@ class OpsItemRelatedItemAlreadyExistsException(ServiceException): code: str = "OpsItemRelatedItemAlreadyExistsException" sender_fault: bool = False status_code: int = 400 - ResourceUri: Optional[OpsItemRelatedItemAssociationResourceUri] - OpsItemId: Optional[OpsItemId] + ResourceUri: OpsItemRelatedItemAssociationResourceUri | None + OpsItemId: OpsItemId | None class OpsItemRelatedItemAssociationNotFoundException(ServiceException): @@ -1817,7 +1824,7 @@ class ResourceDataSyncAlreadyExistsException(ServiceException): code: str = "ResourceDataSyncAlreadyExistsException" sender_fault: bool = False status_code: int = 400 - SyncName: Optional[ResourceDataSyncName] + SyncName: ResourceDataSyncName | None class ResourceDataSyncConflictException(ServiceException): @@ -1842,8 +1849,8 @@ class ResourceDataSyncNotFoundException(ServiceException): code: str = "ResourceDataSyncNotFoundException" sender_fault: bool = False status_code: int = 400 - SyncName: Optional[ResourceDataSyncName] - SyncType: Optional[ResourceDataSyncType] + SyncName: ResourceDataSyncName | None + SyncType: ResourceDataSyncType | None class ResourceInUseException(ServiceException): @@ -1870,22 +1877,22 @@ class ResourcePolicyConflictException(ServiceException): status_code: int = 400 -ResourcePolicyParameterNamesList = List[String] +ResourcePolicyParameterNamesList = list[String] class ResourcePolicyInvalidParameterException(ServiceException): code: str = "ResourcePolicyInvalidParameterException" sender_fault: bool = False status_code: int = 400 - ParameterNames: Optional[ResourcePolicyParameterNamesList] + ParameterNames: ResourcePolicyParameterNamesList | None class ResourcePolicyLimitExceededException(ServiceException): code: str = "ResourcePolicyLimitExceededException" sender_fault: bool = False status_code: int = 400 - Limit: Optional[Integer] - LimitType: Optional[String] + Limit: Integer | None + LimitType: String | None class ResourcePolicyNotFoundException(ServiceException): @@ -1898,8 +1905,8 @@ class ServiceQuotaExceededException(ServiceException): code: str = "ServiceQuotaExceededException" sender_fault: bool = False status_code: int = 400 - ResourceId: Optional[String] - ResourceType: Optional[String] + ResourceId: String | None + ResourceType: String | None QuotaCode: String ServiceCode: String @@ -1938,8 +1945,8 @@ class ThrottlingException(ServiceException): code: str = "ThrottlingException" sender_fault: bool = False status_code: int = 400 - QuotaCode: Optional[String] - ServiceCode: Optional[String] + QuotaCode: String | None + ServiceCode: String | None class TooManyTagsError(ServiceException): @@ -1976,7 +1983,7 @@ class UnsupportedInventoryItemContextException(ServiceException): code: str = "UnsupportedInventoryItemContextException" sender_fault: bool = False status_code: int = 400 - TypeName: Optional[InventoryItemTypeName] + TypeName: InventoryItemTypeName | None class UnsupportedInventorySchemaVersionException(ServiceException): @@ -2013,19 +2020,19 @@ class ValidationException(ServiceException): code: str = "ValidationException" sender_fault: bool = False status_code: int = 400 - ReasonCode: Optional[String] + ReasonCode: String | None -AccountIdList = List[AccountId] +AccountIdList = list[AccountId] class AccountSharingInfo(TypedDict, total=False): - AccountId: Optional[AccountId] - SharedDocumentVersion: Optional[SharedDocumentVersion] + AccountId: AccountId | None + SharedDocumentVersion: SharedDocumentVersion | None -AccountSharingInfoList = List[AccountSharingInfo] -Accounts = List[Account] +AccountSharingInfoList = list[AccountSharingInfo] +Accounts = list[Account] class Tag(TypedDict, total=False): @@ -2033,25 +2040,25 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] CreatedDate = datetime ExpirationDate = datetime class Activation(TypedDict, total=False): - ActivationId: Optional[ActivationId] - Description: Optional[ActivationDescription] - DefaultInstanceName: Optional[DefaultInstanceName] - IamRole: Optional[IamRole] - RegistrationLimit: Optional[RegistrationLimit] - RegistrationsCount: Optional[RegistrationsCount] - ExpirationDate: Optional[ExpirationDate] - Expired: Optional[Boolean] - CreatedDate: Optional[CreatedDate] - Tags: Optional[TagList] + ActivationId: ActivationId | None + Description: ActivationDescription | None + DefaultInstanceName: DefaultInstanceName | None + IamRole: IamRole | None + RegistrationLimit: RegistrationLimit | None + RegistrationsCount: RegistrationsCount | None + ExpirationDate: ExpirationDate | None + Expired: Boolean | None + CreatedDate: CreatedDate | None + Tags: TagList | None -ActivationList = List[Activation] +ActivationList = list[Activation] class AddTagsToResourceRequest(ServiceRequest): @@ -2068,11 +2075,11 @@ class Alarm(TypedDict, total=False): Name: AlarmName -AlarmList = List[Alarm] +AlarmList = list[Alarm] class AlarmConfiguration(TypedDict, total=False): - IgnorePollAlarmFailure: Optional[Boolean] + IgnorePollAlarmFailure: Boolean | None Alarms: AlarmList @@ -2081,7 +2088,7 @@ class AlarmStateInformation(TypedDict, total=False): State: ExternalAlarmState -AlarmStateInformationList = List[AlarmStateInformation] +AlarmStateInformationList = list[AlarmStateInformation] class AssociateOpsItemRelatedItemRequest(ServiceRequest): @@ -2092,138 +2099,139 @@ class AssociateOpsItemRelatedItemRequest(ServiceRequest): class AssociateOpsItemRelatedItemResponse(TypedDict, total=False): - AssociationId: Optional[OpsItemRelatedItemAssociationId] + AssociationId: OpsItemRelatedItemAssociationId | None -TargetMapValueList = List[TargetMapValue] -TargetMap = Dict[TargetMapKey, TargetMapValueList] -TargetMaps = List[TargetMap] -AssociationStatusAggregatedCount = Dict[StatusName, InstanceCount] +TargetMapValueList = list[TargetMapValue] +TargetMap = dict[TargetMapKey, TargetMapValueList] +TargetMaps = list[TargetMap] +AssociationStatusAggregatedCount = dict[StatusName, InstanceCount] class AssociationOverview(TypedDict, total=False): - Status: Optional[StatusName] - DetailedStatus: Optional[StatusName] - AssociationStatusAggregatedCount: Optional[AssociationStatusAggregatedCount] + Status: StatusName | None + DetailedStatus: StatusName | None + AssociationStatusAggregatedCount: AssociationStatusAggregatedCount | None DateTime = datetime -TargetValues = List[TargetValue] +TargetValues = list[TargetValue] class Target(TypedDict, total=False): - Key: Optional[TargetKey] - Values: Optional[TargetValues] + Key: TargetKey | None + Values: TargetValues | None -Targets = List[Target] +Targets = list[Target] class Association(TypedDict, total=False): - Name: Optional[DocumentARN] - InstanceId: Optional[InstanceId] - AssociationId: Optional[AssociationId] - AssociationVersion: Optional[AssociationVersion] - DocumentVersion: Optional[DocumentVersion] - Targets: Optional[Targets] - LastExecutionDate: Optional[DateTime] - Overview: Optional[AssociationOverview] - ScheduleExpression: Optional[ScheduleExpression] - AssociationName: Optional[AssociationName] - ScheduleOffset: Optional[ScheduleOffset] - Duration: Optional[Duration] - TargetMaps: Optional[TargetMaps] + Name: DocumentARN | None + InstanceId: InstanceId | None + AssociationId: AssociationId | None + AssociationVersion: AssociationVersion | None + DocumentVersion: DocumentVersion | None + Targets: Targets | None + LastExecutionDate: DateTime | None + Overview: AssociationOverview | None + ScheduleExpression: ScheduleExpression | None + AssociationName: AssociationName | None + ScheduleOffset: ScheduleOffset | None + Duration: Duration | None + TargetMaps: TargetMaps | None -ExcludeAccounts = List[ExcludeAccount] -Regions = List[Region] +ExcludeAccounts = list[ExcludeAccount] +Regions = list[Region] class TargetLocation(TypedDict, total=False): - Accounts: Optional[Accounts] - Regions: Optional[Regions] - TargetLocationMaxConcurrency: Optional[MaxConcurrency] - TargetLocationMaxErrors: Optional[MaxErrors] - ExecutionRoleName: Optional[ExecutionRoleName] - TargetLocationAlarmConfiguration: Optional[AlarmConfiguration] - IncludeChildOrganizationUnits: Optional[Boolean] - ExcludeAccounts: Optional[ExcludeAccounts] - Targets: Optional[Targets] - TargetsMaxConcurrency: Optional[MaxConcurrency] - TargetsMaxErrors: Optional[MaxErrors] + Accounts: Accounts | None + Regions: Regions | None + TargetLocationMaxConcurrency: MaxConcurrency | None + TargetLocationMaxErrors: MaxErrors | None + ExecutionRoleName: ExecutionRoleName | None + TargetLocationAlarmConfiguration: AlarmConfiguration | None + IncludeChildOrganizationUnits: Boolean | None + ExcludeAccounts: ExcludeAccounts | None + Targets: Targets | None + TargetsMaxConcurrency: MaxConcurrency | None + TargetsMaxErrors: MaxErrors | None -TargetLocations = List[TargetLocation] -CalendarNameOrARNList = List[CalendarNameOrARN] +TargetLocations = list[TargetLocation] +CalendarNameOrARNList = list[CalendarNameOrARN] class S3OutputLocation(TypedDict, total=False): - OutputS3Region: Optional[S3Region] - OutputS3BucketName: Optional[S3BucketName] - OutputS3KeyPrefix: Optional[S3KeyPrefix] + OutputS3Region: S3Region | None + OutputS3BucketName: S3BucketName | None + OutputS3KeyPrefix: S3KeyPrefix | None class InstanceAssociationOutputLocation(TypedDict, total=False): - S3Location: Optional[S3OutputLocation] + S3Location: S3OutputLocation | None -ParameterValueList = List[ParameterValue] -Parameters = Dict[ParameterName, ParameterValueList] +ParameterValueList = list[ParameterValue] +Parameters = dict[ParameterName, ParameterValueList] class AssociationStatus(TypedDict, total=False): Date: DateTime Name: AssociationStatusName Message: StatusMessage - AdditionalInfo: Optional[StatusAdditionalInfo] + AdditionalInfo: StatusAdditionalInfo | None class AssociationDescription(TypedDict, total=False): - Name: Optional[DocumentARN] - InstanceId: Optional[InstanceId] - AssociationVersion: Optional[AssociationVersion] - Date: Optional[DateTime] - LastUpdateAssociationDate: Optional[DateTime] - Status: Optional[AssociationStatus] - Overview: Optional[AssociationOverview] - DocumentVersion: Optional[DocumentVersion] - AutomationTargetParameterName: Optional[AutomationTargetParameterName] - Parameters: Optional[Parameters] - AssociationId: Optional[AssociationId] - Targets: Optional[Targets] - ScheduleExpression: Optional[ScheduleExpression] - OutputLocation: Optional[InstanceAssociationOutputLocation] - LastExecutionDate: Optional[DateTime] - LastSuccessfulExecutionDate: Optional[DateTime] - AssociationName: Optional[AssociationName] - MaxErrors: Optional[MaxErrors] - MaxConcurrency: Optional[MaxConcurrency] - ComplianceSeverity: Optional[AssociationComplianceSeverity] - SyncCompliance: Optional[AssociationSyncCompliance] - ApplyOnlyAtCronInterval: Optional[ApplyOnlyAtCronInterval] - CalendarNames: Optional[CalendarNameOrARNList] - TargetLocations: Optional[TargetLocations] - ScheduleOffset: Optional[ScheduleOffset] - Duration: Optional[Duration] - TargetMaps: Optional[TargetMaps] - AlarmConfiguration: Optional[AlarmConfiguration] - TriggeredAlarms: Optional[AlarmStateInformationList] - - -AssociationDescriptionList = List[AssociationDescription] + Name: DocumentARN | None + InstanceId: InstanceId | None + AssociationVersion: AssociationVersion | None + Date: DateTime | None + LastUpdateAssociationDate: DateTime | None + Status: AssociationStatus | None + Overview: AssociationOverview | None + DocumentVersion: DocumentVersion | None + AutomationTargetParameterName: AutomationTargetParameterName | None + Parameters: Parameters | None + AssociationId: AssociationId | None + Targets: Targets | None + ScheduleExpression: ScheduleExpression | None + OutputLocation: InstanceAssociationOutputLocation | None + LastExecutionDate: DateTime | None + LastSuccessfulExecutionDate: DateTime | None + AssociationName: AssociationName | None + MaxErrors: MaxErrors | None + MaxConcurrency: MaxConcurrency | None + ComplianceSeverity: AssociationComplianceSeverity | None + SyncCompliance: AssociationSyncCompliance | None + ApplyOnlyAtCronInterval: ApplyOnlyAtCronInterval | None + CalendarNames: CalendarNameOrARNList | None + TargetLocations: TargetLocations | None + ScheduleOffset: ScheduleOffset | None + Duration: Duration | None + TargetMaps: TargetMaps | None + AlarmConfiguration: AlarmConfiguration | None + TriggeredAlarms: AlarmStateInformationList | None + AssociationDispatchAssumeRole: AssociationDispatchAssumeRoleArn | None + + +AssociationDescriptionList = list[AssociationDescription] class AssociationExecution(TypedDict, total=False): - AssociationId: Optional[AssociationId] - AssociationVersion: Optional[AssociationVersion] - ExecutionId: Optional[AssociationExecutionId] - Status: Optional[StatusName] - DetailedStatus: Optional[StatusName] - CreatedTime: Optional[DateTime] - LastExecutionDate: Optional[DateTime] - ResourceCountByStatus: Optional[ResourceCountByStatus] - AlarmConfiguration: Optional[AlarmConfiguration] - TriggeredAlarms: Optional[AlarmStateInformationList] + AssociationId: AssociationId | None + AssociationVersion: AssociationVersion | None + ExecutionId: AssociationExecutionId | None + Status: StatusName | None + DetailedStatus: StatusName | None + CreatedTime: DateTime | None + LastExecutionDate: DateTime | None + ResourceCountByStatus: ResourceCountByStatus | None + AlarmConfiguration: AlarmConfiguration | None + TriggeredAlarms: AlarmStateInformationList | None class AssociationExecutionFilter(TypedDict, total=False): @@ -2232,24 +2240,24 @@ class AssociationExecutionFilter(TypedDict, total=False): Type: AssociationFilterOperatorType -AssociationExecutionFilterList = List[AssociationExecutionFilter] +AssociationExecutionFilterList = list[AssociationExecutionFilter] class OutputSource(TypedDict, total=False): - OutputSourceId: Optional[OutputSourceId] - OutputSourceType: Optional[OutputSourceType] + OutputSourceId: OutputSourceId | None + OutputSourceType: OutputSourceType | None class AssociationExecutionTarget(TypedDict, total=False): - AssociationId: Optional[AssociationId] - AssociationVersion: Optional[AssociationVersion] - ExecutionId: Optional[AssociationExecutionId] - ResourceId: Optional[AssociationResourceId] - ResourceType: Optional[AssociationResourceType] - Status: Optional[StatusName] - DetailedStatus: Optional[StatusName] - LastExecutionDate: Optional[DateTime] - OutputSource: Optional[OutputSource] + AssociationId: AssociationId | None + AssociationVersion: AssociationVersion | None + ExecutionId: AssociationExecutionId | None + ResourceId: AssociationResourceId | None + ResourceType: AssociationResourceType | None + Status: StatusName | None + DetailedStatus: StatusName | None + LastExecutionDate: DateTime | None + OutputSource: OutputSource | None class AssociationExecutionTargetsFilter(TypedDict, total=False): @@ -2257,9 +2265,9 @@ class AssociationExecutionTargetsFilter(TypedDict, total=False): Value: AssociationExecutionTargetsFilterValue -AssociationExecutionTargetsFilterList = List[AssociationExecutionTargetsFilter] -AssociationExecutionTargetsList = List[AssociationExecutionTarget] -AssociationExecutionsList = List[AssociationExecution] +AssociationExecutionTargetsFilterList = list[AssociationExecutionTargetsFilter] +AssociationExecutionTargetsList = list[AssociationExecutionTarget] +AssociationExecutionsList = list[AssociationExecution] class AssociationFilter(TypedDict, total=False): @@ -2267,189 +2275,190 @@ class AssociationFilter(TypedDict, total=False): value: AssociationFilterValue -AssociationFilterList = List[AssociationFilter] -AssociationIdList = List[AssociationId] -AssociationList = List[Association] +AssociationFilterList = list[AssociationFilter] +AssociationIdList = list[AssociationId] +AssociationList = list[Association] class AssociationVersionInfo(TypedDict, total=False): - AssociationId: Optional[AssociationId] - AssociationVersion: Optional[AssociationVersion] - CreatedDate: Optional[DateTime] - Name: Optional[DocumentARN] - DocumentVersion: Optional[DocumentVersion] - Parameters: Optional[Parameters] - Targets: Optional[Targets] - ScheduleExpression: Optional[ScheduleExpression] - OutputLocation: Optional[InstanceAssociationOutputLocation] - AssociationName: Optional[AssociationName] - MaxErrors: Optional[MaxErrors] - MaxConcurrency: Optional[MaxConcurrency] - ComplianceSeverity: Optional[AssociationComplianceSeverity] - SyncCompliance: Optional[AssociationSyncCompliance] - ApplyOnlyAtCronInterval: Optional[ApplyOnlyAtCronInterval] - CalendarNames: Optional[CalendarNameOrARNList] - TargetLocations: Optional[TargetLocations] - ScheduleOffset: Optional[ScheduleOffset] - Duration: Optional[Duration] - TargetMaps: Optional[TargetMaps] - - -AssociationVersionList = List[AssociationVersionInfo] + AssociationId: AssociationId | None + AssociationVersion: AssociationVersion | None + CreatedDate: DateTime | None + Name: DocumentARN | None + DocumentVersion: DocumentVersion | None + Parameters: Parameters | None + Targets: Targets | None + ScheduleExpression: ScheduleExpression | None + OutputLocation: InstanceAssociationOutputLocation | None + AssociationName: AssociationName | None + MaxErrors: MaxErrors | None + MaxConcurrency: MaxConcurrency | None + ComplianceSeverity: AssociationComplianceSeverity | None + SyncCompliance: AssociationSyncCompliance | None + ApplyOnlyAtCronInterval: ApplyOnlyAtCronInterval | None + CalendarNames: CalendarNameOrARNList | None + TargetLocations: TargetLocations | None + ScheduleOffset: ScheduleOffset | None + Duration: Duration | None + TargetMaps: TargetMaps | None + AssociationDispatchAssumeRole: AssociationDispatchAssumeRoleArn | None + + +AssociationVersionList = list[AssociationVersionInfo] ContentLength = int class AttachmentContent(TypedDict, total=False): - Name: Optional[AttachmentName] - Size: Optional[ContentLength] - Hash: Optional[AttachmentHash] - HashType: Optional[AttachmentHashType] - Url: Optional[AttachmentUrl] + Name: AttachmentName | None + Size: ContentLength | None + Hash: AttachmentHash | None + HashType: AttachmentHashType | None + Url: AttachmentUrl | None -AttachmentContentList = List[AttachmentContent] +AttachmentContentList = list[AttachmentContent] class AttachmentInformation(TypedDict, total=False): - Name: Optional[AttachmentName] + Name: AttachmentName | None -AttachmentInformationList = List[AttachmentInformation] -AttachmentsSourceValues = List[AttachmentsSourceValue] +AttachmentInformationList = list[AttachmentInformation] +AttachmentsSourceValues = list[AttachmentsSourceValue] class AttachmentsSource(TypedDict, total=False): - Key: Optional[AttachmentsSourceKey] - Values: Optional[AttachmentsSourceValues] - Name: Optional[AttachmentIdentifier] + Key: AttachmentsSourceKey | None + Values: AttachmentsSourceValues | None + Name: AttachmentIdentifier | None -AttachmentsSourceList = List[AttachmentsSource] -AutomationParameterValueList = List[AutomationParameterValue] -AutomationParameterMap = Dict[AutomationParameterKey, AutomationParameterValueList] +AttachmentsSourceList = list[AttachmentsSource] +AutomationParameterValueList = list[AutomationParameterValue] +AutomationParameterMap = dict[AutomationParameterKey, AutomationParameterValueList] class Runbook(TypedDict, total=False): DocumentName: DocumentARN - DocumentVersion: Optional[DocumentVersion] - Parameters: Optional[AutomationParameterMap] - TargetParameterName: Optional[AutomationParameterKey] - Targets: Optional[Targets] - TargetMaps: Optional[TargetMaps] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - TargetLocations: Optional[TargetLocations] + DocumentVersion: DocumentVersion | None + Parameters: AutomationParameterMap | None + TargetParameterName: AutomationParameterKey | None + Targets: Targets | None + TargetMaps: TargetMaps | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + TargetLocations: TargetLocations | None -Runbooks = List[Runbook] +Runbooks = list[Runbook] class ProgressCounters(TypedDict, total=False): - TotalSteps: Optional[Integer] - SuccessSteps: Optional[Integer] - FailedSteps: Optional[Integer] - CancelledSteps: Optional[Integer] - TimedOutSteps: Optional[Integer] + TotalSteps: Integer | None + SuccessSteps: Integer | None + FailedSteps: Integer | None + CancelledSteps: Integer | None + TimedOutSteps: Integer | None -TargetParameterList = List[ParameterValue] +TargetParameterList = list[ParameterValue] class ResolvedTargets(TypedDict, total=False): - ParameterValues: Optional[TargetParameterList] - Truncated: Optional[Boolean] + ParameterValues: TargetParameterList | None + Truncated: Boolean | None class ParentStepDetails(TypedDict, total=False): - StepExecutionId: Optional[String] - StepName: Optional[String] - Action: Optional[AutomationActionName] - Iteration: Optional[Integer] - IteratorValue: Optional[String] + StepExecutionId: String | None + StepName: String | None + Action: AutomationActionName | None + Iteration: Integer | None + IteratorValue: String | None -ValidNextStepList = List[ValidNextStep] +ValidNextStepList = list[ValidNextStep] class FailureDetails(TypedDict, total=False): - FailureStage: Optional[String] - FailureType: Optional[String] - Details: Optional[AutomationParameterMap] + FailureStage: String | None + FailureType: String | None + Details: AutomationParameterMap | None -NormalStringMap = Dict[String, String] +NormalStringMap = dict[String, String] Long = int class StepExecution(TypedDict, total=False): - StepName: Optional[String] - Action: Optional[AutomationActionName] - TimeoutSeconds: Optional[Long] - OnFailure: Optional[String] - MaxAttempts: Optional[Integer] - ExecutionStartTime: Optional[DateTime] - ExecutionEndTime: Optional[DateTime] - StepStatus: Optional[AutomationExecutionStatus] - ResponseCode: Optional[String] - Inputs: Optional[NormalStringMap] - Outputs: Optional[AutomationParameterMap] - Response: Optional[String] - FailureMessage: Optional[String] - FailureDetails: Optional[FailureDetails] - StepExecutionId: Optional[String] - OverriddenParameters: Optional[AutomationParameterMap] - IsEnd: Optional[Boolean] - NextStep: Optional[String] - IsCritical: Optional[Boolean] - ValidNextSteps: Optional[ValidNextStepList] - Targets: Optional[Targets] - TargetLocation: Optional[TargetLocation] - TriggeredAlarms: Optional[AlarmStateInformationList] - ParentStepDetails: Optional[ParentStepDetails] - - -StepExecutionList = List[StepExecution] + StepName: String | None + Action: AutomationActionName | None + TimeoutSeconds: Long | None + OnFailure: String | None + MaxAttempts: Integer | None + ExecutionStartTime: DateTime | None + ExecutionEndTime: DateTime | None + StepStatus: AutomationExecutionStatus | None + ResponseCode: String | None + Inputs: NormalStringMap | None + Outputs: AutomationParameterMap | None + Response: String | None + FailureMessage: String | None + FailureDetails: FailureDetails | None + StepExecutionId: String | None + OverriddenParameters: AutomationParameterMap | None + IsEnd: Boolean | None + NextStep: String | None + IsCritical: Boolean | None + ValidNextSteps: ValidNextStepList | None + Targets: Targets | None + TargetLocation: TargetLocation | None + TriggeredAlarms: AlarmStateInformationList | None + ParentStepDetails: ParentStepDetails | None + + +StepExecutionList = list[StepExecution] class AutomationExecution(TypedDict, total=False): - AutomationExecutionId: Optional[AutomationExecutionId] - DocumentName: Optional[DocumentName] - DocumentVersion: Optional[DocumentVersion] - ExecutionStartTime: Optional[DateTime] - ExecutionEndTime: Optional[DateTime] - AutomationExecutionStatus: Optional[AutomationExecutionStatus] - StepExecutions: Optional[StepExecutionList] - StepExecutionsTruncated: Optional[Boolean] - Parameters: Optional[AutomationParameterMap] - Outputs: Optional[AutomationParameterMap] - FailureMessage: Optional[String] - Mode: Optional[ExecutionMode] - ParentAutomationExecutionId: Optional[AutomationExecutionId] - ExecutedBy: Optional[String] - CurrentStepName: Optional[String] - CurrentAction: Optional[String] - TargetParameterName: Optional[AutomationParameterKey] - Targets: Optional[Targets] - TargetMaps: Optional[TargetMaps] - ResolvedTargets: Optional[ResolvedTargets] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - Target: Optional[String] - TargetLocations: Optional[TargetLocations] - ProgressCounters: Optional[ProgressCounters] - AlarmConfiguration: Optional[AlarmConfiguration] - TriggeredAlarms: Optional[AlarmStateInformationList] - TargetLocationsURL: Optional[TargetLocationsURL] - AutomationSubtype: Optional[AutomationSubtype] - ScheduledTime: Optional[DateTime] - Runbooks: Optional[Runbooks] - OpsItemId: Optional[String] - AssociationId: Optional[String] - ChangeRequestName: Optional[ChangeRequestName] - Variables: Optional[AutomationParameterMap] - - -AutomationExecutionFilterValueList = List[AutomationExecutionFilterValue] + AutomationExecutionId: AutomationExecutionId | None + DocumentName: DocumentName | None + DocumentVersion: DocumentVersion | None + ExecutionStartTime: DateTime | None + ExecutionEndTime: DateTime | None + AutomationExecutionStatus: AutomationExecutionStatus | None + StepExecutions: StepExecutionList | None + StepExecutionsTruncated: Boolean | None + Parameters: AutomationParameterMap | None + Outputs: AutomationParameterMap | None + FailureMessage: String | None + Mode: ExecutionMode | None + ParentAutomationExecutionId: AutomationExecutionId | None + ExecutedBy: String | None + CurrentStepName: String | None + CurrentAction: String | None + TargetParameterName: AutomationParameterKey | None + Targets: Targets | None + TargetMaps: TargetMaps | None + ResolvedTargets: ResolvedTargets | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + Target: String | None + TargetLocations: TargetLocations | None + ProgressCounters: ProgressCounters | None + AlarmConfiguration: AlarmConfiguration | None + TriggeredAlarms: AlarmStateInformationList | None + TargetLocationsURL: TargetLocationsURL | None + AutomationSubtype: AutomationSubtype | None + ScheduledTime: DateTime | None + Runbooks: Runbooks | None + OpsItemId: String | None + AssociationId: String | None + ChangeRequestName: ChangeRequestName | None + Variables: AutomationParameterMap | None + + +AutomationExecutionFilterValueList = list[AutomationExecutionFilterValue] class AutomationExecutionFilter(TypedDict, total=False): @@ -2457,73 +2466,73 @@ class AutomationExecutionFilter(TypedDict, total=False): Values: AutomationExecutionFilterValueList -AutomationExecutionFilterList = List[AutomationExecutionFilter] +AutomationExecutionFilterList = list[AutomationExecutionFilter] class AutomationExecutionInputs(TypedDict, total=False): - Parameters: Optional[AutomationParameterMap] - TargetParameterName: Optional[AutomationParameterKey] - Targets: Optional[Targets] - TargetMaps: Optional[TargetMaps] - TargetLocations: Optional[TargetLocations] - TargetLocationsURL: Optional[TargetLocationsURL] + Parameters: AutomationParameterMap | None + TargetParameterName: AutomationParameterKey | None + Targets: Targets | None + TargetMaps: TargetMaps | None + TargetLocations: TargetLocations | None + TargetLocationsURL: TargetLocationsURL | None class AutomationExecutionMetadata(TypedDict, total=False): - AutomationExecutionId: Optional[AutomationExecutionId] - DocumentName: Optional[DocumentName] - DocumentVersion: Optional[DocumentVersion] - AutomationExecutionStatus: Optional[AutomationExecutionStatus] - ExecutionStartTime: Optional[DateTime] - ExecutionEndTime: Optional[DateTime] - ExecutedBy: Optional[String] - LogFile: Optional[String] - Outputs: Optional[AutomationParameterMap] - Mode: Optional[ExecutionMode] - ParentAutomationExecutionId: Optional[AutomationExecutionId] - CurrentStepName: Optional[String] - CurrentAction: Optional[String] - FailureMessage: Optional[String] - TargetParameterName: Optional[AutomationParameterKey] - Targets: Optional[Targets] - TargetMaps: Optional[TargetMaps] - ResolvedTargets: Optional[ResolvedTargets] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - Target: Optional[String] - AutomationType: Optional[AutomationType] - AlarmConfiguration: Optional[AlarmConfiguration] - TriggeredAlarms: Optional[AlarmStateInformationList] - TargetLocationsURL: Optional[TargetLocationsURL] - AutomationSubtype: Optional[AutomationSubtype] - ScheduledTime: Optional[DateTime] - Runbooks: Optional[Runbooks] - OpsItemId: Optional[String] - AssociationId: Optional[String] - ChangeRequestName: Optional[ChangeRequestName] - - -AutomationExecutionMetadataList = List[AutomationExecutionMetadata] + AutomationExecutionId: AutomationExecutionId | None + DocumentName: DocumentName | None + DocumentVersion: DocumentVersion | None + AutomationExecutionStatus: AutomationExecutionStatus | None + ExecutionStartTime: DateTime | None + ExecutionEndTime: DateTime | None + ExecutedBy: String | None + LogFile: String | None + Outputs: AutomationParameterMap | None + Mode: ExecutionMode | None + ParentAutomationExecutionId: AutomationExecutionId | None + CurrentStepName: String | None + CurrentAction: String | None + FailureMessage: String | None + TargetParameterName: AutomationParameterKey | None + Targets: Targets | None + TargetMaps: TargetMaps | None + ResolvedTargets: ResolvedTargets | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + Target: String | None + AutomationType: AutomationType | None + AlarmConfiguration: AlarmConfiguration | None + TriggeredAlarms: AlarmStateInformationList | None + TargetLocationsURL: TargetLocationsURL | None + AutomationSubtype: AutomationSubtype | None + ScheduledTime: DateTime | None + Runbooks: Runbooks | None + OpsItemId: String | None + AssociationId: String | None + ChangeRequestName: ChangeRequestName | None + + +AutomationExecutionMetadataList = list[AutomationExecutionMetadata] class TargetPreview(TypedDict, total=False): - Count: Optional[Integer] - TargetType: Optional[String] + Count: Integer | None + TargetType: String | None -TargetPreviewList = List[TargetPreview] -RegionList = List[Region] -StepPreviewMap = Dict[ImpactType, Integer] +TargetPreviewList = list[TargetPreview] +RegionList = list[Region] +StepPreviewMap = dict[ImpactType, Integer] class AutomationExecutionPreview(TypedDict, total=False): - StepPreviews: Optional[StepPreviewMap] - Regions: Optional[RegionList] - TargetPreviews: Optional[TargetPreviewList] - TotalAccounts: Optional[Integer] + StepPreviews: StepPreviewMap | None + Regions: RegionList | None + TargetPreviews: TargetPreviewList | None + TotalAccounts: Integer | None -PatchSourceProductList = List[PatchSourceProduct] +PatchSourceProductList = list[PatchSourceProduct] class PatchSource(TypedDict, total=False): @@ -2532,9 +2541,9 @@ class PatchSource(TypedDict, total=False): Configuration: PatchSourceConfiguration -PatchSourceList = List[PatchSource] -PatchIdList = List[PatchId] -PatchFilterValueList = List[PatchFilterValue] +PatchSourceList = list[PatchSource] +PatchIdList = list[PatchId] +PatchFilterValueList = list[PatchFilterValue] class PatchFilter(TypedDict, total=False): @@ -2542,7 +2551,7 @@ class PatchFilter(TypedDict, total=False): Values: PatchFilterValueList -PatchFilterList = List[PatchFilter] +PatchFilterList = list[PatchFilter] class PatchFilterGroup(TypedDict, total=False): @@ -2551,13 +2560,13 @@ class PatchFilterGroup(TypedDict, total=False): class PatchRule(TypedDict, total=False): PatchFilterGroup: PatchFilterGroup - ComplianceLevel: Optional[PatchComplianceLevel] - ApproveAfterDays: Optional[ApproveAfterDays] - ApproveUntilDate: Optional[PatchStringDateTime] - EnableNonSecurity: Optional[Boolean] + ComplianceLevel: PatchComplianceLevel | None + ApproveAfterDays: ApproveAfterDays | None + ApproveUntilDate: PatchStringDateTime | None + EnableNonSecurity: Boolean | None -PatchRuleList = List[PatchRule] +PatchRuleList = list[PatchRule] class PatchRuleGroup(TypedDict, total=False): @@ -2565,24 +2574,24 @@ class PatchRuleGroup(TypedDict, total=False): class BaselineOverride(TypedDict, total=False): - OperatingSystem: Optional[OperatingSystem] - GlobalFilters: Optional[PatchFilterGroup] - ApprovalRules: Optional[PatchRuleGroup] - ApprovedPatches: Optional[PatchIdList] - ApprovedPatchesComplianceLevel: Optional[PatchComplianceLevel] - RejectedPatches: Optional[PatchIdList] - RejectedPatchesAction: Optional[PatchAction] - ApprovedPatchesEnableNonSecurity: Optional[Boolean] - Sources: Optional[PatchSourceList] - AvailableSecurityUpdatesComplianceStatus: Optional[PatchComplianceStatus] + OperatingSystem: OperatingSystem | None + GlobalFilters: PatchFilterGroup | None + ApprovalRules: PatchRuleGroup | None + ApprovedPatches: PatchIdList | None + ApprovedPatchesComplianceLevel: PatchComplianceLevel | None + RejectedPatches: PatchIdList | None + RejectedPatchesAction: PatchAction | None + ApprovedPatchesEnableNonSecurity: Boolean | None + Sources: PatchSourceList | None + AvailableSecurityUpdatesComplianceStatus: PatchComplianceStatus | None -InstanceIdList = List[InstanceId] +InstanceIdList = list[InstanceId] class CancelCommandRequest(ServiceRequest): CommandId: CommandId - InstanceIds: Optional[InstanceIdList] + InstanceIds: InstanceIdList | None class CancelCommandResult(TypedDict, total=False): @@ -2594,54 +2603,54 @@ class CancelMaintenanceWindowExecutionRequest(ServiceRequest): class CancelMaintenanceWindowExecutionResult(TypedDict, total=False): - WindowExecutionId: Optional[MaintenanceWindowExecutionId] + WindowExecutionId: MaintenanceWindowExecutionId | None -CategoryEnumList = List[Category] -CategoryList = List[Category] +CategoryEnumList = list[Category] +CategoryList = list[Category] class CloudWatchOutputConfig(TypedDict, total=False): - CloudWatchLogGroupName: Optional[CloudWatchLogGroupName] - CloudWatchOutputEnabled: Optional[CloudWatchOutputEnabled] + CloudWatchLogGroupName: CloudWatchLogGroupName | None + CloudWatchOutputEnabled: CloudWatchOutputEnabled | None -NotificationEventList = List[NotificationEvent] +NotificationEventList = list[NotificationEvent] class NotificationConfig(TypedDict, total=False): - NotificationArn: Optional[NotificationArn] - NotificationEvents: Optional[NotificationEventList] - NotificationType: Optional[NotificationType] + NotificationArn: NotificationArn | None + NotificationEvents: NotificationEventList | None + NotificationType: NotificationType | None class Command(TypedDict, total=False): - CommandId: Optional[CommandId] - DocumentName: Optional[DocumentName] - DocumentVersion: Optional[DocumentVersion] - Comment: Optional[Comment] - ExpiresAfter: Optional[DateTime] - Parameters: Optional[Parameters] - InstanceIds: Optional[InstanceIdList] - Targets: Optional[Targets] - RequestedDateTime: Optional[DateTime] - Status: Optional[CommandStatus] - StatusDetails: Optional[StatusDetails] - OutputS3Region: Optional[S3Region] - OutputS3BucketName: Optional[S3BucketName] - OutputS3KeyPrefix: Optional[S3KeyPrefix] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - TargetCount: Optional[TargetCount] - CompletedCount: Optional[CompletedCount] - ErrorCount: Optional[ErrorCount] - DeliveryTimedOutCount: Optional[DeliveryTimedOutCount] - ServiceRole: Optional[ServiceRole] - NotificationConfig: Optional[NotificationConfig] - CloudWatchOutputConfig: Optional[CloudWatchOutputConfig] - TimeoutSeconds: Optional[TimeoutSeconds] - AlarmConfiguration: Optional[AlarmConfiguration] - TriggeredAlarms: Optional[AlarmStateInformationList] + CommandId: CommandId | None + DocumentName: DocumentName | None + DocumentVersion: DocumentVersion | None + Comment: Comment | None + ExpiresAfter: DateTime | None + Parameters: Parameters | None + InstanceIds: InstanceIdList | None + Targets: Targets | None + RequestedDateTime: DateTime | None + Status: CommandStatus | None + StatusDetails: StatusDetails | None + OutputS3Region: S3Region | None + OutputS3BucketName: S3BucketName | None + OutputS3KeyPrefix: S3KeyPrefix | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + TargetCount: TargetCount | None + CompletedCount: CompletedCount | None + ErrorCount: ErrorCount | None + DeliveryTimedOutCount: DeliveryTimedOutCount | None + ServiceRole: ServiceRole | None + NotificationConfig: NotificationConfig | None + CloudWatchOutputConfig: CloudWatchOutputConfig | None + TimeoutSeconds: TimeoutSeconds | None + AlarmConfiguration: AlarmConfiguration | None + TriggeredAlarms: AlarmStateInformationList | None class CommandFilter(TypedDict, total=False): @@ -2649,121 +2658,121 @@ class CommandFilter(TypedDict, total=False): value: CommandFilterValue -CommandFilterList = List[CommandFilter] +CommandFilterList = list[CommandFilter] class CommandPlugin(TypedDict, total=False): - Name: Optional[CommandPluginName] - Status: Optional[CommandPluginStatus] - StatusDetails: Optional[StatusDetails] - ResponseCode: Optional[ResponseCode] - ResponseStartDateTime: Optional[DateTime] - ResponseFinishDateTime: Optional[DateTime] - Output: Optional[CommandPluginOutput] - StandardOutputUrl: Optional[Url] - StandardErrorUrl: Optional[Url] - OutputS3Region: Optional[S3Region] - OutputS3BucketName: Optional[S3BucketName] - OutputS3KeyPrefix: Optional[S3KeyPrefix] + Name: CommandPluginName | None + Status: CommandPluginStatus | None + StatusDetails: StatusDetails | None + ResponseCode: ResponseCode | None + ResponseStartDateTime: DateTime | None + ResponseFinishDateTime: DateTime | None + Output: CommandPluginOutput | None + StandardOutputUrl: Url | None + StandardErrorUrl: Url | None + OutputS3Region: S3Region | None + OutputS3BucketName: S3BucketName | None + OutputS3KeyPrefix: S3KeyPrefix | None -CommandPluginList = List[CommandPlugin] +CommandPluginList = list[CommandPlugin] class CommandInvocation(TypedDict, total=False): - CommandId: Optional[CommandId] - InstanceId: Optional[InstanceId] - InstanceName: Optional[InstanceTagName] - Comment: Optional[Comment] - DocumentName: Optional[DocumentName] - DocumentVersion: Optional[DocumentVersion] - RequestedDateTime: Optional[DateTime] - Status: Optional[CommandInvocationStatus] - StatusDetails: Optional[StatusDetails] - TraceOutput: Optional[InvocationTraceOutput] - StandardOutputUrl: Optional[Url] - StandardErrorUrl: Optional[Url] - CommandPlugins: Optional[CommandPluginList] - ServiceRole: Optional[ServiceRole] - NotificationConfig: Optional[NotificationConfig] - CloudWatchOutputConfig: Optional[CloudWatchOutputConfig] - - -CommandInvocationList = List[CommandInvocation] -CommandList = List[Command] + CommandId: CommandId | None + InstanceId: InstanceId | None + InstanceName: InstanceTagName | None + Comment: Comment | None + DocumentName: DocumentName | None + DocumentVersion: DocumentVersion | None + RequestedDateTime: DateTime | None + Status: CommandInvocationStatus | None + StatusDetails: StatusDetails | None + TraceOutput: InvocationTraceOutput | None + StandardOutputUrl: Url | None + StandardErrorUrl: Url | None + CommandPlugins: CommandPluginList | None + ServiceRole: ServiceRole | None + NotificationConfig: NotificationConfig | None + CloudWatchOutputConfig: CloudWatchOutputConfig | None + + +CommandInvocationList = list[CommandInvocation] +CommandList = list[Command] class ComplianceExecutionSummary(TypedDict, total=False): ExecutionTime: DateTime - ExecutionId: Optional[ComplianceExecutionId] - ExecutionType: Optional[ComplianceExecutionType] + ExecutionId: ComplianceExecutionId | None + ExecutionType: ComplianceExecutionType | None -ComplianceItemDetails = Dict[AttributeName, AttributeValue] +ComplianceItemDetails = dict[AttributeName, AttributeValue] class ComplianceItem(TypedDict, total=False): - ComplianceType: Optional[ComplianceTypeName] - ResourceType: Optional[ComplianceResourceType] - ResourceId: Optional[ComplianceResourceId] - Id: Optional[ComplianceItemId] - Title: Optional[ComplianceItemTitle] - Status: Optional[ComplianceStatus] - Severity: Optional[ComplianceSeverity] - ExecutionSummary: Optional[ComplianceExecutionSummary] - Details: Optional[ComplianceItemDetails] + ComplianceType: ComplianceTypeName | None + ResourceType: ComplianceResourceType | None + ResourceId: ComplianceResourceId | None + Id: ComplianceItemId | None + Title: ComplianceItemTitle | None + Status: ComplianceStatus | None + Severity: ComplianceSeverity | None + ExecutionSummary: ComplianceExecutionSummary | None + Details: ComplianceItemDetails | None class ComplianceItemEntry(TypedDict, total=False): - Id: Optional[ComplianceItemId] - Title: Optional[ComplianceItemTitle] + Id: ComplianceItemId | None + Title: ComplianceItemTitle | None Severity: ComplianceSeverity Status: ComplianceStatus - Details: Optional[ComplianceItemDetails] + Details: ComplianceItemDetails | None -ComplianceItemEntryList = List[ComplianceItemEntry] -ComplianceItemList = List[ComplianceItem] -ComplianceResourceIdList = List[ComplianceResourceId] -ComplianceResourceTypeList = List[ComplianceResourceType] -ComplianceStringFilterValueList = List[ComplianceFilterValue] +ComplianceItemEntryList = list[ComplianceItemEntry] +ComplianceItemList = list[ComplianceItem] +ComplianceResourceIdList = list[ComplianceResourceId] +ComplianceResourceTypeList = list[ComplianceResourceType] +ComplianceStringFilterValueList = list[ComplianceFilterValue] class ComplianceStringFilter(TypedDict, total=False): - Key: Optional[ComplianceStringFilterKey] - Values: Optional[ComplianceStringFilterValueList] - Type: Optional[ComplianceQueryOperatorType] + Key: ComplianceStringFilterKey | None + Values: ComplianceStringFilterValueList | None + Type: ComplianceQueryOperatorType | None -ComplianceStringFilterList = List[ComplianceStringFilter] +ComplianceStringFilterList = list[ComplianceStringFilter] class SeveritySummary(TypedDict, total=False): - CriticalCount: Optional[ComplianceSummaryCount] - HighCount: Optional[ComplianceSummaryCount] - MediumCount: Optional[ComplianceSummaryCount] - LowCount: Optional[ComplianceSummaryCount] - InformationalCount: Optional[ComplianceSummaryCount] - UnspecifiedCount: Optional[ComplianceSummaryCount] + CriticalCount: ComplianceSummaryCount | None + HighCount: ComplianceSummaryCount | None + MediumCount: ComplianceSummaryCount | None + LowCount: ComplianceSummaryCount | None + InformationalCount: ComplianceSummaryCount | None + UnspecifiedCount: ComplianceSummaryCount | None class NonCompliantSummary(TypedDict, total=False): - NonCompliantCount: Optional[ComplianceSummaryCount] - SeveritySummary: Optional[SeveritySummary] + NonCompliantCount: ComplianceSummaryCount | None + SeveritySummary: SeveritySummary | None class CompliantSummary(TypedDict, total=False): - CompliantCount: Optional[ComplianceSummaryCount] - SeveritySummary: Optional[SeveritySummary] + CompliantCount: ComplianceSummaryCount | None + SeveritySummary: SeveritySummary | None class ComplianceSummaryItem(TypedDict, total=False): - ComplianceType: Optional[ComplianceTypeName] - CompliantSummary: Optional[CompliantSummary] - NonCompliantSummary: Optional[NonCompliantSummary] + ComplianceType: ComplianceTypeName | None + CompliantSummary: CompliantSummary | None + NonCompliantSummary: NonCompliantSummary | None -ComplianceSummaryItemList = List[ComplianceSummaryItem] +ComplianceSummaryItemList = list[ComplianceSummaryItem] class RegistrationMetadataItem(TypedDict, total=False): @@ -2771,320 +2780,322 @@ class RegistrationMetadataItem(TypedDict, total=False): Value: RegistrationMetadataValue -RegistrationMetadataList = List[RegistrationMetadataItem] +RegistrationMetadataList = list[RegistrationMetadataItem] class CreateActivationRequest(ServiceRequest): - Description: Optional[ActivationDescription] - DefaultInstanceName: Optional[DefaultInstanceName] + Description: ActivationDescription | None + DefaultInstanceName: DefaultInstanceName | None IamRole: IamRole - RegistrationLimit: Optional[RegistrationLimit] - ExpirationDate: Optional[ExpirationDate] - Tags: Optional[TagList] - RegistrationMetadata: Optional[RegistrationMetadataList] + RegistrationLimit: RegistrationLimit | None + ExpirationDate: ExpirationDate | None + Tags: TagList | None + RegistrationMetadata: RegistrationMetadataList | None class CreateActivationResult(TypedDict, total=False): - ActivationId: Optional[ActivationId] - ActivationCode: Optional[ActivationCode] + ActivationId: ActivationId | None + ActivationCode: ActivationCode | None class CreateAssociationBatchRequestEntry(TypedDict, total=False): Name: DocumentARN - InstanceId: Optional[InstanceId] - Parameters: Optional[Parameters] - AutomationTargetParameterName: Optional[AutomationTargetParameterName] - DocumentVersion: Optional[DocumentVersion] - Targets: Optional[Targets] - ScheduleExpression: Optional[ScheduleExpression] - OutputLocation: Optional[InstanceAssociationOutputLocation] - AssociationName: Optional[AssociationName] - MaxErrors: Optional[MaxErrors] - MaxConcurrency: Optional[MaxConcurrency] - ComplianceSeverity: Optional[AssociationComplianceSeverity] - SyncCompliance: Optional[AssociationSyncCompliance] - ApplyOnlyAtCronInterval: Optional[ApplyOnlyAtCronInterval] - CalendarNames: Optional[CalendarNameOrARNList] - TargetLocations: Optional[TargetLocations] - ScheduleOffset: Optional[ScheduleOffset] - Duration: Optional[Duration] - TargetMaps: Optional[TargetMaps] - AlarmConfiguration: Optional[AlarmConfiguration] - - -CreateAssociationBatchRequestEntries = List[CreateAssociationBatchRequestEntry] + InstanceId: InstanceId | None + Parameters: Parameters | None + AutomationTargetParameterName: AutomationTargetParameterName | None + DocumentVersion: DocumentVersion | None + Targets: Targets | None + ScheduleExpression: ScheduleExpression | None + OutputLocation: InstanceAssociationOutputLocation | None + AssociationName: AssociationName | None + MaxErrors: MaxErrors | None + MaxConcurrency: MaxConcurrency | None + ComplianceSeverity: AssociationComplianceSeverity | None + SyncCompliance: AssociationSyncCompliance | None + ApplyOnlyAtCronInterval: ApplyOnlyAtCronInterval | None + CalendarNames: CalendarNameOrARNList | None + TargetLocations: TargetLocations | None + ScheduleOffset: ScheduleOffset | None + Duration: Duration | None + TargetMaps: TargetMaps | None + AlarmConfiguration: AlarmConfiguration | None + + +CreateAssociationBatchRequestEntries = list[CreateAssociationBatchRequestEntry] class CreateAssociationBatchRequest(ServiceRequest): Entries: CreateAssociationBatchRequestEntries + AssociationDispatchAssumeRole: AssociationDispatchAssumeRoleArn | None class FailedCreateAssociation(TypedDict, total=False): - Entry: Optional[CreateAssociationBatchRequestEntry] - Message: Optional[BatchErrorMessage] - Fault: Optional[Fault] + Entry: CreateAssociationBatchRequestEntry | None + Message: BatchErrorMessage | None + Fault: Fault | None -FailedCreateAssociationList = List[FailedCreateAssociation] +FailedCreateAssociationList = list[FailedCreateAssociation] class CreateAssociationBatchResult(TypedDict, total=False): - Successful: Optional[AssociationDescriptionList] - Failed: Optional[FailedCreateAssociationList] + Successful: AssociationDescriptionList | None + Failed: FailedCreateAssociationList | None class CreateAssociationRequest(ServiceRequest): Name: DocumentARN - DocumentVersion: Optional[DocumentVersion] - InstanceId: Optional[InstanceId] - Parameters: Optional[Parameters] - Targets: Optional[Targets] - ScheduleExpression: Optional[ScheduleExpression] - OutputLocation: Optional[InstanceAssociationOutputLocation] - AssociationName: Optional[AssociationName] - AutomationTargetParameterName: Optional[AutomationTargetParameterName] - MaxErrors: Optional[MaxErrors] - MaxConcurrency: Optional[MaxConcurrency] - ComplianceSeverity: Optional[AssociationComplianceSeverity] - SyncCompliance: Optional[AssociationSyncCompliance] - ApplyOnlyAtCronInterval: Optional[ApplyOnlyAtCronInterval] - CalendarNames: Optional[CalendarNameOrARNList] - TargetLocations: Optional[TargetLocations] - ScheduleOffset: Optional[ScheduleOffset] - Duration: Optional[Duration] - TargetMaps: Optional[TargetMaps] - Tags: Optional[TagList] - AlarmConfiguration: Optional[AlarmConfiguration] + DocumentVersion: DocumentVersion | None + InstanceId: InstanceId | None + Parameters: Parameters | None + Targets: Targets | None + ScheduleExpression: ScheduleExpression | None + OutputLocation: InstanceAssociationOutputLocation | None + AssociationName: AssociationName | None + AutomationTargetParameterName: AutomationTargetParameterName | None + MaxErrors: MaxErrors | None + MaxConcurrency: MaxConcurrency | None + ComplianceSeverity: AssociationComplianceSeverity | None + SyncCompliance: AssociationSyncCompliance | None + ApplyOnlyAtCronInterval: ApplyOnlyAtCronInterval | None + CalendarNames: CalendarNameOrARNList | None + TargetLocations: TargetLocations | None + ScheduleOffset: ScheduleOffset | None + Duration: Duration | None + TargetMaps: TargetMaps | None + Tags: TagList | None + AlarmConfiguration: AlarmConfiguration | None + AssociationDispatchAssumeRole: AssociationDispatchAssumeRoleArn | None class CreateAssociationResult(TypedDict, total=False): - AssociationDescription: Optional[AssociationDescription] + AssociationDescription: AssociationDescription | None class DocumentRequires(TypedDict, total=False): Name: DocumentARN - Version: Optional[DocumentVersion] - RequireType: Optional[RequireType] - VersionName: Optional[DocumentVersionName] + Version: DocumentVersion | None + RequireType: RequireType | None + VersionName: DocumentVersionName | None -DocumentRequiresList = List[DocumentRequires] +DocumentRequiresList = list[DocumentRequires] class CreateDocumentRequest(ServiceRequest): Content: DocumentContent - Requires: Optional[DocumentRequiresList] - Attachments: Optional[AttachmentsSourceList] + Requires: DocumentRequiresList | None + Attachments: AttachmentsSourceList | None Name: DocumentName - DisplayName: Optional[DocumentDisplayName] - VersionName: Optional[DocumentVersionName] - DocumentType: Optional[DocumentType] - DocumentFormat: Optional[DocumentFormat] - TargetType: Optional[TargetType] - Tags: Optional[TagList] + DisplayName: DocumentDisplayName | None + VersionName: DocumentVersionName | None + DocumentType: DocumentType | None + DocumentFormat: DocumentFormat | None + TargetType: TargetType | None + Tags: TagList | None class ReviewInformation(TypedDict, total=False): - ReviewedTime: Optional[DateTime] - Status: Optional[ReviewStatus] - Reviewer: Optional[Reviewer] + ReviewedTime: DateTime | None + Status: ReviewStatus | None + Reviewer: Reviewer | None -ReviewInformationList = List[ReviewInformation] -PlatformTypeList = List[PlatformType] +ReviewInformationList = list[ReviewInformation] +PlatformTypeList = list[PlatformType] class DocumentParameter(TypedDict, total=False): - Name: Optional[DocumentParameterName] - Type: Optional[DocumentParameterType] - Description: Optional[DocumentParameterDescrption] - DefaultValue: Optional[DocumentParameterDefaultValue] + Name: DocumentParameterName | None + Type: DocumentParameterType | None + Description: DocumentParameterDescrption | None + DefaultValue: DocumentParameterDefaultValue | None -DocumentParameterList = List[DocumentParameter] +DocumentParameterList = list[DocumentParameter] class DocumentDescription(TypedDict, total=False): - Sha1: Optional[DocumentSha1] - Hash: Optional[DocumentHash] - HashType: Optional[DocumentHashType] - Name: Optional[DocumentARN] - DisplayName: Optional[DocumentDisplayName] - VersionName: Optional[DocumentVersionName] - Owner: Optional[DocumentOwner] - CreatedDate: Optional[DateTime] - Status: Optional[DocumentStatus] - StatusInformation: Optional[DocumentStatusInformation] - DocumentVersion: Optional[DocumentVersion] - Description: Optional[DescriptionInDocument] - Parameters: Optional[DocumentParameterList] - PlatformTypes: Optional[PlatformTypeList] - DocumentType: Optional[DocumentType] - SchemaVersion: Optional[DocumentSchemaVersion] - LatestVersion: Optional[DocumentVersion] - DefaultVersion: Optional[DocumentVersion] - DocumentFormat: Optional[DocumentFormat] - TargetType: Optional[TargetType] - Tags: Optional[TagList] - AttachmentsInformation: Optional[AttachmentInformationList] - Requires: Optional[DocumentRequiresList] - Author: Optional[DocumentAuthor] - ReviewInformation: Optional[ReviewInformationList] - ApprovedVersion: Optional[DocumentVersion] - PendingReviewVersion: Optional[DocumentVersion] - ReviewStatus: Optional[ReviewStatus] - Category: Optional[CategoryList] - CategoryEnum: Optional[CategoryEnumList] + Sha1: DocumentSha1 | None + Hash: DocumentHash | None + HashType: DocumentHashType | None + Name: DocumentARN | None + DisplayName: DocumentDisplayName | None + VersionName: DocumentVersionName | None + Owner: DocumentOwner | None + CreatedDate: DateTime | None + Status: DocumentStatus | None + StatusInformation: DocumentStatusInformation | None + DocumentVersion: DocumentVersion | None + Description: DescriptionInDocument | None + Parameters: DocumentParameterList | None + PlatformTypes: PlatformTypeList | None + DocumentType: DocumentType | None + SchemaVersion: DocumentSchemaVersion | None + LatestVersion: DocumentVersion | None + DefaultVersion: DocumentVersion | None + DocumentFormat: DocumentFormat | None + TargetType: TargetType | None + Tags: TagList | None + AttachmentsInformation: AttachmentInformationList | None + Requires: DocumentRequiresList | None + Author: DocumentAuthor | None + ReviewInformation: ReviewInformationList | None + ApprovedVersion: DocumentVersion | None + PendingReviewVersion: DocumentVersion | None + ReviewStatus: ReviewStatus | None + Category: CategoryList | None + CategoryEnum: CategoryEnumList | None class CreateDocumentResult(TypedDict, total=False): - DocumentDescription: Optional[DocumentDescription] + DocumentDescription: DocumentDescription | None class CreateMaintenanceWindowRequest(ServiceRequest): Name: MaintenanceWindowName - Description: Optional[MaintenanceWindowDescription] - StartDate: Optional[MaintenanceWindowStringDateTime] - EndDate: Optional[MaintenanceWindowStringDateTime] + Description: MaintenanceWindowDescription | None + StartDate: MaintenanceWindowStringDateTime | None + EndDate: MaintenanceWindowStringDateTime | None Schedule: MaintenanceWindowSchedule - ScheduleTimezone: Optional[MaintenanceWindowTimezone] - ScheduleOffset: Optional[MaintenanceWindowOffset] + ScheduleTimezone: MaintenanceWindowTimezone | None + ScheduleOffset: MaintenanceWindowOffset | None Duration: MaintenanceWindowDurationHours Cutoff: MaintenanceWindowCutoff AllowUnassociatedTargets: MaintenanceWindowAllowUnassociatedTargets - ClientToken: Optional[ClientToken] - Tags: Optional[TagList] + ClientToken: ClientToken | None + Tags: TagList | None class CreateMaintenanceWindowResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] + WindowId: MaintenanceWindowId | None class RelatedOpsItem(TypedDict, total=False): OpsItemId: String -RelatedOpsItems = List[RelatedOpsItem] +RelatedOpsItems = list[RelatedOpsItem] class OpsItemNotification(TypedDict, total=False): - Arn: Optional[String] + Arn: String | None -OpsItemNotifications = List[OpsItemNotification] +OpsItemNotifications = list[OpsItemNotification] class OpsItemDataValue(TypedDict, total=False): - Value: Optional[OpsItemDataValueString] - Type: Optional[OpsItemDataType] + Value: OpsItemDataValueString | None + Type: OpsItemDataType | None -OpsItemOperationalData = Dict[OpsItemDataKey, OpsItemDataValue] +OpsItemOperationalData = dict[OpsItemDataKey, OpsItemDataValue] class CreateOpsItemRequest(ServiceRequest): Description: OpsItemDescription - OpsItemType: Optional[OpsItemType] - OperationalData: Optional[OpsItemOperationalData] - Notifications: Optional[OpsItemNotifications] - Priority: Optional[OpsItemPriority] - RelatedOpsItems: Optional[RelatedOpsItems] + OpsItemType: OpsItemType | None + OperationalData: OpsItemOperationalData | None + Notifications: OpsItemNotifications | None + Priority: OpsItemPriority | None + RelatedOpsItems: RelatedOpsItems | None Source: OpsItemSource Title: OpsItemTitle - Tags: Optional[TagList] - Category: Optional[OpsItemCategory] - Severity: Optional[OpsItemSeverity] - ActualStartTime: Optional[DateTime] - ActualEndTime: Optional[DateTime] - PlannedStartTime: Optional[DateTime] - PlannedEndTime: Optional[DateTime] - AccountId: Optional[OpsItemAccountId] + Tags: TagList | None + Category: OpsItemCategory | None + Severity: OpsItemSeverity | None + ActualStartTime: DateTime | None + ActualEndTime: DateTime | None + PlannedStartTime: DateTime | None + PlannedEndTime: DateTime | None + AccountId: OpsItemAccountId | None class CreateOpsItemResponse(TypedDict, total=False): - OpsItemId: Optional[String] - OpsItemArn: Optional[OpsItemArn] + OpsItemId: String | None + OpsItemArn: OpsItemArn | None class MetadataValue(TypedDict, total=False): - Value: Optional[MetadataValueString] + Value: MetadataValueString | None -MetadataMap = Dict[MetadataKey, MetadataValue] +MetadataMap = dict[MetadataKey, MetadataValue] class CreateOpsMetadataRequest(ServiceRequest): ResourceId: OpsMetadataResourceId - Metadata: Optional[MetadataMap] - Tags: Optional[TagList] + Metadata: MetadataMap | None + Tags: TagList | None class CreateOpsMetadataResult(TypedDict, total=False): - OpsMetadataArn: Optional[OpsMetadataArn] + OpsMetadataArn: OpsMetadataArn | None class CreatePatchBaselineRequest(ServiceRequest): - OperatingSystem: Optional[OperatingSystem] + OperatingSystem: OperatingSystem | None Name: BaselineName - GlobalFilters: Optional[PatchFilterGroup] - ApprovalRules: Optional[PatchRuleGroup] - ApprovedPatches: Optional[PatchIdList] - ApprovedPatchesComplianceLevel: Optional[PatchComplianceLevel] - ApprovedPatchesEnableNonSecurity: Optional[Boolean] - RejectedPatches: Optional[PatchIdList] - RejectedPatchesAction: Optional[PatchAction] - Description: Optional[BaselineDescription] - Sources: Optional[PatchSourceList] - AvailableSecurityUpdatesComplianceStatus: Optional[PatchComplianceStatus] - ClientToken: Optional[ClientToken] - Tags: Optional[TagList] + GlobalFilters: PatchFilterGroup | None + ApprovalRules: PatchRuleGroup | None + ApprovedPatches: PatchIdList | None + ApprovedPatchesComplianceLevel: PatchComplianceLevel | None + ApprovedPatchesEnableNonSecurity: Boolean | None + RejectedPatches: PatchIdList | None + RejectedPatchesAction: PatchAction | None + Description: BaselineDescription | None + Sources: PatchSourceList | None + AvailableSecurityUpdatesComplianceStatus: PatchComplianceStatus | None + ClientToken: ClientToken | None + Tags: TagList | None class CreatePatchBaselineResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] + BaselineId: BaselineId | None -ResourceDataSyncSourceRegionList = List[ResourceDataSyncSourceRegion] +ResourceDataSyncSourceRegionList = list[ResourceDataSyncSourceRegion] class ResourceDataSyncOrganizationalUnit(TypedDict, total=False): - OrganizationalUnitId: Optional[ResourceDataSyncOrganizationalUnitId] + OrganizationalUnitId: ResourceDataSyncOrganizationalUnitId | None -ResourceDataSyncOrganizationalUnitList = List[ResourceDataSyncOrganizationalUnit] +ResourceDataSyncOrganizationalUnitList = list[ResourceDataSyncOrganizationalUnit] class ResourceDataSyncAwsOrganizationsSource(TypedDict, total=False): OrganizationSourceType: ResourceDataSyncOrganizationSourceType - OrganizationalUnits: Optional[ResourceDataSyncOrganizationalUnitList] + OrganizationalUnits: ResourceDataSyncOrganizationalUnitList | None class ResourceDataSyncSource(TypedDict, total=False): SourceType: ResourceDataSyncSourceType - AwsOrganizationsSource: Optional[ResourceDataSyncAwsOrganizationsSource] + AwsOrganizationsSource: ResourceDataSyncAwsOrganizationsSource | None SourceRegions: ResourceDataSyncSourceRegionList - IncludeFutureRegions: Optional[ResourceDataSyncIncludeFutureRegions] - EnableAllOpsDataSources: Optional[ResourceDataSyncEnableAllOpsDataSources] + IncludeFutureRegions: ResourceDataSyncIncludeFutureRegions | None + EnableAllOpsDataSources: ResourceDataSyncEnableAllOpsDataSources | None class ResourceDataSyncDestinationDataSharing(TypedDict, total=False): - DestinationDataSharingType: Optional[ResourceDataSyncDestinationDataSharingType] + DestinationDataSharingType: ResourceDataSyncDestinationDataSharingType | None class ResourceDataSyncS3Destination(TypedDict, total=False): BucketName: ResourceDataSyncS3BucketName - Prefix: Optional[ResourceDataSyncS3Prefix] + Prefix: ResourceDataSyncS3Prefix | None SyncFormat: ResourceDataSyncS3Format Region: ResourceDataSyncS3Region - AWSKMSKeyARN: Optional[ResourceDataSyncAWSKMSKeyARN] - DestinationDataSharing: Optional[ResourceDataSyncDestinationDataSharing] + AWSKMSKeyARN: ResourceDataSyncAWSKMSKeyARN | None + DestinationDataSharing: ResourceDataSyncDestinationDataSharing | None class CreateResourceDataSyncRequest(ServiceRequest): SyncName: ResourceDataSyncName - S3Destination: Optional[ResourceDataSyncS3Destination] - SyncType: Optional[ResourceDataSyncType] - SyncSource: Optional[ResourceDataSyncSource] + S3Destination: ResourceDataSyncS3Destination | None + SyncType: ResourceDataSyncType | None + SyncSource: ResourceDataSyncSource | None class CreateResourceDataSyncResult(TypedDict, total=False): @@ -3107,9 +3118,9 @@ class DeleteActivationResult(TypedDict, total=False): class DeleteAssociationRequest(ServiceRequest): - Name: Optional[DocumentARN] - InstanceId: Optional[InstanceId] - AssociationId: Optional[AssociationId] + Name: DocumentARN | None + InstanceId: InstanceId | None + AssociationId: AssociationId | None class DeleteAssociationResult(TypedDict, total=False): @@ -3118,9 +3129,9 @@ class DeleteAssociationResult(TypedDict, total=False): class DeleteDocumentRequest(ServiceRequest): Name: DocumentName - DocumentVersion: Optional[DocumentVersion] - VersionName: Optional[DocumentVersionName] - Force: Optional[Boolean] + DocumentVersion: DocumentVersion | None + VersionName: DocumentVersionName | None + Force: Boolean | None class DeleteDocumentResult(TypedDict, total=False): @@ -3129,30 +3140,30 @@ class DeleteDocumentResult(TypedDict, total=False): class DeleteInventoryRequest(ServiceRequest): TypeName: InventoryItemTypeName - SchemaDeleteOption: Optional[InventorySchemaDeleteOption] - DryRun: Optional[DryRun] - ClientToken: Optional[UUID] + SchemaDeleteOption: InventorySchemaDeleteOption | None + DryRun: DryRun | None + ClientToken: UUID | None class InventoryDeletionSummaryItem(TypedDict, total=False): - Version: Optional[InventoryItemSchemaVersion] - Count: Optional[ResourceCount] - RemainingCount: Optional[RemainingCount] + Version: InventoryItemSchemaVersion | None + Count: ResourceCount | None + RemainingCount: RemainingCount | None -InventoryDeletionSummaryItems = List[InventoryDeletionSummaryItem] +InventoryDeletionSummaryItems = list[InventoryDeletionSummaryItem] class InventoryDeletionSummary(TypedDict, total=False): - TotalCount: Optional[TotalCount] - RemainingCount: Optional[RemainingCount] - SummaryItems: Optional[InventoryDeletionSummaryItems] + TotalCount: TotalCount | None + RemainingCount: RemainingCount | None + SummaryItems: InventoryDeletionSummaryItems | None class DeleteInventoryResult(TypedDict, total=False): - DeletionId: Optional[UUID] - TypeName: Optional[InventoryItemTypeName] - DeletionSummary: Optional[InventoryDeletionSummary] + DeletionId: UUID | None + TypeName: InventoryItemTypeName | None + DeletionSummary: InventoryDeletionSummary | None class DeleteMaintenanceWindowRequest(ServiceRequest): @@ -3160,7 +3171,7 @@ class DeleteMaintenanceWindowRequest(ServiceRequest): class DeleteMaintenanceWindowResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] + WindowId: MaintenanceWindowId | None class DeleteOpsItemRequest(ServiceRequest): @@ -3187,7 +3198,7 @@ class DeleteParameterResult(TypedDict, total=False): pass -ParameterNameList = List[PSParameterName] +ParameterNameList = list[PSParameterName] class DeleteParametersRequest(ServiceRequest): @@ -3195,8 +3206,8 @@ class DeleteParametersRequest(ServiceRequest): class DeleteParametersResult(TypedDict, total=False): - DeletedParameters: Optional[ParameterNameList] - InvalidParameters: Optional[ParameterNameList] + DeletedParameters: ParameterNameList | None + InvalidParameters: ParameterNameList | None class DeletePatchBaselineRequest(ServiceRequest): @@ -3204,12 +3215,12 @@ class DeletePatchBaselineRequest(ServiceRequest): class DeletePatchBaselineResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] + BaselineId: BaselineId | None class DeleteResourceDataSyncRequest(ServiceRequest): SyncName: ResourceDataSyncName - SyncType: Optional[ResourceDataSyncType] + SyncType: ResourceDataSyncType | None class DeleteResourceDataSyncResult(TypedDict, total=False): @@ -3240,19 +3251,19 @@ class DeregisterPatchBaselineForPatchGroupRequest(ServiceRequest): class DeregisterPatchBaselineForPatchGroupResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] - PatchGroup: Optional[PatchGroup] + BaselineId: BaselineId | None + PatchGroup: PatchGroup | None class DeregisterTargetFromMaintenanceWindowRequest(ServiceRequest): WindowId: MaintenanceWindowId WindowTargetId: MaintenanceWindowTargetId - Safe: Optional[Boolean] + Safe: Boolean | None class DeregisterTargetFromMaintenanceWindowResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowTargetId: Optional[MaintenanceWindowTargetId] + WindowId: MaintenanceWindowId | None + WindowTargetId: MaintenanceWindowTargetId | None class DeregisterTaskFromMaintenanceWindowRequest(ServiceRequest): @@ -3261,80 +3272,80 @@ class DeregisterTaskFromMaintenanceWindowRequest(ServiceRequest): class DeregisterTaskFromMaintenanceWindowResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowTaskId: Optional[MaintenanceWindowTaskId] + WindowId: MaintenanceWindowId | None + WindowTaskId: MaintenanceWindowTaskId | None -StringList = List[String] +StringList = list[String] class DescribeActivationsFilter(TypedDict, total=False): - FilterKey: Optional[DescribeActivationsFilterKeys] - FilterValues: Optional[StringList] + FilterKey: DescribeActivationsFilterKeys | None + FilterValues: StringList | None -DescribeActivationsFilterList = List[DescribeActivationsFilter] +DescribeActivationsFilterList = list[DescribeActivationsFilter] class DescribeActivationsRequest(ServiceRequest): - Filters: Optional[DescribeActivationsFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: DescribeActivationsFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class DescribeActivationsResult(TypedDict, total=False): - ActivationList: Optional[ActivationList] - NextToken: Optional[NextToken] + ActivationList: ActivationList | None + NextToken: NextToken | None class DescribeAssociationExecutionTargetsRequest(ServiceRequest): AssociationId: AssociationId ExecutionId: AssociationExecutionId - Filters: Optional[AssociationExecutionTargetsFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: AssociationExecutionTargetsFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class DescribeAssociationExecutionTargetsResult(TypedDict, total=False): - AssociationExecutionTargets: Optional[AssociationExecutionTargetsList] - NextToken: Optional[NextToken] + AssociationExecutionTargets: AssociationExecutionTargetsList | None + NextToken: NextToken | None class DescribeAssociationExecutionsRequest(ServiceRequest): AssociationId: AssociationId - Filters: Optional[AssociationExecutionFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: AssociationExecutionFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class DescribeAssociationExecutionsResult(TypedDict, total=False): - AssociationExecutions: Optional[AssociationExecutionsList] - NextToken: Optional[NextToken] + AssociationExecutions: AssociationExecutionsList | None + NextToken: NextToken | None class DescribeAssociationRequest(ServiceRequest): - Name: Optional[DocumentARN] - InstanceId: Optional[InstanceId] - AssociationId: Optional[AssociationId] - AssociationVersion: Optional[AssociationVersion] + Name: DocumentARN | None + InstanceId: InstanceId | None + AssociationId: AssociationId | None + AssociationVersion: AssociationVersion | None class DescribeAssociationResult(TypedDict, total=False): - AssociationDescription: Optional[AssociationDescription] + AssociationDescription: AssociationDescription | None class DescribeAutomationExecutionsRequest(ServiceRequest): - Filters: Optional[AutomationExecutionFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + Filters: AutomationExecutionFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class DescribeAutomationExecutionsResult(TypedDict, total=False): - AutomationExecutionMetadataList: Optional[AutomationExecutionMetadataList] - NextToken: Optional[NextToken] + AutomationExecutionMetadataList: AutomationExecutionMetadataList | None + NextToken: NextToken | None -StepExecutionFilterValueList = List[StepExecutionFilterValue] +StepExecutionFilterValueList = list[StepExecutionFilterValue] class StepExecutionFilter(TypedDict, total=False): @@ -3342,185 +3353,185 @@ class StepExecutionFilter(TypedDict, total=False): Values: StepExecutionFilterValueList -StepExecutionFilterList = List[StepExecutionFilter] +StepExecutionFilterList = list[StepExecutionFilter] class DescribeAutomationStepExecutionsRequest(ServiceRequest): AutomationExecutionId: AutomationExecutionId - Filters: Optional[StepExecutionFilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - ReverseOrder: Optional[Boolean] + Filters: StepExecutionFilterList | None + NextToken: NextToken | None + MaxResults: MaxResults | None + ReverseOrder: Boolean | None class DescribeAutomationStepExecutionsResult(TypedDict, total=False): - StepExecutions: Optional[StepExecutionList] - NextToken: Optional[NextToken] + StepExecutions: StepExecutionList | None + NextToken: NextToken | None -PatchOrchestratorFilterValues = List[PatchOrchestratorFilterValue] +PatchOrchestratorFilterValues = list[PatchOrchestratorFilterValue] class PatchOrchestratorFilter(TypedDict, total=False): - Key: Optional[PatchOrchestratorFilterKey] - Values: Optional[PatchOrchestratorFilterValues] + Key: PatchOrchestratorFilterKey | None + Values: PatchOrchestratorFilterValues | None -PatchOrchestratorFilterList = List[PatchOrchestratorFilter] +PatchOrchestratorFilterList = list[PatchOrchestratorFilter] class DescribeAvailablePatchesRequest(ServiceRequest): - Filters: Optional[PatchOrchestratorFilterList] - MaxResults: Optional[PatchBaselineMaxResults] - NextToken: Optional[NextToken] + Filters: PatchOrchestratorFilterList | None + MaxResults: PatchBaselineMaxResults | None + NextToken: NextToken | None -PatchCVEIdList = List[PatchCVEId] -PatchBugzillaIdList = List[PatchBugzillaId] -PatchAdvisoryIdList = List[PatchAdvisoryId] +PatchCVEIdList = list[PatchCVEId] +PatchBugzillaIdList = list[PatchBugzillaId] +PatchAdvisoryIdList = list[PatchAdvisoryId] class Patch(TypedDict, total=False): - Id: Optional[PatchId] - ReleaseDate: Optional[DateTime] - Title: Optional[PatchTitle] - Description: Optional[PatchDescription] - ContentUrl: Optional[PatchContentUrl] - Vendor: Optional[PatchVendor] - ProductFamily: Optional[PatchProductFamily] - Product: Optional[PatchProduct] - Classification: Optional[PatchClassification] - MsrcSeverity: Optional[PatchMsrcSeverity] - KbNumber: Optional[PatchKbNumber] - MsrcNumber: Optional[PatchMsrcNumber] - Language: Optional[PatchLanguage] - AdvisoryIds: Optional[PatchAdvisoryIdList] - BugzillaIds: Optional[PatchBugzillaIdList] - CVEIds: Optional[PatchCVEIdList] - Name: Optional[PatchName] - Epoch: Optional[PatchEpoch] - Version: Optional[PatchVersion] - Release: Optional[PatchRelease] - Arch: Optional[PatchArch] - Severity: Optional[PatchSeverity] - Repository: Optional[PatchRepository] - - -PatchList = List[Patch] + Id: PatchId | None + ReleaseDate: DateTime | None + Title: PatchTitle | None + Description: PatchDescription | None + ContentUrl: PatchContentUrl | None + Vendor: PatchVendor | None + ProductFamily: PatchProductFamily | None + Product: PatchProduct | None + Classification: PatchClassification | None + MsrcSeverity: PatchMsrcSeverity | None + KbNumber: PatchKbNumber | None + MsrcNumber: PatchMsrcNumber | None + Language: PatchLanguage | None + AdvisoryIds: PatchAdvisoryIdList | None + BugzillaIds: PatchBugzillaIdList | None + CVEIds: PatchCVEIdList | None + Name: PatchName | None + Epoch: PatchEpoch | None + Version: PatchVersion | None + Release: PatchRelease | None + Arch: PatchArch | None + Severity: PatchSeverity | None + Repository: PatchRepository | None + + +PatchList = list[Patch] class DescribeAvailablePatchesResult(TypedDict, total=False): - Patches: Optional[PatchList] - NextToken: Optional[NextToken] + Patches: PatchList | None + NextToken: NextToken | None class DescribeDocumentPermissionRequest(ServiceRequest): Name: DocumentName PermissionType: DocumentPermissionType - MaxResults: Optional[DocumentPermissionMaxResults] - NextToken: Optional[NextToken] + MaxResults: DocumentPermissionMaxResults | None + NextToken: NextToken | None class DescribeDocumentPermissionResponse(TypedDict, total=False): - AccountIds: Optional[AccountIdList] - AccountSharingInfoList: Optional[AccountSharingInfoList] - NextToken: Optional[NextToken] + AccountIds: AccountIdList | None + AccountSharingInfoList: AccountSharingInfoList | None + NextToken: NextToken | None class DescribeDocumentRequest(ServiceRequest): Name: DocumentARN - DocumentVersion: Optional[DocumentVersion] - VersionName: Optional[DocumentVersionName] + DocumentVersion: DocumentVersion | None + VersionName: DocumentVersionName | None class DescribeDocumentResult(TypedDict, total=False): - Document: Optional[DocumentDescription] + Document: DocumentDescription | None class DescribeEffectiveInstanceAssociationsRequest(ServiceRequest): InstanceId: InstanceId - MaxResults: Optional[EffectiveInstanceAssociationMaxResults] - NextToken: Optional[NextToken] + MaxResults: EffectiveInstanceAssociationMaxResults | None + NextToken: NextToken | None class InstanceAssociation(TypedDict, total=False): - AssociationId: Optional[AssociationId] - InstanceId: Optional[InstanceId] - Content: Optional[DocumentContent] - AssociationVersion: Optional[AssociationVersion] + AssociationId: AssociationId | None + InstanceId: InstanceId | None + Content: DocumentContent | None + AssociationVersion: AssociationVersion | None -InstanceAssociationList = List[InstanceAssociation] +InstanceAssociationList = list[InstanceAssociation] class DescribeEffectiveInstanceAssociationsResult(TypedDict, total=False): - Associations: Optional[InstanceAssociationList] - NextToken: Optional[NextToken] + Associations: InstanceAssociationList | None + NextToken: NextToken | None class DescribeEffectivePatchesForPatchBaselineRequest(ServiceRequest): BaselineId: BaselineId - MaxResults: Optional[PatchBaselineMaxResults] - NextToken: Optional[NextToken] + MaxResults: PatchBaselineMaxResults | None + NextToken: NextToken | None class PatchStatus(TypedDict, total=False): - DeploymentStatus: Optional[PatchDeploymentStatus] - ComplianceLevel: Optional[PatchComplianceLevel] - ApprovalDate: Optional[DateTime] + DeploymentStatus: PatchDeploymentStatus | None + ComplianceLevel: PatchComplianceLevel | None + ApprovalDate: DateTime | None class EffectivePatch(TypedDict, total=False): - Patch: Optional[Patch] - PatchStatus: Optional[PatchStatus] + Patch: Patch | None + PatchStatus: PatchStatus | None -EffectivePatchList = List[EffectivePatch] +EffectivePatchList = list[EffectivePatch] class DescribeEffectivePatchesForPatchBaselineResult(TypedDict, total=False): - EffectivePatches: Optional[EffectivePatchList] - NextToken: Optional[NextToken] + EffectivePatches: EffectivePatchList | None + NextToken: NextToken | None class DescribeInstanceAssociationsStatusRequest(ServiceRequest): InstanceId: InstanceId - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class S3OutputUrl(TypedDict, total=False): - OutputUrl: Optional[Url] + OutputUrl: Url | None class InstanceAssociationOutputUrl(TypedDict, total=False): - S3OutputUrl: Optional[S3OutputUrl] + S3OutputUrl: S3OutputUrl | None class InstanceAssociationStatusInfo(TypedDict, total=False): - AssociationId: Optional[AssociationId] - Name: Optional[DocumentARN] - DocumentVersion: Optional[DocumentVersion] - AssociationVersion: Optional[AssociationVersion] - InstanceId: Optional[InstanceId] - ExecutionDate: Optional[DateTime] - Status: Optional[StatusName] - DetailedStatus: Optional[StatusName] - ExecutionSummary: Optional[InstanceAssociationExecutionSummary] - ErrorCode: Optional[AgentErrorCode] - OutputUrl: Optional[InstanceAssociationOutputUrl] - AssociationName: Optional[AssociationName] + AssociationId: AssociationId | None + Name: DocumentARN | None + DocumentVersion: DocumentVersion | None + AssociationVersion: AssociationVersion | None + InstanceId: InstanceId | None + ExecutionDate: DateTime | None + Status: StatusName | None + DetailedStatus: StatusName | None + ExecutionSummary: InstanceAssociationExecutionSummary | None + ErrorCode: AgentErrorCode | None + OutputUrl: InstanceAssociationOutputUrl | None + AssociationName: AssociationName | None -InstanceAssociationStatusInfos = List[InstanceAssociationStatusInfo] +InstanceAssociationStatusInfos = list[InstanceAssociationStatusInfo] class DescribeInstanceAssociationsStatusResult(TypedDict, total=False): - InstanceAssociationStatusInfos: Optional[InstanceAssociationStatusInfos] - NextToken: Optional[NextToken] + InstanceAssociationStatusInfos: InstanceAssociationStatusInfos | None + NextToken: NextToken | None -InstanceInformationFilterValueSet = List[InstanceInformationFilterValue] +InstanceInformationFilterValueSet = list[InstanceInformationFilterValue] class InstanceInformationStringFilter(TypedDict, total=False): @@ -3528,7 +3539,7 @@ class InstanceInformationStringFilter(TypedDict, total=False): Values: InstanceInformationFilterValueSet -InstanceInformationStringFilterList = List[InstanceInformationStringFilter] +InstanceInformationStringFilterList = list[InstanceInformationStringFilter] class InstanceInformationFilter(TypedDict, total=False): @@ -3536,57 +3547,57 @@ class InstanceInformationFilter(TypedDict, total=False): valueSet: InstanceInformationFilterValueSet -InstanceInformationFilterList = List[InstanceInformationFilter] +InstanceInformationFilterList = list[InstanceInformationFilter] class DescribeInstanceInformationRequest(ServiceRequest): - InstanceInformationFilterList: Optional[InstanceInformationFilterList] - Filters: Optional[InstanceInformationStringFilterList] - MaxResults: Optional[MaxResultsEC2Compatible] - NextToken: Optional[NextToken] + InstanceInformationFilterList: InstanceInformationFilterList | None + Filters: InstanceInformationStringFilterList | None + MaxResults: MaxResultsEC2Compatible | None + NextToken: NextToken | None -InstanceAssociationStatusAggregatedCount = Dict[StatusName, InstanceCount] +InstanceAssociationStatusAggregatedCount = dict[StatusName, InstanceCount] class InstanceAggregatedAssociationOverview(TypedDict, total=False): - DetailedStatus: Optional[StatusName] - InstanceAssociationStatusAggregatedCount: Optional[InstanceAssociationStatusAggregatedCount] + DetailedStatus: StatusName | None + InstanceAssociationStatusAggregatedCount: InstanceAssociationStatusAggregatedCount | None class InstanceInformation(TypedDict, total=False): - InstanceId: Optional[InstanceId] - PingStatus: Optional[PingStatus] - LastPingDateTime: Optional[DateTime] - AgentVersion: Optional[Version] - IsLatestVersion: Optional[Boolean] - PlatformType: Optional[PlatformType] - PlatformName: Optional[String] - PlatformVersion: Optional[String] - ActivationId: Optional[ActivationId] - IamRole: Optional[IamRole] - RegistrationDate: Optional[DateTime] - ResourceType: Optional[ResourceType] - Name: Optional[String] - IPAddress: Optional[IPAddress] - ComputerName: Optional[ComputerName] - AssociationStatus: Optional[StatusName] - LastAssociationExecutionDate: Optional[DateTime] - LastSuccessfulAssociationExecutionDate: Optional[DateTime] - AssociationOverview: Optional[InstanceAggregatedAssociationOverview] - SourceId: Optional[SourceId] - SourceType: Optional[SourceType] - - -InstanceInformationList = List[InstanceInformation] + InstanceId: InstanceId | None + PingStatus: PingStatus | None + LastPingDateTime: DateTime | None + AgentVersion: Version | None + IsLatestVersion: Boolean | None + PlatformType: PlatformType | None + PlatformName: String | None + PlatformVersion: String | None + ActivationId: ActivationId | None + IamRole: IamRole | None + RegistrationDate: DateTime | None + ResourceType: ResourceType | None + Name: String | None + IPAddress: IPAddress | None + ComputerName: ComputerName | None + AssociationStatus: StatusName | None + LastAssociationExecutionDate: DateTime | None + LastSuccessfulAssociationExecutionDate: DateTime | None + AssociationOverview: InstanceAggregatedAssociationOverview | None + SourceId: SourceId | None + SourceType: SourceType | None + + +InstanceInformationList = list[InstanceInformation] class DescribeInstanceInformationResult(TypedDict, total=False): - InstanceInformationList: Optional[InstanceInformationList] - NextToken: Optional[NextToken] + InstanceInformationList: InstanceInformationList | None + NextToken: NextToken | None -InstancePatchStateFilterValues = List[InstancePatchStateFilterValue] +InstancePatchStateFilterValues = list[InstancePatchStateFilterValue] class InstancePatchStateFilter(TypedDict, total=False): @@ -3595,69 +3606,69 @@ class InstancePatchStateFilter(TypedDict, total=False): Type: InstancePatchStateOperatorType -InstancePatchStateFilterList = List[InstancePatchStateFilter] +InstancePatchStateFilterList = list[InstancePatchStateFilter] class DescribeInstancePatchStatesForPatchGroupRequest(ServiceRequest): PatchGroup: PatchGroup - Filters: Optional[InstancePatchStateFilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[PatchComplianceMaxResults] + Filters: InstancePatchStateFilterList | None + NextToken: NextToken | None + MaxResults: PatchComplianceMaxResults | None class InstancePatchState(TypedDict, total=False): InstanceId: InstanceId PatchGroup: PatchGroup BaselineId: BaselineId - SnapshotId: Optional[SnapshotId] - InstallOverrideList: Optional[InstallOverrideList] - OwnerInformation: Optional[OwnerInformation] - InstalledCount: Optional[PatchInstalledCount] - InstalledOtherCount: Optional[PatchInstalledOtherCount] - InstalledPendingRebootCount: Optional[PatchInstalledPendingRebootCount] - InstalledRejectedCount: Optional[PatchInstalledRejectedCount] - MissingCount: Optional[PatchMissingCount] - FailedCount: Optional[PatchFailedCount] - UnreportedNotApplicableCount: Optional[PatchUnreportedNotApplicableCount] - NotApplicableCount: Optional[PatchNotApplicableCount] - AvailableSecurityUpdateCount: Optional[PatchAvailableSecurityUpdateCount] + SnapshotId: SnapshotId | None + InstallOverrideList: InstallOverrideList | None + OwnerInformation: OwnerInformation | None + InstalledCount: PatchInstalledCount | None + InstalledOtherCount: PatchInstalledOtherCount | None + InstalledPendingRebootCount: PatchInstalledPendingRebootCount | None + InstalledRejectedCount: PatchInstalledRejectedCount | None + MissingCount: PatchMissingCount | None + FailedCount: PatchFailedCount | None + UnreportedNotApplicableCount: PatchUnreportedNotApplicableCount | None + NotApplicableCount: PatchNotApplicableCount | None + AvailableSecurityUpdateCount: PatchAvailableSecurityUpdateCount | None OperationStartTime: DateTime OperationEndTime: DateTime Operation: PatchOperationType - LastNoRebootInstallOperationTime: Optional[DateTime] - RebootOption: Optional[RebootOption] - CriticalNonCompliantCount: Optional[PatchCriticalNonCompliantCount] - SecurityNonCompliantCount: Optional[PatchSecurityNonCompliantCount] - OtherNonCompliantCount: Optional[PatchOtherNonCompliantCount] + LastNoRebootInstallOperationTime: DateTime | None + RebootOption: RebootOption | None + CriticalNonCompliantCount: PatchCriticalNonCompliantCount | None + SecurityNonCompliantCount: PatchSecurityNonCompliantCount | None + OtherNonCompliantCount: PatchOtherNonCompliantCount | None -InstancePatchStatesList = List[InstancePatchState] +InstancePatchStatesList = list[InstancePatchState] class DescribeInstancePatchStatesForPatchGroupResult(TypedDict, total=False): - InstancePatchStates: Optional[InstancePatchStatesList] - NextToken: Optional[NextToken] + InstancePatchStates: InstancePatchStatesList | None + NextToken: NextToken | None class DescribeInstancePatchStatesRequest(ServiceRequest): InstanceIds: InstanceIdList - NextToken: Optional[NextToken] - MaxResults: Optional[PatchComplianceMaxResults] + NextToken: NextToken | None + MaxResults: PatchComplianceMaxResults | None -InstancePatchStateList = List[InstancePatchState] +InstancePatchStateList = list[InstancePatchState] class DescribeInstancePatchStatesResult(TypedDict, total=False): - InstancePatchStates: Optional[InstancePatchStateList] - NextToken: Optional[NextToken] + InstancePatchStates: InstancePatchStateList | None + NextToken: NextToken | None class DescribeInstancePatchesRequest(ServiceRequest): InstanceId: InstanceId - Filters: Optional[PatchOrchestratorFilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[PatchComplianceMaxResults] + Filters: PatchOrchestratorFilterList | None + NextToken: NextToken | None + MaxResults: PatchComplianceMaxResults | None class PatchComplianceData(TypedDict, total=False): @@ -3667,27 +3678,27 @@ class PatchComplianceData(TypedDict, total=False): Severity: PatchSeverity State: PatchComplianceDataState InstalledTime: DateTime - CVEIds: Optional[PatchCVEIds] + CVEIds: PatchCVEIds | None -PatchComplianceDataList = List[PatchComplianceData] +PatchComplianceDataList = list[PatchComplianceData] class DescribeInstancePatchesResult(TypedDict, total=False): - Patches: Optional[PatchComplianceDataList] - NextToken: Optional[NextToken] + Patches: PatchComplianceDataList | None + NextToken: NextToken | None -InstancePropertyFilterValueSet = List[InstancePropertyFilterValue] +InstancePropertyFilterValueSet = list[InstancePropertyFilterValue] class InstancePropertyStringFilter(TypedDict, total=False): Key: InstancePropertyStringFilterKey Values: InstancePropertyFilterValueSet - Operator: Optional[InstancePropertyFilterOperator] + Operator: InstancePropertyFilterOperator | None -InstancePropertyStringFilterList = List[InstancePropertyStringFilter] +InstancePropertyStringFilterList = list[InstancePropertyStringFilter] class InstancePropertyFilter(TypedDict, total=False): @@ -3695,57 +3706,57 @@ class InstancePropertyFilter(TypedDict, total=False): valueSet: InstancePropertyFilterValueSet -InstancePropertyFilterList = List[InstancePropertyFilter] +InstancePropertyFilterList = list[InstancePropertyFilter] class DescribeInstancePropertiesRequest(ServiceRequest): - InstancePropertyFilterList: Optional[InstancePropertyFilterList] - FiltersWithOperator: Optional[InstancePropertyStringFilterList] - MaxResults: Optional[DescribeInstancePropertiesMaxResults] - NextToken: Optional[NextToken] + InstancePropertyFilterList: InstancePropertyFilterList | None + FiltersWithOperator: InstancePropertyStringFilterList | None + MaxResults: DescribeInstancePropertiesMaxResults | None + NextToken: NextToken | None class InstanceProperty(TypedDict, total=False): - Name: Optional[InstanceName] - InstanceId: Optional[InstanceId] - InstanceType: Optional[InstanceType] - InstanceRole: Optional[InstanceRole] - KeyName: Optional[KeyName] - InstanceState: Optional[InstanceState] - Architecture: Optional[Architecture] - IPAddress: Optional[IPAddress] - LaunchTime: Optional[DateTime] - PingStatus: Optional[PingStatus] - LastPingDateTime: Optional[DateTime] - AgentVersion: Optional[Version] - PlatformType: Optional[PlatformType] - PlatformName: Optional[PlatformName] - PlatformVersion: Optional[PlatformVersion] - ActivationId: Optional[ActivationId] - IamRole: Optional[IamRole] - RegistrationDate: Optional[DateTime] - ResourceType: Optional[String] - ComputerName: Optional[ComputerName] - AssociationStatus: Optional[StatusName] - LastAssociationExecutionDate: Optional[DateTime] - LastSuccessfulAssociationExecutionDate: Optional[DateTime] - AssociationOverview: Optional[InstanceAggregatedAssociationOverview] - SourceId: Optional[SourceId] - SourceType: Optional[SourceType] - - -InstanceProperties = List[InstanceProperty] + Name: InstanceName | None + InstanceId: InstanceId | None + InstanceType: InstanceType | None + InstanceRole: InstanceRole | None + KeyName: KeyName | None + InstanceState: InstanceState | None + Architecture: Architecture | None + IPAddress: IPAddress | None + LaunchTime: DateTime | None + PingStatus: PingStatus | None + LastPingDateTime: DateTime | None + AgentVersion: Version | None + PlatformType: PlatformType | None + PlatformName: PlatformName | None + PlatformVersion: PlatformVersion | None + ActivationId: ActivationId | None + IamRole: IamRole | None + RegistrationDate: DateTime | None + ResourceType: String | None + ComputerName: ComputerName | None + AssociationStatus: StatusName | None + LastAssociationExecutionDate: DateTime | None + LastSuccessfulAssociationExecutionDate: DateTime | None + AssociationOverview: InstanceAggregatedAssociationOverview | None + SourceId: SourceId | None + SourceType: SourceType | None + + +InstanceProperties = list[InstanceProperty] class DescribeInstancePropertiesResult(TypedDict, total=False): - InstanceProperties: Optional[InstanceProperties] - NextToken: Optional[NextToken] + InstanceProperties: InstanceProperties | None + NextToken: NextToken | None class DescribeInventoryDeletionsRequest(ServiceRequest): - DeletionId: Optional[UUID] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + DeletionId: UUID | None + NextToken: NextToken | None + MaxResults: MaxResults | None InventoryDeletionLastStatusUpdateTime = datetime @@ -3753,270 +3764,270 @@ class DescribeInventoryDeletionsRequest(ServiceRequest): class InventoryDeletionStatusItem(TypedDict, total=False): - DeletionId: Optional[UUID] - TypeName: Optional[InventoryItemTypeName] - DeletionStartTime: Optional[InventoryDeletionStartTime] - LastStatus: Optional[InventoryDeletionStatus] - LastStatusMessage: Optional[InventoryDeletionLastStatusMessage] - DeletionSummary: Optional[InventoryDeletionSummary] - LastStatusUpdateTime: Optional[InventoryDeletionLastStatusUpdateTime] + DeletionId: UUID | None + TypeName: InventoryItemTypeName | None + DeletionStartTime: InventoryDeletionStartTime | None + LastStatus: InventoryDeletionStatus | None + LastStatusMessage: InventoryDeletionLastStatusMessage | None + DeletionSummary: InventoryDeletionSummary | None + LastStatusUpdateTime: InventoryDeletionLastStatusUpdateTime | None -InventoryDeletionsList = List[InventoryDeletionStatusItem] +InventoryDeletionsList = list[InventoryDeletionStatusItem] class DescribeInventoryDeletionsResult(TypedDict, total=False): - InventoryDeletions: Optional[InventoryDeletionsList] - NextToken: Optional[NextToken] + InventoryDeletions: InventoryDeletionsList | None + NextToken: NextToken | None -MaintenanceWindowFilterValues = List[MaintenanceWindowFilterValue] +MaintenanceWindowFilterValues = list[MaintenanceWindowFilterValue] class MaintenanceWindowFilter(TypedDict, total=False): - Key: Optional[MaintenanceWindowFilterKey] - Values: Optional[MaintenanceWindowFilterValues] + Key: MaintenanceWindowFilterKey | None + Values: MaintenanceWindowFilterValues | None -MaintenanceWindowFilterList = List[MaintenanceWindowFilter] +MaintenanceWindowFilterList = list[MaintenanceWindowFilter] class DescribeMaintenanceWindowExecutionTaskInvocationsRequest(ServiceRequest): WindowExecutionId: MaintenanceWindowExecutionId TaskId: MaintenanceWindowExecutionTaskId - Filters: Optional[MaintenanceWindowFilterList] - MaxResults: Optional[MaintenanceWindowMaxResults] - NextToken: Optional[NextToken] + Filters: MaintenanceWindowFilterList | None + MaxResults: MaintenanceWindowMaxResults | None + NextToken: NextToken | None class MaintenanceWindowExecutionTaskInvocationIdentity(TypedDict, total=False): - WindowExecutionId: Optional[MaintenanceWindowExecutionId] - TaskExecutionId: Optional[MaintenanceWindowExecutionTaskId] - InvocationId: Optional[MaintenanceWindowExecutionTaskInvocationId] - ExecutionId: Optional[MaintenanceWindowExecutionTaskExecutionId] - TaskType: Optional[MaintenanceWindowTaskType] - Parameters: Optional[MaintenanceWindowExecutionTaskInvocationParameters] - Status: Optional[MaintenanceWindowExecutionStatus] - StatusDetails: Optional[MaintenanceWindowExecutionStatusDetails] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] - OwnerInformation: Optional[OwnerInformation] - WindowTargetId: Optional[MaintenanceWindowTaskTargetId] - - -MaintenanceWindowExecutionTaskInvocationIdentityList = List[ + WindowExecutionId: MaintenanceWindowExecutionId | None + TaskExecutionId: MaintenanceWindowExecutionTaskId | None + InvocationId: MaintenanceWindowExecutionTaskInvocationId | None + ExecutionId: MaintenanceWindowExecutionTaskExecutionId | None + TaskType: MaintenanceWindowTaskType | None + Parameters: MaintenanceWindowExecutionTaskInvocationParameters | None + Status: MaintenanceWindowExecutionStatus | None + StatusDetails: MaintenanceWindowExecutionStatusDetails | None + StartTime: DateTime | None + EndTime: DateTime | None + OwnerInformation: OwnerInformation | None + WindowTargetId: MaintenanceWindowTaskTargetId | None + + +MaintenanceWindowExecutionTaskInvocationIdentityList = list[ MaintenanceWindowExecutionTaskInvocationIdentity ] class DescribeMaintenanceWindowExecutionTaskInvocationsResult(TypedDict, total=False): - WindowExecutionTaskInvocationIdentities: Optional[ - MaintenanceWindowExecutionTaskInvocationIdentityList - ] - NextToken: Optional[NextToken] + WindowExecutionTaskInvocationIdentities: ( + MaintenanceWindowExecutionTaskInvocationIdentityList | None + ) + NextToken: NextToken | None class DescribeMaintenanceWindowExecutionTasksRequest(ServiceRequest): WindowExecutionId: MaintenanceWindowExecutionId - Filters: Optional[MaintenanceWindowFilterList] - MaxResults: Optional[MaintenanceWindowMaxResults] - NextToken: Optional[NextToken] + Filters: MaintenanceWindowFilterList | None + MaxResults: MaintenanceWindowMaxResults | None + NextToken: NextToken | None class MaintenanceWindowExecutionTaskIdentity(TypedDict, total=False): - WindowExecutionId: Optional[MaintenanceWindowExecutionId] - TaskExecutionId: Optional[MaintenanceWindowExecutionTaskId] - Status: Optional[MaintenanceWindowExecutionStatus] - StatusDetails: Optional[MaintenanceWindowExecutionStatusDetails] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] - TaskArn: Optional[MaintenanceWindowTaskArn] - TaskType: Optional[MaintenanceWindowTaskType] - AlarmConfiguration: Optional[AlarmConfiguration] - TriggeredAlarms: Optional[AlarmStateInformationList] + WindowExecutionId: MaintenanceWindowExecutionId | None + TaskExecutionId: MaintenanceWindowExecutionTaskId | None + Status: MaintenanceWindowExecutionStatus | None + StatusDetails: MaintenanceWindowExecutionStatusDetails | None + StartTime: DateTime | None + EndTime: DateTime | None + TaskArn: MaintenanceWindowTaskArn | None + TaskType: MaintenanceWindowTaskType | None + AlarmConfiguration: AlarmConfiguration | None + TriggeredAlarms: AlarmStateInformationList | None -MaintenanceWindowExecutionTaskIdentityList = List[MaintenanceWindowExecutionTaskIdentity] +MaintenanceWindowExecutionTaskIdentityList = list[MaintenanceWindowExecutionTaskIdentity] class DescribeMaintenanceWindowExecutionTasksResult(TypedDict, total=False): - WindowExecutionTaskIdentities: Optional[MaintenanceWindowExecutionTaskIdentityList] - NextToken: Optional[NextToken] + WindowExecutionTaskIdentities: MaintenanceWindowExecutionTaskIdentityList | None + NextToken: NextToken | None class DescribeMaintenanceWindowExecutionsRequest(ServiceRequest): WindowId: MaintenanceWindowId - Filters: Optional[MaintenanceWindowFilterList] - MaxResults: Optional[MaintenanceWindowMaxResults] - NextToken: Optional[NextToken] + Filters: MaintenanceWindowFilterList | None + MaxResults: MaintenanceWindowMaxResults | None + NextToken: NextToken | None class MaintenanceWindowExecution(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowExecutionId: Optional[MaintenanceWindowExecutionId] - Status: Optional[MaintenanceWindowExecutionStatus] - StatusDetails: Optional[MaintenanceWindowExecutionStatusDetails] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] + WindowId: MaintenanceWindowId | None + WindowExecutionId: MaintenanceWindowExecutionId | None + Status: MaintenanceWindowExecutionStatus | None + StatusDetails: MaintenanceWindowExecutionStatusDetails | None + StartTime: DateTime | None + EndTime: DateTime | None -MaintenanceWindowExecutionList = List[MaintenanceWindowExecution] +MaintenanceWindowExecutionList = list[MaintenanceWindowExecution] class DescribeMaintenanceWindowExecutionsResult(TypedDict, total=False): - WindowExecutions: Optional[MaintenanceWindowExecutionList] - NextToken: Optional[NextToken] + WindowExecutions: MaintenanceWindowExecutionList | None + NextToken: NextToken | None class DescribeMaintenanceWindowScheduleRequest(ServiceRequest): - WindowId: Optional[MaintenanceWindowId] - Targets: Optional[Targets] - ResourceType: Optional[MaintenanceWindowResourceType] - Filters: Optional[PatchOrchestratorFilterList] - MaxResults: Optional[MaintenanceWindowSearchMaxResults] - NextToken: Optional[NextToken] + WindowId: MaintenanceWindowId | None + Targets: Targets | None + ResourceType: MaintenanceWindowResourceType | None + Filters: PatchOrchestratorFilterList | None + MaxResults: MaintenanceWindowSearchMaxResults | None + NextToken: NextToken | None class ScheduledWindowExecution(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - Name: Optional[MaintenanceWindowName] - ExecutionTime: Optional[MaintenanceWindowStringDateTime] + WindowId: MaintenanceWindowId | None + Name: MaintenanceWindowName | None + ExecutionTime: MaintenanceWindowStringDateTime | None -ScheduledWindowExecutionList = List[ScheduledWindowExecution] +ScheduledWindowExecutionList = list[ScheduledWindowExecution] class DescribeMaintenanceWindowScheduleResult(TypedDict, total=False): - ScheduledWindowExecutions: Optional[ScheduledWindowExecutionList] - NextToken: Optional[NextToken] + ScheduledWindowExecutions: ScheduledWindowExecutionList | None + NextToken: NextToken | None class DescribeMaintenanceWindowTargetsRequest(ServiceRequest): WindowId: MaintenanceWindowId - Filters: Optional[MaintenanceWindowFilterList] - MaxResults: Optional[MaintenanceWindowMaxResults] - NextToken: Optional[NextToken] + Filters: MaintenanceWindowFilterList | None + MaxResults: MaintenanceWindowMaxResults | None + NextToken: NextToken | None class MaintenanceWindowTarget(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowTargetId: Optional[MaintenanceWindowTargetId] - ResourceType: Optional[MaintenanceWindowResourceType] - Targets: Optional[Targets] - OwnerInformation: Optional[OwnerInformation] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] + WindowId: MaintenanceWindowId | None + WindowTargetId: MaintenanceWindowTargetId | None + ResourceType: MaintenanceWindowResourceType | None + Targets: Targets | None + OwnerInformation: OwnerInformation | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None -MaintenanceWindowTargetList = List[MaintenanceWindowTarget] +MaintenanceWindowTargetList = list[MaintenanceWindowTarget] class DescribeMaintenanceWindowTargetsResult(TypedDict, total=False): - Targets: Optional[MaintenanceWindowTargetList] - NextToken: Optional[NextToken] + Targets: MaintenanceWindowTargetList | None + NextToken: NextToken | None class DescribeMaintenanceWindowTasksRequest(ServiceRequest): WindowId: MaintenanceWindowId - Filters: Optional[MaintenanceWindowFilterList] - MaxResults: Optional[MaintenanceWindowMaxResults] - NextToken: Optional[NextToken] + Filters: MaintenanceWindowFilterList | None + MaxResults: MaintenanceWindowMaxResults | None + NextToken: NextToken | None class LoggingInfo(TypedDict, total=False): S3BucketName: S3BucketName - S3KeyPrefix: Optional[S3KeyPrefix] + S3KeyPrefix: S3KeyPrefix | None S3Region: S3Region -MaintenanceWindowTaskParameterValueList = List[MaintenanceWindowTaskParameterValue] +MaintenanceWindowTaskParameterValueList = list[MaintenanceWindowTaskParameterValue] class MaintenanceWindowTaskParameterValueExpression(TypedDict, total=False): - Values: Optional[MaintenanceWindowTaskParameterValueList] + Values: MaintenanceWindowTaskParameterValueList | None -MaintenanceWindowTaskParameters = Dict[ +MaintenanceWindowTaskParameters = dict[ MaintenanceWindowTaskParameterName, MaintenanceWindowTaskParameterValueExpression ] class MaintenanceWindowTask(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowTaskId: Optional[MaintenanceWindowTaskId] - TaskArn: Optional[MaintenanceWindowTaskArn] - Type: Optional[MaintenanceWindowTaskType] - Targets: Optional[Targets] - TaskParameters: Optional[MaintenanceWindowTaskParameters] - Priority: Optional[MaintenanceWindowTaskPriority] - LoggingInfo: Optional[LoggingInfo] - ServiceRoleArn: Optional[ServiceRole] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - CutoffBehavior: Optional[MaintenanceWindowTaskCutoffBehavior] - AlarmConfiguration: Optional[AlarmConfiguration] - - -MaintenanceWindowTaskList = List[MaintenanceWindowTask] + WindowId: MaintenanceWindowId | None + WindowTaskId: MaintenanceWindowTaskId | None + TaskArn: MaintenanceWindowTaskArn | None + Type: MaintenanceWindowTaskType | None + Targets: Targets | None + TaskParameters: MaintenanceWindowTaskParameters | None + Priority: MaintenanceWindowTaskPriority | None + LoggingInfo: LoggingInfo | None + ServiceRoleArn: ServiceRole | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + CutoffBehavior: MaintenanceWindowTaskCutoffBehavior | None + AlarmConfiguration: AlarmConfiguration | None + + +MaintenanceWindowTaskList = list[MaintenanceWindowTask] class DescribeMaintenanceWindowTasksResult(TypedDict, total=False): - Tasks: Optional[MaintenanceWindowTaskList] - NextToken: Optional[NextToken] + Tasks: MaintenanceWindowTaskList | None + NextToken: NextToken | None class DescribeMaintenanceWindowsForTargetRequest(ServiceRequest): Targets: Targets ResourceType: MaintenanceWindowResourceType - MaxResults: Optional[MaintenanceWindowSearchMaxResults] - NextToken: Optional[NextToken] + MaxResults: MaintenanceWindowSearchMaxResults | None + NextToken: NextToken | None class MaintenanceWindowIdentityForTarget(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - Name: Optional[MaintenanceWindowName] + WindowId: MaintenanceWindowId | None + Name: MaintenanceWindowName | None -MaintenanceWindowsForTargetList = List[MaintenanceWindowIdentityForTarget] +MaintenanceWindowsForTargetList = list[MaintenanceWindowIdentityForTarget] class DescribeMaintenanceWindowsForTargetResult(TypedDict, total=False): - WindowIdentities: Optional[MaintenanceWindowsForTargetList] - NextToken: Optional[NextToken] + WindowIdentities: MaintenanceWindowsForTargetList | None + NextToken: NextToken | None class DescribeMaintenanceWindowsRequest(ServiceRequest): - Filters: Optional[MaintenanceWindowFilterList] - MaxResults: Optional[MaintenanceWindowMaxResults] - NextToken: Optional[NextToken] + Filters: MaintenanceWindowFilterList | None + MaxResults: MaintenanceWindowMaxResults | None + NextToken: NextToken | None class MaintenanceWindowIdentity(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - Enabled: Optional[MaintenanceWindowEnabled] - Duration: Optional[MaintenanceWindowDurationHours] - Cutoff: Optional[MaintenanceWindowCutoff] - Schedule: Optional[MaintenanceWindowSchedule] - ScheduleTimezone: Optional[MaintenanceWindowTimezone] - ScheduleOffset: Optional[MaintenanceWindowOffset] - EndDate: Optional[MaintenanceWindowStringDateTime] - StartDate: Optional[MaintenanceWindowStringDateTime] - NextExecutionTime: Optional[MaintenanceWindowStringDateTime] + WindowId: MaintenanceWindowId | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + Enabled: MaintenanceWindowEnabled | None + Duration: MaintenanceWindowDurationHours | None + Cutoff: MaintenanceWindowCutoff | None + Schedule: MaintenanceWindowSchedule | None + ScheduleTimezone: MaintenanceWindowTimezone | None + ScheduleOffset: MaintenanceWindowOffset | None + EndDate: MaintenanceWindowStringDateTime | None + StartDate: MaintenanceWindowStringDateTime | None + NextExecutionTime: MaintenanceWindowStringDateTime | None -MaintenanceWindowIdentityList = List[MaintenanceWindowIdentity] +MaintenanceWindowIdentityList = list[MaintenanceWindowIdentity] class DescribeMaintenanceWindowsResult(TypedDict, total=False): - WindowIdentities: Optional[MaintenanceWindowIdentityList] - NextToken: Optional[NextToken] + WindowIdentities: MaintenanceWindowIdentityList | None + NextToken: NextToken | None -OpsItemFilterValues = List[OpsItemFilterValue] +OpsItemFilterValues = list[OpsItemFilterValue] class OpsItemFilter(TypedDict, total=False): @@ -4025,54 +4036,54 @@ class OpsItemFilter(TypedDict, total=False): Operator: OpsItemFilterOperator -OpsItemFilters = List[OpsItemFilter] +OpsItemFilters = list[OpsItemFilter] class DescribeOpsItemsRequest(ServiceRequest): - OpsItemFilters: Optional[OpsItemFilters] - MaxResults: Optional[OpsItemMaxResults] - NextToken: Optional[String] + OpsItemFilters: OpsItemFilters | None + MaxResults: OpsItemMaxResults | None + NextToken: String | None class OpsItemSummary(TypedDict, total=False): - CreatedBy: Optional[String] - CreatedTime: Optional[DateTime] - LastModifiedBy: Optional[String] - LastModifiedTime: Optional[DateTime] - Priority: Optional[OpsItemPriority] - Source: Optional[OpsItemSource] - Status: Optional[OpsItemStatus] - OpsItemId: Optional[OpsItemId] - Title: Optional[OpsItemTitle] - OperationalData: Optional[OpsItemOperationalData] - Category: Optional[OpsItemCategory] - Severity: Optional[OpsItemSeverity] - OpsItemType: Optional[OpsItemType] - ActualStartTime: Optional[DateTime] - ActualEndTime: Optional[DateTime] - PlannedStartTime: Optional[DateTime] - PlannedEndTime: Optional[DateTime] - - -OpsItemSummaries = List[OpsItemSummary] + CreatedBy: String | None + CreatedTime: DateTime | None + LastModifiedBy: String | None + LastModifiedTime: DateTime | None + Priority: OpsItemPriority | None + Source: OpsItemSource | None + Status: OpsItemStatus | None + OpsItemId: OpsItemId | None + Title: OpsItemTitle | None + OperationalData: OpsItemOperationalData | None + Category: OpsItemCategory | None + Severity: OpsItemSeverity | None + OpsItemType: OpsItemType | None + ActualStartTime: DateTime | None + ActualEndTime: DateTime | None + PlannedStartTime: DateTime | None + PlannedEndTime: DateTime | None + + +OpsItemSummaries = list[OpsItemSummary] class DescribeOpsItemsResponse(TypedDict, total=False): - NextToken: Optional[String] - OpsItemSummaries: Optional[OpsItemSummaries] + NextToken: String | None + OpsItemSummaries: OpsItemSummaries | None -ParameterStringFilterValueList = List[ParameterStringFilterValue] +ParameterStringFilterValueList = list[ParameterStringFilterValue] class ParameterStringFilter(TypedDict, total=False): Key: ParameterStringFilterKey - Option: Optional[ParameterStringQueryOption] - Values: Optional[ParameterStringFilterValueList] + Option: ParameterStringQueryOption | None + Values: ParameterStringFilterValueList | None -ParameterStringFilterList = List[ParameterStringFilter] -ParametersFilterValueList = List[ParametersFilterValue] +ParameterStringFilterList = list[ParameterStringFilter] +ParametersFilterValueList = list[ParametersFilterValue] class ParametersFilter(TypedDict, total=False): @@ -4080,70 +4091,70 @@ class ParametersFilter(TypedDict, total=False): Values: ParametersFilterValueList -ParametersFilterList = List[ParametersFilter] +ParametersFilterList = list[ParametersFilter] class DescribeParametersRequest(ServiceRequest): - Filters: Optional[ParametersFilterList] - ParameterFilters: Optional[ParameterStringFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] - Shared: Optional[Boolean] + Filters: ParametersFilterList | None + ParameterFilters: ParameterStringFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None + Shared: Boolean | None class ParameterInlinePolicy(TypedDict, total=False): - PolicyText: Optional[String] - PolicyType: Optional[String] - PolicyStatus: Optional[String] + PolicyText: String | None + PolicyType: String | None + PolicyStatus: String | None -ParameterPolicyList = List[ParameterInlinePolicy] +ParameterPolicyList = list[ParameterInlinePolicy] PSParameterVersion = int class ParameterMetadata(TypedDict, total=False): - Name: Optional[PSParameterName] - ARN: Optional[String] - Type: Optional[ParameterType] - KeyId: Optional[ParameterKeyId] - LastModifiedDate: Optional[DateTime] - LastModifiedUser: Optional[String] - Description: Optional[ParameterDescription] - AllowedPattern: Optional[AllowedPattern] - Version: Optional[PSParameterVersion] - Tier: Optional[ParameterTier] - Policies: Optional[ParameterPolicyList] - DataType: Optional[ParameterDataType] + Name: PSParameterName | None + ARN: String | None + Type: ParameterType | None + KeyId: ParameterKeyId | None + LastModifiedDate: DateTime | None + LastModifiedUser: String | None + Description: ParameterDescription | None + AllowedPattern: AllowedPattern | None + Version: PSParameterVersion | None + Tier: ParameterTier | None + Policies: ParameterPolicyList | None + DataType: ParameterDataType | None -ParameterMetadataList = List[ParameterMetadata] +ParameterMetadataList = list[ParameterMetadata] class DescribeParametersResult(TypedDict, total=False): - Parameters: Optional[ParameterMetadataList] - NextToken: Optional[NextToken] + Parameters: ParameterMetadataList | None + NextToken: NextToken | None class DescribePatchBaselinesRequest(ServiceRequest): - Filters: Optional[PatchOrchestratorFilterList] - MaxResults: Optional[PatchBaselineMaxResults] - NextToken: Optional[NextToken] + Filters: PatchOrchestratorFilterList | None + MaxResults: PatchBaselineMaxResults | None + NextToken: NextToken | None class PatchBaselineIdentity(TypedDict, total=False): - BaselineId: Optional[BaselineId] - BaselineName: Optional[BaselineName] - OperatingSystem: Optional[OperatingSystem] - BaselineDescription: Optional[BaselineDescription] - DefaultBaseline: Optional[DefaultBaseline] + BaselineId: BaselineId | None + BaselineName: BaselineName | None + OperatingSystem: OperatingSystem | None + BaselineDescription: BaselineDescription | None + DefaultBaseline: DefaultBaseline | None -PatchBaselineIdentityList = List[PatchBaselineIdentity] +PatchBaselineIdentityList = list[PatchBaselineIdentity] class DescribePatchBaselinesResult(TypedDict, total=False): - BaselineIdentities: Optional[PatchBaselineIdentityList] - NextToken: Optional[NextToken] + BaselineIdentities: PatchBaselineIdentityList | None + NextToken: NextToken | None class DescribePatchGroupStateRequest(ServiceRequest): @@ -4151,55 +4162,55 @@ class DescribePatchGroupStateRequest(ServiceRequest): class DescribePatchGroupStateResult(TypedDict, total=False): - Instances: Optional[Integer] - InstancesWithInstalledPatches: Optional[Integer] - InstancesWithInstalledOtherPatches: Optional[Integer] - InstancesWithInstalledPendingRebootPatches: Optional[InstancesCount] - InstancesWithInstalledRejectedPatches: Optional[InstancesCount] - InstancesWithMissingPatches: Optional[Integer] - InstancesWithFailedPatches: Optional[Integer] - InstancesWithNotApplicablePatches: Optional[Integer] - InstancesWithUnreportedNotApplicablePatches: Optional[Integer] - InstancesWithCriticalNonCompliantPatches: Optional[InstancesCount] - InstancesWithSecurityNonCompliantPatches: Optional[InstancesCount] - InstancesWithOtherNonCompliantPatches: Optional[InstancesCount] - InstancesWithAvailableSecurityUpdates: Optional[Integer] + Instances: Integer | None + InstancesWithInstalledPatches: Integer | None + InstancesWithInstalledOtherPatches: Integer | None + InstancesWithInstalledPendingRebootPatches: InstancesCount | None + InstancesWithInstalledRejectedPatches: InstancesCount | None + InstancesWithMissingPatches: Integer | None + InstancesWithFailedPatches: Integer | None + InstancesWithNotApplicablePatches: Integer | None + InstancesWithUnreportedNotApplicablePatches: Integer | None + InstancesWithCriticalNonCompliantPatches: InstancesCount | None + InstancesWithSecurityNonCompliantPatches: InstancesCount | None + InstancesWithOtherNonCompliantPatches: InstancesCount | None + InstancesWithAvailableSecurityUpdates: Integer | None class DescribePatchGroupsRequest(ServiceRequest): - MaxResults: Optional[PatchBaselineMaxResults] - Filters: Optional[PatchOrchestratorFilterList] - NextToken: Optional[NextToken] + MaxResults: PatchBaselineMaxResults | None + Filters: PatchOrchestratorFilterList | None + NextToken: NextToken | None class PatchGroupPatchBaselineMapping(TypedDict, total=False): - PatchGroup: Optional[PatchGroup] - BaselineIdentity: Optional[PatchBaselineIdentity] + PatchGroup: PatchGroup | None + BaselineIdentity: PatchBaselineIdentity | None -PatchGroupPatchBaselineMappingList = List[PatchGroupPatchBaselineMapping] +PatchGroupPatchBaselineMappingList = list[PatchGroupPatchBaselineMapping] class DescribePatchGroupsResult(TypedDict, total=False): - Mappings: Optional[PatchGroupPatchBaselineMappingList] - NextToken: Optional[NextToken] + Mappings: PatchGroupPatchBaselineMappingList | None + NextToken: NextToken | None class DescribePatchPropertiesRequest(ServiceRequest): OperatingSystem: OperatingSystem Property: PatchProperty - PatchSet: Optional[PatchSet] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + PatchSet: PatchSet | None + MaxResults: MaxResults | None + NextToken: NextToken | None -PatchPropertyEntry = Dict[AttributeName, AttributeValue] -PatchPropertiesList = List[PatchPropertyEntry] +PatchPropertyEntry = dict[AttributeName, AttributeValue] +PatchPropertiesList = list[PatchPropertyEntry] class DescribePatchPropertiesResult(TypedDict, total=False): - Properties: Optional[PatchPropertiesList] - NextToken: Optional[NextToken] + Properties: PatchPropertiesList | None + NextToken: NextToken | None class SessionFilter(TypedDict, total=False): @@ -4207,42 +4218,42 @@ class SessionFilter(TypedDict, total=False): value: SessionFilterValue -SessionFilterList = List[SessionFilter] +SessionFilterList = list[SessionFilter] class DescribeSessionsRequest(ServiceRequest): State: SessionState - MaxResults: Optional[SessionMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[SessionFilterList] + MaxResults: SessionMaxResults | None + NextToken: NextToken | None + Filters: SessionFilterList | None class SessionManagerOutputUrl(TypedDict, total=False): - S3OutputUrl: Optional[SessionManagerS3OutputUrl] - CloudWatchOutputUrl: Optional[SessionManagerCloudWatchOutputUrl] + S3OutputUrl: SessionManagerS3OutputUrl | None + CloudWatchOutputUrl: SessionManagerCloudWatchOutputUrl | None class Session(TypedDict, total=False): - SessionId: Optional[SessionId] - Target: Optional[SessionTarget] - Status: Optional[SessionStatus] - StartDate: Optional[DateTime] - EndDate: Optional[DateTime] - DocumentName: Optional[DocumentName] - Owner: Optional[SessionOwner] - Reason: Optional[SessionReason] - Details: Optional[SessionDetails] - OutputUrl: Optional[SessionManagerOutputUrl] - MaxSessionDuration: Optional[MaxSessionDuration] - AccessType: Optional[AccessType] + SessionId: SessionId | None + Target: SessionTarget | None + Status: SessionStatus | None + StartDate: DateTime | None + EndDate: DateTime | None + DocumentName: DocumentName | None + Owner: SessionOwner | None + Reason: SessionReason | None + Details: SessionDetails | None + OutputUrl: SessionManagerOutputUrl | None + MaxSessionDuration: MaxSessionDuration | None + AccessType: AccessType | None -SessionList = List[Session] +SessionList = list[Session] class DescribeSessionsResponse(TypedDict, total=False): - Sessions: Optional[SessionList] - NextToken: Optional[NextToken] + Sessions: SessionList | None + NextToken: NextToken | None class DisassociateOpsItemRelatedItemRequest(ServiceRequest): @@ -4255,9 +4266,9 @@ class DisassociateOpsItemRelatedItemResponse(TypedDict, total=False): class DocumentDefaultVersionDescription(TypedDict, total=False): - Name: Optional[DocumentName] - DefaultVersion: Optional[DocumentVersion] - DefaultVersionName: Optional[DocumentVersionName] + Name: DocumentName | None + DefaultVersion: DocumentVersion | None + DefaultVersionName: DocumentVersionName | None class DocumentFilter(TypedDict, total=False): @@ -4265,89 +4276,89 @@ class DocumentFilter(TypedDict, total=False): value: DocumentFilterValue -DocumentFilterList = List[DocumentFilter] +DocumentFilterList = list[DocumentFilter] class DocumentIdentifier(TypedDict, total=False): - Name: Optional[DocumentARN] - CreatedDate: Optional[DateTime] - DisplayName: Optional[DocumentDisplayName] - Owner: Optional[DocumentOwner] - VersionName: Optional[DocumentVersionName] - PlatformTypes: Optional[PlatformTypeList] - DocumentVersion: Optional[DocumentVersion] - DocumentType: Optional[DocumentType] - SchemaVersion: Optional[DocumentSchemaVersion] - DocumentFormat: Optional[DocumentFormat] - TargetType: Optional[TargetType] - Tags: Optional[TagList] - Requires: Optional[DocumentRequiresList] - ReviewStatus: Optional[ReviewStatus] - Author: Optional[DocumentAuthor] - - -DocumentIdentifierList = List[DocumentIdentifier] -DocumentKeyValuesFilterValues = List[DocumentKeyValuesFilterValue] + Name: DocumentARN | None + CreatedDate: DateTime | None + DisplayName: DocumentDisplayName | None + Owner: DocumentOwner | None + VersionName: DocumentVersionName | None + PlatformTypes: PlatformTypeList | None + DocumentVersion: DocumentVersion | None + DocumentType: DocumentType | None + SchemaVersion: DocumentSchemaVersion | None + DocumentFormat: DocumentFormat | None + TargetType: TargetType | None + Tags: TagList | None + Requires: DocumentRequiresList | None + ReviewStatus: ReviewStatus | None + Author: DocumentAuthor | None + + +DocumentIdentifierList = list[DocumentIdentifier] +DocumentKeyValuesFilterValues = list[DocumentKeyValuesFilterValue] class DocumentKeyValuesFilter(TypedDict, total=False): - Key: Optional[DocumentKeyValuesFilterKey] - Values: Optional[DocumentKeyValuesFilterValues] + Key: DocumentKeyValuesFilterKey | None + Values: DocumentKeyValuesFilterValues | None -DocumentKeyValuesFilterList = List[DocumentKeyValuesFilter] +DocumentKeyValuesFilterList = list[DocumentKeyValuesFilter] class DocumentReviewCommentSource(TypedDict, total=False): - Type: Optional[DocumentReviewCommentType] - Content: Optional[DocumentReviewComment] + Type: DocumentReviewCommentType | None + Content: DocumentReviewComment | None -DocumentReviewCommentList = List[DocumentReviewCommentSource] +DocumentReviewCommentList = list[DocumentReviewCommentSource] class DocumentReviewerResponseSource(TypedDict, total=False): - CreateTime: Optional[DateTime] - UpdatedTime: Optional[DateTime] - ReviewStatus: Optional[ReviewStatus] - Comment: Optional[DocumentReviewCommentList] - Reviewer: Optional[Reviewer] + CreateTime: DateTime | None + UpdatedTime: DateTime | None + ReviewStatus: ReviewStatus | None + Comment: DocumentReviewCommentList | None + Reviewer: Reviewer | None -DocumentReviewerResponseList = List[DocumentReviewerResponseSource] +DocumentReviewerResponseList = list[DocumentReviewerResponseSource] class DocumentMetadataResponseInfo(TypedDict, total=False): - ReviewerResponse: Optional[DocumentReviewerResponseList] + ReviewerResponse: DocumentReviewerResponseList | None class DocumentReviews(TypedDict, total=False): Action: DocumentReviewAction - Comment: Optional[DocumentReviewCommentList] + Comment: DocumentReviewCommentList | None class DocumentVersionInfo(TypedDict, total=False): - Name: Optional[DocumentName] - DisplayName: Optional[DocumentDisplayName] - DocumentVersion: Optional[DocumentVersion] - VersionName: Optional[DocumentVersionName] - CreatedDate: Optional[DateTime] - IsDefaultVersion: Optional[Boolean] - DocumentFormat: Optional[DocumentFormat] - Status: Optional[DocumentStatus] - StatusInformation: Optional[DocumentStatusInformation] - ReviewStatus: Optional[ReviewStatus] + Name: DocumentName | None + DisplayName: DocumentDisplayName | None + DocumentVersion: DocumentVersion | None + VersionName: DocumentVersionName | None + CreatedDate: DateTime | None + IsDefaultVersion: Boolean | None + DocumentFormat: DocumentFormat | None + Status: DocumentStatus | None + StatusInformation: DocumentStatusInformation | None + ReviewStatus: ReviewStatus | None -DocumentVersionList = List[DocumentVersionInfo] +DocumentVersionList = list[DocumentVersionInfo] class ExecutionInputs(TypedDict, total=False): - Automation: Optional[AutomationExecutionInputs] + Automation: AutomationExecutionInputs | None class ExecutionPreview(TypedDict, total=False): - Automation: Optional[AutomationExecutionPreview] + Automation: AutomationExecutionPreview | None class GetAccessTokenRequest(ServiceRequest): @@ -4355,8 +4366,8 @@ class GetAccessTokenRequest(ServiceRequest): class GetAccessTokenResponse(TypedDict, total=False): - Credentials: Optional[Credentials] - AccessRequestStatus: Optional[AccessRequestStatus] + Credentials: Credentials | None + AccessRequestStatus: AccessRequestStatus | None class GetAutomationExecutionRequest(ServiceRequest): @@ -4364,44 +4375,44 @@ class GetAutomationExecutionRequest(ServiceRequest): class GetAutomationExecutionResult(TypedDict, total=False): - AutomationExecution: Optional[AutomationExecution] + AutomationExecution: AutomationExecution | None class GetCalendarStateRequest(ServiceRequest): CalendarNames: CalendarNameOrARNList - AtTime: Optional[ISO8601String] + AtTime: ISO8601String | None class GetCalendarStateResponse(TypedDict, total=False): - State: Optional[CalendarState] - AtTime: Optional[ISO8601String] - NextTransitionTime: Optional[ISO8601String] + State: CalendarState | None + AtTime: ISO8601String | None + NextTransitionTime: ISO8601String | None class GetCommandInvocationRequest(ServiceRequest): CommandId: CommandId InstanceId: InstanceId - PluginName: Optional[CommandPluginName] + PluginName: CommandPluginName | None class GetCommandInvocationResult(TypedDict, total=False): - CommandId: Optional[CommandId] - InstanceId: Optional[InstanceId] - Comment: Optional[Comment] - DocumentName: Optional[DocumentName] - DocumentVersion: Optional[DocumentVersion] - PluginName: Optional[CommandPluginName] - ResponseCode: Optional[ResponseCode] - ExecutionStartDateTime: Optional[StringDateTime] - ExecutionElapsedTime: Optional[StringDateTime] - ExecutionEndDateTime: Optional[StringDateTime] - Status: Optional[CommandInvocationStatus] - StatusDetails: Optional[StatusDetails] - StandardOutputContent: Optional[StandardOutputContent] - StandardOutputUrl: Optional[Url] - StandardErrorContent: Optional[StandardErrorContent] - StandardErrorUrl: Optional[Url] - CloudWatchOutputConfig: Optional[CloudWatchOutputConfig] + CommandId: CommandId | None + InstanceId: InstanceId | None + Comment: Comment | None + DocumentName: DocumentName | None + DocumentVersion: DocumentVersion | None + PluginName: CommandPluginName | None + ResponseCode: ResponseCode | None + ExecutionStartDateTime: StringDateTime | None + ExecutionElapsedTime: StringDateTime | None + ExecutionEndDateTime: StringDateTime | None + Status: CommandInvocationStatus | None + StatusDetails: StatusDetails | None + StandardOutputContent: StandardOutputContent | None + StandardOutputUrl: Url | None + StandardErrorContent: StandardErrorContent | None + StandardErrorUrl: Url | None + CloudWatchOutputConfig: CloudWatchOutputConfig | None class GetConnectionStatusRequest(ServiceRequest): @@ -4409,53 +4420,54 @@ class GetConnectionStatusRequest(ServiceRequest): class GetConnectionStatusResponse(TypedDict, total=False): - Target: Optional[SessionTarget] - Status: Optional[ConnectionStatus] + Target: SessionTarget | None + Status: ConnectionStatus | None class GetDefaultPatchBaselineRequest(ServiceRequest): - OperatingSystem: Optional[OperatingSystem] + OperatingSystem: OperatingSystem | None class GetDefaultPatchBaselineResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] - OperatingSystem: Optional[OperatingSystem] + BaselineId: BaselineId | None + OperatingSystem: OperatingSystem | None class GetDeployablePatchSnapshotForInstanceRequest(ServiceRequest): InstanceId: InstanceId SnapshotId: SnapshotId - BaselineOverride: Optional[BaselineOverride] + BaselineOverride: BaselineOverride | None + UseS3DualStackEndpoint: Boolean | None class GetDeployablePatchSnapshotForInstanceResult(TypedDict, total=False): - InstanceId: Optional[InstanceId] - SnapshotId: Optional[SnapshotId] - SnapshotDownloadUrl: Optional[SnapshotDownloadUrl] - Product: Optional[Product] + InstanceId: InstanceId | None + SnapshotId: SnapshotId | None + SnapshotDownloadUrl: SnapshotDownloadUrl | None + Product: Product | None class GetDocumentRequest(ServiceRequest): Name: DocumentARN - VersionName: Optional[DocumentVersionName] - DocumentVersion: Optional[DocumentVersion] - DocumentFormat: Optional[DocumentFormat] + VersionName: DocumentVersionName | None + DocumentVersion: DocumentVersion | None + DocumentFormat: DocumentFormat | None class GetDocumentResult(TypedDict, total=False): - Name: Optional[DocumentARN] - CreatedDate: Optional[DateTime] - DisplayName: Optional[DocumentDisplayName] - VersionName: Optional[DocumentVersionName] - DocumentVersion: Optional[DocumentVersion] - Status: Optional[DocumentStatus] - StatusInformation: Optional[DocumentStatusInformation] - Content: Optional[DocumentContent] - DocumentType: Optional[DocumentType] - DocumentFormat: Optional[DocumentFormat] - Requires: Optional[DocumentRequiresList] - AttachmentsContent: Optional[AttachmentContentList] - ReviewStatus: Optional[ReviewStatus] + Name: DocumentARN | None + CreatedDate: DateTime | None + DisplayName: DocumentDisplayName | None + VersionName: DocumentVersionName | None + DocumentVersion: DocumentVersion | None + Status: DocumentStatus | None + StatusInformation: DocumentStatusInformation | None + Content: DocumentContent | None + DocumentType: DocumentType | None + DocumentFormat: DocumentFormat | None + Requires: DocumentRequiresList | None + AttachmentsContent: AttachmentContentList | None + ReviewStatus: ReviewStatus | None class GetExecutionPreviewRequest(ServiceRequest): @@ -4463,28 +4475,28 @@ class GetExecutionPreviewRequest(ServiceRequest): class GetExecutionPreviewResponse(TypedDict, total=False): - ExecutionPreviewId: Optional[ExecutionPreviewId] - EndedAt: Optional[DateTime] - Status: Optional[ExecutionPreviewStatus] - StatusMessage: Optional[String] - ExecutionPreview: Optional[ExecutionPreview] + ExecutionPreviewId: ExecutionPreviewId | None + EndedAt: DateTime | None + Status: ExecutionPreviewStatus | None + StatusMessage: String | None + ExecutionPreview: ExecutionPreview | None class ResultAttribute(TypedDict, total=False): TypeName: InventoryItemTypeName -ResultAttributeList = List[ResultAttribute] -InventoryFilterValueList = List[InventoryFilterValue] +ResultAttributeList = list[ResultAttribute] +InventoryFilterValueList = list[InventoryFilterValue] class InventoryFilter(TypedDict, total=False): Key: InventoryFilterKey Values: InventoryFilterValueList - Type: Optional[InventoryQueryOperatorType] + Type: InventoryQueryOperatorType | None -InventoryFilterList = List[InventoryFilter] +InventoryFilterList = list[InventoryFilter] class InventoryGroup(TypedDict, total=False): @@ -4492,58 +4504,58 @@ class InventoryGroup(TypedDict, total=False): Filters: InventoryFilterList -InventoryGroupList = List[InventoryGroup] -InventoryAggregatorList = List["InventoryAggregator"] +InventoryGroupList = list[InventoryGroup] +InventoryAggregatorList = list["InventoryAggregator"] class InventoryAggregator(TypedDict, total=False): - Expression: Optional[InventoryAggregatorExpression] - Aggregators: Optional[InventoryAggregatorList] - Groups: Optional[InventoryGroupList] + Expression: InventoryAggregatorExpression | None + Aggregators: InventoryAggregatorList | None + Groups: InventoryGroupList | None class GetInventoryRequest(ServiceRequest): - Filters: Optional[InventoryFilterList] - Aggregators: Optional[InventoryAggregatorList] - ResultAttributes: Optional[ResultAttributeList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Filters: InventoryFilterList | None + Aggregators: InventoryAggregatorList | None + ResultAttributes: ResultAttributeList | None + NextToken: NextToken | None + MaxResults: MaxResults | None -InventoryItemEntry = Dict[AttributeName, AttributeValue] -InventoryItemEntryList = List[InventoryItemEntry] +InventoryItemEntry = dict[AttributeName, AttributeValue] +InventoryItemEntryList = list[InventoryItemEntry] class InventoryResultItem(TypedDict, total=False): TypeName: InventoryItemTypeName SchemaVersion: InventoryItemSchemaVersion - CaptureTime: Optional[InventoryItemCaptureTime] - ContentHash: Optional[InventoryItemContentHash] + CaptureTime: InventoryItemCaptureTime | None + ContentHash: InventoryItemContentHash | None Content: InventoryItemEntryList -InventoryResultItemMap = Dict[InventoryResultItemKey, InventoryResultItem] +InventoryResultItemMap = dict[InventoryResultItemKey, InventoryResultItem] class InventoryResultEntity(TypedDict, total=False): - Id: Optional[InventoryResultEntityId] - Data: Optional[InventoryResultItemMap] + Id: InventoryResultEntityId | None + Data: InventoryResultItemMap | None -InventoryResultEntityList = List[InventoryResultEntity] +InventoryResultEntityList = list[InventoryResultEntity] class GetInventoryResult(TypedDict, total=False): - Entities: Optional[InventoryResultEntityList] - NextToken: Optional[NextToken] + Entities: InventoryResultEntityList | None + NextToken: NextToken | None class GetInventorySchemaRequest(ServiceRequest): - TypeName: Optional[InventoryItemTypeNameFilter] - NextToken: Optional[NextToken] - MaxResults: Optional[GetInventorySchemaMaxResults] - Aggregator: Optional[AggregatorSchemaOnly] - SubType: Optional[IsSubTypeSchema] + TypeName: InventoryItemTypeNameFilter | None + NextToken: NextToken | None + MaxResults: GetInventorySchemaMaxResults | None + Aggregator: AggregatorSchemaOnly | None + SubType: IsSubTypeSchema | None class InventoryItemAttribute(TypedDict, total=False): @@ -4551,38 +4563,38 @@ class InventoryItemAttribute(TypedDict, total=False): DataType: InventoryAttributeDataType -InventoryItemAttributeList = List[InventoryItemAttribute] +InventoryItemAttributeList = list[InventoryItemAttribute] class InventoryItemSchema(TypedDict, total=False): TypeName: InventoryItemTypeName - Version: Optional[InventoryItemSchemaVersion] + Version: InventoryItemSchemaVersion | None Attributes: InventoryItemAttributeList - DisplayName: Optional[InventoryTypeDisplayName] + DisplayName: InventoryTypeDisplayName | None -InventoryItemSchemaResultList = List[InventoryItemSchema] +InventoryItemSchemaResultList = list[InventoryItemSchema] class GetInventorySchemaResult(TypedDict, total=False): - Schemas: Optional[InventoryItemSchemaResultList] - NextToken: Optional[NextToken] + Schemas: InventoryItemSchemaResultList | None + NextToken: NextToken | None class GetMaintenanceWindowExecutionRequest(ServiceRequest): WindowExecutionId: MaintenanceWindowExecutionId -MaintenanceWindowExecutionTaskIdList = List[MaintenanceWindowExecutionTaskId] +MaintenanceWindowExecutionTaskIdList = list[MaintenanceWindowExecutionTaskId] class GetMaintenanceWindowExecutionResult(TypedDict, total=False): - WindowExecutionId: Optional[MaintenanceWindowExecutionId] - TaskIds: Optional[MaintenanceWindowExecutionTaskIdList] - Status: Optional[MaintenanceWindowExecutionStatus] - StatusDetails: Optional[MaintenanceWindowExecutionStatusDetails] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] + WindowExecutionId: MaintenanceWindowExecutionId | None + TaskIds: MaintenanceWindowExecutionTaskIdList | None + Status: MaintenanceWindowExecutionStatus | None + StatusDetails: MaintenanceWindowExecutionStatusDetails | None + StartTime: DateTime | None + EndTime: DateTime | None class GetMaintenanceWindowExecutionTaskInvocationRequest(ServiceRequest): @@ -4592,18 +4604,18 @@ class GetMaintenanceWindowExecutionTaskInvocationRequest(ServiceRequest): class GetMaintenanceWindowExecutionTaskInvocationResult(TypedDict, total=False): - WindowExecutionId: Optional[MaintenanceWindowExecutionId] - TaskExecutionId: Optional[MaintenanceWindowExecutionTaskId] - InvocationId: Optional[MaintenanceWindowExecutionTaskInvocationId] - ExecutionId: Optional[MaintenanceWindowExecutionTaskExecutionId] - TaskType: Optional[MaintenanceWindowTaskType] - Parameters: Optional[MaintenanceWindowExecutionTaskInvocationParameters] - Status: Optional[MaintenanceWindowExecutionStatus] - StatusDetails: Optional[MaintenanceWindowExecutionStatusDetails] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] - OwnerInformation: Optional[OwnerInformation] - WindowTargetId: Optional[MaintenanceWindowTaskTargetId] + WindowExecutionId: MaintenanceWindowExecutionId | None + TaskExecutionId: MaintenanceWindowExecutionTaskId | None + InvocationId: MaintenanceWindowExecutionTaskInvocationId | None + ExecutionId: MaintenanceWindowExecutionTaskExecutionId | None + TaskType: MaintenanceWindowTaskType | None + Parameters: MaintenanceWindowExecutionTaskInvocationParameters | None + Status: MaintenanceWindowExecutionStatus | None + StatusDetails: MaintenanceWindowExecutionStatusDetails | None + StartTime: DateTime | None + EndTime: DateTime | None + OwnerInformation: OwnerInformation | None + WindowTargetId: MaintenanceWindowTaskTargetId | None class GetMaintenanceWindowExecutionTaskRequest(ServiceRequest): @@ -4611,25 +4623,25 @@ class GetMaintenanceWindowExecutionTaskRequest(ServiceRequest): TaskId: MaintenanceWindowExecutionTaskId -MaintenanceWindowTaskParametersList = List[MaintenanceWindowTaskParameters] +MaintenanceWindowTaskParametersList = list[MaintenanceWindowTaskParameters] class GetMaintenanceWindowExecutionTaskResult(TypedDict, total=False): - WindowExecutionId: Optional[MaintenanceWindowExecutionId] - TaskExecutionId: Optional[MaintenanceWindowExecutionTaskId] - TaskArn: Optional[MaintenanceWindowTaskArn] - ServiceRole: Optional[ServiceRole] - Type: Optional[MaintenanceWindowTaskType] - TaskParameters: Optional[MaintenanceWindowTaskParametersList] - Priority: Optional[MaintenanceWindowTaskPriority] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - Status: Optional[MaintenanceWindowExecutionStatus] - StatusDetails: Optional[MaintenanceWindowExecutionStatusDetails] - StartTime: Optional[DateTime] - EndTime: Optional[DateTime] - AlarmConfiguration: Optional[AlarmConfiguration] - TriggeredAlarms: Optional[AlarmStateInformationList] + WindowExecutionId: MaintenanceWindowExecutionId | None + TaskExecutionId: MaintenanceWindowExecutionTaskId | None + TaskArn: MaintenanceWindowTaskArn | None + ServiceRole: ServiceRole | None + Type: MaintenanceWindowTaskType | None + TaskParameters: MaintenanceWindowTaskParametersList | None + Priority: MaintenanceWindowTaskPriority | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + Status: MaintenanceWindowExecutionStatus | None + StatusDetails: MaintenanceWindowExecutionStatusDetails | None + StartTime: DateTime | None + EndTime: DateTime | None + AlarmConfiguration: AlarmConfiguration | None + TriggeredAlarms: AlarmStateInformationList | None class GetMaintenanceWindowRequest(ServiceRequest): @@ -4637,21 +4649,21 @@ class GetMaintenanceWindowRequest(ServiceRequest): class GetMaintenanceWindowResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - StartDate: Optional[MaintenanceWindowStringDateTime] - EndDate: Optional[MaintenanceWindowStringDateTime] - Schedule: Optional[MaintenanceWindowSchedule] - ScheduleTimezone: Optional[MaintenanceWindowTimezone] - ScheduleOffset: Optional[MaintenanceWindowOffset] - NextExecutionTime: Optional[MaintenanceWindowStringDateTime] - Duration: Optional[MaintenanceWindowDurationHours] - Cutoff: Optional[MaintenanceWindowCutoff] - AllowUnassociatedTargets: Optional[MaintenanceWindowAllowUnassociatedTargets] - Enabled: Optional[MaintenanceWindowEnabled] - CreatedDate: Optional[DateTime] - ModifiedDate: Optional[DateTime] + WindowId: MaintenanceWindowId | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + StartDate: MaintenanceWindowStringDateTime | None + EndDate: MaintenanceWindowStringDateTime | None + Schedule: MaintenanceWindowSchedule | None + ScheduleTimezone: MaintenanceWindowTimezone | None + ScheduleOffset: MaintenanceWindowOffset | None + NextExecutionTime: MaintenanceWindowStringDateTime | None + Duration: MaintenanceWindowDurationHours | None + Cutoff: MaintenanceWindowCutoff | None + AllowUnassociatedTargets: MaintenanceWindowAllowUnassociatedTargets | None + Enabled: MaintenanceWindowEnabled | None + CreatedDate: DateTime | None + ModifiedDate: DateTime | None class GetMaintenanceWindowTaskRequest(ServiceRequest): @@ -4663,306 +4675,306 @@ class GetMaintenanceWindowTaskRequest(ServiceRequest): class MaintenanceWindowLambdaParameters(TypedDict, total=False): - ClientContext: Optional[MaintenanceWindowLambdaClientContext] - Qualifier: Optional[MaintenanceWindowLambdaQualifier] - Payload: Optional[MaintenanceWindowLambdaPayload] + ClientContext: MaintenanceWindowLambdaClientContext | None + Qualifier: MaintenanceWindowLambdaQualifier | None + Payload: MaintenanceWindowLambdaPayload | None class MaintenanceWindowStepFunctionsParameters(TypedDict, total=False): - Input: Optional[MaintenanceWindowStepFunctionsInput] - Name: Optional[MaintenanceWindowStepFunctionsName] + Input: MaintenanceWindowStepFunctionsInput | None + Name: MaintenanceWindowStepFunctionsName | None class MaintenanceWindowAutomationParameters(TypedDict, total=False): - DocumentVersion: Optional[DocumentVersion] - Parameters: Optional[AutomationParameterMap] + DocumentVersion: DocumentVersion | None + Parameters: AutomationParameterMap | None class MaintenanceWindowRunCommandParameters(TypedDict, total=False): - Comment: Optional[Comment] - CloudWatchOutputConfig: Optional[CloudWatchOutputConfig] - DocumentHash: Optional[DocumentHash] - DocumentHashType: Optional[DocumentHashType] - DocumentVersion: Optional[DocumentVersion] - NotificationConfig: Optional[NotificationConfig] - OutputS3BucketName: Optional[S3BucketName] - OutputS3KeyPrefix: Optional[S3KeyPrefix] - Parameters: Optional[Parameters] - ServiceRoleArn: Optional[ServiceRole] - TimeoutSeconds: Optional[TimeoutSeconds] + Comment: Comment | None + CloudWatchOutputConfig: CloudWatchOutputConfig | None + DocumentHash: DocumentHash | None + DocumentHashType: DocumentHashType | None + DocumentVersion: DocumentVersion | None + NotificationConfig: NotificationConfig | None + OutputS3BucketName: S3BucketName | None + OutputS3KeyPrefix: S3KeyPrefix | None + Parameters: Parameters | None + ServiceRoleArn: ServiceRole | None + TimeoutSeconds: TimeoutSeconds | None class MaintenanceWindowTaskInvocationParameters(TypedDict, total=False): - RunCommand: Optional[MaintenanceWindowRunCommandParameters] - Automation: Optional[MaintenanceWindowAutomationParameters] - StepFunctions: Optional[MaintenanceWindowStepFunctionsParameters] - Lambda: Optional[MaintenanceWindowLambdaParameters] + RunCommand: MaintenanceWindowRunCommandParameters | None + Automation: MaintenanceWindowAutomationParameters | None + StepFunctions: MaintenanceWindowStepFunctionsParameters | None + Lambda: MaintenanceWindowLambdaParameters | None class GetMaintenanceWindowTaskResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowTaskId: Optional[MaintenanceWindowTaskId] - Targets: Optional[Targets] - TaskArn: Optional[MaintenanceWindowTaskArn] - ServiceRoleArn: Optional[ServiceRole] - TaskType: Optional[MaintenanceWindowTaskType] - TaskParameters: Optional[MaintenanceWindowTaskParameters] - TaskInvocationParameters: Optional[MaintenanceWindowTaskInvocationParameters] - Priority: Optional[MaintenanceWindowTaskPriority] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - LoggingInfo: Optional[LoggingInfo] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - CutoffBehavior: Optional[MaintenanceWindowTaskCutoffBehavior] - AlarmConfiguration: Optional[AlarmConfiguration] + WindowId: MaintenanceWindowId | None + WindowTaskId: MaintenanceWindowTaskId | None + Targets: Targets | None + TaskArn: MaintenanceWindowTaskArn | None + ServiceRoleArn: ServiceRole | None + TaskType: MaintenanceWindowTaskType | None + TaskParameters: MaintenanceWindowTaskParameters | None + TaskInvocationParameters: MaintenanceWindowTaskInvocationParameters | None + Priority: MaintenanceWindowTaskPriority | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + LoggingInfo: LoggingInfo | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + CutoffBehavior: MaintenanceWindowTaskCutoffBehavior | None + AlarmConfiguration: AlarmConfiguration | None class GetOpsItemRequest(ServiceRequest): OpsItemId: OpsItemId - OpsItemArn: Optional[OpsItemArn] + OpsItemArn: OpsItemArn | None class OpsItem(TypedDict, total=False): - CreatedBy: Optional[String] - OpsItemType: Optional[OpsItemType] - CreatedTime: Optional[DateTime] - Description: Optional[OpsItemDescription] - LastModifiedBy: Optional[String] - LastModifiedTime: Optional[DateTime] - Notifications: Optional[OpsItemNotifications] - Priority: Optional[OpsItemPriority] - RelatedOpsItems: Optional[RelatedOpsItems] - Status: Optional[OpsItemStatus] - OpsItemId: Optional[OpsItemId] - Version: Optional[String] - Title: Optional[OpsItemTitle] - Source: Optional[OpsItemSource] - OperationalData: Optional[OpsItemOperationalData] - Category: Optional[OpsItemCategory] - Severity: Optional[OpsItemSeverity] - ActualStartTime: Optional[DateTime] - ActualEndTime: Optional[DateTime] - PlannedStartTime: Optional[DateTime] - PlannedEndTime: Optional[DateTime] - OpsItemArn: Optional[OpsItemArn] + CreatedBy: String | None + OpsItemType: OpsItemType | None + CreatedTime: DateTime | None + Description: OpsItemDescription | None + LastModifiedBy: String | None + LastModifiedTime: DateTime | None + Notifications: OpsItemNotifications | None + Priority: OpsItemPriority | None + RelatedOpsItems: RelatedOpsItems | None + Status: OpsItemStatus | None + OpsItemId: OpsItemId | None + Version: String | None + Title: OpsItemTitle | None + Source: OpsItemSource | None + OperationalData: OpsItemOperationalData | None + Category: OpsItemCategory | None + Severity: OpsItemSeverity | None + ActualStartTime: DateTime | None + ActualEndTime: DateTime | None + PlannedStartTime: DateTime | None + PlannedEndTime: DateTime | None + OpsItemArn: OpsItemArn | None class GetOpsItemResponse(TypedDict, total=False): - OpsItem: Optional[OpsItem] + OpsItem: OpsItem | None class GetOpsMetadataRequest(ServiceRequest): OpsMetadataArn: OpsMetadataArn - MaxResults: Optional[GetOpsMetadataMaxResults] - NextToken: Optional[NextToken] + MaxResults: GetOpsMetadataMaxResults | None + NextToken: NextToken | None class GetOpsMetadataResult(TypedDict, total=False): - ResourceId: Optional[OpsMetadataResourceId] - Metadata: Optional[MetadataMap] - NextToken: Optional[NextToken] + ResourceId: OpsMetadataResourceId | None + Metadata: MetadataMap | None + NextToken: NextToken | None class OpsResultAttribute(TypedDict, total=False): TypeName: OpsDataTypeName -OpsResultAttributeList = List[OpsResultAttribute] -OpsAggregatorList = List["OpsAggregator"] -OpsFilterValueList = List[OpsFilterValue] +OpsResultAttributeList = list[OpsResultAttribute] +OpsAggregatorList = list["OpsAggregator"] +OpsFilterValueList = list[OpsFilterValue] class OpsFilter(TypedDict, total=False): Key: OpsFilterKey Values: OpsFilterValueList - Type: Optional[OpsFilterOperatorType] + Type: OpsFilterOperatorType | None -OpsFilterList = List[OpsFilter] -OpsAggregatorValueMap = Dict[OpsAggregatorValueKey, OpsAggregatorValue] +OpsFilterList = list[OpsFilter] +OpsAggregatorValueMap = dict[OpsAggregatorValueKey, OpsAggregatorValue] class OpsAggregator(TypedDict, total=False): - AggregatorType: Optional[OpsAggregatorType] - TypeName: Optional[OpsDataTypeName] - AttributeName: Optional[OpsDataAttributeName] - Values: Optional[OpsAggregatorValueMap] - Filters: Optional[OpsFilterList] - Aggregators: Optional[OpsAggregatorList] + AggregatorType: OpsAggregatorType | None + TypeName: OpsDataTypeName | None + AttributeName: OpsDataAttributeName | None + Values: OpsAggregatorValueMap | None + Filters: OpsFilterList | None + Aggregators: OpsAggregatorList | None class GetOpsSummaryRequest(ServiceRequest): - SyncName: Optional[ResourceDataSyncName] - Filters: Optional[OpsFilterList] - Aggregators: Optional[OpsAggregatorList] - ResultAttributes: Optional[OpsResultAttributeList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + SyncName: ResourceDataSyncName | None + Filters: OpsFilterList | None + Aggregators: OpsAggregatorList | None + ResultAttributes: OpsResultAttributeList | None + NextToken: NextToken | None + MaxResults: MaxResults | None -OpsEntityItemEntry = Dict[AttributeName, AttributeValue] -OpsEntityItemEntryList = List[OpsEntityItemEntry] +OpsEntityItemEntry = dict[AttributeName, AttributeValue] +OpsEntityItemEntryList = list[OpsEntityItemEntry] class OpsEntityItem(TypedDict, total=False): - CaptureTime: Optional[OpsEntityItemCaptureTime] - Content: Optional[OpsEntityItemEntryList] + CaptureTime: OpsEntityItemCaptureTime | None + Content: OpsEntityItemEntryList | None -OpsEntityItemMap = Dict[OpsEntityItemKey, OpsEntityItem] +OpsEntityItemMap = dict[OpsEntityItemKey, OpsEntityItem] class OpsEntity(TypedDict, total=False): - Id: Optional[OpsEntityId] - Data: Optional[OpsEntityItemMap] + Id: OpsEntityId | None + Data: OpsEntityItemMap | None -OpsEntityList = List[OpsEntity] +OpsEntityList = list[OpsEntity] class GetOpsSummaryResult(TypedDict, total=False): - Entities: Optional[OpsEntityList] - NextToken: Optional[NextToken] + Entities: OpsEntityList | None + NextToken: NextToken | None class GetParameterHistoryRequest(ServiceRequest): Name: PSParameterName - WithDecryption: Optional[Boolean] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + WithDecryption: Boolean | None + MaxResults: MaxResults | None + NextToken: NextToken | None -ParameterLabelList = List[ParameterLabel] +ParameterLabelList = list[ParameterLabel] class ParameterHistory(TypedDict, total=False): - Name: Optional[PSParameterName] - Type: Optional[ParameterType] - KeyId: Optional[ParameterKeyId] - LastModifiedDate: Optional[DateTime] - LastModifiedUser: Optional[String] - Description: Optional[ParameterDescription] - Value: Optional[PSParameterValue] - AllowedPattern: Optional[AllowedPattern] - Version: Optional[PSParameterVersion] - Labels: Optional[ParameterLabelList] - Tier: Optional[ParameterTier] - Policies: Optional[ParameterPolicyList] - DataType: Optional[ParameterDataType] + Name: PSParameterName | None + Type: ParameterType | None + KeyId: ParameterKeyId | None + LastModifiedDate: DateTime | None + LastModifiedUser: String | None + Description: ParameterDescription | None + Value: PSParameterValue | None + AllowedPattern: AllowedPattern | None + Version: PSParameterVersion | None + Labels: ParameterLabelList | None + Tier: ParameterTier | None + Policies: ParameterPolicyList | None + DataType: ParameterDataType | None -ParameterHistoryList = List[ParameterHistory] +ParameterHistoryList = list[ParameterHistory] class GetParameterHistoryResult(TypedDict, total=False): - Parameters: Optional[ParameterHistoryList] - NextToken: Optional[NextToken] + Parameters: ParameterHistoryList | None + NextToken: NextToken | None class GetParameterRequest(ServiceRequest): Name: PSParameterName - WithDecryption: Optional[Boolean] + WithDecryption: Boolean | None class Parameter(TypedDict, total=False): - Name: Optional[PSParameterName] - Type: Optional[ParameterType] - Value: Optional[PSParameterValue] - Version: Optional[PSParameterVersion] - Selector: Optional[PSParameterSelector] - SourceResult: Optional[String] - LastModifiedDate: Optional[DateTime] - ARN: Optional[String] - DataType: Optional[ParameterDataType] + Name: PSParameterName | None + Type: ParameterType | None + Value: PSParameterValue | None + Version: PSParameterVersion | None + Selector: PSParameterSelector | None + SourceResult: String | None + LastModifiedDate: DateTime | None + ARN: String | None + DataType: ParameterDataType | None class GetParameterResult(TypedDict, total=False): - Parameter: Optional[Parameter] + Parameter: Parameter | None class GetParametersByPathRequest(ServiceRequest): Path: PSParameterName - Recursive: Optional[Boolean] - ParameterFilters: Optional[ParameterStringFilterList] - WithDecryption: Optional[Boolean] - MaxResults: Optional[GetParametersByPathMaxResults] - NextToken: Optional[NextToken] + Recursive: Boolean | None + ParameterFilters: ParameterStringFilterList | None + WithDecryption: Boolean | None + MaxResults: GetParametersByPathMaxResults | None + NextToken: NextToken | None -ParameterList = List[Parameter] +ParameterList = list[Parameter] class GetParametersByPathResult(TypedDict, total=False): - Parameters: Optional[ParameterList] - NextToken: Optional[NextToken] + Parameters: ParameterList | None + NextToken: NextToken | None class GetParametersRequest(ServiceRequest): Names: ParameterNameList - WithDecryption: Optional[Boolean] + WithDecryption: Boolean | None class GetParametersResult(TypedDict, total=False): - Parameters: Optional[ParameterList] - InvalidParameters: Optional[ParameterNameList] + Parameters: ParameterList | None + InvalidParameters: ParameterNameList | None class GetPatchBaselineForPatchGroupRequest(ServiceRequest): PatchGroup: PatchGroup - OperatingSystem: Optional[OperatingSystem] + OperatingSystem: OperatingSystem | None class GetPatchBaselineForPatchGroupResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] - PatchGroup: Optional[PatchGroup] - OperatingSystem: Optional[OperatingSystem] + BaselineId: BaselineId | None + PatchGroup: PatchGroup | None + OperatingSystem: OperatingSystem | None class GetPatchBaselineRequest(ServiceRequest): BaselineId: BaselineId -PatchGroupList = List[PatchGroup] +PatchGroupList = list[PatchGroup] class GetPatchBaselineResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] - Name: Optional[BaselineName] - OperatingSystem: Optional[OperatingSystem] - GlobalFilters: Optional[PatchFilterGroup] - ApprovalRules: Optional[PatchRuleGroup] - ApprovedPatches: Optional[PatchIdList] - ApprovedPatchesComplianceLevel: Optional[PatchComplianceLevel] - ApprovedPatchesEnableNonSecurity: Optional[Boolean] - RejectedPatches: Optional[PatchIdList] - RejectedPatchesAction: Optional[PatchAction] - PatchGroups: Optional[PatchGroupList] - CreatedDate: Optional[DateTime] - ModifiedDate: Optional[DateTime] - Description: Optional[BaselineDescription] - Sources: Optional[PatchSourceList] - AvailableSecurityUpdatesComplianceStatus: Optional[PatchComplianceStatus] + BaselineId: BaselineId | None + Name: BaselineName | None + OperatingSystem: OperatingSystem | None + GlobalFilters: PatchFilterGroup | None + ApprovalRules: PatchRuleGroup | None + ApprovedPatches: PatchIdList | None + ApprovedPatchesComplianceLevel: PatchComplianceLevel | None + ApprovedPatchesEnableNonSecurity: Boolean | None + RejectedPatches: PatchIdList | None + RejectedPatchesAction: PatchAction | None + PatchGroups: PatchGroupList | None + CreatedDate: DateTime | None + ModifiedDate: DateTime | None + Description: BaselineDescription | None + Sources: PatchSourceList | None + AvailableSecurityUpdatesComplianceStatus: PatchComplianceStatus | None class GetResourcePoliciesRequest(ServiceRequest): ResourceArn: ResourceArnString - NextToken: Optional[String] - MaxResults: Optional[ResourcePolicyMaxResults] + NextToken: String | None + MaxResults: ResourcePolicyMaxResults | None class GetResourcePoliciesResponseEntry(TypedDict, total=False): - PolicyId: Optional[PolicyId] - PolicyHash: Optional[PolicyHash] - Policy: Optional[Policy] + PolicyId: PolicyId | None + PolicyHash: PolicyHash | None + Policy: Policy | None -GetResourcePoliciesResponseEntries = List[GetResourcePoliciesResponseEntry] +GetResourcePoliciesResponseEntries = list[GetResourcePoliciesResponseEntry] class GetResourcePoliciesResponse(TypedDict, total=False): - NextToken: Optional[String] - Policies: Optional[GetResourcePoliciesResponseEntries] + NextToken: String | None + Policies: GetResourcePoliciesResponseEntries | None class GetServiceSettingRequest(ServiceRequest): @@ -4970,56 +4982,56 @@ class GetServiceSettingRequest(ServiceRequest): class ServiceSetting(TypedDict, total=False): - SettingId: Optional[ServiceSettingId] - SettingValue: Optional[ServiceSettingValue] - LastModifiedDate: Optional[DateTime] - LastModifiedUser: Optional[String] - ARN: Optional[String] - Status: Optional[String] + SettingId: ServiceSettingId | None + SettingValue: ServiceSettingValue | None + LastModifiedDate: DateTime | None + LastModifiedUser: String | None + ARN: String | None + Status: String | None class GetServiceSettingResult(TypedDict, total=False): - ServiceSetting: Optional[ServiceSetting] + ServiceSetting: ServiceSetting | None class InstanceInfo(TypedDict, total=False): - AgentType: Optional[AgentType] - AgentVersion: Optional[AgentVersion] - ComputerName: Optional[ComputerName] - InstanceStatus: Optional[InstanceStatus] - IpAddress: Optional[IpAddress] - ManagedStatus: Optional[ManagedStatus] - PlatformType: Optional[PlatformType] - PlatformName: Optional[PlatformName] - PlatformVersion: Optional[PlatformVersion] - ResourceType: Optional[ResourceType] + AgentType: AgentType | None + AgentVersion: AgentVersion | None + ComputerName: ComputerName | None + InstanceStatus: InstanceStatus | None + IpAddress: IpAddress | None + ManagedStatus: ManagedStatus | None + PlatformType: PlatformType | None + PlatformName: PlatformName | None + PlatformVersion: PlatformVersion | None + ResourceType: ResourceType | None -InventoryItemContentContext = Dict[AttributeName, AttributeValue] +InventoryItemContentContext = dict[AttributeName, AttributeValue] class InventoryItem(TypedDict, total=False): TypeName: InventoryItemTypeName SchemaVersion: InventoryItemSchemaVersion CaptureTime: InventoryItemCaptureTime - ContentHash: Optional[InventoryItemContentHash] - Content: Optional[InventoryItemEntryList] - Context: Optional[InventoryItemContentContext] + ContentHash: InventoryItemContentHash | None + Content: InventoryItemEntryList | None + Context: InventoryItemContentContext | None -InventoryItemList = List[InventoryItem] -KeyList = List[TagKey] +InventoryItemList = list[InventoryItem] +KeyList = list[TagKey] class LabelParameterVersionRequest(ServiceRequest): Name: PSParameterName - ParameterVersion: Optional[PSParameterVersion] + ParameterVersion: PSParameterVersion | None Labels: ParameterLabelList class LabelParameterVersionResult(TypedDict, total=False): - InvalidLabels: Optional[ParameterLabelList] - ParameterVersion: Optional[PSParameterVersion] + InvalidLabels: ParameterLabelList | None + ParameterVersion: PSParameterVersion | None LastResourceDataSyncTime = datetime @@ -5028,209 +5040,209 @@ class LabelParameterVersionResult(TypedDict, total=False): class ListAssociationVersionsRequest(ServiceRequest): AssociationId: AssociationId - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListAssociationVersionsResult(TypedDict, total=False): - AssociationVersions: Optional[AssociationVersionList] - NextToken: Optional[NextToken] + AssociationVersions: AssociationVersionList | None + NextToken: NextToken | None class ListAssociationsRequest(ServiceRequest): - AssociationFilterList: Optional[AssociationFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + AssociationFilterList: AssociationFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListAssociationsResult(TypedDict, total=False): - Associations: Optional[AssociationList] - NextToken: Optional[NextToken] + Associations: AssociationList | None + NextToken: NextToken | None class ListCommandInvocationsRequest(ServiceRequest): - CommandId: Optional[CommandId] - InstanceId: Optional[InstanceId] - MaxResults: Optional[CommandMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[CommandFilterList] - Details: Optional[Boolean] + CommandId: CommandId | None + InstanceId: InstanceId | None + MaxResults: CommandMaxResults | None + NextToken: NextToken | None + Filters: CommandFilterList | None + Details: Boolean | None class ListCommandInvocationsResult(TypedDict, total=False): - CommandInvocations: Optional[CommandInvocationList] - NextToken: Optional[NextToken] + CommandInvocations: CommandInvocationList | None + NextToken: NextToken | None class ListCommandsRequest(ServiceRequest): - CommandId: Optional[CommandId] - InstanceId: Optional[InstanceId] - MaxResults: Optional[CommandMaxResults] - NextToken: Optional[NextToken] - Filters: Optional[CommandFilterList] + CommandId: CommandId | None + InstanceId: InstanceId | None + MaxResults: CommandMaxResults | None + NextToken: NextToken | None + Filters: CommandFilterList | None class ListCommandsResult(TypedDict, total=False): - Commands: Optional[CommandList] - NextToken: Optional[NextToken] + Commands: CommandList | None + NextToken: NextToken | None class ListComplianceItemsRequest(ServiceRequest): - Filters: Optional[ComplianceStringFilterList] - ResourceIds: Optional[ComplianceResourceIdList] - ResourceTypes: Optional[ComplianceResourceTypeList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Filters: ComplianceStringFilterList | None + ResourceIds: ComplianceResourceIdList | None + ResourceTypes: ComplianceResourceTypeList | None + NextToken: NextToken | None + MaxResults: MaxResults | None class ListComplianceItemsResult(TypedDict, total=False): - ComplianceItems: Optional[ComplianceItemList] - NextToken: Optional[NextToken] + ComplianceItems: ComplianceItemList | None + NextToken: NextToken | None class ListComplianceSummariesRequest(ServiceRequest): - Filters: Optional[ComplianceStringFilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Filters: ComplianceStringFilterList | None + NextToken: NextToken | None + MaxResults: MaxResults | None class ListComplianceSummariesResult(TypedDict, total=False): - ComplianceSummaryItems: Optional[ComplianceSummaryItemList] - NextToken: Optional[NextToken] + ComplianceSummaryItems: ComplianceSummaryItemList | None + NextToken: NextToken | None class ListDocumentMetadataHistoryRequest(ServiceRequest): Name: DocumentName - DocumentVersion: Optional[DocumentVersion] + DocumentVersion: DocumentVersion | None Metadata: DocumentMetadataEnum - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + NextToken: NextToken | None + MaxResults: MaxResults | None class ListDocumentMetadataHistoryResponse(TypedDict, total=False): - Name: Optional[DocumentName] - DocumentVersion: Optional[DocumentVersion] - Author: Optional[DocumentAuthor] - Metadata: Optional[DocumentMetadataResponseInfo] - NextToken: Optional[NextToken] + Name: DocumentName | None + DocumentVersion: DocumentVersion | None + Author: DocumentAuthor | None + Metadata: DocumentMetadataResponseInfo | None + NextToken: NextToken | None class ListDocumentVersionsRequest(ServiceRequest): Name: DocumentARN - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + MaxResults: MaxResults | None + NextToken: NextToken | None class ListDocumentVersionsResult(TypedDict, total=False): - DocumentVersions: Optional[DocumentVersionList] - NextToken: Optional[NextToken] + DocumentVersions: DocumentVersionList | None + NextToken: NextToken | None class ListDocumentsRequest(ServiceRequest): - DocumentFilterList: Optional[DocumentFilterList] - Filters: Optional[DocumentKeyValuesFilterList] - MaxResults: Optional[MaxResults] - NextToken: Optional[NextToken] + DocumentFilterList: DocumentFilterList | None + Filters: DocumentKeyValuesFilterList | None + MaxResults: MaxResults | None + NextToken: NextToken | None class ListDocumentsResult(TypedDict, total=False): - DocumentIdentifiers: Optional[DocumentIdentifierList] - NextToken: Optional[NextToken] + DocumentIdentifiers: DocumentIdentifierList | None + NextToken: NextToken | None class ListInventoryEntriesRequest(ServiceRequest): InstanceId: InstanceId TypeName: InventoryItemTypeName - Filters: Optional[InventoryFilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Filters: InventoryFilterList | None + NextToken: NextToken | None + MaxResults: MaxResults | None class ListInventoryEntriesResult(TypedDict, total=False): - TypeName: Optional[InventoryItemTypeName] - InstanceId: Optional[InstanceId] - SchemaVersion: Optional[InventoryItemSchemaVersion] - CaptureTime: Optional[InventoryItemCaptureTime] - Entries: Optional[InventoryItemEntryList] - NextToken: Optional[NextToken] + TypeName: InventoryItemTypeName | None + InstanceId: InstanceId | None + SchemaVersion: InventoryItemSchemaVersion | None + CaptureTime: InventoryItemCaptureTime | None + Entries: InventoryItemEntryList | None + NextToken: NextToken | None -NodeFilterValueList = List[NodeFilterValue] +NodeFilterValueList = list[NodeFilterValue] class NodeFilter(TypedDict, total=False): Key: NodeFilterKey Values: NodeFilterValueList - Type: Optional[NodeFilterOperatorType] + Type: NodeFilterOperatorType | None -NodeFilterList = List[NodeFilter] +NodeFilterList = list[NodeFilter] class ListNodesRequest(ServiceRequest): - SyncName: Optional[ResourceDataSyncName] - Filters: Optional[NodeFilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + SyncName: ResourceDataSyncName | None + Filters: NodeFilterList | None + NextToken: NextToken | None + MaxResults: MaxResults | None class NodeType(TypedDict, total=False): - Instance: Optional[InstanceInfo] + Instance: InstanceInfo | None class NodeOwnerInfo(TypedDict, total=False): - AccountId: Optional[NodeAccountId] - OrganizationalUnitId: Optional[NodeOrganizationalUnitId] - OrganizationalUnitPath: Optional[NodeOrganizationalUnitPath] + AccountId: NodeAccountId | None + OrganizationalUnitId: NodeOrganizationalUnitId | None + OrganizationalUnitPath: NodeOrganizationalUnitPath | None NodeCaptureTime = datetime class Node(TypedDict, total=False): - CaptureTime: Optional[NodeCaptureTime] - Id: Optional[NodeId] - Owner: Optional[NodeOwnerInfo] - Region: Optional[NodeRegion] - NodeType: Optional[NodeType] + CaptureTime: NodeCaptureTime | None + Id: NodeId | None + Owner: NodeOwnerInfo | None + Region: NodeRegion | None + NodeType: NodeType | None -NodeList = List[Node] +NodeList = list[Node] class ListNodesResult(TypedDict, total=False): - Nodes: Optional[NodeList] - NextToken: Optional[NextToken] + Nodes: NodeList | None + NextToken: NextToken | None -NodeAggregatorList = List["NodeAggregator"] +NodeAggregatorList = list["NodeAggregator"] class NodeAggregator(TypedDict, total=False): AggregatorType: NodeAggregatorType TypeName: NodeTypeName AttributeName: NodeAttributeName - Aggregators: Optional[NodeAggregatorList] + Aggregators: NodeAggregatorList | None class ListNodesSummaryRequest(ServiceRequest): - SyncName: Optional[ResourceDataSyncName] - Filters: Optional[NodeFilterList] + SyncName: ResourceDataSyncName | None + Filters: NodeFilterList | None Aggregators: NodeAggregatorList - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + NextToken: NextToken | None + MaxResults: MaxResults | None -NodeSummary = Dict[AttributeName, AttributeValue] -NodeSummaryList = List[NodeSummary] +NodeSummary = dict[AttributeName, AttributeValue] +NodeSummaryList = list[NodeSummary] class ListNodesSummaryResult(TypedDict, total=False): - Summary: Optional[NodeSummaryList] - NextToken: Optional[NextToken] + Summary: NodeSummaryList | None + NextToken: NextToken | None -OpsItemEventFilterValues = List[OpsItemEventFilterValue] +OpsItemEventFilterValues = list[OpsItemEventFilterValue] class OpsItemEventFilter(TypedDict, total=False): @@ -5239,38 +5251,38 @@ class OpsItemEventFilter(TypedDict, total=False): Operator: OpsItemEventFilterOperator -OpsItemEventFilters = List[OpsItemEventFilter] +OpsItemEventFilters = list[OpsItemEventFilter] class ListOpsItemEventsRequest(ServiceRequest): - Filters: Optional[OpsItemEventFilters] - MaxResults: Optional[OpsItemEventMaxResults] - NextToken: Optional[String] + Filters: OpsItemEventFilters | None + MaxResults: OpsItemEventMaxResults | None + NextToken: String | None class OpsItemIdentity(TypedDict, total=False): - Arn: Optional[String] + Arn: String | None class OpsItemEventSummary(TypedDict, total=False): - OpsItemId: Optional[String] - EventId: Optional[String] - Source: Optional[String] - DetailType: Optional[String] - Detail: Optional[String] - CreatedBy: Optional[OpsItemIdentity] - CreatedTime: Optional[DateTime] + OpsItemId: String | None + EventId: String | None + Source: String | None + DetailType: String | None + Detail: String | None + CreatedBy: OpsItemIdentity | None + CreatedTime: DateTime | None -OpsItemEventSummaries = List[OpsItemEventSummary] +OpsItemEventSummaries = list[OpsItemEventSummary] class ListOpsItemEventsResponse(TypedDict, total=False): - NextToken: Optional[String] - Summaries: Optional[OpsItemEventSummaries] + NextToken: String | None + Summaries: OpsItemEventSummaries | None -OpsItemRelatedItemsFilterValues = List[OpsItemRelatedItemsFilterValue] +OpsItemRelatedItemsFilterValues = list[OpsItemRelatedItemsFilterValue] class OpsItemRelatedItemsFilter(TypedDict, total=False): @@ -5279,37 +5291,37 @@ class OpsItemRelatedItemsFilter(TypedDict, total=False): Operator: OpsItemRelatedItemsFilterOperator -OpsItemRelatedItemsFilters = List[OpsItemRelatedItemsFilter] +OpsItemRelatedItemsFilters = list[OpsItemRelatedItemsFilter] class ListOpsItemRelatedItemsRequest(ServiceRequest): - OpsItemId: Optional[OpsItemId] - Filters: Optional[OpsItemRelatedItemsFilters] - MaxResults: Optional[OpsItemRelatedItemsMaxResults] - NextToken: Optional[String] + OpsItemId: OpsItemId | None + Filters: OpsItemRelatedItemsFilters | None + MaxResults: OpsItemRelatedItemsMaxResults | None + NextToken: String | None class OpsItemRelatedItemSummary(TypedDict, total=False): - OpsItemId: Optional[OpsItemId] - AssociationId: Optional[OpsItemRelatedItemAssociationId] - ResourceType: Optional[OpsItemRelatedItemAssociationResourceType] - AssociationType: Optional[OpsItemRelatedItemAssociationType] - ResourceUri: Optional[OpsItemRelatedItemAssociationResourceUri] - CreatedBy: Optional[OpsItemIdentity] - CreatedTime: Optional[DateTime] - LastModifiedBy: Optional[OpsItemIdentity] - LastModifiedTime: Optional[DateTime] + OpsItemId: OpsItemId | None + AssociationId: OpsItemRelatedItemAssociationId | None + ResourceType: OpsItemRelatedItemAssociationResourceType | None + AssociationType: OpsItemRelatedItemAssociationType | None + ResourceUri: OpsItemRelatedItemAssociationResourceUri | None + CreatedBy: OpsItemIdentity | None + CreatedTime: DateTime | None + LastModifiedBy: OpsItemIdentity | None + LastModifiedTime: DateTime | None -OpsItemRelatedItemSummaries = List[OpsItemRelatedItemSummary] +OpsItemRelatedItemSummaries = list[OpsItemRelatedItemSummary] class ListOpsItemRelatedItemsResponse(TypedDict, total=False): - NextToken: Optional[String] - Summaries: Optional[OpsItemRelatedItemSummaries] + NextToken: String | None + Summaries: OpsItemRelatedItemSummaries | None -OpsMetadataFilterValueList = List[OpsMetadataFilterValue] +OpsMetadataFilterValueList = list[OpsMetadataFilterValue] class OpsMetadataFilter(TypedDict, total=False): @@ -5317,60 +5329,60 @@ class OpsMetadataFilter(TypedDict, total=False): Values: OpsMetadataFilterValueList -OpsMetadataFilterList = List[OpsMetadataFilter] +OpsMetadataFilterList = list[OpsMetadataFilter] class ListOpsMetadataRequest(ServiceRequest): - Filters: Optional[OpsMetadataFilterList] - MaxResults: Optional[ListOpsMetadataMaxResults] - NextToken: Optional[NextToken] + Filters: OpsMetadataFilterList | None + MaxResults: ListOpsMetadataMaxResults | None + NextToken: NextToken | None class OpsMetadata(TypedDict, total=False): - ResourceId: Optional[OpsMetadataResourceId] - OpsMetadataArn: Optional[OpsMetadataArn] - LastModifiedDate: Optional[DateTime] - LastModifiedUser: Optional[String] - CreationDate: Optional[DateTime] + ResourceId: OpsMetadataResourceId | None + OpsMetadataArn: OpsMetadataArn | None + LastModifiedDate: DateTime | None + LastModifiedUser: String | None + CreationDate: DateTime | None -OpsMetadataList = List[OpsMetadata] +OpsMetadataList = list[OpsMetadata] class ListOpsMetadataResult(TypedDict, total=False): - OpsMetadataList: Optional[OpsMetadataList] - NextToken: Optional[NextToken] + OpsMetadataList: OpsMetadataList | None + NextToken: NextToken | None class ListResourceComplianceSummariesRequest(ServiceRequest): - Filters: Optional[ComplianceStringFilterList] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Filters: ComplianceStringFilterList | None + NextToken: NextToken | None + MaxResults: MaxResults | None class ResourceComplianceSummaryItem(TypedDict, total=False): - ComplianceType: Optional[ComplianceTypeName] - ResourceType: Optional[ComplianceResourceType] - ResourceId: Optional[ComplianceResourceId] - Status: Optional[ComplianceStatus] - OverallSeverity: Optional[ComplianceSeverity] - ExecutionSummary: Optional[ComplianceExecutionSummary] - CompliantSummary: Optional[CompliantSummary] - NonCompliantSummary: Optional[NonCompliantSummary] + ComplianceType: ComplianceTypeName | None + ResourceType: ComplianceResourceType | None + ResourceId: ComplianceResourceId | None + Status: ComplianceStatus | None + OverallSeverity: ComplianceSeverity | None + ExecutionSummary: ComplianceExecutionSummary | None + CompliantSummary: CompliantSummary | None + NonCompliantSummary: NonCompliantSummary | None -ResourceComplianceSummaryItemList = List[ResourceComplianceSummaryItem] +ResourceComplianceSummaryItemList = list[ResourceComplianceSummaryItem] class ListResourceComplianceSummariesResult(TypedDict, total=False): - ResourceComplianceSummaryItems: Optional[ResourceComplianceSummaryItemList] - NextToken: Optional[NextToken] + ResourceComplianceSummaryItems: ResourceComplianceSummaryItemList | None + NextToken: NextToken | None class ListResourceDataSyncRequest(ServiceRequest): - SyncType: Optional[ResourceDataSyncType] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + SyncType: ResourceDataSyncType | None + NextToken: NextToken | None + MaxResults: MaxResults | None ResourceDataSyncCreatedTime = datetime @@ -5378,33 +5390,33 @@ class ListResourceDataSyncRequest(ServiceRequest): class ResourceDataSyncSourceWithState(TypedDict, total=False): - SourceType: Optional[ResourceDataSyncSourceType] - AwsOrganizationsSource: Optional[ResourceDataSyncAwsOrganizationsSource] - SourceRegions: Optional[ResourceDataSyncSourceRegionList] - IncludeFutureRegions: Optional[ResourceDataSyncIncludeFutureRegions] - State: Optional[ResourceDataSyncState] - EnableAllOpsDataSources: Optional[ResourceDataSyncEnableAllOpsDataSources] + SourceType: ResourceDataSyncSourceType | None + AwsOrganizationsSource: ResourceDataSyncAwsOrganizationsSource | None + SourceRegions: ResourceDataSyncSourceRegionList | None + IncludeFutureRegions: ResourceDataSyncIncludeFutureRegions | None + State: ResourceDataSyncState | None + EnableAllOpsDataSources: ResourceDataSyncEnableAllOpsDataSources | None class ResourceDataSyncItem(TypedDict, total=False): - SyncName: Optional[ResourceDataSyncName] - SyncType: Optional[ResourceDataSyncType] - SyncSource: Optional[ResourceDataSyncSourceWithState] - S3Destination: Optional[ResourceDataSyncS3Destination] - LastSyncTime: Optional[LastResourceDataSyncTime] - LastSuccessfulSyncTime: Optional[LastSuccessfulResourceDataSyncTime] - SyncLastModifiedTime: Optional[ResourceDataSyncLastModifiedTime] - LastStatus: Optional[LastResourceDataSyncStatus] - SyncCreatedTime: Optional[ResourceDataSyncCreatedTime] - LastSyncStatusMessage: Optional[LastResourceDataSyncMessage] + SyncName: ResourceDataSyncName | None + SyncType: ResourceDataSyncType | None + SyncSource: ResourceDataSyncSourceWithState | None + S3Destination: ResourceDataSyncS3Destination | None + LastSyncTime: LastResourceDataSyncTime | None + LastSuccessfulSyncTime: LastSuccessfulResourceDataSyncTime | None + SyncLastModifiedTime: ResourceDataSyncLastModifiedTime | None + LastStatus: LastResourceDataSyncStatus | None + SyncCreatedTime: ResourceDataSyncCreatedTime | None + LastSyncStatusMessage: LastResourceDataSyncMessage | None -ResourceDataSyncItemList = List[ResourceDataSyncItem] +ResourceDataSyncItemList = list[ResourceDataSyncItem] class ListResourceDataSyncResult(TypedDict, total=False): - ResourceDataSyncItems: Optional[ResourceDataSyncItemList] - NextToken: Optional[NextToken] + ResourceDataSyncItems: ResourceDataSyncItemList | None + NextToken: NextToken | None class ListTagsForResourceRequest(ServiceRequest): @@ -5413,25 +5425,25 @@ class ListTagsForResourceRequest(ServiceRequest): class ListTagsForResourceResult(TypedDict, total=False): - TagList: Optional[TagList] + TagList: TagList | None -MetadataKeysToDeleteList = List[MetadataKey] +MetadataKeysToDeleteList = list[MetadataKey] class ModifyDocumentPermissionRequest(ServiceRequest): Name: DocumentName PermissionType: DocumentPermissionType - AccountIdsToAdd: Optional[AccountIdList] - AccountIdsToRemove: Optional[AccountIdList] - SharedDocumentVersion: Optional[SharedDocumentVersion] + AccountIdsToAdd: AccountIdList | None + AccountIdsToRemove: AccountIdList | None + SharedDocumentVersion: SharedDocumentVersion | None class ModifyDocumentPermissionResponse(TypedDict, total=False): pass -OpsItemOpsDataKeysList = List[String] +OpsItemOpsDataKeysList = list[String] class PutComplianceItemsRequest(ServiceRequest): @@ -5440,8 +5452,8 @@ class PutComplianceItemsRequest(ServiceRequest): ComplianceType: ComplianceTypeName ExecutionSummary: ComplianceExecutionSummary Items: ComplianceItemEntryList - ItemContentHash: Optional[ComplianceItemContentHash] - UploadType: Optional[ComplianceUploadType] + ItemContentHash: ComplianceItemContentHash | None + UploadType: ComplianceUploadType | None class PutComplianceItemsResult(TypedDict, total=False): @@ -5454,38 +5466,38 @@ class PutInventoryRequest(ServiceRequest): class PutInventoryResult(TypedDict, total=False): - Message: Optional[PutInventoryMessage] + Message: PutInventoryMessage | None class PutParameterRequest(ServiceRequest): Name: PSParameterName - Description: Optional[ParameterDescription] + Description: ParameterDescription | None Value: PSParameterValue - Type: Optional[ParameterType] - KeyId: Optional[ParameterKeyId] - Overwrite: Optional[Boolean] - AllowedPattern: Optional[AllowedPattern] - Tags: Optional[TagList] - Tier: Optional[ParameterTier] - Policies: Optional[ParameterPolicies] - DataType: Optional[ParameterDataType] + Type: ParameterType | None + KeyId: ParameterKeyId | None + Overwrite: Boolean | None + AllowedPattern: AllowedPattern | None + Tags: TagList | None + Tier: ParameterTier | None + Policies: ParameterPolicies | None + DataType: ParameterDataType | None class PutParameterResult(TypedDict, total=False): - Version: Optional[PSParameterVersion] - Tier: Optional[ParameterTier] + Version: PSParameterVersion | None + Tier: ParameterTier | None class PutResourcePolicyRequest(ServiceRequest): ResourceArn: ResourceArnString Policy: Policy - PolicyId: Optional[PolicyId] - PolicyHash: Optional[PolicyHash] + PolicyId: PolicyId | None + PolicyHash: PolicyHash | None class PutResourcePolicyResponse(TypedDict, total=False): - PolicyId: Optional[PolicyId] - PolicyHash: Optional[PolicyHash] + PolicyId: PolicyId | None + PolicyHash: PolicyHash | None class RegisterDefaultPatchBaselineRequest(ServiceRequest): @@ -5493,7 +5505,7 @@ class RegisterDefaultPatchBaselineRequest(ServiceRequest): class RegisterDefaultPatchBaselineResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] + BaselineId: BaselineId | None class RegisterPatchBaselineForPatchGroupRequest(ServiceRequest): @@ -5502,45 +5514,45 @@ class RegisterPatchBaselineForPatchGroupRequest(ServiceRequest): class RegisterPatchBaselineForPatchGroupResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] - PatchGroup: Optional[PatchGroup] + BaselineId: BaselineId | None + PatchGroup: PatchGroup | None class RegisterTargetWithMaintenanceWindowRequest(ServiceRequest): WindowId: MaintenanceWindowId ResourceType: MaintenanceWindowResourceType Targets: Targets - OwnerInformation: Optional[OwnerInformation] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - ClientToken: Optional[ClientToken] + OwnerInformation: OwnerInformation | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + ClientToken: ClientToken | None class RegisterTargetWithMaintenanceWindowResult(TypedDict, total=False): - WindowTargetId: Optional[MaintenanceWindowTargetId] + WindowTargetId: MaintenanceWindowTargetId | None class RegisterTaskWithMaintenanceWindowRequest(ServiceRequest): WindowId: MaintenanceWindowId - Targets: Optional[Targets] + Targets: Targets | None TaskArn: MaintenanceWindowTaskArn - ServiceRoleArn: Optional[ServiceRole] + ServiceRoleArn: ServiceRole | None TaskType: MaintenanceWindowTaskType - TaskParameters: Optional[MaintenanceWindowTaskParameters] - TaskInvocationParameters: Optional[MaintenanceWindowTaskInvocationParameters] - Priority: Optional[MaintenanceWindowTaskPriority] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - LoggingInfo: Optional[LoggingInfo] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - ClientToken: Optional[ClientToken] - CutoffBehavior: Optional[MaintenanceWindowTaskCutoffBehavior] - AlarmConfiguration: Optional[AlarmConfiguration] + TaskParameters: MaintenanceWindowTaskParameters | None + TaskInvocationParameters: MaintenanceWindowTaskInvocationParameters | None + Priority: MaintenanceWindowTaskPriority | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + LoggingInfo: LoggingInfo | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + ClientToken: ClientToken | None + CutoffBehavior: MaintenanceWindowTaskCutoffBehavior | None + AlarmConfiguration: AlarmConfiguration | None class RegisterTaskWithMaintenanceWindowResult(TypedDict, total=False): - WindowTaskId: Optional[MaintenanceWindowTaskId] + WindowTaskId: MaintenanceWindowTaskId | None class RemoveTagsFromResourceRequest(ServiceRequest): @@ -5558,7 +5570,7 @@ class ResetServiceSettingRequest(ServiceRequest): class ResetServiceSettingResult(TypedDict, total=False): - ServiceSetting: Optional[ServiceSetting] + ServiceSetting: ServiceSetting | None class ResumeSessionRequest(ServiceRequest): @@ -5566,15 +5578,15 @@ class ResumeSessionRequest(ServiceRequest): class ResumeSessionResponse(TypedDict, total=False): - SessionId: Optional[SessionId] - TokenValue: Optional[TokenValue] - StreamUrl: Optional[StreamUrl] + SessionId: SessionId | None + TokenValue: TokenValue | None + StreamUrl: StreamUrl | None class SendAutomationSignalRequest(ServiceRequest): AutomationExecutionId: AutomationExecutionId SignalType: SignalType - Payload: Optional[AutomationParameterMap] + Payload: AutomationParameterMap | None class SendAutomationSignalResult(TypedDict, total=False): @@ -5582,42 +5594,42 @@ class SendAutomationSignalResult(TypedDict, total=False): class SendCommandRequest(ServiceRequest): - InstanceIds: Optional[InstanceIdList] - Targets: Optional[Targets] + InstanceIds: InstanceIdList | None + Targets: Targets | None DocumentName: DocumentARN - DocumentVersion: Optional[DocumentVersion] - DocumentHash: Optional[DocumentHash] - DocumentHashType: Optional[DocumentHashType] - TimeoutSeconds: Optional[TimeoutSeconds] - Comment: Optional[Comment] - Parameters: Optional[Parameters] - OutputS3Region: Optional[S3Region] - OutputS3BucketName: Optional[S3BucketName] - OutputS3KeyPrefix: Optional[S3KeyPrefix] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - ServiceRoleArn: Optional[ServiceRole] - NotificationConfig: Optional[NotificationConfig] - CloudWatchOutputConfig: Optional[CloudWatchOutputConfig] - AlarmConfiguration: Optional[AlarmConfiguration] + DocumentVersion: DocumentVersion | None + DocumentHash: DocumentHash | None + DocumentHashType: DocumentHashType | None + TimeoutSeconds: TimeoutSeconds | None + Comment: Comment | None + Parameters: Parameters | None + OutputS3Region: S3Region | None + OutputS3BucketName: S3BucketName | None + OutputS3KeyPrefix: S3KeyPrefix | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + ServiceRoleArn: ServiceRole | None + NotificationConfig: NotificationConfig | None + CloudWatchOutputConfig: CloudWatchOutputConfig | None + AlarmConfiguration: AlarmConfiguration | None class SendCommandResult(TypedDict, total=False): - Command: Optional[Command] + Command: Command | None -SessionManagerParameterValueList = List[SessionManagerParameterValue] -SessionManagerParameters = Dict[SessionManagerParameterName, SessionManagerParameterValueList] +SessionManagerParameterValueList = list[SessionManagerParameterValue] +SessionManagerParameters = dict[SessionManagerParameterName, SessionManagerParameterValueList] class StartAccessRequestRequest(ServiceRequest): Reason: String1to256 Targets: Targets - Tags: Optional[TagList] + Tags: TagList | None class StartAccessRequestResponse(TypedDict, total=False): - AccessRequestId: Optional[AccessRequestId] + AccessRequestId: AccessRequestId | None class StartAssociationsOnceRequest(ServiceRequest): @@ -5630,69 +5642,69 @@ class StartAssociationsOnceResult(TypedDict, total=False): class StartAutomationExecutionRequest(ServiceRequest): DocumentName: DocumentARN - DocumentVersion: Optional[DocumentVersion] - Parameters: Optional[AutomationParameterMap] - ClientToken: Optional[IdempotencyToken] - Mode: Optional[ExecutionMode] - TargetParameterName: Optional[AutomationParameterKey] - Targets: Optional[Targets] - TargetMaps: Optional[TargetMaps] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - TargetLocations: Optional[TargetLocations] - Tags: Optional[TagList] - AlarmConfiguration: Optional[AlarmConfiguration] - TargetLocationsURL: Optional[TargetLocationsURL] + DocumentVersion: DocumentVersion | None + Parameters: AutomationParameterMap | None + ClientToken: IdempotencyToken | None + Mode: ExecutionMode | None + TargetParameterName: AutomationParameterKey | None + Targets: Targets | None + TargetMaps: TargetMaps | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + TargetLocations: TargetLocations | None + Tags: TagList | None + AlarmConfiguration: AlarmConfiguration | None + TargetLocationsURL: TargetLocationsURL | None class StartAutomationExecutionResult(TypedDict, total=False): - AutomationExecutionId: Optional[AutomationExecutionId] + AutomationExecutionId: AutomationExecutionId | None class StartChangeRequestExecutionRequest(ServiceRequest): - ScheduledTime: Optional[DateTime] + ScheduledTime: DateTime | None DocumentName: DocumentARN - DocumentVersion: Optional[DocumentVersion] - Parameters: Optional[AutomationParameterMap] - ChangeRequestName: Optional[ChangeRequestName] - ClientToken: Optional[IdempotencyToken] - AutoApprove: Optional[Boolean] + DocumentVersion: DocumentVersion | None + Parameters: AutomationParameterMap | None + ChangeRequestName: ChangeRequestName | None + ClientToken: IdempotencyToken | None + AutoApprove: Boolean | None Runbooks: Runbooks - Tags: Optional[TagList] - ScheduledEndTime: Optional[DateTime] - ChangeDetails: Optional[ChangeDetailsValue] + Tags: TagList | None + ScheduledEndTime: DateTime | None + ChangeDetails: ChangeDetailsValue | None class StartChangeRequestExecutionResult(TypedDict, total=False): - AutomationExecutionId: Optional[AutomationExecutionId] + AutomationExecutionId: AutomationExecutionId | None class StartExecutionPreviewRequest(ServiceRequest): DocumentName: DocumentName - DocumentVersion: Optional[DocumentVersion] - ExecutionInputs: Optional[ExecutionInputs] + DocumentVersion: DocumentVersion | None + ExecutionInputs: ExecutionInputs | None class StartExecutionPreviewResponse(TypedDict, total=False): - ExecutionPreviewId: Optional[ExecutionPreviewId] + ExecutionPreviewId: ExecutionPreviewId | None class StartSessionRequest(ServiceRequest): Target: SessionTarget - DocumentName: Optional[DocumentARN] - Reason: Optional[SessionReason] - Parameters: Optional[SessionManagerParameters] + DocumentName: DocumentARN | None + Reason: SessionReason | None + Parameters: SessionManagerParameters | None class StartSessionResponse(TypedDict, total=False): - SessionId: Optional[SessionId] - TokenValue: Optional[TokenValue] - StreamUrl: Optional[StreamUrl] + SessionId: SessionId | None + TokenValue: TokenValue | None + StreamUrl: StreamUrl | None class StopAutomationExecutionRequest(ServiceRequest): AutomationExecutionId: AutomationExecutionId - Type: Optional[StopType] + Type: StopType | None class StopAutomationExecutionResult(TypedDict, total=False): @@ -5704,7 +5716,7 @@ class TerminateSessionRequest(ServiceRequest): class TerminateSessionResponse(TypedDict, total=False): - SessionId: Optional[SessionId] + SessionId: SessionId | None class UnlabelParameterVersionRequest(ServiceRequest): @@ -5714,36 +5726,37 @@ class UnlabelParameterVersionRequest(ServiceRequest): class UnlabelParameterVersionResult(TypedDict, total=False): - RemovedLabels: Optional[ParameterLabelList] - InvalidLabels: Optional[ParameterLabelList] + RemovedLabels: ParameterLabelList | None + InvalidLabels: ParameterLabelList | None class UpdateAssociationRequest(ServiceRequest): AssociationId: AssociationId - Parameters: Optional[Parameters] - DocumentVersion: Optional[DocumentVersion] - ScheduleExpression: Optional[ScheduleExpression] - OutputLocation: Optional[InstanceAssociationOutputLocation] - Name: Optional[DocumentARN] - Targets: Optional[Targets] - AssociationName: Optional[AssociationName] - AssociationVersion: Optional[AssociationVersion] - AutomationTargetParameterName: Optional[AutomationTargetParameterName] - MaxErrors: Optional[MaxErrors] - MaxConcurrency: Optional[MaxConcurrency] - ComplianceSeverity: Optional[AssociationComplianceSeverity] - SyncCompliance: Optional[AssociationSyncCompliance] - ApplyOnlyAtCronInterval: Optional[ApplyOnlyAtCronInterval] - CalendarNames: Optional[CalendarNameOrARNList] - TargetLocations: Optional[TargetLocations] - ScheduleOffset: Optional[ScheduleOffset] - Duration: Optional[Duration] - TargetMaps: Optional[TargetMaps] - AlarmConfiguration: Optional[AlarmConfiguration] + Parameters: Parameters | None + DocumentVersion: DocumentVersion | None + ScheduleExpression: ScheduleExpression | None + OutputLocation: InstanceAssociationOutputLocation | None + Name: DocumentARN | None + Targets: Targets | None + AssociationName: AssociationName | None + AssociationVersion: AssociationVersion | None + AutomationTargetParameterName: AutomationTargetParameterName | None + MaxErrors: MaxErrors | None + MaxConcurrency: MaxConcurrency | None + ComplianceSeverity: AssociationComplianceSeverity | None + SyncCompliance: AssociationSyncCompliance | None + ApplyOnlyAtCronInterval: ApplyOnlyAtCronInterval | None + CalendarNames: CalendarNameOrARNList | None + TargetLocations: TargetLocations | None + ScheduleOffset: ScheduleOffset | None + Duration: Duration | None + TargetMaps: TargetMaps | None + AlarmConfiguration: AlarmConfiguration | None + AssociationDispatchAssumeRole: AssociationDispatchAssumeRoleArn | None class UpdateAssociationResult(TypedDict, total=False): - AssociationDescription: Optional[AssociationDescription] + AssociationDescription: AssociationDescription | None class UpdateAssociationStatusRequest(ServiceRequest): @@ -5753,7 +5766,7 @@ class UpdateAssociationStatusRequest(ServiceRequest): class UpdateAssociationStatusResult(TypedDict, total=False): - AssociationDescription: Optional[AssociationDescription] + AssociationDescription: AssociationDescription | None class UpdateDocumentDefaultVersionRequest(ServiceRequest): @@ -5762,12 +5775,12 @@ class UpdateDocumentDefaultVersionRequest(ServiceRequest): class UpdateDocumentDefaultVersionResult(TypedDict, total=False): - Description: Optional[DocumentDefaultVersionDescription] + Description: DocumentDefaultVersionDescription | None class UpdateDocumentMetadataRequest(ServiceRequest): Name: DocumentName - DocumentVersion: Optional[DocumentVersion] + DocumentVersion: DocumentVersion | None DocumentReviews: DocumentReviews @@ -5777,104 +5790,104 @@ class UpdateDocumentMetadataResponse(TypedDict, total=False): class UpdateDocumentRequest(ServiceRequest): Content: DocumentContent - Attachments: Optional[AttachmentsSourceList] + Attachments: AttachmentsSourceList | None Name: DocumentName - DisplayName: Optional[DocumentDisplayName] - VersionName: Optional[DocumentVersionName] - DocumentVersion: Optional[DocumentVersion] - DocumentFormat: Optional[DocumentFormat] - TargetType: Optional[TargetType] + DisplayName: DocumentDisplayName | None + VersionName: DocumentVersionName | None + DocumentVersion: DocumentVersion | None + DocumentFormat: DocumentFormat | None + TargetType: TargetType | None class UpdateDocumentResult(TypedDict, total=False): - DocumentDescription: Optional[DocumentDescription] + DocumentDescription: DocumentDescription | None class UpdateMaintenanceWindowRequest(ServiceRequest): WindowId: MaintenanceWindowId - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - StartDate: Optional[MaintenanceWindowStringDateTime] - EndDate: Optional[MaintenanceWindowStringDateTime] - Schedule: Optional[MaintenanceWindowSchedule] - ScheduleTimezone: Optional[MaintenanceWindowTimezone] - ScheduleOffset: Optional[MaintenanceWindowOffset] - Duration: Optional[MaintenanceWindowDurationHours] - Cutoff: Optional[MaintenanceWindowCutoff] - AllowUnassociatedTargets: Optional[MaintenanceWindowAllowUnassociatedTargets] - Enabled: Optional[MaintenanceWindowEnabled] - Replace: Optional[Boolean] + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + StartDate: MaintenanceWindowStringDateTime | None + EndDate: MaintenanceWindowStringDateTime | None + Schedule: MaintenanceWindowSchedule | None + ScheduleTimezone: MaintenanceWindowTimezone | None + ScheduleOffset: MaintenanceWindowOffset | None + Duration: MaintenanceWindowDurationHours | None + Cutoff: MaintenanceWindowCutoff | None + AllowUnassociatedTargets: MaintenanceWindowAllowUnassociatedTargets | None + Enabled: MaintenanceWindowEnabled | None + Replace: Boolean | None class UpdateMaintenanceWindowResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - StartDate: Optional[MaintenanceWindowStringDateTime] - EndDate: Optional[MaintenanceWindowStringDateTime] - Schedule: Optional[MaintenanceWindowSchedule] - ScheduleTimezone: Optional[MaintenanceWindowTimezone] - ScheduleOffset: Optional[MaintenanceWindowOffset] - Duration: Optional[MaintenanceWindowDurationHours] - Cutoff: Optional[MaintenanceWindowCutoff] - AllowUnassociatedTargets: Optional[MaintenanceWindowAllowUnassociatedTargets] - Enabled: Optional[MaintenanceWindowEnabled] + WindowId: MaintenanceWindowId | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + StartDate: MaintenanceWindowStringDateTime | None + EndDate: MaintenanceWindowStringDateTime | None + Schedule: MaintenanceWindowSchedule | None + ScheduleTimezone: MaintenanceWindowTimezone | None + ScheduleOffset: MaintenanceWindowOffset | None + Duration: MaintenanceWindowDurationHours | None + Cutoff: MaintenanceWindowCutoff | None + AllowUnassociatedTargets: MaintenanceWindowAllowUnassociatedTargets | None + Enabled: MaintenanceWindowEnabled | None class UpdateMaintenanceWindowTargetRequest(ServiceRequest): WindowId: MaintenanceWindowId WindowTargetId: MaintenanceWindowTargetId - Targets: Optional[Targets] - OwnerInformation: Optional[OwnerInformation] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - Replace: Optional[Boolean] + Targets: Targets | None + OwnerInformation: OwnerInformation | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + Replace: Boolean | None class UpdateMaintenanceWindowTargetResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowTargetId: Optional[MaintenanceWindowTargetId] - Targets: Optional[Targets] - OwnerInformation: Optional[OwnerInformation] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] + WindowId: MaintenanceWindowId | None + WindowTargetId: MaintenanceWindowTargetId | None + Targets: Targets | None + OwnerInformation: OwnerInformation | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None class UpdateMaintenanceWindowTaskRequest(ServiceRequest): WindowId: MaintenanceWindowId WindowTaskId: MaintenanceWindowTaskId - Targets: Optional[Targets] - TaskArn: Optional[MaintenanceWindowTaskArn] - ServiceRoleArn: Optional[ServiceRole] - TaskParameters: Optional[MaintenanceWindowTaskParameters] - TaskInvocationParameters: Optional[MaintenanceWindowTaskInvocationParameters] - Priority: Optional[MaintenanceWindowTaskPriority] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - LoggingInfo: Optional[LoggingInfo] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - Replace: Optional[Boolean] - CutoffBehavior: Optional[MaintenanceWindowTaskCutoffBehavior] - AlarmConfiguration: Optional[AlarmConfiguration] + Targets: Targets | None + TaskArn: MaintenanceWindowTaskArn | None + ServiceRoleArn: ServiceRole | None + TaskParameters: MaintenanceWindowTaskParameters | None + TaskInvocationParameters: MaintenanceWindowTaskInvocationParameters | None + Priority: MaintenanceWindowTaskPriority | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + LoggingInfo: LoggingInfo | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + Replace: Boolean | None + CutoffBehavior: MaintenanceWindowTaskCutoffBehavior | None + AlarmConfiguration: AlarmConfiguration | None class UpdateMaintenanceWindowTaskResult(TypedDict, total=False): - WindowId: Optional[MaintenanceWindowId] - WindowTaskId: Optional[MaintenanceWindowTaskId] - Targets: Optional[Targets] - TaskArn: Optional[MaintenanceWindowTaskArn] - ServiceRoleArn: Optional[ServiceRole] - TaskParameters: Optional[MaintenanceWindowTaskParameters] - TaskInvocationParameters: Optional[MaintenanceWindowTaskInvocationParameters] - Priority: Optional[MaintenanceWindowTaskPriority] - MaxConcurrency: Optional[MaxConcurrency] - MaxErrors: Optional[MaxErrors] - LoggingInfo: Optional[LoggingInfo] - Name: Optional[MaintenanceWindowName] - Description: Optional[MaintenanceWindowDescription] - CutoffBehavior: Optional[MaintenanceWindowTaskCutoffBehavior] - AlarmConfiguration: Optional[AlarmConfiguration] + WindowId: MaintenanceWindowId | None + WindowTaskId: MaintenanceWindowTaskId | None + Targets: Targets | None + TaskArn: MaintenanceWindowTaskArn | None + ServiceRoleArn: ServiceRole | None + TaskParameters: MaintenanceWindowTaskParameters | None + TaskInvocationParameters: MaintenanceWindowTaskInvocationParameters | None + Priority: MaintenanceWindowTaskPriority | None + MaxConcurrency: MaxConcurrency | None + MaxErrors: MaxErrors | None + LoggingInfo: LoggingInfo | None + Name: MaintenanceWindowName | None + Description: MaintenanceWindowDescription | None + CutoffBehavior: MaintenanceWindowTaskCutoffBehavior | None + AlarmConfiguration: AlarmConfiguration | None class UpdateManagedInstanceRoleRequest(ServiceRequest): @@ -5887,22 +5900,22 @@ class UpdateManagedInstanceRoleResult(TypedDict, total=False): class UpdateOpsItemRequest(ServiceRequest): - Description: Optional[OpsItemDescription] - OperationalData: Optional[OpsItemOperationalData] - OperationalDataToDelete: Optional[OpsItemOpsDataKeysList] - Notifications: Optional[OpsItemNotifications] - Priority: Optional[OpsItemPriority] - RelatedOpsItems: Optional[RelatedOpsItems] - Status: Optional[OpsItemStatus] + Description: OpsItemDescription | None + OperationalData: OpsItemOperationalData | None + OperationalDataToDelete: OpsItemOpsDataKeysList | None + Notifications: OpsItemNotifications | None + Priority: OpsItemPriority | None + RelatedOpsItems: RelatedOpsItems | None + Status: OpsItemStatus | None OpsItemId: OpsItemId - Title: Optional[OpsItemTitle] - Category: Optional[OpsItemCategory] - Severity: Optional[OpsItemSeverity] - ActualStartTime: Optional[DateTime] - ActualEndTime: Optional[DateTime] - PlannedStartTime: Optional[DateTime] - PlannedEndTime: Optional[DateTime] - OpsItemArn: Optional[OpsItemArn] + Title: OpsItemTitle | None + Category: OpsItemCategory | None + Severity: OpsItemSeverity | None + ActualStartTime: DateTime | None + ActualEndTime: DateTime | None + PlannedStartTime: DateTime | None + PlannedEndTime: DateTime | None + OpsItemArn: OpsItemArn | None class UpdateOpsItemResponse(TypedDict, total=False): @@ -5911,46 +5924,46 @@ class UpdateOpsItemResponse(TypedDict, total=False): class UpdateOpsMetadataRequest(ServiceRequest): OpsMetadataArn: OpsMetadataArn - MetadataToUpdate: Optional[MetadataMap] - KeysToDelete: Optional[MetadataKeysToDeleteList] + MetadataToUpdate: MetadataMap | None + KeysToDelete: MetadataKeysToDeleteList | None class UpdateOpsMetadataResult(TypedDict, total=False): - OpsMetadataArn: Optional[OpsMetadataArn] + OpsMetadataArn: OpsMetadataArn | None class UpdatePatchBaselineRequest(ServiceRequest): BaselineId: BaselineId - Name: Optional[BaselineName] - GlobalFilters: Optional[PatchFilterGroup] - ApprovalRules: Optional[PatchRuleGroup] - ApprovedPatches: Optional[PatchIdList] - ApprovedPatchesComplianceLevel: Optional[PatchComplianceLevel] - ApprovedPatchesEnableNonSecurity: Optional[Boolean] - RejectedPatches: Optional[PatchIdList] - RejectedPatchesAction: Optional[PatchAction] - Description: Optional[BaselineDescription] - Sources: Optional[PatchSourceList] - AvailableSecurityUpdatesComplianceStatus: Optional[PatchComplianceStatus] - Replace: Optional[Boolean] + Name: BaselineName | None + GlobalFilters: PatchFilterGroup | None + ApprovalRules: PatchRuleGroup | None + ApprovedPatches: PatchIdList | None + ApprovedPatchesComplianceLevel: PatchComplianceLevel | None + ApprovedPatchesEnableNonSecurity: Boolean | None + RejectedPatches: PatchIdList | None + RejectedPatchesAction: PatchAction | None + Description: BaselineDescription | None + Sources: PatchSourceList | None + AvailableSecurityUpdatesComplianceStatus: PatchComplianceStatus | None + Replace: Boolean | None class UpdatePatchBaselineResult(TypedDict, total=False): - BaselineId: Optional[BaselineId] - Name: Optional[BaselineName] - OperatingSystem: Optional[OperatingSystem] - GlobalFilters: Optional[PatchFilterGroup] - ApprovalRules: Optional[PatchRuleGroup] - ApprovedPatches: Optional[PatchIdList] - ApprovedPatchesComplianceLevel: Optional[PatchComplianceLevel] - ApprovedPatchesEnableNonSecurity: Optional[Boolean] - RejectedPatches: Optional[PatchIdList] - RejectedPatchesAction: Optional[PatchAction] - CreatedDate: Optional[DateTime] - ModifiedDate: Optional[DateTime] - Description: Optional[BaselineDescription] - Sources: Optional[PatchSourceList] - AvailableSecurityUpdatesComplianceStatus: Optional[PatchComplianceStatus] + BaselineId: BaselineId | None + Name: BaselineName | None + OperatingSystem: OperatingSystem | None + GlobalFilters: PatchFilterGroup | None + ApprovalRules: PatchRuleGroup | None + ApprovedPatches: PatchIdList | None + ApprovedPatchesComplianceLevel: PatchComplianceLevel | None + ApprovedPatchesEnableNonSecurity: Boolean | None + RejectedPatches: PatchIdList | None + RejectedPatchesAction: PatchAction | None + CreatedDate: DateTime | None + ModifiedDate: DateTime | None + Description: BaselineDescription | None + Sources: PatchSourceList | None + AvailableSecurityUpdatesComplianceStatus: PatchComplianceStatus | None class UpdateResourceDataSyncRequest(ServiceRequest): @@ -5973,8 +5986,8 @@ class UpdateServiceSettingResult(TypedDict, total=False): class SsmApi: - service = "ssm" - version = "2014-11-06" + service: str = "ssm" + version: str = "2014-11-06" @handler("AddTagsToResource") def add_tags_to_resource( @@ -6055,13 +6068,18 @@ def create_association( target_maps: TargetMaps | None = None, tags: TagList | None = None, alarm_configuration: AlarmConfiguration | None = None, + association_dispatch_assume_role: AssociationDispatchAssumeRoleArn | None = None, **kwargs, ) -> CreateAssociationResult: raise NotImplementedError @handler("CreateAssociationBatch") def create_association_batch( - self, context: RequestContext, entries: CreateAssociationBatchRequestEntries, **kwargs + self, + context: RequestContext, + entries: CreateAssociationBatchRequestEntries, + association_dispatch_assume_role: AssociationDispatchAssumeRoleArn | None = None, + **kwargs, ) -> CreateAssociationBatchResult: raise NotImplementedError @@ -6749,6 +6767,7 @@ def get_deployable_patch_snapshot_for_instance( instance_id: InstanceId, snapshot_id: SnapshotId, baseline_override: BaselineOverride | None = None, + use_s3_dual_stack_endpoint: Boolean | None = None, **kwargs, ) -> GetDeployablePatchSnapshotForInstanceResult: raise NotImplementedError @@ -7470,6 +7489,7 @@ def update_association( duration: Duration | None = None, target_maps: TargetMaps | None = None, alarm_configuration: AlarmConfiguration | None = None, + association_dispatch_assume_role: AssociationDispatchAssumeRoleArn | None = None, **kwargs, ) -> UpdateAssociationResult: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/stepfunctions/__init__.py b/localstack-core/localstack/aws/api/stepfunctions/__init__.py index c1dca160d5ffe..dabc70829aeaf 100644 --- a/localstack-core/localstack/aws/api/stepfunctions/__init__.py +++ b/localstack-core/localstack/aws/api/stepfunctions/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -13,6 +13,7 @@ Enabled = bool ErrorMessage = str EvaluationFailureLocation = str +ExceptionHandlerIndex = int HTTPBody = str HTTPHeaders = str HTTPMethod = str @@ -22,10 +23,14 @@ Identity = str IncludeExecutionData = bool IncludeExecutionDataGetExecutionHistory = bool +InspectionMaxConcurrency = int +InspectionToleratedFailureCount = int +InspectionToleratedFailurePercentage = float KmsDataKeyReusePeriodSeconds = int KmsKeyId = str ListExecutionsPageToken = str LongArn = str +MapIterationFailureCount = int MapRunLabel = str MaxConcurrency = int Name = str @@ -33,6 +38,8 @@ PageToken = str Publish = bool RedriveCount = int +RetrierRetryCount = int +RetryBackoffIntervalSeconds = int RevealSecrets = bool ReverseOrder = bool RevisionId = str @@ -44,6 +51,7 @@ TagKey = str TagValue = str TaskToken = str +TestStateStateName = str ToleratedFailurePercentage = float TraceHeader = str URL = str @@ -184,6 +192,12 @@ class MapRunStatus(StrEnum): ABORTED = "ABORTED" +class MockResponseValidationMode(StrEnum): + STRICT = "STRICT" + PRESENT = "PRESENT" + NONE = "NONE" + + class StateMachineStatus(StrEnum): ACTIVE = "ACTIVE" DELETING = "DELETING" @@ -342,7 +356,7 @@ class KmsInvalidStateException(ServiceException): code: str = "KmsInvalidStateException" sender_fault: bool = False status_code: int = 400 - kmsKeyState: Optional[KmsKeyState] + kmsKeyState: KmsKeyState | None class KmsThrottlingException(ServiceException): @@ -361,7 +375,7 @@ class ResourceNotFound(ServiceException): code: str = "ResourceNotFound" sender_fault: bool = False status_code: int = 400 - resourceName: Optional[Arn] + resourceName: Arn | None class ServiceQuotaExceededException(ServiceException): @@ -416,19 +430,19 @@ class TooManyTags(ServiceException): code: str = "TooManyTags" sender_fault: bool = False status_code: int = 400 - resourceName: Optional[Arn] + resourceName: Arn | None class ValidationException(ServiceException): code: str = "ValidationException" sender_fault: bool = False status_code: int = 400 - reason: Optional[ValidationExceptionReason] + reason: ValidationExceptionReason | None class ActivityFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None Timestamp = datetime @@ -440,48 +454,48 @@ class ActivityListItem(TypedDict, total=False): creationDate: Timestamp -ActivityList = List[ActivityListItem] +ActivityList = list[ActivityListItem] class ActivityScheduleFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None TimeoutInSeconds = int class HistoryEventExecutionDataDetails(TypedDict, total=False): - truncated: Optional[truncated] + truncated: truncated | None class ActivityScheduledEventDetails(TypedDict, total=False): resource: Arn - input: Optional[SensitiveData] - inputDetails: Optional[HistoryEventExecutionDataDetails] - timeoutInSeconds: Optional[TimeoutInSeconds] - heartbeatInSeconds: Optional[TimeoutInSeconds] + input: SensitiveData | None + inputDetails: HistoryEventExecutionDataDetails | None + timeoutInSeconds: TimeoutInSeconds | None + heartbeatInSeconds: TimeoutInSeconds | None class ActivityStartedEventDetails(TypedDict, total=False): - workerName: Optional[Identity] + workerName: Identity | None class ActivitySucceededEventDetails(TypedDict, total=False): - output: Optional[SensitiveData] - outputDetails: Optional[HistoryEventExecutionDataDetails] + output: SensitiveData | None + outputDetails: HistoryEventExecutionDataDetails | None class ActivityTimedOutEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None -AssignedVariables = Dict[VariableName, VariableValue] +AssignedVariables = dict[VariableName, VariableValue] class AssignedVariablesDetails(TypedDict, total=False): - truncated: Optional[truncated] + truncated: truncated | None BilledDuration = int @@ -489,41 +503,36 @@ class AssignedVariablesDetails(TypedDict, total=False): class BillingDetails(TypedDict, total=False): - billedMemoryUsedInMB: Optional[BilledMemoryUsed] - billedDurationInMilliseconds: Optional[BilledDuration] + billedMemoryUsedInMB: BilledMemoryUsed | None + billedDurationInMilliseconds: BilledDuration | None class CloudWatchEventsExecutionDataDetails(TypedDict, total=False): - included: Optional[includedDetails] + included: includedDetails | None class CloudWatchLogsLogGroup(TypedDict, total=False): - logGroupArn: Optional[Arn] + logGroupArn: Arn | None -EncryptionConfiguration = TypedDict( - "EncryptionConfiguration", - { - "kmsKeyId": Optional[KmsKeyId], - "kmsDataKeyReusePeriodSeconds": Optional[KmsDataKeyReusePeriodSeconds], - "type": EncryptionType, - }, - total=False, -) +class EncryptionConfiguration(TypedDict, total=False): + kmsKeyId: KmsKeyId | None + kmsDataKeyReusePeriodSeconds: KmsDataKeyReusePeriodSeconds | None + type: EncryptionType class Tag(TypedDict, total=False): - key: Optional[TagKey] - value: Optional[TagValue] + key: TagKey | None + value: TagValue | None -TagList = List[Tag] +TagList = list[Tag] class CreateActivityInput(ServiceRequest): name: Name - tags: Optional[TagList] - encryptionConfiguration: Optional[EncryptionConfiguration] + tags: TagList | None + encryptionConfiguration: EncryptionConfiguration | None class CreateActivityOutput(TypedDict, total=False): @@ -536,11 +545,11 @@ class RoutingConfigurationListItem(TypedDict, total=False): weight: VersionWeight -RoutingConfigurationList = List[RoutingConfigurationListItem] +RoutingConfigurationList = list[RoutingConfigurationListItem] class CreateStateMachineAliasInput(ServiceRequest): - description: Optional[AliasDescription] + description: AliasDescription | None name: CharacterRestrictedName routingConfiguration: RoutingConfigurationList @@ -551,44 +560,39 @@ class CreateStateMachineAliasOutput(TypedDict, total=False): class TracingConfiguration(TypedDict, total=False): - enabled: Optional[Enabled] + enabled: Enabled | None class LogDestination(TypedDict, total=False): - cloudWatchLogsLogGroup: Optional[CloudWatchLogsLogGroup] + cloudWatchLogsLogGroup: CloudWatchLogsLogGroup | None -LogDestinationList = List[LogDestination] +LogDestinationList = list[LogDestination] class LoggingConfiguration(TypedDict, total=False): - level: Optional[LogLevel] - includeExecutionData: Optional[IncludeExecutionData] - destinations: Optional[LogDestinationList] - - -CreateStateMachineInput = TypedDict( - "CreateStateMachineInput", - { - "name": Name, - "definition": Definition, - "roleArn": Arn, - "type": Optional[StateMachineType], - "loggingConfiguration": Optional[LoggingConfiguration], - "tags": Optional[TagList], - "tracingConfiguration": Optional[TracingConfiguration], - "publish": Optional[Publish], - "versionDescription": Optional[VersionDescription], - "encryptionConfiguration": Optional[EncryptionConfiguration], - }, - total=False, -) + level: LogLevel | None + includeExecutionData: IncludeExecutionData | None + destinations: LogDestinationList | None + + +class CreateStateMachineInput(TypedDict, total=False): + name: Name + definition: Definition + roleArn: Arn + type: StateMachineType | None + loggingConfiguration: LoggingConfiguration | None + tags: TagList | None + tracingConfiguration: TracingConfiguration | None + publish: Publish | None + versionDescription: VersionDescription | None + encryptionConfiguration: EncryptionConfiguration | None class CreateStateMachineOutput(TypedDict, total=False): stateMachineArn: Arn creationDate: Timestamp - stateMachineVersionArn: Optional[Arn] + stateMachineVersionArn: Arn | None class DeleteActivityInput(ServiceRequest): @@ -631,35 +635,35 @@ class DescribeActivityOutput(TypedDict, total=False): activityArn: Arn name: Name creationDate: Timestamp - encryptionConfiguration: Optional[EncryptionConfiguration] + encryptionConfiguration: EncryptionConfiguration | None class DescribeExecutionInput(ServiceRequest): executionArn: Arn - includedData: Optional[IncludedData] + includedData: IncludedData | None class DescribeExecutionOutput(TypedDict, total=False): executionArn: Arn stateMachineArn: Arn - name: Optional[Name] + name: Name | None status: ExecutionStatus startDate: Timestamp - stopDate: Optional[Timestamp] - input: Optional[SensitiveData] - inputDetails: Optional[CloudWatchEventsExecutionDataDetails] - output: Optional[SensitiveData] - outputDetails: Optional[CloudWatchEventsExecutionDataDetails] - traceHeader: Optional[TraceHeader] - mapRunArn: Optional[LongArn] - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] - stateMachineVersionArn: Optional[Arn] - stateMachineAliasArn: Optional[Arn] - redriveCount: Optional[RedriveCount] - redriveDate: Optional[Timestamp] - redriveStatus: Optional[ExecutionRedriveStatus] - redriveStatusReason: Optional[SensitiveData] + stopDate: Timestamp | None + input: SensitiveData | None + inputDetails: CloudWatchEventsExecutionDataDetails | None + output: SensitiveData | None + outputDetails: CloudWatchEventsExecutionDataDetails | None + traceHeader: TraceHeader | None + mapRunArn: LongArn | None + error: SensitiveError | None + cause: SensitiveCause | None + stateMachineVersionArn: Arn | None + stateMachineAliasArn: Arn | None + redriveCount: RedriveCount | None + redriveDate: Timestamp | None + redriveStatus: ExecutionRedriveStatus | None + redriveStatusReason: SensitiveData | None class DescribeMapRunInput(ServiceRequest): @@ -679,8 +683,8 @@ class MapRunExecutionCounts(TypedDict, total=False): aborted: UnsignedLong total: UnsignedLong resultsWritten: UnsignedLong - failuresNotRedrivable: Optional[LongObject] - pendingRedrive: Optional[LongObject] + failuresNotRedrivable: LongObject | None + pendingRedrive: LongObject | None class MapRunItemCounts(TypedDict, total=False): @@ -692,8 +696,8 @@ class MapRunItemCounts(TypedDict, total=False): aborted: UnsignedLong total: UnsignedLong resultsWritten: UnsignedLong - failuresNotRedrivable: Optional[LongObject] - pendingRedrive: Optional[LongObject] + failuresNotRedrivable: LongObject | None + pendingRedrive: LongObject | None ToleratedFailureCount = int @@ -704,14 +708,14 @@ class DescribeMapRunOutput(TypedDict, total=False): executionArn: Arn status: MapRunStatus startDate: Timestamp - stopDate: Optional[Timestamp] + stopDate: Timestamp | None maxConcurrency: MaxConcurrency toleratedFailurePercentage: ToleratedFailurePercentage toleratedFailureCount: ToleratedFailureCount itemCounts: MapRunItemCounts executionCounts: MapRunExecutionCounts - redriveCount: Optional[RedriveCount] - redriveDate: Optional[Timestamp] + redriveCount: RedriveCount | None + redriveDate: Timestamp | None class DescribeStateMachineAliasInput(ServiceRequest): @@ -719,21 +723,21 @@ class DescribeStateMachineAliasInput(ServiceRequest): class DescribeStateMachineAliasOutput(TypedDict, total=False): - stateMachineAliasArn: Optional[Arn] - name: Optional[Name] - description: Optional[AliasDescription] - routingConfiguration: Optional[RoutingConfigurationList] - creationDate: Optional[Timestamp] - updateDate: Optional[Timestamp] + stateMachineAliasArn: Arn | None + name: Name | None + description: AliasDescription | None + routingConfiguration: RoutingConfigurationList | None + creationDate: Timestamp | None + updateDate: Timestamp | None class DescribeStateMachineForExecutionInput(ServiceRequest): executionArn: Arn - includedData: Optional[IncludedData] + includedData: IncludedData | None -VariableNameList = List[VariableName] -VariableReferences = Dict[StateName, VariableNameList] +VariableNameList = list[VariableName] +VariableReferences = dict[StateName, VariableNameList] class DescribeStateMachineForExecutionOutput(TypedDict, total=False): @@ -742,46 +746,41 @@ class DescribeStateMachineForExecutionOutput(TypedDict, total=False): definition: Definition roleArn: Arn updateDate: Timestamp - loggingConfiguration: Optional[LoggingConfiguration] - tracingConfiguration: Optional[TracingConfiguration] - mapRunArn: Optional[LongArn] - label: Optional[MapRunLabel] - revisionId: Optional[RevisionId] - encryptionConfiguration: Optional[EncryptionConfiguration] - variableReferences: Optional[VariableReferences] + loggingConfiguration: LoggingConfiguration | None + tracingConfiguration: TracingConfiguration | None + mapRunArn: LongArn | None + label: MapRunLabel | None + revisionId: RevisionId | None + encryptionConfiguration: EncryptionConfiguration | None + variableReferences: VariableReferences | None class DescribeStateMachineInput(ServiceRequest): stateMachineArn: Arn - includedData: Optional[IncludedData] - - -DescribeStateMachineOutput = TypedDict( - "DescribeStateMachineOutput", - { - "stateMachineArn": Arn, - "name": Name, - "status": Optional[StateMachineStatus], - "definition": Definition, - "roleArn": Arn, - "type": StateMachineType, - "creationDate": Timestamp, - "loggingConfiguration": Optional[LoggingConfiguration], - "tracingConfiguration": Optional[TracingConfiguration], - "label": Optional[MapRunLabel], - "revisionId": Optional[RevisionId], - "description": Optional[VersionDescription], - "encryptionConfiguration": Optional[EncryptionConfiguration], - "variableReferences": Optional[VariableReferences], - }, - total=False, -) + includedData: IncludedData | None + + +class DescribeStateMachineOutput(TypedDict, total=False): + stateMachineArn: Arn + name: Name + status: StateMachineStatus | None + definition: Definition + roleArn: Arn + type: StateMachineType + creationDate: Timestamp + loggingConfiguration: LoggingConfiguration | None + tracingConfiguration: TracingConfiguration | None + label: MapRunLabel | None + revisionId: RevisionId | None + description: VersionDescription | None + encryptionConfiguration: EncryptionConfiguration | None + variableReferences: VariableReferences | None class EvaluationFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] - location: Optional[EvaluationFailureLocation] + error: SensitiveError | None + cause: SensitiveCause | None + location: EvaluationFailureLocation | None state: StateName @@ -789,13 +788,13 @@ class EvaluationFailedEventDetails(TypedDict, total=False): class ExecutionAbortedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class ExecutionFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class ExecutionListItem(TypedDict, total=False): @@ -804,158 +803,158 @@ class ExecutionListItem(TypedDict, total=False): name: Name status: ExecutionStatus startDate: Timestamp - stopDate: Optional[Timestamp] - mapRunArn: Optional[LongArn] - itemCount: Optional[UnsignedInteger] - stateMachineVersionArn: Optional[Arn] - stateMachineAliasArn: Optional[Arn] - redriveCount: Optional[RedriveCount] - redriveDate: Optional[Timestamp] + stopDate: Timestamp | None + mapRunArn: LongArn | None + itemCount: UnsignedInteger | None + stateMachineVersionArn: Arn | None + stateMachineAliasArn: Arn | None + redriveCount: RedriveCount | None + redriveDate: Timestamp | None -ExecutionList = List[ExecutionListItem] +ExecutionList = list[ExecutionListItem] class ExecutionRedrivenEventDetails(TypedDict, total=False): - redriveCount: Optional[RedriveCount] + redriveCount: RedriveCount | None class ExecutionStartedEventDetails(TypedDict, total=False): - input: Optional[SensitiveData] - inputDetails: Optional[HistoryEventExecutionDataDetails] - roleArn: Optional[Arn] - stateMachineAliasArn: Optional[Arn] - stateMachineVersionArn: Optional[Arn] + input: SensitiveData | None + inputDetails: HistoryEventExecutionDataDetails | None + roleArn: Arn | None + stateMachineAliasArn: Arn | None + stateMachineVersionArn: Arn | None class ExecutionSucceededEventDetails(TypedDict, total=False): - output: Optional[SensitiveData] - outputDetails: Optional[HistoryEventExecutionDataDetails] + output: SensitiveData | None + outputDetails: HistoryEventExecutionDataDetails | None class ExecutionTimedOutEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class GetActivityTaskInput(ServiceRequest): activityArn: Arn - workerName: Optional[Name] + workerName: Name | None class GetActivityTaskOutput(TypedDict, total=False): - taskToken: Optional[TaskToken] - input: Optional[SensitiveDataJobInput] + taskToken: TaskToken | None + input: SensitiveDataJobInput | None class GetExecutionHistoryInput(ServiceRequest): executionArn: Arn - maxResults: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] - nextToken: Optional[PageToken] - includeExecutionData: Optional[IncludeExecutionDataGetExecutionHistory] + maxResults: PageSize | None + reverseOrder: ReverseOrder | None + nextToken: PageToken | None + includeExecutionData: IncludeExecutionDataGetExecutionHistory | None class MapRunRedrivenEventDetails(TypedDict, total=False): - mapRunArn: Optional[LongArn] - redriveCount: Optional[RedriveCount] + mapRunArn: LongArn | None + redriveCount: RedriveCount | None class MapRunFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class MapRunStartedEventDetails(TypedDict, total=False): - mapRunArn: Optional[LongArn] + mapRunArn: LongArn | None class StateExitedEventDetails(TypedDict, total=False): name: Name - output: Optional[SensitiveData] - outputDetails: Optional[HistoryEventExecutionDataDetails] - assignedVariables: Optional[AssignedVariables] - assignedVariablesDetails: Optional[AssignedVariablesDetails] + output: SensitiveData | None + outputDetails: HistoryEventExecutionDataDetails | None + assignedVariables: AssignedVariables | None + assignedVariablesDetails: AssignedVariablesDetails | None class StateEnteredEventDetails(TypedDict, total=False): name: Name - input: Optional[SensitiveData] - inputDetails: Optional[HistoryEventExecutionDataDetails] + input: SensitiveData | None + inputDetails: HistoryEventExecutionDataDetails | None class LambdaFunctionTimedOutEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class LambdaFunctionSucceededEventDetails(TypedDict, total=False): - output: Optional[SensitiveData] - outputDetails: Optional[HistoryEventExecutionDataDetails] + output: SensitiveData | None + outputDetails: HistoryEventExecutionDataDetails | None class LambdaFunctionStartFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class TaskCredentials(TypedDict, total=False): - roleArn: Optional[LongArn] + roleArn: LongArn | None class LambdaFunctionScheduledEventDetails(TypedDict, total=False): resource: Arn - input: Optional[SensitiveData] - inputDetails: Optional[HistoryEventExecutionDataDetails] - timeoutInSeconds: Optional[TimeoutInSeconds] - taskCredentials: Optional[TaskCredentials] + input: SensitiveData | None + inputDetails: HistoryEventExecutionDataDetails | None + timeoutInSeconds: TimeoutInSeconds | None + taskCredentials: TaskCredentials | None class LambdaFunctionScheduleFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class LambdaFunctionFailedEventDetails(TypedDict, total=False): - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class MapIterationEventDetails(TypedDict, total=False): - name: Optional[Name] - index: Optional[UnsignedInteger] + name: Name | None + index: UnsignedInteger | None class MapStateStartedEventDetails(TypedDict, total=False): - length: Optional[UnsignedInteger] + length: UnsignedInteger | None class TaskTimedOutEventDetails(TypedDict, total=False): resourceType: Name resource: Name - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class TaskSucceededEventDetails(TypedDict, total=False): resourceType: Name resource: Name - output: Optional[SensitiveData] - outputDetails: Optional[HistoryEventExecutionDataDetails] + output: SensitiveData | None + outputDetails: HistoryEventExecutionDataDetails | None class TaskSubmittedEventDetails(TypedDict, total=False): resourceType: Name resource: Name - output: Optional[SensitiveData] - outputDetails: Optional[HistoryEventExecutionDataDetails] + output: SensitiveData | None + outputDetails: HistoryEventExecutionDataDetails | None class TaskSubmitFailedEventDetails(TypedDict, total=False): resourceType: Name resource: Name - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class TaskStartedEventDetails(TypedDict, total=False): @@ -966,8 +965,8 @@ class TaskStartedEventDetails(TypedDict, total=False): class TaskStartFailedEventDetails(TypedDict, total=False): resourceType: Name resource: Name - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class TaskScheduledEventDetails(TypedDict, total=False): @@ -975,132 +974,141 @@ class TaskScheduledEventDetails(TypedDict, total=False): resource: Name region: Name parameters: ConnectorParameters - timeoutInSeconds: Optional[TimeoutInSeconds] - heartbeatInSeconds: Optional[TimeoutInSeconds] - taskCredentials: Optional[TaskCredentials] + timeoutInSeconds: TimeoutInSeconds | None + heartbeatInSeconds: TimeoutInSeconds | None + taskCredentials: TaskCredentials | None class TaskFailedEventDetails(TypedDict, total=False): resourceType: Name resource: Name - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] - - -HistoryEvent = TypedDict( - "HistoryEvent", - { - "timestamp": Timestamp, - "type": HistoryEventType, - "id": EventId, - "previousEventId": Optional[EventId], - "activityFailedEventDetails": Optional[ActivityFailedEventDetails], - "activityScheduleFailedEventDetails": Optional[ActivityScheduleFailedEventDetails], - "activityScheduledEventDetails": Optional[ActivityScheduledEventDetails], - "activityStartedEventDetails": Optional[ActivityStartedEventDetails], - "activitySucceededEventDetails": Optional[ActivitySucceededEventDetails], - "activityTimedOutEventDetails": Optional[ActivityTimedOutEventDetails], - "taskFailedEventDetails": Optional[TaskFailedEventDetails], - "taskScheduledEventDetails": Optional[TaskScheduledEventDetails], - "taskStartFailedEventDetails": Optional[TaskStartFailedEventDetails], - "taskStartedEventDetails": Optional[TaskStartedEventDetails], - "taskSubmitFailedEventDetails": Optional[TaskSubmitFailedEventDetails], - "taskSubmittedEventDetails": Optional[TaskSubmittedEventDetails], - "taskSucceededEventDetails": Optional[TaskSucceededEventDetails], - "taskTimedOutEventDetails": Optional[TaskTimedOutEventDetails], - "executionFailedEventDetails": Optional[ExecutionFailedEventDetails], - "executionStartedEventDetails": Optional[ExecutionStartedEventDetails], - "executionSucceededEventDetails": Optional[ExecutionSucceededEventDetails], - "executionAbortedEventDetails": Optional[ExecutionAbortedEventDetails], - "executionTimedOutEventDetails": Optional[ExecutionTimedOutEventDetails], - "executionRedrivenEventDetails": Optional[ExecutionRedrivenEventDetails], - "mapStateStartedEventDetails": Optional[MapStateStartedEventDetails], - "mapIterationStartedEventDetails": Optional[MapIterationEventDetails], - "mapIterationSucceededEventDetails": Optional[MapIterationEventDetails], - "mapIterationFailedEventDetails": Optional[MapIterationEventDetails], - "mapIterationAbortedEventDetails": Optional[MapIterationEventDetails], - "lambdaFunctionFailedEventDetails": Optional[LambdaFunctionFailedEventDetails], - "lambdaFunctionScheduleFailedEventDetails": Optional[ - LambdaFunctionScheduleFailedEventDetails - ], - "lambdaFunctionScheduledEventDetails": Optional[LambdaFunctionScheduledEventDetails], - "lambdaFunctionStartFailedEventDetails": Optional[LambdaFunctionStartFailedEventDetails], - "lambdaFunctionSucceededEventDetails": Optional[LambdaFunctionSucceededEventDetails], - "lambdaFunctionTimedOutEventDetails": Optional[LambdaFunctionTimedOutEventDetails], - "stateEnteredEventDetails": Optional[StateEnteredEventDetails], - "stateExitedEventDetails": Optional[StateExitedEventDetails], - "mapRunStartedEventDetails": Optional[MapRunStartedEventDetails], - "mapRunFailedEventDetails": Optional[MapRunFailedEventDetails], - "mapRunRedrivenEventDetails": Optional[MapRunRedrivenEventDetails], - "evaluationFailedEventDetails": Optional[EvaluationFailedEventDetails], - }, - total=False, -) -HistoryEventList = List[HistoryEvent] + error: SensitiveError | None + cause: SensitiveCause | None + + +class HistoryEvent(TypedDict, total=False): + timestamp: Timestamp + type: HistoryEventType + id: EventId + previousEventId: EventId | None + activityFailedEventDetails: ActivityFailedEventDetails | None + activityScheduleFailedEventDetails: ActivityScheduleFailedEventDetails | None + activityScheduledEventDetails: ActivityScheduledEventDetails | None + activityStartedEventDetails: ActivityStartedEventDetails | None + activitySucceededEventDetails: ActivitySucceededEventDetails | None + activityTimedOutEventDetails: ActivityTimedOutEventDetails | None + taskFailedEventDetails: TaskFailedEventDetails | None + taskScheduledEventDetails: TaskScheduledEventDetails | None + taskStartFailedEventDetails: TaskStartFailedEventDetails | None + taskStartedEventDetails: TaskStartedEventDetails | None + taskSubmitFailedEventDetails: TaskSubmitFailedEventDetails | None + taskSubmittedEventDetails: TaskSubmittedEventDetails | None + taskSucceededEventDetails: TaskSucceededEventDetails | None + taskTimedOutEventDetails: TaskTimedOutEventDetails | None + executionFailedEventDetails: ExecutionFailedEventDetails | None + executionStartedEventDetails: ExecutionStartedEventDetails | None + executionSucceededEventDetails: ExecutionSucceededEventDetails | None + executionAbortedEventDetails: ExecutionAbortedEventDetails | None + executionTimedOutEventDetails: ExecutionTimedOutEventDetails | None + executionRedrivenEventDetails: ExecutionRedrivenEventDetails | None + mapStateStartedEventDetails: MapStateStartedEventDetails | None + mapIterationStartedEventDetails: MapIterationEventDetails | None + mapIterationSucceededEventDetails: MapIterationEventDetails | None + mapIterationFailedEventDetails: MapIterationEventDetails | None + mapIterationAbortedEventDetails: MapIterationEventDetails | None + lambdaFunctionFailedEventDetails: LambdaFunctionFailedEventDetails | None + lambdaFunctionScheduleFailedEventDetails: LambdaFunctionScheduleFailedEventDetails | None + lambdaFunctionScheduledEventDetails: LambdaFunctionScheduledEventDetails | None + lambdaFunctionStartFailedEventDetails: LambdaFunctionStartFailedEventDetails | None + lambdaFunctionSucceededEventDetails: LambdaFunctionSucceededEventDetails | None + lambdaFunctionTimedOutEventDetails: LambdaFunctionTimedOutEventDetails | None + stateEnteredEventDetails: StateEnteredEventDetails | None + stateExitedEventDetails: StateExitedEventDetails | None + mapRunStartedEventDetails: MapRunStartedEventDetails | None + mapRunFailedEventDetails: MapRunFailedEventDetails | None + mapRunRedrivenEventDetails: MapRunRedrivenEventDetails | None + evaluationFailedEventDetails: EvaluationFailedEventDetails | None + + +HistoryEventList = list[HistoryEvent] class GetExecutionHistoryOutput(TypedDict, total=False): events: HistoryEventList - nextToken: Optional[PageToken] + nextToken: PageToken | None + + +class InspectionErrorDetails(TypedDict, total=False): + catchIndex: ExceptionHandlerIndex | None + retryIndex: ExceptionHandlerIndex | None + retryBackoffIntervalSeconds: RetryBackoffIntervalSeconds | None class InspectionDataResponse(TypedDict, total=False): - protocol: Optional[HTTPProtocol] - statusCode: Optional[HTTPStatusCode] - statusMessage: Optional[HTTPStatusMessage] - headers: Optional[HTTPHeaders] - body: Optional[HTTPBody] + protocol: HTTPProtocol | None + statusCode: HTTPStatusCode | None + statusMessage: HTTPStatusMessage | None + headers: HTTPHeaders | None + body: HTTPBody | None class InspectionDataRequest(TypedDict, total=False): - protocol: Optional[HTTPProtocol] - method: Optional[HTTPMethod] - url: Optional[URL] - headers: Optional[HTTPHeaders] - body: Optional[HTTPBody] + protocol: HTTPProtocol | None + method: HTTPMethod | None + url: URL | None + headers: HTTPHeaders | None + body: HTTPBody | None class InspectionData(TypedDict, total=False): - input: Optional[SensitiveData] - afterArguments: Optional[SensitiveData] - afterInputPath: Optional[SensitiveData] - afterParameters: Optional[SensitiveData] - result: Optional[SensitiveData] - afterResultSelector: Optional[SensitiveData] - afterResultPath: Optional[SensitiveData] - request: Optional[InspectionDataRequest] - response: Optional[InspectionDataResponse] - variables: Optional[SensitiveData] + input: SensitiveData | None + afterArguments: SensitiveData | None + afterInputPath: SensitiveData | None + afterParameters: SensitiveData | None + result: SensitiveData | None + afterResultSelector: SensitiveData | None + afterResultPath: SensitiveData | None + request: InspectionDataRequest | None + response: InspectionDataResponse | None + variables: SensitiveData | None + errorDetails: InspectionErrorDetails | None + afterItemsPath: SensitiveData | None + afterItemSelector: SensitiveData | None + afterItemBatcher: SensitiveData | None + afterItemsPointer: SensitiveData | None + toleratedFailureCount: InspectionToleratedFailureCount | None + toleratedFailurePercentage: InspectionToleratedFailurePercentage | None + maxConcurrency: InspectionMaxConcurrency | None class ListActivitiesInput(ServiceRequest): - maxResults: Optional[PageSize] - nextToken: Optional[PageToken] + maxResults: PageSize | None + nextToken: PageToken | None class ListActivitiesOutput(TypedDict, total=False): activities: ActivityList - nextToken: Optional[PageToken] + nextToken: PageToken | None class ListExecutionsInput(ServiceRequest): - stateMachineArn: Optional[Arn] - statusFilter: Optional[ExecutionStatus] - maxResults: Optional[PageSize] - nextToken: Optional[ListExecutionsPageToken] - mapRunArn: Optional[LongArn] - redriveFilter: Optional[ExecutionRedriveFilter] + stateMachineArn: Arn | None + statusFilter: ExecutionStatus | None + maxResults: PageSize | None + nextToken: ListExecutionsPageToken | None + mapRunArn: LongArn | None + redriveFilter: ExecutionRedriveFilter | None class ListExecutionsOutput(TypedDict, total=False): executions: ExecutionList - nextToken: Optional[ListExecutionsPageToken] + nextToken: ListExecutionsPageToken | None class ListMapRunsInput(ServiceRequest): executionArn: Arn - maxResults: Optional[PageSize] - nextToken: Optional[PageToken] + maxResults: PageSize | None + nextToken: PageToken | None class MapRunListItem(TypedDict, total=False): @@ -1108,21 +1116,21 @@ class MapRunListItem(TypedDict, total=False): mapRunArn: LongArn stateMachineArn: Arn startDate: Timestamp - stopDate: Optional[Timestamp] + stopDate: Timestamp | None -MapRunList = List[MapRunListItem] +MapRunList = list[MapRunListItem] class ListMapRunsOutput(TypedDict, total=False): mapRuns: MapRunList - nextToken: Optional[PageToken] + nextToken: PageToken | None class ListStateMachineAliasesInput(ServiceRequest): stateMachineArn: Arn - nextToken: Optional[PageToken] - maxResults: Optional[PageSize] + nextToken: PageToken | None + maxResults: PageSize | None class StateMachineAliasListItem(TypedDict, total=False): @@ -1130,18 +1138,18 @@ class StateMachineAliasListItem(TypedDict, total=False): creationDate: Timestamp -StateMachineAliasList = List[StateMachineAliasListItem] +StateMachineAliasList = list[StateMachineAliasListItem] class ListStateMachineAliasesOutput(TypedDict, total=False): stateMachineAliases: StateMachineAliasList - nextToken: Optional[PageToken] + nextToken: PageToken | None class ListStateMachineVersionsInput(ServiceRequest): stateMachineArn: Arn - nextToken: Optional[PageToken] - maxResults: Optional[PageSize] + nextToken: PageToken | None + maxResults: PageSize | None class StateMachineVersionListItem(TypedDict, total=False): @@ -1149,35 +1157,32 @@ class StateMachineVersionListItem(TypedDict, total=False): creationDate: Timestamp -StateMachineVersionList = List[StateMachineVersionListItem] +StateMachineVersionList = list[StateMachineVersionListItem] class ListStateMachineVersionsOutput(TypedDict, total=False): stateMachineVersions: StateMachineVersionList - nextToken: Optional[PageToken] + nextToken: PageToken | None class ListStateMachinesInput(ServiceRequest): - maxResults: Optional[PageSize] - nextToken: Optional[PageToken] + maxResults: PageSize | None + nextToken: PageToken | None -StateMachineListItem = TypedDict( - "StateMachineListItem", - { - "stateMachineArn": Arn, - "name": Name, - "type": StateMachineType, - "creationDate": Timestamp, - }, - total=False, -) -StateMachineList = List[StateMachineListItem] +class StateMachineListItem(TypedDict, total=False): + stateMachineArn: Arn + name: Name + type: StateMachineType + creationDate: Timestamp + + +StateMachineList = list[StateMachineListItem] class ListStateMachinesOutput(TypedDict, total=False): stateMachines: StateMachineList - nextToken: Optional[PageToken] + nextToken: PageToken | None class ListTagsForResourceInput(ServiceRequest): @@ -1185,13 +1190,24 @@ class ListTagsForResourceInput(ServiceRequest): class ListTagsForResourceOutput(TypedDict, total=False): - tags: Optional[TagList] + tags: TagList | None + + +class MockErrorOutput(TypedDict, total=False): + error: SensitiveError | None + cause: SensitiveCause | None + + +class MockInput(TypedDict, total=False): + result: SensitiveData | None + errorOutput: MockErrorOutput | None + fieldValidationMode: MockResponseValidationMode | None class PublishStateMachineVersionInput(ServiceRequest): stateMachineArn: Arn - revisionId: Optional[RevisionId] - description: Optional[VersionDescription] + revisionId: RevisionId | None + description: VersionDescription | None class PublishStateMachineVersionOutput(TypedDict, total=False): @@ -1201,7 +1217,7 @@ class PublishStateMachineVersionOutput(TypedDict, total=False): class RedriveExecutionInput(ServiceRequest): executionArn: Arn - clientToken: Optional[ClientToken] + clientToken: ClientToken | None class RedriveExecutionOutput(TypedDict, total=False): @@ -1210,8 +1226,8 @@ class RedriveExecutionOutput(TypedDict, total=False): class SendTaskFailureInput(ServiceRequest): taskToken: TaskToken - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class SendTaskFailureOutput(TypedDict, total=False): @@ -1237,9 +1253,9 @@ class SendTaskSuccessOutput(TypedDict, total=False): class StartExecutionInput(ServiceRequest): stateMachineArn: Arn - name: Optional[Name] - input: Optional[SensitiveData] - traceHeader: Optional[TraceHeader] + name: Name | None + input: SensitiveData | None + traceHeader: TraceHeader | None class StartExecutionOutput(TypedDict, total=False): @@ -1249,40 +1265,40 @@ class StartExecutionOutput(TypedDict, total=False): class StartSyncExecutionInput(ServiceRequest): stateMachineArn: Arn - name: Optional[Name] - input: Optional[SensitiveData] - traceHeader: Optional[TraceHeader] - includedData: Optional[IncludedData] + name: Name | None + input: SensitiveData | None + traceHeader: TraceHeader | None + includedData: IncludedData | None class StartSyncExecutionOutput(TypedDict, total=False): executionArn: Arn - stateMachineArn: Optional[Arn] - name: Optional[Name] + stateMachineArn: Arn | None + name: Name | None startDate: Timestamp stopDate: Timestamp status: SyncExecutionStatus - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] - input: Optional[SensitiveData] - inputDetails: Optional[CloudWatchEventsExecutionDataDetails] - output: Optional[SensitiveData] - outputDetails: Optional[CloudWatchEventsExecutionDataDetails] - traceHeader: Optional[TraceHeader] - billingDetails: Optional[BillingDetails] + error: SensitiveError | None + cause: SensitiveCause | None + input: SensitiveData | None + inputDetails: CloudWatchEventsExecutionDataDetails | None + output: SensitiveData | None + outputDetails: CloudWatchEventsExecutionDataDetails | None + traceHeader: TraceHeader | None + billingDetails: BillingDetails | None class StopExecutionInput(ServiceRequest): executionArn: Arn - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None class StopExecutionOutput(TypedDict, total=False): stopDate: Timestamp -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceInput(ServiceRequest): @@ -1294,22 +1310,33 @@ class TagResourceOutput(TypedDict, total=False): pass +class TestStateConfiguration(TypedDict, total=False): + retrierRetryCount: RetrierRetryCount | None + errorCausedByState: TestStateStateName | None + mapIterationFailureCount: MapIterationFailureCount | None + mapItemReaderData: SensitiveData | None + + class TestStateInput(ServiceRequest): definition: Definition - roleArn: Optional[Arn] - input: Optional[SensitiveData] - inspectionLevel: Optional[InspectionLevel] - revealSecrets: Optional[RevealSecrets] - variables: Optional[SensitiveData] + roleArn: Arn | None + input: SensitiveData | None + inspectionLevel: InspectionLevel | None + revealSecrets: RevealSecrets | None + variables: SensitiveData | None + stateName: TestStateStateName | None + mock: MockInput | None + context: SensitiveData | None + stateConfiguration: TestStateConfiguration | None class TestStateOutput(TypedDict, total=False): - output: Optional[SensitiveData] - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] - inspectionData: Optional[InspectionData] - nextState: Optional[StateName] - status: Optional[TestExecutionStatus] + output: SensitiveData | None + error: SensitiveError | None + cause: SensitiveCause | None + inspectionData: InspectionData | None + nextState: StateName | None + status: TestExecutionStatus | None class UntagResourceInput(ServiceRequest): @@ -1323,9 +1350,9 @@ class UntagResourceOutput(TypedDict, total=False): class UpdateMapRunInput(ServiceRequest): mapRunArn: LongArn - maxConcurrency: Optional[MaxConcurrency] - toleratedFailurePercentage: Optional[ToleratedFailurePercentage] - toleratedFailureCount: Optional[ToleratedFailureCount] + maxConcurrency: MaxConcurrency | None + toleratedFailurePercentage: ToleratedFailurePercentage | None + toleratedFailureCount: ToleratedFailureCount | None class UpdateMapRunOutput(TypedDict, total=False): @@ -1334,8 +1361,8 @@ class UpdateMapRunOutput(TypedDict, total=False): class UpdateStateMachineAliasInput(ServiceRequest): stateMachineAliasArn: Arn - description: Optional[AliasDescription] - routingConfiguration: Optional[RoutingConfigurationList] + description: AliasDescription | None + routingConfiguration: RoutingConfigurationList | None class UpdateStateMachineAliasOutput(TypedDict, total=False): @@ -1344,50 +1371,47 @@ class UpdateStateMachineAliasOutput(TypedDict, total=False): class UpdateStateMachineInput(ServiceRequest): stateMachineArn: Arn - definition: Optional[Definition] - roleArn: Optional[Arn] - loggingConfiguration: Optional[LoggingConfiguration] - tracingConfiguration: Optional[TracingConfiguration] - publish: Optional[Publish] - versionDescription: Optional[VersionDescription] - encryptionConfiguration: Optional[EncryptionConfiguration] + definition: Definition | None + roleArn: Arn | None + loggingConfiguration: LoggingConfiguration | None + tracingConfiguration: TracingConfiguration | None + publish: Publish | None + versionDescription: VersionDescription | None + encryptionConfiguration: EncryptionConfiguration | None class UpdateStateMachineOutput(TypedDict, total=False): updateDate: Timestamp - revisionId: Optional[RevisionId] - stateMachineVersionArn: Optional[Arn] + revisionId: RevisionId | None + stateMachineVersionArn: Arn | None class ValidateStateMachineDefinitionDiagnostic(TypedDict, total=False): severity: ValidateStateMachineDefinitionSeverity code: ValidateStateMachineDefinitionCode message: ValidateStateMachineDefinitionMessage - location: Optional[ValidateStateMachineDefinitionLocation] + location: ValidateStateMachineDefinitionLocation | None -ValidateStateMachineDefinitionDiagnosticList = List[ValidateStateMachineDefinitionDiagnostic] -ValidateStateMachineDefinitionInput = TypedDict( - "ValidateStateMachineDefinitionInput", - { - "definition": Definition, - "type": Optional[StateMachineType], - "severity": Optional[ValidateStateMachineDefinitionSeverity], - "maxResults": Optional[ValidateStateMachineDefinitionMaxResult], - }, - total=False, -) +ValidateStateMachineDefinitionDiagnosticList = list[ValidateStateMachineDefinitionDiagnostic] + + +class ValidateStateMachineDefinitionInput(TypedDict, total=False): + definition: Definition + type: StateMachineType | None + severity: ValidateStateMachineDefinitionSeverity | None + maxResults: ValidateStateMachineDefinitionMaxResult | None class ValidateStateMachineDefinitionOutput(TypedDict, total=False): result: ValidateStateMachineDefinitionResultCode diagnostics: ValidateStateMachineDefinitionDiagnosticList - truncated: Optional[ValidateStateMachineDefinitionTruncated] + truncated: ValidateStateMachineDefinitionTruncated | None class StepfunctionsApi: - service = "stepfunctions" - version = "2016-11-23" + service: str = "stepfunctions" + version: str = "2016-11-23" @handler("CreateActivity") def create_activity( @@ -1667,17 +1691,9 @@ def tag_resource( ) -> TagResourceOutput: raise NotImplementedError - @handler("TestState") + @handler("TestState", expand=False) def test_state( - self, - context: RequestContext, - definition: Definition, - role_arn: Arn | None = None, - input: SensitiveData | None = None, - inspection_level: InspectionLevel | None = None, - reveal_secrets: RevealSecrets | None = None, - variables: SensitiveData | None = None, - **kwargs, + self, context: RequestContext, request: TestStateInput, **kwargs ) -> TestStateOutput: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/sts/__init__.py b/localstack-core/localstack/aws/api/sts/__init__.py index 3a5e4c337c738..99abc13d132d2 100644 --- a/localstack-core/localstack/aws/api/sts/__init__.py +++ b/localstack-core/localstack/aws/api/sts/__init__.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -22,30 +22,39 @@ durationSecondsType = int encodedMessageType = str expiredIdentityTokenMessage = str +expiredTradeInTokenExceptionMessage = str externalIdType = str federatedIdType = str idpCommunicationErrorMessage = str idpRejectedClaimMessage = str invalidAuthorizationMessage = str invalidIdentityTokenMessage = str +jwtAlgorithmType = str +jwtPayloadSizeExceededException = str malformedPolicyDocumentMessage = str nonNegativeIntegerType = int +outboundWebIdentityFederationDisabledException = str packedPolicyTooLargeMessage = str regionDisabledMessage = str roleDurationSecondsType = int roleSessionNameType = str serialNumberType = str +sessionDurationEscalationException = str sessionPolicyDocumentType = str sourceIdentityType = str tagKeyType = str tagValueType = str tokenCodeType = str tokenType = str +tradeInTokenType = str unrestrictedSessionPolicyDocumentType = str urlType = str userIdType = str userNameType = str webIdentitySubjectType = str +webIdentityTokenAudienceStringType = str +webIdentityTokenDurationSecondsType = int +webIdentityTokenType = str class ExpiredTokenException(ServiceException): @@ -54,6 +63,12 @@ class ExpiredTokenException(ServiceException): status_code: int = 400 +class ExpiredTradeInTokenException(ServiceException): + code: str = "ExpiredTradeInTokenException" + sender_fault: bool = True + status_code: int = 400 + + class IDPCommunicationErrorException(ServiceException): code: str = "IDPCommunicationError" sender_fault: bool = True @@ -78,12 +93,24 @@ class InvalidIdentityTokenException(ServiceException): status_code: int = 400 +class JWTPayloadSizeExceededException(ServiceException): + code: str = "JWTPayloadSizeExceededException" + sender_fault: bool = True + status_code: int = 400 + + class MalformedPolicyDocumentException(ServiceException): code: str = "MalformedPolicyDocument" sender_fault: bool = True status_code: int = 400 +class OutboundWebIdentityFederationDisabledException(ServiceException): + code: str = "OutboundWebIdentityFederationDisabledException" + sender_fault: bool = True + status_code: int = 403 + + class PackedPolicyTooLargeException(ServiceException): code: str = "PackedPolicyTooLarge" sender_fault: bool = True @@ -96,13 +123,19 @@ class RegionDisabledException(ServiceException): status_code: int = 403 +class SessionDurationEscalationException(ServiceException): + code: str = "SessionDurationEscalationException" + sender_fault: bool = True + status_code: int = 403 + + class ProvidedContext(TypedDict, total=False): - ProviderArn: Optional[arnType] - ContextAssertion: Optional[contextAssertionType] + ProviderArn: arnType | None + ContextAssertion: contextAssertionType | None -ProvidedContextsListType = List[ProvidedContext] -tagKeyListType = List[tagKeyType] +ProvidedContextsListType = list[ProvidedContext] +tagKeyListType = list[tagKeyType] class Tag(TypedDict, total=False): @@ -110,29 +143,29 @@ class Tag(TypedDict, total=False): Value: tagValueType -tagListType = List[Tag] +tagListType = list[Tag] class PolicyDescriptorType(TypedDict, total=False): - arn: Optional[arnType] + arn: arnType | None -policyDescriptorListType = List[PolicyDescriptorType] +policyDescriptorListType = list[PolicyDescriptorType] class AssumeRoleRequest(ServiceRequest): RoleArn: arnType RoleSessionName: roleSessionNameType - PolicyArns: Optional[policyDescriptorListType] - Policy: Optional[unrestrictedSessionPolicyDocumentType] - DurationSeconds: Optional[roleDurationSecondsType] - Tags: Optional[tagListType] - TransitiveTagKeys: Optional[tagKeyListType] - ExternalId: Optional[externalIdType] - SerialNumber: Optional[serialNumberType] - TokenCode: Optional[tokenCodeType] - SourceIdentity: Optional[sourceIdentityType] - ProvidedContexts: Optional[ProvidedContextsListType] + PolicyArns: policyDescriptorListType | None + Policy: unrestrictedSessionPolicyDocumentType | None + DurationSeconds: roleDurationSecondsType | None + Tags: tagListType | None + TransitiveTagKeys: tagKeyListType | None + ExternalId: externalIdType | None + SerialNumber: serialNumberType | None + TokenCode: tokenCodeType | None + SourceIdentity: sourceIdentityType | None + ProvidedContexts: ProvidedContextsListType | None class AssumedRoleUser(TypedDict, total=False): @@ -151,62 +184,62 @@ class Credentials(TypedDict, total=False): class AssumeRoleResponse(TypedDict, total=False): - Credentials: Optional[Credentials] - AssumedRoleUser: Optional[AssumedRoleUser] - PackedPolicySize: Optional[nonNegativeIntegerType] - SourceIdentity: Optional[sourceIdentityType] + Credentials: Credentials | None + AssumedRoleUser: AssumedRoleUser | None + PackedPolicySize: nonNegativeIntegerType | None + SourceIdentity: sourceIdentityType | None class AssumeRoleWithSAMLRequest(ServiceRequest): RoleArn: arnType PrincipalArn: arnType SAMLAssertion: SAMLAssertionType - PolicyArns: Optional[policyDescriptorListType] - Policy: Optional[sessionPolicyDocumentType] - DurationSeconds: Optional[roleDurationSecondsType] + PolicyArns: policyDescriptorListType | None + Policy: sessionPolicyDocumentType | None + DurationSeconds: roleDurationSecondsType | None class AssumeRoleWithSAMLResponse(TypedDict, total=False): - Credentials: Optional[Credentials] - AssumedRoleUser: Optional[AssumedRoleUser] - PackedPolicySize: Optional[nonNegativeIntegerType] - Subject: Optional[Subject] - SubjectType: Optional[SubjectType] - Issuer: Optional[Issuer] - Audience: Optional[Audience] - NameQualifier: Optional[NameQualifier] - SourceIdentity: Optional[sourceIdentityType] + Credentials: Credentials | None + AssumedRoleUser: AssumedRoleUser | None + PackedPolicySize: nonNegativeIntegerType | None + Subject: Subject | None + SubjectType: SubjectType | None + Issuer: Issuer | None + Audience: Audience | None + NameQualifier: NameQualifier | None + SourceIdentity: sourceIdentityType | None class AssumeRoleWithWebIdentityRequest(ServiceRequest): RoleArn: arnType RoleSessionName: roleSessionNameType WebIdentityToken: clientTokenType - ProviderId: Optional[urlType] - PolicyArns: Optional[policyDescriptorListType] - Policy: Optional[sessionPolicyDocumentType] - DurationSeconds: Optional[roleDurationSecondsType] + ProviderId: urlType | None + PolicyArns: policyDescriptorListType | None + Policy: sessionPolicyDocumentType | None + DurationSeconds: roleDurationSecondsType | None class AssumeRoleWithWebIdentityResponse(TypedDict, total=False): - Credentials: Optional[Credentials] - SubjectFromWebIdentityToken: Optional[webIdentitySubjectType] - AssumedRoleUser: Optional[AssumedRoleUser] - PackedPolicySize: Optional[nonNegativeIntegerType] - Provider: Optional[Issuer] - Audience: Optional[Audience] - SourceIdentity: Optional[sourceIdentityType] + Credentials: Credentials | None + SubjectFromWebIdentityToken: webIdentitySubjectType | None + AssumedRoleUser: AssumedRoleUser | None + PackedPolicySize: nonNegativeIntegerType | None + Provider: Issuer | None + Audience: Audience | None + SourceIdentity: sourceIdentityType | None class AssumeRootRequest(ServiceRequest): TargetPrincipal: TargetPrincipalType TaskPolicyArn: PolicyDescriptorType - DurationSeconds: Optional[RootDurationSecondsType] + DurationSeconds: RootDurationSecondsType | None class AssumeRootResponse(TypedDict, total=False): - Credentials: Optional[Credentials] - SourceIdentity: Optional[sourceIdentityType] + Credentials: Credentials | None + SourceIdentity: sourceIdentityType | None class DecodeAuthorizationMessageRequest(ServiceRequest): @@ -214,7 +247,7 @@ class DecodeAuthorizationMessageRequest(ServiceRequest): class DecodeAuthorizationMessageResponse(TypedDict, total=False): - DecodedMessage: Optional[decodedMessageType] + DecodedMessage: decodedMessageType | None class FederatedUser(TypedDict, total=False): @@ -227,7 +260,7 @@ class GetAccessKeyInfoRequest(ServiceRequest): class GetAccessKeyInfoResponse(TypedDict, total=False): - Account: Optional[accountType] + Account: accountType | None class GetCallerIdentityRequest(ServiceRequest): @@ -235,38 +268,63 @@ class GetCallerIdentityRequest(ServiceRequest): class GetCallerIdentityResponse(TypedDict, total=False): - UserId: Optional[userIdType] - Account: Optional[accountType] - Arn: Optional[arnType] + UserId: userIdType | None + Account: accountType | None + Arn: arnType | None + + +class GetDelegatedAccessTokenRequest(ServiceRequest): + TradeInToken: tradeInTokenType + + +class GetDelegatedAccessTokenResponse(TypedDict, total=False): + Credentials: Credentials | None + PackedPolicySize: nonNegativeIntegerType | None + AssumedPrincipal: arnType | None class GetFederationTokenRequest(ServiceRequest): Name: userNameType - Policy: Optional[sessionPolicyDocumentType] - PolicyArns: Optional[policyDescriptorListType] - DurationSeconds: Optional[durationSecondsType] - Tags: Optional[tagListType] + Policy: sessionPolicyDocumentType | None + PolicyArns: policyDescriptorListType | None + DurationSeconds: durationSecondsType | None + Tags: tagListType | None class GetFederationTokenResponse(TypedDict, total=False): - Credentials: Optional[Credentials] - FederatedUser: Optional[FederatedUser] - PackedPolicySize: Optional[nonNegativeIntegerType] + Credentials: Credentials | None + FederatedUser: FederatedUser | None + PackedPolicySize: nonNegativeIntegerType | None class GetSessionTokenRequest(ServiceRequest): - DurationSeconds: Optional[durationSecondsType] - SerialNumber: Optional[serialNumberType] - TokenCode: Optional[tokenCodeType] + DurationSeconds: durationSecondsType | None + SerialNumber: serialNumberType | None + TokenCode: tokenCodeType | None class GetSessionTokenResponse(TypedDict, total=False): - Credentials: Optional[Credentials] + Credentials: Credentials | None + + +webIdentityTokenAudienceListType = list[webIdentityTokenAudienceStringType] + + +class GetWebIdentityTokenRequest(ServiceRequest): + Audience: webIdentityTokenAudienceListType + DurationSeconds: webIdentityTokenDurationSecondsType | None + SigningAlgorithm: jwtAlgorithmType + Tags: tagListType | None + + +class GetWebIdentityTokenResponse(TypedDict, total=False): + WebIdentityToken: webIdentityTokenType | None + Expiration: dateType | None class StsApi: - service = "sts" - version = "2011-06-15" + service: str = "sts" + version: str = "2011-06-15" @handler("AssumeRole") def assume_role( @@ -344,6 +402,12 @@ def get_access_key_info( def get_caller_identity(self, context: RequestContext, **kwargs) -> GetCallerIdentityResponse: raise NotImplementedError + @handler("GetDelegatedAccessToken") + def get_delegated_access_token( + self, context: RequestContext, trade_in_token: tradeInTokenType, **kwargs + ) -> GetDelegatedAccessTokenResponse: + raise NotImplementedError + @handler("GetFederationToken") def get_federation_token( self, @@ -367,3 +431,15 @@ def get_session_token( **kwargs, ) -> GetSessionTokenResponse: raise NotImplementedError + + @handler("GetWebIdentityToken") + def get_web_identity_token( + self, + context: RequestContext, + audience: webIdentityTokenAudienceListType, + signing_algorithm: jwtAlgorithmType, + duration_seconds: webIdentityTokenDurationSecondsType | None = None, + tags: tagListType | None = None, + **kwargs, + ) -> GetWebIdentityTokenResponse: + raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/support/__init__.py b/localstack-core/localstack/aws/api/support/__init__.py index c1575127c69e6..7dfa181267e33 100644 --- a/localstack-core/localstack/aws/api/support/__init__.py +++ b/localstack-core/localstack/aws/api/support/__init__.py @@ -1,4 +1,4 @@ -from typing import List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -113,128 +113,127 @@ class ThrottlingException(ServiceException): class Attachment(TypedDict, total=False): - fileName: Optional[FileName] - data: Optional[Data] + fileName: FileName | None + data: Data | None -Attachments = List[Attachment] +Attachments = list[Attachment] class AddAttachmentsToSetRequest(ServiceRequest): - attachmentSetId: Optional[AttachmentSetId] + attachmentSetId: AttachmentSetId | None attachments: Attachments class AddAttachmentsToSetResponse(TypedDict, total=False): - attachmentSetId: Optional[AttachmentSetId] - expiryTime: Optional[ExpiryTime] + attachmentSetId: AttachmentSetId | None + expiryTime: ExpiryTime | None -CcEmailAddressList = List[CcEmailAddress] +CcEmailAddressList = list[CcEmailAddress] class AddCommunicationToCaseRequest(ServiceRequest): - caseId: Optional[CaseId] + caseId: CaseId | None communicationBody: CommunicationBody - ccEmailAddresses: Optional[CcEmailAddressList] - attachmentSetId: Optional[AttachmentSetId] + ccEmailAddresses: CcEmailAddressList | None + attachmentSetId: AttachmentSetId | None class AddCommunicationToCaseResponse(TypedDict, total=False): - result: Optional[Result] + result: Result | None class AttachmentDetails(TypedDict, total=False): - attachmentId: Optional[AttachmentId] - fileName: Optional[FileName] + attachmentId: AttachmentId | None + fileName: FileName | None -AttachmentSet = List[AttachmentDetails] +AttachmentSet = list[AttachmentDetails] class Communication(TypedDict, total=False): - caseId: Optional[CaseId] - body: Optional[ValidatedCommunicationBody] - submittedBy: Optional[SubmittedBy] - timeCreated: Optional[TimeCreated] - attachmentSet: Optional[AttachmentSet] + caseId: CaseId | None + body: ValidatedCommunicationBody | None + submittedBy: SubmittedBy | None + timeCreated: TimeCreated | None + attachmentSet: AttachmentSet | None -CommunicationList = List[Communication] +CommunicationList = list[Communication] class RecentCaseCommunications(TypedDict, total=False): - communications: Optional[CommunicationList] - nextToken: Optional[NextToken] + communications: CommunicationList | None + nextToken: NextToken | None class CaseDetails(TypedDict, total=False): - caseId: Optional[CaseId] - displayId: Optional[DisplayId] - subject: Optional[Subject] - status: Optional[Status] - serviceCode: Optional[ServiceCode] - categoryCode: Optional[CategoryCode] - severityCode: Optional[SeverityCode] - submittedBy: Optional[SubmittedBy] - timeCreated: Optional[TimeCreated] - recentCommunications: Optional[RecentCaseCommunications] - ccEmailAddresses: Optional[CcEmailAddressList] - language: Optional[Language] + caseId: CaseId | None + displayId: DisplayId | None + subject: Subject | None + status: Status | None + serviceCode: ServiceCode | None + categoryCode: CategoryCode | None + severityCode: SeverityCode | None + submittedBy: SubmittedBy | None + timeCreated: TimeCreated | None + recentCommunications: RecentCaseCommunications | None + ccEmailAddresses: CcEmailAddressList | None + language: Language | None -CaseIdList = List[CaseId] -CaseList = List[CaseDetails] +CaseIdList = list[CaseId] +CaseList = list[CaseDetails] class Category(TypedDict, total=False): - code: Optional[CategoryCode] - name: Optional[CategoryName] + code: CategoryCode | None + name: CategoryName | None -CategoryList = List[Category] +CategoryList = list[Category] class DateInterval(TypedDict, total=False): - startDateTime: Optional[ValidatedDateTime] - endDateTime: Optional[ValidatedDateTime] + startDateTime: ValidatedDateTime | None + endDateTime: ValidatedDateTime | None -DatesWithoutSupportList = List[DateInterval] +DatesWithoutSupportList = list[DateInterval] class SupportedHour(TypedDict, total=False): - startTime: Optional[StartTime] - endTime: Optional[EndTime] + startTime: StartTime | None + endTime: EndTime | None -SupportedHoursList = List[SupportedHour] -CommunicationTypeOptions = TypedDict( - "CommunicationTypeOptions", - { - "type": Optional[Type], - "supportedHours": Optional[SupportedHoursList], - "datesWithoutSupport": Optional[DatesWithoutSupportList], - }, - total=False, -) -CommunicationTypeOptionsList = List[CommunicationTypeOptions] +SupportedHoursList = list[SupportedHour] + + +class CommunicationTypeOptions(TypedDict, total=False): + type: Type | None + supportedHours: SupportedHoursList | None + datesWithoutSupport: DatesWithoutSupportList | None + + +CommunicationTypeOptionsList = list[CommunicationTypeOptions] class CreateCaseRequest(ServiceRequest): subject: Subject - serviceCode: Optional[ServiceCode] - severityCode: Optional[SeverityCode] - categoryCode: Optional[CategoryCode] + serviceCode: ServiceCode | None + severityCode: SeverityCode | None + categoryCode: CategoryCode | None communicationBody: CommunicationBody - ccEmailAddresses: Optional[CcEmailAddressList] - language: Optional[Language] - issueType: Optional[IssueType] - attachmentSetId: Optional[AttachmentSetId] + ccEmailAddresses: CcEmailAddressList | None + language: Language | None + issueType: IssueType | None + attachmentSetId: AttachmentSetId | None class CreateCaseResponse(TypedDict, total=False): - caseId: Optional[CaseId] + caseId: CaseId | None class DescribeAttachmentRequest(ServiceRequest): @@ -242,37 +241,37 @@ class DescribeAttachmentRequest(ServiceRequest): class DescribeAttachmentResponse(TypedDict, total=False): - attachment: Optional[Attachment] + attachment: Attachment | None class DescribeCasesRequest(ServiceRequest): - caseIdList: Optional[CaseIdList] - displayId: Optional[DisplayId] - afterTime: Optional[AfterTime] - beforeTime: Optional[BeforeTime] - includeResolvedCases: Optional[IncludeResolvedCases] - nextToken: Optional[NextToken] - maxResults: Optional[MaxResults] - language: Optional[Language] - includeCommunications: Optional[IncludeCommunications] + caseIdList: CaseIdList | None + displayId: DisplayId | None + afterTime: AfterTime | None + beforeTime: BeforeTime | None + includeResolvedCases: IncludeResolvedCases | None + nextToken: NextToken | None + maxResults: MaxResults | None + language: Language | None + includeCommunications: IncludeCommunications | None class DescribeCasesResponse(TypedDict, total=False): - cases: Optional[CaseList] - nextToken: Optional[NextToken] + cases: CaseList | None + nextToken: NextToken | None class DescribeCommunicationsRequest(ServiceRequest): caseId: CaseId - beforeTime: Optional[BeforeTime] - afterTime: Optional[AfterTime] - nextToken: Optional[NextToken] - maxResults: Optional[MaxResults] + beforeTime: BeforeTime | None + afterTime: AfterTime | None + nextToken: NextToken | None + maxResults: MaxResults | None class DescribeCommunicationsResponse(TypedDict, total=False): - communications: Optional[CommunicationList] - nextToken: Optional[NextToken] + communications: CommunicationList | None + nextToken: NextToken | None class DescribeCreateCaseOptionsRequest(ServiceRequest): @@ -283,45 +282,45 @@ class DescribeCreateCaseOptionsRequest(ServiceRequest): class DescribeCreateCaseOptionsResponse(TypedDict, total=False): - languageAvailability: Optional[ValidatedLanguageAvailability] - communicationTypes: Optional[CommunicationTypeOptionsList] + languageAvailability: ValidatedLanguageAvailability | None + communicationTypes: CommunicationTypeOptionsList | None -ServiceCodeList = List[ServiceCode] +ServiceCodeList = list[ServiceCode] class DescribeServicesRequest(ServiceRequest): - serviceCodeList: Optional[ServiceCodeList] - language: Optional[Language] + serviceCodeList: ServiceCodeList | None + language: Language | None class Service(TypedDict, total=False): - code: Optional[ServiceCode] - name: Optional[ServiceName] - categories: Optional[CategoryList] + code: ServiceCode | None + name: ServiceName | None + categories: CategoryList | None -ServiceList = List[Service] +ServiceList = list[Service] class DescribeServicesResponse(TypedDict, total=False): - services: Optional[ServiceList] + services: ServiceList | None class DescribeSeverityLevelsRequest(ServiceRequest): - language: Optional[Language] + language: Language | None class SeverityLevel(TypedDict, total=False): - code: Optional[SeverityLevelCode] - name: Optional[SeverityLevelName] + code: SeverityLevelCode | None + name: SeverityLevelName | None -SeverityLevelsList = List[SeverityLevel] +SeverityLevelsList = list[SeverityLevel] class DescribeSeverityLevelsResponse(TypedDict, total=False): - severityLevels: Optional[SeverityLevelsList] + severityLevels: SeverityLevelsList | None class DescribeSupportedLanguagesRequest(ServiceRequest): @@ -331,19 +330,19 @@ class DescribeSupportedLanguagesRequest(ServiceRequest): class SupportedLanguage(TypedDict, total=False): - code: Optional[Code] - language: Optional[Language] - display: Optional[Display] + code: Code | None + language: Language | None + display: Display | None -SupportedLanguagesList = List[SupportedLanguage] +SupportedLanguagesList = list[SupportedLanguage] class DescribeSupportedLanguagesResponse(TypedDict, total=False): - supportedLanguages: Optional[SupportedLanguagesList] + supportedLanguages: SupportedLanguagesList | None -StringList = List[String] +StringList = list[String] class DescribeTrustedAdvisorCheckRefreshStatusesRequest(ServiceRequest): @@ -359,7 +358,7 @@ class TrustedAdvisorCheckRefreshStatus(TypedDict, total=False): millisUntilNextRefreshable: Long -TrustedAdvisorCheckRefreshStatusList = List[TrustedAdvisorCheckRefreshStatus] +TrustedAdvisorCheckRefreshStatusList = list[TrustedAdvisorCheckRefreshStatus] class DescribeTrustedAdvisorCheckRefreshStatusesResponse(TypedDict, total=False): @@ -368,18 +367,18 @@ class DescribeTrustedAdvisorCheckRefreshStatusesResponse(TypedDict, total=False) class DescribeTrustedAdvisorCheckResultRequest(ServiceRequest): checkId: String - language: Optional[String] + language: String | None class TrustedAdvisorResourceDetail(TypedDict, total=False): status: String - region: Optional[String] + region: String | None resourceId: String - isSuppressed: Optional[Boolean] + isSuppressed: Boolean | None metadata: StringList -TrustedAdvisorResourceDetailList = List[TrustedAdvisorResourceDetail] +TrustedAdvisorResourceDetailList = list[TrustedAdvisorResourceDetail] class TrustedAdvisorCostOptimizingSummary(TypedDict, total=False): @@ -388,7 +387,7 @@ class TrustedAdvisorCostOptimizingSummary(TypedDict, total=False): class TrustedAdvisorCategorySpecificSummary(TypedDict, total=False): - costOptimizing: Optional[TrustedAdvisorCostOptimizingSummary] + costOptimizing: TrustedAdvisorCostOptimizingSummary | None class TrustedAdvisorResourcesSummary(TypedDict, total=False): @@ -408,7 +407,7 @@ class TrustedAdvisorCheckResult(TypedDict, total=False): class DescribeTrustedAdvisorCheckResultResponse(TypedDict, total=False): - result: Optional[TrustedAdvisorCheckResult] + result: TrustedAdvisorCheckResult | None class DescribeTrustedAdvisorCheckSummariesRequest(ServiceRequest): @@ -419,12 +418,12 @@ class TrustedAdvisorCheckSummary(TypedDict, total=False): checkId: String timestamp: String status: String - hasFlaggedResources: Optional[Boolean] + hasFlaggedResources: Boolean | None resourcesSummary: TrustedAdvisorResourcesSummary categorySpecificSummary: TrustedAdvisorCategorySpecificSummary -TrustedAdvisorCheckSummaryList = List[TrustedAdvisorCheckSummary] +TrustedAdvisorCheckSummaryList = list[TrustedAdvisorCheckSummary] class DescribeTrustedAdvisorCheckSummariesResponse(TypedDict, total=False): @@ -443,7 +442,7 @@ class TrustedAdvisorCheckDescription(TypedDict, total=False): metadata: StringList -TrustedAdvisorCheckList = List[TrustedAdvisorCheckDescription] +TrustedAdvisorCheckList = list[TrustedAdvisorCheckDescription] class DescribeTrustedAdvisorChecksResponse(TypedDict, total=False): @@ -459,17 +458,17 @@ class RefreshTrustedAdvisorCheckResponse(TypedDict, total=False): class ResolveCaseRequest(ServiceRequest): - caseId: Optional[CaseId] + caseId: CaseId | None class ResolveCaseResponse(TypedDict, total=False): - initialCaseStatus: Optional[CaseStatus] - finalCaseStatus: Optional[CaseStatus] + initialCaseStatus: CaseStatus | None + finalCaseStatus: CaseStatus | None class SupportApi: - service = "support" - version = "2013-04-15" + service: str = "support" + version: str = "2013-04-15" @handler("AddAttachmentsToSet") def add_attachments_to_set( diff --git a/localstack-core/localstack/aws/api/swf/__init__.py b/localstack-core/localstack/aws/api/swf/__init__.py index 23653779f7e9f..1c35461750559 100644 --- a/localstack-core/localstack/aws/api/swf/__init__.py +++ b/localstack-core/localstack/aws/api/swf/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -369,7 +369,7 @@ class ActivityTask(TypedDict, total=False): startedEventId: EventId workflowExecution: WorkflowExecution activityType: ActivityType - input: Optional[Data] + input: Data | None class ActivityTaskCancelRequestedEventAttributes(TypedDict, total=False): @@ -378,21 +378,21 @@ class ActivityTaskCancelRequestedEventAttributes(TypedDict, total=False): class ActivityTaskCanceledEventAttributes(TypedDict, total=False): - details: Optional[Data] + details: Data | None scheduledEventId: EventId startedEventId: EventId - latestCancelRequestedEventId: Optional[EventId] + latestCancelRequestedEventId: EventId | None class ActivityTaskCompletedEventAttributes(TypedDict, total=False): - result: Optional[Data] + result: Data | None scheduledEventId: EventId startedEventId: EventId class ActivityTaskFailedEventAttributes(TypedDict, total=False): - reason: Optional[FailureReason] - details: Optional[Data] + reason: FailureReason | None + details: Data | None scheduledEventId: EventId startedEventId: EventId @@ -404,19 +404,19 @@ class TaskList(TypedDict, total=False): class ActivityTaskScheduledEventAttributes(TypedDict, total=False): activityType: ActivityType activityId: ActivityId - input: Optional[Data] - control: Optional[Data] - scheduleToStartTimeout: Optional[DurationInSecondsOptional] - scheduleToCloseTimeout: Optional[DurationInSecondsOptional] - startToCloseTimeout: Optional[DurationInSecondsOptional] + input: Data | None + control: Data | None + scheduleToStartTimeout: DurationInSecondsOptional | None + scheduleToCloseTimeout: DurationInSecondsOptional | None + startToCloseTimeout: DurationInSecondsOptional | None taskList: TaskList - taskPriority: Optional[TaskPriority] + taskPriority: TaskPriority | None decisionTaskCompletedEventId: EventId - heartbeatTimeout: Optional[DurationInSecondsOptional] + heartbeatTimeout: DurationInSecondsOptional | None class ActivityTaskStartedEventAttributes(TypedDict, total=False): - identity: Optional[Identity] + identity: Identity | None scheduledEventId: EventId @@ -428,16 +428,16 @@ class ActivityTaskTimedOutEventAttributes(TypedDict, total=False): timeoutType: ActivityTaskTimeoutType scheduledEventId: EventId startedEventId: EventId - details: Optional[LimitedData] + details: LimitedData | None class ActivityTypeConfiguration(TypedDict, total=False): - defaultTaskStartToCloseTimeout: Optional[DurationInSecondsOptional] - defaultTaskHeartbeatTimeout: Optional[DurationInSecondsOptional] - defaultTaskList: Optional[TaskList] - defaultTaskPriority: Optional[TaskPriority] - defaultTaskScheduleToStartTimeout: Optional[DurationInSecondsOptional] - defaultTaskScheduleToCloseTimeout: Optional[DurationInSecondsOptional] + defaultTaskStartToCloseTimeout: DurationInSecondsOptional | None + defaultTaskHeartbeatTimeout: DurationInSecondsOptional | None + defaultTaskList: TaskList | None + defaultTaskPriority: TaskPriority | None + defaultTaskScheduleToStartTimeout: DurationInSecondsOptional | None + defaultTaskScheduleToCloseTimeout: DurationInSecondsOptional | None Timestamp = datetime @@ -446,9 +446,9 @@ class ActivityTypeConfiguration(TypedDict, total=False): class ActivityTypeInfo(TypedDict, total=False): activityType: ActivityType status: RegistrationStatus - description: Optional[Description] + description: Description | None creationDate: Timestamp - deprecationDate: Optional[Timestamp] + deprecationDate: Timestamp | None class ActivityTypeDetail(TypedDict, total=False): @@ -456,12 +456,12 @@ class ActivityTypeDetail(TypedDict, total=False): configuration: ActivityTypeConfiguration -ActivityTypeInfoList = List[ActivityTypeInfo] +ActivityTypeInfoList = list[ActivityTypeInfo] class ActivityTypeInfos(TypedDict, total=False): typeInfos: ActivityTypeInfoList - nextPageToken: Optional[PageToken] + nextPageToken: PageToken | None class CancelTimerDecisionAttributes(TypedDict, total=False): @@ -475,7 +475,7 @@ class CancelTimerFailedEventAttributes(TypedDict, total=False): class CancelWorkflowExecutionDecisionAttributes(TypedDict, total=False): - details: Optional[Data] + details: Data | None class CancelWorkflowExecutionFailedEventAttributes(TypedDict, total=False): @@ -491,7 +491,7 @@ class WorkflowType(TypedDict, total=False): class ChildWorkflowExecutionCanceledEventAttributes(TypedDict, total=False): workflowExecution: WorkflowExecution workflowType: WorkflowType - details: Optional[Data] + details: Data | None initiatedEventId: EventId startedEventId: EventId @@ -499,7 +499,7 @@ class ChildWorkflowExecutionCanceledEventAttributes(TypedDict, total=False): class ChildWorkflowExecutionCompletedEventAttributes(TypedDict, total=False): workflowExecution: WorkflowExecution workflowType: WorkflowType - result: Optional[Data] + result: Data | None initiatedEventId: EventId startedEventId: EventId @@ -507,8 +507,8 @@ class ChildWorkflowExecutionCompletedEventAttributes(TypedDict, total=False): class ChildWorkflowExecutionFailedEventAttributes(TypedDict, total=False): workflowExecution: WorkflowExecution workflowType: WorkflowType - reason: Optional[FailureReason] - details: Optional[Data] + reason: FailureReason | None + details: Data | None initiatedEventId: EventId startedEventId: EventId @@ -539,7 +539,7 @@ class CloseStatusFilter(TypedDict, total=False): class CompleteWorkflowExecutionDecisionAttributes(TypedDict, total=False): - result: Optional[Data] + result: Data | None class CompleteWorkflowExecutionFailedEventAttributes(TypedDict, total=False): @@ -547,19 +547,19 @@ class CompleteWorkflowExecutionFailedEventAttributes(TypedDict, total=False): decisionTaskCompletedEventId: EventId -TagList = List[Tag] +TagList = list[Tag] class ContinueAsNewWorkflowExecutionDecisionAttributes(TypedDict, total=False): - input: Optional[Data] - executionStartToCloseTimeout: Optional[DurationInSecondsOptional] - taskList: Optional[TaskList] - taskPriority: Optional[TaskPriority] - taskStartToCloseTimeout: Optional[DurationInSecondsOptional] - childPolicy: Optional[ChildPolicy] - tagList: Optional[TagList] - workflowTypeVersion: Optional[Version] - lambdaRole: Optional[Arn] + input: Data | None + executionStartToCloseTimeout: DurationInSecondsOptional | None + taskList: TaskList | None + taskPriority: TaskPriority | None + taskStartToCloseTimeout: DurationInSecondsOptional | None + childPolicy: ChildPolicy | None + tagList: TagList | None + workflowTypeVersion: Version | None + lambdaRole: Arn | None class ContinueAsNewWorkflowExecutionFailedEventAttributes(TypedDict, total=False): @@ -573,7 +573,7 @@ class TagFilter(TypedDict, total=False): class WorkflowTypeFilter(TypedDict, total=False): name: Name - version: Optional[VersionOptional] + version: VersionOptional | None class WorkflowExecutionFilter(TypedDict, total=False): @@ -582,25 +582,25 @@ class WorkflowExecutionFilter(TypedDict, total=False): class ExecutionTimeFilter(TypedDict, total=False): oldestDate: Timestamp - latestDate: Optional[Timestamp] + latestDate: Timestamp | None class CountClosedWorkflowExecutionsInput(ServiceRequest): domain: DomainName - startTimeFilter: Optional[ExecutionTimeFilter] - closeTimeFilter: Optional[ExecutionTimeFilter] - executionFilter: Optional[WorkflowExecutionFilter] - typeFilter: Optional[WorkflowTypeFilter] - tagFilter: Optional[TagFilter] - closeStatusFilter: Optional[CloseStatusFilter] + startTimeFilter: ExecutionTimeFilter | None + closeTimeFilter: ExecutionTimeFilter | None + executionFilter: WorkflowExecutionFilter | None + typeFilter: WorkflowTypeFilter | None + tagFilter: TagFilter | None + closeStatusFilter: CloseStatusFilter | None class CountOpenWorkflowExecutionsInput(ServiceRequest): domain: DomainName startTimeFilter: ExecutionTimeFilter - typeFilter: Optional[WorkflowTypeFilter] - tagFilter: Optional[TagFilter] - executionFilter: Optional[WorkflowExecutionFilter] + typeFilter: WorkflowTypeFilter | None + tagFilter: TagFilter | None + executionFilter: WorkflowExecutionFilter | None class CountPendingActivityTasksInput(ServiceRequest): @@ -616,53 +616,53 @@ class CountPendingDecisionTasksInput(ServiceRequest): class ScheduleLambdaFunctionDecisionAttributes(TypedDict, total=False): id: FunctionId name: FunctionName - control: Optional[Data] - input: Optional[FunctionInput] - startToCloseTimeout: Optional[DurationInSecondsOptional] + control: Data | None + input: FunctionInput | None + startToCloseTimeout: DurationInSecondsOptional | None class StartChildWorkflowExecutionDecisionAttributes(TypedDict, total=False): workflowType: WorkflowType workflowId: WorkflowId - control: Optional[Data] - input: Optional[Data] - executionStartToCloseTimeout: Optional[DurationInSecondsOptional] - taskList: Optional[TaskList] - taskPriority: Optional[TaskPriority] - taskStartToCloseTimeout: Optional[DurationInSecondsOptional] - childPolicy: Optional[ChildPolicy] - tagList: Optional[TagList] - lambdaRole: Optional[Arn] + control: Data | None + input: Data | None + executionStartToCloseTimeout: DurationInSecondsOptional | None + taskList: TaskList | None + taskPriority: TaskPriority | None + taskStartToCloseTimeout: DurationInSecondsOptional | None + childPolicy: ChildPolicy | None + tagList: TagList | None + lambdaRole: Arn | None class RequestCancelExternalWorkflowExecutionDecisionAttributes(TypedDict, total=False): workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] - control: Optional[Data] + runId: WorkflowRunIdOptional | None + control: Data | None class SignalExternalWorkflowExecutionDecisionAttributes(TypedDict, total=False): workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] + runId: WorkflowRunIdOptional | None signalName: SignalName - input: Optional[Data] - control: Optional[Data] + input: Data | None + control: Data | None class StartTimerDecisionAttributes(TypedDict, total=False): timerId: TimerId - control: Optional[Data] + control: Data | None startToFireTimeout: DurationInSeconds class RecordMarkerDecisionAttributes(TypedDict, total=False): markerName: MarkerName - details: Optional[Data] + details: Data | None class FailWorkflowExecutionDecisionAttributes(TypedDict, total=False): - reason: Optional[FailureReason] - details: Optional[Data] + reason: FailureReason | None + details: Data | None class RequestCancelActivityTaskDecisionAttributes(TypedDict, total=False): @@ -672,52 +672,48 @@ class RequestCancelActivityTaskDecisionAttributes(TypedDict, total=False): class ScheduleActivityTaskDecisionAttributes(TypedDict, total=False): activityType: ActivityType activityId: ActivityId - control: Optional[Data] - input: Optional[Data] - scheduleToCloseTimeout: Optional[DurationInSecondsOptional] - taskList: Optional[TaskList] - taskPriority: Optional[TaskPriority] - scheduleToStartTimeout: Optional[DurationInSecondsOptional] - startToCloseTimeout: Optional[DurationInSecondsOptional] - heartbeatTimeout: Optional[DurationInSecondsOptional] + control: Data | None + input: Data | None + scheduleToCloseTimeout: DurationInSecondsOptional | None + taskList: TaskList | None + taskPriority: TaskPriority | None + scheduleToStartTimeout: DurationInSecondsOptional | None + startToCloseTimeout: DurationInSecondsOptional | None + heartbeatTimeout: DurationInSecondsOptional | None class Decision(TypedDict, total=False): decisionType: DecisionType - scheduleActivityTaskDecisionAttributes: Optional[ScheduleActivityTaskDecisionAttributes] - requestCancelActivityTaskDecisionAttributes: Optional[ - RequestCancelActivityTaskDecisionAttributes - ] - completeWorkflowExecutionDecisionAttributes: Optional[ - CompleteWorkflowExecutionDecisionAttributes - ] - failWorkflowExecutionDecisionAttributes: Optional[FailWorkflowExecutionDecisionAttributes] - cancelWorkflowExecutionDecisionAttributes: Optional[CancelWorkflowExecutionDecisionAttributes] - continueAsNewWorkflowExecutionDecisionAttributes: Optional[ - ContinueAsNewWorkflowExecutionDecisionAttributes - ] - recordMarkerDecisionAttributes: Optional[RecordMarkerDecisionAttributes] - startTimerDecisionAttributes: Optional[StartTimerDecisionAttributes] - cancelTimerDecisionAttributes: Optional[CancelTimerDecisionAttributes] - signalExternalWorkflowExecutionDecisionAttributes: Optional[ - SignalExternalWorkflowExecutionDecisionAttributes - ] - requestCancelExternalWorkflowExecutionDecisionAttributes: Optional[ - RequestCancelExternalWorkflowExecutionDecisionAttributes - ] - startChildWorkflowExecutionDecisionAttributes: Optional[ - StartChildWorkflowExecutionDecisionAttributes - ] - scheduleLambdaFunctionDecisionAttributes: Optional[ScheduleLambdaFunctionDecisionAttributes] - - -DecisionList = List[Decision] + scheduleActivityTaskDecisionAttributes: ScheduleActivityTaskDecisionAttributes | None + requestCancelActivityTaskDecisionAttributes: RequestCancelActivityTaskDecisionAttributes | None + completeWorkflowExecutionDecisionAttributes: CompleteWorkflowExecutionDecisionAttributes | None + failWorkflowExecutionDecisionAttributes: FailWorkflowExecutionDecisionAttributes | None + cancelWorkflowExecutionDecisionAttributes: CancelWorkflowExecutionDecisionAttributes | None + continueAsNewWorkflowExecutionDecisionAttributes: ( + ContinueAsNewWorkflowExecutionDecisionAttributes | None + ) + recordMarkerDecisionAttributes: RecordMarkerDecisionAttributes | None + startTimerDecisionAttributes: StartTimerDecisionAttributes | None + cancelTimerDecisionAttributes: CancelTimerDecisionAttributes | None + signalExternalWorkflowExecutionDecisionAttributes: ( + SignalExternalWorkflowExecutionDecisionAttributes | None + ) + requestCancelExternalWorkflowExecutionDecisionAttributes: ( + RequestCancelExternalWorkflowExecutionDecisionAttributes | None + ) + startChildWorkflowExecutionDecisionAttributes: ( + StartChildWorkflowExecutionDecisionAttributes | None + ) + scheduleLambdaFunctionDecisionAttributes: ScheduleLambdaFunctionDecisionAttributes | None + + +DecisionList = list[Decision] class StartLambdaFunctionFailedEventAttributes(TypedDict, total=False): - scheduledEventId: Optional[EventId] - cause: Optional[StartLambdaFunctionFailedCause] - message: Optional[CauseMessage] + scheduledEventId: EventId | None + cause: StartLambdaFunctionFailedCause | None + message: CauseMessage | None class ScheduleLambdaFunctionFailedEventAttributes(TypedDict, total=False): @@ -730,20 +726,20 @@ class ScheduleLambdaFunctionFailedEventAttributes(TypedDict, total=False): class LambdaFunctionTimedOutEventAttributes(TypedDict, total=False): scheduledEventId: EventId startedEventId: EventId - timeoutType: Optional[LambdaFunctionTimeoutType] + timeoutType: LambdaFunctionTimeoutType | None class LambdaFunctionFailedEventAttributes(TypedDict, total=False): scheduledEventId: EventId startedEventId: EventId - reason: Optional[FailureReason] - details: Optional[Data] + reason: FailureReason | None + details: Data | None class LambdaFunctionCompletedEventAttributes(TypedDict, total=False): scheduledEventId: EventId startedEventId: EventId - result: Optional[Data] + result: Data | None class LambdaFunctionStartedEventAttributes(TypedDict, total=False): @@ -753,9 +749,9 @@ class LambdaFunctionStartedEventAttributes(TypedDict, total=False): class LambdaFunctionScheduledEventAttributes(TypedDict, total=False): id: FunctionId name: FunctionName - control: Optional[Data] - input: Optional[FunctionInput] - startToCloseTimeout: Optional[DurationInSecondsOptional] + control: Data | None + input: FunctionInput | None + startToCloseTimeout: DurationInSecondsOptional | None decisionTaskCompletedEventId: EventId @@ -765,7 +761,7 @@ class StartChildWorkflowExecutionFailedEventAttributes(TypedDict, total=False): workflowId: WorkflowId initiatedEventId: EventId decisionTaskCompletedEventId: EventId - control: Optional[Data] + control: Data | None class StartTimerFailedEventAttributes(TypedDict, total=False): @@ -789,18 +785,18 @@ class ScheduleActivityTaskFailedEventAttributes(TypedDict, total=False): class RequestCancelExternalWorkflowExecutionFailedEventAttributes(TypedDict, total=False): workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] + runId: WorkflowRunIdOptional | None cause: RequestCancelExternalWorkflowExecutionFailedCause initiatedEventId: EventId decisionTaskCompletedEventId: EventId - control: Optional[Data] + control: Data | None class RequestCancelExternalWorkflowExecutionInitiatedEventAttributes(TypedDict, total=False): workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] + runId: WorkflowRunIdOptional | None decisionTaskCompletedEventId: EventId - control: Optional[Data] + control: Data | None class ExternalWorkflowExecutionCancelRequestedEventAttributes(TypedDict, total=False): @@ -810,11 +806,11 @@ class ExternalWorkflowExecutionCancelRequestedEventAttributes(TypedDict, total=F class SignalExternalWorkflowExecutionFailedEventAttributes(TypedDict, total=False): workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] + runId: WorkflowRunIdOptional | None cause: SignalExternalWorkflowExecutionFailedCause initiatedEventId: EventId decisionTaskCompletedEventId: EventId - control: Optional[Data] + control: Data | None class ExternalWorkflowExecutionSignaledEventAttributes(TypedDict, total=False): @@ -824,26 +820,26 @@ class ExternalWorkflowExecutionSignaledEventAttributes(TypedDict, total=False): class SignalExternalWorkflowExecutionInitiatedEventAttributes(TypedDict, total=False): workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] + runId: WorkflowRunIdOptional | None signalName: SignalName - input: Optional[Data] + input: Data | None decisionTaskCompletedEventId: EventId - control: Optional[Data] + control: Data | None class StartChildWorkflowExecutionInitiatedEventAttributes(TypedDict, total=False): workflowId: WorkflowId workflowType: WorkflowType - control: Optional[Data] - input: Optional[Data] - executionStartToCloseTimeout: Optional[DurationInSecondsOptional] + control: Data | None + input: Data | None + executionStartToCloseTimeout: DurationInSecondsOptional | None taskList: TaskList - taskPriority: Optional[TaskPriority] + taskPriority: TaskPriority | None decisionTaskCompletedEventId: EventId childPolicy: ChildPolicy - taskStartToCloseTimeout: Optional[DurationInSecondsOptional] - tagList: Optional[TagList] - lambdaRole: Optional[Arn] + taskStartToCloseTimeout: DurationInSecondsOptional | None + tagList: TagList | None + lambdaRole: Arn | None class TimerCanceledEventAttributes(TypedDict, total=False): @@ -859,7 +855,7 @@ class TimerFiredEventAttributes(TypedDict, total=False): class TimerStartedEventAttributes(TypedDict, total=False): timerId: TimerId - control: Optional[Data] + control: Data | None startToFireTimeout: DurationInSeconds decisionTaskCompletedEventId: EventId @@ -872,15 +868,15 @@ class RecordMarkerFailedEventAttributes(TypedDict, total=False): class MarkerRecordedEventAttributes(TypedDict, total=False): markerName: MarkerName - details: Optional[Data] + details: Data | None decisionTaskCompletedEventId: EventId class WorkflowExecutionSignaledEventAttributes(TypedDict, total=False): signalName: SignalName - input: Optional[Data] - externalWorkflowExecution: Optional[WorkflowExecution] - externalInitiatedEventId: Optional[EventId] + input: Data | None + externalWorkflowExecution: WorkflowExecution | None + externalInitiatedEventId: EventId | None class DecisionTaskTimedOutEventAttributes(TypedDict, total=False): @@ -890,54 +886,54 @@ class DecisionTaskTimedOutEventAttributes(TypedDict, total=False): class DecisionTaskCompletedEventAttributes(TypedDict, total=False): - executionContext: Optional[Data] + executionContext: Data | None scheduledEventId: EventId startedEventId: EventId - taskList: Optional[TaskList] - taskListScheduleToStartTimeout: Optional[DurationInSecondsOptional] + taskList: TaskList | None + taskListScheduleToStartTimeout: DurationInSecondsOptional | None class DecisionTaskStartedEventAttributes(TypedDict, total=False): - identity: Optional[Identity] + identity: Identity | None scheduledEventId: EventId class DecisionTaskScheduledEventAttributes(TypedDict, total=False): taskList: TaskList - taskPriority: Optional[TaskPriority] - startToCloseTimeout: Optional[DurationInSecondsOptional] - scheduleToStartTimeout: Optional[DurationInSecondsOptional] + taskPriority: TaskPriority | None + startToCloseTimeout: DurationInSecondsOptional | None + scheduleToStartTimeout: DurationInSecondsOptional | None class WorkflowExecutionCancelRequestedEventAttributes(TypedDict, total=False): - externalWorkflowExecution: Optional[WorkflowExecution] - externalInitiatedEventId: Optional[EventId] - cause: Optional[WorkflowExecutionCancelRequestedCause] + externalWorkflowExecution: WorkflowExecution | None + externalInitiatedEventId: EventId | None + cause: WorkflowExecutionCancelRequestedCause | None class WorkflowExecutionTerminatedEventAttributes(TypedDict, total=False): - reason: Optional[TerminateReason] - details: Optional[Data] + reason: TerminateReason | None + details: Data | None childPolicy: ChildPolicy - cause: Optional[WorkflowExecutionTerminatedCause] + cause: WorkflowExecutionTerminatedCause | None class WorkflowExecutionContinuedAsNewEventAttributes(TypedDict, total=False): - input: Optional[Data] + input: Data | None decisionTaskCompletedEventId: EventId newExecutionRunId: WorkflowRunId - executionStartToCloseTimeout: Optional[DurationInSecondsOptional] + executionStartToCloseTimeout: DurationInSecondsOptional | None taskList: TaskList - taskPriority: Optional[TaskPriority] - taskStartToCloseTimeout: Optional[DurationInSecondsOptional] + taskPriority: TaskPriority | None + taskStartToCloseTimeout: DurationInSecondsOptional | None childPolicy: ChildPolicy - tagList: Optional[TagList] + tagList: TagList | None workflowType: WorkflowType - lambdaRole: Optional[Arn] + lambdaRole: Arn | None class WorkflowExecutionCanceledEventAttributes(TypedDict, total=False): - details: Optional[Data] + details: Data | None decisionTaskCompletedEventId: EventId @@ -952,134 +948,130 @@ class FailWorkflowExecutionFailedEventAttributes(TypedDict, total=False): class WorkflowExecutionFailedEventAttributes(TypedDict, total=False): - reason: Optional[FailureReason] - details: Optional[Data] + reason: FailureReason | None + details: Data | None decisionTaskCompletedEventId: EventId class WorkflowExecutionCompletedEventAttributes(TypedDict, total=False): - result: Optional[Data] + result: Data | None decisionTaskCompletedEventId: EventId class WorkflowExecutionStartedEventAttributes(TypedDict, total=False): - input: Optional[Data] - executionStartToCloseTimeout: Optional[DurationInSecondsOptional] - taskStartToCloseTimeout: Optional[DurationInSecondsOptional] + input: Data | None + executionStartToCloseTimeout: DurationInSecondsOptional | None + taskStartToCloseTimeout: DurationInSecondsOptional | None childPolicy: ChildPolicy taskList: TaskList - taskPriority: Optional[TaskPriority] + taskPriority: TaskPriority | None workflowType: WorkflowType - tagList: Optional[TagList] - continuedExecutionRunId: Optional[WorkflowRunIdOptional] - parentWorkflowExecution: Optional[WorkflowExecution] - parentInitiatedEventId: Optional[EventId] - lambdaRole: Optional[Arn] + tagList: TagList | None + continuedExecutionRunId: WorkflowRunIdOptional | None + parentWorkflowExecution: WorkflowExecution | None + parentInitiatedEventId: EventId | None + lambdaRole: Arn | None class HistoryEvent(TypedDict, total=False): eventTimestamp: Timestamp eventType: EventType eventId: EventId - workflowExecutionStartedEventAttributes: Optional[WorkflowExecutionStartedEventAttributes] - workflowExecutionCompletedEventAttributes: Optional[WorkflowExecutionCompletedEventAttributes] - completeWorkflowExecutionFailedEventAttributes: Optional[ - CompleteWorkflowExecutionFailedEventAttributes - ] - workflowExecutionFailedEventAttributes: Optional[WorkflowExecutionFailedEventAttributes] - failWorkflowExecutionFailedEventAttributes: Optional[FailWorkflowExecutionFailedEventAttributes] - workflowExecutionTimedOutEventAttributes: Optional[WorkflowExecutionTimedOutEventAttributes] - workflowExecutionCanceledEventAttributes: Optional[WorkflowExecutionCanceledEventAttributes] - cancelWorkflowExecutionFailedEventAttributes: Optional[ - CancelWorkflowExecutionFailedEventAttributes - ] - workflowExecutionContinuedAsNewEventAttributes: Optional[ - WorkflowExecutionContinuedAsNewEventAttributes - ] - continueAsNewWorkflowExecutionFailedEventAttributes: Optional[ - ContinueAsNewWorkflowExecutionFailedEventAttributes - ] - workflowExecutionTerminatedEventAttributes: Optional[WorkflowExecutionTerminatedEventAttributes] - workflowExecutionCancelRequestedEventAttributes: Optional[ - WorkflowExecutionCancelRequestedEventAttributes - ] - decisionTaskScheduledEventAttributes: Optional[DecisionTaskScheduledEventAttributes] - decisionTaskStartedEventAttributes: Optional[DecisionTaskStartedEventAttributes] - decisionTaskCompletedEventAttributes: Optional[DecisionTaskCompletedEventAttributes] - decisionTaskTimedOutEventAttributes: Optional[DecisionTaskTimedOutEventAttributes] - activityTaskScheduledEventAttributes: Optional[ActivityTaskScheduledEventAttributes] - activityTaskStartedEventAttributes: Optional[ActivityTaskStartedEventAttributes] - activityTaskCompletedEventAttributes: Optional[ActivityTaskCompletedEventAttributes] - activityTaskFailedEventAttributes: Optional[ActivityTaskFailedEventAttributes] - activityTaskTimedOutEventAttributes: Optional[ActivityTaskTimedOutEventAttributes] - activityTaskCanceledEventAttributes: Optional[ActivityTaskCanceledEventAttributes] - activityTaskCancelRequestedEventAttributes: Optional[ActivityTaskCancelRequestedEventAttributes] - workflowExecutionSignaledEventAttributes: Optional[WorkflowExecutionSignaledEventAttributes] - markerRecordedEventAttributes: Optional[MarkerRecordedEventAttributes] - recordMarkerFailedEventAttributes: Optional[RecordMarkerFailedEventAttributes] - timerStartedEventAttributes: Optional[TimerStartedEventAttributes] - timerFiredEventAttributes: Optional[TimerFiredEventAttributes] - timerCanceledEventAttributes: Optional[TimerCanceledEventAttributes] - startChildWorkflowExecutionInitiatedEventAttributes: Optional[ - StartChildWorkflowExecutionInitiatedEventAttributes - ] - childWorkflowExecutionStartedEventAttributes: Optional[ - ChildWorkflowExecutionStartedEventAttributes - ] - childWorkflowExecutionCompletedEventAttributes: Optional[ - ChildWorkflowExecutionCompletedEventAttributes - ] - childWorkflowExecutionFailedEventAttributes: Optional[ - ChildWorkflowExecutionFailedEventAttributes - ] - childWorkflowExecutionTimedOutEventAttributes: Optional[ - ChildWorkflowExecutionTimedOutEventAttributes - ] - childWorkflowExecutionCanceledEventAttributes: Optional[ - ChildWorkflowExecutionCanceledEventAttributes - ] - childWorkflowExecutionTerminatedEventAttributes: Optional[ - ChildWorkflowExecutionTerminatedEventAttributes - ] - signalExternalWorkflowExecutionInitiatedEventAttributes: Optional[ - SignalExternalWorkflowExecutionInitiatedEventAttributes - ] - externalWorkflowExecutionSignaledEventAttributes: Optional[ - ExternalWorkflowExecutionSignaledEventAttributes - ] - signalExternalWorkflowExecutionFailedEventAttributes: Optional[ - SignalExternalWorkflowExecutionFailedEventAttributes - ] - externalWorkflowExecutionCancelRequestedEventAttributes: Optional[ - ExternalWorkflowExecutionCancelRequestedEventAttributes - ] - requestCancelExternalWorkflowExecutionInitiatedEventAttributes: Optional[ - RequestCancelExternalWorkflowExecutionInitiatedEventAttributes - ] - requestCancelExternalWorkflowExecutionFailedEventAttributes: Optional[ - RequestCancelExternalWorkflowExecutionFailedEventAttributes - ] - scheduleActivityTaskFailedEventAttributes: Optional[ScheduleActivityTaskFailedEventAttributes] - requestCancelActivityTaskFailedEventAttributes: Optional[ - RequestCancelActivityTaskFailedEventAttributes - ] - startTimerFailedEventAttributes: Optional[StartTimerFailedEventAttributes] - cancelTimerFailedEventAttributes: Optional[CancelTimerFailedEventAttributes] - startChildWorkflowExecutionFailedEventAttributes: Optional[ - StartChildWorkflowExecutionFailedEventAttributes - ] - lambdaFunctionScheduledEventAttributes: Optional[LambdaFunctionScheduledEventAttributes] - lambdaFunctionStartedEventAttributes: Optional[LambdaFunctionStartedEventAttributes] - lambdaFunctionCompletedEventAttributes: Optional[LambdaFunctionCompletedEventAttributes] - lambdaFunctionFailedEventAttributes: Optional[LambdaFunctionFailedEventAttributes] - lambdaFunctionTimedOutEventAttributes: Optional[LambdaFunctionTimedOutEventAttributes] - scheduleLambdaFunctionFailedEventAttributes: Optional[ - ScheduleLambdaFunctionFailedEventAttributes - ] - startLambdaFunctionFailedEventAttributes: Optional[StartLambdaFunctionFailedEventAttributes] - - -HistoryEventList = List[HistoryEvent] + workflowExecutionStartedEventAttributes: WorkflowExecutionStartedEventAttributes | None + workflowExecutionCompletedEventAttributes: WorkflowExecutionCompletedEventAttributes | None + completeWorkflowExecutionFailedEventAttributes: ( + CompleteWorkflowExecutionFailedEventAttributes | None + ) + workflowExecutionFailedEventAttributes: WorkflowExecutionFailedEventAttributes | None + failWorkflowExecutionFailedEventAttributes: FailWorkflowExecutionFailedEventAttributes | None + workflowExecutionTimedOutEventAttributes: WorkflowExecutionTimedOutEventAttributes | None + workflowExecutionCanceledEventAttributes: WorkflowExecutionCanceledEventAttributes | None + cancelWorkflowExecutionFailedEventAttributes: ( + CancelWorkflowExecutionFailedEventAttributes | None + ) + workflowExecutionContinuedAsNewEventAttributes: ( + WorkflowExecutionContinuedAsNewEventAttributes | None + ) + continueAsNewWorkflowExecutionFailedEventAttributes: ( + ContinueAsNewWorkflowExecutionFailedEventAttributes | None + ) + workflowExecutionTerminatedEventAttributes: WorkflowExecutionTerminatedEventAttributes | None + workflowExecutionCancelRequestedEventAttributes: ( + WorkflowExecutionCancelRequestedEventAttributes | None + ) + decisionTaskScheduledEventAttributes: DecisionTaskScheduledEventAttributes | None + decisionTaskStartedEventAttributes: DecisionTaskStartedEventAttributes | None + decisionTaskCompletedEventAttributes: DecisionTaskCompletedEventAttributes | None + decisionTaskTimedOutEventAttributes: DecisionTaskTimedOutEventAttributes | None + activityTaskScheduledEventAttributes: ActivityTaskScheduledEventAttributes | None + activityTaskStartedEventAttributes: ActivityTaskStartedEventAttributes | None + activityTaskCompletedEventAttributes: ActivityTaskCompletedEventAttributes | None + activityTaskFailedEventAttributes: ActivityTaskFailedEventAttributes | None + activityTaskTimedOutEventAttributes: ActivityTaskTimedOutEventAttributes | None + activityTaskCanceledEventAttributes: ActivityTaskCanceledEventAttributes | None + activityTaskCancelRequestedEventAttributes: ActivityTaskCancelRequestedEventAttributes | None + workflowExecutionSignaledEventAttributes: WorkflowExecutionSignaledEventAttributes | None + markerRecordedEventAttributes: MarkerRecordedEventAttributes | None + recordMarkerFailedEventAttributes: RecordMarkerFailedEventAttributes | None + timerStartedEventAttributes: TimerStartedEventAttributes | None + timerFiredEventAttributes: TimerFiredEventAttributes | None + timerCanceledEventAttributes: TimerCanceledEventAttributes | None + startChildWorkflowExecutionInitiatedEventAttributes: ( + StartChildWorkflowExecutionInitiatedEventAttributes | None + ) + childWorkflowExecutionStartedEventAttributes: ( + ChildWorkflowExecutionStartedEventAttributes | None + ) + childWorkflowExecutionCompletedEventAttributes: ( + ChildWorkflowExecutionCompletedEventAttributes | None + ) + childWorkflowExecutionFailedEventAttributes: ChildWorkflowExecutionFailedEventAttributes | None + childWorkflowExecutionTimedOutEventAttributes: ( + ChildWorkflowExecutionTimedOutEventAttributes | None + ) + childWorkflowExecutionCanceledEventAttributes: ( + ChildWorkflowExecutionCanceledEventAttributes | None + ) + childWorkflowExecutionTerminatedEventAttributes: ( + ChildWorkflowExecutionTerminatedEventAttributes | None + ) + signalExternalWorkflowExecutionInitiatedEventAttributes: ( + SignalExternalWorkflowExecutionInitiatedEventAttributes | None + ) + externalWorkflowExecutionSignaledEventAttributes: ( + ExternalWorkflowExecutionSignaledEventAttributes | None + ) + signalExternalWorkflowExecutionFailedEventAttributes: ( + SignalExternalWorkflowExecutionFailedEventAttributes | None + ) + externalWorkflowExecutionCancelRequestedEventAttributes: ( + ExternalWorkflowExecutionCancelRequestedEventAttributes | None + ) + requestCancelExternalWorkflowExecutionInitiatedEventAttributes: ( + RequestCancelExternalWorkflowExecutionInitiatedEventAttributes | None + ) + requestCancelExternalWorkflowExecutionFailedEventAttributes: ( + RequestCancelExternalWorkflowExecutionFailedEventAttributes | None + ) + scheduleActivityTaskFailedEventAttributes: ScheduleActivityTaskFailedEventAttributes | None + requestCancelActivityTaskFailedEventAttributes: ( + RequestCancelActivityTaskFailedEventAttributes | None + ) + startTimerFailedEventAttributes: StartTimerFailedEventAttributes | None + cancelTimerFailedEventAttributes: CancelTimerFailedEventAttributes | None + startChildWorkflowExecutionFailedEventAttributes: ( + StartChildWorkflowExecutionFailedEventAttributes | None + ) + lambdaFunctionScheduledEventAttributes: LambdaFunctionScheduledEventAttributes | None + lambdaFunctionStartedEventAttributes: LambdaFunctionStartedEventAttributes | None + lambdaFunctionCompletedEventAttributes: LambdaFunctionCompletedEventAttributes | None + lambdaFunctionFailedEventAttributes: LambdaFunctionFailedEventAttributes | None + lambdaFunctionTimedOutEventAttributes: LambdaFunctionTimedOutEventAttributes | None + scheduleLambdaFunctionFailedEventAttributes: ScheduleLambdaFunctionFailedEventAttributes | None + startLambdaFunctionFailedEventAttributes: StartLambdaFunctionFailedEventAttributes | None + + +HistoryEventList = list[HistoryEvent] class DecisionTask(TypedDict, total=False): @@ -1088,8 +1080,8 @@ class DecisionTask(TypedDict, total=False): workflowExecution: WorkflowExecution workflowType: WorkflowType events: HistoryEventList - nextPageToken: Optional[PageToken] - previousStartedEventId: Optional[EventId] + nextPageToken: PageToken | None + previousStartedEventId: EventId | None class DeleteActivityTypeInput(ServiceRequest): @@ -1142,8 +1134,8 @@ class DomainConfiguration(TypedDict, total=False): class DomainInfo(TypedDict, total=False): name: DomainName status: RegistrationStatus - description: Optional[Description] - arn: Optional[Arn] + description: Description | None + arn: Arn | None class DomainDetail(TypedDict, total=False): @@ -1151,65 +1143,65 @@ class DomainDetail(TypedDict, total=False): configuration: DomainConfiguration -DomainInfoList = List[DomainInfo] +DomainInfoList = list[DomainInfo] class DomainInfos(TypedDict, total=False): domainInfos: DomainInfoList - nextPageToken: Optional[PageToken] + nextPageToken: PageToken | None class GetWorkflowExecutionHistoryInput(ServiceRequest): domain: DomainName execution: WorkflowExecution - nextPageToken: Optional[PageToken] - maximumPageSize: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] + nextPageToken: PageToken | None + maximumPageSize: PageSize | None + reverseOrder: ReverseOrder | None class History(TypedDict, total=False): events: HistoryEventList - nextPageToken: Optional[PageToken] + nextPageToken: PageToken | None class ListActivityTypesInput(ServiceRequest): domain: DomainName - name: Optional[Name] + name: Name | None registrationStatus: RegistrationStatus - nextPageToken: Optional[PageToken] - maximumPageSize: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] + nextPageToken: PageToken | None + maximumPageSize: PageSize | None + reverseOrder: ReverseOrder | None class ListClosedWorkflowExecutionsInput(ServiceRequest): domain: DomainName - startTimeFilter: Optional[ExecutionTimeFilter] - closeTimeFilter: Optional[ExecutionTimeFilter] - executionFilter: Optional[WorkflowExecutionFilter] - closeStatusFilter: Optional[CloseStatusFilter] - typeFilter: Optional[WorkflowTypeFilter] - tagFilter: Optional[TagFilter] - nextPageToken: Optional[PageToken] - maximumPageSize: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] + startTimeFilter: ExecutionTimeFilter | None + closeTimeFilter: ExecutionTimeFilter | None + executionFilter: WorkflowExecutionFilter | None + closeStatusFilter: CloseStatusFilter | None + typeFilter: WorkflowTypeFilter | None + tagFilter: TagFilter | None + nextPageToken: PageToken | None + maximumPageSize: PageSize | None + reverseOrder: ReverseOrder | None class ListDomainsInput(ServiceRequest): - nextPageToken: Optional[PageToken] + nextPageToken: PageToken | None registrationStatus: RegistrationStatus - maximumPageSize: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] + maximumPageSize: PageSize | None + reverseOrder: ReverseOrder | None class ListOpenWorkflowExecutionsInput(ServiceRequest): domain: DomainName startTimeFilter: ExecutionTimeFilter - typeFilter: Optional[WorkflowTypeFilter] - tagFilter: Optional[TagFilter] - nextPageToken: Optional[PageToken] - maximumPageSize: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] - executionFilter: Optional[WorkflowExecutionFilter] + typeFilter: WorkflowTypeFilter | None + tagFilter: TagFilter | None + nextPageToken: PageToken | None + maximumPageSize: PageSize | None + reverseOrder: ReverseOrder | None + executionFilter: WorkflowExecutionFilter | None class ListTagsForResourceInput(ServiceRequest): @@ -1218,141 +1210,141 @@ class ListTagsForResourceInput(ServiceRequest): class ResourceTag(TypedDict, total=False): key: ResourceTagKey - value: Optional[ResourceTagValue] + value: ResourceTagValue | None -ResourceTagList = List[ResourceTag] +ResourceTagList = list[ResourceTag] class ListTagsForResourceOutput(TypedDict, total=False): - tags: Optional[ResourceTagList] + tags: ResourceTagList | None class ListWorkflowTypesInput(ServiceRequest): domain: DomainName - name: Optional[Name] + name: Name | None registrationStatus: RegistrationStatus - nextPageToken: Optional[PageToken] - maximumPageSize: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] + nextPageToken: PageToken | None + maximumPageSize: PageSize | None + reverseOrder: ReverseOrder | None class PendingTaskCount(TypedDict, total=False): count: Count - truncated: Optional[Truncated] + truncated: Truncated | None class PollForActivityTaskInput(ServiceRequest): domain: DomainName taskList: TaskList - identity: Optional[Identity] + identity: Identity | None class PollForDecisionTaskInput(ServiceRequest): domain: DomainName taskList: TaskList - identity: Optional[Identity] - nextPageToken: Optional[PageToken] - maximumPageSize: Optional[PageSize] - reverseOrder: Optional[ReverseOrder] - startAtPreviousStartedEvent: Optional[StartAtPreviousStartedEvent] + identity: Identity | None + nextPageToken: PageToken | None + maximumPageSize: PageSize | None + reverseOrder: ReverseOrder | None + startAtPreviousStartedEvent: StartAtPreviousStartedEvent | None class RecordActivityTaskHeartbeatInput(ServiceRequest): taskToken: TaskToken - details: Optional[LimitedData] + details: LimitedData | None class RegisterActivityTypeInput(ServiceRequest): domain: DomainName name: Name version: Version - description: Optional[Description] - defaultTaskStartToCloseTimeout: Optional[DurationInSecondsOptional] - defaultTaskHeartbeatTimeout: Optional[DurationInSecondsOptional] - defaultTaskList: Optional[TaskList] - defaultTaskPriority: Optional[TaskPriority] - defaultTaskScheduleToStartTimeout: Optional[DurationInSecondsOptional] - defaultTaskScheduleToCloseTimeout: Optional[DurationInSecondsOptional] + description: Description | None + defaultTaskStartToCloseTimeout: DurationInSecondsOptional | None + defaultTaskHeartbeatTimeout: DurationInSecondsOptional | None + defaultTaskList: TaskList | None + defaultTaskPriority: TaskPriority | None + defaultTaskScheduleToStartTimeout: DurationInSecondsOptional | None + defaultTaskScheduleToCloseTimeout: DurationInSecondsOptional | None class RegisterDomainInput(ServiceRequest): name: DomainName - description: Optional[Description] + description: Description | None workflowExecutionRetentionPeriodInDays: DurationInDays - tags: Optional[ResourceTagList] + tags: ResourceTagList | None class RegisterWorkflowTypeInput(ServiceRequest): domain: DomainName name: Name version: Version - description: Optional[Description] - defaultTaskStartToCloseTimeout: Optional[DurationInSecondsOptional] - defaultExecutionStartToCloseTimeout: Optional[DurationInSecondsOptional] - defaultTaskList: Optional[TaskList] - defaultTaskPriority: Optional[TaskPriority] - defaultChildPolicy: Optional[ChildPolicy] - defaultLambdaRole: Optional[Arn] + description: Description | None + defaultTaskStartToCloseTimeout: DurationInSecondsOptional | None + defaultExecutionStartToCloseTimeout: DurationInSecondsOptional | None + defaultTaskList: TaskList | None + defaultTaskPriority: TaskPriority | None + defaultChildPolicy: ChildPolicy | None + defaultLambdaRole: Arn | None class RequestCancelWorkflowExecutionInput(ServiceRequest): domain: DomainName workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] + runId: WorkflowRunIdOptional | None -ResourceTagKeyList = List[ResourceTagKey] +ResourceTagKeyList = list[ResourceTagKey] class RespondActivityTaskCanceledInput(ServiceRequest): taskToken: TaskToken - details: Optional[Data] + details: Data | None class RespondActivityTaskCompletedInput(ServiceRequest): taskToken: TaskToken - result: Optional[Data] + result: Data | None class RespondActivityTaskFailedInput(ServiceRequest): taskToken: TaskToken - reason: Optional[FailureReason] - details: Optional[Data] + reason: FailureReason | None + details: Data | None class RespondDecisionTaskCompletedInput(ServiceRequest): taskToken: TaskToken - decisions: Optional[DecisionList] - executionContext: Optional[Data] - taskList: Optional[TaskList] - taskListScheduleToStartTimeout: Optional[DurationInSecondsOptional] + decisions: DecisionList | None + executionContext: Data | None + taskList: TaskList | None + taskListScheduleToStartTimeout: DurationInSecondsOptional | None class Run(TypedDict, total=False): - runId: Optional[WorkflowRunId] + runId: WorkflowRunId | None class SignalWorkflowExecutionInput(ServiceRequest): domain: DomainName workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] + runId: WorkflowRunIdOptional | None signalName: SignalName - input: Optional[Data] + input: Data | None class StartWorkflowExecutionInput(ServiceRequest): domain: DomainName workflowId: WorkflowId workflowType: WorkflowType - taskList: Optional[TaskList] - taskPriority: Optional[TaskPriority] - input: Optional[Data] - executionStartToCloseTimeout: Optional[DurationInSecondsOptional] - tagList: Optional[TagList] - taskStartToCloseTimeout: Optional[DurationInSecondsOptional] - childPolicy: Optional[ChildPolicy] - lambdaRole: Optional[Arn] + taskList: TaskList | None + taskPriority: TaskPriority | None + input: Data | None + executionStartToCloseTimeout: DurationInSecondsOptional | None + tagList: TagList | None + taskStartToCloseTimeout: DurationInSecondsOptional | None + childPolicy: ChildPolicy | None + lambdaRole: Arn | None class TagResourceInput(ServiceRequest): @@ -1363,10 +1355,10 @@ class TagResourceInput(ServiceRequest): class TerminateWorkflowExecutionInput(ServiceRequest): domain: DomainName workflowId: WorkflowId - runId: Optional[WorkflowRunIdOptional] - reason: Optional[TerminateReason] - details: Optional[Data] - childPolicy: Optional[ChildPolicy] + runId: WorkflowRunIdOptional | None + reason: TerminateReason | None + details: Data | None + childPolicy: ChildPolicy | None class UndeprecateActivityTypeInput(ServiceRequest): @@ -1392,14 +1384,14 @@ class WorkflowExecutionConfiguration(TypedDict, total=False): taskStartToCloseTimeout: DurationInSeconds executionStartToCloseTimeout: DurationInSeconds taskList: TaskList - taskPriority: Optional[TaskPriority] + taskPriority: TaskPriority | None childPolicy: ChildPolicy - lambdaRole: Optional[Arn] + lambdaRole: Arn | None class WorkflowExecutionCount(TypedDict, total=False): count: Count - truncated: Optional[Truncated] + truncated: Truncated | None class WorkflowExecutionOpenCounts(TypedDict, total=False): @@ -1407,52 +1399,52 @@ class WorkflowExecutionOpenCounts(TypedDict, total=False): openDecisionTasks: OpenDecisionTasksCount openTimers: Count openChildWorkflowExecutions: Count - openLambdaFunctions: Optional[Count] + openLambdaFunctions: Count | None class WorkflowExecutionInfo(TypedDict, total=False): execution: WorkflowExecution workflowType: WorkflowType startTimestamp: Timestamp - closeTimestamp: Optional[Timestamp] + closeTimestamp: Timestamp | None executionStatus: ExecutionStatus - closeStatus: Optional[CloseStatus] - parent: Optional[WorkflowExecution] - tagList: Optional[TagList] - cancelRequested: Optional[Canceled] + closeStatus: CloseStatus | None + parent: WorkflowExecution | None + tagList: TagList | None + cancelRequested: Canceled | None class WorkflowExecutionDetail(TypedDict, total=False): executionInfo: WorkflowExecutionInfo executionConfiguration: WorkflowExecutionConfiguration openCounts: WorkflowExecutionOpenCounts - latestActivityTaskTimestamp: Optional[Timestamp] - latestExecutionContext: Optional[Data] + latestActivityTaskTimestamp: Timestamp | None + latestExecutionContext: Data | None -WorkflowExecutionInfoList = List[WorkflowExecutionInfo] +WorkflowExecutionInfoList = list[WorkflowExecutionInfo] class WorkflowExecutionInfos(TypedDict, total=False): executionInfos: WorkflowExecutionInfoList - nextPageToken: Optional[PageToken] + nextPageToken: PageToken | None class WorkflowTypeConfiguration(TypedDict, total=False): - defaultTaskStartToCloseTimeout: Optional[DurationInSecondsOptional] - defaultExecutionStartToCloseTimeout: Optional[DurationInSecondsOptional] - defaultTaskList: Optional[TaskList] - defaultTaskPriority: Optional[TaskPriority] - defaultChildPolicy: Optional[ChildPolicy] - defaultLambdaRole: Optional[Arn] + defaultTaskStartToCloseTimeout: DurationInSecondsOptional | None + defaultExecutionStartToCloseTimeout: DurationInSecondsOptional | None + defaultTaskList: TaskList | None + defaultTaskPriority: TaskPriority | None + defaultChildPolicy: ChildPolicy | None + defaultLambdaRole: Arn | None class WorkflowTypeInfo(TypedDict, total=False): workflowType: WorkflowType status: RegistrationStatus - description: Optional[Description] + description: Description | None creationDate: Timestamp - deprecationDate: Optional[Timestamp] + deprecationDate: Timestamp | None class WorkflowTypeDetail(TypedDict, total=False): @@ -1460,17 +1452,17 @@ class WorkflowTypeDetail(TypedDict, total=False): configuration: WorkflowTypeConfiguration -WorkflowTypeInfoList = List[WorkflowTypeInfo] +WorkflowTypeInfoList = list[WorkflowTypeInfo] class WorkflowTypeInfos(TypedDict, total=False): typeInfos: WorkflowTypeInfoList - nextPageToken: Optional[PageToken] + nextPageToken: PageToken | None class SwfApi: - service = "swf" - version = "2012-01-25" + service: str = "swf" + version: str = "2012-01-25" @handler("CountClosedWorkflowExecutions") def count_closed_workflow_executions( diff --git a/localstack-core/localstack/aws/api/transcribe/__init__.py b/localstack-core/localstack/aws/api/transcribe/__init__.py index 112611949bdcc..00923bfceb551 100644 --- a/localstack-core/localstack/aws/api/transcribe/__init__.py +++ b/localstack-core/localstack/aws/api/transcribe/__init__.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import Dict, List, Optional, TypedDict +from typing import TypedDict from localstack.aws.api import RequestContext, ServiceException, ServiceRequest, handler @@ -254,6 +254,12 @@ class PiiEntityType(StrEnum): ALL = "ALL" +class Pronouns(StrEnum): + HE_HIM = "HE_HIM" + SHE_HER = "SHE_HER" + THEY_THEM = "THEY_THEM" + + class RedactionOutput(StrEnum): redacted = "redacted" redacted_and_unredacted = "redacted_and_unredacted" @@ -345,10 +351,10 @@ class NotFoundException(ServiceException): class AbsoluteTimeRange(TypedDict, total=False): - StartTime: Optional[TimestampMilliseconds] - EndTime: Optional[TimestampMilliseconds] - First: Optional[TimestampMilliseconds] - Last: Optional[TimestampMilliseconds] + StartTime: TimestampMilliseconds | None + EndTime: TimestampMilliseconds | None + First: TimestampMilliseconds | None + Last: TimestampMilliseconds | None class Tag(TypedDict, total=False): @@ -356,15 +362,15 @@ class Tag(TypedDict, total=False): Value: TagValue -TagList = List[Tag] +TagList = list[Tag] class ChannelDefinition(TypedDict, total=False): - ChannelId: Optional[ChannelId] - ParticipantRole: Optional[ParticipantRole] + ChannelId: ChannelId | None + ParticipantRole: ParticipantRole | None -ChannelDefinitions = List[ChannelDefinition] +ChannelDefinitions = list[ChannelDefinition] class Summarization(TypedDict, total=False): @@ -372,178 +378,178 @@ class Summarization(TypedDict, total=False): class LanguageIdSettings(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - VocabularyFilterName: Optional[VocabularyFilterName] - LanguageModelName: Optional[ModelName] + VocabularyName: VocabularyName | None + VocabularyFilterName: VocabularyFilterName | None + LanguageModelName: ModelName | None -LanguageIdSettingsMap = Dict[LanguageCode, LanguageIdSettings] -LanguageOptions = List[LanguageCode] -PiiEntityTypes = List[PiiEntityType] +LanguageIdSettingsMap = dict[LanguageCode, LanguageIdSettings] +LanguageOptions = list[LanguageCode] +PiiEntityTypes = list[PiiEntityType] class ContentRedaction(TypedDict, total=False): RedactionType: RedactionType RedactionOutput: RedactionOutput - PiiEntityTypes: Optional[PiiEntityTypes] + PiiEntityTypes: PiiEntityTypes | None class CallAnalyticsJobSettings(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - VocabularyFilterName: Optional[VocabularyFilterName] - VocabularyFilterMethod: Optional[VocabularyFilterMethod] - LanguageModelName: Optional[ModelName] - ContentRedaction: Optional[ContentRedaction] - LanguageOptions: Optional[LanguageOptions] - LanguageIdSettings: Optional[LanguageIdSettingsMap] - Summarization: Optional[Summarization] + VocabularyName: VocabularyName | None + VocabularyFilterName: VocabularyFilterName | None + VocabularyFilterMethod: VocabularyFilterMethod | None + LanguageModelName: ModelName | None + ContentRedaction: ContentRedaction | None + LanguageOptions: LanguageOptions | None + LanguageIdSettings: LanguageIdSettingsMap | None + Summarization: Summarization | None DateTime = datetime class Transcript(TypedDict, total=False): - TranscriptFileUri: Optional[Uri] - RedactedTranscriptFileUri: Optional[Uri] + TranscriptFileUri: Uri | None + RedactedTranscriptFileUri: Uri | None class Media(TypedDict, total=False): - MediaFileUri: Optional[Uri] - RedactedMediaFileUri: Optional[Uri] + MediaFileUri: Uri | None + RedactedMediaFileUri: Uri | None class CallAnalyticsSkippedFeature(TypedDict, total=False): - Feature: Optional[CallAnalyticsFeature] - ReasonCode: Optional[CallAnalyticsSkippedReasonCode] - Message: Optional[String] + Feature: CallAnalyticsFeature | None + ReasonCode: CallAnalyticsSkippedReasonCode | None + Message: String | None -CallAnalyticsSkippedFeatureList = List[CallAnalyticsSkippedFeature] +CallAnalyticsSkippedFeatureList = list[CallAnalyticsSkippedFeature] class CallAnalyticsJobDetails(TypedDict, total=False): - Skipped: Optional[CallAnalyticsSkippedFeatureList] + Skipped: CallAnalyticsSkippedFeatureList | None class CallAnalyticsJob(TypedDict, total=False): - CallAnalyticsJobName: Optional[CallAnalyticsJobName] - CallAnalyticsJobStatus: Optional[CallAnalyticsJobStatus] - CallAnalyticsJobDetails: Optional[CallAnalyticsJobDetails] - LanguageCode: Optional[LanguageCode] - MediaSampleRateHertz: Optional[MediaSampleRateHertz] - MediaFormat: Optional[MediaFormat] - Media: Optional[Media] - Transcript: Optional[Transcript] - StartTime: Optional[DateTime] - CreationTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - FailureReason: Optional[FailureReason] - DataAccessRoleArn: Optional[DataAccessRoleArn] - IdentifiedLanguageScore: Optional[IdentifiedLanguageScore] - Settings: Optional[CallAnalyticsJobSettings] - ChannelDefinitions: Optional[ChannelDefinitions] - Tags: Optional[TagList] + CallAnalyticsJobName: CallAnalyticsJobName | None + CallAnalyticsJobStatus: CallAnalyticsJobStatus | None + CallAnalyticsJobDetails: CallAnalyticsJobDetails | None + LanguageCode: LanguageCode | None + MediaSampleRateHertz: MediaSampleRateHertz | None + MediaFormat: MediaFormat | None + Media: Media | None + Transcript: Transcript | None + StartTime: DateTime | None + CreationTime: DateTime | None + CompletionTime: DateTime | None + FailureReason: FailureReason | None + DataAccessRoleArn: DataAccessRoleArn | None + IdentifiedLanguageScore: IdentifiedLanguageScore | None + Settings: CallAnalyticsJobSettings | None + ChannelDefinitions: ChannelDefinitions | None + Tags: TagList | None class CallAnalyticsJobSummary(TypedDict, total=False): - CallAnalyticsJobName: Optional[CallAnalyticsJobName] - CreationTime: Optional[DateTime] - StartTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - LanguageCode: Optional[LanguageCode] - CallAnalyticsJobStatus: Optional[CallAnalyticsJobStatus] - CallAnalyticsJobDetails: Optional[CallAnalyticsJobDetails] - FailureReason: Optional[FailureReason] + CallAnalyticsJobName: CallAnalyticsJobName | None + CreationTime: DateTime | None + StartTime: DateTime | None + CompletionTime: DateTime | None + LanguageCode: LanguageCode | None + CallAnalyticsJobStatus: CallAnalyticsJobStatus | None + CallAnalyticsJobDetails: CallAnalyticsJobDetails | None + FailureReason: FailureReason | None -CallAnalyticsJobSummaries = List[CallAnalyticsJobSummary] +CallAnalyticsJobSummaries = list[CallAnalyticsJobSummary] class RelativeTimeRange(TypedDict, total=False): - StartPercentage: Optional[Percentage] - EndPercentage: Optional[Percentage] - First: Optional[Percentage] - Last: Optional[Percentage] + StartPercentage: Percentage | None + EndPercentage: Percentage | None + First: Percentage | None + Last: Percentage | None -SentimentValueList = List[SentimentValue] +SentimentValueList = list[SentimentValue] class SentimentFilter(TypedDict, total=False): Sentiments: SentimentValueList - AbsoluteTimeRange: Optional[AbsoluteTimeRange] - RelativeTimeRange: Optional[RelativeTimeRange] - ParticipantRole: Optional[ParticipantRole] - Negate: Optional[Boolean] + AbsoluteTimeRange: AbsoluteTimeRange | None + RelativeTimeRange: RelativeTimeRange | None + ParticipantRole: ParticipantRole | None + Negate: Boolean | None -StringTargetList = List[NonEmptyString] +StringTargetList = list[NonEmptyString] class TranscriptFilter(TypedDict, total=False): TranscriptFilterType: TranscriptFilterType - AbsoluteTimeRange: Optional[AbsoluteTimeRange] - RelativeTimeRange: Optional[RelativeTimeRange] - ParticipantRole: Optional[ParticipantRole] - Negate: Optional[Boolean] + AbsoluteTimeRange: AbsoluteTimeRange | None + RelativeTimeRange: RelativeTimeRange | None + ParticipantRole: ParticipantRole | None + Negate: Boolean | None Targets: StringTargetList class InterruptionFilter(TypedDict, total=False): - Threshold: Optional[TimestampMilliseconds] - ParticipantRole: Optional[ParticipantRole] - AbsoluteTimeRange: Optional[AbsoluteTimeRange] - RelativeTimeRange: Optional[RelativeTimeRange] - Negate: Optional[Boolean] + Threshold: TimestampMilliseconds | None + ParticipantRole: ParticipantRole | None + AbsoluteTimeRange: AbsoluteTimeRange | None + RelativeTimeRange: RelativeTimeRange | None + Negate: Boolean | None class NonTalkTimeFilter(TypedDict, total=False): - Threshold: Optional[TimestampMilliseconds] - AbsoluteTimeRange: Optional[AbsoluteTimeRange] - RelativeTimeRange: Optional[RelativeTimeRange] - Negate: Optional[Boolean] + Threshold: TimestampMilliseconds | None + AbsoluteTimeRange: AbsoluteTimeRange | None + RelativeTimeRange: RelativeTimeRange | None + Negate: Boolean | None class Rule(TypedDict, total=False): - NonTalkTimeFilter: Optional[NonTalkTimeFilter] - InterruptionFilter: Optional[InterruptionFilter] - TranscriptFilter: Optional[TranscriptFilter] - SentimentFilter: Optional[SentimentFilter] + NonTalkTimeFilter: NonTalkTimeFilter | None + InterruptionFilter: InterruptionFilter | None + TranscriptFilter: TranscriptFilter | None + SentimentFilter: SentimentFilter | None -RuleList = List[Rule] +RuleList = list[Rule] class CategoryProperties(TypedDict, total=False): - CategoryName: Optional[CategoryName] - Rules: Optional[RuleList] - CreateTime: Optional[DateTime] - LastUpdateTime: Optional[DateTime] - Tags: Optional[TagList] - InputType: Optional[InputType] + CategoryName: CategoryName | None + Rules: RuleList | None + CreateTime: DateTime | None + LastUpdateTime: DateTime | None + Tags: TagList | None + InputType: InputType | None -CategoryPropertiesList = List[CategoryProperties] +CategoryPropertiesList = list[CategoryProperties] class ClinicalNoteGenerationSettings(TypedDict, total=False): - NoteTemplate: Optional[MedicalScribeNoteTemplate] + NoteTemplate: MedicalScribeNoteTemplate | None class CreateCallAnalyticsCategoryRequest(ServiceRequest): CategoryName: CategoryName Rules: RuleList - Tags: Optional[TagList] - InputType: Optional[InputType] + Tags: TagList | None + InputType: InputType | None class CreateCallAnalyticsCategoryResponse(TypedDict, total=False): - CategoryProperties: Optional[CategoryProperties] + CategoryProperties: CategoryProperties | None class InputDataConfig(TypedDict, total=False): S3Uri: Uri - TuningDataS3Uri: Optional[Uri] + TuningDataS3Uri: Uri | None DataAccessRoleArn: DataAccessRoleArn @@ -552,68 +558,68 @@ class CreateLanguageModelRequest(ServiceRequest): BaseModelName: BaseModelName ModelName: ModelName InputDataConfig: InputDataConfig - Tags: Optional[TagList] + Tags: TagList | None class CreateLanguageModelResponse(TypedDict, total=False): - LanguageCode: Optional[CLMLanguageCode] - BaseModelName: Optional[BaseModelName] - ModelName: Optional[ModelName] - InputDataConfig: Optional[InputDataConfig] - ModelStatus: Optional[ModelStatus] + LanguageCode: CLMLanguageCode | None + BaseModelName: BaseModelName | None + ModelName: ModelName | None + InputDataConfig: InputDataConfig | None + ModelStatus: ModelStatus | None class CreateMedicalVocabularyRequest(ServiceRequest): VocabularyName: VocabularyName LanguageCode: LanguageCode VocabularyFileUri: Uri - Tags: Optional[TagList] + Tags: TagList | None class CreateMedicalVocabularyResponse(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - LanguageCode: Optional[LanguageCode] - VocabularyState: Optional[VocabularyState] - LastModifiedTime: Optional[DateTime] - FailureReason: Optional[FailureReason] + VocabularyName: VocabularyName | None + LanguageCode: LanguageCode | None + VocabularyState: VocabularyState | None + LastModifiedTime: DateTime | None + FailureReason: FailureReason | None -Words = List[Word] +Words = list[Word] class CreateVocabularyFilterRequest(ServiceRequest): VocabularyFilterName: VocabularyFilterName LanguageCode: LanguageCode - Words: Optional[Words] - VocabularyFilterFileUri: Optional[Uri] - Tags: Optional[TagList] - DataAccessRoleArn: Optional[DataAccessRoleArn] + Words: Words | None + VocabularyFilterFileUri: Uri | None + Tags: TagList | None + DataAccessRoleArn: DataAccessRoleArn | None class CreateVocabularyFilterResponse(TypedDict, total=False): - VocabularyFilterName: Optional[VocabularyFilterName] - LanguageCode: Optional[LanguageCode] - LastModifiedTime: Optional[DateTime] + VocabularyFilterName: VocabularyFilterName | None + LanguageCode: LanguageCode | None + LastModifiedTime: DateTime | None -Phrases = List[Phrase] +Phrases = list[Phrase] class CreateVocabularyRequest(ServiceRequest): VocabularyName: VocabularyName LanguageCode: LanguageCode - Phrases: Optional[Phrases] - VocabularyFileUri: Optional[Uri] - Tags: Optional[TagList] - DataAccessRoleArn: Optional[DataAccessRoleArn] + Phrases: Phrases | None + VocabularyFileUri: Uri | None + Tags: TagList | None + DataAccessRoleArn: DataAccessRoleArn | None class CreateVocabularyResponse(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - LanguageCode: Optional[LanguageCode] - VocabularyState: Optional[VocabularyState] - LastModifiedTime: Optional[DateTime] - FailureReason: Optional[FailureReason] + VocabularyName: VocabularyName | None + LanguageCode: LanguageCode | None + VocabularyState: VocabularyState | None + LastModifiedTime: DateTime | None + FailureReason: FailureReason | None class DeleteCallAnalyticsCategoryRequest(ServiceRequest): @@ -665,19 +671,19 @@ class DescribeLanguageModelRequest(ServiceRequest): class LanguageModel(TypedDict, total=False): - ModelName: Optional[ModelName] - CreateTime: Optional[DateTime] - LastModifiedTime: Optional[DateTime] - LanguageCode: Optional[CLMLanguageCode] - BaseModelName: Optional[BaseModelName] - ModelStatus: Optional[ModelStatus] - UpgradeAvailability: Optional[Boolean] - FailureReason: Optional[FailureReason] - InputDataConfig: Optional[InputDataConfig] + ModelName: ModelName | None + CreateTime: DateTime | None + LastModifiedTime: DateTime | None + LanguageCode: CLMLanguageCode | None + BaseModelName: BaseModelName | None + ModelStatus: ModelStatus | None + UpgradeAvailability: Boolean | None + FailureReason: FailureReason | None + InputDataConfig: InputDataConfig | None class DescribeLanguageModelResponse(TypedDict, total=False): - LanguageModel: Optional[LanguageModel] + LanguageModel: LanguageModel | None class GetCallAnalyticsCategoryRequest(ServiceRequest): @@ -685,7 +691,7 @@ class GetCallAnalyticsCategoryRequest(ServiceRequest): class GetCallAnalyticsCategoryResponse(TypedDict, total=False): - CategoryProperties: Optional[CategoryProperties] + CategoryProperties: CategoryProperties | None class GetCallAnalyticsJobRequest(ServiceRequest): @@ -693,7 +699,7 @@ class GetCallAnalyticsJobRequest(ServiceRequest): class GetCallAnalyticsJobResponse(TypedDict, total=False): - CallAnalyticsJob: Optional[CallAnalyticsJob] + CallAnalyticsJob: CallAnalyticsJob | None class GetMedicalScribeJobRequest(ServiceRequest): @@ -705,17 +711,17 @@ class MedicalScribeChannelDefinition(TypedDict, total=False): ParticipantRole: MedicalScribeParticipantRole -MedicalScribeChannelDefinitions = List[MedicalScribeChannelDefinition] +MedicalScribeChannelDefinitions = list[MedicalScribeChannelDefinition] class MedicalScribeSettings(TypedDict, total=False): - ShowSpeakerLabels: Optional[Boolean] - MaxSpeakerLabels: Optional[MaxSpeakers] - ChannelIdentification: Optional[Boolean] - VocabularyName: Optional[VocabularyName] - VocabularyFilterName: Optional[VocabularyFilterName] - VocabularyFilterMethod: Optional[VocabularyFilterMethod] - ClinicalNoteGenerationSettings: Optional[ClinicalNoteGenerationSettings] + ShowSpeakerLabels: Boolean | None + MaxSpeakerLabels: MaxSpeakers | None + ChannelIdentification: Boolean | None + VocabularyName: VocabularyName | None + VocabularyFilterName: VocabularyFilterName | None + VocabularyFilterMethod: VocabularyFilterMethod | None + ClinicalNoteGenerationSettings: ClinicalNoteGenerationSettings | None class MedicalScribeOutput(TypedDict, total=False): @@ -724,23 +730,24 @@ class MedicalScribeOutput(TypedDict, total=False): class MedicalScribeJob(TypedDict, total=False): - MedicalScribeJobName: Optional[TranscriptionJobName] - MedicalScribeJobStatus: Optional[MedicalScribeJobStatus] - LanguageCode: Optional[MedicalScribeLanguageCode] - Media: Optional[Media] - MedicalScribeOutput: Optional[MedicalScribeOutput] - StartTime: Optional[DateTime] - CreationTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - FailureReason: Optional[FailureReason] - Settings: Optional[MedicalScribeSettings] - DataAccessRoleArn: Optional[DataAccessRoleArn] - ChannelDefinitions: Optional[MedicalScribeChannelDefinitions] - Tags: Optional[TagList] + MedicalScribeJobName: TranscriptionJobName | None + MedicalScribeJobStatus: MedicalScribeJobStatus | None + LanguageCode: MedicalScribeLanguageCode | None + Media: Media | None + MedicalScribeOutput: MedicalScribeOutput | None + StartTime: DateTime | None + CreationTime: DateTime | None + CompletionTime: DateTime | None + FailureReason: FailureReason | None + Settings: MedicalScribeSettings | None + DataAccessRoleArn: DataAccessRoleArn | None + ChannelDefinitions: MedicalScribeChannelDefinitions | None + MedicalScribeContextProvided: Boolean | None + Tags: TagList | None class GetMedicalScribeJobResponse(TypedDict, total=False): - MedicalScribeJob: Optional[MedicalScribeJob] + MedicalScribeJob: MedicalScribeJob | None class GetMedicalTranscriptionJobRequest(ServiceRequest): @@ -748,39 +755,39 @@ class GetMedicalTranscriptionJobRequest(ServiceRequest): class MedicalTranscriptionSetting(TypedDict, total=False): - ShowSpeakerLabels: Optional[Boolean] - MaxSpeakerLabels: Optional[MaxSpeakers] - ChannelIdentification: Optional[Boolean] - ShowAlternatives: Optional[Boolean] - MaxAlternatives: Optional[MaxAlternatives] - VocabularyName: Optional[VocabularyName] + ShowSpeakerLabels: Boolean | None + MaxSpeakerLabels: MaxSpeakers | None + ChannelIdentification: Boolean | None + ShowAlternatives: Boolean | None + MaxAlternatives: MaxAlternatives | None + VocabularyName: VocabularyName | None class MedicalTranscript(TypedDict, total=False): - TranscriptFileUri: Optional[Uri] + TranscriptFileUri: Uri | None class MedicalTranscriptionJob(TypedDict, total=False): - MedicalTranscriptionJobName: Optional[TranscriptionJobName] - TranscriptionJobStatus: Optional[TranscriptionJobStatus] - LanguageCode: Optional[LanguageCode] - MediaSampleRateHertz: Optional[MedicalMediaSampleRateHertz] - MediaFormat: Optional[MediaFormat] - Media: Optional[Media] - Transcript: Optional[MedicalTranscript] - StartTime: Optional[DateTime] - CreationTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - FailureReason: Optional[FailureReason] - Settings: Optional[MedicalTranscriptionSetting] - ContentIdentificationType: Optional[MedicalContentIdentificationType] - Specialty: Optional[Specialty] - Type: Optional[Type] - Tags: Optional[TagList] + MedicalTranscriptionJobName: TranscriptionJobName | None + TranscriptionJobStatus: TranscriptionJobStatus | None + LanguageCode: LanguageCode | None + MediaSampleRateHertz: MedicalMediaSampleRateHertz | None + MediaFormat: MediaFormat | None + Media: Media | None + Transcript: MedicalTranscript | None + StartTime: DateTime | None + CreationTime: DateTime | None + CompletionTime: DateTime | None + FailureReason: FailureReason | None + Settings: MedicalTranscriptionSetting | None + ContentIdentificationType: MedicalContentIdentificationType | None + Specialty: Specialty | None + Type: Type | None + Tags: TagList | None class GetMedicalTranscriptionJobResponse(TypedDict, total=False): - MedicalTranscriptionJob: Optional[MedicalTranscriptionJob] + MedicalTranscriptionJob: MedicalTranscriptionJob | None class GetMedicalVocabularyRequest(ServiceRequest): @@ -788,93 +795,93 @@ class GetMedicalVocabularyRequest(ServiceRequest): class GetMedicalVocabularyResponse(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - LanguageCode: Optional[LanguageCode] - VocabularyState: Optional[VocabularyState] - LastModifiedTime: Optional[DateTime] - FailureReason: Optional[FailureReason] - DownloadUri: Optional[Uri] + VocabularyName: VocabularyName | None + LanguageCode: LanguageCode | None + VocabularyState: VocabularyState | None + LastModifiedTime: DateTime | None + FailureReason: FailureReason | None + DownloadUri: Uri | None class GetTranscriptionJobRequest(ServiceRequest): TranscriptionJobName: TranscriptionJobName -ToxicityCategories = List[ToxicityCategory] +ToxicityCategories = list[ToxicityCategory] class ToxicityDetectionSettings(TypedDict, total=False): ToxicityCategories: ToxicityCategories -ToxicityDetection = List[ToxicityDetectionSettings] -SubtitleFileUris = List[Uri] -SubtitleFormats = List[SubtitleFormat] +ToxicityDetection = list[ToxicityDetectionSettings] +SubtitleFileUris = list[Uri] +SubtitleFormats = list[SubtitleFormat] class SubtitlesOutput(TypedDict, total=False): - Formats: Optional[SubtitleFormats] - SubtitleFileUris: Optional[SubtitleFileUris] - OutputStartIndex: Optional[SubtitleOutputStartIndex] + Formats: SubtitleFormats | None + SubtitleFileUris: SubtitleFileUris | None + OutputStartIndex: SubtitleOutputStartIndex | None class LanguageCodeItem(TypedDict, total=False): - LanguageCode: Optional[LanguageCode] - DurationInSeconds: Optional[DurationInSeconds] + LanguageCode: LanguageCode | None + DurationInSeconds: DurationInSeconds | None -LanguageCodeList = List[LanguageCodeItem] +LanguageCodeList = list[LanguageCodeItem] class JobExecutionSettings(TypedDict, total=False): - AllowDeferredExecution: Optional[Boolean] - DataAccessRoleArn: Optional[DataAccessRoleArn] + AllowDeferredExecution: Boolean | None + DataAccessRoleArn: DataAccessRoleArn | None class ModelSettings(TypedDict, total=False): - LanguageModelName: Optional[ModelName] + LanguageModelName: ModelName | None class Settings(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - ShowSpeakerLabels: Optional[Boolean] - MaxSpeakerLabels: Optional[MaxSpeakers] - ChannelIdentification: Optional[Boolean] - ShowAlternatives: Optional[Boolean] - MaxAlternatives: Optional[MaxAlternatives] - VocabularyFilterName: Optional[VocabularyFilterName] - VocabularyFilterMethod: Optional[VocabularyFilterMethod] + VocabularyName: VocabularyName | None + ShowSpeakerLabels: Boolean | None + MaxSpeakerLabels: MaxSpeakers | None + ChannelIdentification: Boolean | None + ShowAlternatives: Boolean | None + MaxAlternatives: MaxAlternatives | None + VocabularyFilterName: VocabularyFilterName | None + VocabularyFilterMethod: VocabularyFilterMethod | None class TranscriptionJob(TypedDict, total=False): - TranscriptionJobName: Optional[TranscriptionJobName] - TranscriptionJobStatus: Optional[TranscriptionJobStatus] - LanguageCode: Optional[LanguageCode] - MediaSampleRateHertz: Optional[MediaSampleRateHertz] - MediaFormat: Optional[MediaFormat] - Media: Optional[Media] - Transcript: Optional[Transcript] - StartTime: Optional[DateTime] - CreationTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - FailureReason: Optional[FailureReason] - Settings: Optional[Settings] - ModelSettings: Optional[ModelSettings] - JobExecutionSettings: Optional[JobExecutionSettings] - ContentRedaction: Optional[ContentRedaction] - IdentifyLanguage: Optional[Boolean] - IdentifyMultipleLanguages: Optional[Boolean] - LanguageOptions: Optional[LanguageOptions] - IdentifiedLanguageScore: Optional[IdentifiedLanguageScore] - LanguageCodes: Optional[LanguageCodeList] - Tags: Optional[TagList] - Subtitles: Optional[SubtitlesOutput] - LanguageIdSettings: Optional[LanguageIdSettingsMap] - ToxicityDetection: Optional[ToxicityDetection] + TranscriptionJobName: TranscriptionJobName | None + TranscriptionJobStatus: TranscriptionJobStatus | None + LanguageCode: LanguageCode | None + MediaSampleRateHertz: MediaSampleRateHertz | None + MediaFormat: MediaFormat | None + Media: Media | None + Transcript: Transcript | None + StartTime: DateTime | None + CreationTime: DateTime | None + CompletionTime: DateTime | None + FailureReason: FailureReason | None + Settings: Settings | None + ModelSettings: ModelSettings | None + JobExecutionSettings: JobExecutionSettings | None + ContentRedaction: ContentRedaction | None + IdentifyLanguage: Boolean | None + IdentifyMultipleLanguages: Boolean | None + LanguageOptions: LanguageOptions | None + IdentifiedLanguageScore: IdentifiedLanguageScore | None + LanguageCodes: LanguageCodeList | None + Tags: TagList | None + Subtitles: SubtitlesOutput | None + LanguageIdSettings: LanguageIdSettingsMap | None + ToxicityDetection: ToxicityDetection | None class GetTranscriptionJobResponse(TypedDict, total=False): - TranscriptionJob: Optional[TranscriptionJob] + TranscriptionJob: TranscriptionJob | None class GetVocabularyFilterRequest(ServiceRequest): @@ -882,10 +889,10 @@ class GetVocabularyFilterRequest(ServiceRequest): class GetVocabularyFilterResponse(TypedDict, total=False): - VocabularyFilterName: Optional[VocabularyFilterName] - LanguageCode: Optional[LanguageCode] - LastModifiedTime: Optional[DateTime] - DownloadUri: Optional[Uri] + VocabularyFilterName: VocabularyFilterName | None + LanguageCode: LanguageCode | None + LastModifiedTime: DateTime | None + DownloadUri: Uri | None class GetVocabularyRequest(ServiceRequest): @@ -893,132 +900,132 @@ class GetVocabularyRequest(ServiceRequest): class GetVocabularyResponse(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - LanguageCode: Optional[LanguageCode] - VocabularyState: Optional[VocabularyState] - LastModifiedTime: Optional[DateTime] - FailureReason: Optional[FailureReason] - DownloadUri: Optional[Uri] + VocabularyName: VocabularyName | None + LanguageCode: LanguageCode | None + VocabularyState: VocabularyState | None + LastModifiedTime: DateTime | None + FailureReason: FailureReason | None + DownloadUri: Uri | None -KMSEncryptionContextMap = Dict[NonEmptyString, NonEmptyString] +KMSEncryptionContextMap = dict[NonEmptyString, NonEmptyString] class ListCallAnalyticsCategoriesRequest(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + NextToken: NextToken | None + MaxResults: MaxResults | None class ListCallAnalyticsCategoriesResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - Categories: Optional[CategoryPropertiesList] + NextToken: NextToken | None + Categories: CategoryPropertiesList | None class ListCallAnalyticsJobsRequest(ServiceRequest): - Status: Optional[CallAnalyticsJobStatus] - JobNameContains: Optional[CallAnalyticsJobName] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Status: CallAnalyticsJobStatus | None + JobNameContains: CallAnalyticsJobName | None + NextToken: NextToken | None + MaxResults: MaxResults | None class ListCallAnalyticsJobsResponse(TypedDict, total=False): - Status: Optional[CallAnalyticsJobStatus] - NextToken: Optional[NextToken] - CallAnalyticsJobSummaries: Optional[CallAnalyticsJobSummaries] + Status: CallAnalyticsJobStatus | None + NextToken: NextToken | None + CallAnalyticsJobSummaries: CallAnalyticsJobSummaries | None class ListLanguageModelsRequest(ServiceRequest): - StatusEquals: Optional[ModelStatus] - NameContains: Optional[ModelName] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + StatusEquals: ModelStatus | None + NameContains: ModelName | None + NextToken: NextToken | None + MaxResults: MaxResults | None -Models = List[LanguageModel] +Models = list[LanguageModel] class ListLanguageModelsResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - Models: Optional[Models] + NextToken: NextToken | None + Models: Models | None class ListMedicalScribeJobsRequest(ServiceRequest): - Status: Optional[MedicalScribeJobStatus] - JobNameContains: Optional[TranscriptionJobName] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Status: MedicalScribeJobStatus | None + JobNameContains: TranscriptionJobName | None + NextToken: NextToken | None + MaxResults: MaxResults | None class MedicalScribeJobSummary(TypedDict, total=False): - MedicalScribeJobName: Optional[TranscriptionJobName] - CreationTime: Optional[DateTime] - StartTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - LanguageCode: Optional[MedicalScribeLanguageCode] - MedicalScribeJobStatus: Optional[MedicalScribeJobStatus] - FailureReason: Optional[FailureReason] + MedicalScribeJobName: TranscriptionJobName | None + CreationTime: DateTime | None + StartTime: DateTime | None + CompletionTime: DateTime | None + LanguageCode: MedicalScribeLanguageCode | None + MedicalScribeJobStatus: MedicalScribeJobStatus | None + FailureReason: FailureReason | None -MedicalScribeJobSummaries = List[MedicalScribeJobSummary] +MedicalScribeJobSummaries = list[MedicalScribeJobSummary] class ListMedicalScribeJobsResponse(TypedDict, total=False): - Status: Optional[MedicalScribeJobStatus] - NextToken: Optional[NextToken] - MedicalScribeJobSummaries: Optional[MedicalScribeJobSummaries] + Status: MedicalScribeJobStatus | None + NextToken: NextToken | None + MedicalScribeJobSummaries: MedicalScribeJobSummaries | None class ListMedicalTranscriptionJobsRequest(ServiceRequest): - Status: Optional[TranscriptionJobStatus] - JobNameContains: Optional[TranscriptionJobName] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Status: TranscriptionJobStatus | None + JobNameContains: TranscriptionJobName | None + NextToken: NextToken | None + MaxResults: MaxResults | None class MedicalTranscriptionJobSummary(TypedDict, total=False): - MedicalTranscriptionJobName: Optional[TranscriptionJobName] - CreationTime: Optional[DateTime] - StartTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - LanguageCode: Optional[LanguageCode] - TranscriptionJobStatus: Optional[TranscriptionJobStatus] - FailureReason: Optional[FailureReason] - OutputLocationType: Optional[OutputLocationType] - Specialty: Optional[Specialty] - ContentIdentificationType: Optional[MedicalContentIdentificationType] - Type: Optional[Type] + MedicalTranscriptionJobName: TranscriptionJobName | None + CreationTime: DateTime | None + StartTime: DateTime | None + CompletionTime: DateTime | None + LanguageCode: LanguageCode | None + TranscriptionJobStatus: TranscriptionJobStatus | None + FailureReason: FailureReason | None + OutputLocationType: OutputLocationType | None + Specialty: Specialty | None + ContentIdentificationType: MedicalContentIdentificationType | None + Type: Type | None -MedicalTranscriptionJobSummaries = List[MedicalTranscriptionJobSummary] +MedicalTranscriptionJobSummaries = list[MedicalTranscriptionJobSummary] class ListMedicalTranscriptionJobsResponse(TypedDict, total=False): - Status: Optional[TranscriptionJobStatus] - NextToken: Optional[NextToken] - MedicalTranscriptionJobSummaries: Optional[MedicalTranscriptionJobSummaries] + Status: TranscriptionJobStatus | None + NextToken: NextToken | None + MedicalTranscriptionJobSummaries: MedicalTranscriptionJobSummaries | None class ListMedicalVocabulariesRequest(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - StateEquals: Optional[VocabularyState] - NameContains: Optional[VocabularyName] + NextToken: NextToken | None + MaxResults: MaxResults | None + StateEquals: VocabularyState | None + NameContains: VocabularyName | None class VocabularyInfo(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - LanguageCode: Optional[LanguageCode] - LastModifiedTime: Optional[DateTime] - VocabularyState: Optional[VocabularyState] + VocabularyName: VocabularyName | None + LanguageCode: LanguageCode | None + LastModifiedTime: DateTime | None + VocabularyState: VocabularyState | None -Vocabularies = List[VocabularyInfo] +Vocabularies = list[VocabularyInfo] class ListMedicalVocabulariesResponse(TypedDict, total=False): - Status: Optional[VocabularyState] - NextToken: Optional[NextToken] - Vocabularies: Optional[Vocabularies] + Status: VocabularyState | None + NextToken: NextToken | None + Vocabularies: Vocabularies | None class ListTagsForResourceRequest(ServiceRequest): @@ -1026,162 +1033,171 @@ class ListTagsForResourceRequest(ServiceRequest): class ListTagsForResourceResponse(TypedDict, total=False): - ResourceArn: Optional[TranscribeArn] - Tags: Optional[TagList] + ResourceArn: TranscribeArn | None + Tags: TagList | None class ListTranscriptionJobsRequest(ServiceRequest): - Status: Optional[TranscriptionJobStatus] - JobNameContains: Optional[TranscriptionJobName] - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] + Status: TranscriptionJobStatus | None + JobNameContains: TranscriptionJobName | None + NextToken: NextToken | None + MaxResults: MaxResults | None class TranscriptionJobSummary(TypedDict, total=False): - TranscriptionJobName: Optional[TranscriptionJobName] - CreationTime: Optional[DateTime] - StartTime: Optional[DateTime] - CompletionTime: Optional[DateTime] - LanguageCode: Optional[LanguageCode] - TranscriptionJobStatus: Optional[TranscriptionJobStatus] - FailureReason: Optional[FailureReason] - OutputLocationType: Optional[OutputLocationType] - ContentRedaction: Optional[ContentRedaction] - ModelSettings: Optional[ModelSettings] - IdentifyLanguage: Optional[Boolean] - IdentifyMultipleLanguages: Optional[Boolean] - IdentifiedLanguageScore: Optional[IdentifiedLanguageScore] - LanguageCodes: Optional[LanguageCodeList] - ToxicityDetection: Optional[ToxicityDetection] - - -TranscriptionJobSummaries = List[TranscriptionJobSummary] + TranscriptionJobName: TranscriptionJobName | None + CreationTime: DateTime | None + StartTime: DateTime | None + CompletionTime: DateTime | None + LanguageCode: LanguageCode | None + TranscriptionJobStatus: TranscriptionJobStatus | None + FailureReason: FailureReason | None + OutputLocationType: OutputLocationType | None + ContentRedaction: ContentRedaction | None + ModelSettings: ModelSettings | None + IdentifyLanguage: Boolean | None + IdentifyMultipleLanguages: Boolean | None + IdentifiedLanguageScore: IdentifiedLanguageScore | None + LanguageCodes: LanguageCodeList | None + ToxicityDetection: ToxicityDetection | None + + +TranscriptionJobSummaries = list[TranscriptionJobSummary] class ListTranscriptionJobsResponse(TypedDict, total=False): - Status: Optional[TranscriptionJobStatus] - NextToken: Optional[NextToken] - TranscriptionJobSummaries: Optional[TranscriptionJobSummaries] + Status: TranscriptionJobStatus | None + NextToken: NextToken | None + TranscriptionJobSummaries: TranscriptionJobSummaries | None class ListVocabulariesRequest(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - StateEquals: Optional[VocabularyState] - NameContains: Optional[VocabularyName] + NextToken: NextToken | None + MaxResults: MaxResults | None + StateEquals: VocabularyState | None + NameContains: VocabularyName | None class ListVocabulariesResponse(TypedDict, total=False): - Status: Optional[VocabularyState] - NextToken: Optional[NextToken] - Vocabularies: Optional[Vocabularies] + Status: VocabularyState | None + NextToken: NextToken | None + Vocabularies: Vocabularies | None class ListVocabularyFiltersRequest(ServiceRequest): - NextToken: Optional[NextToken] - MaxResults: Optional[MaxResults] - NameContains: Optional[VocabularyFilterName] + NextToken: NextToken | None + MaxResults: MaxResults | None + NameContains: VocabularyFilterName | None class VocabularyFilterInfo(TypedDict, total=False): - VocabularyFilterName: Optional[VocabularyFilterName] - LanguageCode: Optional[LanguageCode] - LastModifiedTime: Optional[DateTime] + VocabularyFilterName: VocabularyFilterName | None + LanguageCode: LanguageCode | None + LastModifiedTime: DateTime | None -VocabularyFilters = List[VocabularyFilterInfo] +VocabularyFilters = list[VocabularyFilterInfo] class ListVocabularyFiltersResponse(TypedDict, total=False): - NextToken: Optional[NextToken] - VocabularyFilters: Optional[VocabularyFilters] + NextToken: NextToken | None + VocabularyFilters: VocabularyFilters | None + + +class MedicalScribePatientContext(TypedDict, total=False): + Pronouns: Pronouns | None + + +class MedicalScribeContext(TypedDict, total=False): + PatientContext: MedicalScribePatientContext | None class StartCallAnalyticsJobRequest(ServiceRequest): CallAnalyticsJobName: CallAnalyticsJobName Media: Media - OutputLocation: Optional[Uri] - OutputEncryptionKMSKeyId: Optional[KMSKeyId] - DataAccessRoleArn: Optional[DataAccessRoleArn] - Settings: Optional[CallAnalyticsJobSettings] - Tags: Optional[TagList] - ChannelDefinitions: Optional[ChannelDefinitions] + OutputLocation: Uri | None + OutputEncryptionKMSKeyId: KMSKeyId | None + DataAccessRoleArn: DataAccessRoleArn | None + Settings: CallAnalyticsJobSettings | None + Tags: TagList | None + ChannelDefinitions: ChannelDefinitions | None class StartCallAnalyticsJobResponse(TypedDict, total=False): - CallAnalyticsJob: Optional[CallAnalyticsJob] + CallAnalyticsJob: CallAnalyticsJob | None class StartMedicalScribeJobRequest(ServiceRequest): MedicalScribeJobName: TranscriptionJobName Media: Media OutputBucketName: OutputBucketName - OutputEncryptionKMSKeyId: Optional[KMSKeyId] - KMSEncryptionContext: Optional[KMSEncryptionContextMap] + OutputEncryptionKMSKeyId: KMSKeyId | None + KMSEncryptionContext: KMSEncryptionContextMap | None DataAccessRoleArn: DataAccessRoleArn Settings: MedicalScribeSettings - ChannelDefinitions: Optional[MedicalScribeChannelDefinitions] - Tags: Optional[TagList] + ChannelDefinitions: MedicalScribeChannelDefinitions | None + Tags: TagList | None + MedicalScribeContext: MedicalScribeContext | None class StartMedicalScribeJobResponse(TypedDict, total=False): - MedicalScribeJob: Optional[MedicalScribeJob] + MedicalScribeJob: MedicalScribeJob | None class StartMedicalTranscriptionJobRequest(ServiceRequest): MedicalTranscriptionJobName: TranscriptionJobName LanguageCode: LanguageCode - MediaSampleRateHertz: Optional[MedicalMediaSampleRateHertz] - MediaFormat: Optional[MediaFormat] + MediaSampleRateHertz: MedicalMediaSampleRateHertz | None + MediaFormat: MediaFormat | None Media: Media OutputBucketName: OutputBucketName - OutputKey: Optional[OutputKey] - OutputEncryptionKMSKeyId: Optional[KMSKeyId] - KMSEncryptionContext: Optional[KMSEncryptionContextMap] - Settings: Optional[MedicalTranscriptionSetting] - ContentIdentificationType: Optional[MedicalContentIdentificationType] + OutputKey: OutputKey | None + OutputEncryptionKMSKeyId: KMSKeyId | None + KMSEncryptionContext: KMSEncryptionContextMap | None + Settings: MedicalTranscriptionSetting | None + ContentIdentificationType: MedicalContentIdentificationType | None Specialty: Specialty Type: Type - Tags: Optional[TagList] + Tags: TagList | None class StartMedicalTranscriptionJobResponse(TypedDict, total=False): - MedicalTranscriptionJob: Optional[MedicalTranscriptionJob] + MedicalTranscriptionJob: MedicalTranscriptionJob | None class Subtitles(TypedDict, total=False): - Formats: Optional[SubtitleFormats] - OutputStartIndex: Optional[SubtitleOutputStartIndex] + Formats: SubtitleFormats | None + OutputStartIndex: SubtitleOutputStartIndex | None class StartTranscriptionJobRequest(ServiceRequest): TranscriptionJobName: TranscriptionJobName - LanguageCode: Optional[LanguageCode] - MediaSampleRateHertz: Optional[MediaSampleRateHertz] - MediaFormat: Optional[MediaFormat] + LanguageCode: LanguageCode | None + MediaSampleRateHertz: MediaSampleRateHertz | None + MediaFormat: MediaFormat | None Media: Media - OutputBucketName: Optional[OutputBucketName] - OutputKey: Optional[OutputKey] - OutputEncryptionKMSKeyId: Optional[KMSKeyId] - KMSEncryptionContext: Optional[KMSEncryptionContextMap] - Settings: Optional[Settings] - ModelSettings: Optional[ModelSettings] - JobExecutionSettings: Optional[JobExecutionSettings] - ContentRedaction: Optional[ContentRedaction] - IdentifyLanguage: Optional[Boolean] - IdentifyMultipleLanguages: Optional[Boolean] - LanguageOptions: Optional[LanguageOptions] - Subtitles: Optional[Subtitles] - Tags: Optional[TagList] - LanguageIdSettings: Optional[LanguageIdSettingsMap] - ToxicityDetection: Optional[ToxicityDetection] + OutputBucketName: OutputBucketName | None + OutputKey: OutputKey | None + OutputEncryptionKMSKeyId: KMSKeyId | None + KMSEncryptionContext: KMSEncryptionContextMap | None + Settings: Settings | None + ModelSettings: ModelSettings | None + JobExecutionSettings: JobExecutionSettings | None + ContentRedaction: ContentRedaction | None + IdentifyLanguage: Boolean | None + IdentifyMultipleLanguages: Boolean | None + LanguageOptions: LanguageOptions | None + Subtitles: Subtitles | None + Tags: TagList | None + LanguageIdSettings: LanguageIdSettingsMap | None + ToxicityDetection: ToxicityDetection | None class StartTranscriptionJobResponse(TypedDict, total=False): - TranscriptionJob: Optional[TranscriptionJob] + TranscriptionJob: TranscriptionJob | None -TagKeyList = List[TagKey] +TagKeyList = list[TagKey] class TagResourceRequest(ServiceRequest): @@ -1205,11 +1221,11 @@ class UntagResourceResponse(TypedDict, total=False): class UpdateCallAnalyticsCategoryRequest(ServiceRequest): CategoryName: CategoryName Rules: RuleList - InputType: Optional[InputType] + InputType: InputType | None class UpdateCallAnalyticsCategoryResponse(TypedDict, total=False): - CategoryProperties: Optional[CategoryProperties] + CategoryProperties: CategoryProperties | None class UpdateMedicalVocabularyRequest(ServiceRequest): @@ -1219,43 +1235,43 @@ class UpdateMedicalVocabularyRequest(ServiceRequest): class UpdateMedicalVocabularyResponse(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - LanguageCode: Optional[LanguageCode] - LastModifiedTime: Optional[DateTime] - VocabularyState: Optional[VocabularyState] + VocabularyName: VocabularyName | None + LanguageCode: LanguageCode | None + LastModifiedTime: DateTime | None + VocabularyState: VocabularyState | None class UpdateVocabularyFilterRequest(ServiceRequest): VocabularyFilterName: VocabularyFilterName - Words: Optional[Words] - VocabularyFilterFileUri: Optional[Uri] - DataAccessRoleArn: Optional[DataAccessRoleArn] + Words: Words | None + VocabularyFilterFileUri: Uri | None + DataAccessRoleArn: DataAccessRoleArn | None class UpdateVocabularyFilterResponse(TypedDict, total=False): - VocabularyFilterName: Optional[VocabularyFilterName] - LanguageCode: Optional[LanguageCode] - LastModifiedTime: Optional[DateTime] + VocabularyFilterName: VocabularyFilterName | None + LanguageCode: LanguageCode | None + LastModifiedTime: DateTime | None class UpdateVocabularyRequest(ServiceRequest): VocabularyName: VocabularyName LanguageCode: LanguageCode - Phrases: Optional[Phrases] - VocabularyFileUri: Optional[Uri] - DataAccessRoleArn: Optional[DataAccessRoleArn] + Phrases: Phrases | None + VocabularyFileUri: Uri | None + DataAccessRoleArn: DataAccessRoleArn | None class UpdateVocabularyResponse(TypedDict, total=False): - VocabularyName: Optional[VocabularyName] - LanguageCode: Optional[LanguageCode] - LastModifiedTime: Optional[DateTime] - VocabularyState: Optional[VocabularyState] + VocabularyName: VocabularyName | None + LanguageCode: LanguageCode | None + LastModifiedTime: DateTime | None + VocabularyState: VocabularyState | None class TranscribeApi: - service = "transcribe" - version = "2017-10-26" + service: str = "transcribe" + version: str = "2017-10-26" @handler("CreateCallAnalyticsCategory") def create_call_analytics_category( @@ -1576,6 +1592,7 @@ def start_medical_scribe_job( kms_encryption_context: KMSEncryptionContextMap | None = None, channel_definitions: MedicalScribeChannelDefinitions | None = None, tags: TagList | None = None, + medical_scribe_context: MedicalScribeContext | None = None, **kwargs, ) -> StartMedicalScribeJobResponse: raise NotImplementedError diff --git a/localstack-core/localstack/aws/catalog_exceptions.py b/localstack-core/localstack/aws/catalog_exceptions.py new file mode 100644 index 0000000000000..236549d8d3ae1 --- /dev/null +++ b/localstack-core/localstack/aws/catalog_exceptions.py @@ -0,0 +1,73 @@ +from localstack.aws.api import CommonServiceException +from localstack.utils.catalog.common import ( + AwsServiceOperationsSupportInLatest, + AwsServicesSupportInLatest, + AwsServiceSupportAtRuntime, +) + +_DOCS_COVERAGE_URL = "https://docs.localstack.cloud/references/coverage" + + +class AwsServiceAvailabilityException(CommonServiceException): + def __init__(self, message: str, error_code: int): + super().__init__(code="InternalFailure", message=message, status_code=501) + self.error_code = error_code + + +class ServiceOrOperationNotSupportedException(AwsServiceAvailabilityException): + def __init__(self, service_name: str, operation_name: str | None = None): + if operation_name is None: + message = f"Sorry, the {service_name} service is not currently supported by LocalStack." + error_code = 3 + else: + message = f"Sorry, the {operation_name} operation on the {service_name} service is not currently supported by LocalStack." + error_code = 8 + super().__init__(message, error_code) + + +class LatestVersionRequiredException(AwsServiceAvailabilityException): + def __init__(self, service_name: str, operation_name: str | None = None): + if operation_name is None: + message = f"Sorry, the {service_name} service is not supported by this version of LocalStack, but is available if you upgrade to the latest stable version." + error_code = 2 + else: + message = f"Sorry, the {operation_name} operation on the {service_name} service is not supported by this version of LocalStack, but is available if you upgrade to the latest stable version." + error_code = 6 + super().__init__(message, error_code) + + +class LicenseUpgradeRequiredException(AwsServiceAvailabilityException): + def __init__(self, service_name: str, operation_name: str | None = None): + if operation_name is None: + message = f"Sorry, the {service_name} service is not included within your LocalStack license, but is available in an upgraded license. Please refer to {_DOCS_COVERAGE_URL} for more details." + error_code = 1 + else: + message = f"Sorry, the {operation_name} operation on the {service_name} service is not supported with your LocalStack license. Please refer to {_DOCS_COVERAGE_URL} for more details." + error_code = 5 + super().__init__(message, error_code) + + +def get_service_availability_exception( + service_name: str, + operation_name: str | None, + status: AwsServicesSupportInLatest | AwsServiceOperationsSupportInLatest | None, +) -> AwsServiceAvailabilityException: + match status: + case AwsServicesSupportInLatest.SUPPORTED: + return LatestVersionRequiredException(service_name) + case AwsServiceOperationsSupportInLatest.SUPPORTED: + return LatestVersionRequiredException(service_name, operation_name) + case ( + AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE + | AwsServiceSupportAtRuntime.AVAILABLE_WITH_LICENSE_UPGRADE + ): + return LicenseUpgradeRequiredException(service_name) + case AwsServiceOperationsSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE: + return LicenseUpgradeRequiredException(service_name, operation_name) + case AwsServicesSupportInLatest.NOT_SUPPORTED | AwsServiceSupportAtRuntime.NOT_IMPLEMENTED: + return ServiceOrOperationNotSupportedException(service_name, operation_name) + case _: + return AwsServiceAvailabilityException( + message=f"The API for service {service_name} is either not included in your current license plan or has not yet been emulated by LocalStack.", + error_code=4, + ) diff --git a/localstack-core/localstack/aws/chain.py b/localstack-core/localstack/aws/chain.py index 6702d154cefaf..8a371cf0d945c 100644 --- a/localstack-core/localstack/aws/chain.py +++ b/localstack-core/localstack/aws/chain.py @@ -5,7 +5,7 @@ from __future__ import annotations import logging -from typing import Callable, Type +from collections.abc import Callable from rolo.gateway import ( CompositeExceptionHandler, @@ -29,7 +29,7 @@ was raised by the request handler, the RequestContext, and the Response object to be populated.""" -HandlerChain: Type[RoloHandlerChain[RequestContext]] = RoloHandlerChain +HandlerChain: type[RoloHandlerChain[RequestContext]] = RoloHandlerChain __all__ = [ "HandlerChain", diff --git a/localstack-core/localstack/aws/client.py b/localstack-core/localstack/aws/client.py index 6d938c086a8cf..3077592ef11cb 100644 --- a/localstack-core/localstack/aws/client.py +++ b/localstack-core/localstack/aws/client.py @@ -2,8 +2,8 @@ import io import logging -from datetime import datetime, timezone -from typing import Dict, Iterable, Optional +from collections.abc import Iterable +from datetime import UTC, datetime from urllib.parse import urlsplit from botocore import awsrequest @@ -21,6 +21,7 @@ from .api import CommonServiceException, RequestContext, ServiceException, ServiceResponse from .connect import get_service_endpoint from .gateway import Gateway +from .spec import ProtocolName LOG = logging.getLogger(__name__) @@ -130,8 +131,8 @@ def close(self): def _add_modeled_error_fields( - response_dict: Dict, - parsed_response: Dict, + response_dict: dict, + parsed_response: dict, operation_model: OperationModel, parser: ResponseParser, ): @@ -217,7 +218,7 @@ def _patched_decode_epoch_datetime(self) -> datetime: # AWS breaks the CBOR spec by using the millis (instead of seconds with floating point support for millis) # https://github.com/aws/aws-sdk-java-v2/issues/4661 value = value / 1000 - tmp = datetime.fromtimestamp(value, timezone.utc) + tmp = datetime.fromtimestamp(value, UTC) except (OverflowError, OSError, ValueError) as exc: raise CBORDecodeValueError("error decoding datetime from epoch") from exc @@ -284,13 +285,17 @@ def _patch_botocore_endpoint_in_memory(): def parse_response( - operation: OperationModel, response: Response, include_response_metadata: bool = True + operation: OperationModel, + protocol: ProtocolName, + response: Response, + include_response_metadata: bool = True, ) -> ServiceResponse: """ Parses an HTTP Response object into an AWS response object using botocore. It does this by adapting the procedure of ``botocore.endpoint.convert_to_response_dict`` to work with Werkzeug's server-side response object. :param operation: the operation of the original request + :param protocol: the protocol of the original request :param response: the HTTP response object containing the response of the operation :param include_response_metadata: True if the ResponseMetadata (typical for boto response dicts) should be included :return: a parsed dictionary as it is returned by botocore @@ -322,7 +327,7 @@ def parse_response( timestamp_parser=_cbor_timestamp_parser, blob_parser=_cbor_blob_parser ) - parser = factory.create_parser(operation.service_model.protocol) + parser = factory.create_parser(protocol) parsed_response = parser.parse(response_dict, operation.output_shape) if response.status_code >= 301: @@ -335,9 +340,7 @@ def parse_response( return parsed_response -def parse_service_exception( - response: Response, parsed_response: Dict -) -> Optional[ServiceException]: +def parse_service_exception(response: Response, parsed_response: dict) -> ServiceException | None: """ Creates a ServiceException (one ASF can handle) from a parsed response (one that botocore would return). It does not automatically raise the exception (see #raise_service_exception). @@ -363,7 +366,7 @@ def parse_service_exception( return service_exception -def raise_service_exception(response: Response, parsed_response: Dict) -> None: +def raise_service_exception(response: Response, parsed_response: dict) -> None: """ Creates and raises a ServiceException from a parsed response (one that botocore would return). :param response: Un-parsed response diff --git a/localstack-core/localstack/aws/connect.py b/localstack-core/localstack/aws/connect.py index 6a04285e021a2..f1775e6343001 100644 --- a/localstack-core/localstack/aws/connect.py +++ b/localstack-core/localstack/aws/connect.py @@ -7,13 +7,15 @@ import json import logging +import os import re import threading from abc import ABC, abstractmethod +from collections.abc import Callable from functools import lru_cache, partial from random import choice from socket import socket -from typing import Any, Callable, Generic, Optional, TypedDict, TypeVar +from typing import Any, Generic, TypedDict, TypeVar import dns.message import dns.query @@ -88,8 +90,8 @@ def make_hash(o): return hash(frozenset(sorted(new_o.items()))) -def config_equality_patch(self, other: object): - return type(self) == type(other) and self._user_provided_options == other._user_provided_options +def config_equality_patch(self, other: object) -> bool: + return type(self) is type(other) and self._user_provided_options == other._user_provided_options def config_hash_patch(self): @@ -249,9 +251,10 @@ class ClientFactory(ABC): def __init__( self, use_ssl: bool = False, - verify: bool = False, + verify: bool | str = False, session: Session = None, config: Config = None, + endpoint: str = None, ): """ :param use_ssl: Whether to use SSL @@ -267,6 +270,7 @@ def __init__( self._verify = verify self._config: Config = config or Config(max_pool_connections=MAX_POOL_CONNECTIONS) self._session: Session = session or Session() + self._endpoint = endpoint # make sure we consider our custom data paths for legacy specs (like SQS query protocol) if LOCALSTACK_BUILTIN_DATA_PATH not in self._session._loader.search_paths: @@ -277,10 +281,10 @@ def __init__( def __call__( self, *, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, endpoint_url: str = None, config: Config = None, ) -> ServiceLevelClientFactory: @@ -318,11 +322,11 @@ def with_assumed_role( self, *, role_arn: str, - service_principal: Optional[ServicePrincipal] = None, - session_name: Optional[str] = None, - region_name: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + service_principal: ServicePrincipal | None = None, + session_name: str | None = None, + region_name: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> ServiceLevelClientFactory: """ Create a service level client factory with credentials from assuming the given role ARN. @@ -362,12 +366,12 @@ def with_assumed_role( def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ): raise NotImplementedError() @@ -389,11 +393,11 @@ def _get_client( service_name: str, region_name: str, use_ssl: bool, - verify: Optional[bool], - endpoint_url: Optional[str], - aws_access_key_id: Optional[str], - aws_secret_access_key: Optional[str], - aws_session_token: Optional[str], + verify: bool | None, + endpoint_url: str | None, + aws_access_key_id: str | None, + aws_secret_access_key: str | None, + aws_session_token: str | None, config: Config, ) -> BaseClient: """ @@ -480,12 +484,12 @@ def _get_client_post_hook(self, client: BaseClient) -> BaseClient: def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> BaseClient: """ Build and return client for connections originating within LocalStack. @@ -513,10 +517,13 @@ def get_client( else: config = self._config.merge(config) - endpoint_url = endpoint_url or get_service_endpoint() - if service_name == "s3" and endpoint_url: - if re.match(r"https?://localhost(:[0-9]+)?", endpoint_url): - endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}") + endpoint_url = endpoint_url or self._endpoint or get_service_endpoint() + if ( + endpoint_url + and service_name == "s3" + and re.match(r"https?://localhost(:[0-9]+)?", endpoint_url) + ): + endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}") return self._get_client( service_name=service_name, @@ -535,12 +542,12 @@ class ExternalClientFactory(ClientFactory): def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> BaseClient: """ Build and return client for connections originating outside LocalStack and targeting Localstack. @@ -573,14 +580,20 @@ def get_client( # If the region in arg is non-default, it gives the arg the precedence # But if the region in arg is default (us-east-1), it gives precedence to one in config # Below: always give precedence to arg region - if config and config.region_name != AWS_REGION_US_EAST_1: - if region_name == AWS_REGION_US_EAST_1: - config = config.merge(Config(region_name=region_name)) - - endpoint_url = endpoint_url or get_service_endpoint() - if service_name == "s3": - if re.match(r"https?://localhost(:[0-9]+)?", endpoint_url): - endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}") + if ( + config + and config.region_name != AWS_REGION_US_EAST_1 + and region_name == AWS_REGION_US_EAST_1 + ): + config = config.merge(Config(region_name=region_name)) + + endpoint_url = endpoint_url or self._endpoint or get_service_endpoint() + if ( + endpoint_url + and service_name == "s3" + and re.match(r"https?://localhost(:[0-9]+)?", endpoint_url) + ): + endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}") # Prevent `PartialCredentialsError` when only access key ID is provided # The value of secret access key is insignificant and can be set to anything @@ -604,12 +617,12 @@ class ExternalAwsClientFactory(ClientFactory): def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> BaseClient: """ Build and return client for connections originating outside LocalStack and targeting AWS. @@ -682,11 +695,28 @@ def __init__( session: Session = None, config: Config = None, ): - super().__init__(use_ssl=True, verify=True, session=session, config=config) + if ca_cert := os.getenv("REQUESTS_CA_BUNDLE"): + LOG.debug("Creating External AWS Client with REQUESTS_CA_BUNDLE=%s", ca_cert) + + proxy_config = Config( + proxies={ + "http": localstack_config.OUTBOUND_HTTP_PROXY, + "https": localstack_config.OUTBOUND_HTTPS_PROXY, + } + ) + + super().__init__( + use_ssl=localstack_config.is_env_not_false("USE_SSL"), + verify=ca_cert or True, + session=session, + config=config.merge(proxy_config) if config else proxy_config, + ) def _get_client_post_hook(self, client: BaseClient) -> BaseClient: client = super()._get_client_post_hook(client) - client._endpoint.http_session = ExternalBypassDnsSession() + client._endpoint.http_session = ExternalBypassDnsSession( + verify=self._verify, proxies=self._config.proxies + ) return client diff --git a/localstack-core/localstack/aws/forwarder.py b/localstack-core/localstack/aws/forwarder.py index c25d4b90f6c09..b0f64817587da 100644 --- a/localstack-core/localstack/aws/forwarder.py +++ b/localstack-core/localstack/aws/forwarder.py @@ -3,10 +3,13 @@ DynamoDBLocal) from a service provider. """ -from typing import Any, Callable, Mapping, Optional, Union +from collections.abc import Callable, Mapping +from typing import Any from botocore.awsrequest import AWSPreparedRequest, prepare_request_dict from botocore.config import Config as BotoConfig +from botocore.model import OperationModel +from botocore.serialize import create_serializer from werkzeug.datastructures import Headers from localstack.aws.api.core import ( @@ -18,7 +21,7 @@ from localstack.aws.client import create_http_request, parse_response, raise_service_exception from localstack.aws.connect import connect_to from localstack.aws.skeleton import DispatchTable, create_dispatch_table -from localstack.aws.spec import load_service +from localstack.aws.spec import ProtocolName, load_service from localstack.constants import AWS_REGION_US_EAST_1 from localstack.http import Response from localstack.http.proxy import Proxy @@ -54,7 +57,7 @@ def __call__( self, context: RequestContext, service_request: ServiceRequest = None, - ) -> Optional[Union[ServiceResponse, Response]]: + ) -> ServiceResponse | Response | None: """Method to satisfy the ``ServiceRequestHandler`` protocol.""" return self.forward(context, service_request) @@ -62,7 +65,7 @@ def forward( self, context: RequestContext, service_request: ServiceRequest = None, - ) -> Optional[Union[ServiceResponse, Response]]: + ) -> ServiceResponse | Response | None: """ Forwards the given request to the backend configured by ``endpoint_url``. @@ -78,7 +81,7 @@ def forward( if not self.parse_response: return http_response parsed_response = parse_response( - context.operation, http_response, self.include_response_metadata + context.operation, context.protocol, http_response, self.include_response_metadata ) raise_service_exception(http_response, parsed_response) return parsed_response @@ -89,6 +92,7 @@ def new_request_context(self, original: RequestContext, service_request: Service action=original.operation.name, parameters=service_request, region=original.region, + protocol=original.protocol, ) # update the newly created context with non-payload specific request headers (the payload can differ from # the original request, f.e. it could be JSON encoded now while the initial request was CBOR encoded) @@ -183,7 +187,9 @@ def dispatch_to_backend( :raises ServiceException: if the dispatcher returned an error response """ http_response = http_request_dispatcher(context) - parsed_response = parse_response(context.operation, http_response, include_response_metadata) + parsed_response = parse_response( + context.operation, context.protocol, http_response, include_response_metadata + ) raise_service_exception(http_response, parsed_response) return parsed_response @@ -195,9 +201,10 @@ def dispatch_to_backend( def create_aws_request_context( service_name: str, action: str, + protocol: ProtocolName = None, parameters: Mapping[str, Any] = None, region: str = None, - endpoint_url: Optional[str] = None, + endpoint_url: str | None = None, ) -> RequestContext: """ This is a stripped-down version of what the botocore client does to perform an HTTP request from a client call. A @@ -209,6 +216,7 @@ def create_aws_request_context( :param service_name: the AWS service :param action: the action to invoke + :param protocol: the protocol to use :param parameters: the invocation parameters :param region: the region name (default is us-east-1) :param endpoint_url: the endpoint to call (defaults to localstack) @@ -221,6 +229,8 @@ def create_aws_request_context( service = load_service(service_name) operation = service.operation_model(action) + # TODO: remove this once every usage upstream has been removed + protocol = protocol or service.resolved_protocol # we re-use botocore internals here to serialize the HTTP request, # but deactivate validation (validation errors should be handled by the backend) @@ -242,8 +252,14 @@ def create_aws_request_context( endpoint_url = "http://localhost.localstack.cloud" # pre-process the request args (some params are modified using botocore event handlers) parameters = client._emit_api_params(parameters, operation, request_context) - request_dict = client._convert_to_request_dict( - parameters, operation, endpoint_url, context=request_context + + request_dict = _convert_to_request_dict_with_protocol( + client=client, + protocol=protocol, + api_params=parameters, + operation_model=operation, + endpoint_url=endpoint_url, + context=request_context, ) if auth_path := request_dict.get("auth_path"): @@ -265,7 +281,39 @@ def create_aws_request_context( context = RequestContext(request=create_http_request(aws_request)) context.service = service context.operation = operation + context.protocol = protocol context.region = region context.service_request = parameters return context + + +def _convert_to_request_dict_with_protocol( + client, + protocol: ProtocolName, + api_params: dict, + operation_model: OperationModel, + endpoint_url: str, + context: dict, + set_user_agent_header: bool = True, +) -> dict: + """ + This function is taken from botocore Client._convert_to_request_dict, but we are overriding the serializer + Botocore does not expose a way to create a client with a specific protocol, but we need this functionality + to support multi-protocols. + """ + serializer = create_serializer(protocol, include_validation=False) + request_dict = serializer.serialize_to_request(api_params, operation_model) + if not client._client_config.inject_host_prefix: + request_dict.pop("host_prefix", None) + if set_user_agent_header: + user_agent = client._user_agent_creator.to_string() + else: + user_agent = None + prepare_request_dict( + request_dict, + endpoint_url=endpoint_url, + user_agent=user_agent, + context=context, + ) + return request_dict diff --git a/localstack-core/localstack/aws/gateway.py b/localstack-core/localstack/aws/gateway.py index 6fd526b6014fc..34959d6d2d0b7 100644 --- a/localstack-core/localstack/aws/gateway.py +++ b/localstack-core/localstack/aws/gateway.py @@ -1,5 +1,3 @@ -import typing as t - from rolo.gateway import Gateway as RoloGateway from rolo.response import Response @@ -17,7 +15,7 @@ def __init__( response_handlers: list[Handler] = None, finalizers: list[Handler] = None, exception_handlers: list[ExceptionHandler] = None, - context_class: t.Type[RequestContext] = None, + context_class: type[RequestContext] = None, ) -> None: super().__init__( request_handlers, diff --git a/localstack-core/localstack/aws/handlers/analytics.py b/localstack-core/localstack/aws/handlers/analytics.py index 4e5bbfa8aa085..a824b4a9b24d2 100644 --- a/localstack-core/localstack/aws/handlers/analytics.py +++ b/localstack-core/localstack/aws/handlers/analytics.py @@ -1,6 +1,5 @@ import logging import threading -from typing import Optional from localstack import config from localstack.aws.api import RequestContext @@ -52,7 +51,7 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo ) ) - def _get_err_type(self, context: RequestContext, response: Response) -> Optional[str]: + def _get_err_type(self, context: RequestContext, response: Response) -> str | None: """ Attempts to re-use the existing service_response, or parse and return the error type from the response body, e.g. ``ResourceInUseException``. @@ -61,7 +60,7 @@ def _get_err_type(self, context: RequestContext, response: Response) -> Optional if context.service_exception: return context.service_exception.code - response = parse_response(context.operation, response) + response = parse_response(context.operation, context.protocol, response) return response["Error"]["Code"] except Exception: if config.DEBUG_ANALYTICS: diff --git a/localstack-core/localstack/aws/handlers/cors.py b/localstack-core/localstack/aws/handlers/cors.py index 13540e0165710..dc255abe36e6d 100644 --- a/localstack-core/localstack/aws/handlers/cors.py +++ b/localstack-core/localstack/aws/handlers/cors.py @@ -4,7 +4,6 @@ import logging import re -from typing import List, Set from urllib.parse import urlparse from werkzeug.datastructures import Headers @@ -83,7 +82,7 @@ ] -def _get_allowed_cors_internal_domains() -> Set[str]: +def _get_allowed_cors_internal_domains() -> set[str]: """ Construct the list of allowed internal domains for CORS enforcement purposes Defined as function to allow easier testing with monkeypatch of config values @@ -94,7 +93,7 @@ def _get_allowed_cors_internal_domains() -> Set[str]: _ALLOWED_INTERNAL_DOMAINS = _get_allowed_cors_internal_domains() -def _get_allowed_cors_ports() -> Set[int]: +def _get_allowed_cors_ports() -> set[int]: """ Construct the list of allowed ports for CORS enforcement purposes Defined as function to allow easier testing with monkeypatch of config values @@ -105,7 +104,7 @@ def _get_allowed_cors_ports() -> Set[int]: _ALLOWED_INTERNAL_PORTS = _get_allowed_cors_ports() -def _get_allowed_cors_origins() -> List[str]: +def _get_allowed_cors_origins() -> list[str]: """Construct the list of allowed origins for CORS enforcement purposes""" result = [ # allow access from Web app and localhost domains @@ -210,7 +209,7 @@ def is_cors_origin_allowed(headers: Headers) -> bool: return True @staticmethod - def _is_in_allowed_origins(allowed_origins: List[str], origin: str) -> bool: + def _is_in_allowed_origins(allowed_origins: list[str], origin: str) -> bool: """Returns true if the `origin` is in the `allowed_origins`.""" for allowed_origin in allowed_origins: if allowed_origin == "*" or origin == allowed_origin: diff --git a/localstack-core/localstack/aws/handlers/exceptions.py b/localstack-core/localstack/aws/handlers/exceptions.py new file mode 100644 index 0000000000000..54c65d493c0ba --- /dev/null +++ b/localstack-core/localstack/aws/handlers/exceptions.py @@ -0,0 +1,2 @@ +class PluginNotIncludedInUserLicenseError(NotImplementedError): + pass diff --git a/localstack-core/localstack/aws/handlers/internal_requests.py b/localstack-core/localstack/aws/handlers/internal_requests.py index 9e4b0c35fe77b..f9055b3ca2033 100644 --- a/localstack-core/localstack/aws/handlers/internal_requests.py +++ b/localstack-core/localstack/aws/handlers/internal_requests.py @@ -20,7 +20,12 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo try: dto = MappingProxyType(load_dto(header)) except Exception as e: - LOG.exception("Error loading request parameters '%s', Error: %s", header, e) + LOG.error( + "Error loading request parameters '%s', Error: %s", + header, + e, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) return context.internal_request_params = dto diff --git a/localstack-core/localstack/aws/handlers/logging.py b/localstack-core/localstack/aws/handlers/logging.py index 2113b67fa5176..3100ab5df3451 100644 --- a/localstack-core/localstack/aws/handlers/logging.py +++ b/localstack-core/localstack/aws/handlers/logging.py @@ -2,7 +2,6 @@ import logging from functools import cached_property -from typing import Type from localstack.aws.api import RequestContext, ServiceException from localstack.aws.chain import ExceptionHandler, HandlerChain @@ -22,6 +21,18 @@ class ExceptionLogger(ExceptionHandler): def __init__(self, logger=None): self.logger = logger or LOG + try: + import moto.core.exceptions + + self._skip_exceptions( + ServiceException, + moto.core.exceptions.ServiceException, + moto.core.exceptions.RESTError, + ) + except (ModuleNotFoundError, AttributeError): + # Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image. + self._skip_exceptions = (ServiceException,) + def __call__( self, chain: HandlerChain, @@ -29,10 +40,12 @@ def __call__( context: RequestContext, response: Response, ): - if isinstance(exception, ServiceException): + if isinstance(exception, self._skip_exceptions): # We do not want to log an error/stacktrace if the handler is working as expected, but chooses to throw - # a service exception + # a service exception. It may also throw a Moto ServiceException, which should not be logged either + # because ServiceExceptionHandler understands it. return + if self.logger.isEnabledFor(level=logging.DEBUG): self.logger.exception("exception during call chain", exc_info=exception) else: @@ -71,7 +84,7 @@ def internal_http_logger(self): ) # make sure loggers are loaded after logging config is loaded - def _prepare_logger(self, logger: logging.Logger, formatter: Type): + def _prepare_logger(self, logger: logging.Logger, formatter: type): if logger.isEnabledFor(logging.DEBUG): logger.propagate = False handler = create_default_handler(logger.level) diff --git a/localstack-core/localstack/aws/handlers/metric_handler.py b/localstack-core/localstack/aws/handlers/metric_handler.py index 6a1ad8f16b982..0dd921a2d0a7c 100644 --- a/localstack-core/localstack/aws/handlers/metric_handler.py +++ b/localstack-core/localstack/aws/handlers/metric_handler.py @@ -1,10 +1,15 @@ +import csv import logging -from typing import List, Optional +import os +from datetime import datetime +from pathlib import Path from localstack import config from localstack.aws.api import RequestContext from localstack.aws.chain import HandlerChain +from localstack.constants import ENV_INTERNAL_TEST_STORE_METRICS_PATH from localstack.http import Response +from localstack.utils.strings import short_uid LOG = logging.getLogger(__name__) @@ -16,7 +21,7 @@ class MetricHandlerItem: request_id: str request_context: RequestContext - parameters_after_parse: Optional[List[str]] + parameters_after_parse: list[str] | None def __init__(self, request_contex: RequestContext) -> None: super().__init__() @@ -35,7 +40,7 @@ class Metric: headers: str parameters: str status_code: int - response_code: Optional[str] + response_code: str | None exception: str origin: str xfail: bool @@ -134,10 +139,36 @@ def __eq__(self, other): class MetricHandler: - metric_data: List[Metric] = [] + metric_data: list[Metric] = [] def __init__(self) -> None: self.metrics_handler_items = {} + self.local_filename = None + + if self.should_store_metric_locally(): + self.local_filename = self.create_local_file() + + @staticmethod + def should_store_metric_locally() -> bool: + return config.is_collect_metrics_mode() and config.store_test_metrics_in_local_filesystem() + + @staticmethod + def create_local_file(): + folder = Path( + os.environ.get(ENV_INTERNAL_TEST_STORE_METRICS_PATH, "/tmp/localstack-metrics") + ) + if not folder.exists(): + folder.mkdir(parents=True, exist_ok=True) + LOG.debug("Metric reports will be stored in %s", folder) + filename = ( + folder + / f"metric-report-raw-data-{datetime.utcnow().strftime('%Y-%m-%d__%H_%M_%S')}-{short_uid()}.csv" + ) + with open(filename, "w") as fd: + LOG.debug("Creating new metric data file %s", filename) + writer = csv.writer(fd) + writer.writerow(Metric.RAW_DATA_HEADER) + return filename def create_metric_handler_item( self, chain: HandlerChain, context: RequestContext, response: Response @@ -195,7 +226,15 @@ def update_metric_collection( ) # refrain from adding duplicates if metric not in MetricHandler.metric_data: - MetricHandler.metric_data.append(metric) + self.append_metric(metric) # cleanup del self.metrics_handler_items[context] + + def append_metric(self, metric: Metric): + if self.should_store_metric_locally(): + with open(self.local_filename, "a") as fd: + writer = csv.writer(fd) + writer.writerow(metric) + else: + MetricHandler.metric_data.append(metric) diff --git a/localstack-core/localstack/aws/handlers/service.py b/localstack-core/localstack/aws/handlers/service.py index edef0699c3539..21cb228d9ab2c 100644 --- a/localstack-core/localstack/aws/handlers/service.py +++ b/localstack-core/localstack/aws/handlers/service.py @@ -2,23 +2,26 @@ import logging import traceback +import types from collections import defaultdict -from typing import Any, Dict, Union +from typing import Any from botocore.model import OperationModel, ServiceModel from localstack import config from localstack.http import Response -from localstack.utils.coverage_docs import get_coverage_link_for_service +from ...utils.catalog.plugins import get_aws_catalog from ..api import CommonServiceException, RequestContext, ServiceException from ..api.core import ServiceOperation +from ..catalog_exceptions import get_service_availability_exception from ..chain import CompositeResponseHandler, ExceptionHandler, Handler, HandlerChain from ..client import parse_response, parse_service_exception from ..protocol.parser import RequestParser, create_parser from ..protocol.serializer import create_serializer -from ..protocol.service_router import determine_aws_service_model +from ..protocol.service_router import determine_aws_protocol, determine_aws_service_model from ..skeleton import Skeleton, create_skeleton +from .exceptions import PluginNotIncludedInUserLicenseError LOG = logging.getLogger(__name__) @@ -33,6 +36,8 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo # example). If it is already set, we can skip the parsing of the request. It is very important for S3, because # parsing the request will consume the data stream and prevent streaming. if context.service: + if not context.protocol: + context.protocol = determine_aws_protocol(context.request, context.service) return service_model = determine_aws_service_model(context.request) @@ -41,6 +46,7 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo return context.service = service_model + context.protocol = determine_aws_protocol(context.request, service_model) class ServiceRequestParser(Handler): @@ -49,10 +55,10 @@ class ServiceRequestParser(Handler): already be resolved in the RequestContext (e.g., through a ServiceNameParser) """ - parsers: Dict[str, RequestParser] + parsers: dict[str, RequestParser] def __init__(self): - self.parsers = dict() + self.parsers = {} def __call__(self, chain: HandlerChain, context: RequestContext, response: Response): # determine service @@ -63,7 +69,7 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo return self.parse_and_enrich(context) def parse_and_enrich(self, context: RequestContext): - parser = create_parser(context.service) + parser = create_parser(context.service, context.protocol) operation, instance = parser.parse(context.request) # enrich context @@ -89,10 +95,10 @@ class ServiceRequestRouter(Handler): Routes ServiceOperations to Handlers. """ - handlers: Dict[ServiceOperation, Handler] + handlers: dict[ServiceOperation, Handler] def __init__(self): - self.handlers = dict() + self.handlers = {} def __call__(self, chain: HandlerChain, context: RequestContext, response: Response): if not context.service: @@ -118,7 +124,7 @@ def add_handler(self, key: ServiceOperation, handler: Handler): self.handlers[key] = handler - def add_provider(self, provider: Any, service: Union[str, ServiceModel]): + def add_provider(self, provider: Any, service: str | ServiceModel): self.add_skeleton(create_skeleton(service, provider)) def add_skeleton(self, skeleton: Skeleton): @@ -137,7 +143,7 @@ def create_not_implemented_response(self, context): operation_name = operation.name message = f"no handler for operation '{operation_name}' on service '{service_name}'" error = CommonServiceException("InternalFailure", message, status_code=501) - serializer = create_serializer(context.service) + serializer = create_serializer(context.service, context.protocol) return serializer.serialize_error_to_response( error, operation, context.request.headers, context.request_id ) @@ -153,6 +159,18 @@ class ServiceExceptionSerializer(ExceptionHandler): def __init__(self): self.handle_internal_failures = True + self._moto_service_exception = types.EllipsisType + self._moto_rest_error = types.EllipsisType + + try: + import moto.core.exceptions + + self._moto_service_exception = moto.core.exceptions.ServiceException + self._moto_rest_error = moto.core.exceptions.RESTError + except (ModuleNotFoundError, AttributeError) as exc: + # Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image. + LOG.debug("Unable to set up Moto exception translation: %s", exc) + def __call__( self, chain: HandlerChain, @@ -173,46 +191,72 @@ def create_exception_response(self, exception: Exception, context: RequestContex error = exception if operation and isinstance(exception, NotImplementedError): - action_name = operation.name + operation_name = operation.name exception_message: str | None = exception.args[0] if exception.args else None - message = exception_message or get_coverage_link_for_service(service_name, action_name) + if exception_message: + message = exception_message + error = CommonServiceException("InternalFailure", message, status_code=501) + else: + catalog = get_aws_catalog() + if isinstance(exception, PluginNotIncludedInUserLicenseError): + # Operation name is provided when a plugin fails to load, although it is not relevant. + # In such cases, we should return an error without the operation name + service_status = catalog.get_aws_service_status( + service_name, operation_name=None + ) + else: + service_status = catalog.get_aws_service_status(service_name, operation_name) + error = get_service_availability_exception( + service_name, operation_name, service_status + ) + message = error.message LOG.info(message) - error = CommonServiceException("InternalFailure", message, status_code=501) context.service_exception = error + elif isinstance(exception, self._moto_service_exception): + # Translate Moto ServiceException to native ServiceException if Moto is available. + # This allows handler chain to gracefully handles Moto errors when provider handlers invoke Moto methods directly. + error = CommonServiceException( + code=exception.code, + message=exception.message, + ) + elif isinstance(exception, self._moto_rest_error): + # Some Moto exceptions (like ones raised by EC2) are of type RESTError. + error = CommonServiceException( + code=exception.error_type, + message=exception.message, + ) elif not isinstance(exception, ServiceException): if not self.handle_internal_failures: return - if config.DEBUG: - exception = "".join( + if config.INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE: + message = "".join( traceback.format_exception( type(exception), value=exception, tb=exception.__traceback__ ) ) + else: + message = str(exception) # wrap exception for serialization if operation: operation_name = operation.name - msg = "exception while calling %s.%s: %s" % ( - service_name, - operation_name, - exception, - ) + msg = f"exception while calling {service_name}.{operation_name}: {message}" else: # just use any operation for mocking purposes (the parser needs it to populate the default response) operation = context.service.operation_model(context.service.operation_names[0]) - msg = "exception while calling %s with unknown operation: %s" % ( - service_name, - exception, - ) + msg = f"exception while calling {service_name} with unknown operation: {message}" status_code = 501 if config.FAIL_FAST else 500 - error = CommonServiceException("InternalError", msg, status_code=status_code) - context.service_exception = error + error = CommonServiceException( + "InternalError", msg, status_code=status_code + ).with_traceback(exception.__traceback__) + + context.service_exception = error - serializer = create_serializer(context.service) # TODO: serializer cache + serializer = create_serializer(context.service, context.protocol) return serializer.serialize_error_to_response( error, operation, context.request.headers, context.request_id ) @@ -255,7 +299,9 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo return # in this case we need to parse the raw response - parsed = parse_response(context.operation, response, include_response_metadata=False) + parsed = parse_response( + context.operation, context.protocol, response, include_response_metadata=False + ) if service_exception := parse_service_exception(response, parsed): context.service_exception = service_exception else: @@ -278,7 +324,7 @@ class ServiceResponseHandlers(Handler): are only called if the request context has a service, and there are handlers for that particular service. """ - handlers: Dict[str, CompositeResponseHandler] + handlers: dict[str, CompositeResponseHandler] def __init__(self): self.handlers = defaultdict(CompositeResponseHandler) diff --git a/localstack-core/localstack/aws/handlers/service_plugin.py b/localstack-core/localstack/aws/handlers/service_plugin.py index c28bbb7e341c1..dac5e483eee67 100644 --- a/localstack-core/localstack/aws/handlers/service_plugin.py +++ b/localstack-core/localstack/aws/handlers/service_plugin.py @@ -3,6 +3,8 @@ import logging import threading +from plux import PluginDisabled + from localstack.http import Response from localstack.services.plugins import Service, ServiceManager from localstack.utils.sync import SynchronizedDefaultDict @@ -11,7 +13,7 @@ from ..api import RequestContext from ..chain import Handler, HandlerChain from ..protocol.service_router import determine_aws_service_model_for_data_plane -from .service import ServiceRequestRouter +from .service import PluginNotIncludedInUserLicenseError, ServiceRequestRouter LOG = logging.getLogger(__name__) @@ -51,9 +53,13 @@ def require_service(self, _: HandlerChain, context: RequestContext, response: Re ) request_router = self.service_request_router - - # Ensure the Service is loaded and set to ServiceState.RUNNING if not in an erroneous state. - service_plugin: Service = self.service_manager.require(service_name) + try: + # Ensure the Service is loaded and set to ServiceState.RUNNING if not in an erroneous state. + service_plugin: Service = self.service_manager.require(service_name) + except PluginDisabled as e: + if e.reason == "This feature is not part of the active license agreement": + raise PluginNotIncludedInUserLicenseError() + raise with self.service_locks[context.service.service_name]: # try again to avoid race conditions diff --git a/localstack-core/localstack/aws/mocking.py b/localstack-core/localstack/aws/mocking.py index 2231b76eddbfb..b2bbcb9f0053d 100644 --- a/localstack-core/localstack/aws/mocking.py +++ b/localstack-core/localstack/aws/mocking.py @@ -4,7 +4,7 @@ import re from datetime import date, datetime from functools import lru_cache, singledispatch -from typing import Dict, List, Optional, Set, Tuple, Union, cast +from typing import cast import botocore import networkx @@ -32,18 +32,9 @@ "boolean", } -Instance = Union[ - Dict[str, "Instance"], - List["Instance"], - str, - bytes, - map, - list, - float, - int, - bool, - date, -] +Instance = ( + dict[str, "Instance"] | list["Instance"] | str | bytes | map | list | float | int | bool | date +) # https://github.com/boto/botocore/issues/2623 StringShape.METADATA_ATTRS.append("pattern") @@ -66,14 +57,14 @@ class ShapeGraph(networkx.DiGraph): - root: Union[ListShape, StructureShape, MapShape] - cycle: List[Tuple[str, str]] - cycle_shapes: List[str] + root: ListShape | StructureShape | MapShape + cycle: list[tuple[str, str]] + cycle_shapes: list[str] def populate_graph(graph: networkx.DiGraph, root: Shape): - stack: List[Shape] = [root] - visited: Set[str] = set() + stack: list[Shape] = [root] + visited: set[str] = set() while stack: cur = stack.pop() @@ -108,7 +99,7 @@ def shape_graph(root: Shape) -> ShapeGraph: graph.root = root populate_graph(graph, root) - cycles = list() + cycles = [] shapes = set() for node in graph.nodes: try: @@ -230,14 +221,14 @@ def sanitize_arn_pattern(pattern: str) -> str: @singledispatch -def generate_instance(shape: Shape, graph: ShapeGraph) -> Optional[Instance]: +def generate_instance(shape: Shape, graph: ShapeGraph) -> Instance | None: if shape is None: return None - raise ValueError("could not generate shape for type %s" % shape.type_name) + raise ValueError(f"could not generate shape for type {shape.type_name}") @generate_instance.register -def _(shape: StructureShape, graph: ShapeGraph) -> Dict[str, Instance]: +def _(shape: StructureShape, graph: ShapeGraph) -> dict[str, Instance]: if shape.is_tagged_union: k, v = random.choice(list(shape.members.items())) members = {k: v} @@ -255,14 +246,14 @@ def _(shape: StructureShape, graph: ShapeGraph) -> Dict[str, Instance]: @generate_instance.register -def _(shape: ListShape, graph: ShapeGraph) -> List[Instance]: +def _(shape: ListShape, graph: ShapeGraph) -> list[Instance]: if shape.name in graph.cycle_shapes: return [] return [generate_instance(shape.member, graph) for _ in range(shape.metadata.get("min", 1))] @generate_instance.register -def _(shape: MapShape, graph: ShapeGraph) -> Dict[str, Instance]: +def _(shape: MapShape, graph: ShapeGraph) -> dict[str, Instance]: if shape.name in graph.cycle_shapes: return {} return {generate_instance(shape.key, graph): generate_instance(shape.value, graph)} @@ -379,7 +370,7 @@ def _(shape: StringShape, graph: ShapeGraph) -> str: @generate_instance.register -def _(shape: Shape, graph: ShapeGraph) -> Union[int, float, bool, bytes, date]: +def _(shape: Shape, graph: ShapeGraph) -> int | float | bool | bytes | date: if shape.type_name in ["integer", "long"]: return shape.metadata.get("min", 1) if shape.type_name in ["float", "double"]: @@ -392,7 +383,7 @@ def _(shape: Shape, graph: ShapeGraph) -> Union[int, float, bool, bytes, date]: if shape.type_name == "timestamp": return datetime.now() - raise ValueError("unknown type %s" % shape.type_name) + raise ValueError(f"unknown type {shape.type_name}") def generate_response(operation: OperationModel): @@ -427,7 +418,7 @@ def create_mocking_dispatch_table(service) -> DispatchTable: return dispatch_table -@lru_cache() +@lru_cache def get_mocking_skeleton(service: str) -> Skeleton: service = load_service(service) return Skeleton(service, create_mocking_dispatch_table(service)) diff --git a/localstack-core/localstack/aws/patches.py b/localstack-core/localstack/aws/patches.py index f73067ad7f878..a7a1f9b7dd843 100644 --- a/localstack-core/localstack/aws/patches.py +++ b/localstack-core/localstack/aws/patches.py @@ -20,12 +20,12 @@ def new_instance(meta, name, bases, dct): cls = super(InstanceTrackerMeta, meta).__new__(meta, name, bases, dct) if name == "BaseModel": return cls - cls.instances = [] + cls.instances_tracked = [] return cls @patch(BaseModel.__new__, pass_target=False) def new_basemodel(cls, *args, **kwargs): - # skip cls.instances.append(..) which is done by the original/upstream constructor + # skip cls.instances_tracked.append(..) which is done by the original/upstream constructor instance = super(BaseModel, cls).__new__(cls) return instance diff --git a/localstack-core/localstack/aws/protocol/op_router.py b/localstack-core/localstack/aws/protocol/op_router.py index f4c5f1019aa02..ce746341ae88d 100644 --- a/localstack-core/localstack/aws/protocol/op_router.py +++ b/localstack-core/localstack/aws/protocol/op_router.py @@ -1,5 +1,6 @@ from collections import defaultdict -from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple +from collections.abc import Mapping +from typing import Any, NamedTuple from urllib.parse import parse_qs, unquote from botocore.model import OperationModel, ServiceModel, StructureShape @@ -24,8 +25,8 @@ class _HttpOperation(NamedTuple): operation: OperationModel path: str method: str - query_args: Mapping[str, List[str]] - header_args: List[str] + query_args: Mapping[str, list[str]] + header_args: list[str] deprecated: bool @staticmethod @@ -49,11 +50,11 @@ def from_operation(op: OperationModel) -> "_HttpOperation": path_query = uri.split("?") path = path_query[0] header_args = [] - query_args: Dict[str, List[str]] = {} + query_args: dict[str, list[str]] = {} if len(path_query) > 1: # parse the query args of the request URI (they are mandatory) - query_args: Dict[str, List[str]] = parse_qs(path_query[1], keep_blank_values=True) + query_args: dict[str, list[str]] = parse_qs(path_query[1], keep_blank_values=True) # for mandatory keys without values, keep an empty list (instead of [''] - the result of parse_qs) query_args = {k: filter(None, v) for k, v in query_args.items()} @@ -85,8 +86,8 @@ class _RequiredArgsRule: """ endpoint: Any - required_query_args: Optional[Mapping[str, List[Any]]] - required_header_args: List[str] + required_query_args: Mapping[str, list[Any]] | None + required_header_args: list[str] match_score: int def __init__(self, operation: _HttpOperation) -> None: @@ -139,7 +140,7 @@ class _RequestMatchingRule(StrictMethodRule): """ def __init__( - self, string: str, operations: List[_HttpOperation], method: str, **kwargs + self, string: str, operations: list[_HttpOperation], method: str, **kwargs ) -> None: super().__init__(string=string, method=method, **kwargs) # Create a rule which checks all required arguments (not only the path and method) @@ -174,7 +175,7 @@ def _create_service_map(service: ServiceModel) -> Map: rules = [] # group all operations by their path and method - path_index: Dict[(str, str), List[_HttpOperation]] = defaultdict(list) + path_index: dict[(str, str), list[_HttpOperation]] = defaultdict(list) for op in ops: http_op = _HttpOperation.from_operation(op) path_index[(http_op.path, http_op.method)].append(http_op) @@ -216,7 +217,7 @@ class RestServiceOperationRouter: def __init__(self, service: ServiceModel): self._map = _create_service_map(service) - def match(self, request: Request) -> Tuple[OperationModel, Mapping[str, Any]]: + def match(self, request: Request) -> tuple[OperationModel, Mapping[str, Any]]: """ Matches the given request to the operation it targets (or raises an exception if no operation matches). diff --git a/localstack-core/localstack/aws/protocol/parser.py b/localstack-core/localstack/aws/protocol/parser.py index 96fd3d16cf0aa..6bbd7d0edcc8d 100644 --- a/localstack-core/localstack/aws/protocol/parser.py +++ b/localstack-core/localstack/aws/protocol/parser.py @@ -19,22 +19,23 @@ │RequestParser│ └─────────────┘ ▲ ▲ ▲ - ┌─────────────────┘ │ └────────────────────┐ - ┌────────┴─────────┐ ┌─────────┴───────────┐ ┌──────────┴──────────┐ - │QueryRequestParser│ │BaseRestRequestParser│ │BaseJSONRequestParser│ - └──────────────────┘ └─────────────────────┘ └─────────────────────┘ - ▲ ▲ ▲ ▲ ▲ - ┌───────┴────────┐ ┌─────────┴──────────┐ │ │ │ - │EC2RequestParser│ │RestXMLRequestParser│ │ │ │ - └────────────────┘ └────────────────────┘ │ │ │ - ┌────────────────┴───┴┐ ┌────────┴────────┐ - │RestJSONRequestParser│ │JSONRequestParser│ - └─────────────────────┘ └─────────────────┘ + ┌─────────────────┘ │ └────────────────────┬───────────────────────┬───────────────────────┐ + ┌────────┴─────────┐ ┌─────────┴───────────┐ ┌──────────┴──────────┐ ┌──────────┴──────────┐ ┌──────────┴───────────┐ + │QueryRequestParser│ │BaseRestRequestParser│ │BaseJSONRequestParser│ │BaseCBORRequestParser│ │BaseRpcV2RequestParser│ + └──────────────────┘ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ └──────────────────────┘ + ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ + ┌───────┴────────┐ ┌─────────┴──────────┐ │ │ ┌────────┴────────┐ │ ┌───┴─────────────┴────┐ + │EC2RequestParser│ │RestXMLRequestParser│ │ │ │JSONRequestParser│ │ │RpcV2CBORRequestParser│ + └────────────────┘ └────────────────────┘ │ │ └─────────────────┘ │ └──────────────────────┘ + ┌────────────────┴───┴┐ ▲ │ + │RestJSONRequestParser│ ┌───┴──────┴──────┐ + └─────────────────────┘ │CBORRequestParser│ + └─────────────────┘ :: The ``RequestParser`` contains the logic that is used among all the different protocols (``query``, ``json``, ``rest-json``, ``rest-xml``, -and ``ec2``). +``cbor`` and ``ec2``). The relation between the different protocols is described in the ``serializer``. @@ -44,13 +45,21 @@ which is shared among all different protocols. * The ``BaseRestRequestParser`` contains the logic for the REST protocol specifics (i.e. specific HTTP metadata parsing). +* The ``BaseRpcV2RequestParser`` contains the logic for the RPC v2 + protocol specifics (special path routing, no logic about body decoding) * The ``BaseJSONRequestParser`` contains the logic for the JSON body parsing. +* The ``BaseCBORRequestParser`` contains the logic for the CBOR body + parsing. * The ``RestJSONRequestParser`` inherits the ReST specific logic from the ``BaseRestRequestParser`` and the JSON body parsing from the ``BaseJSONRequestParser``. -* The ``QueryRequestParser``, ``RestXMLRequestParser``, and the - ``JSONRequestParser`` have a conventional inheritance structure. +* The ``CBORRequestParser`` inherits the ``json``-protocol specific + logic from the ``JSONRequestParser`` and the CBOR body parsing + from the ``BaseCBORRequestParser``. +* The ``QueryRequestParser``, ``RestXMLRequestParser``, + ``RpcV2CBORRequestParser`` and ``JSONRequestParser`` have a + conventional inheritance structure. The services and their protocols are defined by using AWS's Smithy (a language to define services in a - somewhat - protocol-agnostic @@ -66,10 +75,14 @@ import base64 import datetime import functools +import io +import os import re +import struct from abc import ABC +from collections.abc import Mapping from email.utils import parsedate_to_datetime -from typing import IO, Any, Dict, List, Mapping, Optional, Tuple, Union +from typing import IO, Any from xml.etree import ElementTree as ETree import dateutil.parser @@ -88,6 +101,7 @@ from werkzeug.exceptions import BadRequest, NotFound from localstack.aws.protocol.op_router import RestServiceOperationRouter +from localstack.aws.spec import ProtocolName from localstack.http import Request @@ -107,7 +121,7 @@ def _get_text_content( self, request: Request, shape: Shape, - node_or_string: Union[ETree.Element, str], + node_or_string: ETree.Element | str, uri_params: Mapping[str, Any] = None, ): if hasattr(node_or_string, "text"): @@ -203,7 +217,7 @@ def __init__(self, service: ServiceModel) -> None: self.service = service @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: """ Determines which operation the request was aiming for and parses the incoming request such that the resulting dictionary can be used to invoke the service's function implementation. @@ -233,13 +247,21 @@ def _parse_shape( if location is not None: if location == "header": header_name = shape.serialization.get("name") - payload = request.headers.get(header_name) - if payload and shape.type_name == "list": + if shape.type_name == "list": # headers may contain a comma separated list of values (e.g., the ObjectAttributes member in # s3.GetObjectAttributes), so we prepare it here for the handler, which will be `_parse_list`. # Header lists can contain optional whitespace, so we strip it # https://www.rfc-editor.org/rfc/rfc9110.html#name-lists-rule-abnf-extension - payload = [value.strip() for value in payload.split(",")] + # It can also directly contain a list of headers + # See https://datatracker.ietf.org/doc/html/rfc2616 + payload = request.headers.getlist(header_name) or None + if payload: + headers = ",".join(payload) + payload = [value.strip() for value in headers.split(",")] + + else: + payload = request.headers.get(header_name) + elif location == "headers": payload = self._parse_header_map(shape, request.headers) # shapes with the location trait "headers" only contain strings and are not further processed @@ -256,12 +278,12 @@ def _parse_shape( if uri_param_name in uri_params: payload = uri_params[uri_param_name] else: - raise UnknownParserError("Unknown shape location '%s'." % location) + raise UnknownParserError(f"Unknown shape location '{location}'.") else: # If we don't have to use a specific location, we use the node payload = node - fn_name = "_parse_%s" % shape.type_name + fn_name = f"_parse_{shape.type_name}" handler = getattr(self, fn_name, self._noop_parser) try: return handler(request, shape, payload, uri_params) if payload is not None else None @@ -313,7 +335,7 @@ def _parse_boolean(self, _, __, node: str, ___) -> bool: return True if value == "false": return False - raise ValueError("cannot parse boolean value %s" % node) + raise ValueError(f"cannot parse boolean value {node}") @_text_content def _noop_parser(self, _, __, node: Any, ___): @@ -323,11 +345,11 @@ def _noop_parser(self, _, __, node: Any, ___): _parse_double = _parse_float _parse_long = _parse_integer - def _convert_str_to_timestamp(self, value: str, timestamp_format=None): + def _convert_str_to_timestamp(self, value: str, timestamp_format=None) -> datetime.datetime: if timestamp_format is None: timestamp_format = self.TIMESTAMP_FORMAT timestamp_format = timestamp_format.lower() - converter = getattr(self, "_timestamp_%s" % timestamp_format) + converter = getattr(self, f"_timestamp_{timestamp_format}") final_value = converter(value) return final_value @@ -337,11 +359,11 @@ def _timestamp_iso8601(date_string: str) -> datetime.datetime: @staticmethod def _timestamp_unixtimestamp(timestamp_string: str) -> datetime.datetime: - return datetime.datetime.utcfromtimestamp(int(timestamp_string)) + return datetime.datetime.fromtimestamp(int(timestamp_string), tz=datetime.UTC) @staticmethod def _timestamp_unixtimestampmillis(timestamp_string: str) -> datetime.datetime: - return datetime.datetime.utcfromtimestamp(float(timestamp_string) / 1000) + return datetime.datetime.fromtimestamp(float(timestamp_string) / 1000, tz=datetime.UTC) @staticmethod def _timestamp_rfc822(datetime_string: str) -> datetime.datetime: @@ -367,7 +389,7 @@ class QueryRequestParser(RequestParser): """ @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: instance = request.values if "Action" not in instance: raise ProtocolParserError( @@ -510,7 +532,7 @@ def _parse_list( # We collect the list value as well as the integer indicating the list position so we can # later sort the list by the position, in case they attribute values are unordered - result: List[Tuple[int, Any]] = [] + result: list[tuple[int, Any]] = [] i = 0 while True: @@ -559,7 +581,7 @@ def __init__(self, service: ServiceModel) -> None: self._operation_router = RestServiceOperationRouter(service) @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: try: operation, uri_params = self._operation_router.match(request) except NotFound as e: @@ -578,7 +600,7 @@ def _parse_payload( self, request: Request, shape: Shape, - member_shapes: Dict[str, Shape], + member_shapes: dict[str, Shape], uri_params: Mapping[str, Any], final_parsed: dict, ) -> None: @@ -662,7 +684,7 @@ class RestXMLRequestParser(BaseRestRequestParser): """ def __init__(self, service_model: ServiceModel): - super(RestXMLRequestParser, self).__init__(service_model) + super().__init__(service_model) self.ignore_get_body_errors = True self._namespace_re = re.compile("{.*}") @@ -731,7 +753,7 @@ def _parse_map( elif tag_name == value_location_name: val_name = self._parse_shape(request, value_shape, single_pair, uri_params) else: - raise ProtocolParserError("Unknown tag: %s" % tag_name) + raise ProtocolParserError(f"Unknown tag: {tag_name}") parsed[key_name] = val_name return parsed @@ -749,7 +771,7 @@ def _parse_list( # it's flattened, and if it's not, then we make it a one element list. if shape.serialization.get("flattened") and not isinstance(node, list): node = [node] - return super(RestXMLRequestParser, self)._parse_list(request, shape, node, uri_params) + return super()._parse_list(request, shape, node, uri_params) def _node_tag(self, node: ETree.Element) -> str: return self._namespace_re.sub("", node.tag) @@ -777,11 +799,11 @@ def _parse_xml_string_to_dom(xml_string: str) -> ETree.Element: root = parser.close() except ETree.ParseError as e: raise ProtocolParserError( - "Unable to parse request (%s), invalid XML received:\n%s" % (e, xml_string) + f"Unable to parse request ({e}), invalid XML received:\n{xml_string}" ) from e return root - def _build_name_to_xml_node(self, parent_node: Union[list, ETree.Element]) -> dict: + def _build_name_to_xml_node(self, parent_node: list | ETree.Element) -> dict: # If the parent node is actually a list. We should not be trying # to serialize it to a dictionary. Instead, return the first element # in the list. @@ -824,9 +846,9 @@ def _parse_structure( self, request: Request, shape: StructureShape, - value: Optional[dict], + value: dict | None, uri_params: Mapping[str, Any] = None, - ) -> Optional[dict]: + ) -> dict | None: if shape.is_document_type: final_parsed = value else: @@ -849,9 +871,9 @@ def _parse_map( self, request: Request, shape: MapShape, - value: Optional[dict], + value: dict | None, uri_params: Mapping[str, Any] = None, - ) -> Optional[dict]: + ) -> dict | None: if value is None: return None parsed = {} @@ -916,7 +938,7 @@ class JSONRequestParser(BaseJSONRequestParser): """ @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: target = request.headers["X-Amz-Target"] # assuming that the last part of the target string (e.g., "x.y.z.MyAction") contains the operation name operation_name = target.rpartition(".")[2] @@ -967,6 +989,410 @@ def _create_event_stream(self, request: Request, shape: Shape) -> Any: raise NotImplementedError +class BaseCBORRequestParser(RequestParser, ABC): + """ + The ``BaseCBORRequestParser`` is the base class for all CBOR-based AWS service protocols. + This base-class handles parsing the payload / body as CBOR. + """ + + INDEFINITE_ITEM_ADDITIONAL_INFO = 31 + BREAK_CODE = 0xFF + # timestamp format for requests with CBOR content type + TIMESTAMP_FORMAT = "unixtimestamp" + + @functools.cached_property + def major_type_to_parsing_method_map(self): + return { + 0: self._parse_type_unsigned_integer, + 1: self._parse_type_negative_integer, + 2: self._parse_type_byte_string, + 3: self._parse_type_text_string, + 4: self._parse_type_array, + 5: self._parse_type_map, + 6: self._parse_type_tag, + 7: self._parse_type_simple_and_float, + } + + @staticmethod + def get_peekable_stream_from_bytes(_bytes: bytes) -> io.BufferedReader: + return io.BufferedReader(io.BytesIO(_bytes)) + + def parse_data_item(self, stream: io.BufferedReader) -> Any: + # CBOR data is divided into "data items", and each data item starts + # with an initial byte that describes how the following bytes should be parsed + initial_byte = self._read_bytes_as_int(stream, 1) + # The highest order three bits of the initial byte describe the CBOR major type + major_type = initial_byte >> 5 + # The lowest order 5 bits of the initial byte tells us more information about + # how the bytes should be parsed that will be used + additional_info: int = initial_byte & 0b00011111 + + if major_type in self.major_type_to_parsing_method_map: + method = self.major_type_to_parsing_method_map[major_type] + return method(stream, additional_info) + else: + raise ProtocolParserError( + f"Unsupported initial byte found for data item- " + f"Major type:{major_type}, Additional info: " + f"{additional_info}" + ) + + # Major type 0 - unsigned integers + def _parse_type_unsigned_integer(self, stream: io.BufferedReader, additional_info: int) -> int: + additional_info_to_num_bytes = { + 24: 1, + 25: 2, + 26: 4, + 27: 8, + } + # Values under 24 don't need a full byte to be stored; their values are + # instead stored as the "additional info" in the initial byte + if additional_info < 24: + return additional_info + elif additional_info in additional_info_to_num_bytes: + num_bytes = additional_info_to_num_bytes[additional_info] + return self._read_bytes_as_int(stream, num_bytes) + else: + raise ProtocolParserError( + "Invalid CBOR integer returned from the service; unparsable " + f"additional info found for major type 0 or 1: {additional_info}" + ) + + # Major type 1 - negative integers + def _parse_type_negative_integer(self, stream: io.BufferedReader, additional_info: int) -> int: + return -1 - self._parse_type_unsigned_integer(stream, additional_info) + + # Major type 2 - byte string + def _parse_type_byte_string(self, stream: io.BufferedReader, additional_info: int) -> bytes: + if additional_info != self.INDEFINITE_ITEM_ADDITIONAL_INFO: + length = self._parse_type_unsigned_integer(stream, additional_info) + return self._read_from_stream(stream, length) + else: + chunks = [] + while True: + if self._handle_break_code(stream): + break + initial_byte = self._read_bytes_as_int(stream, 1) + additional_info = initial_byte & 0b00011111 + length = self._parse_type_unsigned_integer(stream, additional_info) + chunks.append(self._read_from_stream(stream, length)) + return b"".join(chunks) + + # Major type 3 - text string + def _parse_type_text_string(self, stream: io.BufferedReader, additional_info: int) -> str: + return self._parse_type_byte_string(stream, additional_info).decode("utf-8") + + # Major type 4 - lists + def _parse_type_array(self, stream: io.BufferedReader, additional_info: int) -> list: + if additional_info != self.INDEFINITE_ITEM_ADDITIONAL_INFO: + length = self._parse_type_unsigned_integer(stream, additional_info) + return [self.parse_data_item(stream) for _ in range(length)] + else: + items = [] + while not self._handle_break_code(stream): + items.append(self.parse_data_item(stream)) + return items + + # Major type 5 - maps + def _parse_type_map(self, stream: io.BufferedReader, additional_info: int) -> dict: + items = {} + if additional_info != self.INDEFINITE_ITEM_ADDITIONAL_INFO: + length = self._parse_type_unsigned_integer(stream, additional_info) + for _ in range(length): + self._parse_type_key_value_pair(stream, items) + return items + + else: + while not self._handle_break_code(stream): + self._parse_type_key_value_pair(stream, items) + return items + + def _parse_type_key_value_pair(self, stream: io.BufferedReader, items: dict) -> None: + key = self.parse_data_item(stream) + value = self.parse_data_item(stream) + if value is not None: + items[key] = value + + # Major type 6 is tags. The only tag we currently support is tag 1 for unix + # timestamps + def _parse_type_tag(self, stream: io.BufferedReader, additional_info: int): + tag = self._parse_type_unsigned_integer(stream, additional_info) + value = self.parse_data_item(stream) + if tag == 1: # Epoch-based date/time in milliseconds + return self._parse_type_datetime(value) + else: + raise ProtocolParserError(f"Found CBOR tag not supported by botocore: {tag}") + + def _parse_type_datetime(self, value: int | float) -> datetime.datetime: + # CBOR overrides any timestamp format defined in the spec: + # https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#timestamp-type-serialization + # > This protocol uses epoch-seconds, also known as Unix timestamps, with millisecond (1/1000th of a second) + # > resolution. The timestampFormat MUST NOT be respected to customize timestamp serialization. + if isinstance(value, (int, float)): + milli_precision_ts = int(value * 1000) / 1000 + return datetime.datetime.fromtimestamp(milli_precision_ts, tz=datetime.UTC) + else: + raise ProtocolParserError(f"Unable to parse datetime value: {value}") + + # Major type 7 includes floats and "simple" types. Supported simple types are + # currently boolean values, CBOR's null, and CBOR's undefined type. All other + # values are either floats or invalid. + def _parse_type_simple_and_float( + self, stream: io.BufferedReader, additional_info: int + ) -> bool | float | None: + # For major type 7, values 20-23 correspond to CBOR "simple" values + additional_info_simple_values = { + 20: False, # CBOR false + 21: True, # CBOR true + 22: None, # CBOR null + 23: None, # CBOR undefined + } + # First we check if the additional info corresponds to a supported simple value + if additional_info in additional_info_simple_values: + return additional_info_simple_values[additional_info] + + # If it's not a simple value, we need to parse it into the correct format and + # number fo bytes + float_formats = { + 25: (">e", 2), + 26: (">f", 4), + 27: (">d", 8), + } + + if additional_info in float_formats: + float_format, num_bytes = float_formats[additional_info] + return struct.unpack(float_format, self._read_from_stream(stream, num_bytes))[0] + raise ProtocolParserError( + f"Invalid additional info found for major type 7: {additional_info}. " + f"This indicates an unsupported simple type or an indefinite float value" + ) + + @_text_content + def _parse_blob(self, _, __, node: bytes, ___) -> bytes: + return node + + @_text_content + def _parse_timestamp( + self, _, shape: Shape, node: datetime.datetime | str, ___ + ) -> datetime.datetime: + if isinstance(node, datetime.datetime): + return node + return super()._parse_timestamp(_, shape, node, ___) + + @_text_content + def _parse_boolean(self, _, __, node: str | bool, ___) -> bool: + if isinstance(node, str): + value = node.lower() + if value == "true": + return True + if value == "false": + return False + raise ValueError(f"cannot parse boolean value {node}") + return node + + # This helper method is intended for use when parsing indefinite length items. + # It does nothing if the next byte is not the break code. If the next byte is + # the break code, it advances past that byte and returns True so the calling + # method knows to stop parsing that data item. + def _handle_break_code(self, stream: io.BufferedReader) -> bool | None: + if int.from_bytes(stream.peek(1)[:1], "big") == self.BREAK_CODE: + stream.seek(1, os.SEEK_CUR) + return True + + def _read_bytes_as_int(self, stream: IO[bytes], num_bytes: int) -> int: + byte = self._read_from_stream(stream, num_bytes) + return int.from_bytes(byte, "big") + + @staticmethod + def _read_from_stream(stream: IO[bytes], num_bytes: int) -> bytes: + value = stream.read(num_bytes) + if len(value) != num_bytes: + raise ProtocolParserError( + "End of stream reached; this indicates a " + "malformed CBOR response from the server or an " + "issue in botocore" + ) + return value + + +class CBORRequestParser(BaseCBORRequestParser, JSONRequestParser): + """ + The ``CBORRequestParser`` is responsible for parsing incoming requests for services which use the ``cbor`` + protocol. + The requests for these services encode the majority of their parameters as CBOR in the request body. + The operation is defined in an HTTP header field. + This protocol is not properly defined in the specs, but it is derived from the ``json`` protocol. Only Kinesis uses + it for now. + """ + + # timestamp format is different from traditional CBOR, and is encoded as a milliseconds integer + TIMESTAMP_FORMAT = "unixtimestampmillis" + + def _do_parse( + self, request: Request, shape: Shape, uri_params: Mapping[str, Any] = None + ) -> dict: + parsed = {} + if shape is not None: + event_name = shape.event_stream_name + if event_name: + parsed = self._handle_event_stream(request, shape, event_name) + else: + self._parse_payload(request, shape, parsed, uri_params) + return parsed + + def _handle_event_stream(self, request: Request, shape: Shape, event_name: str): + # TODO handle event streams + raise NotImplementedError + + def _parse_payload( + self, + request: Request, + shape: Shape, + final_parsed: dict, + uri_params: Mapping[str, Any] = None, + ) -> None: + original_parsed = self._initial_body_parse(request) + body_parsed = self._parse_shape(request, shape, original_parsed, uri_params) + final_parsed.update(body_parsed) + + def _initial_body_parse(self, request: Request) -> Any: + body_contents = request.data + if body_contents == b"": + return body_contents + body_contents_stream = self.get_peekable_stream_from_bytes(body_contents) + return self.parse_data_item(body_contents_stream) + + def _parse_timestamp( + self, request: Request, shape: Shape, node: str, uri_params: Mapping[str, Any] = None + ) -> datetime.datetime: + # TODO: remove once CBOR support has been removed from `JSONRequestParser` + return super()._parse_timestamp(request, shape, node, uri_params) + + +class BaseRpcV2RequestParser(RequestParser): + """ + The ``BaseRpcV2RequestParser`` is the base class for all RPC V2-based AWS service protocols. + This base class handles the routing of the request, which is specific based on the path. + The body decoding is done in the respective subclasses. + """ + + @_handle_exceptions + def parse(self, request: Request) -> tuple[OperationModel, Any]: + # see https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html + if request.method != "POST": + raise ProtocolParserError("RPC v2 only accepts POST requests.") + + headers = request.headers + if "X-Amz-Target" in headers or "X-Amzn-Target" in headers: + raise ProtocolParserError( + "RPC v2 does not accept 'X-Amz-Target' or 'X-Amzn-Target'. " + "Such requests are rejected for security reasons." + ) + # The Smithy RPCv2 CBOR protocol will only use the last four segments of the URL when routing requests. + rpc_v2_params = request.path.lstrip("/").split("/") + if len(rpc_v2_params) < 4 or not ( + operation := self.service.operation_model(rpc_v2_params[-1]) + ): + raise OperationNotFoundParserError( + f"Unable to find operation for request to service " + f"{self.service.service_name}: {request.method} {request.path}" + ) + + # there are no URI params in RPC v2 + uri_params = {} + shape: StructureShape = operation.input_shape + final_parsed = self._do_parse(request, shape, uri_params) + return operation, final_parsed + + @_handle_exceptions + def _do_parse( + self, request: Request, shape: Shape, uri_params: Mapping[str, Any] = None + ) -> dict[str, Any]: + parsed = {} + if shape is not None: + event_stream_name = shape.event_stream_name + if event_stream_name: + parsed = self._handle_event_stream(request, shape, event_stream_name) + else: + parsed = {} + self._parse_payload(request, shape, parsed, uri_params) + + return parsed + + def _handle_event_stream(self, request: Request, shape: Shape, event_name: str): + # TODO handle event streams + raise NotImplementedError + + def _parse_structure( + self, + request: Request, + shape: StructureShape, + node: dict | None, + uri_params: Mapping[str, Any] = None, + ): + if shape.is_document_type: + final_parsed = node + else: + if node is None: + # If the comes across the wire as "null" (None in python), + # we should be returning this unchanged, instead of as an + # empty dict. + return None + final_parsed = {} + members = shape.members + if shape.is_tagged_union: + cleaned_value = node.copy() + cleaned_value.pop("__type", None) + cleaned_value = {k: v for k, v in cleaned_value.items() if v is not None} + if len(cleaned_value) != 1: + raise ProtocolParserError( + f"Invalid service response: {shape.name} must have one and only one member set." + ) + + for member_name, member_shape in members.items(): + member_value = node.get(member_name) + if member_value is not None: + final_parsed[member_name] = self._parse_shape( + request, member_shape, member_value, uri_params + ) + + return final_parsed + + def _parse_payload( + self, + request: Request, + shape: Shape, + final_parsed: dict, + uri_params: Mapping[str, Any] = None, + ) -> None: + original_parsed = self._initial_body_parse(request) + body_parsed = self._parse_shape(request, shape, original_parsed, uri_params) + final_parsed.update(body_parsed) + + def _initial_body_parse(self, request: Request): + # This method should do the initial parsing of the + # body. We still need to walk the parsed body in order + # to convert types, but this method will do the first round + # of parsing. + raise NotImplementedError("_initial_body_parse") + + +class RpcV2CBORRequestParser(BaseRpcV2RequestParser, BaseCBORRequestParser): + """ + The ``RpcV2CBORRequestParser`` is responsible for parsing incoming requests for services which use the + ``rpc-v2-cbor`` protocol. The requests for these services encode all of their parameters as CBOR in the + request body. + """ + + # TODO: investigate datetime format for RpcV2CBOR protocol, which might be different than Kinesis CBOR + def _initial_body_parse(self, request: Request): + body_contents = request.data + if body_contents == b"": + return body_contents + body_contents_stream = self.get_peekable_stream_from_bytes(body_contents) + return self.parse_data_item(body_contents_stream) + + class EC2RequestParser(QueryRequestParser): """ The ``EC2RequestParser`` is responsible for parsing incoming requests for services which use the ``ec2`` @@ -1038,9 +1464,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): ) @staticmethod - def _set_request_props( - request: Request, path: str, host: str, raw_uri: Optional[str] = None - ): + def _set_request_props(request: Request, path: str, host: str, raw_uri: str | None = None): """Sets the HTTP request's path and host and clears the cache in the request object.""" request.path = path request.headers["Host"] = host @@ -1072,7 +1496,7 @@ def _is_vhost_address_get_bucket(request: Request) -> str | None: return uses_host_addressing(request.headers) @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: """Handle virtual-host-addressing for S3.""" with self.VirtualHostRewriter(request): return super().parse(request) @@ -1147,11 +1571,12 @@ def _get_serialized_name(self, shape: Shape, default_name: str, node: dict) -> s @functools.cache -def create_parser(service: ServiceModel) -> RequestParser: +def create_parser(service: ServiceModel, protocol: ProtocolName | None = None) -> RequestParser: """ Creates the right parser for the given service model. :param service: to create the parser for + :param protocol: the protocol for the parser. If not provided, fallback to the service's default protocol :return: RequestParser which can handle the protocol of the service """ # Unfortunately, some services show subtle differences in their parsing or operation detection behavior, even though @@ -1169,14 +1594,20 @@ def create_parser(service: ServiceModel) -> RequestParser: "rest-json": RestJSONRequestParser, "rest-xml": RestXMLRequestParser, "ec2": EC2RequestParser, + "smithy-rpc-v2-cbor": RpcV2CBORRequestParser, + # TODO: implement multi-protocol support for Kinesis, so that it can uses the `cbor` protocol and remove + # CBOR handling from JSONRequestParser + # this is not an "official" protocol defined from the spec, but is derived from ``json`` } + service_protocol = protocol or service.protocol + # Try to select a service- and protocol-specific parser implementation if ( service.service_name in service_specific_parsers - and service.protocol in service_specific_parsers[service.service_name] + and service_protocol in service_specific_parsers[service.service_name] ): - return service_specific_parsers[service.service_name][service.protocol](service) + return service_specific_parsers[service.service_name][service_protocol](service) else: # Otherwise, pick the protocol-specific parser for the protocol of the service - return protocol_specific_parsers[service.protocol](service) + return protocol_specific_parsers[service_protocol](service) diff --git a/localstack-core/localstack/aws/protocol/routing.py b/localstack-core/localstack/aws/protocol/routing.py index f793bd051ec27..4150bbd8a51c6 100644 --- a/localstack-core/localstack/aws/protocol/routing.py +++ b/localstack-core/localstack/aws/protocol/routing.py @@ -1,5 +1,4 @@ import re -from typing import AnyStr from werkzeug.routing import Rule @@ -30,7 +29,7 @@ def __init__(self, string: str, method: str, **kwargs) -> None: self.methods = {method.upper()} -def transform_path_params_to_rule_vars(match: re.Match[AnyStr]) -> str: +def transform_path_params_to_rule_vars(match: re.Match[str]) -> str: """ Transforms a request URI path param to a valid Werkzeug Rule string variable placeholder. This transformation function should be used in combination with _path_param_regex on the request URIs (without any diff --git a/localstack-core/localstack/aws/protocol/serializer.py b/localstack-core/localstack/aws/protocol/serializer.py index 86cabdd3487b6..752680d02d55d 100644 --- a/localstack-core/localstack/aws/protocol/serializer.py +++ b/localstack-core/localstack/aws/protocol/serializer.py @@ -14,27 +14,34 @@ designed such that the serializers share as much logic as possible. The class hierarchy looks as follows: :: - ┌───────────────────┐ - │ResponseSerializer │ - └───────────────────┘ - ▲ ▲ ▲ - ┌──────────────────────┘ │ └──────────────────┐ - ┌────────────┴────────────┐ ┌────────────┴─────────────┐ ┌─────────┴────────────┐ - │BaseXMLResponseSerializer│ │BaseRestResponseSerializer│ │JSONResponseSerializer│ - └─────────────────────────┘ └──────────────────────────┘ └──────────────────────┘ - ▲ ▲ ▲ ▲ ▲ - ┌──────────────────────┴─┐ ┌┴─────────────┴──────────┐ ┌┴──────────────┴──────────┐ - │QueryResponseSerializer │ │RestXMLResponseSerializer│ │RestJSONResponseSerializer│ - └────────────────────────┘ └─────────────────────────┘ └──────────────────────────┘ - ▲ - ┌──────────┴──────────┐ + ┌────────────────────┐ + │ ResponseSerializer │ + └────────────────────┘ + ▲ ▲ ▲ + ┌─────────────────┬───────┘ │ └──────────────┬──────────────────────┐ + ┌────────────┴────────────┐ │ ┌───────┴──────────────┐ │ ┌────────────┴─────────────┐ + │BaseXMLResponseSerializer│ │ │JSONResponseSerializer│ │ │BaseCBORResponseSerializer│ + └─────────────────────────┘ │ └──────────────────────┘ │ └──────────────────────────┘ + ▲ ▲ ┌─────────────┴────────────┐ ▲ ┌─────┴─────────────────────┐ ▲ ▲ + │ │ │BaseRestResponseSerializer│ │ │BaseRpcV2ResponseSerializer│ │ │ + │ │ └──────────────────────────┘ │ └───────────────────────────┘ │ │ + │ │ ▲ ▲ │ ▲ │ │ + │ │ │ │ │ │ │ │ + │ ┌─┴──────────────┴────────┐ ┌──┴───────────┴───────────┐ ┌──────────┴───────────┴────┐ │ + │ │RestXMLResponseSerializer│ │RestJSONResponseSerializer│ │RpcV2CBORResponseSerializer│ │ + │ └─────────────────────────┘ └──────────────────────────┘ └───────────────────────────┘ │ + ┌─────┴──────────────────┐ ┌──────────┴─────────────┐ + │QueryResponseSerializer │ │ CBORResponseSerializer │ + └────────────────────────┘ └────────────────────────┘ + ▲ + ┌─────────┴───────────┐ │EC2ResponseSerializer│ └─────────────────────┘ :: The ``ResponseSerializer`` contains the logic that is used among all the -different protocols (``query``, ``json``, ``rest-json``, ``rest-xml``, and -``ec2``). +different protocols (``query``, ``json``, ``rest-json``, ``rest-xml``, ``cbor`` +and ``ec2``). The protocols relate to each other in the following ways: * The ``query`` and the ``rest-xml`` protocols both have XML bodies in their @@ -42,10 +49,14 @@ type). * The ``json`` and the ``rest-json`` protocols both have JSON bodies in their responses which are serialized the same way. +* The ``cbor`` protocol is not properly defined in the spec, but mirrors the + ``json`` protocol. * The ``rest-json`` and ``rest-xml`` protocols serialize some metadata in - the HTTP response's header fields + the HTTP response's header fields. * The ``ec2`` protocol is basically similar to the ``query`` protocol with a specific error response formatting. +* The ``smithy-rpc-v2-cbor`` protocol defines a specific way to route request + to services via the RPC v2 trait, and encodes its body with the CBOR format. The serializer classes in this module correspond directly to the different protocols. ``#create_serializer`` shows the explicit mapping between the @@ -54,13 +65,23 @@ * The ``ResponseSerializer`` contains all the basic logic for the serialization which is shared among all different protocols. -* The ``BaseXMLResponseSerializer`` and the ``JSONResponseSerializer`` - contain the logic for the XML and the JSON serialization respectively. +* The ``BaseXMLResponseSerializer``, ``JSONResponseSerializer`` and + ``BaseCBORResponseSerializer`` contain the logic for the XML, JSON + and the CBOR serialization respectively. * The ``BaseRestResponseSerializer`` contains the logic for the REST protocol specifics (i.e. specific HTTP header serializations). +* The ``BaseRpcV2ResponseSerializer`` contains the logic for the RPC v2 + protocol specifics (i.e. pretty bare, does not has any specific + about body serialization). * The ``RestXMLResponseSerializer`` and the ``RestJSONResponseSerializer`` inherit the ReST specific logic from the ``BaseRestResponseSerializer`` and the XML / JSON body serialization from their second super class. +* The ``RpcV2CBORResponseSerializer`` inherits the RPC v2 specific logic + from the ``BaseRpcV2ResponseSerializer`` and the CBOR body serialization + from its second super class. +* The ``CBORResponseSerializer`` contains the logic specific to the + non-official ``cbor`` protocol, mirroring the ``json`` protocol but + with CBOR encoded body The services and their protocols are defined by using AWS's Smithy (a language to define services in a - somewhat - protocol-agnostic @@ -73,20 +94,32 @@ import abc import base64 +import datetime import functools import json import logging +import math import string +import struct from abc import ABC from binascii import crc32 -from datetime import datetime +from collections.abc import Iterable, Iterator from email.utils import formatdate from struct import pack -from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union +from typing import IO, Any from xml.etree import ElementTree as ETree import xmltodict -from botocore.model import ListShape, MapShape, OperationModel, ServiceModel, Shape, StructureShape +from botocore.model import ( + ListShape, + MapShape, + OperationModel, + ServiceModel, + Shape, + ShapeResolver, + StringShape, + StructureShape, +) from botocore.serialize import ISO8601, ISO8601_MICRO from botocore.utils import calculate_md5, is_json_value_header, parse_to_aware_datetime @@ -181,14 +214,14 @@ class ResponseSerializer(abc.ABC): AWS_BINARY_DATA_TYPE_STRING = 7 # Defines the supported mime types of the specific serializer. Sorted by priority (preferred / default first). # Needs to be specified by subclasses. - SUPPORTED_MIME_TYPES: List[str] = [] + SUPPORTED_MIME_TYPES: list[str] = [] @_handle_exceptions def serialize_to_response( self, response: dict, operation_model: OperationModel, - headers: Optional[Dict | Headers], + headers: dict | Headers | None, request_id: str, ) -> Response: """ @@ -234,7 +267,7 @@ def serialize_error_to_response( self, error: ServiceException, operation_model: OperationModel, - headers: Optional[Dict | Headers], + headers: dict | Headers | None, request_id: str, ) -> Response: """ @@ -260,7 +293,11 @@ def serialize_error_to_response( f"Error to serialize ({error.__class__.__name__ if error else None}) is not a ServiceException." ) shape = operation_model.service_model.shape_for_error_code(error.code) - serialized_response.status_code = error.status_code + serialized_response.status_code = self._get_error_status_code( + error=error, + headers=headers, + service_model=operation_model.service_model, + ) self._serialize_error( error, serialized_response, shape, operation_model, mime_type, request_id @@ -274,7 +311,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -289,7 +326,7 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: """ Actually serializes the given params for the given shape to a string for the transmission in the body of the response. @@ -390,9 +427,9 @@ def event_stream_serializer() -> Iterable[bytes]: def _encode_event_payload( self, event_type: str, - content: Union[str, bytes] = "", - error_code: Optional[str] = None, - error_message: Optional[str] = None, + content: str | bytes = "", + error_code: str | None = None, + error_message: str | None = None, ) -> bytes: """ Encodes the given event payload according to AWS specific binary event encoding. @@ -469,7 +506,7 @@ def _create_default_response(self, operation_model: OperationModel, mime_type: s """ return Response(status=operation_model.http.get("responseCode", 200)) - def _get_mime_type(self, headers: Optional[Dict | Headers]) -> str: + def _get_mime_type(self, headers: dict | Headers | None) -> str: """ Extracts the accepted mime type from the request headers and returns a matching, supported mime type for the serializer or the default mime type of the service if there is no match. @@ -505,7 +542,7 @@ def _get_mime_type(self, headers: Optional[Dict | Headers]) -> str: # Some extra utility methods subclasses can use. @staticmethod - def _timestamp_iso8601(value: datetime) -> str: + def _timestamp_iso8601(value: datetime.datetime) -> str: if value.microsecond > 0: timestamp_format = ISO8601_MICRO else: @@ -513,22 +550,22 @@ def _timestamp_iso8601(value: datetime) -> str: return value.strftime(timestamp_format) @staticmethod - def _timestamp_unixtimestamp(value: datetime) -> float: + def _timestamp_unixtimestamp(value: datetime.datetime) -> float: return value.timestamp() - def _timestamp_rfc822(self, value: datetime) -> str: - if isinstance(value, datetime): + def _timestamp_rfc822(self, value: datetime.datetime) -> str: + if isinstance(value, datetime.datetime): value = self._timestamp_unixtimestamp(value) return formatdate(value, usegmt=True) def _convert_timestamp_to_str( - self, value: Union[int, str, datetime], timestamp_format=None + self, value: int | str | datetime.datetime, timestamp_format=None ) -> str: if timestamp_format is None: timestamp_format = self.TIMESTAMP_FORMAT timestamp_format = timestamp_format.lower() datetime_obj = parse_to_aware_datetime(value) - converter = getattr(self, "_timestamp_%s" % timestamp_format) + converter = getattr(self, f"_timestamp_{timestamp_format}") final_value = converter(datetime_obj) return final_value @@ -540,7 +577,7 @@ def _get_serialized_name(shape: Shape, default_name: str) -> str: """ return shape.serialization.get("name", default_name) - def _get_base64(self, value: Union[str, bytes]): + def _get_base64(self, value: str | bytes): """ Returns the base64-encoded version of value, handling both strings and bytes. The returned value is a string @@ -550,7 +587,7 @@ def _get_base64(self, value: Union[str, bytes]): value = value.encode(self.DEFAULT_ENCODING) return base64.b64encode(value).strip().decode(self.DEFAULT_ENCODING) - def _encode_payload(self, body: Union[bytes, str]) -> bytes: + def _encode_payload(self, body: bytes | str) -> bytes: if isinstance(body, str): return body.encode(self.DEFAULT_ENCODING) return body @@ -578,9 +615,63 @@ def _add_md5_header(self, response: Response): md5_digest = calculate_md5(body) headers["Content-MD5"] = md5_digest - def _get_error_message(self, error: Exception) -> Optional[str]: + def _get_error_message(self, error: Exception) -> str | None: return str(error) if error is not None and str(error) != "None" else None + def _get_error_status_code( + self, error: ServiceException, headers: Headers, service_model: ServiceModel + ) -> int: + return error.status_code + + +class QueryCompatibleProtocolMixin: + def _get_error_status_code( + self, error: ServiceException, headers: dict | Headers | None, service_model: ServiceModel + ) -> int: + # by default, some protocols (namely `json` and `smithy-rpc-v2-cbor`) might not define exception status code in + # their specs, so they are not defined in the `ServiceException` object and will use the default value of `400` + # But Query compatible service always do define them, so we get the wrong code for service that are + # multi-protocols like CloudWatch + # we need to verify if the service is compatible, and if the client has requested the query compatible error + # code to return the right value + if not service_model.is_query_compatible: + return error.status_code + + if headers and headers.get("x-amzn-query-mode") == "true": + return error.status_code + + # we only want to override status code 4XX + if 400 < error.status_code <= 499: + return 400 + + return error.status_code + + def _add_query_compatible_error_header(self, response: Response, error: ServiceException): + """ + Add an `x-amzn-query-error` header for client to translate errors codes from former `query` services + into other protocols. + """ + + sender_fault = "Sender" if error.sender_fault else "Receiver" + response.headers["x-amzn-query-error"] = f"{error.code};{sender_fault}" + + def _get_error_code( + self, is_query_compatible: bool, error: ServiceException, shape: Shape | None = None + ): + # if the operation is query compatible, we need to add to use shape name + if is_query_compatible: + if shape: + code = shape.name + else: + # if the shape is not defined, we are using the Exception named to derive the `Code`, like you would + # from the shape. This allows us to have Exception that are valid in multi-protocols by defining its + # code and its name to be different + code = error.__class__.__name__ + else: + code = error.code + + return code + class BaseXMLResponseSerializer(ResponseSerializer): """ @@ -661,14 +752,14 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: root = self._serialize_body_params_to_xml(params, shape, operation_model, mime_type) self._prepare_additional_traits_in_xml(root, request_id) return self._node_to_string(root, mime_type) def _serialize_body_params_to_xml( self, params: dict, shape: Shape, operation_model: OperationModel, mime_type: str - ) -> Optional[ETree.Element]: + ) -> ETree.Element | None: if shape is None: return # The botocore serializer expects `shape.serialization["name"]`, but this isn't always present for responses @@ -690,7 +781,7 @@ def _serialize( name = shape.serialization.get("resultWrapper") try: - method = getattr(self, "_serialize_type_%s" % shape.type_name, self._default_serialize) + method = getattr(self, f"_serialize_type_{shape.type_name}", self._default_serialize) method(xmlnode, params, shape, name, mime_type) except (TypeError, ValueError, AttributeError) as e: raise ProtocolSerializerError( @@ -706,7 +797,7 @@ def _serialize_type_structure( namespace_metadata = shape.serialization["xmlNamespace"] attribute_name = "xmlns" if namespace_metadata.get("prefix"): - attribute_name += ":%s" % namespace_metadata["prefix"] + attribute_name += ":{}".format(namespace_metadata["prefix"]) structure_node.attrib[attribute_name] = namespace_metadata["uri"] for key, value in params.items(): if value is None: @@ -812,7 +903,7 @@ def _serialize_type_boolean(xmlnode: ETree.Element, params: bool, _, name: str, node.text = str_value def _serialize_type_blob( - self, xmlnode: ETree.Element, params: Union[str, bytes], _, name: str, __ + self, xmlnode: ETree.Element, params: str | bytes, _, name: str, __ ) -> None: node = ETree.SubElement(xmlnode, name) node.text = self._get_base64(params) @@ -838,7 +929,7 @@ def _default_serialize(self, xmlnode: ETree.Element, params: str, _, name: str, node = ETree.SubElement(xmlnode, name) node.text = str(params) - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): """ Prepares the XML root node before being serialized with additional traits (like the Response ID in the Query protocol). @@ -851,7 +942,7 @@ def _create_default_response(self, operation_model: OperationModel, mime_type: s response.headers["Content-Type"] = mime_type return response - def _node_to_string(self, root: Optional[ETree.Element], mime_type: str) -> Optional[str]: + def _node_to_string(self, root: ETree.Element | None, mime_type: str) -> str | None: """Generates the string representation of the given XML element.""" if root is not None: content = ETree.tostring( @@ -877,7 +968,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -904,7 +995,7 @@ def _serialize_payload( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -975,7 +1066,7 @@ def _serialize_content_type( """ pass - def _has_streaming_payload(self, payload: Optional[str], shape_members): + def _has_streaming_payload(self, payload: str | None, shape_members): """Determine if payload is streaming (a blob or string).""" return payload is not None and shape_members[payload].type_name in ["blob", "string"] @@ -1040,7 +1131,7 @@ def _serialize_header_value(self, shape: Shape, value: Any): else: return value - def _partition_members(self, parameters: dict, shape: Optional[Shape]) -> Tuple[dict, dict]: + def _partition_members(self, parameters: dict, shape: Shape | None) -> tuple[dict, dict]: """Separates the top-level keys in the given parameters dict into header- and payload-located params.""" if not isinstance(shape, StructureShape): # If the shape isn't a structure, we default to the whole response being parsed in the body. @@ -1083,7 +1174,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -1130,13 +1221,22 @@ def _serialize_body_params_to_xml( root.append(node) return root - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): # Add the response metadata here (it's not defined in the specs) # For the ec2 and the query protocol, the root cannot be None at this time. response_metadata = ETree.SubElement(root, "ResponseMetadata") request_id_element = ETree.SubElement(response_metadata, "RequestId") request_id_element.text = request_id + def _prepare_additional_traits_in_response( + self, response: Response, operation_model: OperationModel, request_id: str + ): + response.headers["x-amzn-RequestId"] = request_id + response = super()._prepare_additional_traits_in_response( + response, operation_model, request_id + ) + return response + class EC2ResponseSerializer(QueryResponseSerializer): """ @@ -1179,7 +1279,7 @@ def _serialize_error( request_id_element.text = request_id response.set_response(self._encode_payload(self._node_to_string(root, mime_type))) - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): # The EC2 protocol does not use the root output shape, therefore we need to remove the hierarchy level # below the root level if len(root) > 0: @@ -1194,7 +1294,7 @@ def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], reque request_id_element.text = request_id -class JSONResponseSerializer(ResponseSerializer): +class JSONResponseSerializer(QueryCompatibleProtocolMixin, ResponseSerializer): """ The ``JSONResponseSerializer`` is responsible for the serialization of responses from services with the ``json`` protocol. It implements the JSON response body serialization, which is also used by the @@ -1216,30 +1316,53 @@ def _serialize_error( mime_type: str, request_id: str, ) -> None: - body = dict() + body = {} # TODO implement different service-specific serializer configurations # - currently we set both, the `__type` member as well as the `X-Amzn-Errortype` header # - the specification defines that it's either the __type field OR the header - response.headers["X-Amzn-Errortype"] = error.code - body["__type"] = error.code + # this depends on the JSON protocol version as well. If json-1.0 the Error should be the full shape ID, like + # com.amazon.coral.service#ExceptionName + # if json-1.1, it should only be the name + + is_query_compatible = operation_model.service_model.is_query_compatible + code = self._get_error_code(is_query_compatible, error, shape) + + response.headers["X-Amzn-Errortype"] = code + + # the `__type` field is not defined in default botocore error shapes + body["__type"] = code if shape: remaining_params = {} # TODO add a possibility to serialize simple non-modelled errors (like S3 NoSuchBucket#BucketName) for member in shape.members: if hasattr(error, member): - remaining_params[member] = getattr(error, member) + value = getattr(error, member) + # Default error message fields can sometimes have different casing in the specs elif member.lower() in ["code", "message"] and hasattr(error, member.lower()): - remaining_params[member] = getattr(error, member.lower()) + value = getattr(error, member.lower()) + + else: + continue + + if value is None: + # do not serialize a value that is set to `None` + continue + + # if the value is falsy (empty string, empty list) and not in the Shape required members, AWS will + # not serialize it, and it will not be part of the response body. + if value or member in shape.required_members: + remaining_params[member] = value + self._serialize(body, remaining_params, shape, None, mime_type) - # Only set the message if it has not been set with the shape members + # this is a workaround, some Error Shape do not define a `Message` field, but it is always returned + # this could be solved at the same time as the `__type` field if "message" not in body and "Message" not in body: - message = self._get_error_message(error) - if message is not None: - body["message"] = message + if error_message := self._get_error_message(error): + body["message"] = error_message if mime_type in self.CBOR_TYPES: response.set_response(cbor2_dumps(body, datetime_as_timestamp=True)) @@ -1247,11 +1370,14 @@ def _serialize_error( else: response.set_json(body) + if is_query_compatible: + self._add_query_compatible_error_header(response, error) + def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -1262,7 +1388,7 @@ def _serialize_response( else: json_version = operation_model.metadata.get("jsonVersion") if json_version is not None: - response.headers["Content-Type"] = "application/x-amz-json-%s" % json_version + response.headers["Content-Type"] = f"application/x-amz-json-{json_version}" response.set_response( self._serialize_body_params(parameters, shape, operation_model, mime_type, request_id) ) @@ -1274,7 +1400,7 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: body = {} if shape is not None: self._serialize(body, params, shape, None, mime_type) @@ -1284,10 +1410,10 @@ def _serialize_body_params( else: return json.dumps(body) - def _serialize(self, body: dict, value: Any, shape, key: Optional[str], mime_type: str): + def _serialize(self, body: dict, value: Any, shape, key: str | None, mime_type: str): """This method dynamically invokes the correct `_serialize_type_*` method for each shape type.""" try: - method = getattr(self, "_serialize_type_%s" % shape.type_name, self._default_serialize) + method = getattr(self, f"_serialize_type_{shape.type_name}", self._default_serialize) method(body, value, shape, key, mime_type) except (TypeError, ValueError, AttributeError) as e: raise ProtocolSerializerError( @@ -1295,7 +1421,7 @@ def _serialize(self, body: dict, value: Any, shape, key: Optional[str], mime_typ ) from e def _serialize_type_structure( - self, body: dict, value: dict, shape: StructureShape, key: Optional[str], mime_type: str + self, body: dict, value: dict, shape: StructureShape, key: str | None, mime_type: str ): if value is None: return @@ -1369,9 +1495,7 @@ def _serialize_type_timestamp( timestamp_format = shape.serialization.get("timestampFormat") body[key] = self._convert_timestamp_to_str(value, timestamp_format) - def _serialize_type_blob( - self, body: dict, value: Union[str, bytes], _, key: str, mime_type: str - ): + def _serialize_type_blob(self, body: dict, value: str | bytes, _, key: str, mime_type: str): if mime_type in self.CBOR_TYPES: body[key] = value else: @@ -1380,7 +1504,7 @@ def _serialize_type_blob( def _prepare_additional_traits_in_response( self, response: Response, operation_model: OperationModel, request_id: str ): - response.headers["x-amzn-requestid"] = request_id + response.headers.setdefault("x-amzn-RequestId", request_id) response = super()._prepare_additional_traits_in_response( response, operation_model, request_id ) @@ -1409,6 +1533,515 @@ def _serialize_content_type( if has_body and not has_content_type: serialized.headers["Content-Type"] = mime_type + def _serialize_type_map( + self, body: dict, value: dict, shape: MapShape, key: str, mime_type: str + ): + # REST JSON is different from regular JSON, it returns None values in Map + if value is None: + return + map_obj = {} + body[key] = map_obj + for sub_key, sub_value in value.items(): + self._serialize(map_obj, sub_value, shape.value, sub_key, mime_type) + + +class BaseCBORResponseSerializer(ResponseSerializer): + """ + The ``BaseCBORResponseSerializer`` performs the basic logic for the CBOR response serialization. + + There are two types of map/list in CBOR, indefinite length types and "defined" ones: + You can use the `\xbf` byte marker to indicate a map with indefinite length, then `\xff` to indicate the end + of the map. + You can also use, for example, `\xa4` to indicate a map with exactly 4 things in it, so `\xff` is not + required at the end. + AWS, for both Kinesis and `smithy-rpc-v2-cbor` services, is using indefinite data structures when returning + responses. + + The CBOR serializer cannot serialize an exception if it is not defined in our specs. + LocalStack defines a way to have user-defined exception by subclassing `CommonServiceException`, so it needs to be + able to encode those, as well as InternalError + We are creating a default botocore structure shape (`_DEFAULT_ERROR_STRUCTURE_SHAPE`) to be used in such cases. + """ + + SUPPORTED_MIME_TYPES = [APPLICATION_CBOR, APPLICATION_AMZ_CBOR_1_1] + + UNSIGNED_INT_MAJOR_TYPE = 0 + NEGATIVE_INT_MAJOR_TYPE = 1 + BLOB_MAJOR_TYPE = 2 + STRING_MAJOR_TYPE = 3 + LIST_MAJOR_TYPE = 4 + MAP_MAJOR_TYPE = 5 + TAG_MAJOR_TYPE = 6 + FLOAT_AND_SIMPLE_MAJOR_TYPE = 7 + + INDEFINITE_ITEM_ADDITIONAL_INFO = 31 + BREAK_CODE = b"\xff" + USE_INDEFINITE_DATA_STRUCTURE = True + + _ERROR_TYPE_SHAPE = StringShape(shape_name="__type", shape_model={"type": "string"}) + + _DEFAULT_ERROR_STRUCTURE_SHAPE = StructureShape( + shape_name="DefaultErrorStructure", + shape_model={ + "type": "structure", + "members": { + "message": {"shape": "ErrorMessage"}, + "__type": {"shape": "ErrorType"}, + }, + "error": {"code": "DefaultErrorStructure", "httpStatusCode": 400, "senderFault": True}, + "exception": True, + }, + shape_resolver=ShapeResolver( + shape_map={ + "ErrorMessage": {"type": "string"}, + "ErrorType": {"type": "string"}, + }, + ), + ) + + def _serialize_data_item( + self, serialized: bytearray, value: Any, shape: Shape | None, name: str | None = None + ) -> None: + method = getattr(self, f"_serialize_type_{shape.type_name}") + if method is None: + raise ValueError( + f"Unrecognized C2J type: {shape.type_name}, unable to serialize request" + ) + method(serialized, value, shape, name) + + def _serialize_type_integer( + self, serialized: bytearray, value: int, shape: Shape | None, name: str | None = None + ) -> None: + if value >= 0: + major_type = self.UNSIGNED_INT_MAJOR_TYPE + else: + major_type = self.NEGATIVE_INT_MAJOR_TYPE + # The only differences in serializing negative and positive integers is + # that for negative, we set the major type to 1 and set the value to -1 + # minus the value + value = -1 - value + additional_info, num_bytes = self._get_additional_info_and_num_bytes(value) + initial_byte = self._get_initial_byte(major_type, additional_info) + if num_bytes == 0: + serialized.extend(initial_byte) + else: + serialized.extend(initial_byte + value.to_bytes(num_bytes, "big")) + + def _serialize_type_long( + self, serialized: bytearray, value: int, shape: Shape, name: str | None = None + ) -> None: + self._serialize_type_integer(serialized, value, shape, name) + + def _serialize_type_blob( + self, + serialized: bytearray, + value: str | bytes | IO[bytes], + shape: Shape | None, + name: str | None = None, + ) -> None: + if isinstance(value, str): + value = value.encode("utf-8") + elif not isinstance(value, (bytes, bytearray)): + # We support file-like objects for blobs; these already have been + # validated to ensure they have a read method + value = value.read() + length = len(value) + additional_info, num_bytes = self._get_additional_info_and_num_bytes(length) + initial_byte = self._get_initial_byte(self.BLOB_MAJOR_TYPE, additional_info) + if num_bytes == 0: + serialized.extend(initial_byte) + else: + serialized.extend(initial_byte + length.to_bytes(num_bytes, "big")) + serialized.extend(value) + + def _serialize_type_string( + self, serialized: bytearray, value: str, shape: Shape | None, name: str | None = None + ) -> None: + encoded = value.encode("utf-8") + length = len(encoded) + additional_info, num_bytes = self._get_additional_info_and_num_bytes(length) + initial_byte = self._get_initial_byte(self.STRING_MAJOR_TYPE, additional_info) + if num_bytes == 0: + serialized.extend(initial_byte + encoded) + else: + serialized.extend(initial_byte + length.to_bytes(num_bytes, "big") + encoded) + + def _serialize_type_list( + self, serialized: bytearray, value: list, shape: Shape | None, name: str | None = None + ) -> None: + initial_bytes, closing_bytes = self._get_bytes_for_data_structure( + value, self.LIST_MAJOR_TYPE + ) + serialized.extend(initial_bytes) + + for item in value: + self._serialize_data_item(serialized, item, shape.member) + + if closing_bytes is not None: + serialized.extend(closing_bytes) + + def _serialize_type_map( + self, serialized: bytearray, value: dict, shape: Shape | None, name: str | None = None + ) -> None: + initial_bytes, closing_bytes = self._get_bytes_for_data_structure( + value, self.MAP_MAJOR_TYPE + ) + serialized.extend(initial_bytes) + + for key_item, item in value.items(): + self._serialize_data_item(serialized, key_item, shape.key) + self._serialize_data_item(serialized, item, shape.value) + + if closing_bytes is not None: + serialized.extend(closing_bytes) + + def _serialize_type_structure( + self, + serialized: bytearray, + value: dict, + shape: Shape | None, + name: str | None = None, + shape_members: dict[str, Shape] | None = None, + ) -> None: + # `_serialize_type_structure` has a different signature other `_serialize_type_*` methods as it accepts + # `shape_members`. This is because sometimes, the `StructureShape` does not have some members defined in the + # specs, and we want to be able to pass arbitrary members to serialize undocumented members. + # see `_serialize_error_structure` for its specific usage + + if name is not None: + # For nested structures, we need to serialize the key first + self._serialize_data_item(serialized, name, shape.key_shape) + + # Remove `None` values from the dictionary + value = {k: v for k, v in value.items() if v is not None} + + initial_bytes, closing_bytes = self._get_bytes_for_data_structure( + value, self.MAP_MAJOR_TYPE + ) + serialized.extend(initial_bytes) + members = shape_members or shape.members + for member_key, member_value in value.items(): + member_shape = members[member_key] + if "name" in member_shape.serialization: + member_key = member_shape.serialization["name"] + if member_value is not None: + self._serialize_type_string(serialized, member_key, None, None) + self._serialize_data_item(serialized, member_value, member_shape) + + if closing_bytes is not None: + serialized.extend(closing_bytes) + + def _serialize_type_timestamp( + self, + serialized: bytearray, + value: int | str | datetime.datetime, + shape: Shape | None, + name: str | None = None, + ) -> None: + # https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#timestamp-type-serialization + tag = 1 # Use tag 1 for unix timestamp + initial_byte = self._get_initial_byte(self.TAG_MAJOR_TYPE, tag) + serialized.extend(initial_byte) # Tagging the timestamp + + # we encode the timestamp as a double, like the Go SDK + # https://github.com/aws/aws-sdk-go-v2/blob/5d7c17325a2581afae4455c150549174ebfd9428/internal/protocoltest/smithyrpcv2cbor/serializers.go#L664-L669 + # Currently, the Botocore serializer using unsigned integers, but it does not conform to the Smithy specs: + # > This protocol uses epoch-seconds, also known as Unix timestamps, with millisecond + # > (1/1000th of a second) resolution. + timestamp = float(self._convert_timestamp_to_str(value)) + initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 27) + serialized.extend(initial_byte + struct.pack(">d", timestamp)) + + def _serialize_type_float( + self, serialized: bytearray, value: float, shape: Shape | None, name: str | None = None + ) -> None: + if self._is_special_number(value): + serialized.extend( + self._get_bytes_for_special_numbers(value) + ) # Handle special values like NaN or Infinity + else: + initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 26) + serialized.extend(initial_byte + struct.pack(">f", value)) + + def _serialize_type_double( + self, serialized: bytearray, value: float, shape: Shape | None, name: str | None = None + ) -> None: + if self._is_special_number(value): + serialized.extend( + self._get_bytes_for_special_numbers(value) + ) # Handle special values like NaN or Infinity + else: + initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 27) + serialized.extend(initial_byte + struct.pack(">d", value)) + + def _serialize_type_boolean( + self, serialized: bytearray, value: bool, shape: Shape | None, name: str | None = None + ) -> None: + additional_info = 21 if value else 20 + serialized.extend(self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info)) + + @staticmethod + def _get_additional_info_and_num_bytes(value: int) -> tuple[int, int]: + # Values under 24 can be stored in the initial byte and don't need further + # encoding + if value < 24: + return value, 0 + # Values between 24 and 255 (inclusive) can be stored in 1 byte and + # correspond to additional info 24 + elif value < 256: + return 24, 1 + # Values up to 65535 can be stored in two bytes and correspond to additional + # info 25 + elif value < 65536: + return 25, 2 + # Values up to 4294967296 can be stored in four bytes and correspond to + # additional info 26 + elif value < 4294967296: + return 26, 4 + # The maximum number of bytes in a definite length data items is 8 which + # to additional info 27 + else: + return 27, 8 + + def _get_initial_byte(self, major_type: int, additional_info: int) -> bytes: + # The highest order three bits are the major type, so we need to bitshift the + # major type by 5 + major_type_bytes = major_type << 5 + return (major_type_bytes | additional_info).to_bytes(1, "big") + + @staticmethod + def _is_special_number(value: int | float) -> bool: + return any( + [ + value == float("inf"), + value == float("-inf"), + math.isnan(value), + ] + ) + + def _get_bytes_for_special_numbers(self, value: int | float) -> bytes: + additional_info = 25 + initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info) + if value == float("inf"): + return initial_byte + struct.pack(">H", 0x7C00) + elif value == float("-inf"): + return initial_byte + struct.pack(">H", 0xFC00) + elif math.isnan(value): + return initial_byte + struct.pack(">H", 0x7E00) + + def _get_bytes_for_data_structure( + self, value: list | dict, major_type: int + ) -> tuple[bytes, bytes | None]: + if self.USE_INDEFINITE_DATA_STRUCTURE: + additional_info = self.INDEFINITE_ITEM_ADDITIONAL_INFO + return self._get_initial_byte(major_type, additional_info), self.BREAK_CODE + else: + length = len(value) + additional_info, num_bytes = self._get_additional_info_and_num_bytes(length) + initial_byte = self._get_initial_byte(major_type, additional_info) + if num_bytes != 0: + initial_byte = initial_byte + length.to_bytes(num_bytes, "big") + + return initial_byte, None + + def _serialize_error_structure( + self, body: bytearray, shape: Shape | None, error: ServiceException, code: str + ): + if not shape: + shape = self._DEFAULT_ERROR_STRUCTURE_SHAPE + shape_members = shape.members + else: + # we need to manually add the `__type` field to the shape members as it is not part of the specs + # we do a shallow copy of the shape members + shape_members = shape.members.copy() + shape_members["__type"] = self._ERROR_TYPE_SHAPE + + # Error responses in the rpcv2Cbor protocol MUST be serialized identically to standard responses with one + # additional component to distinguish which error is contained: a body field named __type. + params = {"__type": code} + + for member in shape_members: + if hasattr(error, member): + value = getattr(error, member) + + # Default error message fields can sometimes have different casing in the specs + elif member.lower() in ["code", "message"] and hasattr(error, member.lower()): + value = getattr(error, member.lower()) + + else: + continue + + if value is None: + # do not serialize a value that is set to `None` + continue + + # if the value is falsy (empty string, empty list) and not in the Shape required members, AWS will + # not serialize it, and it will not be part of the response body. + if value or member in shape.required_members: + params[member] = value + + self._serialize_type_structure(body, params, shape, None, shape_members=shape_members) + + +class CBORResponseSerializer(BaseCBORResponseSerializer): + """ + The ``CBORResponseSerializer`` is responsible for the serialization of responses from services with the ``cbor`` + protocol. It implements the CBOR response body serialization, which is only currently used by Kinesis and is derived + conceptually from the ``JSONResponseSerializer`` + """ + + TIMESTAMP_FORMAT = "unixtimestamp" + + def _serialize_error( + self, + error: ServiceException, + response: Response, + shape: StructureShape, + operation_model: OperationModel, + mime_type: str, + request_id: str, + ) -> None: + body = bytearray() + response.content_type = mime_type + response.headers["X-Amzn-Errortype"] = error.code + + self._serialize_error_structure(body, shape, error, code=error.code) + + response.set_response(bytes(body)) + + def _serialize_response( + self, + parameters: dict, + response: Response, + shape: Shape | None, + shape_members: dict, + operation_model: OperationModel, + mime_type: str, + request_id: str, + ) -> None: + response.content_type = mime_type + response.set_response( + self._serialize_body_params(parameters, shape, operation_model, mime_type, request_id) + ) + + def _serialize_body_params( + self, + params: dict, + shape: Shape, + operation_model: OperationModel, + mime_type: str, + request_id: str, + ) -> bytes | None: + if shape is None: + return b"" + body = bytearray() + self._serialize_data_item(body, params, shape) + return bytes(body) + + def _prepare_additional_traits_in_response( + self, response: Response, operation_model: OperationModel, request_id: str + ) -> Response: + response.headers["x-amzn-RequestId"] = request_id + response = super()._prepare_additional_traits_in_response( + response, operation_model, request_id + ) + return response + + +class BaseRpcV2ResponseSerializer(ResponseSerializer): + """ + The BaseRpcV2ResponseSerializer performs the basic logic for the RPC V2 response serialization. + The only variance between the various RPCv2 protocols is the way the body is serialized for regular responses, + and the way they will encode exceptions. + """ + + def _serialize_response( + self, + parameters: dict, + response: Response, + shape: Shape | None, + shape_members: dict, + operation_model: OperationModel, + mime_type: str, + request_id: str, + ) -> None: + response.content_type = mime_type + response.set_response( + self._serialize_body_params(parameters, shape, operation_model, mime_type, request_id) + ) + + def _serialize_body_params( + self, + params: dict, + shape: Shape, + operation_model: OperationModel, + mime_type: str, + request_id: str, + ) -> bytes | None: + raise NotImplementedError + + +class RpcV2CBORResponseSerializer( + QueryCompatibleProtocolMixin, BaseRpcV2ResponseSerializer, BaseCBORResponseSerializer +): + """ + The RpcV2CBORResponseSerializer implements the CBOR body serialization part for the RPC v2 protocol, and implements the + specific exception serialization. + https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html + """ + + # the Smithy spec defines that only `application/cbor` is supported for RPC v2 CBOR + SUPPORTED_MIME_TYPES = [APPLICATION_CBOR] + TIMESTAMP_FORMAT = "unixtimestamp" + + def _serialize_body_params( + self, + params: dict, + shape: Shape, + operation_model: OperationModel, + mime_type: str, + request_id: str, + ) -> bytes | None: + if shape is None: + return b"" + body = bytearray() + self._serialize_data_item(body, params, shape) + return bytes(body) + + def _serialize_error( + self, + error: ServiceException, + response: Response, + shape: StructureShape, + operation_model: OperationModel, + mime_type: str, + request_id: str, + ) -> None: + body = bytearray() + response.content_type = mime_type # can only be 'application/cbor' + + # Responses for the rpcv2Cbor protocol SHOULD NOT contain the X-Amzn-ErrorType header. + # Type information is always serialized in the payload. This is different from the `json` protocol + is_query_compatible = operation_model.service_model.is_query_compatible + code = self._get_error_code(is_query_compatible, error, shape) + + self._serialize_error_structure(body, shape, error, code=code) + + response.set_response(bytes(body)) + + if is_query_compatible: + self._add_query_compatible_error_header(response, error) + + def _prepare_additional_traits_in_response( + self, response: Response, operation_model: OperationModel, request_id: str + ): + response.headers["x-amzn-RequestId"] = request_id + response.headers["Smithy-Protocol"] = "rpc-v2-cbor" + response = super()._prepare_additional_traits_in_response( + response, operation_model, request_id + ) + return response + class S3ResponseSerializer(RestXMLResponseSerializer): """ @@ -1466,7 +2099,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -1528,7 +2161,7 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: root = self._serialize_body_params_to_xml(params, shape, operation_model, mime_type) # S3 does not follow the specs on the root tag name for 41 of 44 operations root.tag = self._RESPONSE_ROOT_TAGS.get(root.tag, root.tag) @@ -1568,15 +2201,15 @@ def _add_error_tags( def _create_empty_node(xmlnode: ETree.Element, name: str) -> None: ETree.SubElement(xmlnode, name) - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): # some tools (Serverless) require a newline after the "\n" preamble line, e.g., for LocationConstraint - if root and not root.tail: + if root is not None and not root.tail: root.tail = "\n" root.attrib["xmlns"] = self.XML_NAMESPACE @staticmethod - def _timestamp_iso8601(value: datetime) -> str: + def _timestamp_iso8601(value: datetime.datetime) -> str: """ This is very specific to S3, S3 returns an ISO8601 timestamp but with milliseconds always set to 000 Some SDKs are very picky about the length @@ -1639,7 +2272,7 @@ def _default_serialize(self, xmlnode: ETree.Element, params: str, _, name: str, .replace("\r", "__marker__-r__marker__") ) - def _node_to_string(self, root: Optional[ETree.ElementTree], mime_type: str) -> Optional[str]: + def _node_to_string(self, root: ETree.ElementTree | None, mime_type: str) -> str | None: """Replaces the previously "marked" characters with their encoded value.""" generated_string = super()._node_to_string(root, mime_type) if generated_string is None: @@ -1703,26 +2336,12 @@ class SqsJsonResponseSerializer(JSONResponseSerializer): "QueueNameExists": "QueueAlreadyExists", } - def _serialize_error( - self, - error: ServiceException, - response: Response, - shape: StructureShape, - operation_model: OperationModel, - mime_type: str, - request_id: str, - ) -> None: - """ - Overrides _serialize_error as SQS has a special header for query API legacy reason: 'x-amzn-query-error', - which contained the exception code as well as a Sender field. - Ex: 'x-amzn-query-error': 'InvalidParameterValue;Sender' - """ - # TODO: for body["__type"] = error.code, it seems AWS differs from what we send for SQS - # AWS: "com.amazon.coral.service#InvalidParameterValueException" - # or AWS: "com.amazonaws.sqs#BatchRequestTooLong" - # LocalStack: "InvalidParameterValue" - super()._serialize_error(error, response, shape, operation_model, mime_type, request_id) - # We need to add a prefix to certain errors, as they have been deleted in the specs. These will not change + # TODO: on body error serialization (body["__type"]),it seems AWS differs from what we send for SQS + # AWS: "com.amazon.coral.service#InvalidParameterValueException" + # or AWS: "com.amazonaws.sqs#BatchRequestTooLong" + # LocalStack: "InvalidParameterValue" + + def _add_query_compatible_error_header(self, response: Response, error: ServiceException): if error.code in self.JSON_TO_QUERY_ERROR_CODES: code = self.JSON_TO_QUERY_ERROR_CODES[error.code] elif error.code in self.QUERY_PREFIXED_ERRORS: @@ -1730,6 +2349,7 @@ def _serialize_error( else: code = error.code + # SQS exceptions all have sender fault set to False, so we hardcode it to `Sender` response.headers["x-amzn-query-error"] = f"{code};Sender" @@ -1747,11 +2367,14 @@ def gen_amzn_requestid(): @functools.cache -def create_serializer(service: ServiceModel) -> ResponseSerializer: +def create_serializer( + service: ServiceModel, protocol: ProtocolName | None = None +) -> ResponseSerializer: """ Creates the right serializer for the given service model. :param service: to create the serializer for + :param protocol: the protocol for the serializer. If not provided, fallback to the service's default protocol :return: ResponseSerializer which can handle the protocol of the service """ @@ -1771,21 +2394,26 @@ def create_serializer(service: ServiceModel) -> ResponseSerializer: "rest-json": RestJSONResponseSerializer, "rest-xml": RestXMLResponseSerializer, "ec2": EC2ResponseSerializer, + "smithy-rpc-v2-cbor": RpcV2CBORResponseSerializer, + # TODO: implement multi-protocol support for Kinesis, so that it can uses the `cbor` protocol and remove + # CBOR handling from JSONResponseParser + # this is not an "official" protocol defined from the spec, but is derived from ``json`` } + service_protocol = protocol or service.protocol # Try to select a service- and protocol-specific serializer implementation if ( service.service_name in service_specific_serializers - and service.protocol in service_specific_serializers[service.service_name] + and service_protocol in service_specific_serializers[service.service_name] ): - return service_specific_serializers[service.service_name][service.protocol]() + return service_specific_serializers[service.service_name][service_protocol]() else: # Otherwise, pick the protocol-specific serializer for the protocol of the service - return protocol_specific_serializers[service.protocol]() + return protocol_specific_serializers[service_protocol]() def aws_response_serializer( - service_name: str, operation: str, protocol: Optional[ProtocolName] = None + service_name: str, operation: str, protocol: ProtocolName | None = None ): """ A decorator for an HTTP route that can serialize return values or exceptions into AWS responses. @@ -1812,7 +2440,7 @@ def my_route(request: Request): def _decorate(fn): service_model = load_service(service_name, protocol=protocol) operation_model = service_model.operation_model(operation) - serializer = create_serializer(service_model) + serializer = create_serializer(service_model, protocol=protocol) def _proxy(*args, **kwargs) -> WerkzeugResponse: # extract request from function invocation (decorator can be used for methods as well as for functions). diff --git a/localstack-core/localstack/aws/protocol/service_router.py b/localstack-core/localstack/aws/protocol/service_router.py index 44d70efaac4df..dae3d3101e62c 100644 --- a/localstack-core/localstack/aws/protocol/service_router.py +++ b/localstack-core/localstack/aws/protocol/service_router.py @@ -1,14 +1,16 @@ import logging -from typing import NamedTuple, Optional, Set +from typing import NamedTuple from botocore.model import ServiceModel from werkzeug.exceptions import RequestEntityTooLarge from werkzeug.http import parse_dict_header from localstack.aws.spec import ( + ProtocolName, ServiceCatalog, ServiceModelIdentifier, get_service_catalog, + is_protocol_in_service_model_identifier, ) from localstack.http import Request from localstack.services.s3.utils import uses_host_addressing @@ -17,6 +19,23 @@ LOG = logging.getLogger(__name__) +_PROTOCOL_DETECTION_PRIORITY: list[ProtocolName] = [ + "smithy-rpc-v2-cbor", + "json", + "query", + "ec2", + "rest-json", + "rest-xml", +] + + +class ProtocolError(Exception): + """ + Error which is thrown if we cannot detect the protocol for the request. + """ + + pass + class _ServiceIndicators(NamedTuple): """ @@ -28,21 +47,22 @@ class _ServiceIndicators(NamedTuple): # AWS service's "signing name" - Contained in the Authorization header # (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html) - signing_name: Optional[str] = None + signing_name: str | None = None # Target prefix as defined in the service specs for non-rest protocols - Contained in the X-Amz-Target header - target_prefix: Optional[str] = None + target_prefix: str | None = None # Targeted operation as defined in the service specs for non-rest protocols - Contained in the X-Amz-Target header - operation: Optional[str] = None + operation: str | None = None # Host field of the HTTP request - host: Optional[str] = None + host: str | None = None # Path of the HTTP request - path: Optional[str] = None + path: str | None = None def _extract_service_indicators(request: Request) -> _ServiceIndicators: """Extracts all different fields that might indicate which service a request is targeting.""" x_amz_target = request.headers.get("x-amz-target") authorization = request.headers.get("authorization") + is_rpc_v2 = "rpc-v2-cbor" in request.headers.get("Smithy-Protocol", "") signing_name = None if authorization: @@ -55,7 +75,15 @@ def _extract_service_indicators(request: Request) -> _ServiceIndicators: except (ValueError, KeyError): LOG.debug("auth header could not be parsed for service routing: %s", authorization) pass - if x_amz_target: + if is_rpc_v2: + # https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#requests + rpc_v2_params = request.path.lstrip("/").split("/") + if len(rpc_v2_params) >= 4: + *_, service_shape_name, __, operation = rpc_v2_params + target_prefix = service_shape_name.split("#")[-1] + else: + target_prefix, operation = None, None + elif x_amz_target: if "." in x_amz_target: target_prefix, operation = x_amz_target.split(".", 1) else: @@ -67,6 +95,48 @@ def _extract_service_indicators(request: Request) -> _ServiceIndicators: return _ServiceIndicators(signing_name, target_prefix, operation, request.host, request.path) +def _matches_protocol(request: Request, protocol: ProtocolName) -> bool: + headers = request.headers + mimetype = request.mimetype.lower() + match protocol: + case "smithy-rpc-v2-cbor": + # Every request for the rpcv2Cbor protocol MUST contain a `Smithy-Protocol` header with the value + # of `rpc-v2-cbor`. + # https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html + return headers.get("Smithy-Protocol", "") == "rpc-v2-cbor" + case "json": + return mimetype.startswith("application/x-amz-json") + case "query" | "ec2": + # https://smithy.io/2.0/aws/protocols/aws-query-protocol.html#request-serialization + return ( + mimetype.startswith("application/x-www-form-urlencoded") or "Action" in request.args + ) + case "rest-xml" | "rest-json": + # `rest-json` and `rest-xml` can accept any kind of Content-Type, and it can be configured on the operation + # level. + # https://smithy.io/2.0/aws/protocols/aws-restjson1-protocol.html + return True + case _: + return False + + +def match_available_protocols( + request: Request, available_protocols: list[ProtocolName] +) -> ProtocolName | None: + """ + Tries to match the current request and determine the protocol used amongst the available protocols given. + We use a priority order to try to determine the protocol, as some protocols are more permissive that others. + :param request: the incoming request + :param available_protocols: the available protocols of the Service the request is directed to + :return: the protocol matched, if any + """ + for protocol in _PROTOCOL_DETECTION_PRIORITY: + if protocol in available_protocols and _matches_protocol(request, protocol): + return protocol + + return None + + signing_name_path_prefix_rules = { # custom rules based on URI path prefixes that are not easily generalizable "apigateway": { @@ -111,7 +181,7 @@ def _extract_service_indicators(request: Request) -> _ServiceIndicators: } -def custom_signing_name_rules(signing_name: str, path: str) -> Optional[ServiceModelIdentifier]: +def custom_signing_name_rules(signing_name: str, path: str) -> ServiceModelIdentifier | None: """ Rules which are based on the signing name (in the auth header) and the request path. """ @@ -134,7 +204,7 @@ def custom_signing_name_rules(signing_name: str, path: str) -> Optional[ServiceM return rules.get("*", ServiceModelIdentifier(signing_name)) -def custom_host_addressing_rules(host: str) -> Optional[ServiceModelIdentifier]: +def custom_host_addressing_rules(host: str) -> ServiceModelIdentifier | None: """ Rules based on the host header of the request, which is typically the data plane of a service. @@ -147,7 +217,7 @@ def custom_host_addressing_rules(host: str) -> Optional[ServiceModelIdentifier]: return ServiceModelIdentifier("s3") -def custom_path_addressing_rules(path: str) -> Optional[ServiceModelIdentifier]: +def custom_path_addressing_rules(path: str) -> ServiceModelIdentifier | None: """ Rules which are only based on the request path. """ @@ -159,7 +229,7 @@ def custom_path_addressing_rules(path: str) -> Optional[ServiceModelIdentifier]: return ServiceModelIdentifier("lambda") -def legacy_s3_rules(request: Request) -> Optional[ServiceModelIdentifier]: +def legacy_s3_rules(request: Request) -> ServiceModelIdentifier | None: """ *Legacy* rules which allow us to fallback to S3 if no other service was matched. All rules which are implemented here should be removed once we make sure it would not break any use-cases. @@ -228,7 +298,7 @@ def legacy_s3_rules(request: Request) -> Optional[ServiceModelIdentifier]: def resolve_conflicts( - candidates: Set[ServiceModelIdentifier], request: Request + candidates: set[ServiceModelIdentifier], request: Request ) -> ServiceModelIdentifier: """ Some service definitions are overlapping to a point where they are _not_ distinguishable at all @@ -248,17 +318,17 @@ def resolve_conflicts( # The `application/x-amz-json-1.0` header is mandatory for requests targeting SQS with the `json` protocol. We # can safely route them to the `sqs` JSON parser/serializer. If not present, route the request to the # sqs-query protocol. - content_type = request.headers.get("Content-Type") + protocol = match_available_protocols(request, available_protocols=["json", "query"]) return ( ServiceModelIdentifier("sqs") - if content_type == "application/x-amz-json-1.0" + if protocol == "json" else ServiceModelIdentifier("sqs", "query") ) def determine_aws_service_model_for_data_plane( request: Request, services: ServiceCatalog = None -) -> Optional[ServiceModel]: +) -> ServiceModel | None: """ A stripped down version of ``determine_aws_service_model`` which only checks hostname indicators for the AWS data plane, such as s3 websites, lambda function URLs, or API gateway routes. @@ -266,12 +336,30 @@ def determine_aws_service_model_for_data_plane( custom_host_match = custom_host_addressing_rules(request.host) if custom_host_match: services = services or get_service_catalog() - return services.get(*custom_host_match) + return services.get(custom_host_match.name, custom_host_match.protocol) + + +def determine_aws_protocol(request: Request, service_model: ServiceModel) -> ProtocolName: + if not (protocols := service_model.metadata.get("protocols")): + # if the service does not define multiple protocols, return the `protocol` defined for the service + return service_model.protocol + + if len(protocols) == 1: + return protocols[0] + + if protocol := match_available_protocols(request, available_protocols=protocols): + return protocol + + raise ProtocolError( + f"Could not determine the protocol for the request: " + f"{request.method} {request.path} for the service '{service_model.service_name}' " + f"(available protocols: {protocols})" + ) def determine_aws_service_model( request: Request, services: ServiceCatalog = None -) -> Optional[ServiceModel]: +) -> ServiceModel | None: """ Tries to determine the name of the AWS service an incoming request is targeting. :param request: to determine the target service name of @@ -287,12 +375,13 @@ def determine_aws_service_model( signing_name_candidates = services.by_signing_name(signing_name) if len(signing_name_candidates) == 1: # a unique signing-name -> service name mapping is the case for ~75% of service operations - return services.get(*signing_name_candidates[0]) + candidate = signing_name_candidates[0] + return services.get(candidate.name, candidate.protocol) # try to find a match with the custom signing name rules custom_match = custom_signing_name_rules(signing_name, path) if custom_match: - return services.get(*custom_match) + return services.get(custom_match.name, custom_match.protocol) # still ambiguous - add the services to the list of candidates candidates.update(signing_name_candidates) @@ -302,33 +391,34 @@ def determine_aws_service_model( target_candidates = services.by_target_prefix(target_prefix) if len(target_candidates) == 1: # a unique target prefix - return services.get(*target_candidates[0]) + candidate = target_candidates[0] + return services.get(candidate.name, candidate.protocol) # still ambiguous - add the services to the list of candidates candidates.update(target_candidates) # exclude services where the operation is not contained in the service spec for service_identifier in list(candidates): - service = services.get(*service_identifier) + service = services.get(service_identifier.name, service_identifier.protocol) if operation not in service.operation_names: candidates.remove(service_identifier) else: # exclude services which have a target prefix (the current request does not have one) for service_identifier in list(candidates): - service = services.get(*service_identifier) + service = services.get(service_identifier.name, service_identifier.protocol) if service.metadata.get("targetPrefix") is not None: candidates.remove(service_identifier) if len(candidates) == 1: service_identifier = candidates.pop() - return services.get(*service_identifier) + return services.get(service_identifier.name, service_identifier.protocol) # 3. check the path if it is set and not a trivial root path if path and path != "/": # try to find a match with the custom path rules custom_path_match = custom_path_addressing_rules(path) if custom_path_match: - return services.get(*custom_path_match) + return services.get(custom_path_match.name, custom_path_match.protocol) # 4. check the host (custom host addressing rules) if host: @@ -337,12 +427,14 @@ def determine_aws_service_model( # this prevents a virtual host addressed bucket to be wrongly recognized if host.startswith(f"{prefix}.") and ".s3." not in host: if len(services_per_prefix) == 1: - return services.get(*services_per_prefix[0]) + candidate = services_per_prefix[0] + return services.get(candidate.name, candidate.protocol) candidates.update(services_per_prefix) custom_host_match = custom_host_addressing_rules(host) if custom_host_match: - return services.get(*custom_host_match) + candidate = custom_host_match[0] + return services.get(candidate.name, candidate.protocol) if request.shallow: # from here on we would need access to the request body, which doesn't exist for shallow requests like @@ -357,21 +449,28 @@ def determine_aws_service_model( query_candidates = [ service for service in services.by_operation(values["Action"]) - if service.protocol in ("ec2", "query") + if any( + is_protocol_in_service_model_identifier(protocol, service) + for protocol in ("ec2", "query") + ) ] if len(query_candidates) == 1: - return services.get(*query_candidates[0]) + candidate = query_candidates[0] + return services.get(candidate.name, candidate.protocol) if "Version" in values: for service_identifier in list(query_candidates): - service_model = services.get(*service_identifier) + service_model = services.get( + service_identifier.name, service_identifier.protocol + ) if values["Version"] != service_model.api_version: # the combination of Version and Action is not unique, add matches to the candidates query_candidates.remove(service_identifier) if len(query_candidates) == 1: - return services.get(*query_candidates[0]) + candidate = query_candidates[0] + return services.get(candidate.name, candidate.protocol) candidates.update(query_candidates) @@ -387,15 +486,16 @@ def determine_aws_service_model( # 6. resolve service spec conflicts resolved_conflict = resolve_conflicts(candidates, request) if resolved_conflict: - return services.get(*resolved_conflict) + return services.get(resolved_conflict.name, resolved_conflict.protocol) # 7. check the legacy S3 rules in the end legacy_match = legacy_s3_rules(request) if legacy_match: - return services.get(*legacy_match) + return services.get(legacy_match.name, legacy_match.protocol) if signing_name: return services.get(name=signing_name) if candidates: - return services.get(*candidates.pop()) + candidate = candidates.pop() + return services.get(candidate.name, candidate.protocol) return None diff --git a/localstack-core/localstack/aws/protocol/validate.py b/localstack-core/localstack/aws/protocol/validate.py index 30d1be4355fb0..895b574fdea75 100644 --- a/localstack-core/localstack/aws/protocol/validate.py +++ b/localstack-core/localstack/aws/protocol/validate.py @@ -1,6 +1,6 @@ """Slightly extends the ``botocore.validate`` package to provide better integration with our parser/serializer.""" -from typing import Any, Dict, List, NamedTuple +from typing import Any, NamedTuple from botocore.model import OperationModel, Shape from botocore.validate import ParamValidator as BotocoreParamValidator @@ -22,7 +22,7 @@ class Error(NamedTuple): reason: str name: str - attributes: Dict[str, Any] + attributes: dict[str, Any] class ParameterValidationError(Exception): @@ -88,11 +88,11 @@ class EmptyInput(ParameterValidationError): class ValidationErrors(BotocoreValidationErrors): - def __init__(self, shape: Shape, params: Dict[str, Any]): + def __init__(self, shape: Shape, params: dict[str, Any]): super().__init__() self.shape = shape self.params = params - self._exceptions: List[ParameterValidationError] = [] + self._exceptions: list[ParameterValidationError] = [] @property def exceptions(self): @@ -133,7 +133,7 @@ def to_exception(self, error: Error) -> ParameterValidationError: class ParamValidator(BotocoreParamValidator): - def validate(self, params: Dict[str, Any], shape: Shape): + def validate(self, params: dict[str, Any], shape: Shape): """Validate parameters against a shape model. This method will validate the parameters against a provided shape model. @@ -159,7 +159,7 @@ def _validate_structure(self, params, shape, errors, name): if required_member in params and params[required_member] is None: params.pop(required_member) - super(ParamValidator, self)._validate_structure(params, shape, errors, name) + super()._validate_structure(params, shape, errors, name) def validate_request(operation: OperationModel, request: ServiceRequest) -> ValidationErrors: diff --git a/localstack-core/localstack/aws/scaffold.py b/localstack-core/localstack/aws/scaffold.py index 3d9c0e3e55db4..7f16539a658c7 100644 --- a/localstack-core/localstack/aws/scaffold.py +++ b/localstack-core/localstack/aws/scaffold.py @@ -1,10 +1,10 @@ import io import keyword import re +from collections import OrderedDict from functools import cached_property from multiprocessing import Pool from pathlib import Path -from typing import Dict, List, Optional, Set import click from botocore import xform_name @@ -18,7 +18,6 @@ StringShape, StructureShape, ) -from typing_extensions import OrderedDict from localstack.aws.spec import load_service from localstack.utils.common import camel_to_snake_case, snake_to_camel_case @@ -65,6 +64,14 @@ def html_to_rst(html: str): return rst +def is_sparse_shape(shape: ListShape | MapShape): + # see https://smithy.io/2.0/spec/type-refinement-traits.html#sparse-trait + # this is needed for generating Map that can have nullable values + # We need to access `_shape_model` directly, because `sparse` is not used by Botocore, so it is not picked in + # `metadata`: see ``botocore.model.Shape.METADATA_ATTRS`` and ``botocore.model.Shape.metadata`` + return getattr(shape, "_shape_model", {}).get("sparse") + + class ShapeNode: service: ServiceModel shape: Shape @@ -75,7 +82,7 @@ def __init__(self, service: ServiceModel, shape: Shape) -> None: self.shape = shape @cached_property - def request_operation(self) -> Optional[OperationModel]: + def request_operation(self) -> OperationModel | None: for operation_name in self.service.operation_names: operation = self.service.operation_model(operation_name) if operation.input_shape is None: @@ -89,7 +96,7 @@ def request_operation(self) -> Optional[OperationModel]: return None @cached_property - def response_operation(self) -> Optional[OperationModel]: + def response_operation(self) -> OperationModel | None: for operation_name in self.service.operation_names: operation = self.service.operation_model(operation_name) if operation.output_shape is None: @@ -128,7 +135,7 @@ def is_enum(self): return isinstance(self.shape, StringShape) and self.shape.enum @property - def dependencies(self) -> List[str]: + def dependencies(self) -> list[str]: shape = self.shape if isinstance(shape, StructureShape): @@ -189,9 +196,7 @@ def _print_as_class(self, output, base: str, doc=True, quote_types=False): if member in self.shape.required_members: output.write(f" {member}: IO[{q}{to_valid_python_name(shape.name)}{q}]\n") else: - output.write( - f" {member}: Optional[IO[{q}{to_valid_python_name(shape.name)}{q}]]\n" - ) + output.write(f" {member}: {q}IO[{to_valid_python_name(shape.name)}] | None{q}\n") del remaining_members[member] # render the streaming payload first if self.is_response and self.response_operation.has_streaming_output: @@ -200,29 +205,30 @@ def _print_as_class(self, output, base: str, doc=True, quote_types=False): shape_name = to_valid_python_name(shape.name) if member in self.shape.required_members: output.write( - f" {member}: Union[{q}{shape_name}{q}, IO[{q}{shape_name}{q}], Iterable[{q}{shape_name}{q}]]\n" + f" {member}: {q}{shape_name} | IO[{shape_name}] | Iterable[{shape_name}]{q}\n" ) else: output.write( - f" {member}: Optional[Union[{q}{shape_name}{q}, IO[{q}{shape_name}{q}], Iterable[{q}{shape_name}{q}]]]\n" + f" {member}: {q}{shape_name} | IO[{shape_name}] | Iterable[{shape_name}] | None{q}\n" ) del remaining_members[member] for k, v in remaining_members.items(): + shape_name = to_valid_python_name(v.name) if k in self.shape.required_members: if v.serialization.get("eventstream"): - output.write(f" {k}: Iterator[{q}{to_valid_python_name(v.name)}{q}]\n") + output.write(f" {k}: Iterator[{q}{shape_name}{q}]\n") else: - output.write(f" {k}: {q}{to_valid_python_name(v.name)}{q}\n") + output.write(f" {k}: {q}{shape_name}{q}\n") else: if v.serialization.get("eventstream"): - output.write(f" {k}: Iterator[{q}{to_valid_python_name(v.name)}{q}]\n") + output.write(f" {k}: Iterator[{q}{shape_name}{q}]\n") else: - output.write(f" {k}: Optional[{q}{to_valid_python_name(v.name)}{q}]\n") + output.write(f" {k}: {q}{shape_name} | None{q}\n") def _print_as_typed_dict(self, output, doc=True, quote_types=False): name = to_valid_python_name(self.shape.name) - output.write('%s = TypedDict("%s", {\n' % (name, name)) + output.write(f'{name} = TypedDict("{name}", {{\n') for k, v in self.shape.members.items(): member_name = to_valid_python_name(v.name) # check if the member name is the same as the type name (recursive types need to use forward references) @@ -237,7 +243,7 @@ def _print_as_typed_dict(self, output, doc=True, quote_types=False): if v.serialization.get("eventstream"): output.write(f' "{k}": Iterator[{q}{member_name}{q}],\n') else: - output.write(f' "{k}": Optional[{q}{member_name}{q}],\n') + output.write(f' "{k}": {q}{member_name} | None{q},\n') output.write("}, total=False)") def print_shape_doc(self, output, shape): @@ -257,11 +263,15 @@ def print_declaration(self, output, doc=True, quote_types=False): self._print_structure_declaration(output, doc, quote_types) elif isinstance(shape, ListShape): output.write( - f"{to_valid_python_name(shape.name)} = List[{q}{to_valid_python_name(shape.member.name)}{q}]" + f"{to_valid_python_name(shape.name)} = list[{q}{to_valid_python_name(shape.member.name)}{q}]" ) elif isinstance(shape, MapShape): + if is_sparse_shape(shape): + value_key = f"{q}{to_valid_python_name(shape.value.name)} | None{q}" + else: + value_key = f"{q}{to_valid_python_name(shape.value.name)}{q}" output.write( - f"{to_valid_python_name(shape.name)} = Dict[{q}{to_valid_python_name(shape.key.name)}{q}, {q}{to_valid_python_name(shape.value.name)}{q}]" + f"{to_valid_python_name(shape.name)} = dict[{q}{to_valid_python_name(shape.key.name)}{q}, {value_key}]" ) elif isinstance(shape, StringShape): if shape.enum: @@ -317,9 +327,8 @@ def get_order(self): def generate_service_types(output, service: ServiceModel, doc=True): output.write("from datetime import datetime\n") output.write("from enum import StrEnum\n") - output.write( - "from typing import Dict, List, Optional, Iterator, Iterable, IO, Union, TypedDict\n" - ) + output.write("from typing import IO, TypedDict\n") + output.write("from collections.abc import Iterable, Iterator\n") output.write("\n") output.write( "from localstack.aws.api import handler, RequestContext, ServiceException, ServiceRequest" @@ -327,7 +336,7 @@ def generate_service_types(output, service: ServiceModel, doc=True): output.write("\n") # ==================================== print type declarations - nodes: Dict[str, ShapeNode] = {} + nodes: dict[str, ShapeNode] = {} for shape_name in service.shape_names: shape = service.shape_for(shape_name) @@ -338,9 +347,9 @@ def generate_service_types(output, service: ServiceModel, doc=True): # output.write(f' "{name}",\n') # output.write("]\n") - printed: Set[str] = set() - visited: Set[str] = set() - stack: List[str] = list(nodes.keys()) + printed: set[str] = set() + visited: set[str] = set() + stack: list[str] = list(nodes.keys()) stack = sorted(stack, key=lambda name: nodes[name].get_order()) stack.reverse() @@ -373,8 +382,8 @@ def generate_service_api(output, service: ServiceModel, doc=True): output.write(f"class {class_name}:\n") output.write("\n") - output.write(f' service = "{service.service_name}"\n') - output.write(f' version = "{service.api_version}"\n') + output.write(f' service: str = "{service.service_name}"\n') + output.write(f' version: str = "{service.api_version}"\n') for op_name in service.operation_names: operation: OperationModel = service.operation_model(op_name) diff --git a/localstack-core/localstack/aws/serving/edge.py b/localstack-core/localstack/aws/serving/edge.py index 0e204a4d96f88..be59bcef1bc58 100644 --- a/localstack-core/localstack/aws/serving/edge.py +++ b/localstack-core/localstack/aws/serving/edge.py @@ -1,6 +1,5 @@ import logging import threading -from typing import List from rolo.gateway.wsgi import WsgiGateway @@ -15,7 +14,7 @@ def serve_gateway( - listen: HostAndPort | List[HostAndPort], use_ssl: bool, asynchronous: bool = False + listen: HostAndPort | list[HostAndPort], use_ssl: bool, asynchronous: bool = False ): """ Implementation of the edge.do_start_edge_proxy interface to start a Hypercorn server instance serving the @@ -37,7 +36,7 @@ def serve_gateway( def _serve_werkzeug( - gateway: LocalstackAwsGateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool + gateway: LocalstackAwsGateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool ): from werkzeug.serving import ThreadedWSGIServer @@ -57,7 +56,7 @@ def _serve_werkzeug( params["ssl_context"] = (cert_file_name, key_file_name) threads = [] - servers: List[ThreadedWSGIServer] = [] + servers: list[ThreadedWSGIServer] = [] for host_port in listen: kwargs = dict(params) @@ -90,7 +89,7 @@ def _shutdown_servers(): def _serve_hypercorn( - gateway: LocalstackAwsGateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool + gateway: LocalstackAwsGateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool ): from localstack.http.hypercorn import GatewayServer @@ -112,7 +111,7 @@ def _shutdown_gateway(): def _serve_twisted( - gateway: LocalstackAwsGateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool + gateway: LocalstackAwsGateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool ): from .twisted import serve_gateway diff --git a/localstack-core/localstack/aws/serving/hypercorn.py b/localstack-core/localstack/aws/serving/hypercorn.py index 450d2664badc9..def66e6ea9a00 100644 --- a/localstack-core/localstack/aws/serving/hypercorn.py +++ b/localstack-core/localstack/aws/serving/hypercorn.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Optional, Tuple +from typing import Any from hypercorn import Config from hypercorn.asyncio import serve as serve_hypercorn @@ -15,7 +15,7 @@ def serve( host: str = "localhost", port: int = constants.DEFAULT_PORT_EDGE, use_reloader: bool = True, - ssl_creds: Optional[Tuple[Any, Any]] = None, + ssl_creds: tuple[Any, Any] | None = None, **kwargs, ) -> None: """ diff --git a/localstack-core/localstack/aws/serving/twisted.py b/localstack-core/localstack/aws/serving/twisted.py index 549150a73ae61..3ba0341387674 100644 --- a/localstack-core/localstack/aws/serving/twisted.py +++ b/localstack-core/localstack/aws/serving/twisted.py @@ -4,7 +4,6 @@ import logging import time -from typing import List from rolo.gateway import Gateway from rolo.serving.twisted import TwistedGateway @@ -130,7 +129,7 @@ def stop_thread_pool(self: ThreadPool, stop, timeout: float = None): def serve_gateway( - gateway: Gateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool = False + gateway: Gateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool = False ): """ Serve a Gateway instance using twisted. diff --git a/localstack-core/localstack/aws/serving/werkzeug.py b/localstack-core/localstack/aws/serving/werkzeug.py index 22e351adc4842..00e378b043a9b 100644 --- a/localstack-core/localstack/aws/serving/werkzeug.py +++ b/localstack-core/localstack/aws/serving/werkzeug.py @@ -1,5 +1,5 @@ import ssl -from typing import TYPE_CHECKING, Any, Optional, Tuple +from typing import TYPE_CHECKING, Any from rolo.gateway import Gateway from rolo.gateway.wsgi import WsgiGateway @@ -17,7 +17,7 @@ def serve( host: str = "localhost", port: int = constants.DEFAULT_PORT_EDGE, use_reloader: bool = True, - ssl_creds: Optional[Tuple[Any, Any]] = None, + ssl_creds: tuple[Any, Any] | None = None, **kwargs, ) -> None: """ diff --git a/localstack-core/localstack/aws/skeleton.py b/localstack-core/localstack/aws/skeleton.py index 9d66fa4b375c1..faae3a49ce13c 100644 --- a/localstack-core/localstack/aws/skeleton.py +++ b/localstack-core/localstack/aws/skeleton.py @@ -1,6 +1,7 @@ import inspect import logging -from typing import Any, Callable, Dict, NamedTuple, Optional, Union +from collections.abc import Callable +from typing import Any, NamedTuple from botocore import xform_name from botocore.model import ServiceModel @@ -11,19 +12,20 @@ ServiceException, ) from localstack.aws.api.core import ServiceRequest, ServiceRequestHandler, ServiceResponse +from localstack.aws.catalog_exceptions import get_service_availability_exception from localstack.aws.protocol.parser import create_parser from localstack.aws.protocol.serializer import ResponseSerializer, create_serializer from localstack.aws.spec import load_service from localstack.http import Response from localstack.utils import analytics -from localstack.utils.coverage_docs import get_coverage_link_for_service +from localstack.utils.catalog.plugins import get_aws_catalog LOG = logging.getLogger(__name__) -DispatchTable = Dict[str, ServiceRequestHandler] +DispatchTable = dict[str, ServiceRequestHandler] -def create_skeleton(service: Union[str, ServiceModel], delegate: Any): +def create_skeleton(service: str | ServiceModel, delegate: Any): if isinstance(service, str): service = load_service(service) @@ -49,10 +51,10 @@ def create_dispatch_table(delegate: object) -> DispatchTable: # scan class tree for @handler wrapped functions (reverse class tree so that inherited functions overwrite parent # functions) cls_tree = inspect.getmro(delegate.__class__) - handlers: Dict[str, HandlerAttributes] = {} + handlers: dict[str, HandlerAttributes] = {} cls_tree = reversed(list(cls_tree)) for cls in cls_tree: - if cls == object: + if cls is object: continue for name, fn in inspect.getmembers(cls, inspect.isfunction): @@ -98,9 +100,7 @@ def __init__( self.pass_context = pass_context self.expand_parameters = expand_parameters - def __call__( - self, context: RequestContext, request: ServiceRequest - ) -> Optional[ServiceResponse]: + def __call__(self, context: RequestContext, request: ServiceRequest) -> ServiceResponse | None: args = [] kwargs = {} @@ -122,7 +122,7 @@ class Skeleton: service: ServiceModel dispatch_table: DispatchTable - def __init__(self, service: ServiceModel, implementation: Union[Any, DispatchTable]): + def __init__(self, service: ServiceModel, implementation: Any | DispatchTable): self.service = service if isinstance(implementation, dict): @@ -131,14 +131,16 @@ def __init__(self, service: ServiceModel, implementation: Union[Any, DispatchTab self.dispatch_table = create_dispatch_table(implementation) def invoke(self, context: RequestContext) -> Response: - serializer = create_serializer(context.service) + serializer = create_serializer(context.service, context.protocol) if context.operation and context.service_request: # if the parsed request is already set in the context, re-use them operation, instance = context.operation, context.service_request else: # otherwise, parse the incoming HTTPRequest - operation, instance = create_parser(context.service).parse(context.request) + operation, instance = create_parser(context.service, context.protocol).parse( + context.request + ) context.operation = operation try: @@ -211,16 +213,30 @@ def on_not_implemented_error( """ operation = context.operation - action_name = operation.name + operation_name = operation.name service_name = operation.service_model.service_name exception_message: str | None = exception.args[0] if exception.args else None - message = exception_message or get_coverage_link_for_service(service_name, action_name) + if exception_message is not None: + message = exception_message + error = CommonServiceException("InternalFailure", message, status_code=501) + # record event + analytics.log.event( + "services_notimplemented", payload={"s": service_name, "a": operation_name} + ) + else: + service_status = get_aws_catalog().get_aws_service_status(service_name, operation_name) + error = get_service_availability_exception(service_name, operation_name, service_status) + message = error.message + analytics.log.event( + "services_notimplemented", + payload={ + "s": service_name, + "a": operation_name, + "c": error.error_code, + }, + ) + LOG.info(message) - error = CommonServiceException("InternalFailure", message, status_code=501) - # record event - analytics.log.event( - "services_notimplemented", payload={"s": service_name, "a": action_name} - ) context.service_exception = error return serializer.serialize_error_to_response( diff --git a/localstack-core/localstack/aws/spec-patches.json b/localstack-core/localstack/aws/spec-patches.json index 37cc8a5c27001..1004603cb0579 100644 --- a/localstack-core/localstack/aws/spec-patches.json +++ b/localstack-core/localstack/aws/spec-patches.json @@ -62,32 +62,6 @@ "type": "string" } }, - { - "op": "add", - "path": "/shapes/HeadBucketOutput", - "value": { - "type": "structure", - "members": { - "BucketRegion": { - "shape": "BucketRegion", - "location": "header", - "locationName": "x-amz-bucket-region" - }, - "BucketContentType": { - "shape": "BucketContentType", - "location": "header", - "locationName": "content-type" - } - } - } - }, - { - "op": "add", - "path": "/operations/HeadBucket/output", - "value": { - "shape": "HeadBucketOutput" - } - }, { "op": "add", "path": "/operations/PutBucketPolicy/http/responseCode", @@ -1329,6 +1303,26 @@ "documentation": "

The Content-MD5 you specified did not match what we received.

", "exception": true } + }, + { + "op": "add", + "path": "/shapes/AuthorizationHeaderMalformed", + "value": { + "type": "structure", + "members": { + "Region": { + "shape": "BucketRegion" + }, + "HostId": { + "shape": "HostId" + } + }, + "error": { + "httpStatusCode": 400 + }, + "documentation": "

The authorization header is malformed.

", + "exception": true + } } ], "apigatewayv2/2018-11-29/service-2": [ @@ -1352,5 +1346,22 @@ "path": "/operations/CreateApiMapping/http/responseCode", "value": 200 } + ], + "apigateway/2015-07-09/service-2": [ + { + "op": "add", + "path": "/shapes/MapOfStringToNullableString", + "value": { + "type":"map", + "key":{"shape":"String"}, + "value":{"shape":"String"}, + "sparse": true + } + }, + { + "op": "replace", + "path": "/shapes/IntegrationResponse/members/responseTemplates/shape", + "value": "MapOfStringToNullableString" + } ] } diff --git a/localstack-core/localstack/aws/spec.py b/localstack-core/localstack/aws/spec.py index 1410ddde3e246..53cb303db3bc4 100644 --- a/localstack-core/localstack/aws/spec.py +++ b/localstack-core/localstack/aws/spec.py @@ -4,8 +4,9 @@ import os import sys from collections import defaultdict +from collections.abc import Generator from functools import cached_property, lru_cache -from typing import Dict, Generator, List, Literal, NamedTuple, Optional, Tuple +from typing import Literal, NamedTuple import botocore import jsonpatch @@ -20,7 +21,7 @@ LOG = logging.getLogger(__name__) ServiceName = str -ProtocolName = Literal["query", "json", "rest-json", "rest-xml", "ec2"] +ProtocolName = Literal["query", "json", "rest-json", "rest-xml", "ec2", "smithy-rpc-v2-cbor"] class ServiceModelIdentifier(NamedTuple): @@ -31,13 +32,14 @@ class ServiceModelIdentifier(NamedTuple): """ name: ServiceName - protocol: Optional[ProtocolName] = None + protocol: ProtocolName | None = None + protocols: tuple[ProtocolName] | None = None spec_patches_json = os.path.join(os.path.dirname(__file__), "spec-patches.json") -def load_spec_patches() -> Dict[str, list]: +def load_spec_patches() -> dict[str, list]: if not os.path.exists(spec_patches_json): return {} with open(spec_patches_json) as fd: @@ -59,16 +61,16 @@ class PatchingLoader(Loader): A custom botocore Loader that applies JSON patches from the given json patch file to the specs as they are loaded. """ - patches: Dict[str, list] + patches: dict[str, list] - def __init__(self, patches: Dict[str, list], *args, **kwargs): + def __init__(self, patches: dict[str, list], *args, **kwargs): # add the builtin data path to the extra_search_paths to ensure they are discovered by the loader super().__init__(*args, **kwargs) self.patches = patches @instance_cache def load_data(self, name: str): - result = super(PatchingLoader, self).load_data(name) + result = super().load_data(name) if patches := self.patches.get(name): return jsonpatch.apply_patch(result, patches) @@ -94,12 +96,12 @@ class UnknownServiceProtocolError(UnknownServiceError): fmt = "Unknown service protocol: '{service_name}-{protocol}'." -def list_services() -> List[ServiceModel]: +def list_services() -> list[ServiceModel]: return [load_service(service) for service in loader.list_available_services("service-2")] def load_service( - service: ServiceName, version: Optional[str] = None, protocol: Optional[ProtocolName] = None + service: ServiceName, version: str | None = None, protocol: ProtocolName | None = None ) -> ServiceModel: """ Loads a service @@ -113,9 +115,13 @@ def load_service( :raises: UnknownServiceProtocolError if the specific protocol of the service cannot be found """ service_description = loader.load_service_model(service, "service-2", version) + service_metadata = service_description.get("metadata", {}) + service_protocols = {service_metadata.get("protocol")} + if protocols := service_metadata.get("protocols"): + service_protocols.update(protocols) # check if the protocol is defined, and if so, if the loaded service defines this protocol - if protocol is not None and protocol != service_description.get("metadata", {}).get("protocol"): + if protocol is not None and protocol not in service_protocols: # if the protocol is defined, but not the one of the currently loaded service, # check if we already loaded the custom spec based on the naming convention (-), # f.e. "sqs-query" @@ -131,12 +137,12 @@ def load_service( # remove potential protocol names from the service name # FIXME add more protocols here if we have to internalize more than just sqs-query - # TODO this should not contain specific internalized serivce names + # TODO this should not contain specific internalized service names service = {"sqs-query": "sqs"}.get(service, service) return ServiceModel(service_description, service) -def iterate_service_operations() -> Generator[Tuple[ServiceModel, OperationModel], None, None]: +def iterate_service_operations() -> Generator[tuple[ServiceModel, OperationModel]]: """ Returns one record per operation in the AWS service spec, where the first item is the service model the operation belongs to, and the second is the operation model. @@ -148,17 +154,38 @@ def iterate_service_operations() -> Generator[Tuple[ServiceModel, OperationModel yield service, service.operation_model(op_name) +def is_protocol_in_service_model_identifier( + protocol: ProtocolName, service_model_identifier: ServiceModelIdentifier +) -> bool: + """ + :param protocol: the protocol name to check + :param service_model_identifier: + :return: boolean to indicate if the protocol is available for that service + """ + protocols = service_model_identifier.protocols or [] + return protocol in protocols or protocol == service_model_identifier.protocol + + +def get_service_model_identifier(service_model: ServiceModel) -> ServiceModelIdentifier: + protocols = service_model.metadata.get("protocols") + return ServiceModelIdentifier( + name=service_model.service_name, + protocol=service_model.protocol, + protocols=tuple(protocols) if protocols else None, + ) + + @dataclasses.dataclass class ServiceCatalogIndex: """ The ServiceCatalogIndex enables fast lookups for common operations to determine a service from service indicators. """ - service_names: List[ServiceName] - target_prefix_index: Dict[str, List[ServiceModelIdentifier]] - signing_name_index: Dict[str, List[ServiceModelIdentifier]] - operations_index: Dict[str, List[ServiceModelIdentifier]] - endpoint_prefix_index: Dict[str, List[ServiceModelIdentifier]] + service_names: list[ServiceName] + target_prefix_index: dict[str, list[ServiceModelIdentifier]] + signing_name_index: dict[str, list[ServiceModelIdentifier]] + operations_index: dict[str, list[ServiceModelIdentifier]] + endpoint_prefix_index: dict[str, list[ServiceModelIdentifier]] class LazyServiceCatalogIndex: @@ -167,58 +194,52 @@ class LazyServiceCatalogIndex: """ @cached_property - def service_names(self) -> List[ServiceName]: + def service_names(self) -> list[ServiceName]: return list(self._services.keys()) @cached_property - def target_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def target_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: target_prefix = service_model.metadata.get("targetPrefix") if target_prefix: - result[target_prefix].append( - ServiceModelIdentifier(service_model.service_name, service_model.protocol) - ) + result[target_prefix].append(get_service_model_identifier(service_model)) return dict(result) @cached_property - def signing_name_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def signing_name_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: result[service_model.signing_name].append( - ServiceModelIdentifier(service_model.service_name, service_model.protocol) + get_service_model_identifier(service_model) ) return dict(result) @cached_property - def operations_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def operations_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: operations = service_model.operation_names if operations: for operation in operations: - result[operation].append( - ServiceModelIdentifier( - service_model.service_name, service_model.protocol - ) - ) + result[operation].append(get_service_model_identifier(service_model)) return dict(result) @cached_property - def endpoint_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def endpoint_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: result[service_model.endpoint_prefix].append( - ServiceModelIdentifier(service_model.service_name, service_model.protocol) + get_service_model_identifier(service_model) ) return dict(result) @cached_property - def _services(self) -> Dict[ServiceName, List[ServiceModel]]: + def _services(self) -> dict[ServiceName, list[ServiceModel]]: services = defaultdict(list) for service in list_services(): services[service.service_name].append(service) @@ -232,38 +253,36 @@ def __init__(self, index: ServiceCatalogIndex = None): self.index = index or LazyServiceCatalogIndex() @lru_cache(maxsize=512) - def get( - self, name: ServiceName, protocol: Optional[ProtocolName] = None - ) -> Optional[ServiceModel]: + def get(self, name: ServiceName, protocol: ProtocolName | None = None) -> ServiceModel | None: return load_service(name, protocol=protocol) @property - def service_names(self) -> List[ServiceName]: + def service_names(self) -> list[ServiceName]: return self.index.service_names @property - def target_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def target_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.target_prefix_index @property - def signing_name_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def signing_name_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.signing_name_index @property - def operations_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def operations_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.operations_index @property - def endpoint_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def endpoint_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.endpoint_prefix_index - def by_target_prefix(self, target_prefix: str) -> List[ServiceModelIdentifier]: + def by_target_prefix(self, target_prefix: str) -> list[ServiceModelIdentifier]: return self.target_prefix_index.get(target_prefix, []) - def by_signing_name(self, signing_name: str) -> List[ServiceModelIdentifier]: + def by_signing_name(self, signing_name: str) -> list[ServiceModelIdentifier]: return self.signing_name_index.get(signing_name, []) - def by_operation(self, operation_name: str) -> List[ServiceModelIdentifier]: + def by_operation(self, operation_name: str) -> list[ServiceModelIdentifier]: return self.operations_index.get(operation_name, []) @@ -344,8 +363,9 @@ def get_service_catalog() -> ServiceCatalog: index = build_service_index_cache(cache_catalog_file) return ServiceCatalog(index) except Exception: - LOG.exception( - "error while processing service catalog index cache, falling back to lazy-loaded index" + LOG.error( + "error while processing service catalog index cache, falling back to lazy-loaded index", + exc_info=LOG.isEnabledFor(logging.DEBUG), ) return ServiceCatalog() diff --git a/localstack-core/localstack/cli/__init__.py b/localstack-core/localstack/cli/__init__.py index fb0407e19e65e..e69de29bb2d1d 100644 --- a/localstack-core/localstack/cli/__init__.py +++ b/localstack-core/localstack/cli/__init__.py @@ -1,10 +0,0 @@ -from .console import console -from .plugin import LocalstackCli, LocalstackCliPlugin - -name = "cli" - -__all__ = [ - "console", - "LocalstackCli", - "LocalstackCliPlugin", -] diff --git a/localstack-core/localstack/cli/console.py b/localstack-core/localstack/cli/console.py deleted file mode 100644 index 24bda10813744..0000000000000 --- a/localstack-core/localstack/cli/console.py +++ /dev/null @@ -1,11 +0,0 @@ -from rich.console import Console - -BANNER = r""" - __ _______ __ __ - / / ____ _________ _/ / ___// /_____ ______/ /__ - / / / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/ - / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,< - /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_| -""" - -console = Console() diff --git a/localstack-core/localstack/cli/exceptions.py b/localstack-core/localstack/cli/exceptions.py index cd65d2ee13d26..eb077c97c0ef7 100644 --- a/localstack-core/localstack/cli/exceptions.py +++ b/localstack-core/localstack/cli/exceptions.py @@ -12,7 +12,7 @@ class CLIError(ClickException): def format_message(self) -> str: return click.style(f"❌ Error: {self.message}", fg="red") - def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: + def show(self, file: t.IO[t.Any] | None = None) -> None: if file is None: file = get_text_stderr() diff --git a/localstack-core/localstack/cli/localstack.py b/localstack-core/localstack/cli/localstack.py deleted file mode 100644 index 016834b3e21b3..0000000000000 --- a/localstack-core/localstack/cli/localstack.py +++ /dev/null @@ -1,946 +0,0 @@ -import json -import logging -import os -import sys -import traceback -from typing import Dict, List, Optional, Tuple, TypedDict - -import click -import requests - -from localstack import config -from localstack.cli.exceptions import CLIError -from localstack.constants import VERSION -from localstack.utils.analytics.cli import publish_invocation -from localstack.utils.bootstrap import get_container_default_logfile_location -from localstack.utils.json import CustomEncoder - -from .console import BANNER, console -from .plugin import LocalstackCli, load_cli_plugins - - -class LocalStackCliGroup(click.Group): - """ - A Click group used for the top-level ``localstack`` command group. It implements global exception handling - by: - - - Ignoring click exceptions (already handled) - - Handling common exceptions (like DockerNotAvailable) - - Wrapping all unexpected exceptions in a ClickException (for a unified error message) - - It also implements a custom help formatter to build more fine-grained groups. - """ - - # FIXME: find a way to communicate this from the actual command - advanced_commands = [ - "aws", - "dns", - "extensions", - "license", - "login", - "logout", - "pod", - "state", - "ephemeral", - "replicator", - ] - - def invoke(self, ctx: click.Context): - try: - return super(LocalStackCliGroup, self).invoke(ctx) - except click.exceptions.Exit: - # raise Exit exceptions unmodified (e.g., raised on --help) - raise - except click.ClickException: - # don't handle ClickExceptions, just reraise - if ctx and ctx.params.get("debug"): - click.echo(traceback.format_exc()) - raise - except Exception as e: - if ctx and ctx.params.get("debug"): - click.echo(traceback.format_exc()) - from localstack.utils.container_utils.container_client import ( - ContainerException, - DockerNotAvailable, - ) - - if isinstance(e, DockerNotAvailable): - raise CLIError( - "Docker could not be found on the system.\n" - "Please make sure that you have a working docker environment on your machine." - ) - elif isinstance(e, ContainerException): - raise CLIError(e.message) - else: - # If we have a generic exception, we wrap it in a ClickException - raise CLIError(str(e)) from e - - def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: - """Extra format methods for multi methods that adds all the commands after the options. It also - groups commands into command categories.""" - categories = {"Commands": [], "Advanced": [], "Deprecated": []} - - commands = [] - for subcommand in self.list_commands(ctx): - cmd = self.get_command(ctx, subcommand) - # What is this, the tool lied about a command. Ignore it - if cmd is None: - continue - if cmd.hidden: - continue - - commands.append((subcommand, cmd)) - - # allow for 3 times the default spacing - if len(commands): - limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) - - for subcommand, cmd in commands: - help = cmd.get_short_help_str(limit) - categories[self._get_category(cmd)].append((subcommand, help)) - - for category, rows in categories.items(): - if rows: - with formatter.section(category): - formatter.write_dl(rows) - - def _get_category(self, cmd) -> str: - if cmd.deprecated: - return "Deprecated" - - if cmd.name in self.advanced_commands: - return "Advanced" - - return "Commands" - - -def create_with_plugins() -> LocalstackCli: - """ - Creates a LocalstackCli instance with all cli plugins loaded. - :return: a LocalstackCli instance - """ - cli = LocalstackCli() - cli.group = localstack - load_cli_plugins(cli) - return cli - - -def _setup_cli_debug() -> None: - from localstack.logging.setup import setup_logging_for_cli - - config.DEBUG = True - os.environ["DEBUG"] = "1" - - setup_logging_for_cli(logging.DEBUG if config.DEBUG else logging.INFO) - - -# Re-usable format option decorator which can be used across multiple commands -_click_format_option = click.option( - "-f", - "--format", - "format_", - type=click.Choice(["table", "plain", "dict", "json"]), - default="table", - help="The formatting style for the command output.", -) - - -@click.group( - name="localstack", - help="The LocalStack Command Line Interface (CLI)", - cls=LocalStackCliGroup, - context_settings={ - # add "-h" as a synonym for "--help" - # https://click.palletsprojects.com/en/8.1.x/documentation/#help-parameter-customization - "help_option_names": ["-h", "--help"], - # show default values for options by default - https://github.com/pallets/click/pull/1225 - "show_default": True, - }, -) -@click.version_option( - VERSION, - "--version", - "-v", - message="LocalStack CLI %(version)s", - help="Show the version of the LocalStack CLI and exit", -) -@click.option("-d", "--debug", is_flag=True, help="Enable CLI debugging mode") -@click.option("-p", "--profile", type=str, help="Set the configuration profile") -def localstack(debug, profile) -> None: - # --profile is read manually in localstack.cli.main because it needs to be read before localstack.config is read - - if debug: - _setup_cli_debug() - - from localstack.utils.files import cache_dir - - # overwrite the config variable here to defer import of cache_dir - if not os.environ.get("LOCALSTACK_VOLUME_DIR", "").strip(): - config.VOLUME_DIR = str(cache_dir() / "volume") - - # FIXME: at some point we should remove the use of `config.dirs` for the CLI, - # see https://github.com/localstack/localstack/pull/7906 - config.dirs.for_cli().mkdirs() - - -@localstack.group( - name="config", - short_help="Manage your LocalStack config", -) -def localstack_config() -> None: - """ - Inspect and validate your LocalStack configuration. - """ - pass - - -@localstack_config.command(name="show", short_help="Show your config") -@_click_format_option -@publish_invocation -def cmd_config_show(format_: str) -> None: - """ - Print the current LocalStack config values. - - This command prints the LocalStack configuration values from your environment. - It analyzes the environment variables as well as the LocalStack CLI profile. - It does _not_ analyze a specific file (like a docker-compose-yml). - """ - # TODO: parse values from potential docker-compose file? - assert config - - try: - # only load the ext config if it's available - from localstack.pro.core import config as ext_config - - assert ext_config - except ImportError: - # the ext package is not available - return None - - if format_ == "table": - _print_config_table() - elif format_ == "plain": - _print_config_pairs() - elif format_ == "dict": - _print_config_dict() - elif format_ == "json": - _print_config_json() - else: - _print_config_pairs() # fall back to plain - - -@localstack_config.command(name="validate", short_help="Validate your config") -@click.option( - "-f", - "--file", - help="Path to compose file", - default="docker-compose.yml", - type=click.Path(exists=True, file_okay=True, readable=True), -) -@publish_invocation -def cmd_config_validate(file: str) -> None: - """ - Validate your LocalStack configuration (docker compose). - - This command inspects the given docker-compose file (by default docker-compose.yml in the current working - directory) and validates if the configuration is valid. - - \b - It will show an error and return a non-zero exit code if: - - The docker-compose file is syntactically incorrect. - - If the file contains common issues when configuring LocalStack. - """ - - from localstack.utils import bootstrap - - if bootstrap.validate_localstack_config(file): - console.print("[green]:heavy_check_mark:[/green] config valid") - sys.exit(0) - else: - console.print("[red]:heavy_multiplication_x:[/red] validation error") - sys.exit(1) - - -def _print_config_json() -> None: - import json - - console.print(json.dumps(dict(config.collect_config_items()), cls=CustomEncoder)) - - -def _print_config_pairs() -> None: - for key, value in config.collect_config_items(): - console.print(f"{key}={value}") - - -def _print_config_dict() -> None: - console.print(dict(config.collect_config_items())) - - -def _print_config_table() -> None: - from rich.table import Table - - grid = Table(show_header=True) - grid.add_column("Key") - grid.add_column("Value") - - for key, value in config.collect_config_items(): - grid.add_row(key, str(value)) - - console.print(grid) - - -@localstack.group( - name="status", - short_help="Query status info", - invoke_without_command=True, -) -@click.pass_context -def localstack_status(ctx: click.Context) -> None: - """ - Query status information about the currently running LocalStack instance. - """ - if ctx.invoked_subcommand is None: - ctx.invoke(localstack_status.get_command(ctx, "docker")) - - -@localstack_status.command(name="docker", short_help="Query LocalStack Docker status") -@_click_format_option -def cmd_status_docker(format_: str) -> None: - """ - Query information about the currently running LocalStack Docker image, its container, - and the LocalStack runtime. - """ - with console.status("Querying Docker status"): - _print_docker_status(format_) - - -class DockerStatus(TypedDict, total=False): - running: bool - runtime_version: str - image_tag: str - image_id: str - image_created: str - container_name: Optional[str] - container_ip: Optional[str] - - -def _print_docker_status(format_: str) -> None: - from localstack.utils import docker_utils - from localstack.utils.bootstrap import get_docker_image_details, get_server_version - from localstack.utils.container_networking import get_main_container_ip, get_main_container_name - - img = get_docker_image_details() - cont_name = config.MAIN_CONTAINER_NAME - running = docker_utils.DOCKER_CLIENT.is_container_running(cont_name) - status = DockerStatus( - runtime_version=get_server_version(), - image_tag=img["tag"], - image_id=img["id"], - image_created=img["created"], - running=running, - ) - if running: - status["container_name"] = get_main_container_name() - status["container_ip"] = get_main_container_ip() - - if format_ == "dict": - console.print(status) - if format_ == "table": - _print_docker_status_table(status) - if format_ == "json": - console.print(json.dumps(status)) - if format_ == "plain": - for key, value in status.items(): - console.print(f"{key}={value}") - - -def _print_docker_status_table(status: DockerStatus) -> None: - from rich.table import Table - - grid = Table(show_header=False) - grid.add_column() - grid.add_column() - - grid.add_row("Runtime version", f"[bold]{status['runtime_version']}[/bold]") - grid.add_row( - "Docker image", - f"tag: {status['image_tag']}, " - f"id: {status['image_id']}, " - f":calendar: {status['image_created']}", - ) - cont_status = "[bold][red]:heavy_multiplication_x: stopped" - if status["running"]: - cont_status = ( - f"[bold][green]:heavy_check_mark: running[/green][/bold] " - f'(name: "[italic]{status["container_name"]}[/italic]", IP: {status["container_ip"]})' - ) - grid.add_row("Runtime status", cont_status) - console.print(grid) - - -@localstack_status.command(name="services", short_help="Query LocalStack services status") -@_click_format_option -def cmd_status_services(format_: str) -> None: - """ - Query information about the services of the currently running LocalStack instance. - """ - url = config.external_service_url() - - try: - health = requests.get(f"{url}/_localstack/health", timeout=2) - doc = health.json() - services = doc.get("services", []) - if format_ == "table": - _print_service_table(services) - if format_ == "plain": - for service, status in services.items(): - console.print(f"{service}={status}") - if format_ == "dict": - console.print(services) - if format_ == "json": - console.print(json.dumps(services)) - except requests.ConnectionError: - if config.DEBUG: - console.print_exception() - raise CLIError(f"could not connect to LocalStack health endpoint at {url}") - - -def _print_service_table(services: Dict[str, str]) -> None: - from rich.table import Table - - status_display = { - "running": "[green]:heavy_check_mark:[/green] running", - "starting": ":hourglass_flowing_sand: starting", - "available": "[grey]:heavy_check_mark:[/grey] available", - "error": "[red]:heavy_multiplication_x:[/red] error", - } - - table = Table() - table.add_column("Service") - table.add_column("Status") - - services = list(services.items()) - services.sort(key=lambda item: item[0]) - - for service, status in services: - if status in status_display: - status = status_display[status] - - table.add_row(service, status) - - console.print(table) - - -@localstack.command(name="start", short_help="Start LocalStack") -@click.option("--docker", is_flag=True, help="Start LocalStack in a docker container [default]") -@click.option("--host", is_flag=True, help="Start LocalStack directly on the host") -@click.option("--no-banner", is_flag=True, help="Disable LocalStack banner", default=False) -@click.option( - "-d", "--detached", is_flag=True, help="Start LocalStack in the background", default=False -) -@click.option( - "--network", - type=str, - help="The container network the LocalStack container should be started in. By default, the default docker bridge network is used.", - required=False, -) -@click.option( - "--env", - "-e", - help="Additional environment variables that are passed to the LocalStack container", - multiple=True, - required=False, -) -@click.option( - "--publish", - "-p", - help="Additional port mappings that are passed to the LocalStack container", - multiple=True, - required=False, -) -@click.option( - "--volume", - "-v", - help="Additional volume mounts that are passed to the LocalStack container", - multiple=True, - required=False, -) -@click.option( - "--host-dns", - help="Expose the LocalStack DNS server to the host using port bindings.", - required=False, - is_flag=True, - default=False, -) -@click.option( - "--stack", - "-s", - type=str, - help="Use a specific stack with optional version. Examples: [localstack:4.5, snowflake]", - required=False, -) -@publish_invocation -def cmd_start( - docker: bool, - host: bool, - no_banner: bool, - detached: bool, - network: str = None, - env: Tuple = (), - publish: Tuple = (), - volume: Tuple = (), - host_dns: bool = False, - stack: str = None, -) -> None: - """ - Start the LocalStack runtime. - - This command starts the LocalStack runtime with your current configuration. - By default, it will start a new Docker container from the latest LocalStack(-Pro) Docker image - with best-practice volume mounts and port mappings. - """ - if docker and host: - raise CLIError("Please specify either --docker or --host") - if host and detached: - raise CLIError("Cannot start detached in host mode") - - if stack: - # Validate allowed stacks - stack_name = stack.split(":")[0] - allowed_stacks = ("localstack", "localstack-pro", "snowflake") - if stack_name.lower() not in allowed_stacks: - raise CLIError(f"Invalid stack '{stack_name}'. Allowed stacks: {allowed_stacks}.") - - # Set IMAGE_NAME, defaulting to :latest if no version specified - if ":" not in stack: - stack = f"{stack}:latest" - os.environ["IMAGE_NAME"] = f"localstack/{stack}" - - if not no_banner: - print_banner() - print_version() - print_profile() - print_app() - console.line() - - from localstack.utils import bootstrap - - if not no_banner: - if host: - console.log("starting LocalStack in host mode :laptop_computer:") - else: - console.log("starting LocalStack in Docker mode :whale:") - - if host: - # call hooks to prepare host - bootstrap.prepare_host(console) - - # from here we abandon the regular CLI control path and start treating the process like a localstack - # runtime process - os.environ["LOCALSTACK_CLI"] = "0" - config.dirs = config.init_directories() - - try: - bootstrap.start_infra_locally() - except ImportError: - if config.DEBUG: - console.print_exception() - raise CLIError( - "It appears you have a light install of localstack which only supports running in docker.\n" - "If you would like to use --host, please install localstack with Python using " - "`pip install localstack[runtime]` instead." - ) - else: - # make sure to initialize the bootstrap environment and directories for the host (even if we're executing - # in Docker), to allow starting the container from within other containers (e.g., Github Codespaces). - config.OVERRIDE_IN_DOCKER = False - config.is_in_docker = False - config.dirs = config.init_directories() - - # call hooks to prepare host (note that this call should stay below the config overrides above) - bootstrap.prepare_host(console) - - # pass the parsed cli params to the start infra command - params = click.get_current_context().params - - if network: - # reconciles the network config and makes sure that MAIN_DOCKER_NETWORK is set automatically if - # `--network` is set. - if config.MAIN_DOCKER_NETWORK: - if config.MAIN_DOCKER_NETWORK != network: - raise CLIError( - f"Values of MAIN_DOCKER_NETWORK={config.MAIN_DOCKER_NETWORK} and --network={network} " - f"do not match" - ) - else: - config.MAIN_DOCKER_NETWORK = network - os.environ["MAIN_DOCKER_NETWORK"] = network - - if detached: - bootstrap.start_infra_in_docker_detached(console, params) - else: - bootstrap.start_infra_in_docker(console, params) - - -@localstack.command(name="stop", short_help="Stop LocalStack") -@publish_invocation -def cmd_stop() -> None: - """ - Stops the current LocalStack runtime. - - This command stops the currently running LocalStack docker container. - By default, this command looks for a container named `localstack-main` (which is the default - container name used by the `localstack start` command). - If your LocalStack container has a different name, set the config variable - `MAIN_CONTAINER_NAME`. - """ - from localstack.utils.docker_utils import DOCKER_CLIENT - - from ..utils.container_utils.container_client import NoSuchContainer - - container_name = config.MAIN_CONTAINER_NAME - - try: - DOCKER_CLIENT.stop_container(container_name) - console.print("container stopped: %s" % container_name) - except NoSuchContainer: - raise CLIError( - f'Expected a running LocalStack container named "{container_name}", but found none' - ) - - -@localstack.command(name="restart", short_help="Restart LocalStack") -@publish_invocation -def cmd_restart() -> None: - """ - Restarts the current LocalStack runtime. - """ - url = config.external_service_url() - - try: - response = requests.post( - f"{url}/_localstack/health", - json={"action": "restart"}, - ) - response.raise_for_status() - console.print("LocalStack restarted within the container.") - except requests.ConnectionError: - if config.DEBUG: - console.print_exception() - raise CLIError("could not restart the LocalStack container") - - -@localstack.command( - name="logs", - short_help="Show LocalStack logs", -) -@click.option( - "-f", - "--follow", - is_flag=True, - help="Block the terminal and follow the log output", - default=False, -) -@click.option( - "-n", - "--tail", - type=int, - help="Print only the last lines of the log output", - default=None, - metavar="N", -) -@publish_invocation -def cmd_logs(follow: bool, tail: int) -> None: - """ - Show the logs of the current LocalStack runtime. - - This command shows the logs of the currently running LocalStack docker container. - By default, this command looks for a container named `localstack-main` (which is the default - container name used by the `localstack start` command). - If your LocalStack container has a different name, set the config variable - `MAIN_CONTAINER_NAME`. - """ - from localstack.utils.docker_utils import DOCKER_CLIENT - - container_name = config.MAIN_CONTAINER_NAME - logfile = get_container_default_logfile_location(container_name) - - if not DOCKER_CLIENT.is_container_running(container_name): - console.print("localstack container not running") - if os.path.exists(logfile): - console.print("printing logs from previous run") - with open(logfile) as fd: - for line in fd: - click.echo(line, nl=False) - sys.exit(1) - - if follow: - num_lines = 0 - for line in DOCKER_CLIENT.stream_container_logs(container_name): - print(line.decode("utf-8").rstrip("\r\n")) - num_lines += 1 - if tail is not None and num_lines >= tail: - break - - else: - logs = DOCKER_CLIENT.get_container_logs(container_name) - if tail is not None: - logs = "\n".join(logs.split("\n")[-tail:]) - print(logs) - - -@localstack.command(name="wait", short_help="Wait for LocalStack") -@click.option( - "-t", - "--timeout", - type=float, - help="Only wait for seconds before raising a timeout error", - default=None, - metavar="N", -) -@publish_invocation -def cmd_wait(timeout: Optional[float] = None) -> None: - """ - Wait for the LocalStack runtime to be up and running. - - This commands waits for a started LocalStack runtime to be up and running, ready to serve - requests. - By default, this command looks for a container named `localstack-main` (which is the default - container name used by the `localstack start` command). - If your LocalStack container has a different name, set the config variable - `MAIN_CONTAINER_NAME`. - """ - from localstack.utils.bootstrap import wait_container_is_ready - - if not wait_container_is_ready(timeout=timeout): - raise CLIError("timeout") - - -@localstack.command(name="ssh", short_help="Obtain a shell in LocalStack") -@publish_invocation -def cmd_ssh() -> None: - """ - Obtain a shell in the current LocalStack runtime. - - This command starts a new interactive shell in the currently running LocalStack container. - By default, this command looks for a container named `localstack-main` (which is the default - container name used by the `localstack start` command). - If your LocalStack container has a different name, set the config variable - `MAIN_CONTAINER_NAME`. - """ - from localstack.utils.docker_utils import DOCKER_CLIENT - - if not DOCKER_CLIENT.is_container_running(config.MAIN_CONTAINER_NAME): - raise CLIError( - f'Expected a running LocalStack container named "{config.MAIN_CONTAINER_NAME}", but found none' - ) - os.execlp("docker", "docker", "exec", "-it", config.MAIN_CONTAINER_NAME, "bash") - - -@localstack.group(name="update", short_help="Update LocalStack") -def localstack_update() -> None: - """ - Update different LocalStack components. - """ - pass - - -@localstack_update.command(name="all", short_help="Update all LocalStack components") -@click.pass_context -@publish_invocation -def cmd_update_all(ctx: click.Context) -> None: - """ - Update all LocalStack components. - - This is the same as executing `localstack update localstack-cli` and - `localstack update docker-images`. - Updating the LocalStack CLI is currently only supported if the CLI - is installed and run via Python / PIP. If you used a different installation method, - please follow the instructions on https://docs.localstack.cloud/. - """ - ctx.invoke(localstack_update.get_command(ctx, "localstack-cli")) - ctx.invoke(localstack_update.get_command(ctx, "docker-images")) - - -@localstack_update.command(name="localstack-cli", short_help="Update LocalStack CLI") -@publish_invocation -def cmd_update_localstack_cli() -> None: - """ - Update the LocalStack CLI. - - This command updates the LocalStack CLI. This is currently only supported if the CLI - is installed and run via Python / PIP. If you used a different installation method, - please follow the instructions on https://docs.localstack.cloud/. - """ - if is_frozen_bundle(): - # "update" can only be performed if running from source / in a non-frozen interpreter - raise CLIError( - "The LocalStack CLI can only update itself if installed via PIP. " - "Please follow the instructions on https://docs.localstack.cloud/ to update your CLI." - ) - - import subprocess - from subprocess import CalledProcessError - - console.rule("Updating LocalStack CLI") - with console.status("Updating LocalStack CLI..."): - try: - subprocess.check_output( - [sys.executable, "-m", "pip", "install", "--upgrade", "localstack"] - ) - console.print(":heavy_check_mark: LocalStack CLI updated") - except CalledProcessError: - console.print(":heavy_multiplication_x: LocalStack CLI update failed", style="bold red") - - -@localstack_update.command( - name="docker-images", short_help="Update docker images LocalStack depends on" -) -@publish_invocation -def cmd_update_docker_images() -> None: - """ - Update all Docker images LocalStack depends on. - - This command updates all Docker LocalStack docker images, as well as other Docker images - LocalStack depends on (and which have been used before / are present on the machine). - """ - from localstack.utils.docker_utils import DOCKER_CLIENT - - console.rule("Updating docker images") - - all_images = DOCKER_CLIENT.get_docker_image_names(strip_latest=False) - image_prefixes = [ - "localstack/", - "public.ecr.aws/lambda", - ] - localstack_images = [ - image - for image in all_images - if any( - image.startswith(image_prefix) or image.startswith(f"docker.io/{image_prefix}") - for image_prefix in image_prefixes - ) - and not image.endswith(":") # ignore dangling images - ] - update_images(localstack_images) - - -def update_images(image_list: List[str]) -> None: - from rich.markup import escape - from rich.progress import MofNCompleteColumn, Progress - - from localstack.utils.container_utils.container_client import ContainerException - from localstack.utils.docker_utils import DOCKER_CLIENT - - updated_count = 0 - failed_count = 0 - progress = Progress( - *Progress.get_default_columns(), MofNCompleteColumn(), transient=True, console=console - ) - with progress: - for image in progress.track(image_list, description="Processing image..."): - try: - updated = False - hash_before_pull = DOCKER_CLIENT.inspect_image(image_name=image, pull=False)["Id"] - DOCKER_CLIENT.pull_image(image) - if ( - hash_before_pull - != DOCKER_CLIENT.inspect_image(image_name=image, pull=False)["Id"] - ): - updated = True - updated_count += 1 - console.print( - f":heavy_check_mark: Image {escape(image)} {'updated' if updated else 'up-to-date'}.", - style="bold" if updated else None, - highlight=False, - ) - except ContainerException as e: - console.print( - f":heavy_multiplication_x: Image {escape(image)} pull failed: {e.message}", - style="bold red", - highlight=False, - ) - failed_count += 1 - console.rule() - console.print( - f"Images updated: {updated_count}, Images failed: {failed_count}, total images processed: {len(image_list)}." - ) - - -@localstack.command(name="completion", short_help="CLI shell completion") -@click.pass_context -@click.argument( - "shell", required=True, type=click.Choice(["bash", "zsh", "fish"], case_sensitive=False) -) -@publish_invocation -def localstack_completion(ctx: click.Context, shell: str) -> None: - """ - Print shell completion code for the specified shell (bash, zsh, or fish). - The shell code must be evaluated to enable the interactive shell completion of LocalStack CLI commands. - This is usually done by sourcing it from the .bash_profile. - - \b - Examples: - # Bash - ## Bash completion on Linux depends on the 'bash-completion' package. - ## Write the LocalStack CLI completion code for bash to a file and source it from .bash_profile - localstack completion bash > ~/.localstack/completion.bash.inc - printf " - # LocalStack CLI bash completion - source '$HOME/.localstack/completion.bash.inc' - " >> $HOME/.bash_profile - source $HOME/.bash_profile - \b - # zsh - ## Set the LocalStack completion code for zsh to autoload on startup: - localstack completion zsh > "${fpath[1]}/_localstack" - \b - # fish - ## Set the LocalStack completion code for fish to autoload on startup: - localstack completion fish > ~/.config/fish/completions/localstack.fish - """ - - # lookup the completion, raise an error if the given completion is not found - import click.shell_completion - - comp_cls = click.shell_completion.get_completion_class(shell) - if comp_cls is None: - raise CLIError("Completion for given shell could not be found.") - - # Click's program name is the base path of sys.argv[0] - path = sys.argv[0] - prog_name = os.path.basename(path) - - # create the completion variable according to the docs - # https://click.palletsprojects.com/en/8.1.x/shell-completion/#enabling-completion - complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() - - # instantiate the completion class and print the completion source - comp = comp_cls(ctx.command, {}, prog_name, complete_var) - click.echo(comp.source()) - - -def print_version() -> None: - console.print(f"- [bold]LocalStack CLI:[/bold] [blue]{VERSION}[/blue]") - - -def print_profile() -> None: - if config.LOADED_PROFILES: - console.print(f"- [bold]Profile:[/bold] [blue]{', '.join(config.LOADED_PROFILES)}[/blue]") - - -def print_app() -> None: - console.print("- [bold]App:[/bold] https://app.localstack.cloud") - - -def print_banner() -> None: - print(BANNER) - - -def is_frozen_bundle() -> bool: - """ - :return: true if we are currently running in a frozen bundle / a pyinstaller binary. - """ - # check if we are in a PyInstaller binary - # https://pyinstaller.org/en/stable/runtime-information.html - return getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS") diff --git a/localstack-core/localstack/cli/lpm.py b/localstack-core/localstack/cli/lpm.py index ad4a6f5489d5c..5d90dbf7b0732 100644 --- a/localstack-core/localstack/cli/lpm.py +++ b/localstack-core/localstack/cli/lpm.py @@ -1,7 +1,6 @@ import itertools import logging from multiprocessing.pool import ThreadPool -from typing import List, Optional import click from rich.console import Console @@ -74,10 +73,10 @@ def _do_install_package(package: Package, version: str = None, target: InstallTa help="target of the installation", ) def install( - package: List[str], - parallel: Optional[int] = 1, - version: Optional[str] = None, - target: Optional[str] = None, + package: list[str], + parallel: int | None = 1, + version: str | None = None, + target: str | None = None, ): """Install one or more packages.""" try: diff --git a/localstack-core/localstack/cli/main.py b/localstack-core/localstack/cli/main.py deleted file mode 100644 index de1f04e38cac5..0000000000000 --- a/localstack-core/localstack/cli/main.py +++ /dev/null @@ -1,22 +0,0 @@ -import os - - -def main(): - # indicate to the environment we are starting from the CLI - os.environ["LOCALSTACK_CLI"] = "1" - - # config profiles are the first thing that need to be loaded (especially before localstack.config!) - from .profiles import set_and_remove_profile_from_sys_argv - - # WARNING: This function modifies sys.argv to remove the profile argument. - set_and_remove_profile_from_sys_argv() - - # initialize CLI plugins - from .localstack import create_with_plugins - - cli = create_with_plugins() - cli() - - -if __name__ == "__main__": - main() diff --git a/localstack-core/localstack/cli/plugin.py b/localstack-core/localstack/cli/plugin.py deleted file mode 100644 index f9af88474a6d5..0000000000000 --- a/localstack-core/localstack/cli/plugin.py +++ /dev/null @@ -1,39 +0,0 @@ -import abc -import logging -import os - -import click -from plux import Plugin, PluginManager - -LOG = logging.getLogger(__name__) - - -class LocalstackCli: - group: click.Group - - def __call__(self, *args, **kwargs): - self.group(*args, **kwargs) - - -class LocalstackCliPlugin(Plugin): - namespace = "localstack.plugins.cli" - - def load(self, cli) -> None: - self.attach(cli) - - @abc.abstractmethod - def attach(self, cli: LocalstackCli) -> None: - """ - Attach commands to the `localstack` CLI. - - :param cli: the cli object - """ - - -def load_cli_plugins(cli): - if os.environ.get("DEBUG_PLUGINS", "0").lower() in ("true", "1"): - # importing localstack.config is still quite expensive... - logging.basicConfig(level=logging.DEBUG) - - loader = PluginManager("localstack.plugins.cli", load_args=(cli,)) - loader.load_all() diff --git a/localstack-core/localstack/cli/plugins.py b/localstack-core/localstack/cli/plugins.py deleted file mode 100644 index c63588161d304..0000000000000 --- a/localstack-core/localstack/cli/plugins.py +++ /dev/null @@ -1,134 +0,0 @@ -import os -import time - -import click -from plux import PluginManager -from plux.build.setuptools import find_plugins -from plux.core.entrypoint import spec_to_entry_point -from rich import print as rprint -from rich.console import Console -from rich.table import Table -from rich.tree import Tree - -from localstack.cli.exceptions import CLIError - -console = Console() - - -@click.group() -def cli(): - """ - The plugins CLI is a set of commands to help troubleshoot LocalStack's plugin mechanism. - """ - pass - - -@cli.command() -@click.option("--where", type=str, default=os.path.abspath(os.curdir)) -@click.option("--exclude", multiple=True, default=()) -@click.option("--include", multiple=True, default=("*",)) -@click.option("--output", type=str, default="tree") -def find(where, exclude, include, output): - """ - Find plugins by scanning the given path for PluginSpecs. - It starts from the current directory if --where is not specified. - This is what a setup.py method would run as a build step, i.e., discovering entry points. - """ - with console.status(f"Scanning path {where}"): - plugins = find_plugins(where, exclude, include) - - if output == "tree": - tree = Tree("Entrypoints") - for namespace, entry_points in plugins.items(): - node = tree.add(f"[bold]{namespace}") - - t = Table() - t.add_column("Name") - t.add_column("Location") - - for ep in entry_points: - key, value = ep.split("=") - t.add_row(key, value) - - node.add(t) - - rprint(tree) - elif output == "dict": - rprint(dict(plugins)) - else: - raise CLIError("unknown output format %s" % output) - - -@cli.command("list") -@click.option("--namespace", type=str, required=True) -def cmd_list(namespace): - """ - List all available plugins using a PluginManager from available endpoints. - """ - manager = PluginManager(namespace) - - t = Table() - t.add_column("Name") - t.add_column("Factory") - - for spec in manager.list_plugin_specs(): - ep = spec_to_entry_point(spec) - t.add_row(spec.name, ep.value) - - rprint(t) - - -@cli.command() -@click.option("--namespace", type=str, required=True) -@click.option("--name", type=str, required=True) -def load(namespace, name): - """ - Attempts to load a plugin using a PluginManager. - """ - manager = PluginManager(namespace) - - with console.status(f"Loading {namespace}:{name}"): - then = time.time() - plugin = manager.load(name) - took = time.time() - then - - rprint( - f":tada: successfully loaded [bold][green]{namespace}[/green][/bold]:[bold][cyan]{name}[/cyan][/bold] ({type(plugin)}" - ) - rprint(f":stopwatch: loading took {took:.4f} s") - - -@cli.command() -@click.option("--namespace", type=str) -def cache(namespace): - """ - Outputs the stevedore entrypoints cache from which plugins are loaded. - """ - from stevedore._cache import _c - - data = _c._get_data_for_path(None) - - tree = Tree("Entrypoints") - for group, entry_points in data.get("groups").items(): - if namespace and group != namespace: - continue - node = tree.add(f"[bold]{group}") - - t = Table() - t.add_column("Name") - t.add_column("Value") - - for key, value, _ in entry_points: - t.add_row(key, value) - - node.add(t) - - if namespace: - rprint(t) - return - - rprint(tree) - - -if __name__ == "__main__": - cli() diff --git a/localstack-core/localstack/cli/profiles.py b/localstack-core/localstack/cli/profiles.py deleted file mode 100644 index 5af5e089658a4..0000000000000 --- a/localstack-core/localstack/cli/profiles.py +++ /dev/null @@ -1,66 +0,0 @@ -import argparse -import os -import sys -from typing import Optional - -# important: this needs to be free of localstack imports - - -def set_and_remove_profile_from_sys_argv(): - """ - Performs the following steps: - - 1. Use argparse to parse the command line arguments for the --profile flag. - All occurrences are removed from the sys.argv list, and the value from - the last occurrence is used. This allows the user to specify a profile - at any point on the command line. - - 2. If a --profile flag is not found, check for the -p flag. The first - occurrence of the -p flag is used and it is not removed from sys.argv. - The reasoning for this is that at least one of the CLI subcommands has - a -p flag, and we want to keep it in sys.argv for that command to - pick up. An existing bug means that if a -p flag is used with a - subcommand, it could erroneously be used as the profile value as well. - This behaviour is undesired, but we must maintain back-compatibility of - allowing the profile to be specified using -p. - - 3. If a profile is found, the 'CONFIG_PROFILE' os variable is set - accordingly. This is later picked up by ``localstack.config``. - - WARNING: Any --profile options are REMOVED from sys.argv, so that they are - not passed to the localstack CLI. This allows the profile option - to be set at any point on the command line. - """ - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--profile") - namespace, sys.argv = parser.parse_known_args(sys.argv) - profile = namespace.profile - - if not profile: - # if no profile is given, check for the -p argument - profile = parse_p_argument(sys.argv) - - if profile: - os.environ["CONFIG_PROFILE"] = profile.strip() - - -def parse_p_argument(args) -> Optional[str]: - """ - Lightweight arg parsing to find the first occurrence of ``-p ``, or ``-p=`` and return the value of - ```` from the given arguments. - - :param args: list of CLI arguments - :returns: the value of ``-p``. - """ - for i, current_arg in enumerate(args): - if current_arg.startswith("-p="): - # if using the "=" notation, we remove the "-p=" prefix to get the value - return current_arg[3:] - if current_arg == "-p": - # otherwise use the next arg in the args list as value - try: - return args[i + 1] - except IndexError: - return None - - return None diff --git a/localstack-core/localstack/config.py b/localstack-core/localstack/config.py index efbfbf83e6fd3..1139d80c35f33 100644 --- a/localstack-core/localstack/config.py +++ b/localstack-core/localstack/config.py @@ -9,7 +9,8 @@ import time import warnings from collections import defaultdict -from typing import Any, Dict, List, Mapping, Optional, Tuple, TypeVar, Union +from collections.abc import Mapping +from typing import Any, TypeVar from localstack import constants from localstack.constants import ( @@ -18,6 +19,7 @@ DEFAULT_VOLUME_DIR, ENV_INTERNAL_TEST_COLLECT_METRIC, ENV_INTERNAL_TEST_RUN, + ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK, FALSE_STRINGS, LOCALHOST, LOCALHOST_IP, @@ -207,13 +209,13 @@ def __str__(self): return str(self.__dict__) -def eval_log_type(env_var_name: str) -> Union[str, bool]: +def eval_log_type(env_var_name: str) -> str | bool: """Get the log type from environment variable""" ls_log = os.environ.get(env_var_name, "").lower().strip() return ls_log if ls_log in LOG_LEVELS else False -def parse_boolean_env(env_var_name: str) -> Optional[bool]: +def parse_boolean_env(env_var_name: str) -> bool | None: """Parse the value of the given env variable and return True/False, or None if it is not a boolean value.""" value = os.environ.get(env_var_name, "").lower().strip() if value in TRUE_STRINGS: @@ -223,6 +225,11 @@ def parse_boolean_env(env_var_name: str) -> Optional[bool]: return None +def parse_comma_separated_list(env_var_name: str) -> list[str]: + """Parse a comma separated list from the given environment variable.""" + return os.environ.get(env_var_name, "").strip().split(",") + + def is_env_true(env_var_name: str) -> bool: """Whether the given environment variable has a truthy value.""" return os.environ.get(env_var_name, "").lower().strip() in TRUE_STRINGS @@ -233,7 +240,7 @@ def is_env_not_false(env_var_name: str) -> bool: return os.environ.get(env_var_name, "").lower().strip() not in FALSE_STRINGS -def load_environment(profiles: str = None, env=os.environ) -> List[str]: +def load_environment(profiles: str = None, env=os.environ) -> list[str]: """Loads the environment variables from ~/.localstack/{profile}.env, for each profile listed in the profiles. :param env: environment to load profile to. Defaults to `os.environ` :param profiles: a comma separated list of profiles to load (defaults to "default") @@ -285,7 +292,7 @@ def ping(host): """Returns True if the host responds to a ping request""" is_in_windows = is_windows() ping_opts = "-n 1 -w 2000" if is_in_windows else "-c 1 -W 2" - args = "ping %s %s" % (ping_opts, host) + args = f"ping {ping_opts} {host}" return ( subprocess.call( args, shell=not is_in_windows, stdout=subprocess.PIPE, stderr=subprocess.PIPE @@ -337,7 +344,7 @@ def in_docker(): return False except Exception: pass - with open("/proc/1/cgroup", "rt") as ifh: + with open("/proc/1/cgroup") as ifh: content = ifh.read() if "docker" in content or "buildkit" in content: return True @@ -348,7 +355,7 @@ def in_docker(): # containerd does not set any specific file or config, but it does use # io.containerd.snapshotter.v1.overlayfs as the overlay filesystem for `/`. try: - with open("/proc/mounts", "rt") as infile: + with open("/proc/mounts") as infile: for line in infile: line = line.strip() @@ -434,8 +441,8 @@ def in_docker(): VOLUME_DIR = os.environ.get("LOCALSTACK_VOLUME_DIR", "").strip() or TMP_FOLDER # fix for Mac OS, to be able to mount /var/folders in Docker -if TMP_FOLDER.startswith("/var/folders/") and os.path.exists("/private%s" % TMP_FOLDER): - TMP_FOLDER = "/private%s" % TMP_FOLDER +if TMP_FOLDER.startswith("/var/folders/") and os.path.exists(f"/private{TMP_FOLDER}"): + TMP_FOLDER = f"/private{TMP_FOLDER}" # whether to enable verbose debug logging ("LOG" is used when using the CLI with LOCALSTACK_LOG instead of LS_LOG) LS_LOG = eval_log_type("LS_LOG") or eval_log_type("LOG") @@ -488,6 +495,9 @@ def in_docker(): # Docker image to use when starting up containers for port checks PORTS_CHECK_DOCKER_IMAGE = os.environ.get("PORTS_CHECK_DOCKER_IMAGE", "").strip() +# global prefix to prepend to Docker image names (e.g., for using a custom registry mirror) +DOCKER_GLOBAL_IMAGE_PREFIX = os.environ.get("DOCKER_GLOBAL_IMAGE_PREFIX", "").strip() + def is_trace_logging_enabled(): if LS_LOG: @@ -603,9 +613,7 @@ def _validate_port(cls, port_s: str) -> int: def _get_unprivileged_port_range_start(self) -> int: try: - with open( - "/proc/sys/net/ipv4/ip_unprivileged_port_start", "rt" - ) as unprivileged_port_start: + with open("/proc/sys/net/ipv4/ip_unprivileged_port_start") as unprivileged_port_start: port = unprivileged_port_start.read() return int(port.strip()) except Exception: @@ -637,7 +645,7 @@ def __repr__(self) -> str: return f"HostAndPort(host={self.host}, port={self.port})" -class UniqueHostAndPortList(List[HostAndPort]): +class UniqueHostAndPortList(list[HostAndPort]): """ Container type that ensures that ports added to the list are unique based on these rules: @@ -650,7 +658,7 @@ class UniqueHostAndPortList(List[HostAndPort]): - Identical identical hosts and ports are de-duped """ - def __init__(self, iterable: Union[List[HostAndPort], None] = None): + def __init__(self, iterable: list[HostAndPort] | None = None): super().__init__(iterable or []) self._ensure_unique() @@ -661,10 +669,10 @@ def _ensure_unique(self): if len(self) <= 1: return - unique: List[HostAndPort] = list() + unique: list[HostAndPort] = [] # Build a dictionary of hosts by port - hosts_by_port: Dict[int, List[str]] = defaultdict(list) + hosts_by_port: dict[int, list[str]] = defaultdict(list) for item in self: hosts_by_port[item.port].append(item.host) @@ -696,7 +704,7 @@ def append(self, value: HostAndPort): def populate_edge_configuration( environment: Mapping[str, str], -) -> Tuple[HostAndPort, UniqueHostAndPortList]: +) -> tuple[HostAndPort, UniqueHostAndPortList]: """Populate the LocalStack edge configuration from environment variables.""" localstack_host_raw = environment.get("LOCALSTACK_HOST") gateway_listen_raw = environment.get("GATEWAY_LISTEN") @@ -824,6 +832,9 @@ def populate_edge_configuration( # Flag to enable the validation of the requests made to the LocalStack internal endpoints. Active by default. OPENAPI_VALIDATE_REQUEST = is_env_true("OPENAPI_VALIDATE_REQUEST") +# environment variable to determine whether to include stack traces in http responses +INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE = is_env_true("INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE") + # whether to skip waiting for the infrastructure to shut down, or exit immediately FORCE_SHUTDOWN = is_env_not_false("FORCE_SHUTDOWN") @@ -855,12 +866,6 @@ def populate_edge_configuration( # get-function call. INTERNAL_RESOURCE_ACCOUNT = os.environ.get("INTERNAL_RESOURCE_ACCOUNT") or "949334387222" -# TODO: remove with 4.1.0 -# Determine which implementation to use for the event rule / event filtering engine used by multiple services: -# EventBridge, EventBridge Pipes, Lambda Event Source Mapping -# Options: python (default) | java (deprecated since 4.0.3) -EVENT_RULE_ENGINE = os.environ.get("EVENT_RULE_ENGINE", "python").strip() - # ----- # SERVICE-SPECIFIC CONFIGS BELOW # ----- @@ -1182,6 +1187,8 @@ def populate_edge_configuration( os.environ["PROVIDER_OVERRIDE_DYNAMODBSTREAMS"] = "v2" DDB_STREAMS_PROVIDER_V2 = True +SNS_PROVIDER_V2 = os.environ.get("PROVIDER_OVERRIDE_SNS", "") == "v2" + # TODO remove fallback to LAMBDA_DOCKER_NETWORK with next minor version MAIN_DOCKER_NETWORK = os.environ.get("MAIN_DOCKER_NETWORK", "") or LAMBDA_DOCKER_NETWORK @@ -1206,6 +1213,18 @@ def populate_edge_configuration( # EXPERIMENTAL CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES = is_env_not_false("CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES") +# Comma-separated list of resource type names that CloudFormation will ignore on stack creation +CFN_IGNORE_UNSUPPORTED_TYPE_CREATE = parse_comma_separated_list( + "CFN_IGNORE_UNSUPPORTED_TYPE_CREATE" +) +# Comma-separated list of resource type names that CloudFormation will ignore on stack update +CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE = parse_comma_separated_list( + "CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE" +) + +# Decrease the waiting time for resource deployment +CFN_NO_WAIT_ITERATIONS: str | int | None = os.environ.get("CFN_NO_WAIT_ITERATIONS") + # bind address of local DNS server DNS_ADDRESS = os.environ.get("DNS_ADDRESS") or "0.0.0.0" # port of the local DNS server @@ -1235,8 +1254,8 @@ def use_custom_dns(): # s3 virtual host name -S3_VIRTUAL_HOSTNAME = "s3.%s" % LOCALSTACK_HOST.host -S3_STATIC_WEBSITE_HOSTNAME = "s3-website.%s" % LOCALSTACK_HOST.host +S3_VIRTUAL_HOSTNAME = f"s3.{LOCALSTACK_HOST.host}" +S3_STATIC_WEBSITE_HOSTNAME = f"s3-website.{LOCALSTACK_HOST.host}" BOTO_WAITER_DELAY = int(os.environ.get("BOTO_WAITER_DELAY") or "1") BOTO_WAITER_MAX_ATTEMPTS = int(os.environ.get("BOTO_WAITER_MAX_ATTEMPTS") or "120") @@ -1254,6 +1273,9 @@ def use_custom_dns(): # This flag enables all responses from LocalStack to contain a `x-localstack` HTTP header. LOCALSTACK_RESPONSE_HEADER_ENABLED = is_env_not_false("LOCALSTACK_RESPONSE_HEADER_ENABLED") +# Serialization backend for the LocalStack internal state (`dill` is used by default`). +STATE_SERIALIZATION_BACKEND = os.environ.get("STATE_SERIALIZATION_BACKEND", "").strip() or "dill" + # List of environment variable names used for configuration that are passed from the host into the LocalStack container. # => Synchronize this list with the above and the configuration docs: # https://docs.localstack.cloud/references/configuration/ @@ -1390,6 +1412,7 @@ def use_custom_dns(): "SQS_ENDPOINT_STRATEGY", "SQS_DISABLE_CLOUDWATCH_METRICS", "SQS_CLOUDWATCH_METRICS_REPORT_INTERVAL", + "STATE_SERIALIZATION_BACKEND", "STRICT_SERVICE_LOADING", "TF_COMPAT_MODE", "USE_SSL", @@ -1443,7 +1466,12 @@ def is_collect_metrics_mode() -> bool: return is_env_true(ENV_INTERNAL_TEST_COLLECT_METRIC) -def collect_config_items() -> List[Tuple[str, Any]]: +def store_test_metrics_in_local_filesystem() -> bool: + """Returns True if test metrics should be stored in the local filesystem (instead of the system that runs pytest).""" + return is_env_true(ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK) + + +def collect_config_items() -> list[tuple[str, Any]]: """Returns a list of key-value tuples of LocalStack configuration values.""" none = object() # sentinel object @@ -1495,10 +1523,10 @@ def get_protocol() -> str: def external_service_url( - host: Optional[str] = None, - port: Optional[int] = None, - protocol: Optional[str] = None, - subdomains: Optional[str] = None, + host: str | None = None, + port: int | None = None, + protocol: str | None = None, + subdomains: str | None = None, ) -> str: """Returns a service URL (e.g., SQS queue URL) to an external client (e.g., boto3) potentially running on another machine than LocalStack. The configurations LOCALSTACK_HOST and USE_SSL can customize these returned URLs. @@ -1515,10 +1543,10 @@ def external_service_url( def internal_service_url( - host: Optional[str] = None, - port: Optional[int] = None, - protocol: Optional[str] = None, - subdomains: Optional[str] = None, + host: str | None = None, + port: int | None = None, + protocol: str | None = None, + subdomains: str | None = None, ) -> str: """Returns a service URL for internal use within LocalStack (i.e., same host). The configuration USE_SSL can customize these returned URLs but LOCALSTACK_HOST has no effect. @@ -1593,7 +1621,7 @@ def get_edge_url(localstack_hostname=None, protocol=None): class ServiceProviderConfig(Mapping[str, str]): - _provider_config: Dict[str, str] + _provider_config: dict[str, str] default_value: str override_prefix: str = "PROVIDER_OVERRIDE_" @@ -1618,7 +1646,7 @@ def set_provider_if_not_exists(self, service: str, provider: str) -> None: def set_provider(self, service: str, provider: str): self._provider_config[service] = provider - def bulk_set_provider_if_not_exists(self, services: List[str], provider: str): + def bulk_set_provider_if_not_exists(self, services: list[str], provider: str): for service in services: self.set_provider_if_not_exists(service, provider) diff --git a/localstack-core/localstack/constants.py b/localstack-core/localstack/constants.py index c8833e557fced..a946c591f4fc6 100644 --- a/localstack-core/localstack/constants.py +++ b/localstack-core/localstack/constants.py @@ -80,6 +80,10 @@ # environment variable name to tag collect metrics during a test run ENV_INTERNAL_TEST_COLLECT_METRIC = "LOCALSTACK_INTERNAL_TEST_COLLECT_METRIC" +# environment variable name to indicate that metrics should be stored within the container +ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK = "LOCALSTACK_INTERNAL_TEST_METRICS_IN_LOCALSTACK" +ENV_INTERNAL_TEST_STORE_METRICS_PATH = "LOCALSTACK_INTERNAL_TEST_STORE_METRICS_PATH" + # environment variable that flags whether pro was activated. do not use it for security purposes! ENV_PRO_ACTIVATED = "PRO_ACTIVATED" @@ -102,32 +106,6 @@ # strings with valid log levels for LS_LOG LOG_LEVELS = ("trace-internal", "trace", "debug", "info", "warn", "error", "warning") -# the version of elasticsearch that is pre-seeded into the base image (sync with Dockerfile.base) -ELASTICSEARCH_DEFAULT_VERSION = "Elasticsearch_7.10" -# See https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/aes-supported-plugins.html -ELASTICSEARCH_PLUGIN_LIST = [ - "analysis-icu", - "ingest-attachment", - "analysis-kuromoji", - "mapper-murmur3", - "mapper-size", - "analysis-phonetic", - "analysis-smartcn", - "analysis-stempel", - "analysis-ukrainian", -] -# Default ES modules to exclude (save apprx 66MB in the final image) -ELASTICSEARCH_DELETE_MODULES = ["ingest-geoip"] - -# the version of opensearch which is used by default -OPENSEARCH_DEFAULT_VERSION = "OpenSearch_2.11" - -# See https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-plugins.html -OPENSEARCH_PLUGIN_LIST = [ - "ingest-attachment", - "analysis-kuromoji", -] - # API endpoint for analytics events API_ENDPOINT = os.environ.get("API_ENDPOINT") or "https://api.localstack.cloud/v1" # new analytics API endpoint @@ -139,6 +117,9 @@ # AWS region us-east-1 AWS_REGION_US_EAST_1 = "us-east-1" +# AWS region eu-west-1 +AWS_REGION_EU_WEST_1 = "eu-west-1" + # environment variable to override max pool connections try: MAX_POOL_CONNECTIONS = int(os.environ["MAX_POOL_CONNECTIONS"]) @@ -171,9 +152,6 @@ DEFAULT_BUCKET_MARKER_LOCAL = "hot-reload" LEGACY_DEFAULT_BUCKET_MARKER_LOCAL = "__local__" -# user that starts the opensearch process if the current user is root -OS_USER_OPENSEARCH = "localstack" - # output string that indicates that the stack is ready READY_MARKER_OUTPUT = "Ready." diff --git a/localstack-core/localstack/deprecations.py b/localstack-core/localstack/deprecations.py index 1690ca227d878..282a6c8cf67ec 100644 --- a/localstack-core/localstack/deprecations.py +++ b/localstack-core/localstack/deprecations.py @@ -1,8 +1,8 @@ # A simple module to track deprecations over time / versions, and some simple functions guiding the affected users. import logging import os +from collections.abc import Callable from dataclasses import dataclass -from typing import Callable, List, Optional from localstack.utils.analytics import log @@ -35,12 +35,6 @@ def is_affected(self) -> bool: # Please make sure this is in-sync with https://docs.localstack.cloud/references/configuration/ # DEPRECATIONS = [ - # Since 0.11.3 - HTTP / HTTPS multiplexing - EnvVarDeprecation( - "USE_SSL", - "0.11.3", - "Each endpoint now supports multiplexing HTTP/HTTPS traffic over the same port. Please remove this environment variable.", # noqa - ), # Since 0.12.8 - PORT_UI was removed EnvVarDeprecation( "PORT_WEB_UI", @@ -325,12 +319,18 @@ def is_affected(self) -> bool: "By default, LocalStack routes Step Functions traffic to its internal runtime. " "Use this variable only if you need to redirect traffic to a different local Step Functions runtime.", ), + EnvVarDeprecation( + "ACTIVATE_PRO", + "4.14.0", + "Starting in March 2026, LocalStack will require an auth token. " + "Go to this page for more infos: https://localstack.cloud/2026-updates", + ), ] def collect_affected_deprecations( - deprecations: Optional[List[EnvVarDeprecation]] = None, -) -> List[EnvVarDeprecation]: + deprecations: list[EnvVarDeprecation] | None = None, +) -> list[EnvVarDeprecation]: """ Collects all deprecations which are used in the OS environ. :param deprecations: List of deprecations to check. Uses DEPRECATIONS list by default. @@ -341,7 +341,7 @@ def collect_affected_deprecations( return [deprecation for deprecation in deprecations if deprecation.is_affected] -def log_env_warning(deprecations: List[EnvVarDeprecation]) -> None: +def log_env_warning(deprecations: list[EnvVarDeprecation]) -> None: """ Logs warnings for the given deprecations. :param deprecations: list of affected deprecations to show a warning for @@ -368,7 +368,7 @@ def log_env_warning(deprecations: List[EnvVarDeprecation]) -> None: log.event(event="deprecated_env_usage", payload={"deprecated_env_vars": env_vars}) -def log_deprecation_warnings(deprecations: Optional[List[EnvVarDeprecation]] = None) -> None: +def log_deprecation_warnings(deprecations: list[EnvVarDeprecation] | None = None) -> None: affected_deprecations = collect_affected_deprecations(deprecations) log_env_warning(affected_deprecations) diff --git a/localstack-core/localstack/dev/kubernetes/__main__.py b/localstack-core/localstack/dev/kubernetes/__main__.py index 8935027298ef0..a72a0dfe32604 100644 --- a/localstack-core/localstack/dev/kubernetes/__main__.py +++ b/localstack-core/localstack/dev/kubernetes/__main__.py @@ -1,10 +1,18 @@ import dataclasses import os +import shlex +import subprocess as sp from typing import Literal import click import yaml +EDGE_SERVICE_NODE_PORT = 30066 +NODE_PORT_START = 30010 +SERVICE_PORT_START = 4510 +NUMBER_OF_SERVICE_PORTS = 50 +EDGE_SERVICE_DNS_PORT = 31053 + @dataclasses.dataclass class MountPoint: @@ -23,11 +31,11 @@ def generate_mount_points( # host paths root_path = os.path.join(os.path.dirname(__file__), "..", "..", "..", "..") localstack_code_path = os.path.join(root_path, "localstack-core", "localstack") - pro_path = os.path.join(root_path, "..", "localstack-ext") + pro_path = os.path.join(root_path, "..", "localstack-pro") # container paths target_path = "/opt/code/localstack/" - venv_path = os.path.join(target_path, ".venv", "lib", "python3.11", "site-packages") + venv_path = os.path.join(target_path, ".venv", "lib", "python3.13", "site-packages") # Community code if pro: @@ -139,7 +147,12 @@ def generate_mount_points( return mount_points -def generate_k8s_cluster_config(mount_points: list[MountPoint], port: int = 4566): +def generate_k8s_cluster_config( + mount_points: list[MountPoint], + port: int = 4566, + expose_dns: bool = False, + dns_port: int = 53, +): volumes = [ { "volume": f"{mount_point.host_path}:{mount_point.node_path}", @@ -148,9 +161,56 @@ def generate_k8s_cluster_config(mount_points: list[MountPoint], port: int = 4566 for mount_point in mount_points ] - ports = [{"port": f"{port}:31566", "nodeFilters": ["server:0"]}] + ports = [ + # main gateway port + { + "nodeFilters": [ + "server:0", + ], + "port": f"{port}:{EDGE_SERVICE_NODE_PORT}", + }, + # 443 https port for main gateway + { + "nodeFilters": [ + "server:0", + ], + "port": f"443:{EDGE_SERVICE_NODE_PORT}", + }, + # Node ports + { + "nodeFilters": [ + "server:0", + ], + "port": f"{SERVICE_PORT_START}-{SERVICE_PORT_START + NUMBER_OF_SERVICE_PORTS - 1}:{NODE_PORT_START}-{NODE_PORT_START + NUMBER_OF_SERVICE_PORTS - 1}", + }, + ] - config = {"apiVersion": "k3d.io/v1alpha5", "kind": "Simple", "volumes": volumes, "ports": ports} + if expose_dns: + ports.append( + { + "nodeFilters": [ + "server:0", + ], + "port": f"{dns_port}:{EDGE_SERVICE_DNS_PORT}", + } + ) + + config = { + "apiVersion": "k3d.io/v1alpha5", + "kind": "Simple", + "volumes": volumes, + "ports": ports, + "options": { + "k3s": { + "extraArgs": [ + { + "arg": "--kubelet-arg=container-log-max-size=1Gi", + "nodeFilters": ["server:*"], + }, + ], + }, + }, + } return config @@ -159,7 +219,7 @@ def snake_to_kebab_case(string: str): return string.lower().replace("_", "-") -def generate_k8s_cluster_overrides( +def generate_k8s_helm_overrides( mount_points: list[MountPoint], pro: bool = False, env: list[str] | None = None ): volumes = [ @@ -200,10 +260,48 @@ def generate_k8s_cluster_overrides( "name": "CONTAINER_RUNTIME", "value": "kubernetes", }, + { + "name": "RDS_MYSQL_DOCKER", + "value": "1", + }, + { + "name": "ENABLE_DMS", + "value": "1", + }, + { + "name": "ENABLE_BEDROCK", + "value": "1", + }, + { + "name": "DOCDB_PROXY_CONTAINER", + "value": "1", + }, + { + "name": "GLUE_JOB_EXECUTOR_PROVIDER", + "value": "v2", + }, + { + "name": "CLOUDFRONT_LAMBDA_EDGE", + "value": "1", + }, ] image_repository = "localstack/localstack-pro" if pro else "localstack/localstack" + service = { + "edgeService": { + "nodePort": EDGE_SERVICE_NODE_PORT, + }, + "externalServicePorts": { + "start": SERVICE_PORT_START, + "end": SERVICE_PORT_START + NUMBER_OF_SERVICE_PORTS, + "nodePortStart": NODE_PORT_START, + }, + "dnsService": { + "enabled": True, + "nodePort": EDGE_SERVICE_DNS_PORT, + }, + } overrides = { "debug": True, "volumes": volumes, @@ -211,17 +309,19 @@ def generate_k8s_cluster_overrides( "extraEnvVars": extra_env_vars, "image": {"repository": image_repository}, "lambda": {"executor": "kubernetes"}, + "service": service, + "readinessProbe": {"initialDelaySeconds": 10}, + "livenessProbe": {"initialDelaySeconds": 10}, } return overrides -def write_file(content: dict, output_path: str, file_name: str): - path = os.path.join(output_path, file_name) - with open(path, "w") as f: +def write_file(content: dict, output_path: str): + with open(output_path, "w") as f: f.write(yaml.dump(content)) f.close() - print(f"Generated file at {path}") + print(f"Generated file at {output_path}") def print_file(content: dict, file_name: str): @@ -231,6 +331,22 @@ def print_file(content: dict, file_name: str): print("=====================================") +def generate_k3d_command(config_file_path: str) -> str: + return f"k3d cluster create --config {config_file_path}" + + +def generate_helm_command(overrides_file_path: str) -> str: + return f"helm upgrade --install localstack localstack/localstack -f {overrides_file_path}" + + +def execute_deployment(config_file_path: str, overrides_file_path: str): + """ + Use the k3d and helm commands to create a cluster and deploy LocalStack in one command + """ + sp.check_call(shlex.split(generate_k3d_command(config_file_path))) + sp.check_call(shlex.split(generate_helm_command(overrides_file_path))) + + @click.command("run") @click.option( "--pro", is_flag=True, default=None, help="Mount the localstack-pro code into the cluster." @@ -275,6 +391,25 @@ def print_file(content: dict, file_name: str): help="Port to expose from the kubernetes node", type=click.IntRange(0, 65535), ) +@click.option( + "--expose-dns", + is_flag=True, + default=False, + help="Expose DNS port from the kubernetes node.", +) +@click.option( + "--dns-port", + default=53, + help="DNS port to expose from the kubernetes node. It is applied only if --expose-dns is set.", + type=click.IntRange(0, 65535), +) +@click.option( + "--execute", + "-x", + is_flag=True, + default=False, + help="Execute deployment from generated config files. Implies -w/--write.", +) @click.argument("command", nargs=-1, required=False) def run( pro: bool = None, @@ -287,39 +422,44 @@ def run( command: str = None, env: list[str] = None, port: int = None, + expose_dns: bool = False, + dns_port: int = 53, + execute: bool = False, ): """ A tool for localstack developers to generate the kubernetes cluster configuration file and the overrides to mount the localstack code into the cluster. """ mount_points = generate_mount_points(pro, mount_moto, mount_entrypoints) - config = generate_k8s_cluster_config(mount_points, port=port) + config = generate_k8s_cluster_config( + mount_points, port=port, expose_dns=expose_dns, dns_port=dns_port + ) - overrides = generate_k8s_cluster_overrides(mount_points, pro=pro, env=env) + overrides = generate_k8s_helm_overrides(mount_points, pro=pro, env=env) output_dir = output_dir or os.getcwd() overrides_file = overrides_file or "overrides.yml" config_file = config_file or "configuration.yml" - if write: - write_file(config, output_dir, config_file) - write_file(overrides, output_dir, overrides_file) + overrides_file_path = os.path.join(output_dir, overrides_file) + config_file_path = os.path.join(output_dir, config_file) + + if write or execute: + write_file(config, config_file_path) + write_file(overrides, overrides_file_path) + if execute: + execute_deployment(config_file, overrides_file) else: print_file(config, config_file) print_file(overrides, overrides_file) - overrides_file_path = os.path.join(output_dir, overrides_file) - config_file_path = os.path.join(output_dir, config_file) - print("\nTo create a k3d cluster with the generated configuration, follow these steps:") print("1. Run the following command to create the cluster:") - print(f"\n k3d cluster create --config {config_file_path}\n") + print(f"\n {generate_k3d_command(config_file_path)}\n") print("2. Once the cluster is created, start LocalStack with the generated overrides:") print("\n helm repo add localstack https://localstack.github.io/helm-charts # (if required)") - print( - f"\n helm upgrade --install localstack localstack/localstack -f {overrides_file_path}\n" - ) + print(f"\n {generate_helm_command(overrides_file_path)}\n") def main(): diff --git a/localstack-core/localstack/dev/run/__main__.py b/localstack-core/localstack/dev/run/__main__.py index 39ab236c9e3c2..ffc6b4d65692f 100644 --- a/localstack-core/localstack/dev/run/__main__.py +++ b/localstack-core/localstack/dev/run/__main__.py @@ -1,12 +1,21 @@ import dataclasses import os -from typing import Iterable, Tuple +from collections.abc import Iterable import click +from rich.console import Console from rich.rule import Rule from localstack import config -from localstack.cli import console +from localstack.dev.run.configurators import ( + ConfigEnvironmentConfigurator, + DependencyMountConfigurator, + EntryPointMountConfigurator, + ImageConfigurator, + PortConfigurator, + SourceVolumeMountConfigurator, +) +from localstack.dev.run.paths import HOST_PATH_MAPPINGS, HostPaths from localstack.runtime import hooks from localstack.utils.bootstrap import Container, ContainerConfigurators from localstack.utils.container_utils.container_client import ( @@ -19,15 +28,7 @@ from localstack.utils.run import run_interactive from localstack.utils.strings import short_uid -from .configurators import ( - ConfigEnvironmentConfigurator, - DependencyMountConfigurator, - EntryPointMountConfigurator, - ImageConfigurator, - PortConfigurator, - SourceVolumeMountConfigurator, -) -from .paths import HOST_PATH_MAPPINGS, HostPaths +console = Console() @click.command("run") @@ -66,7 +67,13 @@ "--mount-source/--no-mount-source", is_flag=True, default=True, - help="Mount source files from localstack and localstack-ext. Use --local-packages for optional dependencies such as moto.", + help="Mount source files from localstack and localstack-pro. Use --local-packages for optional dependencies such as moto.", +) +@click.option( + "--live-reload/--no-live-reload", + is_flag=True, + default=False, + help="Watch mounted source directories for .py file changes and automatically restart the container runtime.", ) @click.option( "--mount-dependencies/--no-mount-dependencies", @@ -130,12 +137,13 @@ def run( develop: bool = False, randomize: bool = False, mount_source: bool = True, + live_reload: bool = False, mount_dependencies: bool = False, mount_entrypoints: bool = False, mount_docker_socket: bool = True, - env: Tuple = (), - volume: Tuple = (), - publish: Tuple = (), + env: tuple = (), + volume: tuple = (), + publish: tuple = (), entrypoint: str = None, network: str = None, local_packages: list[str] | None = None, @@ -143,7 +151,7 @@ def run( ): """ A tool for localstack developers to start localstack containers. Run this in your localstack or - localstack-ext source tree to mount local source files or dependencies into the container. + localstack-pro source tree to mount local source files or dependencies into the container. Here are some examples:: \b @@ -153,7 +161,7 @@ def run( Explanations and more examples: - Start a normal container localstack container. If you run this from the localstack-ext repo, + Start a normal container localstack container. If you run this from the localstack-pro repo, it will start localstack-pro:: python -m localstack.dev.run @@ -179,6 +187,11 @@ def run( python -m localstack.dev.run --entrypoint /bin/bash -- echo "hello" + Use the --live-reload flag to restart LocalStack on code changes. Beware: this will remove any state + that you had in your LocalStack instance. Consider using PERSISTENCE to keep resources: + + python -m localstack.dev.run --live-reload + You can import and expose debugpy: python -m localstack.dev.run --develop @@ -191,7 +204,7 @@ def run( -v $PWD/tests:/opt/code/localstack/tests \\ -- .venv/bin/python -m pytest tests/unit/http_/ - The script generally assumes that you are executing in either localstack or localstack-ext source + The script generally assumes that you are executing in either localstack or localstack-pro source repositories that are organized like this:: \b @@ -204,7 +217,7 @@ def run( │ ├── pyproject.toml │ ├── tests │ └── ... - ├── localstack-ext <- or execute script in here + ├── localstack-pro <- or execute script in here │ ├── ... │ ├── localstack-pro-core │ │ ├── localstack @@ -254,7 +267,7 @@ def run( # auto-set pro flag if pro is None: - if os.getcwd().endswith("localstack-ext"): + if os.getcwd().endswith("localstack-pro"): pro = True else: pro = False @@ -266,7 +279,7 @@ def run( remove=True, interactive=True, tty=True, - env_vars=dict(), + env_vars={}, volumes=VolumeMappings(), ports=PortMappings(), network=network, @@ -339,10 +352,24 @@ def run( rule = Rule(f"Interactive session with {container_id[:12]} 💻") console.print(rule) + stop_live_reload_watcher = None try: + if live_reload and mount_source: + # Some install targets don't include the `watchdog` dependency, and some developers + # don't install LocalStack using the `Makefile`, so they find that they don't have + # the `watchdog` dependency. We lazy import these functions so that we don't trigger + # an import error for these developers. + from localstack.dev.run.watcher import collect_watch_directories, start_file_watcher + + if watch_dirs := collect_watch_directories(host_paths, pro, local_packages): + stop_live_reload_watcher = start_file_watcher(watch_dirs, docker, container_id) + cmd = [*docker._docker_cmd(), "start", "--interactive", "--attach", container_id] run_interactive(cmd) finally: + if stop_live_reload_watcher is not None: + stop_live_reload_watcher.set() + if container_config.remove: try: if docker.is_container_running(container_id): diff --git a/localstack-core/localstack/dev/run/configurators.py b/localstack-core/localstack/dev/run/configurators.py index 4f1b9e3e29cde..fdb6d54d48f3f 100644 --- a/localstack-core/localstack/dev/run/configurators.py +++ b/localstack-core/localstack/dev/run/configurators.py @@ -187,12 +187,23 @@ def try_mount_to_site_packages(self, cfg: ContainerConfiguration, sources_path: class EntryPointMountConfigurator: """ - Mounts ``entry_points.txt`` files of localstack and dependencies into the venv in the container. - - For example, when starting the pro container, the entrypoints of localstack-ext on the host would be in - ``~/workspace/localstack-ext/localstack-pro-core/localstack_ext.egg-info/entry_points.txt`` - which needs to be mounted into the distribution info of the installed dependency within the container: - ``/opt/code/localstack/.venv/.../site-packages/localstack_ext-2.1.0.dev0.dist-info/entry_points.txt``. + Mounts ``entry_points.txt`` files of localstack and dependencies into the venv in the container. + + For example, when starting the pro container, the entrypoints of localstack-pro on the host would be in + ``~/workspace/localstack-pro/localstack-pro-core/localstack_ext.egg-info/entry_points.txt`` + which needs to be mounted into the distribution info of the installed dependency within the container: + Mounts ``plux.ini`` files of localstack and localstack-pro into the venv in the container. + For other dependencies, we mount the ``entry_points.txt`` build artifacts. + + For example, when starting the pro container, the entrypoints of localstack-pro on the host would be in + ``~/workspace/localstack-pro/localstack-pro-core/plux.ini`` which needs to be mounted into the distribution info + of the installed dependency within the container: + ``/opt/code/localstack/.venv/.../site-packages/localstack_ext-4.13.0.dev0.dist-info/entry_points.txt``. + + For a dependency using plugins, the entrypoints would be in the build artifact at + ``~/workspace//.egg-info/entry_points.txt`` + which also needs to be mounted into the distribution info of the installed dependency within the container: + ``/opt/code/localstack/.venv/.../site-packages/.dist-info/entry_points.txt``. """ entry_point_glob = ( @@ -236,12 +247,7 @@ def __call__(self, cfg: ContainerConfiguration): dep, ver = dep_path.split("-") if dep == "localstack_core": - host_path = ( - self.host_paths.localstack_project_dir - / "localstack-core" - / "localstack_core.egg-info" - / "entry_points.txt" - ) + host_path = self.host_paths.localstack_project_dir / "plux.ini" if host_path.is_file(): cfg.volumes.add( BindMount( @@ -250,13 +256,9 @@ def __call__(self, cfg: ContainerConfiguration): read_only=True, ) ) - continue elif dep == "localstack_ext": host_path = ( - self.host_paths.localstack_pro_project_dir - / "localstack-pro-core" - / "localstack_ext.egg-info" - / "entry_points.txt" + self.host_paths.localstack_pro_project_dir / "localstack-pro-core" / "plux.ini" ) if host_path.is_file(): cfg.volumes.add( @@ -266,12 +268,12 @@ def __call__(self, cfg: ContainerConfiguration): read_only=True, ) ) - continue - for host_path in self.host_paths.workspace_dir.glob( - f"*/{dep}.egg-info/entry_points.txt" - ): - cfg.volumes.add(BindMount(str(host_path), str(container_path), read_only=True)) - break + else: + for host_path in self.host_paths.workspace_dir.glob( + f"*/{dep}.egg-info/entry_points.txt" + ): + cfg.volumes.add(BindMount(str(host_path), str(container_path), read_only=True)) + break class DependencyMountConfigurator: @@ -363,10 +365,7 @@ def _list_files_in_container_image(container_client: ContainerClient, image_name try: # docker export yields paths without prefixed slashes, so we add them here # since the file is pretty big (~4MB for community, ~7MB for pro) we gzip it - cmd = "docker export %s | tar -t | awk '{ print \"/\" $0 }' | gzip > %s" % ( - container_id, - cache_file, - ) + cmd = f"docker export {container_id} | tar -t | awk '{{ print \"/\" $0 }}' | gzip > {cache_file}" run(cmd, shell=True) finally: container_client.remove_container(container_id) diff --git a/localstack-core/localstack/dev/run/paths.py b/localstack-core/localstack/dev/run/paths.py index b1fe9a95f24fd..1f7ec8b63ee0f 100644 --- a/localstack-core/localstack/dev/run/paths.py +++ b/localstack-core/localstack/dev/run/paths.py @@ -1,14 +1,14 @@ """Utilities to resolve important paths on the host and in the container.""" import os +from collections.abc import Callable from pathlib import Path -from typing import Callable, Optional, Union class HostPaths: workspace_dir: Path """We assume all repositories live in a workspace directory, e.g., ``~/workspace/ls/localstack``, - ``~/workspace/ls/localstack-ext``, ...""" + ``~/workspace/ls/localstack-pro``, ...""" localstack_project_dir: Path localstack_pro_project_dir: Path @@ -20,13 +20,13 @@ class HostPaths: def __init__( self, - workspace_dir: Union[os.PathLike, str] = None, - volume_dir: Union[os.PathLike, str] = None, - venv_dir: Union[os.PathLike, str] = None, + workspace_dir: os.PathLike | str = None, + volume_dir: os.PathLike | str = None, + venv_dir: os.PathLike | str = None, ): self.workspace_dir = Path(workspace_dir or os.path.abspath(os.path.join(os.getcwd(), ".."))) self.localstack_project_dir = self.workspace_dir / "localstack" - self.localstack_pro_project_dir = self.workspace_dir / "localstack-ext" + self.localstack_pro_project_dir = self.workspace_dir / "localstack-pro" self.moto_project_dir = self.workspace_dir / "moto" self.postgresql_proxy = self.workspace_dir / "postgresql-proxy" self.rolo_dir = self.workspace_dir / "rolo" @@ -68,11 +68,11 @@ class ContainerPaths: """Important paths in the container""" project_dir: str = "/opt/code/localstack" - site_packages_target_dir: str = "/opt/code/localstack/.venv/lib/python3.11/site-packages" + site_packages_target_dir: str = "/opt/code/localstack/.venv/lib/python3.13/site-packages" docker_entrypoint: str = "/usr/local/bin/docker-entrypoint.sh" localstack_supervisor: str = "/usr/local/bin/localstack-supervisor" localstack_source_dir: str - localstack_pro_source_dir: Optional[str] + localstack_pro_source_dir: str | None def dependency_source(self, name: str) -> str: """Returns path of the given source dependency in the site-packages directory.""" diff --git a/localstack-core/localstack/dev/run/watcher.py b/localstack-core/localstack/dev/run/watcher.py new file mode 100644 index 0000000000000..63fa7b07ff7eb --- /dev/null +++ b/localstack-core/localstack/dev/run/watcher.py @@ -0,0 +1,131 @@ +"""File watcher that monitors source directories and signals container restart on changes.""" + +import os +import threading +import time +from pathlib import Path + +from rich.console import Console +from watchdog.events import FileSystemEvent, FileSystemEventHandler +from watchdog.observers import Observer + +from localstack.dev.run.paths import HOST_PATH_MAPPINGS, HostPaths +from localstack.utils.container_utils.container_client import ContainerClient +from localstack.utils.threads import TMP_THREADS, FuncThread + +DEBOUNCE_WINDOW = 0.5 + + +console = Console() + + +class ChangeHandler(FileSystemEventHandler): + """Handles file system events for .py files, debouncing and signalling the container.""" + + def __init__( + self, + directories: list[Path], + docker: ContainerClient, + container_id: str, + ): + super().__init__() + self._directories = directories + self._docker = docker + self._container_id = container_id + self._last_signal_time = 0.0 + self._lock = threading.Lock() + + def _relative_path(self, path: str) -> str: + for d in self._directories: + try: + return os.path.relpath(path, d.parent) + except ValueError: + pass + return path + + def on_any_event(self, event: FileSystemEvent): + if event.is_directory: + return + src = event.src_path + if not src.endswith(".py") or "__pycache__" in src: + return + + with self._lock: + now = time.monotonic() + if now - self._last_signal_time < DEBOUNCE_WINDOW: + return + self._last_signal_time = now + + console.print("Live reload: sending restart signal to container") + _signal_container_restart(self._docker, self._container_id) + + +def _signal_container_restart(docker: ContainerClient, container_id: str): + """Send SIGUSR1 to PID 1 inside the container to trigger supervisor restart.""" + try: + docker.exec_in_container(container_id, ["kill", "-USR1", "1"]) + except Exception as e: + console.print(f"Live reload: failed to signal container: {e}") + + +def start_file_watcher( + directories: list[Path], + docker: ContainerClient, + container_id: str, +) -> threading.Event: + """Start a watchdog observer that watches directories for .py changes and signals the container. + + Returns a stop_event that the caller can set to shut down the watcher. + """ + console.print( + f"Live reload: watching {len(directories)} " + f"director{'y' if len(directories) == 1 else 'ies'} for .py changes" + ) + for d in directories: + console.print(f" {d}") + + handler = ChangeHandler(directories, docker, container_id) + observer = Observer() + for d in directories: + observer.schedule(handler, str(d), recursive=True) + observer.daemon = True + observer.start() + + stop_event = threading.Event() + + def _shutdown_when_signalled(): + stop_event.wait() + observer.stop() + + shutdown_thread = FuncThread( + func=_shutdown_when_signalled, + name="live-reload-shutdown", + ) + shutdown_thread.start() + TMP_THREADS.append(shutdown_thread) + + return stop_event + + +def collect_watch_directories( + host_paths: HostPaths, pro: bool, chosen_packages: list[str] | None +) -> list[Path]: + """Collect host-side source directories that are bind-mounted into the container.""" + dirs: list[Path] = [] + + source = host_paths.aws_community_package_dir + if source.exists(): + dirs.append(source) + + if pro: + source = host_paths.aws_pro_package_dir + if source.exists(): + dirs.append(source) + + for package_name in chosen_packages or []: + extractor = HOST_PATH_MAPPINGS[package_name] + path = extractor(host_paths) + if path.exists(): + dirs.append(path) + + return dirs diff --git a/localstack-core/localstack/dns/models.py b/localstack-core/localstack/dns/models.py index 6df70bf6e0d86..99e57efd7553e 100644 --- a/localstack-core/localstack/dns/models.py +++ b/localstack-core/localstack/dns/models.py @@ -1,6 +1,7 @@ import dataclasses +from collections.abc import Callable from enum import Enum, auto -from typing import Callable, Protocol +from typing import Protocol class RecordType(Enum): diff --git a/localstack-core/localstack/dns/plugins.py b/localstack-core/localstack/dns/plugins.py index 05566573cfec8..7353c0ecf3594 100644 --- a/localstack-core/localstack/dns/plugins.py +++ b/localstack-core/localstack/dns/plugins.py @@ -11,8 +11,12 @@ """Make sure the DNS server is shut down after the ON_AFTER_SERVICE_SHUTDOWN_HANDLERS, which in turn is after SERVICE_SHUTDOWN_PRIORITY. Currently this value needs to be less than -20""" +DNS_START_PRIORITY = 20 +"""Make sure the DNS server is started before the pro activation, to ensure proper DNS resolution for the activate call, +if the resolv.conf is set to localhost from outside the container""" -@hooks.on_infra_start(priority=10) + +@hooks.on_infra_start(priority=DNS_START_PRIORITY) def start_dns_server(): try: from localstack.dns import server diff --git a/localstack-core/localstack/dns/server.py b/localstack-core/localstack/dns/server.py index f32d81292c75e..9ab09bed763aa 100644 --- a/localstack-core/localstack/dns/server.py +++ b/localstack-core/localstack/dns/server.py @@ -5,12 +5,13 @@ import re import textwrap import threading +from collections.abc import Iterable from datetime import datetime from functools import cache from ipaddress import IPv4Address, IPv4Interface from pathlib import Path from socket import AddressFamily -from typing import Iterable, Literal, Tuple +from typing import Literal import psutil from cachetools import TTLCache, cached @@ -33,7 +34,7 @@ DNSRecord, ) from dnslib.server import DNSHandler, DNSServer -from psutil._common import snicaddr +from psutil._ntuples import snicaddr import dns.flags import dns.message @@ -90,7 +91,7 @@ # Type of the value given by DNSHandler.client_address # in the form (ip, port) e.g. ("127.0.0.1", 58291) -ClientAddress = Tuple[str, int] +ClientAddress = tuple[str, int] psutil_cache = TTLCache(maxsize=100, ttl=10) @@ -253,7 +254,7 @@ def handle(self, *args, **kwargs): THREAD_LOCAL.client_address = self.client_address THREAD_LOCAL.server = self.server THREAD_LOCAL.request = self.request - return super(NonLoggingHandler, self).handle(*args, **kwargs) + return super().handle(*args, **kwargs) except Exception: pass @@ -444,13 +445,22 @@ def _skip_local_resolution(self, request) -> bool: return True return False + def _find_matching_aliases(self, question: DNSQuestion) -> list[AliasTarget] | None: + """ + Find aliases matching the question, supporting wildcards. + """ + qlabel = DNSLabel(to_bytes(question.qname)) + qtype = RecordType[QTYPE[question.qtype]] + for (label, rtype), targets in self.aliases.items(): + if rtype == qtype and qlabel.matchWildcard(label): + return targets + return None + def _resolve_alias( self, request: DNSRecord, reply: DNSRecord, client_address: ClientAddress ) -> bool: if request.q.qtype in (QTYPE.A, QTYPE.AAAA, QTYPE.CNAME): - key = (DNSLabel(to_bytes(request.q.qname)), RecordType[QTYPE[request.q.qtype]]) - # check if we have aliases defined for our given qname/qtype pair - if aliases := self.aliases.get(key): + if aliases := self._find_matching_aliases(request.q): for alias in aliases: # if there is no health check, or the healthcheck is successful, we will consider this alias # take the first alias passing this check diff --git a/localstack-core/localstack/http/dispatcher.py b/localstack-core/localstack/http/dispatcher.py index 308450fbd3296..6568080f1fa3a 100644 --- a/localstack-core/localstack/http/dispatcher.py +++ b/localstack-core/localstack/http/dispatcher.py @@ -1,5 +1,4 @@ from json import JSONEncoder -from typing import Type from rolo.routing.handler import Handler, ResultValue from rolo.routing.handler import handler_dispatcher as _handler_dispatcher @@ -14,7 +13,7 @@ ] -def handler_dispatcher(json_encoder: Type[JSONEncoder] = None) -> Dispatcher[Handler]: +def handler_dispatcher(json_encoder: type[JSONEncoder] = None) -> Dispatcher[Handler]: """ Replacement for ``rolo.dispatcher.handler_dispatcher`` that uses by default LocalStack's CustomEncoder for serializing JSON documents. diff --git a/localstack-core/localstack/http/response.py b/localstack-core/localstack/http/response.py index 66863c147d370..c0d9285601669 100644 --- a/localstack-core/localstack/http/response.py +++ b/localstack-core/localstack/http/response.py @@ -1,5 +1,5 @@ from json import JSONEncoder -from typing import Any, Type +from typing import Any from rolo import Response as RoloResponse @@ -11,7 +11,7 @@ class Response(RoloResponse): An HTTP Response object, which simply extends werkzeug's Response object with a few convenience methods. """ - def set_json(self, doc: Any, cls: Type[JSONEncoder] = CustomEncoder): + def set_json(self, doc: Any, cls: type[JSONEncoder] = CustomEncoder): """ Serializes the given dictionary using localstack's ``CustomEncoder`` into a json response, and sets the mimetype automatically to ``application/json``. diff --git a/localstack-core/localstack/http/router.py b/localstack-core/localstack/http/router.py index da3bcdfe043c0..ffa46e31280db 100644 --- a/localstack-core/localstack/http/router.py +++ b/localstack-core/localstack/http/router.py @@ -1,6 +1,6 @@ +from collections.abc import Mapping from typing import ( Any, - Mapping, TypeVar, ) diff --git a/localstack-core/localstack/http/trace.py b/localstack-core/localstack/http/trace.py index 7d52b9ebf36dc..e15c6b2caf004 100644 --- a/localstack-core/localstack/http/trace.py +++ b/localstack-core/localstack/http/trace.py @@ -2,7 +2,8 @@ import inspect import logging import time -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from rolo import Response from rolo.gateway import ExceptionHandler, Handler, HandlerChain, RequestContext diff --git a/localstack-core/localstack/logging/format.py b/localstack-core/localstack/logging/format.py index 5f308e34d9ecf..959deaaf669e5 100644 --- a/localstack-core/localstack/logging/format.py +++ b/localstack-core/localstack/logging/format.py @@ -3,7 +3,7 @@ import logging import re from functools import lru_cache -from typing import Any, Dict +from typing import Any from localstack.utils.numbers import format_bytes from localstack.utils.strings import to_bytes @@ -32,7 +32,7 @@ class DefaultFormatter(logging.Formatter): """ def __init__(self, fmt=LOG_FORMAT, datefmt=LOG_DATE_FORMAT): - super(DefaultFormatter, self).__init__(fmt=fmt, datefmt=datefmt) + super().__init__(fmt=fmt, datefmt=datefmt) class AddFormattedAttributes(logging.Filter): @@ -48,7 +48,7 @@ class AddFormattedAttributes(logging.Filter): max_thread_len: int def __init__(self, max_name_len: int = None, max_thread_len: int = None): - super(AddFormattedAttributes, self).__init__() + super().__init__() self.max_name_len = max_name_len if max_name_len else MAX_NAME_LEN self.max_thread_len = max_thread_len if max_thread_len else MAX_THREAD_NAME_LEN @@ -75,7 +75,7 @@ class MaskSensitiveInputFilter(logging.Filter): patterns: list[tuple[re.Pattern[bytes], bytes]] def __init__(self, sensitive_keys: list[str]): - super(MaskSensitiveInputFilter, self).__init__() + super().__init__() self.patterns = [ (re.compile(to_bytes(rf'"{key}":\s*"[^"]+"')), to_bytes(f'"{key}": "******"')) @@ -173,8 +173,8 @@ class AwsTraceLoggingFormatter(TraceLoggingFormatter): def __init__(self): super().__init__() - def _copy_service_dict(self, service_dict: Dict) -> Dict: - if not isinstance(service_dict, Dict): + def _copy_service_dict(self, service_dict: dict) -> dict: + if not isinstance(service_dict, dict): return service_dict result = {} for key, value in service_dict.items(): diff --git a/localstack-core/localstack/openapi.yaml b/localstack-core/localstack/openapi.yaml index b3656c3f6f1af..fce767a9bde22 100644 --- a/localstack-core/localstack/openapi.yaml +++ b/localstack-core/localstack/openapi.yaml @@ -81,10 +81,10 @@ components: START: type: boolean required: - - BOOT - - START - - READY - - SHUTDOWN + - BOOT + - START + - READY + - SHUTDOWN type: object scripts: items: @@ -97,14 +97,14 @@ components: state: type: string required: - - stage - - name - - state + - stage + - name + - state type: object type: array required: - - completed - - scripts + - completed + - scripts type: object InitScriptsStage: additionalProperties: false @@ -162,7 +162,7 @@ components: text_part: type: string required: - - text_part + - text_part type: object Destination: $ref: '#/components/schemas/SESDestination' @@ -183,10 +183,10 @@ components: Timestamp: type: string required: - - Id - - Region - - Timestamp - - Source + - Id + - Region + - Timestamp + - Source type: object SessionInfo: additionalProperties: false @@ -210,15 +210,15 @@ components: version: type: string required: - - version - - edition - - is_license_activated - - session_id - - machine_id - - system - - is_docker - - server_time_utc - - uptime + - version + - edition + - is_license_activated + - session_id + - machine_id + - system + - is_docker + - server_time_utc + - uptime type: object SnsSubscriptionTokenError: additionalProperties: false @@ -228,8 +228,8 @@ components: subscription_arn: type: string required: - - error - - subscription_arn + - error + - subscription_arn type: object SNSPlatformEndpointMessage: type: object @@ -456,7 +456,7 @@ paths: description: Number of expired items that were deleted type: integer required: - - ExpiredItems + - ExpiredItems type: object description: Operation was successful /_aws/events/rules/{rule_arn}/trigger: @@ -465,12 +465,12 @@ paths: operationId: trigger_event_bridge_rule tags: [aws] parameters: - - description: EventBridge rule ARN - in: path - name: rule_arn - required: true - schema: - type: string + - description: EventBridge rule ARN + in: path + name: rule_arn + required: true + schema: + type: string responses: '200': description: EventBridge rule was triggered @@ -492,16 +492,16 @@ paths: operationId: get_lambda_runtimes tags: [aws] parameters: - - in: query - name: filter - required: false - schema: - default: supported - enum: - - all - - deprecated - - supported - type: string + - in: query + name: filter + required: false + schema: + default: supported + enum: + - all + - deprecated + - supported + type: string responses: '200': content: @@ -514,7 +514,7 @@ paths: type: string type: array required: - - Runtimes + - Runtimes type: object description: Available Lambda runtimes /_aws/ses: @@ -523,7 +523,7 @@ paths: operationId: discard_ses_messages tags: [aws] parameters: - - $ref: '#/components/parameters/SesIdFilter' + - $ref: '#/components/parameters/SesIdFilter' responses: '204': description: Message was successfully discarded @@ -532,8 +532,8 @@ paths: operationId: get_ses_messages tags: [aws] parameters: - - $ref: '#/components/parameters/SesIdFilter' - - $ref: '#/components/parameters/SesEmailFilter' + - $ref: '#/components/parameters/SesIdFilter' + - $ref: '#/components/parameters/SesEmailFilter' responses: '200': content: @@ -546,7 +546,7 @@ paths: $ref: '#/components/schemas/SesSentEmail' type: array required: - - messages + - messages type: object description: List of sent messages /_aws/sns/platform-endpoint-messages: @@ -555,9 +555,9 @@ paths: operationId: discard_sns_endpoint_messages tags: [aws] parameters: - - $ref: '#/components/parameters/SnsAccountId' - - $ref: '#/components/parameters/SnsRegion' - - $ref: '#/components/parameters/SnsEndpointArn' + - $ref: '#/components/parameters/SnsAccountId' + - $ref: '#/components/parameters/SnsRegion' + - $ref: '#/components/parameters/SnsEndpointArn' responses: '204': description: Platform endpoint message was discarded @@ -566,9 +566,9 @@ paths: operationId: get_sns_endpoint_messages tags: [aws] parameters: - - $ref: '#/components/parameters/SnsAccountId' - - $ref: '#/components/parameters/SnsRegion' - - $ref: '#/components/parameters/SnsEndpointArn' + - $ref: '#/components/parameters/SnsAccountId' + - $ref: '#/components/parameters/SnsRegion' + - $ref: '#/components/parameters/SnsEndpointArn' responses: '200': content: @@ -582,9 +582,9 @@ paths: operationId: discard_sns_sms_messages tags: [aws] parameters: - - $ref: '#/components/parameters/SnsAccountId' - - $ref: '#/components/parameters/SnsRegion' - - $ref: '#/components/parameters/SnsPhoneNumber' + - $ref: '#/components/parameters/SnsAccountId' + - $ref: '#/components/parameters/SnsRegion' + - $ref: '#/components/parameters/SnsPhoneNumber' responses: '204': description: SMS message was discarded @@ -593,9 +593,9 @@ paths: operationId: get_sns_sms_messages tags: [aws] parameters: - - $ref: '#/components/parameters/SnsAccountId' - - $ref: '#/components/parameters/SnsRegion' - - $ref: '#/components/parameters/SnsPhoneNumber' + - $ref: '#/components/parameters/SnsAccountId' + - $ref: '#/components/parameters/SnsRegion' + - $ref: '#/components/parameters/SnsPhoneNumber' responses: '200': content: @@ -609,12 +609,12 @@ paths: operationId: get_sns_subscription_token tags: [aws] parameters: - - description: '`subscriptionArn` resource of subscription token' - in: path - name: subscription_arn - required: true - schema: - type: string + - description: '`subscriptionArn` resource of subscription token' + in: path + name: subscription_arn + required: true + schema: + type: string responses: '200': content: @@ -627,8 +627,8 @@ paths: subscription_token: type: string required: - - subscription_token - - subscription_arn + - subscription_token + - subscription_arn type: object description: Subscription token '400': @@ -649,12 +649,12 @@ paths: operationId: list_all_sqs_messages tags: [aws] parameters: - - description: SQS queue URL - in: query - name: QueueUrl - required: false - schema: - type: string + - description: SQS queue URL + in: query + name: QueueUrl + required: false + schema: + type: string responses: '200': content: @@ -686,10 +686,10 @@ paths: content: application/x-www-form-urlencoded: schema: - $ref: '#/components/schemas/ReceiveMessageRequest' + $ref: '#/components/schemas/ReceiveMessageRequest' application/json: schema: - $ref: '#/components/schemas/ReceiveMessageRequest' + $ref: '#/components/schemas/ReceiveMessageRequest' responses: '200': content: @@ -714,24 +714,24 @@ paths: operationId: list_sqs_messages tags: [aws] parameters: - - description: SQS queue region - in: path - name: region - required: true - schema: - type: string - - description: SQS queue account ID - in: path - name: account_id - required: true - schema: - type: string - - description: SQS queue name - in: path - name: queue_name - required: true - schema: - type: string + - description: SQS queue region + in: path + name: region + required: true + schema: + type: string + - description: SQS queue account ID + in: path + name: account_id + required: true + schema: + type: string + - description: SQS queue name + in: path + name: queue_name + required: true + schema: + type: string responses: '200': content: @@ -762,6 +762,8 @@ paths: schema: type: object description: Current LocalStack configuration + '404': + description: Not found post: description: Configuration option to update with new value operationId: update_config_option @@ -774,14 +776,14 @@ paths: properties: value: type: - - number - - string + - number + - string variable: pattern: ^[_a-zA-Z0-9]+$ type: string required: - - variable - - value + - variable + - value type: object required: true responses: @@ -793,19 +795,21 @@ paths: properties: value: type: - - number - - string + - number + - string variable: type: string required: - - variable - - value + - variable + - value type: object description: Configuration option is updated '400': content: application/json: {} description: Bad request + '404': + description: Not found /_localstack/diagnose: get: description: Get diagnostics report @@ -820,6 +824,8 @@ paths: properties: config: type: object + docker-dependent-image-hashes: + type: object docker-dependent-image-hosts: type: object docker-inspect: @@ -836,7 +842,7 @@ paths: docker: type: string required: - - docker + - docker type: object services: type: object @@ -851,7 +857,7 @@ paths: kernel: type: string required: - - kernel + - kernel type: object image-version: additionalProperties: false @@ -865,47 +871,47 @@ paths: tag: type: string required: - - id - - sha256 - - tag - - created + - id + - sha256 + - tag + - created type: object localstack-version: additionalProperties: false properties: build-date: type: - - string - - 'null' + - string + - 'null' build-git-hash: type: - - string - - 'null' + - string + - 'null' build-version: type: - - string - - 'null' + - string + - 'null' required: - - build-date - - build-git-hash - - build-version + - build-date + - build-git-hash + - build-version type: object required: - - image-version - - localstack-version - - host + - image-version + - localstack-version + - host type: object required: - - version - - info - - services - - config - - docker-inspect - - docker-dependent-image-hosts - - file-tree - - important-endpoints - - logs - - usage + - version + - info + - services + - config + - docker-inspect + - docker-dependent-image-hosts + - file-tree + - important-endpoints + - logs + - usage type: object description: Diagnostics report /_localstack/health: @@ -914,12 +920,12 @@ paths: operationId: get_features_and_services tags: [localstack] parameters: - - allowEmptyValue: true - in: query - name: reload - required: false - schema: - type: string + - allowEmptyValue: true + in: query + name: reload + required: false + schema: + type: string responses: '200': content: @@ -929,10 +935,10 @@ paths: properties: edition: enum: - - community - - pro - - enterprise - - unknown + - community + - pro + - enterprise + - unknown type: string features: type: object @@ -941,9 +947,9 @@ paths: version: type: string required: - - edition - - services - - version + - edition + - services + - version type: object description: Available LocalStack features and AWS services head: @@ -966,11 +972,11 @@ paths: properties: action: enum: - - restart - - kill + - restart + - kill type: string required: - - action + - action type: object description: Action to perform required: true @@ -1003,7 +1009,7 @@ paths: status: type: string required: - - status + - status type: object description: Data was saved /_localstack/info: @@ -1036,11 +1042,11 @@ paths: operationId: get_init_script_info_stage tags: [localstack] parameters: - - in: path - name: stage - required: true - schema: - type: string + - in: path + name: stage + required: true + schema: + type: string responses: '200': content: @@ -1048,6 +1054,12 @@ paths: schema: $ref: '#/components/schemas/InitScriptsStage' description: Information about init scripts in a specific stage + '404': + content: + text/plain: + schema: + type: string + description: Stage not found /_localstack/plugins: get: description: '' diff --git a/localstack-core/localstack/packages/api.py b/localstack-core/localstack/packages/api.py index bcc8add9577c5..88a9e4a54b63b 100644 --- a/localstack-core/localstack/packages/api.py +++ b/localstack-core/localstack/packages/api.py @@ -3,10 +3,11 @@ import logging import os from collections import defaultdict +from collections.abc import Callable from enum import Enum from inspect import getmodule -from threading import RLock -from typing import Any, Callable, Generic, List, Optional, ParamSpec, TypeVar +from threading import Lock, RLock +from typing import Any, Generic, ParamSpec, TypeVar from plux import Plugin, PluginManager, PluginSpec # type: ignore @@ -56,7 +57,7 @@ class PackageInstaller(abc.ABC): multiple versions). """ - def __init__(self, name: str, version: str, install_lock: Optional[RLock] = None): + def __init__(self, name: str, version: str, install_lock: Lock | None = None): """ :param name: technical package name, f.e. "opensearch" :param version: version of the package to install @@ -70,7 +71,7 @@ def __init__(self, name: str, version: str, install_lock: Optional[RLock] = None self.install_lock = install_lock or RLock() self._setup_for_target: dict[InstallTarget, bool] = defaultdict(lambda: False) - def install(self, target: Optional[InstallTarget] = None) -> None: + def install(self, target: InstallTarget | None = None) -> None: """ Performs the package installation. @@ -210,7 +211,7 @@ def get_installed_dir(self, version: str | None = None) -> str | None: """ return self.get_installer(version).get_installed_dir() - def install(self, version: str | None = None, target: Optional[InstallTarget] = None) -> None: + def install(self, version: str | None = None, target: InstallTarget | None = None) -> None: """ Installs the package in the given version in the preferred target location. :param version: version of the package to install. If None, the default version of the package will be used. @@ -219,7 +220,7 @@ def install(self, version: str | None = None, target: Optional[InstallTarget] = """ self.get_installer(version).install(target) - @functools.lru_cache() + @functools.lru_cache def get_installer(self, version: str | None = None) -> T: """ Returns the installer instance for a specific version of the package. @@ -237,7 +238,7 @@ def get_installer(self, version: str | None = None) -> T: raise NoSuchVersionException(package=self.name, version=version) return self._get_installer(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: """ :return: List of all versions available for this package. """ @@ -262,7 +263,7 @@ class MultiPackageInstaller(PackageInstaller): PackageInstaller implementation which composes of multiple package installers. """ - def __init__(self, name: str, version: str, package_installer: List[PackageInstaller]): + def __init__(self, name: str, version: str, package_installer: list[PackageInstaller]): """ :param name: of the (multi-)package installer :param version: of this (multi-)package installer @@ -274,7 +275,7 @@ def __init__(self, name: str, version: str, package_installer: List[PackageInsta assert len(package_installer) > 0 self.package_installer = package_installer - def install(self, target: Optional[InstallTarget] = None) -> None: + def install(self, target: InstallTarget | None = None) -> None: """ Installs the different packages this installer is composed of. @@ -317,7 +318,7 @@ def __init__( self, name: str, scope: str, - get_package: Callable[[], Package[PackageInstaller] | List[Package[PackageInstaller]]], + get_package: Callable[[], Package[PackageInstaller] | list[Package[PackageInstaller]]], should_load: Callable[[], bool] | None = None, ) -> None: super().__init__() @@ -356,7 +357,7 @@ def get_all_packages(self) -> list[tuple[str, str, Package[PackageInstaller]]]: ) def get_packages( - self, package_names: list[str], version: Optional[str] = None + self, package_names: list[str], version: str | None = None ) -> list[Package[PackageInstaller]]: # Plugin names are unique, but there could be multiple packages with the same name in different scopes plugin_specs_per_name = defaultdict(list) @@ -390,7 +391,7 @@ def get_packages( def package( name: str | None = None, scope: str = "community", - should_load: Optional[Callable[[], bool]] = None, + should_load: Callable[[], bool] | None = None, ) -> Callable[[Callable[[], Package[Any] | list[Package[Any]]]], PluginSpec]: """ Decorator for marking methods that create Package instances as a PackagePlugin. diff --git a/localstack-core/localstack/packages/core.py b/localstack-core/localstack/packages/core.py index fde294492cc3a..c969521f9872e 100644 --- a/localstack-core/localstack/packages/core.py +++ b/localstack-core/localstack/packages/core.py @@ -4,7 +4,7 @@ from abc import ABC from functools import lru_cache from sys import version_info -from typing import Any, Optional, Tuple +from typing import Any import requests @@ -13,7 +13,7 @@ from ..constants import LOCALSTACK_VENV_FOLDER, MAVEN_REPO_URL from ..utils.archives import download_and_extract from ..utils.files import chmod_r, chown_r, mkdir, rm_rf -from ..utils.http import download +from ..utils.http import download, get_proxies from ..utils.run import is_root, run from ..utils.venv import VirtualEnvironment from .api import InstallTarget, PackageException, PackageInstaller @@ -190,7 +190,7 @@ def __init__(self, name: str, tag: str, github_slug: str): f"https://api.github.com/repos/{github_slug}/releases/tags/{self.version}" ) - @lru_cache() + @lru_cache def _get_download_url(self) -> str: asset_name = self._get_github_asset_name() # try to use a token when calling the GH API for increased API rate limits @@ -198,7 +198,7 @@ def _get_download_url(self) -> str: gh_token = os.environ.get("GITHUB_API_TOKEN") if gh_token: headers = {"authorization": f"Bearer {gh_token}"} - response = requests.get(self.github_tag_url, headers=headers) + response = requests.get(self.github_tag_url, headers=headers, proxies=get_proxies()) if not response.ok: raise PackageException( f"Could not get list of releases from {self.github_tag_url}: {response.text}" @@ -238,7 +238,7 @@ def __init__( self, package_name: str, version: str, - package_spec: Optional[str] = None, + package_spec: str | None = None, main_module: str = "main.js", ): """ @@ -404,7 +404,7 @@ def _install(self, target: InstallTarget) -> None: super()._install(target) -def parse_maven_package_url(package_url: str) -> Tuple[str, str, str]: +def parse_maven_package_url(package_url: str) -> tuple[str, str, str]: """Example: parse_maven_package_url("pkg:maven/software.amazon.event.ruler/event-ruler@1.7.3") -> software.amazon.event.ruler, event-ruler, 1.7.3 """ diff --git a/localstack-core/localstack/packages/debugpy.py b/localstack-core/localstack/packages/debugpy.py index 2731236f747a1..a31a08f5725fc 100644 --- a/localstack-core/localstack/packages/debugpy.py +++ b/localstack-core/localstack/packages/debugpy.py @@ -1,5 +1,3 @@ -from typing import List - from localstack.packages import InstallTarget, Package, PackageInstaller from localstack.utils.run import run @@ -8,7 +6,7 @@ class DebugPyPackage(Package["DebugPyPackageInstaller"]): def __init__(self) -> None: super().__init__("DebugPy", "latest") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["latest"] def _get_installer(self, version: str) -> "DebugPyPackageInstaller": @@ -20,7 +18,7 @@ class DebugPyPackageInstaller(PackageInstaller): def is_installed(self) -> bool: try: - import debugpy # type: ignore[import-not-found] # noqa: T100 + import debugpy # type: ignore # noqa: T100 assert debugpy return True diff --git a/localstack-core/localstack/packages/ffmpeg.py b/localstack-core/localstack/packages/ffmpeg.py index 230d114347b68..a503016754803 100644 --- a/localstack-core/localstack/packages/ffmpeg.py +++ b/localstack-core/localstack/packages/ffmpeg.py @@ -1,5 +1,4 @@ import os -from typing import List from localstack.packages import Package from localstack.packages.core import ArchiveDownloadAndExtractInstaller @@ -21,7 +20,7 @@ def __init__(self) -> None: def _get_installer(self, version: str) -> "FfmpegPackageInstaller": return FfmpegPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["7.1"] diff --git a/localstack-core/localstack/packages/java.py b/localstack-core/localstack/packages/java.py index c8a2e9f7c7f21..eeb51fd2885d1 100644 --- a/localstack-core/localstack/packages/java.py +++ b/localstack-core/localstack/packages/java.py @@ -1,6 +1,5 @@ import logging import os -from typing import List import requests @@ -8,6 +7,7 @@ from localstack.packages import InstallTarget, Package from localstack.packages.core import ArchiveDownloadAndExtractInstaller from localstack.utils.files import rm_rf +from localstack.utils.http import get_proxies from localstack.utils.platform import Arch, get_arch, is_linux, is_mac_os from localstack.utils.run import run @@ -22,6 +22,7 @@ "11": "11.0.25+9", "17": "17.0.13+11", "21": "21.0.5+11", + "24": "24.0.1+9", } @@ -45,6 +46,11 @@ def get_java_lib_path(self) -> str | None: """ if java_home := self.get_java_home(): if is_mac_os(): + # location in JRE versions 12+, see https://bugs.openjdk.org/browse/JDK-8210931 + jli_path = os.path.join(java_home, "lib", "libjli.dylib") + if os.path.isfile(jli_path): + return jli_path + # location in JRE versions 11- return os.path.join(java_home, "lib", "jli", "libjli.dylib") return os.path.join(java_home, "lib", "server", "libjvm.so") return None @@ -114,23 +120,49 @@ def _post_process(self, target: InstallTarget) -> None: # Build a custom JRE with only the necessary bits to minimise disk footprint LOG.debug("Optimising JRE installation") - cmd = ( - "bin/jlink --add-modules " + + base_modules = [ # Required modules - "java.base,java.desktop,java.instrument,java.management," - "java.naming,java.scripting,java.sql,java.xml,jdk.compiler," + "java.base", + "java.desktop", + "java.instrument", + "java.management", + "java.naming", + "java.scripting", + "java.sql", + "java.xml", + "jdk.compiler", # jdk.unsupported contains sun.misc.Unsafe which is required by some dependencies - "jdk.unsupported," + "jdk.unsupported", # Additional cipher suites - "jdk.crypto.cryptoki," + "jdk.crypto.cryptoki", # Archive support - "jdk.zipfs," + "jdk.zipfs", # Required by MQ broker - "jdk.httpserver,jdk.management,jdk.management.agent," + "jdk.httpserver", + "jdk.management", + "jdk.management.agent", # Required by Spark and Hadoop - "java.security.jgss,jdk.security.auth," + "java.security.jgss", + "jdk.security.auth", + # Include required locales + "jdk.localedata", + ] + + # Add version-specific modules, not all versions require/support the same set + version_specific_modules = { + "24": ["jdk.incubator.vector"], # Required for Trino latest version + } + + modules = base_modules + version_specific_modules.get(self.version, []) + modules_str = ",".join(modules) + + cmd = ( + "bin/jlink " + # Add modules + f"--add-modules {modules_str} " # Include required locales - "jdk.localedata --include-locales en " + "--include-locales en " # Supplementary args "--compress 2 --strip-debug --no-header-files --no-man-pages " # Output directory @@ -169,7 +201,9 @@ def _download_url_latest_release(self) -> str: f"os={self.os_name}&architecture={self.arch}&image_type=jdk" ) # Override user-agent because Adoptium API denies service to `requests` library - response = requests.get(endpoint, headers={"user-agent": USER_AGENT_STRING}).json() + response = requests.get( + endpoint, headers={"user-agent": USER_AGENT_STRING}, proxies=get_proxies() + ).json() return response[0]["binary"]["package"]["link"] def _download_url_fallback(self) -> str: @@ -195,7 +229,7 @@ class JavaPackage(Package[JavaPackageInstaller]): def __init__(self, default_version: str = DEFAULT_JAVA_VERSION): super().__init__(name="Java", default_version=default_version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return list(JAVA_VERSIONS.keys()) def _get_installer(self, version: str) -> JavaPackageInstaller: diff --git a/localstack-core/localstack/packages/plugins.py b/localstack-core/localstack/packages/plugins.py index fdeba86a04204..94d0cc62f09ee 100644 --- a/localstack-core/localstack/packages/plugins.py +++ b/localstack-core/localstack/packages/plugins.py @@ -5,14 +5,6 @@ if TYPE_CHECKING: from localstack.packages.ffmpeg import FfmpegPackageInstaller from localstack.packages.java import JavaPackageInstaller - from localstack.packages.terraform import TerraformPackageInstaller - - -@package(name="terraform") -def terraform_package() -> Package["TerraformPackageInstaller"]: - from .terraform import terraform_package - - return terraform_package @package(name="ffmpeg") diff --git a/localstack-core/localstack/packages/terraform.py b/localstack-core/localstack/packages/terraform.py deleted file mode 100644 index 2a5da95b8472a..0000000000000 --- a/localstack-core/localstack/packages/terraform.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -import platform -from typing import List - -from localstack.packages import InstallTarget, Package -from localstack.packages.core import ArchiveDownloadAndExtractInstaller -from localstack.utils.files import chmod_r -from localstack.utils.platform import get_arch - -TERRAFORM_VERSION = os.getenv("TERRAFORM_VERSION", "1.5.7") -TERRAFORM_URL_TEMPLATE = ( - "https://releases.hashicorp.com/terraform/{version}/terraform_{version}_{os}_{arch}.zip" -) -TERRAFORM_CHECKSUM_URL_TEMPLATE = ( - "https://releases.hashicorp.com/terraform/{version}/terraform_{version}_SHA256SUMS" -) - - -class TerraformPackage(Package["TerraformPackageInstaller"]): - def __init__(self) -> None: - super().__init__("Terraform", TERRAFORM_VERSION) - - def get_versions(self) -> List[str]: - return [TERRAFORM_VERSION] - - def _get_installer(self, version: str) -> "TerraformPackageInstaller": - return TerraformPackageInstaller("terraform", version) - - -class TerraformPackageInstaller(ArchiveDownloadAndExtractInstaller): - def _get_install_marker_path(self, install_dir: str) -> str: - return os.path.join(install_dir, "terraform") - - def _get_download_url(self) -> str: - system = platform.system().lower() - arch = get_arch() - return TERRAFORM_URL_TEMPLATE.format(version=TERRAFORM_VERSION, os=system, arch=arch) - - def _install(self, target: InstallTarget) -> None: - super()._install(target) - chmod_r(self.get_executable_path(), 0o777) # type: ignore[arg-type] - - def _get_checksum_url(self) -> str | None: - return TERRAFORM_CHECKSUM_URL_TEMPLATE.format(version=TERRAFORM_VERSION) - - -terraform_package = TerraformPackage() diff --git a/localstack-core/localstack/runtime/analytics.py b/localstack-core/localstack/runtime/analytics.py index 2612ee8637bf9..1afd0dab777e9 100644 --- a/localstack-core/localstack/runtime/analytics.py +++ b/localstack-core/localstack/runtime/analytics.py @@ -1,15 +1,20 @@ import logging import os -from localstack import config from localstack.runtime import hooks from localstack.utils.analytics import log LOG = logging.getLogger(__name__) +# Config options for which both usage and values are reported in analytics. +# Important: This list must only contain options whose values do not contain PII or sensitive data. TRACKED_ENV_VAR = [ + "ACTIVATE_PRO", "ALLOW_NONSTANDARD_REGIONS", "BEDROCK_PREWARM", + "CFN_IGNORE_UNSUPPORTED_TYPE_CREATE", + "CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE", + "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES", "CLOUDFRONT_LAMBDA_EDGE", "CONTAINER_RUNTIME", "DEBUG", @@ -24,9 +29,12 @@ "DYNAMODB_IN_MEMORY", "DYNAMODB_REMOVE_EXPIRED_ITEMS", "EAGER_SERVICE_LOADING", + "EC2_DOCKER_INIT", "EC2_VM_MANAGER", "ECS_TASK_EXECUTOR", "EDGE_PORT", + "EKS_K8S_PROVIDER", + "EKS_PERSIST_CLUSTER_CONTENTS", "ENABLE_REPLICATOR", "ENFORCE_IAM", "ES_CUSTOM_BACKEND", # deprecated in 0.14.0, removed in 3.0.0 @@ -52,6 +60,7 @@ "LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT", "LEGACY_EDGE_PROXY", # Not functional; deprecated in 1.0.0, removed in 2.0.0 "LS_LOG", + "LOCALSTACK_K8S_DEPLOYMENT_METHOD", "MOCK_UNIMPLEMENTED", # Not functional; deprecated in 1.3.0, removed in 3.0.0 "OPENSEARCH_ENDPOINT_STRATEGY", "PERSISTENCE", @@ -68,15 +77,22 @@ "USE_SSL", ] +# Config options for which only the usage is reported in analytics. +# Use this for options which may hold sensitive data or PII. PRESENCE_ENV_VAR = [ "DATA_DIR", "EDGE_FORWARD_URL", # Not functional; deprecated in 1.4.0, removed in 3.0.0 + "EC2_HYPERVISOR_URI", + "EC2_REFERENCE_DOMAIN", + "EC2_LIBVIRT_NETWORK", + "EC2_LIBVIRT_POOL", "GATEWAY_LISTEN", "HOSTNAME", "HOSTNAME_EXTERNAL", "HOSTNAME_FROM_LAMBDA", "HOST_TMP_FOLDER", # Not functional; deprecated in 1.0.0, removed in 2.0.0 "INIT_SCRIPTS_PATH", # Not functional; deprecated in 1.1.0, removed in 2.0.0 + "KUBERNETES_SERVICE_HOST", "LAMBDA_DEBUG_MODE_CONFIG_PATH", "LEGACY_DIRECTORIES", # Not functional; deprecated in 1.1.0, removed in 2.0.0 "LEGACY_INIT_DIR", # Not functional; deprecated in 1.1.0, removed in 2.0.0 @@ -104,33 +120,8 @@ def _publish_config_as_analytics_event(): env_vars = {key: os.getenv(key) for key in env_vars} present_env_vars = {env_var: 1 for env_var in PRESENCE_ENV_VAR if os.getenv(env_var)} - log.event("config", env_vars=env_vars, set_vars=present_env_vars) - - -class LocalstackContainerInfo: - def get_image_variant(self) -> str: - for f in os.listdir("/usr/lib/localstack"): - if f.startswith(".") and f.endswith("-version"): - return f[1:-8] - return "unknown" - - def has_docker_socket(self) -> bool: - return os.path.exists("/run/docker.sock") - - def to_dict(self): - return { - "variant": self.get_image_variant(), - "has_docker_socket": self.has_docker_socket(), - } + # filter out irrelevant None values, making the payload significantly smaller. + env_vars = {k: v for k, v in env_vars.items() if v is not None} + present_env_vars = {k: v for k, v in present_env_vars.items() if v is not None} - -@hooks.on_infra_start() -def _publish_container_info(): - if not config.is_in_docker: - return - - try: - log.event("container_info", payload=LocalstackContainerInfo().to_dict()) - except Exception as e: - if config.DEBUG_ANALYTICS: - LOG.debug("error gathering container information: %s", e) + log.event("config", env_vars=env_vars, set_vars=present_env_vars) diff --git a/localstack-core/localstack/runtime/hooks.py b/localstack-core/localstack/runtime/hooks.py index 05161679cf54e..c28ce90ed5648 100644 --- a/localstack-core/localstack/runtime/hooks.py +++ b/localstack-core/localstack/runtime/hooks.py @@ -65,7 +65,7 @@ def run_in_order(self, *args, **kwargs): fn_plugin(*args, **kwargs) def __str__(self): - return "HookManager(%s)" % self.namespace + return f"HookManager({self.namespace})" def __repr__(self): return self.__str__() diff --git a/localstack-core/localstack/runtime/init.py b/localstack-core/localstack/runtime/init.py index e9b2f97dccf9e..52682bc5b87f9 100644 --- a/localstack-core/localstack/runtime/init.py +++ b/localstack-core/localstack/runtime/init.py @@ -7,7 +7,6 @@ import time from enum import Enum from functools import cached_property -from typing import Dict, List, Optional from plux import Plugin, PluginManager @@ -90,9 +89,9 @@ class ShellScriptRunner(ScriptRunner): suffixes = [".sh"] def run(self, path: str) -> None: - exit_code = subprocess.call(args=[], executable=path) + exit_code = subprocess.call(args=[path]) if exit_code != 0: - raise OSError("Script %s returned a non-zero exit code %s" % (path, exit_code)) + raise OSError(f"Script {path} returned a non-zero exit code {exit_code}") class PythonScriptRunner(ScriptRunner): @@ -109,7 +108,7 @@ def run(self, path: str) -> None: class InitScriptManager: - _stage_directories: Dict[Stage, str] = { + _stage_directories: dict[Stage, str] = { Stage.BOOT: "boot.d", Stage.START: "start.d", Stage.READY: "ready.d", @@ -117,7 +116,7 @@ class InitScriptManager: } script_root: str - stage_completed: Dict[Stage, bool] + stage_completed: dict[Stage, bool] def __init__(self, script_root: str): self.script_root = script_root @@ -125,10 +124,10 @@ def __init__(self, script_root: str): self.runner_manager: PluginManager[ScriptRunner] = PluginManager(ScriptRunner.namespace) @cached_property - def scripts(self) -> Dict[Stage, List[Script]]: + def scripts(self) -> dict[Stage, list[Script]]: return self._find_scripts() - def get_script_runner(self, script_file: str) -> Optional[ScriptRunner]: + def get_script_runner(self, script_file: str) -> ScriptRunner | None: runners = self.runner_manager.load_all() for runner in runners: if runner.should_run(script_file): @@ -138,7 +137,7 @@ def get_script_runner(self, script_file: str) -> Optional[ScriptRunner]: def has_script_runner(self, script_file: str) -> bool: return self.get_script_runner(script_file) is not None - def run_stage(self, stage: Stage) -> List[Script]: + def run_stage(self, stage: Stage) -> list[Script]: """ Runs all scripts in the given stage. @@ -188,7 +187,7 @@ def run_stage(self, stage: Stage) -> List[Script]: return scripts - def _find_scripts(self) -> Dict[Stage, List[Script]]: + def _find_scripts(self) -> dict[Stage, list[Script]]: scripts = {} if self.script_root is None: diff --git a/localstack-core/localstack/runtime/main.py b/localstack-core/localstack/runtime/main.py index 3a0357e230ad0..50db1c63ddd10 100644 --- a/localstack-core/localstack/runtime/main.py +++ b/localstack-core/localstack/runtime/main.py @@ -20,13 +20,13 @@ def print_runtime_information(in_docker: bool = False): if in_docker: try: container_name = get_main_container_name() - print("LocalStack Docker container name: %s" % container_name) + print(f"LocalStack Docker container name: {container_name}") inspect_result = DOCKER_CLIENT.inspect_container(container_name) container_id = inspect_result["Id"] - print("LocalStack Docker container id: %s" % container_id[:12]) + print(f"LocalStack Docker container id: {container_id[:12]}") image_details = DOCKER_CLIENT.inspect_image(inspect_result["Image"]) digests = image_details.get("RepoDigests") or ["Unavailable"] - print("LocalStack Docker image sha: %s" % digests[0]) + print(f"LocalStack Docker image sha: {digests[0]}") except ContainerException: print( "LocalStack Docker container info: Failed to inspect the LocalStack docker container. " @@ -44,10 +44,10 @@ def print_runtime_information(in_docker: bool = False): ) if config.LOCALSTACK_BUILD_DATE: - print("LocalStack build date: %s" % config.LOCALSTACK_BUILD_DATE) + print(f"LocalStack build date: {config.LOCALSTACK_BUILD_DATE}") if config.LOCALSTACK_BUILD_GIT_HASH: - print("LocalStack build git hash: %s" % config.LOCALSTACK_BUILD_GIT_HASH) + print(f"LocalStack build git hash: {config.LOCALSTACK_BUILD_GIT_HASH}") print() diff --git a/localstack-core/localstack/runtime/patches.py b/localstack-core/localstack/runtime/patches.py index 4772a480bfee1..46a200949de7f 100644 --- a/localstack-core/localstack/runtime/patches.py +++ b/localstack-core/localstack/runtime/patches.py @@ -38,14 +38,14 @@ def patch_urllib3_connection_pool(**constructor_kwargs): class MyHTTPSConnectionPool(connectionpool.HTTPSConnectionPool): def __init__(self, *args, **kwargs): kwargs.update(constructor_kwargs) - super(MyHTTPSConnectionPool, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) poolmanager.pool_classes_by_scheme["https"] = MyHTTPSConnectionPool class MyHTTPConnectionPool(connectionpool.HTTPConnectionPool): def __init__(self, *args, **kwargs): kwargs.update(constructor_kwargs) - super(MyHTTPConnectionPool, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) poolmanager.pool_classes_by_scheme["http"] = MyHTTPConnectionPool except Exception: diff --git a/localstack-core/localstack/runtime/shutdown.py b/localstack-core/localstack/runtime/shutdown.py index a64dab86ef930..6d9cfb5e094b6 100644 --- a/localstack-core/localstack/runtime/shutdown.py +++ b/localstack-core/localstack/runtime/shutdown.py @@ -1,5 +1,6 @@ import logging -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from localstack.runtime import hooks from localstack.utils.functions import call_safe diff --git a/localstack-core/localstack/services/acm/provider.py b/localstack-core/localstack/services/acm/provider.py index 7425b88832e6b..97873fd2d1cb3 100644 --- a/localstack-core/localstack/services/acm/provider.py +++ b/localstack-core/localstack/services/acm/provider.py @@ -10,12 +10,23 @@ RequestCertificateResponse, ) from localstack.services import moto +from localstack.state import StateVisitor from localstack.utils.patch import patch # reduce the validation wait time from 60 (default) to 10 seconds moto_settings.ACM_VALIDATION_WAIT = min(10, moto_settings.ACM_VALIDATION_WAIT) +@patch(acm_models.AWSCertificateManagerBackend.list_certificates) +def list_certificates(list_certificates_orig, self, statuses, includes): + # Normalize keyTypes filter to match our describe() output format (hyphens) + if includes and "keyTypes" in includes: + includes["keyTypes"] = [ + kt.replace("RSA_", "RSA-").replace("EC_", "EC-") for kt in includes["keyTypes"] + ] + return list_certificates_orig(self, statuses, includes) + + @patch(acm_models.CertBundle.describe) def describe(describe_orig, self): # TODO fix! Terrible hack (for parity). Moto adds certain required fields only if status is PENDING_VALIDATION. @@ -70,8 +81,10 @@ def describe(describe_orig, self): cert[key] = value cert["Serial"] = str(cert.get("Serial") or "") - if cert.get("KeyAlgorithm") in ["RSA_1024", "RSA_2048"]: + if cert.get("KeyAlgorithm") in ["RSA_1024", "RSA_2048", "RSA_3072", "RSA_4096"]: cert["KeyAlgorithm"] = cert["KeyAlgorithm"].replace("RSA_", "RSA-") + if cert.get("KeyAlgorithm") in ["EC_prime256v1", "EC_secp384r1", "EC_secp521r1"]: + cert["KeyAlgorithm"] = cert["KeyAlgorithm"].replace("EC_", "EC-") # add subject alternative names if cert["DomainName"] not in sans: @@ -100,6 +113,9 @@ def describe(describe_orig, self): class AcmProvider(AcmApi): + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(acm_models.acm_backends) + @handler("RequestCertificate", expand=False) def request_certificate( self, diff --git a/localstack-core/localstack/services/apigateway/exporter.py b/localstack-core/localstack/services/apigateway/exporter.py index 0706e794c1651..c0fdc0b456a9e 100644 --- a/localstack-core/localstack/services/apigateway/exporter.py +++ b/localstack-core/localstack/services/apigateway/exporter.py @@ -1,6 +1,5 @@ import abc import json -from typing import Type from apispec import APISpec @@ -320,7 +319,7 @@ def export( class OpenApiExporter: - exporters: dict[str, Type[_BaseOpenApiExporter]] + exporters: dict[str, type[_BaseOpenApiExporter]] def __init__(self): self.exporters = {"swagger": _OpenApiSwaggerExporter, "oas30": _OpenApiOAS30Exporter} diff --git a/localstack-core/localstack/services/apigateway/helpers.py b/localstack-core/localstack/services/apigateway/helpers.py index 8e69a9218e6e2..75337e5f1807e 100644 --- a/localstack-core/localstack/services/apigateway/helpers.py +++ b/localstack-core/localstack/services/apigateway/helpers.py @@ -3,7 +3,7 @@ import hashlib import json import logging -from typing import List, Optional, TypedDict, Union +from typing import TypedDict from urllib import parse as urlparse from jsonpatch import apply_patch @@ -23,7 +23,7 @@ IntegrationType, Model, NotFoundException, - PutRestApiRequest, + PutMode, RequestValidator, ) from localstack.constants import ( @@ -39,8 +39,7 @@ apigateway_stores, ) from localstack.utils import common -from localstack.utils.json import parse_json_or_yaml -from localstack.utils.strings import short_uid, to_bytes, to_str +from localstack.utils.strings import short_uid, to_bytes from localstack.utils.urls import localstack_host LOG = logging.getLogger(__name__) @@ -93,7 +92,7 @@ class OpenAPIExt: class AuthorizerConfig(TypedDict): authorizer: Authorizer - authorization_scopes: Optional[list[str]] + authorization_scopes: list[str] | None # TODO: make the CRUD operations in this file generic for the different model types (authorizes, validators, ...) @@ -198,11 +197,11 @@ def _resolve_refpath(self, refpath: str) -> dict: return cur - def _namespaced_resolution(self, namespace: str, data: Union[dict, list]) -> Union[dict, list]: + def _namespaced_resolution(self, namespace: str, data: dict | list) -> dict | list: with self._pathctx(namespace): return self._resolve_references(data) - def _resolve_references(self, data) -> Union[dict, list]: + def _resolve_references(self, data) -> dict | list: if self._is_ref(data): return self._resolve_refpath(data["$ref"]) @@ -367,10 +366,7 @@ def resolve_references(data: dict, rest_api_id, allow_recursive=True) -> dict: def path_based_url(api_id: str, stage_name: str, path: str) -> str: """Return URL for inbound API gateway for given API ID, stage name, and path""" - pattern = "%s/restapis/{api_id}/{stage_name}/%s{path}" % ( - config.external_service_url(), - PATH_USER_REQUEST, - ) + pattern = f"{config.external_service_url()}/restapis/{{api_id}}/{{stage_name}}/{PATH_USER_REQUEST}{{path}}" return pattern.format(api_id=api_id, stage_name=stage_name, path=path) @@ -459,6 +455,9 @@ def add_documentation_parts(rest_api_container, documentation): for doc_part in documentation.get("documentationParts", []): entity_id = short_uid()[:6] location = doc_part["location"] + properties = doc_part["properties"] + if not isinstance(properties, str): + properties = json.dumps(properties) rest_api_container.documentation_parts[entity_id] = DocumentationPart( id=entity_id, location=DocumentationPartLocation( @@ -470,16 +469,14 @@ def add_documentation_parts(rest_api_container, documentation): statusCode=location.get("statusCode"), name=location.get("name"), ), - properties=doc_part["properties"], + properties=properties, ) def import_api_from_openapi_spec( - rest_api: MotoRestAPI, context: RequestContext, request: PutRestApiRequest + rest_api: MotoRestAPI, context: RequestContext, open_api_spec: dict, mode: PutMode ) -> tuple[MotoRestAPI, list[str]]: """Import an API from an OpenAPI spec document""" - body = parse_json_or_yaml(to_str(request["body"].read())) - warnings = [] # TODO There is an issue with the botocore specs so the parameters doesn't get populated as it should @@ -487,15 +484,14 @@ def import_api_from_openapi_spec( # query_params = request.get("parameters") or {} query_params: dict = context.request.values.to_dict() - resolved_schema = resolve_references(copy.deepcopy(body), rest_api_id=rest_api.id) + resolved_schema = resolve_references(copy.deepcopy(open_api_spec), rest_api_id=rest_api.id) account_id = context.account_id region_name = context.region # TODO: - # 1. validate the "mode" property of the spec document, "merge" or "overwrite", and properly apply it + # 1. properly apply the mode (overwrite or merge) # for now, it only considers it for the binaryMediaTypes # 2. validate the document type, "swagger" or "openapi" - mode = request.get("mode", "merge") rest_api.version = ( str(version) if (version := resolved_schema.get("info", {}).get("version")) else None @@ -578,7 +574,7 @@ def create_authorizers(security_schemes: dict) -> None: authorizers[security_scheme_name] = authorizer - def get_authorizer(path_payload: dict) -> Optional[AuthorizerConfig]: + def get_authorizer(path_payload: dict) -> AuthorizerConfig | None: if not (security_schemes := path_payload.get("security")): return None @@ -605,7 +601,7 @@ def get_or_create_path(abs_path: str, base_path: str): rel_path = abs_path.removeprefix(base_path) return add_path_methods(rel_path, parts, parent_id=parent_id) - def add_path_methods(rel_path: str, parts: List[str], parent_id=""): + def add_path_methods(rel_path: str, parts: list[str], parent_id=""): rel_path = rel_path or "/" child_id = ApigwResourceIdentifier(account_id, region_name, parent_id, rel_path).generate() @@ -991,20 +987,20 @@ def is_variable_path(path_part: str) -> bool: return path_part.startswith("{") and path_part.endswith("}") -def get_domain_name_hash(domain_name: str) -> str: +def get_domain_name_hash(domain_name: str, region_name: str) -> str: """ Return a hash of the given domain name, which help construct regional domain names for APIs. TODO: use this in the future to dispatch API Gateway API invocations made to the regional domain name """ - return hashlib.shake_128(to_bytes(domain_name)).hexdigest(4) + return hashlib.shake_128(to_bytes(domain_name + region_name)).hexdigest(4) -def get_regional_domain_name(domain_name: str) -> str: +def get_regional_domain_name(domain_name: str, region_name: str) -> str: """ Return the regional domain name for the given domain name. In real AWS, this would look something like: "d-oplm2qchq0.execute-api.us-east-1.amazonaws.com" In LocalStack, we're returning this format: "d-.execute-api.localhost.localstack.cloud" """ - domain_name_hash = get_domain_name_hash(domain_name) + domain_name_hash = get_domain_name_hash(domain_name, region_name) host = localstack_host().host return f"d-{domain_name_hash}.execute-api.{host}" diff --git a/localstack-core/localstack/services/apigateway/legacy/context.py b/localstack-core/localstack/services/apigateway/legacy/context.py index 37b9725f3feb8..9c6e75b424a8d 100644 --- a/localstack-core/localstack/services/apigateway/legacy/context.py +++ b/localstack-core/localstack/services/apigateway/legacy/context.py @@ -1,7 +1,7 @@ import base64 import json from enum import Enum -from typing import Any, Dict, List, Optional, Union +from typing import Any from responses import Response @@ -10,7 +10,7 @@ from localstack.utils.strings import short_uid, to_str # type definition for data parameters (i.e., invocation payloads) -InvocationPayload = Union[Dict, str, bytes] +InvocationPayload = dict | str | bytes class ApiGatewayVersion(Enum): @@ -25,15 +25,15 @@ class ApiInvocationContext: method: str path: str data: InvocationPayload - headers: Dict[str, str] + headers: dict[str, str] # raw URI (including query string) retired from werkzeug "RAW_URI" environment variable raw_uri: str # invocation context - context: Dict[str, Any] + context: dict[str, Any] # authentication info for this invocation - auth_context: Dict[str, Any] + auth_context: dict[str, Any] # target API/resource details extracted from the invocation apigw_version: ApiGatewayVersion @@ -43,24 +43,24 @@ class ApiInvocationContext: region_name: str # resource path, including any path parameter placeholders (e.g., "/my/path/{id}") resource_path: str - integration: Dict - resource: Dict + integration: dict + resource: dict # Invocation path with query string, e.g., "/my/path?test". Defaults to "path", can be used # to overwrite the actual API path, in case the path format "../_user_request_/.." is used. _path_with_query_string: str # response templates to be applied to the invocation result - response_templates: Dict + response_templates: dict - route: Dict + route: dict connection_id: str - path_params: Dict + path_params: dict # response object response: Response # dict of stage variables (mapping names to values) - stage_variables: Dict[str, str] + stage_variables: dict[str, str] # websockets route selection ws_route: str @@ -69,12 +69,12 @@ def __init__( self, method: str, path: str, - data: Union[str, bytes], - headers: Dict[str, str], + data: str | bytes, + headers: dict[str, str], api_id: str = None, stage: str = None, - context: Dict[str, Any] = None, - auth_context: Dict[str, Any] = None, + context: dict[str, Any] = None, + auth_context: dict[str, Any] = None, ): self.method = method self._path = path @@ -109,7 +109,7 @@ def path(self, new_path: str): self._path = new_path @property - def resource_id(self) -> Optional[str]: + def resource_id(self) -> str | None: return (self.resource or {}).get("id") @property @@ -130,18 +130,18 @@ def path_with_query_string(self, new_path: str): new_path = "/" + new_path.lstrip("/") self._path_with_query_string = new_path - def query_params(self) -> Dict[str, str]: + def query_params(self) -> dict[str, str]: """Extract the query parameters from the target URL or path in this request context.""" query_string = self.path_with_query_string.partition("?")[2] return parse_query_string(query_string) @property - def integration_uri(self) -> Optional[str]: + def integration_uri(self) -> str | None: integration = self.integration or {} return integration.get("uri") or integration.get("integrationUri") @property - def auth_identity(self) -> Optional[Dict]: + def auth_identity(self) -> dict | None: if isinstance(self.auth_context, dict): if self.auth_context.get("identity") is None: self.auth_context["identity"] = {} @@ -153,7 +153,7 @@ def authorizer_type(self) -> str: return self.auth_context.get("authorizer_type") if self.auth_context else None @property - def authorizer_result(self) -> Dict[str, Any]: + def authorizer_result(self) -> dict[str, Any]: if isinstance(self.auth_context, dict): return self.auth_context.get("authorizer") if self.auth_context else {} @@ -165,7 +165,7 @@ def is_v1(self) -> bool: """Whether this is an API Gateway v1 request""" return self.apigw_version == ApiGatewayVersion.V1 - def cookies(self) -> Optional[List[str]]: + def cookies(self) -> list[str] | None: if cookies := self.headers.get("cookie") or "": return list(cookies.split(";")) return None diff --git a/localstack-core/localstack/services/apigateway/legacy/helpers.py b/localstack-core/localstack/services/apigateway/legacy/helpers.py index 62a91a32e78b0..c3d40e689555d 100644 --- a/localstack-core/localstack/services/apigateway/legacy/helpers.py +++ b/localstack-core/localstack/services/apigateway/legacy/helpers.py @@ -3,8 +3,8 @@ import re import time from collections import defaultdict -from datetime import datetime, timezone -from typing import Any, Dict, List, Optional, Tuple, TypedDict, Union +from datetime import UTC, datetime +from typing import Any, TypedDict from urllib import parse as urlparse from botocore.utils import InvalidArnException @@ -37,7 +37,7 @@ # regex path pattern for user requests, handles stages like $default PATH_REGEX_USER_REQUEST = ( - r"^/restapis/([A-Za-z0-9_\\-]+)(?:/([A-Za-z0-9\_($|%%24)\\-]+))?/%s/(.*)$" % PATH_USER_REQUEST + rf"^/restapis/([A-Za-z0-9_\\-]+)(?:/([A-Za-z0-9\_($|%24)\\-]+))?/{PATH_USER_REQUEST}/(.*)$" ) # URL pattern for invocations HOST_REGEX_EXECUTE_API = r"(?:.*://)?([a-zA-Z0-9]+)(?:(-vpce-[^.]+))?\.execute-api\.(.*)" @@ -90,7 +90,7 @@ def resolve(self, context: ApiInvocationContext) -> IntegrationParameters: :return: IntegrationParameters """ - method_request_params: Dict[str, Any] = self.method_request_dict(context) + method_request_params: dict[str, Any] = self.method_request_dict(context) # requestParameters: { # "integration.request.path.pathParam": "method.request.header.Content-Type" @@ -128,13 +128,13 @@ def resolve(self, context: ApiInvocationContext) -> IntegrationParameters: return result - def method_request_dict(self, context: ApiInvocationContext) -> Dict[str, Any]: + def method_request_dict(self, context: ApiInvocationContext) -> dict[str, Any]: """ Build a dict with all method request parameters and their values. :return: dict with all method request parameters and their values, and all keys in lowercase """ - params: Dict[str, str] = {} + params: dict[str, str] = {} # TODO: add support for multi-values headers and multi-values querystring @@ -183,7 +183,7 @@ def method_request_dict(self, context: ApiInvocationContext) -> Dict[str, Any]: class ResponseParametersResolver: - def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: + def resolve(self, context: ApiInvocationContext) -> dict[str, str]: """ Resolve integration response parameters into method response parameters. Integration response parameters can map header, body, @@ -191,7 +191,7 @@ def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: :return: dict with all method response parameters and their values """ - integration_request_params: Dict[str, Any] = self.integration_request_dict(context) + integration_request_params: dict[str, Any] = self.integration_request_dict(context) # "responseParameters" : { # "method.response.header.Location" : "integration.response.body.redirect.url", @@ -213,7 +213,7 @@ def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: method_parameters[k] = v.replace("'", "") # build the integration parameters - result: Dict[str, str] = {} + result: dict[str, str] = {} for k, v in method_parameters.items(): # headers if k.startswith("method.response.header."): @@ -222,8 +222,8 @@ def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: return result - def integration_request_dict(self, context: ApiInvocationContext) -> Dict[str, Any]: - params: Dict[str, str] = {} + def integration_request_dict(self, context: ApiInvocationContext) -> dict[str, Any]: + params: dict[str, str] = {} for k, v in context.headers.items(): params[f"integration.request.header.{k}"] = v @@ -293,7 +293,7 @@ def is_test_invoke_method(method, path): return method == "POST" and bool(re.match(PATH_REGEX_TEST_INVOKE_API, path)) -def get_stage_variables(context: ApiInvocationContext) -> Optional[Dict[str, str]]: +def get_stage_variables(context: ApiInvocationContext) -> dict[str, str] | None: if is_test_invoke_method(context.method, context.path): return None @@ -316,7 +316,7 @@ def tokenize_path(path): return path.lstrip("/").split("/") -def extract_path_params(path: str, extracted_path: str) -> Dict[str, str]: +def extract_path_params(path: str, extracted_path: str) -> dict[str, str]: tokenized_extracted_path = tokenize_path(extracted_path) # Looks for '{' in the tokenized extracted path path_params_list = [(i, v) for i, v in enumerate(tokenized_extracted_path) if "{" in v] @@ -335,7 +335,7 @@ def extract_path_params(path: str, extracted_path: str) -> Dict[str, str]: return path_params -def extract_query_string_params(path: str) -> Tuple[str, Dict[str, str]]: +def extract_query_string_params(path: str) -> tuple[str, dict[str, str]]: parsed_path = urlparse.urlparse(path) if not path.startswith("//"): path = parsed_path.path @@ -375,12 +375,12 @@ def get_apigateway_path_for_resource( path_part = target_resource.get("pathPart", "") if path_suffix: if path_part: - path_suffix = "%s/%s" % (path_part, path_suffix) + path_suffix = f"{path_part}/{path_suffix}" else: path_suffix = path_part parent_id = target_resource.get("parentId") if not parent_id: - return "/%s" % path_suffix + return f"/{path_suffix}" return get_apigateway_path_for_resource( api_id, parent_id, @@ -411,15 +411,15 @@ def get_rest_api_paths(account_id: str, region_name: str, rest_api_id: str): # -method-request.html # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html def get_resource_for_path( - path: str, method: str, path_map: Dict[str, Dict] -) -> tuple[Optional[str], Optional[dict]]: + path: str, method: str, path_map: dict[str, dict] +) -> tuple[str | None, dict | None]: matches = [] # creates a regex from the input path if there are parameters, e.g /foo/{bar}/baz -> /foo/[ # ^\]+/baz, otherwise is a direct match. for api_path, details in path_map.items(): api_path_regex = re.sub(r"{[^+]+\+}", r"[^\?#]+", api_path) api_path_regex = re.sub(r"{[^}]+}", r"[^/]+", api_path_regex) - if re.match(r"^%s$" % api_path_regex, path): + if re.match(rf"^{api_path_regex}$", path): matches.append((api_path, details)) # if there are no matches, it's not worth to proceed, bail here! @@ -508,8 +508,7 @@ def connect_api_gateway_to_sqs(gateway_name, stage_name, queue_arn, path, accoun "integrations": [ { "type": "AWS", - "uri": "arn:%s:apigateway:%s:sqs:path/%s/%s" - % (partition, sqs_region, sqs_account, queue_name), + "uri": f"arn:{partition}:apigateway:{sqs_region}:sqs:path/{sqs_account}/{queue_name}", "requestTemplates": {"application/json": template}, "requestParameters": { "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" @@ -528,7 +527,7 @@ def connect_api_gateway_to_sqs(gateway_name, stage_name, queue_arn, path, accoun def get_target_resource_details( invocation_context: ApiInvocationContext, -) -> Tuple[Optional[str], Optional[dict]]: +) -> tuple[str | None, dict | None]: """Look up and return the API GW resource (path pattern + resource dict) for the given invocation context.""" path_map = get_rest_api_paths( account_id=invocation_context.account_id, @@ -557,7 +556,7 @@ def get_target_resource_details( return None, None -def get_target_resource_method(invocation_context: ApiInvocationContext) -> Optional[Dict]: +def get_target_resource_method(invocation_context: ApiInvocationContext) -> dict | None: """Look up and return the API GW resource method for the given invocation context.""" _, resource = get_target_resource_details(invocation_context) if not resource: @@ -614,7 +613,7 @@ def get_event_request_context(invocation_context: ApiInvocationContext): }, "httpMethod": method, "protocol": "HTTP/1.1", - "requestTime": datetime.now(timezone.utc).strftime(REQUEST_TIME_DATE_FORMAT), + "requestTime": datetime.now(UTC).strftime(REQUEST_TIME_DATE_FORMAT), "requestTimeEpoch": int(time.time() * 1000), "authorizer": {}, } @@ -660,11 +659,11 @@ def set_api_id_stage_invocation_path( if path_match: api_id = path_match.group(1) stage = path_match.group(2) - relative_path_w_query_params = "/%s" % path_match.group(3) + relative_path_w_query_params = f"/{path_match.group(3)}" elif host_match: api_id = extract_api_id_from_hostname_in_url(host_header) stage = path.strip("/").split("/")[0] - relative_path_w_query_params = "/%s" % path.lstrip("/").partition("/")[2] + relative_path_w_query_params = "/{}".format(path.lstrip("/").partition("/")[2]) elif test_invoke_match: stage = invocation_context.stage api_id = invocation_context.api_id @@ -681,7 +680,7 @@ def set_api_id_stage_invocation_path( return invocation_context -def get_api_account_id_and_region(api_id: str) -> Tuple[Optional[str], Optional[str]]: +def get_api_account_id_and_region(api_id: str) -> tuple[str | None, str | None]: """Return the region name for the given REST API ID""" for account_id, account in apigateway_backends.items(): for region_name, region in account.items(): @@ -698,7 +697,7 @@ def extract_api_id_from_hostname_in_url(hostname: str) -> str: return match.group(1) -def multi_value_dict_for_list(elements: Union[List, Dict]) -> Dict: +def multi_value_dict_for_list(elements: list | dict) -> dict: temp_mv_dict = defaultdict(list) for key in elements: if isinstance(key, (list, tuple)): diff --git a/localstack-core/localstack/services/apigateway/legacy/integration.py b/localstack-core/localstack/services/apigateway/legacy/integration.py index 12852fff266af..6c98b91487b9e 100644 --- a/localstack-core/localstack/services/apigateway/legacy/integration.py +++ b/localstack-core/localstack/services/apigateway/legacy/integration.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from functools import lru_cache from http import HTTPMethod, HTTPStatus -from typing import Any, Dict +from typing import Any from urllib.parse import urljoin import requests @@ -91,7 +91,7 @@ def _create_response(cls, status_code, headers, data=""): @classmethod def apply_request_parameters( - cls, integration_params: IntegrationParameters, headers: Dict[str, Any] + cls, integration_params: IntegrationParameters, headers: dict[str, Any] ): for k, v in integration_params.get("headers").items(): headers.update({k: v}) @@ -569,7 +569,7 @@ def invoke(self, invocation_context: ApiInvocationContext): return invocation_context.response @classmethod - def _validate_required_params(cls, request_parameters: Dict[str, Any]) -> None: + def _validate_required_params(cls, request_parameters: dict[str, Any]) -> None: if not request_parameters: raise BadRequestException("Missing required parameters") # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html#Kinesis-PutRecord @@ -588,7 +588,7 @@ def _validate_required_params(cls, request_parameters: Dict[str, Any]) -> None: def _create_request_parameters( self, invocation_context: ApiInvocationContext - ) -> Dict[str, Any]: + ) -> dict[str, Any]: request_parameters = invocation_context.integration.get("requestParameters", {}) self._validate_required_params(request_parameters) @@ -756,7 +756,7 @@ def invoke(self, invocation_context: ApiInvocationContext): class HTTPIntegration(BackendIntegration): @staticmethod - def _set_http_apigw_headers(headers: Dict[str, Any], invocation_context: ApiInvocationContext): + def _set_http_apigw_headers(headers: dict[str, Any], invocation_context: ApiInvocationContext): del headers["host"] headers["x-amzn-apigateway-api-id"] = invocation_context.api_id return headers @@ -783,7 +783,7 @@ def invoke(self, invocation_context: ApiInvocationContext): instances = client.list_instances(ServiceId=service_id)["Instances"] instance = (instances or [None])[0] if instance and instance.get("Id"): - uri = "http://%s/%s" % (instance["Id"], invocation_path.lstrip("/")) + uri = "http://{}/{}".format(instance["Id"], invocation_path.lstrip("/")) # apply custom request template invocation_context.context = get_event_request_context(invocation_context) @@ -902,7 +902,7 @@ def invoke(self, invocation_context: ApiInvocationContext) -> Response: class StepFunctionIntegration(BackendIntegration): @classmethod - def _validate_required_params(cls, request_parameters: Dict[str, Any]) -> None: + def _validate_required_params(cls, request_parameters: dict[str, Any]) -> None: if not request_parameters: raise BadRequestException("Missing required parameters") # stateMachineArn and input are required @@ -977,8 +977,9 @@ def invoke(self, invocation_context: ApiInvocationContext): headers={"Content-Type": APPLICATION_JSON}, data=json.dumps( { - "message": "StepFunctions execution %s failed with status '%s'" - % (result["executionArn"], result_status) + "message": "StepFunctions execution {} failed with status '{}'".format( + result["executionArn"], result_status + ) } ), ) @@ -1065,7 +1066,7 @@ def invoke(self, invocation_context: ApiInvocationContext) -> Response: # TODO: remove once we migrate all usages to `apply_request_parameters` on BackendIntegration def apply_request_parameters( - uri: str, integration: Dict[str, Any], path_params: Dict[str, str], query_params: Dict[str, str] + uri: str, integration: dict[str, Any], path_params: dict[str, str], query_params: dict[str, str] ): request_parameters = integration.get("requestParameters") uri = uri or integration.get("uri") or integration.get("integrationUri") or "" diff --git a/localstack-core/localstack/services/apigateway/legacy/invocations.py b/localstack-core/localstack/services/apigateway/legacy/invocations.py index 18085fc52e22e..1416e6e24b956 100644 --- a/localstack-core/localstack/services/apigateway/legacy/invocations.py +++ b/localstack-core/localstack/services/apigateway/legacy/invocations.py @@ -142,8 +142,9 @@ def _is_body_valid(self, resource) -> bool: # try to get the resolved model first resolved_schema = model_resolver.get_resolved_model() if not resolved_schema: - LOG.exception( - "An exception occurred while trying to validate the request: could not find the model" + LOG.error( + "An exception occurred while trying to validate the request: could not find the model", + exc_info=LOG.isEnabledFor(logging.DEBUG), ) return False @@ -279,7 +280,7 @@ def invoke_rest_api(invocation_context: ApiInvocationContext): extracted_path, resource = get_target_resource_details(invocation_context) if not resource: - return make_error_response("Unable to find path %s" % invocation_context.path, 404) + return make_error_response(f"Unable to find path {invocation_context.path}", 404) # validate request validator = RequestValidator(invocation_context) @@ -306,7 +307,7 @@ def invoke_rest_api(invocation_context: ApiInvocationContext): # default to returning CORS headers if this is an OPTIONS request return get_cors_response(headers) return make_error_response( - "Unable to find integration for: %s %s (%s)" % (method, invocation_path, raw_path), + f"Unable to find integration for: {method} {invocation_path} ({raw_path})", 404, ) @@ -334,7 +335,7 @@ def invoke_rest_api_integration(invocation_context: ApiInvocationContext): return e.to_response() except Exception as e: msg = f"Error invoking integration for API Gateway ID '{invocation_context.api_id}': {e}" - LOG.exception(msg) + LOG.error(msg, exc_info=LOG.isEnabledFor(logging.DEBUG)) return make_error_response(msg, 400) diff --git a/localstack-core/localstack/services/apigateway/legacy/provider.py b/localstack-core/localstack/services/apigateway/legacy/provider.py index 846a965628402..c946f0a7733d4 100644 --- a/localstack-core/localstack/services/apigateway/legacy/provider.py +++ b/localstack-core/localstack/services/apigateway/legacy/provider.py @@ -42,6 +42,7 @@ DomainName, DomainNames, DomainNameStatus, + EndpointAccessMode, EndpointConfiguration, EndpointType, ExportResponse, @@ -93,6 +94,7 @@ UsagePlans, VpcLink, VpcLinks, + VpcLinkStatus, ) from localstack.aws.connect import connect_to from localstack.aws.forwarder import create_aws_request_context @@ -117,7 +119,11 @@ from localstack.services.apigateway.legacy.helpers import multi_value_dict_for_list from localstack.services.apigateway.legacy.invocations import invoke_rest_api_from_request from localstack.services.apigateway.legacy.router_asf import ApigatewayRouter, to_invocation_context -from localstack.services.apigateway.models import ApiGatewayStore, RestApiContainer +from localstack.services.apigateway.models import ( + ApiGatewayStore, + RestApiContainer, + apigateway_stores, +) from localstack.services.apigateway.next_gen.execute_api.router import ( ApiGatewayRouter as ApiGatewayRouterNextGen, ) @@ -125,6 +131,7 @@ from localstack.services.edge import ROUTER from localstack.services.moto import call_moto, call_moto_with_request from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.aws.arns import InvalidArnException, get_partition, parse_arn from localstack.utils.collections import ( DelSafeDict, @@ -191,6 +198,12 @@ def on_after_init(self): apply_patches() self.router.register_routes() + def accept_state_visitor(self, visitor: StateVisitor): + from moto.apigateway import apigateway_backends + + visitor.visit(apigateway_backends) + visitor.visit(apigateway_stores) + @handler("TestInvokeMethod", expand=False) def test_invoke_method( self, context: RequestContext, request: TestInvokeMethodRequest @@ -323,8 +336,18 @@ def create_api_key( tags: MapOfStringToString = None, **kwargs, ) -> ApiKey: + if name and len(name) > 1024: + raise BadRequestException("Invalid API Key name, can be at most 1024 characters.") + if value: + if len(value) > 128: + raise BadRequestException("API Key value exceeds maximum size of 128 characters") + elif len(value) < 20: + raise BadRequestException("API Key value should be at least 20 characters") + if description and len(description) > 125000: + raise BadRequestException("Invalid API Key description specified.") api_key = call_moto(context) - + if name == "": + api_key.pop("name", None) # transform array of stage keys [{'restApiId': '0iscapk09u', 'stageName': 'dev'}] into # array of strings ['0iscapk09u/dev'] stage_keys = api_key.get("stageKeys", []) @@ -466,11 +489,19 @@ def update_rest_api( @handler("PutRestApi", expand=False) def put_rest_api(self, context: RequestContext, request: PutRestApiRequest) -> RestApi: + body_data = request["body"].read() + try: + openapi_spec = parse_json_or_yaml(to_str(body_data)) + except Exception: + raise BadRequestException("Invalid OpenAPI input.") # TODO: take into account the mode: overwrite or merge # the default is now `merge`, but we are removing everything rest_api = get_moto_rest_api(context, request["restApiId"]) rest_api, warnings = import_api_from_openapi_spec( - rest_api, context=context, request=request + rest_api, + context=context, + open_api_spec=openapi_spec, + mode=request.get("mode") or PutMode.merge, ) rest_api.root_resource_id = get_moto_rest_api_root_resource(rest_api) @@ -493,20 +524,21 @@ def create_domain_name( self, context: RequestContext, domain_name: String, - certificate_name: String = None, - certificate_body: String = None, - certificate_private_key: String = None, - certificate_chain: String = None, - certificate_arn: String = None, - regional_certificate_name: String = None, - regional_certificate_arn: String = None, - endpoint_configuration: EndpointConfiguration = None, - tags: MapOfStringToString = None, - security_policy: SecurityPolicy = None, - mutual_tls_authentication: MutualTlsAuthenticationInput = None, - ownership_verification_certificate_arn: String = None, - policy: String = None, - routing_mode: RoutingMode = None, + certificate_name: String | None = None, + certificate_body: String | None = None, + certificate_private_key: String | None = None, + certificate_chain: String | None = None, + certificate_arn: String | None = None, + regional_certificate_name: String | None = None, + regional_certificate_arn: String | None = None, + endpoint_configuration: EndpointConfiguration | None = None, + tags: MapOfStringToString | None = None, + security_policy: SecurityPolicy | None = None, + endpoint_access_mode: EndpointAccessMode | None = None, + mutual_tls_authentication: MutualTlsAuthenticationInput | None = None, + ownership_verification_certificate_arn: String | None = None, + policy: String | None = None, + routing_mode: RoutingMode | None = None, **kwargs, ) -> DomainName: if not domain_name: @@ -530,7 +562,7 @@ def create_domain_name( domainName=domain_name, certificateName=certificate_name, certificateArn=certificate_arn, - regionalDomainName=get_regional_domain_name(domain_name), + regionalDomainName=get_regional_domain_name(domain_name, context.region), domainNameStatus=DomainNameStatus.AVAILABLE, regionalHostedZoneId=zone_id, regionalCertificateName=regional_certificate_name, @@ -665,66 +697,6 @@ def _delete_children(resource_to_delete: str): parent_id = moto_resource.parent_id api_resources[parent_id].remove(resource_id) - def update_integration_response( - self, - context: RequestContext, - rest_api_id: String, - resource_id: String, - http_method: String, - status_code: StatusCode, - patch_operations: ListOfPatchOperation = None, - **kwargs, - ) -> IntegrationResponse: - # XXX: THIS IS NOT A COMPLETE IMPLEMENTATION, just the minimum required to get tests going - # TODO: validate patch operations - - moto_rest_api = get_moto_rest_api(context, rest_api_id) - moto_resource = moto_rest_api.resources.get(resource_id) - if not moto_resource: - raise NotFoundException("Invalid Resource identifier specified") - - moto_method = moto_resource.resource_methods.get(http_method) - if not moto_method: - raise NotFoundException("Invalid Method identifier specified") - - integration_response = moto_method.method_integration.integration_responses.get(status_code) - if not integration_response: - raise NotFoundException("Invalid Integration Response identifier specified") - - for patch_operation in patch_operations: - op = patch_operation.get("op") - path = patch_operation.get("path") - - # for path "/responseTemplates/application~1json" - if "/responseTemplates" in path: - integration_response.response_templates = ( - integration_response.response_templates or {} - ) - value = patch_operation.get("value") - if not isinstance(value, str): - raise BadRequestException( - f"Invalid patch value '{value}' specified for op '{op}'. Must be a string" - ) - param = path.removeprefix("/responseTemplates/") - param = param.replace("~1", "/") - if op == "remove": - integration_response.response_templates.pop(param) - elif op in ("add", "replace"): - integration_response.response_templates[param] = value - - elif "/contentHandling" in path and op == "replace": - integration_response.content_handling = patch_operation.get("value") - - elif "/selectionPattern" in path and op == "replace": - integration_response.selection_pattern = patch_operation.get("value") - - response: IntegrationResponse = integration_response.to_json() - # in case it's empty, we still want to pass it on as "" - # TODO: add a test case for this - response["selectionPattern"] = integration_response.selection_pattern - - return response - def update_resource( self, context: RequestContext, @@ -757,6 +729,9 @@ def update_resource( f"Invalid patch path '{path}' specified for op '{op}'. Please choose supported operations" ) + if moto_resource.parent_id is None: + raise BadRequestException(f"Root resource cannot update its {path.strip('/')}.") + if path == "/parentId": value = patch_operation.get("value") future_parent_resource = moto_rest_api.resources.get(value) @@ -793,7 +768,7 @@ def update_resource( api_resources.pop(current_parent_id) # add it to the new parent children - future_sibling_resources = api_resources[moto_resource.parent_id] + future_sibling_resources = api_resources.setdefault(moto_resource.parent_id, []) future_sibling_resources.append(resource_id) response = moto_resource.to_dict() @@ -1199,6 +1174,10 @@ def get_stages( self._patch_stage_response(stage) if not stage.get("description"): stage.pop("description", None) + if deployment_id: + response["item"] = [ + s for s in response["item"] if s.get("deploymentId") == deployment_id + ] return Stages(**response) @handler("UpdateStage") @@ -1559,7 +1538,10 @@ def import_documentation_parts( **kwargs, ) -> DocumentationPartIds: body_data = body.read() - openapi_spec = parse_json_or_yaml(to_str(body_data)) + try: + openapi_spec = parse_json_or_yaml(to_str(body_data)) + except Exception: + raise BadRequestException("Unable to build importer with provided input.") rest_api_container = get_rest_api_container(context, rest_api_id=rest_api_id) @@ -1573,6 +1555,7 @@ def import_documentation_parts( rest_api_container.documentation_parts.clear() for doc_part in documentation["documentationParts"]: entity_id = short_uid()[:6] + doc_part["properties"] = json.dumps(doc_part.get("properties", "")) rest_api_container.documentation_parts[entity_id] = DocumentationPart( id=entity_id, **doc_part ) @@ -1844,11 +1827,31 @@ def create_vpc_link( tags: MapOfStringToString = None, **kwargs, ) -> VpcLink: + # TODO add tag support + if not name: + raise BadRequestException("Name cannot be empty") + for arn in target_arns: + try: + parse_arn(arn) + except InvalidArnException: + raise BadRequestException(f"Invalid ARN. ARN is not well formed {arn}") + region_details = get_apigateway_store(context=context) link_id = short_uid() - entry = {"id": link_id, "status": "AVAILABLE"} + entry = { + "id": link_id, + "status": VpcLinkStatus.PENDING, + "name": name, + "description": description, + "targetArns": target_arns, + } region_details.vpc_links[link_id] = entry result = to_vpc_link_response_json(entry) + + # update the status and status message of the store object + entry["status"] = VpcLinkStatus.AVAILABLE + entry["statusMessage"] = "Your vpc link is ready for use" + return VpcLink(**result) def get_vpc_links( @@ -1868,7 +1871,7 @@ def get_vpc_link(self, context: RequestContext, vpc_link_id: String, **kwargs) - region_details = get_apigateway_store(context=context) vpc_link = region_details.vpc_links.get(vpc_link_id) if vpc_link is None: - raise NotFoundException(f'VPC link ID "{vpc_link_id}" not found') + raise NotFoundException("Invalid Vpc Link identifier specified") result = to_vpc_link_response_json(vpc_link) return VpcLink(**result) @@ -1882,7 +1885,7 @@ def update_vpc_link( region_details = get_apigateway_store(context=context) vpc_link = region_details.vpc_links.get(vpc_link_id) if vpc_link is None: - raise NotFoundException(f'VPC link ID "{vpc_link_id}" not found') + raise NotFoundException("Invalid Vpc Link identifier specified") result = apply_json_patch_safe(vpc_link, patch_operations) result = to_vpc_link_response_json(result) return VpcLink(**result) @@ -1891,7 +1894,7 @@ def delete_vpc_link(self, context: RequestContext, vpc_link_id: String, **kwargs region_details = get_apigateway_store(context=context) vpc_link = region_details.vpc_links.pop(vpc_link_id, None) if vpc_link is None: - raise NotFoundException(f'VPC link ID "{vpc_link_id}" not found for deletion') + raise NotFoundException("Invalid Vpc Link identifier specified") # request validators @@ -2059,7 +2062,11 @@ def import_rest_api( body_data = body.read() # create rest api - openapi_spec = parse_json_or_yaml(to_str(body_data)) + try: + openapi_spec = parse_json_or_yaml(to_str(body_data)) + except Exception: + raise BadRequestException("Invalid OpenAPI input.") + create_api_request = CreateRestApiRequest(name=openapi_spec.get("info").get("title")) create_api_context = create_custom_context( context, @@ -2100,17 +2107,39 @@ def get_integration( **kwargs, ) -> Integration: try: - response: Integration = call_moto(context) - except CommonServiceException as e: - # the Exception raised by moto does not have the right message not status code - if e.code == "NotFoundException": - raise NotFoundException("Invalid Integration identifier specified") - raise + moto_rest_api = get_moto_rest_api(context, rest_api_id) + except NotFoundException: + raise NotFoundException("Invalid Resource identifier specified") + + if not (moto_resource := moto_rest_api.resources.get(resource_id)): + raise NotFoundException("Invalid Resource identifier specified") + + if not (moto_method := moto_resource.resource_methods.get(http_method)): + raise NotFoundException("Invalid Method identifier specified") + + if not moto_method.method_integration: + raise NotFoundException("Invalid Integration identifier specified") + + response: Integration = call_moto(context) if integration_responses := response.get("integrationResponses"): for integration_response in integration_responses.values(): remove_empty_attributes_from_integration_response(integration_response) + if response.get("connectionType") == "VPC_LINK": + # FIXME: this is hacky to workaround moto not saving the VPC Link `connectionId` + # only do this internal check of Moto if the integration is of VPC_LINK type + moto_rest_api = get_moto_rest_api(context=context, rest_api_id=rest_api_id) + try: + method = moto_rest_api.resources[resource_id].resource_methods[http_method] + integration = method.method_integration + if connection_id := getattr(integration, "connection_id", None): + response["connectionId"] = connection_id + + except (AttributeError, KeyError): + # this error should have been caught by `call_moto` + pass + return response def put_integration( @@ -2165,6 +2194,7 @@ def put_integration( moto_request.setdefault("timeoutInMillis", 29000) if integration_type in (IntegrationType.HTTP, IntegrationType.HTTP_PROXY): moto_request.setdefault("connectionType", ConnectionType.INTERNET) + response = call_moto_with_request(context, moto_request) remove_empty_attributes_from_integration(integration=response) @@ -2172,6 +2202,13 @@ def put_integration( if integration_type == "MOCK": response.pop("uri", None) + # TODO: moto does not save the connection_id + elif moto_request.get("connectionType") == "VPC_LINK": + connection_id = moto_request.get("connectionId", "") + # attach the connection id to the moto object + method.method_integration.connection_id = connection_id + response["connectionId"] = connection_id + return response def update_integration( @@ -2193,6 +2230,7 @@ def update_integration( raise NotFoundException("Invalid Integration identifier specified") integration = method.method_integration + # TODO: validate the patch operations patch_api_gateway_entity(integration, patch_operations) # fix data types @@ -2201,8 +2239,12 @@ def update_integration( if skip_verification := (integration.tls_config or {}).get("insecureSkipVerification"): integration.tls_config["insecureSkipVerification"] = str_to_bool(skip_verification) - integration_dict: Integration = integration.to_json() - return integration_dict + response: Integration = integration.to_json() + + if connection_id := getattr(integration, "connection_id", None): + response["connectionId"] = connection_id + + return response def delete_integration( self, @@ -2307,6 +2349,93 @@ def put_integration_response( return response + def update_integration_response( + self, + context: RequestContext, + rest_api_id: String, + resource_id: String, + http_method: String, + status_code: StatusCode, + patch_operations: ListOfPatchOperation = None, + **kwargs, + ) -> IntegrationResponse: + # XXX: THIS IS NOT A COMPLETE IMPLEMENTATION, just the minimum required to get tests going + # TODO: validate patch operations + + moto_rest_api = get_moto_rest_api(context, rest_api_id) + moto_resource = moto_rest_api.resources.get(resource_id) + if not moto_resource: + raise NotFoundException("Invalid Resource identifier specified") + + moto_method = moto_resource.resource_methods.get(http_method) + if not moto_method: + raise NotFoundException("Invalid Method identifier specified") + + integration_response = moto_method.method_integration.integration_responses.get(status_code) + if not integration_response: + raise NotFoundException("Invalid Integration Response identifier specified") + + for patch_operation in patch_operations: + op = patch_operation.get("op") + path = patch_operation.get("path") + + # for path "/responseTemplates/application~1json" + if "/responseTemplates" in path: + integration_response.response_templates = ( + integration_response.response_templates or {} + ) + value = patch_operation.get("value") + if not isinstance(value, str): + raise BadRequestException( + f"Invalid patch value '{value}' specified for op '{op}'. Must be a string" + ) + param = path.removeprefix("/responseTemplates/") + param = param.replace("~1", "/") + if op == "remove": + integration_response.response_templates.pop(param) + elif op in ("add", "replace"): + integration_response.response_templates[param] = value + + elif "/contentHandling" in path and op == "replace": + integration_response.content_handling = patch_operation.get("value") + + elif "/selectionPattern" in path and op == "replace": + integration_response.selection_pattern = patch_operation.get("value") + + response: IntegrationResponse = integration_response.to_json() + # in case it's empty, we still want to pass it on as "" + # TODO: add a test case for this + response["selectionPattern"] = integration_response.selection_pattern + + return response + + def delete_integration_response( + self, + context: RequestContext, + rest_api_id: String, + resource_id: String, + http_method: String, + status_code: StatusCode, + **kwargs, + ) -> None: + moto_backend = apigw_models.apigateway_backends[context.account_id][context.region] + moto_rest_api = moto_backend.apis.get(rest_api_id) + if not moto_rest_api: + raise NotFoundException("Invalid Resource identifier specified") + + if not (moto_resource := moto_rest_api.resources.get(resource_id)): + raise NotFoundException("Invalid Resource identifier specified") + + if not (moto_method := moto_resource.resource_methods.get(http_method)): + raise NotFoundException("Invalid Integration identifier specified") + + if not moto_method.method_integration: + raise NotFoundException("Invalid Integration identifier specified") + if not ( + integration_responses := moto_method.method_integration.integration_responses + ) or not integration_responses.pop(status_code, None): + raise NotFoundException("Invalid Response status code specified") + def get_export( self, context: RequestContext, @@ -2363,6 +2492,10 @@ def get_api_keys( for api_key in api_keys: api_key.pop("value") + if limit is not None: + if limit < 1 or limit > 500: + limit = None + item_list = PaginatedList(api_keys) def token_generator(item): @@ -2387,6 +2520,14 @@ def update_api_key( patch_operations: ListOfPatchOperation = None, **kwargs, ) -> ApiKey: + for patch_op in patch_operations: + if patch_op["path"] not in ("/description", "/enabled", "/name", "/customerId"): + raise BadRequestException( + f"Invalid patch path '{patch_op['path']}' specified for op '{patch_op['op']}'. Must be one of: [/description, /enabled, /name, /customerId]" + ) + + if patch_op["path"] == "/description" and len(patch_op["value"]) > 125000: + raise BadRequestException("Invalid API Key description specified.") response: ApiKey = call_moto(context) if "value" in response: response.pop("value", None) @@ -2947,6 +3088,7 @@ def create_custom_context( ctx = create_aws_request_context( service_name=context.service.service_name, action=action, + protocol=context.service.protocol, parameters=parameters, region=context.region, ) @@ -2998,7 +3140,7 @@ def to_documentation_part_response_json(api_id, data): def to_base_mapping_response_json(domain_name, base_path, data): - self_link = "/domainnames/%s/basepathmappings/%s" % (domain_name, base_path) + self_link = f"/domainnames/{domain_name}/basepathmappings/{base_path}" result = to_response_json("basepathmapping", data, self_link=self_link) result = select_from_typed_dict(BasePathMapping, result) return result @@ -3034,9 +3176,9 @@ def to_response_json(model_type, data, api_id=None, self_link=None, id_attr=None id_attr = id_attr or "id" result = deepcopy(data) if not self_link: - self_link = "/%ss/%s" % (model_type, data[id_attr]) + self_link = f"/{model_type}s/{data[id_attr]}" if api_id: - self_link = "/restapis/%s/%s" % (api_id, self_link) + self_link = f"/restapis/{api_id}/{self_link}" # TODO: check if this is still required - "_links" are listed in the sample responses in the docs, but # recent parity tests indicate that this field is not returned by real AWS... # https://docs.aws.amazon.com/apigateway/latest/api/API_GetAuthorizers.html#API_GetAuthorizers_Example_1_Response @@ -3048,7 +3190,7 @@ def to_response_json(model_type, data, api_id=None, self_link=None, id_attr=None "name": model_type, "templated": True, } - result["_links"]["%s:delete" % model_type] = {"href": self_link} + result["_links"][f"{model_type}:delete"] = {"href": self_link} return result diff --git a/localstack-core/localstack/services/apigateway/legacy/router_asf.py b/localstack-core/localstack/services/apigateway/legacy/router_asf.py index 0664c98c56f20..c786ba4ed2bd8 100644 --- a/localstack-core/localstack/services/apigateway/legacy/router_asf.py +++ b/localstack-core/localstack/services/apigateway/legacy/router_asf.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from requests.models import Response as RequestsResponse from werkzeug.datastructures import Headers @@ -22,7 +22,7 @@ # invocation context property decorators and use the url_params directly, # something asked for a long time. def to_invocation_context( - request: Request, url_params: Dict[str, Any] = None + request: Request, url_params: dict[str, Any] = None ) -> ApiInvocationContext: """ Converts an HTTP Request object into an ApiInvocationContext. diff --git a/localstack-core/localstack/services/apigateway/legacy/templates.py b/localstack-core/localstack/services/apigateway/legacy/templates.py index 0ae853981ac02..63164a36b6c2e 100644 --- a/localstack-core/localstack/services/apigateway/legacy/templates.py +++ b/localstack-core/localstack/services/apigateway/legacy/templates.py @@ -3,7 +3,7 @@ import json import logging from enum import Enum -from typing import Any, Dict, Union +from typing import Any from urllib.parse import quote_plus, unquote_plus import xmltodict @@ -68,7 +68,7 @@ class AttributeDict(dict): """ def __init__(self, *args, **kwargs): - super(AttributeDict, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for key, value in self.items(): if isinstance(value, dict): self[key] = AttributeDict(value) @@ -184,7 +184,7 @@ def __repr__(self): class ApiGatewayVtlTemplate(VtlTemplate): """Util class for rendering VTL templates with API Gateway specific extensions""" - def prepare_namespace(self, variables, source: str = APIGW_SOURCE) -> Dict[str, Any]: + def prepare_namespace(self, variables, source: str = APIGW_SOURCE) -> dict[str, Any]: namespace = super().prepare_namespace(variables, source) if stage_var := variables.get("stage_variables") or {}: namespace["stageVariables"] = stage_var @@ -203,7 +203,7 @@ class Templates: def __init__(self): self.vtl = ApiGatewayVtlTemplate() - def render(self, api_context: ApiInvocationContext) -> Union[bytes, str]: + def render(self, api_context: ApiInvocationContext) -> bytes | str: pass def render_vtl(self, template, variables): @@ -250,7 +250,7 @@ class RequestTemplates(Templates): def render( self, api_context: ApiInvocationContext, template_key: str = APPLICATION_JSON - ) -> Union[bytes, str]: + ) -> bytes | str: LOG.debug( "Method request body before transformations: %s", to_str(api_context.data_as_string()) ) @@ -278,7 +278,7 @@ class ResponseTemplates(Templates): template is used. """ - def render(self, api_context: ApiInvocationContext, **kwargs) -> Union[bytes, str]: + def render(self, api_context: ApiInvocationContext, **kwargs) -> bytes | str: # XXX: keep backwards compatibility until we migrate all integrations to this new classes # api_context contains a response object that we want slowly remove from it data = kwargs.get("response", "") diff --git a/localstack-core/localstack/services/apigateway/models.py b/localstack-core/localstack/services/apigateway/models.py index 44fca6b65ae29..c5b04f4211e97 100644 --- a/localstack-core/localstack/services/apigateway/models.py +++ b/localstack-core/localstack/services/apigateway/models.py @@ -1,9 +1,10 @@ -from typing import Any, Dict, List - -from requests.structures import CaseInsensitiveDict +from typing import Any from localstack.aws.api.apigateway import ( + Account, Authorizer, + BasePathMapping, + ClientCertificate, DocumentationPart, DocumentationVersion, DomainName, @@ -13,6 +14,7 @@ RequestValidator, Resource, RestApi, + VpcLink, ) from localstack.services.stores import ( AccountRegionBundle, @@ -28,21 +30,21 @@ class RestApiContainer: # contains the RestApi dictionary. We're not making use of it yet, still using moto data. rest_api: RestApi # maps AuthorizerId -> Authorizer - authorizers: Dict[str, Authorizer] + authorizers: dict[str, Authorizer] # maps RequestValidatorId -> RequestValidator - validators: Dict[str, RequestValidator] + validators: dict[str, RequestValidator] # map DocumentationPartId -> DocumentationPart - documentation_parts: Dict[str, DocumentationPart] + documentation_parts: dict[str, DocumentationPart] # map doc version name -> DocumentationVersion - documentation_versions: Dict[str, DocumentationVersion] + documentation_versions: dict[str, DocumentationVersion] # not used yet, still in moto - gateway_responses: Dict[GatewayResponseType, GatewayResponse] + gateway_responses: dict[GatewayResponseType, GatewayResponse] # maps Model name -> Model - models: Dict[str, Model] + models: dict[str, Model] # maps Model name -> resolved dict Model, so we don't need to load the JSON everytime - resolved_models: Dict[str, dict] + resolved_models: dict[str, dict[str, Any]] # maps ResourceId of a Resource to its children ResourceIds - resource_children: Dict[str, List[str]] + resource_children: dict[str, list[str]] def __init__(self, rest_api: RestApi): self.rest_api = rest_api @@ -88,6 +90,10 @@ def from_rest_api_container( class RestApiDeployment: + account_id: str + region: str + rest_api: MergedRestApi + def __init__( self, account_id: str, @@ -101,26 +107,25 @@ def __init__( class ApiGatewayStore(BaseStore): # maps (API id) -> RestApiContainer - # TODO: remove CaseInsensitiveDict, and lower the value of the ID when getting it from the tags - rest_apis: Dict[str, RestApiContainer] = LocalAttribute(default=CaseInsensitiveDict) + rest_apis: dict[str, RestApiContainer] = LocalAttribute(default=dict) # account details - _account: Dict[str, Any] = LocalAttribute(default=dict) + _account: Account = LocalAttribute(default=dict) # maps (domain_name) -> [path_mappings] - base_path_mappings: Dict[str, List[Dict]] = LocalAttribute(default=dict) + base_path_mappings: dict[str, list[BasePathMapping]] = LocalAttribute(default=dict) # maps ID to VPC link details - vpc_links: Dict[str, Dict] = LocalAttribute(default=dict) + vpc_links: dict[str, VpcLink] = LocalAttribute(default=dict) # maps cert ID to client certificate details - client_certificates: Dict[str, Dict] = LocalAttribute(default=dict) + client_certificates: dict[str, ClientCertificate] = LocalAttribute(default=dict) # maps domain name to domain name model - domain_names: Dict[str, DomainName] = LocalAttribute(default=dict) + domain_names: dict[str, DomainName] = LocalAttribute(default=dict) # maps resource ARN to tags - TAGS: Dict[str, Dict[str, str]] = CrossRegionAttribute(default=dict) + TAGS: dict[str, dict[str, str]] = CrossRegionAttribute(default=dict) # internal deployments, represents a frozen REST API for a deployment, used in our router # TODO: make sure API ID are unique across all accounts @@ -137,7 +142,7 @@ def __init__(self): super().__init__() @property - def account(self): + def account(self) -> Account: if not self._account: self._account.update( { diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py index 843938e0611ed..ec3a932b0a6b1 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py @@ -1,4 +1,4 @@ -from typing import Callable, Type +from collections.abc import Callable from rolo import Response from rolo.gateway.chain import HandlerChain as RoloHandlerChain @@ -14,4 +14,4 @@ None, ] -RestApiGatewayHandlerChain: Type[RoloHandlerChain[RestApiInvocationContext]] = RoloHandlerChain +RestApiGatewayHandlerChain: type[RoloHandlerChain[RestApiInvocationContext]] = RoloHandlerChain diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py index 9f6be795d9af8..cb6bec52b23d9 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py @@ -1,5 +1,5 @@ from http import HTTPMethod -from typing import Optional, TypedDict +from typing import TypedDict from rolo import Request from rolo.gateway import RequestContext @@ -14,12 +14,12 @@ class InvocationRequest(TypedDict, total=False): http_method: HTTPMethod """HTTP Method of the incoming request""" - raw_path: Optional[str] + raw_path: str | None # TODO: verify if raw_path is needed """Raw path of the incoming request with no modification, needed to keep double forward slashes""" - path: Optional[str] + path: str | None """Path of the request with no URL decoding""" - path_parameters: Optional[dict[str, str]] + path_parameters: dict[str, str] | None """Path parameters of the request""" query_string_parameters: dict[str, str] """Query string parameters of the request""" @@ -72,48 +72,48 @@ class RestApiInvocationContext(RequestContext): This context is going to be used to pass relevant information across an API Gateway invocation. """ - deployment: Optional[RestApiDeployment] + deployment: RestApiDeployment | None """Contains the invoked REST API Resources""" - integration: Optional[Integration] + integration: Integration | None """The Method Integration for the invoked request""" - api_id: Optional[str] + api_id: str | None """The REST API identifier of the invoked API""" - stage: Optional[str] + stage: str | None """The REST API stage name linked to this invocation""" - base_path: Optional[str] + base_path: str | None """The REST API base path mapped to the stage of this invocation""" - deployment_id: Optional[str] + deployment_id: str | None """The REST API deployment linked to this invocation""" - region: Optional[str] + region: str | None """The region the REST API is living in.""" - account_id: Optional[str] + account_id: str | None """The account the REST API is living in.""" - trace_id: Optional[str] + trace_id: str | None """The X-Ray trace ID for the request.""" - resource: Optional[Resource] + resource: Resource | None """The resource the invocation matched""" - resource_method: Optional[Method] + resource_method: Method | None """The method of the resource the invocation matched""" - stage_variables: Optional[dict[str, str]] + stage_variables: dict[str, str] | None """The Stage variables, also used in parameters mapping and mapping templates""" - stage_configuration: Optional[Stage] + stage_configuration: Stage | None """The Stage configuration, containing canary deployment settings""" - is_canary: Optional[bool] + is_canary: bool | None """If the current call was directed to a canary deployment""" - context_variables: Optional[ContextVariables] + context_variables: ContextVariables | None """The $context used in data models, authorizers, mapping templates, and CloudWatch access logging""" - context_variable_overrides: Optional[ContextVariableOverrides] + context_variable_overrides: ContextVariableOverrides | None """requestOverrides and responseOverrides are passed from request templates to response templates but are not in the integration context""" - logging_context_variables: Optional[LoggingContextVariables] + logging_context_variables: LoggingContextVariables | None """Additional $context variables available only for access logging, not yet implemented""" - invocation_request: Optional[InvocationRequest] + invocation_request: InvocationRequest | None """Contains the data relative to the invocation request""" - integration_request: Optional[IntegrationRequest] + integration_request: IntegrationRequest | None """Contains the data needed to construct an HTTP request to an Integration""" - endpoint_response: Optional[EndpointResponse] + endpoint_response: EndpointResponse | None """Contains the data returned by an Integration""" - invocation_response: Optional[InvocationResponse] + invocation_response: InvocationResponse | None """Contains the data serialized and to be returned by an invocation""" def __init__(self, request: Request): diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py index ba8ada9769f17..1030f21b6c18a 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py @@ -1,5 +1,4 @@ import logging -from typing import Optional from localstack.aws.api.apigateway import ApiKey, ApiKeySourceType, RestApi from localstack.http import Response @@ -56,9 +55,7 @@ def __call__( LOG.debug("Updating $context.identity.apiKeyId='%s'", validated_key["id"]) identity["apiKeyId"] = validated_key["id"] - def validate_api_key( - self, api_key_value, context: RestApiInvocationContext - ) -> Optional[ApiKey]: + def validate_api_key(self, api_key_value, context: RestApiInvocationContext) -> ApiKey | None: api_id = context.api_id stage = context.stage account_id = context.account_id @@ -95,7 +92,7 @@ def validate_api_key( def get_request_api_key( self, rest_api: RestApi, request: InvocationRequest, identity: ContextVarsIdentity - ) -> Optional[str]: + ) -> str | None: """https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-key-source.html The source of the API key for metering requests according to a usage plan. Valid values are: diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py index 00a35129225b1..7c0081ef7b838 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py @@ -50,7 +50,11 @@ def validate_request( # check if there is a validator for this request if not (validator := rest_api.validators.get(request_validator_id)): # TODO Should we raise an exception instead? - LOG.exception("No validator were found with matching id: '%s'", request_validator_id) + LOG.error( + "No validator were found with matching id: '%s'", + request_validator_id, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) return if self.should_validate_request(validator) and ( @@ -87,9 +91,10 @@ def _is_body_valid( # try to get the resolved model first resolved_schema = model_resolver.get_resolved_model() if not resolved_schema: - LOG.exception( + LOG.error( "An exception occurred while trying to validate the request: could not resolve the model '%s'", model_name, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) return False diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py index 3da898bf8845e..16a2b16a0c179 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py @@ -2,7 +2,6 @@ import logging import re from collections import defaultdict -from typing import Optional from urllib.parse import urlparse from rolo.request import restore_payload @@ -178,7 +177,7 @@ def create_context_variables(context: RestApiInvocationContext) -> ContextVariab return context_variables @staticmethod - def get_stage_variables(context: RestApiInvocationContext) -> Optional[dict[str, str]]: + def get_stage_variables(context: RestApiInvocationContext) -> dict[str, str] | None: stage_variables = context.stage_configuration.get("variables") if context.is_canary: overrides = ( diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py index 4dfe6f95dbcbe..8e69438a012ef 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py @@ -1,7 +1,7 @@ import logging +from collections.abc import Iterable from functools import cache from http import HTTPMethod -from typing import Iterable from werkzeug.exceptions import MethodNotAllowed, NotFound from werkzeug.routing import Map, MapAdapter, Rule @@ -85,12 +85,11 @@ def match(self, context: RestApiInvocationContext) -> tuple[Resource, dict[str, rule, args = matcher.match(path, method=request.method, return_rule=True) except (MethodNotAllowed, NotFound) as e: # MethodNotAllowed (405) exception is raised if a path is matching, but the method does not. - # Our router might handle this as a 404, validate with AWS. + # AWS handles this and the regular 404 as a '403 MissingAuthTokenError' LOG.warning( "API Gateway: No resource or method was found for: %s %s", request.method, path, - exc_info=LOG.isEnabledFor(logging.DEBUG), ) raise MissingAuthTokenError("Missing Authentication Token") from e diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py index 1b1fcbfa3f35a..84c4108665c9a 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py @@ -1,6 +1,6 @@ import logging from collections import defaultdict -from typing import Iterable +from collections.abc import Iterable from werkzeug.datastructures.headers import Headers diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py index 33999b69ea1a9..403208a84d0fa 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py @@ -4,7 +4,7 @@ import re import time from secrets import token_hex -from typing import Type, TypedDict +from typing import TypedDict from moto.apigateway.models import RestAPI as MotoRestAPI @@ -115,7 +115,7 @@ def get_lambda_function_arn_from_invocation_uri(uri: str) -> str: return uri.split("functions/")[1].removesuffix("/invocations") -def validate_sub_dict_of_typed_dict(typed_dict: Type[TypedDict], obj: dict) -> bool: +def validate_sub_dict_of_typed_dict(typed_dict: type[TypedDict], obj: dict) -> bool: """ Validate that the object is a subset off the keys of a given `TypedDict`. :param typed_dict: the `TypedDict` blueprint diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py index 5e65458ed4ac3..16a79b7e077af 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py @@ -3,7 +3,7 @@ import logging from functools import lru_cache from http import HTTPMethod -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict from urllib.parse import urlparse import requests @@ -51,11 +51,11 @@ class LambdaProxyResponse(TypedDict, total=False): - body: Optional[str] - statusCode: Optional[int | str] - headers: Optional[dict[str, str]] - isBase64Encoded: Optional[bool] - multiValueHeaders: Optional[dict[str, list[str]]] + body: str | None + statusCode: int | str | None + headers: dict[str, str] | None + isBase64Encoded: bool | None + multiValueHeaders: dict[str, list[str]] | None class LambdaInputEvent(TypedDict, total=False): @@ -211,6 +211,9 @@ def invoke(self, context: RestApiInvocationContext) -> EndpointResponse: action = parsed_uri["path"] if target := self.get_action_service_target(service_name, action): + # TODO: properly implement the auto-`Content-Type` headers depending on the service protocol + # e.g. `x-amz-json-1.0` for DynamoDB + # this is needed to properly support multi-protocol headers["X-Amz-Target"] = target query_params["Action"] = action diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py index fa0511072c9d1..6bc9d0f0fb163 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py @@ -1,6 +1,6 @@ import logging from http import HTTPMethod -from typing import Optional, TypedDict +from typing import TypedDict import requests from werkzeug.datastructures import Headers @@ -8,7 +8,7 @@ from localstack.aws.api.apigateway import Integration from ..context import EndpointResponse, IntegrationRequest, RestApiInvocationContext -from ..gateway_response import ApiConfigurationError, IntegrationFailureError +from ..gateway_response import ApiConfigurationError, IntegrationFailureError, InternalServerError from ..header_utils import build_multi_value_headers from .core import RestApiIntegration @@ -20,16 +20,16 @@ class SimpleHttpRequest(TypedDict, total=False): method: HTTPMethod | str url: str - params: Optional[dict[str, str | list[str]]] + params: dict[str, str | list[str]] | None data: bytes - headers: Optional[dict[str, str]] - cookies: Optional[dict[str, str]] - timeout: Optional[int] - allow_redirects: Optional[bool] - stream: Optional[bool] - verify: Optional[bool] + headers: dict[str, str] | None + cookies: dict[str, str] | None + timeout: int | None + allow_redirects: bool | None + stream: bool | None + verify: bool | None # TODO: check if there was a situation where we'd pass certs? - cert: Optional[str | tuple[str, str]] + cert: str | tuple[str, str] | None class BaseRestApiHttpIntegration(RestApiIntegration): @@ -72,7 +72,7 @@ def invoke(self, context: RestApiInvocationContext) -> EndpointResponse: except (requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema) as e: LOG.warning("Execution failed due to configuration error: Invalid endpoint address") LOG.debug("The URI specified for the HTTP/HTTP_PROXY integration is invalid: %s", uri) - raise ApiConfigurationError("Internal server error") from e + raise InternalServerError("Internal server error") from e except (requests.exceptions.Timeout, requests.exceptions.SSLError) as e: # TODO make the exception catching more fine grained @@ -127,7 +127,7 @@ def invoke(self, context: RestApiInvocationContext) -> EndpointResponse: except (requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema) as e: LOG.warning("Execution failed due to configuration error: Invalid endpoint address") LOG.debug("The URI specified for the HTTP/HTTP_PROXY integration is invalid: %s", uri) - raise ApiConfigurationError("Internal server error") from e + raise InternalServerError("Internal server error") from e except (requests.exceptions.Timeout, requests.exceptions.SSLError): # TODO make the exception catching more fine grained diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py index 6c0ca3245164b..9f670dcf12156 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py @@ -33,6 +33,7 @@ class RouteHostPathParameters(TypedDict, total=False): server: str | None stage: str | None vpce_suffix: str | None + vpce_dns: str | None class ApiGatewayEndpoint: @@ -144,6 +145,13 @@ def create_not_found_response(api_id: str) -> Response: ) return not_found + def vpc_endpoint_handler( + self, request: Request, **kwargs: Unpack[RouteHostPathParameters] + ) -> Response: + # TODO validate the vpc endpoint exists in the account/region before routing + kwargs["api_id"] = request.headers.get("x-apigw-api-id") + return self.__call__(request, **kwargs) + class ApiGatewayRouter: router: Router[Handler] @@ -158,6 +166,9 @@ def __init__(self, router: Router[Handler] = None, handler: ApiGatewayEndpoint = def register_routes(self) -> None: LOG.debug("Registering API Gateway routes.") host_pattern = ".execute-api." + vpce_host_pattern = ( + ".execute-api.vpce." + ) deprecated_route_endpoint = deprecated_endpoint( endpoint=self.handler, previous_path="/restapis///_user_request_", @@ -214,6 +225,26 @@ def register_routes(self) -> None: endpoint=self.handler, strict_slashes=True, ), + self.router.add( + path="/", + host=vpce_host_pattern, + endpoint=self.handler.vpc_endpoint_handler, + defaults={"path": "", "stage": None}, + strict_slashes=True, + ), + self.router.add( + path="//", + host=vpce_host_pattern, + endpoint=self.handler.vpc_endpoint_handler, + defaults={"path": ""}, + strict_slashes=False, + ), + self.router.add( + path="//", + host=vpce_host_pattern, + endpoint=self.handler.vpc_endpoint_handler, + strict_slashes=True, + ), ] for rule in rules: self.registered_rules.append(rule) diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/template_mapping.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/template_mapping.py index fd729f853d187..7d13695cc6a27 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/template_mapping.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/template_mapping.py @@ -109,7 +109,7 @@ class VTLJsonList(list): """ def __init__(self, *args): - super(VTLJsonList, self).__init__(*args) + super().__init__(*args) for idx, item in enumerate(self): self[idx] = cast_to_vtl_json_object(item) @@ -138,7 +138,7 @@ class AttributeDict(dict): """ def __init__(self, *args, **kwargs): - super(AttributeDict, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for key, value in self.items(): if isinstance(value, dict): self[key] = AttributeDict(value) diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/test_invoke.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/test_invoke.py index 0d871077aa707..b2a6f86ea892d 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/test_invoke.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/test_invoke.py @@ -1,4 +1,5 @@ import datetime +import logging from urllib.parse import parse_qs from rolo import Request @@ -22,6 +23,9 @@ ContextVarsResponseOverride, ) +LOG = logging.getLogger(__name__) + + # TODO: we probably need to write and populate those logs as part of the handler chain itself # and store it in the InvocationContext. That way, we could also retrieve in when calling TestInvoke @@ -45,6 +49,30 @@ {formatted_date} : Method completed with status: {method_response_status} """ +TEST_INVOKE_TEMPLATE_MOCK = """Execution log for request {request_id} +{formatted_date} : Starting execution for request: {request_id} +{formatted_date} : HTTP Method: {request_method}, Resource Path: {resource_path} +{formatted_date} : Method request path: {method_request_path_parameters} +{formatted_date} : Method request query string: {method_request_query_string} +{formatted_date} : Method request headers: {method_request_headers} +{formatted_date} : Method request body before transformations: {method_request_body} +{formatted_date} : Method response body after transformations: {method_response_body} +{formatted_date} : Method response headers: {method_response_headers} +{formatted_date} : Successfully completed execution +{formatted_date} : Method completed with status: {method_response_status} +""" + +TEST_INVOKE_TEMPLATE_FAILED = """Execution log for request {request_id} +{formatted_date} : Starting execution for request: {request_id} +{formatted_date} : HTTP Method: {request_method}, Resource Path: {resource_path} +{formatted_date} : Method request path: {method_request_path_parameters} +{formatted_date} : Method request query string: {method_request_query_string} +{formatted_date} : Method request headers: {method_request_headers} +{formatted_date} : Method request body before transformations: {method_request_body} +{formatted_date} : Execution failed due to {error_type}: {error_message} +{formatted_date} : Method completed with status: {method_response_status} +""" + def _dump_headers(headers: Headers) -> str: if not headers: @@ -63,9 +91,9 @@ def log_template(invocation_context: RestApiInvocationContext, response_headers: formatted_date = datetime.datetime.now(tz=datetime.UTC).strftime("%a %b %d %H:%M:%S %Z %Y") request = invocation_context.invocation_request context_var = invocation_context.context_variables - integration_req = invocation_context.integration_request - endpoint_resp = invocation_context.endpoint_response - method_resp = invocation_context.invocation_response + integration_req = invocation_context.integration_request or {} + endpoint_resp = invocation_context.endpoint_response or {} + method_resp = invocation_context.invocation_response or {} # TODO: if endpoint_uri is an ARN, it means it's an AWS_PROXY integration # this should be transformed to the true URL of a lambda invoke call endpoint_uri = integration_req.get("uri", "") @@ -93,6 +121,52 @@ def log_template(invocation_context: RestApiInvocationContext, response_headers: ) +def log_mock_template( + invocation_context: RestApiInvocationContext, response_headers: Headers +) -> str: + formatted_date = datetime.datetime.now(tz=datetime.UTC).strftime("%a %b %d %H:%M:%S %Z %Y") + request = invocation_context.invocation_request + context_var = invocation_context.context_variables + method_resp = invocation_context.invocation_response or {} + + return TEST_INVOKE_TEMPLATE_MOCK.format( + formatted_date=formatted_date, + request_id=context_var["requestId"], + resource_path=request["path"], + request_method=request["http_method"], + method_request_path_parameters=dict_to_string(request["path_parameters"]), + method_request_query_string=dict_to_string(request["query_string_parameters"]), + method_request_headers=_dump_headers(request.get("headers")), + method_request_body=to_str(request.get("body", "")), + method_response_status=method_resp.get("status_code"), + method_response_body=to_str(method_resp.get("body", "")), + method_response_headers=_dump_headers(response_headers), + ) + + +def log_failed_template( + invocation_context: RestApiInvocationContext, response_status_code: int +) -> str: + formatted_date = datetime.datetime.now(tz=datetime.UTC).strftime("%a %b %d %H:%M:%S %Z %Y") + request = invocation_context.invocation_request + context_var = invocation_context.context_variables + + return TEST_INVOKE_TEMPLATE_FAILED.format( + formatted_date=formatted_date, + request_id=context_var["requestId"], + resource_path=request["path"], + request_method=request["http_method"], + method_request_path_parameters=dict_to_string(request["path_parameters"]), + method_request_query_string=dict_to_string(request["query_string_parameters"]), + method_request_headers=_dump_headers(request.get("headers")), + method_request_body=to_str(request.get("body", "")), + method_response_status=response_status_code, + # TODO: fix the error message + error_type="", + error_message="", + ) + + def create_test_chain() -> HandlerChain[RestApiInvocationContext]: return HandlerChain( request_handlers=[ @@ -114,13 +188,16 @@ def create_test_invocation_context( ) -> RestApiInvocationContext: parse_handler = handlers.parse_request http_method = test_request["httpMethod"] + resource = deployment.rest_api.resources[test_request["resourceId"]] + resource_path = resource["path"] # we do not need a true HTTP request for the context, as we are skipping all the parsing steps and using the # provider data invocation_context = RestApiInvocationContext( request=Request(method=http_method), ) - path_query = test_request.get("pathWithQueryString", "/").split("?") + test_request_path = test_request.get("pathWithQueryString") or resource_path + path_query = test_request_path.split("?") path = path_query[0] multi_query_args: dict[str, list[str]] = {} @@ -140,9 +217,22 @@ def create_test_invocation_context( # TODO: handle multiValueHeaders body=to_bytes(test_request.get("body") or ""), ) + invocation_context.invocation_request = invocation_request + try: + # this is AWS behavior, it will accept any value for the `pathWithQueryString`, even if it doesn't match + # the expected format. It will just fall back to no value if it cannot parse the path parameters out of it + _, path_parameters = RestAPIResourceRouter(deployment).match(invocation_context) + except Exception as e: + LOG.warning( + "Error while trying to extract path parameters from user-provided 'pathWithQueryString=%s' " + "for the following resource path: '%s'. Error: '%s'", + path, + resource_path, + e, + ) + path_parameters = {} - _, path_parameters = RestAPIResourceRouter(deployment).match(invocation_context) invocation_request["path_parameters"] = path_parameters invocation_context.deployment = deployment @@ -160,8 +250,9 @@ def create_test_invocation_context( responseOverride=ContextVarsResponseOverride(header={}, status=0), ) invocation_context.trace_id = parse_handler.populate_trace_id({}) - resource = deployment.rest_api.resources[test_request["resourceId"]] - resource_method = resource["resourceMethods"][http_method] + resource_method = ( + resource["resourceMethods"].get(http_method) or resource["resourceMethods"]["ANY"] + ) invocation_context.resource = resource invocation_context.resource_method = resource_method invocation_context.integration = resource_method["methodIntegration"] @@ -179,8 +270,10 @@ def run_test_invocation( invocation_context = create_test_invocation_context(test_request, deployment) test_chain = create_test_chain() + is_mock_integration = invocation_context.integration["type"] == "MOCK" + # header order is important - if invocation_context.integration["type"] == "MOCK": + if is_mock_integration: base_headers = {"Content-Type": APPLICATION_JSON} else: # we manually add the trace-id, as it is normally added by handlers.response_enricher which adds to much data @@ -199,7 +292,19 @@ def run_test_invocation( # AWS does not return the Content-Length for TestInvokeMethod response_headers.remove("Content-Length") - log = log_template(invocation_context, response_headers) + if not invocation_context.invocation_response: + # TODO: this is an heuristic to guess if we encounter an exception in the call + # in the future, we should attach the exception to the context so we could act on it and properly + # log as we go through the invocation, so that if we have an error we stop logging at the right moment + for header in ("Content-Type", "X-Amzn-Trace-Id"): + response_headers.remove(header) + log = log_failed_template(invocation_context, test_response.status_code) + + elif is_mock_integration: + # TODO: revisit how we're building the logs + log = log_mock_template(invocation_context, response_headers) + else: + log = log_template(invocation_context, response_headers) headers = dict(response_headers) multi_value_headers = build_multi_value_headers(response_headers) diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py index e457c61180353..f51d6b6ab0b86 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py @@ -1,4 +1,4 @@ -from typing import Optional, TypedDict +from typing import TypedDict class ContextVarsAuthorizer(TypedDict, total=False): @@ -6,9 +6,9 @@ class ContextVarsAuthorizer(TypedDict, total=False): # format # https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html - claims: Optional[dict[str, str]] + claims: dict[str, str] | None """Claims returned from the Amazon Cognito user pool after the method caller is successfully authenticated""" - principalId: Optional[str] + principalId: str | None """The principal user identification associated with the token sent by the client and returned from an API Gateway Lambda authorizer""" @@ -29,38 +29,38 @@ class ContextVarsIdentityClientCert(TypedDict, total=False): class ContextVarsIdentity(TypedDict, total=False): # https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html - accountId: Optional[str] + accountId: str | None """The AWS account ID associated with the request.""" - accessKey: Optional[str] + accessKey: str | None """The AWS access key associated with the request.""" - apiKey: Optional[str] + apiKey: str | None """For API methods that require an API key, this variable is the API key associated with the method request.""" - apiKeyId: Optional[str] + apiKeyId: str | None """The API key ID associated with an API request that requires an API key.""" - caller: Optional[str] + caller: str | None """The principal identifier of the caller that signed the request. Supported for resources that use IAM authorization.""" - cognitoAuthenticationProvider: Optional[str] + cognitoAuthenticationProvider: str | None """A comma-separated list of the Amazon Cognito authentication providers used by the caller making the request""" - cognitoAuthenticationType: Optional[str] + cognitoAuthenticationType: str | None """The Amazon Cognito authentication type of the caller making the request""" - cognitoIdentityId: Optional[str] + cognitoIdentityId: str | None """The Amazon Cognito identity ID of the caller making the request""" - cognitoIdentityPoolId: Optional[str] + cognitoIdentityPoolId: str | None """The Amazon Cognito identity pool ID of the caller making the request""" - principalOrgId: Optional[str] + principalOrgId: str | None """The AWS organization ID.""" - sourceIp: Optional[str] + sourceIp: str | None """The source IP address of the immediate TCP connection making the request to the API Gateway endpoint""" clientCert: ContextVarsIdentityClientCert - vpcId: Optional[str] + vpcId: str | None """The VPC ID of the VPC making the request to the API Gateway endpoint.""" - vpceId: Optional[str] + vpceId: str | None """The VPC endpoint ID of the VPC endpoint making the request to the API Gateway endpoint.""" - user: Optional[str] + user: str | None """The principal identifier of the user that will be authorized against resource access for resources that use IAM authorization.""" - userAgent: Optional[str] + userAgent: str | None """The User-Agent header of the API caller.""" - userArn: Optional[str] + userArn: str | None """The Amazon Resource Name (ARN) of the effective user identified after authentication.""" @@ -95,9 +95,9 @@ class ContextVariables(TypedDict, total=False): """The API owner's AWS account ID.""" apiId: str """The identifier API Gateway assigns to your API.""" - authorizer: Optional[ContextVarsAuthorizer] + authorizer: ContextVarsAuthorizer | None """The principal user identification associated with the token.""" - awsEndpointRequestId: Optional[str] + awsEndpointRequestId: str | None """The AWS endpoint's request ID.""" deploymentId: str """The ID of the API deployment.""" @@ -111,8 +111,8 @@ class ContextVariables(TypedDict, total=False): """The extended ID that API Gateway generates and assigns to the API request. """ httpMethod: str """The HTTP method used""" - identity: Optional[ContextVarsIdentity] - isCanaryRequest: Optional[bool] + identity: ContextVarsIdentity | None + isCanaryRequest: bool | None """Indicates if the request was directed to the canary""" path: str """The request path.""" @@ -120,76 +120,76 @@ class ContextVariables(TypedDict, total=False): """The request protocol""" requestId: str """An ID for the request. Clients can override this request ID. """ - requestOverride: Optional[ContextVarsRequestOverride] + requestOverride: ContextVarsRequestOverride | None """Request override. Only exists for request mapping template""" requestTime: str """The CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm).""" requestTimeEpoch: int """The Epoch-formatted request time, in milliseconds.""" - resourceId: Optional[str] + resourceId: str | None """The identifier that API Gateway assigns to your resource.""" - resourcePath: Optional[str] + resourcePath: str | None """The path to your resource""" - responseOverride: Optional[ContextVarsResponseOverride] + responseOverride: ContextVarsResponseOverride | None """Response override. Only exists for response mapping template""" stage: str """The deployment stage of the API request """ - wafResponseCode: Optional[str] + wafResponseCode: str | None """The response received from AWS WAF: WAF_ALLOW or WAF_BLOCK. Will not be set if the stage is not associated with a web ACL""" - webaclArn: Optional[str] + webaclArn: str | None """The complete ARN of the web ACL that is used to decide whether to allow or block the request. Will not be set if the stage is not associated with a web ACL.""" class LoggingContextVarsAuthorize(TypedDict, total=False): - error: Optional[str] - latency: Optional[str] - status: Optional[str] + error: str | None + latency: str | None + status: str | None class LoggingContextVarsAuthorizer(TypedDict, total=False): - error: Optional[str] - integrationLatency: Optional[str] - integrationStatus: Optional[str] - latency: Optional[str] - requestId: Optional[str] - status: Optional[str] + error: str | None + integrationLatency: str | None + integrationStatus: str | None + latency: str | None + requestId: str | None + status: str | None class LoggingContextVarsAuthenticate(TypedDict, total=False): - error: Optional[str] - latency: Optional[str] - status: Optional[str] + error: str | None + latency: str | None + status: str | None class LoggingContextVarsCustomDomain(TypedDict, total=False): - basePathMatched: Optional[str] + basePathMatched: str | None class LoggingContextVarsIntegration(TypedDict, total=False): - error: Optional[str] - integrationStatus: Optional[str] - latency: Optional[str] - requestId: Optional[str] - status: Optional[str] + error: str | None + integrationStatus: str | None + latency: str | None + requestId: str | None + status: str | None class LoggingContextVarsWaf(TypedDict, total=False): - error: Optional[str] - latency: Optional[str] - status: Optional[str] + error: str | None + latency: str | None + status: str | None class LoggingContextVariables(TypedDict, total=False): - authorize: Optional[LoggingContextVarsAuthorize] - authorizer: Optional[LoggingContextVarsAuthorizer] - authenticate: Optional[LoggingContextVarsAuthenticate] - customDomain: Optional[LoggingContextVarsCustomDomain] - endpointType: Optional[str] - integration: Optional[LoggingContextVarsIntegration] - integrationLatency: Optional[str] - integrationStatus: Optional[str] - responseLatency: Optional[str] - responseLength: Optional[str] - status: Optional[str] - waf: Optional[LoggingContextVarsWaf] - xrayTraceId: Optional[str] + authorize: LoggingContextVarsAuthorize | None + authorizer: LoggingContextVarsAuthorizer | None + authenticate: LoggingContextVarsAuthenticate | None + customDomain: LoggingContextVarsCustomDomain | None + endpointType: str | None + integration: LoggingContextVarsIntegration | None + integrationLatency: str | None + integrationStatus: str | None + responseLatency: str | None + responseLength: str | None + status: str | None + waf: LoggingContextVarsWaf | None + xrayTraceId: str | None diff --git a/localstack-core/localstack/services/apigateway/next_gen/provider.py b/localstack-core/localstack/services/apigateway/next_gen/provider.py index 5153463c60a4c..9b87ea7cb8f15 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/provider.py +++ b/localstack-core/localstack/services/apigateway/next_gen/provider.py @@ -149,10 +149,17 @@ def update_stage( if is_canary := patch_path.startswith("/canarySettings"): skip_moto_apply = True path_valid = is_canary_settings_update_patch_valid(op=patch_op, path=patch_path) - # it seems our JSON Patch utility does not handle replace properly if the value does not exists before + # it seems our JSON Patch utility does not handle replace properly if the value does not exist before # it seems to maybe be a Stage-only thing, so replacing it here if patch_op == "replace": patch_operation["op"] = "add" + elif patch_path.startswith("/accessLogSettings"): + validate_access_log_settings_update_patch_valid( + op=patch_op, path=patch_path, value=patch_operation.get("value") + ) + # for AccessLogSettings, Moto does support its patching, but does not support `add`, so we replace it + if patch_op == "add": + patch_operation["op"] = "replace" if patch_op == "copy": copy_from = patch_operation.get("from") @@ -429,6 +436,11 @@ def test_invoke_method( if not resource: raise NotFoundException("Invalid Resource identifier specified") + resource_methods = resource.resource_methods + + if request["httpMethod"] not in resource_methods and "ANY" not in resource_methods: + raise NotFoundException("Invalid Method identifier specified") + # test httpMethod rest_api_container = get_rest_api_container(context, rest_api_id=rest_api_id) @@ -474,6 +486,26 @@ def is_canary_settings_update_patch_valid(op: str, path: str) -> bool: return True +def validate_access_log_settings_update_patch_valid(op: str, path: str, value: str | None) -> None: + # See https://docs.aws.amazon.com/apigateway/latest/api/patch-operations.html#UpdateStage-Patch + # not everything is right on the table, for example no path supports `remove` accept the root path + # TODO: validate destinationArn is a valid ARN (does not have to validate it exists) + valid_paths = ["/accessLogSettings/destinationArn", "/accessLogSettings/format"] + if op == "remove": + if path != "/accessLogSettings": + stripped_path = path.removeprefix("/") + raise BadRequestException( + f"Cannot remove method setting {stripped_path} because there is no method setting for this method " + ) + elif op in ("add", "replace"): + if path not in valid_paths: + raise BadRequestException( + "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]" + ) + if not value: + raise BadRequestException("Access Log value must not be empty") + + def _get_gateway_response_or_default( response_type: GatewayResponseType, gateway_responses: dict[GatewayResponseType, GatewayResponse], diff --git a/localstack-core/localstack/services/apigateway/patches.py b/localstack-core/localstack/services/apigateway/patches.py index ca12f96284fff..6f9839f76d520 100644 --- a/localstack-core/localstack/services/apigateway/patches.py +++ b/localstack-core/localstack/services/apigateway/patches.py @@ -4,9 +4,8 @@ from moto.apigateway import models as apigateway_models from moto.apigateway.exceptions import ( + BadRequestException, DeploymentNotFoundException, - NoIntegrationDefined, - RestAPINotFound, StageStillActive, ) from moto.apigateway.responses import APIGatewayResponse @@ -113,14 +112,6 @@ def _get_default_method_settings(fn, self): ) return result - # patch integration error responses - @patch(apigateway_models.Resource.get_integration) - def apigateway_models_resource_get_integration(fn, self, method_type): - resource_method = self.resource_methods.get(method_type, {}) - if not resource_method.method_integration: - raise NoIntegrationDefined() - return resource_method.method_integration - @patch(apigateway_models.RestAPI.to_dict) def apigateway_models_rest_api_to_dict(fn, self): resp = fn(self) @@ -178,19 +169,19 @@ def create_rest_api(fn, self, *args, tags=None, **kwargs): """ tags = tags or {} result = fn(self, *args, tags=tags, **kwargs) - # TODO: lower the custom_id when getting it from the tags, as AWS is case insensitive + if custom_id := tags.get(TAG_KEY_CUSTOM_ID): self.apis.pop(result.id) result.id = custom_id self.apis[custom_id] = result - return result - @patch(apigateway_models.APIGatewayBackend.get_rest_api, pass_target=False) - def get_rest_api(self, function_id): - for key in self.apis.keys(): - if key.lower() == function_id.lower(): - return self.apis[key] - raise RestAPINotFound() + if not (result.id.islower() or result.id.isnumeric()): + self.apis.pop(result.id) + raise BadRequestException( + f"The RestApiId '{result.id}' cannot contain uppercase characters" + ) + + return result @patch(apigateway_models.RestAPI.delete_deployment, pass_target=False) def patch_delete_deployment(self, deployment_id: str) -> apigateway_models.Deployment: diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py index 8c78925a5a8b8..40974b41688a9 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,8 +14,8 @@ class ApiGatewayAccountProperties(TypedDict): - CloudWatchRoleArn: Optional[str] - Id: Optional[str] + CloudWatchRoleArn: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py index d7dc5c91ce0d1..3fa69d04da2f1 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayAccountProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Account" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_account import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py index 1385cd6c5d01c..c1d99abec70be 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,25 +15,25 @@ class ApiGatewayApiKeyProperties(TypedDict): - APIKeyId: Optional[str] - CustomerId: Optional[str] - Description: Optional[str] - Enabled: Optional[bool] - GenerateDistinctId: Optional[bool] - Name: Optional[str] - StageKeys: Optional[list[StageKey]] - Tags: Optional[list[Tag]] - Value: Optional[str] + APIKeyId: str | None + CustomerId: str | None + Description: str | None + Enabled: bool | None + GenerateDistinctId: bool | None + Name: str | None + StageKeys: list[StageKey] | None + Tags: list[Tag] | None + Value: str | None class StageKey(TypedDict): - RestApiId: Optional[str] - StageName: Optional[str] + RestApiId: str | None + StageName: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py index 352ec19eec4d3..63243f3901f46 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayApiKeyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::ApiKey" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_apikey import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py index 51debd7811631..4fa7006911c50 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class ApiGatewayBasePathMappingProperties(TypedDict): - DomainName: Optional[str] - BasePath: Optional[str] - RestApiId: Optional[str] - Stage: Optional[str] + DomainName: str | None + BasePath: str | None + RestApiId: str | None + Stage: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -66,6 +66,7 @@ def create( } response = apigw.create_base_path_mapping(**params) model["RestApiId"] = response["restApiId"] + model["BasePath"] = response["basePath"] # TODO: validations return ProgressEvent( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py index 2dcb4b036e9ef..7e02201f7df56 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayBasePathMappingProviderPlugin(CloudFormationResourceProviderPlug name = "AWS::ApiGateway::BasePathMapping" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_basepathmapping import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py index 68bae12d2af24..293440f594802 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,69 +15,69 @@ class ApiGatewayDeploymentProperties(TypedDict): - RestApiId: Optional[str] - DeploymentCanarySettings: Optional[DeploymentCanarySettings] - DeploymentId: Optional[str] - Description: Optional[str] - StageDescription: Optional[StageDescription] - StageName: Optional[str] + RestApiId: str | None + DeploymentCanarySettings: DeploymentCanarySettings | None + DeploymentId: str | None + Description: str | None + StageDescription: StageDescription | None + StageName: str | None class DeploymentCanarySettings(TypedDict): - PercentTraffic: Optional[float] - StageVariableOverrides: Optional[dict] - UseStageCache: Optional[bool] + PercentTraffic: float | None + StageVariableOverrides: dict | None + UseStageCache: bool | None class AccessLogSetting(TypedDict): - DestinationArn: Optional[str] - Format: Optional[str] + DestinationArn: str | None + Format: str | None class CanarySetting(TypedDict): - PercentTraffic: Optional[float] - StageVariableOverrides: Optional[dict] - UseStageCache: Optional[bool] + PercentTraffic: float | None + StageVariableOverrides: dict | None + UseStageCache: bool | None class MethodSetting(TypedDict): - CacheDataEncrypted: Optional[bool] - CacheTtlInSeconds: Optional[int] - CachingEnabled: Optional[bool] - DataTraceEnabled: Optional[bool] - HttpMethod: Optional[str] - LoggingLevel: Optional[str] - MetricsEnabled: Optional[bool] - ResourcePath: Optional[str] - ThrottlingBurstLimit: Optional[int] - ThrottlingRateLimit: Optional[float] + CacheDataEncrypted: bool | None + CacheTtlInSeconds: int | None + CachingEnabled: bool | None + DataTraceEnabled: bool | None + HttpMethod: str | None + LoggingLevel: str | None + MetricsEnabled: bool | None + ResourcePath: str | None + ThrottlingBurstLimit: int | None + ThrottlingRateLimit: float | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class StageDescription(TypedDict): - AccessLogSetting: Optional[AccessLogSetting] - CacheClusterEnabled: Optional[bool] - CacheClusterSize: Optional[str] - CacheDataEncrypted: Optional[bool] - CacheTtlInSeconds: Optional[int] - CachingEnabled: Optional[bool] - CanarySetting: Optional[CanarySetting] - ClientCertificateId: Optional[str] - DataTraceEnabled: Optional[bool] - Description: Optional[str] - DocumentationVersion: Optional[str] - LoggingLevel: Optional[str] - MethodSettings: Optional[list[MethodSetting]] - MetricsEnabled: Optional[bool] - Tags: Optional[list[Tag]] - ThrottlingBurstLimit: Optional[int] - ThrottlingRateLimit: Optional[float] - TracingEnabled: Optional[bool] - Variables: Optional[dict] + AccessLogSetting: AccessLogSetting | None + CacheClusterEnabled: bool | None + CacheClusterSize: str | None + CacheDataEncrypted: bool | None + CacheTtlInSeconds: int | None + CachingEnabled: bool | None + CanarySetting: CanarySetting | None + ClientCertificateId: str | None + DataTraceEnabled: bool | None + Description: str | None + DocumentationVersion: str | None + LoggingLevel: str | None + MethodSettings: list[MethodSetting] | None + MetricsEnabled: bool | None + Tags: list[Tag] | None + ThrottlingBurstLimit: int | None + ThrottlingRateLimit: float | None + TracingEnabled: bool | None + Variables: dict | None REPEATED_INVOCATION = "repeated_invocation" @@ -126,6 +126,9 @@ def create( if model.get("Description"): params["description"] = model["Description"] + if model.get("DeploymentCanarySettings"): + params["canarySettings"] = model["DeploymentCanarySettings"] + response = api.create_deployment(**params) model["DeploymentId"] = response["id"] diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py index 80ff9801a1ed5..3efdea755c554 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayDeploymentProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Deployment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_deployment import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py index 778ec9da3cbf8..74f7be6e9602a 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,32 +15,32 @@ class ApiGatewayDomainNameProperties(TypedDict): - CertificateArn: Optional[str] - DistributionDomainName: Optional[str] - DistributionHostedZoneId: Optional[str] - DomainName: Optional[str] - EndpointConfiguration: Optional[EndpointConfiguration] - MutualTlsAuthentication: Optional[MutualTlsAuthentication] - OwnershipVerificationCertificateArn: Optional[str] - RegionalCertificateArn: Optional[str] - RegionalDomainName: Optional[str] - RegionalHostedZoneId: Optional[str] - SecurityPolicy: Optional[str] - Tags: Optional[list[Tag]] + CertificateArn: str | None + DistributionDomainName: str | None + DistributionHostedZoneId: str | None + DomainName: str | None + EndpointConfiguration: EndpointConfiguration | None + MutualTlsAuthentication: MutualTlsAuthentication | None + OwnershipVerificationCertificateArn: str | None + RegionalCertificateArn: str | None + RegionalDomainName: str | None + RegionalHostedZoneId: str | None + SecurityPolicy: str | None + Tags: list[Tag] | None class EndpointConfiguration(TypedDict): - Types: Optional[list[str]] + Types: list[str] | None class MutualTlsAuthentication(TypedDict): - TruststoreUri: Optional[str] - TruststoreVersion: Optional[str] + TruststoreUri: str | None + TruststoreVersion: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py index 49e6db22f12d8..ed8a86494b681 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayDomainNameProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::DomainName" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_domainname import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py index bb52d43256e7b..963a907be3d58 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,12 +15,12 @@ class ApiGatewayGatewayResponseProperties(TypedDict): - ResponseType: Optional[str] - RestApiId: Optional[str] - Id: Optional[str] - ResponseParameters: Optional[dict] - ResponseTemplates: Optional[dict] - StatusCode: Optional[str] + ResponseType: str | None + RestApiId: str | None + Id: str | None + ResponseParameters: dict | None + ResponseTemplates: dict | None + StatusCode: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py index 86f43d46cdd21..55113f1f23533 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayGatewayResponseProviderPlugin(CloudFormationResourceProviderPlug name = "AWS::ApiGateway::GatewayResponse" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_gatewayresponse import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py index 64598a4463898..752fbbde53fe4 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py @@ -3,7 +3,7 @@ from copy import deepcopy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,50 +15,50 @@ class ApiGatewayMethodProperties(TypedDict): - HttpMethod: Optional[str] - ResourceId: Optional[str] - RestApiId: Optional[str] - ApiKeyRequired: Optional[bool] - AuthorizationScopes: Optional[list[str]] - AuthorizationType: Optional[str] - AuthorizerId: Optional[str] - Integration: Optional[Integration] - MethodResponses: Optional[list[MethodResponse]] - OperationName: Optional[str] - RequestModels: Optional[dict] - RequestParameters: Optional[dict] - RequestValidatorId: Optional[str] + HttpMethod: str | None + ResourceId: str | None + RestApiId: str | None + ApiKeyRequired: bool | None + AuthorizationScopes: list[str] | None + AuthorizationType: str | None + AuthorizerId: str | None + Integration: Integration | None + MethodResponses: list[MethodResponse] | None + OperationName: str | None + RequestModels: dict | None + RequestParameters: dict | None + RequestValidatorId: str | None class IntegrationResponse(TypedDict): - StatusCode: Optional[str] - ContentHandling: Optional[str] - ResponseParameters: Optional[dict] - ResponseTemplates: Optional[dict] - SelectionPattern: Optional[str] + StatusCode: str | None + ContentHandling: str | None + ResponseParameters: dict | None + ResponseTemplates: dict | None + SelectionPattern: str | None class Integration(TypedDict): - Type: Optional[str] - CacheKeyParameters: Optional[list[str]] - CacheNamespace: Optional[str] - ConnectionId: Optional[str] - ConnectionType: Optional[str] - ContentHandling: Optional[str] - Credentials: Optional[str] - IntegrationHttpMethod: Optional[str] - IntegrationResponses: Optional[list[IntegrationResponse]] - PassthroughBehavior: Optional[str] - RequestParameters: Optional[dict] - RequestTemplates: Optional[dict] - TimeoutInMillis: Optional[int] - Uri: Optional[str] + Type: str | None + CacheKeyParameters: list[str] | None + CacheNamespace: str | None + ConnectionId: str | None + ConnectionType: str | None + ContentHandling: str | None + Credentials: str | None + IntegrationHttpMethod: str | None + IntegrationResponses: list[IntegrationResponse] | None + PassthroughBehavior: str | None + RequestParameters: dict | None + RequestTemplates: dict | None + TimeoutInMillis: int | None + Uri: str | None class MethodResponse(TypedDict): - StatusCode: Optional[str] - ResponseModels: Optional[dict] - ResponseParameters: Optional[dict] + StatusCode: str | None + ResponseModels: dict | None + ResponseParameters: dict | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py index 34e0cec7971a9..78fcefff25ad5 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayMethodProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Method" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_method import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py index 07883e62983ca..4a431fcef79fc 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,11 +15,11 @@ class ApiGatewayModelProperties(TypedDict): - RestApiId: Optional[str] - ContentType: Optional[str] - Description: Optional[str] - Name: Optional[str] - Schema: Optional[dict | str] + RestApiId: str | None + ContentType: str | None + Description: str | None + Name: str | None + Schema: dict | str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py index d1bd727b602e5..4396c861c7e30 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayModelProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Model" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_model import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py index 55d2a3bc4964e..03e0f7371100e 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,11 +14,11 @@ class ApiGatewayRequestValidatorProperties(TypedDict): - RestApiId: Optional[str] - Name: Optional[str] - RequestValidatorId: Optional[str] - ValidateRequestBody: Optional[bool] - ValidateRequestParameters: Optional[bool] + RestApiId: str | None + Name: str | None + RequestValidatorId: str | None + ValidateRequestBody: bool | None + ValidateRequestParameters: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py index 41175341a69de..7dc730bc55e1a 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayRequestValidatorProviderPlugin(CloudFormationResourceProviderPlu name = "AWS::ApiGateway::RequestValidator" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_requestvalidator import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py index 89b868306e68d..1d75418415e4c 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -17,10 +17,10 @@ class ApiGatewayResourceProperties(TypedDict): - ParentId: Optional[str] - PathPart: Optional[str] - RestApiId: Optional[str] - ResourceId: Optional[str] + ParentId: str | None + PathPart: str | None + RestApiId: str | None + ResourceId: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -72,7 +72,7 @@ def create( root_resource = ([r for r in resources if r["path"] == "/"] or [None])[0] if not root_resource: raise Exception( - "Unable to find root resource for REST API %s" % params["restApiId"] + "Unable to find root resource for REST API {}".format(params["restApiId"]) ) params["parentId"] = root_resource["id"] response = apigw.create_resource(**params) diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py index f7ece7204435d..8d37d2cad25ed 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayResourceProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Resource" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_resource import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py index c90e2b36f328b..9371d5acb09a9 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,40 +17,40 @@ class ApiGatewayRestApiProperties(TypedDict): - ApiKeySourceType: Optional[str] - BinaryMediaTypes: Optional[list[str]] - Body: Optional[dict | str] - BodyS3Location: Optional[S3Location] - CloneFrom: Optional[str] - Description: Optional[str] - DisableExecuteApiEndpoint: Optional[bool] - EndpointConfiguration: Optional[EndpointConfiguration] - FailOnWarnings: Optional[bool] - MinimumCompressionSize: Optional[int] - Mode: Optional[str] - Name: Optional[str] - Parameters: Optional[dict | str] - Policy: Optional[dict | str] - RestApiId: Optional[str] - RootResourceId: Optional[str] - Tags: Optional[list[Tag]] + ApiKeySourceType: str | None + BinaryMediaTypes: list[str] | None + Body: dict | str | None + BodyS3Location: S3Location | None + CloneFrom: str | None + Description: str | None + DisableExecuteApiEndpoint: bool | None + EndpointConfiguration: EndpointConfiguration | None + FailOnWarnings: bool | None + MinimumCompressionSize: int | None + Mode: str | None + Name: str | None + Parameters: dict | str | None + Policy: dict | str | None + RestApiId: str | None + RootResourceId: str | None + Tags: list[Tag] | None class S3Location(TypedDict): - Bucket: Optional[str] - ETag: Optional[str] - Key: Optional[str] - Version: Optional[str] + Bucket: str | None + ETag: str | None + Key: str | None + Version: str | None class EndpointConfiguration(TypedDict): - Types: Optional[list[str]] - VpcEndpointIds: Optional[list[str]] + Types: list[str] | None + VpcEndpointIds: list[str] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py index e53c4a4d8205f..044badf1b3426 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayRestApiProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::RestApi" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_restapi import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py index b2b98bc715455..df5f93623675e 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py @@ -3,7 +3,7 @@ import copy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,50 +16,50 @@ class ApiGatewayStageProperties(TypedDict): - RestApiId: Optional[str] - AccessLogSetting: Optional[AccessLogSetting] - CacheClusterEnabled: Optional[bool] - CacheClusterSize: Optional[str] - CanarySetting: Optional[CanarySetting] - ClientCertificateId: Optional[str] - DeploymentId: Optional[str] - Description: Optional[str] - DocumentationVersion: Optional[str] - MethodSettings: Optional[list[MethodSetting]] - StageName: Optional[str] - Tags: Optional[list[Tag]] - TracingEnabled: Optional[bool] - Variables: Optional[dict] + RestApiId: str | None + AccessLogSetting: AccessLogSetting | None + CacheClusterEnabled: bool | None + CacheClusterSize: str | None + CanarySetting: CanarySetting | None + ClientCertificateId: str | None + DeploymentId: str | None + Description: str | None + DocumentationVersion: str | None + MethodSettings: list[MethodSetting] | None + StageName: str | None + Tags: list[Tag] | None + TracingEnabled: bool | None + Variables: dict | None class AccessLogSetting(TypedDict): - DestinationArn: Optional[str] - Format: Optional[str] + DestinationArn: str | None + Format: str | None class CanarySetting(TypedDict): - DeploymentId: Optional[str] - PercentTraffic: Optional[float] - StageVariableOverrides: Optional[dict] - UseStageCache: Optional[bool] + DeploymentId: str | None + PercentTraffic: float | None + StageVariableOverrides: dict | None + UseStageCache: bool | None class MethodSetting(TypedDict): - CacheDataEncrypted: Optional[bool] - CacheTtlInSeconds: Optional[int] - CachingEnabled: Optional[bool] - DataTraceEnabled: Optional[bool] - HttpMethod: Optional[str] - LoggingLevel: Optional[str] - MetricsEnabled: Optional[bool] - ResourcePath: Optional[str] - ThrottlingBurstLimit: Optional[int] - ThrottlingRateLimit: Optional[float] + CacheDataEncrypted: bool | None + CacheTtlInSeconds: int | None + CachingEnabled: bool | None + DataTraceEnabled: bool | None + HttpMethod: str | None + LoggingLevel: str | None + MetricsEnabled: bool | None + ResourcePath: str | None + ThrottlingBurstLimit: int | None + ThrottlingRateLimit: float | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -102,7 +102,6 @@ def create( stage_variables = model.get("Variables") # we need to deep copy as several fields are nested dict and arrays params = keys_to_lower(copy.deepcopy(model)) - # TODO: add methodSettings # TODO: add custom CfN tags param_names = [ "restApiId", @@ -124,6 +123,23 @@ def create( result = apigw.create_stage(**params) model["StageName"] = result["stageName"] + # TODO: add methodSettings with the same principle + patch_operations = [] + if access_log_settings := model.get("AccessLogSetting"): + access_patch_ops = [ + {"op": "replace", "path": f"/accessLogSettings/{key}", "value": value} + for key, value in keys_to_lower(copy.deepcopy(access_log_settings)).items() + if value + ] + patch_operations.extend(access_patch_ops) + + if patch_operations: + apigw.update_stage( + restApiId=params["restApiId"], + stageName=stage_name, + patchOperations=patch_operations, + ) + return ProgressEvent( status=OperationStatus.SUCCESS, resource_model=model, diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py index e0898bae2c695..58a9c1236bec7 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayStageProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Stage" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_stage import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py index 1e10c9badfc3f..6ac6a0d40edc6 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -18,35 +18,35 @@ class ApiGatewayUsagePlanProperties(TypedDict): - ApiStages: Optional[list[ApiStage]] - Description: Optional[str] - Id: Optional[str] - Quota: Optional[QuotaSettings] - Tags: Optional[list[Tag]] - Throttle: Optional[ThrottleSettings] - UsagePlanName: Optional[str] + ApiStages: list[ApiStage] | None + Description: str | None + Id: str | None + Quota: QuotaSettings | None + Tags: list[Tag] | None + Throttle: ThrottleSettings | None + UsagePlanName: str | None class ApiStage(TypedDict): - ApiId: Optional[str] - Stage: Optional[str] - Throttle: Optional[dict] + ApiId: str | None + Stage: str | None + Throttle: dict | None class QuotaSettings(TypedDict): - Limit: Optional[int] - Offset: Optional[int] - Period: Optional[str] + Limit: int | None + Offset: int | None + Period: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ThrottleSettings(TypedDict): - BurstLimit: Optional[int] - RateLimit: Optional[float] + BurstLimit: int | None + RateLimit: float | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py index 154207ac69b58..531f507adf929 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayUsagePlanProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::UsagePlan" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_usageplan import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py index 33a6e155d5c4f..1ce7395510918 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,10 +15,10 @@ class ApiGatewayUsagePlanKeyProperties(TypedDict): - KeyId: Optional[str] - KeyType: Optional[str] - UsagePlanId: Optional[str] - Id: Optional[str] + KeyId: str | None + KeyType: str | None + UsagePlanId: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py index eb21b610bfc22..8bd408b166123 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayUsagePlanKeyProviderPlugin(CloudFormationResourceProviderPlugin) name = "AWS::ApiGateway::UsagePlanKey" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_usageplankey import ( diff --git a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py index 7e5eb5ca2f988..e2b221461ab36 100644 --- a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py +++ b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,7 +14,7 @@ class CDKMetadataProperties(TypedDict): - Id: Optional[str] + Id: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -83,8 +83,9 @@ def update( """ model = request.desired_state + result_model = {**model, "Id": request.previous_state["Id"]} return ProgressEvent( status=OperationStatus.SUCCESS, - resource_model=model, + resource_model=result_model, ) diff --git a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py index 924ca3cb79eae..e8d40a6c9e3e5 100644 --- a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py +++ b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaAliasProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CDK::Metadata" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cdk.resource_providers.cdk_metadata import CDKMetadataProvider diff --git a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py index d79d62975e87f..1ce68c51aab2d 100644 --- a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py +++ b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,25 +14,25 @@ class CertificateManagerCertificateProperties(TypedDict): - DomainName: Optional[str] - CertificateAuthorityArn: Optional[str] - CertificateTransparencyLoggingPreference: Optional[str] - DomainValidationOptions: Optional[list[DomainValidationOption]] - Id: Optional[str] - SubjectAlternativeNames: Optional[list[str]] - Tags: Optional[list[Tag]] - ValidationMethod: Optional[str] + DomainName: str | None + CertificateAuthorityArn: str | None + CertificateTransparencyLoggingPreference: str | None + DomainValidationOptions: list[DomainValidationOption] | None + Id: str | None + SubjectAlternativeNames: list[str] | None + Tags: list[Tag] | None + ValidationMethod: str | None class DomainValidationOption(TypedDict): - DomainName: Optional[str] - HostedZoneId: Optional[str] - ValidationDomain: Optional[str] + DomainName: str | None + HostedZoneId: str | None + ValidationDomain: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py index 5aae4de01c7b3..134a953c2dd21 100644 --- a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py +++ b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CertificateManagerCertificateProviderPlugin(CloudFormationResourceProvider name = "AWS::CertificateManager::Certificate" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.certificatemanager.resource_providers.aws_certificatemanager_certificate import ( diff --git a/localstack-core/localstack/services/cloudformation/analytics.py b/localstack-core/localstack/services/cloudformation/analytics.py index 80ec4d1960005..95bae2b357bf2 100644 --- a/localstack-core/localstack/services/cloudformation/analytics.py +++ b/localstack-core/localstack/services/cloudformation/analytics.py @@ -1,12 +1,17 @@ import enum +import traceback from typing import Self from localstack.aws.api.cloudformation import ChangeAction +from localstack.utils.analytics import log from localstack.utils.analytics.metrics import LabeledCounter COUNTER_NAMESPACE = "cloudformation" COUNTER_VERSION = 2 +FAILURE_ANALYTICS_NAMESPACE = "cfn_stack_deploy_failures" +FAILURE_ANALYTICS_VERSION = 1 + class ActionOptions(enum.StrEnum): """ @@ -65,3 +70,22 @@ def track_resource_operation( missing=missing, action=ActionOptions.from_action(action), ).increment() + + +def emit_stack_failure(reason: str, exception: Exception | None = None) -> None: + """ + Capture the stack failure reason in telemetry + """ + + payload = { + "reason": reason, + "version": FAILURE_ANALYTICS_VERSION, + } + if exception: + tb = "".join(traceback.format_exception(exception)) + payload["tb"] = tb + + log.event( + FAILURE_ANALYTICS_NAMESPACE, + payload, + ) diff --git a/localstack-core/localstack/services/cloudformation/api_utils.py b/localstack-core/localstack/services/cloudformation/api_utils.py index c4172974cec35..d02b12a25b3ef 100644 --- a/localstack-core/localstack/services/cloudformation/api_utils.py +++ b/localstack-core/localstack/services/cloudformation/api_utils.py @@ -77,9 +77,7 @@ def get_remote_template_body(url: str) -> str: result = client.get_object(Bucket=parts[0], Key=parts[2]) body = to_str(result["Body"].read()) return body - raise RuntimeError( - "Unable to fetch template body (code %s) from URL %s" % (status_code, url) - ) + raise RuntimeError(f"Unable to fetch template body (code {status_code}) from URL {url}") else: raise RuntimeError( f"Bad status code from fetching template from url '{url}' ({status_code})", @@ -112,11 +110,9 @@ def get_template_body(req_data: dict) -> str: result = client.get_object(Bucket=parts[0], Key=parts[2]) body = to_str(result["Body"].read()) return body - raise Exception( - "Unable to fetch template body (code %s) from URL %s" % (status_code, url) - ) + raise Exception(f"Unable to fetch template body (code {status_code}) from URL {url}") return to_str(response.content) - raise Exception("Unable to get template body from input: %s" % req_data) + raise Exception(f"Unable to get template body from input: {req_data}") def is_local_service_url(url: str) -> bool: @@ -127,7 +123,7 @@ def is_local_service_url(url: str) -> bool: constants.LOCALHOST_HOSTNAME, localstack_host().host, ) - if any(re.match(r"^[^:]+://[^:/]*%s([:/]|$)" % host, url) for host in candidates): + if any(re.match(rf"^[^:]+://[^:/]*{host}([:/]|$)", url) for host in candidates): return True host = url.split("://")[-1].split("/")[0] return "localhost" in host diff --git a/localstack-core/localstack/services/cloudformation/cfn_utils.py b/localstack-core/localstack/services/cloudformation/cfn_utils.py index 6fcc5d16fb573..20a0399393d38 100644 --- a/localstack-core/localstack/services/cloudformation/cfn_utils.py +++ b/localstack-core/localstack/services/cloudformation/cfn_utils.py @@ -1,5 +1,5 @@ import json -from typing import Callable +from collections.abc import Callable from localstack.utils.objects import recurse_object @@ -19,10 +19,8 @@ def do_rename(account_id, region_name, params, logical_resource_id, *args, **kwa def lambda_convert_types(func, types): - return ( - lambda account_id, region_name, params, logical_resource_id, *args, **kwargs: convert_types( - func(account_id, region_name, params, *args, **kwargs), types - ) + return lambda account_id, region_name, params, logical_resource_id, *args, **kwargs: ( + convert_types(func(account_id, region_name, params, *args, **kwargs), types) ) @@ -42,13 +40,8 @@ def recurse(o, path): return o func = func or (lambda account_id, region_name, x, logical_resource_id, *args, **kwargs: x) - return ( - lambda account_id, - region_name, - params, - logical_resource_id, - *args, - **kwargs: recurse_object( + return lambda account_id, region_name, params, logical_resource_id, *args, **kwargs: ( + recurse_object( func(account_id, region_name, params, logical_resource_id, *args, **kwargs), recurse ) ) @@ -59,7 +52,7 @@ def fix_types(key, type_class): def recurse(o, path): if isinstance(o, dict): for k, v in dict(o).items(): - key_path = "%s%s" % (path or ".", k) + key_path = "{}{}".format(path or ".", k) if key in [k, key_path]: o[k] = type_class(v) return o diff --git a/localstack-core/localstack/services/cloudformation/deploy.html b/localstack-core/localstack/services/cloudformation/deploy.html deleted file mode 100644 index 47af619288057..0000000000000 --- a/localstack-core/localstack/services/cloudformation/deploy.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - LocalStack - CloudFormation Deployment - - - - - - - - -
- - - diff --git a/localstack-core/localstack/services/cloudformation/deploy_ui.py b/localstack-core/localstack/services/cloudformation/deploy_ui.py deleted file mode 100644 index deac95b408b1f..0000000000000 --- a/localstack-core/localstack/services/cloudformation/deploy_ui.py +++ /dev/null @@ -1,47 +0,0 @@ -import json -import logging -import os - -import requests -from rolo import Response - -from localstack import constants -from localstack.utils.files import load_file -from localstack.utils.json import parse_json_or_yaml - -LOG = logging.getLogger(__name__) - - -class CloudFormationUi: - def on_get(self, request): - from localstack.utils.aws.aws_stack import get_valid_regions - - deploy_html_file = os.path.join( - constants.MODULE_MAIN_PATH, "services", "cloudformation", "deploy.html" - ) - deploy_html = load_file(deploy_html_file) - req_params = request.values - params = { - "stackName": "stack1", - "templateBody": "{}", - "errorMessage": "''", - "regions": json.dumps(sorted(get_valid_regions())), - } - - download_url = req_params.get("templateURL") - if download_url: - try: - LOG.debug("Attempting to download CloudFormation template URL: %s", download_url) - template_body = requests.get(download_url).text - template_body = parse_json_or_yaml(template_body) - params["templateBody"] = json.dumps(template_body) - except Exception as e: - msg = f"Unable to download CloudFormation template URL: {e}" - LOG.info(msg) - params["errorMessage"] = json.dumps(msg.replace("\n", " - ")) - - # using simple string replacement here, for simplicity (could be replaced with, e.g., jinja) - for key, value in params.items(): - deploy_html = deploy_html.replace(f"<{key}>", value) - - return Response(deploy_html, mimetype="text/html") diff --git a/localstack-core/localstack/services/cloudformation/deployment_utils.py b/localstack-core/localstack/services/cloudformation/deployment_utils.py index 6355db6b5c27a..fd25ec5dffc33 100644 --- a/localstack-core/localstack/services/cloudformation/deployment_utils.py +++ b/localstack-core/localstack/services/cloudformation/deployment_utils.py @@ -2,8 +2,8 @@ import json import logging import re +from collections.abc import Callable from copy import deepcopy -from typing import Callable, List from localstack import config from localstack.utils import common @@ -88,27 +88,17 @@ def do_replace(account_id: str, region_name: str, params, logical_resource_id, * return do_replace -def lambda_keys_to_lower(key=None, skip_children_of: List[str] = None): - return ( - lambda account_id, - region_name, - params, - logical_resource_id, - *args, - **kwargs: common.keys_to_lower( +def lambda_keys_to_lower(key=None, skip_children_of: list[str] = None): + return lambda account_id, region_name, params, logical_resource_id, *args, **kwargs: ( + common.keys_to_lower( obj=(params.get(key) if key else params), skip_children_of=skip_children_of ) ) def merge_parameters(func1, func2): - return ( - lambda account_id, - region_name, - properties, - logical_resource_id, - *args, - **kwargs: common.merge_dicts( + return lambda account_id, region_name, properties, logical_resource_id, *args, **kwargs: ( + common.merge_dicts( func1(account_id, region_name, properties, logical_resource_id, *args, **kwargs), func2(account_id, region_name, properties, logical_resource_id, *args, **kwargs), ) @@ -159,13 +149,8 @@ def lambda_select_params(*selected): def select_parameters(*param_names): - return ( - lambda account_id, - region_name, - properties, - logical_resource_id, - *args, - **kwargs: select_attributes(properties, param_names) + return lambda account_id, region_name, properties, logical_resource_id, *args, **kwargs: ( + select_attributes(properties, param_names) ) @@ -224,7 +209,7 @@ def fix_boto_parameters_based_on_report(original_params: dict, report: str) -> d cast_class = getattr(builtins, valid_class) old_value = get_nested(params, param_name) - if cast_class == bool and str(old_value).lower() in ["true", "false"]: + if isinstance(cast_class, bool) and str(old_value).lower() in ["true", "false"]: new_value = str(old_value).lower() == "true" else: new_value = cast_class(old_value) @@ -252,15 +237,17 @@ def convert_data_types(type_conversions: dict[str, Callable], params: dict) -> d attr_names = type_conversions.keys() or [] def cast(_obj, _type): - if _type == bool: - return _obj in ["True", "true", True] - if _type == str: - if isinstance(_obj, bool): - return str(_obj).lower() - return str(_obj) - if _type in (int, float): - return _type(_obj) - return _obj + match _type: + case builtins.bool: + return _obj in ["True", "true", True] + case builtins.str: + if isinstance(_obj, bool): + return str(_obj).lower() + return str(_obj) + case builtins.int | builtins.float: + return _type(_obj) + case _: + return _obj def fix_types(o, **kwargs): if isinstance(o, dict): diff --git a/localstack-core/localstack/services/cloudformation/engine/changes.py b/localstack-core/localstack/services/cloudformation/engine/changes.py index ae6ced9e5563e..2a3f1ed8347d0 100644 --- a/localstack-core/localstack/services/cloudformation/engine/changes.py +++ b/localstack-core/localstack/services/cloudformation/engine/changes.py @@ -1,4 +1,4 @@ -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict Action = str @@ -6,11 +6,11 @@ class ResourceChange(TypedDict): Action: Action LogicalResourceId: str - PhysicalResourceId: Optional[str] + PhysicalResourceId: str | None ResourceType: str Scope: list Details: list - Replacement: Optional[Literal["False"]] + Replacement: Literal["False"] | None class ChangeConfig(TypedDict): diff --git a/localstack-core/localstack/services/cloudformation/engine/entities.py b/localstack-core/localstack/services/cloudformation/engine/entities.py index e1498258694ee..633bf3be9b86d 100644 --- a/localstack-core/localstack/services/cloudformation/engine/entities.py +++ b/localstack-core/localstack/services/cloudformation/engine/entities.py @@ -1,5 +1,5 @@ import logging -from typing import Optional, TypedDict +from typing import TypedDict from localstack.aws.api.cloudformation import Capability, ChangeSetType, Parameter from localstack.services.cloudformation.engine.parameters import ( @@ -14,7 +14,13 @@ ) from localstack.utils.aws import arns from localstack.utils.collections import select_attributes -from localstack.utils.id_generator import ExistingIds, ResourceIdentifier, Tags, generate_short_uid +from localstack.utils.id_generator import ( + ExistingIds, + ResourceIdentifier, + Tags, + generate_short_uid, + generate_uid, +) from localstack.utils.json import clone_safe from localstack.utils.objects import recurse_object from localstack.utils.strings import long_uid, short_uid @@ -52,14 +58,14 @@ def __init__(self, metadata: dict): class CreateChangeSetInput(TypedDict): StackName: str Capabilities: list[Capability] - ChangeSetName: Optional[str] - ChangSetType: Optional[ChangeSetType] + ChangeSetName: str | None + ChangSetType: ChangeSetType | None Parameters: list[Parameter] class StackTemplate(TypedDict): StackName: str - ChangeSetName: Optional[str] + ChangeSetName: str | None Outputs: dict Resources: dict @@ -75,6 +81,11 @@ def generate(self, existing_ids: ExistingIds = None, tags: Tags = None) -> str: return generate_short_uid(resource_identifier=self, existing_ids=existing_ids, tags=tags) +class StackIdentifierV2(StackIdentifier): + def generate(self, existing_ids: ExistingIds = None, tags: Tags = None) -> str: + return generate_uid(resource_identifier=self, existing_ids=existing_ids, tags=tags) + + # TODO: remove metadata (flatten into individual fields) class Stack: change_sets: list["StackChangeSet"] @@ -83,9 +94,9 @@ def __init__( self, account_id: str, region_name: str, - metadata: Optional[CreateChangeSetInput] = None, - template: Optional[StackTemplate] = None, - template_body: Optional[str] = None, + metadata: CreateChangeSetInput | None = None, + template: StackTemplate | None = None, + template_body: str | None = None, ): self.account_id = account_id self.region_name = region_name @@ -93,7 +104,7 @@ def __init__( if template is None: template = {} - self.resolved_outputs = list() # TODO + self.resolved_outputs = [] # TODO self.resolved_parameters: dict[str, StackParameter] = {} self.resolved_conditions: dict[str, bool] = {} @@ -185,7 +196,7 @@ def describe_details(self): result.setdefault(attr, []) return result - def set_stack_status(self, status: str, status_reason: Optional[str] = None): + def set_stack_status(self, status: str, status_reason: str | None = None): self.metadata["StackStatus"] = status if "FAILED" in status: self.metadata["StackStatusReason"] = status_reason or "Deployment failed" @@ -360,8 +371,7 @@ def _lookup(self, resource_map, resource_id): resource = resource_map.get(resource_id) if not resource: raise Exception( - 'Unable to find details for resource "%s" in stack "%s"' - % (resource_id, self.stack_name) + f'Unable to find details for resource "{resource_id}" in stack "{self.stack_name}"' ) return resource @@ -393,7 +403,7 @@ def __init__( template = {} if params is None: params = {} - super(StackChangeSet, self).__init__(account_id, region_name, params, template) + super().__init__(account_id, region_name, params, template) name = self.metadata["ChangeSetName"] if not self.metadata.get("ChangeSetId"): @@ -428,10 +438,10 @@ def changes(self): # V2 only def populate_update_graph( self, - before_template: Optional[dict], - after_template: Optional[dict], - before_parameters: Optional[dict], - after_parameters: Optional[dict], + before_template: dict | None, + after_template: dict | None, + before_parameters: dict | None, + after_parameters: dict | None, ) -> None: change_set_model = ChangeSetModel( before_template=before_template, diff --git a/localstack-core/localstack/services/cloudformation/engine/parameters.py b/localstack-core/localstack/services/cloudformation/engine/parameters.py index ba39fafc40db2..024c5e4a54150 100644 --- a/localstack-core/localstack/services/cloudformation/engine/parameters.py +++ b/localstack-core/localstack/services/cloudformation/engine/parameters.py @@ -19,7 +19,7 @@ """ import logging -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict from botocore.exceptions import ClientError @@ -76,7 +76,7 @@ def resolve_parameters( :param old_parameters: The old parameters from the previous stack deployment, if available :return: a copy of new_parameters with resolved values """ - resolved_parameters = dict() + resolved_parameters = {} # populate values for every parameter declared in the template for pm in parameter_declarations.values(): @@ -179,8 +179,8 @@ def convert_stack_parameters_to_dict(in_params: list[Parameter] | None) -> dict[ class LegacyParameterProperties(TypedDict): Value: str ParameterType: str - ParameterValue: Optional[str] - ResolvedValue: Optional[str] + ParameterValue: str | None + ResolvedValue: str | None class LegacyParameter(TypedDict): diff --git a/localstack-core/localstack/services/cloudformation/engine/template_deployer.py b/localstack-core/localstack/services/cloudformation/engine/template_deployer.py index e3a0802c54bed..8ef7999f6faf8 100644 --- a/localstack-core/localstack/services/cloudformation/engine/template_deployer.py +++ b/localstack-core/localstack/services/cloudformation/engine/template_deployer.py @@ -4,7 +4,6 @@ import re import traceback import uuid -from typing import Optional from botocore.exceptions import ClientError @@ -86,7 +85,7 @@ def get_attr_from_model_instance( attribute_name: str, resource_type: str, resource_id: str, - attribute_sub_name: Optional[str] = None, + attribute_sub_name: str | None = None, ) -> str: if resource["PhysicalResourceId"] == MOCK_REFERENCE: LOG.warning( @@ -330,7 +329,7 @@ def _resolve_refs_recursively( account_id, region_name, stack_name, resources, parameters, value["Ref"] ) if ref is None: - msg = 'Unable to resolve Ref for resource "%s" (yet)' % value["Ref"] + msg = 'Unable to resolve Ref for resource "{}" (yet)'.format(value["Ref"]) LOG.debug("%s - %s", msg, resources.get(value["Ref"]) or set(resources.keys())) raise DependencyNotYetSatisfied(resource_ids=value["Ref"], message=msg) @@ -451,7 +450,7 @@ def _resolve_refs_recursively( raise DependencyNotYetSatisfied( resource_ids=key, message=f"Could not resolve {val} to terminal value type" ) - result = result.replace("${%s}" % key, str(resolved_val)) + result = result.replace(f"${{{key}}}", str(resolved_val)) # resolve placeholders result = resolve_placeholders_in_string( @@ -1029,7 +1028,7 @@ def init_resource_status(self, resources=None, stack=None, action="CREATE"): stack.set_resource_status(resource_id, f"{action}_IN_PROGRESS") def get_change_config( - self, action: str, resource: dict, change_set_id: Optional[str] = None + self, action: str, resource: dict, change_set_id: str | None = None ) -> ChangeConfig: result = ChangeConfig( **{ @@ -1105,10 +1104,10 @@ def construct_changes( existing_stack, new_stack, # TODO: remove initialize argument from here, and determine action based on resource status - initialize: Optional[bool] = False, + initialize: bool | None = False, change_set_id=None, - append_to_changeset: Optional[bool] = False, - filter_unchanged_resources: Optional[bool] = False, + append_to_changeset: bool | None = False, + filter_unchanged_resources: bool | None = False, ) -> list[ChangeConfig]: old_resources = existing_stack.template["Resources"] new_resources = new_stack.template["Resources"] @@ -1140,9 +1139,9 @@ def apply_changes( self, existing_stack: Stack, new_stack: StackChangeSet, - change_set_id: Optional[str] = None, - initialize: Optional[bool] = False, - action: Optional[str] = None, + change_set_id: str | None = None, + initialize: bool | None = False, + action: str | None = None, ): old_resources = existing_stack.template["Resources"] new_resources = new_stack.template["Resources"] @@ -1201,7 +1200,7 @@ def apply_changes_in_loop( self, changes: list[ChangeConfig], stack: Stack, - action: Optional[str] = None, + action: str | None = None, new_stack=None, ): def _run(*args): @@ -1480,10 +1479,11 @@ def delete_stack(self): # correct order yet. continue case OperationStatus.FAILED: - LOG.exception( + LOG.error( "Failed to delete resource with id %s. Reason: %s", resource_id, event.message or "unknown", + exc_info=LOG.isEnabledFor(logging.DEBUG), ) case OperationStatus.IN_PROGRESS: # the resource provider executor should not return this state, so @@ -1495,10 +1495,11 @@ def delete_stack(self): raise Exception(f"Use of unsupported status found: {other_status}") except Exception as e: - LOG.exception( + LOG.error( "Failed to delete resource with id %s. Final exception: %s", resource_id, e, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) # update status diff --git a/localstack-core/localstack/services/cloudformation/engine/template_preparer.py b/localstack-core/localstack/services/cloudformation/engine/template_preparer.py index 8206a7d6a99fc..db1265164ea6c 100644 --- a/localstack-core/localstack/services/cloudformation/engine/template_preparer.py +++ b/localstack-core/localstack/services/cloudformation/engine/template_preparer.py @@ -6,6 +6,7 @@ apply_global_transformations, apply_intrinsic_transformations, ) +from localstack.services.cloudformation.engine.validations import ValidationError from localstack.utils.json import clone_safe LOG = logging.getLogger(__name__) @@ -17,9 +18,12 @@ def parse_template(template: str) -> dict: except Exception: try: return clone_safe(yaml_parser.parse_yaml(template)) - except Exception as e: - LOG.debug("Unable to parse CloudFormation template (%s): %s", e, template) + except ValidationError: + # The error is handled in the yaml parsing helper raise + except Exception: + # TODO: present the user with a better error message including error location + raise ValidationError("Template format error: YAML not well-formed.") def template_to_json(template: str) -> str: diff --git a/localstack-core/localstack/services/cloudformation/engine/template_utils.py b/localstack-core/localstack/services/cloudformation/engine/template_utils.py index 062e4a3f1f840..c156e22e9cfcb 100644 --- a/localstack-core/localstack/services/cloudformation/engine/template_utils.py +++ b/localstack-core/localstack/services/cloudformation/engine/template_utils.py @@ -322,18 +322,40 @@ def resolve_condition( case "Fn::Select": index = v[0] options = v[1] - for i, option in enumerate(options): - if isinstance(option, dict): - options[i] = resolve_condition( - account_id, - region_name, - option, - conditions, - parameters, - mappings, - stack_name, - ) - return options[index] + + if isinstance(options, dict): + options = resolve_condition( + account_id, + region_name, + options, + conditions, + parameters, + mappings, + stack_name, + ) + + if isinstance(options, list): + for i, option in enumerate(options): + if isinstance(option, dict): + options[i] = resolve_condition( + account_id, + region_name, + option, + conditions, + parameters, + mappings, + stack_name, + ) + + return options[index] + + if index != 0: + raise Exception( + f"Template error: Fn::Select cannot select nonexistent value at index {index}" + ) + + return options + case "Fn::Sub": # we can assume anything in there is a ref if isinstance(v, str): diff --git a/localstack-core/localstack/services/cloudformation/engine/transformers.py b/localstack-core/localstack/services/cloudformation/engine/transformers.py index 1518750fb1bc7..d4c140fb2361e 100644 --- a/localstack-core/localstack/services/cloudformation/engine/transformers.py +++ b/localstack-core/localstack/services/cloudformation/engine/transformers.py @@ -3,9 +3,10 @@ import logging import os import re +from collections.abc import Callable from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable, Dict, Optional, Type, Union +from typing import Any import boto3 from botocore.exceptions import ClientError @@ -13,6 +14,7 @@ from localstack.aws.api import CommonServiceException from localstack.aws.connect import connect_to +from localstack.services.cloudformation.engine.parameters import StackParameter from localstack.services.cloudformation.engine.policy_loader import create_policy_loader from localstack.services.cloudformation.engine.template_deployer import resolve_refs_recursively from localstack.services.cloudformation.engine.validations import ValidationError @@ -27,7 +29,7 @@ EXTENSIONS_TRANSFORM = "AWS::LanguageExtensions" SECRETSMANAGER_TRANSFORM = "AWS::SecretsManager-2020-07-23" -TransformResult = Union[dict, str] +TransformResult = dict | str @dataclass @@ -38,7 +40,7 @@ class ResolveRefsRecursivelyContext: resources: dict mappings: dict conditions: dict - parameters: dict + parameters: dict[str, StackParameter] def resolve(self, value: Any) -> Any: return resolve_refs_recursively( @@ -83,7 +85,7 @@ def transform(self, account_id: str, region_name: str, parameters: dict) -> Tran # maps transformer names to implementing classes -transformers: Dict[str, Type] = {"AWS::Include": AwsIncludeTransformer} +transformers: dict[str, type] = {"AWS::Include": AwsIncludeTransformer} def apply_intrinsic_transformations( @@ -256,10 +258,11 @@ def execute_macro( formatted_stack_parameters = {} for key, value in stack_parameters.items(): # TODO: we want to support other types of parameters - if value.get("ParameterType") == "CommaDelimitedList": - formatted_stack_parameters[key] = value.get("ParameterValue").split(",") + parameter_value = value.get("ParameterValue") + if value.get("ParameterType") == "CommaDelimitedList" and isinstance(parameter_value, str): + formatted_stack_parameters[key] = parameter_value.split(",") else: - formatted_stack_parameters[key] = value.get("ParameterValue") + formatted_stack_parameters[key] = parameter_value transformation_id = f"{account_id}::{macro['Name']}" event = { @@ -449,7 +452,7 @@ def _visit(obj: Any, path: Any): def apply_serverless_transformation( account_id: str, region_name: str, parsed_template: dict, template_parameters: dict -) -> Optional[str]: +) -> str | None: """only returns string when parsing SAM template, otherwise None""" # TODO: we might also want to override the access key ID to account ID region_before = os.environ.get("AWS_DEFAULT_REGION") diff --git a/localstack-core/localstack/services/cloudformation/engine/types.py b/localstack-core/localstack/services/cloudformation/engine/types.py index 2a4f6efa06031..087c99766b751 100644 --- a/localstack-core/localstack/services/cloudformation/engine/types.py +++ b/localstack-core/localstack/services/cloudformation/engine/types.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional, TypedDict +from collections.abc import Callable +from typing import Any, TypedDict # --------------------- # TYPES @@ -25,16 +26,16 @@ class FuncDetailsValue(TypedDict): # - stack_name # - resources # - resource_id - parameters: Optional[ResourceDefinition | Callable[[dict, str, list[dict], str], dict]] + parameters: ResourceDefinition | Callable[[dict, str, list[dict], str], dict] | None """arguments to the function, or a function that generates the arguments to the function""" # Callable here takes the arguments # - result # - resource_id # - resources # - resource_type - result_handler: Optional[Callable[[dict, str, list[dict], str], None]] + result_handler: Callable[[dict, str, list[dict], str], None] | None """Take the result of the operation and patch the state of the resources, yuck...""" - types: Optional[dict[str, Callable]] + types: dict[str, Callable] | None """Possible type conversions""" diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py index ce0cd63f00912..ac7d2e11b47da 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py @@ -2,11 +2,17 @@ import abc import enum +from collections.abc import Generator from itertools import zip_longest -from typing import Any, Final, Generator, Optional, TypedDict, Union, cast - -from typing_extensions import TypeVar - +from typing import Any, Final, TypedDict, TypeVar, cast + +from localstack.aws.api.cloudformation import ChangeAction +from localstack.services.cloudformation.resource_provider import ResourceProviderExecutor +from localstack.services.cloudformation.v2.types import ( + EngineParameter, + engine_parameter_value, +) +from localstack.utils.json import extract_jsonpath from localstack.utils.strings import camel_to_snake_case T = TypeVar("T") @@ -42,7 +48,7 @@ def __contains__(self, item): return False -Maybe = Union[T, NothingType] +Maybe = T | NothingType Nothing = NothingType() @@ -62,6 +68,9 @@ def parent_change_type_of(children: list[Maybe[ChangeSetEntity]]): change_types = [c.change_type for c in children if not is_nothing(c)] if not change_types: return ChangeType.UNCHANGED + # TODO: rework this logic. Currently if any values are different then we consider it + # modified, but e.g. if everything is unchanged or created, the result should probably be + # "created" first_type = change_types[0] if all(ct == first_type for ct in change_types): return first_type @@ -84,7 +93,7 @@ class NormalisedGlobalTransformDefinition(TypedDict): class Scope(str): - _ROOT_SCOPE: Final[str] = str() + _ROOT_SCOPE: Final[str] = "" _SEPARATOR: Final[str] = "/" def __new__(cls, scope: str = _ROOT_SCOPE) -> Scope: @@ -99,6 +108,30 @@ def open_index(self, index: int) -> Scope: def unwrap(self) -> list[str]: return self.split(self._SEPARATOR) + @property + def parent(self) -> Scope: + return Scope(self._SEPARATOR.join(self.split(self._SEPARATOR)[:-1])) + + @property + def jsonpath(self) -> str: + parts = self.split("/") + json_parts = [] + + for part in parts: + if not part: # Skip empty strings from leading/trailing slashes + continue + + if part == "divergence": + continue + + # Wrap keys with special characters (e.g., colon) in quotes + if ":" in part: + json_parts.append(f'"{part}"') + else: + json_parts.append(part) + + return f"$.{'.'.join(json_parts)}" + class ChangeType(enum.Enum): UNCHANGED = "Unchanged" @@ -109,10 +142,18 @@ class ChangeType(enum.Enum): def __str__(self): return self.value + def to_change_action(self) -> ChangeAction: + # Convert this change type into the change action used throughout the CFn API + return { + ChangeType.CREATED: ChangeAction.Add, + ChangeType.MODIFIED: ChangeAction.Modify, + ChangeType.REMOVED: ChangeAction.Remove, + }.get(self, ChangeAction.Add) + class ChangeSetEntity(abc.ABC): scope: Final[Scope] - change_type: Final[ChangeType] + change_type: ChangeType def __init__(self, scope: Scope, change_type: ChangeType): self.scope = scope @@ -159,8 +200,8 @@ def __init__( node_template: NodeTemplate, ): self.node_template = node_template - self.before_runtime_cache = dict() - self.after_runtime_cache = dict() + self.before_runtime_cache = {} + self.after_runtime_cache = {} class NodeTemplate(ChangeSetNode): @@ -181,7 +222,9 @@ def __init__( resources: NodeResources, outputs: NodeOutputs, ): - change_type = parent_change_type_of([transform, resources, outputs]) + change_type = parent_change_type_of( + [transform, mappings, parameters, conditions, resources, outputs] + ) super().__init__(scope=scope, change_type=change_type) self.transform = transform self.mappings = mappings @@ -326,11 +369,21 @@ def __init__(self, scope: Scope, global_transforms: list[NodeGlobalTransform]): class NodeResources(ChangeSetNode): resources: Final[list[NodeResource]] + fn_transform: Final[Maybe[NodeIntrinsicFunctionFnTransform]] + fn_foreaches: Final[list[NodeForEach]] - def __init__(self, scope: Scope, resources: list[NodeResource]): - change_type = parent_change_type_of(resources) + def __init__( + self, + scope: Scope, + resources: list[NodeResource], + fn_transform: Maybe[NodeIntrinsicFunctionFnTransform], + fn_foreaches: list[NodeForEach], + ): + change_type = parent_change_type_of(resources + [fn_transform] + fn_foreaches) super().__init__(scope=scope, change_type=change_type) self.resources = resources + self.fn_transform = fn_transform + self.fn_foreaches = fn_foreaches class NodeResource(ChangeSetNode): @@ -339,6 +392,10 @@ class NodeResource(ChangeSetNode): properties: Final[NodeProperties] condition_reference: Final[Maybe[TerminalValue]] depends_on: Final[Maybe[NodeDependsOn]] + requires_replacement: Final[bool] + deletion_policy: Final[Maybe[ChangeSetTerminal]] + update_replace_policy: Final[Maybe[ChangeSetTerminal]] + fn_transform: Final[Maybe[NodeIntrinsicFunctionFnTransform]] def __init__( self, @@ -349,6 +406,10 @@ def __init__( properties: NodeProperties, condition_reference: Maybe[TerminalValue], depends_on: Maybe[NodeDependsOn], + requires_replacement: bool, + deletion_policy: Maybe[ChangeSetTerminal], + update_replace_policy: Maybe[ChangeSetTerminal], + fn_transform: Maybe[NodeIntrinsicFunctionFnTransform], ): super().__init__(scope=scope, change_type=change_type) self.name = name @@ -356,15 +417,26 @@ def __init__( self.properties = properties self.condition_reference = condition_reference self.depends_on = depends_on + self.requires_replacement = requires_replacement + self.deletion_policy = deletion_policy + self.update_replace_policy = update_replace_policy + self.fn_transform = fn_transform class NodeProperties(ChangeSetNode): properties: Final[list[NodeProperty]] + fn_transform: Final[Maybe[NodeIntrinsicFunctionFnTransform]] - def __init__(self, scope: Scope, properties: list[NodeProperty]): + def __init__( + self, + scope: Scope, + properties: list[NodeProperty], + fn_transform: Maybe[NodeIntrinsicFunctionFnTransform], + ): change_type = parent_change_type_of(properties) super().__init__(scope=scope, change_type=change_type) self.properties = properties + self.fn_transform = fn_transform class NodeDependsOn(ChangeSetNode): @@ -401,6 +473,40 @@ def __init__( self.arguments = arguments +class NodeIntrinsicFunctionFnTransform(NodeIntrinsicFunction): + def __init__( + self, + scope: Scope, + change_type: ChangeType, + intrinsic_function: str, + arguments: ChangeSetEntity, + before_siblings: list[Any], + after_siblings: list[Any], + ): + super().__init__( + scope=scope, + change_type=change_type, + intrinsic_function=intrinsic_function, + arguments=arguments, + ) + self.before_siblings = before_siblings + self.after_siblings = after_siblings + + +class NodeForEach(ChangeSetNode): + def __init__( + self, + scope: Scope, + change_type: Final[ChangeType], + arguments: Final[ChangeSetEntity], + ): + super().__init__( + scope=scope, + change_type=change_type, + ) + self.arguments = arguments + + class NodeObject(ChangeSetNode): bindings: Final[dict[str, ChangeSetEntity]] @@ -462,6 +568,8 @@ def __init__(self, scope: Scope, value: Any): ExportKey: Final[str] = "Export" OutputsKey: Final[str] = "Outputs" DependsOnKey: Final[str] = "DependsOn" +DeletionPolicyKey: Final[str] = "DeletionPolicy" +UpdateReplacePolicyKey: Final[str] = "UpdateReplacePolicy" # TODO: expand intrinsic functions set. RefKey: Final[str] = "Ref" RefConditionKey: Final[str] = "Condition" @@ -479,6 +587,7 @@ def __init__(self, scope: Scope, value: Any): FnSplit: Final[str] = "Fn::Split" FnGetAZs: Final[str] = "Fn::GetAZs" FnBase64: Final[str] = "Fn::Base64" +FnImportValue: Final[str] = "Fn::ImportValue" INTRINSIC_FUNCTIONS: Final[set[str]] = { RefKey, RefConditionKey, @@ -496,6 +605,7 @@ def __init__(self, scope: Scope, value: Any): FnSplit, FnGetAZs, FnBase64, + FnImportValue, } @@ -516,16 +626,17 @@ class ChangeSetModel: def __init__( self, - before_template: Optional[dict], - after_template: Optional[dict], - before_parameters: Optional[dict], - after_parameters: Optional[dict], + before_template: dict | None, + after_template: dict | None, + before_parameters: dict | None, + after_parameters: dict[str, EngineParameter] | None, ): self._before_template = before_template or Nothing self._after_template = after_template or Nothing self._before_parameters = before_parameters or Nothing self._after_parameters = after_parameters or Nothing - self._visited_scopes = dict() + self._visited_scopes = {} + # TODO: move this modeling process to the `get_update_model` method as constructors shouldn't do work self._node_template = self._model( before_template=self._before_template, after_template=self._after_template ) @@ -567,6 +678,7 @@ def _visit_intrinsic_function( arguments = self._visit_value( scope=arguments_scope, before_value=before_arguments, after_value=after_arguments ) + if is_created(before=before_arguments, after=after_arguments): change_type = ChangeType.CREATED elif is_removed(before=before_arguments, after=after_arguments): @@ -580,15 +692,47 @@ def _visit_intrinsic_function( change_type = resolve_function(arguments) else: change_type = arguments.change_type - node_intrinsic_function = NodeIntrinsicFunction( - scope=scope, - change_type=change_type, - intrinsic_function=intrinsic_function, - arguments=arguments, - ) + + if intrinsic_function == FnTransform: + if scope.count(FnTransform) > 1: + raise RuntimeError( + "Invalid: Fn::Transforms cannot be nested inside another Fn::Transform" + ) + + path = scope.parent.jsonpath + before_siblings = extract_jsonpath(self._before_template, path) + after_siblings = extract_jsonpath(self._after_template, path) + + node_intrinsic_function = NodeIntrinsicFunctionFnTransform( + scope=scope, + change_type=change_type, + arguments=arguments, + intrinsic_function=intrinsic_function, + before_siblings=before_siblings, + after_siblings=after_siblings, + ) + else: + node_intrinsic_function = NodeIntrinsicFunction( + scope=scope, + change_type=change_type, + intrinsic_function=intrinsic_function, + arguments=arguments, + ) self._visited_scopes[scope] = node_intrinsic_function return node_intrinsic_function + def _visit_foreach( + self, scope: Scope, before_arguments: Maybe[list], after_arguments: Maybe[list] + ) -> NodeForEach: + node_foreach = self._visited_scopes.get(scope) + if isinstance(node_foreach, NodeForEach): + return node_foreach + arguments_scope = scope.open_scope("args") + arguments = self._visit_array( + arguments_scope, before_array=before_arguments, after_array=after_arguments + ) + return NodeForEach(scope=scope, change_type=arguments.change_type, arguments=arguments) + def _resolve_intrinsic_function_fn_sub(self, arguments: ChangeSetEntity) -> ChangeType: # TODO: This routine should instead export the implicit Ref and GetAtt calls within the first # string template parameter and compute the respective change set types. Currently, @@ -636,6 +780,9 @@ def _resolve_intrinsic_function_ref(self, arguments: ChangeSetEntity) -> ChangeT logical_id = arguments.value + if isinstance(logical_id, str) and logical_id.startswith("AWS::"): + return arguments.change_type + node_condition = self._retrieve_condition_if_exists(condition_name=logical_id) if isinstance(node_condition, NodeCondition): return node_condition.change_type @@ -664,19 +811,24 @@ def _resolve_intrinsic_function_fn_find_in_map(self, arguments: ChangeSetEntity) if arguments.change_type != ChangeType.UNCHANGED: return arguments.change_type # TODO: validate arguments structure and type. - # TODO: add support for nested functions, here we assume the arguments are string literals. if not isinstance(arguments, NodeArray) or not arguments.array: raise RuntimeError() argument_mapping_name = arguments.array[0] - if not isinstance(argument_mapping_name, TerminalValue): - raise NotImplementedError() argument_top_level_key = arguments.array[1] - if not isinstance(argument_top_level_key, TerminalValue): - raise NotImplementedError() argument_second_level_key = arguments.array[2] - if not isinstance(argument_second_level_key, TerminalValue): - raise NotImplementedError() + + # If any argument is not a terminal value (e.g., it contains nested intrinsic functions + # like Ref, Fn::Sub, etc.), we cannot perform the static mapping lookup at this stage. + # Instead, return the parent change type based on all arguments' change states. + if any( + not isinstance(arg, TerminalValue) + for arg in [argument_mapping_name, argument_top_level_key, argument_second_level_key] + ): + return parent_change_type_of( + [argument_mapping_name, argument_top_level_key, argument_second_level_key] + ) + mapping_name = argument_mapping_name.value top_level_key = argument_top_level_key.value second_level_key = argument_second_level_key.value @@ -706,13 +858,38 @@ def _resolve_intrinsic_function_fn_if(self, arguments: ChangeSetEntity) -> Chang ) if not isinstance(node_condition, NodeCondition): raise RuntimeError() - change_type = parent_change_type_of([node_condition, *arguments[1:]]) + change_type = parent_change_type_of([node_condition, *arguments.array[1:]]) return change_type + def _resolve_requires_replacement( + self, node_properties: NodeProperties, resource_type: TerminalValue + ) -> bool: + # a bit hacky but we have to load the resource provider executor _and_ resource provider to get the schema + # Note: we don't log the attempt to load the resource provider, we need to make sure this is only done once and we already do this in the executor + + resource_provider = ResourceProviderExecutor.try_load_resource_provider(resource_type.value) + if not resource_provider: + # if we don't support a resource, assume an in-place update for simplicity + return False + + create_only_properties: list[str] = resource_provider.SCHEMA.get("createOnlyProperties", []) + # TODO: also hacky: strip the leading `/properties/` string from the definition + # ideally we should use a jsonpath or similar + create_only_properties = [ + property.replace("/properties/", "", 1) for property in create_only_properties + ] + for node_property in node_properties.properties: + if node_property.name not in create_only_properties: + continue + + if node_property.change_type != ChangeType.UNCHANGED: + return True + return False + def _visit_array( self, scope: Scope, before_array: Maybe[list], after_array: Maybe[list] ) -> NodeArray: - array: list[ChangeSetEntity] = list() + array: list[ChangeSetEntity] = [] for index, (before_value, after_value) in enumerate( zip_longest(before_array, after_array, fillvalue=Nothing) ): @@ -731,7 +908,7 @@ def _visit_object( if isinstance(node_object, NodeObject): return node_object binding_names = self._safe_keys_of(before_object, after_object) - bindings: dict[str, ChangeSetEntity] = dict() + bindings: dict[str, ChangeSetEntity] = {} for binding_name in binding_names: binding_scope, (before_value, after_value) = self._safe_access_in( scope, binding_name, before_object, after_object @@ -833,11 +1010,19 @@ def _visit_properties( if isinstance(node_properties, NodeProperties): return node_properties property_names: list[str] = self._safe_keys_of(before_properties, after_properties) - properties: list[NodeProperty] = list() + properties: list[NodeProperty] = [] + fn_transform = Nothing + for property_name in property_names: property_scope, (before_property, after_property) = self._safe_access_in( scope, property_name, before_properties, after_properties ) + if property_name == FnTransform: + fn_transform = self._visit_intrinsic_function( + property_scope, FnTransform, before_property, after_property + ) + continue + property_ = self._visit_property( scope=property_scope, property_name=property_name, @@ -845,7 +1030,10 @@ def _visit_properties( after_property=after_property, ) properties.append(property_) - node_properties = NodeProperties(scope=scope, properties=properties) + + node_properties = NodeProperties( + scope=scope, properties=properties, fn_transform=fn_transform + ) self._visited_scopes[scope] = node_properties return node_properties @@ -856,6 +1044,24 @@ def _visit_type(self, scope: Scope, before_type: Any, after_type: Any) -> Termin raise RuntimeError() return value + def _visit_deletion_policy( + self, scope: Scope, before_deletion_policy: Any, after_deletion_policy: Any + ) -> ChangeSetEntity: + value = self._visit_value( + scope=scope, before_value=before_deletion_policy, after_value=after_deletion_policy + ) + return value + + def _visit_update_replace_policy( + self, scope: Scope, before_update_replace_policy: Any, after_deletion_policy: Any + ) -> ChangeSetEntity: + value = self._visit_value( + scope=scope, + before_value=before_update_replace_policy, + after_value=after_deletion_policy, + ) + return value + def _visit_resource( self, scope: Scope, @@ -901,8 +1107,76 @@ def _visit_resource( after_properties=after_properties, ) + deletion_policy = Nothing + scope_deletion_policy, (before_deletion_policy, after_deletion_policy) = ( + self._safe_access_in(scope, DeletionPolicyKey, before_resource, after_resource) + ) + if before_deletion_policy or after_deletion_policy: + deletion_policy = self._visit_deletion_policy( + scope_deletion_policy, before_deletion_policy, after_deletion_policy + ) + + update_replace_policy = Nothing + scope_update_replace_policy, (before_update_replace_policy, after_update_replace_policy) = ( + self._safe_access_in(scope, UpdateReplacePolicyKey, before_resource, after_resource) + ) + if before_update_replace_policy or after_update_replace_policy: + update_replace_policy = self._visit_update_replace_policy( + scope_update_replace_policy, + before_update_replace_policy, + after_update_replace_policy, + ) + + fn_transform = Nothing + scope_fn_transform, (before_fn_transform_args, after_fn_transform_args) = ( + self._safe_access_in(scope, FnTransform, before_resource, after_resource) + ) + if not is_nothing(before_fn_transform_args) or not is_nothing(after_fn_transform_args): + if scope_fn_transform.count(FnTransform) > 1: + raise RuntimeError( + "Invalid: Fn::Transforms cannot be nested inside another Fn::Transform" + ) + path = "$" + ".".join(scope_fn_transform.split("/")[:-1]) + before_siblings = extract_jsonpath(self._before_template, path) + after_siblings = extract_jsonpath(self._after_template, path) + arguments_scope = scope.open_scope("args") + arguments = self._visit_value( + scope=arguments_scope, + before_value=before_fn_transform_args, + after_value=after_fn_transform_args, + ) + fn_transform = NodeIntrinsicFunctionFnTransform( + scope=scope_fn_transform, + change_type=ChangeType.MODIFIED, # TODO + arguments=arguments, # TODO + intrinsic_function=FnTransform, + before_siblings=before_siblings, + after_siblings=after_siblings, + ) + change_type = change_type_of( - before_resource, after_resource, [properties, condition_reference, depends_on] + before_resource, + after_resource, + [ + properties, + condition_reference, + depends_on, + deletion_policy, + update_replace_policy, + fn_transform, + ], + ) + + # special case of where either the before or after state does not specify properties but + # the resource was in the previous template + if ( + terminal_value_type.change_type == ChangeType.UNCHANGED + and properties.change_type != ChangeType.UNCHANGED + ): + change_type = ChangeType.MODIFIED + + requires_replacement = self._resolve_requires_replacement( + node_properties=properties, resource_type=terminal_value_type ) node_resource = NodeResource( scope=scope, @@ -912,6 +1186,10 @@ def _visit_resource( properties=properties, condition_reference=condition_reference, depends_on=depends_on, + requires_replacement=requires_replacement, + deletion_policy=deletion_policy, + update_replace_policy=update_replace_policy, + fn_transform=fn_transform, ) self._visited_scopes[scope] = node_resource return node_resource @@ -920,12 +1198,30 @@ def _visit_resources( self, scope: Scope, before_resources: Maybe[dict], after_resources: Maybe[dict] ) -> NodeResources: # TODO: investigate type changes behavior. - resources: list[NodeResource] = list() + resources: list[NodeResource] = [] resource_names = self._safe_keys_of(before_resources, after_resources) + fn_transform = Nothing + fn_foreaches = [] for resource_name in resource_names: resource_scope, (before_resource, after_resource) = self._safe_access_in( scope, resource_name, before_resources, after_resources ) + if resource_name == FnTransform: + fn_transform = self._visit_intrinsic_function( + scope=resource_scope, + intrinsic_function=resource_name, + before_arguments=before_resource, + after_arguments=after_resource, + ) + continue + elif resource_name.startswith("Fn::ForEach"): + fn_for_each = self._visit_foreach( + scope=resource_scope, + before_arguments=before_resource, + after_arguments=after_resource, + ) + fn_foreaches.append(fn_for_each) + continue resource = self._visit_resource( scope=resource_scope, resource_name=resource_name, @@ -933,7 +1229,12 @@ def _visit_resources( after_resource=after_resource, ) resources.append(resource) - return NodeResources(scope=scope, resources=resources) + return NodeResources( + scope=scope, + resources=resources, + fn_transform=fn_transform, + fn_foreaches=fn_foreaches, + ) def _visit_mapping( self, scope: Scope, name: str, before_mapping: Maybe[dict], after_mapping: Maybe[dict] @@ -946,7 +1247,7 @@ def _visit_mapping( def _visit_mappings( self, scope: Scope, before_mappings: Maybe[dict], after_mappings: Maybe[dict] ) -> NodeMappings: - mappings: list[NodeMapping] = list() + mappings: list[NodeMapping] = [] mapping_names = self._safe_keys_of(before_mappings, after_mappings) for mapping_name in mapping_names: scope_mapping, (before_mapping, after_mapping) = self._safe_access_in( @@ -963,9 +1264,22 @@ def _visit_mappings( def _visit_dynamic_parameter(self, parameter_name: str) -> ChangeSetEntity: scope = Scope("Dynamic").open_scope("Parameters") - scope_parameter, (before_parameter, after_parameter) = self._safe_access_in( + scope_parameter, (before_parameter_dct, after_parameter_dct) = self._safe_access_in( scope, parameter_name, self._before_parameters, self._after_parameters ) + + before_parameter = Nothing + if not is_nothing(before_parameter_dct): + before_parameter = before_parameter_dct.get("resolved_value") or engine_parameter_value( + before_parameter_dct + ) + + after_parameter = Nothing + if not is_nothing(after_parameter_dct): + after_parameter = after_parameter_dct.get("resolved_value") or engine_parameter_value( + after_parameter_dct + ) + parameter = self._visit_value( scope=scope_parameter, before_value=before_parameter, after_value=after_parameter ) @@ -1011,7 +1325,7 @@ def _visit_parameters( if isinstance(node_parameters, NodeParameters): return node_parameters parameter_names: list[str] = self._safe_keys_of(before_parameters, after_parameters) - parameters: list[NodeParameter] = list() + parameters: list[NodeParameter] = [] for parameter_name in parameter_names: parameter_scope, (before_parameter, after_parameter) = self._safe_access_in( scope, parameter_name, before_parameters, after_parameters @@ -1081,7 +1395,7 @@ def _visit_conditions( if isinstance(node_conditions, NodeConditions): return node_conditions condition_names: list[str] = self._safe_keys_of(before_conditions, after_conditions) - conditions: list[NodeCondition] = list() + conditions: list[NodeCondition] = [] for condition_name in condition_names: condition_scope, (before_condition, after_condition) = self._safe_access_in( scope, condition_name, before_conditions, after_conditions @@ -1133,7 +1447,7 @@ def _visit_output( def _visit_outputs( self, scope: Scope, before_outputs: Maybe[dict], after_outputs: Maybe[dict] ) -> NodeOutputs: - outputs: list[NodeOutput] = list() + outputs: list[NodeOutput] = [] output_names: list[str] = self._safe_keys_of(before_outputs, after_outputs) for output_name in output_names: scope_output, (before_output, after_output) = self._safe_access_in( @@ -1180,7 +1494,7 @@ def _normalise_transformer_value(value: Maybe[str | list[Any]]) -> Maybe[list[An elif isinstance(value, str): value = [NormalisedGlobalTransformDefinition(Name=value, Parameters=Nothing)] elif isinstance(value, list): - tmp_value = list() + tmp_value = [] for item in value: if isinstance(item, str): tmp_value.append( @@ -1204,7 +1518,7 @@ def _visit_transform( ) -> NodeTransform: before_transform_normalised = self._normalise_transformer_value(before_transform) after_transform_normalised = self._normalise_transformer_value(after_transform) - global_transforms = list() + global_transforms = [] for index, (before_global_transform, after_global_transform) in enumerate( zip_longest(before_transform_normalised, after_transform_normalised, fillvalue=Nothing) ): @@ -1285,8 +1599,8 @@ def _retrieve_condition_if_exists(self, condition_name: str) -> Maybe[NodeCondit conditions_scope, (before_conditions, after_conditions) = self._safe_access_in( Scope(), ConditionsKey, self._before_template, self._after_template ) - before_conditions = before_conditions or dict() - after_conditions = after_conditions or dict() + before_conditions = before_conditions or {} + after_conditions = after_conditions or {} if condition_name in before_conditions or condition_name in after_conditions: condition_scope, (before_condition, after_condition) = self._safe_access_in( conditions_scope, condition_name, before_conditions, after_conditions @@ -1356,7 +1670,7 @@ def _is_intrinsic_function_name(function_name: str) -> bool: @staticmethod def _safe_access_in(scope: Scope, key: str, *objects: Maybe[dict]) -> tuple[Scope, Maybe[Any]]: - results = list() + results = [] for obj in objects: if not isinstance(obj, (dict, NothingType)): raise RuntimeError(f"Invalid definition type at '{obj}'") @@ -1380,7 +1694,7 @@ def _safe_keys_of(*objects: Maybe[dict]) -> list[str]: return keys @staticmethod - def _name_if_intrinsic_function(value: Maybe[Any]) -> Optional[str]: + def _name_if_intrinsic_function(value: Maybe[Any]) -> str | None: if isinstance(value, dict): keys = ChangeSetModel._safe_keys_of(value) if len(keys) == 1: diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py index 14535d44a6f40..672cd619d395f 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py @@ -1,9 +1,10 @@ from __future__ import annotations import json -from typing import Final, Optional +from typing import Final import localstack.aws.api.cloudformation as cfn_api +from localstack.aws.api.cloudformation import Replacement from localstack.services.cloudformation.engine.v2.change_set_model import ( NodeIntrinsicFunction, NodeProperty, @@ -19,6 +20,7 @@ PreprocResource, ) from localstack.services.cloudformation.v2.entities import ChangeSet +from localstack.utils.numbers import is_number CHANGESET_KNOWN_AFTER_APPLY: Final[str] = "{{changeSet:KNOWN_AFTER_APPLY}}" @@ -34,7 +36,7 @@ def __init__( ): super().__init__(change_set=change_set) self._include_property_values = include_property_values - self._changes = list() + self._changes = [] def get_changes(self) -> cfn_api.Changes: self._changes.clear() @@ -75,7 +77,7 @@ def _resolve_attribute(self, arguments: str | list[str], select_before: bool) -> resource_name=logical_name_of_resource, node_template=self._change_set.update_model.node_template, ) - node_property: Optional[NodeProperty] = self._get_node_property_for( + node_property: NodeProperty | None = self._get_node_property_for( property_name=attribute_name, node_resource=node_resource ) if node_property is not None: @@ -95,11 +97,27 @@ def _resolve_attribute(self, arguments: str | list[str], select_before: bool) -> return value + def visit_node_intrinsic_function(self, node_intrinsic_function: NodeIntrinsicFunction): + """ + Intrinsic function results are always strings when referring to the describe output + """ + # TODO: what about other places? + # TODO: should this be put in the preproc? + delta = super().visit_node_intrinsic_function(node_intrinsic_function) + if is_number(delta.before): + delta.before = str(delta.before) + if is_number(delta.after): + delta.after = str(delta.after) + return delta + def visit_node_intrinsic_function_fn_join( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: - # TODO: investigate the behaviour and impact of this logic with the user defining - # {{changeSet:KNOWN_AFTER_APPLY}} string literals as delimiters or arguments. + delta_args = super().visit(node_intrinsic_function.arguments) + if isinstance(delta_args.after, list) and CHANGESET_KNOWN_AFTER_APPLY in delta_args.after: + delta_args.after = CHANGESET_KNOWN_AFTER_APPLY + return delta_args + delta = super().visit_node_intrinsic_function_fn_join( node_intrinsic_function=node_intrinsic_function ) @@ -111,13 +129,39 @@ def visit_node_intrinsic_function_fn_join( delta.after = CHANGESET_KNOWN_AFTER_APPLY return delta + def visit_node_intrinsic_function_fn_select( + self, node_intrinsic_function: NodeIntrinsicFunction + ): + # TODO: should this not _ALWAYS_ return CHANGESET_KNOWN_AFTER_APPLY? + arguments_delta = self.visit(node_intrinsic_function.arguments) + delta = PreprocEntityDelta() + if not is_nothing(arguments_delta.before): + idx = arguments_delta.before[0] + arr = arguments_delta.before[1] + try: + delta.before = arr[int(idx)] + except Exception: + delta.before = CHANGESET_KNOWN_AFTER_APPLY + + if not is_nothing(arguments_delta.after): + idx = arguments_delta.after[0] + arr = arguments_delta.after[1] + try: + delta.after = arr[int(idx)] + except Exception: + delta.after = CHANGESET_KNOWN_AFTER_APPLY + + return delta + def _register_resource_change( self, logical_id: str, type_: str, - physical_id: Optional[str], - before_properties: Optional[PreprocProperties], - after_properties: Optional[PreprocProperties], + physical_id: str | None, + before_properties: PreprocProperties | None, + after_properties: PreprocProperties | None, + # TODO: remove default + requires_replacement: bool = False, ) -> None: action = cfn_api.ChangeAction.Modify if before_properties is None: @@ -131,20 +175,29 @@ def _register_resource_change( resource_change["ResourceType"] = type_ if physical_id: resource_change["PhysicalResourceId"] = physical_id - if self._include_property_values and before_properties is not None: - before_context_properties = {PropertiesKey: before_properties.properties} - before_context_properties_json_str = json.dumps(before_context_properties) - resource_change["BeforeContext"] = before_context_properties_json_str - if self._include_property_values and after_properties is not None: - after_context_properties = {PropertiesKey: after_properties.properties} - after_context_properties_json_str = json.dumps(after_context_properties) - resource_change["AfterContext"] = after_context_properties_json_str + if self._include_property_values: + if before_properties is not None: + before_context_properties = {PropertiesKey: before_properties.properties} + before_context_properties_json_str = json.dumps(before_context_properties) + resource_change["BeforeContext"] = before_context_properties_json_str + + if after_properties is not None: + after_context_properties = {PropertiesKey: after_properties.properties} + after_context_properties_json_str = json.dumps(after_context_properties) + resource_change["AfterContext"] = after_context_properties_json_str + + if action == cfn_api.ChangeAction.Modify: + # TODO: handle "Conditional" case + resource_change["Replacement"] = ( + Replacement.True_ if requires_replacement else Replacement.False_ + ) + self._changes.append( cfn_api.Change(Type=cfn_api.ChangeType.Resource, ResourceChange=resource_change) ) def _describe_resource_change( - self, name: str, before: Optional[PreprocResource], after: Optional[PreprocResource] + self, name: str, before: PreprocResource | None, after: PreprocResource | None ) -> None: if before == after: # unchanged: nothing to do. @@ -159,6 +212,7 @@ def _describe_resource_change( type_=before.resource_type, before_properties=before.properties, after_properties=after.properties, + requires_replacement=after.requires_replacement, ) # Case: type migration. # TODO: Add test to assert that on type change the resources are replaced. @@ -213,3 +267,28 @@ def visit_node_resource( if not is_nothing(after_resource) and after_resource.physical_resource_id is None: after_resource.physical_resource_id = CHANGESET_KNOWN_AFTER_APPLY return delta + + def visit_node_intrinsic_function_fn_import_value( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + delta = super().visit_node_intrinsic_function_fn_import_value( + node_intrinsic_function=node_intrinsic_function + ) + after_value = delta.after + if is_nothing(after_value) and self._include_property_values: + # TODO find correct way to obtain parent resource + resource_name = node_intrinsic_function.scope.split("/")[2] + export_name = node_intrinsic_function.arguments.value + + self._change_set.status_reason = f"[WARN] --include-property-values option can return incomplete ChangeSet data because: ChangeSet creation failed for resource [{resource_name}] because: No export named {export_name}" + delta.after = CHANGESET_KNOWN_AFTER_APPLY + + return delta + + def visit_node_intrinsic_function_fn_split( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + delta = super().visit_node_intrinsic_function_fn_split(node_intrinsic_function) + if isinstance(delta.after, list) and ":".join(delta.after) == CHANGESET_KNOWN_AFTER_APPLY: + delta.after = [CHANGESET_KNOWN_AFTER_APPLY] + return delta diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py index e308512e74ddb..249a91db45182 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py @@ -1,96 +1,147 @@ import copy import logging +import os +import re import uuid +from collections.abc import Callable from dataclasses import dataclass -from typing import Final, Optional +from datetime import UTC, datetime +from typing import Final, Protocol, TypeVar from localstack import config from localstack.aws.api.cloudformation import ( ChangeAction, + Output, ResourceStatus, StackStatus, ) from localstack.constants import INTERNAL_AWS_SECRET_ACCESS_KEY -from localstack.services.cloudformation.analytics import track_resource_operation +from localstack.services.cloudformation.analytics import ( + emit_stack_failure, + track_resource_operation, +) from localstack.services.cloudformation.deployment_utils import log_not_available_message -from localstack.services.cloudformation.engine.parameters import resolve_ssm_parameter from localstack.services.cloudformation.engine.v2.change_set_model import ( NodeDependsOn, NodeOutput, - NodeParameter, NodeResource, is_nothing, ) from localstack.services.cloudformation.engine.v2.change_set_model_preproc import ( + _AWS_URL_SUFFIX, + MOCKED_REFERENCE, ChangeSetModelPreproc, + DeletionPolicy, PreprocEntityDelta, PreprocOutput, PreprocProperties, PreprocResource, + UpdateReplacePolicy, +) +from localstack.services.cloudformation.engine.v2.unsupported_resource import ( + should_ignore_unsupported_resource_type, ) from localstack.services.cloudformation.resource_provider import ( Credentials, - NoResourceProvider, OperationStatus, ProgressEvent, ResourceProviderExecutor, ResourceProviderPayload, ) -from localstack.services.cloudformation.v2.entities import ChangeSet +from localstack.services.cloudformation.v2.entities import ChangeSet, ResolvedResource LOG = logging.getLogger(__name__) EventOperationFromAction = {"Add": "CREATE", "Modify": "UPDATE", "Remove": "DELETE"} +REGEX_OUTPUT_APIGATEWAY = re.compile( + rf"^(https?://.+\.execute-api\.)(?:[^-]+-){{2,3}}\d\.(amazonaws\.com|{_AWS_URL_SUFFIX})/?(.*)$" +) + +_T = TypeVar("_T") + @dataclass class ChangeSetModelExecutorResult: - resources: dict - parameters: dict - outputs: dict + resources: dict[str, ResolvedResource] + outputs: list[Output] + failure_message: str | None = None + + +class DeferredAction(Protocol): + def __call__(self) -> None: ... + + +@dataclass +class Deferred: + name: str + action: DeferredAction + + +class TriggerRollback(Exception): + """ + Sentinel exception to signal that the deployment should be stopped for a reason + """ + + def __init__(self, logical_resource_id: str, reason: str | None): + self.logical_resource_id = logical_resource_id + self.reason = reason class ChangeSetModelExecutor(ChangeSetModelPreproc): # TODO: add typing for resolved resources and parameters. - resources: Final[dict] - outputs: Final[dict] - resolved_parameters: Final[dict] + resources: Final[dict[str, ResolvedResource]] + outputs: Final[list[Output]] + _deferred_actions: list[Deferred] def __init__(self, change_set: ChangeSet): super().__init__(change_set=change_set) - self.resources = dict() - self.outputs = dict() - self.resolved_parameters = dict() + self.resources = {} + self.outputs = [] + self._deferred_actions = [] + self.resource_provider_executor = ResourceProviderExecutor( + stack_name=change_set.stack.stack_name, + stack_id=change_set.stack.stack_id, + ) - # TODO: use a structured type for the return value def execute(self) -> ChangeSetModelExecutorResult: - self.process() + # constructive process + failure_message = None + try: + self.process() + except TriggerRollback as e: + failure_message = e.reason + except Exception as e: + failure_message = str(e) + + is_deletion = self._change_set.stack.status == StackStatus.DELETE_IN_PROGRESS + if self._deferred_actions: + if not is_deletion: + # TODO: correct status + # TODO: differentiate between update and create + self._change_set.stack.set_stack_status( + StackStatus.ROLLBACK_IN_PROGRESS + if failure_message + else StackStatus.UPDATE_COMPLETE_CLEANUP_IN_PROGRESS + ) + + # perform all deferred actions such as deletions. These must happen in reverse from their + # defined order so that resource dependencies are honoured + # TODO: errors will stop all rollbacks; get parity on this behaviour + for deferred in self._deferred_actions[::-1]: + LOG.debug("executing deferred action: '%s'", deferred.name) + deferred.action() + + if failure_message and not is_deletion: + # TODO: differentiate between update and create + self._change_set.stack.set_stack_status(StackStatus.ROLLBACK_COMPLETE) + return ChangeSetModelExecutorResult( - resources=self.resources, parameters=self.resolved_parameters, outputs=self.outputs + resources=self.resources, outputs=self.outputs, failure_message=failure_message ) - def visit_node_parameter(self, node_parameter: NodeParameter) -> PreprocEntityDelta: - delta = super().visit_node_parameter(node_parameter) - - # handle dynamic references, e.g. references to SSM parameters - # TODO: support more parameter types - parameter_type: str = node_parameter.type_.value - if parameter_type.startswith("AWS::SSM"): - if parameter_type in [ - "AWS::SSM::Parameter::Value", - "AWS::SSM::Parameter::Value", - "AWS::SSM::Parameter::Value", - ]: - delta.after = resolve_ssm_parameter( - account_id=self._change_set.account_id, - region_name=self._change_set.region_name, - stack_parameter_value=delta.after, - ) - else: - raise Exception(f"Unsupported stack parameter type: {parameter_type}") - - self.resolved_parameters[node_parameter.name] = delta.after - return delta + def _defer_action(self, name: str, action: DeferredAction): + self._deferred_actions.append(Deferred(name=name, action=action)) def _get_physical_id(self, logical_resource_id, strict: bool = True) -> str | None: physical_resource_id = None @@ -112,24 +163,32 @@ def _get_physical_id(self, logical_resource_id, strict: bool = True) -> str | No def _process_event( self, + *, action: ChangeAction, logical_resource_id, event_status: OperationStatus, + resource_type: str, special_action: str = None, reason: str = None, - resource_type=None, + custom_status: ResourceStatus | str | None = None, ): status_from_action = special_action or EventOperationFromAction[action.value] + + status: ResourceStatus if event_status == OperationStatus.SUCCESS: - status = f"{status_from_action}_COMPLETE" + status = ResourceStatus(f"{status_from_action}_COMPLETE") else: - status = f"{status_from_action}_{event_status.name}" + status = ResourceStatus(f"{status_from_action}_{event_status.name}") + if custom_status: + status = ResourceStatus(custom_status) + + physical_resource_id = self._get_physical_id(logical_resource_id, False) self._change_set.stack.set_resource_status( logical_resource_id=logical_resource_id, - physical_resource_id=self._get_physical_id(logical_resource_id, False), + physical_resource_id=physical_resource_id, resource_type=resource_type, - status=ResourceStatus(status), + status=status, resource_status_reason=reason, ) @@ -177,7 +236,24 @@ def visit_node_resource( Overrides the default preprocessing for NodeResource objects by annotating the `after` delta with the physical resource ID, if side effects resulted in an update. """ - delta = super().visit_node_resource(node_resource=node_resource) + try: + delta = super().visit_node_resource(node_resource=node_resource) + except Exception as e: + LOG.debug( + "preprocessing resource '%s' failed: %s", + node_resource.name, + e, + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, + ) + self._process_event( + action=node_resource.change_type.to_change_action(), + logical_resource_id=node_resource.name, + event_status=OperationStatus.FAILED, + resource_type=node_resource.type_.value, + reason=str(e), + ) + raise e + before = delta.before after = delta.after @@ -190,16 +266,30 @@ def visit_node_resource( # references or other downstream operations. if not is_nothing(before): before_logical_id = delta.before.logical_id - before_resource = self._before_resolved_resources.get(before_logical_id, dict()) + before_resource = self._before_resolved_resources.get(before_logical_id, {}) self.resources[before_logical_id] = before_resource # Update the latest version of this resource for downstream references. if not is_nothing(after): after_logical_id = after.logical_id - after_physical_id: str = self._after_resource_physical_id( - resource_logical_id=after_logical_id - ) - after.physical_resource_id = after_physical_id + resource = self.resources[after_logical_id] + resource_failed_to_deploy = resource["ResourceStatus"] in { + ResourceStatus.CREATE_FAILED, + ResourceStatus.UPDATE_FAILED, + } + if not resource_failed_to_deploy: + after_physical_id: str = self._after_resource_physical_id( + resource_logical_id=after_logical_id + ) + after.physical_resource_id = after_physical_id + after.status = resource["ResourceStatus"] + + # terminate the deployment process + if resource_failed_to_deploy: + raise TriggerRollback( + logical_resource_id=after_logical_id, + reason=resource.get("ResourceStatusReason"), + ) return delta def visit_node_output( @@ -209,11 +299,20 @@ def visit_node_output( after = delta.after if is_nothing(after) or (isinstance(after, PreprocOutput) and after.condition is False): return delta - self.outputs[delta.after.name] = delta.after.value + + output = Output( + OutputKey=delta.after.name, + OutputValue=delta.after.value, + # TODO + # Description=delta.after.description + ) + if after.export: + output["ExportName"] = after.export["Name"] + self.outputs.append(output) return delta def _execute_resource_change( - self, name: str, before: Optional[PreprocResource], after: Optional[PreprocResource] + self, name: str, before: PreprocResource | None, after: PreprocResource | None ) -> None: # Changes are to be made about this resource. # TODO: this logic is a POC and should be revised. @@ -224,21 +323,77 @@ def _execute_resource_change( # XXX hacky, stick the previous resources' properties into the payload before_properties = self._merge_before_properties(name, before) - self._process_event(ChangeAction.Modify, name, OperationStatus.IN_PROGRESS) - event = self._execute_resource_action( + self._process_event( action=ChangeAction.Modify, logical_resource_id=name, - resource_type=before.resource_type, - before_properties=before_properties, - after_properties=after.properties, - ) - self._process_event( - ChangeAction.Modify, - name, - event.status, - reason=event.message, + event_status=OperationStatus.IN_PROGRESS, resource_type=before.resource_type, ) + if after.requires_replacement: + event = self._execute_resource_action( + action=ChangeAction.Add, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=None, + after_properties=after.properties, + ) + self._process_event( + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + + def cleanup(): + # TODO: handle other update replace policy values + if after.update_replace_policy != UpdateReplacePolicy.Retain: + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=OperationStatus.IN_PROGRESS, + resource_type=before.resource_type, + ) + event = self._execute_resource_action( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=None, + part_of_replacement=True, + ) + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + else: + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=OperationStatus.SUCCESS, + resource_type=before.resource_type, + custom_status=ResourceStatus.DELETE_SKIPPED, + ) + + self._defer_action(f"cleanup-from-replacement-{name}", cleanup) + else: + event = self._execute_resource_action( + action=ChangeAction.Modify, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=after.properties, + ) + self._process_event( + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) # Case: type migration. # TODO: Add test to assert that on type change the resources are replaced. else: @@ -246,21 +401,24 @@ def _execute_resource_change( before_properties = self._merge_before_properties(name, before) # Register a Removed for the previous type. - event = self._execute_resource_action( - action=ChangeAction.Remove, - logical_resource_id=name, - resource_type=before.resource_type, - before_properties=before_properties, - after_properties=None, - ) - # Register a Create for the next type. - self._process_event( - ChangeAction.Modify, - name, - event.status, - reason=event.message, - resource_type=before.resource_type, - ) + def perform_deletion(): + event = self._execute_resource_action( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=None, + ) + self._process_event( + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + + self._defer_action(f"type-migration-{name}", perform_deletion) + event = self._execute_resource_action( action=ChangeAction.Add, logical_resource_id=name, @@ -269,43 +427,58 @@ def _execute_resource_change( after_properties=after.properties, ) self._process_event( - ChangeAction.Modify, - name, - event.status, - reason=event.message, + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, resource_type=before.resource_type, + reason=event.message, ) elif not is_nothing(before): # Case: removal # XXX hacky, stick the previous resources' properties into the payload # XXX hacky, stick the previous resources' properties into the payload before_properties = self._merge_before_properties(name, before) - self._process_event( - ChangeAction.Remove, - name, - OperationStatus.IN_PROGRESS, - resource_type=before.resource_type, - ) - event = self._execute_resource_action( - action=ChangeAction.Remove, - logical_resource_id=name, - resource_type=before.resource_type, - before_properties=before_properties, - after_properties=None, - ) - self._process_event( - ChangeAction.Remove, - name, - event.status, - reason=event.message, - resource_type=before.resource_type, - ) + + def perform_deletion(): + # TODO: other deletion policies + if before.deletion_policy != DeletionPolicy.Retain: + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + event_status=OperationStatus.IN_PROGRESS, + ) + event = self._execute_resource_action( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=None, + ) + + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + else: + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=OperationStatus.SUCCESS, + resource_type=before.resource_type, + custom_status=ResourceStatus.DELETE_SKIPPED, + ) + + self._defer_action(f"remove-{name}", perform_deletion) elif not is_nothing(after): # Case: addition self._process_event( - ChangeAction.Add, - name, - OperationStatus.IN_PROGRESS, + action=ChangeAction.Add, + logical_resource_id=name, + event_status=OperationStatus.IN_PROGRESS, resource_type=after.resource_type, ) event = self._execute_resource_action( @@ -316,11 +489,11 @@ def _execute_resource_change( after_properties=after.properties, ) self._process_event( - ChangeAction.Add, - name, - event.status, - reason=event.message, + action=ChangeAction.Add, + logical_resource_id=name, + event_status=event.status, resource_type=after.resource_type, + reason=event.message, ) def _merge_before_properties( @@ -339,13 +512,11 @@ def _execute_resource_action( action: ChangeAction, logical_resource_id: str, resource_type: str, - before_properties: Optional[PreprocProperties], - after_properties: Optional[PreprocProperties], + before_properties: PreprocProperties | None, + after_properties: PreprocProperties | None, + part_of_replacement: bool = False, ) -> ProgressEvent: LOG.debug("Executing resource action: %s for resource '%s'", action, logical_resource_id) - resource_provider_executor = ResourceProviderExecutor( - stack_name=self._change_set.stack.stack_name, stack_id=self._change_set.stack.stack_id - ) payload = self.create_resource_provider_payload( action=action, logical_resource_id=logical_resource_id, @@ -353,22 +524,15 @@ def _execute_resource_action( before_properties=before_properties, after_properties=after_properties, ) - resource_provider = resource_provider_executor.try_load_resource_provider(resource_type) + resource_provider = self.resource_provider_executor.try_load_resource_provider( + resource_type + ) track_resource_operation(action, resource_type, missing=resource_provider is not None) - if resource_provider is None: - log_not_available_message( - resource_type, - f'No resource provider found for "{resource_type}"', - ) - if not config.CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES: - raise NoResourceProvider extra_resource_properties = {} - event = ProgressEvent(OperationStatus.SUCCESS, resource_model={}) if resource_provider is not None: - # TODO: stack events try: - event = resource_provider_executor.deploy_loop( + event = self.resource_provider_executor.deploy_loop( resource_provider, extra_resource_properties, payload ) except Exception as e: @@ -376,26 +540,55 @@ def _execute_resource_action( LOG.warning( "Resource provider operation failed: '%s'", reason, - exc_info=LOG.isEnabledFor(logging.DEBUG), - ) - stack = self._change_set.stack - stack.set_resource_status( - logical_resource_id=logical_resource_id, - # TODO, - physical_resource_id="", - resource_type=resource_type, - status=ResourceStatus.CREATE_FAILED - if action == ChangeAction.Add - else ResourceStatus.UPDATE_FAILED, - resource_status_reason=reason, + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) event = ProgressEvent( OperationStatus.FAILED, resource_model={}, message=f"Resource provider operation failed: {reason}", + custom_context={"exception": e}, + ) + elif should_ignore_unsupported_resource_type( + resource_type=resource_type, change_set_type=self._change_set.change_set_type + ): + log_not_available_message( + resource_type, + f'No resource provider found for "{resource_type}"', + ) + if "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES" not in os.environ: + LOG.warning( + "Deployment of resource type %s succeeded, but will fail in upcoming LocalStack releases unless CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES is explicitly enabled.", + resource_type, ) + event = ProgressEvent( + OperationStatus.SUCCESS, + resource_model={}, + message=f"Resource type {resource_type} is not supported but was deployed as a fallback", + ) + else: + log_not_available_message( + resource_type, + f'No resource provider found for "{resource_type}"', + ) + event = ProgressEvent( + OperationStatus.FAILED, + resource_model={}, + message=f"Resource type {resource_type} not supported", + ) - self.resources.setdefault(logical_resource_id, {"Properties": {}}) + if part_of_replacement and action == ChangeAction.Remove: + # Early return as we don't want to update internal state of the executor if this is a + # cleanup of an old resource. The new resource has already been created and the state + # updated + return event + + status_from_action = EventOperationFromAction[action.value] + resolved_resource = ResolvedResource( + Properties=event.resource_model, + LogicalResourceId=logical_resource_id, + Type=resource_type, + LastUpdatedTimestamp=datetime.now(UTC), + ) match event.status: case OperationStatus.SUCCESS: # merge the resources state with the external state @@ -408,23 +601,35 @@ def _execute_resource_action( # TODO: avoid the use of setdefault (debuggability/readability) # TODO: review the use of merge - self.resources[logical_resource_id]["Properties"].update(event.resource_model) - self.resources[logical_resource_id].update(extra_resource_properties) - # XXX for legacy delete_stack compatibility - self.resources[logical_resource_id]["LogicalResourceId"] = logical_resource_id - self.resources[logical_resource_id]["Type"] = resource_type - - physical_resource_id = self._get_physical_id(logical_resource_id) - self.resources[logical_resource_id]["PhysicalResourceId"] = physical_resource_id - + # Don't update the resolved resources if we have deleted that resource + if action != ChangeAction.Remove: + physical_resource_id = ( + extra_resource_properties["PhysicalResourceId"] + if resource_provider + else MOCKED_REFERENCE + ) + resolved_resource["PhysicalResourceId"] = physical_resource_id + resolved_resource["ResourceStatus"] = ResourceStatus( + f"{status_from_action}_COMPLETE" + ) + # TODO: do we actually need this line? + resolved_resource.update(extra_resource_properties) case OperationStatus.FAILED: reason = event.message LOG.warning( "Resource provider operation failed: '%s'", reason, ) + resolved_resource["ResourceStatus"] = ResourceStatus(f"{status_from_action}_FAILED") + resolved_resource["ResourceStatusReason"] = reason + + exception = event.custom_context.get("exception") + emit_stack_failure(reason, exception=exception) + case other: raise NotImplementedError(f"Event status '{other}' not handled") + + self.resources[logical_resource_id] = resolved_resource return event def create_resource_provider_payload( @@ -432,9 +637,9 @@ def create_resource_provider_payload( action: ChangeAction, logical_resource_id: str, resource_type: str, - before_properties: Optional[PreprocProperties], - after_properties: Optional[PreprocProperties], - ) -> Optional[ResourceProviderPayload]: + before_properties: PreprocProperties | None, + after_properties: PreprocProperties | None, + ) -> ResourceProviderPayload | None: # FIXME: use proper credentials creds: Credentials = { "accessKeyId": self._change_set.stack.account_id, @@ -482,3 +687,11 @@ def create_resource_provider_payload( }, } return resource_provider_payload + + def _maybe_perform_on_delta( + self, delta: PreprocEntityDelta, f: Callable[[_T], _T] + ) -> PreprocEntityDelta: + # we only care about the after state + if isinstance(delta.after, str): + delta.after = f(delta.after) + return delta diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py index abaae139c741f..d82f9b4a84baa 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py @@ -3,17 +3,16 @@ import base64 import copy import re -from typing import Any, Callable, Final, Generic, Optional, TypeVar +from collections.abc import Callable +from enum import StrEnum +from typing import Any, Final from botocore.exceptions import ClientError +from localstack import config +from localstack.aws.api.cloudformation import ResourceStatus from localstack.aws.api.ec2 import AvailabilityZoneList, DescribeAvailabilityZonesResult from localstack.aws.connect import connect_to -from localstack.services.cloudformation.engine.transformers import ( - Transformer, - execute_macro, - transformers, -) from localstack.services.cloudformation.engine.v2.change_set_model import ( ChangeSetEntity, ChangeType, @@ -32,6 +31,7 @@ NodeProperties, NodeProperty, NodeResource, + NodeResources, NodeTemplate, Nothing, NothingType, @@ -46,9 +46,23 @@ from localstack.services.cloudformation.engine.v2.change_set_model_visitor import ( ChangeSetModelVisitor, ) -from localstack.services.cloudformation.stores import get_cloudformation_store +from localstack.services.cloudformation.engine.v2.resolving import ( + REGEX_DYNAMIC_REF, + extract_dynamic_reference, + perform_dynamic_reference_lookup, +) +from localstack.services.cloudformation.engine.v2.unsupported_resource import ( + should_ignore_unsupported_resource_type, +) +from localstack.services.cloudformation.engine.validations import ValidationError +from localstack.services.cloudformation.stores import ( + exports_map, +) from localstack.services.cloudformation.v2.entities import ChangeSet +from localstack.services.cloudformation.v2.types import ResolvedResource from localstack.utils.aws.arns import get_partition +from localstack.utils.numbers import to_number +from localstack.utils.objects import get_value_from_path from localstack.utils.run import to_str from localstack.utils.strings import to_bytes from localstack.utils.urls import localstack_host @@ -66,11 +80,16 @@ "AWS::NotificationARNs", } -TBefore = TypeVar("TBefore") -TAfter = TypeVar("TAfter") +REGEX_OUTPUT_APIGATEWAY = re.compile( + rf"^(https?://.+\.execute-api\.)(?:[^-]+-){{2,3}}\d\.(amazonaws\.com|{_AWS_URL_SUFFIX})/?(.*)$" +) +MOCKED_REFERENCE = "unknown" -class PreprocEntityDelta(Generic[TBefore, TAfter]): +VALID_LOGICAL_RESOURCE_ID_RE = re.compile(r"^[A-Za-z0-9]+$") + + +class PreprocEntityDelta[TBefore, TAfter]: before: Maybe[TBefore] after: Maybe[TAfter] @@ -96,22 +115,44 @@ def __eq__(self, other): return self.properties == other.properties +class DeletionPolicy(StrEnum): + Retain = "Retain" + Delete = "Delete" + RetainExceptOnCreate = "RetainExceptOnCreate" + Snapshot = "Snapshot" + + +class UpdateReplacePolicy(StrEnum): + Delete = "Delete" + Retain = "Retain" + Snapshot = "Snapshot" + + class PreprocResource: logical_id: str - physical_resource_id: Optional[str] - condition: Optional[bool] + physical_resource_id: str | None + condition: bool | None resource_type: str properties: PreprocProperties - depends_on: Optional[list[str]] + depends_on: list[str] | None + requires_replacement: bool + status: ResourceStatus | None + # TODO: typing + deletion_policy: DeletionPolicy | None + update_replace_policy: UpdateReplacePolicy | None def __init__( self, logical_id: str, physical_resource_id: str, - condition: Optional[bool], + condition: bool | None, resource_type: str, properties: PreprocProperties, - depends_on: Optional[list[str]], + depends_on: list[str] | None, + requires_replacement: bool, + status: ResourceStatus | None = None, + deletion_policy: str | None = None, + update_replace_policy: str | None = None, ): self.logical_id = logical_id self.physical_resource_id = physical_resource_id @@ -119,6 +160,10 @@ def __init__( self.resource_type = resource_type self.properties = properties self.depends_on = depends_on + self.requires_replacement = requires_replacement + self.status = status + self.deletion_policy = deletion_policy + self.update_replace_policy = update_replace_policy @staticmethod def _compare_conditions(c1: bool, c2: bool): @@ -143,10 +188,10 @@ def __eq__(self, other): class PreprocOutput: name: str value: Any - export: Optional[Any] - condition: Optional[bool] + export: Any | None + condition: bool | None - def __init__(self, name: str, value: Any, export: Optional[Any], condition: Optional[bool]): + def __init__(self, name: str, value: Any, export: Any | None, condition: bool | None): self.name = name self.value = value self.export = export @@ -174,8 +219,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor): def __init__(self, change_set: ChangeSet): self._change_set = change_set self._before_resolved_resources = change_set.stack.resolved_resources - self._before_cache = dict() - self._after_cache = dict() + self._before_cache = {} + self._after_cache = {} def _setup_runtime_cache(self) -> None: runtime_cache_key = self.__class__.__name__ @@ -203,6 +248,8 @@ def _save_runtime_cache(self) -> None: def process(self) -> None: self._setup_runtime_cache() node_template = self._change_set.update_model.node_template + node_conditions = self._change_set.update_model.node_template.conditions + self.visit(node_conditions) self.visit(node_template) self._save_runtime_cache() @@ -214,11 +261,13 @@ def _get_node_resource_for( if node_resource.name == resource_name: self.visit(node_resource) return node_resource - raise RuntimeError(f"No resource '{resource_name}' was found") + raise ValidationError( + f"Template format error: Unresolved resource dependencies [{resource_name}] in the Resources block of the template" + ) def _get_node_property_for( self, property_name: str, node_resource: NodeResource - ) -> Optional[NodeProperty]: + ) -> NodeProperty | None: # TODO: this could be improved with hashmap lookups if the Node contained bindings and not lists. for node_property in node_resource.properties.properties: if node_property.name == property_name: @@ -229,7 +278,8 @@ def _get_node_property_for( def _deployed_property_value_of( self, resource_logical_id: str, property_name: str, resolved_resources: dict ) -> Any: - # TODO: typing around resolved resources is needed and should be reflected here. + # We have to override this function to make sure it does not try to access the + # resolved resource # Before we can obtain deployed value for a resource, we need to first ensure to # process the resource if this wasn't processed already. Ideally, values should only @@ -244,12 +294,26 @@ def _deployed_property_value_of( raise RuntimeError( f"No deployed instances of resource '{resource_logical_id}' were found" ) - properties = resolved_resource.get("Properties", dict()) - property_value: Optional[Any] = properties.get(property_name) - if property_value is None: - raise RuntimeError( - f"No '{property_name}' found for deployed resource '{resource_logical_id}' was found" - ) + properties = resolved_resource.get("Properties", {}) + resource_type = resolved_resource.get("Type") + # TODO support structured properties, e.g. NestedStack.Outputs.OutputName + property_value: Any | None = get_value_from_path(properties, property_name) + + if property_value: + if not isinstance(property_value, (str, list, dict)): + # Str: Standard expected type. TODO validate bools and numbers + # List: Multiple resource types can return a list of values e.g. AWS::EC2::VPC. + # Dict: Custom resources in CloudFormation can return arbitrary data structures. + raise RuntimeError( + f"Accessing property '{property_name}' from '{resource_logical_id}' resulted in a non-string value nor list" + ) + return property_value + elif resource_type and should_ignore_unsupported_resource_type( + resource_type=resource_type, + change_set_type=self._change_set.change_set_type, + ): + return MOCKED_REFERENCE + return property_value def _before_deployed_property_value_of( @@ -263,7 +327,7 @@ def _before_deployed_property_value_of( def _after_deployed_property_value_of( self, resource_logical_id: str, property_name: str - ) -> Optional[str]: + ) -> str | None: return self._before_deployed_property_value_of( resource_logical_id=resource_logical_id, property_name=property_name ) @@ -353,8 +417,12 @@ def _resolve_mapping( node_mapping: NodeMapping = self._get_node_mapping(map_name=map_name) top_level_value = node_mapping.bindings.bindings.get(top_level_key) if not isinstance(top_level_value, NodeObject): - raise RuntimeError() + error_key = "::".join([map_name, top_level_key, second_level_key]) + raise ValidationError(f"Template error: Unable to get mapping for {error_key}") second_level_value = top_level_value.bindings.get(second_level_key) + if not isinstance(second_level_value, (TerminalValue, NodeArray, NodeObject)): + error_key = "::".join([map_name, top_level_key, second_level_key]) + raise ValidationError(f"Template error: Unable to get mapping for {error_key}") mapping_value_delta = self.visit(second_level_value) return mapping_value_delta @@ -366,10 +434,63 @@ def visit(self, change_set_entity: ChangeSetEntity) -> PreprocEntityDelta: return PreprocEntityDelta(before=before, after=after) delta = super().visit(change_set_entity=change_set_entity) if isinstance(delta, PreprocEntityDelta): + delta = self._maybe_perform_replacements(delta) self._before_cache[entity_scope] = delta.before self._after_cache[entity_scope] = delta.after return delta + def _maybe_perform_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta: + delta = self._maybe_perform_static_replacements(delta) + delta = self._maybe_perform_dynamic_replacements(delta) + return delta + + def _maybe_perform_static_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta: + return self._maybe_perform_on_delta(delta, self._perform_static_replacements) + + def _maybe_perform_dynamic_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta: + return self._maybe_perform_on_delta(delta, self._perform_dynamic_replacements) + + def _maybe_perform_on_delta[T]( + self, delta: PreprocEntityDelta | None, f: Callable[[T], T] + ) -> PreprocEntityDelta | None: + if isinstance(delta.before, str): + delta.before = f(delta.before) + if isinstance(delta.after, str): + delta.after = f(delta.after) + return delta + + def _perform_dynamic_replacements[T](self, value: T) -> T: + if not isinstance(value, str): + return value + + if dynamic_ref := extract_dynamic_reference(value): + new_value = perform_dynamic_reference_lookup( + reference=dynamic_ref, + account_id=self._change_set.account_id, + region_name=self._change_set.region_name, + ) + if new_value: + # We need to use a function here, to avoid backslash processing by regex. + # From the regex sub documentation: + # repl can be a string or a function; if it is a string, any backslash escapes in it are processed. + # Using a function, we can avoid this processing. + return REGEX_DYNAMIC_REF.sub(lambda _: new_value, value) + + return value + + @staticmethod + def _perform_static_replacements(value: str) -> str: + api_match = REGEX_OUTPUT_APIGATEWAY.match(value) + if api_match and value not in config.CFN_STRING_REPLACEMENT_DENY_LIST: + prefix = api_match[1] + host = api_match[2] + path = api_match[3] + port = localstack_host().port + value = f"{prefix}{host}:{port}/{path}" + return value + + return value + def _cached_apply( self, scope: Scope, arguments_delta: PreprocEntityDelta, resolver: Callable[[Any], Any] ) -> PreprocEntityDelta: @@ -418,6 +539,9 @@ def _cached_apply( return PreprocEntityDelta(before=before, after=after) + def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta: + return self.visit(node_property.value) + def visit_terminal_value_modified( self, terminal_value_modified: TerminalValueModified ) -> PreprocEntityDelta: @@ -451,15 +575,15 @@ def visit_node_divergence(self, node_divergence: NodeDivergence) -> PreprocEntit def visit_node_object(self, node_object: NodeObject) -> PreprocEntityDelta: node_change_type = node_object.change_type - before = dict() if node_change_type != ChangeType.CREATED else Nothing - after = dict() if node_change_type != ChangeType.REMOVED else Nothing + before = {} if node_change_type != ChangeType.CREATED else Nothing + after = {} if node_change_type != ChangeType.REMOVED else Nothing for name, change_set_entity in node_object.bindings.items(): delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity) delta_before = delta.before delta_after = delta.after - if not is_nothing(before) and not is_nothing(delta_before) and delta_before is not None: + if not is_nothing(before) and not is_nothing(delta_before): before[name] = delta_before - if not is_nothing(after) and not is_nothing(delta_after) and delta_after is not None: + if not is_nothing(after) and not is_nothing(delta_after): after[name] = delta_after return PreprocEntityDelta(before=before, after=after) @@ -470,33 +594,57 @@ def _resolve_attribute(self, arguments: str | list[str], select_before: bool) -> arguments_list = arguments.split(".") else: arguments_list = arguments + + if len(arguments_list) < 2: + raise ValidationError( + "Template error: every Fn::GetAtt object requires two non-empty parameters, the resource name and the resource attribute" + ) + logical_name_of_resource = arguments_list[0] - attribute_name = arguments_list[1] + attribute_name = ".".join(arguments_list[1:]) node_resource = self._get_node_resource_for( resource_name=logical_name_of_resource, node_template=self._change_set.update_model.node_template, ) - node_property: Optional[NodeProperty] = self._get_node_property_for( + + if not is_nothing(node_resource.condition_reference): + condition = self._get_node_condition_if_exists(node_resource.condition_reference.value) + evaluation_result = self._resolve_condition(condition.name) + + if select_before and not evaluation_result.before: + raise ValidationError( + f"Template format error: Unresolved resource dependencies [{logical_name_of_resource}] in the Resources block of the template" + ) + + if not select_before and not evaluation_result.after: + raise ValidationError( + f"Template format error: Unresolved resource dependencies [{logical_name_of_resource}] in the Resources block of the template" + ) + + # Custom Resources can mutate their definition + # So the preproc should search first in the resource values and then check the template + if select_before: + value = self._before_deployed_property_value_of( + resource_logical_id=logical_name_of_resource, + property_name=attribute_name, + ) + else: + value = self._after_deployed_property_value_of( + resource_logical_id=logical_name_of_resource, + property_name=attribute_name, + ) + if value is not None: + return value + + node_property: NodeProperty | None = self._get_node_property_for( property_name=attribute_name, node_resource=node_resource ) if node_property is not None: # The property is statically defined in the template and its value can be computed. property_delta = self.visit(node_property) value = property_delta.before if select_before else property_delta.after - else: - # The property is not statically defined and must therefore be available in - # the properties deployed set. - if select_before: - value = self._before_deployed_property_value_of( - resource_logical_id=logical_name_of_resource, - property_name=attribute_name, - ) - else: - value = self._after_deployed_property_value_of( - resource_logical_id=logical_name_of_resource, - property_name=attribute_name, - ) + return value def visit_node_intrinsic_function_fn_get_att( @@ -525,6 +673,12 @@ def _compute_fn_equals(args: list[Any]) -> bool: return args[0] == args[1] arguments_delta = self.visit(node_intrinsic_function.arguments) + + if isinstance(arguments_delta.after, list) and len(arguments_delta.after) != 2: + raise ValidationError( + "Template error: every Fn::Equals object requires a list of 2 string parameters." + ) + delta = self._cached_apply( scope=node_intrinsic_function.scope, arguments_delta=arguments_delta, @@ -535,21 +689,50 @@ def _compute_fn_equals(args: list[Any]) -> bool: def visit_node_intrinsic_function_fn_if( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: - def _compute_delta_for_if_statement(args: list[Any]) -> PreprocEntityDelta: - condition_name = args[0] - boolean_expression_delta = self._resolve_condition(logical_id=condition_name) - return PreprocEntityDelta( - before=args[1] if boolean_expression_delta.before else args[2], - after=args[1] if boolean_expression_delta.after else args[2], + # `if` needs to be short-circuiting i.e. if the condition is True we don't evaluate the + # False branch. If the condition is False, we don't evaluate the True branch. + if len(node_intrinsic_function.arguments.array) != 3: + raise ValueError( + f"Incorrectly constructed Fn::If usage, expected 3 arguments, found {len(node_intrinsic_function.arguments.array)}" ) - arguments_delta = self.visit(node_intrinsic_function.arguments) - delta = self._cached_apply( - scope=node_intrinsic_function.scope, - arguments_delta=arguments_delta, - resolver=_compute_delta_for_if_statement, - ) - return delta + condition_delta = self.visit(node_intrinsic_function.arguments.array[0]) + if_delta = PreprocEntityDelta() + if not is_nothing(condition_delta.before): + node_condition = self._get_node_condition_if_exists( + condition_name=condition_delta.before + ) + if is_nothing(node_condition): + # TODO: I don't think this is a possible state since for us to be evaluating the before state, + # we must have successfully deployed the stack and as such this case was not reached before + raise ValidationError( + f"Template error: unresolved condition dependency {condition_delta.before} in Fn::If" + ) + + condition_value = self.visit(node_condition).before + if condition_value: + arg_delta = self.visit(node_intrinsic_function.arguments.array[1]) + else: + arg_delta = self.visit(node_intrinsic_function.arguments.array[2]) + if_delta.before = arg_delta.before + + if not is_nothing(condition_delta.after): + node_condition = self._get_node_condition_if_exists( + condition_name=condition_delta.after + ) + if is_nothing(node_condition): + raise ValidationError( + f"Template error: unresolved condition dependency {condition_delta.after} in Fn::If" + ) + + condition_value = self.visit(node_condition).after + if condition_value: + arg_delta = self.visit(node_intrinsic_function.arguments.array[1]) + else: + arg_delta = self.visit(node_intrinsic_function.arguments.array[2]) + if_delta.after = arg_delta.after + + return if_delta def visit_node_intrinsic_function_fn_and( self, node_intrinsic_function: NodeIntrinsicFunction @@ -584,8 +767,12 @@ def _compute_fn_or(args: list[bool]): def visit_node_intrinsic_function_fn_not( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: - def _compute_fn_not(arg: bool) -> bool: - return not arg + def _compute_fn_not(arg: list[bool] | bool) -> bool: + # Is the argument ever a lone boolean? + if isinstance(arg, list): + return not arg[0] + else: + return not arg arguments_delta = self.visit(node_intrinsic_function.arguments) delta = self._cached_apply( @@ -595,65 +782,6 @@ def _compute_fn_not(arg: bool) -> bool: ) return delta - def _compute_fn_transform(self, args: dict[str, Any]) -> Any: - # TODO: add typing to arguments before this level. - # TODO: add schema validation - # TODO: add support for other transform types - - account_id = self._change_set.account_id - region_name = self._change_set.region_name - transform_name: str = args.get("Name") - if not isinstance(transform_name, str): - raise RuntimeError("Invalid or missing Fn::Transform 'Name' argument") - transform_parameters: dict = args.get("Parameters") - if not isinstance(transform_parameters, dict): - raise RuntimeError("Invalid or missing Fn::Transform 'Parameters' argument") - - if transform_name in transformers: - # TODO: port and refactor this 'transformers' logic to this package. - builtin_transformer_class = transformers[transform_name] - builtin_transformer: Transformer = builtin_transformer_class() - transform_output: Any = builtin_transformer.transform( - account_id=account_id, region_name=region_name, parameters=transform_parameters - ) - return transform_output - - macros_store = get_cloudformation_store( - account_id=account_id, region_name=region_name - ).macros - if transform_name in macros_store: - # TODO: this formatting of stack parameters is odd but required to integrate with v1 execute_macro util. - # consider porting this utils and passing the plain list of parameters instead. - stack_parameters = { - parameter["ParameterKey"]: parameter - for parameter in self._change_set.stack.parameters - } - transform_output: Any = execute_macro( - account_id=account_id, - region_name=region_name, - parsed_template=dict(), # TODO: review the requirements for this argument. - macro=args, # TODO: review support for non dict bindings (v1). - stack_parameters=stack_parameters, - transformation_parameters=transform_parameters, - is_intrinsic=True, - ) - return transform_output - - raise RuntimeError( - f"Unsupported transform function '{transform_name}' in '{self._change_set.stack.stack_name}'" - ) - - def visit_node_intrinsic_function_fn_transform( - self, node_intrinsic_function: NodeIntrinsicFunction - ) -> PreprocEntityDelta: - arguments_delta = self.visit(node_intrinsic_function.arguments) - delta = self._cached_apply( - scope=node_intrinsic_function.scope, - arguments_delta=arguments_delta, - resolver=self._compute_fn_transform, - ) - return delta - def visit_node_intrinsic_function_fn_sub( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: @@ -663,7 +791,7 @@ def _compute_sub(args: str | list[Any], select_before: bool) -> str: sub_parameters: dict if isinstance(args, str): string_template = args - sub_parameters = dict() + sub_parameters = {} elif ( isinstance(args, list) and len(args) == 2 @@ -770,7 +898,7 @@ def _compute_fn_join(args: list[Any]) -> str | NothingType: if values == "": return "" raise RuntimeError(f"Invalid arguments list definition for Fn::Join: '{args}'") - str_values: list[str] = list() + str_values: list[str] = [] for value in values: if value is None: continue @@ -792,13 +920,27 @@ def visit_node_intrinsic_function_fn_select( ): # TODO: add further support for schema validation def _compute_fn_select(args: list[Any]) -> Any: - values: list[Any] = args[1] + values = args[1] + # defer evaluation if the selection list contains unresolved elements (e.g., unresolved intrinsics) + if isinstance(values, list) and not all(isinstance(value, str) for value in values): + raise RuntimeError("Fn::Select list contains unresolved elements") + if not isinstance(values, list) or not values: - raise RuntimeError(f"Invalid arguments list value for Fn::Select: '{values}'") + raise ValidationError( + "Template error: Fn::Select requires a list argument with two elements: an integer index and a list" + ) + try: + index: int = int(args[0]) + except ValueError as e: + raise ValidationError( + "Template error: Fn::Select requires a list argument with two elements: an integer index and a list" + ) from e + values_len = len(values) - index: int = int(args[0]) - if not isinstance(index, int) or index < 0 or index > values_len: - raise RuntimeError(f"Invalid or out of range index value for Fn::Select: '{index}'") + if index < 0 or index >= values_len: + raise ValidationError( + "Template error: Fn::Select requires a list argument with two elements: an integer index and a list" + ) selection = values[index] return selection @@ -825,6 +967,17 @@ def _compute_fn_split(args: list[Any]) -> Any: return split_string arguments_delta = self.visit(node_intrinsic_function.arguments) + + if not ( + is_nothing(arguments_delta.after) + or isinstance(arguments_delta.after, list) + and len(arguments_delta.after) == 2 + ): + raise ValidationError( + "Template error: every Fn::Split object requires two parameters, " + "(1) a string delimiter and (2) a string to be split or a function that returns a string to be split." + ) + delta = self._cached_apply( scope=node_intrinsic_function.scope, arguments_delta=arguments_delta, @@ -911,8 +1064,8 @@ def visit_node_mapping(self, node_mapping: NodeMapping) -> PreprocEntityDelta: def visit_node_parameters( self, node_parameters: NodeParameters ) -> PreprocEntityDelta[dict[str, Any], dict[str, Any]]: - before_parameters = dict() - after_parameters = dict() + before_parameters = {} + after_parameters = {} for parameter in node_parameters.parameters: parameter_delta = self.visit(parameter) parameter_before = parameter_delta.before @@ -924,6 +1077,10 @@ def visit_node_parameters( return PreprocEntityDelta(before=before_parameters, after=after_parameters) def visit_node_parameter(self, node_parameter: NodeParameter) -> PreprocEntityDelta: + if not VALID_LOGICAL_RESOURCE_ID_RE.match(node_parameter.name): + raise ValidationError( + f"Template format error: Parameter name {node_parameter.name} is non alphanumeric." + ) dynamic_value = node_parameter.dynamic_value dynamic_delta = self.visit(dynamic_value) @@ -933,6 +1090,22 @@ def visit_node_parameter(self, node_parameter: NodeParameter) -> PreprocEntityDe before = dynamic_delta.before or default_delta.before after = dynamic_delta.after or default_delta.after + parameter_type = self.visit(node_parameter.type_) + + def _resolve_parameter_type(value: str, type_: str) -> Any: + match type_: + case s if re.match(r"List<[^>]+>", s): + return [item.strip() for item in value.split(",")] + case "CommaDelimitedList": + return [item.strip() for item in value.split(",")] + case "Number": + # TODO: validate the parameter type at template parse time (or whatever is in parity with AWS) so we know this cannot fail + return to_number(value) + return value + + if not is_nothing(after): + after = _resolve_parameter_type(after, parameter_type.after) + return PreprocEntityDelta(before=before, after=after) def visit_node_depends_on(self, node_depends_on: NodeDependsOn) -> PreprocEntityDelta: @@ -944,11 +1117,17 @@ def visit_node_condition(self, node_condition: NodeCondition) -> PreprocEntityDe return delta def _resource_physical_resource_id_from( - self, logical_resource_id: str, resolved_resources: dict - ) -> str: + self, logical_resource_id: str, resolved_resources: dict[str, ResolvedResource] + ) -> str | None: # TODO: typing around resolved resources is needed and should be reflected here. - resolved_resource = resolved_resources.get(logical_resource_id, dict()) - physical_resource_id: Optional[str] = resolved_resource.get("PhysicalResourceId") + resolved_resource = resolved_resources.get(logical_resource_id, {}) + if resolved_resource.get("ResourceStatus") not in { + ResourceStatus.CREATE_COMPLETE, + ResourceStatus.UPDATE_COMPLETE, + }: + return None + + physical_resource_id = resolved_resource.get("PhysicalResourceId") if not isinstance(physical_resource_id, str): raise RuntimeError(f"No PhysicalResourceId found for resource '{logical_resource_id}'") return physical_resource_id @@ -967,6 +1146,9 @@ def visit_node_intrinsic_function_ref( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: def _compute_fn_ref(logical_id: str) -> PreprocEntityDelta: + if logical_id == "AWS::NoValue": + return Nothing + reference_delta: PreprocEntityDelta = self._resolve_reference(logical_id=logical_id) if isinstance(before := reference_delta.before, PreprocResource): reference_delta.before = before.physical_resource_id @@ -1003,8 +1185,8 @@ def _delta_of_condition(name: str) -> PreprocEntityDelta: def visit_node_array(self, node_array: NodeArray) -> PreprocEntityDelta: node_change_type = node_array.change_type - before = list() if node_change_type != ChangeType.CREATED else Nothing - after = list() if node_change_type != ChangeType.REMOVED else Nothing + before = [] if node_change_type != ChangeType.CREATED else Nothing + after = [] if node_change_type != ChangeType.REMOVED else Nothing for change_set_entity in node_array.array: delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity) delta_before = delta.before @@ -1015,15 +1197,12 @@ def visit_node_array(self, node_array: NodeArray) -> PreprocEntityDelta: after.append(delta_after) return PreprocEntityDelta(before=before, after=after) - def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta: - return self.visit(node_property.value) - def visit_node_properties( self, node_properties: NodeProperties ) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]: node_change_type = node_properties.change_type - before_bindings = dict() if node_change_type != ChangeType.CREATED else Nothing - after_bindings = dict() if node_change_type != ChangeType.REMOVED else Nothing + before_bindings = {} if node_change_type != ChangeType.CREATED else Nothing + after_bindings = {} if node_change_type != ChangeType.REMOVED else Nothing for node_property in node_properties.properties: property_name = node_property.name delta = self.visit(node_property) @@ -1063,9 +1242,27 @@ def _resolve_resource_condition_reference(self, reference: TerminalValue) -> Pre after = after_delta.after return PreprocEntityDelta(before=before, after=after) + def visit_node_resources(self, node_resources: NodeResources): + """ + Skip resources where they conditionally evaluate to False + """ + for node_resource in node_resources.resources: + if not is_nothing(node_resource.condition_reference): + condition_delta = self._resolve_resource_condition_reference( + node_resource.condition_reference + ) + condition_after = condition_delta.after + if condition_after is False: + continue + self.visit(node_resource) + def visit_node_resource( self, node_resource: NodeResource ) -> PreprocEntityDelta[PreprocResource, PreprocResource]: + if not VALID_LOGICAL_RESOURCE_ID_RE.match(node_resource.name): + raise ValidationError( + f"Template format error: Resource name {node_resource.name} is non alphanumeric." + ) change_type = node_resource.change_type condition_before = Nothing condition_after = Nothing @@ -1084,13 +1281,39 @@ def visit_node_resource( depends_on_after = depends_on_delta.after type_delta = self.visit(node_resource.type_) - properties_delta: PreprocEntityDelta[PreprocProperties, PreprocProperties] = self.visit( - node_resource.properties + + # Check conditions before visiting properties to avoid resolving references + # (e.g. GetAtt) to conditional resources that were never created. + should_process_before = change_type != ChangeType.CREATED and ( + is_nothing(condition_before) or condition_before + ) + should_process_after = change_type != ChangeType.REMOVED and ( + is_nothing(condition_after) or condition_after ) + properties_delta: PreprocEntityDelta[PreprocProperties, PreprocProperties] + if should_process_before or should_process_after: + properties_delta = self.visit(node_resource.properties) + else: + properties_delta = PreprocEntityDelta(before=Nothing, after=Nothing) + + deletion_policy_before = Nothing + deletion_policy_after = Nothing + if not is_nothing(node_resource.deletion_policy): + deletion_policy_delta = self.visit(node_resource.deletion_policy) + deletion_policy_before = deletion_policy_delta.before + deletion_policy_after = deletion_policy_delta.after + + update_replace_policy_before = Nothing + update_replace_policy_after = Nothing + if not is_nothing(node_resource.update_replace_policy): + update_replace_policy_delta = self.visit(node_resource.update_replace_policy) + update_replace_policy_before = update_replace_policy_delta.before + update_replace_policy_after = update_replace_policy_delta.after + before = Nothing after = Nothing - if change_type != ChangeType.CREATED and is_nothing(condition_before) or condition_before: + if should_process_before: logical_resource_id = node_resource.name before_physical_resource_id = self._before_resource_physical_id( resource_logical_id=logical_resource_id @@ -1102,8 +1325,11 @@ def visit_node_resource( resource_type=type_delta.before, properties=properties_delta.before, depends_on=depends_on_before, + requires_replacement=False, + deletion_policy=deletion_policy_before, + update_replace_policy=update_replace_policy_before, ) - if change_type != ChangeType.REMOVED and is_nothing(condition_after) or condition_after: + if should_process_after: logical_resource_id = node_resource.name try: after_physical_resource_id = self._after_resource_physical_id( @@ -1118,6 +1344,9 @@ def visit_node_resource( resource_type=type_delta.after, properties=properties_delta.after, depends_on=depends_on_after, + requires_replacement=node_resource.requires_replacement, + deletion_policy=deletion_policy_after, + update_replace_policy=update_replace_policy_after, ) return PreprocEntityDelta(before=before, after=after) @@ -1164,9 +1393,17 @@ def visit_node_output( def visit_node_outputs( self, node_outputs: NodeOutputs ) -> PreprocEntityDelta[list[PreprocOutput], list[PreprocOutput]]: - before: list[PreprocOutput] = list() - after: list[PreprocOutput] = list() + before: list[PreprocOutput] = [] + after: list[PreprocOutput] = [] for node_output in node_outputs.outputs: + if not is_nothing(node_output.condition_reference): + condition_delta = self._resolve_resource_condition_reference( + node_output.condition_reference + ) + condition_after = condition_delta.after + if condition_after is False: + continue + output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output) output_before = output_delta.before output_after = output_delta.after @@ -1175,3 +1412,29 @@ def visit_node_outputs( if not is_nothing(output_after): after.append(output_after) return PreprocEntityDelta(before=before, after=after) + + def visit_node_intrinsic_function_fn_import_value( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + def _compute_fn_import_value(string) -> str: + if not isinstance(string, str): + raise RuntimeError(f"Invalid parameter for import: '{string}'") + + exports = exports_map( + account_id=self._change_set.account_id, region_name=self._change_set.region_name + ) + + return exports.get(string, {}).get("Value") or Nothing + + arguments_delta = self.visit(node_intrinsic_function.arguments) + delta = self._cached_apply( + scope=node_intrinsic_function.scope, + arguments_delta=arguments_delta, + resolver=_compute_fn_import_value, + ) + return delta + + def visit_node_intrinsic_function_fn_transform( + self, node_intrinsic_function: NodeIntrinsicFunction + ): + raise RuntimeError("Fn::Transform should have been handled by the Transformer") diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py index 70981d014747c..4aaae2d129ebd 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py @@ -1,21 +1,36 @@ import copy +import json import logging import os -from typing import Any, Final, Optional, TypedDict +import re +from typing import Any, Final, TypedDict import boto3 +import jsonpath_ng +from botocore.exceptions import ClientError, ParamValidationError from samtranslator.translator.transform import transform as transform_sam +from localstack.aws.connect import connect_to +from localstack.services.cloudformation.engine.parameters import StackParameter from localstack.services.cloudformation.engine.policy_loader import create_policy_loader +from localstack.services.cloudformation.engine.template_preparer import parse_template from localstack.services.cloudformation.engine.transformers import ( FailedTransformationException, - execute_macro, + ResolveRefsRecursivelyContext, + apply_language_extensions_transform, ) from localstack.services.cloudformation.engine.v2.change_set_model import ( ChangeType, + FnTransform, Maybe, + NodeForEach, NodeGlobalTransform, - NodeParameter, + NodeIntrinsicFunction, + NodeIntrinsicFunctionFnTransform, + NodeProperties, + NodeProperty, + NodeResource, + NodeResources, NodeTransform, Nothing, Scope, @@ -24,19 +39,39 @@ from localstack.services.cloudformation.engine.v2.change_set_model_preproc import ( ChangeSetModelPreproc, PreprocEntityDelta, + PreprocProperties, ) +from localstack.services.cloudformation.engine.validations import ValidationError from localstack.services.cloudformation.stores import get_cloudformation_store from localstack.services.cloudformation.v2.entities import ChangeSet +from localstack.services.cloudformation.v2.types import EngineParameter, engine_parameter_value +from localstack.utils import testutil +from localstack.utils.strings import long_uid LOG = logging.getLogger(__name__) SERVERLESS_TRANSFORM = "AWS::Serverless-2016-10-31" EXTENSIONS_TRANSFORM = "AWS::LanguageExtensions" SECRETSMANAGER_TRANSFORM = "AWS::SecretsManager-2020-07-23" +INCLUDE_TRANSFORM = "AWS::Include" _SCOPE_TRANSFORM_TEMPLATE_OUTCOME: Final[Scope] = Scope("TRANSFORM_TEMPLATE_OUTCOME") +def engine_parameters_to_stack_parameters( + engine_parameters: dict[str, EngineParameter], +) -> dict[str, StackParameter]: + out = {} + for name, engine_param in engine_parameters.items(): + out[name] = StackParameter( + ParameterKey=name, + ParameterValue=engine_parameter_value(engine_param), + ResolvedValue=engine_param.get("resolved_value"), + ParameterType=engine_param["type_"], + ) + return out + + # TODO: evaluate the use of subtypes to represent and validate types of transforms class GlobalTransform: name: str @@ -51,12 +86,12 @@ class TransformPreprocParameter(TypedDict): # TODO: expand ParameterKey: str ParameterValue: Any - ParameterType: Optional[str] + ParameterType: str | None class ChangeSetModelTransform(ChangeSetModelPreproc): - _before_parameters: Final[dict] - _after_parameters: Final[dict] + _before_parameters: Final[dict[str, EngineParameter] | None] + _after_parameters: Final[dict[str, EngineParameter] | None] _before_template: Final[Maybe[dict]] _after_template: Final[Maybe[dict]] @@ -65,8 +100,8 @@ def __init__( change_set: ChangeSet, before_parameters: dict, after_parameters: dict, - before_template: Optional[dict], - after_template: Optional[dict], + before_template: dict | None, + after_template: dict | None, ): super().__init__(change_set=change_set) self._before_parameters = before_parameters @@ -74,44 +109,13 @@ def __init__( self._before_template = before_template or Nothing self._after_template = after_template or Nothing - def visit_node_parameter( - self, node_parameter: NodeParameter - ) -> PreprocEntityDelta[ - dict[str, TransformPreprocParameter], dict[str, TransformPreprocParameter] - ]: - # Enable compatability with v1 util. - # TODO: port v1's SSM parameter resolution - - parameter_value_delta = super().visit_node_parameter(node_parameter=node_parameter) - parameter_value_before = parameter_value_delta.before - parameter_value_after = parameter_value_delta.after - - parameter_type_delta = self.visit(node_parameter.type_) - parameter_type_before = parameter_type_delta.before - parameter_type_after = parameter_type_delta.after - - parameter_key = node_parameter.name - - before = Nothing - if not is_nothing(parameter_value_before): - before = TransformPreprocParameter( - ParameterKey=parameter_key, - ParameterValue=parameter_value_before, - ParameterType=parameter_type_before - if not is_nothing(parameter_type_before) - else None, - ) - after = Nothing - if not is_nothing(parameter_value_after): - after = TransformPreprocParameter( - ParameterKey=parameter_key, - ParameterValue=parameter_value_after, - ParameterType=parameter_type_after - if not is_nothing(parameter_type_after) - else None, - ) + def transform(self) -> tuple[dict, dict]: + self._setup_runtime_cache() + self._execute_local_transforms() + transformed_before_template, transformed_after_template = self._execute_global_transforms() + self._save_runtime_cache() - return PreprocEntityDelta(before=before, after=after) + return transformed_before_template, transformed_after_template # Ported from v1: @staticmethod @@ -138,68 +142,92 @@ def _apply_global_serverless_transformation( if region_before is not None: os.environ["AWS_DEFAULT_REGION"] = region_before - @staticmethod - def _apply_global_macro_transformation( - account_id: str, - region_name, - global_transform: GlobalTransform, - template: dict, - parameters: dict, - ) -> Optional[dict]: - macro_name = global_transform.name - macros_store = get_cloudformation_store( - account_id=account_id, region_name=region_name - ).macros - macro = macros_store.get(macro_name) - if macro is None: - raise RuntimeError(f"No definitions for global transform '{macro_name}'") - transformation_parameters = global_transform.parameters or dict() - transformed_template = execute_macro( - account_id, - region_name, - parsed_template=template, - macro=macro, - stack_parameters=parameters, - transformation_parameters=transformation_parameters, - ) - # The type annotation on the v1 util appears to be incorrect. - return transformed_template # noqa + def _compute_include_transform(self, parameters: dict, fragment: dict) -> dict: + location = parameters.get("Location") + if not location or not location.startswith("s3://"): + raise FailedTransformationException( + transformation=INCLUDE_TRANSFORM, + message=f"Unexpected Location parameter for AWS::Include transformer: {location}", + ) + + s3_client = connect_to( + aws_access_key_id=self._change_set.account_id, region_name=self._change_set.region_name + ).s3 + bucket, _, path = location.removeprefix("s3://").partition("/") + try: + content = testutil.download_s3_object(s3_client, bucket, path) + except ClientError: + raise FailedTransformationException( + transformation=INCLUDE_TRANSFORM, + message=f"Error downloading S3 object '{bucket}/{path}'", + ) + try: + template_to_include = parse_template(content) + except Exception as e: + raise FailedTransformationException(transformation=INCLUDE_TRANSFORM, message=str(e)) + + return {**fragment, **template_to_include} def _apply_global_transform( - self, global_transform: GlobalTransform, template: dict, parameters: dict + self, + global_transform: GlobalTransform, + template: dict, + parameters: dict[str, EngineParameter], ) -> dict: transform_name = global_transform.name if transform_name == EXTENSIONS_TRANSFORM: - # Applied lazily in downstream tasks (see ChangeSetModelPreproc). - transformed_template = template + resources = template["Resources"] + mappings = template.get("Mappings", {}) + conditions = template.get("Conditions", {}) + + resolve_context = ResolveRefsRecursivelyContext( + self._change_set.account_id, + self._change_set.region_name, + self._change_set.stack.stack_name, + resources, + mappings, + conditions, + parameters=engine_parameters_to_stack_parameters(parameters), + ) + transformed_template = apply_language_extensions_transform(template, resolve_context) elif transform_name == SERVERLESS_TRANSFORM: + # serverless transform just requires the key/value pairs + serverless_parameters = {} + for name, param in parameters.items(): + serverless_parameters[name] = param.get("resolved_value") or engine_parameter_value( + param + ) transformed_template = self._apply_global_serverless_transformation( region_name=self._change_set.region_name, template=template, - parameters=parameters, + parameters=serverless_parameters, ) elif transform_name == SECRETSMANAGER_TRANSFORM: # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-secretsmanager.html LOG.warning("%s is not yet supported. Ignoring.", SECRETSMANAGER_TRANSFORM) transformed_template = template + elif transform_name == INCLUDE_TRANSFORM: + transformed_template = self._compute_include_transform( + parameters=global_transform.parameters, + fragment=template, + ) else: - transformed_template = self._apply_global_macro_transformation( - account_id=self._change_set.account_id, - region_name=self._change_set.region_name, - global_transform=global_transform, - template=template, - parameters=parameters, + transformed_template = self._invoke_macro( + name=global_transform.name, + parameters=global_transform.parameters + if not is_nothing(global_transform.parameters) + else {}, + fragment=template, + allow_string=False, ) return transformed_template - def transform(self) -> tuple[dict, dict]: - self._setup_runtime_cache() - + def _execute_local_transforms(self): node_template = self._change_set.update_model.node_template + self.visit_node_resources(node_template.resources) - parameters_delta = self.visit_node_parameters(node_template.parameters) - parameters_before = parameters_delta.before - parameters_after = parameters_delta.after + def _execute_global_transforms(self) -> tuple[dict, dict]: + node_template = self._change_set.update_model.node_template transform_delta: PreprocEntityDelta[list[GlobalTransform], list[GlobalTransform]] = ( self.visit_node_transform(node_template.transform) @@ -209,31 +237,36 @@ def transform(self) -> tuple[dict, dict]: transformed_before_template = self._before_template if transform_before and not is_nothing(self._before_template): - transformed_before_template = self._before_cache.get(_SCOPE_TRANSFORM_TEMPLATE_OUTCOME) - if not transformed_before_template: - transformed_before_template = self._before_template + if _SCOPE_TRANSFORM_TEMPLATE_OUTCOME in self._before_cache: + transformed_before_template = self._before_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] + else: for before_global_transform in transform_before: - transformed_before_template = self._apply_global_transform( - global_transform=before_global_transform, - parameters=parameters_before, - template=transformed_before_template, - ) + if not is_nothing(before_global_transform.name): + transformed_before_template = self._apply_global_transform( + global_transform=before_global_transform, + parameters=self._before_parameters, + template=transformed_before_template, + ) + + # Macro transformations won't remove the transform from the template + if "Transform" in transformed_before_template: + transformed_before_template.pop("Transform") self._before_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = transformed_before_template transformed_after_template = self._after_template if transform_after and not is_nothing(self._after_template): - transformed_after_template = self._after_cache.get(_SCOPE_TRANSFORM_TEMPLATE_OUTCOME) - if not transformed_after_template: - transformed_after_template = self._after_template - for after_global_transform in transform_after: + transformed_after_template = self._after_template + for after_global_transform in transform_after: + if not is_nothing(after_global_transform.name): transformed_after_template = self._apply_global_transform( global_transform=after_global_transform, - parameters=parameters_after, + parameters=self._after_parameters, template=transformed_after_template, ) - self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = transformed_after_template - - self._save_runtime_cache() + # Macro transformations won't remove the transform from the template + if "Transform" in transformed_after_template: + transformed_after_template.pop("Transform") + self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = transformed_after_template return transformed_before_template, transformed_after_template @@ -257,9 +290,12 @@ def visit_node_transform( self, node_transform: NodeTransform ) -> PreprocEntityDelta[list[GlobalTransform], list[GlobalTransform]]: change_type = node_transform.change_type - before = list() if change_type != ChangeType.CREATED else Nothing - after = list() if change_type != ChangeType.REMOVED else Nothing + before = [] if change_type != ChangeType.CREATED else Nothing + after = [] if change_type != ChangeType.REMOVED else Nothing for change_set_entity in node_transform.global_transforms: + if not isinstance(change_set_entity.name.value, str): + raise ValidationError("Key Name of transform definition must be a string.") + delta: PreprocEntityDelta[GlobalTransform, GlobalTransform] = self.visit( change_set_entity=change_set_entity ) @@ -270,3 +306,242 @@ def visit_node_transform( if not is_nothing(after) and not is_nothing(delta_after): after.append(delta_after) return PreprocEntityDelta(before=before, after=after) + + def _compute_fn_transform( + self, macro_definition: Any, siblings: Any, allow_string: False + ) -> Any: + def _normalize_transform(obj): + transforms = [] + + if isinstance(obj, str): + transforms.append({"Name": obj, "Parameters": {}}) + + if isinstance(obj, dict): + transforms.append(obj) + + if isinstance(obj, list): + for v in obj: + if isinstance(v, str): + transforms.append({"Name": v, "Parameters": {}}) + + if isinstance(v, dict): + if not v.get("Parameters"): + v["Parameters"] = {} + transforms.append(v) + + return transforms + + normalized_transforms = _normalize_transform(macro_definition) + transform_output = copy.deepcopy(siblings) + for transform in normalized_transforms: + transform_name = transform["Name"] + if transform_name == INCLUDE_TRANSFORM: + transform_output = self._compute_include_transform( + parameters=transform["Parameters"], fragment=transform_output + ) + else: + transform_output: dict | str = self._invoke_macro( + fragment=transform_output, + name=transform["Name"], + parameters=transform.get("Parameters", {}), + allow_string=allow_string, + ) + + if isinstance(transform_output, dict) and FnTransform in transform_output: + transform_output.pop(FnTransform) + + return transform_output + + def _replace_at_jsonpath(self, template: dict, path: str, result: Any): + pattern = jsonpath_ng.parse(path) + result_template = pattern.update(template, result) + + return result_template + + def visit_node_for_each(self, node_foreach: NodeForEach) -> PreprocEntityDelta: + return PreprocEntityDelta() + + def visit_node_intrinsic_function_fn_transform( + self, node_intrinsic_function: NodeIntrinsicFunctionFnTransform + ) -> PreprocEntityDelta: + arguments_delta = self.visit(node_intrinsic_function.arguments) + parent_json_path = node_intrinsic_function.scope.parent.jsonpath + + # Only when a FnTransform is used as Property value the macro function is allowed to return a str + property_value_regex = r"\.(Properties)" + allow_string = False + if re.search(property_value_regex, parent_json_path): + allow_string = True + + if not is_nothing(arguments_delta.before): + before = self._compute_fn_transform( + arguments_delta.before, + node_intrinsic_function.before_siblings, + allow_string=allow_string, + ) + updated_before_template = self._replace_at_jsonpath( + self._before_template, parent_json_path, before + ) + self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = updated_before_template + else: + before = Nothing + + if not is_nothing(arguments_delta.after): + after = self._compute_fn_transform( + arguments_delta.after, + node_intrinsic_function.after_siblings, + allow_string=allow_string, + ) + updated_after_template = self._replace_at_jsonpath( + self._after_template, parent_json_path, after + ) + self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = updated_after_template + else: + after = Nothing + + self._save_runtime_cache() + return PreprocEntityDelta(before=before, after=after) + + def visit_node_properties( + self, node_properties: NodeProperties + ) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]: + if not is_nothing(node_properties.fn_transform): + self.visit_node_intrinsic_function_fn_transform(node_properties.fn_transform) + + return super().visit_node_properties(node_properties=node_properties) + + def visit_node_resource(self, node_resource: NodeResource) -> PreprocEntityDelta: + if not is_nothing(node_resource.fn_transform): + self.visit_node_intrinsic_function_fn_transform( + node_intrinsic_function=node_resource.fn_transform + ) + + try: + if delta := super().visit_node_resource(node_resource): + return delta + return super().visit_node_properties(node_resource.properties) + except RuntimeError: + return super().visit_node_properties(node_resource.properties) + + def visit_node_resources(self, node_resources: NodeResources) -> PreprocEntityDelta: + if not is_nothing(node_resources.fn_transform): + self.visit_node_intrinsic_function_fn_transform( + node_intrinsic_function=node_resources.fn_transform + ) + + return super().visit_node_resources(node_resources=node_resources) + + def _invoke_macro(self, name: str, parameters: dict, fragment: dict, allow_string=False): + account_id = self._change_set.account_id + region_name = self._change_set.region_name + macro_definition = get_cloudformation_store( + account_id=account_id, region_name=region_name + ).macros.get(name) + + if not macro_definition: + raise FailedTransformationException(name, f"Transformation {name} is not supported.") + + simplified_parameters = {} + if resolved_parameters := self._change_set.resolved_parameters: + for key, resolved_parameter in resolved_parameters.items(): + final_value = engine_parameter_value(resolved_parameter) + simplified_parameters[key] = ( + final_value.split(",") + if resolved_parameter["type_"] == "CommaDelimitedList" + else final_value + ) + + transformation_id = f"{account_id}::{name}" + event = { + "region": region_name, + "accountId": account_id, + "fragment": fragment, + "transformId": transformation_id, + "params": parameters, + "requestId": long_uid(), + "templateParameterValues": simplified_parameters, + } + + client = connect_to(aws_access_key_id=account_id, region_name=region_name).lambda_ + try: + invocation = client.invoke( + FunctionName=macro_definition["FunctionName"], Payload=json.dumps(event) + ) + except ClientError: + LOG.error( + "client error executing lambda function '%s' with payload '%s'", + macro_definition["FunctionName"], + json.dumps(event), + ) + raise + if invocation.get("StatusCode") != 200 or invocation.get("FunctionError") == "Unhandled": + raise FailedTransformationException( + transformation=name, + message=f"Received malformed response from transform {transformation_id}. Rollback requested by user.", + ) + result = json.loads(invocation["Payload"].read()) + + if result.get("status") != "success": + error_message = result.get("errorMessage") + message = ( + f"Transform {transformation_id} failed with: {error_message}. Rollback requested by user." + if error_message + else f"Transform {transformation_id} failed without an error message.. Rollback requested by user." + ) + raise FailedTransformationException(transformation=name, message=message) + + if not isinstance(result.get("fragment"), dict) and not allow_string: + raise FailedTransformationException( + transformation=name, + message="Template format error: unsupported structure.. Rollback requested by user.", + ) + + return result.get("fragment") + + def visit_node_intrinsic_function_fn_get_att( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + try: + return super().visit_node_intrinsic_function_fn_get_att(node_intrinsic_function) + except RuntimeError: + return self.visit(node_intrinsic_function.arguments) + + def visit_node_intrinsic_function_fn_sub( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + try: + # If an argument is a Parameter it should be resolved, any other case, ignore it + return super().visit_node_intrinsic_function_fn_sub(node_intrinsic_function) + except RuntimeError: + return self.visit(node_intrinsic_function.arguments) + + def visit_node_intrinsic_function_fn_split( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + try: + # If an argument is a Parameter it should be resolved, any other case, ignore it + return super().visit_node_intrinsic_function_fn_split(node_intrinsic_function) + except RuntimeError: + return self.visit(node_intrinsic_function.arguments) + + def visit_node_intrinsic_function_fn_select( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + try: + # If an argument is a Parameter it should be resolved, any other case, ignore it + return super().visit_node_intrinsic_function_fn_select(node_intrinsic_function) + except RuntimeError: + return self.visit(node_intrinsic_function.arguments) + + def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta: + try: + return super().visit_node_property(node_property) + except ParamValidationError: + return self.visit(node_property.value) + + # ignore errors from dynamic replacements + def _maybe_perform_dynamic_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta: + try: + return super()._maybe_perform_dynamic_replacements(delta) + except Exception: + return delta diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_validator.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_validator.py new file mode 100644 index 0000000000000..9d4ee68cd8fcb --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_validator.py @@ -0,0 +1,188 @@ +import re +from typing import Any + +from botocore.exceptions import ParamValidationError + +from localstack.services.cloudformation.engine.v2.change_set_model import ( + NodeIntrinsicFunction, + NodeProperty, + NodeResource, + NodeTemplate, + Nothing, + is_nothing, +) +from localstack.services.cloudformation.engine.v2.change_set_model_preproc import ( + _PSEUDO_PARAMETERS, + ChangeSetModelPreproc, + PreprocEntityDelta, + PreprocResource, +) +from localstack.services.cloudformation.engine.validations import ValidationError + + +class ChangeSetModelValidator(ChangeSetModelPreproc): + def validate(self): + self.process() + + def visit_node_template(self, node_template: NodeTemplate): + self.visit(node_template.mappings) + self.visit(node_template.resources) + self.visit(node_template.parameters) + + def visit_node_intrinsic_function_fn_get_att( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + try: + return super().visit_node_intrinsic_function_fn_get_att(node_intrinsic_function) + except RuntimeError: + return self.visit(node_intrinsic_function.arguments) + + def visit_node_intrinsic_function_fn_sub( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + def _compute_sub(args: str | list[Any], select_before: bool) -> str: + # TODO: add further schema validation. + string_template: str + sub_parameters: dict + if isinstance(args, str): + string_template = args + sub_parameters = {} + elif ( + isinstance(args, list) + and len(args) == 2 + and isinstance(args[0], str) + and isinstance(args[1], dict) + ): + string_template = args[0] + sub_parameters = args[1] + else: + raise RuntimeError( + "Invalid arguments shape for Fn::Sub, expected a String " + f"or a Tuple of String and Map but got '{args}'" + ) + sub_string = string_template + template_variable_names = re.findall("\\${([^}]+)}", string_template) + for template_variable_name in template_variable_names: + template_variable_value = Nothing + + # Try to resolve the variable name as pseudo parameter. + if template_variable_name in _PSEUDO_PARAMETERS: + template_variable_value = self._resolve_pseudo_parameter( + pseudo_parameter_name=template_variable_name + ) + + # Try to resolve the variable name as an entry to the defined parameters. + elif template_variable_name in sub_parameters: + template_variable_value = sub_parameters[template_variable_name] + + # Try to resolve the variable name as GetAtt. + elif "." in template_variable_name: + try: + template_variable_value = self._resolve_attribute( + arguments=template_variable_name, select_before=select_before + ) + except RuntimeError: + pass + + # Try to resolve the variable name as Ref. + else: + try: + resource_delta = self._resolve_reference(logical_id=template_variable_name) + template_variable_value = ( + resource_delta.before if select_before else resource_delta.after + ) + if isinstance(template_variable_value, PreprocResource): + template_variable_value = template_variable_value.physical_resource_id + except RuntimeError: + pass + + if is_nothing(template_variable_value): + # override the base method just for this line to prevent accessing the + # resource properties since we are not deploying any resources + template_variable_value = "" + + if not isinstance(template_variable_value, str): + template_variable_value = str(template_variable_value) + + sub_string = sub_string.replace( + f"${{{template_variable_name}}}", template_variable_value + ) + + # FIXME: the following type reduction is ported from v1; however it appears as though such + # reduction is not performed by the engine, and certainly not at this depth given the + # lack of context. This section should be removed with Fn::Sub always retuning a string + # and the resource providers reviewed. + account_id = self._change_set.account_id + is_another_account_id = sub_string.isdigit() and len(sub_string) == len(account_id) + if sub_string == account_id or is_another_account_id: + result = sub_string + elif sub_string.isdigit(): + result = int(sub_string) + else: + try: + result = float(sub_string) + except ValueError: + result = sub_string + return result + + arguments_delta = self.visit(node_intrinsic_function.arguments) + arguments_before = arguments_delta.before + arguments_after = arguments_delta.after + before = self._before_cache.get(node_intrinsic_function.scope, Nothing) + if is_nothing(before) and not is_nothing(arguments_before): + before = _compute_sub(args=arguments_before, select_before=True) + after = self._after_cache.get(node_intrinsic_function.scope, Nothing) + if is_nothing(after) and not is_nothing(arguments_after): + after = _compute_sub(args=arguments_after, select_before=False) + return PreprocEntityDelta(before=before, after=after) + + def visit_node_intrinsic_function_fn_transform( + self, node_intrinsic_function: NodeIntrinsicFunction + ): + # TODO Research this issue: + # Function is already resolved in the template reaching this point + # But transformation is still present in update model + return self.visit(node_intrinsic_function.arguments) + + def visit_node_intrinsic_function_fn_split( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + try: + # If an argument is a Parameter it should be resolved, any other case, ignore it + return super().visit_node_intrinsic_function_fn_split(node_intrinsic_function) + except RuntimeError: + return self.visit(node_intrinsic_function.arguments) + + def visit_node_intrinsic_function_fn_select( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + try: + # If an argument is a Parameter it should be resolved, any other case, ignore it + return super().visit_node_intrinsic_function_fn_select(node_intrinsic_function) + except RuntimeError: + return self.visit(node_intrinsic_function.arguments) + + def visit_node_resource(self, node_resource: NodeResource) -> PreprocEntityDelta: + if is_nothing(node_resource.type_.value): + raise ValidationError( + f"Template format error: [{node_resource.scope}] Every Resources object must contain a Type member." + ) + try: + if delta := super().visit_node_resource(node_resource): + return delta + return super().visit_node_properties(node_resource.properties) + except RuntimeError: + return super().visit_node_properties(node_resource.properties) + + def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta: + try: + return super().visit_node_property(node_property) + except ParamValidationError: + return self.visit(node_property.value) + + # ignore errors from dynamic replacements + def _maybe_perform_dynamic_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta: + try: + return super()._maybe_perform_dynamic_replacements(delta) + except Exception: + return delta diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py index 6333e9f8dbae2..ace45f5001184 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py @@ -54,6 +54,7 @@ def visit_node_template(self, node_template: NodeTemplate): # entities (parameters, mappings, conditions, etc.). Then compute the output fields; computing # only the output fields would only result in the deployment logic of the referenced outputs # being evaluated, hence enforce the visiting of all the resources first. + self.visit(node_template.conditions) self.visit(node_template.resources) self.visit(node_template.outputs) @@ -177,6 +178,11 @@ def visit_node_intrinsic_function_condition( ): self.visit_children(node_intrinsic_function) + def visit_node_intrinsic_function_fn_import_value( + self, node_intrinsic_function: NodeIntrinsicFunction + ): + self.visit_children(node_intrinsic_function) + def visit_node_divergence(self, node_divergence: NodeDivergence): self.visit_children(node_divergence) diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py new file mode 100644 index 0000000000000..f590996f05524 --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py @@ -0,0 +1,122 @@ +from localstack.aws.api.cloudformation import ChangeSetType +from localstack.services.cloudformation.engine.v2.change_set_model import NodeResource +from localstack.services.cloudformation.engine.v2.change_set_model_visitor import ( + ChangeSetModelVisitor, +) +from localstack.services.cloudformation.engine.v2.unsupported_resource import ( + should_ignore_unsupported_resource_type, +) +from localstack.services.cloudformation.resources import AWS_AVAILABLE_CFN_RESOURCES +from localstack.utils.catalog.catalog import ( + AwsServicesSupportStatus, + CatalogPlugin, + CfnResourceSupportStatus, +) +from localstack.utils.catalog.common import ( + AwsServicesSupportInLatest, + AwsServiceSupportAtRuntime, + CloudFormationResourcesSupportAtRuntime, + CloudFormationResourcesSupportInLatest, +) +from localstack.utils.catalog.plugins import get_aws_catalog + + +# TODO handle all available resource types +def _get_service_name(resource_type: str) -> str | None: + parts = resource_type.split("::") + if len(parts) == 1: + return None + + match parts: + case _ if "Cognito::IdentityPool" in resource_type: + return "cognito-identity" + case [*_, "Cognito", "UserPool"]: + return "cognito-idp" + case [*_, "Cognito", _]: + return "cognito-idp" + case [*_, "Elasticsearch", _]: + return "es" + case [*_, "OpenSearchService", _]: + return "opensearch" + case [*_, "KinesisFirehose", _]: + return "firehose" + case [*_, "ResourceGroups", _]: + return "resource-groups" + case [*_, "CertificateManager", _]: + return "acm" + case _ if "ElasticLoadBalancing::" in resource_type: + return "elb" + case _ if "ElasticLoadBalancingV2::" in resource_type: + return "elbv2" + case _ if "ApplicationAutoScaling::" in resource_type: + return "application-autoscaling" + case _ if "MSK::" in resource_type: + return "kafka" + case _ if "Timestream::" in resource_type: + return "timestream-write" + case [_, service, *_]: + return service.lower() + + +def _build_resource_failure_message( + resource_type: str, status: AwsServicesSupportStatus | CfnResourceSupportStatus +) -> str: + service_name = _get_service_name(resource_type) or "malformed" + template = "Sorry, the {resource} resource in the {service} service is not supported." + match status: + case CloudFormationResourcesSupportAtRuntime.NOT_IMPLEMENTED: + template = "Sorry, the {resource} resource (from the {service} service) is not supported by this version of LocalStack, but is available in the latest version." + case CloudFormationResourcesSupportInLatest.NOT_SUPPORTED: + template = "Sorry, the {resource} resource (from the {service} service) is not currently supported by LocalStack." + case AwsServiceSupportAtRuntime.AVAILABLE_WITH_LICENSE_UPGRADE: + template = "Sorry, the {service} service (for the {resource} resource) is not included within your LocalStack license, but is available in an upgraded license." + case AwsServiceSupportAtRuntime.NOT_IMPLEMENTED: + template = "The API for service {service} (for the {resource} resource) is either not included in your current license plan or has not yet been emulated by LocalStack." + case AwsServicesSupportInLatest.NOT_SUPPORTED: + template = "Sorry, the {service} (for the {resource} resource) service is not currently supported by LocalStack." + case AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE: + template = "Sorry, the {service} service (for the {resource} resource) is not supported by this version of LocalStack, but is available in the latest version if you upgrade to the latest stable version." + return template.format( + resource=resource_type, + service=service_name, + ) + + +class ChangeSetResourceSupportChecker(ChangeSetModelVisitor): + change_set_type: ChangeSetType + catalog: CatalogPlugin + + TITLE_MESSAGE = "Unsupported resources detected:" + + def __init__(self, change_set_type: ChangeSetType): + self._resource_failure_messages: dict[str, str] = {} + self.change_set_type = change_set_type + self.catalog = get_aws_catalog() + + def visit_node_resource(self, node_resource: NodeResource): + resource_type = node_resource.type_.value + ignore_unsupported = should_ignore_unsupported_resource_type( + resource_type=resource_type, change_set_type=self.change_set_type + ) + + if resource_type not in self._resource_failure_messages and not ignore_unsupported: + if resource_type not in AWS_AVAILABLE_CFN_RESOURCES: + # Ignore non-AWS resources + pass + support_status = self._resource_support_status(resource_type) + if support_status == CloudFormationResourcesSupportAtRuntime.AVAILABLE: + pass + else: + failure_message = _build_resource_failure_message(resource_type, support_status) + self._resource_failure_messages[resource_type] = failure_message + super().visit_node_resource(node_resource) + + def _resource_support_status( + self, resource_type: str + ) -> AwsServicesSupportStatus | CfnResourceSupportStatus: + service_name = _get_service_name(resource_type) + return self.catalog.get_cloudformation_resource_status(resource_type, service_name, True) + + @property + def failure_messages(self) -> list[str]: + return list(self._resource_failure_messages.values()) diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/resolving.py b/localstack-core/localstack/services/cloudformation/engine/v2/resolving.py new file mode 100644 index 0000000000000..0be0bffd8a8de --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/engine/v2/resolving.py @@ -0,0 +1,102 @@ +import json +import logging +import re +from dataclasses import dataclass +from typing import Any + +from botocore.exceptions import ClientError + +from localstack.aws.connect import connect_to + +LOG = logging.getLogger(__name__) + +# CloudFormation allows using dynamic references in `Fn::Sub` expressions, so we must make sure +# we don't capture the parameter usage by excluding ${} characters +REGEX_DYNAMIC_REF = re.compile(r"{{resolve:([^:]+):([^${}]+)}}") + + +@dataclass +class DynamicReference: + service_name: str + reference_key: str + + +def extract_dynamic_reference(value: Any) -> DynamicReference | None: + if isinstance(value, str): + if dynamic_ref_match := REGEX_DYNAMIC_REF.search(value): + return DynamicReference(dynamic_ref_match[1], dynamic_ref_match[2]) + return None + + +def perform_dynamic_reference_lookup( + reference: DynamicReference, account_id: str, region_name: str +) -> str | None: + # basic dynamic reference support + # see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html + # technically there are more restrictions for each of these services but checking each of these + # isn't really necessary for the current level of emulation + + # only these 3 services are supported for dynamic references right now + if reference.service_name == "ssm": + ssm_client = connect_to(aws_access_key_id=account_id, region_name=region_name).ssm + try: + return ssm_client.get_parameter(Name=reference.reference_key)["Parameter"]["Value"] + except ClientError as e: + LOG.error("client error accessing SSM parameter '%s': %s", reference.reference_key, e) + raise + elif reference.service_name == "ssm-secure": + ssm_client = connect_to(aws_access_key_id=account_id, region_name=region_name).ssm + try: + return ssm_client.get_parameter(Name=reference.reference_key, WithDecryption=True)[ + "Parameter" + ]["Value"] + except ClientError as e: + LOG.error("client error accessing SSM parameter '%s': %s", reference.reference_key, e) + raise + elif reference.service_name == "secretsmanager": + # reference key needs to be parsed further + # because {{resolve:secretsmanager:secret-id:secret-string:json-key:version-stage:version-id}} + # we match for "secret-id:secret-string:json-key:version-stage:version-id" + # where + # secret-id can either be the secret name or the full ARN of the secret + # secret-string *must* be SecretString + # all other values are optional + secret_id = reference.reference_key + [json_key, version_stage, version_id] = [None, None, None] + if "SecretString" in reference.reference_key: + parts = reference.reference_key.split(":SecretString:") + secret_id = parts[0] + # json-key, version-stage and version-id are optional. + [json_key, version_stage, version_id] = f"{parts[1]}::".split(":")[:3] + + kwargs = {} # optional args for get_secret_value + if version_id: + kwargs["VersionId"] = version_id + if version_stage: + kwargs["VersionStage"] = version_stage + + secretsmanager_client = connect_to( + aws_access_key_id=account_id, region_name=region_name + ).secretsmanager + try: + secret_value = secretsmanager_client.get_secret_value(SecretId=secret_id, **kwargs)[ + "SecretString" + ] + except ClientError: + LOG.error("client error while trying to access key '%s': %s", secret_id) + raise + + if json_key: + json_secret = json.loads(secret_value) + if json_key not in json_secret: + raise RuntimeError( + f"JSON value for {reference.service_name}.{reference.reference_key} not present" + ) + return str(json_secret[json_key]) + else: + return str(secret_value) + + LOG.warning( + "Unsupported service for dynamic parameter: service_name=%s", reference.service_name + ) + return None diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/unsupported_resource.py b/localstack-core/localstack/services/cloudformation/engine/v2/unsupported_resource.py new file mode 100644 index 0000000000000..5059f88e556d1 --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/engine/v2/unsupported_resource.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from localstack import config +from localstack.aws.api.cloudformation import ChangeSetType + + +def should_ignore_unsupported_resource_type( + resource_type: str, change_set_type: ChangeSetType +) -> bool: + if config.CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES: + return True + + match change_set_type: + case ChangeSetType.CREATE: + return resource_type in config.CFN_IGNORE_UNSUPPORTED_TYPE_CREATE + case ChangeSetType.UPDATE | ChangeSetType.IMPORT: + return resource_type in config.CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE + case _: + return False diff --git a/localstack-core/localstack/services/cloudformation/engine/yaml_parser.py b/localstack-core/localstack/services/cloudformation/engine/yaml_parser.py index c0b72ead58f8f..3d12004f42423 100644 --- a/localstack-core/localstack/services/cloudformation/engine/yaml_parser.py +++ b/localstack-core/localstack/services/cloudformation/engine/yaml_parser.py @@ -1,5 +1,7 @@ import yaml +from localstack.services.cloudformation.engine.validations import ValidationError + def construct_raw(_, node): return node.value @@ -60,5 +62,10 @@ def shorthand_constructor(loader: yaml.Loader, tag_suffix: str, node: yaml.Node) yaml.add_multi_constructor("!", shorthand_constructor, customloader) -def parse_yaml(input_data: str): - return yaml.load(input_data, customloader) +def parse_yaml(input_data: str) -> dict: + parsed = yaml.load(input_data, Loader=customloader) + + if not isinstance(parsed, dict): + raise ValidationError("Template format error: unsupported structure.") + + return parsed diff --git a/localstack-core/localstack/services/cloudformation/plugins.py b/localstack-core/localstack/services/cloudformation/plugins.py deleted file mode 100644 index 72ef0104aaeb2..0000000000000 --- a/localstack-core/localstack/services/cloudformation/plugins.py +++ /dev/null @@ -1,12 +0,0 @@ -from rolo import Resource - -from localstack.runtime import hooks - - -@hooks.on_infra_start() -def register_cloudformation_deploy_ui(): - from localstack.services.internal import get_internal_apis - - from .deploy_ui import CloudFormationUi - - get_internal_apis().add(Resource("/_localstack/cloudformation/deploy", CloudFormationUi())) diff --git a/localstack-core/localstack/services/cloudformation/provider.py b/localstack-core/localstack/services/cloudformation/provider.py index 4d41a4576ae91..8335084903fc1 100644 --- a/localstack-core/localstack/services/cloudformation/provider.py +++ b/localstack-core/localstack/services/cloudformation/provider.py @@ -5,6 +5,7 @@ from collections import defaultdict from copy import deepcopy +from localstack import config from localstack.aws.api import CommonServiceException, RequestContext, handler from localstack.aws.api.cloudformation import ( AlreadyExistsException, @@ -120,7 +121,9 @@ find_stack_by_id, get_cloudformation_store, ) +from localstack.services.plugins import ServiceLifecycleHook from localstack.state import StateVisitor +from localstack.utils.aws.arns import ARN_PARTITION_REGEX from localstack.utils.collections import ( remove_attributes, select_attributes, @@ -132,10 +135,13 @@ LOG = logging.getLogger(__name__) ARN_CHANGESET_REGEX = re.compile( - r"arn:(aws|aws-us-gov|aws-cn):cloudformation:[-a-zA-Z0-9]+:\d{12}:changeSet/[a-zA-Z][-a-zA-Z0-9]*/[-a-zA-Z0-9:/._+]+" + rf"{ARN_PARTITION_REGEX}:cloudformation:[-a-zA-Z0-9]+:\d{{12}}:changeSet/[a-zA-Z][-a-zA-Z0-9]*/[-a-zA-Z0-9:/._+]+" ) ARN_STACK_REGEX = re.compile( - r"arn:(aws|aws-us-gov|aws-cn):cloudformation:[-a-zA-Z0-9]+:\d{12}:stack/[a-zA-Z][-a-zA-Z0-9]*/[-a-zA-Z0-9:/._+]+" + rf"{ARN_PARTITION_REGEX}:cloudformation:[-a-zA-Z0-9]+:\d{{12}}:stack/[a-zA-Z][-a-zA-Z0-9]*/[-a-zA-Z0-9:/._+]+" +) +ARN_STACK_SET_REGEX = re.compile( + rf"{ARN_PARTITION_REGEX}:cloudformation:[-a-zA-Z0-9]+:\d{{12}}:stack-set/[a-zA-Z][-a-zA-Z0-9]*/[-a-zA-Z0-9:/._+]+" ) @@ -156,7 +162,7 @@ def find_stack_instance(stack_set: StackSet, account: str, region: str): def stack_not_found_error(stack_name: str): # FIXME - raise ValidationError("Stack with id %s does not exist" % stack_name) + raise ValidationError(f"Stack with id {stack_name} does not exist") def not_found_error(message: str): @@ -174,7 +180,30 @@ def __init__(self, message=None): super().__init__("InternalFailure", status_code=500, message=message, sender_fault=False) -class CloudformationProvider(CloudformationApi): +class CloudformationProvider(CloudformationApi, ServiceLifecycleHook): + def on_before_start(self): + self._validate_config() + + def _validate_config(self): + no_wait_value: int = 5 + try: + no_wait_value = int(config.CFN_NO_WAIT_ITERATIONS or 5) + except (TypeError, ValueError): + LOG.warning( + "You have set CFN_NO_WAIT_ITERATIONS to an invalid value: '%s'. It must be an integer greater or equal to 0. Using the default of 5", + config.CFN_NO_WAIT_ITERATIONS, + ) + + if no_wait_value < 0: + LOG.warning( + "You have set CFN_NO_WAIT_ITERATIONS to an invalid value: '%s'. It must be an integer greater or equal to 0. Using the default of 5", + config.CFN_NO_WAIT_ITERATIONS, + ) + no_wait_value = 5 + + # Set the configuration back + config.CFN_NO_WAIT_ITERATIONS = no_wait_value + def _stack_status_is_active(self, stack_status: str) -> bool: return stack_status not in [StackStatus.DELETE_COMPLETE] @@ -302,8 +331,8 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr deployer.deploy_stack() except Exception as e: stack.set_stack_status("CREATE_FAILED") - msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e) - LOG.exception("%s") + msg = f'Unable to create stack "{stack.stack_name}": {e}' + LOG.error("%s", exc_info=LOG.isEnabledFor(logging.DEBUG)) raise ValidationError(msg) from e return CreateStackOutput(StackId=stack.stack_id) @@ -420,7 +449,7 @@ def update_stack( except Exception as e: stack.set_stack_status("UPDATE_FAILED") msg = f'Unable to update stack "{stack_name}": {e}' - LOG.exception("%s", msg) + LOG.error("%s", msg, exc_info=LOG.isEnabledFor(logging.DEBUG)) raise ValidationError(msg) from e return UpdateStackOutput(StackId=stack.stack_id) @@ -602,6 +631,8 @@ def create_change_set( req_params = request change_set_type = req_params.get("ChangeSetType", "UPDATE") stack_name = req_params.get("StackName") + if not stack_name: + raise ValidationError("Member must have length greater than or equal to 1") change_set_name = req_params.get("ChangeSetName") template_body = req_params.get("TemplateBody") # s3 or secretsmanager url @@ -924,7 +955,7 @@ def list_exports( self, context: RequestContext, next_token: NextToken = None, **kwargs ) -> ListExportsOutput: state = get_cloudformation_store(context.account_id, context.region) - return ListExportsOutput(Exports=state.exports) + return ListExportsOutput(Exports=state.exports.values()) @handler("ListImports") def list_imports( @@ -947,8 +978,8 @@ def list_imports( def describe_stack_events( self, context: RequestContext, - stack_name: StackName = None, - next_token: NextToken = None, + stack_name: StackName, + next_token: NextToken | None = None, **kwargs, ) -> DescribeStackEventsOutput: if stack_name is None: @@ -1049,7 +1080,7 @@ def validate_template( Description=valid_template.get("Description"), Parameters=parameters ) except Exception as e: - LOG.exception("Error validating template") + LOG.error("Error validating template", exc_info=LOG.isEnabledFor(logging.DEBUG)) raise ValidationError("Template Validation Error") from e # ======================================= diff --git a/localstack-core/localstack/services/cloudformation/provider_utils.py b/localstack-core/localstack/services/cloudformation/provider_utils.py index d7e3eb49b79f2..677f023af2796 100644 --- a/localstack-core/localstack/services/cloudformation/provider_utils.py +++ b/localstack-core/localstack/services/cloudformation/provider_utils.py @@ -9,9 +9,9 @@ import json import re import uuid +from collections.abc import Callable from copy import deepcopy from pathlib import Path -from typing import Callable, List, Optional from botocore.model import Shape, StructureShape @@ -73,9 +73,12 @@ def recurse_properties(properties: dict, fn: Callable) -> dict: return _recurse_properties(deepcopy(properties), fn) -def keys_pascalcase_to_lower_camelcase(model: dict) -> dict: +def keys_pascalcase_to_lower_camelcase(model: dict, skip_keys: set = None) -> dict: """Recursively change any dicts keys to lower camelcase""" + if skip_keys: + return _pascal_to_camel_keys_preserve_values(model, skip_keys) + def _keys_pascalcase_to_lower_camelcase(obj): if isinstance(obj, dict): return {convert_pascalcase_to_lower_camelcase(k): v for k, v in obj.items()} @@ -85,6 +88,33 @@ def _keys_pascalcase_to_lower_camelcase(obj): return _recurse_properties(model, _keys_pascalcase_to_lower_camelcase) +def _pascal_to_camel_keys_preserve_values(model: dict, skip_keys: set = None) -> dict: + """ + Variant of keys_pascalcase_to_lower_camelcase + All VALUES of provided keys are skipped and not transformed to lower camelcase. + The keys themselves will be transformed. + The function simply stops recursion if a key matches, so make sure no lower level values are ignored. + """ + skip_keys = skip_keys or set() + + def _transform(obj): + if isinstance(obj, dict): + new_dict = {} + for k, v in obj.items(): + new_key = convert_pascalcase_to_lower_camelcase(k) + if k in skip_keys: + new_dict[new_key] = v + else: + new_dict[new_key] = _transform(v) + return new_dict + elif isinstance(obj, list): + return [_transform(i) for i in obj] + else: + return obj + + return _transform(model) + + def keys_lower_camelcase_to_pascalcase(model: dict) -> dict: """Recursively change any dicts keys to PascalCase""" @@ -152,7 +182,7 @@ def fix_boto_parameters_based_on_report(original_params: dict, report: str) -> d cast_class = getattr(builtins, valid_class) old_value = get_nested(params, param_name) - if cast_class == bool and str(old_value).lower() in ["true", "false"]: + if isinstance(cast_class, bool) and str(old_value).lower() in ["true", "false"]: new_value = str(old_value).lower() == "true" else: new_value = cast_class(old_value) @@ -213,7 +243,7 @@ def transform_value(value, member_shape): return transformed_dict -def convert_values_to_numbers(input_dict: dict, keys_to_skip: Optional[List[str]] = None): +def convert_values_to_numbers(input_dict: dict, keys_to_skip: list[str] | None = None): """ Recursively converts all string values that represent valid integers in a dictionary (including nested dictionaries and lists) to integers. @@ -245,8 +275,30 @@ def recursive_convert(obj): return recursive_convert(input_dict) +def resource_tags_to_remove_or_update( + prev_tags: list[dict], new_tags: list[dict] +) -> tuple[list[str], dict[str, str]]: + """ + When updating resources that have tags, we need to determine which tags to remove and which to add/update, + as these are typically done in separate API calls. The format of prev_tags and new_tags is expected to + be [{ "Key": tagName, "Value": tagValue }, ...]. The return value will be a tuple of (tags_to_remove, tags_to_update), + where: + - tags_to_remove is a list of tag keys that are present in prev_tags but not in new_tags. + - tags_to_update is a dict of tags to add or update, with the format: { tagName: tagValue, ... }. + """ + prev_tag_keys = [tag["Key"] for tag in prev_tags] + new_tag_keys = [tag["Key"] for tag in new_tags] + tags_to_remove = list(set(prev_tag_keys) - set(new_tag_keys)) + + # convert from list of dicts, to a single dict because that's what tag_queue APIs expect. + tags_to_update = {tag["Key"]: tag["Value"] for tag in new_tags} + return (tags_to_remove, tags_to_update) + + # LocalStack specific utilities def get_schema_path(file_path: Path) -> dict: - file_name_base = file_path.name.removesuffix(".py").removesuffix(".py.enc") + file_name_base = ( + file_path.name.removesuffix("_base.py").removesuffix(".py").removesuffix(".py.enc") + ) with Path(file_path).parent.joinpath(f"{file_name_base}.schema.json").open() as fd: return json.load(fd) diff --git a/localstack-core/localstack/services/cloudformation/resource_provider.py b/localstack-core/localstack/services/cloudformation/resource_provider.py index 421ad8ecd2b30..b84ed961bdd50 100644 --- a/localstack-core/localstack/services/cloudformation/resource_provider.py +++ b/localstack-core/localstack/services/cloudformation/resource_provider.py @@ -5,11 +5,12 @@ import re import time import uuid +from collections.abc import Callable from dataclasses import dataclass, field from enum import Enum, auto from logging import Logger from math import ceil -from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Type, TypedDict, TypeVar +from typing import TYPE_CHECKING, Any, TypedDict, TypeVar import botocore from botocore.client import BaseClient @@ -51,7 +52,7 @@ Properties = TypeVar("Properties") -PUBLIC_REGISTRY: dict[str, Type[ResourceProvider]] = {} +PUBLIC_REGISTRY: dict[str, type[ResourceProvider]] = {} PROVIDER_DEFAULTS = {} # TODO: remove this after removing patching in -ext @@ -64,14 +65,14 @@ class OperationStatus(Enum): @dataclass -class ProgressEvent(Generic[Properties]): +class ProgressEvent[Properties]: status: OperationStatus - resource_model: Optional[Properties] = None - resource_models: Optional[list[Properties]] = None + resource_model: Properties | None = None + resource_models: list[Properties] | None = None message: str = "" - result: Optional[str] = None - error_code: Optional[str] = None # TODO: enum + result: str | None = None + error_code: str | None = None # TODO: enum custom_context: dict = field(default_factory=dict) @@ -84,7 +85,7 @@ class Credentials(TypedDict): class ResourceProviderPayloadRequestData(TypedDict): logicalResourceId: str resourceProperties: Properties - previousResourceProperties: Optional[Properties] + previousResourceProperties: Properties | None callerCredentials: Credentials providerCredentials: Credentials systemTags: dict[str, str] @@ -164,7 +165,7 @@ def convert_payload( @dataclass -class ResourceRequest(Generic[Properties]): +class ResourceRequest[Properties]: _original_payload: Properties aws_client_factory: ServiceLevelClientFactory @@ -184,8 +185,8 @@ class ResourceRequest(Generic[Properties]): custom_context: dict = field(default_factory=dict) - previous_state: Optional[Properties] = None - previous_tags: Optional[dict[str, str]] = None + previous_state: Properties | None = None + previous_tags: dict[str, str] | None = None tags: dict[str, str] = field(default_factory=dict) @@ -197,7 +198,7 @@ class CloudFormationResourceProviderPlugin(Plugin): namespace = "localstack.cloudformation.resource_providers" -class ResourceProvider(Generic[Properties]): +class ResourceProvider[Properties]: """ This provides a base class onto which service-specific resource providers are built. """ @@ -235,7 +236,7 @@ def get_resource_type(resource: dict) -> str: LOG.warning( "Failed to retrieve resource type %s", resource.get("Type"), - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) @@ -400,7 +401,7 @@ class NoResourceProvider(Exception): pass -def resolve_json_pointer(resource_props: Properties, primary_id_path: str) -> str: +def resolve_json_pointer[Properties](resource_props: Properties, primary_id_path: str) -> str: primary_id_path = primary_id_path.replace("/properties", "") parts = [p for p in primary_id_path.split("/") if p] @@ -435,11 +436,11 @@ def deploy_loop( resource: dict, raw_payload: ResourceProviderPayload, max_timeout: int = config.CFN_PER_RESOURCE_TIMEOUT, - sleep_time: float = 5, + sleep_time: float = 1, ) -> ProgressEvent[Properties]: payload = copy.deepcopy(raw_payload) - max_iterations = max(ceil(max_timeout / sleep_time), 2) + max_iterations = max(ceil(max_timeout / sleep_time), 10) for current_iteration in range(max_iterations): resource_type = get_resource_type({"Type": raw_payload["resourceType"]}) @@ -465,15 +466,18 @@ def deploy_loop( "A ResourceProvider should always have a SCHEMA property defined." ) resource_type_schema = resource_provider.SCHEMA - physical_resource_id = self.extract_physical_resource_id_from_model_with_schema( - event.resource_model, - raw_payload["resourceType"], - resource_type_schema, - ) + if raw_payload["action"] != "Remove": + physical_resource_id = ( + self.extract_physical_resource_id_from_model_with_schema( + event.resource_model, + raw_payload["resourceType"], + resource_type_schema, + ) + ) - resource["PhysicalResourceId"] = physical_resource_id - resource["Properties"] = event.resource_model - resource["_last_deployed_state"] = copy.deepcopy(event.resource_model) + resource["PhysicalResourceId"] = physical_resource_id + resource["Properties"] = event.resource_model + resource["_last_deployed_state"] = copy.deepcopy(event.resource_model) return event case OperationStatus.IN_PROGRESS: # update the shared state @@ -482,10 +486,11 @@ def deploy_loop( payload["requestData"]["resourceProperties"] = event.resource_model resource["Properties"] = event.resource_model - if current_iteration == 0: - time.sleep(0) + if current_iteration < config.CFN_NO_WAIT_ITERATIONS: + pass else: time.sleep(sleep_time) + case OperationStatus.PENDING: # come back to this resource in another iteration return event @@ -514,10 +519,14 @@ def execute_action( try: return resource_provider.update(request) except NotImplementedError: + feature_request_url = "https://github.com/localstack/localstack/issues/new?template=feature-request.yml" LOG.warning( - 'Unable to update resource type "%s", id "%s"', + 'Unable to update resource type "%s", id "%s", ' + "the update operation is not implemented for this resource. " + "Please consider submitting a feature request at this URL: %s", request.resource_type, request.logical_resource_id, + feature_request_url, ) if request.previous_state is None: # this is an issue with our update detection. We should never be in this state. @@ -539,6 +548,7 @@ def execute_action( status=OperationStatus.FAILED, resource_model={}, message=f"Failed to delete resource with id {request.logical_resource_id} of type {request.resource_type}", + custom_context={"exception": e}, ) case "Remove": try: @@ -552,6 +562,7 @@ def execute_action( status=OperationStatus.FAILED, resource_model={}, message=f"Failed to delete resource with id {request.logical_resource_id} of type {request.resource_type}", + custom_context={"exception": e}, ) case _: raise NotImplementedError(change_type) # TODO: change error type @@ -559,6 +570,8 @@ def execute_action( @staticmethod def try_load_resource_provider(resource_type: str) -> ResourceProvider | None: # TODO: unify namespace of plugins + if resource_type and resource_type.startswith("Custom"): + resource_type = "AWS::CloudFormation::CustomResource" # 1. try to load pro resource provider # prioritise pro resource providers @@ -573,7 +586,7 @@ def try_load_resource_provider(resource_type: str) -> ResourceProvider | None: LOG.warning( "Failed to load PRO resource type %s as a ResourceProvider.", resource_type, - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) # 2. try to load community resource provider @@ -588,7 +601,7 @@ def try_load_resource_provider(resource_type: str) -> ResourceProvider | None: LOG.warning( "Failed to load community resource type %s as a ResourceProvider.", resource_type, - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) # we could not find the resource provider diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py index 8f17b3d36368e..806f1df253e4d 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,12 +15,12 @@ class CloudFormationMacroProperties(TypedDict): - FunctionName: Optional[str] - Name: Optional[str] - Description: Optional[str] - Id: Optional[str] - LogGroupName: Optional[str] - LogRoleARN: Optional[str] + FunctionName: str | None + Name: str | None + Description: str | None + Id: str | None + LogGroupName: str | None + LogRoleARN: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py index 9c6572792fc21..76c615e9179f1 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationMacroProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CloudFormation::Macro" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_macro import ( diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py index b30c629682cc6..1defe49ff3730 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class CloudFormationStackProperties(TypedDict): - TemplateURL: Optional[str] - Id: Optional[str] - NotificationARNs: Optional[list[str]] - Parameters: Optional[dict] - Tags: Optional[list[Tag]] - TimeoutInMinutes: Optional[int] + TemplateURL: str | None + Id: str | None + NotificationARNs: list[str] | None + Parameters: dict | None + Tags: list[Tag] | None + TimeoutInMinutes: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py index 9dc020a564aa4..17c604a09e06d 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationStackProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CloudFormation::Stack" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_stack import ( diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py index 051c901e425d9..136bf77f6a1a2 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py @@ -3,7 +3,7 @@ import uuid from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,11 +15,11 @@ class CloudFormationWaitConditionProperties(TypedDict): - Count: Optional[int] - Data: Optional[dict] - Handle: Optional[str] - Id: Optional[str] - Timeout: Optional[str] + Count: int | None + Data: dict | None + Handle: str | None + Id: str | None + Timeout: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py index bdc8b49fd2e6d..75b2afe98483f 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationWaitConditionProviderPlugin(CloudFormationResourceProviderPl name = "AWS::CloudFormation::WaitCondition" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_waitcondition import ( diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py index f2b5237876fe0..60df870b9649e 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,7 +14,7 @@ class CloudFormationWaitConditionHandleProperties(TypedDict): - Id: Optional[str] + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py index f5888171517ab..13bafb3e8a0fc 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationWaitConditionHandleProviderPlugin(CloudFormationResourceProv name = "AWS::CloudFormation::WaitConditionHandle" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_waitconditionhandle import ( diff --git a/localstack-core/localstack/services/cloudformation/resources.py b/localstack-core/localstack/services/cloudformation/resources.py new file mode 100644 index 0000000000000..61e0c9c1753a9 --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/resources.py @@ -0,0 +1,25667 @@ +"""Generated by scripts/update_cfn_resources.py – do not edit manually.""" + +AWS_AVAILABLE_CFN_RESOURCES = { + "AMZN::SDC::Deployment": [ + "ap-northeast-2", + ], + "AWS::ACMPCA::Certificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ACMPCA::CertificateAuthority": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ACMPCA::CertificateAuthorityActivation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ACMPCA::Permission": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AIOps::InvestigationGroup": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::APS::AnomalyDetector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::APS::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::APS::RuleGroupsNamespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::APS::Scraper": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::APS::Workspace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ARCRegionSwitch::Plan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ARCZonalShift::AutoshiftObserverNotificationStatus": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ARCZonalShift::ZonalAutoshiftConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AccessAnalyzer::Analyzer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AmazonMQ::Broker": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AmazonMQ::Configuration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AmazonMQ::ConfigurationAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Amplify::App": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Amplify::Branch": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Amplify::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AmplifyUIBuilder::Component": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AmplifyUIBuilder::Form": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AmplifyUIBuilder::Theme": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::Account": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::ApiKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::Authorizer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::BasePathMapping": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::BasePathMappingV2": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::ClientCertificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::Deployment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::DocumentationPart": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::DocumentationVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::DomainName": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::DomainNameAccessAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::DomainNameV2": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::GatewayResponse": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::Method": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::Model": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::RequestValidator": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::Resource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::RestApi": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::Stage": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::UsagePlan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::UsagePlanKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGateway::VpcLink": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::Api": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::ApiGatewayManagedOverrides": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::ApiMapping": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::Authorizer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::Deployment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::DomainName": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::Integration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::IntegrationResponse": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::Model": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::Route": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::RouteResponse": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::RoutingRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::Stage": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApiGatewayV2::VpcLink": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::ConfigurationProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::Deployment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::DeploymentStrategy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::Environment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::Extension": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::ExtensionAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppConfig::HostedConfigurationVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppFlow::Connector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppFlow::ConnectorProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppFlow::Flow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppIntegrations::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::AppIntegrations::DataIntegration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::AppIntegrations::EventIntegration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::AppMesh::GatewayRoute": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppMesh::Mesh": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppMesh::Route": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppMesh::VirtualGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppMesh::VirtualNode": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppMesh::VirtualRouter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppMesh::VirtualService": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppRunner::AutoScalingConfiguration": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppRunner::ObservabilityConfiguration": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppRunner::Service": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppRunner::VpcConnector": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppRunner::VpcIngressConnection": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppStream::AppBlock": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppStream::AppBlockBuilder": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppStream::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppStream::ApplicationEntitlementAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppStream::ApplicationFleetAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppStream::DirectoryConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppStream::Entitlement": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::AppStream::Fleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppStream::ImageBuilder": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppStream::Stack": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppStream::StackFleetAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppStream::StackUserAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppStream::User": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::Api": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::ApiCache": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::ApiKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::ChannelNamespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::DataSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::DomainName": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::DomainNameApiAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::FunctionConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::GraphQLApi": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::GraphQLSchema": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::Resolver": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppSync::SourceApiAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AppTest::TestCase": [ + "ap-southeast-2", + "eu-central-1", + "sa-east-1", + "us-east-1", + ], + "AWS::ApplicationAutoScaling::ScalableTarget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApplicationAutoScaling::ScalingPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApplicationInsights::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApplicationSignals::Discovery": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApplicationSignals::GroupingConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ApplicationSignals::ServiceLevelObjective": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Athena::CapacityReservation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Athena::DataCatalog": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Athena::NamedQuery": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Athena::PreparedStatement": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Athena::WorkGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AuditManager::Assessment": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AutoScaling::AutoScalingGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AutoScaling::LaunchConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AutoScaling::LifecycleHook": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AutoScaling::ScalingPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AutoScaling::ScheduledAction": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AutoScaling::WarmPool": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::AutoScalingPlans::ScalingPlan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::B2BI::Capability": [ + "eu-west-1", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::B2BI::Partnership": [ + "eu-west-1", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::B2BI::Profile": [ + "eu-west-1", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::B2BI::Transformer": [ + "eu-west-1", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BCMDataExports::Export": [ + "eu-west-2", + "us-east-1", + ], + "AWS::Backup::BackupPlan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::BackupSelection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::BackupVault": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::Framework": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::LogicallyAirGappedBackupVault": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::ReportPlan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::RestoreTestingPlan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::RestoreTestingSelection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Backup::TieringConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::BackupGateway::Hypervisor": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Batch::ComputeEnvironment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Batch::ConsumableResource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Batch::JobDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Batch::JobQueue": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Batch::SchedulingPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Batch::ServiceEnvironment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Bedrock::Agent": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::AgentAlias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::ApplicationInferenceProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::AutomatedReasoningPolicy": [ + "eu-central-1", + "eu-west-1", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::AutomatedReasoningPolicyVersion": [ + "eu-central-1", + "eu-west-1", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::Blueprint": [ + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Bedrock::DataAutomationProject": [ + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Bedrock::DataSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::Flow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::FlowAlias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::FlowVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::Guardrail": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Bedrock::GuardrailVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Bedrock::IntelligentPromptRouter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::KnowledgeBase": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::Prompt": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Bedrock::PromptVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::BrowserCustom": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::BrowserProfile": [ + "ap-northeast-2", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-west-2", + ], + "AWS::BedrockAgentCore::CodeInterpreterCustom": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::Evaluator": [ + "ap-southeast-2", + "eu-central-1", + "us-east-1", + "us-west-2", + ], + "AWS::BedrockAgentCore::Gateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::GatewayTarget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::Memory": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::OnlineEvaluationConfig": [ + "ap-southeast-2", + "eu-central-1", + "us-east-1", + "us-west-2", + ], + "AWS::BedrockAgentCore::Runtime": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::RuntimeEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockAgentCore::WorkloadIdentity": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::BedrockMantle::Project": [ + "ap-northeast-1", + "ap-south-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Billing::BillingView": [ + "us-east-1", + "us-west-2", + ], + "AWS::BillingConductor::BillingGroup": [ + "us-east-1", + ], + "AWS::BillingConductor::CustomLineItem": [ + "us-east-1", + ], + "AWS::BillingConductor::PricingPlan": [ + "us-east-1", + ], + "AWS::BillingConductor::PricingRule": [ + "us-east-1", + ], + "AWS::Budgets::Budget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Budgets::BudgetsAction": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CE::AnomalyMonitor": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CE::AnomalySubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CE::CostCategory": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CUR::ReportDefinition": [ + "us-east-1", + ], + "AWS::Cases::CaseRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Cases::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Cases::Field": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Cases::Layout": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Cases::Template": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Cassandra::Keyspace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cassandra::Table": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cassandra::Type": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CertificateManager::Account": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CertificateManager::Certificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Chatbot::CustomAction": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Chatbot::MicrosoftTeamsChannelConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Chatbot::SlackChannelConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CleanRooms::AnalysisTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRooms::Collaboration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRooms::ConfiguredTable": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRooms::ConfiguredTableAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRooms::IdMappingTable": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRooms::IdNamespaceAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRooms::Membership": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRooms::PrivacyBudgetTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CleanRoomsML::TrainingDataset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Cloud9::EnvironmentEC2": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::CustomResource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::GuardHook": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::HookDefaultVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::HookTypeConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::HookVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::LambdaHook": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::Macro": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::ModuleDefaultVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::ModuleVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::PublicTypeVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::Publisher": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::ResourceDefaultVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::ResourceVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::Stack": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::StackSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::TypeActivation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::WaitCondition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFormation::WaitConditionHandle": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::AnycastIpList": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::CachePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::CloudFrontOriginAccessIdentity": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::ConnectionFunction": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::ConnectionGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::ContinuousDeploymentPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::Distribution": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::DistributionTenant": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::Function": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::KeyGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::KeyValueStore": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::MonitoringSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::OriginAccessControl": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::OriginRequestPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::PublicKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::RealtimeLogConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::ResponseHeadersPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::StreamingDistribution": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::TrustStore": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudFront::VpcOrigin": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudTrail::Channel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudTrail::Dashboard": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudTrail::EventDataStore": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudTrail::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudTrail::Trail": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudWatch::Alarm": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudWatch::AlarmMuteRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudWatch::AnomalyDetector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudWatch::CompositeAlarm": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudWatch::Dashboard": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudWatch::InsightRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CloudWatch::MetricStream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeArtifact::Domain": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CodeArtifact::PackageGroup": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CodeArtifact::Repository": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CodeBuild::Fleet": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CodeBuild::Project": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeBuild::ReportGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeBuild::SourceCredential": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeCommit::Repository": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeConnections::Connection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeDeploy::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeDeploy::DeploymentConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeDeploy::DeploymentGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeGuruProfiler::ProfilingGroup": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CodeGuruReviewer::RepositoryAssociation": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::CodePipeline::CustomActionType": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodePipeline::Pipeline": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodePipeline::Webhook": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeStar::GitHubRepository": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeStarConnections::Connection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeStarConnections::RepositoryLink": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeStarConnections::SyncConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CodeStarNotifications::NotificationRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::IdentityPool": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::IdentityPoolPrincipalTag": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::IdentityPoolRoleAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::LogDeliveryConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::ManagedLoginBranding": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::Terms": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPool": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolClient": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolDomain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolIdentityProvider": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolResourceServer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolRiskConfigurationAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolUICustomizationAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolUser": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Cognito::UserPoolUserToGroupAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Comprehend::DocumentClassifier": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Comprehend::Flywheel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::ComputeOptimizer::AutomationRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::AggregationAuthorization": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::ConfigRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::ConfigurationAggregator": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::ConfigurationRecorder": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::ConformancePack": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::DeliveryChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::OrganizationConfigRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::OrganizationConformancePack": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::RemediationConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Config::StoredQuery": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Connect::AgentStatus": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::ApprovedOrigin": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::ContactFlow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::ContactFlowModule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::ContactFlowModuleAlias": [ + "ap-northeast-1", + "eu-central-1", + "us-west-2", + ], + "AWS::Connect::ContactFlowModuleVersion": [ + "ap-northeast-1", + "eu-central-1", + "us-west-2", + ], + "AWS::Connect::ContactFlowVersion": [ + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::DataTable": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::DataTableAttribute": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::DataTableRecord": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::EmailAddress": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::EvaluationForm": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::HoursOfOperation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::Instance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::InstanceStorageConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::IntegrationAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::Notification": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::PhoneNumber": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::PredefinedAttribute": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::Prompt": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::Queue": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::QuickConnect": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::RoutingProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::Rule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::SecurityKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::SecurityProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::TaskTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::TrafficDistributionGroup": [ + "ap-northeast-1", + "ap-northeast-3", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::User": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::UserHierarchyGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::UserHierarchyStructure": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::View": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::ViewVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Connect::Workspace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::ConnectCampaigns::Campaign": [ + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::ConnectCampaignsV2::Campaign": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::ControlTower::EnabledBaseline": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ControlTower::EnabledControl": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ControlTower::LandingZone": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::CustomerProfiles::CalculatedAttributeDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::CustomerProfiles::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::CustomerProfiles::EventStream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::CustomerProfiles::EventTrigger": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::CustomerProfiles::Integration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::CustomerProfiles::ObjectType": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::CustomerProfiles::SegmentDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::DAX::Cluster": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DAX::ParameterGroup": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DAX::SubnetGroup": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DLM::LifecyclePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::Certificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::DataMigration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::DataProvider": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::Endpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::EventSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::InstanceProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::MigrationProject": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::ReplicationConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::ReplicationInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::ReplicationSubnetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DMS::ReplicationTask": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DSQL::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataBrew::Dataset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataBrew::Job": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataBrew::Project": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataBrew::Recipe": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataBrew::Ruleset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataBrew::Schedule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataPipeline::Pipeline": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::Agent": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationAzureBlob": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationEFS": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationFSxLustre": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationFSxONTAP": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationFSxOpenZFS": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataSync::LocationFSxWindows": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationHDFS": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationNFS": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationObjectStorage": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationS3": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::LocationSMB": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataSync::Task": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DataZone::Connection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::DataSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::DomainUnit": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::Environment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::EnvironmentActions": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::EnvironmentBlueprintConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::EnvironmentProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::FormType": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::GroupProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::Owner": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::PolicyGrant": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::Project": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::ProjectMembership": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::ProjectProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::SubscriptionTarget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DataZone::UserProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::Farm": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::Fleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::LicenseEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::Limit": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::MeteredProduct": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::Monitor": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::Queue": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::QueueEnvironment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::QueueFleetAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::QueueLimitAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Deadline::StorageProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Detective::Graph": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Detective::MemberInvitation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Detective::OrganizationAdmin": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DevOpsAgent::AgentSpace": [ + "us-east-1", + ], + "AWS::DevOpsAgent::Association": [ + "us-east-1", + ], + "AWS::DevOpsAgent::Service": [ + "us-east-1", + ], + "AWS::DevOpsGuru::LogAnomalyDetectionIntegration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DevOpsGuru::NotificationChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DevOpsGuru::ResourceCollection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DeviceFarm::DevicePool": [ + "us-west-2", + ], + "AWS::DeviceFarm::InstanceProfile": [ + "us-west-2", + ], + "AWS::DeviceFarm::NetworkProfile": [ + "us-west-2", + ], + "AWS::DeviceFarm::Project": [ + "us-west-2", + ], + "AWS::DeviceFarm::TestGridProject": [ + "us-west-2", + ], + "AWS::DeviceFarm::VPCEConfiguration": [ + "us-west-2", + ], + "AWS::DirectConnect::Connection": [ + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectConnect::DirectConnectGateway": [ + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectConnect::DirectConnectGatewayAssociation": [ + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectConnect::Lag": [ + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectConnect::PrivateVirtualInterface": [ + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectConnect::PublicVirtualInterface": [ + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectConnect::TransitVirtualInterface": [ + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectoryService::MicrosoftAD": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DirectoryService::SimpleAD": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DocDB::DBCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DocDB::DBClusterParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DocDB::DBInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DocDB::DBSubnetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DocDB::EventSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DocDB::GlobalCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DocDBElastic::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::DynamoDB::GlobalTable": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::DynamoDB::Table": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::CapacityManagerDataExport": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::CapacityReservation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::CapacityReservationFleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::CarrierGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::EC2::ClientVpnAuthorizationRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::ClientVpnEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::ClientVpnRoute": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::ClientVpnTargetNetworkAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::CustomerGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::DHCPOptions": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::EC2Fleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::EIP": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::EIPAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::EgressOnlyInternetGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::EnclaveCertificateIamRoleAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::FlowLog": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::GatewayRouteTableAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::Host": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAM": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAMAllocation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAMPool": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAMPoolCidr": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAMPrefixListResolver": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAMResourceDiscovery": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAMResourceDiscoveryAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IPAMScope": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::Instance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::InstanceConnectEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::InternetGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::IpPoolRouteTableAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::KeyPair": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::LaunchTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::LocalGatewayRoute": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::LocalGatewayRouteTable": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::LocalGatewayRouteTableVPCAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::LocalGatewayRouteTableVirtualInterfaceGroupAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::LocalGatewayVirtualInterface": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::LocalGatewayVirtualInterfaceGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NatGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkAcl": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkAclEntry": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkInsightsAccessScope": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkInsightsAccessScopeAnalysis": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkInsightsAnalysis": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkInsightsPath": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkInterface": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkInterfaceAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkInterfacePermission": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::NetworkPerformanceMetricSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::PlacementGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::PrefixList": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::Route": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::RouteServer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::RouteServerAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::RouteServerEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::RouteServerPeer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::RouteServerPropagation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::RouteTable": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SecurityGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SecurityGroupEgress": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SecurityGroupIngress": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SecurityGroupVpcAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SnapshotBlockPublicAccess": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SpotFleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::Subnet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SubnetCidrBlock": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SubnetNetworkAclAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::SubnetRouteTableAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TrafficMirrorFilter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TrafficMirrorFilterRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TrafficMirrorSession": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TrafficMirrorTarget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayConnect": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayConnectPeer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayMeteringPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayMeteringPolicyEntry": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayMulticastDomain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayMulticastDomainAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayMulticastGroupMember": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayMulticastGroupSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayPeeringAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayRoute": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayRouteTable": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayRouteTableAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayRouteTablePropagation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::TransitGatewayVpcAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPC": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCBlockPublicAccessExclusion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCBlockPublicAccessOptions": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCCidrBlock": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCDHCPOptionsAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCEncryptionControl": [ + "ap-northeast-1", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCEndpointConnectionNotification": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCEndpointService": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCEndpointServicePermissions": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCGatewayAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPCPeeringConnection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPNConcentrator": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPNConnection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPNConnectionRoute": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPNGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VPNGatewayRoutePropagation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VerifiedAccessEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VerifiedAccessGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VerifiedAccessInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VerifiedAccessTrustProvider": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::Volume": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EC2::VolumeAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::PublicRepository": [ + "us-east-1", + ], + "AWS::ECR::PullThroughCacheRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::PullTimeUpdateExclusion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::RegistryPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::RegistryScanningConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::ReplicationConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::Repository": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::RepositoryCreationTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECR::SigningConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::CapacityProvider": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::ClusterCapacityProviderAssociations": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::ExpressGatewayService": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::PrimaryTaskSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::Service": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::TaskDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ECS::TaskSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EFS::AccessPoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EFS::FileSystem": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EFS::MountTarget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::AccessEntry": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::Addon": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::Capability": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::FargateProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::IdentityProviderConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::Nodegroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EKS::PodIdentityAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::InstanceFleetConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::InstanceGroupConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::SecurityConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::Step": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::Studio": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::StudioSessionMapping": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMR::WALWorkspace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMRContainers::Endpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMRContainers::SecurityConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMRContainers::VirtualCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EMRServerless::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EVS::Environment": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::CacheCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::GlobalReplicationGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::ParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::ReplicationGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::SecurityGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::SecurityGroupIngress": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::ServerlessCache": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::SubnetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::User": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElastiCache::UserGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticBeanstalk::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticBeanstalk::ApplicationVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticBeanstalk::ConfigurationTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticBeanstalk::Environment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancing::LoadBalancer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancingV2::Listener": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancingV2::ListenerCertificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancingV2::ListenerRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancingV2::LoadBalancer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancingV2::TargetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancingV2::TrustStore": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ElasticLoadBalancingV2::TrustStoreRevocation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Elasticsearch::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EntityResolution::IdMappingWorkflow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::EntityResolution::IdNamespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::EntityResolution::MatchingWorkflow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::EntityResolution::PolicyStatement": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::EntityResolution::SchemaMapping": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::EventSchemas::Discoverer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EventSchemas::Registry": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EventSchemas::RegistryPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::EventSchemas::Schema": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Events::ApiDestination": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Events::Archive": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Events::Connection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Events::Endpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Events::EventBus": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Events::EventBusPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Events::Rule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Evidently::Experiment": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Evidently::Feature": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Evidently::Launch": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Evidently::Project": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Evidently::Segment": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FIS::ExperimentTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FIS::TargetAccountConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FMS::NotificationChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FMS::Policy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FMS::ResourceSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FSx::DataRepositoryAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FSx::FileSystem": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FSx::S3AccessPointAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FSx::Snapshot": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FSx::StorageVirtualMachine": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FSx::Volume": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::FinSpace::Environment": [ + "ca-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Forecast::Dataset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Forecast::DatasetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FraudDetector::Detector": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FraudDetector::EntityType": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FraudDetector::EventType": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FraudDetector::Label": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FraudDetector::List": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FraudDetector::Outcome": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::FraudDetector::Variable": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::GameLift::Alias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::Build": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::ContainerFleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::GameLift::ContainerGroupDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::GameLift::Fleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::GameServerGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::GameSessionQueue": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::Location": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::MatchmakingConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::MatchmakingRuleSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLift::Script": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GameLiftStreams::Application": [ + "ap-northeast-1", + "eu-central-1", + "us-east-2", + "us-west-2", + ], + "AWS::GameLiftStreams::StreamGroup": [ + "ap-northeast-1", + "eu-central-1", + "us-east-2", + "us-west-2", + ], + "AWS::GammaDilithium::JobDefinition": [ + "ap-southeast-1", + "us-west-1", + ], + "AWS::GlobalAccelerator::Accelerator": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GlobalAccelerator::CrossAccountAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GlobalAccelerator::EndpointGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GlobalAccelerator::Listener": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Classifier": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Connection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Crawler": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::CustomEntityType": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::DataCatalogEncryptionSettings": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::DataQualityRuleset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Database": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::DevEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::IdentityCenterConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Integration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Glue::IntegrationResourceProperty": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Glue::Job": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::MLTransform": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Partition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Registry": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Schema": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::SchemaVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::SchemaVersionMetadata": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::SecurityConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Table": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::TableOptimizer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Glue::Trigger": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::UsageProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Glue::Workflow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Grafana::Workspace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Greengrass::ConnectorDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::ConnectorDefinitionVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::CoreDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::CoreDefinitionVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::DeviceDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::DeviceDefinitionVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::FunctionDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::FunctionDefinitionVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::Group": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::GroupVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::LoggerDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::LoggerDefinitionVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::ResourceDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::ResourceDefinitionVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::SubscriptionDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Greengrass::SubscriptionDefinitionVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GreengrassV2::ComponentVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::GreengrassV2::Deployment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::GroundStation::Config": [ + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::GroundStation::DataflowEndpointGroup": [ + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::GroundStation::DataflowEndpointGroupV2": [ + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::GroundStation::MissionProfile": [ + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::GuardDuty::Detector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::Filter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::IPSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::MalwareProtectionPlan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::Master": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::Member": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::PublishingDestination": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::ThreatEntitySet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::ThreatIntelSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::GuardDuty::TrustedEntitySet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::HealthImaging::Datastore": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::HealthLake::FHIRDatastore": [ + "ap-south-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IAM::AccessKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::Group": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::GroupPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::InstanceProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::ManagedPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::OIDCProvider": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::Policy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::Role": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::RolePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::SAMLProvider": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::ServerCertificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::ServiceLinkedRole": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::User": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::UserPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::UserToGroupAddition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IAM::VirtualMFADevice": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IVS::Channel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::EncoderConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::IngestConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::PlaybackKeyPair": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::PlaybackRestrictionPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::PublicKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::RecordingConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::Stage": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::StorageConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVS::StreamKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVSChat::LoggingConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IVSChat::Room": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IdentityStore::Group": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IdentityStore::GroupMembership": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::Component": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::ContainerRecipe": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::DistributionConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::Image": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::ImagePipeline": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::ImageRecipe": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::InfrastructureConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::LifecyclePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ImageBuilder::Workflow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Inspector::AssessmentTarget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Inspector::AssessmentTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Inspector::ResourceGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::InspectorV2::CisScanConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::InspectorV2::CodeSecurityIntegration": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::InspectorV2::CodeSecurityScanConfiguration": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::InspectorV2::Filter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::InternetMonitor::Monitor": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Invoicing::InvoiceUnit": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::AccountAuditConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::Authorizer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::BillingGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::CACertificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::Certificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::CertificateProvider": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::Command": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::CustomMetric": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::Dimension": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::DomainConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::EncryptionConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::FleetMetric": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::JobTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::Logging": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::MitigationAction": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::Policy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::PolicyPrincipalAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::ProvisioningTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::ResourceSpecificLogging": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::RoleAlias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::ScheduledAudit": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::SecurityProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::SoftwarePackage": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::SoftwarePackageVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::Thing": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::ThingGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::ThingPrincipalAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::ThingType": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::TopicRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoT::TopicRuleDestination": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::IoTAnalytics::Channel": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTAnalytics::Dataset": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTAnalytics::Datastore": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTAnalytics::Pipeline": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTCoreDeviceAdvisor::SuiteDefinition": [ + "ap-northeast-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTEvents::AlarmModel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTEvents::DetectorModel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTEvents::Input": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTFleetWise::Campaign": [ + "ap-south-1", + "eu-central-1", + "us-east-1", + ], + "AWS::IoTFleetWise::DecoderManifest": [ + "ap-south-1", + "eu-central-1", + "us-east-1", + ], + "AWS::IoTFleetWise::Fleet": [ + "ap-south-1", + "eu-central-1", + "us-east-1", + ], + "AWS::IoTFleetWise::ModelManifest": [ + "ap-south-1", + "eu-central-1", + "us-east-1", + ], + "AWS::IoTFleetWise::SignalCatalog": [ + "ap-south-1", + "eu-central-1", + "us-east-1", + ], + "AWS::IoTFleetWise::StateTemplate": [ + "ap-south-1", + "eu-central-1", + "us-east-1", + ], + "AWS::IoTFleetWise::Vehicle": [ + "ap-south-1", + "eu-central-1", + "us-east-1", + ], + "AWS::IoTManagedIntegrations::CredentialLocker": [ + "ca-central-1", + "eu-west-1", + ], + "AWS::IoTManagedIntegrations::ManagedThing": [ + "ca-central-1", + "eu-west-1", + ], + "AWS::IoTManagedIntegrations::ProvisioningProfile": [ + "ca-central-1", + "eu-west-1", + ], + "AWS::IoTSiteWise::AccessPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTSiteWise::Asset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTSiteWise::AssetModel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTSiteWise::ComputationModel": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + ], + "AWS::IoTSiteWise::Dashboard": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTSiteWise::Dataset": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTSiteWise::Gateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTSiteWise::Portal": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTSiteWise::Project": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::IoTThingsGraph::FlowTemplate": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTTwinMaker::ComponentType": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTTwinMaker::Entity": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTTwinMaker::Scene": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTTwinMaker::SyncJob": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTTwinMaker::Workspace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::Destination": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::DeviceProfile": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::FuotaTask": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::MulticastGroup": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::NetworkAnalyzerConfiguration": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::PartnerAccount": [ + "us-east-1", + ], + "AWS::IoTWireless::ServiceProfile": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::TaskDefinition": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::WirelessDevice": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::IoTWireless::WirelessDeviceImportTask": [ + "us-east-1", + ], + "AWS::IoTWireless::WirelessGateway": [ + "ap-northeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::KMS::Alias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KMS::Key": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KMS::ReplicaKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KafkaConnect::Connector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KafkaConnect::CustomPlugin": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KafkaConnect::WorkerConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Kendra::DataSource": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Kendra::Faq": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Kendra::Index": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::KendraRanking::ExecutionPlan": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Kinesis::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Kinesis::Stream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Kinesis::StreamConsumer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisAnalytics::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisAnalytics::ApplicationOutput": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisAnalytics::ApplicationReferenceDataSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisAnalyticsV2::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisAnalyticsV2::ApplicationCloudWatchLoggingOption": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisAnalyticsV2::ApplicationOutput": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisAnalyticsV2::ApplicationReferenceDataSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisFirehose::DeliveryStream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::KinesisVideo::SignalingChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::KinesisVideo::Stream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::LakeFormation::DataCellsFilter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LakeFormation::DataLakeSettings": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LakeFormation::Permissions": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LakeFormation::PrincipalPermissions": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LakeFormation::Resource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LakeFormation::Tag": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LakeFormation::TagAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::Alias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::CapacityProvider": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lambda::CodeSigningConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::EventInvokeConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::EventSourceMapping": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::Function": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::LayerVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::LayerVersionPermission": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::Permission": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::ResourcePolicy": [ + "eu-west-1", + ], + "AWS::Lambda::Url": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lambda::Version": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LaunchWizard::Deployment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lex::Bot": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Lex::BotAlias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Lex::BotVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Lex::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::LicenseManager::Grant": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LicenseManager::License": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Lightsail::Alarm": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::Bucket": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::Certificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::Container": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::Database": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::DatabaseSnapshot": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::Disk": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::DiskSnapshot": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::Distribution": [ + "us-east-1", + ], + "AWS::Lightsail::Domain": [ + "us-east-1", + ], + "AWS::Lightsail::Instance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::InstanceSnapshot": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::LoadBalancer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::LoadBalancerTlsCertificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Lightsail::StaticIp": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Location::APIKey": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Location::GeofenceCollection": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Location::Map": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Location::PlaceIndex": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Location::RouteCalculator": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Location::Tracker": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Location::TrackerConsumer": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Logs::AccountPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::Delivery": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::DeliveryDestination": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::DeliverySource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::Destination": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::Integration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::LogAnomalyDetector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::LogGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::LogStream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::MetricFilter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::QueryDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::ScheduledQuery": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::SubscriptionFilter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Logs::Transformer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::LookoutEquipment::InferenceScheduler": [ + "ap-northeast-2", + "eu-west-1", + "us-east-1", + ], + "AWS::LookoutVision::Project": [ + "ap-northeast-1", + "ap-northeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::M2::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::M2::Deployment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::M2::Environment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MPA::ApprovalTeam": [ + "us-east-1", + ], + "AWS::MPA::IdentitySource": [ + "us-east-1", + ], + "AWS::MSK::BatchScramSecret": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MSK::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MSK::ClusterPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MSK::Configuration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MSK::Replicator": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MSK::ServerlessCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MSK::Topic": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MSK::VpcConnection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MWAA::Environment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MWAAServerless::Workflow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Macie::AllowList": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Macie::CustomDataIdentifier": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Macie::FindingsFilter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Macie::Session": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ManagedBlockchain::Accessor": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + ], + "AWS::ManagedBlockchain::Member": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::ManagedBlockchain::Node": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + ], + "AWS::MediaConnect::Bridge": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::BridgeOutput": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::BridgeSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::Flow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::FlowEntitlement": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::FlowOutput": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::FlowSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::FlowVpcInterface": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::Gateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::RouterInput": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::RouterNetworkInterface": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConnect::RouterOutput": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConvert::JobTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConvert::Preset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaConvert::Queue": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaLive::Channel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::ChannelPlacementGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::CloudWatchAlarmTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::CloudWatchAlarmTemplateGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::EventBridgeRuleTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::EventBridgeRuleTemplateGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::Input": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::MediaLive::InputSecurityGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::Multiplex": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::Multiplexprogram": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::Network": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::SdiSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaLive::SignalMap": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaPackage::Asset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackage::Channel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackage::OriginEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackage::PackagingConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackage::PackagingGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackageV2::Channel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackageV2::ChannelGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackageV2::ChannelPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackageV2::OriginEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaPackageV2::OriginEndpointPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MediaStore::Container": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::MediaTailor::Channel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaTailor::ChannelPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaTailor::LiveSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaTailor::PlaybackConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaTailor::SourceLocation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MediaTailor::VodSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::MemoryDB::ACL": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MemoryDB::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MemoryDB::MultiRegionCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MemoryDB::ParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MemoryDB::SubnetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::MemoryDB::User": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Neptune::DBCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Neptune::DBClusterParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Neptune::DBInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Neptune::DBParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Neptune::DBSubnetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Neptune::EventSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NeptuneGraph::Graph": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NeptuneGraph::PrivateGraphEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkFirewall::Firewall": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkFirewall::FirewallPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkFirewall::LoggingConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkFirewall::RuleGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkFirewall::TLSInspectionConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkFirewall::VpcEndpointAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::ConnectAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::ConnectPeer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::CoreNetwork": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::CoreNetworkPrefixListAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::CustomerGatewayAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::Device": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::DirectConnectGatewayAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::GlobalNetwork": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::Link": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::LinkAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::Site": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::SiteToSiteVpnAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::TransitGatewayPeering": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::TransitGatewayRegistration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::TransitGatewayRouteTableAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NetworkManager::VpcAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::NimbleStudio::Studio": [ + "ap-southeast-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-2", + ], + "AWS::Notifications::ChannelAssociation": [ + "us-east-1", + ], + "AWS::Notifications::EventRule": [ + "us-east-1", + ], + "AWS::Notifications::ManagedNotificationAccountContactAssociation": [ + "us-east-1", + ], + "AWS::Notifications::ManagedNotificationAdditionalChannelAssociation": [ + "us-east-1", + ], + "AWS::Notifications::NotificationConfiguration": [ + "us-east-1", + ], + "AWS::Notifications::NotificationHub": [ + "us-east-1", + ], + "AWS::Notifications::OrganizationalUnitAssociation": [ + "us-east-1", + ], + "AWS::NotificationsContacts::EmailContact": [ + "us-east-1", + ], + "AWS::ODB::CloudAutonomousVmCluster": [ + "ap-northeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::ODB::CloudExadataInfrastructure": [ + "ap-northeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::ODB::CloudVmCluster": [ + "ap-northeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::ODB::OdbNetwork": [ + "ap-northeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::ODB::OdbPeeringConnection": [ + "ap-northeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::OSIS::Pipeline": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Oam::Link": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Oam::Sink": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ObservabilityAdmin::OrganizationCentralizationRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ObservabilityAdmin::OrganizationTelemetryRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ObservabilityAdmin::S3TableIntegration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ObservabilityAdmin::TelemetryPipelines": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ObservabilityAdmin::TelemetryRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Omics::AnnotationStore": [ + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Omics::ReferenceStore": [ + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Omics::RunGroup": [ + "ap-northeast-2", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Omics::SequenceStore": [ + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Omics::VariantStore": [ + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Omics::Workflow": [ + "ap-northeast-2", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Omics::WorkflowVersion": [ + "ap-northeast-2", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::AccessPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::Collection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::CollectionGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::Index": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::LifecyclePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::SecurityConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::SecurityPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchServerless::VpcEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchService::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpenSearchService::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorks::App": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorks::ElasticLoadBalancerAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorks::Instance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorks::Layer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorks::Stack": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorks::UserProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorks::Volume": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::OpsWorksCM::Server": [ + "ap-northeast-1", + ], + "AWS::Organizations::Account": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Organizations::Organization": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Organizations::OrganizationalUnit": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Organizations::Policy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Organizations::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCAConnectorAD::Connector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCAConnectorAD::DirectoryRegistration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCAConnectorAD::ServicePrincipalName": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCAConnectorAD::Template": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCAConnectorAD::TemplateGroupAccessControlEntry": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCAConnectorSCEP::Challenge": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCAConnectorSCEP::Connector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::PCS::Cluster": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::PCS::ComputeNodeGroup": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::PCS::Queue": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Panorama::ApplicationInstance": [ + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::Panorama::Package": [ + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::Panorama::PackageVersion": [ + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::PaymentCryptography::Alias": [ + "ap-northeast-1", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::PaymentCryptography::Key": [ + "ap-northeast-1", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Personalize::Dataset": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Personalize::DatasetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Personalize::Schema": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Personalize::Solution": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Pinpoint::ADMChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::APNSChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::APNSSandboxChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::APNSVoipChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::APNSVoipSandboxChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::App": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::ApplicationSettings": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::BaiduChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::Campaign": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::EmailChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::EmailTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::EventStream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::GCMChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::InAppTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Pinpoint::PushTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::SMSChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::Segment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::SmsTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Pinpoint::VoiceChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::PinpointEmail::ConfigurationSet": [ + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::PinpointEmail::ConfigurationSetEventDestination": [ + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::PinpointEmail::DedicatedIpPool": [ + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::PinpointEmail::Identity": [ + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::Pipes::Pipe": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Proton::EnvironmentAccountConnection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Proton::EnvironmentTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Proton::ServiceTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QBusiness::Application": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QBusiness::DataAccessor": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QBusiness::DataSource": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QBusiness::Index": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QBusiness::Permission": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QBusiness::Plugin": [ + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QBusiness::Retriever": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QBusiness::WebExperience": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QLDB::Ledger": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QLDB::Stream": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::ActionConnector": [ + "ap-southeast-2", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::QuickSight::Analysis": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::CustomPermissions": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::Dashboard": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::DataSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::DataSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::Folder": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::RefreshSchedule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::Template": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::Theme": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::Topic": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::QuickSight::VPCConnection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RAM::Permission": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RAM::ResourceShare": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::CustomDBEngineVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RDS::DBCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBClusterParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBProxy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBProxyEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBProxyTargetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBSecurityGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBSecurityGroupIngress": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBShardGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::DBSubnetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::EventSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::GlobalCluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::Integration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RDS::OptionGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RTBFabric::InboundExternalLink": [ + "ap-northeast-1", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::RTBFabric::Link": [ + "ap-northeast-1", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::RTBFabric::OutboundExternalLink": [ + "ap-northeast-1", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::RTBFabric::RequesterGateway": [ + "ap-northeast-1", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::RTBFabric::ResponderGateway": [ + "ap-northeast-1", + "ap-southeast-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-west-2", + ], + "AWS::RUM::AppMonitor": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Rbin::Rule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::Cluster": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::ClusterParameterGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::ClusterSecurityGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::ClusterSecurityGroupIngress": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::ClusterSubnetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::EndpointAccess": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::EndpointAuthorization": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::EventSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::Integration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Redshift::ScheduledAction": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RedshiftServerless::Namespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RedshiftServerless::Snapshot": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RedshiftServerless::Workgroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RefactorSpaces::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RefactorSpaces::Environment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RefactorSpaces::Route": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RefactorSpaces::Service": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Rekognition::Collection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Rekognition::Project": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Rekognition::StreamProcessor": [ + "ap-northeast-1", + "ap-south-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::ResilienceHub::App": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ResilienceHub::ResiliencyPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ResourceExplorer2::DefaultViewAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ResourceExplorer2::Index": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ResourceExplorer2::View": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ResourceGroups::Group": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ResourceGroups::TagSyncTask": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RoboMaker::Fleet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RoboMaker::Robot": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RoboMaker::RobotApplication": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RoboMaker::RobotApplicationVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RoboMaker::SimulationApplication": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RoboMaker::SimulationApplicationVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::RolesAnywhere::CRL": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RolesAnywhere::Profile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::RolesAnywhere::TrustAnchor": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53::CidrCollection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53::DNSSEC": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53::HealthCheck": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53::HostedZone": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53::KeySigningKey": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53::RecordSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53::RecordSetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Profiles::Profile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Profiles::ProfileAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Profiles::ProfileResourceAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53RecoveryControl::Cluster": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53RecoveryControl::ControlPanel": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53RecoveryControl::RoutingControl": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53RecoveryControl::SafetyRule": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53RecoveryReadiness::Cell": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53RecoveryReadiness::ReadinessCheck": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53RecoveryReadiness::RecoveryGroup": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53RecoveryReadiness::ResourceSet": [ + "us-east-1", + "us-west-2", + ], + "AWS::Route53Resolver::FirewallDomainList": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::FirewallRuleGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::FirewallRuleGroupAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::OutpostResolver": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::ResolverConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::ResolverDNSSECConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::ResolverEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::ResolverQueryLoggingConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::ResolverRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Route53Resolver::ResolverRuleAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::AccessGrant": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::AccessGrantsInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::AccessGrantsLocation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::AccessPoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::Bucket": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::BucketPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::MultiRegionAccessPoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::MultiRegionAccessPointPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::StorageLens": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3::StorageLensGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Express::AccessPoint": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::S3Express::BucketPolicy": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::S3Express::DirectoryBucket": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::S3ObjectLambda::AccessPoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3ObjectLambda::AccessPointPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Outposts::AccessPoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Outposts::Bucket": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Outposts::BucketPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Outposts::Endpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Tables::Namespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Tables::Table": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Tables::TableBucket": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Tables::TableBucketPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Tables::TablePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::S3Vectors::Index": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::S3Vectors::VectorBucket": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::S3Vectors::VectorBucketPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::SDB::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::ConfigurationSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::ConfigurationSetEventDestination": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::ContactList": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::CustomVerificationEmailTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::DedicatedIpPool": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::EmailIdentity": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerAddonInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerAddonSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerAddressList": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerArchive": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerIngressPoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerRelay": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerRuleSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MailManagerTrafficPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::MultiRegionEndpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::ReceiptFilter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::ReceiptRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::ReceiptRuleSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::Template": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::Tenant": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SES::VdmAttributes": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SMSVOICE::ConfigurationSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SMSVOICE::OptOutList": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SMSVOICE::PhoneNumber": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SMSVOICE::Pool": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SMSVOICE::ProtectConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SMSVOICE::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SMSVOICE::SenderId": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SNS::Subscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SNS::Topic": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SNS::TopicInlinePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SNS::TopicPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SQS::Queue": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SQS::QueueInlinePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SQS::QueuePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::Association": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::Document": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::MaintenanceWindow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::MaintenanceWindowTarget": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::MaintenanceWindowTask": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::Parameter": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::PatchBaseline": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::ResourceDataSync": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSM::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMContacts::Contact": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMContacts::ContactChannel": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMContacts::Plan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMContacts::Rotation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMGuiConnect::Preferences": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMIncidents::ReplicationSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMIncidents::ResponsePlan": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMQuickSetup::ConfigurationManager": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSMQuickSetup::LifecycleAutomation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSO::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSO::ApplicationAssignment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSO::Assignment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSO::Instance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSO::InstanceAccessControlAttributeConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SSO::PermissionSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::App": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::AppImageConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Cluster": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::CodeRepository": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::DataQualityJobDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Device": [ + "ap-northeast-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::SageMaker::DeviceFleet": [ + "ap-northeast-1", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::SageMaker::Domain": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Endpoint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::EndpointConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::FeatureGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Image": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ImageVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::InferenceComponent": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::SageMaker::InferenceExperiment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::MlflowTrackingServer": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Model": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ModelBiasJobDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ModelCard": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ModelExplainabilityJobDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ModelPackage": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ModelPackageGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ModelQualityJobDefinition": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::MonitoringSchedule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::NotebookInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::NotebookInstanceLifecycleConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::PartnerApp": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Pipeline": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::ProcessingJob": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Project": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Space": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::StudioLifecycleConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::UserProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SageMaker::Workteam": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Scheduler::Schedule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Scheduler::ScheduleGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecretsManager::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecretsManager::RotationSchedule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecretsManager::Secret": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecretsManager::SecretTargetAttachment": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::AggregatorV2": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::AutomationRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::AutomationRuleV2": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::ConfigurationPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::ConnectorV2": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::DelegatedAdmin": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::FindingAggregator": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::Hub": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::HubV2": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::Insight": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::OrganizationConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::PolicyAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::ProductSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::SecurityControl": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityHub::Standard": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityLake::AwsLogSource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityLake::DataLake": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityLake::Subscriber": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SecurityLake::SubscriberNotification": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::AcceptedPortfolioShare": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::CloudFormationProduct": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::CloudFormationProvisionedProduct": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::LaunchNotificationConstraint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::LaunchRoleConstraint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::LaunchTemplateConstraint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::Portfolio": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::PortfolioPrincipalAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::PortfolioProductAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::PortfolioShare": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::ResourceUpdateConstraint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::ServiceAction": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::ServiceActionAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::StackSetConstraint": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::TagOption": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalog::TagOptionAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalogAppRegistry::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalogAppRegistry::AttributeGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceCatalogAppRegistry::ResourceAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceDiscovery::HttpNamespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceDiscovery::Instance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceDiscovery::PrivateDnsNamespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceDiscovery::PublicDnsNamespace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::ServiceDiscovery::Service": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Shield::DRTAccess": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Shield::ProactiveEngagement": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Shield::Protection": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Shield::ProtectionGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Signer::ProfilePermission": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Signer::SigningProfile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SimSpaceWeaver::Simulation": [ + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::StepFunctions::Activity": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::StepFunctions::StateMachine": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::StepFunctions::StateMachineAlias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::StepFunctions::StateMachineVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SupportApp::AccountAlias": [ + "ap-northeast-1", + "ap-southeast-1", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::SupportApp::SlackChannelConfiguration": [ + "ap-northeast-1", + "ap-southeast-1", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::SupportApp::SlackWorkspaceConfiguration": [ + "ap-northeast-1", + "ap-southeast-1", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Synthetics::Canary": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Synthetics::Group": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::SystemsManagerSAP::Application": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Timestream::Database": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Timestream::InfluxDBCluster": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Timestream::InfluxDBInstance": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Timestream::ScheduledQuery": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Timestream::Table": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-2", + ], + "AWS::Transfer::Agreement": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Transfer::Certificate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Transfer::Connector": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Transfer::Profile": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Transfer::Server": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Transfer::User": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Transfer::WebApp": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Transfer::Workflow": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VerifiedPermissions::IdentitySource": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VerifiedPermissions::Policy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VerifiedPermissions::PolicyStore": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VerifiedPermissions::PolicyTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VoiceID::Domain": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::VpcLattice::AccessLogSubscription": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::AuthPolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::DomainVerification": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::Listener": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::ResourceConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::ResourceGateway": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::Rule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::Service": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::ServiceNetwork": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::ServiceNetworkResourceAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::ServiceNetworkServiceAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::ServiceNetworkVpcAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::VpcLattice::TargetGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAF::ByteMatchSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAF::IPSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAF::Rule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAF::SizeConstraintSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAF::SqlInjectionMatchSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAF::WebACL": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAF::XssMatchSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::ByteMatchSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::GeoMatchSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::IPSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::RateBasedRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::RegexPatternSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::Rule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::SizeConstraintSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::SqlInjectionMatchSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::WebACL": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::WebACLAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFRegional::XssMatchSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFv2::IPSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFv2::LoggingConfiguration": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFv2::RegexPatternSet": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFv2::RuleGroup": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFv2::WebACL": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WAFv2::WebACLAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::Wisdom::AIAgent": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::AIAgentVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::AIGuardrail": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::AIGuardrailVersion": [ + "ap-northeast-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::AIPrompt": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::AIPromptVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::Assistant": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::AssistantAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::KnowledgeBase": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::MessageTemplate": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::MessageTemplateVersion": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::Wisdom::QuickResponse": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpaces::ConnectionAlias": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpaces::Workspace": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::WorkSpaces::WorkspacesPool": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesThinClient::Environment": [ + "ap-south-1", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::BrowserSettings": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::DataProtectionSettings": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::IdentityProvider": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::IpAccessSettings": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::NetworkSettings": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::Portal": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::SessionLogger": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::TrustStore": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::UserAccessLoggingSettings": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkSpacesWeb::UserSettings": [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-west-2", + ], + "AWS::WorkspacesInstances::Volume": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::WorkspacesInstances::VolumeAssociation": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::WorkspacesInstances::WorkspaceInstance": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-west-2", + ], + "AWS::XRay::Group": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::XRay::ResourcePolicy": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::XRay::SamplingRule": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "AWS::XRay::TransactionSearchConfig": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], + "Alexa::ASK::Skill": [ + "ap-northeast-1", + "ap-northeast-2", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ], +} + +AWS_CFN_REGIONS_SCANNED = { + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", +} diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/CloudformationSchema.zip b/localstack-core/localstack/services/cloudformation/scaffolding/CloudformationSchema.zip deleted file mode 100644 index f9c8e2f6dbf4d..0000000000000 Binary files a/localstack-core/localstack/services/cloudformation/scaffolding/CloudformationSchema.zip and /dev/null differ diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py b/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py index d6eb97f8dbbf1..88888eaff9183 100644 --- a/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py +++ b/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py @@ -2,13 +2,17 @@ import json import os +import subprocess +import sys import zipfile +from collections.abc import Generator from dataclasses import dataclass from enum import Enum, auto from functools import reduce from pathlib import Path -from typing import Any, Generator, Literal, Optional, TypedDict, TypeVar +from typing import Any, Literal, TypedDict, TypeVar +import boto3 import click from jinja2 import Environment, FileSystemLoader from yaml import safe_dump @@ -40,12 +44,12 @@ def Syntax(text: str, *args, **kwargs) -> str: class Property(TypedDict): - type: Optional[Literal["str"]] - items: Optional[dict] + type: Literal["str"] | None + items: dict | None class HandlerDefinition(TypedDict): - permissions: Optional[list[str]] + permissions: list[str] | None class HandlersDefinition(TypedDict): @@ -58,8 +62,8 @@ class HandlersDefinition(TypedDict): class ResourceSchema(TypedDict): typeName: str - description: Optional[str] - required: Optional[list[str]] + description: str | None + required: list[str] | None properties: dict[str, Property] handlers: HandlersDefinition @@ -139,14 +143,76 @@ def schema(self, resource_name: ResourceName) -> ResourceSchema: ) from e +class LiveSchemaProvider: + """ + Provides CloudFormation resource schemas by fetching them from the live AWS CloudFormation service, rather than + a local zip file. + """ + + def __init__(self, cfn_client): + self.cfn_client = cfn_client + + def available_schemas(self, pattern: str) -> list[str]: + """ + Return the names of available CloudFormation resource types. `pattern` should be something like + AWS::S3::Bucket or AWS::S3::*, depending on whether you want all resources for a service or a specific one. + The result is a list of matching resource type names (e.g. [AWS::S3::Bucket, AWS::S3::Object, ...]) + """ + + is_wildcard = pattern.endswith("*") + pattern = pattern[:-1] if is_wildcard else pattern + matching_names = [] + + params = { + "Visibility": "PUBLIC", + "Type": "RESOURCE", + "DeprecatedStatus": "LIVE", + "Filters": {"Category": "AWS_TYPES", "TypeNamePrefix": pattern}, + } + next_token: str | None = None + + # Note: pagination is necessary since list_types requires multiple calls even to get a single result. + while True: + if next_token: + params["NextToken"] = next_token + response = self.cfn_client.list_types(**params) + + # collect any matching type names (if wildcard, all; else exact match only) + matching_names.extend( + [ + type_summary["TypeName"] + for type_summary in response.get("TypeSummaries", []) + if (is_wildcard or type_summary["TypeName"] == pattern) + ] + ) + + next_token = response.get("NextToken") + if not next_token: + break + + return matching_names + + def schema(self, type_name: ResourceName) -> ResourceSchema: + """ + Given a CloudFormation ResourceName (representing something like "AWS::S3::Bucket"), return the resource + schema as dict. + """ + response = self.cfn_client.describe_type( + Type="RESOURCE", + TypeName=type_name.full_name, + ) + schema_str = response.get("Schema") + if not schema_str: + raise click.ClickException( + f"Could not fetch schema for CloudFormation resource type: {type_name}" + ) + return json.loads(schema_str) + + LOCALSTACK_ROOT_DIR = Path(__file__).parent.joinpath("../../../../..").resolve() -LOCALSTACK_PRO_ROOT_DIR = LOCALSTACK_ROOT_DIR.joinpath("../localstack-ext").resolve() -TESTS_ROOT_DIR = LOCALSTACK_ROOT_DIR.joinpath( - "tests/aws/services/cloudformation/resource_providers" -) -TESTS_PRO_ROOT_DIR = LOCALSTACK_PRO_ROOT_DIR.joinpath( - "localstack-pro-core/tests/aws/services/cloudformation/resource_providers" -) +LOCALSTACK_PRO_ROOT_DIR = LOCALSTACK_ROOT_DIR.joinpath("../localstack-pro").resolve() +TESTS_ROOT_DIR = LOCALSTACK_ROOT_DIR.joinpath("tests/aws/services") +TESTS_PRO_ROOT_DIR = LOCALSTACK_PRO_ROOT_DIR.joinpath("localstack-pro-core/tests/aws/services") assert LOCALSTACK_ROOT_DIR.is_dir(), f"{LOCALSTACK_ROOT_DIR} does not exist" assert LOCALSTACK_PRO_ROOT_DIR.is_dir(), f"{LOCALSTACK_PRO_ROOT_DIR} does not exist" @@ -171,7 +237,7 @@ def tests_root_dir(pro: bool = False) -> Path: def template_path( resource_name: ResourceName, file_type: FileType, - root: Optional[Path] = None, + root: Path | None = None, pro: bool = False, ) -> Path: """ @@ -192,7 +258,7 @@ def template_path( output_path = ( tests_root_dir(pro) .joinpath( - f"{resource_name.python_compatible_service_name.lower()}/{resource_name.path_compatible_full_name()}/templates/{stub}" + f"{resource_name.python_compatible_service_name.lower()}/resource_providers/templates/{stub}" ) .resolve() ) @@ -201,7 +267,7 @@ def template_path( test_path = ( root_dir(pro) .joinpath( - f"tests/aws/cloudformation/resource_providers/{resource_name.python_compatible_service_name.lower()}/{resource_name.path_compatible_full_name()}" + f"tests/aws/{resource_name.python_compatible_service_name.lower()}/resource_providers/templates" ) .resolve() ) @@ -216,6 +282,7 @@ class FileType(Enum): # service code plugin = auto() provider = auto() + provider_base = auto() # test files integration_test = auto() @@ -261,21 +328,22 @@ def render( template_mapping = { FileType.plugin: "plugin_template.py.j2", FileType.provider: "provider_template.py.j2", + FileType.provider_base: "provider_base_template.py.j2", FileType.getatt_test: "test_getatt_template.py.j2", FileType.integration_test: "test_integration_template.py.j2", # FileType.cloudcontrol_test: "test_cloudcontrol_template.py.j2", FileType.parity_test: "test_parity_template.py.j2", } - kwargs = dict( - name=resource_name.full_name, # AWS::SNS::Topic - resource=resource_name.provider_name(), # SNSTopic - scaffolding_version=f"v{SCAFFOLDING_VERSION}", - ) + kwargs = { + "name": resource_name.full_name, # AWS::SNS::Topic + "resource": resource_name.provider_name(), # SNSTopic + "scaffolding_version": f"v{SCAFFOLDING_VERSION}", + } # TODO: we might want to segregate each provider in its own directory # e.g. .../resource_providers/aws_iam_role/test_X.py vs. .../resource_providers/iam/test_X.py # add extra parameters tests_output_path = root_dir(self.pro).joinpath( - f"tests/aws/cloudformation/resource_providers/{resource_name.python_compatible_service_name.lower()}/{resource_name.full_name.lower()}" + f"tests/aws/{resource_name.python_compatible_service_name.lower()}/resource_providers/templates" ) match file_type: case FileType.getatt_test: @@ -283,9 +351,11 @@ def render( kwargs["service"] = resource_name.service.lower() kwargs["resource"] = resource_name.resource.lower() kwargs["template_path"] = str( - template_path(resource_name, FileType.attribute_template, tests_output_path) + template_path( + resource_name, FileType.attribute_template, tests_output_path, pro=self.pro + ) ) - case FileType.provider: + case FileType.provider | FileType.provider_base: property_ir = generate_ir_for_type( [self.schema], resource_name.full_name, @@ -311,23 +381,34 @@ def render( kwargs["list_permissions"] = ( self.schema.get("handlers", {}).get("list", {}).get("permissions") ) + kwargs["service"] = resource_name.python_compatible_service_name.lower() + kwargs["lower_resource"] = resource_name.resource.lower() + kwargs["pro"] = self.pro case FileType.plugin: kwargs["service"] = resource_name.python_compatible_service_name.lower() kwargs["lower_resource"] = resource_name.resource.lower() kwargs["pro"] = self.pro case FileType.integration_test: kwargs["black_box_template_path"] = str( - template_path(resource_name, FileType.minimal_template, tests_output_path) + template_path( + resource_name, FileType.minimal_template, tests_output_path, pro=self.pro + ) ) kwargs["update_template_path"] = str( template_path( resource_name, FileType.update_without_replacement_template, tests_output_path, + pro=self.pro, ) ) kwargs["autogenerated_template_path"] = str( - template_path(resource_name, FileType.autogenerated_template, tests_output_path) + template_path( + resource_name, + FileType.autogenerated_template, + tests_output_path, + pro=self.pro, + ) ) # case FileType.cloudcontrol_test: case FileType.parity_test: @@ -339,7 +420,7 @@ def render( self.environment, template_mapping[file_type], **kwargs ) - def get_getatt_targets(self) -> Generator[str, None, None]: + def get_getatt_targets(self) -> Generator[str]: for name, defn in self.schema["properties"].items(): if "type" in defn and defn["type"] in ["string"]: yield name @@ -514,11 +595,20 @@ def __init__( "resource_providers", f"{self.resource_name.namespace.lower()}_{self.resource_name.service.lower()}_{self.resource_name.resource.lower()}.py", ), + FileType.provider_base: root_dir(self.pro).joinpath( + *base_path, + "services", + self.resource_name.python_compatible_service_name.lower(), + "resource_providers", + "generated", + f"{self.resource_name.namespace.lower()}_{self.resource_name.service.lower()}_{self.resource_name.resource.lower()}_base.py", + ), FileType.plugin: root_dir(self.pro).joinpath( *base_path, "services", self.resource_name.python_compatible_service_name.lower(), "resource_providers", + "generated", f"{self.resource_name.namespace.lower()}_{self.resource_name.service.lower()}_{self.resource_name.resource.lower()}_plugin.py", ), FileType.schema: root_dir(self.pro).joinpath( @@ -526,24 +616,29 @@ def __init__( "services", self.resource_name.python_compatible_service_name.lower(), "resource_providers", + "generated", f"aws_{self.resource_name.service.lower()}_{self.resource_name.resource.lower()}.schema.json", ), FileType.integration_test: tests_root_dir(self.pro).joinpath( self.resource_name.python_compatible_service_name.lower(), + "resource_providers", self.resource_name.path_compatible_full_name(), "test_basic.py", ), FileType.getatt_test: tests_root_dir(self.pro).joinpath( self.resource_name.python_compatible_service_name.lower(), + "resource_providers", self.resource_name.path_compatible_full_name(), "test_exploration.py", ), # FileType.cloudcontrol_test: tests_root_dir(self.pro).joinpath( # self.resource_name.python_compatible_service_name.lower(), + # "resource_providers", # f"test_aws_{self.resource_name.service.lower()}_{self.resource_name.resource.lower()}_cloudcontrol.py", # ), FileType.parity_test: tests_root_dir(self.pro).joinpath( self.resource_name.python_compatible_service_name.lower(), + "resource_providers", self.resource_name.path_compatible_full_name(), "test_parity.py", ), @@ -557,7 +652,9 @@ def __init__( FileType.autogenerated_template, ] for template_type in templates: - self.destination_files[template_type] = template_path(self.resource_name, template_type) + self.destination_files[template_type] = template_path( + self.resource_name, template_type, pro=self.pro + ) def write(self, file_type: FileType, contents: str): file_destination = self.destination_files[file_type] @@ -576,6 +673,10 @@ def write(self, file_type: FileType, contents: str): self.ensure_python_init_files(destination_path) self.write_text(contents, file_destination) self.console.print(f"Written provider to {file_destination}") + case FileType.provider_base: + self.ensure_python_init_files(destination_path) + self.write_text(contents, file_destination) + self.console.print(f"Written provider base to {file_destination}") case FileType.plugin: self.ensure_python_init_files(destination_path) self.write_text(contents, file_destination) @@ -627,13 +728,25 @@ def confirm_overwrite(self, destination_file: Path) -> bool: :return True if file should be (over-)written, False otherwise """ - return self.overwrite or click.confirm("Destination files already exist, overwrite?") + return self.overwrite or click.confirm( + f"Destination file {destination_file} already exists, overwrite?" + ) @staticmethod def write_text(contents: str, destination: Path): with destination.open("wt") as outfile: print(contents, file=outfile) + # for Python files, use ruff to clean up formatting errors introduced by scaffolding + if destination.suffix == ".py": + command = [sys.executable, "-m", "ruff", "format", destination] + try: + subprocess.run(command, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as e: + print( + f"Ruff fix command failed (exit code {e.returncode}):\n{e.stdout}\n{e.stderr}" + ) + @staticmethod def ensure_python_init_files(path: Path): """ @@ -697,6 +810,9 @@ def print(self): case FileType.provider: self.printer.print("\n[underline]Provider template[/underline]\n") self.printer.print(Syntax(self.contents, "python")) + case FileType.provider_base: + self.printer.print("\n[underline]Provider base template[/underline]\n") + self.printer.print(Syntax(self.contents, "python")) case FileType.plugin: self.printer.print("\n[underline]Plugin[/underline]\n") self.printer.print(Syntax(self.contents, "python")) @@ -762,21 +878,14 @@ def generate( console = Console() console.rule(title=resource_type) - schema_provider = SchemaProvider( - zipfile_path=Path(__file__).parent.joinpath("CloudformationSchema.zip") - ) + schema_provider = LiveSchemaProvider(boto3.client("cloudformation")) template_root = Path(__file__).parent.joinpath("templates") env = Environment( loader=FileSystemLoader(template_root), ) - parts = resource_type.rpartition("::") - if parts[-1] == "*": - # generate all resource types for that service - matching_resources = [x for x in schema_provider.schemas.keys() if x.startswith(parts[0])] - else: - matching_resources = [resource_type] + matching_resources = schema_provider.available_schemas(resource_type) for matching_resource in matching_resources: console.rule(title=matching_resource) @@ -813,7 +922,7 @@ def generate( ) console.print("\nWondering where to get started?") console.print( - "First run `make entrypoints` to make sure your resource provider plugin is actually registered." + "First run `make entrypoints` to update the plux.ini file with your new resource provider plugin." ) console.print( 'Then start off by finalizing the generated minimal ("basic") template and get it to deploy against AWS.' diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py b/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py index 6a7e90166b490..626f0a9b7a82e 100644 --- a/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py +++ b/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py @@ -7,7 +7,7 @@ import logging import textwrap from dataclasses import dataclass -from typing import Optional, TypedDict +from typing import TypedDict LOG = logging.getLogger(__name__) @@ -86,7 +86,7 @@ class Schema(TypedDict): properties: dict definitions: dict typeName: str - required: Optional[list[str]] + required: list[str] | None TYPE_MAP = { diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/templates/plugin_template.py.j2 b/localstack-core/localstack/services/cloudformation/scaffolding/templates/plugin_template.py.j2 index 0a9a530cdfccc..6f5b7da7a5a16 100644 --- a/localstack-core/localstack/services/cloudformation/scaffolding/templates/plugin_template.py.j2 +++ b/localstack-core/localstack/services/cloudformation/scaffolding/templates/plugin_template.py.j2 @@ -1,3 +1,6 @@ +# +# AUTOGENERATED FILE - DO NOT EDIT +# from typing import Optional, Type from localstack.services.cloudformation.resource_provider import ResourceProvider diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/templates/provider_base_template.py.j2 b/localstack-core/localstack/services/cloudformation/scaffolding/templates/provider_base_template.py.j2 new file mode 100644 index 0000000000000..f25cf665d6a35 --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/scaffolding/templates/provider_base_template.py.j2 @@ -0,0 +1,139 @@ +# LocalStack Resource Provider Base Class Scaffolding {{ scaffolding_version }} +# +# AUTOGENERATED FILE - DO NOT EDIT +# + +from __future__ import annotations + +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Optional, TypedDict + +import localstack.services.cloudformation.provider_utils as util +from localstack.services.cloudformation.resource_provider import ( + ProgressEvent, + ResourceProvider, + ResourceRequest, +) + +{{ provider_properties }} + +REPEATED_INVOCATION = "repeated_invocation" + +class {{ resource }}ProviderBase(ResourceProvider[{{ resource }}Properties], ABC): + + TYPE = "{{ name }}" # Autogenerated. Don't change + SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change + + @abstractmethod + def create( + self, + request: ResourceRequest[{{ resource }}Properties], + ) -> ProgressEvent[{{ resource }}Properties]: + """ + Create a new resource. + + {% if primary_identifier -%} + Primary identifier fields: + {%- for property in primary_identifier %} + - {{ property }} + {%- endfor %} + {%- endif %} + + {% if required_properties -%} + Required properties: + {%- for property in required_properties %} + - {{ property }} + {%- endfor %} + {%- endif %} + + {% if create_only_properties -%} + Create-only properties: + {%- for property in create_only_properties %} + - {{ property }} + {%- endfor %} + {%- endif %} + + {% if read_only_properties -%} + Read-only properties: + {%- for property in read_only_properties %} + - {{ property }} + {%- endfor %} + {%- endif %} + + {% if create_permissions -%} + IAM permissions required: + {%- for permission in create_permissions %} + - {{ permission }} + {%- endfor -%} + {%- endif %} + + """ + raise NotImplementedError + + @abstractmethod + def read( + self, + request: ResourceRequest[{{ resource }}Properties], + ) -> ProgressEvent[{{ resource }}Properties]: + """ + Fetch resource information + + {% if read_permissions -%} + IAM permissions required: + {%- for permission in read_permissions %} + - {{ permission }} + {%- endfor %} + {%- endif %} + """ + raise NotImplementedError + + @abstractmethod + def delete( + self, + request: ResourceRequest[{{ resource }}Properties], + ) -> ProgressEvent[{{ resource }}Properties]: + """ + Delete a resource + + {% if delete_permissions -%} + IAM permissions required: + {%- for permission in delete_permissions %} + - {{ permission }} + {%- endfor %} + {%- endif %} + """ + raise NotImplementedError + + @abstractmethod + def update( + self, + request: ResourceRequest[{{ resource }}Properties], + ) -> ProgressEvent[{{ resource }}Properties]: + """ + Update a resource + + {% if update_permissions -%} + IAM permissions required: + {%- for permission in update_permissions %} + - {{ permission }} + {%- endfor %} + {%- endif %} + """ + raise NotImplementedError + + @abstractmethod + def list( + self, + request: ResourceRequest[{{ resource }}Properties], + ) -> ProgressEvent[{{ resource }}Properties]: + """ + List available resources of this type + {% if list_permissions -%} + IAM permissions required: + {%- for permission in list_permissions %} + - {{ permission }} + {%- endfor %} + {%- endif %} + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/templates/provider_template.py.j2 b/localstack-core/localstack/services/cloudformation/scaffolding/templates/provider_template.py.j2 index 3d52dbd6b7a83..81d81cec10eab 100644 --- a/localstack-core/localstack/services/cloudformation/scaffolding/templates/provider_template.py.j2 +++ b/localstack-core/localstack/services/cloudformation/scaffolding/templates/provider_template.py.j2 @@ -1,26 +1,22 @@ # LocalStack Resource Provider Scaffolding {{ scaffolding_version }} -from __future__ import annotations -from pathlib import Path -from typing import Optional, TypedDict - -import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( OperationStatus, ProgressEvent, - ResourceProvider, ResourceRequest, ) +{%- if pro %} +{%- set root_module = "localstack.pro.core" %} +{%- else %} +{%- set root_module = "localstack" %} +{%- endif %} +from {{ root_module }}.services.{{ service }}.resource_providers.generated.aws_{{ service }}_{{ lower_resource }}_base import ( + {{ resource }}ProviderBase, + {{ resource }}Properties, + REPEATED_INVOCATION +) -{{ provider_properties }} - - -REPEATED_INVOCATION = "repeated_invocation" - -class {{ resource }}Provider(ResourceProvider[{{ resource }}Properties]): - - TYPE = "{{ name }}" # Autogenerated. Don't change - SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change +class {{ resource }}Provider({{ resource }}ProviderBase): def create( self, @@ -63,7 +59,6 @@ class {{ resource }}Provider(ResourceProvider[{{ resource }}Properties]): - {{ permission }} {%- endfor -%} {%- endif %} - """ model = request.desired_state @@ -136,3 +131,18 @@ class {{ resource }}Provider(ResourceProvider[{{ resource }}Properties]): {%- endif %} """ raise NotImplementedError + + def list( + self, + request: ResourceRequest[{{ resource }}Properties], + ) -> ProgressEvent[{{ resource }}Properties]: + """ + List available resources of this type + {% if list_permissions -%} + IAM permissions required: + {%- for permission in list_permissions %} + - {{ permission }} + {%- endfor %} + {%- endif %} + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/cloudformation/service_models.py b/localstack-core/localstack/services/cloudformation/service_models.py index aeadbeb85f305..e89567730da59 100644 --- a/localstack-core/localstack/services/cloudformation/service_models.py +++ b/localstack-core/localstack/services/cloudformation/service_models.py @@ -13,8 +13,8 @@ class DependencyNotYetSatisfied(Exception): """Exception indicating that a resource dependency is not (yet) deployed/available.""" def __init__(self, resource_ids, message=None): - message = message or "Unresolved dependencies: %s" % resource_ids - super(DependencyNotYetSatisfied, self).__init__(message) + message = message or f"Unresolved dependencies: {resource_ids}" + super().__init__(message) resource_ids = resource_ids if isinstance(resource_ids, list) else [resource_ids] self.resource_ids = resource_ids diff --git a/localstack-core/localstack/services/cloudformation/stores.py b/localstack-core/localstack/services/cloudformation/stores.py index 7191f5491b4e1..994c277c76305 100644 --- a/localstack-core/localstack/services/cloudformation/stores.py +++ b/localstack-core/localstack/services/cloudformation/stores.py @@ -1,10 +1,10 @@ import logging -from typing import Optional -from localstack.aws.api.cloudformation import StackStatus +from localstack.aws.api.cloudformation import Export, StackStatus from localstack.services.cloudformation.engine.entities import Stack, StackChangeSet, StackSet from localstack.services.cloudformation.v2.entities import ChangeSet as ChangeSetV2 from localstack.services.cloudformation.v2.entities import Stack as StackV2 +from localstack.services.cloudformation.v2.entities import StackSet as StackSetV2 from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute LOG = logging.getLogger(__name__) @@ -19,35 +19,49 @@ class CloudFormationStore(BaseStore): # maps stack set ID to stack set details stack_sets: dict[str, StackSet] = LocalAttribute(default=dict) + stack_sets_v2: dict[str, StackSetV2] = LocalAttribute(default=dict) # maps macro ID to macros macros: dict[str, dict] = LocalAttribute(default=dict) - # exports: dict[str, str] @property - def exports(self): - exports = [] - output_keys = {} + def exports(self) -> dict[str, Export]: + exports = {} + for stack_id, stack in self.stacks.items(): + if stack.status == StackStatus.DELETE_COMPLETE: + continue + for output in stack.resolved_outputs: export_name = output.get("ExportName") if not export_name: continue - if export_name in output_keys: + if export_name in exports.keys(): # TODO: raise exception on stack creation in case of duplicate exports LOG.warning( "Found duplicate export name %s in stacks: %s %s", export_name, - output_keys[export_name], + output["OutputValue"], stack.stack_id, ) - entry = { - "ExportingStackId": stack.stack_id, - "Name": export_name, - "Value": output["OutputValue"], - } - exports.append(entry) - output_keys[export_name] = stack.stack_id + exports[export_name] = Export( + ExportingStackId=stack.stack_id, Name=export_name, Value=output["OutputValue"] + ) + + return exports + + @property + def exports_v2(self) -> dict[str, Export]: + exports = {} + stacks_v2 = self.stacks_v2.values() + for stack in stacks_v2: + if stack.status == StackStatus.DELETE_COMPLETE: + continue + for export_name, export_value in stack.resolved_exports.items(): + exports[export_name] = Export( + ExportingStackId=stack.stack_id, Name=export_name, Value=export_value + ) + return exports @@ -112,9 +126,9 @@ def find_change_set( account_id: str, region_name: str, cs_name: str, - stack_name: Optional[str] = None, + stack_name: str | None = None, active_only: bool = False, -) -> Optional[StackChangeSet]: +) -> StackChangeSet | None: store = get_cloudformation_store(account_id, region_name) for stack in store.stacks.values(): if active_only and stack.status == StackStatus.DELETE_COMPLETE: @@ -126,9 +140,6 @@ def find_change_set( return None -def exports_map(account_id: str, region_name: str): - result = {} +def exports_map(account_id: str, region_name: str) -> dict[str, Export]: store = get_cloudformation_store(account_id, region_name) - for export in store.exports: - result[export["Name"]] = export - return result + return {**store.exports, **store.exports_v2} diff --git a/localstack-core/localstack/services/cloudformation/v2/entities.py b/localstack-core/localstack/services/cloudformation/v2/entities.py index 0d44ae1276ade..bb6101a426c8f 100644 --- a/localstack-core/localstack/services/cloudformation/v2/entities.py +++ b/localstack-core/localstack/services/cloudformation/v2/entities.py @@ -1,86 +1,100 @@ -from datetime import datetime, timezone -from typing import NotRequired, Optional, TypedDict +from datetime import UTC, datetime +from typing import NotRequired, TypedDict from localstack.aws.api.cloudformation import ( + Capability, ChangeSetStatus, ChangeSetType, CreateChangeSetInput, CreateStackInput, + CreateStackSetInput, ExecutionStatus, Output, - Parameter, ResourceStatus, - StackDriftInformation, - StackDriftStatus, StackEvent, + StackInstanceComprehensiveStatus, + StackInstanceDetailedStatus, + StackInstanceStatus, StackResource, + StackSetOperation, StackStatus, StackStatusReason, + Tag, ) from localstack.aws.api.cloudformation import ( - Stack as ApiStack, + Parameter as ApiParameter, ) from localstack.services.cloudformation.engine.entities import ( - StackIdentifier, + StackIdentifierV2, ) from localstack.services.cloudformation.engine.v2.change_set_model import ( + ChangeType, UpdateModel, ) +from localstack.services.cloudformation.v2.types import EngineParameter, ResolvedResource from localstack.utils.aws import arns from localstack.utils.strings import long_uid, short_uid -class ResolvedResource(TypedDict): - Type: str - Properties: dict - - class Stack: stack_name: str - parameters: list[Parameter] + description: str | None + parameters: list[ApiParameter] change_set_id: str | None + change_set_ids: set[str] status: StackStatus status_reason: StackStatusReason | None stack_id: str creation_time: datetime deletion_time: datetime | None - events = list[StackEvent] + events: list[StackEvent] + capabilities: list[Capability] + enable_termination_protection: bool + template: dict | None + processed_template: dict | None + template_body: str | None + tags: list[Tag] # state after deploy - resolved_parameters: dict[str, str] + resolved_parameters: dict[str, EngineParameter] resolved_resources: dict[str, ResolvedResource] - resolved_outputs: dict[str, str] + resolved_outputs: list[Output] resource_states: dict[str, StackResource] + resolved_exports: dict[str, str] def __init__( self, account_id: str, region_name: str, request_payload: CreateChangeSetInput | CreateStackInput, - template: dict | None = None, - template_body: str | None = None, + initial_status: StackStatus = StackStatus.CREATE_IN_PROGRESS, + tags: list[Tag] | None = None, ): self.account_id = account_id self.region_name = region_name - self.template = template - self.template_body = template_body - self.status = StackStatus.CREATE_IN_PROGRESS + self.status = initial_status self.status_reason = None - self.change_set_ids = [] - self.creation_time = datetime.now(tz=timezone.utc) + self.change_set_ids = set() + self.creation_time = datetime.now(tz=UTC) self.deletion_time = None self.change_set_id = None + self.enable_termination_protection = False + self.template = None + self.processed_template = None + self.template_body = None + self.tags = tags or [] self.stack_name = request_payload["StackName"] self.parameters = request_payload.get("Parameters", []) self.stack_id = arns.cloudformation_stack_arn( self.stack_name, - stack_id=StackIdentifier( + stack_id=StackIdentifierV2( account_id=self.account_id, region=self.region_name, stack_name=self.stack_name ).generate(tags=request_payload.get("Tags")), account_id=self.account_id, region_name=self.region_name, ) + self.capabilities = request_payload.get("Capabilities", []) or [] # TODO: only kept for v1 compatibility self.request_payload = request_payload @@ -88,16 +102,24 @@ def __init__( # state after deploy self.resolved_parameters = {} self.resolved_resources = {} - self.resolved_outputs = {} + self.resolved_outputs = [] self.resource_states = {} self.events = [] + self.resolved_exports = {} + self.description = None def set_stack_status(self, status: StackStatus, reason: StackStatusReason | None = None): self.status = status if reason: self.status_reason = reason - self._store_event(self.stack_name, self.stack_id, status.value, status_reason=reason) + self._store_event( + resource_id=self.stack_name, + resource_type="AWS::CloudFormation::Stack", + physical_resource_id=self.stack_id, + status=status, + status_reason=reason, + ) def set_resource_status( self, @@ -114,7 +136,7 @@ def set_resource_status( LogicalResourceId=logical_resource_id, PhysicalResourceId=physical_resource_id, ResourceType=resource_type, - Timestamp=datetime.now(tz=timezone.utc), + Timestamp=datetime.now(tz=UTC), ResourceStatus=status, ResourceStatusReason=resource_status_reason, ) @@ -122,75 +144,43 @@ def set_resource_status( if not resource_status_reason: resource_description.pop("ResourceStatusReason") - self.resource_states[logical_resource_id] = resource_description - self._store_event(logical_resource_id, physical_resource_id, status, resource_status_reason) + if status == ResourceStatus.DELETE_COMPLETE: + self.resource_states.pop(logical_resource_id) + else: + self.resource_states[logical_resource_id] = resource_description + + self._store_event( + resource_id=logical_resource_id, + resource_type=resource_type, + physical_resource_id=physical_resource_id, + status=status, + status_reason=resource_status_reason, + ) def _store_event( self, resource_id: str = None, - physical_res_id: str = None, - status: str = "", + resource_type: str | None = "", + physical_resource_id: str = None, + status: StackStatus | ResourceStatus = "", status_reason: str = "", ): - resource_id = resource_id - physical_res_id = physical_res_id - resource_type = ( - self.template.get("Resources", {}) - .get(resource_id, {}) - .get("Type", "AWS::CloudFormation::Stack") + event = StackEvent( + EventId=long_uid(), + Timestamp=datetime.now(tz=UTC), + StackId=self.stack_id, + StackName=self.stack_name, + LogicalResourceId=resource_id, + PhysicalResourceId=physical_resource_id, + ResourceStatus=status, + ResourceType=resource_type, ) - event: StackEvent = { - "EventId": long_uid(), - "Timestamp": datetime.now(tz=timezone.utc), - "StackId": self.stack_id, - "StackName": self.stack_name, - "LogicalResourceId": resource_id, - "PhysicalResourceId": physical_res_id, - "ResourceStatus": status, - "ResourceType": resource_type, - } - if status_reason: event["ResourceStatusReason"] = status_reason self.events.insert(0, event) - def describe_details(self) -> ApiStack: - result = { - "CreationTime": self.creation_time, - "DeletionTime": self.deletion_time, - "StackId": self.stack_id, - "StackName": self.stack_name, - "StackStatus": self.status, - "StackStatusReason": self.status_reason, - # fake values - "DisableRollback": False, - "DriftInformation": StackDriftInformation( - StackDriftStatus=StackDriftStatus.NOT_CHECKED - ), - "EnableTerminationProtection": False, - "LastUpdatedTime": self.creation_time, - "RollbackConfiguration": {}, - "Tags": [], - } - if change_set_id := self.change_set_id: - result["ChangeSetId"] = change_set_id - - if self.resolved_outputs: - describe_outputs = [] - for key, value in self.resolved_outputs.items(): - describe_outputs.append( - Output( - # TODO(parity): Description, ExportName - # TODO(parity): what happens on describe stack when the stack has not been deployed yet? - OutputKey=key, - OutputValue=value, - ) - ) - result["Outputs"] = describe_outputs - return result - def is_active(self) -> bool: return self.status != StackStatus.DELETE_COMPLETE @@ -204,32 +194,44 @@ class ChangeSet: change_set_name: str change_set_id: str change_set_type: ChangeSetType - update_model: Optional[UpdateModel] + update_model: UpdateModel | None status: ChangeSetStatus + status_reason: str | None execution_status: ExecutionStatus creation_time: datetime + processed_template: dict | None + resolved_parameters: dict[str, EngineParameter] + description: str | None + tags: list[Tag] def __init__( self, stack: Stack, request_payload: ChangeSetRequestPayload, + template_body: str, template: dict | None = None, ): self.stack = stack + self.template_body = template_body self.template = template self.status = ChangeSetStatus.CREATE_IN_PROGRESS + self.status_reason = None self.execution_status = ExecutionStatus.AVAILABLE self.update_model = None - self.creation_time = datetime.now(tz=timezone.utc) + self.creation_time = datetime.now(tz=UTC) + self.resolved_parameters = {} + self.tags = request_payload.get("Tags") or [] self.change_set_name = request_payload["ChangeSetName"] self.change_set_type = request_payload.get("ChangeSetType", ChangeSetType.UPDATE) + self.description = request_payload.get("Description") self.change_set_id = arns.cloudformation_change_set_arn( self.change_set_name, change_set_id=short_uid(), account_id=self.stack.account_id, region_name=self.stack.region_name, ) + self.processed_template = None def set_update_model(self, update_model: UpdateModel) -> None: self.update_model = update_model @@ -240,6 +242,9 @@ def set_change_set_status(self, status: ChangeSetStatus): def set_execution_status(self, execution_status: ExecutionStatus): self.execution_status = execution_status + def has_changes(self) -> bool: + return self.update_model.node_template.change_type != ChangeType.UNCHANGED + @property def account_id(self) -> str: return self.stack.account_id @@ -247,3 +252,36 @@ def account_id(self) -> str: @property def region_name(self) -> str: return self.stack.region_name + + +class StackInstance: + def __init__( + self, account_id: str, region_name: str, stack_set_id: str, operation_id: str, stack_id: str + ): + self.account_id = account_id + self.region_name = region_name + self.stack_set_id = stack_set_id + self.operation_id = operation_id + self.stack_id = stack_id + + self.status: StackInstanceStatus = StackInstanceStatus.CURRENT + self.stack_instance_status = StackInstanceComprehensiveStatus( + DetailedStatus=StackInstanceDetailedStatus.SUCCEEDED + ) + + +class StackSet: + stack_instances: list[StackInstance] + operations: dict[str, StackSetOperation] + + def __init__(self, account_id: str, region_name: str, request_payload: CreateStackSetInput): + self.account_id = account_id + self.region_name = region_name + + self.stack_set_name = request_payload["StackSetName"] + self.stack_set_id = f"{self.stack_set_name}:{long_uid()}" + self.template_body = request_payload.get("TemplateBody") + self.template_url = request_payload.get("TemplateURL") + + self.stack_instances = [] + self.operations = {} diff --git a/localstack-core/localstack/services/cloudformation/v2/provider.py b/localstack-core/localstack/services/cloudformation/v2/provider.py index 5e96a743780b0..e3d4629cb55c6 100644 --- a/localstack-core/localstack/services/cloudformation/v2/provider.py +++ b/localstack-core/localstack/services/cloudformation/v2/provider.py @@ -1,49 +1,91 @@ import copy +import json import logging +import re from collections import defaultdict -from datetime import datetime, timezone -from typing import Any, Optional +from datetime import UTC, datetime +from urllib.parse import urlencode +from localstack import config from localstack.aws.api import RequestContext, handler from localstack.aws.api.cloudformation import ( + AlreadyExistsException, + CallAs, Changes, ChangeSetNameOrId, ChangeSetNotFoundException, ChangeSetStatus, + ChangeSetSummary, ChangeSetType, ClientRequestToken, CreateChangeSetInput, CreateChangeSetOutput, CreateStackInput, + CreateStackInstancesInput, + CreateStackInstancesOutput, CreateStackOutput, + CreateStackSetInput, + CreateStackSetOutput, + DeleteChangeSetOutput, + DeleteStackInstancesInput, + DeleteStackInstancesOutput, + DeleteStackSetOutput, DeletionMode, DescribeChangeSetOutput, DescribeStackEventsOutput, + DescribeStackResourceOutput, DescribeStackResourcesOutput, + DescribeStackSetOperationOutput, DescribeStacksOutput, DisableRollback, + EnableTerminationProtection, ExecuteChangeSetOutput, ExecutionStatus, + GetTemplateOutput, GetTemplateSummaryInput, GetTemplateSummaryOutput, IncludePropertyValues, + InsufficientCapabilitiesException, InvalidChangeSetStatusException, + ListChangeSetsOutput, + ListExportsOutput, + ListStackResourcesOutput, + ListStacksOutput, LogicalResourceId, NextToken, Parameter, PhysicalResourceId, + ResourceStatus, RetainExceptOnCreate, RetainResources, RoleARN, RollbackConfiguration, + StackDriftInformation, + StackDriftStatus, StackName, StackNameOrId, + StackResourceDetail, + StackResourceSummary, + StackSetName, + StackSetNotFoundException, + StackSetOperation, + StackSetOperationAction, + StackSetOperationStatus, StackStatus, + StackStatusFilter, + TemplateStage, UpdateStackInput, UpdateStackOutput, + UpdateTerminationProtectionOutput, ) +from localstack.aws.api.cloudformation import ( + Stack as ApiStack, +) +from localstack.aws.connect import connect_to from localstack.services.cloudformation import api_utils from localstack.services.cloudformation.engine import template_preparer +from localstack.services.cloudformation.engine.parameters import resolve_ssm_parameter +from localstack.services.cloudformation.engine.transformers import FailedTransformationException from localstack.services.cloudformation.engine.v2.change_set_model import ( ChangeSetModel, ChangeType, @@ -58,33 +100,69 @@ from localstack.services.cloudformation.engine.v2.change_set_model_transform import ( ChangeSetModelTransform, ) +from localstack.services.cloudformation.engine.v2.change_set_model_validator import ( + ChangeSetModelValidator, +) +from localstack.services.cloudformation.engine.v2.change_set_resource_support_checker import ( + ChangeSetResourceSupportChecker, +) from localstack.services.cloudformation.engine.validations import ValidationError from localstack.services.cloudformation.provider import ( ARN_CHANGESET_REGEX, ARN_STACK_REGEX, + ARN_STACK_SET_REGEX, CloudformationProvider, ) from localstack.services.cloudformation.stores import ( CloudFormationStore, get_cloudformation_store, ) -from localstack.services.cloudformation.v2.entities import ChangeSet, Stack +from localstack.services.cloudformation.v2.entities import ( + ChangeSet, + Stack, + StackInstance, + StackSet, +) +from localstack.services.cloudformation.v2.types import EngineParameter, engine_parameter_value +from localstack.services.plugins import ServiceLifecycleHook +from localstack.utils.collections import select_attributes +from localstack.utils.numbers import is_number +from localstack.utils.strings import short_uid from localstack.utils.threads import start_worker_thread LOG = logging.getLogger(__name__) +SSM_PARAMETER_TYPE_RE = re.compile( + r"^AWS::SSM::Parameter::Value<(?PList<)?(?P[^>]+)>?>$" +) + def is_stack_arn(stack_name_or_id: str) -> bool: - return ARN_STACK_REGEX.match(stack_name_or_id) is not None + return stack_name_or_id and ARN_STACK_REGEX.match(stack_name_or_id) is not None def is_changeset_arn(change_set_name_or_id: str) -> bool: - return ARN_CHANGESET_REGEX.match(change_set_name_or_id) is not None + return change_set_name_or_id and ARN_CHANGESET_REGEX.match(change_set_name_or_id) is not None + + +def is_stack_set_arn(stack_set_name_or_id: str) -> bool: + return stack_set_name_or_id and ARN_STACK_SET_REGEX.match(stack_set_name_or_id) is not None class StackNotFoundError(ValidationError): - def __init__(self, stack_name: str): - super().__init__(f"Stack with id {stack_name} does not exist") + def __init__(self, stack_name_or_id: str, message_override: str | None = None): + if message_override: + super().__init__(message_override) + else: + if is_stack_arn(stack_name_or_id): + super().__init__(f"Stack with id {stack_name_or_id} does not exist") + else: + super().__init__(f"Stack [{stack_name_or_id}] does not exist") + + +class StackSetNotFoundError(StackSetNotFoundException): + def __init__(self, stack_set_name: str): + super().__init__(f"StackSet {stack_set_name} not found") def find_stack_v2(state: CloudFormationStore, stack_name: str | None) -> Stack | None: @@ -103,14 +181,14 @@ def find_stack_v2(state: CloudFormationStore, stack_name: str | None) -> Stack | else: return stack_candidates[0] else: - raise NotImplementedError + raise ValueError("No stack name specified when finding stack") def find_change_set_v2( state: CloudFormationStore, change_set_name: str, stack_name: str | None = None ) -> ChangeSet | None: if is_changeset_arn(change_set_name): - return state.change_sets[change_set_name] + return state.change_sets.get(change_set_name) else: if stack_name is not None: stack = find_stack_v2(state, stack_name) @@ -122,25 +200,172 @@ def find_change_set_v2( if change_set_candidate.change_set_name == change_set_name: return change_set_candidate else: - raise NotImplementedError + raise ValidationError( + "StackName must be specified if ChangeSetName is not specified as an ARN." + ) + + +def find_stack_set_v2(state: CloudFormationStore, stack_set_name: str) -> StackSet | None: + if is_stack_set_arn(stack_set_name): + return state.stack_sets.get(stack_set_name) + + for stack_set in state.stack_sets_v2.values(): + if stack_set.stack_set_name == stack_set_name: + return stack_set + return None + + +def find_stack_instance(stack_set: StackSet, account: str, region: str) -> StackInstance | None: + for instance in stack_set.stack_instances: + if instance.account_id == account and instance.region_name == region: + return instance + return None + + +class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook): + def on_before_start(self): + # TODO: make sure to bring `_validate_config` from the base class when removing it + # as this ensures we have a valid CFN_NO_WAIT_ITERATIONS value + super().on_before_start() + self._log_create_issue_info() + + def _log_create_issue_info(self): + base = "https://github.com/localstack/localstack/issues/new" + query_args = { + "template": "bug-report.yml", + "labels": ",".join( + [ + "aws:cloudformation:v2", + "status: triage needed", + "type: bug", + ] + ), + "title": "CFNV2: ", + } + issue_url = "?".join([base, urlencode(query_args)]) + LOG.info( + "You have opted in to the new CloudFormation deployment engine. " + "You can opt in to using the old engine by setting PROVIDER_OVERRIDE_CLOUDFORMATION=engine-legacy. " + "If you experience issues, please submit a bug report at this URL: %s", + issue_url, + ) -class CloudformationProviderV2(CloudformationProvider): @staticmethod + def _resolve_parameters( + template: dict | None, + parameters: dict | None, + account_id: str, + region_name: str, + before_parameters: dict | None, + ) -> dict[str, EngineParameter]: + template_parameters = template.get("Parameters", {}) + resolved_parameters = {} + invalid_parameters = [] + for name, parameter in template_parameters.items(): + given_value = parameters.get(name) + default_value = parameter.get("Default") + resolved_parameter = EngineParameter( + type_=parameter["Type"], + given_value=given_value, + default_value=default_value, + no_echo=parameter.get("NoEcho"), + ) + + # validate the type + if parameter["Type"] == "Number" and not is_number( + engine_parameter_value(resolved_parameter) + ): + raise ValidationError(f"Parameter '{name}' must be a number.") + + # TODO: support other parameter types + if match := SSM_PARAMETER_TYPE_RE.match(parameter["Type"]): + inner_type = match.group("innertype") + is_list_type = match.group("listtype") is not None + if is_list_type or inner_type == "CommaDelimitedList": + # list types + try: + resolved_value = resolve_ssm_parameter( + account_id, region_name, given_value or default_value + ) + resolved_parameter["resolved_value"] = resolved_value.split(",") + except Exception: + raise ValidationError( + f"Parameter {name} should either have input value or default value" + ) + else: + try: + resolved_parameter["resolved_value"] = resolve_ssm_parameter( + account_id, region_name, given_value or default_value + ) + except Exception as e: + # we could not find the parameter however CDK provides the resolved value rather than the + # parameter name again so try to look up the value in the previous parameters + if ( + before_parameters + and (before_param := before_parameters.get(name)) + and isinstance(before_param, dict) + and (resolved_value := before_param.get("resolved_value")) + ): + LOG.debug( + "Parameter %s could not be resolved, using previous value of %s", + name, + resolved_value, + ) + resolved_parameter["resolved_value"] = resolved_value + else: + raise ValidationError( + f"Parameter {name} should either have input value or default value" + ) from e + elif given_value is None and default_value is None: + invalid_parameters.append(name) + continue + + resolved_parameters[name] = resolved_parameter + + if invalid_parameters: + raise ValidationError(f"Parameters: [{','.join(invalid_parameters)}] must have values") + + for name, parameter in resolved_parameters.items(): + if ( + parameter.get("resolved_value") is None + and parameter.get("given_value") is None + and parameter.get("default_value") is None + ): + raise ValidationError( + f"Parameter {name} should either have input value or default value" + ) + + return resolved_parameters + + @classmethod def _setup_change_set_model( + cls, change_set: ChangeSet, - before_template: Optional[dict], - after_template: Optional[dict], - before_parameters: Optional[dict], - after_parameters: Optional[dict], - previous_update_model: Optional[UpdateModel], + before_template: dict | None, + after_template: dict | None, + before_parameters: dict | None, + after_parameters: dict | None, + previous_update_model: UpdateModel | None = None, ): + resolved_parameters = None + if after_parameters is not None: + resolved_parameters = cls._resolve_parameters( + after_template, + after_parameters, + change_set.stack.account_id, + change_set.stack.region_name, + before_parameters, + ) + + change_set.resolved_parameters = resolved_parameters + # Create and preprocess the update graph for this template update. change_set_model = ChangeSetModel( before_template=before_template, after_template=after_template, before_parameters=before_parameters, - after_parameters=after_parameters, + after_parameters=resolved_parameters, ) raw_update_model: UpdateModel = change_set_model.get_update_model() # If there exists an update model which operated in the 'before' version of this change set, @@ -155,20 +380,29 @@ def _setup_change_set_model( change_set_model_transform = ChangeSetModelTransform( change_set=change_set, before_parameters=before_parameters, - after_parameters=after_parameters, + after_parameters=resolved_parameters, before_template=before_template, after_template=after_template, ) - transformed_before_template, transformed_after_template = ( - change_set_model_transform.transform() - ) + try: + transformed_before_template, transformed_after_template = ( + change_set_model_transform.transform() + ) + except FailedTransformationException as e: + change_set.status = ChangeSetStatus.FAILED + change_set.status_reason = e.message + change_set.stack.set_stack_status( + status=StackStatus.ROLLBACK_IN_PROGRESS, reason=e.message + ) + change_set.stack.set_stack_status(status=StackStatus.CREATE_FAILED) + return # Remodel the update graph after the applying the global transforms. change_set_model = ChangeSetModel( before_template=transformed_before_template, after_template=transformed_after_template, before_parameters=before_parameters, - after_parameters=after_parameters, + after_parameters=resolved_parameters, ) update_model = change_set_model.get_update_model() # Bring the cache for the previous operations forward in the update graph for this version @@ -179,13 +413,59 @@ def _setup_change_set_model( update_model.after_runtime_cache.update(raw_update_model.after_runtime_cache) change_set.set_update_model(update_model) + # perform validations + validator = ChangeSetModelValidator( + change_set=change_set, + ) + validator.validate() + + # hacky + if transform := raw_update_model.node_template.transform: + if transform.global_transforms: + # global transforms should always be considered "MODIFIED" + update_model.node_template.change_type = ChangeType.MODIFIED + change_set.processed_template = transformed_after_template + + if not config.CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES: + support_visitor = ChangeSetResourceSupportChecker( + change_set_type=change_set.change_set_type + ) + support_visitor.visit(change_set.update_model.node_template) + failure_messages = support_visitor.failure_messages + if failure_messages: + reason_suffix = ", ".join(failure_messages) + status_reason = f"{ChangeSetResourceSupportChecker.TITLE_MESSAGE} {reason_suffix}" + + change_set.status_reason = status_reason + change_set.set_change_set_status(ChangeSetStatus.FAILED) + failure_transitions = { + ChangeSetType.CREATE: ( + StackStatus.ROLLBACK_IN_PROGRESS, + StackStatus.CREATE_FAILED, + ), + ChangeSetType.UPDATE: ( + StackStatus.UPDATE_ROLLBACK_IN_PROGRESS, + StackStatus.UPDATE_ROLLBACK_FAILED, + ), + ChangeSetType.IMPORT: ( + StackStatus.IMPORT_ROLLBACK_IN_PROGRESS, + StackStatus.IMPORT_ROLLBACK_FAILED, + ), + } + transitions = failure_transitions.get(change_set.change_set_type) + if transitions: + first_status, *remaining_statuses = transitions + change_set.stack.set_stack_status(first_status, status_reason) + for status in remaining_statuses: + change_set.stack.set_stack_status(status) + return + @handler("CreateChangeSet", expand=False) def create_change_set( self, context: RequestContext, request: CreateChangeSetInput ) -> CreateChangeSetOutput: - try: - stack_name = request["StackName"] - except KeyError: + stack_name = request.get("StackName") + if not stack_name: # TODO: proper exception raise ValidationError("StackName must be specified") try: @@ -215,12 +495,19 @@ def create_change_set( template_body = api_utils.extract_template_body(request) structured_template = template_preparer.parse_template(template_body) + if len(template_body) > 51200 and not template_url: + raise ValidationError( + f"1 validation error detected: Value '{template_body}' at 'templateBody' " + "failed to satisfy constraint: Member must have length less than or equal to 51200" + ) + # this is intentionally not in a util yet. Let's first see how the different operations deal with these before generalizing # handle ARN stack_name here (not valid for initial CREATE, since stack doesn't exist yet) if is_stack_arn(stack_name): stack = state.stacks_v2.get(stack_name) if not stack: raise ValidationError(f"Stack '{stack_name}' does not exist.") + stack.capabilities = request.get("Capabilities") or [] else: # stack name specified, so fetch the stack by name stack_candidates: list[Stack] = [ @@ -234,19 +521,15 @@ def create_change_set( account_id=context.account_id, region_name=context.region, request_payload=request, - template=structured_template, - template_body=template_body, + initial_status=StackStatus.REVIEW_IN_PROGRESS, ) state.stacks_v2[stack.stack_id] = stack else: if not active_stack_candidates: raise ValidationError(f"Stack '{stack_name}' does not exist.") stack = active_stack_candidates[0] - - if stack.status in [StackStatus.CREATE_COMPLETE, StackStatus.UPDATE_COMPLETE]: - stack.set_stack_status(StackStatus.UPDATE_IN_PROGRESS) - else: - stack.set_stack_status(StackStatus.REVIEW_IN_PROGRESS) + # propagate capabilities from create change set request + stack.capabilities = request.get("Capabilities") or [] # TODO: test if rollback status is allowed as well if ( @@ -257,6 +540,14 @@ def create_change_set( f"Stack [{stack_name}] already exists and cannot be created again with the changeSet [{change_set_name}]." ) + if change_set_type == ChangeSetType.UPDATE and ( + stack.status == StackStatus.DELETE_COMPLETE + or stack.status == StackStatus.DELETE_IN_PROGRESS + ): + raise ValidationError( + f"Stack:{stack.stack_id} is in {stack.status} state and can not be updated." + ) + before_parameters: dict[str, Parameter] | None = None match change_set_type: case ChangeSetType.UPDATE: @@ -281,12 +572,9 @@ def create_change_set( # The options might be reduce to using the current style, or passing the extra information # as a metadata object. The choice should be made considering when the extra information # is needed for the update graph building, or only looked up in downstream tasks (metadata). - request_parameters = request.get("Parameters", list()) + request_parameters = request.get("Parameters", []) # TODO: handle parameter defaults and resolution - after_parameters: dict[str, Any] = { - parameter["ParameterKey"]: parameter["ParameterValue"] - for parameter in request_parameters - } + after_parameters = self._extract_after_parameters(request_parameters, before_parameters) # TODO: update this logic to always pass the clean template object if one exists. The # current issue with relaying on stack.template_original is that this appears to have @@ -306,7 +594,12 @@ def create_change_set( pass # create change set for the stack and apply changes - change_set = ChangeSet(stack, request, template=after_template) + change_set = ChangeSet( + stack, + request, + template=after_template, + template_body=template_body, + ) self._setup_change_set_model( change_set=change_set, before_template=before_template, @@ -315,12 +608,21 @@ def create_change_set( after_parameters=after_parameters, previous_update_model=previous_update_model, ) + if change_set.status == ChangeSetStatus.FAILED: + change_set.set_execution_status(ExecutionStatus.UNAVAILABLE) + else: + if not change_set.has_changes(): + change_set.set_change_set_status(ChangeSetStatus.FAILED) + change_set.set_execution_status(ExecutionStatus.UNAVAILABLE) + change_set.status_reason = "The submitted information didn't contain changes. Submit different information to create a change set." + else: + if stack.status not in [StackStatus.CREATE_COMPLETE, StackStatus.UPDATE_COMPLETE]: + stack.set_stack_status(StackStatus.REVIEW_IN_PROGRESS, "User Initiated") - change_set.set_change_set_status(ChangeSetStatus.CREATE_COMPLETE) - stack.change_set_id = change_set.change_set_id - stack.change_set_ids.append(change_set.change_set_id) - state.change_sets[change_set.change_set_id] = change_set + change_set.set_change_set_status(ChangeSetStatus.CREATE_COMPLETE) + stack.change_set_ids.add(change_set.change_set_id) + state.change_sets[change_set.change_set_id] = change_set return CreateChangeSetOutput(StackId=stack.stack_id, Id=change_set.change_set_id) @handler("ExecuteChangeSet") @@ -355,6 +657,8 @@ def execute_change_set( raise RuntimeError("Programming error: no update graph found for change set") change_set.set_execution_status(ExecutionStatus.EXECUTE_IN_PROGRESS) + # propagate the tags as this is done during execution + change_set.stack.tags = change_set.tags change_set.stack.set_stack_status( StackStatus.UPDATE_IN_PROGRESS if change_set.change_set_type == ChangeSetType.UPDATE @@ -366,37 +670,104 @@ def execute_change_set( ) def _run(*args): - try: - result = change_set_executor.execute() + # TODO: should this be cleared before or after execution? + change_set.stack.status_reason = None + result = change_set_executor.execute() + change_set.stack.resolved_parameters = change_set.resolved_parameters + change_set.stack.resolved_resources = result.resources + change_set.stack.template = change_set.template + change_set.stack.processed_template = change_set.processed_template + change_set.stack.template_body = change_set.template_body + change_set.stack.description = change_set.template.get("Description") + + if not result.failure_message: new_stack_status = StackStatus.UPDATE_COMPLETE if change_set.change_set_type == ChangeSetType.CREATE: new_stack_status = StackStatus.CREATE_COMPLETE change_set.stack.set_stack_status(new_stack_status) change_set.set_execution_status(ExecutionStatus.EXECUTE_COMPLETE) - change_set.stack.resolved_resources = result.resources - change_set.stack.resolved_parameters = result.parameters change_set.stack.resolved_outputs = result.outputs - # if the deployment succeeded, update the stack's template representation to that - # which was just deployed - change_set.stack.template = change_set.template - except Exception as e: + + change_set.stack.resolved_exports = {} + for output in result.outputs: + if export_name := output.get("ExportName"): + change_set.stack.resolved_exports[export_name] = output["OutputValue"] + + change_set.stack.change_set_id = change_set.change_set_id + else: LOG.error( - "Execute change set failed: %s", e, exc_info=LOG.isEnabledFor(logging.WARNING) + "Execute change set failed: %s", + result.failure_message, + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) - new_stack_status = StackStatus.UPDATE_FAILED - if change_set.change_set_type == ChangeSetType.CREATE: - new_stack_status = StackStatus.CREATE_FAILED - - change_set.stack.set_stack_status(new_stack_status) + # stack status is taken care of in the executor change_set.set_execution_status(ExecutionStatus.EXECUTE_FAILED) + change_set.stack.deletion_time = datetime.now(tz=UTC) start_worker_thread(_run) return ExecuteChangeSetOutput() - def _describe_change_set( - self, change_set: ChangeSet, include_property_values: bool + @staticmethod + def _render_resolved_parameters( + resolved_parameters: dict[str, EngineParameter], + ) -> list[Parameter]: + result = [] + for name, resolved_parameter in resolved_parameters.items(): + parameter = Parameter( + ParameterKey=name, + ParameterValue=resolved_parameter.get("given_value") + or resolved_parameter.get("default_value"), + ) + if resolved_value := resolved_parameter.get("resolved_value"): + parameter["ResolvedValue"] = resolved_value + + # TODO :what happens to the resolved value? + if resolved_parameter.get("no_echo", False): + parameter["ParameterValue"] = "****" + result.append(parameter) + + return result + + @handler("DescribeChangeSet") + def describe_change_set( + self, + context: RequestContext, + change_set_name: ChangeSetNameOrId, + stack_name: StackNameOrId | None = None, + next_token: NextToken | None = None, + include_property_values: IncludePropertyValues | None = None, + **kwargs, ) -> DescribeChangeSetOutput: + # TODO add support for include_property_values + # only relevant if change_set_name isn't an ARN + state = get_cloudformation_store(context.account_id, context.region) + change_set = find_change_set_v2(state, change_set_name, stack_name) + + if not change_set: + raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist") + + # if the change set failed to create, then we can return a blank response + if change_set.status == ChangeSetStatus.FAILED: + return DescribeChangeSetOutput( + Status=change_set.status, + ChangeSetId=change_set.change_set_id, + ChangeSetName=change_set.change_set_name, + ExecutionStatus=change_set.execution_status, + RollbackConfiguration=RollbackConfiguration(), + StackId=change_set.stack.stack_id, + StackName=change_set.stack.stack_name, + CreationTime=change_set.creation_time, + Changes=[], + Capabilities=change_set.stack.capabilities, + StatusReason=change_set.status_reason, + Description=change_set.description, + # TODO: static information + IncludeNestedStacks=False, + NotificationARNs=[], + Tags=change_set.tags or None, + ) + # TODO: The ChangeSetModelDescriber currently matches AWS behavior by listing # resource changes in the order they appear in the template. However, when # a resource change is triggered indirectly (e.g., via Ref or GetAtt), the @@ -418,35 +789,88 @@ def _describe_change_set( StackId=change_set.stack.stack_id, StackName=change_set.stack.stack_name, CreationTime=change_set.creation_time, - Parameters=[ - # TODO: add masking support. - Parameter(ParameterKey=key, ParameterValue=value) - for (key, value) in change_set.stack.resolved_parameters.items() - ], Changes=changes, + Capabilities=change_set.stack.capabilities, + StatusReason=change_set.status_reason, + Description=change_set.description, + # TODO: static information + IncludeNestedStacks=False, + NotificationARNs=[], + Tags=change_set.tags or None, ) + if change_set.resolved_parameters: + result["Parameters"] = self._render_resolved_parameters(change_set.resolved_parameters) return result - @handler("DescribeChangeSet") - def describe_change_set( + @handler("ListChangeSets") + def list_change_sets( + self, + context: RequestContext, + stack_name: StackNameOrId, + next_token: NextToken = None, + **kwargs, + ) -> ListChangeSetsOutput: + store = get_cloudformation_store(account_id=context.account_id, region_name=context.region) + stack = find_stack_v2(store, stack_name) + if not stack: + raise StackNotFoundError(stack_name) + summaries = [] + for change_set_id in stack.change_set_ids: + change_set = store.change_sets[change_set_id] + if ( + change_set.status != ChangeSetStatus.CREATE_COMPLETE + or change_set.execution_status != ExecutionStatus.AVAILABLE + ): + continue + + summaries.append( + ChangeSetSummary( + StackId=change_set.stack.stack_id, + StackName=change_set.stack.stack_name, + ChangeSetId=change_set_id, + ChangeSetName=change_set.change_set_name, + ExecutionStatus=change_set.execution_status, + Status=change_set.status, + StatusReason=change_set.status_reason, + CreationTime=change_set.creation_time, + # mocked information + IncludeNestedStacks=False, + ) + ) + + return ListChangeSetsOutput(Summaries=summaries) + + @handler("DeleteChangeSet") + def delete_change_set( self, context: RequestContext, change_set_name: ChangeSetNameOrId, - stack_name: StackNameOrId | None = None, - next_token: NextToken | None = None, - include_property_values: IncludePropertyValues | None = None, + stack_name: StackNameOrId = None, **kwargs, - ) -> DescribeChangeSetOutput: - # TODO add support for include_property_values - # only relevant if change_set_name isn't an ARN + ) -> DeleteChangeSetOutput: state = get_cloudformation_store(context.account_id, context.region) change_set = find_change_set_v2(state, change_set_name, stack_name) if not change_set: - raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist") - result = self._describe_change_set( - change_set=change_set, include_property_values=include_property_values or False - ) - return result + return DeleteChangeSetOutput() + + try: + change_set.stack.change_set_ids.remove(change_set.change_set_id) + except KeyError: + LOG.warning( + "Could not disassociatei change set '%s' from stack '%s', it does not seem to be associated", + change_set.change_set_id, + change_set.stack.stack_id, + ) + try: + state.change_sets.pop(change_set.change_set_id) + except KeyError: + # This _should_ never fail since if we cannot find the change set in the store (using + # `find_change_set_v2`) then we early return from this function + LOG.warning( + "Could not delete change set '%s', it does not exist", change_set.change_set_id + ) + + return DeleteChangeSetOutput() @handler("CreateStack", expand=False) def create_stack(self, context: RequestContext, request: CreateStackInput) -> CreateStackOutput: @@ -457,6 +881,26 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr raise ValidationError("StackName must be specified") state = get_cloudformation_store(context.account_id, context.region) + + active_stack_candidates = [ + stack + for stack in state.stacks_v2.values() + if stack.stack_name == stack_name and stack.status not in [StackStatus.DELETE_COMPLETE] + ] + + # TODO: fix/implement this code path + # this needs more investigation how Cloudformation handles it (e.g. normal stack create or does it create a separate changeset?) + # REVIEW_IN_PROGRESS is another special status + # in this case existing changesets are set to obsolete and the stack is created + # review_stack_candidates = [s for s in stack_candidates if s.status == StackStatus.REVIEW_IN_PROGRESS] + # if review_stack_candidates: + # set changesets to obsolete + # for cs in review_stack_candidates[0].change_sets: + # cs.execution_status = ExecutionStatus.OBSOLETE + + if active_stack_candidates: + raise AlreadyExistsException(f"Stack [{stack_name}] already exists") + # TODO: copied from create_change_set, consider unifying template_body = request.get("TemplateBody") # s3 or secretsmanager url @@ -476,12 +920,24 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr template_body = api_utils.extract_template_body(request) structured_template = template_preparer.parse_template(template_body) + if len(template_body) > 51200 and not template_url: + raise ValidationError( + f"1 validation error detected: Value '{template_body}' at 'templateBody' " + "failed to satisfy constraint: Member must have length less than or equal to 51200" + ) + + if "CAPABILITY_AUTO_EXPAND" not in request.get("Capabilities", []) and ( + "Transform" in structured_template.keys() or "Fn::Transform" in template_body + ): + raise InsufficientCapabilitiesException( + "Requires capabilities : [CAPABILITY_AUTO_EXPAND]" + ) + stack = Stack( account_id=context.account_id, region_name=context.region, request_payload=request, - template=structured_template, - template_body=template_body, + tags=request.get("Tags"), ) # TODO: what is the correct initial status? state.stacks_v2[stack.stack_id] = stack @@ -490,12 +946,9 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr # The options might be reduce to using the current style, or passing the extra information # as a metadata object. The choice should be made considering when the extra information # is needed for the update graph building, or only looked up in downstream tasks (metadata). - request_parameters = request.get("Parameters", list()) + request_parameters = request.get("Parameters", []) # TODO: handle parameter defaults and resolution - after_parameters: dict[str, Any] = { - parameter["ParameterKey"]: parameter["ParameterValue"] - for parameter in request_parameters - } + after_parameters = self._extract_after_parameters(request_parameters) after_template = structured_template # Create internal change set to execute @@ -503,6 +956,7 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr stack, {"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE}, template=after_template, + template_body=template_body, ) self._setup_change_set_model( change_set=change_set, @@ -512,6 +966,10 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr after_parameters=after_parameters, previous_update_model=None, ) + if change_set.status == ChangeSetStatus.FAILED: + return CreateStackOutput(StackId=stack.stack_id) + + stack.processed_template = change_set.processed_template # deployment process stack.set_stack_status(StackStatus.CREATE_IN_PROGRESS) @@ -520,16 +978,31 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr def _run(*args): try: result = change_set_executor.execute() - stack.set_stack_status(StackStatus.CREATE_COMPLETE) stack.resolved_resources = result.resources - stack.resolved_parameters = result.parameters stack.resolved_outputs = result.outputs + if all( + resource["ResourceStatus"] == ResourceStatus.CREATE_COMPLETE + for resource in stack.resolved_resources.values() + ): + stack.set_stack_status(StackStatus.CREATE_COMPLETE) + else: + stack.set_stack_status(StackStatus.CREATE_FAILED) + # if the deployment succeeded, update the stack's template representation to that # which was just deployed stack.template = change_set.template + stack.template_body = change_set.template_body + stack.processed_template = change_set.processed_template + stack.resolved_parameters = change_set.resolved_parameters + stack.resolved_exports = {} + for output in result.outputs: + if export_name := output.get("ExportName"): + stack.resolved_exports[export_name] = output["OutputValue"] except Exception as e: LOG.error( - "Create Stack set failed: %s", e, exc_info=LOG.isEnabledFor(logging.WARNING) + "Create Stack set failed: %s", + e, + exc_info=LOG.isEnabledFor(logging.WARNING) and config.CFN_VERBOSE_ERRORS, ) stack.set_stack_status(StackStatus.CREATE_FAILED) @@ -537,6 +1010,16 @@ def _run(*args): return CreateStackOutput(StackId=stack.stack_id) + @handler("CreateStackSet", expand=False) + def create_stack_set( + self, context: RequestContext, request: CreateStackSetInput + ) -> CreateStackSetOutput: + state = get_cloudformation_store(context.account_id, context.region) + stack_set = StackSet(context.account_id, context.region, request) + state.stack_sets_v2[stack_set.stack_set_id] = stack_set + + return CreateStackSetOutput(StackSetId=stack_set.stack_set_id) + @handler("DescribeStacks") def describe_stacks( self, @@ -545,11 +1028,152 @@ def describe_stacks( next_token: NextToken = None, **kwargs, ) -> DescribeStacksOutput: + state = get_cloudformation_store(context.account_id, context.region) + if stack_name: + stack = find_stack_v2(state, stack_name) + if not stack: + raise ValidationError(f"Stack with id {stack_name} does not exist") + stacks = [stack] + else: + stacks = state.stacks_v2.values() + + describe_stack_output: list[ApiStack] = [] + for stack in stacks: + describe_stack_output.append(self._describe_stack(stack)) + + return DescribeStacksOutput(Stacks=describe_stack_output) + + def _describe_stack(self, stack: Stack) -> ApiStack: + stack_description = ApiStack( + Description=stack.description, + CreationTime=stack.creation_time, + StackId=stack.stack_id, + StackName=stack.stack_name, + StackStatus=stack.status, + StackStatusReason=stack.status_reason, + # fake values + DisableRollback=False, + DriftInformation=StackDriftInformation(StackDriftStatus=StackDriftStatus.NOT_CHECKED), + EnableTerminationProtection=stack.enable_termination_protection, + RollbackConfiguration=RollbackConfiguration(), + Tags=stack.tags, + NotificationARNs=[], + ) + if stack.status != StackStatus.REVIEW_IN_PROGRESS: + # TODO: actually track updated time + stack_description["LastUpdatedTime"] = stack.creation_time + if stack.deletion_time: + stack_description["DeletionTime"] = stack.deletion_time + if stack.capabilities: + stack_description["Capabilities"] = stack.capabilities + # TODO: confirm the logic for this + if change_set_id := stack.change_set_id: + stack_description["ChangeSetId"] = change_set_id + + if stack.resolved_parameters: + stack_description["Parameters"] = self._render_resolved_parameters( + stack.resolved_parameters + ) + + if stack.resolved_outputs: + stack_description["Outputs"] = stack.resolved_outputs + + return stack_description + + @handler("ListStacks") + def list_stacks( + self, + context: RequestContext, + next_token: NextToken = None, + stack_status_filter: StackStatusFilter = None, + **kwargs, + ) -> ListStacksOutput: + state = get_cloudformation_store(context.account_id, context.region) + + stacks = [ + self._describe_stack(s) + for s in state.stacks_v2.values() + if not stack_status_filter or s.status in stack_status_filter + ] + + attrs = [ + "StackId", + "StackName", + "TemplateDescription", + "CreationTime", + "LastUpdatedTime", + "DeletionTime", + "StackStatus", + "StackStatusReason", + "ParentId", + "RootId", + "DriftInformation", + ] + stacks = [select_attributes(stack, attrs) for stack in stacks] + return ListStacksOutput(StackSummaries=stacks) + + @handler("ListStackResources") + def list_stack_resources( + self, context: RequestContext, stack_name: StackName, next_token: NextToken = None, **kwargs + ) -> ListStackResourcesOutput: + result = self.describe_stack_resources(context, stack_name) + + resources = [] + for resource in result.get("StackResources", []): + resources.append( + StackResourceSummary( + LogicalResourceId=resource["LogicalResourceId"], + PhysicalResourceId=resource["PhysicalResourceId"], + ResourceType=resource["ResourceType"], + LastUpdatedTimestamp=resource["Timestamp"], + ResourceStatus=resource["ResourceStatus"], + ResourceStatusReason=resource.get("ResourceStatusReason"), + DriftInformation=resource.get("DriftInformation"), + ModuleInfo=resource.get("ModuleInfo"), + ) + ) + + return ListStackResourcesOutput(StackResourceSummaries=resources) + + @handler("DescribeStackResource") + def describe_stack_resource( + self, + context: RequestContext, + stack_name: StackName, + logical_resource_id: LogicalResourceId, + **kwargs, + ) -> DescribeStackResourceOutput: state = get_cloudformation_store(context.account_id, context.region) stack = find_stack_v2(state, stack_name) if not stack: - raise StackNotFoundError(stack_name) - return DescribeStacksOutput(Stacks=[stack.describe_details()]) + raise StackNotFoundError( + stack_name, message_override=f"Stack '{stack_name}' does not exist" + ) + + try: + resource = stack.resolved_resources[logical_resource_id] + if resource.get("ResourceStatus") not in [ + StackStatus.CREATE_COMPLETE, + StackStatus.UPDATE_COMPLETE, + StackStatus.ROLLBACK_COMPLETE, + ]: + raise KeyError + except KeyError: + raise ValidationError( + f"Resource {logical_resource_id} does not exist for stack {stack_name}" + ) + + resource_detail = StackResourceDetail( + StackName=stack.stack_name, + StackId=stack.stack_id, + LogicalResourceId=logical_resource_id, + PhysicalResourceId=resource["PhysicalResourceId"], + ResourceType=resource["Type"], + LastUpdatedTimestamp=resource["LastUpdatedTimestamp"], + ResourceStatus=resource["ResourceStatus"], + DriftInformation={"StackResourceDriftStatus": "NOT_CHECKED"}, + ) + return DescribeStackResourceOutput(StackResourceDetail=resource_detail) @handler("DescribeStackResources") def describe_stack_resources( @@ -575,20 +1199,250 @@ def describe_stack_resources( statuses.append(status) return DescribeStackResourcesOutput(StackResources=statuses) + @handler("CreateStackInstances", expand=False) + def create_stack_instances( + self, + context: RequestContext, + request: CreateStackInstancesInput, + ) -> CreateStackInstancesOutput: + state = get_cloudformation_store(context.account_id, context.region) + + stack_set_name = request["StackSetName"] + stack_set = find_stack_set_v2(state, stack_set_name) + if not stack_set: + raise StackSetNotFoundError(stack_set_name) + + op_id = request.get("OperationId") or short_uid() + accounts = request["Accounts"] + regions = request["Regions"] + + stacks_to_await = [] + for account in accounts: + for region in regions: + # deploy new stack + LOG.debug( + 'Deploying instance for stack set "%s" in account: %s region %s', + stack_set_name, + account, + region, + ) + cf_client = connect_to(aws_access_key_id=account, region_name=region).cloudformation + if stack_set.template_body: + kwargs = { + "TemplateBody": stack_set.template_body, + } + elif stack_set.template_url: + kwargs = { + "TemplateURL": stack_set.template_url, + } + else: + # TODO: wording + raise ValueError("Neither StackSet Template URL nor TemplateBody provided") + stack_name = f"sset-{stack_set_name}-{account}-{region}" + + # skip creation of existing stacks + if find_stack_v2(state, stack_name): + continue + + result = cf_client.create_stack(StackName=stack_name, **kwargs) + # store stack instance + stack_instance = StackInstance( + account_id=account, + region_name=region, + stack_set_id=stack_set.stack_set_id, + operation_id=op_id, + stack_id=result["StackId"], + ) + stack_set.stack_instances.append(stack_instance) + + stacks_to_await.append((stack_name, account, region)) + + # wait for completion of stack + for stack_name, account_id, region_name in stacks_to_await: + client = connect_to( + aws_access_key_id=account_id, region_name=region_name + ).cloudformation + client.get_waiter("stack_create_complete").wait(StackName=stack_name) + + # record operation + operation = StackSetOperation( + OperationId=op_id, + StackSetId=stack_set.stack_set_id, + Action=StackSetOperationAction.CREATE, + Status=StackSetOperationStatus.SUCCEEDED, + ) + stack_set.operations[op_id] = operation + + return CreateStackInstancesOutput(OperationId=op_id) + + @handler("DescribeStackSetOperation") + def describe_stack_set_operation( + self, + context: RequestContext, + stack_set_name: StackSetName, + operation_id: ClientRequestToken, + call_as: CallAs = None, + **kwargs, + ) -> DescribeStackSetOperationOutput: + state = get_cloudformation_store(context.account_id, context.region) + stack_set = find_stack_set_v2(state, stack_set_name) + if not stack_set: + raise StackSetNotFoundError(stack_set_name) + + result = stack_set.operations.get(operation_id) + if not result: + LOG.debug( + 'Unable to find operation ID "%s" for stack set "%s" in list: %s', + operation_id, + stack_set_name, + list(stack_set.operations.keys()), + ) + # TODO: proper exception + raise ValueError( + f'Unable to find operation ID "{operation_id}" for stack set "{stack_set_name}"' + ) + + return DescribeStackSetOperationOutput(StackSetOperation=result) + + @handler("DeleteStackInstances", expand=False) + def delete_stack_instances( + self, + context: RequestContext, + request: DeleteStackInstancesInput, + ) -> DeleteStackInstancesOutput: + state = get_cloudformation_store(context.account_id, context.region) + + stack_set_name = request["StackSetName"] + stack_set = find_stack_set_v2(state, stack_set_name) + if not stack_set: + raise StackSetNotFoundError(stack_set_name) + + op_id = request.get("OperationId") or short_uid() + + accounts = request["Accounts"] + regions = request["Regions"] + + operations_to_await = [] + for account in accounts: + for region in regions: + cf_client = connect_to(aws_access_key_id=account, region_name=region).cloudformation + instance = find_stack_instance(stack_set, account, region) + + # TODO: check parity with AWS + # TODO: delete stack instance? + if not instance: + continue + + cf_client.delete_stack(StackName=instance.stack_id) + operations_to_await.append(instance) + + for instance in operations_to_await: + cf_client = connect_to( + aws_access_key_id=instance.account_id, region_name=instance.region_name + ).cloudformation + cf_client.get_waiter("stack_delete_complete").wait(StackName=instance.stack_id) + stack_set.stack_instances.remove(instance) + + # record operation + operation = StackSetOperation( + OperationId=op_id, + StackSetId=stack_set.stack_set_id, + Action=StackSetOperationAction.DELETE, + Status=StackSetOperationStatus.SUCCEEDED, + ) + stack_set.operations[op_id] = operation + + return DeleteStackInstancesOutput(OperationId=op_id) + + @handler("DeleteStackSet") + def delete_stack_set( + self, + context: RequestContext, + stack_set_name: StackSetName, + call_as: CallAs = None, + **kwargs, + ) -> DeleteStackSetOutput: + state = get_cloudformation_store(context.account_id, context.region) + stack_set = find_stack_set_v2(state, stack_set_name) + if not stack_set: + # operation is idempotent + return DeleteStackSetOutput() + + # clean up any left-over instances + operations_to_await = [] + for instance in stack_set.stack_instances: + cf_client = connect_to( + aws_access_key_id=instance.account_id, region_name=instance.region_name + ).cloudformation + cf_client.delete_stack(StackName=instance.stack_id) + operations_to_await.append(instance) + + for instance in operations_to_await: + cf_client = connect_to( + aws_access_key_id=instance.account_id, region_name=instance.region_name + ).cloudformation + cf_client.get_waiter("stack_delete_complete").wait(StackName=instance.stack_id) + stack_set.stack_instances.remove(instance) + + state.stack_sets_v2.pop(stack_set.stack_set_id) + + return DeleteStackSetOutput() + @handler("DescribeStackEvents") def describe_stack_events( self, context: RequestContext, - stack_name: StackName = None, - next_token: NextToken = None, + stack_name: StackName, + next_token: NextToken | None = None, **kwargs, ) -> DescribeStackEventsOutput: + if not stack_name: + raise ValidationError( + "1 validation error detected: Value null at 'stackName' failed to satisfy constraint: Member must not be null" + ) state = get_cloudformation_store(context.account_id, context.region) stack = find_stack_v2(state, stack_name) if not stack: raise StackNotFoundError(stack_name) return DescribeStackEventsOutput(StackEvents=stack.events) + @handler("GetTemplate") + def get_template( + self, + context: RequestContext, + stack_name: StackName = None, + change_set_name: ChangeSetNameOrId = None, + template_stage: TemplateStage = None, + **kwargs, + ) -> GetTemplateOutput: + state = get_cloudformation_store(context.account_id, context.region) + if change_set_name: + if not is_changeset_arn(change_set_name) and not stack_name: + raise ValidationError("StackName is a required parameter.") + + change_set = find_change_set_v2(state, change_set_name, stack_name=stack_name) + if not change_set: + raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist") + stack = change_set.stack + elif stack_name: + stack = find_stack_v2(state, stack_name) + if not stack: + raise StackNotFoundError( + stack_name, message_override=f"Stack with id {stack_name} does not exist" + ) + else: + raise ValidationError("StackName is required if ChangeSetName is not specified.") + + if template_stage == TemplateStage.Processed and "Transform" in stack.template_body: + template_body = json.dumps(stack.processed_template) + else: + template_body = stack.template_body + + return GetTemplateOutput( + TemplateBody=template_body, + StagesAvailable=[TemplateStage.Original, TemplateStage.Processed], + ) + @handler("GetTemplateSummary", expand=False) def get_template_summary( self, @@ -602,6 +1456,12 @@ def get_template_summary( stack = find_stack_v2(state, stack_name) if not stack: raise StackNotFoundError(stack_name) + + if stack.status == StackStatus.REVIEW_IN_PROGRESS: + raise ValidationError( + "GetTemplateSummary cannot be called on REVIEW_IN_PROGRESS stacks." + ) + template = stack.template else: template_body = request.get("TemplateBody") @@ -623,6 +1483,11 @@ def get_template_summary( template = template_preparer.parse_template(template_body) id_summaries = defaultdict(list) + if "Resources" not in template: + raise ValidationError( + "Template format error: At least one Resources member must be defined." + ) + for resource_id, resource in template["Resources"].items(): res_type = resource["Type"] id_summaries[res_type].append(resource_id) @@ -650,6 +1515,22 @@ def get_template_summary( return result + @handler("UpdateTerminationProtection") + def update_termination_protection( + self, + context: RequestContext, + enable_termination_protection: EnableTerminationProtection, + stack_name: StackNameOrId, + **kwargs, + ) -> UpdateTerminationProtectionOutput: + state = get_cloudformation_store(context.account_id, context.region) + stack = find_stack_v2(state, stack_name) + if not stack: + raise StackNotFoundError(stack_name) + + stack.enable_termination_protection = enable_termination_protection + return UpdateTerminationProtectionOutput(StackId=stack.stack_id) + @handler("UpdateStack", expand=False) def update_stack( self, @@ -680,6 +1561,13 @@ def update_stack( template_body = api_utils.extract_template_body(request) structured_template = template_preparer.parse_template(template_body) + if "CAPABILITY_AUTO_EXPAND" not in request.get("Capabilities", []) and ( + "Transform" in structured_template.keys() or "Fn::Transform" in template_body + ): + raise InsufficientCapabilitiesException( + "Requires capabilities : [CAPABILITY_AUTO_EXPAND]" + ) + # this is intentionally not in a util yet. Let's first see how the different operations deal with these before generalizing # handle ARN stack_name here (not valid for initial CREATE, since stack doesn't exist yet) stack: Stack @@ -703,28 +1591,36 @@ def update_stack( raise RuntimeError("Multiple stacks matched, update matching logic") stack = active_stack_candidates[0] + if ( + stack.status == StackStatus.DELETE_COMPLETE + or stack.status == StackStatus.DELETE_IN_PROGRESS + ): + raise ValidationError( + f"Stack:{stack.stack_id} is in {stack.status} state and can not be updated." + ) + # TODO: proper status modeling before_parameters = stack.resolved_parameters # TODO: reconsider the way parameters are modelled in the update graph process. # The options might be reduce to using the current style, or passing the extra information # as a metadata object. The choice should be made considering when the extra information # is needed for the update graph building, or only looked up in downstream tasks (metadata). - request_parameters = request.get("Parameters", list()) + request_parameters = request.get("Parameters", []) # TODO: handle parameter defaults and resolution - after_parameters: dict[str, Any] = { - parameter["ParameterKey"]: parameter["ParameterValue"] - for parameter in request_parameters - } + after_parameters = self._extract_after_parameters(request_parameters, before_parameters) + before_template = stack.template after_template = structured_template previous_update_model = None - if previous_change_set := find_change_set_v2(state, stack.change_set_id): - previous_update_model = previous_change_set.update_model + if stack.change_set_id: + if previous_change_set := find_change_set_v2(state, stack.change_set_id): + previous_update_model = previous_change_set.update_model change_set = ChangeSet( stack, {"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE}, + template_body=template_body, template=after_template, ) self._setup_change_set_model( @@ -739,7 +1635,7 @@ def update_stack( # TODO: some changes are only detectable at runtime; consider using # the ChangeSetModelDescriber, or a new custom visitors, to # pick-up on runtime changes. - if change_set.update_model.node_template.change_type == ChangeType.UNCHANGED: + if not change_set.has_changes(): raise ValidationError("No updates are to be performed.") stack.set_stack_status(StackStatus.UPDATE_IN_PROGRESS) @@ -750,20 +1646,51 @@ def _run(*args): result = change_set_executor.execute() stack.set_stack_status(StackStatus.UPDATE_COMPLETE) stack.resolved_resources = result.resources - stack.resolved_parameters = result.parameters stack.resolved_outputs = result.outputs # if the deployment succeeded, update the stack's template representation to that # which was just deployed stack.template = change_set.template + stack.template_body = change_set.template_body + stack.resolved_parameters = change_set.resolved_parameters + stack.resolved_exports = {} + for output in result.outputs: + if export_name := output.get("ExportName"): + stack.resolved_exports[export_name] = output["OutputValue"] except Exception as e: - LOG.error("Update Stack failed: %s", e, exc_info=LOG.isEnabledFor(logging.WARNING)) + LOG.error( + "Update Stack failed: %s", + e, + exc_info=LOG.isEnabledFor(logging.WARNING) and config.CFN_VERBOSE_ERRORS, + ) stack.set_stack_status(StackStatus.UPDATE_FAILED) start_worker_thread(_run) - # TODO: stack id return UpdateStackOutput(StackId=stack.stack_id) + @staticmethod + def _extract_after_parameters( + request_parameters, before_parameters: dict[str, str] | None = None + ) -> dict[str, str]: + before_parameters = before_parameters or {} + after_parameters = {} + for parameter in request_parameters: + key = parameter["ParameterKey"] + if parameter.get("UsePreviousValue", False): + # todo: what if the parameter does not exist in the before parameters + before = before_parameters[key] + after_parameters[key] = ( + before.get("resolved_value") + or before.get("given_value") + or before.get("default_value") + ) + continue + + if "ParameterValue" in parameter: + after_parameters[key] = parameter["ParameterValue"] + continue + return after_parameters + @handler("DeleteStack") def delete_stack( self, @@ -785,39 +1712,45 @@ def delete_stack( # created, but never executed if stack.status == StackStatus.REVIEW_IN_PROGRESS and not stack.resolved_resources: stack.set_stack_status(StackStatus.DELETE_COMPLETE) - stack.deletion_time = datetime.now(tz=timezone.utc) + stack.deletion_time = datetime.now(tz=UTC) return - previous_update_model = None - if previous_change_set := find_change_set_v2(state, stack.change_set_id): - previous_update_model = previous_change_set.update_model + stack.set_stack_status(StackStatus.DELETE_IN_PROGRESS) # create a dummy change set - change_set = ChangeSet(stack, {"ChangeSetName": f"delete-stack_{stack.stack_name}"}) # noqa + change_set = ChangeSet( + stack, {"ChangeSetName": f"delete-stack_{stack.stack_name}"}, template_body="" + ) # noqa self._setup_change_set_model( change_set=change_set, - before_template=stack.template, + before_template=stack.processed_template, after_template=None, before_parameters=stack.resolved_parameters, after_parameters=None, - previous_update_model=previous_update_model, ) change_set_executor = ChangeSetModelExecutor(change_set) def _run(*args): try: - stack.set_stack_status(StackStatus.DELETE_IN_PROGRESS) change_set_executor.execute() stack.set_stack_status(StackStatus.DELETE_COMPLETE) - stack.deletion_time = datetime.now(tz=timezone.utc) + stack.deletion_time = datetime.now(tz=UTC) except Exception as e: LOG.warning( "Failed to delete stack '%s': %s", stack.stack_name, e, - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) stack.set_stack_status(StackStatus.DELETE_FAILED) start_worker_thread(_run) + return ExecuteChangeSetOutput() + + @handler("ListExports") + def list_exports( + self, context: RequestContext, next_token: NextToken = None, **kwargs + ) -> ListExportsOutput: + store = get_cloudformation_store(account_id=context.account_id, region_name=context.region) + return ListExportsOutput(Exports=store.exports_v2.values()) diff --git a/localstack-core/localstack/services/cloudformation/v2/types.py b/localstack-core/localstack/services/cloudformation/v2/types.py new file mode 100644 index 0000000000000..4e5b404dfbc96 --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/v2/types.py @@ -0,0 +1,38 @@ +from datetime import datetime +from typing import NotRequired, TypedDict + +from localstack.aws.api.cloudformation import ResourceStatus + + +class EngineParameter(TypedDict): + """ + Parameters supplied by the user. The resolved value field is populated by the engine + """ + + type_: str + given_value: NotRequired[str | None] + resolved_value: NotRequired[str | None] + default_value: NotRequired[str | None] + no_echo: NotRequired[bool | None] + + +def engine_parameter_value(parameter: EngineParameter) -> str: + given_value = parameter.get("given_value") + if given_value is not None: + return given_value + + default_value = parameter.get("default_value") + if default_value is not None: + return default_value + + raise RuntimeError("Parameter value is None") + + +class ResolvedResource(TypedDict): + LogicalResourceId: str + Type: str + Properties: dict + LastUpdatedTimestamp: datetime + ResourceStatus: NotRequired[ResourceStatus] + PhysicalResourceId: NotRequired[str] + ResourceStatusReason: NotRequired[str] diff --git a/localstack-core/localstack/services/cloudformation/v2/utils.py b/localstack-core/localstack/services/cloudformation/v2/utils.py index 02a6cbb971a99..c062ada712bbc 100644 --- a/localstack-core/localstack/services/cloudformation/v2/utils.py +++ b/localstack-core/localstack/services/cloudformation/v2/utils.py @@ -2,4 +2,7 @@ def is_v2_engine() -> bool: - return config.SERVICE_PROVIDER_CONFIG.get_provider("cloudformation") == "engine-v2" + return config.SERVICE_PROVIDER_CONFIG.get_provider("cloudformation") not in { + "engine-legacy", + "engine-legacy_pro", + } diff --git a/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py b/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py index 2b0675f121450..07018e4124f20 100644 --- a/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py +++ b/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py @@ -2,13 +2,14 @@ import logging import math import threading -from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING, List, Optional +from datetime import UTC, datetime, timedelta +from typing import TYPE_CHECKING from localstack.aws.api.cloudwatch import MetricAlarm, MetricDataQuery, MetricStat, StateValue from localstack.aws.connect import connect_to +from localstack.runtime.shutdown import SHUTDOWN_HANDLERS from localstack.utils.aws import arns, aws_stack -from localstack.utils.scheduler import Scheduler +from localstack.utils.scheduler import ScheduledTask, Scheduler if TYPE_CHECKING: from mypy_boto3_cloudwatch import CloudWatchClient @@ -38,16 +39,26 @@ def __init__(self) -> None: """ super().__init__() self.scheduler = Scheduler() - self.thread = threading.Thread(target=self.scheduler.run, name="cloudwatch-scheduler") - self.thread.start() - self.scheduled_alarms = {} + self.scheduled_alarms: dict[str, ScheduledTask] = {} + self.thread: threading.Thread | None = None - def shutdown_scheduler(self) -> None: + def start(self) -> None: + if not (self.thread and self.thread.is_alive()): + LOG.debug("Starting CloudWatch scheduler") + self.thread = threading.Thread(target=self.scheduler.run, name="cloudwatch-scheduler") + self.thread.start() + SHUTDOWN_HANDLERS.register(self.shutdown) + + def shutdown(self) -> None: """ - Shutsdown the scheduler, must be called before application stops + Shutdown the scheduler, must be called before application stops """ + LOG.debug("Stopping CloudWatch scheduler") self.scheduler.close() - self.thread.join(10) + self.scheduled_alarms.clear() + SHUTDOWN_HANDLERS.unregister(self.shutdown) + if self.thread: + self.thread.join(10) def schedule_metric_alarm(self, alarm_arn: str) -> None: """(Re-)schedules the alarm, if the alarm is re-scheduled, the running alarm scheduler will be cancelled before @@ -69,7 +80,10 @@ def schedule_metric_alarm(self, alarm_arn: str) -> None: schedule_period = evaluation_periods * period def on_error(e): - LOG.exception("Error executing scheduled alarm", exc_info=e) + if LOG.isEnabledFor(logging.DEBUG): + LOG.exception("Error executing scheduled alarm", exc_info=e) + else: + LOG.error("Error executing scheduled alarm") task = self.scheduler.schedule( func=calculate_alarm_state, @@ -120,7 +134,7 @@ def _is_alarm_supported(self, alarm_details: MetricAlarm) -> bool: return True -def get_metric_alarm_details_for_alarm_arn(alarm_arn: str) -> Optional[MetricAlarm]: +def get_metric_alarm_details_for_alarm_arn(alarm_arn: str) -> MetricAlarm | None: alarm_name = arns.extract_resource_from_arn(alarm_arn).split(":", 1)[1] client = get_cloudwatch_client_for_region_of_alarm(alarm_arn) metric_alarms = client.describe_alarms(AlarmNames=[alarm_name])["MetricAlarms"] @@ -155,7 +169,7 @@ def generate_metric_query(alarm_details: MetricAlarm) -> MetricDataQuery: ) -def is_threshold_exceeded(metric_values: List[float], alarm_details: MetricAlarm) -> bool: +def is_threshold_exceeded(metric_values: list[float], alarm_details: MetricAlarm) -> bool: """Evaluates if the threshold is exceeded for the configured alarm and given metric values :param metric_values: values to compare against threshold @@ -185,7 +199,7 @@ def is_threshold_exceeded(metric_values: List[float], alarm_details: MetricAlarm return False -def is_triggering_premature_alarm(metric_values: List[float], alarm_details: MetricAlarm) -> bool: +def is_triggering_premature_alarm(metric_values: list[float], alarm_details: MetricAlarm) -> bool: """ Checks if a premature alarm should be triggered. https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#CloudWatch-alarms-avoiding-premature-transition: @@ -217,7 +231,7 @@ def is_triggering_premature_alarm(metric_values: List[float], alarm_details: Met return False -def collect_metric_data(alarm_details: MetricAlarm, client: "CloudWatchClient") -> List[float]: +def collect_metric_data(alarm_details: MetricAlarm, client: "CloudWatchClient") -> list[float]: """ Collects the metric data for the evaluation interval. @@ -230,15 +244,16 @@ def collect_metric_data(alarm_details: MetricAlarm, client: "CloudWatchClient") evaluation_periods = alarm_details["EvaluationPeriods"] period = alarm_details["Period"] - # From the docs: "Whenever an alarm evaluates whether to change state, CloudWatch attempts to retrieve a higher number of data - # points than the number specified as Evaluation Periods." + # From the docs: "Whenever an alarm evaluates whether to change state, CloudWatch attempts to retrieve a + # higher number of data points than the number specified as Evaluation Periods." # No other indication, try to calculate a reasonable value: magic_number = max(math.floor(evaluation_periods / 3), 2) collected_periods = evaluation_periods + magic_number - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.now(tz=UTC) metric_query = generate_metric_query(alarm_details) + # Fetching the evaluation range: # get_metric_data needs to be run in a loop, so we also collect empty data points on the right position for i in range(0, collected_periods): start_time = now - timedelta(seconds=period) @@ -390,6 +405,7 @@ def calculate_alarm_state(alarm_arn: str) -> None: alarm_name, alarm_state, StateValue.OK, + # TODO: cannot find a snapshot with StateValue.OK and the Threshold crossed value, verify this case THRESHOLD_CROSSED, state_reason_data=state_reason_data, ) diff --git a/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py b/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py index 43383cf2782ad..63bd8c691819e 100644 --- a/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py +++ b/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py @@ -2,8 +2,7 @@ import os import sqlite3 import threading -from datetime import datetime, timezone -from typing import Dict, List, Optional +from datetime import UTC, datetime from localstack import config from localstack.aws.api.cloudwatch import MetricData, MetricDataQuery, ScanBy @@ -92,7 +91,7 @@ def add_metric_data( self, account_id: str, region: str, namespace: str, metric_data: MetricData ): def _get_current_unix_timestamp_utc(): - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) return int(now.timestamp()) for metric in metric_data: @@ -218,7 +217,7 @@ def get_metric_data_stat( start_time: datetime, end_time: datetime, scan_by: str, - ) -> Dict[str, List]: + ) -> dict[str, list]: metric_stat = query.get("MetricStat") metric = metric_stat.get("Metric") period = metric_stat.get("Period") @@ -389,7 +388,7 @@ def clear_tables(self): cur.execute("VACUUM") conn.commit() - def _get_ordered_dimensions_with_separator(self, dims: Optional[List[Dict]], for_search=False): + def _get_ordered_dimensions_with_separator(self, dims: list[dict] | None, for_search=False): """ Returns a string with the dimensions in the format "Name=Value\tName=Value\tName=Value" in order to store the metric with the dimensions in a single column in the database diff --git a/localstack-core/localstack/services/cloudwatch/models.py b/localstack-core/localstack/services/cloudwatch/models.py index a1246569f4f97..247ea45903641 100644 --- a/localstack-core/localstack/services/cloudwatch/models.py +++ b/localstack-core/localstack/services/cloudwatch/models.py @@ -1,16 +1,19 @@ import datetime -from datetime import timezone -from typing import Dict, List -from localstack.aws.api.cloudwatch import CompositeAlarm, DashboardBody, MetricAlarm, StateValue +from localstack.aws.api.cloudwatch import ( + AlarmHistoryItem, + CompositeAlarm, + DashboardBody, + MetricAlarm, + StateValue, +) from localstack.services.stores import ( AccountRegionBundle, BaseStore, - CrossRegionAttribute, LocalAttribute, ) from localstack.utils.aws import arns -from localstack.utils.tagging import TaggingService +from localstack.utils.tagging import Tags class LocalStackMetricAlarm: @@ -22,10 +25,12 @@ def __init__(self, account_id: str, region: str, alarm: MetricAlarm): self.account_id = account_id self.region = region self.alarm = alarm + # Tags are already stored as part of Tagging Service or RGTA plugin + self.alarm.pop("Tags", None) self.set_default_attributes() def set_default_attributes(self): - current_time = datetime.datetime.now(timezone.utc) + current_time = datetime.datetime.now(datetime.UTC) self.alarm["AlarmArn"] = arns.cloudwatch_alarm_arn( self.alarm["AlarmName"], account_id=self.account_id, region_name=self.region ) @@ -50,10 +55,12 @@ def __init__(self, account_id: str, region: str, alarm: CompositeAlarm): self.account_id = account_id self.region = region self.alarm = alarm + # Tags are already stored as part of Tagging Service or RGTA plugin + self.alarm.pop("Tags", None) self.set_default_attributes() def set_default_attributes(self): - current_time = datetime.datetime.now(timezone.utc) + current_time = datetime.datetime.now(datetime.UTC) self.alarm["AlarmArn"] = arns.cloudwatch_alarm_arn( self.alarm["AlarmName"], account_id=self.account_id, region_name=self.region ) @@ -74,6 +81,8 @@ class LocalStackDashboard: dashboard_name: str dashboard_arn: str dashboard_body: DashboardBody + last_modified: datetime.datetime + size: int def __init__( self, account_id: str, region: str, dashboard_name: str, dashboard_body: DashboardBody @@ -93,17 +102,16 @@ def __init__( class CloudWatchStore(BaseStore): - # maps resource ARN to tags - TAGS: TaggingService = CrossRegionAttribute(default=TaggingService) - # maps resource ARN to alarms - alarms: Dict[str, LocalStackAlarm] = LocalAttribute(default=dict) + alarms: dict[str, LocalStackAlarm] = LocalAttribute(default=dict) # Contains all the Alarm Histories. Per documentation, an alarm history is retained even if the alarm is deleted, # making it necessary to save this at store level - histories: List[Dict] = LocalAttribute(default=list) + histories: list[AlarmHistoryItem] = LocalAttribute(default=list) - dashboards: Dict[str, LocalStackDashboard] = LocalAttribute(default=dict) + dashboards: dict[str, LocalStackDashboard] = LocalAttribute(default=dict) + # Maps resource ARN to tags + tags: Tags = LocalAttribute(default=Tags) cloudwatch_stores = AccountRegionBundle("cloudwatch", CloudWatchStore) diff --git a/localstack-core/localstack/services/cloudwatch/provider.py b/localstack-core/localstack/services/cloudwatch/provider.py index 42e4b5fe94e58..1c706369bfc40 100644 --- a/localstack-core/localstack/services/cloudwatch/provider.py +++ b/localstack-core/localstack/services/cloudwatch/provider.py @@ -1,11 +1,11 @@ import json import logging import uuid -from typing import Any, Optional -from xml.sax.saxutils import escape +from datetime import datetime +from typing import Any from moto.cloudwatch import cloudwatch_backends -from moto.cloudwatch.models import CloudWatchBackend, FakeAlarm, MetricDatum +from moto.cloudwatch.models import Alarm, CloudWatchBackend, MetricDatum from localstack.aws.accounts import get_account_id_from_access_key_id from localstack.aws.api import CommonServiceException, RequestContext, handler @@ -35,6 +35,7 @@ from localstack.services.cloudwatch.alarm_scheduler import AlarmScheduler from localstack.services.edge import ROUTER from localstack.services.plugins import SERVICE_PLUGINS, ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.aws import arns from localstack.utils.aws.arns import extract_account_id_from_arn, lambda_function_name from localstack.utils.aws.request_context import ( @@ -54,7 +55,7 @@ LOG = logging.getLogger(__name__) -@patch(target=FakeAlarm.update_state) +@patch(target=Alarm.update_state) def update_state(target, self, reason, reason_data, state_value): if reason_data is None: reason_data = "" @@ -115,21 +116,19 @@ def put_metric_alarm( description: str, dimensions: list[dict[str, str]], alarm_actions: list[str], - metric_data_queries: Optional[list[Any]] = None, - datapoints_to_alarm: Optional[int] = None, - extended_statistic: Optional[str] = None, - ok_actions: Optional[list[str]] = None, - insufficient_data_actions: Optional[list[str]] = None, - unit: Optional[str] = None, + metric_data_queries: list[Any] | None = None, + datapoints_to_alarm: int | None = None, + extended_statistic: str | None = None, + ok_actions: list[str] | None = None, + insufficient_data_actions: list[str] | None = None, + unit: str | None = None, actions_enabled: bool = True, - treat_missing_data: Optional[str] = None, - evaluate_low_sample_count_percentile: Optional[str] = None, - threshold_metric_id: Optional[str] = None, - rule: Optional[str] = None, - tags: Optional[list[dict[str, str]]] = None, -) -> FakeAlarm: - if description: - description = escape(description) + treat_missing_data: str | None = None, + evaluate_low_sample_count_percentile: str | None = None, + threshold_metric_id: str | None = None, + rule: str | None = None, + tags: list[dict[str, str]] | None = None, +) -> Alarm: return target( self, name, @@ -158,7 +157,7 @@ def put_metric_alarm( ) -def create_metric_data_query_from_alarm(alarm: FakeAlarm): +def create_metric_data_query_from_alarm(alarm: Alarm): # TODO may need to be adapted for other use cases # verified return value with a snapshot test return [ @@ -179,7 +178,7 @@ def create_metric_data_query_from_alarm(alarm: FakeAlarm): def create_message_response_update_state_lambda( - alarm: FakeAlarm, old_state, old_state_reason, old_state_timestamp + alarm: Alarm, old_state, old_state_reason, old_state_timestamp ): response = { "accountId": extract_account_id_from_arn(alarm.alarm_arn), @@ -189,12 +188,12 @@ def create_message_response_update_state_lambda( "state": { "value": alarm.state_value, "reason": alarm.state_reason, - "timestamp": alarm.state_updated_timestamp, + "timestamp": _to_iso_8601_datetime_with_nanoseconds(alarm.state_updated_timestamp), }, "previousState": { "value": old_state, "reason": old_state_reason, - "timestamp": old_state_timestamp, + "timestamp": _to_iso_8601_datetime_with_nanoseconds(old_state_timestamp), }, "configuration": { "description": alarm.description or "", @@ -204,7 +203,7 @@ def create_message_response_update_state_lambda( ), # TODO: add test with metric_data_queries }, }, - "time": alarm.state_updated_timestamp, + "time": _to_iso_8601_datetime_with_nanoseconds(alarm.state_updated_timestamp), "region": alarm.region_name, "source": "aws.cloudwatch", } @@ -217,10 +216,12 @@ def create_message_response_update_state_sns(alarm, old_state): "OldStateValue": old_state, "AlarmName": alarm.name, "AlarmDescription": alarm.description or "", - "AlarmConfigurationUpdatedTimestamp": alarm.configuration_updated_timestamp, + "AlarmConfigurationUpdatedTimestamp": _to_iso_8601_datetime_with_nanoseconds( + alarm.configuration_updated_timestamp + ), "NewStateValue": alarm.state_value, "NewStateReason": alarm.state_reason, - "StateChangeTime": alarm.state_updated_timestamp, + "StateChangeTime": _to_iso_8601_datetime_with_nanoseconds(alarm.state_updated_timestamp), # the long-name for 'region' should be used - as we don't have it, we use the short name # which needs to be slightly changed to make snapshot tests work "Region": alarm.region_name.replace("-", " ").capitalize(), @@ -268,6 +269,11 @@ def __init__(self, message: str): super().__init__("ValidationError", message, 400, True) +def _to_iso_8601_datetime_with_nanoseconds(date: datetime | None) -> str | None: + if date is not None: + return date.strftime("%Y-%m-%dT%H:%M:%S.%f000Z") + + def _set_alarm_actions(context, alarm_names, enabled): backend = cloudwatch_backends[context.account_id][context.region] for name in alarm_names: @@ -299,23 +305,28 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook): def __init__(self): self.tags = TaggingService() - self.alarm_scheduler = None + self.alarm_scheduler = AlarmScheduler() + + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(cloudwatch_backends) def on_after_init(self): ROUTER.add(PATH_GET_RAW_METRICS, self.get_raw_metrics) - self.start_alarm_scheduler() + + def on_before_start(self): + self.alarm_scheduler.start() def on_before_state_reset(self): - self.shutdown_alarm_scheduler() + self.alarm_scheduler.shutdown() def on_after_state_reset(self): - self.start_alarm_scheduler() + self.alarm_scheduler.start() def on_before_state_load(self): - self.shutdown_alarm_scheduler() + self.alarm_scheduler.shutdown() def on_after_state_load(self): - self.start_alarm_scheduler() + self.alarm_scheduler.start() def restart_alarms(*args): poll_condition(lambda: SERVICE_PLUGINS.is_running("cloudwatch")) @@ -324,17 +335,7 @@ def restart_alarms(*args): start_worker_thread(restart_alarms) def on_before_stop(self): - self.shutdown_alarm_scheduler() - - def start_alarm_scheduler(self): - if not self.alarm_scheduler: - LOG.debug("starting cloudwatch scheduler") - self.alarm_scheduler = AlarmScheduler() - - def shutdown_alarm_scheduler(self): - LOG.debug("stopping cloudwatch scheduler") - self.alarm_scheduler.shutdown_scheduler() - self.alarm_scheduler = None + self.alarm_scheduler.shutdown() def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwargs) -> None: moto.call_moto(context) diff --git a/localstack-core/localstack/services/cloudwatch/provider_v2.py b/localstack-core/localstack/services/cloudwatch/provider_v2.py index 31f737fec9e23..8428e6eaf8b90 100644 --- a/localstack-core/localstack/services/cloudwatch/provider_v2.py +++ b/localstack-core/localstack/services/cloudwatch/provider_v2.py @@ -4,19 +4,19 @@ import re import threading import uuid -from datetime import timezone -from typing import List from localstack.aws.api import CommonServiceException, RequestContext, handler from localstack.aws.api.cloudwatch import ( AccountId, ActionPrefix, + AlarmHistoryItem, AlarmName, AlarmNamePrefix, AlarmNames, AlarmTypes, AmazonResourceName, CloudwatchApi, + ContributorId, DashboardBody, DashboardName, DashboardNamePrefix, @@ -67,6 +67,7 @@ Statistic, Statistics, StrictEntityValidation, + Tag, TagKeyList, TagList, TagResourceOutput, @@ -109,17 +110,11 @@ AWS_MAX_DATAPOINTS_ACCEPTED: int = 1440 -class ValidationError(CommonServiceException): - # TODO: check this error against AWS (doesn't exist in the API) +class ValidationException(CommonServiceException): def __init__(self, message: str): super().__init__("ValidationError", message, 400, True) -class InvalidParameterCombination(CommonServiceException): - def __init__(self, message: str): - super().__init__("InvalidParameterCombination", message, 400, True) - - def _validate_parameters_for_put_metric_data(metric_data: MetricData) -> None: for index, metric_item in enumerate(metric_data): indexplusone = index + 1 @@ -154,9 +149,9 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook): """ def __init__(self): - self.alarm_scheduler: AlarmScheduler = None self.store = None self.cloudwatch_database = CloudwatchDatabase() + self.alarm_scheduler = AlarmScheduler() @staticmethod def get_store(account_id: str, region: str) -> CloudWatchStore: @@ -168,21 +163,23 @@ def accept_state_visitor(self, visitor: StateVisitor): def on_after_init(self): ROUTER.add(PATH_GET_RAW_METRICS, self.get_raw_metrics) - self.start_alarm_scheduler() + + def on_before_start(self): + self.alarm_scheduler.start() def on_before_state_reset(self): - self.shutdown_alarm_scheduler() + self.alarm_scheduler.shutdown() self.cloudwatch_database.clear_tables() def on_after_state_reset(self): self.cloudwatch_database = CloudwatchDatabase() - self.start_alarm_scheduler() + self.alarm_scheduler.start() def on_before_state_load(self): - self.shutdown_alarm_scheduler() + self.alarm_scheduler.shutdown() def on_after_state_load(self): - self.start_alarm_scheduler() + self.alarm_scheduler.start() def restart_alarms(*args): poll_condition(lambda: SERVICE_PLUGINS.is_running("cloudwatch")) @@ -191,17 +188,7 @@ def restart_alarms(*args): start_worker_thread(restart_alarms) def on_before_stop(self): - self.shutdown_alarm_scheduler() - - def start_alarm_scheduler(self): - if not self.alarm_scheduler: - LOG.debug("starting cloudwatch scheduler") - self.alarm_scheduler = AlarmScheduler() - - def shutdown_alarm_scheduler(self): - LOG.debug("stopping cloudwatch scheduler") - self.alarm_scheduler.shutdown_scheduler() - self.alarm_scheduler = None + self.alarm_scheduler.shutdown() def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwargs) -> None: """ @@ -215,6 +202,7 @@ def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwar self.alarm_scheduler.delete_scheduler_for_alarm(alarm_arn) store = self.get_store(context.account_id, context.region) store.alarms.pop(alarm_arn, None) + self._remove_all_resource_tags(alarm_arn, context.account_id, context.region) def put_metric_data( self, @@ -244,10 +232,10 @@ def get_metric_data( label_options: LabelOptions = None, **kwargs, ) -> GetMetricDataOutput: - results: List[MetricDataResult] = [] + results: list[MetricDataResult] = [] limit = max_datapoints or 100_800 messages: MetricDataResultMessages = [] - nxt = None + nxt: str | None = None label_additions = [] for diff in LABEL_DIFFERENTIATORS: @@ -280,15 +268,15 @@ def get_metric_data( # Paginate timestamp_value_dicts = [ { - "Timestamp": timestamp, - "Value": value, + "Timestamp": datetime.datetime.fromtimestamp(timestamp, tz=datetime.UTC), + "Value": float(value), } for timestamp, value in zip(timestamps, values, strict=False) ] pagination = PaginatedList(timestamp_value_dicts) timestamp_page, nxt = pagination.get_page( - lambda item: item.get("Timestamp"), + lambda item: str(item.get("Timestamp")), next_token=next_token, page_size=limit, ) @@ -316,6 +304,11 @@ def set_alarm_state( state_reason_data: StateReasonData = None, **kwargs, ) -> None: + if state_value not in ("OK", "ALARM", "INSUFFICIENT_DATA"): + raise ValidationException( + f"1 validation error detected: Value '{state_value}' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]" + ) + try: if state_reason_data: state_reason_data = json.loads(state_reason_data) @@ -334,10 +327,6 @@ def set_alarm_state( raise ResourceNotFound() old_state = alarm.alarm["StateValue"] - if state_value not in ("OK", "ALARM", "INSUFFICIENT_DATA"): - raise ValidationError( - f"1 validation error detected: Value '{state_value}' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]" - ) old_state_reason = alarm.alarm["StateReason"] old_state_update_timestamp = alarm.alarm["StateUpdatedTimestamp"] @@ -345,7 +334,7 @@ def set_alarm_state( if old_state == state_value: return - alarm.alarm["StateTransitionedTimestamp"] = datetime.datetime.now(timezone.utc) + alarm.alarm["StateTransitionedTimestamp"] = datetime.datetime.now(datetime.UTC) # update startDate (=last ALARM date) - should only update when a new alarm is triggered # the date is only updated if we have a reason-data, which is set by an alarm if state_reason_data: @@ -408,6 +397,32 @@ def get_raw_metrics(self, request: Request): """ return {"metrics": self.cloudwatch_database.get_all_metric_data() or []} + def _get_resource_tags( + self, resource_arn: AmazonResourceName, account_id: str, region: str + ) -> TagList: + store = self.get_store(account_id, region) + return [ + Tag(Key=key, Value=value) for key, value in store.tags.get_tags(resource_arn).items() + ] + + def _set_resource_tags( + self, resource_arn: AmazonResourceName, account_id: str, region: str, tags: TagList + ) -> None: + store = self.get_store(account_id, region) + store.tags.update_tags(resource_arn, {tag["Key"]: tag["Value"] for tag in tags}) + + def _remove_resource_tags( + self, resource_arn: AmazonResourceName, account_id: str, region: str, tag_keys: TagKeyList + ) -> None: + store = self.get_store(account_id, region) + store.tags.delete_tags(resource_arn, tag_keys) + + def _remove_all_resource_tags( + self, resource_arn: AmazonResourceName, account_id: str, region: str + ): + store = self.get_store(account_id, region) + store.tags.delete_all_tags(resource_arn) + @handler("PutMetricAlarm", expand=False) def put_metric_alarm(self, context: RequestContext, request: PutMetricAlarmInput) -> None: # missing will be the default, when not set (but it will not explicitly be set) @@ -417,7 +432,7 @@ def put_metric_alarm(self, context: RequestContext, request: PutMetricAlarmInput "ignore", "missing", ]: - raise ValidationError( + raise ValidationException( f"The value {request['TreatMissingData']} is not supported for TreatMissingData parameter. Supported values are [breaching, notBreaching, ignore, missing]." ) # do some sanity checks: @@ -426,7 +441,7 @@ def put_metric_alarm(self, context: RequestContext, request: PutMetricAlarmInput value = request.get("Period") if value not in (10, 30): if value % 60 != 0: - raise ValidationError("Period must be 10, 30 or a multiple of 60") + raise ValidationException("Period must be 10, 30 or a multiple of 60") if request.get("Statistic"): if request.get("Statistic") not in [ "SampleCount", @@ -435,7 +450,7 @@ def put_metric_alarm(self, context: RequestContext, request: PutMetricAlarmInput "Minimum", "Maximum", ]: - raise ValidationError( + raise ValidationException( f"Value '{request.get('Statistic')}' at 'statistic' failed to satisfy constraint: Member must satisfy enum value set: [Maximum, SampleCount, Sum, Minimum, Average]" ) @@ -449,7 +464,7 @@ def put_metric_alarm(self, context: RequestContext, request: PutMetricAlarmInput "evaluate", "ignore", ): - raise ValidationError( + raise ValidationException( f"Option {evaluate_low_sample_count_percentile} is not supported. " "Supported options for parameter EvaluateLowSampleCountPercentile are evaluate and ignore." ) @@ -459,6 +474,9 @@ def put_metric_alarm(self, context: RequestContext, request: PutMetricAlarmInput alarm_arn = metric_alarm.alarm["AlarmArn"] store.alarms[alarm_arn] = metric_alarm self.alarm_scheduler.schedule_metric_alarm(alarm_arn) + self._set_resource_tags( + alarm_arn, context.account_id, context.region, request.get("Tags", []) + ) @handler("PutCompositeAlarm", expand=False) def put_composite_alarm(self, context: RequestContext, request: PutCompositeAlarmInput) -> None: @@ -474,6 +492,9 @@ def put_composite_alarm(self, context: RequestContext, request: PutCompositeAlar alarm_arn = composite_alarm.alarm["AlarmArn"] store.alarms[alarm_arn] = composite_alarm + self._set_resource_tags( + alarm_arn, context.account_id, context.region, request.get("Tags", []) + ) def describe_alarms( self, @@ -541,9 +562,8 @@ def describe_alarms_for_metric( def list_tags_for_resource( self, context: RequestContext, resource_arn: AmazonResourceName, **kwargs ) -> ListTagsForResourceOutput: - store = self.get_store(context.account_id, context.region) - tags = store.TAGS.list_tags_for_resource(resource_arn) - return ListTagsForResourceOutput(Tags=tags.get("Tags", [])) + tags = self._get_resource_tags(resource_arn, context.account_id, context.region) + return ListTagsForResourceOutput(Tags=tags) def untag_resource( self, @@ -552,15 +572,13 @@ def untag_resource( tag_keys: TagKeyList, **kwargs, ) -> UntagResourceOutput: - store = self.get_store(context.account_id, context.region) - store.TAGS.untag_resource(resource_arn, tag_keys) + self._remove_resource_tags(resource_arn, context.account_id, context.region, tag_keys) return UntagResourceOutput() def tag_resource( self, context: RequestContext, resource_arn: AmazonResourceName, tags: TagList, **kwargs ) -> TagResourceOutput: - store = self.get_store(context.account_id, context.region) - store.TAGS.tag_resource(resource_arn, tags) + self._set_resource_tags(resource_arn, context.account_id, context.region, tags) return TagResourceOutput() def put_dashboard( @@ -661,7 +679,9 @@ def list_metrics( ] aliases_list = PaginatedList(metrics) page, nxt = aliases_list.get_page( - lambda metric: f"{metric.get('Namespace')}-{metric.get('MetricName')}-{metric.get('Dimensions')}", + lambda metric: ( + f"{metric.get('Namespace')}-{metric.get('MetricName')}-{metric.get('Dimensions')}" + ), next_token=next_token, page_size=LIST_METRICS_MAX_RESULTS, ) @@ -692,7 +712,7 @@ def get_metric_statistics( expected_datapoints = (end_time_unix - start_time_unix) / period if expected_datapoints > AWS_MAX_DATAPOINTS_ACCEPTED: - raise InvalidParameterCombination( + raise InvalidParameterCombinationException( f"You have requested up to {int(expected_datapoints)} datapoints, which exceeds the limit of {AWS_MAX_DATAPOINTS_ACCEPTED}. " f"You may reduce the datapoints requested by increasing Period, or decreasing the time range." ) @@ -739,7 +759,7 @@ def get_metric_statistics( for i, timestamp in enumerate(timestamps): stat_datapoints.setdefault(selected_unit, {}) stat_datapoints[selected_unit].setdefault(timestamp, {}) - stat_datapoints[selected_unit][timestamp][stat] = values[i] + stat_datapoints[selected_unit][timestamp][stat] = float(values[i]) stat_datapoints[selected_unit][timestamp]["Unit"] = selected_unit datapoints: list[Datapoint] = [] @@ -763,7 +783,7 @@ def _update_state( self, context: RequestContext, alarm: LocalStackAlarm, - state_value: str, + state_value: StateValue, state_reason: str, state_reason_data: dict = None, ): @@ -783,18 +803,17 @@ def _update_state( "stateReasonData": state_reason_data, }, } - store.histories.append( - { - "Timestamp": timestamp_millis(alarm.alarm["StateUpdatedTimestamp"]), - "HistoryItemType": HistoryItemType.StateUpdate, - "AlarmName": alarm.alarm["AlarmName"], - "HistoryData": json.dumps(history_data), - "HistorySummary": f"Alarm updated from {old_state} to {state_value}", - "AlarmType": "MetricAlarm" - if isinstance(alarm, LocalStackMetricAlarm) - else "CompositeAlarm", - } + alarm_history_item = AlarmHistoryItem( + Timestamp=alarm.alarm["StateUpdatedTimestamp"], + HistoryItemType=HistoryItemType.StateUpdate, + AlarmName=alarm.alarm["AlarmName"], + HistoryData=json.dumps(history_data), + HistorySummary=f"Alarm updated from {old_state} to {state_value}", + AlarmType="MetricAlarm" + if isinstance(alarm, LocalStackMetricAlarm) + else "CompositeAlarm", ) + store.histories.append(alarm_history_item) alarm.alarm["StateValue"] = state_value alarm.alarm["StateReason"] = state_reason if state_reason_data: @@ -824,14 +843,15 @@ def _set_alarm_actions(self, context, alarm_names, enabled): def describe_alarm_history( self, context: RequestContext, - alarm_name: AlarmName = None, - alarm_types: AlarmTypes = None, - history_item_type: HistoryItemType = None, - start_date: Timestamp = None, - end_date: Timestamp = None, - max_records: MaxRecords = None, - next_token: NextToken = None, - scan_by: ScanBy = None, + alarm_name: AlarmName | None = None, + alarm_contributor_id: ContributorId | None = None, + alarm_types: AlarmTypes | None = None, + history_item_type: HistoryItemType | None = None, + start_date: Timestamp | None = None, + end_date: Timestamp | None = None, + max_records: MaxRecords | None = None, + next_token: NextToken | None = None, + scan_by: ScanBy | None = None, **kwargs, ) -> DescribeAlarmHistoryOutput: store = self.get_store(context.account_id, context.region) @@ -839,15 +859,10 @@ def describe_alarm_history( if alarm_name: history = [h for h in history if h["AlarmName"] == alarm_name] - def _get_timestamp(input: dict): - if timestamp_string := input.get("Timestamp"): - return datetime.datetime.fromisoformat(timestamp_string) - return None - if start_date: - history = [h for h in history if (date := _get_timestamp(h)) and date >= start_date] + history = [h for h in history if (date := h.get("Timestamp")) and date >= start_date] if end_date: - history = [h for h in history if (date := _get_timestamp(h)) and date <= end_date] + history = [h for h in history if (date := h.get("Timestamp")) and date <= end_date] return DescribeAlarmHistoryOutput(AlarmHistoryItems=history) def _evaluate_composite_alarms(self, context: RequestContext, triggering_alarm): diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py index 56aa3292de1f4..a9cb85b83b61e 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,57 +14,57 @@ class CloudWatchAlarmProperties(TypedDict): - ComparisonOperator: Optional[str] - EvaluationPeriods: Optional[int] - ActionsEnabled: Optional[bool] - AlarmActions: Optional[list[str]] - AlarmDescription: Optional[str] - AlarmName: Optional[str] - Arn: Optional[str] - DatapointsToAlarm: Optional[int] - Dimensions: Optional[list[Dimension]] - EvaluateLowSampleCountPercentile: Optional[str] - ExtendedStatistic: Optional[str] - Id: Optional[str] - InsufficientDataActions: Optional[list[str]] - MetricName: Optional[str] - Metrics: Optional[list[MetricDataQuery]] - Namespace: Optional[str] - OKActions: Optional[list[str]] - Period: Optional[int] - Statistic: Optional[str] - Threshold: Optional[float] - ThresholdMetricId: Optional[str] - TreatMissingData: Optional[str] - Unit: Optional[str] + ComparisonOperator: str | None + EvaluationPeriods: int | None + ActionsEnabled: bool | None + AlarmActions: list[str] | None + AlarmDescription: str | None + AlarmName: str | None + Arn: str | None + DatapointsToAlarm: int | None + Dimensions: list[Dimension] | None + EvaluateLowSampleCountPercentile: str | None + ExtendedStatistic: str | None + Id: str | None + InsufficientDataActions: list[str] | None + MetricName: str | None + Metrics: list[MetricDataQuery] | None + Namespace: str | None + OKActions: list[str] | None + Period: int | None + Statistic: str | None + Threshold: float | None + ThresholdMetricId: str | None + TreatMissingData: str | None + Unit: str | None class Dimension(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class Metric(TypedDict): - Dimensions: Optional[list[Dimension]] - MetricName: Optional[str] - Namespace: Optional[str] + Dimensions: list[Dimension] | None + MetricName: str | None + Namespace: str | None class MetricStat(TypedDict): - Metric: Optional[Metric] - Period: Optional[int] - Stat: Optional[str] - Unit: Optional[str] + Metric: Metric | None + Period: int | None + Stat: str | None + Unit: str | None class MetricDataQuery(TypedDict): - Id: Optional[str] - AccountId: Optional[str] - Expression: Optional[str] - Label: Optional[str] - MetricStat: Optional[MetricStat] - Period: Optional[int] - ReturnData: Optional[bool] + Id: str | None + AccountId: str | None + Expression: str | None + Label: str | None + MetricStat: MetricStat | None + Period: int | None + ReturnData: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py index 6dfffe39b52a4..13c82fed034ac 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudWatchAlarmProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CloudWatch::Alarm" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudwatch.resource_providers.aws_cloudwatch_alarm import ( diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py index b6ca22b2e9f3f..cddbba144416f 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,17 +15,17 @@ class CloudWatchCompositeAlarmProperties(TypedDict): - AlarmRule: Optional[str] - ActionsEnabled: Optional[bool] - ActionsSuppressor: Optional[str] - ActionsSuppressorExtensionPeriod: Optional[int] - ActionsSuppressorWaitPeriod: Optional[int] - AlarmActions: Optional[list[str]] - AlarmDescription: Optional[str] - AlarmName: Optional[str] - Arn: Optional[str] - InsufficientDataActions: Optional[list[str]] - OKActions: Optional[list[str]] + AlarmRule: str | None + ActionsEnabled: bool | None + ActionsSuppressor: str | None + ActionsSuppressorExtensionPeriod: int | None + ActionsSuppressorWaitPeriod: int | None + AlarmActions: list[str] | None + AlarmDescription: str | None + AlarmName: str | None + Arn: str | None + InsufficientDataActions: list[str] | None + OKActions: list[str] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py index 867cebdbfe31d..9196997cea9e7 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudWatchCompositeAlarmProviderPlugin(CloudFormationResourceProviderPlugi name = "AWS::CloudWatch::CompositeAlarm" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudwatch.resource_providers.aws_cloudwatch_compositealarm import ( diff --git a/localstack-core/localstack/services/configservice/provider.py b/localstack-core/localstack/services/configservice/provider.py index 3087c6b23e270..b570dc05b5962 100644 --- a/localstack-core/localstack/services/configservice/provider.py +++ b/localstack-core/localstack/services/configservice/provider.py @@ -1,5 +1,9 @@ from localstack.aws.api.config import ConfigApi +from localstack.state import StateVisitor class ConfigProvider(ConfigApi): - pass + def accept_state_visitor(self, visitor: StateVisitor): + from moto.config.models import config_backends + + visitor.visit(config_backends) diff --git a/localstack-core/localstack/services/dynamodb/models.py b/localstack-core/localstack/services/dynamodb/models.py index cc6d7ee2e4939..46492f822dc50 100644 --- a/localstack-core/localstack/services/dynamodb/models.py +++ b/localstack-core/localstack/services/dynamodb/models.py @@ -3,10 +3,15 @@ from localstack.aws.api.dynamodb import ( AttributeMap, + BackupDetails, + ContinuousBackupsDescription, + GlobalTableDescription, Key, + KinesisDataStreamDestination, RegionName, ReplicaDescription, StreamViewType, + TableDescription, TableName, TimeToLiveSpecification, ) @@ -91,9 +96,20 @@ class TableRecords(TypedDict): RecordsMap = dict[TableName, TableRecords] +class TableProperties(TypedDict, total=False): + ContinuousBackupsDescription: ContinuousBackupsDescription + + +@dataclasses.dataclass +class Backup: + details: BackupDetails + backup_file: str + table_name: str + + class DynamoDBStore(BaseStore): # maps global table names to configurations (for the legacy v.2017 tables) - GLOBAL_TABLES: dict[str, dict] = CrossRegionAttribute(default=dict) + GLOBAL_TABLES: dict[str, GlobalTableDescription] = CrossRegionAttribute(default=dict) # Maps table name to the region they exist in on DDBLocal (for v.2019 global tables) TABLE_REGION: dict[TableName, RegionName] = CrossRegionAttribute(default=dict) @@ -104,19 +120,24 @@ class DynamoDBStore(BaseStore): ) # cache table taggings - maps table ARN to tags dict - TABLE_TAGS: dict[str, dict] = CrossRegionAttribute(default=dict) + TABLE_TAGS: dict[str, dict[str, str]] = CrossRegionAttribute(default=dict) # maps table names to cached table definitions - table_definitions: dict[str, dict] = LocalAttribute(default=dict) + table_definitions: dict[str, TableDescription] = LocalAttribute(default=dict) + + # map table name to streaming destinations + streaming_destinations: dict[str, list[KinesisDataStreamDestination]] = LocalAttribute( + default=dict + ) # maps table names to additional table properties that are not stored upstream (e.g., ReplicaUpdates) - table_properties: dict[str, dict] = LocalAttribute(default=dict) + table_properties: dict[str, TableProperties] = LocalAttribute(default=dict) # maps table names to TTL specifications ttl_specifications: dict[str, TimeToLiveSpecification] = LocalAttribute(default=dict) # maps backups - backups: dict[str, dict] = LocalAttribute(default=dict) + backups: dict[str, Backup] = LocalAttribute(default=dict) dynamodb_stores = AccountRegionBundle("dynamodb", DynamoDBStore) diff --git a/localstack-core/localstack/services/dynamodb/packages.py b/localstack-core/localstack/services/dynamodb/packages.py index db2ca14c49bf6..47d52471262a6 100644 --- a/localstack-core/localstack/services/dynamodb/packages.py +++ b/localstack-core/localstack/services/dynamodb/packages.py @@ -1,5 +1,4 @@ import os -from typing import List from localstack import config from localstack.constants import ARTIFACTS_REPO, MAVEN_REPO_URL @@ -15,9 +14,10 @@ from localstack.utils.http import download from localstack.utils.run import run -DDB_AGENT_JAR_URL = f"{ARTIFACTS_REPO}/raw/388cd73f45bfd3bcf7ad40aa35499093061c7962/dynamodb-local-patch/target/ddb-local-loader-0.1.jar" +DDB_AGENT_JAR_URL = f"{ARTIFACTS_REPO}/raw/e4e8c8e294b1fcda90c678ff6af5d5ebe1f091eb/dynamodb-local-patch/target/ddb-local-loader-0.2.jar" JAVASSIST_JAR_URL = f"{MAVEN_REPO_URL}/org/javassist/javassist/3.30.2-GA/javassist-3.30.2-GA.jar" +# URL points to 2.x here - however the latest 3.x builds are available under this URL DDBLOCAL_URL = "https://d1ni2b6xgvw0s0.cloudfront.net/v2.x/dynamodb_local_latest.zip" @@ -28,7 +28,7 @@ def __init__(self): def _get_installer(self, _) -> PackageInstaller: return DynamoDBLocalPackageInstaller() - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["2"] diff --git a/localstack-core/localstack/services/dynamodb/provider.py b/localstack-core/localstack/services/dynamodb/provider.py index 407e6400414ca..a5511a877c062 100644 --- a/localstack-core/localstack/services/dynamodb/provider.py +++ b/localstack-core/localstack/services/dynamodb/provider.py @@ -6,13 +6,11 @@ import re import threading import time -import traceback from collections import defaultdict from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager from datetime import datetime from operator import itemgetter -from typing import Dict, List, Optional import requests import werkzeug @@ -27,6 +25,7 @@ handler, ) from localstack.aws.api.dynamodb import ( + ApproximateCreationDateTimePrecision, AttributeMap, BatchExecuteStatementOutput, BatchGetItemOutput, @@ -47,6 +46,8 @@ DeleteRequest, DeleteTableOutput, DescribeContinuousBackupsOutput, + DescribeContributorInsightsInput, + DescribeContributorInsightsOutput, DescribeGlobalTableOutput, DescribeKinesisStreamingDestinationOutput, DescribeTableOutput, @@ -62,6 +63,7 @@ GetItemOutput, GlobalTableAlreadyExistsException, GlobalTableNotFoundException, + KinesisDataStreamDestination, KinesisStreamingDestinationOutput, ListGlobalTablesOutput, ListTablesInputLimit, @@ -92,6 +94,7 @@ ScanInput, ScanOutput, StreamArn, + TableArn, TableDescription, TableName, TagKeyList, @@ -108,6 +111,8 @@ UpdateGlobalTableOutput, UpdateItemInput, UpdateItemOutput, + UpdateKinesisStreamingConfiguration, + UpdateKinesisStreamingDestinationOutput, UpdateTableInput, UpdateTableOutput, UpdateTimeToLiveOutput, @@ -115,6 +120,7 @@ ) from localstack.aws.api.dynamodbstreams import StreamStatus from localstack.aws.connect import connect_to +from localstack.config import is_persistence_enabled from localstack.constants import ( AUTH_CREDENTIAL_REGEX, AWS_REGION_US_EAST_1, @@ -158,6 +164,7 @@ ) from localstack.utils.collections import select_attributes, select_from_typed_dict from localstack.utils.common import short_uid, to_bytes +from localstack.utils.files import cp_r, rm_rf from localstack.utils.json import BytesEncoder, canonical_json from localstack.utils.scheduler import Scheduler from localstack.utils.strings import long_uid, md5, to_str @@ -267,7 +274,12 @@ def forward_to_kinesis_stream( table_arn = arns.dynamodb_table_arn(table_name, account_id, region_name) records = table_records["records"] table_def = store.table_definitions.get(table_name) or {} - stream_arn = table_def["KinesisDataStreamDestinations"][-1]["StreamArn"] + destinations = store.streaming_destinations.get(table_name) + if not destinations: + LOG.debug("Table %s has no Kinesis streaming destinations enabled", table_name) + continue + + stream_arn = destinations[-1]["StreamArn"] for record in records: kinesis_record = dict( tableName=table_name, @@ -486,7 +498,7 @@ class ExpiredItemsWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None self.mutex = threading.RLock() def start(self): @@ -519,14 +531,26 @@ def stop(self): class DynamoDBProvider(DynamodbApi, ServiceLifecycleHook): server: DynamodbServer """The instance of the server managing the instance of DynamoDB local""" + asset_directory = f"{config.dirs.data}/dynamodb" + """The directory that contains the .db files saved by DynamoDB Local""" + tmp_asset_directory = f"{config.dirs.tmp}/dynamodb" + """Temporary directory for the .db files saved by DynamoDB Local when MANUAL snapshot persistence is enabled""" def __init__(self): self.server = self._new_dynamodb_server() self._expired_items_worker = ExpiredItemsWorker() self._router_rules = [] + # TODO: fix _event_forwarder to have lazy instantiation of the ThreadPoolExecutor self._event_forwarder = EventForwarder() def on_before_start(self): + # We must copy back whatever state is saved to the temporary location to avoid to start always from a blank + # state. See the `on_before_state_save` hook. + if is_persistence_enabled() and config.SNAPSHOT_SAVE_STRATEGY == "MANUAL": + if os.path.exists(self.asset_directory): + LOG.debug("Copying %s to %s", self.tmp_asset_directory, self.asset_directory) + cp_r(self.asset_directory, self.tmp_asset_directory, rm_dest_on_conflict=True) + self.server.start_dynamodb() if config.DYNAMODB_REMOVE_EXPIRED_ITEMS: self._expired_items_worker.start() @@ -544,6 +568,7 @@ def accept_state_visitor(self, visitor: StateVisitor): def on_before_state_reset(self): self.server.stop_dynamodb() + rm_rf(self.tmp_asset_directory) def on_before_state_load(self): self.server.stop_dynamodb() @@ -558,6 +583,21 @@ def _new_dynamodb_server() -> DynamodbServer: def on_after_state_load(self): self.server.start_dynamodb() + def on_before_state_save(self) -> None: + # When the save strategy is MANUAL, we do not save the DB path to the usual ``confid.dirs.data`` folder. + # With the MANUAL strategy, we want to take a snapshot on-demand but this is not possible if the DB files + # are already in ``config.dirs.data``. For instance, the set of operation below will result in both tables + # being implicitly saved. + # - awslocal dynamodb create-table table1 + # - curl -X POST http://localhost:4566/_localstack/state/save + # - awslocal dynamodb create-table table2 + # To avoid this problem, we start the DDBLocal server in a temporary directory that is then copied over + # ``config.dirs.data`` when the save needs to be saved. + # The ideal solution to the problem would be to always start the server in memory and have a dump capability. + if is_persistence_enabled() and config.SNAPSHOT_SAVE_STRATEGY == "MANUAL": + LOG.debug("Copying %s to %s", self.tmp_asset_directory, self.asset_directory) + cp_r(self.tmp_asset_directory, self.asset_directory, rm_dest_on_conflict=True) + def on_after_init(self): # add response processor specific to ddblocal handlers.modify_service_response.append(self.service, modify_ddblocal_arns) @@ -714,6 +754,9 @@ def create_table( if "NumberOfDecreasesToday" not in table_description["ProvisionedThroughput"]: table_description["ProvisionedThroughput"]["NumberOfDecreasesToday"] = 0 + if "WarmThroughput" in table_description: + table_description["WarmThroughput"]["Status"] = "UPDATING" + tags = table_definitions.pop("Tags", []) if tags: get_store(context.account_id, context.region).TABLE_TAGS[table_arn] = { @@ -731,6 +774,13 @@ def delete_table( ) -> DeleteTableOutput: global_table_region = self.get_global_table_region(context, table_name) + self.ensure_table_exists( + context.account_id, + global_table_region, + table_name, + error_message=f"Requested resource not found: Table: {table_name} not found", + ) + # Limitation note: On AWS, for a replicated table, if the source table is deleted, the replicated tables continue to exist. # This is not the case for LocalStack, where all replicated tables will also be removed if source is deleted. @@ -763,7 +813,7 @@ def describe_table( store = get_store(context.account_id, context.region) # Update replication details - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) replica_description_list = [] @@ -791,6 +841,9 @@ def describe_table( table_description["TableClassSummary"] = { "TableClass": table_definitions["TableClass"] } + if warm_throughput := table_definitions.get("WarmThroughput"): + table_description["WarmThroughput"] = warm_throughput.copy() + table_description["WarmThroughput"].setdefault("Status", "ACTIVE") if "GlobalSecondaryIndexes" in table_description: for gsi in table_description["GlobalSecondaryIndexes"]: @@ -803,6 +856,17 @@ def describe_table( # Terraform depends on this parity for update operations gsi["ProvisionedThroughput"] = default_values | gsi.get("ProvisionedThroughput", {}) + # Set defaults for warm throughput + if "WarmThroughput" not in table_description: + billing_mode = table_definitions.get("BillingMode") if table_definitions else None + table_description["WarmThroughput"] = { + "ReadUnitsPerSecond": 12000 if billing_mode == "PAY_PER_REQUEST" else 5, + "WriteUnitsPerSecond": 4000 if billing_mode == "PAY_PER_REQUEST" else 5, + } + table_description["WarmThroughput"]["Status"] = ( + table_description.get("TableStatus") or "ACTIVE" + ) + return DescribeTableOutput( Table=select_from_typed_dict(TableDescription, table_description) ) @@ -832,7 +896,7 @@ def update_table( store = get_store(context.account_id, global_table_region) # Dict with source region to set of replicated regions - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) for replica_update in replica_updates: for key, details in replica_update.items(): @@ -923,6 +987,22 @@ def list_tables( return response + # + # Contributor Insights + # + + @handler("DescribeContributorInsights", expand=False) + def describe_contributor_insights( + self, + context: RequestContext, + describe_contributor_insights_input: DescribeContributorInsightsInput, + ) -> DescribeContributorInsightsOutput: + return DescribeContributorInsightsOutput( + TableName=describe_contributor_insights_input["TableName"], + IndexName=describe_contributor_insights_input.get("IndexName"), + ContributorInsightsStatus="DISABLED", + ) + # # Item ops # @@ -1249,11 +1329,15 @@ def transact_write_items( for item in transact_items: item: TransactWriteItem - for key in ["Put", "Update", "Delete"]: + for key in ["Put", "Update", "Delete", "ConditionCheck"]: inner_item: Put | Delete | Update = item.get(key) if inner_item: - table_name = inner_item["TableName"] - # if we've seen the table already and it does not have streams, skip + # Extract the table name from the ARN; DynamoDB Local does not currently support + # full ARNs in this operation: https://github.com/awslabs/amazon-dynamodb-local-samples/issues/34 + inner_item["TableName"] = table_name = inner_item["TableName"].split(":table/")[ + -1 + ] + # if we've seen the table already exists and it does not have streams, skip if table_name in no_stream_tables: continue @@ -1279,6 +1363,10 @@ def transact_write_items( updated_items_to_fetch_for_table.append(inner_item) continue + # Normalize the request structure to ensure it matches the expected format for DynamoDB Local. + data = json.loads(context.request.data) + data["TransactItems"] = transact_items + context.request.data = to_bytes(json.dumps(data, cls=BytesEncoder)) if existing_items_to_fetch: existing_items = ItemFinder.find_existing_items( @@ -1329,6 +1417,17 @@ def transact_get_items( transact_items: TransactGetItemList, return_consumed_capacity: ReturnConsumedCapacity = None, ) -> TransactGetItemsOutput: + for transact_item in transact_items["TransactItems"]: + if item := transact_item.get("Get"): + # Extract the table name from the ARN; DynamoDB Local does not currently support + # full ARNs in this operation: https://github.com/awslabs/amazon-dynamodb-local-samples/issues/34 + item["TableName"] = item["TableName"].split(":table/")[-1] + + # Normalize the request structure to ensure it matches the expected format for DynamoDB Local. + data = json.loads(context.request.data) + data["TransactItems"] = transact_items["TransactItems"] + context.request.data = to_bytes(json.dumps(data, cls=BytesEncoder)) + return self.forward_request(context) @handler("ExecuteTransaction", expand=False) @@ -1479,7 +1578,7 @@ def create_global_table( replication_group: ReplicaList, **kwargs, ) -> CreateGlobalTableOutput: - global_tables: Dict = get_store(context.account_id, context.region).GLOBAL_TABLES + global_tables: dict = get_store(context.account_id, context.region).GLOBAL_TABLES if global_table_name in global_tables: raise GlobalTableAlreadyExistsException("Global table with this name already exists") replication_group = [grp.copy() for grp in replication_group or []] @@ -1557,42 +1656,51 @@ def enable_kinesis_streaming_destination( enable_kinesis_streaming_configuration: EnableKinesisStreamingConfiguration = None, **kwargs, ) -> KinesisStreamingDestinationOutput: - self.ensure_table_exists(context.account_id, context.region, table_name) + self.ensure_table_exists( + context.account_id, + context.region, + table_name, + error_message=f"Requested resource not found: Table: {table_name} not found", + ) + + # TODO: Use the time precision in config if set + enable_kinesis_streaming_configuration = enable_kinesis_streaming_configuration or {} stream = self._event_forwarder.is_kinesis_stream_exists(stream_arn=stream_arn) if not stream: raise ValidationException("User does not have a permission to use kinesis stream") - table_def = get_store(context.account_id, context.region).table_definitions.setdefault( - table_name, {} - ) + store = get_store(context.account_id, context.region) + streaming_destinations = store.streaming_destinations.get(table_name) or [] - dest_status = table_def.get("KinesisDataStreamDestinationStatus") - if dest_status not in ["DISABLED", "ENABLE_FAILED", None]: - raise ValidationException( - "Table is not in a valid state to enable Kinesis Streaming " - "Destination:EnableKinesisStreamingDestination must be DISABLED or ENABLE_FAILED " - "to perform ENABLE operation." - ) + destinations = [d for d in streaming_destinations if d["StreamArn"] == stream_arn] + if destinations: + status = destinations[0].get("DestinationStatus", None) + if status not in ["DISABLED", "ENABLED_FAILED", None]: + raise ValidationException( + "Table is not in a valid state to enable Kinesis Streaming " + "Destination:EnableKinesisStreamingDestination must be DISABLED or ENABLE_FAILED " + "to perform ENABLE operation." + ) - table_def["KinesisDataStreamDestinations"] = ( - table_def.get("KinesisDataStreamDestinations") or [] - ) # remove the stream destination if already present - table_def["KinesisDataStreamDestinations"] = [ - t for t in table_def["KinesisDataStreamDestinations"] if t["StreamArn"] != stream_arn + store.streaming_destinations[table_name] = [ + _d for _d in streaming_destinations if _d["StreamArn"] != stream_arn ] # append the active stream destination at the end of the list - table_def["KinesisDataStreamDestinations"].append( - { - "DestinationStatus": DestinationStatus.ACTIVE, - "DestinationStatusDescription": "Stream is active", - "StreamArn": stream_arn, - } + store.streaming_destinations[table_name].append( + KinesisDataStreamDestination( + DestinationStatus=DestinationStatus.ACTIVE, + DestinationStatusDescription="Stream is active", + StreamArn=stream_arn, + ApproximateCreationDateTimePrecision=ApproximateCreationDateTimePrecision.MILLISECOND, + ) ) - table_def["KinesisDataStreamDestinationStatus"] = DestinationStatus.ACTIVE return KinesisStreamingDestinationOutput( - DestinationStatus=DestinationStatus.ACTIVE, StreamArn=stream_arn, TableName=table_name + DestinationStatus=DestinationStatus.ENABLING, + StreamArn=stream_arn, + TableName=table_name, + EnableKinesisStreamingConfiguration=enable_kinesis_streaming_configuration, ) def disable_kinesis_streaming_destination( @@ -1603,7 +1711,12 @@ def disable_kinesis_streaming_destination( enable_kinesis_streaming_configuration: EnableKinesisStreamingConfiguration = None, **kwargs, ) -> KinesisStreamingDestinationOutput: - self.ensure_table_exists(context.account_id, context.region, table_name) + self.ensure_table_exists( + context.account_id, + context.region, + table_name, + error_message=f"Requested resource not found: Table: {table_name} not found", + ) stream = self._event_forwarder.is_kinesis_stream_exists(stream_arn=stream_arn) if not stream: @@ -1611,26 +1724,19 @@ def disable_kinesis_streaming_destination( "User does not have a permission to use kinesis stream", ) - table_def = get_store(context.account_id, context.region).table_definitions.setdefault( - table_name, {} - ) - - stream_destinations = table_def.get("KinesisDataStreamDestinations") - if stream_destinations: - if table_def["KinesisDataStreamDestinationStatus"] == DestinationStatus.ACTIVE: - for dest in stream_destinations: - if ( - dest["StreamArn"] == stream_arn - and dest["DestinationStatus"] == DestinationStatus.ACTIVE - ): - dest["DestinationStatus"] = DestinationStatus.DISABLED - dest["DestinationStatusDescription"] = ("Stream is disabled",) - table_def["KinesisDataStreamDestinationStatus"] = DestinationStatus.DISABLED - return KinesisStreamingDestinationOutput( - DestinationStatus=DestinationStatus.DISABLED, - StreamArn=stream_arn, - TableName=table_name, - ) + store = get_store(context.account_id, context.region) + streaming_destinations = store.streaming_destinations.get(table_name) or [] + + # Get the right destination based on the arn + destinations = [d for d in streaming_destinations if d["StreamArn"] == stream_arn] + if destinations: + destinations[0]["DestinationStatus"] = DestinationStatus.DISABLED + destinations[0]["DestinationStatusDescription"] = "Stream is disabled" + return KinesisStreamingDestinationOutput( + DestinationStatus=DestinationStatus.DISABLING, + StreamArn=stream_arn, + TableName=table_name, + ) raise ValidationException( "Table is not in a valid state to disable Kinesis Streaming Destination:" "DisableKinesisStreamingDestination must be ACTIVE to perform DISABLE operation." @@ -1641,13 +1747,80 @@ def describe_kinesis_streaming_destination( ) -> DescribeKinesisStreamingDestinationOutput: self.ensure_table_exists(context.account_id, context.region, table_name) - table_def = ( - get_store(context.account_id, context.region).table_definitions.get(table_name) or {} - ) + store = get_store(context.account_id, context.region) + table_destinations = store.streaming_destinations.get(table_name) or [] + stream_destinations = copy.deepcopy(table_destinations) + + for destination in stream_destinations: + destination.pop("ApproximateCreationDateTimePrecision", None) + destination.pop("DestinationStatusDescription", None) - stream_destinations = table_def.get("KinesisDataStreamDestinations") or [] return DescribeKinesisStreamingDestinationOutput( - KinesisDataStreamDestinations=stream_destinations, TableName=table_name + KinesisDataStreamDestinations=stream_destinations, + TableName=table_name, + ) + + def update_kinesis_streaming_destination( + self, + context: RequestContext, + table_name: TableArn, + stream_arn: StreamArn, + update_kinesis_streaming_configuration: UpdateKinesisStreamingConfiguration | None = None, + **kwargs, + ) -> UpdateKinesisStreamingDestinationOutput: + self.ensure_table_exists(context.account_id, context.region, table_name) + + if not update_kinesis_streaming_configuration: + raise ValidationException( + "Streaming destination cannot be updated with given parameters: " + "UpdateKinesisStreamingConfiguration cannot be null or contain only null values" + ) + + time_precision = update_kinesis_streaming_configuration.get( + "ApproximateCreationDateTimePrecision" + ) + if time_precision not in ( + ApproximateCreationDateTimePrecision.MILLISECOND, + ApproximateCreationDateTimePrecision.MICROSECOND, + ): + raise ValidationException( + f"1 validation error detected: Value '{time_precision}' at " + "'updateKinesisStreamingConfiguration.approximateCreationDateTimePrecision' failed to satisfy constraint: " + "Member must satisfy enum value set: [MILLISECOND, MICROSECOND]" + ) + + store = get_store(context.account_id, context.region) + table_destinations = store.streaming_destinations.get(table_name) or [] + + # filter the right destination based on the stream ARN + destinations = [d for d in table_destinations if d["StreamArn"] == stream_arn] + if not destinations: + raise ValidationException( + "Table is not in a valid state to enable Kinesis Streaming Destination: " + f"No streaming destination with streamArn: {stream_arn} found for table with tableName: {table_name}" + ) + + destination = destinations[0] + table_def = store.table_definitions.get(table_name) or {} + table_def.setdefault("KinesisDataStreamDestinations", []) + + table_id = store.table_definitions.get(table_name, {}).get("TableId") + if ( + existing_precision := destination["ApproximateCreationDateTimePrecision"] + ) == update_kinesis_streaming_configuration["ApproximateCreationDateTimePrecision"]: + raise ValidationException( + f"Invalid Request: Precision is already set to the desired value of {existing_precision} " + f"for tableId: {table_id}, kdsArn: {stream_arn}" + ) + destination["ApproximateCreationDateTimePrecision"] = time_precision + + return UpdateKinesisStreamingDestinationOutput( + TableName=table_name, + StreamArn=stream_arn, + DestinationStatus=DestinationStatus.UPDATING, + UpdateKinesisStreamingConfiguration=UpdateKinesisStreamingConfiguration( + ApproximateCreationDateTimePrecision=time_precision, + ), ) # @@ -1725,7 +1898,12 @@ def table_exists(account_id: str, region_name: str, table_name: str) -> bool: return dynamodb_table_exists(table_name, client) @staticmethod - def ensure_table_exists(account_id: str, region_name: str, table_name: str): + def ensure_table_exists( + account_id: str, + region_name: str, + table_name: str, + error_message: str = "Cannot do operations on a non-existent table", + ): """ Raise ResourceNotFoundException if the given table does not exist. @@ -1735,7 +1913,7 @@ def ensure_table_exists(account_id: str, region_name: str, table_name: str): :raise: ResourceNotFoundException if table does not exist in DynamoDB Local """ if not DynamoDBProvider.table_exists(account_id, region_name, table_name): - raise ResourceNotFoundException("Cannot do operations on a non-existent table") + raise ResourceNotFoundException(error_message) @staticmethod def get_global_table_region(context: RequestContext, table_name: str) -> str: @@ -1762,7 +1940,7 @@ def get_global_table_region(context: RequestContext, table_name: str) -> str: return context.region @staticmethod - def prepare_request_headers(headers: Dict, account_id: str, region_name: str): + def prepare_request_headers(headers: dict, account_id: str, region_name: str): """ Modify the Credentials field of Authorization header to achieve namespacing in DynamoDBLocal. """ @@ -1779,7 +1957,7 @@ def prepare_request_headers(headers: Dict, account_id: str, region_name: str): flags=re.IGNORECASE, ) - def fix_consumed_capacity(self, request: Dict, result: Dict): + def fix_consumed_capacity(self, request: dict, result: dict): # make sure we append 'ConsumedCapacity', which is properly # returned by dynalite, but not by AWS's DynamoDBLocal table_name = request.get("TableName") @@ -2127,19 +2305,20 @@ def get_table_stream_type( :return: a TableStreamViewType object if the table has streams enabled. If not, return None """ if not table_name_or_arn: - return + return None table_name = table_name_or_arn.split(":table/")[-1] is_kinesis = False stream_view_type = None - if table_definition := get_store(account_id, region_name).table_definitions.get(table_name): - if table_definition.get("KinesisDataStreamDestinationStatus") == "ACTIVE": + # To determine if stream to kinesis is enabled, we look for active kinesis destinations + destinations = get_store(account_id, region_name).streaming_destinations.get(table_name) or [] + for destination in destinations: + if destination["DestinationStatus"] == DestinationStatus.ACTIVE: is_kinesis = True table_arn = arns.dynamodb_table_arn(table_name, account_id=account_id, region_name=region_name) - if ( stream := dynamodbstreams_api.get_stream_for_table(account_id, region_name, table_arn) ) and stream["StreamStatus"] in (StreamStatus.ENABLING, StreamStatus.ENABLED): @@ -2147,13 +2326,14 @@ def get_table_stream_type( if is_kinesis or stream_view_type: return TableStreamType(stream_view_type, is_kinesis=is_kinesis) + return None def get_updated_records( account_id: str, region_name: str, table_name: str, - existing_items: List, + existing_items: list, server_url: str, table_stream_type: TableStreamType, ) -> RecordsMap: @@ -2220,7 +2400,9 @@ def _add_record(item, comparison_set: ItemSet): return {table_name: TableRecords(records=result, table_stream_type=table_stream_type)} -def create_dynamodb_stream(account_id: str, region_name: str, data, latest_stream_label): +def create_dynamodb_stream( + account_id: str, region_name: str, data: CreateTableInput, latest_stream_label: str | None +) -> None: stream = data["StreamSpecification"] enabled = stream.get("StreamEnabled") @@ -2238,22 +2420,6 @@ def create_dynamodb_stream(account_id: str, region_name: str, data, latest_strea ) -def dynamodb_get_table_stream_specification(account_id: str, region_name: str, table_name: str): - try: - table_schema = SchemaExtractor.get_table_schema( - table_name, account_id=account_id, region_name=region_name - ) - return table_schema["Table"].get("StreamSpecification") - except Exception as e: - LOG.info( - "Unable to get stream specification for table %s: %s %s", - table_name, - e, - traceback.format_exc(), - ) - raise e - - def find_item_for_keys_values_in_batch( table_name: str, item_keys: dict, batch: BatchGetResponseMap ) -> AttributeMap | None: diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py index af199a479576c..5fea595fd0605 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,125 +14,125 @@ class DynamoDBGlobalTableProperties(TypedDict): - AttributeDefinitions: Optional[list[AttributeDefinition]] - KeySchema: Optional[list[KeySchema]] - Replicas: Optional[list[ReplicaSpecification]] - Arn: Optional[str] - BillingMode: Optional[str] - GlobalSecondaryIndexes: Optional[list[GlobalSecondaryIndex]] - LocalSecondaryIndexes: Optional[list[LocalSecondaryIndex]] - SSESpecification: Optional[SSESpecification] - StreamArn: Optional[str] - StreamSpecification: Optional[StreamSpecification] - TableId: Optional[str] - TableName: Optional[str] - TimeToLiveSpecification: Optional[TimeToLiveSpecification] - WriteProvisionedThroughputSettings: Optional[WriteProvisionedThroughputSettings] + AttributeDefinitions: list[AttributeDefinition] | None + KeySchema: list[KeySchema] | None + Replicas: list[ReplicaSpecification] | None + Arn: str | None + BillingMode: str | None + GlobalSecondaryIndexes: list[GlobalSecondaryIndex] | None + LocalSecondaryIndexes: list[LocalSecondaryIndex] | None + SSESpecification: SSESpecification | None + StreamArn: str | None + StreamSpecification: StreamSpecification | None + TableId: str | None + TableName: str | None + TimeToLiveSpecification: TimeToLiveSpecification | None + WriteProvisionedThroughputSettings: WriteProvisionedThroughputSettings | None class AttributeDefinition(TypedDict): - AttributeName: Optional[str] - AttributeType: Optional[str] + AttributeName: str | None + AttributeType: str | None class KeySchema(TypedDict): - AttributeName: Optional[str] - KeyType: Optional[str] + AttributeName: str | None + KeyType: str | None class Projection(TypedDict): - NonKeyAttributes: Optional[list[str]] - ProjectionType: Optional[str] + NonKeyAttributes: list[str] | None + ProjectionType: str | None class TargetTrackingScalingPolicyConfiguration(TypedDict): - TargetValue: Optional[float] - DisableScaleIn: Optional[bool] - ScaleInCooldown: Optional[int] - ScaleOutCooldown: Optional[int] + TargetValue: float | None + DisableScaleIn: bool | None + ScaleInCooldown: int | None + ScaleOutCooldown: int | None class CapacityAutoScalingSettings(TypedDict): - MaxCapacity: Optional[int] - MinCapacity: Optional[int] - TargetTrackingScalingPolicyConfiguration: Optional[TargetTrackingScalingPolicyConfiguration] - SeedCapacity: Optional[int] + MaxCapacity: int | None + MinCapacity: int | None + TargetTrackingScalingPolicyConfiguration: TargetTrackingScalingPolicyConfiguration | None + SeedCapacity: int | None class WriteProvisionedThroughputSettings(TypedDict): - WriteCapacityAutoScalingSettings: Optional[CapacityAutoScalingSettings] + WriteCapacityAutoScalingSettings: CapacityAutoScalingSettings | None class GlobalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] - WriteProvisionedThroughputSettings: Optional[WriteProvisionedThroughputSettings] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None + WriteProvisionedThroughputSettings: WriteProvisionedThroughputSettings | None class LocalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None class ContributorInsightsSpecification(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class ReadProvisionedThroughputSettings(TypedDict): - ReadCapacityAutoScalingSettings: Optional[CapacityAutoScalingSettings] - ReadCapacityUnits: Optional[int] + ReadCapacityAutoScalingSettings: CapacityAutoScalingSettings | None + ReadCapacityUnits: int | None class ReplicaGlobalSecondaryIndexSpecification(TypedDict): - IndexName: Optional[str] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - ReadProvisionedThroughputSettings: Optional[ReadProvisionedThroughputSettings] + IndexName: str | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + ReadProvisionedThroughputSettings: ReadProvisionedThroughputSettings | None class PointInTimeRecoverySpecification(TypedDict): - PointInTimeRecoveryEnabled: Optional[bool] + PointInTimeRecoveryEnabled: bool | None class ReplicaSSESpecification(TypedDict): - KMSMasterKeyId: Optional[str] + KMSMasterKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class KinesisStreamSpecification(TypedDict): - StreamArn: Optional[str] + StreamArn: str | None class ReplicaSpecification(TypedDict): - Region: Optional[str] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - DeletionProtectionEnabled: Optional[bool] - GlobalSecondaryIndexes: Optional[list[ReplicaGlobalSecondaryIndexSpecification]] - KinesisStreamSpecification: Optional[KinesisStreamSpecification] - PointInTimeRecoverySpecification: Optional[PointInTimeRecoverySpecification] - ReadProvisionedThroughputSettings: Optional[ReadProvisionedThroughputSettings] - SSESpecification: Optional[ReplicaSSESpecification] - TableClass: Optional[str] - Tags: Optional[list[Tag]] + Region: str | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + DeletionProtectionEnabled: bool | None + GlobalSecondaryIndexes: list[ReplicaGlobalSecondaryIndexSpecification] | None + KinesisStreamSpecification: KinesisStreamSpecification | None + PointInTimeRecoverySpecification: PointInTimeRecoverySpecification | None + ReadProvisionedThroughputSettings: ReadProvisionedThroughputSettings | None + SSESpecification: ReplicaSSESpecification | None + TableClass: str | None + Tags: list[Tag] | None class SSESpecification(TypedDict): - SSEEnabled: Optional[bool] - SSEType: Optional[str] + SSEEnabled: bool | None + SSEType: str | None class StreamSpecification(TypedDict): - StreamViewType: Optional[str] + StreamViewType: str | None class TimeToLiveSpecification(TypedDict): - Enabled: Optional[bool] - AttributeName: Optional[str] + Enabled: bool | None + AttributeName: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py index 8de0265d3d5f1..ca7cb4448f530 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class DynamoDBGlobalTableProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::DynamoDB::GlobalTable" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.dynamodb.resource_providers.aws_dynamodb_globaltable import ( diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py index 469c944cca898..1c27ee05eebae 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,113 +14,113 @@ class DynamoDBTableProperties(TypedDict): - KeySchema: Optional[list[KeySchema] | dict] - Arn: Optional[str] - AttributeDefinitions: Optional[list[AttributeDefinition]] - BillingMode: Optional[str] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - DeletionProtectionEnabled: Optional[bool] - GlobalSecondaryIndexes: Optional[list[GlobalSecondaryIndex]] - ImportSourceSpecification: Optional[ImportSourceSpecification] - KinesisStreamSpecification: Optional[KinesisStreamSpecification] - LocalSecondaryIndexes: Optional[list[LocalSecondaryIndex]] - PointInTimeRecoverySpecification: Optional[PointInTimeRecoverySpecification] - ProvisionedThroughput: Optional[ProvisionedThroughput] - SSESpecification: Optional[SSESpecification] - StreamArn: Optional[str] - StreamSpecification: Optional[StreamSpecification] - TableClass: Optional[str] - TableName: Optional[str] - Tags: Optional[list[Tag]] - TimeToLiveSpecification: Optional[TimeToLiveSpecification] + KeySchema: list[KeySchema] | dict | None + Arn: str | None + AttributeDefinitions: list[AttributeDefinition] | None + BillingMode: str | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + DeletionProtectionEnabled: bool | None + GlobalSecondaryIndexes: list[GlobalSecondaryIndex] | None + ImportSourceSpecification: ImportSourceSpecification | None + KinesisStreamSpecification: KinesisStreamSpecification | None + LocalSecondaryIndexes: list[LocalSecondaryIndex] | None + PointInTimeRecoverySpecification: PointInTimeRecoverySpecification | None + ProvisionedThroughput: ProvisionedThroughput | None + SSESpecification: SSESpecification | None + StreamArn: str | None + StreamSpecification: StreamSpecification | None + TableClass: str | None + TableName: str | None + Tags: list[Tag] | None + TimeToLiveSpecification: TimeToLiveSpecification | None class AttributeDefinition(TypedDict): - AttributeName: Optional[str] - AttributeType: Optional[str] + AttributeName: str | None + AttributeType: str | None class KeySchema(TypedDict): - AttributeName: Optional[str] - KeyType: Optional[str] + AttributeName: str | None + KeyType: str | None class Projection(TypedDict): - NonKeyAttributes: Optional[list[str]] - ProjectionType: Optional[str] + NonKeyAttributes: list[str] | None + ProjectionType: str | None class ProvisionedThroughput(TypedDict): - ReadCapacityUnits: Optional[int] - WriteCapacityUnits: Optional[int] + ReadCapacityUnits: int | None + WriteCapacityUnits: int | None class ContributorInsightsSpecification(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class GlobalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - ProvisionedThroughput: Optional[ProvisionedThroughput] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + ProvisionedThroughput: ProvisionedThroughput | None class LocalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None class PointInTimeRecoverySpecification(TypedDict): - PointInTimeRecoveryEnabled: Optional[bool] + PointInTimeRecoveryEnabled: bool | None class SSESpecification(TypedDict): - SSEEnabled: Optional[bool] - KMSMasterKeyId: Optional[str] - SSEType: Optional[str] + SSEEnabled: bool | None + KMSMasterKeyId: str | None + SSEType: str | None class StreamSpecification(TypedDict): - StreamViewType: Optional[str] + StreamViewType: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class TimeToLiveSpecification(TypedDict): - AttributeName: Optional[str] - Enabled: Optional[bool] + AttributeName: str | None + Enabled: bool | None class KinesisStreamSpecification(TypedDict): - StreamArn: Optional[str] + StreamArn: str | None class S3BucketSource(TypedDict): - S3Bucket: Optional[str] - S3BucketOwner: Optional[str] - S3KeyPrefix: Optional[str] + S3Bucket: str | None + S3BucketOwner: str | None + S3KeyPrefix: str | None class Csv(TypedDict): - Delimiter: Optional[str] - HeaderList: Optional[list[str]] + Delimiter: str | None + HeaderList: list[str] | None class InputFormatOptions(TypedDict): - Csv: Optional[Csv] + Csv: Csv | None class ImportSourceSpecification(TypedDict): - InputFormat: Optional[str] - S3BucketSource: Optional[S3BucketSource] - InputCompressionType: Optional[str] - InputFormatOptions: Optional[InputFormatOptions] + InputFormat: str | None + S3BucketSource: S3BucketSource | None + InputCompressionType: str | None + InputFormatOptions: InputFormatOptions | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py index 5f263b9e9d068..a2fba6f7af626 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class DynamoDBTableProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::DynamoDB::Table" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.dynamodb.resource_providers.aws_dynamodb_table import ( diff --git a/localstack-core/localstack/services/dynamodb/server.py b/localstack-core/localstack/services/dynamodb/server.py index dba7c321ebbd2..d25f01181bc0c 100644 --- a/localstack-core/localstack/services/dynamodb/server.py +++ b/localstack-core/localstack/services/dynamodb/server.py @@ -5,7 +5,7 @@ from localstack import config from localstack.aws.connect import connect_externally_to from localstack.aws.forwarder import AwsRequestProxy -from localstack.config import is_env_true +from localstack.config import is_env_true, is_persistence_enabled from localstack.constants import DEFAULT_AWS_ACCOUNT_ID from localstack.services.dynamodb.packages import dynamodblocal_package from localstack.utils.common import TMP_THREADS, ShellCommandThread, get_free_tcp_port, mkdir @@ -57,6 +57,15 @@ def __init__( f"{config.dirs.data}/dynamodb" if not db_path and config.dirs.data else db_path ) + # With persistence and MANUAL save strategy, we start DDBLocal from a temporary folder than gets copied over + # the "usual" data directory in the `on_before_state_save` hook (see the provider code for more details). + if is_persistence_enabled() and config.SNAPSHOT_SAVE_STRATEGY == "MANUAL": + self.db_path = f"{config.dirs.tmp}/dynamodb" + LOG.debug( + "Persistence save strategy set to MANUAL. The DB path is temporarily stored at: %s", + self.db_path, + ) + # the DYNAMODB_IN_MEMORY variable takes precedence and will set the DB path to None which forces inMemory=true if is_env_true("DYNAMODB_IN_MEMORY"): # note: with DYNAMODB_IN_MEMORY we do not support persistence @@ -153,7 +162,7 @@ def _create_shell_command(self) -> list[str]: cmd = [ "java", *self._get_java_vm_options(), - "-Xmx%s" % self.heap_size, + f"-Xmx{self.heap_size}", f"-javaagent:{dynamodblocal_package.get_installer().get_ddb_agent_jar_path()}", f"-Djava.library.path={self.library_path}", "-jar", @@ -210,7 +219,7 @@ def check_dynamodb(self, expect_shutdown: bool = False) -> None: aws_secret_access_key=DEFAULT_AWS_ACCOUNT_ID, ).dynamodb.list_tables() except Exception: - LOG.exception("DynamoDB health check failed") + LOG.error("DynamoDB health check failed", exc_info=LOG.isEnabledFor(logging.DEBUG)) if expect_shutdown: assert out is None else: diff --git a/localstack-core/localstack/services/dynamodb/utils.py b/localstack-core/localstack/services/dynamodb/utils.py index 4ff065440abec..530c937d0424e 100644 --- a/localstack-core/localstack/services/dynamodb/utils.py +++ b/localstack-core/localstack/services/dynamodb/utils.py @@ -1,7 +1,6 @@ import logging import re from binascii import crc32 -from typing import Dict, List, Optional from boto3.dynamodb.types import TypeDeserializer, TypeSerializer from cachetools import TTLCache @@ -61,7 +60,7 @@ def get_ddb_access_key(account_id: str, region_name: str) -> str: class ItemSet: """Represents a set of items and provides utils to find individual items in the set""" - def __init__(self, items: List[Dict], key_schema: List[Dict]): + def __init__(self, items: list[dict], key_schema: list[dict]): self.items_list = items self.key_schema = key_schema self._build_dict() @@ -71,11 +70,11 @@ def _build_dict(self): for item in self.items_list: self.items_dict[self._hashable_key(item)] = item - def _hashable_key(self, item: Dict): + def _hashable_key(self, item: dict): keys = SchemaExtractor.extract_keys_for_schema(item=item, key_schema=self.key_schema) return canonical_json(keys) - def find_item(self, item: Dict) -> Optional[Dict]: + def find_item(self, item: dict) -> dict | None: key = self._hashable_key(item) return self.items_dict.get(key) @@ -83,13 +82,13 @@ def find_item(self, item: Dict) -> Optional[Dict]: class SchemaExtractor: @classmethod def extract_keys( - cls, item: Dict, table_name: str, account_id: str, region_name: str - ) -> Optional[Dict]: + cls, item: dict, table_name: str, account_id: str, region_name: str + ) -> dict | None: key_schema = cls.get_key_schema(table_name, account_id, region_name) return cls.extract_keys_for_schema(item, key_schema) @classmethod - def extract_keys_for_schema(cls, item: Dict, key_schema: List[Dict]): + def extract_keys_for_schema(cls, item: dict, key_schema: list[dict]): result = {} for key in key_schema: attr_name = key["AttributeName"] @@ -104,10 +103,10 @@ def extract_keys_for_schema(cls, item: Dict, key_schema: List[Dict]): @classmethod def get_key_schema( cls, table_name: str, account_id: str, region_name: str - ) -> Optional[List[Dict]]: + ) -> list[dict] | None: from localstack.services.dynamodb.provider import get_store - table_definitions: Dict = get_store( + table_definitions: dict = get_store( account_id=account_id, region_name=region_name, ).table_definitions @@ -169,12 +168,12 @@ def get_ddb_local_client(account_id: str, region_name: str, endpoint_url: str): @staticmethod def find_existing_item( - put_item: Dict, + put_item: dict, table_name: str, account_id: str, region_name: str, endpoint_url: str, - ) -> Optional[AttributeMap]: + ) -> AttributeMap | None: from localstack.services.dynamodb.provider import ValidationException ddb_client = ItemFinder.get_ddb_local_client(account_id, region_name, endpoint_url) @@ -273,7 +272,7 @@ def find_existing_items( @classmethod def list_existing_items_for_statement( cls, partiql_statement: str, account_id: str, region_name: str, endpoint_url: str - ) -> List: + ) -> list: table_name = extract_table_name_from_partiql_update(partiql_statement) if not table_name: return [] @@ -288,7 +287,7 @@ def list_existing_items_for_statement( @staticmethod def get_all_table_items( account_id: str, region_name: str, table_name: str, endpoint_url: str - ) -> List: + ) -> list: ddb_client = ItemFinder.get_ddb_local_client(account_id, region_name, endpoint_url) dynamodb_kwargs = {"TableName": table_name} all_items = list_all_resources( @@ -300,7 +299,7 @@ def get_all_table_items( return all_items -def extract_table_name_from_partiql_update(statement: str) -> Optional[str]: +def extract_table_name_from_partiql_update(statement: str) -> str | None: regex = r"^\s*(UPDATE|INSERT\s+INTO|DELETE\s+FROM)\s+([^\s]+).*" match = re.match(regex, statement, flags=re.IGNORECASE | re.MULTILINE) return match and match.group(2) diff --git a/localstack-core/localstack/services/dynamodb/v2/provider.py b/localstack-core/localstack/services/dynamodb/v2/provider.py index f6dee3a68e854..88e2c0b93cd4d 100644 --- a/localstack-core/localstack/services/dynamodb/v2/provider.py +++ b/localstack-core/localstack/services/dynamodb/v2/provider.py @@ -9,7 +9,6 @@ from contextlib import contextmanager from datetime import datetime from operator import itemgetter -from typing import Dict, Optional import requests import werkzeug @@ -24,6 +23,7 @@ handler, ) from localstack.aws.api.dynamodb import ( + ApproximateCreationDateTimePrecision, BatchExecuteStatementOutput, BatchGetItemOutput, BatchGetRequestMap, @@ -40,6 +40,8 @@ DeleteRequest, DeleteTableOutput, DescribeContinuousBackupsOutput, + DescribeContributorInsightsInput, + DescribeContributorInsightsOutput, DescribeGlobalTableOutput, DescribeKinesisStreamingDestinationOutput, DescribeTableOutput, @@ -51,10 +53,12 @@ ExecuteStatementOutput, ExecuteTransactionInput, ExecuteTransactionOutput, + Get, GetItemInput, GetItemOutput, GlobalTableAlreadyExistsException, GlobalTableNotFoundException, + KinesisDataStreamDestination, KinesisStreamingDestinationOutput, ListGlobalTablesOutput, ListTablesInputLimit, @@ -84,6 +88,7 @@ ScanInput, ScanOutput, StreamArn, + TableArn, TableDescription, TableName, TagKeyList, @@ -97,6 +102,8 @@ UpdateGlobalTableOutput, UpdateItemInput, UpdateItemOutput, + UpdateKinesisStreamingConfiguration, + UpdateKinesisStreamingDestinationOutput, UpdateTableInput, UpdateTableOutput, UpdateTimeToLiveOutput, @@ -137,7 +144,7 @@ ) from localstack.utils.collections import select_attributes, select_from_typed_dict from localstack.utils.common import short_uid, to_bytes -from localstack.utils.json import canonical_json +from localstack.utils.json import BytesEncoder, canonical_json from localstack.utils.scheduler import Scheduler from localstack.utils.strings import long_uid, to_str from localstack.utils.threads import FuncThread, start_thread @@ -336,7 +343,7 @@ class ExpiredItemsWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None self.mutex = threading.RLock() def start(self): @@ -387,6 +394,7 @@ def on_before_stop(self): def accept_state_visitor(self, visitor: StateVisitor): visitor.visit(dynamodb_stores) + # FIXME: DDB v2 does not depend on dynamodbstreams_stores as DynamoDB Local holds all the state visitor.visit(dynamodbstreams_stores) visitor.visit(AssetDirectory(self.service, os.path.join(config.dirs.data, self.service))) @@ -555,6 +563,9 @@ def create_table( if "NumberOfDecreasesToday" not in table_description["ProvisionedThroughput"]: table_description["ProvisionedThroughput"]["NumberOfDecreasesToday"] = 0 + if "WarmThroughput" in table_description: + table_description["WarmThroughput"]["Status"] = "UPDATING" + tags = table_definitions.pop("Tags", []) if tags: get_store(context.account_id, context.region).TABLE_TAGS[table_arn] = { @@ -572,6 +583,13 @@ def delete_table( ) -> DeleteTableOutput: global_table_region = self.get_global_table_region(context, table_name) + self.ensure_table_exists( + context.account_id, + global_table_region, + table_name, + error_message=f"Requested resource not found: Table: {table_name} not found", + ) + # Limitation note: On AWS, for a replicated table, if the source table is deleted, the replicated tables continue to exist. # This is not the case for LocalStack, where all replicated tables will also be removed if source is deleted. @@ -603,7 +621,7 @@ def describe_table( store = get_store(context.account_id, context.region) # Update replication details - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) replica_description_list = [] @@ -631,6 +649,9 @@ def describe_table( table_description["TableClassSummary"] = { "TableClass": table_definitions["TableClass"] } + if warm_throughput := table_definitions.get("WarmThroughput"): + table_description["WarmThroughput"] = warm_throughput.copy() + table_description["WarmThroughput"].setdefault("Status", "ACTIVE") if "GlobalSecondaryIndexes" in table_description: for gsi in table_description["GlobalSecondaryIndexes"]: @@ -643,6 +664,17 @@ def describe_table( # Terraform depends on this parity for update operations gsi["ProvisionedThroughput"] = default_values | gsi.get("ProvisionedThroughput", {}) + # Set defaults for warm throughput + if "WarmThroughput" not in table_description: + billing_mode = table_definitions.get("BillingMode") if table_definitions else None + table_description["WarmThroughput"] = { + "ReadUnitsPerSecond": 12000 if billing_mode == "PAY_PER_REQUEST" else 5, + "WriteUnitsPerSecond": 4000 if billing_mode == "PAY_PER_REQUEST" else 5, + } + table_description["WarmThroughput"]["Status"] = ( + table_description.get("TableStatus") or "ACTIVE" + ) + return DescribeTableOutput( Table=select_from_typed_dict(TableDescription, table_description) ) @@ -672,7 +704,7 @@ def update_table( store = get_store(context.account_id, global_table_region) # Dict with source region to set of replicated regions - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) for replica_update in replica_updates: for key, details in replica_update.items(): @@ -754,6 +786,22 @@ def list_tables( return response + # + # Contributor Insights + # + + @handler("DescribeContributorInsights", expand=False) + def describe_contributor_insights( + self, + context: RequestContext, + describe_contributor_insights_input: DescribeContributorInsightsInput, + ) -> DescribeContributorInsightsOutput: + return DescribeContributorInsightsOutput( + TableName=describe_contributor_insights_input["TableName"], + IndexName=describe_contributor_insights_input.get("IndexName"), + ContributorInsightsStatus="DISABLED", + ) + # # Item ops # @@ -895,6 +943,21 @@ def transact_write_items( transact_write_items_input: TransactWriteItemsInput, ) -> TransactWriteItemsOutput: # TODO: add global table support + + transact_items = transact_write_items_input["TransactItems"] + for item in transact_items: + for key in ["Put", "Update", "Delete", "ConditionCheck"]: + inner_item = item.get(key) + if inner_item: + # Extract the table name from the ARN; DynamoDB Local does not currently support + # full ARNs in this operation: https://github.com/awslabs/amazon-dynamodb-local-samples/issues/34 + inner_item["TableName"] = inner_item["TableName"].split(":table/")[-1] + + # Normalize the request structure to ensure it matches the expected format for DynamoDB Local. + data = json.loads(context.request.data) + data["TransactItems"] = transact_items + context.request.data = to_bytes(json.dumps(data, cls=BytesEncoder)) + client_token: str | None = transact_write_items_input.get("ClientRequestToken") if client_token: @@ -911,6 +974,18 @@ def transact_get_items( transact_items: TransactGetItemList, return_consumed_capacity: ReturnConsumedCapacity = None, ) -> TransactGetItemsOutput: + for transact_item in transact_items["TransactItems"]: + item: Get = transact_item.get("Get") + if item: + # Extract the table name from the ARN; DynamoDB Local does not currently support + # full ARNs in this operation: https://github.com/awslabs/amazon-dynamodb-local-samples/issues/34 + item["TableName"] = item["TableName"].split(":table/")[-1] + + # Normalize the request structure to ensure it matches the expected format for DynamoDB Local. + data = json.loads(context.request.data) + data["TransactItems"] = transact_items["TransactItems"] + context.request.data = to_bytes(json.dumps(data, cls=BytesEncoder)) + return self.forward_request(context) @handler("ExecuteTransaction", expand=False) @@ -1032,7 +1107,7 @@ def create_global_table( replication_group: ReplicaList, **kwargs, ) -> CreateGlobalTableOutput: - global_tables: Dict = get_store(context.account_id, context.region).GLOBAL_TABLES + global_tables: dict = get_store(context.account_id, context.region).GLOBAL_TABLES if global_table_name in global_tables: raise GlobalTableAlreadyExistsException("Global table with this name already exists") replication_group = [grp.copy() for grp in replication_group or []] @@ -1110,41 +1185,49 @@ def enable_kinesis_streaming_destination( enable_kinesis_streaming_configuration: EnableKinesisStreamingConfiguration = None, **kwargs, ) -> KinesisStreamingDestinationOutput: - self.ensure_table_exists(context.account_id, context.region, table_name) + self.ensure_table_exists( + context.account_id, + context.region, + table_name, + error_message=f"Requested resource not found: Table: {table_name} not found", + ) + + enable_kinesis_streaming_configuration = enable_kinesis_streaming_configuration or {} if not kinesis_stream_exists(stream_arn=stream_arn): raise ValidationException("User does not have a permission to use kinesis stream") - table_def = get_store(context.account_id, context.region).table_definitions.setdefault( - table_name, {} - ) + store = get_store(context.account_id, context.region) + streaming_destinations = store.streaming_destinations.get(table_name) or [] - dest_status = table_def.get("KinesisDataStreamDestinationStatus") - if dest_status not in ["DISABLED", "ENABLE_FAILED", None]: - raise ValidationException( - "Table is not in a valid state to enable Kinesis Streaming " - "Destination:EnableKinesisStreamingDestination must be DISABLED or ENABLE_FAILED " - "to perform ENABLE operation." - ) + destinations = [d for d in streaming_destinations if d["StreamArn"] == stream_arn] + if destinations: + status = destinations[0].get("DestinationStatus", None) + if status not in ["DISABLED", "ENABLED_FAILED", None]: + raise ValidationException( + "Table is not in a valid state to enable Kinesis Streaming " + "Destination:EnableKinesisStreamingDestination must be DISABLED or ENABLE_FAILED " + "to perform ENABLE operation." + ) - table_def["KinesisDataStreamDestinations"] = ( - table_def.get("KinesisDataStreamDestinations") or [] - ) # remove the stream destination if already present - table_def["KinesisDataStreamDestinations"] = [ - t for t in table_def["KinesisDataStreamDestinations"] if t["StreamArn"] != stream_arn + store.streaming_destinations[table_name] = [ + _d for _d in streaming_destinations if _d["StreamArn"] != stream_arn ] # append the active stream destination at the end of the list - table_def["KinesisDataStreamDestinations"].append( - { - "DestinationStatus": DestinationStatus.ACTIVE, - "DestinationStatusDescription": "Stream is active", - "StreamArn": stream_arn, - } + store.streaming_destinations[table_name].append( + KinesisDataStreamDestination( + DestinationStatus=DestinationStatus.ACTIVE, + DestinationStatusDescription="Stream is active", + StreamArn=stream_arn, + ApproximateCreationDateTimePrecision=ApproximateCreationDateTimePrecision.MILLISECOND, + ) ) - table_def["KinesisDataStreamDestinationStatus"] = DestinationStatus.ACTIVE return KinesisStreamingDestinationOutput( - DestinationStatus=DestinationStatus.ACTIVE, StreamArn=stream_arn, TableName=table_name + DestinationStatus=DestinationStatus.ENABLING, + StreamArn=stream_arn, + TableName=table_name, + EnableKinesisStreamingConfiguration=enable_kinesis_streaming_configuration, ) def disable_kinesis_streaming_destination( @@ -1155,32 +1238,31 @@ def disable_kinesis_streaming_destination( enable_kinesis_streaming_configuration: EnableKinesisStreamingConfiguration = None, **kwargs, ) -> KinesisStreamingDestinationOutput: - self.ensure_table_exists(context.account_id, context.region, table_name) - if not kinesis_stream_exists(stream_arn): + self.ensure_table_exists( + context.account_id, + context.region, + table_name, + error_message=f"Requested resource not found: Table: {table_name} not found", + ) + + if not kinesis_stream_exists(stream_arn=stream_arn): raise ValidationException( "User does not have a permission to use kinesis stream", ) - table_def = get_store(context.account_id, context.region).table_definitions.setdefault( - table_name, {} - ) - - stream_destinations = table_def.get("KinesisDataStreamDestinations") - if stream_destinations: - if table_def["KinesisDataStreamDestinationStatus"] == DestinationStatus.ACTIVE: - for dest in stream_destinations: - if ( - dest["StreamArn"] == stream_arn - and dest["DestinationStatus"] == DestinationStatus.ACTIVE - ): - dest["DestinationStatus"] = DestinationStatus.DISABLED - dest["DestinationStatusDescription"] = ("Stream is disabled",) - table_def["KinesisDataStreamDestinationStatus"] = DestinationStatus.DISABLED - return KinesisStreamingDestinationOutput( - DestinationStatus=DestinationStatus.DISABLED, - StreamArn=stream_arn, - TableName=table_name, - ) + store = get_store(context.account_id, context.region) + streaming_destinations = store.streaming_destinations.get(table_name) or [] + + # Get the right destination based on the arn + destinations = [d for d in streaming_destinations if d["StreamArn"] == stream_arn] + if destinations: + destinations[0]["DestinationStatus"] = DestinationStatus.DISABLED + destinations[0]["DestinationStatusDescription"] = "Stream is disabled" + return KinesisStreamingDestinationOutput( + DestinationStatus=DestinationStatus.DISABLING, + StreamArn=stream_arn, + TableName=table_name, + ) raise ValidationException( "Table is not in a valid state to disable Kinesis Streaming Destination:" "DisableKinesisStreamingDestination must be ACTIVE to perform DISABLE operation." @@ -1191,13 +1273,80 @@ def describe_kinesis_streaming_destination( ) -> DescribeKinesisStreamingDestinationOutput: self.ensure_table_exists(context.account_id, context.region, table_name) - table_def = ( - get_store(context.account_id, context.region).table_definitions.get(table_name) or {} - ) + store = get_store(context.account_id, context.region) + table_destinations = store.streaming_destinations.get(table_name) or [] + stream_destinations = copy.deepcopy(table_destinations) + + for destination in stream_destinations: + destination.pop("ApproximateCreationDateTimePrecision", None) + destination.pop("DestinationStatusDescription", None) - stream_destinations = table_def.get("KinesisDataStreamDestinations") or [] return DescribeKinesisStreamingDestinationOutput( - KinesisDataStreamDestinations=stream_destinations, TableName=table_name + KinesisDataStreamDestinations=stream_destinations, + TableName=table_name, + ) + + def update_kinesis_streaming_destination( + self, + context: RequestContext, + table_name: TableArn, + stream_arn: StreamArn, + update_kinesis_streaming_configuration: UpdateKinesisStreamingConfiguration | None = None, + **kwargs, + ) -> UpdateKinesisStreamingDestinationOutput: + self.ensure_table_exists(context.account_id, context.region, table_name) + + if not update_kinesis_streaming_configuration: + raise ValidationException( + "Streaming destination cannot be updated with given parameters: " + "UpdateKinesisStreamingConfiguration cannot be null or contain only null values" + ) + + time_precision = update_kinesis_streaming_configuration.get( + "ApproximateCreationDateTimePrecision" + ) + if time_precision not in ( + ApproximateCreationDateTimePrecision.MILLISECOND, + ApproximateCreationDateTimePrecision.MICROSECOND, + ): + raise ValidationException( + f"1 validation error detected: Value '{time_precision}' at " + "'updateKinesisStreamingConfiguration.approximateCreationDateTimePrecision' failed to satisfy constraint: " + "Member must satisfy enum value set: [MILLISECOND, MICROSECOND]" + ) + + store = get_store(context.account_id, context.region) + table_destinations = store.streaming_destinations.get(table_name) or [] + + # filter the right destination based on the stream ARN + destinations = [d for d in table_destinations if d["StreamArn"] == stream_arn] + if not destinations: + raise ValidationException( + "Table is not in a valid state to enable Kinesis Streaming Destination: " + f"No streaming destination with streamArn: {stream_arn} found for table with tableName: {table_name}" + ) + + destination = destinations[0] + table_def = store.table_definitions.get(table_name) or {} + table_def.setdefault("KinesisDataStreamDestinations", []) + + table_id = store.table_definitions.get(table_name, {}).get("TableId") + if ( + existing_precision := destination["ApproximateCreationDateTimePrecision"] + ) == update_kinesis_streaming_configuration["ApproximateCreationDateTimePrecision"]: + raise ValidationException( + f"Invalid Request: Precision is already set to the desired value of {existing_precision} " + f"for tableId: {table_id}, kdsArn: {stream_arn}" + ) + destination["ApproximateCreationDateTimePrecision"] = time_precision + + return UpdateKinesisStreamingDestinationOutput( + TableName=table_name, + StreamArn=stream_arn, + DestinationStatus=DestinationStatus.UPDATING, + UpdateKinesisStreamingConfiguration=UpdateKinesisStreamingConfiguration( + ApproximateCreationDateTimePrecision=time_precision, + ), ) # @@ -1275,7 +1424,12 @@ def table_exists(account_id: str, region_name: str, table_name: str) -> bool: return dynamodb_table_exists(table_name, client) @staticmethod - def ensure_table_exists(account_id: str, region_name: str, table_name: str): + def ensure_table_exists( + account_id: str, + region_name: str, + table_name: str, + error_message: str = "Cannot do operations on a non-existent table", + ): """ Raise ResourceNotFoundException if the given table does not exist. @@ -1285,7 +1439,7 @@ def ensure_table_exists(account_id: str, region_name: str, table_name: str): :raise: ResourceNotFoundException if table does not exist in DynamoDB Local """ if not DynamoDBProvider.table_exists(account_id, region_name, table_name): - raise ResourceNotFoundException("Cannot do operations on a non-existent table") + raise ResourceNotFoundException(error_message) @staticmethod def get_global_table_region(context: RequestContext, table_name: str) -> str: @@ -1312,7 +1466,7 @@ def get_global_table_region(context: RequestContext, table_name: str) -> str: return context.region @staticmethod - def prepare_request_headers(headers: Dict, account_id: str, region_name: str): + def prepare_request_headers(headers: dict, account_id: str, region_name: str): """ Modify the Credentials field of Authorization header to achieve namespacing in DynamoDBLocal. """ @@ -1329,7 +1483,7 @@ def prepare_request_headers(headers: Dict, account_id: str, region_name: str): flags=re.IGNORECASE, ) - def fix_consumed_capacity(self, request: Dict, result: Dict): + def fix_consumed_capacity(self, request: dict, result: dict): # make sure we append 'ConsumedCapacity', which is properly # returned by dynalite, but not by AWS's DynamoDBLocal table_name = request.get("TableName") @@ -1457,7 +1611,7 @@ def is_index_query_valid(account_id: str, region_name: str, query_data: dict) -> return True -def kinesis_stream_exists(stream_arn): +def kinesis_stream_exists(stream_arn: str) -> bool: account_id = extract_account_id_from_arn(stream_arn) region_name = extract_region_from_arn(stream_arn) diff --git a/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py b/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py index e9164465fdd57..16c0451bacb4d 100644 --- a/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py +++ b/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py @@ -1,15 +1,24 @@ import logging import threading -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING from bson.json_util import dumps from localstack import config from localstack.aws.api import RequestContext -from localstack.aws.api.dynamodbstreams import StreamStatus, StreamViewType, TableName +from localstack.aws.api.dynamodbstreams import ( + StreamDescription, + StreamStatus, + StreamViewType, + TableName, +) from localstack.aws.connect import connect_to from localstack.services.dynamodb.v2.provider import DynamoDBProvider -from localstack.services.dynamodbstreams.models import DynamoDbStreamsStore, dynamodbstreams_stores +from localstack.services.dynamodbstreams.models import ( + DynamoDbStreamsStore, + StreamWrapper, + dynamodbstreams_stores, +) from localstack.utils.aws import arns, resources from localstack.utils.common import now_utc from localstack.utils.threads import FuncThread @@ -65,28 +74,32 @@ def add_dynamodb_stream( stream_name=stream_name, ) latest_stream_label = latest_stream_label or "latest" - stream = { - "StreamArn": arns.dynamodb_stream_arn( - table_name=table_name, - latest_stream_label=latest_stream_label, - account_id=account_id, - region_name=region_name, - ), - "TableName": table_name, - "StreamLabel": latest_stream_label, - "StreamStatus": StreamStatus.ENABLING, - "KeySchema": [], - "Shards": [], - "StreamViewType": view_type, - "shards_id_map": {}, - } - store.ddb_streams[table_name] = stream - - -def get_stream_for_table(account_id: str, region_name: str, table_arn: str) -> dict: + stream_arn = arns.dynamodb_stream_arn( + table_name=table_name, + latest_stream_label=latest_stream_label, + account_id=account_id, + region_name=region_name, + ) + stream = StreamDescription( + TableName=table_name, + StreamArn=stream_arn, + StreamLabel=latest_stream_label, + StreamStatus=StreamStatus.ENABLING, + KeySchema=[], + Shards=[], + StreamViewType=view_type, + ) + store.ddb_streams[table_name] = StreamWrapper(StreamDescription=stream) + + +def get_stream_for_table( + account_id: str, region_name: str, table_arn: str +) -> StreamDescription | None: store = get_dynamodbstreams_store(account_id, region_name) table_name = table_name_from_stream_arn(table_arn) - return store.ddb_streams.get(table_name) + if stream := store.ddb_streams.get(table_name): + return stream.StreamDescription + return None def _process_forwarded_records( @@ -206,11 +219,11 @@ def kinesis_shard_id(dynamodbstream_shard_id: str) -> str: return f"{shard_params[0]}-{shard_params[-1]}" -def get_shard_id(stream: Dict, kinesis_shard_id: str) -> str: - ddb_stream_shard_id = stream.get("shards_id_map", {}).get(kinesis_shard_id) +def get_shard_id(stream: StreamWrapper, kinesis_shard_id: str) -> str: + ddb_stream_shard_id = stream.shards_id_map.get(kinesis_shard_id) if not ddb_stream_shard_id: ddb_stream_shard_id = shard_id(kinesis_shard_id) - stream["shards_id_map"][kinesis_shard_id] = ddb_stream_shard_id + stream.shards_id_map[kinesis_shard_id] = ddb_stream_shard_id return ddb_stream_shard_id diff --git a/localstack-core/localstack/services/dynamodbstreams/models.py b/localstack-core/localstack/services/dynamodbstreams/models.py index a8a6672babf11..811f09e9a6536 100644 --- a/localstack-core/localstack/services/dynamodbstreams/models.py +++ b/localstack-core/localstack/services/dynamodbstreams/models.py @@ -1,11 +1,19 @@ -from typing import Dict +import dataclasses +from localstack.aws.api.dynamodbstreams import StreamDescription from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute +@dataclasses.dataclass +class StreamWrapper: + """Wrapper for the API stub and additional information about a store""" + + StreamDescription: StreamDescription + shards_id_map: dict[str, str] = dataclasses.field(default_factory=dict) + + class DynamoDbStreamsStore(BaseStore): - # maps table names to DynamoDB stream descriptions - ddb_streams: Dict[str, dict] = LocalAttribute(default=dict) + ddb_streams: dict[str, StreamWrapper] = LocalAttribute(default=dict) dynamodbstreams_stores = AccountRegionBundle("dynamodbstreams", DynamoDbStreamsStore) diff --git a/localstack-core/localstack/services/dynamodbstreams/provider.py b/localstack-core/localstack/services/dynamodbstreams/provider.py index 6c9548bb81ebf..7f5412a6cb0d0 100644 --- a/localstack-core/localstack/services/dynamodbstreams/provider.py +++ b/localstack-core/localstack/services/dynamodbstreams/provider.py @@ -15,11 +15,11 @@ PositiveIntegerObject, ResourceNotFoundException, SequenceNumber, + ShardFilter, ShardId, ShardIteratorType, Stream, StreamArn, - StreamDescription, StreamStatus, TableName, ) @@ -36,6 +36,7 @@ table_name_from_stream_arn, ) from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.collections import select_from_typed_dict LOG = logging.getLogger(__name__) @@ -56,32 +57,40 @@ class DynamoDBStreamsProvider(DynamodbstreamsApi, ServiceLifecycleHook): def __init__(self): self.shard_to_region = {} + def accept_state_visitor(self, visitor: StateVisitor): + from localstack.services.dynamodbstreams.models import dynamodbstreams_stores + + visitor.visit(dynamodbstreams_stores) + def describe_stream( self, context: RequestContext, stream_arn: StreamArn, - limit: PositiveIntegerObject = None, - exclusive_start_shard_id: ShardId = None, + limit: PositiveIntegerObject | None = None, + exclusive_start_shard_id: ShardId | None = None, + shard_filter: ShardFilter | None = None, **kwargs, ) -> DescribeStreamOutput: + # TODO add support for shard_filter og_region = get_original_region(context=context, stream_arn=stream_arn) store = get_dynamodbstreams_store(context.account_id, og_region) kinesis = get_kinesis_client(account_id=context.account_id, region_name=og_region) for stream in store.ddb_streams.values(): + stream_description = stream.StreamDescription _stream_arn = stream_arn if context.region != og_region: _stream_arn = change_region_in_ddb_stream_arn(_stream_arn, og_region) - if stream["StreamArn"] == _stream_arn: + if stream_description["StreamArn"] == _stream_arn: # get stream details dynamodb = connect_to( aws_access_key_id=context.account_id, region_name=og_region ).dynamodb - table_name = table_name_from_stream_arn(stream["StreamArn"]) + table_name = table_name_from_stream_arn(stream_description["StreamArn"]) stream_name = get_kinesis_stream_name(table_name) stream_details = kinesis.describe_stream(StreamName=stream_name) table_details = dynamodb.describe_table(TableName=table_name) - stream["KeySchema"] = table_details["Table"]["KeySchema"] - stream["StreamStatus"] = STREAM_STATUS_MAP.get( + stream_description["KeySchema"] = table_details["Table"]["KeySchema"] + stream_description["StreamStatus"] = STREAM_STATUS_MAP.get( stream_details["StreamDescription"]["StreamStatus"] ) @@ -101,8 +110,7 @@ def describe_stream( # slicing the resulting shards after the exclusive_start_shard_id parameters stream_shards = stream_shards[start_index + 1 :] - stream["Shards"] = stream_shards - stream_description = select_from_typed_dict(StreamDescription, stream) + stream_description["Shards"] = stream_shards stream_description["StreamArn"] = _stream_arn return DescribeStreamOutput(StreamDescription=stream_description) @@ -181,7 +189,10 @@ def list_streams( ) -> ListStreamsOutput: og_region = get_original_region(context=context, table_name=table_name) store = get_dynamodbstreams_store(context.account_id, og_region) - result = [select_from_typed_dict(Stream, res) for res in store.ddb_streams.values()] + result = [ + select_from_typed_dict(Stream, _s.StreamDescription) + for _s in store.ddb_streams.values() + ] if table_name: result: list[Stream] = [res for res in result if res["TableName"] == table_name] # If this is a stream from a table replica, we need to change the region in the stream ARN, as LocalStack diff --git a/localstack-core/localstack/services/dynamodbstreams/v2/provider.py b/localstack-core/localstack/services/dynamodbstreams/v2/provider.py index a91fbc592a992..9c8bb6d02bd8a 100644 --- a/localstack-core/localstack/services/dynamodbstreams/v2/provider.py +++ b/localstack-core/localstack/services/dynamodbstreams/v2/provider.py @@ -18,6 +18,7 @@ from localstack.services.dynamodb.v2.provider import DynamoDBProvider, modify_context_region from localstack.services.dynamodbstreams.dynamodbstreams_api import get_original_region from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.aws.arns import parse_arn LOG = logging.getLogger(__name__) @@ -32,6 +33,11 @@ def __init__(self): self.server = DynamodbServer.get() self.shard_to_region = {} + def accept_state_visitor(self, visitor: StateVisitor): + # DynamoDB Streams state is entirely dependent on DynamoDB Local state, and does not hold state itself + # the DynamoDB provider is responsible for the persistence of DDB Streams + pass + def on_after_init(self): # add response processor specific to ddblocal handlers.modify_service_response.append(self.service, modify_ddblocal_arns) diff --git a/localstack-core/localstack/services/ec2/models.py b/localstack-core/localstack/services/ec2/models.py index cf2bc854900da..bb64aa3fd3b22 100644 --- a/localstack-core/localstack/services/ec2/models.py +++ b/localstack-core/localstack/services/ec2/models.py @@ -1,27 +1,6 @@ from moto.ec2 import ec2_backends from moto.ec2.models import EC2Backend -from moto.ec2.models.subnets import Subnet def get_ec2_backend(account_id: str, region: str) -> EC2Backend: return ec2_backends[account_id][region] - - -# -# Pickle patches -# - - -def set_state(self, state): - state["_subnet_ip_generator"] = state["cidr"].hosts() - self.__dict__.update(state) - - -def get_state(self): - state = self.__dict__.copy() - state.pop("_subnet_ip_generator", None) - return state - - -Subnet.__setstate__ = set_state -Subnet.__getstate__ = get_state diff --git a/localstack-core/localstack/services/ec2/patches.py b/localstack-core/localstack/services/ec2/patches.py index d2037015905ef..66ead222acfa6 100644 --- a/localstack-core/localstack/services/ec2/patches.py +++ b/localstack-core/localstack/services/ec2/patches.py @@ -1,7 +1,7 @@ import logging -from typing import Optional from moto.ec2 import models as ec2_models +from moto.ec2.models.vpcs import VPCEndPoint from moto.utilities.id_generator import Tags from localstack.services.ec2.exceptions import ( @@ -15,6 +15,8 @@ localstack_id, ) from localstack.utils.patch import patch +from localstack.utils.strings import short_uid +from localstack.utils.urls import localstack_host LOG = logging.getLogger(__name__) @@ -93,12 +95,31 @@ def generate(self, existing_ids: ExistingIds = None, tags: Tags = None) -> str: def apply_patches(): + @patch(ec2_models.vpcs.VPCBackend.create_vpc_endpoint) + def ec2_create_vpc_endpoint( + fn: ec2_models.VPCBackend.create_vpc_endpoint, self: ec2_models.VPCBackend, **kwargs + ) -> VPCEndPoint: + vpc_endpoint: VPCEndPoint = fn(self=self, **kwargs) + + # moto returns the dns entries as `.com.amazonaws..` + # to keep the aws style `...vpce.amazonaws.com` and ensure it can be routed + # by the individual services in LocalStack we will be creating the following entries: + # `...vpce.` + if service_name := kwargs.get("service_name"): + ls_service_name = ".".join(service_name.split(".")[::-1]).replace( + "amazonaws.com", f"vpce.{localstack_host()}" + ) + for dns_entry in vpc_endpoint.dns_entries or []: + dns_entry["dns_name"] = f"{vpc_endpoint.id}-{short_uid()}.{ls_service_name}" + + return vpc_endpoint + @patch(ec2_models.subnets.SubnetBackend.create_subnet) def ec2_create_subnet( fn: ec2_models.subnets.SubnetBackend.create_subnet, self: ec2_models.subnets.SubnetBackend, *args, - tags: Optional[dict[str, str]] = None, + tags: dict[str, str] | None = None, **kwargs, ): # Patch this method so that we can create a subnet with a specific "custom" @@ -151,8 +172,8 @@ def ec2_create_security_group( self: ec2_models.security_groups.SecurityGroupBackend, name: str, *args, - vpc_id: Optional[str] = None, - tags: Optional[dict[str, str]] = None, + vpc_id: str | None = None, + tags: dict[str, str] | None = None, force: bool = False, **kwargs, ): @@ -191,7 +212,7 @@ def ec2_create_vpc( self: ec2_models.vpcs.VPCBackend, cidr_block: str, *args, - tags: Optional[list[dict[str, str]]] = None, + tags: list[dict[str, str]] | None = None, is_default: bool = False, **kwargs, ): diff --git a/localstack-core/localstack/services/ec2/provider.py b/localstack-core/localstack/services/ec2/provider.py index ab52195e4cfa8..2480058d5f089 100644 --- a/localstack-core/localstack/services/ec2/provider.py +++ b/localstack-core/localstack/services/ec2/provider.py @@ -3,7 +3,7 @@ import logging import re from abc import ABC -from datetime import datetime, timezone +from datetime import UTC, datetime from botocore.parsers import ResponseParserError from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase @@ -94,6 +94,7 @@ from localstack.services.ec2.patches import apply_patches from localstack.services.moto import call_moto, call_moto_with_request from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.patch import patch from localstack.utils.strings import first_char_to_upper, long_uid, short_uid @@ -107,6 +108,11 @@ class Ec2Provider(Ec2Api, ABC, ServiceLifecycleHook): def on_after_init(self): apply_patches() + def accept_state_visitor(self, visitor: StateVisitor): + from moto.ec2.models import ec2_backends + + visitor.visit(ec2_backends) + @handler("DescribeAvailabilityZones", expand=False) def describe_availability_zones( self, @@ -173,13 +179,13 @@ def describe_reserved_instances( ReservedInstances( AvailabilityZone="eu-central-1a", Duration=2628000, - End=datetime(2016, 6, 30, tzinfo=timezone.utc), + End=datetime(2016, 6, 30, tzinfo=UTC), FixedPrice=0.0, InstanceCount=2, InstanceType=InstanceType.t2_small, ProductDescription=RIProductDescription.Linux_UNIX, ReservedInstancesId=long_uid(), - Start=datetime(2016, 1, 1, tzinfo=timezone.utc), + Start=datetime(2016, 1, 1, tzinfo=UTC), State=ReservedInstanceState.active, UsagePrice=0.05, CurrencyCode=CurrencyCodeValues.USD, diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py index 03665a7c45fb6..c89efbef56a00 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class EC2DHCPOptionsProperties(TypedDict): - DhcpOptionsId: Optional[str] - DomainName: Optional[str] - DomainNameServers: Optional[list[str]] - NetbiosNameServers: Optional[list[str]] - NetbiosNodeType: Optional[int] - NtpServers: Optional[list[str]] - Tags: Optional[list[Tag]] + DhcpOptionsId: str | None + DomainName: str | None + DomainNameServers: list[str] | None + NetbiosNameServers: list[str] | None + NetbiosNodeType: int | None + NtpServers: list[str] | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py index c3ac8bb5a5827..947069d5b29d7 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2DHCPOptionsProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::DHCPOptions" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_dhcpoptions import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py index 8c33cde7b2ab8..457dd54450f27 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py @@ -3,7 +3,7 @@ import base64 from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,155 +16,155 @@ class EC2InstanceProperties(TypedDict): - AdditionalInfo: Optional[str] - Affinity: Optional[str] - AvailabilityZone: Optional[str] - BlockDeviceMappings: Optional[list[BlockDeviceMapping]] - CpuOptions: Optional[CpuOptions] - CreditSpecification: Optional[CreditSpecification] - DisableApiTermination: Optional[bool] - EbsOptimized: Optional[bool] - ElasticGpuSpecifications: Optional[list[ElasticGpuSpecification]] - ElasticInferenceAccelerators: Optional[list[ElasticInferenceAccelerator]] - EnclaveOptions: Optional[EnclaveOptions] - HibernationOptions: Optional[HibernationOptions] - HostId: Optional[str] - HostResourceGroupArn: Optional[str] - IamInstanceProfile: Optional[str] - Id: Optional[str] - ImageId: Optional[str] - InstanceInitiatedShutdownBehavior: Optional[str] - InstanceType: Optional[str] - Ipv6AddressCount: Optional[int] - Ipv6Addresses: Optional[list[InstanceIpv6Address]] - KernelId: Optional[str] - KeyName: Optional[str] - LaunchTemplate: Optional[LaunchTemplateSpecification] - LicenseSpecifications: Optional[list[LicenseSpecification]] - Monitoring: Optional[bool] - NetworkInterfaces: Optional[list[NetworkInterface]] - PlacementGroupName: Optional[str] - PrivateDnsName: Optional[str] - PrivateDnsNameOptions: Optional[PrivateDnsNameOptions] - PrivateIp: Optional[str] - PrivateIpAddress: Optional[str] - PropagateTagsToVolumeOnCreation: Optional[bool] - PublicDnsName: Optional[str] - PublicIp: Optional[str] - RamdiskId: Optional[str] - SecurityGroupIds: Optional[list[str]] - SecurityGroups: Optional[list[str]] - SourceDestCheck: Optional[bool] - SsmAssociations: Optional[list[SsmAssociation]] - SubnetId: Optional[str] - Tags: Optional[list[Tag]] - Tenancy: Optional[str] - UserData: Optional[str] - Volumes: Optional[list[Volume]] + AdditionalInfo: str | None + Affinity: str | None + AvailabilityZone: str | None + BlockDeviceMappings: list[BlockDeviceMapping] | None + CpuOptions: CpuOptions | None + CreditSpecification: CreditSpecification | None + DisableApiTermination: bool | None + EbsOptimized: bool | None + ElasticGpuSpecifications: list[ElasticGpuSpecification] | None + ElasticInferenceAccelerators: list[ElasticInferenceAccelerator] | None + EnclaveOptions: EnclaveOptions | None + HibernationOptions: HibernationOptions | None + HostId: str | None + HostResourceGroupArn: str | None + IamInstanceProfile: str | None + Id: str | None + ImageId: str | None + InstanceInitiatedShutdownBehavior: str | None + InstanceType: str | None + Ipv6AddressCount: int | None + Ipv6Addresses: list[InstanceIpv6Address] | None + KernelId: str | None + KeyName: str | None + LaunchTemplate: LaunchTemplateSpecification | None + LicenseSpecifications: list[LicenseSpecification] | None + Monitoring: bool | None + NetworkInterfaces: list[NetworkInterface] | None + PlacementGroupName: str | None + PrivateDnsName: str | None + PrivateDnsNameOptions: PrivateDnsNameOptions | None + PrivateIp: str | None + PrivateIpAddress: str | None + PropagateTagsToVolumeOnCreation: bool | None + PublicDnsName: str | None + PublicIp: str | None + RamdiskId: str | None + SecurityGroupIds: list[str] | None + SecurityGroups: list[str] | None + SourceDestCheck: bool | None + SsmAssociations: list[SsmAssociation] | None + SubnetId: str | None + Tags: list[Tag] | None + Tenancy: str | None + UserData: str | None + Volumes: list[Volume] | None class Ebs(TypedDict): - DeleteOnTermination: Optional[bool] - Encrypted: Optional[bool] - Iops: Optional[int] - KmsKeyId: Optional[str] - SnapshotId: Optional[str] - VolumeSize: Optional[int] - VolumeType: Optional[str] + DeleteOnTermination: bool | None + Encrypted: bool | None + Iops: int | None + KmsKeyId: str | None + SnapshotId: str | None + VolumeSize: int | None + VolumeType: str | None class BlockDeviceMapping(TypedDict): - DeviceName: Optional[str] - Ebs: Optional[Ebs] - NoDevice: Optional[dict] - VirtualName: Optional[str] + DeviceName: str | None + Ebs: Ebs | None + NoDevice: dict | None + VirtualName: str | None class InstanceIpv6Address(TypedDict): - Ipv6Address: Optional[str] + Ipv6Address: str | None class ElasticGpuSpecification(TypedDict): - Type: Optional[str] + Type: str | None class ElasticInferenceAccelerator(TypedDict): - Type: Optional[str] - Count: Optional[int] + Type: str | None + Count: int | None class Volume(TypedDict): - Device: Optional[str] - VolumeId: Optional[str] + Device: str | None + VolumeId: str | None class LaunchTemplateSpecification(TypedDict): - Version: Optional[str] - LaunchTemplateId: Optional[str] - LaunchTemplateName: Optional[str] + Version: str | None + LaunchTemplateId: str | None + LaunchTemplateName: str | None class EnclaveOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class PrivateIpAddressSpecification(TypedDict): - Primary: Optional[bool] - PrivateIpAddress: Optional[str] + Primary: bool | None + PrivateIpAddress: str | None class NetworkInterface(TypedDict): - DeviceIndex: Optional[str] - AssociateCarrierIpAddress: Optional[bool] - AssociatePublicIpAddress: Optional[bool] - DeleteOnTermination: Optional[bool] - Description: Optional[str] - GroupSet: Optional[list[str]] - Ipv6AddressCount: Optional[int] - Ipv6Addresses: Optional[list[InstanceIpv6Address]] - NetworkInterfaceId: Optional[str] - PrivateIpAddress: Optional[str] - PrivateIpAddresses: Optional[list[PrivateIpAddressSpecification]] - SecondaryPrivateIpAddressCount: Optional[int] - SubnetId: Optional[str] + DeviceIndex: str | None + AssociateCarrierIpAddress: bool | None + AssociatePublicIpAddress: bool | None + DeleteOnTermination: bool | None + Description: str | None + GroupSet: list[str] | None + Ipv6AddressCount: int | None + Ipv6Addresses: list[InstanceIpv6Address] | None + NetworkInterfaceId: str | None + PrivateIpAddress: str | None + PrivateIpAddresses: list[PrivateIpAddressSpecification] | None + SecondaryPrivateIpAddressCount: int | None + SubnetId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class HibernationOptions(TypedDict): - Configured: Optional[bool] + Configured: bool | None class LicenseSpecification(TypedDict): - LicenseConfigurationArn: Optional[str] + LicenseConfigurationArn: str | None class CpuOptions(TypedDict): - CoreCount: Optional[int] - ThreadsPerCore: Optional[int] + CoreCount: int | None + ThreadsPerCore: int | None class PrivateDnsNameOptions(TypedDict): - EnableResourceNameDnsAAAARecord: Optional[bool] - EnableResourceNameDnsARecord: Optional[bool] - HostnameType: Optional[str] + EnableResourceNameDnsAAAARecord: bool | None + EnableResourceNameDnsARecord: bool | None + HostnameType: str | None class AssociationParameter(TypedDict): - Key: Optional[str] - Value: Optional[list[str]] + Key: str | None + Value: list[str] | None class SsmAssociation(TypedDict): - DocumentName: Optional[str] - AssociationParameters: Optional[list[AssociationParameter]] + DocumentName: str | None + AssociationParameters: list[AssociationParameter] | None class CreditSpecification(TypedDict): - CPUCredits: Optional[str] + CPUCredits: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py index 60f400297a47f..8a218e994d068 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2InstanceProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::Instance" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_instance import EC2InstanceProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py index 1ad0d6981b9c0..075f8abe7bfa6 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,13 +14,13 @@ class EC2InternetGatewayProperties(TypedDict): - InternetGatewayId: Optional[str] - Tags: Optional[list[Tag]] + InternetGatewayId: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py index 51c889fae01a0..37df146193039 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2InternetGatewayProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::InternetGateway" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_internetgateway import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py index 8c03d6bc738b5..34eeb3d6e638d 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class EC2KeyPairProperties(TypedDict): - KeyName: Optional[str] - KeyFingerprint: Optional[str] - KeyFormat: Optional[str] - KeyPairId: Optional[str] - KeyType: Optional[str] - PublicKeyMaterial: Optional[str] - Tags: Optional[list[Tag]] + KeyName: str | None + KeyFingerprint: str | None + KeyFormat: str | None + KeyPairId: str | None + KeyType: str | None + PublicKeyMaterial: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py index 5bb9524b1f667..655c7d6d7e204 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2KeyPairProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::KeyPair" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_keypair import EC2KeyPairProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py index de03079d89699..c29b695adc4d8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,21 +14,21 @@ class EC2NatGatewayProperties(TypedDict): - SubnetId: Optional[str] - AllocationId: Optional[str] - ConnectivityType: Optional[str] - MaxDrainDurationSeconds: Optional[int] - NatGatewayId: Optional[str] - PrivateIpAddress: Optional[str] - SecondaryAllocationIds: Optional[list[str]] - SecondaryPrivateIpAddressCount: Optional[int] - SecondaryPrivateIpAddresses: Optional[list[str]] - Tags: Optional[list[Tag]] + SubnetId: str | None + AllocationId: str | None + ConnectivityType: str | None + MaxDrainDurationSeconds: int | None + NatGatewayId: str | None + PrivateIpAddress: str | None + SecondaryAllocationIds: list[str] | None + SecondaryPrivateIpAddressCount: int | None + SecondaryPrivateIpAddresses: list[str] | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py index e8036702f5e79..9fe089b969d68 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2NatGatewayProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::NatGateway" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_natgateway import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py index 47d36951d0068..f4f2c07634cd8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class EC2NetworkAclProperties(TypedDict): - VpcId: Optional[str] - Id: Optional[str] - Tags: Optional[list[Tag]] + VpcId: str | None + Id: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py index 0f24a9cd40adc..026799db63eee 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2NetworkAclProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::NetworkAcl" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_networkacl import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py index 8308fb5bfa990..298e41b209219 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,25 +14,25 @@ class EC2PrefixListProperties(TypedDict): - AddressFamily: Optional[str] - MaxEntries: Optional[int] - PrefixListName: Optional[str] - Arn: Optional[str] - Entries: Optional[list[Entry]] - OwnerId: Optional[str] - PrefixListId: Optional[str] - Tags: Optional[list[Tag]] - Version: Optional[int] + AddressFamily: str | None + MaxEntries: int | None + PrefixListName: str | None + Arn: str | None + Entries: list[Entry] | None + OwnerId: str | None + PrefixListId: str | None + Tags: list[Tag] | None + Version: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class Entry(TypedDict): - Cidr: Optional[str] - Description: Optional[str] + Cidr: str | None + Description: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py index 5d8b993d28409..5aadb404eda7c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2PrefixListProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::PrefixList" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_prefixlist import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py index c779541d04229..6665b4b1594b0 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from moto.ec2.utils import generate_route_id @@ -16,20 +16,20 @@ class EC2RouteProperties(TypedDict): - RouteTableId: Optional[str] - CarrierGatewayId: Optional[str] - DestinationCidrBlock: Optional[str] - DestinationIpv6CidrBlock: Optional[str] - EgressOnlyInternetGatewayId: Optional[str] - GatewayId: Optional[str] - Id: Optional[str] - InstanceId: Optional[str] - LocalGatewayId: Optional[str] - NatGatewayId: Optional[str] - NetworkInterfaceId: Optional[str] - TransitGatewayId: Optional[str] - VpcEndpointId: Optional[str] - VpcPeeringConnectionId: Optional[str] + RouteTableId: str | None + CarrierGatewayId: str | None + DestinationCidrBlock: str | None + DestinationIpv6CidrBlock: str | None + EgressOnlyInternetGatewayId: str | None + GatewayId: str | None + Id: str | None + InstanceId: str | None + LocalGatewayId: str | None + NatGatewayId: str | None + NetworkInterfaceId: str | None + TransitGatewayId: str | None + VpcEndpointId: str | None + VpcPeeringConnectionId: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py index abd759b08aaca..ae127a0a939a7 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2RouteProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::Route" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_route import EC2RouteProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py index 618c3fad99c08..6769004c93454 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class EC2RouteTableProperties(TypedDict): - VpcId: Optional[str] - RouteTableId: Optional[str] - Tags: Optional[list[Tag]] + VpcId: str | None + RouteTableId: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py index 07396c832bf66..2184aadb1a0b5 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2RouteTableProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::RouteTable" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_routetable import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py index 39621b8e5178e..77fd0e60f2ca1 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,43 +15,43 @@ class EC2SecurityGroupProperties(TypedDict): - GroupDescription: Optional[str] - GroupId: Optional[str] - GroupName: Optional[str] - Id: Optional[str] - SecurityGroupEgress: Optional[list[Egress]] - SecurityGroupIngress: Optional[list[Ingress]] - Tags: Optional[list[Tag]] - VpcId: Optional[str] + GroupDescription: str | None + GroupId: str | None + GroupName: str | None + Id: str | None + SecurityGroupEgress: list[Egress] | None + SecurityGroupIngress: list[Ingress] | None + Tags: list[Tag] | None + VpcId: str | None class Ingress(TypedDict): - IpProtocol: Optional[str] - CidrIp: Optional[str] - CidrIpv6: Optional[str] - Description: Optional[str] - FromPort: Optional[int] - SourcePrefixListId: Optional[str] - SourceSecurityGroupId: Optional[str] - SourceSecurityGroupName: Optional[str] - SourceSecurityGroupOwnerId: Optional[str] - ToPort: Optional[int] + IpProtocol: str | None + CidrIp: str | None + CidrIpv6: str | None + Description: str | None + FromPort: int | None + SourcePrefixListId: str | None + SourceSecurityGroupId: str | None + SourceSecurityGroupName: str | None + SourceSecurityGroupOwnerId: str | None + ToPort: int | None class Egress(TypedDict): - IpProtocol: Optional[str] - CidrIp: Optional[str] - CidrIpv6: Optional[str] - Description: Optional[str] - DestinationPrefixListId: Optional[str] - DestinationSecurityGroupId: Optional[str] - FromPort: Optional[int] - ToPort: Optional[int] + IpProtocol: str | None + CidrIp: str | None + CidrIpv6: str | None + Description: str | None + DestinationPrefixListId: str | None + DestinationSecurityGroupId: str | None + FromPort: int | None + ToPort: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py index 176bddb74e703..c867891dbc50b 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2SecurityGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::SecurityGroup" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_securitygroup import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py index e7c82a0d3669c..2b37495466411 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,33 +16,33 @@ class EC2SubnetProperties(TypedDict): - VpcId: Optional[str] - AssignIpv6AddressOnCreation: Optional[bool] - AvailabilityZone: Optional[str] - AvailabilityZoneId: Optional[str] - CidrBlock: Optional[str] - EnableDns64: Optional[bool] - Ipv6CidrBlock: Optional[str] - Ipv6CidrBlocks: Optional[list[str]] - Ipv6Native: Optional[bool] - MapPublicIpOnLaunch: Optional[bool] - NetworkAclAssociationId: Optional[str] - OutpostArn: Optional[str] - PrivateDnsNameOptionsOnLaunch: Optional[dict] - SubnetId: Optional[str] - Tags: Optional[list[Tag]] + VpcId: str | None + AssignIpv6AddressOnCreation: bool | None + AvailabilityZone: str | None + AvailabilityZoneId: str | None + CidrBlock: str | None + EnableDns64: bool | None + Ipv6CidrBlock: str | None + Ipv6CidrBlocks: list[str] | None + Ipv6Native: bool | None + MapPublicIpOnLaunch: bool | None + NetworkAclAssociationId: str | None + OutpostArn: str | None + PrivateDnsNameOptionsOnLaunch: dict | None + SubnetId: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" def generate_subnet_read_payload( - ec2_client, schema, subnet_ids: Optional[list[str]] = None + ec2_client, schema, subnet_ids: list[str] | None = None ) -> list[EC2SubnetProperties]: kwargs = {} if subnet_ids: diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py index 65349afd2f656..1efd975b0696c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2SubnetProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::Subnet" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_subnet import EC2SubnetProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py index d07bbdcb6665e..9b5f527b47d4c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,9 +14,9 @@ class EC2SubnetRouteTableAssociationProperties(TypedDict): - RouteTableId: Optional[str] - SubnetId: Optional[str] - Id: Optional[str] + RouteTableId: str | None + SubnetId: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py index 6841f27741847..7f76157a67283 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2SubnetRouteTableAssociationProviderPlugin(CloudFormationResourceProvide name = "AWS::EC2::SubnetRouteTableAssociation" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_subnetroutetableassociation import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py index 4a4b5825966cc..b6488e2410bd8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,24 +14,24 @@ class EC2TransitGatewayProperties(TypedDict): - AmazonSideAsn: Optional[int] - AssociationDefaultRouteTableId: Optional[str] - AutoAcceptSharedAttachments: Optional[str] - DefaultRouteTableAssociation: Optional[str] - DefaultRouteTablePropagation: Optional[str] - Description: Optional[str] - DnsSupport: Optional[str] - Id: Optional[str] - MulticastSupport: Optional[str] - PropagationDefaultRouteTableId: Optional[str] - Tags: Optional[list[Tag]] - TransitGatewayCidrBlocks: Optional[list[str]] - VpnEcmpSupport: Optional[str] + AmazonSideAsn: int | None + AssociationDefaultRouteTableId: str | None + AutoAcceptSharedAttachments: str | None + DefaultRouteTableAssociation: str | None + DefaultRouteTablePropagation: str | None + Description: str | None + DnsSupport: str | None + Id: str | None + MulticastSupport: str | None + PropagationDefaultRouteTableId: str | None + Tags: list[Tag] | None + TransitGatewayCidrBlocks: list[str] | None + VpnEcmpSupport: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py index eac947d512bd5..916d4a9daec47 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2TransitGatewayProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::TransitGateway" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_transitgateway import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py index 59aac3a6a15d4..e022ea2aa0bce 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class EC2TransitGatewayAttachmentProperties(TypedDict): - SubnetIds: Optional[list[str]] - TransitGatewayId: Optional[str] - VpcId: Optional[str] - Id: Optional[str] - Options: Optional[dict] - Tags: Optional[list[Tag]] + SubnetIds: list[str] | None + TransitGatewayId: str | None + VpcId: str | None + Id: str | None + Options: dict | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py index 7b34a535f56e6..331cecf66c630 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2TransitGatewayAttachmentProviderPlugin(CloudFormationResourceProviderPl name = "AWS::EC2::TransitGatewayAttachment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_transitgatewayattachment import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py index 3244a72b8b863..e1e374d936922 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py @@ -3,7 +3,7 @@ import logging from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,23 +17,23 @@ class EC2VPCProperties(TypedDict): - CidrBlock: Optional[str] - CidrBlockAssociations: Optional[list[str]] - DefaultNetworkAcl: Optional[str] - DefaultSecurityGroup: Optional[str] - EnableDnsHostnames: Optional[bool] - EnableDnsSupport: Optional[bool] - InstanceTenancy: Optional[str] - Ipv4IpamPoolId: Optional[str] - Ipv4NetmaskLength: Optional[int] - Ipv6CidrBlocks: Optional[list[str]] - Tags: Optional[list[Tag]] - VpcId: Optional[str] + CidrBlock: str | None + CidrBlockAssociations: list[str] | None + DefaultNetworkAcl: str | None + DefaultSecurityGroup: str | None + EnableDnsHostnames: bool | None + EnableDnsSupport: bool | None + InstanceTenancy: str | None + Ipv4IpamPoolId: str | None + Ipv4NetmaskLength: int | None + Ipv6CidrBlocks: list[str] | None + Tags: list[Tag] | None + VpcId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py index 3f4aea38386f0..786ddc69de131 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2VPCProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::VPC" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_vpc import EC2VPCProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py index 420efcb8029ee..6f5656e86728c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py @@ -1,8 +1,9 @@ # LocalStack Resource Provider Scaffolding v2 from __future__ import annotations +import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +15,18 @@ class EC2VPCEndpointProperties(TypedDict): - ServiceName: Optional[str] - VpcId: Optional[str] - CreationTimestamp: Optional[str] - DnsEntries: Optional[list[str]] - Id: Optional[str] - NetworkInterfaceIds: Optional[list[str]] - PolicyDocument: Optional[str | dict] - PrivateDnsEnabled: Optional[bool] - RouteTableIds: Optional[list[str]] - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] - VpcEndpointType: Optional[str] + ServiceName: str | None + VpcId: str | None + CreationTimestamp: str | None + DnsEntries: list[str] | None + Id: str | None + NetworkInterfaceIds: list[str] | None + PolicyDocument: str | dict | None + PrivateDnsEnabled: bool | None + RouteTableIds: list[str] | None + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None + VpcEndpointType: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -66,12 +67,14 @@ def create( """ model = request.desired_state + ec2 = request.aws_client_factory.ec2 + create_params = util.select_attributes( - model, - [ - "PolidyDocument", + model=model, + params=[ + "PolicyDocument", "PrivateDnsEnabled", - "RouteTablesIds", + "RouteTableIds", "SecurityGroupIds", "ServiceName", "SubnetIds", @@ -80,12 +83,18 @@ def create( ], ) + if policy := model.get("PolicyDocument"): + create_params["PolicyDocument"] = json.dumps(policy) + if not request.custom_context.get(REPEATED_INVOCATION): - response = request.aws_client_factory.ec2.create_vpc_endpoint(**create_params) + response = ec2.create_vpc_endpoint(**create_params) model["Id"] = response["VpcEndpoint"]["VpcEndpointId"] model["DnsEntries"] = response["VpcEndpoint"]["DnsEntries"] - model["CreationTimestamp"] = response["VpcEndpoint"]["CreationTimestamp"] + model["CreationTimestamp"] = response["VpcEndpoint"]["CreationTimestamp"].isoformat() model["NetworkInterfaceIds"] = response["VpcEndpoint"]["NetworkInterfaceIds"] + model["VpcEndpointType"] = model.get("VpcEndpointType") or "Gateway" + model["PrivateDnsEnabled"] = bool(model.get("VpcEndpointType", False)) + request.custom_context[REPEATED_INVOCATION] = True return ProgressEvent( status=OperationStatus.IN_PROGRESS, @@ -93,9 +102,7 @@ def create( custom_context=request.custom_context, ) - response = request.aws_client_factory.ec2.describe_vpc_endpoints( - VpcEndpointIds=[model["Id"]] - ) + response = ec2.describe_vpc_endpoints(VpcEndpointIds=[model["Id"]]) if not response["VpcEndpoints"]: return ProgressEvent( status=OperationStatus.FAILED, diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py index e0e1d228a95de..a865440caed43 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2VPCEndpointProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::VPCEndpoint" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_vpcendpoint import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py index 8f4656e317b7f..a770bb3071cd8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class EC2VPCGatewayAttachmentProperties(TypedDict): - VpcId: Optional[str] - Id: Optional[str] - InternetGatewayId: Optional[str] - VpnGatewayId: Optional[str] + VpcId: str | None + Id: str | None + InternetGatewayId: str | None + VpnGatewayId: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -50,13 +50,23 @@ def create( """ model = request.desired_state ec2 = request.aws_client_factory.ec2 - # TODO: validations - if model.get("InternetGatewayId"): - ec2.attach_internet_gateway( - InternetGatewayId=model["InternetGatewayId"], VpcId=model["VpcId"] + + if not model.get("InternetGatewayId") and not model.get("VpnGatewayId"): + return ProgressEvent( + status=OperationStatus.FAILED, + resource_model=model, + message="Either 'InternetGatewayId' or 'VpnGatewayId' is required but neither specified", ) + + vpc_id = model["VpcId"] + if ig_id := model.get("InternetGatewayId"): + model["Id"] = f"IGW|{vpc_id}" + ec2.attach_internet_gateway(InternetGatewayId=ig_id, VpcId=vpc_id) + elif vpn_id := model.get("VpnGatewayId"): + model["Id"] = f"VGW|{vpc_id}" + ec2.attach_vpn_gateway(VpnGatewayId=vpn_id, VpcId=vpc_id) else: - ec2.attach_vpn_gateway(VpnGatewayId=model["VpnGatewayId"], VpcId=model["VpcId"]) + raise RuntimeError("Unreachable due to validations above") # TODO: idempotency return ProgressEvent( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py index f210fa0ff8c1d..b6c84cab930c5 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2VPCGatewayAttachmentProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::EC2::VPCGatewayAttachment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_vpcgatewayattachment import ( diff --git a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py index a42735467d146..a94f089335e50 100644 --- a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py +++ b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py @@ -3,10 +3,9 @@ import logging from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util -from localstack.constants import AWS_REGION_US_EAST_1, DEFAULT_AWS_ACCOUNT_ID from localstack.services.cloudformation.resource_provider import ( OperationStatus, ProgressEvent, @@ -22,34 +21,34 @@ class ECRRepositoryProperties(TypedDict): - Arn: Optional[str] - EncryptionConfiguration: Optional[EncryptionConfiguration] - ImageScanningConfiguration: Optional[ImageScanningConfiguration] - ImageTagMutability: Optional[str] - LifecyclePolicy: Optional[LifecyclePolicy] - RepositoryName: Optional[str] - RepositoryPolicyText: Optional[dict | str] - RepositoryUri: Optional[str] - Tags: Optional[list[Tag]] + Arn: str | None + EncryptionConfiguration: EncryptionConfiguration | None + ImageScanningConfiguration: ImageScanningConfiguration | None + ImageTagMutability: str | None + LifecyclePolicy: LifecyclePolicy | None + RepositoryName: str | None + RepositoryPolicyText: dict | str | None + RepositoryUri: str | None + Tags: list[Tag] | None class LifecyclePolicy(TypedDict): - LifecyclePolicyText: Optional[str] - RegistryId: Optional[str] + LifecyclePolicyText: str | None + RegistryId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ImageScanningConfiguration(TypedDict): - ScanOnPush: Optional[bool] + ScanOnPush: bool | None class EncryptionConfiguration(TypedDict): - EncryptionType: Optional[str] - KmsKey: Optional[str] + EncryptionType: str | None + KmsKey: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -90,6 +89,10 @@ def create( """ model = request.desired_state + model["RepositoryName"] = ( + model.get("RepositoryName") + or util.generate_default_name(request.stack_name, request.logical_resource_id).lower() + ) default_repos_per_stack[request.stack_name] = model["RepositoryName"] LOG.warning( @@ -98,7 +101,7 @@ def create( model.update( { "Arn": arns.ecr_repository_arn( - model["RepositoryName"], DEFAULT_AWS_ACCOUNT_ID, AWS_REGION_US_EAST_1 + model["RepositoryName"], request.account_id, request.region_name ), "RepositoryUri": "http://localhost:4566", "ImageTagMutability": "MUTABLE", diff --git a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py index 7d7ba440a668d..5f00d944024a5 100644 --- a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py +++ b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ECRRepositoryProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ECR::Repository" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ecr.resource_providers.aws_ecr_repository import ( diff --git a/localstack-core/localstack/services/edge.py b/localstack-core/localstack/services/edge.py index 5c3ede66b65e5..90217f3c38df3 100644 --- a/localstack-core/localstack/services/edge.py +++ b/localstack-core/localstack/services/edge.py @@ -3,7 +3,7 @@ import shlex import subprocess import sys -from typing import List, Optional, TypeVar +from typing import TypeVar from localstack import config, constants from localstack.config import HostAndPort @@ -32,7 +32,7 @@ def do_start_edge( - listen: HostAndPort | List[HostAndPort], use_ssl: bool, asynchronous: bool = False + listen: HostAndPort | list[HostAndPort], use_ssl: bool, asynchronous: bool = False ): from localstack.aws.serving.edge import serve_gateway @@ -50,7 +50,7 @@ def can_use_sudo(): def ensure_can_use_sudo(): if not is_root() and not can_use_sudo(): if not sys.stdin.isatty(): - raise IOError("cannot get sudo password from non-tty input") + raise OSError("cannot get sudo password from non-tty input") print("Please enter your sudo password (required to configure local network):") run("sudo -v", stdin=True) @@ -72,7 +72,7 @@ def start_component( default_port=constants.DEFAULT_PORT_EDGE, ), ) - raise Exception("Unexpected component name '%s' received during start up" % component) + raise Exception(f"Unexpected component name '{component}' received during start up") def start_proxy( @@ -166,7 +166,7 @@ def start_edge(listen_str: str, use_ssl: bool = True, asynchronous: bool = False def run_module_as_sudo( - module: str, arguments: Optional[List[str]] = None, asynchronous=False, env_vars=None + module: str, arguments: list[str] | None = None, asynchronous=False, env_vars=None ): # prepare environment env_vars = env_vars or {} @@ -195,7 +195,7 @@ def run_command(*_): return result -def parse_gateway_listen(listen: str, default_host: str, default_port: int) -> List[HostAndPort]: +def parse_gateway_listen(listen: str, default_host: str, default_port: int) -> list[HostAndPort]: addresses = [] for address in listen.split(","): addresses.append(HostAndPort.parse(address, default_host, default_port)) diff --git a/localstack-core/localstack/services/es/provider.py b/localstack-core/localstack/services/es/provider.py index 4519e417bceaa..0490568228975 100644 --- a/localstack-core/localstack/services/es/provider.py +++ b/localstack-core/localstack/services/es/provider.py @@ -1,9 +1,8 @@ from contextlib import contextmanager -from typing import Dict, Optional, cast +from typing import cast from botocore.exceptions import ClientError -from localstack import constants from localstack.aws.api import RequestContext, handler from localstack.aws.api.es import ( ARN, @@ -68,11 +67,13 @@ VersionString, ) from localstack.aws.connect import connect_to +from localstack.services.opensearch.packages import ELASTICSEARCH_DEFAULT_VERSION +from localstack.state import StateVisitor def _version_to_opensearch( - version: Optional[ElasticsearchVersionString], -) -> Optional[VersionString]: + version: ElasticsearchVersionString | None, +) -> VersionString | None: if version is not None: if version.startswith("OpenSearch_"): return version @@ -81,8 +82,8 @@ def _version_to_opensearch( def _version_from_opensearch( - version: Optional[VersionString], -) -> Optional[ElasticsearchVersionString]: + version: VersionString | None, +) -> ElasticsearchVersionString | None: if version is not None: if version.startswith("Elasticsearch_"): return version.split("_")[1] @@ -90,19 +91,19 @@ def _version_from_opensearch( return version -def _instancetype_to_opensearch(instance_type: Optional[str]) -> Optional[str]: +def _instancetype_to_opensearch(instance_type: str | None) -> str | None: if instance_type is not None: return instance_type.replace("elasticsearch", "search") -def _instancetype_from_opensearch(instance_type: Optional[str]) -> Optional[str]: +def _instancetype_from_opensearch(instance_type: str | None) -> str | None: if instance_type is not None: return instance_type.replace("search", "elasticsearch") def _clusterconfig_from_opensearch( - cluster_config: Optional[ClusterConfig], -) -> Optional[ElasticsearchClusterConfig]: + cluster_config: ClusterConfig | None, +) -> ElasticsearchClusterConfig | None: if cluster_config is not None: # Just take the whole typed dict and typecast it to our target type result = cast(ElasticsearchClusterConfig, cluster_config) @@ -117,8 +118,8 @@ def _clusterconfig_from_opensearch( def _domainstatus_from_opensearch( - domain_status: Optional[DomainStatus], -) -> Optional[ElasticsearchDomainStatus]: + domain_status: DomainStatus | None, +) -> ElasticsearchDomainStatus | None: if domain_status is not None: # Just take the whole typed dict and typecast it to our target type result = cast(ElasticsearchDomainStatus, domain_status) @@ -135,8 +136,8 @@ def _domainstatus_from_opensearch( def _clusterconfig_to_opensearch( - elasticsearch_cluster_config: Optional[ElasticsearchClusterConfig], -) -> Optional[ClusterConfig]: + elasticsearch_cluster_config: ElasticsearchClusterConfig | None, +) -> ClusterConfig | None: if elasticsearch_cluster_config is not None: result = cast(ClusterConfig, elasticsearch_cluster_config) if instance_type := result.get("InstanceType"): @@ -149,8 +150,8 @@ def _clusterconfig_to_opensearch( def _domainconfig_from_opensearch( - domain_config: Optional[DomainConfig], -) -> Optional[ElasticsearchDomainConfig]: + domain_config: DomainConfig | None, +) -> ElasticsearchDomainConfig | None: if domain_config is not None: result = cast(ElasticsearchDomainConfig, domain_config) engine_version = domain_config.get("EngineVersion", {}) @@ -169,8 +170,8 @@ def _domainconfig_from_opensearch( def _compatible_version_list_from_opensearch( - compatible_version_list: Optional[CompatibleVersionsList], -) -> Optional[CompatibleElasticsearchVersionsList]: + compatible_version_list: CompatibleVersionsList | None, +) -> CompatibleElasticsearchVersionsList | None: if compatible_version_list is not None: return [ CompatibleVersionsMap( @@ -208,6 +209,11 @@ def exception_mapper(): class EsProvider(EsApi): + def accept_state_visitor(self, visitor: StateVisitor): + # ES state entirely depends on `opensearch`, and delegates its entire state to it + # we do not need to manage state in ES + pass + def create_elasticsearch_domain( self, context: RequestContext, @@ -236,7 +242,7 @@ def create_elasticsearch_domain( engine_version = ( _version_to_opensearch(elasticsearch_version) if elasticsearch_version - else constants.ELASTICSEARCH_DEFAULT_VERSION + else ELASTICSEARCH_DEFAULT_VERSION ) kwargs = { "DomainName": domain_name, @@ -304,7 +310,7 @@ def update_elasticsearch_domain_config( region_name=context.region, aws_access_key_id=context.account_id ).opensearch - payload: Dict + payload: dict if "ElasticsearchClusterConfig" in payload: payload["ClusterConfig"] = payload["ElasticsearchClusterConfig"] payload["ClusterConfig"]["InstanceType"] = _instancetype_to_opensearch( @@ -347,7 +353,7 @@ def list_domain_names( with exception_mapper(): domain_names = opensearch_client.list_domain_names(**kwargs)["DomainNames"] - return ListDomainNamesResponse(DomainNames=cast(Optional[DomainInfoList], domain_names)) + return ListDomainNamesResponse(DomainNames=cast(DomainInfoList | None, domain_names)) def list_elasticsearch_versions( self, diff --git a/localstack-core/localstack/services/events/archive.py b/localstack-core/localstack/services/events/archive.py index 12d7e4601747f..60cb7f6906eec 100644 --- a/localstack-core/localstack/services/events/archive.py +++ b/localstack-core/localstack/services/events/archive.py @@ -1,6 +1,6 @@ import json import logging -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Self from botocore.client import BaseClient @@ -60,9 +60,9 @@ def create_archive_service( region: str, account_id: str, event_source_arn: Arn, - description: ArchiveDescription, - event_pattern: EventPattern, - retention_days: RetentionDays, + description: ArchiveDescription | None, + event_pattern: EventPattern | None, + retention_days: RetentionDays | None, ) -> Self: return cls( Archive( @@ -95,7 +95,7 @@ def set_state(self, state: ArchiveState) -> None: self.archive.state = state def set_creation_time(self) -> None: - self.archive.creation_time = datetime.now(timezone.utc) + self.archive.creation_time = datetime.now(UTC) def update( self, diff --git a/localstack-core/localstack/services/events/connection.py b/localstack-core/localstack/services/events/connection.py index c2b72a2025328..709393d626968 100644 --- a/localstack-core/localstack/services/events/connection.py +++ b/localstack-core/localstack/services/events/connection.py @@ -2,7 +2,7 @@ import logging import re import uuid -from datetime import datetime, timezone +from datetime import UTC, datetime from localstack.aws.api.events import ( Arn, @@ -138,7 +138,7 @@ def update( auth_parameters, ) self.connection.secret_arn = secret_arn - self.connection.last_authorized_time = datetime.now(timezone.utc) + self.connection.last_authorized_time = datetime.now(UTC) # Set new values self.connection.authorization_type = auth_type @@ -149,7 +149,7 @@ def update( ) self.connection.auth_parameters = public_auth_parameters self.set_state(ConnectionState.AUTHORIZED) - self.connection.last_modified_time = datetime.now(timezone.utc) + self.connection.last_modified_time = datetime.now(UTC) except Exception as error: LOG.warning( @@ -162,7 +162,7 @@ def delete(self) -> None: self.set_state(ConnectionState.DELETING) self.delete_connection_secret(self.connection.secret_arn) self.set_state(ConnectionState.DELETING) # required for AWS parity - self.connection.last_modified_time = datetime.now(timezone.utc) + self.connection.last_modified_time = datetime.now(UTC) def create_connection_secret( self, @@ -204,7 +204,7 @@ def update_connection_secret( try: secretsmanager_client.update_secret(SecretId=secret_arn, SecretString=secret_value) self.set_state(ConnectionState.AUTHORIZED) - self.connection.last_authorized_time = datetime.now(timezone.utc) + self.connection.last_authorized_time = datetime.now(UTC) except Exception as error: LOG.warning("Secret with id %s updating failed with errors: %s.", secret_arn, error) diff --git a/localstack-core/localstack/services/events/event_bus.py b/localstack-core/localstack/services/events/event_bus.py index 1ea6f332a493b..4d86279387aa0 100644 --- a/localstack-core/localstack/services/events/event_bus.py +++ b/localstack-core/localstack/services/events/event_bus.py @@ -1,6 +1,6 @@ import json -from datetime import datetime, timezone -from typing import Optional, Self +from datetime import UTC, datetime +from typing import Any, Self from localstack.aws.api.events import ( Action, @@ -12,7 +12,7 @@ StatementId, TagList, ) -from localstack.services.events.models import EventBus, ResourcePolicy, RuleDict, Statement +from localstack.services.events.models import EventBus, ResourcePolicy, RuleDict from localstack.utils.aws.arns import get_partition @@ -34,11 +34,11 @@ def create_event_bus_service( name: EventBusName, region: str, account_id: str, - event_source_name: Optional[str] = None, - description: Optional[str] = None, - tags: Optional[TagList] = None, - policy: Optional[str] = None, - rules: Optional[RuleDict] = None, + event_source_name: str | None = None, + description: str | None = None, + tags: TagList | None = None, + policy: str | None = None, + rules: RuleDict | None = None, ) -> Self: return cls( EventBus( @@ -68,7 +68,7 @@ def put_permission( # TODO: cover via test # if policy and any([action, principal, statement_id, condition]): # raise ValueError("Combination of policy with other arguments is not allowed") - self.event_bus.last_modified_time = datetime.now(timezone.utc) + self.event_bus.last_modified_time = datetime.now(UTC) if policy: # policy document replaces all existing permissions policy = json.loads(policy) parsed_policy = ResourcePolicy(**policy) @@ -102,7 +102,7 @@ def revoke_put_events_permission(self, statement_id: str): for statement in policy["Statement"] if statement.get("Sid") != statement_id ] - self.event_bus.last_modified_time = datetime.now(timezone.utc) + self.event_bus.last_modified_time = datetime.now(UTC) def _parse_statement( self, @@ -111,21 +111,20 @@ def _parse_statement( principal: Principal, resource_arn: Arn, condition: Condition, - ) -> Statement: + ) -> dict[str, Any]: # TODO: cover via test # if condition and principal != "*": # raise ValueError("Condition can only be set when principal is '*'") if principal != "*": principal = {"AWS": f"arn:{get_partition(self.event_bus.region)}:iam::{principal}:root"} - statement = Statement( - Sid=statement_id, - Effect="Allow", - Principal=principal, - Action=action, - Resource=resource_arn, - Condition=condition, - ) - return statement + return { + "Sid": statement_id, + "Effect": "Allow", + "Principal": principal, + "Action": action, + "Resource": resource_arn, + "Condition": condition, + } EventBusServiceDict = dict[Arn, EventBusService] diff --git a/localstack-core/localstack/services/events/event_rule_engine.py b/localstack-core/localstack/services/events/event_rule_engine.py index a1af9a9cdb339..0014e933d76f6 100644 --- a/localstack-core/localstack/services/events/event_rule_engine.py +++ b/localstack-core/localstack/services/events/event_rule_engine.py @@ -59,14 +59,21 @@ def _evaluate_nested_event_pattern_on_dict(self, event_pattern, payload: dict) - for flat_pattern in flat_pattern_conditions ) - def _evaluate_condition(self, value, condition, field_exists: bool): + def _evaluate_condition(self, value: t.Any, condition: t.Any, field_exists: bool) -> bool: if not isinstance(condition, dict): return field_exists and value == condition + elif (must_exist := condition.get("exists")) is not None: # if must_exists is True then field_exists must be True # if must_exists is False then fields_exists must be False return must_exist == field_exists + elif (anything_but := condition.get("anything-but")) is not None: + if not field_exists: + # anything-but can handle None `value`, but it needs to differentiate between user-set `null` and + # missing value + return False + if isinstance(anything_but, dict): if (not_condition := anything_but.get("prefix")) is not None: predicate = self._evaluate_prefix @@ -95,6 +102,7 @@ def _evaluate_condition(self, value, condition, field_exists: bool): elif value is None: # the remaining conditions require the value to not be None return False + elif (prefix := condition.get("prefix")) is not None: if isinstance(prefix, dict): if (prefix_equal_ignore_case := prefix.get("equals-ignore-case")) is not None: @@ -104,7 +112,7 @@ def _evaluate_condition(self, value, condition, field_exists: bool): elif (suffix := condition.get("suffix")) is not None: if isinstance(suffix, dict): - if suffix_equal_ignore_case := suffix.get("equals-ignore-case"): + if (suffix_equal_ignore_case := suffix.get("equals-ignore-case")) is not None: return self._evaluate_suffix(suffix_equal_ignore_case.lower(), value.lower()) else: return self._evaluate_suffix(suffix, value) @@ -126,19 +134,19 @@ def _evaluate_condition(self, value, condition, field_exists: bool): return False @staticmethod - def _evaluate_prefix(condition: str | list, value: str) -> bool: - return value.startswith(condition) + def _evaluate_prefix(condition: str | list, value: t.Any) -> bool: + return isinstance(value, str) and value.startswith(condition) @staticmethod - def _evaluate_suffix(condition: str | list, value: str) -> bool: - return value.endswith(condition) + def _evaluate_suffix(condition: str | list, value: t.Any) -> bool: + return isinstance(value, str) and value.endswith(condition) @staticmethod - def _evaluate_equal_ignore_case(condition: str, value: str) -> bool: - return condition.lower() == value.lower() + def _evaluate_equal_ignore_case(condition: str, value: t.Any) -> bool: + return isinstance(value, str) and condition.lower() == value.lower() @staticmethod - def _evaluate_cidr(condition: str, value: str) -> bool: + def _evaluate_cidr(condition: str, value: t.Any) -> bool: try: ip = ipaddress.ip_address(value) return ip in ipaddress.ip_network(condition) @@ -146,8 +154,10 @@ def _evaluate_cidr(condition: str, value: str) -> bool: return False @staticmethod - def _evaluate_wildcard(condition: str, value: str) -> bool: - return bool(re.match(re.escape(condition).replace("\\*", ".+") + "$", value)) + def _evaluate_wildcard(condition: str, value: t.Any) -> bool: + return isinstance(value, str) and bool( + re.match(re.escape(condition).replace("\\*", ".+") + "$", value) + ) @staticmethod def _evaluate_numeric_condition(conditions: list, value: t.Any) -> bool: @@ -457,10 +467,18 @@ def _validate_rule(self, rule: t.Any, from_: str | None = None) -> None: return elif operator == "anything-but": - # anything-but can actually contain any kind of simple rule (str, number, and list) + # anything-but can actually contain any kind of simple rule (str, number, and list) except Null + if value is None: + raise InvalidEventPatternException( + f"{self.error_prefix}Value of anything-but must be an array or single string/number value." + ) if isinstance(value, list): for v in value: - self._validate_rule(v) + if v is None: + raise InvalidEventPatternException( + f"{self.error_prefix}Inside anything but list, start|null|boolean is not supported." + ) + self._validate_rule(v, from_="anything-but") return diff --git a/localstack-core/localstack/services/events/models.py b/localstack-core/localstack/services/events/models.py index 95e64ece83711..dfff8f2d34177 100644 --- a/localstack-core/localstack/services/events/models.py +++ b/localstack-core/localstack/services/events/models.py @@ -1,10 +1,10 @@ import uuid from dataclasses import dataclass, field -from datetime import datetime, timezone +from datetime import UTC, datetime from enum import Enum -from typing import Literal, Optional, TypeAlias, TypedDict +from typing import Any, TypedDict -from localstack.aws.api.core import ServiceException +from localstack.aws.api import CommonServiceException from localstack.aws.api.events import ( ApiDestinationDescription, ApiDestinationHttpMethod, @@ -27,7 +27,6 @@ EventPattern, EventResourceList, EventSourceName, - EventTime, HttpsEndpoint, ManagedBy, ReplayDescription, @@ -66,10 +65,9 @@ TargetDict = dict[TargetId, Target] -class ValidationException(ServiceException): - code: str = "ValidationException" - sender_fault: bool = True - status_code: int = 400 +class ValidationException(CommonServiceException): + def __init__(self, message: str): + super().__init__("ValidationException", message, 400, True) class InvalidEventPatternException(Exception): @@ -85,14 +83,14 @@ def __init__(self, reason=None, message=None) -> None: { "version": str, "id": str, - "detail-type": Optional[str], - "source": Optional[EventSourceName], + "detail-type": str | None, + "source": EventSourceName | None, "account": str, - "time": EventTime, + "time": str, "region": str, - "resources": Optional[EventResourceList], - "detail": dict[str, str | dict], - "replay-name": Optional[ReplayName], + "resources": EventResourceList | None, + "detail": dict[str, Any], + "replay-name": ReplayName | None, "event-bus-name": EventBusName, }, ) @@ -101,7 +99,7 @@ def __init__(self, reason=None, message=None) -> None: FormattedEventDict = dict[str, FormattedEvent] FormattedEventList = list[FormattedEvent] -TransformedEvent: TypeAlias = FormattedEvent | dict | str +type TransformedEvent = FormattedEvent | dict | str class ResourceType(Enum): @@ -109,24 +107,9 @@ class ResourceType(Enum): RULE = "rule" -class Condition(TypedDict): - Type: Literal["StringEquals"] - Key: Literal["aws:PrincipalOrgID"] - Value: str - - -class Statement(TypedDict): - Sid: str - Effect: str - Principal: str | dict[str, str] - Action: str - Resource: str - Condition: Condition - - class ResourcePolicy(TypedDict): Version: str - Statement: list[Statement] + Statement: list[dict[str, Any]] @dataclass @@ -134,15 +117,15 @@ class Rule: name: RuleName region: str account_id: str - schedule_expression: Optional[ScheduleExpression] = None - event_pattern: Optional[EventPattern] = None - state: Optional[RuleState] = None - description: Optional[RuleDescription] = None - role_arn: Optional[RoleArn] = None + schedule_expression: ScheduleExpression | None = None + event_pattern: EventPattern | None = None + state: RuleState | None = None + description: RuleDescription | None = None + role_arn: RoleArn | None = None tags: TagList = field(default_factory=list) event_bus_name: EventBusName = "default" targets: TargetDict = field(default_factory=dict) - managed_by: Optional[ManagedBy] = None # can only be set by AWS services + managed_by: ManagedBy | None = None # can only be set by AWS services created_by: CreatedBy = field(init=False) def __post_init__(self): @@ -171,12 +154,12 @@ class Replay: destination: ReplayDestination # Event Bus Arn or Rule Arns event_start_time: Timestamp event_end_time: Timestamp - description: Optional[ReplayDescription] = None - state: Optional[ReplayState] = None - state_reason: Optional[ReplayStateReason] = None - event_last_replayed_time: Optional[Timestamp] = None - replay_start_time: Optional[Timestamp] = None - replay_end_time: Optional[Timestamp] = None + description: ReplayDescription | None = None + state: ReplayState | None = None + state_reason: ReplayStateReason | None = None + event_last_replayed_time: Timestamp | None = None + replay_start_time: Timestamp | None = None + replay_end_time: Timestamp | None = None @property def arn(self) -> Arn: @@ -192,13 +175,14 @@ class Archive: region: str account_id: str event_source_arn: Arn - description: ArchiveDescription = None - event_pattern: EventPattern = None - retention_days: RetentionDays = None - state: ArchiveState = ArchiveState.DISABLED - creation_time: Timestamp = None - size_bytes: int = 0 # TODO how to deal with updating this value? - events: FormattedEventDict = field(default_factory=dict) + description: ArchiveDescription | None = None + event_pattern: EventPattern | None = None + retention_days: RetentionDays | None = None + state: ArchiveState = field(init=False, default=ArchiveState.DISABLED) + creation_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) + events: FormattedEventDict = field(init=False, default_factory=dict) + # TODO how to deal with updating this value? + size_bytes: int = field(init=False, default=0) @property def arn(self) -> Arn: @@ -217,17 +201,15 @@ class EventBus: name: EventBusName region: str account_id: str - event_source_name: Optional[str] = None - description: Optional[str] = None + event_source_name: str | None = None + description: str | None = None tags: TagList = field(default_factory=list) - policy: Optional[ResourcePolicy] = None + policy: ResourcePolicy | None = None rules: RuleDict = field(default_factory=dict) - creation_time: Timestamp = field(init=False) - last_modified_time: Timestamp = field(init=False) + creation_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) + last_modified_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) def __post_init__(self): - self.creation_time = datetime.now(timezone.utc) - self.last_modified_time = datetime.now(timezone.utc) if self.rules is None: self.rules = {} if self.tags is None: @@ -252,17 +234,13 @@ class Connection: secret_arn: Arn description: ConnectionDescription | None = None invocation_connectivity_parameters: ConnectivityResourceParameters | None = None - creation_time: Timestamp = field(init=False) - last_modified_time: Timestamp = field(init=False) - last_authorized_time: Timestamp = field(init=False) + creation_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) + last_modified_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) + last_authorized_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) tags: TagList = field(default_factory=list) id: str = str(uuid.uuid4()) def __post_init__(self): - timestamp_now = datetime.now(timezone.utc) - self.creation_time = timestamp_now - self.last_modified_time = timestamp_now - self.last_authorized_time = timestamp_now if self.tags is None: self.tags = [] @@ -285,17 +263,13 @@ class ApiDestination: state: ApiDestinationState _invocation_rate_limit_per_second: ApiDestinationInvocationRateLimitPerSecond | None = None description: ApiDestinationDescription | None = None - creation_time: Timestamp = field(init=False) - last_modified_time: Timestamp = field(init=False) - last_authorized_time: Timestamp = field(init=False) + creation_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) + last_modified_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) + last_authorized_time: Timestamp = field(init=False, default_factory=lambda: datetime.now(UTC)) tags: TagList = field(default_factory=list) id: str = str(short_uid()) def __post_init__(self): - timestamp_now = datetime.now(timezone.utc) - self.creation_time = timestamp_now - self.last_modified_time = timestamp_now - self.last_authorized_time = timestamp_now if self.tags is None: self.tags = [] diff --git a/localstack-core/localstack/services/events/provider.py b/localstack-core/localstack/services/events/provider.py index 91e95b5100374..de561563f9ae9 100644 --- a/localstack-core/localstack/services/events/provider.py +++ b/localstack-core/localstack/services/events/provider.py @@ -2,7 +2,7 @@ import json import logging import re -from typing import Callable, Optional +from collections.abc import Callable from localstack.aws.api import RequestContext, handler from localstack.aws.api.config import TagsList @@ -65,6 +65,7 @@ ListRulesResponse, ListTagsForResourceResponse, ListTargetsByRuleResponse, + LogConfig, NextToken, NonPartnerEventBusName, Principal, @@ -167,9 +168,11 @@ recursive_remove_none_values_from_dict, ) from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.common import truncate from localstack.utils.event_matcher import matches_event from localstack.utils.strings import long_uid +from localstack.utils.threads import start_worker_thread from localstack.utils.time import TIMESTAMP_FORMAT_TZ, timestamp from localstack.utils.xray.trace_header import TraceHeader @@ -192,7 +195,7 @@ def encode_next_token(token: int) -> NextToken: def get_filtered_dict(name_prefix: str, input_dict: dict) -> dict: """Filter dictionary by prefix.""" - return {name: value for name, value in input_dict.items() if name.startswith(name_prefix)} + return {name: value for name, value in dict(input_dict).items() if name.startswith(name_prefix)} def validate_event(event: PutEventsRequestEntry) -> None | PutEventsResultEntry: @@ -245,6 +248,9 @@ def __init__(self): self._connection_service_store: ConnectionServiceDict = {} self._api_destination_service_store: ApiDestinationServiceDict = {} + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(events_stores) + def on_before_start(self): JobScheduler.start() @@ -523,11 +529,12 @@ def create_event_bus( self, context: RequestContext, name: EventBusName, - event_source_name: EventSourceName = None, - description: EventBusDescription = None, - kms_key_identifier: KmsKeyIdentifier = None, - dead_letter_config: DeadLetterConfig = None, - tags: TagList = None, + event_source_name: EventSourceName | None = None, + description: EventBusDescription | None = None, + kms_key_identifier: KmsKeyIdentifier | None = None, + dead_letter_config: DeadLetterConfig | None = None, + log_config: LogConfig | None = None, + tags: TagList | None = None, **kwargs, ) -> CreateEventBusResponse: region = context.region @@ -766,8 +773,8 @@ def list_rule_names_by_target( # Find all rules that have a target with the specified ARN matching_rule_names = [] - for rule_name, rule in event_bus.rules.items(): - for target_id, target in rule.targets.items(): + for rule_name, rule in dict(event_bus.rules).items(): + for target_id, target in dict(rule.targets).items(): if target["Arn"] == target_arn: matching_rule_names.append(rule_name) break # Found a match in this rule, no need to check other targets @@ -1026,7 +1033,7 @@ def list_archives( self._check_event_bus_exists(event_source_arn, store) archives = { key: archive - for key, archive in store.archives.items() + for key, archive in dict(store.archives).items() if archive.event_source_arn == event_source_arn } elif name_prefix: @@ -1159,7 +1166,7 @@ def list_replays( if event_source_arn: replays = { key: replay - for key, replay in store.replays.items() + for key, replay in dict(store.replays).items() if replay.event_source_arn == event_source_arn } elif name_prefix: @@ -1347,9 +1354,9 @@ def create_event_bus_service( name: EventBusName, region: str, account_id: str, - event_source_name: Optional[EventSourceName], - description: Optional[EventBusDescription], - tags: Optional[TagList], + event_source_name: EventSourceName | None, + description: EventBusDescription | None, + tags: TagList | None, ) -> EventBusService: event_bus_service = EventBusService.create_event_bus_service( name, @@ -1367,14 +1374,14 @@ def create_rule_service( name: RuleName, region: str, account_id: str, - schedule_expression: Optional[ScheduleExpression], - event_pattern: Optional[EventPattern], - state: Optional[RuleState], - description: Optional[RuleDescription], - role_arn: Optional[RoleArn], - tags: Optional[TagList], - event_bus_name: Optional[EventBusName], - targets: Optional[TargetDict], + schedule_expression: ScheduleExpression | None, + event_pattern: EventPattern | None, + state: RuleState | None, + description: RuleDescription | None, + role_arn: RoleArn | None, + tags: TagList | None, + event_bus_name: EventBusName | None, + targets: TargetDict | None, ) -> RuleService: rule_service = RuleService.create_rule_service( name, @@ -1506,7 +1513,7 @@ def _delete_rule_services(self, rules: RuleDict | Rule) -> None: """ if isinstance(rules, Rule): rules = {rules.name: rules} - for rule in rules.values(): + for rule in list(rules.values()): del self._rule_services_store[rule.arn] def _delete_target_sender(self, ids: TargetIdList, rule) -> None: @@ -1569,7 +1576,7 @@ def _check_resource_exists( def _get_scheduled_rule_job_function(self, account_id, region, rule: Rule) -> Callable: def func(*args, **kwargs): """Create custom scheduled event and send it to all targets specified by associated rule using respective TargetSender""" - for target in rule.targets.values(): + for target in list(rule.targets.values()): if custom_input := target.get("Input"): event = json.loads(custom_input) else: @@ -1634,7 +1641,8 @@ def _event_bust_dict_to_event_bus_response_list( ) -> EventBusList: """Return a converted dict of EventBus model objects as a list of event buses in API type EventBus format.""" event_bus_list = [ - self._event_bus_to_api_type_event_bus(event_bus) for event_bus in event_buses.values() + self._event_bus_to_api_type_event_bus(event_bus) + for event_bus in list(event_buses.values()) ] return event_bus_list @@ -1678,7 +1686,7 @@ def _event_to_error_type_event(self, entry: PutEventsRequestEntry) -> str: def _rule_dict_to_rule_response_list(self, rules: RuleDict) -> RuleResponseList: """Return a converted dict of Rule model objects as a list of rules in API type Rule format.""" - rule_list = [self._rule_to_api_type_rule(rule) for rule in rules.values()] + rule_list = [self._rule_to_api_type_rule(rule) for rule in list(rules.values())] return rule_list def _rule_to_api_type_rule(self, rule: Rule) -> ApiTypeRule: @@ -1698,7 +1706,9 @@ def _rule_to_api_type_rule(self, rule: Rule) -> ApiTypeRule: def _archive_dict_to_archive_response_list(self, archives: ArchiveDict) -> ArchiveResponseList: """Return a converted dict of Archive model objects as a list of archives in API type Archive format.""" - archive_list = [self._archive_to_api_type_archive(archive) for archive in archives.values()] + archive_list = [ + self._archive_to_api_type_archive(archive) for archive in list(archives.values()) + ] return archive_list def _archive_to_api_type_archive(self, archive: Archive) -> ApiTypeArchive: @@ -1732,7 +1742,7 @@ def _archive_to_describe_archive_response(self, archive: Archive) -> DescribeArc def _replay_dict_to_replay_response_list(self, replays: ReplayDict) -> ReplayList: """Return a converted dict of Replay model objects as a list of replays in API type Replay format.""" - replay_list = [self._replay_to_api_type_replay(replay) for replay in replays.values()] + replay_list = [self._replay_to_api_type_replay(replay) for replay in list(replays.values())] return replay_list def _replay_to_api_type_replay(self, replay: Replay) -> ApiTypeReplay: @@ -1787,7 +1797,7 @@ def _connection_dict_to_connection_response_list( """Return a converted dict of Connection model objects as a list of connections in API type Connection format.""" connection_list = [ self._connection_to_api_type_connection(connection) - for connection in connections.values() + for connection in list(connections.values()) ] return connection_list @@ -1814,7 +1824,7 @@ def _api_destination_dict_to_api_destination_response_list( """Return a converted dict of ApiDestination model objects as a list of connections in API type ApiDestination format.""" api_destination_list = [ self._api_destination_to_api_type_api_destination(api_destination) - for api_destination in api_destinations.values() + for api_destination in list(api_destinations.values()) ] return api_destination_list @@ -1894,13 +1904,19 @@ def _process_entry( # Always add the successful EventId entry, even if target processing might fail processed_entries.append({"EventId": event_formatted["id"]}) + # Process rules asynchronously to match AWS behavior where put-events returns immediately if configured_rules := list(event_bus.rules.values()): - for rule in configured_rules: - if rule.schedule_expression: - # we do not want to execute Scheduled Rules on PutEvents - continue - - self._process_rules(rule, region, account_id, event_formatted, trace_header) + start_worker_thread( + self._process_rules_async, + params={ + "rules": configured_rules, + "region": region, + "account_id": account_id, + "event_formatted": event_formatted, + "trace_header": trace_header, + }, + name="events-process-rules", + ) else: LOG.info( json.dumps( @@ -1917,6 +1933,27 @@ def _proxy_capture_input_event( # only required for EventStudio to capture input event if no rule is configured pass + def _process_rules_async(self, params: dict) -> None: + """Process rules asynchronously in a background thread. + + TODO: Use a worker pool (similar to SNS) instead of spawning a new thread + for each request to improve performance and resource management. + """ + rules = params["rules"] + region = params["region"] + account_id = params["account_id"] + event_formatted = params["event_formatted"] + trace_header = params["trace_header"] + + for rule in rules: + if rule.schedule_expression: + # we do not want to execute Scheduled Rules on PutEvents + continue + + # TODO: Process each rule asynchronously instead of sequentially to further + # improve performance when multiple rules need to be evaluated. + self._process_rules(rule, region, account_id, event_formatted, trace_header) + def _process_rules( self, rule: Rule, @@ -1940,7 +1977,7 @@ def _process_rules( ) return - for target in rule.targets.values(): + for target in list(rule.targets.values()): target_id = target["Id"] if is_archive_arn(target["Arn"]): self._put_to_archive( @@ -1974,7 +2011,7 @@ def _process_rules( ) ) else: - LOG.info( + LOG.debug( json.dumps( { "InfoCode": "InternalInfoEvents at matches_rule", diff --git a/localstack-core/localstack/services/events/replay.py b/localstack-core/localstack/services/events/replay.py index 7a58fb3534d05..6b4579453890b 100644 --- a/localstack-core/localstack/services/events/replay.py +++ b/localstack-core/localstack/services/events/replay.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from localstack.aws.api.events import ( Arn, @@ -61,13 +61,13 @@ def set_state(self, state: ReplayState) -> None: def start(self, events: FormattedEventList | None) -> None: self.set_state(ReplayState.RUNNING) - self.replay.replay_start_time = datetime.now(timezone.utc) + self.replay.replay_start_time = datetime.now(UTC) if events: self._set_event_last_replayed_time(events) def finish(self) -> None: self.set_state(ReplayState.COMPLETED) - self.replay.replay_end_time = datetime.now(timezone.utc) + self.replay.replay_end_time = datetime.now(UTC) def stop(self) -> None: self.set_state(ReplayState.CANCELLING) diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py index 372d45de40dce..7d62bcc0e4be1 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,13 +14,13 @@ class EventsApiDestinationProperties(TypedDict): - ConnectionArn: Optional[str] - HttpMethod: Optional[str] - InvocationEndpoint: Optional[str] - Arn: Optional[str] - Description: Optional[str] - InvocationRateLimitPerSecond: Optional[int] - Name: Optional[str] + ConnectionArn: str | None + HttpMethod: str | None + InvocationEndpoint: str | None + Arn: str | None + Description: str | None + InvocationRateLimitPerSecond: int | None + Name: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py index 0aa7ada08cc50..5e9c09f9e4b31 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsApiDestinationProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::ApiDestination" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_apidestination import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py b/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py index a99f8df743aca..2802a0b17239c 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,53 +14,53 @@ class EventsConnectionProperties(TypedDict): - AuthParameters: Optional[AuthParameters] - AuthorizationType: Optional[str] - Arn: Optional[str] - Description: Optional[str] - Name: Optional[str] - SecretArn: Optional[str] + AuthParameters: AuthParameters | None + AuthorizationType: str | None + Arn: str | None + Description: str | None + Name: str | None + SecretArn: str | None class ApiKeyAuthParameters(TypedDict): - ApiKeyName: Optional[str] - ApiKeyValue: Optional[str] + ApiKeyName: str | None + ApiKeyValue: str | None class BasicAuthParameters(TypedDict): - Password: Optional[str] - Username: Optional[str] + Password: str | None + Username: str | None class ClientParameters(TypedDict): - ClientID: Optional[str] - ClientSecret: Optional[str] + ClientID: str | None + ClientSecret: str | None class Parameter(TypedDict): - Key: Optional[str] - Value: Optional[str] - IsValueSecret: Optional[bool] + Key: str | None + Value: str | None + IsValueSecret: bool | None class ConnectionHttpParameters(TypedDict): - BodyParameters: Optional[list[Parameter]] - HeaderParameters: Optional[list[Parameter]] - QueryStringParameters: Optional[list[Parameter]] + BodyParameters: list[Parameter] | None + HeaderParameters: list[Parameter] | None + QueryStringParameters: list[Parameter] | None class OAuthParameters(TypedDict): - AuthorizationEndpoint: Optional[str] - ClientParameters: Optional[ClientParameters] - HttpMethod: Optional[str] - OAuthHttpParameters: Optional[ConnectionHttpParameters] + AuthorizationEndpoint: str | None + ClientParameters: ClientParameters | None + HttpMethod: str | None + OAuthHttpParameters: ConnectionHttpParameters | None class AuthParameters(TypedDict): - ApiKeyAuthParameters: Optional[ApiKeyAuthParameters] - BasicAuthParameters: Optional[BasicAuthParameters] - InvocationHttpParameters: Optional[ConnectionHttpParameters] - OAuthParameters: Optional[OAuthParameters] + ApiKeyAuthParameters: ApiKeyAuthParameters | None + BasicAuthParameters: BasicAuthParameters | None + InvocationHttpParameters: ConnectionHttpParameters | None + OAuthParameters: OAuthParameters | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py index c8b16c6c961a1..652ac6d0a861e 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsConnectionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::Connection" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_connection import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py index 5929d42f7252b..74f0e654e9bf4 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class EventsEventBusProperties(TypedDict): - Name: Optional[str] - Arn: Optional[str] - EventSourceName: Optional[str] - Id: Optional[str] - Policy: Optional[str] - Tags: Optional[list[TagEntry]] + Name: str | None + Arn: str | None + EventSourceName: str | None + Id: str | None + Policy: str | None + Tags: list[TagEntry] | None class TagEntry(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py index 25f94f1940bb2..0be6346557600 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsEventBusProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::EventBus" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_eventbus import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py index 9da54ceeff6bf..891809580b2e8 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -18,19 +18,19 @@ class EventsEventBusPolicyProperties(TypedDict): - StatementId: Optional[str] - Action: Optional[str] - Condition: Optional[Condition] - EventBusName: Optional[str] - Id: Optional[str] - Principal: Optional[str] - Statement: Optional[dict] + StatementId: str | None + Action: str | None + Condition: Condition | None + EventBusName: str | None + Id: str | None + Principal: str | None + Statement: dict | None class Condition(TypedDict): - Key: Optional[str] - Type: Optional[str] - Value: Optional[str] + Key: str | None + Type: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py index 5368348690773..e835ffb41f488 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsEventBusPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::EventBusPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_eventbuspolicy import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py b/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py index a10d23360a41c..6bca937fc47ac 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,153 +16,153 @@ class EventsRuleProperties(TypedDict): - Arn: Optional[str] - Description: Optional[str] - EventBusName: Optional[str] - EventPattern: Optional[dict] - Id: Optional[str] - Name: Optional[str] - RoleArn: Optional[str] - ScheduleExpression: Optional[str] - State: Optional[str] - Targets: Optional[list[Target]] + Arn: str | None + Description: str | None + EventBusName: str | None + EventPattern: dict | None + Id: str | None + Name: str | None + RoleArn: str | None + ScheduleExpression: str | None + State: str | None + Targets: list[Target] | None class HttpParameters(TypedDict): - HeaderParameters: Optional[dict] - PathParameterValues: Optional[list[str]] - QueryStringParameters: Optional[dict] + HeaderParameters: dict | None + PathParameterValues: list[str] | None + QueryStringParameters: dict | None class DeadLetterConfig(TypedDict): - Arn: Optional[str] + Arn: str | None class RunCommandTarget(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class RunCommandParameters(TypedDict): - RunCommandTargets: Optional[list[RunCommandTarget]] + RunCommandTargets: list[RunCommandTarget] | None class InputTransformer(TypedDict): - InputTemplate: Optional[str] - InputPathsMap: Optional[dict] + InputTemplate: str | None + InputPathsMap: dict | None class KinesisParameters(TypedDict): - PartitionKeyPath: Optional[str] + PartitionKeyPath: str | None class RedshiftDataParameters(TypedDict): - Database: Optional[str] - Sql: Optional[str] - DbUser: Optional[str] - SecretManagerArn: Optional[str] - StatementName: Optional[str] - WithEvent: Optional[bool] + Database: str | None + Sql: str | None + DbUser: str | None + SecretManagerArn: str | None + StatementName: str | None + WithEvent: bool | None class SqsParameters(TypedDict): - MessageGroupId: Optional[str] + MessageGroupId: str | None class PlacementConstraint(TypedDict): - Expression: Optional[str] - Type: Optional[str] + Expression: str | None + Type: str | None class PlacementStrategy(TypedDict): - Field: Optional[str] - Type: Optional[str] + Field: str | None + Type: str | None class CapacityProviderStrategyItem(TypedDict): - CapacityProvider: Optional[str] - Base: Optional[int] - Weight: Optional[int] + CapacityProvider: str | None + Base: int | None + Weight: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class AwsVpcConfiguration(TypedDict): - Subnets: Optional[list[str]] - AssignPublicIp: Optional[str] - SecurityGroups: Optional[list[str]] + Subnets: list[str] | None + AssignPublicIp: str | None + SecurityGroups: list[str] | None class NetworkConfiguration(TypedDict): - AwsVpcConfiguration: Optional[AwsVpcConfiguration] + AwsVpcConfiguration: AwsVpcConfiguration | None class EcsParameters(TypedDict): - TaskDefinitionArn: Optional[str] - CapacityProviderStrategy: Optional[list[CapacityProviderStrategyItem]] - EnableECSManagedTags: Optional[bool] - EnableExecuteCommand: Optional[bool] - Group: Optional[str] - LaunchType: Optional[str] - NetworkConfiguration: Optional[NetworkConfiguration] - PlacementConstraints: Optional[list[PlacementConstraint]] - PlacementStrategies: Optional[list[PlacementStrategy]] - PlatformVersion: Optional[str] - PropagateTags: Optional[str] - ReferenceId: Optional[str] - TagList: Optional[list[Tag]] - TaskCount: Optional[int] + TaskDefinitionArn: str | None + CapacityProviderStrategy: list[CapacityProviderStrategyItem] | None + EnableECSManagedTags: bool | None + EnableExecuteCommand: bool | None + Group: str | None + LaunchType: str | None + NetworkConfiguration: NetworkConfiguration | None + PlacementConstraints: list[PlacementConstraint] | None + PlacementStrategies: list[PlacementStrategy] | None + PlatformVersion: str | None + PropagateTags: str | None + ReferenceId: str | None + TagList: list[Tag] | None + TaskCount: int | None class BatchRetryStrategy(TypedDict): - Attempts: Optional[int] + Attempts: int | None class BatchArrayProperties(TypedDict): - Size: Optional[int] + Size: int | None class BatchParameters(TypedDict): - JobDefinition: Optional[str] - JobName: Optional[str] - ArrayProperties: Optional[BatchArrayProperties] - RetryStrategy: Optional[BatchRetryStrategy] + JobDefinition: str | None + JobName: str | None + ArrayProperties: BatchArrayProperties | None + RetryStrategy: BatchRetryStrategy | None class SageMakerPipelineParameter(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class SageMakerPipelineParameters(TypedDict): - PipelineParameterList: Optional[list[SageMakerPipelineParameter]] + PipelineParameterList: list[SageMakerPipelineParameter] | None class RetryPolicy(TypedDict): - MaximumEventAgeInSeconds: Optional[int] - MaximumRetryAttempts: Optional[int] + MaximumEventAgeInSeconds: int | None + MaximumRetryAttempts: int | None class Target(TypedDict): - Arn: Optional[str] - Id: Optional[str] - BatchParameters: Optional[BatchParameters] - DeadLetterConfig: Optional[DeadLetterConfig] - EcsParameters: Optional[EcsParameters] - HttpParameters: Optional[HttpParameters] - Input: Optional[str] - InputPath: Optional[str] - InputTransformer: Optional[InputTransformer] - KinesisParameters: Optional[KinesisParameters] - RedshiftDataParameters: Optional[RedshiftDataParameters] - RetryPolicy: Optional[RetryPolicy] - RoleArn: Optional[str] - RunCommandParameters: Optional[RunCommandParameters] - SageMakerPipelineParameters: Optional[SageMakerPipelineParameters] - SqsParameters: Optional[SqsParameters] + Arn: str | None + Id: str | None + BatchParameters: BatchParameters | None + DeadLetterConfig: DeadLetterConfig | None + EcsParameters: EcsParameters | None + HttpParameters: HttpParameters | None + Input: str | None + InputPath: str | None + InputTransformer: InputTransformer | None + KinesisParameters: KinesisParameters | None + RedshiftDataParameters: RedshiftDataParameters | None + RetryPolicy: RetryPolicy | None + RoleArn: str | None + RunCommandParameters: RunCommandParameters | None + SageMakerPipelineParameters: SageMakerPipelineParameters | None + SqsParameters: SqsParameters | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py index 3fa01b6717fdc..68fd6bd27831c 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsRuleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::Rule" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_rule import EventsRuleProvider diff --git a/localstack-core/localstack/services/events/rule.py b/localstack-core/localstack/services/events/rule.py index 576cfc36e781c..c43cbee079527 100644 --- a/localstack-core/localstack/services/events/rule.py +++ b/localstack-core/localstack/services/events/rule.py @@ -1,5 +1,5 @@ import re -from typing import Callable, Optional +from collections.abc import Callable from localstack.aws.api.events import ( Arn, @@ -24,11 +24,11 @@ TARGET_ID_REGEX = re.compile(r"^[\.\-_A-Za-z0-9]+$") TARGET_ARN_REGEX = re.compile(r"arn:[\d\w:\-/]*") -CRON_REGEX = ( # borrowed from https://regex101.com/r/I80Eu0/1 - r"^(?:cron[(](?:(?:(?:[0-5]?[0-9])|[*])(?:(?:[-](?:(?:[0-5]?[0-9])|[*]))|(?:[/][0-9]+))?" - r"(?:[,](?:(?:[0-5]?[0-9])|[*])(?:(?:[-](?:(?:[0-5]?[0-9])|[*]))|(?:[/][0-9]+))?)*)[ ]+" - r"(?:(?:(?:[0-2]?[0-9])|[*])(?:(?:[-](?:(?:[0-2]?[0-9])|[*]))|(?:[/][0-9]+))?" - r"(?:[,](?:(?:[0-2]?[0-9])|[*])(?:(?:[-](?:(?:[0-2]?[0-9])|[*]))|(?:[/][0-9]+))?)*)[ ]+" +CRON_REGEX = ( # based on https://regex101.com/r/I80Eu0/1, fixed to allow range/step combined (e.g. 0-15/5) + r"^(?:cron[(](?:(?:(?:[0-5]?[0-9])|[*])(?:[-](?:(?:[0-5]?[0-9])|[*]))?(?:[/][0-9]+)?" # minutes + r"(?:[,](?:(?:[0-5]?[0-9])|[*])(?:[-](?:(?:[0-5]?[0-9])|[*]))?(?:[/][0-9]+)?)*)[ ]+" + r"(?:(?:(?:[0-2]?[0-9])|[*])(?:[-](?:(?:[0-2]?[0-9])|[*]))?(?:[/][0-9]+)?" # hours + r"(?:[,](?:(?:[0-2]?[0-9])|[*])(?:[-](?:(?:[0-2]?[0-9])|[*]))?(?:[/][0-9]+)?)*)[ ]+" r"(?:(?:[?][ ]+(?:(?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])" r"(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|" r"(?:[/][0-9]+))?(?:[,](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])" @@ -75,17 +75,17 @@ def __init__(self, rule: Rule): def create_rule_service( cls, name: RuleName, - region: Optional[str] = None, - account_id: Optional[str] = None, - schedule_expression: Optional[ScheduleExpression] = None, - event_pattern: Optional[EventPattern] = None, - state: Optional[RuleState] = None, - description: Optional[RuleDescription] = None, - role_arn: Optional[RoleArn] = None, - tags: Optional[TagList] = None, - event_bus_name: Optional[EventBusName] = None, - targets: Optional[TargetDict] = None, - managed_by: Optional[ManagedBy] = None, + region: str | None = None, + account_id: str | None = None, + schedule_expression: ScheduleExpression | None = None, + event_pattern: EventPattern | None = None, + state: RuleState | None = None, + description: RuleDescription | None = None, + role_arn: RoleArn | None = None, + tags: TagList | None = None, + event_bus_name: EventBusName | None = None, + targets: TargetDict | None = None, + managed_by: ManagedBy | None = None, ): cls._validate_input(event_pattern, schedule_expression, event_bus_name) # required to keep data and functionality separate for persistence @@ -209,9 +209,9 @@ def validate_targets_input(self, targets: TargetList) -> PutTargetsResultEntryLi @classmethod def _validate_input( cls, - event_pattern: Optional[EventPattern], - schedule_expression: Optional[ScheduleExpression], - event_bus_name: Optional[EventBusName] = "default", + event_pattern: EventPattern | None, + schedule_expression: ScheduleExpression | None, + event_bus_name: EventBusName | None = "default", ) -> None: if not event_pattern and not schedule_expression: raise ValidationException( diff --git a/localstack-core/localstack/services/events/target.py b/localstack-core/localstack/services/events/target.py index fe18ce999412c..70b2ad7d3428a 100644 --- a/localstack-core/localstack/services/events/target.py +++ b/localstack-core/localstack/services/events/target.py @@ -4,7 +4,7 @@ import re import uuid from abc import ABC, abstractmethod -from typing import Any, Dict, Set, Type +from typing import Any from urllib.parse import urlencode import requests @@ -59,7 +59,7 @@ } AWS_PREDEFINED_PLACEHOLDERS_JSON_VALUES = {"aws.events.event", "aws.events.event.json"} -PREDEFINED_PLACEHOLDERS: Set[str] = AWS_PREDEFINED_PLACEHOLDERS_STRING_VALUES.union( +PREDEFINED_PLACEHOLDERS: set[str] = AWS_PREDEFINED_PLACEHOLDERS_STRING_VALUES.union( AWS_PREDEFINED_PLACEHOLDERS_JSON_VALUES ) @@ -90,11 +90,20 @@ def get_template_replacements( return template_replacements -def replace_template_placeholders( - template: str, replacements: dict[str, Any], is_json_template: bool -) -> TransformedEvent: - """Replace placeholders defined by in the template with the values from the replacements dict. - Can handle single template string or template dict.""" +def replace_template_placeholders(template: str, replacements: dict[str, Any]) -> TransformedEvent: + """ + Replaces placeholders in an EventBridge-style InputTemplate string. + + :param template: The template string containing placeholders like ``<$.foo.bar>``. + :type template: str + :param replacements: A dictionary providing values to fill in. + :type replacements: dict + :returns: The transformed string with placeholders replaced by values from ``replacements``. + :rtype: str + """ + + ... + is_json_template = template.strip().startswith("{") def replace_placeholder(match): key = match.group(1) @@ -110,6 +119,8 @@ def replace_placeholder(match): if is_json_template: return json.dumps(value) return f"[{','.join(value)}]" + if isinstance(value, bool): + return json.dumps(value) if is_nested_in_string(template, match): return value if is_json_template: @@ -222,10 +233,7 @@ def transform_event_with_target_input_transformer( predefined_template_replacements = self._get_predefined_template_replacements(event) template_replacements.update(predefined_template_replacements) - is_json_template = input_template.strip().startswith(("{")) - populated_template = replace_template_placeholders( - input_template, template_replacements, is_json_template - ) + populated_template = replace_template_placeholders(input_template, template_replacements) return populated_template @@ -424,7 +432,7 @@ def _validate_input(self, target: Target): # if not collections.get_safe(target, "$.RoleArn"): # raise ValueError("RoleArn is required for ApiGateway target") - def _get_predefined_template_replacements(self, event: Dict[str, Any]) -> Dict[str, Any]: + def _get_predefined_template_replacements(self, event: dict[str, Any]) -> dict[str, Any]: """Extracts predefined values from the event.""" predefined_template_replacements = {} predefined_template_replacements["aws.events.rule-arn"] = self.rule_arn @@ -731,7 +739,7 @@ def __init__( self.account_id = account_id @classmethod - def register_target_sender(cls, service_name: str, sender_class: Type[TargetSender]): + def register_target_sender(cls, service_name: str, sender_class: type[TargetSender]): cls.target_map[service_name] = sender_class def get_target_sender(self) -> TargetSender: diff --git a/localstack-core/localstack/services/events/utils.py b/localstack-core/localstack/services/events/utils.py index 5ac8e835b136f..a162dc51b101a 100644 --- a/localstack-core/localstack/services/events/utils.py +++ b/localstack-core/localstack/services/events/utils.py @@ -1,8 +1,8 @@ import json import logging import re -from datetime import datetime, timezone -from typing import Any, Dict, Optional +from datetime import UTC, datetime +from typing import Any from botocore.utils import ArnParser @@ -57,7 +57,7 @@ def default(self, obj): return super().default(obj) -def to_json_str(obj: Any, separators: Optional[tuple[str, str]] = (",", ":")) -> str: +def to_json_str(obj: Any, separators: tuple[str, str] | None = (",", ":")) -> str: return json.dumps(obj, cls=EventJSONEncoder, separators=separators) @@ -132,11 +132,11 @@ def get_resource_type(arn: Arn) -> ResourceType: def get_event_time(event: PutEventsRequestEntry) -> EventTime: - event_time = datetime.now(timezone.utc) + event_time = datetime.now(UTC) if event_timestamp := event.get("Time"): try: # use time from event if provided - event_time = event_timestamp.replace(tzinfo=timezone.utc) + event_time = event_timestamp.replace(tzinfo=UTC) except ValueError: # use current time if event time is invalid LOG.debug( @@ -154,11 +154,11 @@ def convert_to_timezone_aware_datetime( timestamp: Timestamp, ) -> Timestamp: if timestamp.tzinfo is None: - timestamp = timestamp.replace(tzinfo=timezone.utc) + timestamp = timestamp.replace(tzinfo=UTC) return timestamp -def recursive_remove_none_values_from_dict(d: Dict[str, Any]) -> Dict[str, Any]: +def recursive_remove_none_values_from_dict(d: dict[str, Any]) -> dict[str, Any]: """ Recursively removes keys with non values from a dictionary. """ diff --git a/localstack-core/localstack/services/events/v1/models.py b/localstack-core/localstack/services/events/v1/models.py index 4096215c82499..edf99a8a35537 100644 --- a/localstack-core/localstack/services/events/v1/models.py +++ b/localstack-core/localstack/services/events/v1/models.py @@ -1,11 +1,9 @@ -from typing import Dict - from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute class EventsStore(BaseStore): # maps rule name to job_id - rule_scheduled_jobs: Dict[str, str] = LocalAttribute(default=dict) + rule_scheduled_jobs: dict[str, str] = LocalAttribute(default=dict) events_stores = AccountRegionBundle("events", EventsStore) diff --git a/localstack-core/localstack/services/events/v1/provider.py b/localstack-core/localstack/services/events/v1/provider.py index 9e3da8e447f6a..15716399b47c1 100644 --- a/localstack-core/localstack/services/events/v1/provider.py +++ b/localstack-core/localstack/services/events/v1/provider.py @@ -4,7 +4,7 @@ import os import re import time -from typing import Any, Dict, Optional +from typing import Any from moto.events import events_backends from moto.events.responses import EventsHandler as MotoEventsHandler @@ -45,6 +45,7 @@ from localstack.services.events.v1.models import EventsStore, events_stores from localstack.services.moto import call_moto from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.aws.arns import event_bus_arn, parse_arn from localstack.utils.aws.client_types import ServicePrincipal from localstack.utils.aws.message_forwarding import send_event_to_target @@ -83,6 +84,14 @@ def on_before_start(self): def on_before_stop(self): JobScheduler.shutdown() + def accept_state_visitor(self, visitor: StateVisitor): + from moto.events.models import events_backends + + from localstack.services.events.v1.models import events_stores + + visitor.visit(events_backends) + visitor.visit(events_stores) + @route("/_aws/events/rules//trigger") def trigger_scheduled_rule(self, request: Request, rule_arn: str): """Developer endpoint to trigger a scheduled rule.""" @@ -119,7 +128,7 @@ def test_event_pattern( def get_scheduled_rule_func( store: EventsStore, rule_name: RuleName, - event_bus_name_or_arn: Optional[EventBusNameOrArn] = None, + event_bus_name_or_arn: EventBusNameOrArn | None = None, ): def func(*args, **kwargs): account_id = store._account_id @@ -206,21 +215,21 @@ def convert_schedule_to_cron(schedule): raise ValueError("If the value is greater than 1, the unit must be plural") if "minute" in unit: - return "*/%s * * * *" % value + return f"*/{value} * * * *" if "hour" in unit: - return "0 */%s * * *" % value + return f"0 */{value} * * *" if "day" in unit: - return "0 0 */%s * *" % value - raise ValueError("Unable to parse events schedule expression: %s" % schedule) + return f"0 0 */{value} * *" + raise ValueError(f"Unable to parse events schedule expression: {schedule}") return schedule @staticmethod def put_rule_job_scheduler( store: EventsStore, - name: Optional[RuleName], - state: Optional[RuleState], - schedule_expression: Optional[ScheduleExpression], - event_bus_name_or_arn: Optional[EventBusNameOrArn] = None, + name: RuleName | None, + state: RuleState | None, + schedule_expression: ScheduleExpression | None, + event_bus_name_or_arn: EventBusNameOrArn | None = None, ): if not schedule_expression: return @@ -374,7 +383,7 @@ def _dump_events_to_files(events_with_added_uuid): for event in events_with_added_uuid: target = os.path.join( _get_events_tmp_dir(), - "%s_%s" % (current_time_millis, event["uuid"]), + "{}_{}".format(current_time_millis, event["uuid"]), ) save_file(target, json.dumps(event["event"])) except Exception as e: @@ -398,14 +407,14 @@ def filter_event_based_on_event_format( return True -def filter_event_with_target_input_path(target: Dict, event: Dict) -> Dict: +def filter_event_with_target_input_path(target: dict, event: dict) -> dict: input_path = target.get("InputPath") if input_path: event = extract_jsonpath(event, input_path) return event -def process_event_with_input_transformer(input_transformer: Dict, event: Dict) -> Dict: +def process_event_with_input_transformer(input_transformer: dict, event: dict) -> dict: """ Process the event with the input transformer of the target event, by replacing the message with the populated InputTemplate. @@ -426,7 +435,7 @@ def process_event_with_input_transformer(input_transformer: Dict, event: Dict) - return templated_event -def process_events(event: Dict, targets: list[Dict]): +def process_events(event: dict, targets: list[dict]): for target in targets: arn = target["Arn"] changed_event = filter_event_with_target_input_path(target, event) @@ -453,7 +462,7 @@ def process_events(event: Dict, targets: list[Dict]): ) -def get_event_bus_name(event_bus_name_or_arn: Optional[EventBusNameOrArn] = None) -> str: +def get_event_bus_name(event_bus_name_or_arn: EventBusNameOrArn | None = None) -> str: event_bus_name_or_arn = event_bus_name_or_arn or DEFAULT_EVENT_BUS_NAME return event_bus_name_or_arn.split("/")[-1] diff --git a/localstack-core/localstack/services/firehose/models.py b/localstack-core/localstack/services/firehose/models.py index ef2e395ef9229..77d05b8a607ac 100644 --- a/localstack-core/localstack/services/firehose/models.py +++ b/localstack-core/localstack/services/firehose/models.py @@ -1,21 +1,18 @@ -from typing import Dict - from localstack.aws.api.firehose import DeliveryStreamDescription from localstack.services.stores import ( AccountRegionBundle, BaseStore, - CrossRegionAttribute, LocalAttribute, ) -from localstack.utils.tagging import TaggingService +from localstack.utils.tagging import Tags class FirehoseStore(BaseStore): # maps delivery stream names to DeliveryStreamDescription - delivery_streams: Dict[str, DeliveryStreamDescription] = LocalAttribute(default=dict) + delivery_streams: dict[str, DeliveryStreamDescription] = LocalAttribute(default=dict) - # static tagging service instance - TAGS = CrossRegionAttribute(default=TaggingService) + # resource tags + tags: Tags = LocalAttribute(default=Tags) firehose_stores = AccountRegionBundle("firehose", FirehoseStore) diff --git a/localstack-core/localstack/services/firehose/provider.py b/localstack-core/localstack/services/firehose/provider.py index 18142ae80d88b..a38c8e447c9ce 100644 --- a/localstack-core/localstack/services/firehose/provider.py +++ b/localstack-core/localstack/services/firehose/provider.py @@ -8,7 +8,6 @@ import time import uuid from datetime import datetime -from typing import Dict, List from urllib.parse import urlparse import requests @@ -53,7 +52,6 @@ ListDeliveryStreamsOutput, ListTagsForDeliveryStreamInputLimit, ListTagsForDeliveryStreamOutput, - ListTagsForDeliveryStreamOutputTagList, MSKSourceConfiguration, PutRecordBatchOutput, PutRecordBatchRequestEntryList, @@ -95,6 +93,7 @@ convert_source_config_to_desc, ) from localstack.services.firehose.models import FirehoseStore, firehose_stores +from localstack.state import StateVisitor from localstack.utils.aws.arns import ( extract_account_id_from_arn, extract_region_from_arn, @@ -118,6 +117,7 @@ from localstack.utils.kinesis import kinesis_connector from localstack.utils.kinesis.kinesis_connector import KinesisProcessorThread from localstack.utils.run import run_for_max_seconds +from localstack.utils.tagging import tag_list_to_map, tag_map_to_list LOG = logging.getLogger(__name__) @@ -252,8 +252,12 @@ class FirehoseProvider(FirehoseApi): def __init__(self) -> None: super().__init__() + # TODO: stop/restart the kinesis listeners when stopping the service / reset the state / restore the state self.kinesis_listeners = {} + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(firehose_stores) + @staticmethod def get_store(account_id: str, region_name: str) -> FirehoseStore: return firehose_stores[account_id][region_name] @@ -404,7 +408,9 @@ def _startup(): run_for_max_seconds(25, _startup) - store.TAGS.tag_resource(delivery_stream_arn, tags) + if tags: + tag_map = tag_list_to_map(tags) + store.tags.update_tags(delivery_stream_arn, tag_map) store.delivery_streams[delivery_stream_name] = stream return CreateDeliveryStreamOutput(DeliveryStreamARN=stream["DeliveryStreamARN"]) @@ -432,6 +438,8 @@ def delete_delivery_stream( LOG.debug("Stopping kinesis listener for %s", delivery_stream_name) kinesis_process.stop() + store.tags.delete_all_tags(delivery_stream_arn) + return DeleteDeliveryStreamOutput() def describe_delivery_stream( @@ -504,7 +512,8 @@ def tag_delivery_stream( delivery_stream_description = _get_description_or_raise_not_found( context, delivery_stream_name ) - store.TAGS.tag_resource(delivery_stream_description["DeliveryStreamARN"], tags) + tag_map = tag_list_to_map(tags) + store.tags.update_tags(delivery_stream_description["DeliveryStreamARN"], tag_map) return ListTagsForDeliveryStreamOutput() def list_tags_for_delivery_stream( @@ -519,12 +528,8 @@ def list_tags_for_delivery_stream( delivery_stream_description = _get_description_or_raise_not_found( context, delivery_stream_name ) - # The tagging service returns a dictionary with the given root name - tags = store.TAGS.list_tags_for_resource( - arn=delivery_stream_description["DeliveryStreamARN"], root_name="root" - ) - # Extract the actual list of tags for the typed response - tag_list: ListTagsForDeliveryStreamOutputTagList = tags["root"] + tag_map = store.tags.get_tags(delivery_stream_description["DeliveryStreamARN"]) + tag_list = tag_map_to_list(tag_map) return ListTagsForDeliveryStreamOutput(Tags=tag_list, HasMoreTags=False) def untag_delivery_stream( @@ -538,10 +543,7 @@ def untag_delivery_stream( delivery_stream_description = _get_description_or_raise_not_found( context, delivery_stream_name ) - # The tagging service returns a dictionary with the given root name - store.TAGS.untag_resource( - arn=delivery_stream_description["DeliveryStreamARN"], tag_names=tag_keys - ) + store.tags.delete_tags(delivery_stream_description["DeliveryStreamARN"], tag_keys) return UntagDeliveryStreamOutput() def update_destination( @@ -609,7 +611,7 @@ def _reencode_record(self, record: Record) -> Record: record["Data"] = base64.b64encode(record["Data"]) return record - def _reencode_records(self, records: List[Record]) -> List[Record]: + def _reencode_records(self, records: list[Record]) -> list[Record]: return [self._reencode_record(r) for r in records] def _process_records( @@ -617,7 +619,7 @@ def _process_records( account_id: str, region_name: str, fh_d_stream: str, - records: List[Record], + records: list[Record], ): """Process the given records from the underlying Kinesis stream""" return self._put_records(account_id, region_name, fh_d_stream, records) @@ -638,8 +640,8 @@ def _put_records( account_id: str, region_name: str, delivery_stream_name: str, - unprocessed_records: List[Record], - ) -> List[PutRecordBatchResponseEntry]: + unprocessed_records: list[Record], + ) -> list[PutRecordBatchResponseEntry]: """Put a list of records to the firehose stream - either directly from a PutRecord API call, or received from an underlying Kinesis stream (if 'KinesisStreamAsSource' is configured)""" store = self.get_store(account_id, region_name) @@ -707,7 +709,11 @@ def _put_records( try: requests.post(url, json=record_to_send, headers=headers) except Exception as e: - LOG.exception("Unable to put Firehose records to HTTP endpoint %s.", url) + LOG.error( + "Unable to put Firehose records to HTTP endpoint %s.", + url, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) raise e if "RedshiftDestinationDescription" in destination: s3_dest_desc = destination["RedshiftDestinationDescription"][ @@ -783,10 +789,14 @@ def _put_to_search_db( try: db_connection.create(index=search_db_index, id=obj_id, body=body) except Exception as e: - LOG.exception("Unable to put record to stream %s.", delivery_stream_name) + LOG.error( + "Unable to put record to stream %s.", + delivery_stream_name, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) raise e - def _add_missing_record_attributes(self, records: List[Dict]) -> None: + def _add_missing_record_attributes(self, records: list[dict]) -> None: def _get_entry(obj, key): return obj.get(key) or obj.get(first_char_to_lower(key)) @@ -806,7 +816,7 @@ def _get_entry(obj, key): "subsequenceNumber": "", } - def _preprocess_records(self, processor: Dict, records: List[Record]) -> List[Dict]: + def _preprocess_records(self, processor: dict, records: list[Record]) -> list[dict]: """Preprocess the list of records by calling the given processor (e.g., Lamnda function).""" proc_type = processor.get("Type") parameters = processor.get("Parameters", []) @@ -838,7 +848,7 @@ def _preprocess_records(self, processor: Dict, records: List[Record]) -> List[Di def _put_records_to_s3_bucket( self, stream_name: str, - records: List[Dict], + records: list[dict], s3_destination_description: S3DestinationDescription, ): bucket = s3_bucket_name(s3_destination_description["BucketARN"]) @@ -861,9 +871,10 @@ def _put_records_to_s3_bucket( LOG.debug("Publishing to S3 destination: %s. Data: %s", bucket, batched_data) s3.put_object(Bucket=bucket, Key=obj_path, Body=batched_data) except Exception as e: - LOG.exception( + LOG.error( "Unable to put records %s to s3 bucket.", records, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) raise e @@ -884,7 +895,7 @@ def _get_s3_object_path(self, stream_name, prefix, file_extension): def _put_to_redshift( self, - records: List[Dict], + records: list[dict], redshift_destination_description: RedshiftDestinationDescription, ): jdbcurl = redshift_destination_description.get("ClusterJDBCURL") @@ -918,9 +929,10 @@ def _put_to_redshift( ) redshift_data.execute_statement(Parameters=row_to_insert, **execute_statement) except Exception as e: - LOG.exception( + LOG.error( "Unable to put records %s to redshift cluster.", row_to_insert, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) raise e @@ -940,13 +952,13 @@ def _get_region_from_jdbc_url(self, jdbc_url: str) -> str | None: LOG.debug("Cannot extract region from JDBC url '%s'", jdbc_url) return None - def _decode_record(self, record: Dict) -> Dict: + def _decode_record(self, record: dict) -> dict: data = base64.b64decode(record.get("Data") or record.get("data")) data = to_str(data) data = json.loads(data) return data - def _prepare_records_for_redshift(self, record: Dict) -> List[Dict]: + def _prepare_records_for_redshift(self, record: dict) -> list[dict]: data = self._decode_record(record) parameters = [] @@ -963,7 +975,7 @@ def _prepare_records_for_redshift(self, record: Dict) -> List[Dict]: return parameters - def _extract_columns(self, record: Dict) -> str: + def _extract_columns(self, record: dict) -> str: data = self._decode_record(record) placeholders = [f":{key}" for key in data] placeholder_str = ", ".join(placeholders) diff --git a/localstack-core/localstack/services/iam/iam_patches.py b/localstack-core/localstack/services/iam/iam_patches.py index bec31419c3c8f..65e7df8c20a8b 100644 --- a/localstack-core/localstack/services/iam/iam_patches.py +++ b/localstack-core/localstack/services/iam/iam_patches.py @@ -1,5 +1,4 @@ import threading -from typing import Dict, List, Optional from moto.iam.models import ( AccessKey, @@ -106,11 +105,11 @@ def iam_backend_create_role( role_name: str, assume_role_policy_document: str, path: str, - permissions_boundary: Optional[str], + permissions_boundary: str | None, description: str, - tags: List[Dict[str, str]], - max_session_duration: Optional[str], - linked_service: Optional[str] = None, + tags: list[dict[str, str]], + max_session_duration: str | None, + linked_service: str | None = None, ): role = fn( self, @@ -143,7 +142,7 @@ def inline_policy_unapply_policy(fn, self, backend): def access_key__init__( fn, self, - user_name: Optional[str], + user_name: str | None, prefix: str, account_id: str, status: str = "Active", diff --git a/localstack-core/localstack/services/iam/provider.py b/localstack-core/localstack/services/iam/provider.py index d5e1af505867d..e62b43f503894 100644 --- a/localstack-core/localstack/services/iam/provider.py +++ b/localstack-core/localstack/services/iam/provider.py @@ -6,7 +6,7 @@ import string import uuid from datetime import datetime -from typing import Any, Dict, List, TypeVar +from typing import Any, TypeVar from urllib.parse import quote from moto.iam.models import ( @@ -20,10 +20,7 @@ from localstack.aws.api import CommonServiceException, RequestContext, handler from localstack.aws.api.iam import ( - ActionNameListType, - ActionNameType, AttachedPermissionsBoundary, - ContextEntryListType, CreateRoleRequest, CreateRoleResponse, CreateServiceLinkedRoleResponse, @@ -33,7 +30,6 @@ DeleteServiceLinkedRoleResponse, DeletionTaskIdType, DeletionTaskStatusType, - EvaluationResult, GetServiceLinkedRoleDeletionStatusResponse, GetUserResponse, IamApi, @@ -43,16 +39,12 @@ ListServiceSpecificCredentialsResponse, MalformedPolicyDocumentException, NoSuchEntityException, - PolicyEvaluationDecisionType, ResetServiceSpecificCredentialResponse, - ResourceHandlingOptionType, - ResourceNameListType, - ResourceNameType, Role, ServiceSpecificCredential, ServiceSpecificCredentialMetadata, SimulatePolicyResponse, - SimulationPolicyListType, + SimulatePrincipalPolicyRequest, User, allUsers, arnType, @@ -65,7 +57,6 @@ maxItemsType, pathPrefixType, pathType, - policyDocumentType, roleDescriptionType, roleNameType, serviceName, @@ -78,8 +69,13 @@ from localstack.aws.connect import connect_to from localstack.constants import INTERNAL_AWS_SECRET_ACCESS_KEY from localstack.services.iam.iam_patches import apply_iam_patches +from localstack.services.iam.resources.policy_simulator import ( + BasicIAMPolicySimulator, + IAMPolicySimulator, +) from localstack.services.iam.resources.service_linked_roles import SERVICE_LINKED_ROLES from localstack.services.moto import call_moto +from localstack.state import StateVisitor from localstack.utils.aws.request_context import extract_access_key_id_from_auth_header LOG = logging.getLogger(__name__) @@ -108,58 +104,15 @@ def get_iam_backend(context: RequestContext) -> IAMBackend: return iam_backends[context.account_id][context.partition] -def get_policies_from_principal(backend: IAMBackend, principal_arn: str) -> list[dict]: - policies = [] - if ":role" in principal_arn: - role_name = principal_arn.split("/")[-1] - - policies.append(backend.get_role(role_name=role_name).assume_role_policy_document) - - policy_names = backend.list_role_policies(role_name=role_name) - policies.extend( - [ - backend.get_role_policy(role_name=role_name, policy_name=policy_name)[1] - for policy_name in policy_names - ] - ) - - attached_policies, _ = backend.list_attached_role_policies(role_name=role_name) - policies.extend([policy.document for policy in attached_policies]) - - if ":group" in principal_arn: - print(principal_arn) - group_name = principal_arn.split("/")[-1] - policy_names = backend.list_group_policies(group_name=group_name) - policies.extend( - [ - backend.get_group_policy(group_name=group_name, policy_name=policy_name)[1] - for policy_name in policy_names - ] - ) - - attached_policies, _ = backend.list_attached_group_policies(group_name=group_name) - policies.extend([policy.document for policy in attached_policies]) - - if ":user" in principal_arn: - print(principal_arn) - user_name = principal_arn.split("/")[-1] - policy_names = backend.list_user_policies(user_name=user_name) - policies.extend( - [ - backend.get_user_policy(user_name=user_name, policy_name=policy_name)[1] - for policy_name in policy_names - ] - ) - - attached_policies, _ = backend.list_attached_user_policies(user_name=user_name) - policies.extend([policy.document for policy in attached_policies]) - - return policies - - class IamProvider(IamApi): + policy_simulator: IAMPolicySimulator + def __init__(self): apply_iam_patches() + self.policy_simulator = BasicIAMPolicySimulator() + + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(iam_backends) @handler("CreateRole", expand=False) def create_role( @@ -181,75 +134,21 @@ def create_role( return result - @staticmethod - def build_evaluation_result( - action_name: ActionNameType, resource_name: ResourceNameType, policy_statements: List[Dict] - ) -> EvaluationResult: - eval_res = EvaluationResult() - eval_res["EvalActionName"] = action_name - eval_res["EvalResourceName"] = resource_name - eval_res["EvalDecision"] = PolicyEvaluationDecisionType.explicitDeny - for statement in policy_statements: - # TODO Implement evaluation logic here - if ( - action_name in statement["Action"] - and resource_name in statement["Resource"] - and statement["Effect"] == "Allow" - ): - eval_res["EvalDecision"] = PolicyEvaluationDecisionType.allowed - eval_res["MatchedStatements"] = [] # TODO: add support for statement compilation. - return eval_res - + @handler("SimulatePrincipalPolicy", expand=False) def simulate_principal_policy( self, context: RequestContext, - policy_source_arn: arnType, - action_names: ActionNameListType, - policy_input_list: SimulationPolicyListType = None, - permissions_boundary_policy_input_list: SimulationPolicyListType = None, - resource_arns: ResourceNameListType = None, - resource_policy: policyDocumentType = None, - resource_owner: ResourceNameType = None, - caller_arn: ResourceNameType = None, - context_entries: ContextEntryListType = None, - resource_handling_option: ResourceHandlingOptionType = None, - max_items: maxItemsType = None, - marker: markerType = None, + request: SimulatePrincipalPolicyRequest, **kwargs, ) -> SimulatePolicyResponse: - backend = get_iam_backend(context) - - policies = get_policies_from_principal(backend, policy_source_arn) - - def _get_statements_from_policy_list(policies: list[str]): - statements = [] - for policy_str in policies: - policy_dict = json.loads(policy_str) - if isinstance(policy_dict["Statement"], list): - statements.extend(policy_dict["Statement"]) - else: - statements.append(policy_dict["Statement"]) - return statements - - policy_statements = _get_statements_from_policy_list(policies) - - evaluations = [ - self.build_evaluation_result(action_name, resource_arn, policy_statements) - for action_name in action_names - for resource_arn in resource_arns - ] - - response = SimulatePolicyResponse() - response["IsTruncated"] = False - response["EvaluationResults"] = evaluations - return response + return self.policy_simulator.simulate_principal_policy(context, request) def delete_policy(self, context: RequestContext, policy_arn: arnType, **kwargs) -> None: backend = get_iam_backend(context) if backend.managed_policies.get(policy_arn): backend.managed_policies.pop(policy_arn, None) else: - raise NoSuchEntityException("Policy {0} was not found.".format(policy_arn)) + raise NoSuchEntityException(f"Policy {policy_arn} was not found.") def detach_role_policy( self, context: RequestContext, role_name: roleNameType, policy_arn: arnType, **kwargs @@ -260,7 +159,7 @@ def detach_role_policy( policy = role.managed_policies[policy_arn] policy.detach_from(role) except KeyError: - raise NoSuchEntityException("Policy {0} was not found.".format(policy_arn)) + raise NoSuchEntityException(f"Policy {policy_arn} was not found.") @staticmethod def moto_role_to_role_type(moto_role: MotoRole) -> Role: diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py index a945e5af67a47..d1833e44b3d7f 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,11 +14,11 @@ class IAMAccessKeyProperties(TypedDict): - UserName: Optional[str] - Id: Optional[str] - SecretAccessKey: Optional[str] - Serial: Optional[int] - Status: Optional[str] + UserName: str | None + Id: str | None + SecretAccessKey: str | None + Serial: int | None + Status: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py index a54ee6f94b3db..690bc331be4d6 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMAccessKeyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::AccessKey" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_accesskey import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py index 69c2b15ab1bfe..54b4918e5e1e0 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,17 +15,17 @@ class IAMGroupProperties(TypedDict): - Arn: Optional[str] - GroupName: Optional[str] - Id: Optional[str] - ManagedPolicyArns: Optional[list[str]] - Path: Optional[str] - Policies: Optional[list[Policy]] + Arn: str | None + GroupName: str | None + Id: str | None + ManagedPolicyArns: list[str] | None + Path: str | None + Policies: list[Policy] | None class Policy(TypedDict): - PolicyDocument: Optional[dict] - PolicyName: Optional[str] + PolicyDocument: dict | None + PolicyName: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py index 24af55af719b1..abb34b8751d2c 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::Group" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_group import IAMGroupProvider diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py index b65f5f079d0ff..b322af1c5c20e 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class IAMInstanceProfileProperties(TypedDict): - Roles: Optional[list[str]] - Arn: Optional[str] - InstanceProfileName: Optional[str] - Path: Optional[str] + Roles: list[str] | None + Arn: str | None + InstanceProfileName: str | None + Path: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py index 875b729a55323..f6ffb39efc944 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMInstanceProfileProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::InstanceProfile" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_instanceprofile import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py index 0bca0e5a02169..3bebb37a28191 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,14 +15,14 @@ class IAMManagedPolicyProperties(TypedDict): - PolicyDocument: Optional[dict] - Description: Optional[str] - Groups: Optional[list[str]] - Id: Optional[str] - ManagedPolicyName: Optional[str] - Path: Optional[str] - Roles: Optional[list[str]] - Users: Optional[list[str]] + PolicyDocument: dict | None + Description: str | None + Groups: list[str] | None + Id: str | None + ManagedPolicyName: str | None + Path: str | None + Roles: list[str] | None + Users: list[str] | None REPEATED_INVOCATION = "repeated_invocation" @@ -61,7 +61,13 @@ def create( group_name = util.generate_default_name(request.stack_name, request.logical_resource_id) model["ManagedPolicyName"] = group_name - policy_doc = json.dumps(util.remove_none_values(model["PolicyDocument"])) + # PolicyDocument can be either a dict or a JSON string (e.g., from Fn::Sub) + policy_document = model["PolicyDocument"] + if isinstance(policy_document, str): + policy_doc = policy_document + else: + policy_doc = json.dumps(util.remove_none_values(policy_document)) + policy = iam_client.create_policy( PolicyName=model["ManagedPolicyName"], PolicyDocument=policy_doc ) diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py index d33ce61ef26b5..c47e80f66cd7e 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMManagedPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::ManagedPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_managedpolicy import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py index 97fdb19341b57..61a16551ec793 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py @@ -5,7 +5,7 @@ import random import string from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,12 +17,12 @@ class IAMPolicyProperties(TypedDict): - PolicyDocument: Optional[dict] - PolicyName: Optional[str] - Groups: Optional[list[str]] - Id: Optional[str] - Roles: Optional[list[str]] - Users: Optional[list[str]] + PolicyDocument: dict | None + PolicyName: str | None + Groups: list[str] | None + Id: str | None + Roles: list[str] | None + Users: list[str] | None REPEATED_INVOCATION = "repeated_invocation" @@ -53,7 +53,13 @@ def create( model = request.desired_state iam_client = request.aws_client_factory.iam - policy_doc = json.dumps(util.remove_none_values(model["PolicyDocument"])) + # PolicyDocument can be either a dict or a JSON string (e.g., from Fn::Sub) + policy_document = model["PolicyDocument"] + if isinstance(policy_document, str): + policy_doc = policy_document + else: + policy_doc = json.dumps(util.remove_none_values(policy_document)) + policy_name = model["PolicyName"] if not any([model.get("Roles"), model.get("Users"), model.get("Groups")]): @@ -122,7 +128,14 @@ def update( iam_client = request.aws_client_factory.iam model = request.desired_state # FIXME: this wasn't properly implemented before as well, still needs to be rewritten - policy_doc = json.dumps(util.remove_none_values(model["PolicyDocument"])) + + # PolicyDocument can be either a dict or a JSON string (e.g., from Fn::Sub) + policy_document = model["PolicyDocument"] + if isinstance(policy_document, str): + policy_doc = policy_document + else: + policy_doc = json.dumps(util.remove_none_values(policy_document)) + policy_name = model["PolicyName"] for role in model.get("Roles", []): diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py index a3fdd7e9c9dc3..43fd7a554b3f8 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::Policy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_policy import IAMPolicyProvider diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py index f3687337e332d..226cb4f68a8e0 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,27 +16,27 @@ class IAMRoleProperties(TypedDict): - AssumeRolePolicyDocument: Optional[dict | str] - Arn: Optional[str] - Description: Optional[str] - ManagedPolicyArns: Optional[list[str]] - MaxSessionDuration: Optional[int] - Path: Optional[str] - PermissionsBoundary: Optional[str] - Policies: Optional[list[Policy]] - RoleId: Optional[str] - RoleName: Optional[str] - Tags: Optional[list[Tag]] + AssumeRolePolicyDocument: dict | str | None + Arn: str | None + Description: str | None + ManagedPolicyArns: list[str] | None + MaxSessionDuration: int | None + Path: str | None + PermissionsBoundary: str | None + Policies: list[Policy] | None + RoleId: str | None + RoleName: str | None + Tags: list[Tag] | None class Policy(TypedDict): - PolicyDocument: Optional[str | dict] - PolicyName: Optional[str] + PolicyDocument: str | dict | None + PolicyName: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py index d6c7059f611eb..bc1b98e625923 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMRoleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::Role" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_role import IAMRoleProvider diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py index 233f9554efcc0..9778529826ad5 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class IAMServerCertificateProperties(TypedDict): - Arn: Optional[str] - CertificateBody: Optional[str] - CertificateChain: Optional[str] - Path: Optional[str] - PrivateKey: Optional[str] - ServerCertificateName: Optional[str] - Tags: Optional[list[Tag]] + Arn: str | None + CertificateBody: str | None + CertificateChain: str | None + Path: str | None + PrivateKey: str | None + ServerCertificateName: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py index 13723bd73ce2b..a90f432110b8d 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMServerCertificateProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::ServerCertificate" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_servercertificate import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py index 2437966df10e7..441e6a0545aed 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class IAMServiceLinkedRoleProperties(TypedDict): - AWSServiceName: Optional[str] - CustomSuffix: Optional[str] - Description: Optional[str] - Id: Optional[str] + AWSServiceName: str | None + CustomSuffix: str | None + Description: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py index e81cc105f85c1..98c74c9912779 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMServiceLinkedRoleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::ServiceLinkedRole" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_servicelinkedrole import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py index 8600522013b39..373c7442ef326 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,31 +14,31 @@ class IAMUserProperties(TypedDict): - Arn: Optional[str] - Groups: Optional[list[str]] - Id: Optional[str] - LoginProfile: Optional[LoginProfile] - ManagedPolicyArns: Optional[list[str]] - Path: Optional[str] - PermissionsBoundary: Optional[str] - Policies: Optional[list[Policy]] - Tags: Optional[list[Tag]] - UserName: Optional[str] + Arn: str | None + Groups: list[str] | None + Id: str | None + LoginProfile: LoginProfile | None + ManagedPolicyArns: list[str] | None + Path: str | None + PermissionsBoundary: str | None + Policies: list[Policy] | None + Tags: list[Tag] | None + UserName: str | None class Policy(TypedDict): - PolicyDocument: Optional[dict] - PolicyName: Optional[str] + PolicyDocument: dict | None + PolicyName: str | None class LoginProfile(TypedDict): - Password: Optional[str] - PasswordResetRequired: Optional[bool] + Password: str | None + PasswordResetRequired: bool | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py index 60acd8fc1493c..abb2910dbe3e7 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMUserProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::User" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_user import IAMUserProvider diff --git a/localstack-core/localstack/services/iam/resources/policy_simulator.py b/localstack-core/localstack/services/iam/resources/policy_simulator.py new file mode 100644 index 0000000000000..341ace67bc482 --- /dev/null +++ b/localstack-core/localstack/services/iam/resources/policy_simulator.py @@ -0,0 +1,133 @@ +import abc +import json + +from moto.iam import iam_backends +from moto.iam.models import IAMBackend + +from localstack.aws.api import RequestContext +from localstack.aws.api.iam import ( + ActionNameType, + EvaluationResult, + PolicyEvaluationDecisionType, + ResourceNameType, + SimulatePolicyResponse, + SimulatePrincipalPolicyRequest, +) + + +class IAMPolicySimulator(abc.ABC): + @abc.abstractmethod + def simulate_principal_policy( + self, context: RequestContext, request: SimulatePrincipalPolicyRequest + ) -> SimulatePolicyResponse: + """ + Simulate principal policy + :param request: SimulatePrincipalPolicyRequest + :param context: RequestContext + :return: SimulatePrincipalResponse + """ + pass + + +class BasicIAMPolicySimulator(IAMPolicySimulator): + def simulate_principal_policy( + self, + context: RequestContext, + request: SimulatePrincipalPolicyRequest, + ) -> SimulatePolicyResponse: + backend = self.get_iam_backend(context) + policies = self.get_policies_from_principal(backend, request.get("PolicySourceArn")) + + def _get_statements_from_policy_list(_policies: list[str]): + statements = [] + for policy_str in _policies: + policy_dict = json.loads(policy_str) + if isinstance(policy_dict["Statement"], list): + statements.extend(policy_dict["Statement"]) + else: + statements.append(policy_dict["Statement"]) + return statements + + policy_statements = _get_statements_from_policy_list(policies) + + evaluations = [ + self.build_evaluation_result(action_name, resource_arn, policy_statements) + for action_name in request.get("ActionNames") + for resource_arn in request.get("ResourceArns") + ] + + response = SimulatePolicyResponse() + response["IsTruncated"] = False + response["EvaluationResults"] = evaluations + + return response + + @staticmethod + def build_evaluation_result( + action_name: ActionNameType, resource_name: ResourceNameType, policy_statements: list[dict] + ) -> EvaluationResult: + eval_res = EvaluationResult() + eval_res["EvalActionName"] = action_name + eval_res["EvalResourceName"] = resource_name + eval_res["EvalDecision"] = PolicyEvaluationDecisionType.explicitDeny + for statement in policy_statements: + # TODO Implement evaluation logic here + if ( + action_name in statement["Action"] + and resource_name in statement["Resource"] + and statement["Effect"] == "Allow" + ): + eval_res["EvalDecision"] = PolicyEvaluationDecisionType.allowed + eval_res["MatchedStatements"] = [] # TODO: add support for statement compilation. + return eval_res + + @staticmethod + def get_iam_backend(context: RequestContext) -> IAMBackend: + return iam_backends[context.account_id][context.partition] + + @staticmethod + def get_policies_from_principal(backend: IAMBackend, principal_arn: str) -> list[dict]: + policies = [] + if ":role" in principal_arn: + role_name = principal_arn.split("/")[-1] + + policies.append(backend.get_role(role_name=role_name).assume_role_policy_document) + + policy_names = backend.list_role_policies(role_name=role_name) + policies.extend( + [ + backend.get_role_policy(role_name=role_name, policy_name=policy_name)[1] + for policy_name in policy_names + ] + ) + + attached_policies, _ = backend.list_attached_role_policies(role_name=role_name) + policies.extend([policy.document for policy in attached_policies]) + + if ":group" in principal_arn: + group_name = principal_arn.split("/")[-1] + policy_names = backend.list_group_policies(group_name=group_name) + policies.extend( + [ + backend.get_group_policy(group_name=group_name, policy_name=policy_name)[1] + for policy_name in policy_names + ] + ) + + attached_policies, _ = backend.list_attached_group_policies(group_name=group_name) + policies.extend([policy.document for policy in attached_policies]) + + if ":user" in principal_arn: + user_name = principal_arn.split("/")[-1] + policy_names = backend.list_user_policies(user_name=user_name) + policies.extend( + [ + backend.get_user_policy(user_name=user_name, policy_name=policy_name)[1] + for policy_name in policy_names + ] + ) + + attached_policies, _ = backend.list_attached_user_policies(user_name=user_name) + policies.extend([policy.document for policy in attached_policies]) + + return policies diff --git a/localstack-core/localstack/services/internal.py b/localstack-core/localstack/services/internal.py index 85c4de12ff351..a1b1333ce66e2 100644 --- a/localstack-core/localstack/services/internal.py +++ b/localstack-core/localstack/services/internal.py @@ -8,7 +8,6 @@ from datetime import datetime from plux import PluginManager -from werkzeug.exceptions import NotFound from localstack import config, constants from localstack.deprecations import deprecated_endpoint @@ -260,8 +259,8 @@ def on_get(self, request, stage: str): try: stage = Stage[stage.upper()] - except KeyError as e: - raise NotFound(f"no such stage {stage}") from e + except KeyError: + return Response(f"no such stage {stage}", 404) return { "completed": manager.stage_completed.get(stage), diff --git a/localstack-core/localstack/services/kinesis/kinesis_mock_server.py b/localstack-core/localstack/services/kinesis/kinesis_mock_server.py index b9ce394e1415d..92ef250bec6e8 100644 --- a/localstack-core/localstack/services/kinesis/kinesis_mock_server.py +++ b/localstack-core/localstack/services/kinesis/kinesis_mock_server.py @@ -3,7 +3,6 @@ import threading from abc import abstractmethod from pathlib import Path -from typing import Dict, List, Optional, Tuple from localstack import config from localstack.services.kinesis.packages import ( @@ -31,7 +30,7 @@ def __init__( account_id: str, host: str = "localhost", log_level: str = "INFO", - data_dir: Optional[str] = None, + data_dir: str | None = None, ) -> None: self._account_id = account_id self._latency = latency @@ -57,7 +56,7 @@ def do_start_thread(self) -> FuncThread: return t @property - def _environment_variables(self) -> Dict: + def _environment_variables(self) -> dict: env_vars = { "KINESIS_MOCK_PLAIN_PORT": self.port, # Each kinesis-mock instance listens to two ports - secure and insecure. @@ -94,7 +93,7 @@ def _environment_variables(self) -> Dict: return env_vars @abstractmethod - def _create_shell_command(self) -> Tuple[List, Dict]: + def _create_shell_command(self) -> tuple[list, dict]: """ Helper method for creating kinesis mock invocation command :return: returns a tuple containing the command list and a dictionary with the environment variables @@ -106,12 +105,12 @@ def _log_listener(self, line, **_kwargs): class KinesisMockScalaServer(KinesisMockServer): - def _create_shell_command(self) -> Tuple[List, Dict]: + def _create_shell_command(self) -> tuple[list, dict]: cmd = ["java", "-jar", *self._get_java_vm_options(), str(self._exe_path)] return cmd, self._environment_variables @property - def _environment_variables(self) -> Dict: + def _environment_variables(self) -> dict: default_env_vars = super()._environment_variables kinesis_mock_installer = kinesismock_scala_package.get_installer() return { @@ -130,7 +129,7 @@ def _get_java_vm_options(self) -> list[str]: class KinesisMockNodeServer(KinesisMockServer): @property - def _environment_variables(self) -> Dict: + def _environment_variables(self) -> dict: node_env_vars = { # Use the `server.json` packaged next to the main.js "KINESIS_MOCK_CERT_PATH": str((self._exe_path.parent / "server.json").absolute()), @@ -139,7 +138,7 @@ def _environment_variables(self) -> Dict: default_env_vars = super()._environment_variables return {**node_env_vars, **default_env_vars} - def _create_shell_command(self) -> Tuple[List, Dict]: + def _create_shell_command(self) -> tuple[list, dict]: cmd = ["node", self._exe_path] return cmd, self._environment_variables diff --git a/localstack-core/localstack/services/kinesis/models.py b/localstack-core/localstack/services/kinesis/models.py index 3247ac060fbb0..f601fda0ab1ca 100644 --- a/localstack-core/localstack/services/kinesis/models.py +++ b/localstack-core/localstack/services/kinesis/models.py @@ -1,18 +1,30 @@ from collections import defaultdict -from typing import Dict, List, Set -from localstack.aws.api.kinesis import ConsumerDescription, MetricsName, StreamName -from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute +from localstack.aws.api.kinesis import ( + ConsumerDescription, + MetricsName, + Policy, + ResourceARN, + StreamName, +) +from localstack.services.stores import ( + AccountRegionBundle, + BaseStore, + CrossAccountAttribute, + LocalAttribute, +) class KinesisStore(BaseStore): # list of stream consumer details - stream_consumers: List[ConsumerDescription] = LocalAttribute(default=list) + stream_consumers: list[ConsumerDescription] = LocalAttribute(default=list) # maps stream name to list of enhanced monitoring metrics - enhanced_metrics: Dict[StreamName, Set[MetricsName]] = LocalAttribute( + enhanced_metrics: dict[StreamName, set[MetricsName]] = LocalAttribute( default=lambda: defaultdict(set) ) + resource_policies: dict[ResourceARN, Policy] = CrossAccountAttribute(default=dict) + kinesis_stores = AccountRegionBundle("kinesis", KinesisStore) diff --git a/localstack-core/localstack/services/kinesis/packages.py b/localstack-core/localstack/services/kinesis/packages.py index 1d64bb4194b63..0f10c3d2415f5 100644 --- a/localstack-core/localstack/services/kinesis/packages.py +++ b/localstack-core/localstack/services/kinesis/packages.py @@ -1,13 +1,13 @@ import os from enum import StrEnum from functools import lru_cache -from typing import Any, List +from typing import Any from localstack.packages import InstallTarget, Package from localstack.packages.core import GitHubReleaseInstaller, NodePackageInstaller from localstack.packages.java import JavaInstallerMixin, java_package -_KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.4.12" +_KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.5.2" class KinesisMockEngine(StrEnum): @@ -58,7 +58,7 @@ def __init__( def _get_installer(self, version: str) -> KinesisMockScalaPackageInstaller: return KinesisMockScalaPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [_KINESIS_MOCK_VERSION] # Only supported on v0.4.12+ @@ -73,7 +73,7 @@ def __init__( def _get_installer(self, version: str) -> KinesisMockNodePackageInstaller: return KinesisMockNodePackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [_KINESIS_MOCK_VERSION] diff --git a/localstack-core/localstack/services/kinesis/provider.py b/localstack-core/localstack/services/kinesis/provider.py index 7f080e35fc122..09f8f335a6eb9 100644 --- a/localstack-core/localstack/services/kinesis/provider.py +++ b/localstack-core/localstack/services/kinesis/provider.py @@ -1,5 +1,7 @@ +import json import logging import os +import re import time from random import random @@ -8,22 +10,28 @@ from localstack.aws.api.kinesis import ( ConsumerARN, Data, + GetResourcePolicyOutput, HashKey, KinesisApi, PartitionKey, + Policy, ProvisionedThroughputExceededException, PutRecordOutput, PutRecordsOutput, PutRecordsRequestEntryList, PutRecordsResultEntry, + ResourceARN, + ResourceNotFoundException, SequenceNumber, ShardId, StartingPosition, StreamARN, + StreamId, StreamName, SubscribeToShardEvent, SubscribeToShardEventStream, SubscribeToShardOutput, + ValidationException, ) from localstack.aws.connect import connect_to from localstack.constants import LOCALHOST @@ -39,6 +47,13 @@ MAX_SUBSCRIPTION_SECONDS = 300 SERVER_STARTUP_TIMEOUT = 120 +DATA_STREAM_ARN_REGEX = re.compile( + r"^arn:aws(?:-[a-z]+)*:kinesis:[a-z0-9-]+:\d{12}:stream\/[a-zA-Z0-9_.\-]+$" +) +CONSUMER_ARN_REGEX = re.compile( + r"^arn:aws(?:-[a-z]+)*:kinesis:[a-z0-9-]+:\d{12}:stream\/[a-zA-Z0-9_.\-]+\/consumer\/[a-zA-Z0-9_.\-]+:\d+$" +) + def find_stream_for_consumer(consumer_arn): account_id = extract_account_id_from_arn(consumer_arn) @@ -49,7 +64,12 @@ def find_stream_for_consumer(consumer_arn): for cons in kinesis.list_stream_consumers(StreamARN=stream_arn)["Consumers"]: if cons["ConsumerARN"] == consumer_arn: return stream_name - raise Exception("Unable to find stream for stream consumer %s" % consumer_arn) + raise Exception(f"Unable to find stream for stream consumer {consumer_arn}") + + +def is_valid_kinesis_arn(resource_arn: ResourceARN) -> bool: + """Check if the provided ARN is a valid Kinesis ARN.""" + return bool(CONSUMER_ARN_REGEX.match(resource_arn) or DATA_STREAM_ARN_REGEX.match(resource_arn)) class KinesisProvider(KinesisApi, ServiceLifecycleHook): @@ -81,12 +101,74 @@ def get_forward_url(self, account_id: str, region_name: str) -> str: def get_store(account_id: str, region_name: str) -> KinesisStore: return kinesis_stores[account_id][region_name] + def put_resource_policy( + self, + context: RequestContext, + resource_arn: ResourceARN, + policy: Policy, + stream_id: StreamId | None = None, + **kwargs, + ) -> None: + if not is_valid_kinesis_arn(resource_arn): + raise ValidationException(f"invalid kinesis arn {resource_arn}") + + kinesis = connect_to( + aws_access_key_id=context.account_id, region_name=context.region + ).kinesis + try: + kinesis.describe_stream_summary(StreamARN=resource_arn) + except kinesis.exceptions.ResourceNotFoundException: + raise ResourceNotFoundException(f"Stream with ARN {resource_arn} not found") + + store = self.get_store(context.account_id, context.region) + store.resource_policies[resource_arn] = policy + + def get_resource_policy( + self, + context: RequestContext, + resource_arn: ResourceARN, + stream_id: StreamId | None = None, + **kwargs, + ) -> GetResourcePolicyOutput: + if not is_valid_kinesis_arn(resource_arn): + raise ValidationException(f"invalid kinesis arn {resource_arn}") + + kinesis = connect_to( + aws_access_key_id=context.account_id, region_name=context.region + ).kinesis + try: + kinesis.describe_stream_summary(StreamARN=resource_arn) + except kinesis.exceptions.ResourceNotFoundException: + raise ResourceNotFoundException(f"Stream with ARN {resource_arn} not found") + + store = self.get_store(context.account_id, context.region) + policy = store.resource_policies.get(resource_arn, json.dumps({})) + return GetResourcePolicyOutput(Policy=policy) + + def delete_resource_policy( + self, + context: RequestContext, + resource_arn: ResourceARN, + stream_id: StreamId | None = None, + **kwargs, + ) -> None: + if not is_valid_kinesis_arn(resource_arn): + raise ValidationException(f"invalid kinesis arn {resource_arn}") + + store = self.get_store(context.account_id, context.region) + if resource_arn not in store.resource_policies: + raise ResourceNotFoundException( + f"No resource policy found for resource ARN {resource_arn}" + ) + del store.resource_policies[resource_arn] + def subscribe_to_shard( self, context: RequestContext, consumer_arn: ConsumerARN, shard_id: ShardId, starting_position: StartingPosition, + stream_id: StreamId | None = None, **kwargs, ) -> SubscribeToShardOutput: kinesis = connect_to( @@ -125,7 +207,13 @@ def event_generator(): raise shard_iterator = result.get("NextShardIterator") records = result.get("Records", []) - if not records: + if records: + # Update the last sequence number to the last record's sequence number + # TODO: This will suffice for now but does not properly capture checkpointing when + # no data is written to a shard. See AWS docs: + # https://docs.aws.amazon.com/kinesis/latest/APIReference/API_SubscribeToShardEvent.html#API_SubscribeToShardEvent_Contents + last_sequence_number = records[-1].get("SequenceNumber", last_sequence_number) + else: # On AWS there is *at least* 1 event every 5 seconds # but this is not possible in this structure. # In order to avoid a 5-second blocking call, we make the compromise of 3 seconds. @@ -136,7 +224,7 @@ def event_generator(): Records=records, ContinuationSequenceNumber=str(last_sequence_number), MillisBehindLatest=0, - ChildShards=[], + ChildShards=None, # TODO: Include shard children info ) ) @@ -151,6 +239,7 @@ def put_record( explicit_hash_key: HashKey = None, sequence_number_for_ordering: SequenceNumber = None, stream_arn: StreamARN = None, + stream_id: StreamId | None = None, **kwargs, ) -> PutRecordOutput: # TODO: Ensure use of `stream_arn` works. Currently kinesis-mock only works with ctx request account ID and region @@ -168,6 +257,7 @@ def put_records( records: PutRecordsRequestEntryList, stream_name: StreamName = None, stream_arn: StreamARN = None, + stream_id: StreamId | None = None, **kwargs, ) -> PutRecordsOutput: # TODO: Ensure use of `stream_arn` works. Currently kinesis-mock only works with ctx request account ID and region diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py index 28d231d666484..5d805de0bba97 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,27 +14,27 @@ class KinesisStreamProperties(TypedDict): - Arn: Optional[str] - Name: Optional[str] - RetentionPeriodHours: Optional[int] - ShardCount: Optional[int] - StreamEncryption: Optional[StreamEncryption] - StreamModeDetails: Optional[StreamModeDetails] - Tags: Optional[list[Tag]] + Arn: str | None + Name: str | None + RetentionPeriodHours: int | None + ShardCount: int | None + StreamEncryption: StreamEncryption | None + StreamModeDetails: StreamModeDetails | None + Tags: list[Tag] | None class StreamModeDetails(TypedDict): - StreamMode: Optional[str] + StreamMode: str | None class StreamEncryption(TypedDict): - EncryptionType: Optional[str] - KeyId: Optional[str] + EncryptionType: str | None + KeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py index d7e834e7bb0bf..9f6a783801fd2 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KinesisStreamProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Kinesis::Stream" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kinesis.resource_providers.aws_kinesis_stream import ( diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py index 3f0faee08ffda..03d4bf6d41dfc 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,12 +14,12 @@ class KinesisStreamConsumerProperties(TypedDict): - ConsumerName: Optional[str] - StreamARN: Optional[str] - ConsumerARN: Optional[str] - ConsumerCreationTimestamp: Optional[str] - ConsumerStatus: Optional[str] - Id: Optional[str] + ConsumerName: str | None + StreamARN: str | None + ConsumerARN: str | None + ConsumerCreationTimestamp: str | None + ConsumerStatus: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -66,7 +66,7 @@ def create( response = kinesis.register_stream_consumer( StreamARN=model["StreamARN"], ConsumerName=model["ConsumerName"] ) - model["ConsumerARN"] = response["Consumer"]["ConsumerARN"] + model["Id"] = model["ConsumerARN"] = response["Consumer"]["ConsumerARN"] model["ConsumerStatus"] = response["Consumer"]["ConsumerStatus"] request.custom_context[REPEATED_INVOCATION] = True return ProgressEvent( diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py index b1f2cab38423d..9710d11adbb49 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KinesisStreamConsumerProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Kinesis::StreamConsumer" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kinesis.resource_providers.aws_kinesis_streamconsumer import ( diff --git a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py index 6764a783667f0..0af6cfaee3069 100644 --- a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py +++ b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,328 +14,328 @@ class KinesisFirehoseDeliveryStreamProperties(TypedDict): - AmazonOpenSearchServerlessDestinationConfiguration: Optional[ - AmazonOpenSearchServerlessDestinationConfiguration - ] - AmazonopensearchserviceDestinationConfiguration: Optional[ - AmazonopensearchserviceDestinationConfiguration - ] - Arn: Optional[str] - DeliveryStreamEncryptionConfigurationInput: Optional[DeliveryStreamEncryptionConfigurationInput] - DeliveryStreamName: Optional[str] - DeliveryStreamType: Optional[str] - ElasticsearchDestinationConfiguration: Optional[ElasticsearchDestinationConfiguration] - ExtendedS3DestinationConfiguration: Optional[ExtendedS3DestinationConfiguration] - HttpEndpointDestinationConfiguration: Optional[HttpEndpointDestinationConfiguration] - KinesisStreamSourceConfiguration: Optional[KinesisStreamSourceConfiguration] - RedshiftDestinationConfiguration: Optional[RedshiftDestinationConfiguration] - S3DestinationConfiguration: Optional[S3DestinationConfiguration] - SplunkDestinationConfiguration: Optional[SplunkDestinationConfiguration] - Tags: Optional[list[Tag]] + AmazonOpenSearchServerlessDestinationConfiguration: ( + AmazonOpenSearchServerlessDestinationConfiguration | None + ) + AmazonopensearchserviceDestinationConfiguration: ( + AmazonopensearchserviceDestinationConfiguration | None + ) + Arn: str | None + DeliveryStreamEncryptionConfigurationInput: DeliveryStreamEncryptionConfigurationInput | None + DeliveryStreamName: str | None + DeliveryStreamType: str | None + ElasticsearchDestinationConfiguration: ElasticsearchDestinationConfiguration | None + ExtendedS3DestinationConfiguration: ExtendedS3DestinationConfiguration | None + HttpEndpointDestinationConfiguration: HttpEndpointDestinationConfiguration | None + KinesisStreamSourceConfiguration: KinesisStreamSourceConfiguration | None + RedshiftDestinationConfiguration: RedshiftDestinationConfiguration | None + S3DestinationConfiguration: S3DestinationConfiguration | None + SplunkDestinationConfiguration: SplunkDestinationConfiguration | None + Tags: list[Tag] | None class DeliveryStreamEncryptionConfigurationInput(TypedDict): - KeyType: Optional[str] - KeyARN: Optional[str] + KeyType: str | None + KeyARN: str | None class ElasticsearchBufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class CloudWatchLoggingOptions(TypedDict): - Enabled: Optional[bool] - LogGroupName: Optional[str] - LogStreamName: Optional[str] + Enabled: bool | None + LogGroupName: str | None + LogStreamName: str | None class ProcessorParameter(TypedDict): - ParameterName: Optional[str] - ParameterValue: Optional[str] + ParameterName: str | None + ParameterValue: str | None class Processor(TypedDict): - Type: Optional[str] - Parameters: Optional[list[ProcessorParameter]] + Type: str | None + Parameters: list[ProcessorParameter] | None class ProcessingConfiguration(TypedDict): - Enabled: Optional[bool] - Processors: Optional[list[Processor]] + Enabled: bool | None + Processors: list[Processor] | None class ElasticsearchRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class BufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class KMSEncryptionConfig(TypedDict): - AWSKMSKeyARN: Optional[str] + AWSKMSKeyARN: str | None class EncryptionConfiguration(TypedDict): - KMSEncryptionConfig: Optional[KMSEncryptionConfig] - NoEncryptionConfig: Optional[str] + KMSEncryptionConfig: KMSEncryptionConfig | None + NoEncryptionConfig: str | None class S3DestinationConfiguration(TypedDict): - BucketARN: Optional[str] - RoleARN: Optional[str] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - CompressionFormat: Optional[str] - EncryptionConfiguration: Optional[EncryptionConfiguration] - ErrorOutputPrefix: Optional[str] - Prefix: Optional[str] + BucketARN: str | None + RoleARN: str | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + CompressionFormat: str | None + EncryptionConfiguration: EncryptionConfiguration | None + ErrorOutputPrefix: str | None + Prefix: str | None class VpcConfiguration(TypedDict): - RoleARN: Optional[str] - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + RoleARN: str | None + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class DocumentIdOptions(TypedDict): - DefaultDocumentIdFormat: Optional[str] + DefaultDocumentIdFormat: str | None class ElasticsearchDestinationConfiguration(TypedDict): - IndexName: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[ElasticsearchBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ClusterEndpoint: Optional[str] - DocumentIdOptions: Optional[DocumentIdOptions] - DomainARN: Optional[str] - IndexRotationPeriod: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[ElasticsearchRetryOptions] - S3BackupMode: Optional[str] - TypeName: Optional[str] - VpcConfiguration: Optional[VpcConfiguration] + IndexName: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: ElasticsearchBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ClusterEndpoint: str | None + DocumentIdOptions: DocumentIdOptions | None + DomainARN: str | None + IndexRotationPeriod: str | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: ElasticsearchRetryOptions | None + S3BackupMode: str | None + TypeName: str | None + VpcConfiguration: VpcConfiguration | None class AmazonopensearchserviceBufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class AmazonopensearchserviceRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class AmazonopensearchserviceDestinationConfiguration(TypedDict): - IndexName: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[AmazonopensearchserviceBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ClusterEndpoint: Optional[str] - DocumentIdOptions: Optional[DocumentIdOptions] - DomainARN: Optional[str] - IndexRotationPeriod: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[AmazonopensearchserviceRetryOptions] - S3BackupMode: Optional[str] - TypeName: Optional[str] - VpcConfiguration: Optional[VpcConfiguration] + IndexName: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: AmazonopensearchserviceBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ClusterEndpoint: str | None + DocumentIdOptions: DocumentIdOptions | None + DomainARN: str | None + IndexRotationPeriod: str | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: AmazonopensearchserviceRetryOptions | None + S3BackupMode: str | None + TypeName: str | None + VpcConfiguration: VpcConfiguration | None class AmazonOpenSearchServerlessBufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class AmazonOpenSearchServerlessRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class AmazonOpenSearchServerlessDestinationConfiguration(TypedDict): - IndexName: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[AmazonOpenSearchServerlessBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - CollectionEndpoint: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[AmazonOpenSearchServerlessRetryOptions] - S3BackupMode: Optional[str] - VpcConfiguration: Optional[VpcConfiguration] + IndexName: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: AmazonOpenSearchServerlessBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + CollectionEndpoint: str | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: AmazonOpenSearchServerlessRetryOptions | None + S3BackupMode: str | None + VpcConfiguration: VpcConfiguration | None class HiveJsonSerDe(TypedDict): - TimestampFormats: Optional[list[str]] + TimestampFormats: list[str] | None class OpenXJsonSerDe(TypedDict): - CaseInsensitive: Optional[bool] - ColumnToJsonKeyMappings: Optional[dict] - ConvertDotsInJsonKeysToUnderscores: Optional[bool] + CaseInsensitive: bool | None + ColumnToJsonKeyMappings: dict | None + ConvertDotsInJsonKeysToUnderscores: bool | None class Deserializer(TypedDict): - HiveJsonSerDe: Optional[HiveJsonSerDe] - OpenXJsonSerDe: Optional[OpenXJsonSerDe] + HiveJsonSerDe: HiveJsonSerDe | None + OpenXJsonSerDe: OpenXJsonSerDe | None class InputFormatConfiguration(TypedDict): - Deserializer: Optional[Deserializer] + Deserializer: Deserializer | None class OrcSerDe(TypedDict): - BlockSizeBytes: Optional[int] - BloomFilterColumns: Optional[list[str]] - BloomFilterFalsePositiveProbability: Optional[float] - Compression: Optional[str] - DictionaryKeyThreshold: Optional[float] - EnablePadding: Optional[bool] - FormatVersion: Optional[str] - PaddingTolerance: Optional[float] - RowIndexStride: Optional[int] - StripeSizeBytes: Optional[int] + BlockSizeBytes: int | None + BloomFilterColumns: list[str] | None + BloomFilterFalsePositiveProbability: float | None + Compression: str | None + DictionaryKeyThreshold: float | None + EnablePadding: bool | None + FormatVersion: str | None + PaddingTolerance: float | None + RowIndexStride: int | None + StripeSizeBytes: int | None class ParquetSerDe(TypedDict): - BlockSizeBytes: Optional[int] - Compression: Optional[str] - EnableDictionaryCompression: Optional[bool] - MaxPaddingBytes: Optional[int] - PageSizeBytes: Optional[int] - WriterVersion: Optional[str] + BlockSizeBytes: int | None + Compression: str | None + EnableDictionaryCompression: bool | None + MaxPaddingBytes: int | None + PageSizeBytes: int | None + WriterVersion: str | None class Serializer(TypedDict): - OrcSerDe: Optional[OrcSerDe] - ParquetSerDe: Optional[ParquetSerDe] + OrcSerDe: OrcSerDe | None + ParquetSerDe: ParquetSerDe | None class OutputFormatConfiguration(TypedDict): - Serializer: Optional[Serializer] + Serializer: Serializer | None class SchemaConfiguration(TypedDict): - CatalogId: Optional[str] - DatabaseName: Optional[str] - Region: Optional[str] - RoleARN: Optional[str] - TableName: Optional[str] - VersionId: Optional[str] + CatalogId: str | None + DatabaseName: str | None + Region: str | None + RoleARN: str | None + TableName: str | None + VersionId: str | None class DataFormatConversionConfiguration(TypedDict): - Enabled: Optional[bool] - InputFormatConfiguration: Optional[InputFormatConfiguration] - OutputFormatConfiguration: Optional[OutputFormatConfiguration] - SchemaConfiguration: Optional[SchemaConfiguration] + Enabled: bool | None + InputFormatConfiguration: InputFormatConfiguration | None + OutputFormatConfiguration: OutputFormatConfiguration | None + SchemaConfiguration: SchemaConfiguration | None class RetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class DynamicPartitioningConfiguration(TypedDict): - Enabled: Optional[bool] - RetryOptions: Optional[RetryOptions] + Enabled: bool | None + RetryOptions: RetryOptions | None class ExtendedS3DestinationConfiguration(TypedDict): - BucketARN: Optional[str] - RoleARN: Optional[str] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - CompressionFormat: Optional[str] - DataFormatConversionConfiguration: Optional[DataFormatConversionConfiguration] - DynamicPartitioningConfiguration: Optional[DynamicPartitioningConfiguration] - EncryptionConfiguration: Optional[EncryptionConfiguration] - ErrorOutputPrefix: Optional[str] - Prefix: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupConfiguration: Optional[S3DestinationConfiguration] - S3BackupMode: Optional[str] + BucketARN: str | None + RoleARN: str | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + CompressionFormat: str | None + DataFormatConversionConfiguration: DataFormatConversionConfiguration | None + DynamicPartitioningConfiguration: DynamicPartitioningConfiguration | None + EncryptionConfiguration: EncryptionConfiguration | None + ErrorOutputPrefix: str | None + Prefix: str | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupConfiguration: S3DestinationConfiguration | None + S3BackupMode: str | None class KinesisStreamSourceConfiguration(TypedDict): - KinesisStreamARN: Optional[str] - RoleARN: Optional[str] + KinesisStreamARN: str | None + RoleARN: str | None class CopyCommand(TypedDict): - DataTableName: Optional[str] - CopyOptions: Optional[str] - DataTableColumns: Optional[str] + DataTableName: str | None + CopyOptions: str | None + DataTableColumns: str | None class RedshiftRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class RedshiftDestinationConfiguration(TypedDict): - ClusterJDBCURL: Optional[str] - CopyCommand: Optional[CopyCommand] - Password: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - Username: Optional[str] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[RedshiftRetryOptions] - S3BackupConfiguration: Optional[S3DestinationConfiguration] - S3BackupMode: Optional[str] + ClusterJDBCURL: str | None + CopyCommand: CopyCommand | None + Password: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + Username: str | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: RedshiftRetryOptions | None + S3BackupConfiguration: S3DestinationConfiguration | None + S3BackupMode: str | None class SplunkRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class SplunkDestinationConfiguration(TypedDict): - HECEndpoint: Optional[str] - HECEndpointType: Optional[str] - HECToken: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - HECAcknowledgmentTimeoutInSeconds: Optional[int] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[SplunkRetryOptions] - S3BackupMode: Optional[str] + HECEndpoint: str | None + HECEndpointType: str | None + HECToken: str | None + S3Configuration: S3DestinationConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + HECAcknowledgmentTimeoutInSeconds: int | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: SplunkRetryOptions | None + S3BackupMode: str | None class HttpEndpointConfiguration(TypedDict): - Url: Optional[str] - AccessKey: Optional[str] - Name: Optional[str] + Url: str | None + AccessKey: str | None + Name: str | None class HttpEndpointCommonAttribute(TypedDict): - AttributeName: Optional[str] - AttributeValue: Optional[str] + AttributeName: str | None + AttributeValue: str | None class HttpEndpointRequestConfiguration(TypedDict): - CommonAttributes: Optional[list[HttpEndpointCommonAttribute]] - ContentEncoding: Optional[str] + CommonAttributes: list[HttpEndpointCommonAttribute] | None + ContentEncoding: str | None class HttpEndpointDestinationConfiguration(TypedDict): - EndpointConfiguration: Optional[HttpEndpointConfiguration] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RequestConfiguration: Optional[HttpEndpointRequestConfiguration] - RetryOptions: Optional[RetryOptions] - RoleARN: Optional[str] - S3BackupMode: Optional[str] + EndpointConfiguration: HttpEndpointConfiguration | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + RequestConfiguration: HttpEndpointRequestConfiguration | None + RetryOptions: RetryOptions | None + RoleARN: str | None + S3BackupMode: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py index 772007e6ce18d..b497397f1e8ec 100644 --- a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py +++ b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KinesisFirehoseDeliveryStreamProviderPlugin(CloudFormationResourceProvider name = "AWS::KinesisFirehose::DeliveryStream" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kinesisfirehose.resource_providers.aws_kinesisfirehose_deliverystream import ( diff --git a/localstack-core/localstack/services/kms/models.py b/localstack-core/localstack/services/kms/models.py index 3479e309d4903..2f1334801433a 100644 --- a/localstack-core/localstack/services/kms/models.py +++ b/localstack-core/localstack/services/kms/models.py @@ -7,10 +7,10 @@ import random import re import struct +import typing import uuid from collections import namedtuple from dataclasses import dataclass -from typing import Dict, Optional, Tuple from cryptography.exceptions import InvalidSignature, InvalidTag, UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend @@ -21,7 +21,6 @@ from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey from cryptography.hazmat.primitives.asymmetric.utils import Prehashed -from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.serialization import load_der_public_key from localstack.aws.api.kms import ( @@ -46,16 +45,15 @@ OriginType, ReplicateKeyRequest, SigningAlgorithmSpec, - TagList, UnsupportedOperationException, ) -from localstack.constants import TAG_KEY_CUSTOM_ID -from localstack.services.kms.exceptions import TagException, ValidationException -from localstack.services.kms.utils import is_valid_key_arn, validate_tag +from localstack.services.kms.exceptions import ValidationException +from localstack.services.kms.utils import is_valid_key_arn from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute from localstack.utils.aws.arns import get_partition, kms_alias_arn, kms_key_arn from localstack.utils.crypto import decrypt, encrypt from localstack.utils.strings import long_uid, to_bytes, to_str +from localstack.utils.tagging import Tags LOG = logging.getLogger(__name__) @@ -91,9 +89,7 @@ # Moto uses IV_LEN of 12, as it is fine for GCM encryption mode, but we use CBC, so have to set it to 16. IV_LEN = 16 TAG_LEN = 16 -CIPHERTEXT_HEADER_FORMAT = ">{key_id_len}s{iv_len}s{tag_len}s".format( - key_id_len=KEY_ID_LEN, iv_len=IV_LEN, tag_len=TAG_LEN -) +CIPHERTEXT_HEADER_FORMAT = f">{KEY_ID_LEN}s{IV_LEN}s{TAG_LEN}s" HEADER_LEN = KEY_ID_LEN + IV_LEN + TAG_LEN Ciphertext = namedtuple("Ciphertext", ("key_id", "iv", "ciphertext", "tag")) @@ -117,9 +113,6 @@ # list of key names that should be skipped when serializing the encryption context IGNORED_CONTEXT_KEYS = ["aws-crypto-public-key"] -# special tag name to allow specifying a custom key material for created keys -TAG_KEY_CUSTOM_KEY_MATERIAL = "_custom_key_material_" - def _serialize_ciphertext_blob(ciphertext: Ciphertext) -> bytes: header = struct.pack( @@ -138,7 +131,7 @@ def deserialize_ciphertext_blob(ciphertext_blob: bytes) -> Ciphertext: return Ciphertext(key_id=key_id.decode("utf-8"), iv=iv, ciphertext=ciphertext, tag=tag) -def _serialize_encryption_context(encryption_context: Optional[EncryptionContextType]) -> bytes: +def _serialize_encryption_context(encryption_context: EncryptionContextType | None) -> bytes: if encryption_context: aad = io.BytesIO() for key, value in sorted(encryption_context.items(), key=lambda x: x[0]): @@ -173,9 +166,10 @@ class KmsCryptoKey: by AWS and is not used by AWS. """ - public_key: Optional[bytes] - private_key: Optional[bytes] - key_material: bytes + public_key: bytes | None + private_key: bytes | None + key_material: bytes | None + pending_key_material: bytes | None key_spec: str @staticmethod @@ -217,9 +211,10 @@ def raise_validation(): raise UnsupportedOperationException(f"KeySpec {key_spec} is not supported") - def __init__(self, key_spec: str, key_material: Optional[bytes] = None): + def __init__(self, key_spec: str, key_material: bytes | None = None): self.private_key = None self.public_key = None + self.pending_key_material = None # Technically, key_material, being a symmetric encryption key, is only relevant for # key_spec == SYMMETRIC_DEFAULT. # But LocalStack uses symmetric encryption with this key_material even for other specs. Asymmetric keys are @@ -234,7 +229,10 @@ def __init__(self, key_spec: str, key_material: Optional[bytes] = None): if key_spec.startswith("RSA"): key_size = RSA_CRYPTO_KEY_LENGTHS.get(key_spec) - key = rsa.generate_private_key(public_exponent=65537, key_size=key_size) + if key_material: + key = crypto_serialization.load_der_private_key(key_material, password=None) + else: + key = rsa.generate_private_key(public_exponent=65537, key_size=key_size) elif key_spec.startswith("ECC"): curve = ECC_CURVES.get(key_spec) if key_material: @@ -251,7 +249,14 @@ def __init__(self, key_spec: str, key_material: Optional[bytes] = None): self._serialize_key(key) def load_key_material(self, material: bytes): - if self.key_spec == "SYMMETRIC_DEFAULT": + if self.key_spec == KeySpec.SYMMETRIC_DEFAULT: + self.pending_key_material = material + elif self.key_spec in [ + KeySpec.HMAC_224, + KeySpec.HMAC_256, + KeySpec.HMAC_384, + KeySpec.HMAC_512, + ]: self.key_material = material else: key = crypto_serialization.load_der_private_key(material, password=None) @@ -280,30 +285,25 @@ def key(self) -> RSAPrivateKey | EllipticCurvePrivateKey: class KmsKey: metadata: KeyMetadata crypto_key: KmsCryptoKey - tags: Dict[str, str] policy: str is_key_rotation_enabled: bool rotation_period_in_days: int - next_rotation_date: datetime.datetime - previous_keys = [str] + next_rotation_date: datetime.datetime | None + previous_keys: list[bytes | None] + _internal_key_id: uuid.UUID def __init__( self, create_key_request: CreateKeyRequest = None, account_id: str = None, region: str = None, + custom_key_material: bytes | None = None, + custom_key_id: str | None = None, ): create_key_request = create_key_request or CreateKeyRequest() self.previous_keys = [] - # Please keep in mind that tags of a key could be present in the request, they are not a part of metadata. At - # least in the sense of DescribeKey not returning them with the rest of the metadata. Instead, tags are more - # like aliases: - # https://docs.aws.amazon.com/kms/latest/APIReference/API_DescribeKey.html - # "DescribeKey does not return the following information: ... Tags on the KMS key." - self.tags = {} - self.add_tags(create_key_request.get("Tags")) - # Same goes for the policy. It is in the request, but not in the metadata. + # Policy is in the request but not in the metadata. self.policy = create_key_request.get("Policy") or self._get_default_key_policy( account_id, region ) @@ -312,17 +312,30 @@ def __init__( # disable it." self.is_key_rotation_enabled = False - self._populate_metadata(create_key_request, account_id, region) - custom_key_material = None - if TAG_KEY_CUSTOM_KEY_MATERIAL in self.tags: - # check if the _custom_key_material_ tag is specified, to use a custom key material for this key - custom_key_material = base64.b64decode(self.tags[TAG_KEY_CUSTOM_KEY_MATERIAL]) - # remove the _custom_key_material_ tag from the tags to not readily expose the custom key material - del self.tags[TAG_KEY_CUSTOM_KEY_MATERIAL] + self._populate_metadata(create_key_request, account_id, region, custom_key_id=custom_key_id) self.crypto_key = KmsCryptoKey(self.metadata.get("KeySpec"), custom_key_material) + self._internal_key_id = uuid.uuid4() + + # The KMS implementation always provides a crypto key with key material which doesn't suit scenarios where a + # KMS Key may have no key material e.g. for external keys. Don't expose the CurrentKeyMaterialId in those cases. + if custom_key_material or ( + self.metadata["Origin"] == "AWS_KMS" + and self.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT + ): + self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id( + self.crypto_key.key_material + ) + self.rotation_period_in_days = 365 self.next_rotation_date = None + def generate_key_material_id(self, key_material: bytes) -> str: + # The KeyMaterialId depends on the key material and the KeyId. Use an internal ID to prevent brute forcing + # the value of the key material from the public KeyId and KeyMaterialId. + # https://docs.aws.amazon.com/kms/latest/APIReference/API_ImportKeyMaterial.html + key_material_id_hex = uuid.uuid5(self._internal_key_id, key_material).hex + return str(key_material_id_hex) * 2 + def calculate_and_set_arn(self, account_id, region): self.metadata["Arn"] = kms_key_arn(self.metadata.get("KeyId"), account_id, region) @@ -417,17 +430,15 @@ def verify( def derive_shared_secret(self, public_key: bytes) -> bytes: key_spec = self.metadata.get("KeySpec") - match key_spec: - case KeySpec.ECC_NIST_P256 | KeySpec.ECC_SECG_P256K1: - algorithm = hashes.SHA256() - case KeySpec.ECC_NIST_P384: - algorithm = hashes.SHA384() - case KeySpec.ECC_NIST_P521: - algorithm = hashes.SHA512() - case _: - raise InvalidKeyUsageException( - f"{self.metadata['Arn']} key usage is {self.metadata['KeyUsage']} which is not valid for DeriveSharedSecret." - ) + if key_spec not in ( + KeySpec.ECC_NIST_P256, + KeySpec.ECC_SECG_P256K1, + KeySpec.ECC_NIST_P384, + KeySpec.ECC_NIST_P521, + ): + raise InvalidKeyUsageException( + f"{self.metadata['Arn']} key usage is {self.metadata['KeyUsage']} which is not valid for DeriveSharedSecret." + ) # Deserialize public key from DER encoded data to EllipticCurvePublicKey. try: @@ -435,14 +446,7 @@ def derive_shared_secret(self, public_key: bytes) -> bytes: except (UnsupportedAlgorithm, ValueError): raise ValidationException("") shared_secret = self.crypto_key.key.exchange(ec.ECDH(), pub_key) - # Perform shared secret derivation. - return HKDF( - algorithm=algorithm, - salt=None, - info=b"", - length=algorithm.digest_size, - backend=default_backend(), - ).derive(shared_secret) + return shared_secret # This method gets called when a key is replicated to another region. It's meant to populate the required metadata # fields in a new replica key. @@ -540,7 +544,11 @@ def _construct_sign_verify_padding( # # Data keys are symmetric, data key pairs are asymmetric. def _populate_metadata( - self, create_key_request: CreateKeyRequest, account_id: str, region: str + self, + create_key_request: CreateKeyRequest, + account_id: str, + region: str, + custom_key_id: str | None = None, ) -> None: self.metadata = KeyMetadata() # Metadata fields coming from a creation request @@ -577,9 +585,8 @@ def _populate_metadata( else KeyState.PendingImport ) - if TAG_KEY_CUSTOM_ID in self.tags: - # check if the _custom_id_ tag is specified, to set a user-defined KeyId for this key - self.metadata["KeyId"] = self.tags[TAG_KEY_CUSTOM_ID].strip() + if custom_key_id: + self.metadata["KeyId"] = custom_key_id elif self.metadata.get("MultiRegion"): # https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html # "Notice that multi-Region keys have a distinctive key ID that begins with mrk-. You can use the mrk- prefix to @@ -605,25 +612,6 @@ def _populate_metadata( ReplicaKeys=[], ) - def add_tags(self, tags: TagList) -> None: - # Just in case we get None from somewhere. - if not tags: - return - - unique_tag_keys = {tag["TagKey"] for tag in tags} - if len(unique_tag_keys) < len(tags): - raise TagException("Duplicate tag keys") - - if len(tags) > 50: - raise TagException("Too many tags") - - # Do not care if we overwrite an existing tag: - # https://docs.aws.amazon.com/kms/latest/APIReference/API_TagResource.html - # "To edit a tag, specify an existing tag key and a new tag value." - for i, tag in enumerate(tags, start=1): - validate_tag(i, tag) - self.tags[tag.get("TagKey")] = tag.get("TagValue") - def schedule_key_deletion(self, pending_window_in_days: int) -> None: self.metadata["Enabled"] = False # TODO For MultiRegion keys, the status of replicas get set to "PendingDeletion", while the primary key @@ -743,8 +731,16 @@ def rotate_key_on_demand(self): f"The on-demand rotations limit has been reached for the given keyId. " f"No more on-demand rotations can be performed for this key: {self.metadata['Arn']}" ) - self.previous_keys.append(self.crypto_key.key_material) - self.crypto_key = KmsCryptoKey(KeySpec.SYMMETRIC_DEFAULT) + current_key_material = self.crypto_key.key_material + pending_key_material = self.crypto_key.pending_key_material + + self.previous_keys.append(current_key_material) + + # If there is no pending material stored on the key, then key material will be generated. + self.crypto_key = KmsCryptoKey(KeySpec.SYMMETRIC_DEFAULT, pending_key_material) + self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id( + self.crypto_key.key_material + ) class KmsGrant: @@ -752,7 +748,7 @@ class KmsGrant: # keys. But, based on our understanding of AWS documentation for CreateGrant, ListGrants operations etc, # AWS has some set of fields for grants like it has for keys. So we are going to call them `metadata` here for # consistency. - metadata: Dict + metadata: dict[str, typing.Any] # dumped to JSON for persistence serialization # Tokens are not a part of metadata, as their use is more limited and specific than for the rest of the # metadata: https://docs.aws.amazon.com/kms/latest/developerguide/grant-manage.html#using-grant-token # Tokens are used to refer to a grant in a short period right after the grant gets created. Normally it might @@ -797,18 +793,19 @@ def __init__(self, create_grant_request: CreateGrantRequest, account_id: str, re class KmsAlias: # Like with grants (see comment for KmsGrant), there is no mention of some specific object modeling metadata # for KMS aliases. But there is data that is some metadata, so we model it in a way similar to KeyMetadata for keys. - metadata: Dict + metadata: dict[str, typing.Any] # dumped to JSON for persistence serialization def __init__( self, - create_alias_request: CreateAliasRequest = None, - account_id: str = None, - region: str = None, + create_alias_request: CreateAliasRequest | None = None, + account_id: str | None = None, + region: str | None = None, ): create_alias_request = create_alias_request or CreateAliasRequest() - self.metadata = {} - self.metadata["AliasName"] = create_alias_request.get("AliasName") - self.metadata["TargetKeyId"] = create_alias_request.get("TargetKeyId") + self.metadata = { + "AliasName": create_alias_request.get("AliasName"), + "TargetKeyId": create_alias_request.get("TargetKeyId"), + } self.update_date_of_last_update() self.metadata["CreationDate"] = self.metadata["LastUpdateDate"] self.metadata["AliasArn"] = kms_alias_arn(self.metadata["AliasName"], account_id, region) @@ -827,25 +824,26 @@ class KeyImportState: class KmsStore(BaseStore): # maps key ids to keys - keys: Dict[str, KmsKey] = LocalAttribute(default=dict) + keys: dict[str, KmsKey] = LocalAttribute(default=dict) # According to AWS documentation on grants https://docs.aws.amazon.com/kms/latest/APIReference/API_RetireGrant.html # "Cross-account use: Yes. You can retire a grant on a KMS key in a different AWS account." # maps grant ids to grants - grants: Dict[str, KmsGrant] = LocalAttribute(default=dict) - - # maps from (grant names (used for idempotency), key id) to grant ids - grant_names: Dict[Tuple[str, str], str] = LocalAttribute(default=dict) + # TODO: KmsKey might hold the grant + grants: dict[str, KmsGrant] = LocalAttribute(default=dict) # maps grant tokens to grant ids - grant_tokens: Dict[str, str] = LocalAttribute(default=dict) + grant_tokens: dict[str, str] = LocalAttribute(default=dict) # maps key alias names to aliases - aliases: Dict[str, KmsAlias] = LocalAttribute(default=dict) + aliases: dict[str, KmsAlias] = LocalAttribute(default=dict) # maps import tokens to import data - imports: Dict[str, KeyImportState] = LocalAttribute(default=dict) + imports: dict[str, KeyImportState] = LocalAttribute(default=dict) + + # maps key arn to tags + tags: Tags = LocalAttribute(default=Tags) kms_stores = AccountRegionBundle("kms", KmsStore) diff --git a/localstack-core/localstack/services/kms/provider.py b/localstack-core/localstack/services/kms/provider.py index a243e1d7fcea6..e3b8af6530b51 100644 --- a/localstack-core/localstack/services/kms/provider.py +++ b/localstack-core/localstack/services/kms/provider.py @@ -3,11 +3,14 @@ import datetime import logging import os -from typing import Dict, Tuple +from cbor2 import loads as cbor2_loads from cryptography.exceptions import InvalidTag -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, keywrap from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.serialization import load_der_public_key from localstack.aws.api import CommonServiceException, RequestContext, handler from localstack.aws.api.kms import ( @@ -33,6 +36,7 @@ DisabledException, DisableKeyRequest, DisableKeyRotationRequest, + DryRunModifierList, EnableKeyRequest, EnableKeyRotationRequest, EncryptionAlgorithmSpec, @@ -85,6 +89,7 @@ MacAlgorithmSpec, MarkerType, MultiRegionKey, + MultiRegionKeyType, NotFoundException, NullableBooleanType, OriginType, @@ -102,6 +107,9 @@ ScheduleKeyDeletionResponse, SignRequest, SignResponse, + Tag, + TagKeyList, + TagList, TagResourceRequest, UnsupportedOperationException, UntagResourceRequest, @@ -129,14 +137,19 @@ ) from localstack.services.kms.utils import ( execute_dry_run_capable, + get_custom_key_id, + get_custom_key_material, is_valid_key_arn, parse_key_arn, validate_alias_name, + validate_and_filter_tags, ) from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.aws.arns import get_partition, kms_alias_arn, parse_arn from localstack.utils.collections import PaginatedList from localstack.utils.common import select_attributes +from localstack.utils.crypto import pkcs7_envelope_encrypt from localstack.utils.strings import short_uid, to_bytes, to_str LOG = logging.getLogger(__name__) @@ -196,6 +209,9 @@ class KmsProvider(KmsApi, ServiceLifecycleHook): - VerifyMac """ + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(kms_stores) + # # Helpers # @@ -216,7 +232,13 @@ def _create_kms_key( account_id: str, region_name: str, request: CreateKeyRequest = None ) -> KmsKey: store = kms_stores[account_id][region_name] - key = KmsKey(request, account_id, region_name) + key = KmsKey( + request, + account_id, + region_name, + get_custom_key_material(request), + get_custom_key_id(request), + ) key_id = key.metadata["KeyId"] store.keys[key_id] = key return key @@ -286,11 +308,9 @@ def _create_alias_if_reserved_and_not_exists( store = kms_stores[account_id][region_name] if alias_name not in RESERVED_ALIASES or alias_name in store.aliases: return - create_key_request = {} key_id = KmsProvider._create_kms_key( account_id, region_name, - create_key_request, ).metadata.get("KeyId") create_alias_request = CreateAliasRequest(AliasName=alias_name, TargetKeyId=key_id) KmsProvider._create_kms_alias(account_id, region_name, create_alias_request) @@ -358,7 +378,7 @@ def _get_kms_alias(account_id: str, region_name: str, alias_name_or_arn: str) -> return store.aliases.get(alias_name) @staticmethod - def _parse_key_id(key_id_or_arn: str, context: RequestContext) -> Tuple[str, str, str]: + def _parse_key_id(key_id_or_arn: str, context: RequestContext) -> tuple[str, str, str]: """ Return locator attributes (account ID, region_name, key ID) of a given KMS key. @@ -380,6 +400,26 @@ def _parse_key_id(key_id_or_arn: str, context: RequestContext) -> Tuple[str, str def _is_rsa_spec(key_spec: str) -> bool: return key_spec in [KeySpec.RSA_2048, KeySpec.RSA_3072, KeySpec.RSA_4096] + def _get_key_tags(self, account_id: str, region: str, resource_arn: str) -> TagList: + store = self._get_store(account_id, region) + return [ + Tag(TagKey=key, TagValue=value) + for key, value in store.tags.get_tags(resource_arn).items() + ] + + def _set_key_tags(self, account_id: str, region: str, resource_arn: str, tags: TagList) -> None: + validated_tags = validate_and_filter_tags(tags) + store = self._get_store(account_id, region) + store.tags.update_tags( + resource_arn, {tag["TagKey"]: tag["TagValue"] for tag in validated_tags} + ) + + def _remove_key_tags( + self, account_id: str, region: str, resource_arn: str, tag_keys: TagKeyList + ) -> None: + store = self._get_store(account_id, region) + store.tags.delete_tags(resource_arn, tag_keys) + # # Operation Handlers # @@ -390,7 +430,10 @@ def create_key( context: RequestContext, request: CreateKeyRequest = None, ) -> CreateKeyResponse: + tags = validate_and_filter_tags(request.get("Tags", [])) key = self._create_kms_key(context.account_id, context.region, request) + if tags: + self._set_key_tags(context.account_id, context.region, key.metadata["Arn"], tags) return CreateKeyResponse(KeyMetadata=key.metadata) @handler("ScheduleKeyDeletion", expand=False) @@ -490,26 +533,39 @@ def replicate_key( self, context: RequestContext, request: ReplicateKeyRequest ) -> ReplicateKeyResponse: account_id = context.account_id - key = self._get_kms_key(account_id, context.region, request.get("KeyId")) - key_id = key.metadata.get("KeyId") - if not key.metadata.get("MultiRegion"): + primary_key = self._get_kms_key(account_id, context.region, request.get("KeyId")) + key_id = primary_key.metadata.get("KeyId") + key_arn = primary_key.metadata.get("Arn") + if not primary_key.metadata.get("MultiRegion"): raise UnsupportedOperationException( f"Unable to replicate a non-MultiRegion key {key_id}" ) replica_region = request.get("ReplicaRegion") replicate_to_store = kms_stores[account_id][replica_region] + + if ( + primary_key.metadata.get("MultiRegionConfiguration", {}).get("MultiRegionKeyType") + != MultiRegionKeyType.PRIMARY + ): + raise UnsupportedOperationException(f"{key_arn} is not a multi-region primary key.") + if key_id in replicate_to_store.keys: raise AlreadyExistsException( f"Unable to replicate key {key_id} to region {replica_region}, as the key " f"already exist there" ) - replica_key = copy.deepcopy(key) + replica_key = copy.deepcopy(primary_key) replica_key.replicate_metadata(request, account_id, replica_region) replicate_to_store.keys[key_id] = replica_key - self.update_primary_key_with_replica_keys(key, replica_key, replica_region) + self.update_primary_key_with_replica_keys(primary_key, replica_key, replica_region) - return ReplicateKeyResponse(ReplicaKeyMetadata=replica_key.metadata) + # CurrentKeyMaterialId is not returned in the ReplicaKeyMetadata. May be due to not being evaluated until + # the key has been successfully replicated as it does not show up in DescribeKey immediately either. + replica_key_metadata_response = copy.deepcopy(replica_key.metadata) + replica_key_metadata_response.pop("CurrentKeyMaterialId", None) + + return ReplicateKeyResponse(ReplicaKeyMetadata=replica_key_metadata_response) @staticmethod # Adds new multi region replica key to the primary key's metadata. @@ -549,14 +605,21 @@ def create_grant( grant_name = request.get("Name") store = self._get_store(context.account_id, context.region) - if grant_name and (grant_name, key_id) in store.grant_names: - grant = store.grants[store.grant_names[(grant_name, key_id)]] - else: + + # Check for existing grant with the same name for idempotency + grant: KmsGrant | None = None + if grant_name: + for existing_grant in store.grants.values(): + if existing_grant.metadata.get("Name") != grant_name: + continue + if existing_grant.metadata.get("KeyId") == key_id: + grant = existing_grant + break + + if grant is None: grant = KmsGrant(request, context.account_id, context.region) grant_id = grant.metadata["GrantId"] store.grants[grant_id] = grant - if grant_name: - store.grant_names[(grant_name, key_id)] = grant_id store.grant_tokens[grant.token] = grant_id # At the moment we do not support multiple GrantTokens for grant creation request. Instead, we always use @@ -621,7 +684,6 @@ def _delete_grant(store: KmsStore, grant_id: str, key_id: str): raise ValidationError(f"Invalid KeyId={key_id} specified for grant {grant_id}") store.grant_tokens.pop(grant.token) - store.grant_names.pop((grant.metadata.get("Name"), key_id), None) store.grants.pop(grant_id) def revoke_grant( @@ -947,15 +1009,16 @@ def verify(self, context: RequestContext, request: VerifyRequest) -> VerifyRespo def re_encrypt( self, context: RequestContext, - ciphertext_blob: CiphertextType, destination_key_id: KeyIdType, - source_encryption_context: EncryptionContextType = None, - source_key_id: KeyIdType = None, - destination_encryption_context: EncryptionContextType = None, - source_encryption_algorithm: EncryptionAlgorithmSpec = None, - destination_encryption_algorithm: EncryptionAlgorithmSpec = None, - grant_tokens: GrantTokenList = None, - dry_run: NullableBooleanType = None, + ciphertext_blob: CiphertextType | None = None, + source_encryption_context: EncryptionContextType | None = None, + source_key_id: KeyIdType | None = None, + destination_encryption_context: EncryptionContextType | None = None, + source_encryption_algorithm: EncryptionAlgorithmSpec | None = None, + destination_encryption_algorithm: EncryptionAlgorithmSpec | None = None, + grant_tokens: GrantTokenList | None = None, + dry_run: NullableBooleanType | None = None, + dry_run_modifiers: DryRunModifierList | None = None, **kwargs, ) -> ReEncryptResponse: # TODO: when implementing, ensure cross-account support for source_key_id and destination_key_id @@ -1025,13 +1088,14 @@ def encrypt( def decrypt( self, context: RequestContext, - ciphertext_blob: CiphertextType, - encryption_context: EncryptionContextType = None, - grant_tokens: GrantTokenList = None, - key_id: KeyIdType = None, - encryption_algorithm: EncryptionAlgorithmSpec = None, - recipient: RecipientInfo = None, - dry_run: NullableBooleanType = None, + ciphertext_blob: CiphertextType | None = None, + encryption_context: EncryptionContextType | None = None, + grant_tokens: GrantTokenList | None = None, + key_id: KeyIdType | None = None, + encryption_algorithm: EncryptionAlgorithmSpec | None = None, + recipient: RecipientInfo | None = None, + dry_run: NullableBooleanType | None = None, + dry_run_modifiers: DryRunModifierList | None = None, **kwargs, ) -> DecryptResponse: # In AWS, key_id is only supplied for data encrypted with an asymmetrical algorithm. For symmetrical @@ -1043,7 +1107,8 @@ def decrypt( account_id, region_name, key_id = self._parse_key_id(key_id, context) try: ciphertext = deserialize_ciphertext_blob(ciphertext_blob=ciphertext_blob) - except Exception: + except Exception as e: + logging.error("Error deserializing ciphertext blob: %s", e) ciphertext = None pass else: @@ -1065,6 +1130,25 @@ def decrypt( self._validate_key_for_encryption_decryption(context, key) self._validate_key_state_not_pending_import(key) + # Handle the recipient field. This is used by AWS Nitro to re-encrypt the plaintext to the key specified + # by the enclave. Proper support for this will take significant work to figure out how to model enforcing + # the attestation measurements; for now, if recipient is specified and has an attestation doc in it including + # a public key where it's expected to be, we encrypt to that public key. This at least allows users to use + # localstack as a drop-in replacement for AWS when testing without having to skip the secondary decryption + # when using localstack. + recipient_pubkey = None + if recipient: + attestation_document = recipient["AttestationDocument"] + # We do all of this in a try/catch and warn if it fails so that if users are currently passing a nonsense + # value we don't break it for them. In the future we could do a breaking change to require a valid attestation + # (or at least one that contains the public key). + try: + recipient_pubkey = self._extract_attestation_pubkey(attestation_document) + except Exception as e: + logging.warning( + "Unable to extract public key from non-empty attestation document: %s", e + ) + try: # TODO: Extend the implementation to handle additional encryption/decryption scenarios # beyond the current support for offline encryption and online decryption using RSA keys if key id exists in @@ -1072,23 +1156,33 @@ def decrypt( if self._is_rsa_spec(key.crypto_key.key_spec) and not ciphertext: plaintext = key.decrypt_rsa(ciphertext_blob) else: + # if symmetric encryption then ciphertext must not be None + if ciphertext is None: + raise InvalidCiphertextException() plaintext = key.decrypt(ciphertext, encryption_context) except InvalidTag: raise InvalidCiphertextException() + # For compatibility, we return EncryptionAlgorithm values expected from AWS. But LocalStack currently always # encrypts with symmetric encryption no matter the key settings. # # We return a key ARN instead of KeyId despite the name of the parameter, as this is what AWS does and states # in its docs. - # TODO add support for "recipient" # https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html#API_Decrypt_RequestSyntax # TODO add support for "dry_run" - return DecryptResponse( + response = DecryptResponse( KeyId=key.metadata.get("Arn"), - Plaintext=plaintext, EncryptionAlgorithm=encryption_algorithm, ) + # Encrypt to the recipient pubkey if specified. Otherwise, return the actual plaintext + if recipient_pubkey: + response["CiphertextForRecipient"] = pkcs7_envelope_encrypt(plaintext, recipient_pubkey) + else: + response["Plaintext"] = plaintext + + return response + def get_parameters_for_import( self, context: RequestContext, @@ -1160,28 +1254,13 @@ def import_key_material( disabled_key_allowed=True, ) - if import_state.wrapping_algo == AlgorithmSpec.RSAES_PKCS1_V1_5: - decrypt_padding = padding.PKCS1v15() - elif import_state.wrapping_algo == AlgorithmSpec.RSAES_OAEP_SHA_1: - decrypt_padding = padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None) - elif import_state.wrapping_algo == AlgorithmSpec.RSAES_OAEP_SHA_256: - decrypt_padding = padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None) - else: - raise KMSInvalidStateException( - f"Unsupported padding, requested wrapping algorithm:'{import_state.wrapping_algo}'" - ) - # TODO check if there was already a key imported for this kms key # if so, it has to be identical. We cannot change keys by reimporting after deletion/expiry - key_material = import_state.key.crypto_key.key.decrypt( - encrypted_key_material, decrypt_padding + key_material = self._decrypt_wrapped_key_material(import_state, encrypted_key_material) + key_material_id = key_to_import_material_to.generate_key_material_id(key_material) + key_to_import_material_to.metadata["ExpirationModel"] = ( + expiration_model or ExpirationModelType.KEY_MATERIAL_EXPIRES ) - if expiration_model: - key_to_import_material_to.metadata["ExpirationModel"] = expiration_model - else: - key_to_import_material_to.metadata["ExpirationModel"] = ( - ExpirationModelType.KEY_MATERIAL_EXPIRES - ) if ( key_to_import_material_to.metadata["ExpirationModel"] == ExpirationModelType.KEY_MATERIAL_EXPIRES @@ -1190,12 +1269,42 @@ def import_key_material( raise ValidationException( "A validTo date must be set if the ExpirationModel is KEY_MATERIAL_EXPIRES" ) + if existing_pending_material := key_to_import_material_to.crypto_key.pending_key_material: + pending_key_material_id = key_to_import_material_to.generate_key_material_id( + existing_pending_material + ) + raise KMSInvalidStateException( + f"New key material (id: {key_material_id}) cannot be imported into KMS key " + f"{key_to_import_material_to.metadata['Arn']}, because another key material " + f"(id: {pending_key_material_id}) is pending rotation." + ) + # TODO actually set validTo and make the key expire key_to_import_material_to.metadata["Enabled"] = True key_to_import_material_to.metadata["KeyState"] = KeyState.Enabled key_to_import_material_to.crypto_key.load_key_material(key_material) - return ImportKeyMaterialResponse() + # KeyMaterialId / CurrentKeyMaterialId is only exposed for symmetric encryption keys. + key_material_id_response = None + if key_to_import_material_to.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT: + key_material_id_response = key_to_import_material_to.generate_key_material_id( + key_material + ) + + # If there is no CurrentKeyMaterialId, instantly promote the pending key material to the current. + if key_to_import_material_to.metadata.get("CurrentKeyMaterialId") is None: + key_to_import_material_to.metadata["CurrentKeyMaterialId"] = ( + key_material_id_response + ) + key_to_import_material_to.crypto_key.key_material = ( + key_to_import_material_to.crypto_key.pending_key_material + ) + key_to_import_material_to.crypto_key.pending_key_material = None + + return ImportKeyMaterialResponse( + KeyId=key_to_import_material_to.metadata["Arn"], + KeyMaterialId=key_material_id_response, + ) def delete_imported_key_material( self, @@ -1322,7 +1431,7 @@ def get_key_rotation_status( key = self._get_kms_key(account_id, region_name, key_id, any_key_state_allowed=True) response = GetKeyRotationStatusResponse( - KeyId=key_id, + KeyId=key.metadata["Arn"], KeyRotationEnabled=key.is_key_rotation_enabled, NextRotationDate=key.next_rotation_date, ) @@ -1394,14 +1503,16 @@ def list_resource_tags( context.account_id, context.region, request.get("KeyId"), any_key_state_allowed=True ) keys_list = PaginatedList( - [{"TagKey": tag_key, "TagValue": tag_value} for tag_key, tag_value in key.tags.items()] + self._get_key_tags(context.account_id, context.region, key.metadata["Arn"]) ) page, next_token = keys_list.get_page( lambda tag: tag.get("TagKey"), next_token=request.get("Marker"), page_size=request.get("Limit", 50), ) - kwargs = {"NextMarker": next_token, "Truncated": True} if next_token else {} + kwargs = ( + {"NextMarker": next_token, "Truncated": True} if next_token else {"Truncated": False} + ) return ListResourceTagsResponse(Tags=page, **kwargs) @handler("RotateKeyOnDemand", expand=False) @@ -1414,15 +1525,13 @@ def rotate_key_on_demand( if key.metadata["KeySpec"] != KeySpec.SYMMETRIC_DEFAULT: raise UnsupportedOperationException() - if key.metadata["Origin"] == OriginType.EXTERNAL: - raise UnsupportedOperationException( - f"{key.metadata['Arn']} origin is EXTERNAL which is not valid for this operation." - ) + self._validate_key_state_not_pending_import(key) + self._validate_external_key_has_pending_material(key) key.rotate_key_on_demand() return RotateKeyOnDemandResponse( - KeyId=key_id, + KeyId=key.metadata["Arn"], ) @handler("TagResource", expand=False) @@ -1434,7 +1543,8 @@ def tag_resource(self, context: RequestContext, request: TagResourceRequest) -> enabled_key_allowed=True, disabled_key_allowed=True, ) - key.add_tags(request.get("Tags")) + if tags := request["Tags"]: + self._set_key_tags(context.account_id, context.region, key.metadata["Arn"], tags) @handler("UntagResource", expand=False) def untag_resource(self, context: RequestContext, request: UntagResourceRequest) -> None: @@ -1445,11 +1555,9 @@ def untag_resource(self, context: RequestContext, request: UntagResourceRequest) enabled_key_allowed=True, disabled_key_allowed=True, ) - if not request.get("TagKeys"): - return - for tag_key in request.get("TagKeys"): - # AWS doesn't seem to mind removal of a non-existent tag, so we do not raise any exception. - key.tags.pop(tag_key, None) + self._remove_key_tags( + context.account_id, context.region, key.metadata["Arn"], request["TagKeys"] + ) def derive_shared_secret( self, @@ -1499,6 +1607,12 @@ def _validate_key_state_not_pending_import(self, key: KmsKey): if key.metadata["KeyState"] == KeyState.PendingImport: raise KMSInvalidStateException(f"{key.metadata['Arn']} is pending import.") + def _validate_external_key_has_pending_material(self, key: KmsKey): + if key.metadata["Origin"] == "EXTERNAL" and key.crypto_key.pending_key_material is None: + raise KMSInvalidStateException( + f"No available key material pending rotation for the key: {key.metadata['Arn']}." + ) + def _validate_key_for_encryption_decryption(self, context: RequestContext, key: KmsKey): key_usage = key.metadata["KeyUsage"] if key_usage != "ENCRYPT_DECRYPT": @@ -1549,7 +1663,7 @@ def _validate_plaintext_length(self, plaintext: bytes): "Member must have length less than or equal to 4096" ) - def _validate_grant_request(self, data: Dict): + def _validate_grant_request(self, data: dict): if "KeyId" not in data or "GranteePrincipal" not in data or "Operations" not in data: raise ValidationError("Grant ID, key ID and grantee principal must be specified") @@ -1560,6 +1674,56 @@ def _validate_grant_request(self, data: Dict): f" constraint: [Member must satisfy enum value set: {VALID_OPERATIONS}]" ) + def _extract_attestation_pubkey(self, attestation_document: bytes) -> RSAPublicKey: + # The attestation document comes as a COSE (CBOR Object Signing and Encryption) object: the CBOR + # attestation is signed and then the attestation and signature are again CBOR-encoded. For now + # we don't bother validating the signature, though in the future we could. + cose_document = cbor2_loads(attestation_document) + attestation = cbor2_loads(cose_document[2]) + public_key_bytes = attestation["public_key"] + return load_der_public_key(public_key_bytes) + + def _decrypt_wrapped_key_material( + self, + import_state: KeyImportState, + encrypted_key_material: CiphertextType, + ) -> bytes: + algo = import_state.wrapping_algo + decrypt_key = import_state.key.crypto_key.key + + match algo: + case AlgorithmSpec.RSAES_PKCS1_V1_5: + padding_scheme = padding.PKCS1v15() + return decrypt_key.decrypt(encrypted_key_material, padding_scheme) + case AlgorithmSpec.RSAES_OAEP_SHA_1: + padding_scheme = padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None) + return decrypt_key.decrypt(encrypted_key_material, padding_scheme) + case AlgorithmSpec.RSAES_OAEP_SHA_256: + padding_scheme = padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None) + return decrypt_key.decrypt(encrypted_key_material, padding_scheme) + case AlgorithmSpec.RSA_AES_KEY_WRAP_SHA_256: + rsa_key_size_bytes = decrypt_key.key_size // 8 + wrapped_aes_key = encrypted_key_material[:rsa_key_size_bytes] + wrapped_key_material = encrypted_key_material[rsa_key_size_bytes:] + + aes_key = decrypt_key.decrypt( + wrapped_aes_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + + return keywrap.aes_key_unwrap_with_padding( + aes_key, wrapped_key_material, default_backend() + ) + + case _: + raise KMSInvalidStateException( + f"Unsupported padding, requested wrapping algorithm: '{algo}'" + ) + def _validate_plaintext_key_type_based( self, plaintext: PlaintextType, diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py index 81ecef65ca520..a039747e815eb 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,8 +14,8 @@ class KMSAliasProperties(TypedDict): - AliasName: Optional[str] - TargetKeyId: Optional[str] + AliasName: str | None + TargetKeyId: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py index 172d4915576ce..4a5c0177d1fcb 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KMSAliasProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::KMS::Alias" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kms.resource_providers.aws_kms_alias import KMSAliasProvider diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py index 6228292ed2953..cce09f36459a9 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,22 +15,22 @@ class KMSKeyProperties(TypedDict): - KeyPolicy: Optional[dict | str] - Arn: Optional[str] - Description: Optional[str] - EnableKeyRotation: Optional[bool] - Enabled: Optional[bool] - KeyId: Optional[str] - KeySpec: Optional[str] - KeyUsage: Optional[str] - MultiRegion: Optional[bool] - PendingWindowInDays: Optional[int] - Tags: Optional[list[Tag]] + KeyPolicy: dict | str | None + Arn: str | None + Description: str | None + EnableKeyRotation: bool | None + Enabled: bool | None + KeyId: str | None + KeySpec: str | None + KeyUsage: str | None + MultiRegion: bool | None + PendingWindowInDays: int | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py index a03c3c714af8c..13f2a28e7a517 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KMSKeyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::KMS::Key" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kms.resource_providers.aws_kms_key import KMSKeyProvider diff --git a/localstack-core/localstack/services/kms/utils.py b/localstack-core/localstack/services/kms/utils.py index ae9ff4580caa1..e51c080a89b4a 100644 --- a/localstack-core/localstack/services/kms/utils.py +++ b/localstack-core/localstack/services/kms/utils.py @@ -1,15 +1,23 @@ +import base64 import re -from typing import Callable, Tuple, TypeVar - -from localstack.aws.api.kms import DryRunOperationException, Tag, TagException +from collections.abc import Callable + +from localstack.aws.api.kms import ( + CreateKeyRequest, + DryRunOperationException, + Tag, + TagException, + TagList, +) +from localstack.constants import TAG_KEY_CUSTOM_ID from localstack.services.kms.exceptions import ValidationException from localstack.utils.aws.arns import ARN_PARTITION_REGEX -T = TypeVar("T") - KMS_KEY_ARN_PATTERN = re.compile( - rf"{ARN_PARTITION_REGEX}:kms:(?P[^:]+):(?P\d{{12}}):key\/(?P[^:]+)$" + rf"{ARN_PARTITION_REGEX}:kms:(?P[^:]+):(?P\d{{12}}):((?=key/)key/|(?=alias/))(?P[^:]+)$" ) +# special tag name to allow specifying a custom key material for created keys +TAG_KEY_CUSTOM_KEY_MATERIAL = "_custom_key_material_" def get_hash_algorithm(signing_algorithm: str) -> str: @@ -20,7 +28,7 @@ def get_hash_algorithm(signing_algorithm: str) -> str: return "_".join(signing_algorithm.rsplit(sep="_", maxsplit=-2)[-2:]) -def parse_key_arn(key_arn: str) -> Tuple[str, str, str]: +def parse_key_arn(key_arn: str) -> tuple[str, str, str]: """ Parse a valid KMS key arn into its constituents. @@ -62,7 +70,63 @@ def validate_tag(tag_position: int, tag: Tag) -> None: raise TagException("Tags beginning with aws: are reserved") -def execute_dry_run_capable(func: Callable[..., T], dry_run: bool, *args, **kwargs) -> T: +def validate_and_filter_tags(tag_list: TagList) -> TagList: + """ + Validates tags and filters out LocalStack specific tags + + :param tag_list: The list of tags provided to apply to the KMS key. + :returns: Filtered and validated list of tags to apply to the KMS key. + """ + unique_tag_keys = {tag["TagKey"] for tag in tag_list} + if len(unique_tag_keys) < len(tag_list): + raise TagException("Duplicate tag keys") + if len(tag_list) > 50: + raise TagException("Too many tags") + + validated_tags = [] + for i, tag in enumerate(tag_list, start=1): + if tag["TagKey"] != TAG_KEY_CUSTOM_KEY_MATERIAL: + validate_tag(i, tag) + validated_tags.append(tag) + + return validated_tags + + +def get_custom_key_material(request: CreateKeyRequest | None) -> bytes | None: + """ + Retrieves custom material which is sent in a CreateKeyRequest via the Tags. + + :param request: The request for creating a KMS key. + :returns: Custom key material for the KMS key. + """ + if not request: + return None + + tags = request.get("Tags", []) + for tag in tags: + if tag["TagKey"] == TAG_KEY_CUSTOM_KEY_MATERIAL: + return base64.b64decode(tag["TagValue"]) + return None + + +def get_custom_key_id(request: CreateKeyRequest | None) -> bytes | None: + """ + Retrieves a custom Key ID for the KMS key which is sent in a CreateKeyRequest via the Tags. + + :param request: The request for creating a KMS key. + :returns: THe custom Key ID for the KMS key. + """ + if not request: + return None + + tags = request.get("Tags", []) + for tag in tags: + if tag["TagKey"] == TAG_KEY_CUSTOM_ID: + return tag["TagValue"] + return None + + +def execute_dry_run_capable[T](func: Callable[..., T], dry_run: bool, *args, **kwargs) -> T: """ Executes a function unless dry run mode is enabled. diff --git a/localstack-core/localstack/services/lambda_/analytics.py b/localstack-core/localstack/services/lambda_/analytics.py index ff4a1ae6f516c..df100137e87c7 100644 --- a/localstack-core/localstack/services/lambda_/analytics.py +++ b/localstack-core/localstack/services/lambda_/analytics.py @@ -14,9 +14,10 @@ "status", "runtime", "package_type", - # only for operation "invoke" - "invocation_type", + "invocation_type", # only for operation "invoke", otherwise "n/a" + "initialization_type", ], + schema_version=2, ) @@ -38,6 +39,15 @@ class FunctionStatus(StrEnum): invocation_error = "invocation_error" +class FunctionInitializationType(StrEnum): + # Maps to the Lambda environment variable AWS_LAMBDA_INITIALIZATION_TYPE + # Matches with lambda_models.InitializationType + on_demand = "on-demand" + lambda_managed_instances = "lambda-managed-instances" + # Only applies to the operation "invoke" because provisioned concurrency is not configured on "create" + provisioned_concurrency = "provisioned-concurrency" + + esm_counter = LabeledCounter(namespace=NAMESPACE, name="esm", labels=["source", "status"]) diff --git a/localstack-core/localstack/services/lambda_/api_utils.py b/localstack-core/localstack/services/lambda_/api_utils.py index bc573c5e019f6..cfb507a7111c5 100644 --- a/localstack-core/localstack/services/lambda_/api_utils.py +++ b/localstack-core/localstack/services/lambda_/api_utils.py @@ -3,11 +3,14 @@ """ import datetime +import hashlib +import json import random import re import string -from typing import TYPE_CHECKING, Any, Optional, Tuple +from typing import TYPE_CHECKING, Any +from localstack import config from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api import lambda_ as api_spec from localstack.aws.api.lambda_ import ( @@ -61,13 +64,14 @@ r"^$|arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\-])+:([a-z]{2}(-gov)?-[a-z]+-\d{1})?:(\d{12})?:(.*)" ) +# TODO: what's the difference between AWS_FUNCTION_NAME_REGEX and FUNCTION_NAME_REGEX? Can we unify? AWS_FUNCTION_NAME_REGEX = re.compile( - "^(arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?$" + "^(arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?$" ) # Pattern for extracting various attributes from a full or partial ARN or just a function name. FUNCTION_NAME_REGEX = re.compile( - r"(arn:(aws[a-zA-Z-]*):lambda:)?((?P[a-z]{2}(-gov)?-[a-z]+-\d{1}):)?(?:(?P\d{12}):)?(function:)?(?P[a-zA-Z0-9-_\.]+)(:(?P\$LATEST|[a-zA-Z0-9-_]+))?" + r"(arn:(aws[a-zA-Z-]*):lambda:)?((?P[a-z]{2}(-gov)?-[a-z]+-\d{1}):)?(?:(?P\d{12}):)?(function:)?(?P[a-zA-Z0-9-_\.]+)(:(?P\$LATEST(\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ) # also length 1-170 incl. # Pattern for a lambda function handler HANDLER_REGEX = re.compile(r"[^\s]+") @@ -85,9 +89,11 @@ SIGNING_PROFILE_VERSION_ARN_REGEX = re.compile( r"arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\-])+:([a-z]{2}(-gov)?-[a-z]+-\d{1})?:(\d{12})?:(.*)" ) -# Combined pattern for alias and version based on AWS error using "(|[a-zA-Z0-9$_-]+)" -QUALIFIER_REGEX = re.compile(r"(^[a-zA-Z0-9$_-]+$)") +# Combined pattern for alias and version based on AWS error using "\\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+" +# This regex is based on the snapshotted validation message, just removing the double \\ before $LATEST +QUALIFIER_REGEX = re.compile(r"^\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+$") # Pattern for a version qualifier +# TODO: do we need to consider $LATEST.PUBLISHED here? VERSION_REGEX = re.compile(r"^[0-9]+$") # Pattern for an alias qualifier # Rules: https://docs.aws.amazon.com/lambda/latest/dg/API_CreateAlias.html#SSS-CreateAlias-request-Name @@ -106,36 +112,42 @@ # An unordered list of all Lambda CPU architectures supported by LocalStack. ARCHITECTURES = [Architecture.arm64, Architecture.x86_64] -# ARN pattern returned in validation exception messages. -# Some excpetions from AWS return a '\.' in the function name regex -# pattern therefore we can sub this value in when appropriate. -ARN_NAME_PATTERN_VALIDATION_TEMPLATE = "(arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{{2}}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{{1}}:)?(\\d{{12}}:)?(function:)?([a-zA-Z0-9-_{0}]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" +# ARN patterns returned in validation exception messages +ARN_NAME_PATTERN_GET = r"(arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_\.]+)(:(\$LATEST(\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" +ARN_NAME_PATTERN_CREATE = r"(arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?" # AWS response when invalid ARNs are used in Tag operations. -TAGGABLE_RESOURCE_ARN_PATTERN = "arn:(aws[a-zA-Z-]*):lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" +TAGGABLE_RESOURCE_ARN_PATTERN = "arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)" def validate_function_name(function_name_or_arn: str, operation_type: str): function_name, *_ = function_locators_from_arn(function_name_or_arn) - arn_name_pattern = ARN_NAME_PATTERN_VALIDATION_TEMPLATE.format("") + arn_name_pattern = ARN_NAME_PATTERN_CREATE max_length = 170 - match operation_type: - case "GetFunction" | "Invoke": - arn_name_pattern = ARN_NAME_PATTERN_VALIDATION_TEMPLATE.format(r"\.") - case "CreateFunction" if function_name == function_name_or_arn: # only a function name + if operation_type == "GetFunction" or operation_type == "Invoke": + arn_name_pattern = ARN_NAME_PATTERN_GET + elif operation_type == "CreateFunction": + # https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#lambda-CreateFunction-request-FunctionName + if function_name == function_name_or_arn: # only a function name max_length = 64 - case "CreateFunction" | "DeleteFunction": + else: # full or partial ARN max_length = 140 + elif operation_type == "DeleteFunction": + max_length = 140 + arn_name_pattern = ARN_NAME_PATTERN_GET validations = [] - if len(function_name_or_arn) > max_length: - constraint = f"Member must have length less than or equal to {max_length}" + if not AWS_FUNCTION_NAME_REGEX.match(function_name_or_arn) or not function_name: + constraint = f"Member must satisfy regular expression pattern: {arn_name_pattern}" validation_msg = f"Value '{function_name_or_arn}' at 'functionName' failed to satisfy constraint: {constraint}" validations.append(validation_msg) + if not operation_type == "CreateFunction": + # Immediately raises rather than summarizing all validations, except for CreateFunction + return validations - if not AWS_FUNCTION_NAME_REGEX.match(function_name_or_arn) or not function_name: - constraint = f"Member must satisfy regular expression pattern: {arn_name_pattern}" + if len(function_name_or_arn) > max_length: + constraint = f"Member must have length less than or equal to {max_length}" validation_msg = f"Value '{function_name_or_arn}' at 'functionName' failed to satisfy constraint: {constraint}" validations.append(validation_msg) @@ -153,7 +165,7 @@ def validate_qualifier(qualifier: str): validations.append(validation_msg) if not QUALIFIER_REGEX.match(qualifier): - constraint = "Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)" + constraint = "Member must satisfy regular expression pattern: \\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+" validation_msg = ( f"Value '{qualifier}' at 'qualifier' failed to satisfy constraint: {constraint}" ) @@ -192,7 +204,7 @@ def map_csc(model: "CodeSigningConfig") -> api_spec.CodeSigningConfig: ) -def get_config_for_url(store: "LambdaStore", url_id: str) -> "Optional[FunctionUrlConfig]": +def get_config_for_url(store: "LambdaStore", url_id: str) -> "FunctionUrlConfig | None": """ Get a config object when resolving a URL @@ -269,7 +281,7 @@ def function_locators_from_arn(arn: str) -> tuple[str | None, str | None, str | return None, None, None, None -def get_account_and_region(function_arn_or_name: str, context: RequestContext) -> Tuple[str, str]: +def get_account_and_region(function_arn_or_name: str, context: RequestContext) -> tuple[str, str]: """ Takes a full ARN, partial ARN or a name. Returns account ID and region from ARN if available, else falls back to context account ID and region. @@ -345,11 +357,11 @@ def build_statement( statement_id: str, action: str, principal: str, - source_arn: Optional[str] = None, - source_account: Optional[str] = None, - principal_org_id: Optional[str] = None, - event_source_token: Optional[str] = None, - auth_type: Optional[FunctionUrlAuthType] = None, + source_arn: str | None = None, + source_account: str | None = None, + principal_org_id: str | None = None, + event_source_token: str | None = None, + auth_type: FunctionUrlAuthType | None = None, ) -> dict[str, Any]: statement = { "Sid": statement_id, @@ -377,7 +389,7 @@ def build_statement( Type="User", ) - condition = dict() + condition = {} if auth_type: update = {"StringEquals": {"lambda:FunctionUrlAuthType": auth_type}} condition = merge_recursive(condition, update) @@ -425,7 +437,7 @@ def unqualified_lambda_arn(function_name: str, account: str, region: str): def qualified_lambda_arn( - function_name: str, qualifier: Optional[str], account: str, region: str + function_name: str, qualifier: str | None, account: str, region: str ) -> str: """ Generate a qualified lambda arn @@ -440,7 +452,7 @@ def qualified_lambda_arn( return f"{unqualified_lambda_arn(function_name=function_name, account=account, region=region)}:{qualifier}" -def lambda_arn(function_name: str, qualifier: Optional[str], account: str, region: str) -> str: +def lambda_arn(function_name: str, qualifier: str | None, account: str, region: str) -> str: """ Return the lambda arn for the given parameters, with a qualifier if supplied, without otherwise @@ -570,6 +582,12 @@ def map_config_out( optional_kwargs["CodeSize"] = 0 optional_kwargs["CodeSha256"] = version.config.image.code_sha256 + if version.config.capacity_provider_config: + optional_kwargs["CapacityProviderConfig"] = version.config.capacity_provider_config + data = json.dumps(version.config.capacity_provider_config, sort_keys=True).encode("utf-8") + config_sha_256 = hashlib.sha256(data).hexdigest() + optional_kwargs["ConfigSha256"] = config_sha_256 + # output for an alias qualifier is completely the same except for the returned ARN if alias_name: function_arn = f"{':'.join(version.id.qualified_arn().split(':')[:-1])}:{alias_name}" @@ -637,7 +655,7 @@ def map_alias_out(alias: "VersionAlias", function: "Function") -> AliasConfigura ) -def validate_and_set_batch_size(service: str, batch_size: Optional[int] = None) -> int: +def validate_and_set_batch_size(service: str, batch_size: int | None = None) -> int: min_batch_size = 1 BATCH_SIZE_RANGES = { @@ -665,7 +683,9 @@ def validate_and_set_batch_size(service: str, batch_size: Optional[int] = None) def map_layer_out(layer_version: "LayerVersion") -> PublishLayerVersionResponse: return PublishLayerVersionResponse( Content=LayerVersionContentOutput( - Location=layer_version.code.generate_presigned_url(), + Location=layer_version.code.generate_presigned_url( + endpoint_url=config.external_service_url() + ), CodeSha256=layer_version.code.code_sha256, CodeSize=layer_version.code.code_size, # SigningProfileVersionArn="", # same as in function configuration @@ -690,7 +710,7 @@ def layer_version_arn(layer_name: str, account: str, region: str, version: str): return f"arn:{get_partition(region)}:lambda:{region}:{account}:layer:{layer_name}:{version}" -def parse_layer_arn(layer_version_arn: str) -> Tuple[str, str, str, str]: +def parse_layer_arn(layer_version_arn: str) -> tuple[str, str, str, str]: return LAYER_VERSION_ARN_PATTERN.match(layer_version_arn).group( "region_name", "account_id", "layer_name", "layer_version" ) @@ -715,12 +735,14 @@ def validate_layer_runtimes_and_architectures( if compatible_runtimes and set(compatible_runtimes).difference(ALL_RUNTIMES): constraint = f"Member must satisfy enum value set: {VALID_RUNTIMES}" - validation_msg = f"Value '[{', '.join([s for s in compatible_runtimes])}]' at 'compatibleRuntimes' failed to satisfy constraint: {constraint}" + validation_msg = f"Value '[{', '.join(list(compatible_runtimes))}]' at 'compatibleRuntimes' failed to satisfy constraint: {constraint}" validations.append(validation_msg) if compatible_architectures and set(compatible_architectures).difference(ARCHITECTURES): - constraint = "[Member must satisfy enum value set: [x86_64, arm64]]" - validation_msg = f"Value '[{', '.join([s for s in compatible_architectures])}]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: {constraint}" + constraint = ( + "[Member must satisfy enum value set: [x86_64, arm64], Member must not be null]" + ) + validation_msg = f"Value '[{', '.join(list(compatible_architectures))}]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: {constraint}" validations.append(validation_msg) return validations diff --git a/localstack-core/localstack/services/lambda_/custom_endpoints.py b/localstack-core/localstack/services/lambda_/custom_endpoints.py index b8267f1e7d06b..b04077ad192b4 100644 --- a/localstack-core/localstack/services/lambda_/custom_endpoints.py +++ b/localstack-core/localstack/services/lambda_/custom_endpoints.py @@ -1,5 +1,5 @@ import urllib.parse -from typing import List, TypedDict +from typing import TypedDict from rolo import Request, route @@ -14,7 +14,7 @@ class LambdaRuntimesResponse(TypedDict, total=False): - Runtimes: List[Runtime] + Runtimes: list[Runtime] class LambdaCustomEndpoints: diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py b/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py index 0bf30dfb15d79..89ae16ea15b16 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable import botocore.config @@ -18,6 +18,7 @@ PipeTargetParameters, ) from localstack.services.lambda_ import hooks as lambda_hooks +from localstack.services.lambda_ import ldm from localstack.services.lambda_.event_source_mapping.esm_event_processor import ( EsmEventProcessor, ) @@ -39,10 +40,6 @@ from localstack.services.lambda_.event_source_mapping.senders.lambda_sender import LambdaSender from localstack.utils.aws.arns import parse_arn from localstack.utils.aws.client_types import ServicePrincipal -from localstack.utils.lambda_debug_mode.lambda_debug_mode import ( - DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS, - is_lambda_debug_mode, -) class PollerHolder: @@ -65,8 +62,8 @@ def get_esm_worker(self) -> EsmWorker: # Sender (always Lambda) function_arn = self.esm_config["FunctionArn"] - if is_lambda_debug_mode(): - timeout_seconds = DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS + if ldm.IS_LDM_ENABLED: + timeout_seconds = ldm.DEFAULT_LDM_TIMEOUT_SECONDS else: # 900s is the maximum amount of time a Lambda can run for. lambda_max_timeout_seconds = 900 diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py b/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py index 644e99c264035..1e113ccc0df53 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py @@ -1,5 +1,5 @@ import json -from datetime import datetime, timezone +from datetime import UTC, datetime import botocore from botocore.client import BaseClient @@ -57,7 +57,7 @@ def get_standardized_service_name(service_name: str) -> str: def get_current_time() -> datetime: - return datetime.now(tz=timezone.utc) + return datetime.now(tz=UTC) def get_datetime_from_timestamp(timestamp: float) -> datetime: diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py b/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py index 07ef9a7d9cca5..288e932c441d7 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py @@ -4,8 +4,8 @@ from abc import abstractmethod from bisect import bisect_left from collections import defaultdict +from collections.abc import Iterator from datetime import datetime -from typing import Iterator from botocore.client import BaseClient from botocore.exceptions import ClientError @@ -36,7 +36,7 @@ ) from localstack.utils.aws.arns import parse_arn, s3_bucket_name from localstack.utils.backoff import ExponentialBackoff -from localstack.utils.batch_policy import Batcher +from localstack.utils.batching import Batcher from localstack.utils.strings import long_uid LOG = logging.getLogger(__name__) diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py b/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py index ab1180adbdd1d..7ad836d222496 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py @@ -1,6 +1,7 @@ import sys +from collections.abc import Iterable, Iterator from itertools import islice -from typing import Any, Iterable, Iterator +from typing import Any def batched(iterable, n): diff --git a/localstack-core/localstack/services/lambda_/hooks.py b/localstack-core/localstack/services/lambda_/hooks.py index 16195ae538bca..6d2a308f30ca2 100644 --- a/localstack-core/localstack/services/lambda_/hooks.py +++ b/localstack-core/localstack/services/lambda_/hooks.py @@ -2,19 +2,24 @@ from localstack.runtime.hooks import hook_spec +HOOKS_LAMBDA_CREATE_FUNCTION_VERSION = "localstack.hooks.lambda_create_function_version" +HOOKS_LAMBDA_DELETE_FUNCTION_VERSION = "localstack.hooks.lambda_delete_function_version" HOOKS_LAMBDA_START_DOCKER_EXECUTOR = "localstack.hooks.lambda_start_docker_executor" HOOKS_LAMBDA_PREPARE_DOCKER_EXECUTOR = "localstack.hooks.lambda_prepare_docker_executors" HOOKS_LAMBDA_INJECT_LAYER_FETCHER = "localstack.hooks.lambda_inject_layer_fetcher" +HOOKS_LAMBDA_INJECT_LDM_PROVISIONER = "localstack.hooks.lambda_inject_ldm_provisioner" HOOKS_LAMBDA_PREBUILD_ENVIRONMENT_IMAGE = "localstack.hooks.lambda_prebuild_environment_image" HOOKS_LAMBDA_CREATE_EVENT_SOURCE_POLLER = "localstack.hooks.lambda_create_event_source_poller" HOOKS_LAMBDA_SET_EVENT_SOURCE_CONFIG_DEFAULTS = ( "localstack.hooks.lambda_set_event_source_config_defaults" ) - +create_function_version = hook_spec(HOOKS_LAMBDA_CREATE_FUNCTION_VERSION) +delete_function_version = hook_spec(HOOKS_LAMBDA_DELETE_FUNCTION_VERSION) start_docker_executor = hook_spec(HOOKS_LAMBDA_START_DOCKER_EXECUTOR) prepare_docker_executor = hook_spec(HOOKS_LAMBDA_PREPARE_DOCKER_EXECUTOR) inject_layer_fetcher = hook_spec(HOOKS_LAMBDA_INJECT_LAYER_FETCHER) +inject_ldm_provisioner = hook_spec(HOOKS_LAMBDA_INJECT_LDM_PROVISIONER) prebuild_environment_image = hook_spec(HOOKS_LAMBDA_PREBUILD_ENVIRONMENT_IMAGE) create_event_source_poller = hook_spec(HOOKS_LAMBDA_CREATE_EVENT_SOURCE_POLLER) set_event_source_config_defaults = hook_spec(HOOKS_LAMBDA_SET_EVENT_SOURCE_CONFIG_DEFAULTS) diff --git a/localstack-core/localstack/services/lambda_/invocation/assignment.py b/localstack-core/localstack/services/lambda_/invocation/assignment.py index 39f4d04383e26..9edd5f7dc2cc2 100644 --- a/localstack-core/localstack/services/lambda_/invocation/assignment.py +++ b/localstack-core/localstack/services/lambda_/invocation/assignment.py @@ -1,8 +1,9 @@ import contextlib import logging +import threading from collections import defaultdict +from collections.abc import Iterator from concurrent.futures import Future, ThreadPoolExecutor -from typing import ContextManager from localstack.services.lambda_.invocation.execution_environment import ( EnvironmentStartupTimeoutException, @@ -15,10 +16,6 @@ InitializationType, OtherServiceEndpoint, ) -from localstack.utils.lambda_debug_mode.lambda_debug_mode import ( - is_lambda_debug_enabled_for, - is_lambda_debug_timeout_enabled_for, -) LOG = logging.getLogger(__name__) @@ -38,9 +35,16 @@ class AssignmentService(OtherServiceEndpoint): # Global pool for spawning and killing provisioned Lambda runtime environments provisioning_pool: ThreadPoolExecutor + # Semaphore limiting the number of on-demand containers starting simultaneously. + # Concurrent container starts are I/O-heavy (Docker API calls, copying runtime files) + # and can exhaust OS file descriptor limits on machines with low ulimits. + on_demand_start_semaphore: threading.Semaphore + def __init__(self): self.environments = defaultdict(dict) self.provisioning_pool = ThreadPoolExecutor(thread_name_prefix="lambda-provisioning-pool") + # TODO: make this value configurable; 16 is a conservative default + self.on_demand_start_semaphore = threading.Semaphore(16) @contextlib.contextmanager def get_environment( @@ -48,12 +52,14 @@ def get_environment( version_manager_id: str, function_version: FunctionVersion, provisioning_type: InitializationType, - ) -> ContextManager[ExecutionEnvironment]: - applicable_envs = ( + ) -> Iterator[ExecutionEnvironment]: + # Snapshot the values list before iterating to avoid skipped entries + # that can be caused by concurrent invocations + applicable_envs = [ env - for env in self.environments[version_manager_id].values() + for env in list(self.environments[version_manager_id].values()) if env.initialization_type == provisioning_type - ) + ] execution_environment = None for environment in applicable_envs: try: @@ -64,12 +70,15 @@ def get_environment( pass if execution_environment is None: - if provisioning_type == "provisioned-concurrency": + if provisioning_type == InitializationType.provisioned_concurrency: raise AssignmentException( "No provisioned concurrency environment available despite lease." ) - elif provisioning_type == "on-demand": - execution_environment = self.start_environment(version_manager_id, function_version) + elif provisioning_type == InitializationType.on_demand: + with self.on_demand_start_semaphore: + execution_environment = self.start_environment( + version_manager_id, function_version + ) self.environments[version_manager_id][execution_environment.id] = ( execution_environment ) @@ -79,26 +88,35 @@ def get_environment( try: yield execution_environment - if is_lambda_debug_timeout_enabled_for(lambda_arn=function_version.qualified_arn): - self.stop_environment(execution_environment) - else: - execution_environment.release() + execution_environment.release() except InvalidStatusException as invalid_e: LOG.error("InvalidStatusException: %s", invalid_e) except Exception as e: LOG.error( "Failed invocation <%s>: %s", type(e), e, exc_info=LOG.isEnabledFor(logging.DEBUG) ) - self.stop_environment(execution_environment) + if execution_environment.initialization_type == InitializationType.on_demand: + self.stop_environment(execution_environment) + else: + # Try to restore to READY rather than stopping. + # Transient errors (e.g., OS-level connection failures) should not + # permanently remove healthy provisioned containers from the pool. + try: + execution_environment.release() + except InvalidStatusException: + self.stop_environment(execution_environment) raise e def start_environment( self, version_manager_id: str, function_version: FunctionVersion ) -> ExecutionEnvironment: LOG.debug("Starting new environment") + initialization_type = InitializationType.on_demand + if function_version.config.capacity_provider_config: + initialization_type = InitializationType.lambda_managed_instances execution_environment = ExecutionEnvironment( function_version=function_version, - initialization_type="on-demand", + initialization_type=initialization_type, on_timeout=self.on_timeout, version_manager_id=version_manager_id, ) @@ -143,38 +161,23 @@ def scale_provisioned_concurrency( function_version: FunctionVersion, target_provisioned_environments: int, ) -> list[Future[None]]: - # Enforce a single environment per lambda version if this is a target - # of an active Lambda Debug Mode. - qualified_lambda_version_arn = function_version.qualified_arn - if ( - is_lambda_debug_enabled_for(qualified_lambda_version_arn) - and target_provisioned_environments > 0 - ): - LOG.warning( - "Environments for '%s' enforced to '1' by Lambda Debug Mode, " - "configurations will continue to report the set value '%s'", - qualified_lambda_version_arn, - target_provisioned_environments, - ) - target_provisioned_environments = 1 - current_provisioned_environments = [ e for e in self.environments[version_manager_id].values() - if e.initialization_type == "provisioned-concurrency" + if e.initialization_type == InitializationType.provisioned_concurrency ] # TODO: refine scaling loop to re-use existing environments instead of re-creating all # current_provisioned_environments_count = len(current_provisioned_environments) # diff = target_provisioned_environments - current_provisioned_environments_count - # TODO: handle case where no provisioned environment is available during scaling + # TODO: handle case where no provisioned environment is available during scaling. Does AWS serve on-demand? # Most simple scaling implementation for now: futures = [] # 1) Re-create new target for _ in range(target_provisioned_environments): execution_environment = ExecutionEnvironment( function_version=function_version, - initialization_type="provisioned-concurrency", + initialization_type=InitializationType.provisioned_concurrency, on_timeout=self.on_timeout, version_manager_id=version_manager_id, ) diff --git a/localstack-core/localstack/services/lambda_/invocation/counting_service.py b/localstack-core/localstack/services/lambda_/invocation/counting_service.py index 3c7024288a305..39226a0a08e5e 100644 --- a/localstack-core/localstack/services/lambda_/invocation/counting_service.py +++ b/localstack-core/localstack/services/lambda_/invocation/counting_service.py @@ -1,19 +1,18 @@ import contextlib import logging from collections import defaultdict +from collections.abc import Iterator from threading import RLock from localstack import config -from localstack.aws.api.lambda_ import TooManyRequestsException +from localstack.aws.api.lambda_ import ProvisionedConcurrencyStatusEnum, TooManyRequestsException from localstack.services.lambda_.invocation.lambda_models import ( Function, FunctionVersion, InitializationType, + ProvisionedConcurrencyState, ) from localstack.services.lambda_.invocation.models import lambda_stores -from localstack.utils.lambda_debug_mode.lambda_debug_mode import ( - is_lambda_debug_enabled_for, -) LOG = logging.getLogger(__name__) @@ -69,12 +68,12 @@ class CountingService: """ # (account, region) => ConcurrencyTracker (unqualified arn) => concurrent executions - on_demand_concurrency_trackers: dict[(str, str), ConcurrencyTracker] + on_demand_concurrency_trackers: dict[tuple[str, str], ConcurrencyTracker] # Lock for safely initializing new on-demand concurrency trackers on_demand_init_lock: RLock # (account, region) => ConcurrencyTracker (qualified arn) => concurrent executions - provisioned_concurrency_trackers: dict[(str, str), ConcurrencyTracker] + provisioned_concurrency_trackers: dict[tuple[str, str], ConcurrencyTracker] # Lock for safely initializing new provisioned concurrency trackers provisioned_concurrency_init_lock: RLock @@ -86,8 +85,11 @@ def __init__(self): @contextlib.contextmanager def get_invocation_lease( - self, function: Function | None, function_version: FunctionVersion - ) -> InitializationType: + self, + function: Function | None, + function_version: FunctionVersion, + provisioned_state: ProvisionedConcurrencyState | None = None, + ) -> Iterator[InitializationType]: """An invocation lease reserves the right to schedule an invocation. The returned lease type can either be on-demand or provisioned. Scheduling preference: @@ -120,34 +122,9 @@ def get_invocation_lease( ConcurrencyTracker() ) - # TODO: check that we don't give a lease while updating provisioned concurrency - # Potential challenge if an update happens in between reserving the lease here and actually assigning - # * Increase provisioned: It could happen that we give a lease for provisioned-concurrency although - # brand new provisioned environments are not yet initialized. - # * Decrease provisioned: It could happen that we have running invocations that should still be counted - # against the limit but they are not because we already updated the concurrency config to fewer envs. - unqualified_function_arn = function_version.id.unqualified_arn() qualified_arn = function_version.id.qualified_arn() - # Enforce one lease per ARN if the global flag is set - if is_lambda_debug_enabled_for(qualified_arn): - with provisioned_tracker.lock, on_demand_tracker.lock: - on_demand_executions: int = on_demand_tracker.concurrent_executions[ - unqualified_function_arn - ] - provisioned_executions = provisioned_tracker.concurrent_executions[qualified_arn] - if on_demand_executions or provisioned_executions: - LOG.warning( - "Concurrent lambda invocations disabled for '%s' by Lambda Debug Mode", - qualified_arn, - ) - raise TooManyRequestsException( - "Rate Exceeded.", - Reason="SingleLeaseEnforcement", - Type="User", - ) - lease_type = None # HACK: skip reserved and provisioned concurrency if function not available (e.g., in Lambda@Edge) if function is not None: @@ -165,14 +142,24 @@ def get_invocation_lease( function.provisioned_concurrency_configs.get(alias.name) ) break - if provisioned_concurrency_config: + # Favor provisioned concurrency if configured and ready + # TODO: test updating provisioned concurrency? Does AWS serve on-demand during updates? + # Potential challenge if an update happens in between reserving the lease here and actually assigning + # * Increase provisioned: It could happen that we give a lease for provisioned-concurrency although + # brand new provisioned environments are not yet initialized. + # * Decrease provisioned: It could happen that we have running invocations that should still be counted + # against the limit but they are not because we already updated the concurrency config to fewer envs. + if ( + provisioned_concurrency_config + and provisioned_state.status == ProvisionedConcurrencyStatusEnum.READY + ): available_provisioned_concurrency = ( provisioned_concurrency_config.provisioned_concurrent_executions - provisioned_tracker.concurrent_executions[qualified_arn] ) if available_provisioned_concurrency > 0: provisioned_tracker.increment(qualified_arn) - lease_type = "provisioned-concurrency" + lease_type = InitializationType.provisioned_concurrency if not lease_type: with on_demand_tracker.lock: @@ -190,7 +177,7 @@ def get_invocation_lease( ) if available_reserved_concurrency > 0: on_demand_tracker.increment(unqualified_function_arn) - lease_type = "on-demand" + lease_type = InitializationType.on_demand else: extras = { "available_reserved_concurrency": available_reserved_concurrency, @@ -232,7 +219,7 @@ def get_invocation_lease( ) if available_unreserved_concurrency > 0: on_demand_tracker.increment(unqualified_function_arn) - lease_type = "on-demand" + lease_type = InitializationType.on_demand else: if available_unreserved_concurrency < 0: LOG.error( @@ -254,9 +241,9 @@ def get_invocation_lease( try: yield lease_type finally: - if lease_type == "provisioned-concurrency": + if lease_type == InitializationType.provisioned_concurrency: provisioned_tracker.atomic_decrement(qualified_arn) - elif lease_type == "on-demand": + elif lease_type == InitializationType.on_demand: on_demand_tracker.atomic_decrement(unqualified_function_arn) else: LOG.error( diff --git a/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py b/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py index c67f39addb414..719fa2ca90648 100644 --- a/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py +++ b/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py @@ -5,8 +5,8 @@ import tempfile import threading from collections import defaultdict +from collections.abc import Callable from pathlib import Path -from typing import Callable, Dict, Literal, Optional from localstack import config from localstack.aws.api.lambda_ import Architecture, PackageType, Runtime @@ -43,7 +43,6 @@ ) from localstack.utils.docker_utils import DOCKER_CLIENT as CONTAINER_CLIENT from localstack.utils.files import chmod_r, rm_rf -from localstack.utils.lambda_debug_mode.lambda_debug_mode import lambda_debug_port_for from localstack.utils.net import get_free_tcp_port from localstack.utils.strings import short_uid, truncate @@ -54,15 +53,13 @@ RAPID_ENTRYPOINT = "/var/rapid/init" -InitializationType = Literal["on-demand", "provisioned-concurrency"] - LAMBDA_DOCKERFILE = """FROM {base_img} COPY init {rapid_entrypoint} COPY code/ /var/task """ -PULLED_IMAGES: set[(str, DockerPlatform)] = set() -PULL_LOCKS: dict[(str, DockerPlatform), threading.RLock] = defaultdict(threading.RLock) +PULLED_IMAGES: set[tuple[str, DockerPlatform]] = set() +PULL_LOCKS: dict[tuple[str, DockerPlatform], threading.RLock] = defaultdict(threading.RLock) HOT_RELOADING_ENV_VARIABLE = "LOCALSTACK_HOT_RELOADING_PATHS" @@ -146,7 +143,7 @@ class RuntimeImageResolver: def __init__( self, default_resolve_fn: Callable[[Runtime], str] = get_default_image_for_runtime ): - self._mapping = dict() + self._mapping = {} self._default_resolve_fn = default_resolve_fn def _resolve(self, runtime: Runtime, custom_image_mapping: str = "") -> str: @@ -275,12 +272,12 @@ class LambdaContainerConfiguration(ContainerConfiguration): class DockerRuntimeExecutor(RuntimeExecutor): - ip: Optional[str] - executor_endpoint: Optional[ExecutorEndpoint] + ip: str | None + executor_endpoint: ExecutorEndpoint | None container_name: str def __init__(self, id: str, function_version: FunctionVersion) -> None: - super(DockerRuntimeExecutor, self).__init__(id=id, function_version=function_version) + super().__init__(id=id, function_version=function_version) self.ip = None self.executor_endpoint = ExecutorEndpoint(self.id) self.container_name = self._generate_container_name() @@ -321,9 +318,6 @@ def start(self, env_vars: dict[str, str]) -> None: platform=docker_platform(self.function_version.config.architectures[0]), additional_flags=config.LAMBDA_DOCKER_FLAGS, ) - debug_port = lambda_debug_port_for(self.function_version.qualified_arn) - if debug_port is not None: - container_config.ports.add(debug_port, debug_port) if self.function_version.config.package_type == PackageType.Zip: if self.function_version.config.code.is_hot_reloading(): @@ -459,7 +453,7 @@ def get_endpoint_from_executor(self) -> str: def _get_networks_for_executor(self) -> list[str]: return get_all_container_networks_for_lambda() - def invoke(self, payload: Dict[str, str]): + def invoke(self, payload: dict[str, str]): LOG.debug( "Sending invoke-payload '%s' to executor '%s'", truncate(json.dumps(payload), config.LAMBDA_TRUNCATE_STDOUT), diff --git a/localstack-core/localstack/services/lambda_/invocation/event_manager.py b/localstack-core/localstack/services/lambda_/invocation/event_manager.py index a433460543b7b..e68785d5bba15 100644 --- a/localstack-core/localstack/services/lambda_/invocation/event_manager.py +++ b/localstack-core/localstack/services/lambda_/invocation/event_manager.py @@ -13,6 +13,7 @@ from localstack import config from localstack.aws.api.lambda_ import InvocationType, TooManyRequestsException from localstack.services.lambda_.analytics import ( + FunctionInitializationType, FunctionOperation, FunctionStatus, function_counter, @@ -42,7 +43,7 @@ def get_sqs_client(function_version: FunctionVersion, client_config=None): # TODO: remove once DLQ handling is refactored following the removal of the legacy lambda provider class LegacyInvocationException(Exception): def __init__(self, message, log_output=None, result=None): - super(LegacyInvocationException, self).__init__(message) + super().__init__(message) self.log_output = log_output self.result = result @@ -198,22 +199,22 @@ def stop(self): def handle_message(self, message: dict) -> None: failure_cause = None qualifier = self.version_manager.function_version.id.qualifier + function_config = self.version_manager.function_version.config event_invoke_config = self.version_manager.function.event_invoke_configs.get(qualifier) runtime = None status = None + # TODO: handle initialization_type provisioned-concurrency, which requires enriching invocation_result + initialization_type = ( + FunctionInitializationType.lambda_managed_instances + if function_config.capacity_provider_config + else FunctionInitializationType.on_demand + ) try: sqs_invocation = SQSInvocation.decode(message["Body"]) invocation = sqs_invocation.invocation try: invocation_result = self.version_manager.invoke(invocation=invocation) - function_config = self.version_manager.function_version.config - function_counter.labels( - operation=FunctionOperation.invoke, - runtime=function_config.runtime or "n/a", - status=FunctionStatus.success, - invocation_type=InvocationType.Event, - package_type=function_config.package_type, - ).increment() + status = FunctionStatus.success except Exception as e: # Reserved concurrency == 0 if self.version_manager.function.reserved_concurrent_executions == 0: @@ -223,6 +224,7 @@ def handle_message(self, message: dict) -> None: elif not has_enough_time_for_retry(sqs_invocation, event_invoke_config): failure_cause = "EventAgeExceeded" status = FunctionStatus.event_age_exceeded_error + if failure_cause: invocation_result = InvocationResult( is_error=True, request_id=invocation.request_id, payload=None, logs=None @@ -240,14 +242,14 @@ def handle_message(self, message: dict) -> None: sqs_client.delete_message( QueueUrl=self.event_queue_url, ReceiptHandle=message["ReceiptHandle"] ) - # status MUST be set before returning - package_type = self.version_manager.function_version.config.package_type + assert status, "status MUST be set before returning" function_counter.labels( operation=FunctionOperation.invoke, runtime=runtime or "n/a", status=status, invocation_type=InvocationType.Event, - package_type=package_type, + package_type=function_config.package_type, + initialization_type=initialization_type, ).increment() # Good summary blogpost: https://haithai91.medium.com/aws-lambdas-retry-behaviors-edff90e1cf1b @@ -257,6 +259,8 @@ def handle_message(self, message: dict) -> None: if event_invoke_config and event_invoke_config.maximum_retry_attempts is not None: max_retry_attempts = event_invoke_config.maximum_retry_attempts + assert invocation_result, "Invocation result MUST exist if we are not returning before" + # An invocation error either leads to a terminal failure or to a scheduled retry if invocation_result.is_error: # invocation error failure_cause = None diff --git a/localstack-core/localstack/services/lambda_/invocation/execution_environment.py b/localstack-core/localstack/services/lambda_/invocation/execution_environment.py index 139ec4d877fbe..8279df83a75e5 100644 --- a/localstack-core/localstack/services/lambda_/invocation/execution_environment.py +++ b/localstack-core/localstack/services/lambda_/invocation/execution_environment.py @@ -2,12 +2,13 @@ import random import string import time +from collections.abc import Callable from datetime import date, datetime from enum import Enum, auto from threading import RLock, Timer -from typing import Callable, Dict, Optional from localstack import config +from localstack.aws.api.lambda_ import LogFormat from localstack.aws.connect import connect_to from localstack.services.lambda_.invocation.lambda_models import ( Credentials, @@ -20,10 +21,6 @@ RuntimeExecutor, get_runtime_executor, ) -from localstack.utils.lambda_debug_mode.lambda_debug_mode import ( - DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS, - is_lambda_debug_timeout_enabled_for, -) from localstack.utils.strings import to_str from localstack.utils.xray.trace_header import TraceHeader @@ -65,8 +62,8 @@ class ExecutionEnvironment: status: RuntimeStatus initialization_type: InitializationType last_returned: datetime - startup_timer: Optional[Timer] - keepalive_timer: Optional[Timer] + startup_timer: Timer | None + keepalive_timer: Timer | None on_timeout: Callable[[str, str], None] def __init__( @@ -95,7 +92,7 @@ def get_log_group_name(self) -> str: def get_log_stream_name(self) -> str: return f"{date.today():%Y/%m/%d}/[{self.function_version.id.qualifier}]{self.id}" - def get_environment_variables(self) -> Dict[str, str]: + def get_environment_variables(self) -> dict[str, str]: """ Returns the environment variable set for the runtime container :return: Dict of environment variables @@ -139,7 +136,7 @@ def get_environment_variables(self) -> Dict[str, str]: # AWS_LAMBDA_DOTNET_PREJIT "TZ": ":UTC", # 2) Public AWS RIE interface: https://github.com/aws/aws-lambda-runtime-interface-emulator - "AWS_LAMBDA_FUNCTION_TIMEOUT": self._get_execution_timeout_seconds(), + "AWS_LAMBDA_FUNCTION_TIMEOUT": self.function_version.config.timeout, # 3) Public LocalStack endpoint "LOCALSTACK_HOSTNAME": self.runtime_executor.get_endpoint_from_executor(), "EDGE_PORT": str(config.GATEWAY_LISTEN[0].port), @@ -153,6 +150,22 @@ def get_environment_variables(self) -> Dict[str, str]: # LOCALSTACK_USER conditionally added below } # Conditionally added environment variables + # Lambda advanced logging controls: + # https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/ + logging_config = self.function_version.config.logging_config + if logging_config.get("LogFormat") == LogFormat.JSON: + env_vars["AWS_LAMBDA_LOG_FORMAT"] = logging_config["LogFormat"] + # TODO: test this (currently not implemented in LocalStack) + env_vars["AWS_LAMBDA_LOG_LEVEL"] = logging_config["ApplicationLogLevel"].capitalize() + # Lambda Managed Instances + if capacity_provider_config := self.function_version.config.capacity_provider_config: + # Disable dropping privileges for parity + # TODO: implement mixed permissions (maybe in RIE) + # env_vars["LOCALSTACK_USER"] = "root" + env_vars["AWS_LAMBDA_MAX_CONCURRENCY"] = capacity_provider_config[ + "LambdaManagedInstancesCapacityProviderConfig" + ]["PerExecutionEnvironmentMaxConcurrency"] + env_vars["TZ"] = ":/etc/localtime" if not config.LAMBDA_DISABLE_AWS_ENDPOINT_URL: env_vars["AWS_ENDPOINT_URL"] = ( f"http://{self.runtime_executor.get_endpoint_from_executor()}:{config.GATEWAY_LISTEN[0].port}" @@ -163,8 +176,6 @@ def get_environment_variables(self) -> Dict[str, str]: # Will be overridden by the runtime itself unless it is a provided runtime if self.function_version.config.runtime: env_vars["AWS_EXECUTION_ENV"] = "AWS_Lambda_rapid" - if self.function_version.config.environment: - env_vars.update(self.function_version.config.environment) if config.LAMBDA_INIT_DEBUG: # Disable dropping privileges because it breaks debugging env_vars["LOCALSTACK_USER"] = "root" @@ -179,6 +190,10 @@ def get_environment_variables(self) -> Dict[str, str]: env_vars["LOCALSTACK_MAX_PAYLOAD_SIZE"] = int( config.LAMBDA_LIMITS_MAX_FUNCTION_PAYLOAD_SIZE_BYTES ) + + # Let users overwrite any environment variable at last (if they want so) + if self.function_version.config.environment: + env_vars.update(self.function_version.config.environment) return env_vars # Lifecycle methods @@ -254,7 +269,7 @@ def release(self) -> None: ) self.status = RuntimeStatus.READY - if self.initialization_type == "on-demand": + if self.initialization_type == InitializationType.on_demand: self.keepalive_timer = Timer(config.LAMBDA_KEEPALIVE_MS / 1000, self.keepalive_passed) self.keepalive_timer.start() @@ -388,18 +403,5 @@ def get_credentials(self) -> Credentials: DurationSeconds=43200, )["Credentials"] - def _get_execution_timeout_seconds(self) -> int: - # Returns the timeout value in seconds to be enforced during the execution of the - # lambda function. This is the configured value or the DEBUG MODE default if this - # is enabled. - if is_lambda_debug_timeout_enabled_for(self.function_version.qualified_arn): - return DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS - return self.function_version.config.timeout - def _get_startup_timeout_seconds(self) -> int: - # Returns the timeout value in seconds to be enforced during lambda container startups. - # This is the value defined through LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT or the LAMBDA - # DEBUG MODE default if this is enabled. - if is_lambda_debug_timeout_enabled_for(self.function_version.qualified_arn): - return DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS return STARTUP_TIMEOUT_SEC diff --git a/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py b/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py index eea6e0c77ebaa..08a6a7ce9d86d 100644 --- a/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py +++ b/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py @@ -3,19 +3,16 @@ import time from concurrent.futures import CancelledError, Future from http import HTTPStatus -from typing import Any, Dict, Optional +from typing import Any import requests from werkzeug import Request from localstack.http import Response, route from localstack.services.edge import ROUTER +from localstack.services.lambda_ import ldm from localstack.services.lambda_.invocation.lambda_models import InvocationResult from localstack.utils.backoff import ExponentialBackoff -from localstack.utils.lambda_debug_mode.lambda_debug_mode import ( - DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS, - is_lambda_debug_mode, -) from localstack.utils.objects import singleton_factory from localstack.utils.strings import to_str @@ -121,8 +118,8 @@ class ExecutorEndpoint(Endpoint): def __init__( self, executor_id: str, - container_address: Optional[str] = None, - container_port: Optional[int] = INVOCATION_PORT, + container_address: str | None = None, + container_port: int | None = INVOCATION_PORT, ) -> None: self.container_address = container_address self.container_port = container_port @@ -143,7 +140,7 @@ def invocation_error(self, request: Request, req_id: str) -> Response: def invocation_logs(self, request: Request, invoke_id: str) -> Response: logs = request.json - if isinstance(logs, Dict): + if isinstance(logs, dict): self.logs = logs["logs"] else: LOG.error("Invalid logs from init! Logs: %s", logs) @@ -186,7 +183,7 @@ def shutdown(self) -> None: if self.invocation_future: self.invocation_future.cancel() - def invoke(self, payload: Dict[str, str]) -> InvocationResult: + def invoke(self, payload: dict[str, str]) -> InvocationResult: self.invocation_future = Future() self.logs = None if not self.container_address: @@ -209,9 +206,9 @@ def invoke(self, payload: Dict[str, str]) -> InvocationResult: # Note that if timeouts are enforced for the lambda function invoked at this endpoint # (this is needs to be configured in the Lambda Debug Mode Config file), the lambda # function will continue to enforce the expected timeouts. - if is_lambda_debug_mode(): + if ldm.IS_LDM_ENABLED: # The value is set to a default high value to ensure eventual termination. - timeout_seconds = DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS + timeout_seconds = ldm.DEFAULT_LDM_TIMEOUT_SECONDS else: # Do not wait longer for an invoke than the maximum lambda timeout plus a buffer lambda_max_timeout_seconds = 900 diff --git a/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py b/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py index 41da58b681701..742a1f3c83089 100644 --- a/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py +++ b/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py @@ -1,6 +1,6 @@ import logging import threading -from typing import Iterable +from collections.abc import Iterable from localstack import config from localstack.aws.api.sqs import ( @@ -19,13 +19,9 @@ String, TagMap, ) -from localstack.services.sqs.models import SqsQueue, StandardQueue -from localstack.services.sqs.provider import ( - QueueUpdateWorker, - _create_message_attribute_hash, - to_sqs_api_message, -) -from localstack.services.sqs.utils import generate_message_id +from localstack.services.sqs.models import SqsQueue, StandardQueue, to_sqs_api_message +from localstack.services.sqs.provider import QueueUpdateWorker +from localstack.services.sqs.utils import create_message_attribute_hash, generate_message_id from localstack.utils.objects import singleton_factory from localstack.utils.strings import md5 from localstack.utils.time import now @@ -189,7 +185,7 @@ def send_message( MD5OfBody=md5(MessageBody), Body=MessageBody, Attributes=self._create_message_attributes(MessageSystemAttributes), - MD5OfMessageAttributes=_create_message_attribute_hash(MessageAttributes), + MD5OfMessageAttributes=create_message_attribute_hash(MessageAttributes), MessageAttributes=MessageAttributes, ) queue_item = queue.put( @@ -204,7 +200,7 @@ def send_message( "MD5OfMessageBody": message["MD5OfBody"], "MD5OfMessageAttributes": message.get("MD5OfMessageAttributes"), "SequenceNumber": queue_item.sequence_number, - "MD5OfMessageSystemAttributes": _create_message_attribute_hash(MessageSystemAttributes), + "MD5OfMessageSystemAttributes": create_message_attribute_hash(MessageSystemAttributes), } diff --git a/localstack-core/localstack/services/lambda_/invocation/lambda_models.py b/localstack-core/localstack/services/lambda_/invocation/lambda_models.py index 0ce171cff6cc6..90c51d20d240b 100644 --- a/localstack-core/localstack/services/lambda_/invocation/lambda_models.py +++ b/localstack-core/localstack/services/lambda_/invocation/lambda_models.py @@ -11,9 +11,11 @@ import threading from abc import ABCMeta, abstractmethod from datetime import datetime +from enum import StrEnum from pathlib import Path -from typing import IO, Dict, Literal, Optional, TypedDict +from typing import IO, Any, TypedDict +import boto3 from botocore.exceptions import ClientError from localstack import config @@ -21,12 +23,20 @@ from localstack.aws.api.lambda_ import ( AllowedPublishers, Architecture, + CapacityProviderArn, + CapacityProviderConfig, + CapacityProviderPermissionsConfig, + CapacityProviderScalingConfig, + CapacityProviderVpcConfig, CodeSigningPolicies, Cors, DestinationConfig, + FunctionScalingConfig, FunctionUrlAuthType, + InstanceRequirements, InvocationType, InvokeMode, + KMSKeyArn, LastUpdateStatus, LoggingConfig, PackageType, @@ -37,12 +47,14 @@ SnapStartResponse, State, StateReasonCode, + Timestamp, TracingMode, ) from localstack.aws.connect import connect_to -from localstack.constants import AWS_REGION_US_EAST_1 +from localstack.constants import AWS_REGION_US_EAST_1, INTERNAL_AWS_SECRET_ACCESS_KEY from localstack.services.lambda_.api_utils import qualified_lambda_arn, unqualified_lambda_arn from localstack.utils.archives import unzip +from localstack.utils.files import chmod_r from localstack.utils.strings import long_uid, short_uid LOG = logging.getLogger(__name__) @@ -52,8 +64,8 @@ @dataclasses.dataclass(frozen=True) class VersionState: state: State - code: Optional[StateReasonCode] = None - reason: Optional[str] = None + code: StateReasonCode | None = None + reason: str | None = None @dataclasses.dataclass @@ -66,14 +78,18 @@ class Invocation: # = invocation_id request_id: str trace_context: dict + user_agent: str | None = None -InitializationType = Literal["on-demand", "provisioned-concurrency"] +class InitializationType(StrEnum): + on_demand = "on-demand" + provisioned_concurrency = "provisioned-concurrency" + lambda_managed_instances = "lambda-managed-instances" class ArchiveCode(metaclass=ABCMeta): @abstractmethod - def generate_presigned_url(self, endpoint_url: str | None = None): + def generate_presigned_url(self, endpoint_url: str): """ Generates a presigned url pointing to the code archive """ @@ -167,15 +183,17 @@ def _download_archive_to_file(self, target_file: IO) -> None: ) target_file.flush() - def generate_presigned_url(self, endpoint_url: str | None = None) -> str: + def generate_presigned_url(self, endpoint_url: str) -> str: """ Generates a presigned url pointing to the code archive """ - s3_client = connect_to( + s3_client = boto3.client( + "s3", region_name=AWS_REGION_US_EAST_1, aws_access_key_id=config.INTERNAL_RESOURCE_ACCOUNT, + aws_secret_access_key=INTERNAL_AWS_SECRET_ACCESS_KEY, endpoint_url=endpoint_url, - ).s3 + ) params = {"Bucket": self.s3_bucket, "Key": self.s3_key} if self.s3_object_version: params["VersionId"] = self.s3_object_version @@ -204,10 +222,20 @@ def prepare_for_execution(self) -> None: if target_path.exists(): return LOG.debug("Saving code %s to disk", self.id) - target_path.mkdir(parents=True, exist_ok=True) - with tempfile.NamedTemporaryFile() as file: - self._download_archive_to_file(file) - unzip(file.name, str(target_path)) + target_path.parent.mkdir(parents=True, exist_ok=True) + # Use a temp directory for atomic operation to prevent partial reads + # if the process crashes or is killed during unzip. + # Create temp dir in same parent to ensure same filesystem for atomic rename. + with tempfile.TemporaryDirectory(dir=target_path.parent) as temp_dir: + temp_path = Path(temp_dir) + + with tempfile.NamedTemporaryFile() as file: + self._download_archive_to_file(file) + unzip(file.name, str(temp_path)) + chmod_r(str(temp_path), 0o755) + + # Atomic move/rename + temp_path.rename(target_path) def destroy_cached(self) -> None: """ @@ -256,8 +284,8 @@ class HotReloadingCode(ArchiveCode): code_sha256: str = "hot-reloading-hash-not-available" code_size: int = 0 - def generate_presigned_url(self, endpoint_url: str | None = None) -> str: - return f"Code location: {self.host_path}" + def generate_presigned_url(self, endpoint_url: str) -> str: + return f"file://{self.host_path}" def get_unzipped_code_location(self) -> Path: path = os.path.expandvars(self.host_path) @@ -312,9 +340,9 @@ class FileSystemConfig: @dataclasses.dataclass(frozen=True) class ImageConfig: - working_directory: str - command: list[str] = dataclasses.field(default_factory=list) - entrypoint: list[str] = dataclasses.field(default_factory=list) + working_directory: str | None + command: list[str] | None = dataclasses.field(default_factory=list) + entrypoint: list[str] | None = dataclasses.field(default_factory=list) @dataclasses.dataclass @@ -327,7 +355,7 @@ class VpcConfig: @dataclasses.dataclass(frozen=True) class UpdateStatus: status: LastUpdateStatus | None - code: str | None = None # TODO: probably not a string + code: str | None = None reason: str | None = None @@ -346,16 +374,16 @@ class FunctionUrlConfig: function_arn: str # fully qualified ARN function_name: str # resolved name - cors: Cors url_id: str # Custom URL (via tag), or generated unique subdomain id e.g. pfn5bdb2dl5mzkbn6eb2oi3xfe0nthdn url: str # full URL (e.g. "https://pfn5bdb2dl5mzkbn6eb2oi3xfe0nthdn.lambda-url.eu-west-3.on.aws/") auth_type: FunctionUrlAuthType creation_time: str # time - last_modified_time: Optional[str] = ( + cors: Cors | None + last_modified_time: str | None = ( None # TODO: check if this is the creation time when initially creating ) - function_qualifier: Optional[str] = "$LATEST" # only $LATEST or alias name - invoke_mode: Optional[InvokeMode] = None + function_qualifier: str | None = "$LATEST" # only $LATEST or alias name + invoke_mode: InvokeMode | None = None @dataclasses.dataclass @@ -373,12 +401,12 @@ class ProvisionedConcurrencyState: status: ProvisionedConcurrencyStatusEnum = dataclasses.field( default=ProvisionedConcurrencyStatusEnum.IN_PROGRESS ) - status_reason: Optional[str] = None + status_reason: str | None = None @dataclasses.dataclass class AliasRoutingConfig: - version_weights: Dict[str, float] + version_weights: dict[str, float] @dataclasses.dataclass(frozen=True) @@ -417,7 +445,7 @@ class VersionAlias: class ResourcePolicy: Version: str Id: str - Statement: list[dict] + Statement: list[dict[str, Any]] @dataclasses.dataclass @@ -430,10 +458,10 @@ class EventInvokeConfig: function_name: str qualifier: str - last_modified: Optional[str] = dataclasses.field(compare=False) - destination_config: Optional[DestinationConfig] = None - maximum_retry_attempts: Optional[int] = None - maximum_event_age_in_seconds: Optional[int] = None + last_modified: str | None = dataclasses.field(compare=False) + destination_config: DestinationConfig | None = None + maximum_retry_attempts: int | None = None + maximum_event_age_in_seconds: int | None = None # Result Models @@ -481,9 +509,9 @@ class CodeSigningConfig: arn: str allowed_publishers: AllowedPublishers - policies: CodeSigningPolicies last_modified: str - description: Optional[str] = None + policies: CodeSigningPolicies | None = None + description: str | None = None @dataclasses.dataclass @@ -491,7 +519,7 @@ class LayerPolicyStatement: sid: str action: str principal: str - organization_id: Optional[str] + organization_id: str | None @dataclasses.dataclass @@ -510,14 +538,15 @@ class LayerVersion: layer_arn: str version: int - code: ArchiveCode - license_info: str - compatible_runtimes: list[Runtime] - compatible_architectures: list[Architecture] + # we need to use Union types as inheritance is not supported by serialization framework + code: S3Code | HotReloadingCode | None + license_info: str | None + compatible_runtimes: list[Runtime] | None + compatible_architectures: list[Architecture] | None created: str # date description: str = "" - policy: LayerPolicy = None + policy: LayerPolicy | None = None @dataclasses.dataclass @@ -534,36 +563,38 @@ class VersionFunctionConfiguration: description: str role: str timeout: int - runtime: Runtime + runtime: Runtime | None memory_size: int - handler: str + handler: str | None package_type: PackageType - environment: dict[str, str] + environment: dict[str, str] | None architectures: list[Architecture] # internal revision is updated when runtime restart is necessary internal_revision: str ephemeral_storage: LambdaEphemeralStorage - snap_start: SnapStartResponse + snap_start: SnapStartResponse | None tracing_config_mode: TracingMode - code: ArchiveCode + # we need to use Union types as inheritance is not supported by serialization framework + code: S3Code | HotReloadingCode | None last_modified: str # ISO string state: VersionState - image: Optional[ImageCode] = None - image_config: Optional[ImageConfig] = None - runtime_version_config: Optional[RuntimeVersionConfig] = None - last_update: Optional[UpdateStatus] = None + image: ImageCode | None = None + image_config: ImageConfig | None = None + runtime_version_config: RuntimeVersionConfig | None = None + last_update: UpdateStatus | None = None revision_id: str = dataclasses.field(init=False, default_factory=long_uid) layers: list[LayerVersion] = dataclasses.field(default_factory=list) - dead_letter_arn: Optional[str] = None + dead_letter_arn: str | None = None # kms_key_arn: str # file_system_configs: FileSystemConfig - vpc_config: Optional[VpcConfig] = None + vpc_config: VpcConfig | None = None logging_config: LoggingConfig = dataclasses.field(default_factory=dict) + capacity_provider_config: CapacityProviderConfig | None = None @dataclasses.dataclass(frozen=True) @@ -576,10 +607,38 @@ def qualified_arn(self) -> str: return self.id.qualified_arn() +class DesiredCapacityProviderState(StrEnum): + Running = "Running" + Stopped = "Stopped" + + +@dataclasses.dataclass +class CapacityProvider: + CapacityProviderArn: CapacityProviderArn + # State is determined dynamically based on DesiredState + VpcConfig: CapacityProviderVpcConfig + PermissionsConfig: CapacityProviderPermissionsConfig + InstanceRequirements: InstanceRequirements + CapacityProviderScalingConfig: CapacityProviderScalingConfig + LastModified: Timestamp + KmsKeyArn: KMSKeyArn | None = None + # Tracks whether the capacity provider should be running or stopped. + # Set to Stopped when deletion is initiated; used to skip restoration on state load. + DesiredState: DesiredCapacityProviderState = DesiredCapacityProviderState.Running + + +@dataclasses.dataclass +class FunctionScalingState: + """Tracks both applied and requested scaling configs for async updates.""" + + applied: FunctionScalingConfig = dataclasses.field(default_factory=dict) + requested: FunctionScalingConfig | None = None + + @dataclasses.dataclass class Function: function_name: str - code_signing_config_arn: Optional[str] = None + code_signing_config_arn: str | None = None aliases: dict[str, VersionAlias] = dataclasses.field(default_factory=dict) versions: dict[str, FunctionVersion] = dataclasses.field(default_factory=dict) function_url_configs: dict[str, FunctionUrlConfig] = dataclasses.field( @@ -591,43 +650,22 @@ class Function: event_invoke_configs: dict[str, EventInvokeConfig] = dataclasses.field( default_factory=dict ) # key is $LATEST(?), version or alias - reserved_concurrent_executions: Optional[int] = None + reserved_concurrent_executions: int | None = None recursive_loop: RecursiveLoop = RecursiveLoop.Terminate provisioned_concurrency_configs: dict[str, ProvisionedConcurrencyConfiguration] = ( dataclasses.field(default_factory=dict) ) + function_scaling_configs: dict[str, FunctionScalingState] = dataclasses.field( + default_factory=dict + ) lock: threading.RLock = dataclasses.field(default_factory=threading.RLock) next_version: int = 1 + instance_id: str = dataclasses.field(default_factory=short_uid, init=False) def latest(self) -> FunctionVersion: return self.versions["$LATEST"] - # HACK to model a volatile variable that should be ignored for persistence - def __post_init__(self): - # Identifier unique to this function and LocalStack instance. - # A LocalStack restart or persistence load should create a new instance id. - # Used for retaining invoke queues across version updates for $LATEST, but separate unrelated instances. - self.instance_id = short_uid() - - def __getstate__(self): - """Ignore certain volatile fields for pickling. - # https://docs.python.org/3/library/pickle.html#handling-stateful-objects - """ - # Copy the object's state from self.__dict__ which contains - # all our instance attributes. Always use the dict.copy() - # method to avoid modifying the original state. - state = self.__dict__.copy() - # Remove the volatile entries. - del state["instance_id"] - return state - - def __setstate__(self, state): - # Inject persistent state - self.__dict__.update(state) - # Create new instance id - self.__post_init__() - class ValidationException(CommonServiceException): def __init__(self, message: str): diff --git a/localstack-core/localstack/services/lambda_/invocation/lambda_service.py b/localstack-core/localstack/services/lambda_/invocation/lambda_service.py index 837d766444c5d..043171ab7fd0b 100644 --- a/localstack-core/localstack/services/lambda_/invocation/lambda_service.py +++ b/localstack-core/localstack/services/lambda_/invocation/lambda_service.py @@ -5,13 +5,14 @@ import logging import os.path import random +import time import uuid from concurrent.futures import Executor, Future, ThreadPoolExecutor from datetime import datetime from hashlib import sha256 from pathlib import PurePosixPath, PureWindowsPath from threading import RLock -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from localstack import config from localstack.aws.api.lambda_ import ( @@ -19,13 +20,16 @@ InvalidRequestContentException, InvocationType, LastUpdateStatus, + NoPublishedVersionException, ResourceConflictException, ResourceNotFoundException, State, ) from localstack.aws.connect import connect_to from localstack.constants import AWS_REGION_US_EAST_1 +from localstack.services.lambda_ import hooks as lambda_hooks from localstack.services.lambda_.analytics import ( + FunctionInitializationType, FunctionOperation, FunctionStatus, function_counter, @@ -52,7 +56,7 @@ VersionAlias, VersionState, ) -from localstack.services.lambda_.invocation.models import lambda_stores +from localstack.services.lambda_.invocation.models import LambdaStore, lambda_stores from localstack.services.lambda_.invocation.version_manager import LambdaVersionManager from localstack.services.lambda_.lambda_utils import HINT_LOG from localstack.utils.archives import get_unzipped_size, is_zip_file @@ -130,6 +134,7 @@ def stop_version(self, qualified_arn: str) -> None: if not version_manager: raise ValueError(f"Unable to find version manager for {qualified_arn}") self.task_executor.submit(version_manager.stop) + lambda_hooks.delete_function_version.run(qualified_arn) def get_lambda_version_manager(self, function_arn: str) -> LambdaVersionManager: """ @@ -185,8 +190,72 @@ def create_function_version(self, function_version: FunctionVersion) -> Future[N assignment_service=self.assignment_service, ) self.lambda_starting_versions[qualified_arn] = version_manager + lambda_hooks.create_function_version.run(function_version.qualified_arn) return self.task_executor.submit(self._start_lambda_version, version_manager) + def publish_version_async(self, function_version: FunctionVersion): + self.task_executor.submit(self.publish_version, function_version) + + def delete_function_version_async( + self, function: Function, version: FunctionVersion, qualifier: str + ): + """ + Simulates async function cleanup after function deletion API is called + by introducing a small delay before actually removing the function from the store + to allow for getting the function details after deletion. + """ + + def _cleanup(): + time.sleep(0.5) + function.versions.pop(qualifier, None) + + new_state = VersionState(state=State.Deleting) + new_last_status = UpdateStatus(status=LastUpdateStatus.InProgress) + function.versions[version.id.qualifier] = dataclasses.replace( + version, + config=dataclasses.replace( + version.config, state=new_state, last_update=new_last_status + ), + ) + destroy_code_if_not_used(code=version.config.code, function=function) + + self.task_executor.submit(_cleanup) + + def delete_function_async(self, store: LambdaStore, function_name: str): + """ + Simulates async function version cleanup after function deletion API is called + by introducing a small delay before actually removing the function from the store + to allow for getting the function version details after deletion. + """ + + def _cleanup(): + time.sleep(0.5) + store.functions.pop(function_name) + + # set each version of the function to deleting state first, to allow for getting the function version details after deletion + function = store.functions.get(function_name) + if function: + for version in function.versions.values(): + new_state = VersionState(state=State.Deleting) + new_last_status = UpdateStatus(status=LastUpdateStatus.InProgress) + previous_revision_id = version.config.revision_id + + function.versions[version.id.qualifier] = dataclasses.replace( + version, + config=dataclasses.replace( + version.config, state=new_state, last_update=new_last_status + ), + ) + # Seems the revision id doesn't change when deleting a function right after it has been created (even though state has changed) + # reassign revision id to avoid dataclass replace removing it, since it's init=False + object.__setattr__( + function.versions[version.id.qualifier].config, + "revision_id", + previous_revision_id, + ) + + self.task_executor.submit(_cleanup) + def publish_version(self, function_version: FunctionVersion): """ Synchronously create a function version (manager) @@ -197,6 +266,14 @@ def publish_version(self, function_version: FunctionVersion): :param function_version: Function Version to create """ + # HACK: trying to match the AWS timing behavior of Lambda Managed Instances for the operation + # publish_version followed by get_function because transitioning LastUpdateStatus from InProgress to + # Successful happens too fast on LocalStack (thanks to caching in prepare_version). + # Without this hack, test_latest_published_update_config fails at get_function_response_postpublish + # and test_lifecycle_invoke is flaky, sometimes not triggering the ResourceConflictException + # Increasing this sleep too much (e.g., 10s) shouldn't cause any side effects apart from slow responsiveness + if function_version.config.capacity_provider_config: + time.sleep(0.1) with self.lambda_version_manager_lock: qualified_arn = function_version.id.qualified_arn() version_manager = self.lambda_starting_versions.get(qualified_arn) @@ -222,7 +299,7 @@ def publish_version(self, function_version: FunctionVersion): def invoke( self, function_name: str, - qualifier: str, + qualifier: str | None, region: str, account_id: str, invocation_type: InvocationType | None, @@ -230,6 +307,7 @@ def invoke( request_id: str, payload: bytes | None, trace_context: dict | None = None, + user_agent: str | None = None, ) -> InvocationResult | None: """ Invokes a specific version of a lambda @@ -254,13 +332,27 @@ def invoke( account=account_id, region=region, ) - qualifier = qualifier or "$LATEST" state = lambda_stores[account_id][region] function = state.functions.get(function_name) if function is None: + if not qualifier: + invoked_arn += ":$LATEST" raise ResourceNotFoundException(f"Function not found: {invoked_arn}", Type="User") + # A provided qualifier always takes precedence, but the default depends on whether $LATEST.PUBLISHED exists + version_latest_published = function.versions.get("$LATEST.PUBLISHED") + if version_latest_published: + qualifier = qualifier or "$LATEST.PUBLISHED" + invoked_arn = lambda_arn( + function_name=function_name, + qualifier=qualifier, + account=account_id, + region=region, + ) + else: + qualifier = qualifier or "$LATEST" + if qualifier_is_alias(qualifier): alias = function.aliases.get(qualifier) if not alias: @@ -278,8 +370,27 @@ def invoke( # Need the qualified arn to exactly get the target lambda qualified_arn = qualified_lambda_arn(function_name, version_qualifier, account_id, region) version = function.versions.get(version_qualifier) + if version is None: + raise ResourceNotFoundException(f"Function not found: {invoked_arn}", Type="User") runtime = version.config.runtime or "n/a" package_type = version.config.package_type + # Not considering provisioned concurrency for such early errors + initialization_type = ( + FunctionInitializationType.lambda_managed_instances + if version.config.capacity_provider_config + else FunctionInitializationType.on_demand + ) + if version.config.capacity_provider_config and qualifier == "$LATEST": + if function.versions.get("$LATEST.PUBLISHED"): + raise InvalidParameterValueException( + "Functions configured with capacity provider configuration can't be invoked with $LATEST qualifier. To invoke this function, specify a published version qualifier or $LATEST.PUBLISHED.", + Type="User", + ) + else: + raise NoPublishedVersionException( + "The function can't be invoked because no published version exists. For functions with capacity provider configuration, either publish a version to $LATEST.PUBLISHED, or specify a published version qualifier.", + Type="User", + ) try: version_manager = self.get_lambda_version_manager(qualified_arn) event_manager = self.get_lambda_event_manager(qualified_arn) @@ -311,6 +422,7 @@ def invoke( status=status, invocation_type=invocation_type, package_type=package_type, + initialization_type=initialization_type, ).increment() raise ResourceConflictException( f"The operation cannot be performed at this time. The function is currently in the following state: {state}" @@ -329,6 +441,7 @@ def invoke( status=FunctionStatus.invalid_payload_error, invocation_type=invocation_type, package_type=package_type, + initialization_type=initialization_type, ).increment() # MAYBE: improve parity of detailed exception message (quite cumbersome) raise InvalidRequestContentException( @@ -352,6 +465,7 @@ def invoke( invoke_time=datetime.now(), request_id=request_id, trace_context=trace_context, + user_agent=user_agent, ) ) @@ -364,6 +478,7 @@ def invoke( invoke_time=datetime.now(), request_id=request_id, trace_context=trace_context, + user_agent=user_agent, ) ) status = ( @@ -371,12 +486,14 @@ def invoke( if invocation_result.is_error else FunctionStatus.success ) + # TODO: handle initialization_type provisioned-concurrency, requires enriching invocation_result function_counter.labels( operation=FunctionOperation.invoke, runtime=runtime, status=status, invocation_type=invocation_type, package_type=package_type, + initialization_type=initialization_type, ).increment() return invocation_result @@ -387,7 +504,15 @@ def update_version(self, new_version: FunctionVersion) -> Future[None]: :param new_version: New version (with the same qualifier as an older one) """ - if new_version.qualified_arn not in self.lambda_running_versions: + if new_version.config.capacity_provider_config: + # simulate AWS behavior with a slight delay after update_function_configuration, + # so we can observe LastUpdateStatus transitioning to InProgress before it becomes Successful + time.sleep(0.5) + + if ( + new_version.qualified_arn not in self.lambda_running_versions + and not new_version.config.capacity_provider_config + ): raise ValueError( f"Version {new_version.qualified_arn} cannot be updated if an old one is not running" ) @@ -431,6 +556,11 @@ def update_version_state( elif new_state.state == State.Failed: update_status = UpdateStatus(status=LastUpdateStatus.Failed) self.task_executor.submit(new_version_manager.stop) + elif ( + new_state.state == State.ActiveNonInvocable + and function_version.config.capacity_provider_config + ): + update_status = UpdateStatus(status=LastUpdateStatus.Successful) else: # TODO what to do if state pending or inactive is supported? self.task_executor.submit(new_version_manager.stop) @@ -472,7 +602,11 @@ def update_version_state( ] = new_version_state except Exception: - LOG.exception("Failed to update function version for arn %s", function_arn) + LOG.error( + "Failed to update function version for arn %s", + function_arn, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) def update_alias(self, old_alias: VersionAlias, new_alias: VersionAlias, function: Function): # if pointer changed, need to restart provisioned @@ -621,7 +755,7 @@ def create_hot_reloading_code(path: str) -> HotReloadingCode: def store_s3_bucket_archive( archive_bucket: str, archive_key: str, - archive_version: Optional[str], + archive_version: str | None, function_name: str, region_name: str, account_id: str, @@ -640,11 +774,17 @@ def store_s3_bucket_archive( if archive_bucket == config.BUCKET_MARKER_LOCAL: hotreload_counter.labels(operation="create").increment() return create_hot_reloading_code(path=archive_key) - s3_client: "S3Client" = connect_to().s3 + s3_client: S3Client = connect_to().s3 kwargs = {"VersionId": archive_version} if archive_version else {} - archive_file = s3_client.get_object(Bucket=archive_bucket, Key=archive_key, **kwargs)[ - "Body" - ].read() + try: + archive_file = s3_client.get_object(Bucket=archive_bucket, Key=archive_key, **kwargs)[ + "Body" + ].read() + except s3_client.exceptions.ClientError as e: + raise InvalidParameterValueException( + f"Error occurred while GetObject. S3 Error Code: {e.response['Error']['Code']}. S3 Error Message: {e.response['Error']['Message']}", + Type="User", + ) return store_lambda_archive( archive_file, function_name=function_name, region_name=region_name, account_id=account_id ) diff --git a/localstack-core/localstack/services/lambda_/invocation/logs.py b/localstack-core/localstack/services/lambda_/invocation/logs.py index 2ff2ab35d951b..32f7d579448d8 100644 --- a/localstack-core/localstack/services/lambda_/invocation/logs.py +++ b/localstack-core/localstack/services/lambda_/invocation/logs.py @@ -3,7 +3,6 @@ import threading import time from queue import Queue -from typing import Optional, Union from localstack.aws.connect import connect_to from localstack.utils.aws.client_types import ServicePrincipal @@ -28,9 +27,9 @@ class LogItem: class LogHandler: - log_queue: "Queue[Union[LogItem, ShutdownPill]]" + log_queue: "Queue[LogItem | ShutdownPill]" role_arn: str - _thread: Optional[FuncThread] + _thread: FuncThread | None _shutdown_event: threading.Event def __init__(self, role_arn: str, region: str) -> None: diff --git a/localstack-core/localstack/services/lambda_/invocation/models.py b/localstack-core/localstack/services/lambda_/invocation/models.py index bc0eef5e7ebf0..c10f97650ddc3 100644 --- a/localstack-core/localstack/services/lambda_/invocation/models.py +++ b/localstack-core/localstack/services/lambda_/invocation/models.py @@ -1,7 +1,12 @@ from localstack.aws.api.lambda_ import EventSourceMappingConfiguration -from localstack.services.lambda_.invocation.lambda_models import CodeSigningConfig, Function, Layer +from localstack.services.lambda_.invocation.lambda_models import ( + CapacityProvider, + CodeSigningConfig, + Function, + Layer, +) from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute -from localstack.utils.tagging import TaggingService +from localstack.utils.tagging import Tags class LambdaStore(BaseStore): @@ -17,8 +22,11 @@ class LambdaStore(BaseStore): # maps layer names to Layers layers: dict[str, Layer] = LocalAttribute(default=dict) + # maps capacity provider names to respective CapacityProvider + capacity_providers: dict[str, CapacityProvider] = LocalAttribute(default=dict) + # maps resource ARNs for EventSourceMappings and CodeSigningConfiguration to tags - TAGS = LocalAttribute(default=TaggingService) + tags: Tags = LocalAttribute(default=Tags) lambda_stores = AccountRegionBundle("lambda", LambdaStore) diff --git a/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py b/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py index 93ed5cc600532..59b37499f76c1 100644 --- a/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py +++ b/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py @@ -2,7 +2,7 @@ import logging from abc import ABC, abstractmethod from pathlib import Path -from typing import Type, TypedDict +from typing import TypedDict from plux import PluginManager @@ -133,12 +133,12 @@ class ChmodPath(TypedDict): mode: str -EXECUTOR_PLUGIN_MANAGER: PluginManager[Type[RuntimeExecutor]] = PluginManager( +EXECUTOR_PLUGIN_MANAGER: PluginManager[type[RuntimeExecutor]] = PluginManager( RuntimeExecutorPlugin.namespace ) -def get_runtime_executor() -> Type[RuntimeExecutor]: +def get_runtime_executor() -> type[RuntimeExecutor]: plugin_name = config.LAMBDA_RUNTIME_EXECUTOR or "docker" if not EXECUTOR_PLUGIN_MANAGER.exists(plugin_name): LOG.warning( diff --git a/localstack-core/localstack/services/lambda_/invocation/version_manager.py b/localstack-core/localstack/services/lambda_/invocation/version_manager.py index e53049dc82754..dec627bb8a27c 100644 --- a/localstack-core/localstack/services/lambda_/invocation/version_manager.py +++ b/localstack-core/localstack/services/lambda_/invocation/version_manager.py @@ -3,6 +3,7 @@ import threading import time from concurrent.futures import Future +from concurrent.futures._base import ALL_COMPLETED, CancelledError from localstack import config from localstack.aws.api.lambda_ import ( @@ -11,6 +12,7 @@ State, StateReasonCode, ) +from localstack.services.lambda_ import hooks as lambda_hooks from localstack.services.lambda_.invocation.assignment import AssignmentService from localstack.services.lambda_.invocation.counting_service import CountingService from localstack.services.lambda_.invocation.execution_environment import ExecutionEnvironment @@ -29,7 +31,8 @@ record_cw_metric_invocation, ) from localstack.services.lambda_.invocation.runtime_executor import get_runtime_executor -from localstack.utils.strings import long_uid, truncate +from localstack.services.lambda_.ldm import LDMProvisioner +from localstack.utils.strings import long_uid, to_bytes, truncate from localstack.utils.threads import FuncThread, start_thread LOG = logging.getLogger(__name__) @@ -52,6 +55,8 @@ class LambdaVersionManager: counting_service: CountingService assignment_service: AssignmentService + ldm_provisioner: LDMProvisioner | None + def __init__( self, function_arn: str, @@ -74,10 +79,13 @@ def __init__( self.shutdown_event = threading.Event() # async state - self.provisioned_state = None + self.provisioned_state: ProvisionedConcurrencyState | None = None self.provisioned_state_lock = threading.RLock() # https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/ - self.state = VersionState(state=State.Pending) + self.state: VersionState = VersionState(state=State.Pending) + + self.ldm_provisioner = None + lambda_hooks.inject_ldm_provisioner.run(self) def start(self) -> VersionState: try: @@ -92,11 +100,26 @@ def start(self) -> VersionState: # code and reason not set for success scenario because only failed states provide this field: # https://docs.aws.amazon.com/lambda/latest/dg/API_GetFunctionConfiguration.html#SSS-GetFunctionConfiguration-response-LastUpdateStatusReasonCode - self.state = VersionState(state=State.Active) + new_state = State.Active + if ( + self.function_version.config.capacity_provider_config + and self.function_version.id.qualifier == "$LATEST" + ): + new_state = State.ActiveNonInvocable + # HACK: trying to match the AWS timing behavior of Lambda Managed Instances for the operation + # update_function_configuration followed by get_function because transitioning LastUpdateStatus from + # InProgress to Successful happens too fast on LocalStack (thanks to caching in prepare_version). + # Without this hack, test_latest_published_update_config fails at get_function_response_postupdate_latest + # TODO: this sleep has side-effects and we should be looking into alternatives + # Increasing this sleep too much (e.g., 3s) could cause the side effect that a created function is not + # ready for updates (i.e., rejected with a ResourceConflictException) and failing other tests + # time.sleep(0.1) + self.state = VersionState(state=new_state) LOG.debug( - "Changing Lambda %s (id %s) to active", + "Changing Lambda %s (id %s) to %s", self.function_arn, self.function_version.config.internal_revision, + new_state, ) except Exception as e: self.state = VersionState( @@ -105,7 +128,7 @@ def start(self) -> VersionState: reason=f"Error while creating lambda: {e}", ) LOG.debug( - "Changing Lambda %s (id %s) to failed. Reason: %s", + "Changing Lambda %s (id %s) to Failed. Reason: %s", self.function_arn, self.function_version.config.internal_revision, e, @@ -130,8 +153,6 @@ def update_provisioned_concurrency_config( TODO: implement update while in progress (see test_provisioned_concurrency test) TODO: loop until diff == 0 and retry to remove/add diff environments TODO: alias routing & allocated (i.e., the status while updating provisioned concurrency) - TODO: ProvisionedConcurrencyStatusEnum.FAILED - TODO: status reason :param provisioned_concurrent_executions: set to 0 to stop all provisioned environments """ @@ -152,16 +173,37 @@ def scale_environments(*args, **kwargs) -> None: futures = self.assignment_service.scale_provisioned_concurrency( self.id, self.function_version, provisioned_concurrent_executions ) + # Wait for all provisioning/de-provisioning tasks to finish using a timeout longer than max Lambda execution + concurrent.futures.wait(futures, timeout=20 * 60, return_when=ALL_COMPLETED) - concurrent.futures.wait(futures) + success_count = 0 + start_error = None + for i, future in enumerate(futures): + try: + future.result() + success_count += 1 + except Exception as e: + start_error = e with self.provisioned_state_lock: if provisioned_concurrent_executions == 0: self.provisioned_state = None else: - self.provisioned_state.available = provisioned_concurrent_executions - self.provisioned_state.allocated = provisioned_concurrent_executions - self.provisioned_state.status = ProvisionedConcurrencyStatusEnum.READY + # TODO: check whether available changes with active invokes while updating + self.provisioned_state.available = success_count + self.provisioned_state.allocated = success_count + if start_error or success_count < provisioned_concurrent_executions: + self.provisioned_state.status = ProvisionedConcurrencyStatusEnum.FAILED + self.provisioned_state.status_reason = "FUNCTION_ERROR_INIT_FAILURE" + LOG.warning( + "Failed to provision %d/%s environments for function %s. Error: %s", + provisioned_concurrent_executions - success_count, + provisioned_concurrent_executions, + self.function_arn, + start_error, + ) + else: + self.provisioned_state.status = ProvisionedConcurrencyStatusEnum.READY self.provisioning_thread = start_thread(scale_environments) return self.provisioning_thread.result_future @@ -191,8 +233,48 @@ def invoke(self, *, invocation: Invocation) -> InvocationResult: LOG.warning(message) raise ServiceException(message) + # If the environment has debugging enabled, route the invocation there; + # debug environments bypass Lambda service quotas. + if self.ldm_provisioner and ( + ldm_execution_environment := self.ldm_provisioner.get_execution_environment( + qualified_lambda_arn=self.function_version.qualified_arn, + user_agent=invocation.user_agent, + ) + ): + try: + invocation_result = ldm_execution_environment.invoke(invocation) + invocation_result.executed_version = self.function_version.id.qualifier + self.store_logs( + invocation_result=invocation_result, execution_env=ldm_execution_environment + ) + except CancelledError as e: + # Timeouts for invocation futures are managed by LDM, a cancelled error here is + # aligned with the debug container terminating whilst debugging/invocation. + LOG.debug("LDM invocation future encountered a cancelled error: '%s'", e) + invocation_result = InvocationResult( + request_id="", + payload=to_bytes( + "The invocation was canceled because the debug configuration " + "was removed or the operation timed out" + ), + is_error=True, + logs="", + executed_version=self.function_version.id.qualifier, + ) + except StatusErrorException as e: + invocation_result = InvocationResult( + request_id="", + payload=e.payload, + is_error=True, + logs="", + executed_version=self.function_version.id.qualifier, + ) + finally: + ldm_execution_environment.release() + return invocation_result + with self.counting_service.get_invocation_lease( - self.function, self.function_version + self.function, self.function_version, self.provisioned_state ) as provisioning_type: # TODO: potential race condition when changing provisioned concurrency after getting the lease but before # getting an environment diff --git a/localstack-core/localstack/services/lambda_/ldm.py b/localstack-core/localstack/services/lambda_/ldm.py new file mode 100644 index 0000000000000..a5b10e48de9db --- /dev/null +++ b/localstack-core/localstack/services/lambda_/ldm.py @@ -0,0 +1,14 @@ +import abc + +from localstack.aws.api.lambda_ import Arn +from localstack.services.lambda_.invocation.execution_environment import ExecutionEnvironment + +DEFAULT_LDM_TIMEOUT_SECONDS: int = 3_600 +IS_LDM_ENABLED: bool = False + + +class LDMProvisioner(abc.ABC): + @abc.abstractmethod + def get_execution_environment( + self, qualified_lambda_arn: Arn, user_agent: str | None + ) -> ExecutionEnvironment | None: ... diff --git a/localstack-core/localstack/services/lambda_/packages.py b/localstack-core/localstack/services/lambda_/packages.py index 0600b4310ae30..c0f1a6c0a83dc 100644 --- a/localstack-core/localstack/services/lambda_/packages.py +++ b/localstack-core/localstack/services/lambda_/packages.py @@ -4,7 +4,6 @@ import stat from functools import cache from pathlib import Path -from typing import List from localstack import config from localstack.packages import DownloadInstaller, InstallTarget, Package, PackageInstaller @@ -13,7 +12,7 @@ """Customized LocalStack version of the AWS Lambda Runtime Interface Emulator (RIE). https://github.com/localstack/lambda-runtime-init/blob/localstack/README-LOCALSTACK.md """ -LAMBDA_RUNTIME_DEFAULT_VERSION = "v0.1.34-pre" +LAMBDA_RUNTIME_DEFAULT_VERSION = "v0.1.41-pre" LAMBDA_RUNTIME_VERSION = config.LAMBDA_INIT_RELEASE_VERSION or LAMBDA_RUNTIME_DEFAULT_VERSION LAMBDA_RUNTIME_INIT_URL = "https://github.com/localstack/lambda-runtime-init/releases/download/{version}/aws-lambda-rie-{arch}" @@ -35,7 +34,7 @@ class LambdaRuntimePackage(Package): def __init__(self, default_version: str = LAMBDA_RUNTIME_VERSION): super().__init__(name="Lambda", default_version=default_version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [LAMBDA_RUNTIME_VERSION] def _get_installer(self, version: str) -> PackageInstaller: @@ -67,7 +66,10 @@ def _install(self, target: InstallTarget) -> None: super()._install(target) install_location = self.get_executable_path() st = os.stat(install_location) - os.chmod(install_location, mode=st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + os.chmod( + install_location, + mode=st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH, + ) # TODO: replace usage in LocalStack tests with locally built Java jar and remove this unmaintained dependency. @@ -75,7 +77,7 @@ class LambdaJavaPackage(Package): def __init__(self): super().__init__("LambdaJavaLibs", "0.2.22") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["0.2.22", "0.2.21"] def _get_installer(self, version: str) -> PackageInstaller: diff --git a/localstack-core/localstack/services/lambda_/provider.py b/localstack-core/localstack/services/lambda_/provider.py index 516b931723293..51d0d78393f1d 100644 --- a/localstack-core/localstack/services/lambda_/provider.py +++ b/localstack-core/localstack/services/lambda_/provider.py @@ -7,7 +7,7 @@ import re import threading import time -from typing import IO, Any, Optional, Tuple +from typing import IO, Any from botocore.exceptions import ClientError @@ -27,6 +27,7 @@ Arn, Blob, BlobStream, + CapacityProviderConfig, CodeSigningConfigArn, CodeSigningConfigNotFoundException, CodeSigningPolicies, @@ -39,8 +40,10 @@ CreateFunctionRequest, CreateFunctionUrlConfigResponse, DeleteCodeSigningConfigResponse, + DeleteFunctionResponse, Description, DestinationConfig, + DurableExecutionName, EventSourceMappingConfiguration, FunctionCodeLocation, FunctionConfiguration, @@ -48,6 +51,7 @@ FunctionName, FunctionUrlAuthType, FunctionUrlQualifier, + FunctionVersionLatestPublished, GetAccountSettingsResponse, GetCodeSigningConfigResponse, GetFunctionCodeSigningConfigResponse, @@ -65,6 +69,7 @@ InvokeAsyncResponse, InvokeMode, LambdaApi, + LambdaManagedInstancesCapacityProviderConfig, LastUpdateStatus, LayerName, LayerPermissionAllowedAction, @@ -99,6 +104,7 @@ MaxProvisionedConcurrencyConfigListItems, NamespacedFunctionName, NamespacedStatementId, + NumericLatestPublishedOrAliasQualifier, OnFailure, OnSuccess, OrganizationId, @@ -130,6 +136,7 @@ TaggableResource, TagKeyList, Tags, + TenantId, TracingMode, UnqualifiedFunctionName, UpdateCodeSigningConfigResponse, @@ -137,7 +144,7 @@ UpdateFunctionCodeRequest, UpdateFunctionConfigurationRequest, UpdateFunctionUrlConfigResponse, - Version, + VersionWithLatestPublished, ) from localstack.aws.api.lambda_ import FunctionVersion as FunctionVersionApi from localstack.aws.api.lambda_ import ServiceException as LambdaServiceException @@ -151,6 +158,7 @@ from localstack.services.lambda_ import api_utils from localstack.services.lambda_ import hooks as lambda_hooks from localstack.services.lambda_.analytics import ( + FunctionInitializationType, FunctionOperation, FunctionStatus, function_counter, @@ -179,6 +187,7 @@ from localstack.services.lambda_.invocation.lambda_models import ( AliasRoutingConfig, CodeSigningConfig, + DesiredCapacityProviderState, EventInvokeConfig, Function, FunctionResourcePolicy, @@ -209,6 +218,7 @@ store_lambda_archive, store_s3_bucket_archive, ) +from localstack.services.lambda_.invocation.models import CapacityProvider as CapacityProviderModel from localstack.services.lambda_.invocation.models import LambdaStore from localstack.services.lambda_.invocation.runtime_executor import get_runtime_executor from localstack.services.lambda_.lambda_utils import HINT_LOG @@ -224,6 +234,7 @@ DEPRECATED_RUNTIMES_UPGRADES, RUNTIMES_AGGREGATED, SNAP_START_SUPPORTED_RUNTIMES, + VALID_MANAGED_INSTANCE_RUNTIMES, VALID_RUNTIMES, ) from localstack.services.lambda_.urlrouter import FunctionUrlRouter @@ -231,6 +242,7 @@ from localstack.state import StateVisitor from localstack.utils.aws.arns import ( ArnData, + capacity_provider_arn, extract_resource_from_arn, extract_service_from_arn, get_partition, @@ -239,15 +251,15 @@ ) from localstack.utils.aws.client_types import ServicePrincipal from localstack.utils.bootstrap import is_api_enabled -from localstack.utils.collections import PaginatedList +from localstack.utils.collections import PaginatedList, merge_recursive from localstack.utils.event_matcher import validate_event_pattern -from localstack.utils.lambda_debug_mode.lambda_debug_mode_session import LambdaDebugModeSession from localstack.utils.strings import get_random_hex, short_uid, to_bytes, to_str from localstack.utils.sync import poll_condition from localstack.utils.urls import localstack_host LOG = logging.getLogger(__name__) +CAPACITY_PROVIDER_ARN_NAME = "arn:aws[a-zA-Z-]*:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:capacity-provider:[a-zA-Z0-9-_]+" LAMBDA_DEFAULT_TIMEOUT = 3 LAMBDA_DEFAULT_MEMORY_SIZE = 128 @@ -280,18 +292,10 @@ def __init__(self) -> None: def accept_state_visitor(self, visitor: StateVisitor): visitor.visit(lambda_stores) - def on_before_start(self): - # Attempt to start the Lambda Debug Mode session object. - try: - lambda_debug_mode_session = LambdaDebugModeSession.get() - lambda_debug_mode_session.ensure_running() - except Exception as ex: - LOG.error( - "Unexpected error encountered when attempting to initialise Lambda Debug Mode '%s'.", - ex, - ) - def on_before_state_reset(self): + for esm_worker in self.esm_workers.values(): + esm_worker.stop_for_shutdown() + self.esm_workers = {} self.lambda_service.stop() def on_after_state_reset(self): @@ -307,25 +311,53 @@ def on_after_state_load(self): for account_id, account_bundle in lambda_stores.items(): for region_name, state in account_bundle.items(): for fn in state.functions.values(): + # HACK to model a volatile variable that should be ignored for persistence + # Identifier unique to this function and LocalStack instance. + # A LocalStack restart or persistence load should create a new instance id. + # Used for retaining invoke queues across version updates for $LATEST, but + # separate unrelated instances. + fn.instance_id = short_uid() + for fn_version in fn.versions.values(): - # restore the "Pending" state for every function version and start it try: - new_state = VersionState( - state=State.Pending, - code=StateReasonCode.Creating, - reason="The function is being created.", - ) - new_config = dataclasses.replace(fn_version.config, state=new_state) - new_version = dataclasses.replace(fn_version, config=new_config) - fn.versions[fn_version.id.qualifier] = new_version - self.lambda_service.create_function_version(fn_version).result( - timeout=5 + # Skip function versions that were being deleted + if fn_version.config.state.state == State.Deleting: + continue + + # Skip function versions whose capacity provider has been stopped + if fn_version.config.capacity_provider_config: + cp_arn = fn_version.config.capacity_provider_config[ + "LambdaManagedInstancesCapacityProviderConfig" + ]["CapacityProviderArn"] + cp_name = cp_arn.split(":")[-1] + cp = state.capacity_providers.get(cp_name) + if cp and cp.DesiredState == DesiredCapacityProviderState.Stopped: + continue + + # $LATEST is not invokable for Lambda functions with a capacity provider + # and has a different State (i.e., ActiveNonInvokable) + is_capacity_provider_latest = ( + fn_version.config.capacity_provider_config + and fn_version.id.qualifier == "$LATEST" ) + if not is_capacity_provider_latest: + # Restore the "Pending" state for the function version and start it + new_state = VersionState( + state=State.Pending, + code=StateReasonCode.Creating, + reason="The function is being created.", + ) + new_config = dataclasses.replace(fn_version.config, state=new_state) + new_version = dataclasses.replace(fn_version, config=new_config) + fn.versions[fn_version.id.qualifier] = new_version + self.lambda_service.create_function_version(fn_version).result( + timeout=5 + ) except Exception: LOG.warning( "Failed to restore function version %s", fn_version.id.qualified_arn(), - exc_info=True, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) # restore provisioned concurrency per function considering both versions and aliases for ( @@ -356,7 +388,7 @@ def on_after_state_load(self): "Failed to restore provisioned concurrency %s for function %s", provisioned_config, fn_arn, - exc_info=True, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) for esm in state.event_source_mappings.values(): @@ -366,8 +398,10 @@ def on_after_state_load(self): # TODO: How do we know the event source is up? # A basic poll to see if the mapped Lambda function is active/failed if not poll_condition( - lambda: get_function_version_from_arn(function_arn).config.state.state - in [State.Active, State.Failed], + lambda: ( + get_function_version_from_arn(function_arn).config.state.state + in [State.Active, State.Failed] + ), timeout=10, ): LOG.warning( @@ -402,15 +436,6 @@ def on_before_stop(self) -> None: # TODO: should probably unregister routes? self.lambda_service.stop() - # Attempt to signal to the Lambda Debug Mode session object to stop. - try: - lambda_debug_mode_session = LambdaDebugModeSession.get() - lambda_debug_mode_session.signal_stop() - except Exception as ex: - LOG.error( - "Unexpected error encountered when attempting to signal Lambda Debug Mode to stop '%s'.", - ex, - ) @staticmethod def _get_function(function_name: str, account_id: str, region: str) -> Function: @@ -440,6 +465,23 @@ def _get_esm(uuid: str, account_id: str, region: str) -> EventSourceMappingConfi ) return esm + @staticmethod + def _get_capacity_provider( + capacity_provider_name: str, + account_id: str, + region: str, + error_msg_template: str = "Capacity provider not found: {}", + ) -> CapacityProviderModel: + state = lambda_stores[account_id][region] + cp = state.capacity_providers.get(capacity_provider_name) + if not cp: + arn = capacity_provider_arn(capacity_provider_name, account_id, region) + raise ResourceNotFoundException( + error_msg_template.format(arn), + Type="User", + ) + return cp + @staticmethod def _validate_qualifier_expression(qualifier: str) -> None: if error_messages := api_utils.validate_qualifier(qualifier): @@ -447,6 +489,13 @@ def _validate_qualifier_expression(qualifier: str) -> None: message=api_utils.construct_validation_exception_message(error_messages) ) + @staticmethod + def _validate_publish_to(publish_to: str): + if publish_to != FunctionVersionLatestPublished.LATEST_PUBLISHED: + raise ValidationException( + message=f"1 validation error detected: Value '{publish_to}' at 'publishTo' failed to satisfy constraint: Member must satisfy enum value set: [LATEST_PUBLISHED]" + ) + @staticmethod def _resolve_fn_qualifier(resolved_fn: Function, qualifier: str | None) -> tuple[str, str]: """Attempts to resolve a given qualifier and returns a qualifier that exists or @@ -498,7 +547,7 @@ def _build_vpc_config( self, account_id: str, region_name: str, - vpc_config: Optional[dict] = None, + vpc_config: dict | None = None, ) -> VpcConfig | None: if not vpc_config or not is_api_enabled("ec2"): return None @@ -510,7 +559,7 @@ def _build_vpc_config( subnet_id = subnet_ids[0] if not bool(SUBNET_ID_REGEX.match(subnet_id)): raise ValidationException( - f"1 validation error detected: Value '[{subnet_id}]' at 'vpcConfig.subnetIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: ^subnet-[0-9a-z]*$]" + f"1 validation error detected: Value '[{subnet_id}]' at 'vpcConfig.subnetIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: subnet-[0-9a-z]*]" ) return VpcConfig( @@ -527,6 +576,8 @@ def _create_version_model( description: str | None = None, revision_id: str | None = None, code_sha256: str | None = None, + publish_to: FunctionVersionLatestPublished | None = None, + is_active: bool = False, ) -> tuple[FunctionVersion, bool]: """ Release a new version to the model if all restrictions are met. @@ -589,38 +640,57 @@ def _create_version_model( ): return prev_version, False # TODO check if there was a change since last version - next_version = str(function.next_version) - function.next_version += 1 + if publish_to == FunctionVersionLatestPublished.LATEST_PUBLISHED: + qualifier = "$LATEST.PUBLISHED" + else: + qualifier = str(function.next_version) + function.next_version += 1 new_id = VersionIdentifier( function_name=function_name, - qualifier=next_version, + qualifier=qualifier, region=region, account=account_id, ) - apply_on = current_latest_version.config.snap_start["ApplyOn"] - optimization_status = SnapStartOptimizationStatus.Off - if apply_on == SnapStartApplyOn.PublishedVersions: - optimization_status = SnapStartOptimizationStatus.On - snap_start = SnapStartResponse( - ApplyOn=apply_on, - OptimizationStatus=optimization_status, + + if current_latest_version.config.capacity_provider_config: + # for lambda managed functions, snap start is not supported + snap_start = None + else: + apply_on = current_latest_version.config.snap_start["ApplyOn"] + optimization_status = SnapStartOptimizationStatus.Off + if apply_on == SnapStartApplyOn.PublishedVersions: + optimization_status = SnapStartOptimizationStatus.On + snap_start = SnapStartResponse( + ApplyOn=apply_on, + OptimizationStatus=optimization_status, + ) + + last_update = None + new_state = VersionState( + state=State.Pending, + code=StateReasonCode.Creating, + reason="The function is being created.", ) + if publish_to == FunctionVersionLatestPublished.LATEST_PUBLISHED: + last_update = UpdateStatus( + status=LastUpdateStatus.InProgress, + code="Updating", + reason="The function is being updated.", + ) + if is_active: + new_state = VersionState(state=State.Active) new_version = dataclasses.replace( current_latest_version, config=dataclasses.replace( current_latest_version.config, - last_update=None, # versions never have a last update status - state=VersionState( - state=State.Pending, - code=StateReasonCode.Creating, - reason="The function is being created.", - ), + last_update=last_update, + state=new_state, snap_start=snap_start, **changes, ), id=new_id, ) - function.versions[next_version] = new_version + function.versions[qualifier] = new_version return new_version, True def _publish_version_from_existing_version( @@ -631,6 +701,7 @@ def _publish_version_from_existing_version( description: str | None = None, revision_id: str | None = None, code_sha256: str | None = None, + publish_to: FunctionVersionLatestPublished | None = None, ) -> FunctionVersion: """ Publish version from an existing, already initialized LATEST @@ -643,6 +714,7 @@ def _publish_version_from_existing_version( :param code_sha256: code sha (check if current code matches) :return: new version """ + is_active = True if publish_to == FunctionVersionLatestPublished.LATEST_PUBLISHED else False new_version, changed = self._create_version_model( function_name=function_name, region=region, @@ -650,18 +722,34 @@ def _publish_version_from_existing_version( description=description, revision_id=revision_id, code_sha256=code_sha256, + publish_to=publish_to, + is_active=is_active, ) if not changed: return new_version - self.lambda_service.publish_version(new_version) + + if new_version.config.capacity_provider_config: + self.lambda_service.publish_version_async(new_version) + else: + self.lambda_service.publish_version(new_version) state = lambda_stores[account_id][region] function = state.functions.get(function_name) + + # Update revision id for $LATEST version # TODO: re-evaluate data model to prevent this dirty hack just for bumping the revision id latest_version = function.versions["$LATEST"] function.versions["$LATEST"] = dataclasses.replace( latest_version, config=dataclasses.replace(latest_version.config) ) - return function.versions.get(new_version.id.qualifier) + if new_version.config.capacity_provider_config: + # publish_version happens async for functions with a capacity provider. + # Therefore, we return the new_version with State=Pending or LastUpdateStatus=InProgress ($LATEST.PUBLISHED) + return new_version + else: + # Regular functions yield an Active state modified during `publish_version` (sync). + # Therefore, we need to get the updated version from the store. + updated_version = function.versions.get(new_version.id.qualifier) + return updated_version def _publish_version_with_changes( self, @@ -671,6 +759,8 @@ def _publish_version_with_changes( description: str | None = None, revision_id: str | None = None, code_sha256: str | None = None, + publish_to: FunctionVersionLatestPublished | None = None, + is_active: bool = False, ) -> FunctionVersion: """ Publish version together with a new latest version (publish on create / update) @@ -690,6 +780,8 @@ def _publish_version_with_changes( description=description, revision_id=revision_id, code_sha256=code_sha256, + publish_to=publish_to, + is_active=is_active, ) if not changed: return new_version @@ -730,7 +822,7 @@ def _validate_layers(self, new_layers: list[str], region: str, account_id: str): "Cannot reference more than 5 layers.", Type="User" ) - visited_layers = dict() + visited_layers = {} for layer_version_arn in new_layers: ( layer_region, @@ -741,7 +833,8 @@ def _validate_layers(self, new_layers: list[str], region: str, account_id: str): if layer_version_str is None: raise ValidationException( f"1 validation error detected: Value '[{layer_version_arn}]'" - + r" at 'layers' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 140, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: (arn:[a-zA-Z0-9-]+:lambda:[a-z]{2}((-gov)|(-iso(b?)))?-[a-z]+-\d{1}:\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+), Member must not be null]", + + " at 'layers' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 2048, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: " + + "(arn:(aws[a-zA-Z-]*)?:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+), Member must not be null]", ) state = lambda_stores[layer_account_id][layer_region] @@ -804,6 +897,30 @@ def _validate_layers(self, new_layers: list[str], region: str, account_id: str): ) visited_layers[layer_arn] = layer_version_arn + def _validate_capacity_provider_config( + self, capacity_provider_config: CapacityProviderConfig, context: RequestContext + ): + if not capacity_provider_config.get("LambdaManagedInstancesCapacityProviderConfig"): + raise ValidationException( + "1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig' failed to satisfy constraint: Member must not be null" + ) + + capacity_provider_arn = capacity_provider_config.get( + "LambdaManagedInstancesCapacityProviderConfig", {} + ).get("CapacityProviderArn") + if not capacity_provider_arn: + raise ValidationException( + "1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig.capacityProviderArn' failed to satisfy constraint: Member must not be null" + ) + + if not re.match(CAPACITY_PROVIDER_ARN_NAME, capacity_provider_arn): + raise ValidationException( + f"1 validation error detected: Value '{capacity_provider_arn}' at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig.capacityProviderArn' failed to satisfy constraint: Member must satisfy regular expression pattern: {CAPACITY_PROVIDER_ARN_NAME}" + ) + + capacity_provider_name = capacity_provider_arn.split(":")[-1] + self.get_capacity_provider(context, capacity_provider_name) + @staticmethod def map_layers(new_layers: list[str]) -> list[LayerVersion]: layers = [] @@ -858,7 +975,7 @@ def create_function( context_region = context.region context_account_id = context.account_id - zip_file = request.get("Code", {}).get("ZipFile") + zip_file = (request.get("Code") or {}).get("ZipFile") if zip_file and len(zip_file) > config.LAMBDA_LIMITS_CODE_SIZE_ZIPPED: raise RequestEntityTooLargeException( f"Zipped size must be smaller than {config.LAMBDA_LIMITS_CODE_SIZE_ZIPPED} bytes" @@ -919,6 +1036,8 @@ def create_function( ) if snap_start := request.get("SnapStart"): self._validate_snapstart(snap_start, runtime) + if publish_to := request.get("PublishTo"): + self._validate_publish_to(publish_to) state = lambda_stores[context_account_id][context_region] with self.create_fn_lock: @@ -940,7 +1059,7 @@ def create_function( # Potential implementation: provide (cached) sha256 hash of used Docker image RuntimeVersionArn=f"arn:{context.partition}:lambda:{context_region}::runtime:8eeff65f6809a3ce81507fe733fe09b835899b99481ba22fd75b5a7338290ec1" ) - request_code = request.get("Code") + request_code = request.get("Code") or {} if package_type == PackageType.Zip: # TODO verify if correct combination of code is set if zip_file := request_code.get("ZipFile"): @@ -962,14 +1081,16 @@ def create_function( account_id=context_account_id, ) else: - raise LambdaServiceException("Gotta have s3 bucket or zip file") + raise LambdaServiceException("A ZIP file or S3 bucket is required") elif package_type == PackageType.Image: image = request_code.get("ImageUri") if not image: - raise LambdaServiceException("Gotta have an image when package type is image") + raise LambdaServiceException( + "An image is required when the package type is set to 'image'" + ) image = create_image_code(image_uri=image) - image_config_req = request.get("ImageConfig", {}) + image_config_req = request.get("ImageConfig") or {} image_config = ImageConfig( command=image_config_req.get("Command"), entrypoint=image_config_req.get("EntryPoint"), @@ -977,6 +1098,27 @@ def create_function( ) # Runtime management controls are not available when providing a custom image runtime_version_config = None + + capacity_provider_config = None + memory_size = request.get("MemorySize", LAMBDA_DEFAULT_MEMORY_SIZE) + if "CapacityProviderConfig" in request: + capacity_provider_config = request["CapacityProviderConfig"] + self._validate_capacity_provider_config(capacity_provider_config, context) + self._validate_managed_instances_runtime(runtime) + + default_config = CapacityProviderConfig( + LambdaManagedInstancesCapacityProviderConfig=LambdaManagedInstancesCapacityProviderConfig( + ExecutionEnvironmentMemoryGiBPerVCpu=2.0, + PerExecutionEnvironmentMaxConcurrency=16, + ) + ) + capacity_provider_config = merge_recursive(default_config, capacity_provider_config) + memory_size = 2048 + if (request.get("LoggingConfig") or {}).get("LogFormat") == LogFormat.Text: + raise InvalidParameterValueException( + 'LogLevel is not supported when LogFormat is set to "Text". Remove LogLevel from your request or change the LogFormat to "JSON" and try again.', + Type="User", + ) if "LoggingConfig" in request: logging_config = request["LoggingConfig"] LOG.warning( @@ -1000,11 +1142,25 @@ def create_function( | logging_config ) + elif capacity_provider_config: + logging_config = LoggingConfig( + LogFormat=LogFormat.JSON, + LogGroup=f"/aws/lambda/{function_name}", + ApplicationLogLevel="INFO", + SystemLogLevel="INFO", + ) else: logging_config = LoggingConfig( LogFormat=LogFormat.Text, LogGroup=f"/aws/lambda/{function_name}" ) - + snap_start = ( + None + if capacity_provider_config + else SnapStartResponse( + ApplyOn=request.get("SnapStart", {}).get("ApplyOn", SnapStartApplyOn.None_), + OptimizationStatus=SnapStartOptimizationStatus.Off, + ) + ) version = FunctionVersion( id=arn, config=VersionFunctionConfiguration( @@ -1013,7 +1169,7 @@ def create_function( role=request["Role"], timeout=request.get("Timeout", LAMBDA_DEFAULT_TIMEOUT), runtime=request.get("Runtime"), - memory_size=request.get("MemorySize", LAMBDA_DEFAULT_MEMORY_SIZE), + memory_size=memory_size, handler=request.get("Handler"), package_type=package_type, environment=env_vars, @@ -1029,10 +1185,7 @@ def create_function( ephemeral_storage=LambdaEphemeralStorage( size=request.get("EphemeralStorage", {}).get("Size", 512) ), - snap_start=SnapStartResponse( - ApplyOn=request.get("SnapStart", {}).get("ApplyOn", SnapStartApplyOn.None_), - OptimizationStatus=SnapStartOptimizationStatus.Off, - ), + snap_start=snap_start, runtime_version_config=runtime_version_config, dead_letter_arn=request.get("DeadLetterConfig", {}).get("TargetArn"), vpc_config=self._build_vpc_config( @@ -1044,18 +1197,40 @@ def create_function( reason="The function is being created.", ), logging_config=logging_config, + # TODO: might need something like **optional_kwargs if None + # -> Test with regular GetFunction (i.e., without a capacity provider) + capacity_provider_config=capacity_provider_config, ), ) - fn.versions["$LATEST"] = version + version_post_response = None + if capacity_provider_config: + version_post_response = dataclasses.replace( + version, + config=dataclasses.replace( + version.config, + last_update=UpdateStatus(status=LastUpdateStatus.Successful), + state=VersionState(state=State.ActiveNonInvocable), + ), + ) + fn.versions["$LATEST"] = version_post_response or version state.functions[function_name] = fn + initialization_type = ( + FunctionInitializationType.lambda_managed_instances + if capacity_provider_config + else FunctionInitializationType.on_demand + ) function_counter.labels( operation=FunctionOperation.create, runtime=runtime or "n/a", status=FunctionStatus.success, invocation_type="n/a", package_type=package_type, + initialization_type=initialization_type, ) - self.lambda_service.create_function_version(version) + # TODO: consider potential other side effects of not having a function version for $LATEST + # Provisioning happens upon publishing for functions using a capacity provider + if not capacity_provider_config: + self.lambda_service.create_function_version(version) if tags := request.get("Tags"): # This will check whether the function exists. @@ -1063,16 +1238,21 @@ def create_function( if request.get("Publish"): version = self._publish_version_with_changes( - function_name=function_name, region=context_region, account_id=context_account_id + function_name=function_name, + region=context_region, + account_id=context_account_id, + publish_to=request.get("PublishTo"), ) if config.LAMBDA_SYNCHRONOUS_CREATE: # block via retrying until "terminal" condition reached before returning if not poll_condition( - lambda: get_function_version( - function_name, version.id.qualifier, version.id.account, version.id.region - ).config.state.state - in [State.Active, State.Failed], + lambda: ( + get_function_version( + function_name, version.id.qualifier, version.id.account, version.id.region + ).config.state.state + in [State.Active, State.ActiveNonInvocable, State.Failed] + ), timeout=10, ): LOG.warning( @@ -1103,6 +1283,12 @@ def _validate_runtime(self, package_type, runtime): Type="User", ) + def _validate_managed_instances_runtime(self, runtime): + if runtime not in VALID_MANAGED_INSTANCE_RUNTIMES: + raise InvalidParameterValueException( + f"Runtime Enum {runtime} does not support specified feature: Lambda Managed Instances" + ) + def _check_for_recomended_migration_target(self, deprecated_runtime): # AWS offers recommended runtime for migration for "newly" deprecated runtimes # in order to preserve parity with error messages we need the code bellow @@ -1263,6 +1449,30 @@ def update_function_configuration( if new_mode: replace_kwargs["tracing_config_mode"] = new_mode + if "CapacityProviderConfig" in request: + capacity_provider_config = request["CapacityProviderConfig"] + self._validate_capacity_provider_config(capacity_provider_config, context) + + if latest_version.config.capacity_provider_config and not request[ + "CapacityProviderConfig" + ].get("LambdaManagedInstancesCapacityProviderConfig"): + raise ValidationException( + "1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig' failed to satisfy constraint: Member must not be null" + ) + if not latest_version.config.capacity_provider_config: + raise InvalidParameterValueException( + "CapacityProviderConfig isn't supported for Lambda Default functions.", + Type="User", + ) + + default_config = CapacityProviderConfig( + LambdaManagedInstancesCapacityProviderConfig=LambdaManagedInstancesCapacityProviderConfig( + ExecutionEnvironmentMemoryGiBPerVCpu=2.0, + PerExecutionEnvironmentMaxConcurrency=16, + ) + ) + capacity_provider_config = merge_recursive(default_config, capacity_provider_config) + replace_kwargs["capacity_provider_config"] = capacity_provider_config new_latest_version = dataclasses.replace( latest_version, config=dataclasses.replace( @@ -1278,7 +1488,22 @@ def update_function_configuration( ), ) function.versions["$LATEST"] = new_latest_version # TODO: notify - self.lambda_service.update_version(new_version=new_latest_version) + + if function.latest().config.capacity_provider_config: + + def _update_version_with_logging(): + try: + self.lambda_service.update_version(new_latest_version) + except Exception: + LOG.error( + "Failed to update Lambda Managed Instances function version %s", + new_latest_version.id.qualified_arn(), + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) + + self.lambda_service.task_executor.submit(_update_version_with_logging) + else: + self.lambda_service.update_version(new_version=new_latest_version) return api_utils.map_config_out(new_latest_version) @@ -1326,6 +1551,9 @@ def update_function_code( Type="User", ) + if publish_to := request.get("PublishTo"): + self._validate_publish_to(publish_to) + if zip_file := request.get("ZipFile"): code = store_lambda_archive( archive_file=zip_file, @@ -1348,7 +1576,7 @@ def update_function_code( code = None image = create_image_code(image_uri=image) else: - raise LambdaServiceException("Gotta have s3 bucket or zip file or image") + raise LambdaServiceException("A ZIP file, S3 bucket, or image is required") old_function_version = function.versions.get("$LATEST") replace_kwargs = {"code": code} if code else {"image": image} @@ -1386,7 +1614,11 @@ def update_function_code( self.lambda_service.update_version(new_version=function_version) if request.get("Publish"): function_version = self._publish_version_with_changes( - function_name=function_name, region=region, account_id=account_id + function_name=function_name, + region=region, + account_id=account_id, + publish_to=publish_to, + is_active=True, ) return api_utils.map_config_out( function_version, return_qualified_arn=bool(request.get("Publish")) @@ -1401,10 +1633,10 @@ def update_function_code( def delete_function( self, context: RequestContext, - function_name: FunctionName, - qualifier: Qualifier = None, + function_name: NamespacedFunctionName, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, - ) -> None: + ) -> DeleteFunctionResponse: account_id, region = api_utils.get_account_and_region(function_name, context) function_name, qualifier = api_utils.get_name_and_qualifier( function_name, qualifier, context @@ -1422,30 +1654,55 @@ def delete_function( "$LATEST version cannot be deleted without deleting the function.", Type="User" ) + unqualified_function_arn = api_utils.unqualified_lambda_arn( + function_name=function_name, region=region, account=account_id + ) if function_name not in store.functions: e = ResourceNotFoundException( - f"Function not found: {api_utils.unqualified_lambda_arn(function_name=function_name, region=region, account=account_id)}", + f"Function not found: {unqualified_function_arn}", Type="User", ) raise e function = store.functions.get(function_name) + function_has_capacity_provider = False if qualifier: # delete a version of the function - version = function.versions.pop(qualifier, None) + version = function.versions.get(qualifier, None) if version: + if version.config.capacity_provider_config: + function_has_capacity_provider = True + # async delete from store + self.lambda_service.delete_function_version_async(function, version, qualifier) + else: + function.versions.pop(qualifier, None) self.lambda_service.stop_version(version.id.qualified_arn()) destroy_code_if_not_used(code=version.config.code, function=function) else: # delete the whole function + self._remove_all_tags(unqualified_function_arn) # TODO: introduce locking for safe deletion: We could create a new version at the API layer before # the old version gets cleaned up in the internal lambda service. - function = store.functions.pop(function_name) + function = store.functions.get(function_name) + if function.latest().config.capacity_provider_config: + function_has_capacity_provider = True + # async delete version from store + self.lambda_service.delete_function_async(store, function_name) + for version in function.versions.values(): - self.lambda_service.stop_version(qualified_arn=version.id.qualified_arn()) + # Functions with a capacity provider do NOT have a version manager for $LATEST because only + # published versions are invokable. + if not function_has_capacity_provider or ( + function_has_capacity_provider and version.id.qualifier != "$LATEST" + ): + self.lambda_service.stop_version(qualified_arn=version.id.qualified_arn()) # we can safely destroy the code here if version.config.code: version.config.code.destroy() + if not function_has_capacity_provider: + store.functions.pop(function_name, None) + + return DeleteFunctionResponse(StatusCode=202 if function_has_capacity_provider else 204) def list_functions( self, @@ -1490,7 +1747,7 @@ def get_function( self, context: RequestContext, function_name: NamespacedFunctionName, - qualifier: Qualifier = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> GetFunctionResponse: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -1533,7 +1790,8 @@ def get_function( code_location = None if code := version.config.code: code_location = FunctionCodeLocation( - Location=code.generate_presigned_url(), RepositoryType="S3" + Location=code.generate_presigned_url(endpoint_url=config.external_service_url()), + RepositoryType="S3", ) elif image := version.config.image: code_location = FunctionCodeLocation( @@ -1560,7 +1818,7 @@ def get_function_configuration( self, context: RequestContext, function_name: NamespacedFunctionName, - qualifier: Qualifier = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> FunctionConfiguration: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -1580,11 +1838,13 @@ def invoke( self, context: RequestContext, function_name: NamespacedFunctionName, - invocation_type: InvocationType = None, - log_type: LogType = None, - client_context: String = None, - payload: IO[Blob] = None, - qualifier: Qualifier = None, + invocation_type: InvocationType | None = None, + log_type: LogType | None = None, + client_context: String | None = None, + durable_execution_name: DurableExecutionName | None = None, + payload: IO[Blob] | None = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, + tenant_id: TenantId | None = None, **kwargs, ) -> InvocationResponse: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -1592,6 +1852,8 @@ def invoke( function_name, qualifier, context ) + user_agent = context.request.user_agent.string + time_before = time.perf_counter() try: invocation_result = self.lambda_service.invoke( @@ -1604,6 +1866,7 @@ def invoke( request_id=context.request_id, trace_context=context.trace_context, payload=payload.read() if payload else None, + user_agent=user_agent, ) except ServiceException: raise @@ -1651,13 +1914,16 @@ def publish_version( self, context: RequestContext, function_name: FunctionName, - code_sha256: String = None, - description: Description = None, - revision_id: String = None, + code_sha256: String | None = None, + description: Description | None = None, + revision_id: String | None = None, + publish_to: FunctionVersionLatestPublished | None = None, **kwargs, ) -> FunctionConfiguration: account_id, region = api_utils.get_account_and_region(function_name, context) function_name = api_utils.get_function_name(function_name, context) + if publish_to: + self._validate_publish_to(publish_to) new_version = self._publish_version_from_existing_version( function_name=function_name, description=description, @@ -1665,6 +1931,7 @@ def publish_version( region=region, revision_id=revision_id, code_sha256=code_sha256, + publish_to=publish_to, ) return api_utils.map_config_out(new_version, return_qualified_arn=True) @@ -1723,7 +1990,7 @@ def _create_routing_config_model( ) if not api_utils.qualifier_is_version(key): raise ValidationException( - f"1 validation error detected: Value '{{{key}={value}}}' at 'routingConfig.additionalVersionWeights' failed to satisfy constraint: Map keys must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: [0-9]+, Member must not be null]" + f"1 validation error detected: Value '{{{key}={value}}}' at 'routingConfig.additionalVersionWeights' failed to satisfy constraint: Map keys must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: [0-9]+]" ) # checking if the version in the config exists @@ -1740,7 +2007,7 @@ def create_alias( context: RequestContext, function_name: FunctionName, name: Alias, - function_version: Version, + function_version: VersionWithLatestPublished, description: Description = None, routing_config: AliasRoutingConfiguration = None, **kwargs, @@ -1791,7 +2058,7 @@ def list_aliases( self, context: RequestContext, function_name: FunctionName, - function_version: Version = None, + function_version: VersionWithLatestPublished = None, marker: String = None, max_items: MaxListItems = None, **kwargs, @@ -1859,7 +2126,7 @@ def update_alias( context: RequestContext, function_name: FunctionName, name: Alias, - function_version: Version = None, + function_version: VersionWithLatestPublished = None, description: Description = None, routing_config: AliasRoutingConfiguration = None, revision_id: String = None, @@ -2086,6 +2353,9 @@ def validate_event_source_mapping(self, context, request): raise Exception("unknown version") # TODO: cover via test elif qualifier == "$LATEST": pass + elif qualifier == "$LATEST.PUBLISHED": + if fn.versions.get(qualifier): + pass else: raise Exception("invalid functionname") # TODO: cover via test fn_arn = api_utils.qualified_lambda_arn(function_name, qualifier, account, region) @@ -2241,6 +2511,10 @@ def delete_event_source_mapping( raise ResourceNotFoundException( "The resource you requested does not exist.", Type="User" ) + # the full deletion of the ESM is happening asynchronously, but we delete the Tags instantly + # this behavior is similar to ``get_event_source_mapping`` which will raise right after deletion, but it is not + # always the case in AWS. Add more testing and align behavior with ``get_event_source_mapping``. + self._remove_all_tags(event_source_mapping["EventSourceMappingArn"]) esm_worker.delete() return {**esm, "State": EsmState.DELETING} @@ -2305,7 +2579,9 @@ def get_source_type_from_request(self, request: dict[str, Any]) -> str: @staticmethod def _validate_qualifier(qualifier: str) -> None: - if qualifier == "$LATEST" or (qualifier and api_utils.qualifier_is_version(qualifier)): + if qualifier in ["$LATEST", "$LATEST.PUBLISHED"] or ( + qualifier and api_utils.qualifier_is_version(qualifier) + ): raise ValidationException( f"1 validation error detected: Value '{qualifier}' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: ((?!^\\d+$)^[0-9a-zA-Z-_]+$)" ) @@ -2608,7 +2884,7 @@ def add_permission( if revision_id != fn_revision_id: raise PreconditionFailedException( "The Revision Id provided does not match the latest Revision Id. " - "Call the GetFunction/GetAlias API to retrieve the latest Revision Id", + "Call the GetPolicy API to retrieve the latest Revision Id", Type="User", ) @@ -2666,10 +2942,10 @@ def add_permission( def remove_permission( self, context: RequestContext, - function_name: FunctionName, + function_name: NamespacedFunctionName, statement_id: NamespacedStatementId, - qualifier: Qualifier = None, - revision_id: String = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, + revision_id: String | None = None, **kwargs, ) -> None: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -2733,7 +3009,7 @@ def get_policy( self, context: RequestContext, function_name: NamespacedFunctionName, - qualifier: Qualifier = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> GetPolicyResponse: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -2775,9 +3051,9 @@ def create_code_signing_config( self, context: RequestContext, allowed_publishers: AllowedPublishers, - description: Description = None, - code_signing_policies: CodeSigningPolicies = None, - tags: Tags = None, + description: Description | None = None, + code_signing_policies: CodeSigningPolicies | None = None, + tags: Tags | None = None, **kwargs, ) -> CreateCodeSigningConfigResponse: account = context.account_id @@ -2802,7 +3078,7 @@ def put_function_code_signing_config( self, context: RequestContext, code_signing_config_arn: CodeSigningConfigArn, - function_name: FunctionName, + function_name: NamespacedFunctionName, **kwargs, ) -> PutFunctionCodeSigningConfigResponse: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -2869,7 +3145,7 @@ def get_code_signing_config( return GetCodeSigningConfigResponse(CodeSigningConfig=api_utils.map_csc(csc)) def get_function_code_signing_config( - self, context: RequestContext, function_name: FunctionName, **kwargs + self, context: RequestContext, function_name: NamespacedFunctionName, **kwargs ) -> GetFunctionCodeSigningConfigResponse: account_id, region = api_utils.get_account_and_region(function_name, context) state = lambda_stores[account_id][region] @@ -2887,7 +3163,7 @@ def get_function_code_signing_config( return GetFunctionCodeSigningConfigResponse() def delete_function_code_signing_config( - self, context: RequestContext, function_name: FunctionName, **kwargs + self, context: RequestContext, function_name: NamespacedFunctionName, **kwargs ) -> None: account_id, region = api_utils.get_account_and_region(function_name, context) state = lambda_stores[account_id][region] @@ -3297,7 +3573,8 @@ def _validate_destination_arn(destination_arn) -> bool: raise ValidationException( "1 validation error detected: Value '" + destination_arn - + r"' at 'destinationConfig.onFailure.destination' failed to satisfy constraint: Member must satisfy regular expression pattern: ^$|arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\-])+:([a-z]{2}((-gov)|(-iso(b?)))?-[a-z]+-\d{1})?:(\d{12})?:(.*)" + + "' at 'destinationConfig.onFailure.destination' failed to satisfy constraint: Member must satisfy regular expression pattern: " + + "$|kafka://([^.]([a-zA-Z0-9\\-_.]{0,248}))|arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\\-])+:((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1})?:(\\d{12})?:(.*)" ) match destination_arn.split(":")[2]: @@ -3347,7 +3624,7 @@ def put_function_event_invoke_config( self, context: RequestContext, function_name: FunctionName, - qualifier: Qualifier = None, + qualifier: NumericLatestPublishedOrAliasQualifier = None, maximum_retry_attempts: MaximumRetryAttempts = None, maximum_event_age_in_seconds: MaximumEventAgeInSeconds = None, destination_config: DestinationConfig = None, @@ -3429,7 +3706,7 @@ def get_function_event_invoke_config( self, context: RequestContext, function_name: FunctionName, - qualifier: Qualifier = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> FunctionEventInvokeConfig: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -3506,7 +3783,7 @@ def delete_function_event_invoke_config( self, context: RequestContext, function_name: FunctionName, - qualifier: Qualifier = None, + qualifier: NumericLatestPublishedOrAliasQualifier | None = None, **kwargs, ) -> None: account_id, region = api_utils.get_account_and_region(function_name, context) @@ -3534,7 +3811,7 @@ def update_function_event_invoke_config( self, context: RequestContext, function_name: FunctionName, - qualifier: Qualifier = None, + qualifier: NumericLatestPublishedOrAliasQualifier = None, maximum_retry_attempts: MaximumRetryAttempts = None, maximum_event_age_in_seconds: MaximumEventAgeInSeconds = None, destination_config: DestinationConfig = None, @@ -3607,7 +3884,7 @@ def update_function_event_invoke_config( @staticmethod def _resolve_layer( layer_name_or_arn: str, context: RequestContext - ) -> Tuple[str, str, str, Optional[str]]: + ) -> tuple[str, str, str, str | None]: """ Return locator attributes for a given Lambda layer. @@ -3625,10 +3902,10 @@ def publish_layer_version( context: RequestContext, layer_name: LayerName, content: LayerVersionContentInput, - description: Description = None, - compatible_runtimes: CompatibleRuntimes = None, - license_info: LicenseInfo = None, - compatible_architectures: CompatibleArchitectures = None, + description: Description | None = None, + compatible_runtimes: CompatibleRuntimes | None = None, + license_info: LicenseInfo | None = None, + compatible_architectures: CompatibleArchitectures | None = None, **kwargs, ) -> PublishLayerVersionResponse: """ @@ -3745,7 +4022,7 @@ def get_layer_version_by_arn( if not layer_version: raise ValidationException( f"1 validation error detected: Value '{arn}' at 'arn' failed to satisfy constraint: Member must satisfy regular expression pattern: " - + "(arn:(aws[a-zA-Z-]*)?:lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+)" + + "(arn:(aws[a-zA-Z-]*)?:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+)" ) store = lambda_stores[account_id][region_name] @@ -3766,10 +4043,10 @@ def get_layer_version_by_arn( def list_layers( self, context: RequestContext, - compatible_runtime: Runtime = None, - marker: String = None, - max_items: MaxLayerListItems = None, - compatible_architecture: Architecture = None, + compatible_runtime: Runtime | None = None, + marker: String | None = None, + max_items: MaxLayerListItems | None = None, + compatible_architecture: Architecture | None = None, **kwargs, ) -> ListLayersResponse: validation_errors = [] @@ -3821,10 +4098,10 @@ def list_layer_versions( self, context: RequestContext, layer_name: LayerName, - compatible_runtime: Runtime = None, - marker: String = None, - max_items: MaxLayerListItems = None, - compatible_architecture: Architecture = None, + compatible_runtime: Runtime | None = None, + marker: String | None = None, + max_items: MaxLayerListItems | None = None, + compatible_architecture: Architecture | None = None, **kwargs, ) -> ListLayerVersionsResponse: validation_errors = api_utils.validate_layer_runtimes_and_architectures( @@ -4150,29 +4427,66 @@ def delete_function_concurrency( # ======================================= # =============== TAGS =============== # ======================================= - # only Function, Event Source Mapping, and Code Signing Config (not currently supported by LocalStack) ARNs an are available for tagging in AWS + # only Function, Event Source Mapping, and Code Signing Config (not currently supported by LocalStack) ARNs + # are available for tagging in AWS - def _get_tags(self, resource: TaggableResource) -> dict[str, str]: - state = self.fetch_lambda_store_for_tagging(resource) - lambda_adapted_tags = { - tag["Key"]: tag["Value"] - for tag in state.TAGS.list_tags_for_resource(resource).get("Tags") - } - return lambda_adapted_tags + @staticmethod + def _update_resource_tags( + resource_arn: str, account_id: str, region: str, tags: dict[str, str] + ) -> None: + lambda_stores[account_id][region].tags.update_tags(resource_arn, tags) + + @staticmethod + def _list_resource_tags(resource_arn: str, account_id: str, region: str) -> dict[str, str]: + return lambda_stores[account_id][region].tags.get_tags(resource_arn) - def _store_tags(self, resource: TaggableResource, tags: dict[str, str]): - state = self.fetch_lambda_store_for_tagging(resource) - if len(state.TAGS.tags.get(resource, {}) | tags) > LAMBDA_TAG_LIMIT_PER_RESOURCE: + @staticmethod + def _remove_resource_tags( + resource_arn: str, account_id: str, region: str, keys: TagKeyList + ) -> None: + lambda_stores[account_id][region].tags.delete_tags(resource_arn, keys) + + @staticmethod + def _remove_all_resource_tags(resource_arn: str, account_id: str, region: str) -> None: + lambda_stores[account_id][region].tags.delete_all_tags(resource_arn) + + def _get_tags(self, resource: TaggableResource) -> dict[str, str]: + account_id, region = self._get_account_id_and_region_for_taggable_resource(resource) + tags = self._list_resource_tags(resource_arn=resource, account_id=account_id, region=region) + return tags + + def _store_tags(self, resource: TaggableResource, tags: dict[str, str]) -> None: + account_id, region = self._get_account_id_and_region_for_taggable_resource(resource) + existing_tags = self._list_resource_tags( + resource_arn=resource, account_id=account_id, region=region + ) + if len({**existing_tags, **tags}) > LAMBDA_TAG_LIMIT_PER_RESOURCE: + # note: we cannot use | on `ImmutableDict` and regular `dict` raise InvalidParameterValueException( "Number of tags exceeds resource tag limit.", Type="User" ) + self._update_resource_tags( + resource_arn=resource, + account_id=account_id, + region=region, + tags=tags, + ) - tag_svc_adapted_tags = [{"Key": key, "Value": value} for key, value in tags.items()] - state.TAGS.tag_resource(resource, tag_svc_adapted_tags) + def _remove_tags(self, resource: TaggableResource, keys: TagKeyList) -> None: + account_id, region = self._get_account_id_and_region_for_taggable_resource(resource) + self._remove_resource_tags( + resource_arn=resource, account_id=account_id, region=region, keys=keys + ) - def fetch_lambda_store_for_tagging(self, resource: TaggableResource) -> LambdaStore: + def _remove_all_tags(self, resource: TaggableResource) -> None: + account_id, region = self._get_account_id_and_region_for_taggable_resource(resource) + self._remove_all_resource_tags(resource_arn=resource, account_id=account_id, region=region) + + def _get_account_id_and_region_for_taggable_resource( + self, resource: TaggableResource + ) -> tuple[str, str]: """ - Takes a resource ARN for a TaggableResource (Lambda Function, Event Source Mapping, or Code Signing Config) and returns a corresponding + Takes a resource ARN for a TaggableResource (Lambda Function, Event Source Mapping, Code Signing Config, or Capacity Provider) and returns a corresponding LambdaStore for its region and account. In addition, this function validates that the ARN is a valid TaggableResource type, and that the TaggableResource exists. @@ -4207,9 +4521,8 @@ def _raise_validation_exception(): _raise_validation_exception() resource_type, resource_identifier, *qualifier = parts - if resource_type not in {"event-source-mapping", "code-signing-config", "function"}: - _raise_validation_exception() + # Qualifier validation raises before checking for NotFound if qualifier: if resource_type == "function": raise InvalidParameterValueException( @@ -4218,18 +4531,21 @@ def _raise_validation_exception(): ) _raise_validation_exception() - match resource_type: - case "event-source-mapping": - self._get_esm(resource_identifier, account_id, region) - case "code-signing-config": - raise NotImplementedError("Resource tagging on CSC not yet implemented.") - case "function": - self._get_function( - function_name=resource_identifier, account_id=account_id, region=region - ) + if resource_type == "event-source-mapping": + self._get_esm(resource_identifier, account_id, region) + elif resource_type == "code-signing-config": + raise NotImplementedError("Resource tagging on CSC not yet implemented.") + elif resource_type == "function": + self._get_function( + function_name=resource_identifier, account_id=account_id, region=region + ) + elif resource_type == "capacity-provider": + self._get_capacity_provider(resource_identifier, account_id, region) + else: + _raise_validation_exception() # If no exceptions are raised, assume ARN and referenced resource is valid for tag operations - return lambda_stores[account_id][region] + return account_id, region def tag_resource( self, context: RequestContext, resource: TaggableResource, tags: Tags, **kwargs @@ -4266,8 +4582,7 @@ def untag_resource( "1 validation error detected: Value null at 'tagKeys' failed to satisfy constraint: Member must not be null" ) # should probably be generalized a bit - state = self.fetch_lambda_store_for_tagging(resource) - state.TAGS.untag_resource(resource, tag_keys) + self._remove_tags(resource, tag_keys) if (resource_id := extract_resource_from_arn(resource)) and resource_id.startswith( "function" diff --git a/localstack-core/localstack/services/lambda_/provider_utils.py b/localstack-core/localstack/services/lambda_/provider_utils.py index 4c0c4e7e1bc8b..4e6e8778e645f 100644 --- a/localstack-core/localstack/services/lambda_/provider_utils.py +++ b/localstack-core/localstack/services/lambda_/provider_utils.py @@ -74,7 +74,7 @@ class LambdaLayerVersionIdentifier(ResourceIdentifier): resource = "layer-version" def __init__(self, account_id: str, region: str, layer_name: str): - super(LambdaLayerVersionIdentifier, self).__init__(account_id, region, layer_name) + super().__init__(account_id, region, layer_name) def generate( self, existing_ids: ExistingIds = None, tags: Tags = None, next_version: int = None diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py index 8a23156e4ab13..c6044d26b564d 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,19 +14,19 @@ class LambdaCodeSigningConfigProperties(TypedDict): - AllowedPublishers: Optional[AllowedPublishers] - CodeSigningConfigArn: Optional[str] - CodeSigningConfigId: Optional[str] - CodeSigningPolicies: Optional[CodeSigningPolicies] - Description: Optional[str] + AllowedPublishers: AllowedPublishers | None + CodeSigningConfigArn: str | None + CodeSigningConfigId: str | None + CodeSigningPolicies: CodeSigningPolicies | None + Description: str | None class AllowedPublishers(TypedDict): - SigningProfileVersionArns: Optional[list[str]] + SigningProfileVersionArns: list[str] | None class CodeSigningPolicies(TypedDict): - UntrustedArtifactOnDeployment: Optional[str] + UntrustedArtifactOnDeployment: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py index b165c1253e910..7b9b830fbb5a1 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaCodeSigningConfigProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::Lambda::CodeSigningConfig" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_codesigningconfig import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py index 60dec55595e95..a084602b9b271 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py @@ -3,7 +3,7 @@ import uuid from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,25 +15,25 @@ class LambdaEventInvokeConfigProperties(TypedDict): - FunctionName: Optional[str] - Qualifier: Optional[str] - DestinationConfig: Optional[DestinationConfig] - Id: Optional[str] - MaximumEventAgeInSeconds: Optional[int] - MaximumRetryAttempts: Optional[int] + FunctionName: str | None + Qualifier: str | None + DestinationConfig: DestinationConfig | None + Id: str | None + MaximumEventAgeInSeconds: int | None + MaximumRetryAttempts: int | None class OnSuccess(TypedDict): - Destination: Optional[str] + Destination: str | None class OnFailure(TypedDict): - Destination: Optional[str] + Destination: str | None class DestinationConfig(TypedDict): - OnFailure: Optional[OnFailure] - OnSuccess: Optional[OnSuccess] + OnFailure: OnFailure | None + OnSuccess: OnSuccess | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py index 6ebda8450ef65..f921fa0283145 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaEventInvokeConfigProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::Lambda::EventInvokeConfig" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_eventinvokeconfig import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py index 1f82478526dd8..ea576c25edb17 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py @@ -3,7 +3,7 @@ import copy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,77 +15,77 @@ class LambdaEventSourceMappingProperties(TypedDict): - FunctionName: Optional[str] - AmazonManagedKafkaEventSourceConfig: Optional[AmazonManagedKafkaEventSourceConfig] - BatchSize: Optional[int] - BisectBatchOnFunctionError: Optional[bool] - DestinationConfig: Optional[DestinationConfig] - DocumentDBEventSourceConfig: Optional[DocumentDBEventSourceConfig] - Enabled: Optional[bool] - EventSourceArn: Optional[str] - FilterCriteria: Optional[FilterCriteria] - FunctionResponseTypes: Optional[list[str]] - Id: Optional[str] - MaximumBatchingWindowInSeconds: Optional[int] - MaximumRecordAgeInSeconds: Optional[int] - MaximumRetryAttempts: Optional[int] - ParallelizationFactor: Optional[int] - Queues: Optional[list[str]] - ScalingConfig: Optional[ScalingConfig] - SelfManagedEventSource: Optional[SelfManagedEventSource] - SelfManagedKafkaEventSourceConfig: Optional[SelfManagedKafkaEventSourceConfig] - SourceAccessConfigurations: Optional[list[SourceAccessConfiguration]] - StartingPosition: Optional[str] - StartingPositionTimestamp: Optional[float] - Topics: Optional[list[str]] - TumblingWindowInSeconds: Optional[int] + FunctionName: str | None + AmazonManagedKafkaEventSourceConfig: AmazonManagedKafkaEventSourceConfig | None + BatchSize: int | None + BisectBatchOnFunctionError: bool | None + DestinationConfig: DestinationConfig | None + DocumentDBEventSourceConfig: DocumentDBEventSourceConfig | None + Enabled: bool | None + EventSourceArn: str | None + FilterCriteria: FilterCriteria | None + FunctionResponseTypes: list[str] | None + Id: str | None + MaximumBatchingWindowInSeconds: int | None + MaximumRecordAgeInSeconds: int | None + MaximumRetryAttempts: int | None + ParallelizationFactor: int | None + Queues: list[str] | None + ScalingConfig: ScalingConfig | None + SelfManagedEventSource: SelfManagedEventSource | None + SelfManagedKafkaEventSourceConfig: SelfManagedKafkaEventSourceConfig | None + SourceAccessConfigurations: list[SourceAccessConfiguration] | None + StartingPosition: str | None + StartingPositionTimestamp: float | None + Topics: list[str] | None + TumblingWindowInSeconds: int | None class OnFailure(TypedDict): - Destination: Optional[str] + Destination: str | None class DestinationConfig(TypedDict): - OnFailure: Optional[OnFailure] + OnFailure: OnFailure | None class Filter(TypedDict): - Pattern: Optional[str] + Pattern: str | None class FilterCriteria(TypedDict): - Filters: Optional[list[Filter]] + Filters: list[Filter] | None class SourceAccessConfiguration(TypedDict): - Type: Optional[str] - URI: Optional[str] + Type: str | None + URI: str | None class Endpoints(TypedDict): - KafkaBootstrapServers: Optional[list[str]] + KafkaBootstrapServers: list[str] | None class SelfManagedEventSource(TypedDict): - Endpoints: Optional[Endpoints] + Endpoints: Endpoints | None class AmazonManagedKafkaEventSourceConfig(TypedDict): - ConsumerGroupId: Optional[str] + ConsumerGroupId: str | None class SelfManagedKafkaEventSourceConfig(TypedDict): - ConsumerGroupId: Optional[str] + ConsumerGroupId: str | None class ScalingConfig(TypedDict): - MaximumConcurrency: Optional[int] + MaximumConcurrency: int | None class DocumentDBEventSourceConfig(TypedDict): - CollectionName: Optional[str] - DatabaseName: Optional[str] - FullDocument: Optional[str] + CollectionName: str | None + DatabaseName: str | None + FullDocument: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py index f4dd5b69a5423..db2e1b238f364 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaEventSourceMappingProviderPlugin(CloudFormationResourceProviderPlugi name = "AWS::Lambda::EventSourceMapping" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_eventsourcemapping import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py index bbcc61e335934..f25851ea25613 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py @@ -2,116 +2,130 @@ from __future__ import annotations import os -from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( OperationStatus, ProgressEvent, - ResourceProvider, ResourceRequest, ) from localstack.services.lambda_.lambda_utils import get_handler_file_from_name +from localstack.services.lambda_.resource_providers.generated.aws_lambda_function_base import ( + LambdaFunctionProviderBase, +) from localstack.utils.archives import is_zip_file from localstack.utils.files import mkdir, new_tmp_dir, rm_rf, save_file from localstack.utils.strings import is_base64, to_bytes from localstack.utils.testutil import create_zip_file +class LambdaManagedInstancesCapacityProviderConfig(TypedDict): + CapacityProviderArn: str | None + PerExecutionEnvironmentMaxConcurrency: int | None + ExecutionEnvironmentMemoryGiBPerVCpu: float | None + + +class CapacityProviderConfig(TypedDict): + LambdaManagedInstancesCapacityProviderConfig: ( + LambdaManagedInstancesCapacityProviderConfig | None + ) + + class LambdaFunctionProperties(TypedDict): - Code: Optional[Code] - Role: Optional[str] - Architectures: Optional[list[str]] - Arn: Optional[str] - CodeSigningConfigArn: Optional[str] - DeadLetterConfig: Optional[DeadLetterConfig] - Description: Optional[str] - Environment: Optional[Environment] - EphemeralStorage: Optional[EphemeralStorage] - FileSystemConfigs: Optional[list[FileSystemConfig]] - FunctionName: Optional[str] - Handler: Optional[str] - ImageConfig: Optional[ImageConfig] - KmsKeyArn: Optional[str] - Layers: Optional[list[str]] - MemorySize: Optional[int] - PackageType: Optional[str] - ReservedConcurrentExecutions: Optional[int] - Runtime: Optional[str] - RuntimeManagementConfig: Optional[RuntimeManagementConfig] - SnapStart: Optional[SnapStart] - SnapStartResponse: Optional[SnapStartResponse] - Tags: Optional[list[Tag]] - Timeout: Optional[int] - TracingConfig: Optional[TracingConfig] - VpcConfig: Optional[VpcConfig] + Code: Code | None + Role: str | None + Architectures: list[str] | None + Arn: str | None + CapacityProviderConfig: CapacityProviderConfig | None + CodeSigningConfigArn: str | None + DeadLetterConfig: DeadLetterConfig | None + Description: str | None + Environment: Environment | None + EphemeralStorage: EphemeralStorage | None + FileSystemConfigs: list[FileSystemConfig] | None + FunctionName: str | None + Handler: str | None + ImageConfig: ImageConfig | None + KmsKeyArn: str | None + Layers: list[str] | None + MemorySize: int | None + PackageType: str | None + ReservedConcurrentExecutions: int | None + Runtime: str | None + RuntimeManagementConfig: RuntimeManagementConfig | None + SnapStart: SnapStart | None + SnapStartResponse: SnapStartResponse | None + Tags: list[Tag] | None + Timeout: int | None + TracingConfig: TracingConfig | None + VpcConfig: VpcConfig | None class TracingConfig(TypedDict): - Mode: Optional[str] + Mode: str | None class VpcConfig(TypedDict): - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class RuntimeManagementConfig(TypedDict): - UpdateRuntimeOn: Optional[str] - RuntimeVersionArn: Optional[str] + UpdateRuntimeOn: str | None + RuntimeVersionArn: str | None class SnapStart(TypedDict): - ApplyOn: Optional[str] + ApplyOn: str | None class FileSystemConfig(TypedDict): - Arn: Optional[str] - LocalMountPath: Optional[str] + Arn: str | None + LocalMountPath: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ImageConfig(TypedDict): - Command: Optional[list[str]] - EntryPoint: Optional[list[str]] - WorkingDirectory: Optional[str] + Command: list[str] | None + EntryPoint: list[str] | None + WorkingDirectory: str | None class DeadLetterConfig(TypedDict): - TargetArn: Optional[str] + TargetArn: str | None class SnapStartResponse(TypedDict): - ApplyOn: Optional[str] - OptimizationStatus: Optional[str] + ApplyOn: str | None + OptimizationStatus: str | None class Code(TypedDict): - ImageUri: Optional[str] - S3Bucket: Optional[str] - S3Key: Optional[str] - S3ObjectVersion: Optional[str] - ZipFile: Optional[str] + ImageUri: str | None + S3Bucket: str | None + S3Key: str | None + S3ObjectVersion: str | None + ZipFile: str | None class LoggingConfig(TypedDict): - ApplicationLogLevel: Optional[str] - LogFormat: Optional[str] - LogGroup: Optional[str] - SystemLogLevel: Optional[str] + ApplicationLogLevel: str | None + LogFormat: str | None + LogGroup: str | None + SystemLogLevel: str | None class Environment(TypedDict): - Variables: Optional[dict] + Variables: dict | None class EphemeralStorage(TypedDict): - Size: Optional[int] + Size: int | None REPEATED_INVOCATION = "repeated_invocation" @@ -297,16 +311,14 @@ def _transform_function_to_model(function): "Arn", "EphemeralStorage", "Architectures", + "CapacityProviderConfig", ] response_model = util.select_attributes(function, model_properties) response_model["Arn"] = function["FunctionArn"] return response_model -class LambdaFunctionProvider(ResourceProvider[LambdaFunctionProperties]): - TYPE = "AWS::Lambda::Function" # Autogenerated. Don't change - SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change - +class LambdaFunctionProvider(LambdaFunctionProviderBase): def create( self, request: ResourceRequest[LambdaFunctionProperties], @@ -323,6 +335,8 @@ def create( Create-only properties: - /properties/FunctionName + - /properties/PackageType + - /properties/TenancyConfig Read-only properties: - /properties/Arn @@ -352,10 +366,10 @@ def create( - lambda:PutRuntimeManagementConfig - lambda:TagResource - lambda:GetPolicy - - lambda:AddPermission - - lambda:RemovePermission - - lambda:GetResourcePolicy - - lambda:PutResourcePolicy + - lambda:PutFunctionRecursionConfig + - lambda:GetFunctionRecursionConfig + - lambda:PutFunctionScalingConfig + - lambda:PassCapacityProvider """ model = request.desired_state @@ -387,6 +401,7 @@ def create( "TracingConfig", "VpcConfig", "LoggingConfig", + "CapacityProviderConfig", ], ) if "Timeout" in kwargs: @@ -408,11 +423,27 @@ def create( } kwargs["Code"] = _get_lambda_code_param(model) + + # For managed instance lambdas, we publish them immediately + if "CapacityProviderConfig" in kwargs: + kwargs["Publish"] = True + kwargs["PublishTo"] = "LATEST_PUBLISHED" + create_response = lambda_client.create_function(**kwargs) + # TODO: if version is in the schema, just put it in the model instead of the custom context + request.custom_context["Version"] = create_response["Version"] # $LATEST.PUBLISHED model["Arn"] = create_response["FunctionArn"] - get_fn_response = lambda_client.get_function(FunctionName=model["Arn"]) + if request.custom_context.get("Version") == "$LATEST.PUBLISHED": + # for managed instance lambdas, we need to wait until the version is published & active + get_fn_response = lambda_client.get_function( + FunctionName=model["FunctionName"], Qualifier=request.custom_context["Version"] + ) + else: + get_fn_response = lambda_client.get_function(FunctionName=model["Arn"]) + match get_fn_response["Configuration"]["State"]: + # TODO: explicitly handle new ActiveNonInvocable state? case "Pending": return ProgressEvent( status=OperationStatus.IN_PROGRESS, @@ -449,6 +480,9 @@ def read( IAM permissions required: - lambda:GetFunction - lambda:GetFunctionCodeSigningConfig + - lambda:GetFunctionRecursionConfig + - lambda:GetRuntimeManagementConfig + - lambda:GetFunctionScalingConfig """ function_name = request.desired_state["FunctionName"] lambda_client = request.aws_client_factory.lambda_ @@ -468,6 +502,7 @@ def delete( IAM permissions required: - lambda:DeleteFunction + - lambda:GetFunction - ec2:DescribeNetworkInterfaces """ try: @@ -489,7 +524,6 @@ def update( - lambda:DeleteFunctionConcurrency - lambda:GetFunction - lambda:PutFunctionConcurrency - - lambda:ListTags - lambda:TagResource - lambda:UntagResource - lambda:UpdateFunctionConfiguration @@ -510,12 +544,11 @@ def update( - lambda:DeleteFunctionCodeSigningConfig - lambda:GetCodeSigningConfig - lambda:GetFunctionCodeSigningConfig - - lambda:GetPolicy - - lambda:AddPermission - - lambda:RemovePermission - - lambda:GetResourcePolicy - - lambda:PutResourcePolicy - - lambda:DeleteResourcePolicy + - lambda:PutFunctionRecursionConfig + - lambda:GetFunctionRecursionConfig + - lambda:PutFunctionScalingConfig + - lambda:PublishVersion + - lambda:PassCapacityProvider """ client = request.aws_client_factory.lambda_ @@ -541,6 +574,7 @@ def update( "TracingConfig", "VpcConfig", "LoggingConfig", + "CapacityProviderConfig", ] update_config_props = util.select_attributes(request.desired_state, config_keys) function_name = request.previous_state["FunctionName"] @@ -578,6 +612,11 @@ def list( self, request: ResourceRequest[LambdaFunctionProperties], ) -> ProgressEvent[LambdaFunctionProperties]: + """ + List available resources of this type + IAM permissions required: + - lambda:ListFunctions + """ functions = request.aws_client_factory.lambda_.list_functions() return ProgressEvent( status=OperationStatus.SUCCESS, diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.schema.json b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.schema.json deleted file mode 100644 index b1d128047b150..0000000000000 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.schema.json +++ /dev/null @@ -1,566 +0,0 @@ -{ - "tagging": { - "taggable": true, - "tagOnCreate": true, - "tagUpdatable": true, - "tagProperty": "/properties/Tags", - "cloudFormationSystemTags": true - }, - "handlers": { - "read": { - "permissions": [ - "lambda:GetFunction", - "lambda:GetFunctionCodeSigningConfig" - ] - }, - "create": { - "permissions": [ - "lambda:CreateFunction", - "lambda:GetFunction", - "lambda:PutFunctionConcurrency", - "iam:PassRole", - "s3:GetObject", - "s3:GetObjectVersion", - "ec2:DescribeSecurityGroups", - "ec2:DescribeSubnets", - "ec2:DescribeVpcs", - "elasticfilesystem:DescribeMountTargets", - "kms:CreateGrant", - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "lambda:GetCodeSigningConfig", - "lambda:GetFunctionCodeSigningConfig", - "lambda:GetLayerVersion", - "lambda:GetRuntimeManagementConfig", - "lambda:PutRuntimeManagementConfig", - "lambda:TagResource", - "lambda:GetPolicy", - "lambda:AddPermission", - "lambda:RemovePermission", - "lambda:GetResourcePolicy", - "lambda:PutResourcePolicy" - ] - }, - "update": { - "permissions": [ - "lambda:DeleteFunctionConcurrency", - "lambda:GetFunction", - "lambda:PutFunctionConcurrency", - "lambda:ListTags", - "lambda:TagResource", - "lambda:UntagResource", - "lambda:UpdateFunctionConfiguration", - "lambda:UpdateFunctionCode", - "iam:PassRole", - "s3:GetObject", - "s3:GetObjectVersion", - "ec2:DescribeSecurityGroups", - "ec2:DescribeSubnets", - "ec2:DescribeVpcs", - "elasticfilesystem:DescribeMountTargets", - "kms:CreateGrant", - "kms:Decrypt", - "kms:GenerateDataKey", - "lambda:GetRuntimeManagementConfig", - "lambda:PutRuntimeManagementConfig", - "lambda:PutFunctionCodeSigningConfig", - "lambda:DeleteFunctionCodeSigningConfig", - "lambda:GetCodeSigningConfig", - "lambda:GetFunctionCodeSigningConfig", - "lambda:GetPolicy", - "lambda:AddPermission", - "lambda:RemovePermission", - "lambda:GetResourcePolicy", - "lambda:PutResourcePolicy", - "lambda:DeleteResourcePolicy" - ] - }, - "list": { - "permissions": [ - "lambda:ListFunctions" - ] - }, - "delete": { - "permissions": [ - "lambda:DeleteFunction", - "ec2:DescribeNetworkInterfaces" - ] - } - }, - "typeName": "AWS::Lambda::Function", - "readOnlyProperties": [ - "/properties/SnapStartResponse", - "/properties/SnapStartResponse/ApplyOn", - "/properties/SnapStartResponse/OptimizationStatus", - "/properties/Arn" - ], - "description": "Resource Type definition for AWS::Lambda::Function in region", - "writeOnlyProperties": [ - "/properties/SnapStart", - "/properties/SnapStart/ApplyOn", - "/properties/Code", - "/properties/Code/ImageUri", - "/properties/Code/S3Bucket", - "/properties/Code/S3Key", - "/properties/Code/S3ObjectVersion", - "/properties/Code/ZipFile" - ], - "createOnlyProperties": [ - "/properties/FunctionName" - ], - "additionalProperties": false, - "primaryIdentifier": [ - "/properties/FunctionName" - ], - "definitions": { - "ImageConfig": { - "additionalProperties": false, - "type": "object", - "properties": { - "WorkingDirectory": { - "description": "WorkingDirectory.", - "type": "string" - }, - "Command": { - "maxItems": 1500, - "uniqueItems": true, - "description": "Command.", - "type": "array", - "items": { - "type": "string" - } - }, - "EntryPoint": { - "maxItems": 1500, - "uniqueItems": true, - "description": "EntryPoint.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "TracingConfig": { - "description": "The function's AWS X-Ray tracing configuration. To sample and record incoming requests, set Mode to Active.", - "additionalProperties": false, - "type": "object", - "properties": { - "Mode": { - "description": "The tracing mode.", - "type": "string", - "enum": [ - "Active", - "PassThrough" - ] - } - } - }, - "VpcConfig": { - "description": "The VPC security groups and subnets that are attached to a Lambda function. When you connect a function to a VPC, Lambda creates an elastic network interface for each combination of security group and subnet in the function's VPC configuration. The function can only access resources and the internet through that VPC.", - "additionalProperties": false, - "type": "object", - "properties": { - "Ipv6AllowedForDualStack": { - "description": "A boolean indicating whether IPv6 protocols will be allowed for dual stack subnets", - "type": "boolean" - }, - "SecurityGroupIds": { - "maxItems": 5, - "uniqueItems": false, - "description": "A list of VPC security groups IDs.", - "type": "array", - "items": { - "type": "string" - } - }, - "SubnetIds": { - "maxItems": 16, - "uniqueItems": false, - "description": "A list of VPC subnet IDs.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "DeadLetterConfig": { - "description": "The dead-letter queue for failed asynchronous invocations.", - "additionalProperties": false, - "type": "object", - "properties": { - "TargetArn": { - "pattern": "^(arn:(aws[a-zA-Z-]*)?:[a-z0-9-.]+:.*)|()$", - "description": "The Amazon Resource Name (ARN) of an Amazon SQS queue or Amazon SNS topic.", - "type": "string" - } - } - }, - "RuntimeManagementConfig": { - "additionalProperties": false, - "type": "object", - "properties": { - "UpdateRuntimeOn": { - "description": "Trigger for runtime update", - "type": "string", - "enum": [ - "Auto", - "FunctionUpdate", - "Manual" - ] - }, - "RuntimeVersionArn": { - "description": "Unique identifier for a runtime version arn", - "type": "string" - } - }, - "required": [ - "UpdateRuntimeOn" - ] - }, - "SnapStart": { - "description": "The function's SnapStart setting. When set to PublishedVersions, Lambda creates a snapshot of the execution environment when you publish a function version.", - "additionalProperties": false, - "type": "object", - "properties": { - "ApplyOn": { - "description": "Applying SnapStart setting on function resource type.", - "type": "string", - "enum": [ - "PublishedVersions", - "None" - ] - } - }, - "required": [ - "ApplyOn" - ] - }, - "SnapStartResponse": { - "description": "The function's SnapStart Response. When set to PublishedVersions, Lambda creates a snapshot of the execution environment when you publish a function version.", - "additionalProperties": false, - "type": "object", - "properties": { - "OptimizationStatus": { - "description": "Indicates whether SnapStart is activated for the specified function version.", - "type": "string", - "enum": [ - "On", - "Off" - ] - }, - "ApplyOn": { - "description": "Applying SnapStart setting on function resource type.", - "type": "string", - "enum": [ - "PublishedVersions", - "None" - ] - } - } - }, - "Code": { - "additionalProperties": false, - "type": "object", - "properties": { - "S3ObjectVersion": { - "minLength": 1, - "description": "For versioned objects, the version of the deployment package object to use.", - "type": "string", - "maxLength": 1024 - }, - "S3Bucket": { - "minLength": 3, - "pattern": "^[0-9A-Za-z\\.\\-_]*(?``. To use a different log group, enter an existing log group or enter a new log group name.", + "type": "string", + "maxLength": 512 + }, + "SystemLogLevel": { + "description": "Set this property to filter the system logs for your function that Lambda sends to CloudWatch. Lambda only sends system logs at the selected level of detail and lower, where ``DEBUG`` is the highest level and ``WARN`` is the lowest.", + "type": "string", + "enum": [ + "DEBUG", + "INFO", + "WARN" + ] + } + } + }, + "RecursiveLoop": { + "description": "The function recursion configuration.", + "type": "string", + "enum": [ + "Allow", + "Terminate" + ] + }, + "Environment": { + "description": "A function's environment variable settings. You can use environment variables to adjust your function's behavior without updating code. An environment variable is a pair of strings that are stored in a function's version-specific configuration.", + "additionalProperties": false, + "type": "object", + "properties": { + "Variables": { + "patternProperties": { + "[a-zA-Z][a-zA-Z0-9_]+": { + "type": "string" + } + }, + "description": "Environment variable key-value pairs. For more information, see [Using Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html).\n If the value of the environment variable is a time or a duration, enclose the value in quotes.", + "additionalProperties": false, + "type": "object" + } + } + }, + "FileSystemConfig": { + "description": "Details about the connection between a Lambda function and an [Amazon EFS file system](https://docs.aws.amazon.com/lambda/latest/dg/configuration-filesystem.html).", + "additionalProperties": false, + "type": "object", + "properties": { + "Arn": { + "pattern": "^arn:aws[a-zA-Z-]*:elasticfilesystem:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:access-point/fsap-[a-f0-9]{17}$", + "description": "The Amazon Resource Name (ARN) of the Amazon EFS access point that provides access to the file system.", + "type": "string", + "maxLength": 200 + }, + "LocalMountPath": { + "pattern": "^/mnt/[a-zA-Z0-9-_.]+$", + "description": "The path where the function can access the file system, starting with ``/mnt/``.", + "type": "string", + "maxLength": 160 + } + }, + "required": [ + "Arn", + "LocalMountPath" + ] + }, + "Tag": { + "description": "A [tag](https://docs.aws.amazon.com/lambda/latest/dg/tagging.html) to apply to the function.", + "additionalProperties": false, + "type": "object", + "properties": { + "Value": { + "minLength": 0, + "description": "The value for this tag.", + "type": "string", + "maxLength": 256 + }, + "Key": { + "minLength": 1, + "description": "The key for this tag.", + "type": "string", + "maxLength": 128 + } + }, + "required": [ + "Key" + ] + }, + "EphemeralStorage": { + "description": "The size of the function's ``/tmp`` directory in MB. The default value is 512, but it can be any whole number between 512 and 10,240 MB.", + "additionalProperties": false, + "type": "object", + "properties": { + "Size": { + "description": "The size of the function's ``/tmp`` directory.", + "maximum": 10240, + "type": "integer", + "minimum": 512 + } + }, + "required": [ + "Size" + ] + }, + "TenancyConfig": { + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "TenantIsolationMode": { + "description": "Determines how your Lambda function isolates execution environments between tenants.", + "type": "string", + "enum": [ + "PER_TENANT" + ] + } + }, + "required": [ + "TenantIsolationMode" + ] + } + }, + "properties": { + "FunctionScalingConfig": { + "description": "", + "$ref": "#/definitions/FunctionScalingConfig" + }, + "Description": { + "description": "A description of the function.", + "type": "string", + "maxLength": 256 + }, + "TracingConfig": { + "description": "Set ``Mode`` to ``Active`` to sample and trace a subset of incoming requests with [X-Ray](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html).", + "$ref": "#/definitions/TracingConfig" + }, + "VpcConfig": { + "description": "For network connectivity to AWS resources in a VPC, specify a list of security groups and subnets in the VPC. When you connect a function to a VPC, it can access resources and the internet only through that VPC. For more information, see [Configuring a Lambda function to access resources in a VPC](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html).", + "$ref": "#/definitions/VpcConfig" + }, + "RuntimeManagementConfig": { + "description": "Sets the runtime management configuration for a function's version. For more information, see [Runtime updates](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html).", + "$ref": "#/definitions/RuntimeManagementConfig" + }, + "DurableConfig": { + "description": "", + "$ref": "#/definitions/DurableConfig" + }, + "ReservedConcurrentExecutions": { + "description": "The number of simultaneous executions to reserve for the function.", + "type": "integer", + "minimum": 0 + }, + "SnapStart": { + "description": "The function's [SnapStart](https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html) setting.", + "$ref": "#/definitions/SnapStart" + }, + "FileSystemConfigs": { + "maxItems": 1, + "description": "Connection settings for an Amazon EFS file system. To connect a function to a file system, a mount target must be available in every Availability Zone that your function connects to. If your template contains an [AWS::EFS::MountTarget](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-mounttarget.html) resource, you must also specify a ``DependsOn`` attribute to ensure that the mount target is created or updated before the function.\n For more information about using the ``DependsOn`` attribute, see [DependsOn Attribute](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html).", + "type": "array", + "items": { + "$ref": "#/definitions/FileSystemConfig" + } + }, + "FunctionName": { + "minLength": 1, + "description": "The name of the Lambda function, up to 64 characters in length. If you don't specify a name, CFN generates one.\n If you specify a name, you cannot perform updates that require replacement of this resource. You can perform updates that require no or some interruption. If you must replace the resource, specify a new name.", + "type": "string" + }, + "Runtime": { + "description": "The identifier of the function's [runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). Runtime is required if the deployment package is a .zip file archive. Specifying a runtime results in an error if you're deploying a function using a container image.\n The following list includes deprecated runtimes. Lambda blocks creating new functions and updating existing functions shortly after each runtime is deprecated. For more information, see [Runtime use after deprecation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtime-deprecation-levels).\n For a list of all currently supported runtimes, see [Supported runtimes](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-supported).", + "type": "string" + }, + "KmsKeyArn": { + "pattern": "^(arn:(aws[a-zA-Z-]*)?:[a-z0-9-.]+:.*)|()$", + "description": "The ARN of the KMSlong (KMS) customer managed key that's used to encrypt the following resources:\n + The function's [environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-encryption).\n + The function's [Lambda SnapStart](https://docs.aws.amazon.com/lambda/latest/dg/snapstart-security.html) snapshots.\n + When used with ``SourceKMSKeyArn``, the unzipped version of the .zip deployment package that's used for function invocations. For more information, see [Specifying a customer managed key for Lambda](https://docs.aws.amazon.com/lambda/latest/dg/encrypt-zip-package.html#enable-zip-custom-encryption).\n + The optimized version of the container image that's used for function invocations. Note that this is not the same key that's used to protect your container image in the Amazon Elastic Container Registry (Amazon ECR). For more information, see [Function lifecycle](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-lifecycle).\n \n If you don't provide a customer managed key, Lambda uses an [owned key](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#aws-owned-cmk) or an [](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#aws-managed-cmk).", + "type": "string" + }, + "PublishToLatestPublished": { + "description": "", + "type": "boolean" + }, + "PackageType": { + "description": "The type of deployment package. Set to ``Image`` for container image and set ``Zip`` for .zip file archive.", + "type": "string", + "enum": [ + "Image", + "Zip" + ] + }, + "CodeSigningConfigArn": { + "pattern": "arn:(aws[a-zA-Z-]*)?:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:code-signing-config:csc-[a-z0-9]{17}", + "description": "To enable code signing for this function, specify the ARN of a code-signing configuration. A code-signing configuration includes a set of signing profiles, which define the trusted publishers for this function.", + "type": "string" + }, + "Layers": { + "uniqueItems": false, + "description": "A list of [function layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) to add to the function's execution environment. Specify each layer by its ARN, including the version.", + "type": "array", + "items": { + "type": "string" + } + }, + "TenancyConfig": { + "description": "", + "$ref": "#/definitions/TenancyConfig" + }, + "Tags": { + "uniqueItems": true, + "description": "A list of [tags](https://docs.aws.amazon.com/lambda/latest/dg/tagging.html) to apply to the function.\n You must have the ``lambda:TagResource``, ``lambda:UntagResource``, and ``lambda:ListTags`` permissions for your [principal](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html) to manage the CFN stack. If you don't have these permissions, there might be unexpected behavior with stack-level tags propagating to the resource during resource creation and update.", + "insertionOrder": false, + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + } + }, + "ImageConfig": { + "description": "Configuration values that override the container image Dockerfile settings. For more information, see [Container image settings](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-parms).", + "$ref": "#/definitions/ImageConfig" + }, + "MemorySize": { + "description": "The amount of [memory available to the function](https://docs.aws.amazon.com/lambda/latest/dg/configuration-function-common.html#configuration-memory-console) at runtime. Increasing the function memory also increases its CPU allocation. The default value is 128 MB. The value can be any multiple of 1 MB. Note that new AWS accounts have reduced concurrency and memory quotas. AWS raises these quotas automatically based on your usage. You can also request a quota increase.", + "type": "integer" + }, + "DeadLetterConfig": { + "description": "A dead-letter queue configuration that specifies the queue or topic where Lambda sends asynchronous events when they fail processing. For more information, see [Dead-letter queues](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html#invocation-dlq).", + "$ref": "#/definitions/DeadLetterConfig" + }, + "Timeout": { + "description": "The amount of time (in seconds) that Lambda allows a function to run before stopping it. The default is 3 seconds. The maximum allowed value is 900 seconds. For more information, see [Lambda execution environment](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html).", + "type": "integer", + "minimum": 1 + }, + "CapacityProviderConfig": { + "description": "", + "$ref": "#/definitions/CapacityProviderConfig" + }, + "Handler": { + "pattern": "^[^\\s]+$", + "description": "The name of the method within your code that Lambda calls to run your function. Handler is required if the deployment package is a .zip file archive. The format includes the file name. It can also include namespaces and other qualifiers, depending on the runtime. For more information, see [Lambda programming model](https://docs.aws.amazon.com/lambda/latest/dg/foundation-progmodel.html).", + "type": "string", + "maxLength": 128 + }, + "SnapStartResponse": { + "description": "", + "$ref": "#/definitions/SnapStartResponse" + }, + "Code": { + "description": "The code for the function. You can define your function code in multiple ways:\n + For .zip deployment packages, you can specify the S3 location of the .zip file in the ``S3Bucket``, ``S3Key``, and ``S3ObjectVersion`` properties.\n + For .zip deployment packages, you can alternatively define the function code inline in the ``ZipFile`` property. This method works only for Node.js and Python functions.\n + For container images, specify the URI of your container image in the ECR registry in the ``ImageUri`` property.", + "$ref": "#/definitions/Code" + }, + "Role": { + "pattern": "^arn:(aws[a-zA-Z-]*)?:iam::\\d{12}:role/?[a-zA-Z_0-9+=,.@\\-_/]+$", + "description": "The Amazon Resource Name (ARN) of the function's execution role.", + "type": "string" + }, + "LoggingConfig": { + "description": "The function's Amazon CloudWatch Logs configuration settings.", + "$ref": "#/definitions/LoggingConfig" + }, + "RecursiveLoop": { + "description": "The status of your function's recursive loop detection configuration.\n When this value is set to ``Allow``and Lambda detects your function being invoked as part of a recursive loop, it doesn't take any action.\n When this value is set to ``Terminate`` and Lambda detects your function being invoked as part of a recursive loop, it stops your function being invoked and notifies you.", + "$ref": "#/definitions/RecursiveLoop" + }, + "Environment": { + "description": "Environment variables that are accessible from function code during execution.", + "$ref": "#/definitions/Environment" + }, + "Arn": { + "description": "", + "type": "string" + }, + "EphemeralStorage": { + "description": "The size of the function's ``/tmp`` directory in MB. The default value is 512, but it can be any whole number between 512 and 10,240 MB.", + "$ref": "#/definitions/EphemeralStorage" + }, + "Architectures": { + "minItems": 1, + "maxItems": 1, + "uniqueItems": true, + "description": "The instruction set architecture that the function supports. Enter a string array with one of the valid values (arm64 or x86_64). The default value is ``x86_64``.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "x86_64", + "arm64" + ] + } + } + } +} diff --git a/localstack-core/localstack/services/lambda_/resource_providers/generated/aws_lambda_function_base.py b/localstack-core/localstack/services/lambda_/resource_providers/generated/aws_lambda_function_base.py new file mode 100644 index 0000000000000..19c348048d3ec --- /dev/null +++ b/localstack-core/localstack/services/lambda_/resource_providers/generated/aws_lambda_function_base.py @@ -0,0 +1,294 @@ +# LocalStack Resource Provider Base Class Scaffolding v2 +# +# AUTOGENERATED FILE - DO NOT EDIT +# + +from __future__ import annotations + +from abc import ABC, abstractmethod +from pathlib import Path +from typing import TypedDict + +import localstack.services.cloudformation.provider_utils as util +from localstack.services.cloudformation.resource_provider import ( + ProgressEvent, + ResourceProvider, + ResourceRequest, +) + + +class LambdaFunctionProperties(TypedDict): + Code: Code | None + Role: str | None + Architectures: list[str] | None + Arn: str | None + CapacityProviderConfig: CapacityProviderConfig | None + CodeSigningConfigArn: str | None + DeadLetterConfig: DeadLetterConfig | None + Description: str | None + DurableConfig: DurableConfig | None + Environment: Environment | None + EphemeralStorage: EphemeralStorage | None + FileSystemConfigs: list[FileSystemConfig] | None + FunctionName: str | None + FunctionScalingConfig: FunctionScalingConfig | None + Handler: str | None + ImageConfig: ImageConfig | None + KmsKeyArn: str | None + Layers: list[str] | None + LoggingConfig: LoggingConfig | None + MemorySize: int | None + PackageType: str | None + PublishToLatestPublished: bool | None + RecursiveLoop: str | None + ReservedConcurrentExecutions: int | None + Runtime: str | None + RuntimeManagementConfig: RuntimeManagementConfig | None + SnapStart: SnapStart | None + SnapStartResponse: SnapStartResponse | None + Tags: list[Tag] | None + TenancyConfig: TenancyConfig | None + Timeout: int | None + TracingConfig: TracingConfig | None + VpcConfig: VpcConfig | None + + +class FunctionScalingConfig(TypedDict): + MaxExecutionEnvironments: int | None + MinExecutionEnvironments: int | None + + +class TracingConfig(TypedDict): + Mode: str | None + + +class VpcConfig(TypedDict): + Ipv6AllowedForDualStack: bool | None + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None + + +class RuntimeManagementConfig(TypedDict): + UpdateRuntimeOn: str | None + RuntimeVersionArn: str | None + + +class DurableConfig(TypedDict): + ExecutionTimeout: int | None + RetentionPeriodInDays: int | None + + +class SnapStart(TypedDict): + ApplyOn: str | None + + +class FileSystemConfig(TypedDict): + Arn: str | None + LocalMountPath: str | None + + +class TenancyConfig(TypedDict): + TenantIsolationMode: str | None + + +class Tag(TypedDict): + Key: str | None + Value: str | None + + +class ImageConfig(TypedDict): + Command: list[str] | None + EntryPoint: list[str] | None + WorkingDirectory: str | None + + +class DeadLetterConfig(TypedDict): + TargetArn: str | None + + +class LambdaManagedInstancesCapacityProviderConfig(TypedDict): + CapacityProviderArn: str | None + ExecutionEnvironmentMemoryGiBPerVCpu: float | None + PerExecutionEnvironmentMaxConcurrency: int | None + + +class CapacityProviderConfig(TypedDict): + LambdaManagedInstancesCapacityProviderConfig: ( + LambdaManagedInstancesCapacityProviderConfig | None + ) + + +class SnapStartResponse(TypedDict): + ApplyOn: str | None + OptimizationStatus: str | None + + +class Code(TypedDict): + ImageUri: str | None + S3Bucket: str | None + S3Key: str | None + S3ObjectVersion: str | None + SourceKMSKeyArn: str | None + ZipFile: str | None + + +class LoggingConfig(TypedDict): + ApplicationLogLevel: str | None + LogFormat: str | None + LogGroup: str | None + SystemLogLevel: str | None + + +class Environment(TypedDict): + Variables: dict | None + + +class EphemeralStorage(TypedDict): + Size: int | None + + +REPEATED_INVOCATION = "repeated_invocation" + + +class LambdaFunctionProviderBase(ResourceProvider[LambdaFunctionProperties], ABC): + TYPE = "AWS::Lambda::Function" # Autogenerated. Don't change + SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change + + @abstractmethod + def create( + self, + request: ResourceRequest[LambdaFunctionProperties], + ) -> ProgressEvent[LambdaFunctionProperties]: + """ + Create a new resource. + + Primary identifier fields: + - /properties/FunctionName + + Required properties: + - Code + - Role + + Create-only properties: + - /properties/FunctionName + - /properties/PackageType + - /properties/TenancyConfig + + Read-only properties: + - /properties/SnapStartResponse + - /properties/SnapStartResponse/ApplyOn + - /properties/SnapStartResponse/OptimizationStatus + - /properties/Arn + + IAM permissions required: + - lambda:CreateFunction + - lambda:GetFunction + - lambda:PutFunctionConcurrency + - iam:PassRole + - s3:GetObject + - s3:GetObjectVersion + - ec2:DescribeSecurityGroups + - ec2:DescribeSubnets + - ec2:DescribeVpcs + - elasticfilesystem:DescribeMountTargets + - kms:CreateGrant + - kms:Decrypt + - kms:Encrypt + - kms:GenerateDataKey + - lambda:GetCodeSigningConfig + - lambda:GetFunctionCodeSigningConfig + - lambda:GetLayerVersion + - lambda:GetRuntimeManagementConfig + - lambda:PutRuntimeManagementConfig + - lambda:TagResource + - lambda:PutFunctionRecursionConfig + - lambda:GetFunctionRecursionConfig + - lambda:PutFunctionScalingConfig + - lambda:PassCapacityProvider + + """ + raise NotImplementedError + + @abstractmethod + def read( + self, + request: ResourceRequest[LambdaFunctionProperties], + ) -> ProgressEvent[LambdaFunctionProperties]: + """ + Fetch resource information + + IAM permissions required: + - lambda:GetFunction + - lambda:GetFunctionCodeSigningConfig + - lambda:GetFunctionRecursionConfig + - lambda:GetRuntimeManagementConfig + - lambda:GetFunctionScalingConfig + """ + raise NotImplementedError + + @abstractmethod + def delete( + self, + request: ResourceRequest[LambdaFunctionProperties], + ) -> ProgressEvent[LambdaFunctionProperties]: + """ + Delete a resource + + IAM permissions required: + - lambda:DeleteFunction + - lambda:GetFunction + - ec2:DescribeNetworkInterfaces + """ + raise NotImplementedError + + @abstractmethod + def update( + self, + request: ResourceRequest[LambdaFunctionProperties], + ) -> ProgressEvent[LambdaFunctionProperties]: + """ + Update a resource + + IAM permissions required: + - lambda:DeleteFunctionConcurrency + - lambda:GetFunction + - lambda:PutFunctionConcurrency + - lambda:TagResource + - lambda:UntagResource + - lambda:UpdateFunctionConfiguration + - lambda:UpdateFunctionCode + - iam:PassRole + - s3:GetObject + - s3:GetObjectVersion + - ec2:DescribeSecurityGroups + - ec2:DescribeSubnets + - ec2:DescribeVpcs + - elasticfilesystem:DescribeMountTargets + - kms:CreateGrant + - kms:Decrypt + - kms:GenerateDataKey + - lambda:GetRuntimeManagementConfig + - lambda:PutRuntimeManagementConfig + - lambda:PutFunctionCodeSigningConfig + - lambda:DeleteFunctionCodeSigningConfig + - lambda:GetCodeSigningConfig + - lambda:GetFunctionCodeSigningConfig + - lambda:PutFunctionRecursionConfig + - lambda:GetFunctionRecursionConfig + - lambda:PutFunctionScalingConfig + - lambda:PublishVersion + - lambda:PassCapacityProvider + """ + raise NotImplementedError + + @abstractmethod + def list( + self, + request: ResourceRequest[LambdaFunctionProperties], + ) -> ProgressEvent[LambdaFunctionProperties]: + """ + List available resources of this type + IAM permissions required: + - lambda:ListFunctions + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/generated/aws_lambda_function_plugin.py similarity index 82% rename from localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py rename to localstack-core/localstack/services/lambda_/resource_providers/generated/aws_lambda_function_plugin.py index 2f07aaca0e74b..074a1fd9ff312 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/generated/aws_lambda_function_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaFunctionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::Function" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_function import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py index 044eeed162845..8b92d78a45fda 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,26 +14,26 @@ class LambdaAliasProperties(TypedDict): - FunctionName: Optional[str] - FunctionVersion: Optional[str] - Name: Optional[str] - Description: Optional[str] - Id: Optional[str] - ProvisionedConcurrencyConfig: Optional[ProvisionedConcurrencyConfiguration] - RoutingConfig: Optional[AliasRoutingConfiguration] + FunctionName: str | None + FunctionVersion: str | None + Name: str | None + Description: str | None + Id: str | None + ProvisionedConcurrencyConfig: ProvisionedConcurrencyConfiguration | None + RoutingConfig: AliasRoutingConfiguration | None class ProvisionedConcurrencyConfiguration(TypedDict): - ProvisionedConcurrentExecutions: Optional[int] + ProvisionedConcurrentExecutions: int | None class VersionWeight(TypedDict): - FunctionVersion: Optional[str] - FunctionWeight: Optional[float] + FunctionVersion: str | None + FunctionWeight: float | None class AliasRoutingConfiguration(TypedDict): - AdditionalVersionWeights: Optional[list[VersionWeight]] + AdditionalVersionWeights: list[VersionWeight] | None REPEATED_INVOCATION = "repeated_invocation" @@ -116,7 +116,7 @@ def create( return ProgressEvent( status=OperationStatus.FAILED, resource_model=model, - message="", + message=result.get("StatusReason", "Unknown"), error_code="VersionStateFailure", # TODO: not parity tested ) diff --git a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py index 406b05deddd45..e1b5120633dd7 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaAliasProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::Alias" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.lambda_alias import LambdaAliasProvider diff --git a/localstack-core/localstack/services/lambda_/runtimes.py b/localstack-core/localstack/services/lambda_/runtimes.py index 3fa96216257f6..7c5ba22131479 100644 --- a/localstack-core/localstack/services/lambda_/runtimes.py +++ b/localstack-core/localstack/services/lambda_/runtimes.py @@ -1,7 +1,5 @@ """This Lambda Runtimes reference defines everything around Lambda runtimes to facilitate adding new runtimes.""" -from typing import Optional - from localstack.aws.api.lambda_ import Runtime # LocalStack Lambda runtimes support policy @@ -15,7 +13,7 @@ # 2. Add the new runtime to these variables below: # a) `IMAGE_MAPPING` # b) `RUNTIMES_AGGREGATED` -# c) `SNAP_START_SUPPORTED_RUNTIMES` if supported (currently only new Java runtimes) +# c) `SNAP_START_SUPPORTED_RUNTIMES` if supported # 3. Re-create snapshots for Lambda tests with the marker @markers.lambda_runtime_update # => Filter the tests using pytest -m lambda_runtime_update (i.e., additional arguments in PyCharm) # Depending on the runtime, `test_lambda_runtimes.py` might require further snapshot updates. @@ -25,10 +23,12 @@ # 5. Run the unit test to check the runtime setup: # tests.unit.services.lambda_.test_api_utils.TestApiUtils.test_check_runtime # 6. Review special tests including: -# a) [ext] tests.aws.services.lambda_.test_lambda_endpoint_injection +# a) [pro] tests.aws.services.lambda_.test_lambda_endpoint_injection # 7. Before merging, run the ext integration tests to cover transparent endpoint injection testing. # 8. Add the new runtime to the K8 image build: https://github.com/localstack/lambda-images -# 9. Inform the web team to update the resource browser (consider offering an endpoint in the future) +# 9. Check that the Resource Browser shows the new runtime or reach out to the web team. +# The internal endpoint /_aws/lambda/runtimes yields all supported runtimes +# 10. Inform #devrel to announce the new feature in social media # Mapping from a) AWS Lambda runtime identifier => b) official AWS image on Amazon ECR Public # a) AWS Lambda runtimes: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html @@ -36,12 +36,14 @@ # => Synchronize the order with the "Supported runtimes" under "AWS Lambda runtimes" (a) # => Add comments for deprecated runtimes using => => IMAGE_MAPPING: dict[Runtime, str] = { + Runtime.nodejs24_x: "nodejs:24", Runtime.nodejs22_x: "nodejs:22", Runtime.nodejs20_x: "nodejs:20", Runtime.nodejs18_x: "nodejs:18", Runtime.nodejs16_x: "nodejs:16", Runtime.nodejs14_x: "nodejs:14", # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024 Runtime.nodejs12_x: "nodejs:12", # deprecated Mar 31, 2023 => Mar 31, 2023 => Apr 30, 2023 + Runtime.python3_14: "python:3.14", Runtime.python3_13: "python:3.13", Runtime.python3_12: "python:3.12", Runtime.python3_11: "python:3.11", @@ -49,11 +51,13 @@ Runtime.python3_9: "python:3.9", Runtime.python3_8: "python:3.8", Runtime.python3_7: "python:3.7", # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024 + Runtime.java25: "java:25", Runtime.java21: "java:21", Runtime.java17: "java:17", Runtime.java11: "java:11", Runtime.java8_al2: "java:8.al2", Runtime.java8: "java:8", # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024 + Runtime.dotnet10: "dotnet:10", Runtime.dotnet8: "dotnet:8", # dotnet7 (container only) Runtime.dotnet6: "dotnet:6", @@ -77,7 +81,7 @@ # We currently use them in LocalStack logs as bonus recommendation (DevX). # When updating the recommendation, # please regenerate all tests with @markers.lambda_runtime_update -DEPRECATED_RUNTIMES_UPGRADES: dict[Runtime, Optional[Runtime]] = { +DEPRECATED_RUNTIMES_UPGRADES: dict[Runtime, Runtime | None] = { # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024 Runtime.java8: Runtime.java21, # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024 @@ -112,12 +116,14 @@ # => Remove deprecated runtimes from this testing list RUNTIMES_AGGREGATED = { "nodejs": [ + Runtime.nodejs24_x, Runtime.nodejs22_x, Runtime.nodejs20_x, Runtime.nodejs18_x, Runtime.nodejs16_x, ], "python": [ + Runtime.python3_14, Runtime.python3_13, Runtime.python3_12, Runtime.python3_11, @@ -126,6 +132,7 @@ Runtime.python3_8, ], "java": [ + Runtime.java25, Runtime.java21, Runtime.java17, Runtime.java11, @@ -139,6 +146,7 @@ "dotnet": [ Runtime.dotnet6, Runtime.dotnet8, + Runtime.dotnet10, ], "provided": [ Runtime.provided_al2023, @@ -157,12 +165,28 @@ Runtime.java11, Runtime.java17, Runtime.java21, + Runtime.java25, Runtime.python3_12, Runtime.python3_13, Runtime.dotnet8, + Runtime.dotnet10, ] # An ordered list of all Lambda runtimes considered valid by AWS. Matching snapshots in test_create_lambda_exceptions -VALID_RUNTIMES: str = "[nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9]" +VALID_RUNTIMES: str = "[nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2]" # An ordered list of all Lambda runtimes for layers considered valid by AWS. Matching snapshots in test_layer_exceptions -VALID_LAYER_RUNTIMES: str = "[ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java25, java17, nodejs, nodejs4.3, java8.al2, go1.x, dotnet10, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs24.x, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, python3.13, python3.14, nodejs16.x, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby3.4, ruby2.5, python3.6, python2.7]" +VALID_LAYER_RUNTIMES: str = "[ruby3.5, ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java25, java17, nodejs, nodejs4.3, java8.al2, go1.x, dotnet10, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs24.x, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, nodejs26.x, python3.13, python3.14, nodejs16.x, python3.15, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby3.4, ruby2.5, python3.6, python2.7]" +# An unordered list of runtimes supporting Lambda Managed Instances: https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-runtimes.html#lambda-managed-instances-supported-runtimes + +VALID_MANAGED_INSTANCE_RUNTIMES = [ + Runtime.nodejs24_x, + Runtime.nodejs22_x, + Runtime.python3_14, + Runtime.python3_13, + Runtime.java25, + Runtime.java21, + Runtime.dotnet10, + Runtime.dotnet8, + # not officially listed, but can be invoked + Runtime.provided_al2023, +] diff --git a/localstack-core/localstack/services/lambda_/urlrouter.py b/localstack-core/localstack/services/lambda_/urlrouter.py index 992909d0e57a2..c43fd52a7fb5f 100644 --- a/localstack-core/localstack/services/lambda_/urlrouter.py +++ b/localstack-core/localstack/services/lambda_/urlrouter.py @@ -5,6 +5,7 @@ import logging from datetime import datetime from http import HTTPStatus +from json import JSONDecodeError from rolo.request import restore_payload @@ -192,7 +193,18 @@ def lambda_result_to_response(result: InvocationResult): ) original_payload = to_str(result.payload) - parsed_result = json.loads(original_payload) + try: + parsed_result = json.loads(original_payload) + except JSONDecodeError: + # URL router must be able to parse a Streaming Response without necessary defining it in the URL Config + # And if the body is a simple string, it should be returned without issues + split_index = original_payload.find("\x00" * 8) + if split_index == -1: + parsed_result = {"body": original_payload} + else: + metadata = original_payload[:split_index] + body_str = original_payload[split_index + 8 :] + parsed_result = {**json.loads(metadata), "body": body_str} # patch to fix whitespaces # TODO: check if this is a downstream issue of invocation result serialization @@ -201,6 +213,7 @@ def lambda_result_to_response(result: InvocationResult): if isinstance(parsed_result, str): # a string is a special case here and is returned as-is response.data = parsed_result + elif isinstance(parsed_result, dict): # if it's a dict it might be a proper response if isinstance(parsed_result.get("headers"), dict): diff --git a/localstack-core/localstack/services/logs/models.py b/localstack-core/localstack/services/logs/models.py index 5e2ba973cab93..2fb082085ff62 100644 --- a/localstack-core/localstack/services/logs/models.py +++ b/localstack-core/localstack/services/logs/models.py @@ -1,5 +1,3 @@ -from typing import Dict - from moto.logs.models import LogsBackend as MotoLogsBackend from moto.logs.models import logs_backends as moto_logs_backend @@ -12,7 +10,7 @@ def get_moto_logs_backend(account_id: str, region_name: str) -> MotoLogsBackend: class LogsStore(BaseStore): # maps resource ARN to tags - TAGS: Dict[str, Dict[str, str]] = CrossRegionAttribute(default=dict) + TAGS: dict[str, dict[str, str]] = CrossRegionAttribute(default=dict) logs_stores = AccountRegionBundle("logs", LogsStore) diff --git a/localstack-core/localstack/services/logs/provider.py b/localstack-core/localstack/services/logs/provider.py index 2ded5f5d31f0d..15007079d9802 100644 --- a/localstack-core/localstack/services/logs/provider.py +++ b/localstack-core/localstack/services/logs/provider.py @@ -3,8 +3,8 @@ import io import json import logging +from collections.abc import Callable from gzip import GzipFile -from typing import Callable, Dict from moto.core.utils import unix_time_millis from moto.logs.models import LogEvent, LogsBackend @@ -14,6 +14,7 @@ from localstack.aws.api import CommonServiceException, RequestContext, handler from localstack.aws.api.logs import ( AmazonResourceName, + DeletionProtectionEnabled, DescribeLogGroupsRequest, DescribeLogGroupsResponse, DescribeLogStreamsRequest, @@ -22,10 +23,13 @@ InputLogEvents, InvalidParameterException, KmsKeyId, + ListLogGroupsRequest, + ListLogGroupsResponse, ListTagsForResourceResponse, ListTagsLogGroupResponse, LogGroupClass, LogGroupName, + LogGroupSummary, LogsApi, LogStreamName, PutLogEventsResponse, @@ -40,10 +44,11 @@ from localstack.services.logs.models import get_moto_logs_backend, logs_stores from localstack.services.moto import call_moto from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.aws import arns from localstack.utils.aws.client_types import ServicePrincipal from localstack.utils.bootstrap import is_api_enabled -from localstack.utils.common import is_number +from localstack.utils.numbers import is_number from localstack.utils.patch import patch LOG = logging.getLogger(__name__) @@ -54,14 +59,20 @@ def __init__(self): super().__init__() self.cw_client = connect_to().cloudwatch + def accept_state_visitor(self, visitor: StateVisitor): + from moto.logs.models import logs_backends + + visitor.visit(logs_backends) + visitor.visit(logs_stores) + def put_log_events( self, context: RequestContext, log_group_name: LogGroupName, log_stream_name: LogStreamName, log_events: InputLogEvents, - sequence_token: SequenceToken = None, - entity: Entity = None, + sequence_token: SequenceToken | None = None, + entity: Entity | None = None, **kwargs, ) -> PutLogEventsResponse: logs_backend = get_moto_logs_backend(context.account_id, context.region) @@ -97,33 +108,32 @@ def describe_log_groups( ) -> DescribeLogGroupsResponse: region_backend = get_moto_logs_backend(context.account_id, context.region) - prefix: str = request.get("logGroupNamePrefix", "") - pattern: str = request.get("logGroupNamePattern", "") + prefix: str | None = request.get("logGroupNamePrefix", "") + pattern: str | None = request.get("logGroupNamePattern", "") if pattern and prefix: raise InvalidParameterException( "LogGroup name prefix and LogGroup name pattern are mutually exclusive parameters." ) - copy_groups = copy.deepcopy(region_backend.groups) + moto_groups = copy.deepcopy(dict(region_backend.groups)).values() groups = [ - group.to_describe_dict() - for name, group in copy_groups.items() + {"logGroupClass": LogGroupClass.STANDARD} | group.to_describe_dict() + for group in sorted(moto_groups, key=lambda g: g.name) if not (prefix or pattern) - or (prefix and name.startswith(prefix)) - or (pattern and pattern in name) + or (prefix and group.name.startswith(prefix)) + or (pattern and pattern in group.name) ] - groups = sorted(groups, key=lambda x: x["logGroupName"]) return DescribeLogGroupsResponse(logGroups=groups) @handler("DescribeLogStreams", expand=False) def describe_log_streams( self, context: RequestContext, request: DescribeLogStreamsRequest ) -> DescribeLogStreamsResponse: - log_group_name: str = request.get("logGroupName") - log_group_identifier: str = request.get("logGroupIdentifier") + log_group_name: str | None = request.get("logGroupName") + log_group_identifier: str | None = request.get("logGroupIdentifier") if log_group_identifier and log_group_name: raise CommonServiceException( @@ -138,13 +148,30 @@ def describe_log_streams( return moto.call_moto_with_request(context, request_copy) + @handler("ListLogGroups", expand=False) + def list_log_groups( + self, context: RequestContext, request: ListLogGroupsRequest + ) -> ListLogGroupsResponse: + pattern: str | None = request.get("logGroupNamePattern") + region_backend: LogsBackend = get_moto_logs_backend(context.account_id, context.region) + moto_groups = copy.deepcopy(region_backend.groups).values() + groups = [ + LogGroupSummary( + logGroupName=group.name, logGroupArn=group.arn, logGroupClass=LogGroupClass.STANDARD + ) + for group in sorted(moto_groups, key=lambda g: g.name) + if not pattern or pattern in group.name + ] + return ListLogGroupsResponse(logGroups=groups) + def create_log_group( self, context: RequestContext, log_group_name: LogGroupName, - kms_key_id: KmsKeyId = None, - tags: Tags = None, - log_group_class: LogGroupClass = None, + kms_key_id: KmsKeyId | None = None, + tags: Tags | None = None, + log_group_class: LogGroupClass | None = None, + deletion_protection_enabled: DeletionProtectionEnabled | None = None, **kwargs, ) -> None: call_moto(context) @@ -243,7 +270,7 @@ def _check_resource_arn_tagging(self, resource_arn): ) -def get_pattern_matcher(pattern: str) -> Callable[[str, Dict], bool]: +def get_pattern_matcher(pattern: str) -> Callable[[str, dict], bool]: """Returns a pattern matcher. Can be patched by plugins to return a more sophisticated pattern matcher.""" return lambda _pattern, _log_event: True @@ -410,7 +437,7 @@ def moto_put_log_events(self: "MotoLogStream", log_events): Record={"Data": payload_gz_encoded}, ) - return "{:056d}".format(self.upload_sequence_token) + return f"{self.upload_sequence_token:056d}" @patch(MotoLogStream.filter_log_events) @@ -442,10 +469,9 @@ def moto_to_describe_dict(target, self): # reported race condition in https://github.com/localstack/localstack/issues/8011 # making copy of "streams" dict here to avoid issues while summing up storedBytes copy_streams = copy.deepcopy(self.streams) - # parity tests shows that the arn ends with ":*" - arn = self.arn if self.arn.endswith(":*") else f"{self.arn}:*" log_group = { - "arn": arn, + "arn": f"{self.arn}:*", + "logGroupArn": self.arn, "creationTime": self.creation_time, "logGroupName": self.name, "metricFilterCount": 0, diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py index 6dd6b66190bf3..a5e7bca25cfd8 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class LogsLogGroupProperties(TypedDict): - Arn: Optional[str] - DataProtectionPolicy: Optional[dict] - KmsKeyId: Optional[str] - LogGroupName: Optional[str] - RetentionInDays: Optional[int] - Tags: Optional[list[Tag]] + Arn: str | None + DataProtectionPolicy: dict | None + KmsKeyId: str | None + LogGroupName: str | None + RetentionInDays: int | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py index 5dd8087a87561..46899a144adba 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LogsLogGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Logs::LogGroup" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.logs.resource_providers.aws_logs_loggroup import ( diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py index 4cb21339b6e77..bc36c8dc5c0e2 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,9 +14,9 @@ class LogsLogStreamProperties(TypedDict): - LogGroupName: Optional[str] - Id: Optional[str] - LogStreamName: Optional[str] + LogGroupName: str | None + Id: str | None + LogStreamName: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py index 578e23c4ae628..51dde35fc97ca 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LogsLogStreamProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Logs::LogStream" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.logs.resource_providers.aws_logs_logstream import ( diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py index 26f204e52e78e..28134dbfedf0d 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,12 +14,12 @@ class LogsSubscriptionFilterProperties(TypedDict): - DestinationArn: Optional[str] - FilterPattern: Optional[str] - LogGroupName: Optional[str] - Distribution: Optional[str] - FilterName: Optional[str] - RoleArn: Optional[str] + DestinationArn: str | None + FilterPattern: str | None + LogGroupName: str | None + Distribution: str | None + FilterName: str | None + RoleArn: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py index def55ff386045..7055e96585c43 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LogsSubscriptionFilterProviderPlugin(CloudFormationResourceProviderPlugin) name = "AWS::Logs::SubscriptionFilter" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.logs.resource_providers.aws_logs_subscriptionfilter import ( diff --git a/localstack-core/localstack/services/moto.py b/localstack-core/localstack/services/moto.py index c98989c39a967..c2180066b06b6 100644 --- a/localstack-core/localstack/services/moto.py +++ b/localstack-core/localstack/services/moto.py @@ -1,11 +1,11 @@ """ -This module provides tools to call moto using moto and botocore internals without going through the moto HTTP server. +This module provides tools to call Moto service implementations. """ import copy import sys +from collections.abc import Callable from functools import lru_cache -from typing import Callable, Optional, Union import moto.backends as moto_backends from moto.core.base_backend import BackendDict @@ -65,6 +65,7 @@ def call_moto_with_request( action=context.operation.name, parameters=service_request, region=context.region, + protocol=context.protocol, ) # we keep the headers from the original request, but override them with the ones created from the `service_request` headers = copy.deepcopy(context.request.headers) @@ -76,7 +77,7 @@ def call_moto_with_request( def _proxy_moto( context: RequestContext, request: ServiceRequest -) -> Optional[Union[ServiceResponse, Response]]: +) -> ServiceResponse | Response | None: """ Wraps `call_moto` such that the interface is compliant with a ServiceRequestHandler. @@ -154,7 +155,7 @@ def get_dispatcher(service: str, path: str) -> MotoDispatcher: return endpoint -@lru_cache() +@lru_cache def get_moto_routing_table(service: str) -> Map: """Cached version of load_moto_routing_table.""" return load_moto_routing_table(service) diff --git a/localstack-core/localstack/services/opensearch/cluster.py b/localstack-core/localstack/services/opensearch/cluster.py index cae1916c90b09..e19c92bccb086 100644 --- a/localstack-core/localstack/services/opensearch/cluster.py +++ b/localstack-core/localstack/services/opensearch/cluster.py @@ -2,7 +2,7 @@ import logging import os import threading -from typing import Dict, List, NamedTuple, Optional, Tuple +from typing import NamedTuple from urllib.parse import urlparse import requests @@ -18,7 +18,12 @@ from localstack.http.proxy import ProxyHandler from localstack.services.edge import ROUTER from localstack.services.opensearch import versions -from localstack.services.opensearch.packages import elasticsearch_package, opensearch_package +from localstack.services.opensearch.packages import ( + ELASTICSEARCH_DEFAULT_VERSION, + OPENSEARCH_DEFAULT_VERSION, + elasticsearch_package, + opensearch_package, +) from localstack.utils.aws.arns import parse_arn from localstack.utils.common import ( ShellCommandThread, @@ -37,7 +42,10 @@ INTERNAL_USER_AUTH = ("localstack-internal", "localstack-internal") DEFAULT_BACKEND_HOST = "127.0.0.1" -CommandSettings = Dict[str, str] +# user that starts the opensearch process if the current user is root +OS_USER_OPENSEARCH = "localstack" + +CommandSettings = dict[str, str] class Directories(NamedTuple): @@ -49,8 +57,8 @@ class Directories(NamedTuple): def get_cluster_health_status( - url: str, auth: Tuple[str, str] | None, host: str | None = None -) -> Optional[str]: + url: str, auth: tuple[str, str] | None, host: str | None = None +) -> str | None: """ Queries the health endpoint of OpenSearch/Elasticsearch and returns either the status ('green', 'yellow', ...) or None if the response returned a non-200 response. @@ -125,7 +133,7 @@ def resolve_directories(version: str, cluster_path: str, data_root: str = None) return Directories(install_dir, tmp_dir, modules_dir, data_dir, backup_dir) -def build_cluster_run_command(cluster_bin: str, settings: CommandSettings) -> List[str]: +def build_cluster_run_command(cluster_bin: str, settings: CommandSettings) -> list[str]: """ Takes the command settings dict and builds the actual command (which can then be executed as a shell command). @@ -169,13 +177,13 @@ class SecurityOptions: master_password: str | None @property - def auth(self) -> Tuple[str, str] | None: + def auth(self) -> tuple[str, str] | None: """Returns an auth tuple which can be used for HTTP requests or None, if disabled.""" return None if not self.enabled else (self.master_username, self.master_password) @staticmethod def from_input( - advanced_security_options: Optional[AdvancedSecurityOptionsInput], + advanced_security_options: AdvancedSecurityOptionsInput | None, ) -> "SecurityOptions": """ Parses the given AdvancedSecurityOptionsInput, performs some validation, and returns the parsed SecurityOptions. @@ -215,7 +223,7 @@ def from_input( def register_cluster( host: str, path: str, forward_url: str, custom_endpoint: CustomEndpoint -) -> List[Rule]: +) -> list[Rule]: """ Registers routes for a cluster at the edge router. Depending on which endpoint strategy is employed, and if a custom endpoint is enabled, different routes are @@ -314,7 +322,7 @@ def __init__( @property def default_version(self) -> str: - return constants.OPENSEARCH_DEFAULT_VERSION + return OPENSEARCH_DEFAULT_VERSION @property def version(self) -> str: @@ -336,9 +344,9 @@ def bin_name(self) -> str: @property def os_user(self): - return constants.OS_USER_OPENSEARCH + return OS_USER_OPENSEARCH - def health(self) -> Optional[str]: + def health(self) -> str | None: return get_cluster_health_status(self.url, auth=self.auth) def do_start_thread(self) -> FuncThread: @@ -451,8 +459,8 @@ def _base_settings(self, dirs) -> CommandSettings: return settings def _create_run_command( - self, directories: Directories, additional_settings: Optional[CommandSettings] = None - ) -> List[str]: + self, directories: Directories, additional_settings: CommandSettings | None = None + ) -> list[str]: # delete opensearch data that may be cached locally from a previous test run bin_path = os.path.join(directories.install, "bin", self.bin_name) @@ -464,7 +472,7 @@ def _create_run_command( cmd = build_cluster_run_command(bin_path, settings) return cmd - def _create_env_vars(self, directories: Directories) -> Dict: + def _create_env_vars(self, directories: Directories) -> dict: env_vars = { "JAVA_HOME": os.path.join(directories.install, "jdk"), "OPENSEARCH_JAVA_OPTS": os.environ.get("OPENSEARCH_JAVA_OPTS", "-Xms200m -Xmx600m"), @@ -580,7 +588,7 @@ def version(self): @property def default_version(self): - return constants.OPENSEARCH_DEFAULT_VERSION + return OPENSEARCH_DEFAULT_VERSION @property def url(self) -> str: @@ -658,7 +666,7 @@ def __init__( @property def default_version(self) -> str: - return constants.ELASTICSEARCH_DEFAULT_VERSION + return ELASTICSEARCH_DEFAULT_VERSION @property def bin_name(self) -> str: @@ -666,7 +674,7 @@ def bin_name(self) -> str: @property def os_user(self): - return constants.OS_USER_OPENSEARCH + return OS_USER_OPENSEARCH def _ensure_installed(self): elasticsearch_package.install(self.version) @@ -699,7 +707,7 @@ def _base_settings(self, dirs) -> CommandSettings: return settings - def _create_env_vars(self, directories: Directories) -> Dict: + def _create_env_vars(self, directories: Directories) -> dict: return { **elasticsearch_package.get_installer(self.version).get_java_env_vars(), "ES_JAVA_OPTS": os.environ.get("ES_JAVA_OPTS", "-Xms200m -Xmx600m"), @@ -710,7 +718,7 @@ def _create_env_vars(self, directories: Directories) -> Dict: class EdgeProxiedElasticsearchCluster(EdgeProxiedOpensearchCluster): @property def default_version(self): - return constants.ELASTICSEARCH_DEFAULT_VERSION + return ELASTICSEARCH_DEFAULT_VERSION def _backend_cluster(self) -> OpensearchCluster: return ElasticsearchCluster( diff --git a/localstack-core/localstack/services/opensearch/cluster_manager.py b/localstack-core/localstack/services/opensearch/cluster_manager.py index 8a286daf661fc..1ceb42ed64c18 100644 --- a/localstack-core/localstack/services/opensearch/cluster_manager.py +++ b/localstack-core/localstack/services/opensearch/cluster_manager.py @@ -1,7 +1,6 @@ import dataclasses import logging import threading -from typing import Dict, Optional from botocore.utils import ArnParser @@ -80,9 +79,9 @@ def from_arn(arn: str) -> "DomainKey": def build_cluster_endpoint( domain_key: DomainKey, - custom_endpoint: Optional[CustomEndpoint] = None, + custom_endpoint: CustomEndpoint | None = None, engine_type: EngineType = EngineType.OpenSearch, - preferred_port: Optional[int] = None, + preferred_port: int | None = None, ) -> str: """ Builds the cluster endpoint from and optional custom_endpoint and the localstack opensearch config. Example @@ -131,7 +130,7 @@ def build_cluster_endpoint( def determine_custom_endpoint( domain_endpoint_options: DomainEndpointOptions, -) -> Optional[CustomEndpoint]: +) -> CustomEndpoint | None: if not domain_endpoint_options: return @@ -146,7 +145,7 @@ def determine_custom_endpoint( class ClusterManager: - clusters: Dict[str, Server] + clusters: dict[str, Server] def __init__(self) -> None: self.clusters = {} @@ -155,9 +154,9 @@ def create( self, arn: str, version: str, - endpoint_options: Optional[DomainEndpointOptions] = None, - security_options: Optional[SecurityOptions] = None, - preferred_port: Optional[int] = None, + endpoint_options: DomainEndpointOptions | None = None, + security_options: SecurityOptions | None = None, + preferred_port: int | None = None, ) -> Server: """ Creates a new cluster. @@ -190,7 +189,7 @@ def create( self.clusters[arn] = cluster return cluster - def get(self, arn: str) -> Optional[Server]: + def get(self, arn: str) -> Server | None: return self.clusters.get(arn) def remove(self, arn: str): @@ -257,8 +256,8 @@ class MultiplexingClusterManager(ClusterManager): - OPENSEARCH_ENDPOINT_STRATEGY = domain / path """ - cluster: Optional[Server] - endpoints: Dict[str, ClusterEndpoint] + cluster: Server | None + endpoints: dict[str, ClusterEndpoint] def __init__(self) -> None: super().__init__() @@ -366,7 +365,7 @@ class SingletonClusterManager(ClusterManager): - ES_MULTI_CLUSTER == False """ - cluster: Optional[Server] + cluster: Server | None def __init__(self) -> None: super().__init__() @@ -378,8 +377,8 @@ def create( self, arn: str, version: str, - endpoint_options: Optional[DomainEndpointOptions] = None, - security_options: Optional[SecurityOptions] = None, + endpoint_options: DomainEndpointOptions | None = None, + security_options: SecurityOptions | None = None, preferred_port: int = None, ) -> Server: with self.mutex: diff --git a/localstack-core/localstack/services/opensearch/models.py b/localstack-core/localstack/services/opensearch/models.py index 5748ac7639ec3..0cf6d2d769533 100644 --- a/localstack-core/localstack/services/opensearch/models.py +++ b/localstack-core/localstack/services/opensearch/models.py @@ -1,21 +1,16 @@ -from typing import Dict - from localstack.aws.api.opensearch import DomainStatus from localstack.services.stores import ( AccountRegionBundle, BaseStore, - CrossRegionAttribute, LocalAttribute, ) -from localstack.utils.tagging import TaggingService +from localstack.utils.tagging import Tags class OpenSearchStore(BaseStore): # storage for domain resources (access should be protected with the _domain_mutex) - opensearch_domains: Dict[str, DomainStatus] = LocalAttribute(default=dict) - - # static tagging service instance - TAGS = CrossRegionAttribute(default=TaggingService) + opensearch_domains: dict[str, DomainStatus] = LocalAttribute(default=dict) + tags: Tags = LocalAttribute(default=Tags) opensearch_stores = AccountRegionBundle("opensearch", OpenSearchStore) diff --git a/localstack-core/localstack/services/opensearch/packages.py b/localstack-core/localstack/services/opensearch/packages.py index 35a7fd933ea91..9ef799a69b0a2 100644 --- a/localstack-core/localstack/services/opensearch/packages.py +++ b/localstack-core/localstack/services/opensearch/packages.py @@ -5,18 +5,10 @@ import shutil import textwrap import threading -from typing import List import semver from localstack import config -from localstack.constants import ( - ELASTICSEARCH_DEFAULT_VERSION, - ELASTICSEARCH_DELETE_MODULES, - ELASTICSEARCH_PLUGIN_LIST, - OPENSEARCH_DEFAULT_VERSION, - OPENSEARCH_PLUGIN_LIST, -) from localstack.packages import InstallTarget, Package, PackageInstaller from localstack.packages.java import java_package from localstack.services.opensearch import versions @@ -33,6 +25,32 @@ LOG = logging.getLogger(__name__) +# the version of opensearch which is used by default +OPENSEARCH_DEFAULT_VERSION = "OpenSearch_3.1" + +# See https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-plugins.html +OPENSEARCH_PLUGIN_LIST = [ + "ingest-attachment", + "analysis-kuromoji", +] + +# the version of elasticsearch that is pre-seeded into the base image (sync with Dockerfile.base) +ELASTICSEARCH_DEFAULT_VERSION = "Elasticsearch_7.10" + +# See https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/aes-supported-plugins.html +ELASTICSEARCH_PLUGIN_LIST = [ + "analysis-icu", + "ingest-attachment", + "analysis-kuromoji", + "mapper-murmur3", + "mapper-size", + "analysis-phonetic", + "analysis-smartcn", + "analysis-stempel", + "analysis-ukrainian", +] +# Default ES modules to exclude (save apprx 66MB in the final image) +ELASTICSEARCH_DELETE_MODULES = ["ingest-geoip"] _OPENSEARCH_INSTALL_LOCKS = SynchronizedDefaultDict(threading.RLock) @@ -49,7 +67,7 @@ def _get_installer(self, version: str) -> PackageInstaller: else: return OpensearchPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return list(versions.install_versions.keys()) @@ -89,19 +107,31 @@ def _install(self, target: InstallTarget): # setup security based on the version self._setup_security(install_dir, parsed_version) + # Determine network configuration to use for plugin downloads + sys_props = { + **java_system_properties_proxy(), + **java_system_properties_ssl( + os.path.join(install_dir, "jdk", "bin", "keytool"), + {"JAVA_HOME": os.path.join(install_dir, "jdk")}, + ), + } + java_opts = system_properties_to_cli_args(sys_props) + + keystore_binary = os.path.join(install_dir, "bin", "opensearch-keystore") + if os.path.exists(keystore_binary): + # initialize and create the keystore. Concurrent starts of ES will all try to create it at the same + # time, and fail with a race condition. Creating once when installing solves the issue without + # the need to lock the starts + # Ultimately, each cluster should have its own `config` file and maybe not share the same one + output = run( + [keystore_binary, "create"], + env_vars={"OPENSEARCH_JAVA_OPTS": " ".join(java_opts)}, + ) + LOG.debug("Keystore init output: %s", output) + # install other default plugins for opensearch 1.1+ # https://forum.opensearch.org/t/ingest-attachment-cannot-be-installed/6494/12 if parsed_version >= "1.1.0": - # Determine network configuration to use for plugin downloads - sys_props = { - **java_system_properties_proxy(), - **java_system_properties_ssl( - os.path.join(install_dir, "jdk", "bin", "keytool"), - {"JAVA_HOME": os.path.join(install_dir, "jdk")}, - ), - } - java_opts = system_properties_to_cli_args(sys_props) - for plugin in OPENSEARCH_PLUGIN_LIST: plugin_binary = os.path.join(install_dir, "bin", "opensearch-plugin") plugin_dir = os.path.join(install_dir, "plugins", plugin) @@ -304,6 +334,18 @@ def try_install(): if not os.environ.get("IGNORE_ES_DOWNLOAD_ERRORS"): raise + keystore_binary = os.path.join(install_dir, "bin", "elasticsearch-keystore") + if os.path.exists(keystore_binary): + # initialize and create the keystore. Concurrent starts of ES will all try to create it at the same + # time, and fail with a race condition. Creating once when installing solves the issue without + # the need to lock the starts + # Ultimately, each cluster should have its own `config` file and maybe not share the same one + output = run( + [keystore_binary, "create"], + env_vars={"ES_JAVA_OPTS": " ".join(java_opts)}, + ) + LOG.debug("Keystore init output: %s", output) + # delete some plugins to free up space for plugin in ELASTICSEARCH_DELETE_MODULES: module_dir = os.path.join(install_dir, "modules", plugin) @@ -323,16 +365,6 @@ def try_install(): if jvm_options != jvm_options_replaced: save_file(jvm_options_file, jvm_options_replaced) - # patch JVM options file - replace hardcoded heap size settings - jvm_options_file = os.path.join(install_dir, "config", "jvm.options") - if os.path.exists(jvm_options_file): - jvm_options = load_file(jvm_options_file) - jvm_options_replaced = re.sub( - r"(^-Xm[sx][a-zA-Z0-9.]+$)", r"# \1", jvm_options, flags=re.MULTILINE - ) - if jvm_options != jvm_options_replaced: - save_file(jvm_options_file, jvm_options_replaced) - def _get_install_marker_path(self, install_dir: str) -> str: return os.path.join(install_dir, "bin", "elasticsearch") diff --git a/localstack-core/localstack/services/opensearch/provider.py b/localstack-core/localstack/services/opensearch/provider.py index e33554c347ad8..da870bfe6586f 100644 --- a/localstack-core/localstack/services/opensearch/provider.py +++ b/localstack-core/localstack/services/opensearch/provider.py @@ -3,9 +3,8 @@ import re import threading from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from random import randint -from typing import Dict, Optional from urllib.parse import urlparse from localstack import config @@ -27,6 +26,7 @@ CognitoOptions, CognitoOptionsStatus, ColdStorageOptions, + CompatibleVersionsMap, CreateDomainRequest, CreateDomainResponse, DeleteDomainResponse, @@ -76,7 +76,6 @@ VolumeType, VPCDerivedInfoStatus, ) -from localstack.constants import OPENSEARCH_DEFAULT_VERSION from localstack.services.opensearch import versions from localstack.services.opensearch.cluster import SecurityOptions from localstack.services.opensearch.cluster_manager import ( @@ -85,6 +84,7 @@ create_cluster_manager, ) from localstack.services.opensearch.models import OpenSearchStore, opensearch_stores +from localstack.services.opensearch.packages import OPENSEARCH_DEFAULT_VERSION from localstack.services.plugins import ServiceLifecycleHook from localstack.state import AssetDirectory, StateVisitor from localstack.utils.aws.arns import parse_arn @@ -116,6 +116,11 @@ CustomEndpointEnabled=False, ) +DEFAULT_AUTOTUNE_OPTIONS = AutoTuneOptionsOutput( + State=AutoTuneState.ENABLED, + UseOffPeakWindow=False, +) + def cluster_manager() -> ClusterManager: global __CLUSTER_MANAGER @@ -143,9 +148,9 @@ def _run_cluster_startup_monitor(cluster: Server, domain_name: str, region: str) def create_cluster( domain_key: DomainKey, engine_version: str, - domain_endpoint_options: Optional[DomainEndpointOptions], - security_options: Optional[SecurityOptions], - preferred_port: Optional[int] = None, + domain_endpoint_options: DomainEndpointOptions | None, + security_options: SecurityOptions | None, + preferred_port: int | None = None, ): """ Uses the ClusterManager to create a new cluster for the given domain key. NOT thread safe, needs to be called @@ -203,6 +208,13 @@ def _status_to_config(status: DomainStatus) -> DomainConfig: cluster_cfg = status.get("ClusterConfig") or {} default_cfg = DEFAULT_OPENSEARCH_CLUSTER_CONFIG config_status = get_domain_config_status() + autotune_options = status.get("AutoTuneOptions") or DEFAULT_AUTOTUNE_OPTIONS + autotune_state = autotune_options.get("State") or AutoTuneState.ENABLED + desired_state = ( + AutoTuneDesiredState.ENABLED + if autotune_state == AutoTuneState.ENABLED + else AutoTuneDesiredState.DISABLED + ) return DomainConfig( AccessPolicies=AccessPoliciesStatus( Options=status.get("AccessPolicies", ""), @@ -275,15 +287,16 @@ def _status_to_config(status: DomainStatus) -> DomainConfig: ), AutoTuneOptions=AutoTuneOptionsStatus( Options=AutoTuneOptions( - DesiredState=AutoTuneDesiredState.ENABLED, + DesiredState=desired_state, RollbackOnDisable=RollbackOnDisable.NO_ROLLBACK, MaintenanceSchedules=[], + UseOffPeakWindow=autotune_options.get("UseOffPeakWindow", False), ), Status=AutoTuneStatus( CreationDate=config_status.get("CreationDate"), UpdateDate=config_status.get("UpdateDate"), UpdateVersion=config_status.get("UpdateVersion"), - State=AutoTuneState.ENABLED, + State=autotune_state, PendingDeletion=config_status.get("PendingDeletion"), ), ), @@ -315,6 +328,22 @@ def get_domain_status( stored_status.update(request) default_cfg.update(request.get("ClusterConfig", {})) + autotune_options = stored_status.get("AutoTuneOptions") or deepcopy(DEFAULT_AUTOTUNE_OPTIONS) + if request and (request_options := request.get("AutoTuneOptions")): + desired_state = request_options.get("DesiredState") or AutoTuneDesiredState.ENABLED + state = ( + AutoTuneState.ENABLED + if desired_state == AutoTuneDesiredState.ENABLED + else AutoTuneState.DISABLED + ) + autotune_options = AutoTuneOptionsOutput( + State=state, + UseOffPeakWindow=request_options.get( + "UseOffPeakWindow", autotune_options.get("UseOffPeakWindow", False) + ), + ) + stored_status["AutoTuneOptions"] = autotune_options + domain_processing_status = stored_status.get("DomainProcessingStatus", None) processing = stored_status.get("Processing", True) if deleted: @@ -369,7 +398,7 @@ def get_domain_status( Cancellable=False, UpdateStatus=DeploymentStatus.COMPLETED, Description="There is no software update available for this domain.", - AutomatedUpdateDate=datetime.fromtimestamp(0, tz=timezone.utc), + AutomatedUpdateDate=datetime.fromtimestamp(0, tz=UTC), OptionalDeployment=True, ), DomainEndpointOptions=stored_status.get("DomainEndpointOptions") @@ -377,7 +406,10 @@ def get_domain_status( AdvancedSecurityOptions=AdvancedSecurityOptions( Enabled=False, InternalUserDatabaseEnabled=False ), - AutoTuneOptions=AutoTuneOptionsOutput(State=AutoTuneState.ENABLE_IN_PROGRESS), + AutoTuneOptions=AutoTuneOptionsOutput( + State=stored_status.get("AutoTuneOptions", {}).get("State"), + UseOffPeakWindow=autotune_options.get("UseOffPeakWindow", False), + ), ) return new_status @@ -399,7 +431,7 @@ def _ensure_domain_exists(arn: ARN) -> None: def _update_domain_config_request_to_status(request: UpdateDomainConfigRequest) -> DomainStatus: - request: Dict + request: dict request.pop("DryRun", None) request.pop("DomainName", None) return request @@ -467,10 +499,11 @@ def on_after_state_load(self): preferred_port=preferred_port, ) except Exception: - LOG.exception( + LOG.error( "Could not restore domain %s in region %s.", domain_name, region, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) def on_before_state_reset(self): @@ -484,6 +517,22 @@ def _stop_clusters(self): for domain_name in store.opensearch_domains.keys(): cluster_manager().remove(DomainKey(domain_name, region, account_id).arn) + def _add_tags(self, context: RequestContext, arn: ARN, tag_list: TagList) -> None: + self.get_store(context.account_id, context.region).tags.update_tags( + arn, {tag["Key"]: tag["Value"] for tag in tag_list} + ) + + def _remove_tags(self, context: RequestContext, arn: ARN, tag_keys: StringList) -> None: + self.get_store(context.account_id, context.region).tags.delete_tags(arn, tag_keys) + + def _remove_all_tags(self, context: RequestContext, arn: ARN) -> None: + self.get_store(context.account_id, context.region).tags.delete_all_tags(arn) + + def _list_tags(self, context: RequestContext, arn: ARN) -> TagList: + store = self.get_store(context.account_id, context.region) + tags = store.tags.get_tags(arn) + return [{"Key": key, "Value": value} for key, value in tags.items()] + @handler("CreateDomain", expand=False) def create_domain( self, context: RequestContext, request: CreateDomainRequest @@ -525,7 +574,8 @@ def create_domain( ) # set the tags - self.add_tags(context, domain_key.arn, request.get("TagList")) + if tags := request.get("TagList", []): + self._add_tags(context, domain_key.arn, tags) # get the (updated) status status = get_domain_status(domain_key) @@ -547,6 +597,7 @@ def delete_domain( status = get_domain_status(domain_key, deleted=True) _remove_cluster(domain_key) + self._remove_all_tags(context, domain_key.arn) return DeleteDomainResponse(DomainStatus=status) @@ -581,7 +632,25 @@ def update_domain_config( if domain_status is None: raise ResourceNotFoundException(f"Domain not found: {domain_key.domain_name}") - status_update: Dict = _update_domain_config_request_to_status(payload) + if payload.get("AutoTuneOptions"): + auto_request = payload.pop("AutoTuneOptions") + desired_state = auto_request.get("DesiredState") or AutoTuneDesiredState.ENABLED + + state = ( + AutoTuneState.ENABLED + if desired_state == AutoTuneDesiredState.ENABLED + else AutoTuneState.DISABLED + ) + + current_autotune = domain_status.get("AutoTuneOptions", {}) + domain_status["AutoTuneOptions"] = AutoTuneOptionsOutput( + State=state, + UseOffPeakWindow=auto_request.get( + "UseOffPeakWindow", current_autotune.get("UseOffPeakWindow", False) + ), + ) + + status_update: dict = _update_domain_config_request_to_status(payload) domain_status.update(status_update) return UpdateDomainConfigResponse(DomainConfig=_status_to_config(domain_status)) @@ -650,6 +719,10 @@ def get_compatible_versions( for comp in versions.compatible_versions if comp["SourceVersion"] == version_filter ] + if not compatible_versions: + compatible_versions = [ + CompatibleVersionsMap(SourceVersion=version_filter, TargetVersions=[]) + ] return GetCompatibleVersionsResponse(CompatibleVersions=compatible_versions) def describe_domain_config( @@ -669,20 +742,15 @@ def describe_domain_config( def add_tags(self, context: RequestContext, arn: ARN, tag_list: TagList, **kwargs) -> None: _ensure_domain_exists(arn) - self.get_store(context.account_id, context.region).TAGS.tag_resource(arn, tag_list) + self._add_tags(context, arn, tag_list) def list_tags(self, context: RequestContext, arn: ARN, **kwargs) -> ListTagsResponse: _ensure_domain_exists(arn) - - # The tagging service returns a dictionary with the given root name - store = self.get_store(context.account_id, context.region) - tags = store.TAGS.list_tags_for_resource(arn=arn, root_name="root") - # Extract the actual list of tags for the typed response - tag_list: TagList = tags["root"] + tag_list = self._list_tags(context, arn) return ListTagsResponse(TagList=tag_list) def remove_tags( self, context: RequestContext, arn: ARN, tag_keys: StringList, **kwargs ) -> None: _ensure_domain_exists(arn) - self.get_store(context.account_id, context.region).TAGS.untag_resource(arn, tag_keys) + self._remove_tags(context, arn, tag_keys) diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py index 4de950b722ce9..610b6df18e125 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py @@ -3,7 +3,7 @@ import copy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.aws.api.es import CreateElasticsearchDomainRequest @@ -17,105 +17,105 @@ class ElasticsearchDomainProperties(TypedDict): - AccessPolicies: Optional[dict] - AdvancedOptions: Optional[dict] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - Arn: Optional[str] - CognitoOptions: Optional[CognitoOptions] - DomainArn: Optional[str] - DomainEndpoint: Optional[str] - DomainEndpointOptions: Optional[DomainEndpointOptions] - DomainName: Optional[str] - EBSOptions: Optional[EBSOptions] - ElasticsearchClusterConfig: Optional[ElasticsearchClusterConfig] - ElasticsearchVersion: Optional[str] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - Id: Optional[str] - LogPublishingOptions: Optional[dict] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - SnapshotOptions: Optional[SnapshotOptions] - Tags: Optional[list[Tag]] - VPCOptions: Optional[VPCOptions] + AccessPolicies: dict | None + AdvancedOptions: dict | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + Arn: str | None + CognitoOptions: CognitoOptions | None + DomainArn: str | None + DomainEndpoint: str | None + DomainEndpointOptions: DomainEndpointOptions | None + DomainName: str | None + EBSOptions: EBSOptions | None + ElasticsearchClusterConfig: ElasticsearchClusterConfig | None + ElasticsearchVersion: str | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + Id: str | None + LogPublishingOptions: dict | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + SnapshotOptions: SnapshotOptions | None + Tags: list[Tag] | None + VPCOptions: VPCOptions | None class ZoneAwarenessConfig(TypedDict): - AvailabilityZoneCount: Optional[int] + AvailabilityZoneCount: int | None class ColdStorageOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class ElasticsearchClusterConfig(TypedDict): - ColdStorageOptions: Optional[ColdStorageOptions] - DedicatedMasterCount: Optional[int] - DedicatedMasterEnabled: Optional[bool] - DedicatedMasterType: Optional[str] - InstanceCount: Optional[int] - InstanceType: Optional[str] - WarmCount: Optional[int] - WarmEnabled: Optional[bool] - WarmType: Optional[str] - ZoneAwarenessConfig: Optional[ZoneAwarenessConfig] - ZoneAwarenessEnabled: Optional[bool] + ColdStorageOptions: ColdStorageOptions | None + DedicatedMasterCount: int | None + DedicatedMasterEnabled: bool | None + DedicatedMasterType: str | None + InstanceCount: int | None + InstanceType: str | None + WarmCount: int | None + WarmEnabled: bool | None + WarmType: str | None + ZoneAwarenessConfig: ZoneAwarenessConfig | None + ZoneAwarenessEnabled: bool | None class SnapshotOptions(TypedDict): - AutomatedSnapshotStartHour: Optional[int] + AutomatedSnapshotStartHour: int | None class VPCOptions(TypedDict): - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class NodeToNodeEncryptionOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class DomainEndpointOptions(TypedDict): - CustomEndpoint: Optional[str] - CustomEndpointCertificateArn: Optional[str] - CustomEndpointEnabled: Optional[bool] - EnforceHTTPS: Optional[bool] - TLSSecurityPolicy: Optional[str] + CustomEndpoint: str | None + CustomEndpointCertificateArn: str | None + CustomEndpointEnabled: bool | None + EnforceHTTPS: bool | None + TLSSecurityPolicy: str | None class CognitoOptions(TypedDict): - Enabled: Optional[bool] - IdentityPoolId: Optional[str] - RoleArn: Optional[str] - UserPoolId: Optional[str] + Enabled: bool | None + IdentityPoolId: str | None + RoleArn: str | None + UserPoolId: str | None class MasterUserOptions(TypedDict): - MasterUserARN: Optional[str] - MasterUserName: Optional[str] - MasterUserPassword: Optional[str] + MasterUserARN: str | None + MasterUserName: str | None + MasterUserPassword: str | None class AdvancedSecurityOptionsInput(TypedDict): - AnonymousAuthEnabled: Optional[bool] - Enabled: Optional[bool] - InternalUserDatabaseEnabled: Optional[bool] - MasterUserOptions: Optional[MasterUserOptions] + AnonymousAuthEnabled: bool | None + Enabled: bool | None + InternalUserDatabaseEnabled: bool | None + MasterUserOptions: MasterUserOptions | None class EBSOptions(TypedDict): - EBSEnabled: Optional[bool] - Iops: Optional[int] - VolumeSize: Optional[int] - VolumeType: Optional[str] + EBSEnabled: bool | None + Iops: int | None + VolumeSize: int | None + VolumeType: str | None class EncryptionAtRestOptions(TypedDict): - Enabled: Optional[bool] - KmsKeyId: Optional[str] + Enabled: bool | None + KmsKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py index c5f22fa0b816e..918a07ddb4f5a 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ElasticsearchDomainProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Elasticsearch::Domain" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.opensearch.resource_providers.aws_elasticsearch_domain import ( diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py index 96b8c60ec0b2b..e27139b836454 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,151 +15,151 @@ class OpenSearchServiceDomainProperties(TypedDict): - AccessPolicies: Optional[dict] - AdvancedOptions: Optional[dict] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - Arn: Optional[str] - ClusterConfig: Optional[ClusterConfig] - CognitoOptions: Optional[CognitoOptions] - DomainArn: Optional[str] - DomainEndpoint: Optional[str] - DomainEndpointOptions: Optional[DomainEndpointOptions] - DomainEndpoints: Optional[dict] - DomainName: Optional[str] - EBSOptions: Optional[EBSOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - EngineVersion: Optional[str] - Id: Optional[str] - LogPublishingOptions: Optional[dict] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - OffPeakWindowOptions: Optional[OffPeakWindowOptions] - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] - SnapshotOptions: Optional[SnapshotOptions] - SoftwareUpdateOptions: Optional[SoftwareUpdateOptions] - Tags: Optional[list[Tag]] - VPCOptions: Optional[VPCOptions] + AccessPolicies: dict | None + AdvancedOptions: dict | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + Arn: str | None + ClusterConfig: ClusterConfig | None + CognitoOptions: CognitoOptions | None + DomainArn: str | None + DomainEndpoint: str | None + DomainEndpointOptions: DomainEndpointOptions | None + DomainEndpoints: dict | None + DomainName: str | None + EBSOptions: EBSOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + EngineVersion: str | None + Id: str | None + LogPublishingOptions: dict | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + OffPeakWindowOptions: OffPeakWindowOptions | None + ServiceSoftwareOptions: ServiceSoftwareOptions | None + SnapshotOptions: SnapshotOptions | None + SoftwareUpdateOptions: SoftwareUpdateOptions | None + Tags: list[Tag] | None + VPCOptions: VPCOptions | None class ZoneAwarenessConfig(TypedDict): - AvailabilityZoneCount: Optional[int] + AvailabilityZoneCount: int | None class ClusterConfig(TypedDict): - DedicatedMasterCount: Optional[int] - DedicatedMasterEnabled: Optional[bool] - DedicatedMasterType: Optional[str] - InstanceCount: Optional[int] - InstanceType: Optional[str] - WarmCount: Optional[int] - WarmEnabled: Optional[bool] - WarmType: Optional[str] - ZoneAwarenessConfig: Optional[ZoneAwarenessConfig] - ZoneAwarenessEnabled: Optional[bool] + DedicatedMasterCount: int | None + DedicatedMasterEnabled: bool | None + DedicatedMasterType: str | None + InstanceCount: int | None + InstanceType: str | None + WarmCount: int | None + WarmEnabled: bool | None + WarmType: str | None + ZoneAwarenessConfig: ZoneAwarenessConfig | None + ZoneAwarenessEnabled: bool | None class SnapshotOptions(TypedDict): - AutomatedSnapshotStartHour: Optional[int] + AutomatedSnapshotStartHour: int | None class VPCOptions(TypedDict): - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class NodeToNodeEncryptionOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class DomainEndpointOptions(TypedDict): - CustomEndpoint: Optional[str] - CustomEndpointCertificateArn: Optional[str] - CustomEndpointEnabled: Optional[bool] - EnforceHTTPS: Optional[bool] - TLSSecurityPolicy: Optional[str] + CustomEndpoint: str | None + CustomEndpointCertificateArn: str | None + CustomEndpointEnabled: bool | None + EnforceHTTPS: bool | None + TLSSecurityPolicy: str | None class CognitoOptions(TypedDict): - Enabled: Optional[bool] - IdentityPoolId: Optional[str] - RoleArn: Optional[str] - UserPoolId: Optional[str] + Enabled: bool | None + IdentityPoolId: str | None + RoleArn: str | None + UserPoolId: str | None class MasterUserOptions(TypedDict): - MasterUserARN: Optional[str] - MasterUserName: Optional[str] - MasterUserPassword: Optional[str] + MasterUserARN: str | None + MasterUserName: str | None + MasterUserPassword: str | None class Idp(TypedDict): - EntityId: Optional[str] - MetadataContent: Optional[str] + EntityId: str | None + MetadataContent: str | None class SAMLOptions(TypedDict): - Enabled: Optional[bool] - Idp: Optional[Idp] - MasterBackendRole: Optional[str] - MasterUserName: Optional[str] - RolesKey: Optional[str] - SessionTimeoutMinutes: Optional[int] - SubjectKey: Optional[str] + Enabled: bool | None + Idp: Idp | None + MasterBackendRole: str | None + MasterUserName: str | None + RolesKey: str | None + SessionTimeoutMinutes: int | None + SubjectKey: str | None class AdvancedSecurityOptionsInput(TypedDict): - AnonymousAuthDisableDate: Optional[str] - AnonymousAuthEnabled: Optional[bool] - Enabled: Optional[bool] - InternalUserDatabaseEnabled: Optional[bool] - MasterUserOptions: Optional[MasterUserOptions] - SAMLOptions: Optional[SAMLOptions] + AnonymousAuthDisableDate: str | None + AnonymousAuthEnabled: bool | None + Enabled: bool | None + InternalUserDatabaseEnabled: bool | None + MasterUserOptions: MasterUserOptions | None + SAMLOptions: SAMLOptions | None class EBSOptions(TypedDict): - EBSEnabled: Optional[bool] - Iops: Optional[int] - Throughput: Optional[int] - VolumeSize: Optional[int] - VolumeType: Optional[str] + EBSEnabled: bool | None + Iops: int | None + Throughput: int | None + VolumeSize: int | None + VolumeType: str | None class EncryptionAtRestOptions(TypedDict): - Enabled: Optional[bool] - KmsKeyId: Optional[str] + Enabled: bool | None + KmsKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ServiceSoftwareOptions(TypedDict): - AutomatedUpdateDate: Optional[str] - Cancellable: Optional[bool] - CurrentVersion: Optional[str] - Description: Optional[str] - NewVersion: Optional[str] - OptionalDeployment: Optional[bool] - UpdateAvailable: Optional[bool] - UpdateStatus: Optional[str] + AutomatedUpdateDate: str | None + Cancellable: bool | None + CurrentVersion: str | None + Description: str | None + NewVersion: str | None + OptionalDeployment: bool | None + UpdateAvailable: bool | None + UpdateStatus: str | None class WindowStartTime(TypedDict): - Hours: Optional[int] - Minutes: Optional[int] + Hours: int | None + Minutes: int | None class OffPeakWindow(TypedDict): - WindowStartTime: Optional[WindowStartTime] + WindowStartTime: WindowStartTime | None class OffPeakWindowOptions(TypedDict): - Enabled: Optional[bool] - OffPeakWindow: Optional[OffPeakWindow] + Enabled: bool | None + OffPeakWindow: OffPeakWindow | None class SoftwareUpdateOptions(TypedDict): - AutoSoftwareUpdateEnabled: Optional[bool] + AutoSoftwareUpdateEnabled: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py index 029076b1aefa8..b07e149ff6db2 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class OpenSearchServiceDomainProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::OpenSearchService::Domain" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.opensearch.resource_providers.aws_opensearchservice_domain import ( diff --git a/localstack-core/localstack/services/opensearch/versions.py b/localstack-core/localstack/services/opensearch/versions.py index 205b9b33d5202..44332006510bd 100644 --- a/localstack-core/localstack/services/opensearch/versions.py +++ b/localstack-core/localstack/services/opensearch/versions.py @@ -6,8 +6,6 @@ """ -from typing import Dict - import semver from localstack.aws.api.opensearch import CompatibleVersionsMap, EngineType @@ -15,20 +13,24 @@ # Internal representation of the OpenSearch versions (without the "OpenSearch_" prefix) _opensearch_install_versions = { + "3.1": "3.1.0", + "2.19": "2.19.3", + "2.17": "2.17.1", + "2.15": "2.15.0", "2.13": "2.13.0", "2.11": "2.11.1", "2.9": "2.9.0", "2.7": "2.7.0", "2.5": "2.5.0", "2.3": "2.3.0", - "1.3": "1.3.12", + "1.3": "1.3.20", "1.2": "1.2.4", "1.1": "1.1.0", "1.0": "1.0.0", } # Internal representation of the Elasticsearch versions (without the "Elasticsearch_" prefix) _elasticsearch_install_versions = { - "7.10": "7.10.0", + "7.10": "7.10.2", "7.9": "7.9.3", "7.8": "7.8.1", "7.7": "7.7.1", @@ -223,6 +225,9 @@ "OpenSearch_2.9", "OpenSearch_2.11", "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19", ], ), CompatibleVersionsMap( @@ -233,28 +238,68 @@ "OpenSearch_2.9", "OpenSearch_2.11", "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19", ], ), CompatibleVersionsMap( SourceVersion="OpenSearch_2.5", - TargetVersions=["OpenSearch_2.7", "OpenSearch_2.9", "OpenSearch_2.11", "OpenSearch_2.13"], + TargetVersions=[ + "OpenSearch_2.7", + "OpenSearch_2.9", + "OpenSearch_2.11", + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19", + ], ), CompatibleVersionsMap( SourceVersion="OpenSearch_2.7", - TargetVersions=["OpenSearch_2.9", "OpenSearch_2.11", "OpenSearch_2.13"], + TargetVersions=[ + "OpenSearch_2.9", + "OpenSearch_2.11", + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19", + ], ), CompatibleVersionsMap( SourceVersion="OpenSearch_2.9", - TargetVersions=["OpenSearch_2.11", "OpenSearch_2.13"], + TargetVersions=[ + "OpenSearch_2.11", + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19", + ], ), CompatibleVersionsMap( SourceVersion="OpenSearch_2.11", - TargetVersions=["OpenSearch_2.13"], + TargetVersions=["OpenSearch_2.13", "OpenSearch_2.15", "OpenSearch_2.17", "OpenSearch_2.19"], + ), + CompatibleVersionsMap( + SourceVersion="OpenSearch_2.13", + TargetVersions=["OpenSearch_2.15", "OpenSearch_2.17", "OpenSearch_2.19"], + ), + CompatibleVersionsMap( + SourceVersion="OpenSearch_2.15", + TargetVersions=["OpenSearch_2.17", "OpenSearch_2.19"], + ), + CompatibleVersionsMap( + SourceVersion="OpenSearch_2.17", + TargetVersions=["OpenSearch_2.19"], + ), + CompatibleVersionsMap( + SourceVersion="OpenSearch_2.19", + TargetVersions=["OpenSearch_3.1"], ), ] -def get_install_type_and_version(version: str) -> (EngineType, str): +def get_install_type_and_version(version: str) -> tuple[EngineType, str]: engine_type = EngineType(version.split("_")[0]) if version not in install_versions: @@ -299,9 +344,11 @@ def get_download_url(install_version: str, engine_type: EngineType) -> str: return _opensearch_url(install_version) elif engine_type == EngineType.Elasticsearch: return _es_url(install_version) + else: + raise ValueError(f"Unknown OpenSearch engine type: {engine_type}") -def fetch_latest_versions() -> Dict[str, str]: # pragma: no cover +def fetch_latest_versions() -> dict[str, str]: # pragma: no cover """ Fetches from the opensearch git repository tags the latest patch versions for a minor version and returns a dictionary where the key corresponds to the minor version, and the value to the patch version. Run this once in a diff --git a/localstack-core/localstack/services/plugins.py b/localstack-core/localstack/services/plugins.py index fbd75a53f0ca7..412796b52d9c9 100644 --- a/localstack-core/localstack/services/plugins.py +++ b/localstack-core/localstack/services/plugins.py @@ -3,9 +3,10 @@ import logging import threading from collections import defaultdict +from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor from enum import Enum -from typing import Callable, Dict, List, Optional, Protocol, Tuple +from typing import Protocol from plux import Plugin, PluginLifecycleListener, PluginManager, PluginSpec @@ -190,7 +191,7 @@ class ServiceContainer: service: Service state: ServiceState lock: threading.RLock - errors: List[Exception] + errors: list[Exception] def __init__(self, service: Service, state=ServiceState.UNKNOWN): self.service = service @@ -236,13 +237,13 @@ def stop(self): class ServiceManager: def __init__(self) -> None: super().__init__() - self._services: Dict[str, ServiceContainer] = {} + self._services: dict[str, ServiceContainer] = {} self._mutex = threading.RLock() - def get_service_container(self, name: str) -> Optional[ServiceContainer]: + def get_service_container(self, name: str) -> ServiceContainer | None: return self._services.get(name) - def get_service(self, name: str) -> Optional[Service]: + def get_service(self, name: str) -> Service | None: container = self.get_service_container(name) return container.service if container else None @@ -252,7 +253,7 @@ def add_service(self, service: Service) -> bool: return True - def list_available(self) -> List[str]: + def list_available(self) -> list[str]: return list(self._services.keys()) def exists(self, name: str) -> bool: @@ -268,11 +269,11 @@ def check(self, name: str) -> bool: def check_all(self): return any(self.check(service_name) for service_name in self.list_available()) - def get_state(self, name: str) -> Optional[ServiceState]: + def get_state(self, name: str) -> ServiceState | None: container = self.get_service_container(name) return container.state if container else None - def get_states(self) -> Dict[str, ServiceState]: + def get_states(self) -> dict[str, ServiceState]: return {name: self.get_state(name) for name in self.list_available()} @log_duration() @@ -285,19 +286,19 @@ def require(self, name: str) -> Service: container = self.get_service_container(name) if not container: - raise ValueError("no such service %s" % name) + raise ValueError(f"no such service {name}") if container.state == ServiceState.STARTING: if not poll_condition(lambda: container.state != ServiceState.STARTING, timeout=30): - raise TimeoutError("gave up waiting for service %s to start" % name) + raise TimeoutError(f"gave up waiting for service {name} to start") if container.state == ServiceState.STOPPING: if not poll_condition(lambda: container.state == ServiceState.STOPPED, timeout=30): - raise TimeoutError("gave up waiting for service %s to stop" % name) + raise TimeoutError(f"gave up waiting for service {name} to stop") with container.lock: if container.state == ServiceState.DISABLED: - raise ServiceDisabled("service %s is disabled" % name) + raise ServiceDisabled(f"service {name} is disabled") if container.state == ServiceState.RUNNING: return container.service @@ -313,7 +314,7 @@ def require(self, name: str) -> Service: raise container.errors[-1] raise ServiceStateException( - "service %s is not ready (%s) and could not be started" % (name, container.state) + f"service {name} is not ready ({container.state}) and could not be started" ) # legacy map compatibility @@ -397,13 +398,13 @@ class ServicePluginErrorCollector(PluginLifecycleListener): A PluginLifecycleListener that collects errors related to service plugins. """ - errors: Dict[Tuple[str, str], Exception] # keys are: (api, provider) + errors: dict[tuple[str, str], Exception] # keys are: (api, provider) - def __init__(self, errors: Dict[str, Exception] = None) -> None: + def __init__(self, errors: dict[str, Exception] = None) -> None: super().__init__() self.errors = errors or {} - def get_key(self, plugin_name) -> Tuple[str, str]: + def get_key(self, plugin_name) -> tuple[str, str]: # the convention is :, currently we don't really expose the provider # TODO: faulty plugin names would break this return tuple(plugin_name.split(":", maxsplit=1)) @@ -446,7 +447,7 @@ def __init__( self.provider_config = provider_config or config.SERVICE_PROVIDER_CONFIG # locks used to make sure plugin loading is thread safe - will be cleared after single use - self._plugin_load_locks: Dict[str, threading.RLock] = SynchronizedDefaultDict( + self._plugin_load_locks: dict[str, threading.RLock] = SynchronizedDefaultDict( threading.RLock ) @@ -469,7 +470,7 @@ def get_default_provider(self) -> str: # TODO make the abstraction clearer, to provide better information if service is available versus discoverable # especially important when considering pro services - def list_available(self) -> List[str]: + def list_available(self) -> list[str]: """ List all available services, which have an available, configured provider @@ -482,8 +483,8 @@ def list_available(self) -> List[str]: ] def _get_loaded_service_containers( - self, services: Optional[List[str]] = None - ) -> List[ServiceContainer]: + self, services: list[str] | None = None + ) -> list[ServiceContainer]: """ Returns all the available service containers. :param services: the list of services to restrict the search to. If empty or NULL then service containers for @@ -495,7 +496,7 @@ def _get_loaded_service_containers( c for s in services if (c := super(ServicePluginManager, self).get_service_container(s)) ] - def list_loaded_services(self) -> List[str]: + def list_loaded_services(self) -> list[str]: """ Lists all the services which have a provider that has been initialized @@ -506,7 +507,7 @@ def list_loaded_services(self) -> List[str]: for service_container in self._get_loaded_service_containers() ] - def list_active_services(self) -> List[str]: + def list_active_services(self) -> list[str]: """ Lists all services that have an initialised provider and are currently running. @@ -521,7 +522,7 @@ def list_active_services(self) -> List[str]: def exists(self, name: str) -> bool: return name in self.list_available() - def get_state(self, name: str) -> Optional[ServiceState]: + def get_state(self, name: str) -> ServiceState | None: if name in self._services: # ServiceContainer exists, which means the plugin has been loaded return super().get_state(name) @@ -537,7 +538,7 @@ def get_state(self, name: str) -> Optional[ServiceState]: return ServiceState.AVAILABLE if is_api_enabled(name) else ServiceState.DISABLED - def get_service_container(self, name: str) -> Optional[ServiceContainer]: + def get_service_container(self, name: str) -> ServiceContainer | None: if container := self._services.get(name): return container @@ -565,7 +566,7 @@ def get_service_container(self, name: str) -> Optional[ServiceContainer]: return self._services.get(name) @property - def api_provider_specs(self) -> Dict[str, List[str]]: + def api_provider_specs(self) -> dict[str, list[str]]: """ Returns all provider names within the service plugin namespace and parses their name according to the convention, that is ":". The result is a dictionary that maps api => List[str (name of a provider)]. @@ -579,7 +580,7 @@ def api_provider_specs(self) -> Dict[str, List[str]]: return self._api_provider_specs @log_duration() - def _load_service_plugin(self, name: str) -> Optional[ServicePlugin]: + def _load_service_plugin(self, name: str) -> ServicePlugin | None: providers = self.api_provider_specs.get(name) if not providers: # no providers for this api @@ -608,7 +609,7 @@ def _load_service_plugin(self, name: str) -> Optional[ServicePlugin]: return plugin @log_duration() - def _resolve_api_provider_specs(self) -> Dict[str, List[str]]: + def _resolve_api_provider_specs(self) -> dict[str, list[str]]: result = defaultdict(list) for spec in self.plugin_manager.list_plugin_specs(): @@ -619,7 +620,7 @@ def _resolve_api_provider_specs(self) -> Dict[str, List[str]]: return result - def apis_with_provider(self, provider: str) -> List[str]: + def apis_with_provider(self, provider: str) -> list[str]: """ Lists all apis where a given provider exists for. :param provider: Name of the provider @@ -631,7 +632,7 @@ def apis_with_provider(self, provider: str) -> List[str]: apis.append(api) return apis - def _stop_services(self, service_containers: List[ServiceContainer]) -> None: + def _stop_services(self, service_containers: list[ServiceContainer]) -> None: """ Atomically attempts to stop all given 'ServiceState.STARTING' and 'ServiceState.RUNNING' services. :param service_containers: the list of service containers to be stopped. @@ -642,7 +643,7 @@ def _stop_services(self, service_containers: List[ServiceContainer]) -> None: if service_container.state in target_service_states: service_container.stop() - def stop_services(self, services: List[str] = None): + def stop_services(self, services: list[str] = None): """ Stops services for this service manager, if they are currently active. Will not stop services not already started or in and error state. @@ -691,7 +692,7 @@ def check_service_health(api, expect_shutdown=False): LOG.warning('Service "%s" not yet available, retrying...', api) else: LOG.warning('Service "%s" still shutting down, retrying...', api) - raise Exception("Service check failed for api: %s" % api) + raise Exception(f"Service check failed for api: {api}") @hooks.on_infra_start(should_load=lambda: config.EAGER_SERVICE_LOADING) @@ -707,4 +708,8 @@ def eager_load_services(): except ServiceDisabled as e: LOG.debug("%s", e) except Exception: - LOG.exception("could not load service plugin %s", api) + LOG.error( + "could not load service plugin %s", + api, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) diff --git a/localstack-core/localstack/services/providers.py b/localstack-core/localstack/services/providers.py index 2a09121d430f1..ef1d9a55c5435 100644 --- a/localstack-core/localstack/services/providers.py +++ b/localstack-core/localstack/services/providers.py @@ -41,7 +41,7 @@ def apigateway_legacy(): return Service.for_provider(provider, dispatch_table_factory=MotoFallbackDispatcher) -@aws_provider() +@aws_provider(api="cloudformation", name="engine-legacy") def cloudformation(): from localstack.services.cloudformation.provider import CloudformationProvider @@ -49,7 +49,7 @@ def cloudformation(): return Service.for_provider(provider) -@aws_provider(api="cloudformation", name="engine-v2") +@aws_provider(api="cloudformation") def cloudformation_v2(): from localstack.services.cloudformation.v2.provider import CloudformationProviderV2 @@ -311,11 +311,10 @@ def ses(): @aws_provider() def sns(): - from localstack.services.moto import MotoFallbackDispatcher from localstack.services.sns.provider import SnsProvider provider = SnsProvider() - return Service.for_provider(provider, dispatch_table_factory=MotoFallbackDispatcher) + return Service.for_provider(provider) @aws_provider() diff --git a/localstack-core/localstack/services/redshift/provider.py b/localstack-core/localstack/services/redshift/provider.py index 4f432e3a1aef5..4b4e5be1f5cd3 100644 --- a/localstack-core/localstack/services/redshift/provider.py +++ b/localstack-core/localstack/services/redshift/provider.py @@ -1,6 +1,5 @@ import os -from moto.redshift import responses as redshift_responses from moto.redshift.models import redshift_backends from localstack import config @@ -12,26 +11,6 @@ ) from localstack.services.moto import call_moto from localstack.state import AssetDirectory, StateVisitor -from localstack.utils.common import recurse_object -from localstack.utils.patch import patch - - -@patch(redshift_responses.itemize) -def itemize(fn, data, parent_key=None, *args, **kwargs): - # TODO: potentially add additional required tags here! - list_parent_tags = ["ClusterSubnetGroups"] - - def fix_keys(o, **kwargs): - if isinstance(o, dict): - for k, v in o.items(): - if k in list_parent_tags: - if isinstance(v, dict) and "item" in v: - v[k[:-1]] = v.pop("item") - return o - - result = fn(data, *args, **kwargs) - recurse_object(result, fix_keys) - return result class RedshiftProvider(RedshiftApi): diff --git a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py index 629a7ca7a5b2e..ad3e6d3860d49 100644 --- a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py +++ b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,71 +14,71 @@ class RedshiftClusterProperties(TypedDict): - ClusterType: Optional[str] - DBName: Optional[str] - MasterUserPassword: Optional[str] - MasterUsername: Optional[str] - NodeType: Optional[str] - AllowVersionUpgrade: Optional[bool] - AquaConfigurationStatus: Optional[str] - AutomatedSnapshotRetentionPeriod: Optional[int] - AvailabilityZone: Optional[str] - AvailabilityZoneRelocation: Optional[bool] - AvailabilityZoneRelocationStatus: Optional[str] - Classic: Optional[bool] - ClusterIdentifier: Optional[str] - ClusterParameterGroupName: Optional[str] - ClusterSecurityGroups: Optional[list[str]] - ClusterSubnetGroupName: Optional[str] - ClusterVersion: Optional[str] - DeferMaintenance: Optional[bool] - DeferMaintenanceDuration: Optional[int] - DeferMaintenanceEndTime: Optional[str] - DeferMaintenanceIdentifier: Optional[str] - DeferMaintenanceStartTime: Optional[str] - DestinationRegion: Optional[str] - ElasticIp: Optional[str] - Encrypted: Optional[bool] - Endpoint: Optional[Endpoint] - EnhancedVpcRouting: Optional[bool] - HsmClientCertificateIdentifier: Optional[str] - HsmConfigurationIdentifier: Optional[str] - IamRoles: Optional[list[str]] - Id: Optional[str] - KmsKeyId: Optional[str] - LoggingProperties: Optional[LoggingProperties] - MaintenanceTrackName: Optional[str] - ManualSnapshotRetentionPeriod: Optional[int] - NumberOfNodes: Optional[int] - OwnerAccount: Optional[str] - Port: Optional[int] - PreferredMaintenanceWindow: Optional[str] - PubliclyAccessible: Optional[bool] - ResourceAction: Optional[str] - RevisionTarget: Optional[str] - RotateEncryptionKey: Optional[bool] - SnapshotClusterIdentifier: Optional[str] - SnapshotCopyGrantName: Optional[str] - SnapshotCopyManual: Optional[bool] - SnapshotCopyRetentionPeriod: Optional[int] - SnapshotIdentifier: Optional[str] - Tags: Optional[list[Tag]] - VpcSecurityGroupIds: Optional[list[str]] + ClusterType: str | None + DBName: str | None + MasterUserPassword: str | None + MasterUsername: str | None + NodeType: str | None + AllowVersionUpgrade: bool | None + AquaConfigurationStatus: str | None + AutomatedSnapshotRetentionPeriod: int | None + AvailabilityZone: str | None + AvailabilityZoneRelocation: bool | None + AvailabilityZoneRelocationStatus: str | None + Classic: bool | None + ClusterIdentifier: str | None + ClusterParameterGroupName: str | None + ClusterSecurityGroups: list[str] | None + ClusterSubnetGroupName: str | None + ClusterVersion: str | None + DeferMaintenance: bool | None + DeferMaintenanceDuration: int | None + DeferMaintenanceEndTime: str | None + DeferMaintenanceIdentifier: str | None + DeferMaintenanceStartTime: str | None + DestinationRegion: str | None + ElasticIp: str | None + Encrypted: bool | None + Endpoint: Endpoint | None + EnhancedVpcRouting: bool | None + HsmClientCertificateIdentifier: str | None + HsmConfigurationIdentifier: str | None + IamRoles: list[str] | None + Id: str | None + KmsKeyId: str | None + LoggingProperties: LoggingProperties | None + MaintenanceTrackName: str | None + ManualSnapshotRetentionPeriod: int | None + NumberOfNodes: int | None + OwnerAccount: str | None + Port: int | None + PreferredMaintenanceWindow: str | None + PubliclyAccessible: bool | None + ResourceAction: str | None + RevisionTarget: str | None + RotateEncryptionKey: bool | None + SnapshotClusterIdentifier: str | None + SnapshotCopyGrantName: str | None + SnapshotCopyManual: bool | None + SnapshotCopyRetentionPeriod: int | None + SnapshotIdentifier: str | None + Tags: list[Tag] | None + VpcSecurityGroupIds: list[str] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class LoggingProperties(TypedDict): - BucketName: Optional[str] - S3KeyPrefix: Optional[str] + BucketName: str | None + S3KeyPrefix: str | None class Endpoint(TypedDict): - Address: Optional[str] - Port: Optional[str] + Address: str | None + Port: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py index 742fa8c2c1c39..0afc2929dc385 100644 --- a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py +++ b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class RedshiftClusterProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Redshift::Cluster" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.redshift.resource_providers.aws_redshift_cluster import ( diff --git a/localstack-core/localstack/services/resource_groups/provider.py b/localstack-core/localstack/services/resource_groups/provider.py index 647dbadbae1e3..f98ce1fd1e796 100644 --- a/localstack-core/localstack/services/resource_groups/provider.py +++ b/localstack-core/localstack/services/resource_groups/provider.py @@ -1,5 +1,9 @@ from localstack.aws.api.resource_groups import ResourceGroupsApi +from localstack.state import StateVisitor class ResourceGroupsProvider(ResourceGroupsApi): - pass + def accept_state_visitor(self, visitor: StateVisitor): + from moto.resourcegroups.models import resourcegroups_backends + + visitor.visit(resourcegroups_backends) diff --git a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py index 0105de3b2233f..981babca150ff 100644 --- a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py +++ b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,44 +15,44 @@ class ResourceGroupsGroupProperties(TypedDict): - Name: Optional[str] - Arn: Optional[str] - Configuration: Optional[list[ConfigurationItem]] - Description: Optional[str] - ResourceQuery: Optional[ResourceQuery] - Resources: Optional[list[str]] - Tags: Optional[list[Tag]] + Name: str | None + Arn: str | None + Configuration: list[ConfigurationItem] | None + Description: str | None + ResourceQuery: ResourceQuery | None + Resources: list[str] | None + Tags: list[Tag] | None class TagFilter(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class Query(TypedDict): - ResourceTypeFilters: Optional[list[str]] - StackIdentifier: Optional[str] - TagFilters: Optional[list[TagFilter]] + ResourceTypeFilters: list[str] | None + StackIdentifier: str | None + TagFilters: list[TagFilter] | None class ResourceQuery(TypedDict): - Query: Optional[Query] - Type: Optional[str] + Query: Query | None + Type: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ConfigurationParameter(TypedDict): - Name: Optional[str] - Values: Optional[list[str]] + Name: str | None + Values: list[str] | None class ConfigurationItem(TypedDict): - Parameters: Optional[list[ConfigurationParameter]] - Type: Optional[str] + Parameters: list[ConfigurationParameter] | None + Type: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py index 99e589abd9722..5f2a825667068 100644 --- a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py +++ b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ResourceGroupsGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ResourceGroups::Group" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.resource_groups.resource_providers.aws_resourcegroups_group import ( diff --git a/localstack-core/localstack/services/resourcegroupstaggingapi/provider.py b/localstack-core/localstack/services/resourcegroupstaggingapi/provider.py index a9535da68eaae..ea7629b5304e1 100644 --- a/localstack-core/localstack/services/resourcegroupstaggingapi/provider.py +++ b/localstack-core/localstack/services/resourcegroupstaggingapi/provider.py @@ -1,7 +1,12 @@ from abc import ABC from localstack.aws.api.resourcegroupstaggingapi import ResourcegroupstaggingapiApi +from localstack.state import StateVisitor class ResourcegroupstaggingapiProvider(ResourcegroupstaggingapiApi, ABC): - pass + def accept_state_visitor(self, visitor: StateVisitor): + # currently, Moto resourcegroupstaggingapi stores all tags into the other services backend, so their backend + # does not hold any state and is not worth saving. It only holds direct references to other services + # It only holds pagination tokens that are not worth keeping + pass diff --git a/localstack-core/localstack/services/route53/models.py b/localstack-core/localstack/services/route53/models.py index 9bfd65e612d33..32173d6aee236 100644 --- a/localstack-core/localstack/services/route53/models.py +++ b/localstack-core/localstack/services/route53/models.py @@ -1,12 +1,12 @@ -from typing import Dict - from localstack.aws.api.route53 import DelegationSet from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute +from localstack.utils.tagging import Tags class Route53Store(BaseStore): # maps delegation set ID to reusable delegation set details - reusable_delegation_sets: Dict[str, DelegationSet] = LocalAttribute(default=dict) + reusable_delegation_sets: dict[str, DelegationSet] = LocalAttribute(default=dict) + tags: Tags = LocalAttribute(default=Tags) -route53_stores = AccountRegionBundle("route53", Route53Store) +route53_stores = AccountRegionBundle("route53", Route53Store, validate=False) diff --git a/localstack-core/localstack/services/route53/provider.py b/localstack-core/localstack/services/route53/provider.py index cdd3650adf274..ad07970af4731 100644 --- a/localstack-core/localstack/services/route53/provider.py +++ b/localstack-core/localstack/services/route53/provider.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Optional import moto.route53.models as route53_models from botocore.exceptions import ClientError @@ -12,6 +11,7 @@ ChangeStatus, CreateHostedZoneResponse, DeleteHealthCheckResponse, + DeleteHostedZoneResponse, DNSName, GetChangeResponse, GetHealthCheckResponse, @@ -27,9 +27,22 @@ from localstack.aws.connect import connect_to from localstack.services.moto import call_moto from localstack.services.plugins import ServiceLifecycleHook +from localstack.services.route53.models import route53_stores +from localstack.state import StateVisitor class Route53Provider(Route53Api, ServiceLifecycleHook): + def accept_state_visitor(self, visitor: StateVisitor): + + visitor.visit(route53_backends) + visitor.visit(route53_stores) + + # No tag deletion logic to handle in Community. Overwritten in Pro implementation. + def remove_resource_tags( + self, context: RequestContext, resource_type: str, resource_id: str + ) -> None: + return + def create_hosted_zone( self, context: RequestContext, @@ -79,7 +92,7 @@ def get_change(self, context: RequestContext, id: ResourceId, **kwargs) -> GetCh def get_health_check( self, context: RequestContext, health_check_id: HealthCheckId, **kwargs ) -> GetHealthCheckResponse: - health_check: Optional[route53_models.HealthCheck] = route53_backends[context.account_id][ + health_check: route53_models.HealthCheck | None = route53_backends[context.account_id][ context.partition ].health_checks.get(health_check_id, None) if not health_check: @@ -108,6 +121,13 @@ def get_health_check( ) ) + def delete_hosted_zone( + self, context: RequestContext, id: ResourceId, **kwargs + ) -> DeleteHostedZoneResponse: + response = call_moto(context) + self.remove_resource_tags(context=context, resource_type="hostedzone", resource_id=id) + return response + def delete_health_check( self, context: RequestContext, health_check_id: HealthCheckId, **kwargs ) -> DeleteHealthCheckResponse: @@ -120,4 +140,8 @@ def delete_health_check( ) route53_backends[context.account_id][context.partition].delete_health_check(health_check_id) - return {} + self.remove_resource_tags( + context=context, resource_type="healthcheck", resource_id=health_check_id + ) + + return DeleteHealthCheckResponse() diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py index ddd156b7e638f..440f7323d2142 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class Route53HealthCheckProperties(TypedDict): - HealthCheckConfig: Optional[dict] - HealthCheckId: Optional[str] - HealthCheckTags: Optional[list[HealthCheckTag]] + HealthCheckConfig: dict | None + HealthCheckId: str | None + HealthCheckTags: list[HealthCheckTag] | None class HealthCheckTag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py index 7a8e244561cf8..9db349c9853a1 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class Route53HealthCheckProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Route53::HealthCheck" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.route53.resource_providers.aws_route53_healthcheck import ( diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py index c3d0e3866e14c..1a985f9f4cde3 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py @@ -2,10 +2,11 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Optional, TypedDict +from typing import TYPE_CHECKING, TypedDict if TYPE_CHECKING: from mypy_boto3_route53 import Route53Client + from mypy_boto3_route53.type_defs import ResourceRecordSetTypeDef import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,40 +18,40 @@ class Route53RecordSetProperties(TypedDict): - Name: Optional[str] - Type: Optional[str] - AliasTarget: Optional[AliasTarget] - CidrRoutingConfig: Optional[CidrRoutingConfig] - Comment: Optional[str] - Failover: Optional[str] - GeoLocation: Optional[GeoLocation] - HealthCheckId: Optional[str] - HostedZoneId: Optional[str] - HostedZoneName: Optional[str] - Id: Optional[str] - MultiValueAnswer: Optional[bool] - Region: Optional[str] - ResourceRecords: Optional[list[str]] - SetIdentifier: Optional[str] - TTL: Optional[str] - Weight: Optional[int] + Name: str | None + Type: str | None + AliasTarget: AliasTarget | None + CidrRoutingConfig: CidrRoutingConfig | None + Comment: str | None + Failover: str | None + GeoLocation: GeoLocation | None + HealthCheckId: str | None + HostedZoneId: str | None + HostedZoneName: str | None + Id: str | None + MultiValueAnswer: bool | None + Region: str | None + ResourceRecords: list[str] | None + SetIdentifier: str | None + TTL: str | None + Weight: int | None class AliasTarget(TypedDict): - DNSName: Optional[str] - HostedZoneId: Optional[str] - EvaluateTargetHealth: Optional[bool] + DNSName: str | None + HostedZoneId: str | None + EvaluateTargetHealth: bool | None class CidrRoutingConfig(TypedDict): - CollectionId: Optional[str] - LocationName: Optional[str] + CollectionId: str | None + LocationName: str | None class GeoLocation(TypedDict): - ContinentCode: Optional[str] - CountryCode: Optional[str] - SubdivisionCode: Optional[str] + ContinentCode: str | None + CountryCode: str | None + SubdivisionCode: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -91,33 +92,7 @@ def create( hosted_zone_id = self.get_hosted_zone_id_from_name(hosted_zone_name, route53) model["HostedZoneId"] = hosted_zone_id - attr_names = [ - "Name", - "Type", - "SetIdentifier", - "Weight", - "Region", - "GeoLocation", - "Failover", - "MultiValueAnswer", - "TTL", - "ResourceRecords", - "AliasTarget", - "HealthCheckId", - ] - attrs = util.select_attributes(model, attr_names) - - if "AliasTarget" in attrs: - # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset-aliastarget.html - if "EvaluateTargetHealth" not in attrs["AliasTarget"]: - attrs["AliasTarget"]["EvaluateTargetHealth"] = False - else: - # TODO: CNAME & SOA only allow 1 record type. should we check that here? - attrs["ResourceRecords"] = [{"Value": record} for record in attrs["ResourceRecords"]] - - if "TTL" in attrs: - if isinstance(attrs["TTL"], str): - attrs["TTL"] = int(attrs["TTL"]) + attrs = self._get_resource_record_set_from_model(model) route53.change_resource_record_sets( HostedZoneId=model["HostedZoneId"], @@ -138,7 +113,7 @@ def create( resource_model=model, ) - def get_hosted_zone_id_from_name(self, hosted_zone_name: str, client: "Route53Client"): + def get_hosted_zone_id_from_name(self, hosted_zone_name: str, client: Route53Client): if not hosted_zone_name: raise Exception("Either HostedZoneId or HostedZoneName must be present.") @@ -171,27 +146,15 @@ def delete( """ model = request.previous_state route53 = request.aws_client_factory.route53 - rrset_kwargs = { - "Name": model["Name"], - "Type": model["Type"], - } - - if "AliasTarget" in model: - rrset_kwargs["AliasTarget"] = model["AliasTarget"] - if "ResourceRecords" in model: - rrset_kwargs["ResourceRecords"] = [ - {"Value": record} for record in model["ResourceRecords"] - ] - if "TTL" in model: - rrset_kwargs["TTL"] = int(model["TTL"]) + resource_record_set = self._get_resource_record_set_from_model(model) route53.change_resource_record_sets( HostedZoneId=model["HostedZoneId"], ChangeBatch={ "Changes": [ { "Action": "DELETE", - "ResourceRecordSet": rrset_kwargs, + "ResourceRecordSet": resource_record_set, }, ] }, @@ -210,4 +173,72 @@ def update( """ - raise NotImplementedError + model = request.desired_state + prev_model = request.previous_state + + assert request.previous_state is not None + + route53 = request.aws_client_factory.route53 + changes = [] + + if model.get("SetIdentifier") != prev_model.get("SetIdentifier"): + prev_rrset = self._get_resource_record_set_from_model(prev_model) + changes.append( + { + "Action": "DELETE", + "ResourceRecordSet": prev_rrset, + } + ) + + updated_rrset = self._get_resource_record_set_from_model(model) + changes.append( + { + "Action": "UPSERT", + "ResourceRecordSet": updated_rrset, + } + ) + + route53.change_resource_record_sets( + HostedZoneId=model["HostedZoneId"], + ChangeBatch={"Changes": changes}, + ) + model["Id"] = model["Name"] + + return ProgressEvent( + status=OperationStatus.SUCCESS, + resource_model=model, + ) + + @staticmethod + def _get_resource_record_set_from_model( + model: Route53RecordSetProperties, + ) -> ResourceRecordSetTypeDef: + attr_names = [ + "Name", + "Type", + "SetIdentifier", + "Weight", + "Region", + "GeoLocation", + "Failover", + "MultiValueAnswer", + "TTL", + "ResourceRecords", + "AliasTarget", + "HealthCheckId", + ] + attrs = util.select_attributes(model, attr_names) + + if "AliasTarget" in attrs: + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset-aliastarget.html + if "EvaluateTargetHealth" not in attrs["AliasTarget"]: + attrs["AliasTarget"]["EvaluateTargetHealth"] = False + else: + # TODO: CNAME & SOA only allow 1 record type. should we check that here? + attrs["ResourceRecords"] = [{"Value": record} for record in attrs["ResourceRecords"]] + + if "TTL" in attrs: + if isinstance(attrs["TTL"], str): + attrs["TTL"] = int(attrs["TTL"]) + + return attrs diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py index bdecf6996b78d..0f890fef915a7 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class Route53RecordSetProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Route53::RecordSet" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.route53.resource_providers.aws_route53_recordset import ( diff --git a/localstack-core/localstack/services/route53resolver/models.py b/localstack-core/localstack/services/route53resolver/models.py index 57c5436bab568..b29a7b8bf4236 100644 --- a/localstack-core/localstack/services/route53resolver/models.py +++ b/localstack-core/localstack/services/route53resolver/models.py @@ -1,5 +1,3 @@ -from typing import Dict - import localstack.services.route53resolver.utils from localstack.aws.api.route53resolver import ( FirewallConfig, @@ -18,16 +16,16 @@ class Route53ResolverStore(BaseStore): - firewall_configs: Dict[str, FirewallConfig] = LocalAttribute(default=dict) - firewall_domain_lists: Dict[str, FirewallDomainList] = LocalAttribute(default=dict) - firewall_domains: Dict[str, FirewallDomains] = LocalAttribute(default=dict) - firewall_rules: Dict[str, FirewallRule] = LocalAttribute(default=dict) - firewall_rule_groups: Dict[str, FirewallRuleGroup] = LocalAttribute(default=dict) - firewall_rule_group_associations: Dict[str, FirewallRuleGroupAssociation] = LocalAttribute( + firewall_configs: dict[str, FirewallConfig] = LocalAttribute(default=dict) + firewall_domain_lists: dict[str, FirewallDomainList] = LocalAttribute(default=dict) + firewall_domains: dict[str, FirewallDomains] = LocalAttribute(default=dict) + firewall_rules: dict[str, FirewallRule] = LocalAttribute(default=dict) + firewall_rule_groups: dict[str, FirewallRuleGroup] = LocalAttribute(default=dict) + firewall_rule_group_associations: dict[str, FirewallRuleGroupAssociation] = LocalAttribute( default=dict ) - resolver_query_log_configs: Dict[str, ResolverQueryLogConfig] = LocalAttribute(default=dict) - resolver_query_log_config_associations: Dict[str, ResolverQueryLogConfigAssociation] = ( + resolver_query_log_configs: dict[str, ResolverQueryLogConfig] = LocalAttribute(default=dict) + resolver_query_log_config_associations: dict[str, ResolverQueryLogConfigAssociation] = ( LocalAttribute(default=dict) ) diff --git a/localstack-core/localstack/services/route53resolver/provider.py b/localstack-core/localstack/services/route53resolver/provider.py index e002748d9aa17..d7cda705e2afb 100644 --- a/localstack-core/localstack/services/route53resolver/provider.py +++ b/localstack-core/localstack/services/route53resolver/provider.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from moto.route53resolver.models import Route53ResolverBackend as MotoRoute53ResolverBackend from moto.route53resolver.models import route53resolver_backends @@ -76,11 +76,13 @@ ResolverQueryLogConfigStatus, ResourceId, ResourceNotFoundException, + RniEnhancedMetricsEnabled, Route53ResolverApi, SecurityGroupIds, SortByKey, SortOrder, TagList, + TargetNameServerMetricsEnabled, UpdateFirewallConfigResponse, UpdateFirewallDomainsResponse, UpdateFirewallRuleGroupAssociationResponse, @@ -100,6 +102,7 @@ validate_mutation_protection, validate_priority, ) +from localstack.state import StateVisitor from localstack.utils.aws import arns from localstack.utils.aws.arns import extract_account_id_from_arn, extract_region_from_arn from localstack.utils.collections import select_from_typed_dict @@ -107,6 +110,10 @@ class Route53ResolverProvider(Route53ResolverApi): + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(route53resolver_backends) + visitor.visit(route53resolver_stores) + @staticmethod def get_store(account_id: str, region: str) -> Route53ResolverStore: return route53resolver_stores[account_id][region] @@ -135,8 +142,8 @@ def create_firewall_rule_group( ShareStatus="NOT_SHARED", StatusMessage="Created Firewall Rule Group", CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), ) store.firewall_rule_groups[firewall_rule_group_id] = firewall_rule_group store.firewall_rules[firewall_rule_group_id] = {} @@ -202,8 +209,8 @@ def create_firewall_domain_list( StatusMessage="Created Firewall Domain List", ManagedOwnerName=context.account_id, CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), ) store.firewall_domain_lists[id] = firewall_domain_list route53resolver_backends[context.account_id][context.region].tagger.tag_resource( @@ -283,7 +290,7 @@ def update_firewall_domains( store.firewall_domains[firewall_domain_list_id] = domains firewall_domain_list["StatusMessage"] = "Finished domain list update" - firewall_domain_list["ModificationTime"] = datetime.now(timezone.utc).isoformat() + firewall_domain_list["ModificationTime"] = datetime.now(UTC).isoformat() return UpdateFirewallDomainsResponse( Id=firewall_domain_list.get("Id"), Name=firewall_domain_list.get("Name"), @@ -340,8 +347,8 @@ def create_firewall_rule( BlockOverrideDnsType=block_override_dns_type, BlockOverrideTtl=block_override_ttl, CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), FirewallDomainRedirectionAction=firewall_domain_redirection_action, Qtype=qtype, ) @@ -492,8 +499,8 @@ def associate_firewall_rule_group( Status="COMPLETE", StatusMessage="Creating Firewall Rule Group Association", CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), ) store.firewall_rule_group_associations[id] = firewall_rule_group_association route53resolver_backends[context.account_id][context.region].tagger.tag_resource( @@ -579,7 +586,7 @@ def create_resolver_query_log_config( ShareStatus="NOT_SHARED", DestinationArn=destination_arn, CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), ) store.resolver_query_log_configs[id] = resolver_query_log_config route53resolver_backends[context.account_id][context.region].tagger.tag_resource( @@ -596,12 +603,14 @@ def create_resolver_endpoint( security_group_ids: SecurityGroupIds, direction: ResolverEndpointDirection, ip_addresses: IpAddressesRequest, - name: Name = None, - outpost_arn: OutpostArn = None, - preferred_instance_type: OutpostInstanceType = None, - tags: TagList = None, - resolver_endpoint_type: ResolverEndpointType = None, - protocols: ProtocolList = None, + name: Name | None = None, + outpost_arn: OutpostArn | None = None, + preferred_instance_type: OutpostInstanceType | None = None, + tags: TagList | None = None, + resolver_endpoint_type: ResolverEndpointType | None = None, + protocols: ProtocolList | None = None, + rni_enhanced_metrics_enabled: RniEnhancedMetricsEnabled | None = None, + target_name_server_metrics_enabled: TargetNameServerMetricsEnabled | None = None, **kwargs, ) -> CreateResolverEndpointResponse: create_resolver_endpoint_resp = call_moto(context) @@ -667,7 +676,7 @@ def associate_resolver_query_log_config( Status="ACTIVE", Error="NONE", ErrorMessage="", - CreationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), ) ) diff --git a/localstack-core/localstack/services/s3/codec.py b/localstack-core/localstack/services/s3/codec.py index 9d1b3167ccda8..98917156627aa 100644 --- a/localstack-core/localstack/services/s3/codec.py +++ b/localstack-core/localstack/services/s3/codec.py @@ -1,5 +1,5 @@ import io -from typing import IO, Any, Optional +from typing import IO, Any class AwsChunkedDecoder(io.RawIOBase): @@ -15,7 +15,7 @@ def readable(self): return True def __init__( - self, stream: IO[bytes], decoded_content_length: int, s3_object: Optional[Any] = None + self, stream: IO[bytes], decoded_content_length: int, s3_object: Any | None = None ): self._stream = stream diff --git a/localstack-core/localstack/services/s3/constants.py b/localstack-core/localstack/services/s3/constants.py index 510494d048d47..f5e73f0647adc 100644 --- a/localstack-core/localstack/services/s3/constants.py +++ b/localstack-core/localstack/services/s3/constants.py @@ -1,4 +1,5 @@ from localstack.aws.api.s3 import ( + BucketLocationConstraint, ChecksumAlgorithm, Grantee, Permission, @@ -10,8 +11,6 @@ ) from localstack.aws.api.s3 import Type as GranteeType -S3_VIRTUAL_HOST_FORWARDED_HEADER = "x-s3-vhost-forwarded-for" - S3_UPLOAD_PART_MIN_SIZE = 5242880 """ This is minimum size allowed by S3 when uploading more than one part for a Multipart Upload, except for the last part @@ -21,6 +20,11 @@ DEFAULT_PRE_SIGNED_ACCESS_KEY_ID = "test" DEFAULT_PRE_SIGNED_SECRET_ACCESS_KEY = "test" +S3_HOST_ID = "9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg=" +""" +S3 is returning a Host Id as part of its exceptions +""" + AUTHENTICATED_USERS_ACL_GROUP = "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" ALL_USERS_ACL_GROUP = "http://acs.amazonaws.com/groups/global/AllUsers" LOG_DELIVERY_ACL_GROUP = "http://acs.amazonaws.com/groups/s3/LogDelivery" @@ -63,6 +67,10 @@ ChecksumAlgorithm.CRC64NVME, ] +BUCKET_LOCATION_CONSTRAINTS = [constraint.value for constraint in BucketLocationConstraint] + +EU_WEST_1_LOCATION_CONSTRAINTS = [BucketLocationConstraint.EU, BucketLocationConstraint.eu_west_1] + # response header overrides the client may request ALLOWED_HEADER_OVERRIDES = { "ResponseContentType": "ContentType", diff --git a/localstack-core/localstack/services/s3/cors.py b/localstack-core/localstack/services/s3/cors.py index 9051f3679c6ca..3d7a11f348a9b 100644 --- a/localstack-core/localstack/services/s3/cors.py +++ b/localstack-core/localstack/services/s3/cors.py @@ -1,6 +1,6 @@ import logging import re -from typing import Optional, Protocol, Tuple +from typing import Protocol from werkzeug.datastructures import Headers @@ -21,13 +21,13 @@ from localstack.aws.spec import get_service_catalog from localstack.config import S3_VIRTUAL_HOSTNAME from localstack.http import Request, Response +from localstack.services.s3.constants import S3_HOST_ID from localstack.services.s3.utils import S3_VIRTUAL_HOSTNAME_REGEX # TODO: add more logging statements LOG = logging.getLogger(__name__) _s3_virtual_host_regex = re.compile(S3_VIRTUAL_HOSTNAME_REGEX) -FAKE_HOST_ID = "9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg=" # TODO: refactor those to expose the needed methods maybe in another way that both can import add_default_headers = CorsResponseEnricher.add_cors_headers @@ -58,7 +58,7 @@ def __init__(self, bucket_cors_index: BucketCorsIndex): def __call__(self, chain: HandlerChain, context: RequestContext, response: Response): self.handle_cors(chain, context, response) - def pre_parse_s3_request(self, request: Request) -> Tuple[bool, Optional[str]]: + def pre_parse_s3_request(self, request: Request) -> tuple[bool, str | None]: """ Parse the request to try to determine if it's directed towards S3. It tries to match on host, then check if the targeted bucket exists. If we could not determine it was a s3 request from the host, but the first @@ -135,7 +135,7 @@ def stop_options_chain(): if is_options_request: context.operation = self._get_op_from_request(request) raise BadRequest( - "Insufficient information. Origin request header needed.", HostId=FAKE_HOST_ID + "Insufficient information. Origin request header needed.", HostId=S3_HOST_ID ) else: # If the header is missing, Amazon S3 doesn't treat the request as a cross-origin request, @@ -167,7 +167,7 @@ def stop_options_chain(): context.operation = self._get_op_from_request(request) raise AccessForbidden( message, - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, Method=request.headers.get("Access-Control-Request-Method", "OPTIONS"), ResourceType="BUCKET", ) @@ -182,7 +182,7 @@ def stop_options_chain(): context.operation = self._get_op_from_request(request) raise AccessForbidden( "CORSResponse: This CORS request is not allowed. This is usually because the evalution of Origin, request method / Access-Control-Request-Method or Access-Control-Request-Headers are not whitelisted by the resource's CORS spec.", - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, Method=request.headers.get("Access-Control-Request-Method"), ResourceType="OBJECT", ) @@ -224,7 +224,7 @@ def stop_options_chain(): def invalidate_cache(self): self.bucket_cors_index.invalidate() - def match_rules(self, request: Request, rules: CORSRules) -> Optional[CORSRule]: + def match_rules(self, request: Request, rules: CORSRules) -> CORSRule | None: """ Try to match the request to the bucket rules. How to match rules: - The request's Origin header must match AllowedOrigin elements. @@ -243,7 +243,7 @@ def match_rules(self, request: Request, rules: CORSRules) -> Optional[CORSRule]: return matched_rule @staticmethod - def _match_rule(rule: CORSRule, method: str, headers: Headers) -> Optional[CORSRule]: + def _match_rule(rule: CORSRule, method: str, headers: Headers) -> CORSRule | None: """ Check if the request method and headers matches the given CORS rule. :param rule: CORSRule: a CORS Rule from the bucket diff --git a/localstack-core/localstack/services/s3/exceptions.py b/localstack-core/localstack/services/s3/exceptions.py index 382631de91a50..7497469b719ff 100644 --- a/localstack-core/localstack/services/s3/exceptions.py +++ b/localstack-core/localstack/services/s3/exceptions.py @@ -51,3 +51,12 @@ def __init__(self, message=None) -> None: class TooManyConfigurations(CommonServiceException): def __init__(self, message=None) -> None: super().__init__("TooManyConfigurations", status_code=400, message=message) + + +class IllegalLocationConstraintException(CommonServiceException): + def __init__(self, location_constraint: str): + super().__init__( + "IllegalLocationConstraintException", + status_code=400, + message=f"The {location_constraint} location constraint is incompatible for the region specific endpoint this request was sent to.", + ) diff --git a/localstack-core/localstack/services/s3/headers.py b/localstack-core/localstack/services/s3/headers.py new file mode 100644 index 0000000000000..71e95c462f98e --- /dev/null +++ b/localstack-core/localstack/services/s3/headers.py @@ -0,0 +1,103 @@ +# Code inspired by the standard library ``email.quoprimime.header_encode``, but the safe characters set is different +# in AWS, so we need to override it +import unicodedata +from base64 import b64encode +from email.errors import HeaderParseError +from email.header import decode_header as _decode_header + +from localstack.utils.strings import to_str + +# Build a mapping of octets to the expansion of that octet. Since we're only +# going to have 256 of these things, this isn't terribly inefficient +# space-wise. Initialize the map with the full expansion, and then override +# the safe bytes with the more compact form. +_QUOPRI_HEADER_MAP = [f"={c:02X}" for c in range(256)] + +_SAFE_HEADERS_CHARS = b"!\"#$%&'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~\t" +_NO_ENCODING_CHARS = _SAFE_HEADERS_CHARS + b" _=?" + +# For AWS, it seems it uses the std lib "safe body" bytes list which need no encoding. +for c in _SAFE_HEADERS_CHARS: + _QUOPRI_HEADER_MAP[c] = chr(c) + +# Headers have one other special encoding; spaces become underscores. +_QUOPRI_HEADER_MAP[ord(" ")] = "_" + + +def encode_header_rfc2047(header: str | None) -> str | None: + if header is None: + return None + + header_bytes = header.encode("utf-8") + # When all chars are "safe chars" plus " " and "_", AWS returns it as is. + # But if " " and "_" are presented in an encoded header, it will encode them as well + if all(c in _NO_ENCODING_CHARS for c in header_bytes): + return header + + if "�" in header or any(unicodedata.category(c).startswith("C") for c in header): + # if there are any character which cannot be printed (not a symbol, but will be escaped with \xNN), we need to + # base64 encode the header + # See https://www.unicode.org/reports/tr44/tr44-34.html#General_Category_Values + encoder = encoder_header_rfc2047_base64 + else: + encoder = encode_header_rfc2047_quote_printable + + return encoder(header_bytes) + + +def encode_header_rfc2047_quote_printable(header_bytes: bytes) -> str: + """ + Encode the header value in an RFC 2047 Quote-printable format. By default, Python would encode it in Base64, but + AWS encodes it in the QP (a format similar to Quoted-printable, but used for emails). + This is the same as the standard library ``email.quoprimime.header_encode``, used by ``email.header.Header`` and + ``email.charset.Charset``, but the list of safe characters that do not need to be encoded is different and not + overridable. + + See: + - https://www.rfc-editor.org/rfc/rfc2047.html + - https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html#UserMetadata + :param header_bytes: header byte value, UTF-8-encoded + :return: encoded header value in RFC 2047 Quoted-printable format + """ + # we use our own safe character map here + encoded = header_bytes.decode("latin1").translate(_QUOPRI_HEADER_MAP) + return f"=?UTF-8?Q?{encoded}?=" + + +def encoder_header_rfc2047_base64(header_bytes: bytes) -> str: + encoded = b64encode(header_bytes).decode("ascii") + return f"=?UTF-8?B?{encoded}?=" + + +def decode_header_rfc2047(header: str) -> str: + try: + header_parts = _decode_header(header) + return "".join(to_str(part, charset) for part, charset in header_parts) + except HeaderParseError: + if header.lower().startswith("=?utf-8?b?"): + # if the header is badly B64 encoded, AWS will return random data, which we cannot make sense of. + # we can use the Unicode replacement character instead to indicate an error + # we return as many replacement chars as there are b64 chars + replacement_header = "\ufffd" * (len(header) - 13) + return replacement_header + return header + + +def replace_non_iso_8859_1_characters(header: str, repl: str = " ") -> str: + """ + Sanitize the header value to not contain any character which cannot be encoded to latin-1, to be compatible with + webservers. + :param header: header value, UTF-8 encoded + :param repl: replacement value for latin-1 incompatible char, empty space by default in AWS + :return: sanitized header value which can be encoded as latin-1 + """ + sanitized_header = header + while True: + try: + sanitized_header.encode("iso-8859-1", errors="strict") + break + except UnicodeEncodeError as exc: + bad_char = sanitized_header[exc.start : exc.end] + sanitized_header = sanitized_header.replace(bad_char, repl) + + return sanitized_header diff --git a/localstack-core/localstack/services/s3/models.py b/localstack-core/localstack/services/s3/models.py index 6246d394dad33..a612468431e61 100644 --- a/localstack-core/localstack/services/s3/models.py +++ b/localstack-core/localstack/services/s3/models.py @@ -4,7 +4,7 @@ from collections import defaultdict from datetime import datetime from secrets import token_urlsafe -from typing import Literal, NamedTuple, Optional, Union +from typing import Literal, NamedTuple, Union from zoneinfo import ZoneInfo from localstack.aws.api import CommonServiceException @@ -16,6 +16,7 @@ BadDigest, BucketAccelerateStatus, BucketKeyEnabled, + BucketLocationConstraint, BucketName, BucketRegion, BucketVersioningStatus, @@ -76,7 +77,12 @@ S3_UPLOAD_PART_MIN_SIZE, ) from localstack.services.s3.exceptions import InvalidRequest -from localstack.services.s3.utils import CombinedCrcHash, get_s3_checksum, rfc_1123_datetime +from localstack.services.s3.headers import replace_non_iso_8859_1_characters +from localstack.services.s3.utils import ( + CombinedCrcHash, + get_s3_checksum, + rfc_1123_datetime, +) from localstack.services.stores import ( AccountRegionBundle, BaseStore, @@ -85,7 +91,7 @@ LocalAttribute, ) from localstack.utils.aws import arns -from localstack.utils.tagging import TaggingService +from localstack.utils.tagging import Tags LOG = logging.getLogger(__name__) @@ -96,35 +102,36 @@ class InternalObjectPart(Part): _position: int -# note: not really a need to use a dataclass here, as it has a lot of fields, but only a few are set at creation class S3Bucket: name: BucketName bucket_account_id: AccountId bucket_region: BucketRegion + bucket_arn: str + location_constraint: BucketLocationConstraint | Literal[""] creation_date: datetime multiparts: dict[MultipartUploadId, "S3Multipart"] objects: Union["KeyStore", "VersionedKeyStore"] versioning_status: BucketVersioningStatus | None - lifecycle_rules: Optional[LifecycleRules] - transition_default_minimum_object_size: Optional[TransitionDefaultMinimumObjectSize] - policy: Optional[Policy] - website_configuration: Optional[WebsiteConfiguration] + lifecycle_rules: LifecycleRules | None + transition_default_minimum_object_size: TransitionDefaultMinimumObjectSize | None + policy: Policy | None + website_configuration: WebsiteConfiguration | None acl: AccessControlPolicy - cors_rules: Optional[CORSConfiguration] + cors_rules: CORSConfiguration | None logging: LoggingEnabled notification_configuration: NotificationConfiguration payer: Payer - encryption_rule: Optional[ServerSideEncryptionRule] - public_access_block: Optional[PublicAccessBlockConfiguration] - accelerate_status: Optional[BucketAccelerateStatus] + encryption_rule: ServerSideEncryptionRule | None + public_access_block: PublicAccessBlockConfiguration | None + accelerate_status: BucketAccelerateStatus | None object_lock_enabled: bool - object_ownership: ObjectOwnership + object_ownership: ObjectOwnership | None # can be set to None manually in S3 intelligent_tiering_configurations: dict[IntelligentTieringId, IntelligentTieringConfiguration] analytics_configurations: dict[AnalyticsId, AnalyticsConfiguration] inventory_configurations: dict[InventoryId, InventoryConfiguration] metric_configurations: dict[MetricsId, MetricsConfiguration] - object_lock_default_retention: Optional[DefaultRetention] - replication: ReplicationConfiguration + object_lock_default_retention: DefaultRetention | None + replication: ReplicationConfiguration | None owner: Owner # set all buckets parameters here @@ -137,10 +144,13 @@ def __init__( acl: AccessControlPolicy = None, object_ownership: ObjectOwnership = None, object_lock_enabled_for_bucket: bool = None, + location_constraint: BucketLocationConstraint | Literal[""] = "", ): self.name = name self.bucket_account_id = account_id self.bucket_region = bucket_region + self.bucket_arn = arns.s3_bucket_arn(self.name, region=bucket_region) + self.location_constraint = location_constraint # If ObjectLock is enabled, it forces the bucket to be versioned as well self.versioning_status = None if not object_lock_enabled_for_bucket else "Enabled" self.objects = KeyStore() if not object_lock_enabled_for_bucket else VersionedKeyStore() @@ -168,7 +178,6 @@ def __init__( self.acl = acl # see https://docs.aws.amazon.com/AmazonS3/latest/API/API_Owner.html self.owner = owner - self.bucket_arn = arns.s3_bucket_arn(self.name, region=bucket_region) def get_object( self, @@ -256,63 +265,62 @@ def get_object( class S3Object: key: ObjectKey - version_id: Optional[ObjectVersionId] - bucket: BucketName - owner: Optional[Owner] - size: Optional[Size] - etag: Optional[ETag] + version_id: ObjectVersionId | None + owner: Owner | None + size: Size | None + etag: ETag | None user_metadata: Metadata system_metadata: Metadata last_modified: datetime - expires: Optional[datetime] - expiration: Optional[Expiration] # right now, this is stored in the provider cache + expires: datetime | None + expiration: Expiration | None # right now, this is stored in the provider cache storage_class: StorageClass | ObjectStorageClass - encryption: Optional[ServerSideEncryption] # inherit bucket - kms_key_id: Optional[SSEKMSKeyId] # inherit bucket - bucket_key_enabled: Optional[bool] # inherit bucket - sse_key_hash: Optional[SSECustomerKeyMD5] - checksum_algorithm: ChecksumAlgorithm - checksum_value: str - checksum_type: ChecksumType - lock_mode: Optional[ObjectLockMode | ObjectLockRetentionMode] - lock_legal_status: Optional[ObjectLockLegalHoldStatus] - lock_until: Optional[datetime] - website_redirect_location: Optional[WebsiteRedirectLocation] - acl: Optional[AccessControlPolicy] + encryption: ServerSideEncryption | None # inherit bucket + kms_key_id: SSEKMSKeyId | None # inherit bucket + bucket_key_enabled: bool | None # inherit bucket + sse_key_hash: SSECustomerKeyMD5 | None + # ``checksum_algorithm`` can only be None when SSE-C is set and while creating a Multipart. + # TODO: remove `| None` when SSE-C is removed from AWS S3 + checksum_algorithm: ChecksumAlgorithm | None + checksum_value: str | None + checksum_type: ChecksumType | None + lock_mode: ObjectLockMode | ObjectLockRetentionMode | None + lock_legal_status: ObjectLockLegalHoldStatus | None + lock_until: datetime | None + website_redirect_location: WebsiteRedirectLocation | None + acl: AccessControlPolicy | None is_current: bool - parts: Optional[dict[int, InternalObjectPart]] - restore: Optional[Restore] + parts: dict[str, InternalObjectPart] + restore: Restore | None internal_last_modified: int def __init__( self, key: ObjectKey, - etag: Optional[ETag] = None, - size: Optional[int] = None, - version_id: Optional[ObjectVersionId] = None, - user_metadata: Optional[Metadata] = None, - system_metadata: Optional[Metadata] = None, + etag: ETag | None = None, + size: int | None = None, + version_id: ObjectVersionId | None = None, + user_metadata: Metadata | None = None, + system_metadata: Metadata | None = None, storage_class: StorageClass = StorageClass.STANDARD, - expires: Optional[datetime] = None, - expiration: Optional[Expiration] = None, - checksum_algorithm: Optional[ChecksumAlgorithm] = None, - checksum_value: Optional[str] = None, - checksum_type: Optional[ChecksumType] = ChecksumType.FULL_OBJECT, - encryption: Optional[ServerSideEncryption] = None, - kms_key_id: Optional[SSEKMSKeyId] = None, - sse_key_hash: Optional[SSECustomerKeyMD5] = None, + expires: datetime | None = None, + expiration: Expiration | None = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + checksum_value: str | None = None, + checksum_type: ChecksumType | None = ChecksumType.FULL_OBJECT, + encryption: ServerSideEncryption | None = None, + kms_key_id: SSEKMSKeyId | None = None, + sse_key_hash: SSECustomerKeyMD5 | None = None, bucket_key_enabled: bool = False, - lock_mode: Optional[ObjectLockMode | ObjectLockRetentionMode] = None, - lock_legal_status: Optional[ObjectLockLegalHoldStatus] = None, - lock_until: Optional[datetime] = None, - website_redirect_location: Optional[WebsiteRedirectLocation] = None, - acl: Optional[AccessControlPolicy] = None, # TODO - owner: Optional[Owner] = None, + lock_mode: ObjectLockMode | ObjectLockRetentionMode | None = None, + lock_legal_status: ObjectLockLegalHoldStatus | None = None, + lock_until: datetime | None = None, + website_redirect_location: WebsiteRedirectLocation | None = None, + acl: AccessControlPolicy | None = None, # TODO + owner: Owner | None = None, ): self.key = key - self.user_metadata = ( - {k.lower(): v for k, v in user_metadata.items()} if user_metadata else {} - ) + self.user_metadata = user_metadata or {} self.system_metadata = system_metadata or {} self.version_id = version_id self.storage_class = storage_class or StorageClass.STANDARD @@ -340,6 +348,7 @@ def __init__( self.internal_last_modified = 0 def get_system_metadata_fields(self) -> dict: + # TODO: change when updating the schema -> make it a property headers = { "LastModified": self.last_modified_rfc1123, "ContentLength": str(self.size), @@ -349,7 +358,7 @@ def get_system_metadata_fields(self) -> dict: headers["Expires"] = self.expires_rfc1123 for metadata_key, metadata_value in self.system_metadata.items(): - headers[metadata_key] = metadata_value + headers[metadata_key] = replace_non_iso_8859_1_characters(metadata_value) if self.storage_class != StorageClass.STANDARD: headers["StorageClass"] = self.storage_class @@ -384,7 +393,6 @@ def is_locked(self, bypass_governance: bool = False) -> bool: return False -# TODO: could use dataclass, validate after models are set class S3DeleteMarker: key: ObjectKey version_id: str @@ -403,22 +411,21 @@ def is_locked(*args, **kwargs) -> bool: return False -# TODO: could use dataclass, validate after models are set class S3Part: part_number: PartNumber - etag: Optional[ETag] + etag: ETag | None last_modified: datetime - size: Optional[int] - checksum_algorithm: Optional[ChecksumAlgorithm] - checksum_value: Optional[str] + size: int | None + checksum_algorithm: ChecksumAlgorithm | None + checksum_value: str | None def __init__( self, part_number: PartNumber, size: int = None, etag: ETag = None, - checksum_algorithm: Optional[ChecksumAlgorithm] = None, - checksum_value: Optional[str] = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + checksum_value: str | None = None, ): self.last_modified = datetime.now(tz=_gmt_zone_info) self.part_number = part_number @@ -433,38 +440,40 @@ def quoted_etag(self) -> str: class S3Multipart: - parts: dict[PartNumber, S3Part] + id: MultipartUploadId + parts: dict[str, S3Part] object: S3Object - upload_id: MultipartUploadId - checksum_value: Optional[str] - checksum_type: Optional[ChecksumType] - checksum_algorithm: ChecksumAlgorithm + checksum_value: str | None + checksum_type: ChecksumType | None + checksum_algorithm: ChecksumAlgorithm | None initiated: datetime - precondition: bool + precondition: bool | None + initiator: Owner | None + tagging: dict[str, str] | None def __init__( self, key: ObjectKey, storage_class: StorageClass | ObjectStorageClass = StorageClass.STANDARD, - expires: Optional[datetime] = None, - expiration: Optional[datetime] = None, # come from lifecycle - checksum_algorithm: Optional[ChecksumAlgorithm] = None, - checksum_type: Optional[ChecksumType] = None, - encryption: Optional[ServerSideEncryption] = None, # inherit bucket - kms_key_id: Optional[SSEKMSKeyId] = None, # inherit bucket + expires: datetime | None = None, + expiration: datetime | None = None, # come from lifecycle + checksum_algorithm: ChecksumAlgorithm | None = None, + checksum_type: ChecksumType | None = None, + encryption: ServerSideEncryption | None = None, # inherit bucket + kms_key_id: SSEKMSKeyId | None = None, # inherit bucket bucket_key_enabled: bool = False, # inherit bucket - sse_key_hash: Optional[SSECustomerKeyMD5] = None, - lock_mode: Optional[ObjectLockMode] = None, - lock_legal_status: Optional[ObjectLockLegalHoldStatus] = None, - lock_until: Optional[datetime] = None, - website_redirect_location: Optional[WebsiteRedirectLocation] = None, - acl: Optional[AccessControlPolicy] = None, # TODO - user_metadata: Optional[Metadata] = None, - system_metadata: Optional[Metadata] = None, - initiator: Optional[Owner] = None, - tagging: Optional[dict[str, str]] = None, - owner: Optional[Owner] = None, - precondition: Optional[bool] = None, + sse_key_hash: SSECustomerKeyMD5 | None = None, + lock_mode: ObjectLockMode | None = None, + lock_legal_status: ObjectLockLegalHoldStatus | None = None, + lock_until: datetime | None = None, + website_redirect_location: WebsiteRedirectLocation | None = None, + acl: AccessControlPolicy | None = None, # TODO + user_metadata: Metadata | None = None, + system_metadata: Metadata | None = None, + initiator: Owner | None = None, + tagging: dict[str, str] | None = None, + owner: Owner | None = None, + precondition: bool | None = None, ): self.id = token_urlsafe(96) # MultipartUploadId is 128 characters long self.initiated = datetime.now(tz=_gmt_zone_info) @@ -512,9 +521,9 @@ def complete_multipart( checksum_hash = CombinedCrcHash(self.checksum_algorithm) pos = 0 - parts_map: dict[int, InternalObjectPart] = {} + parts_map: dict[str, InternalObjectPart] = {} for index, part in enumerate(parts): - part_number = part["PartNumber"] + part_number = str(part["PartNumber"]) part_etag = part["ETag"].strip('"') s3_part = self.parts.get(part_number) @@ -624,6 +633,8 @@ class KeyStore: retrieve the object from that key. """ + _store: dict[ObjectKey, S3Object | S3DeleteMarker] + def __init__(self): self._store = {} @@ -638,7 +649,7 @@ def pop(self, object_key: ObjectKey, default=None) -> S3Object | None: def values(self, *_, **__) -> list[S3Object | S3DeleteMarker]: # we create a shallow copy with dict to avoid size changed during iteration - return [value for value in dict(self._store).values()] + return list(dict(self._store).values()) def is_empty(self) -> bool: return not self._store @@ -656,6 +667,8 @@ class VersionedKeyStore: See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/versioning-workflows.html """ + _store: dict[ObjectKey, dict[ObjectVersionId, S3Object | S3DeleteMarker]] + def __init__(self): self._store = defaultdict(dict) @@ -750,9 +763,7 @@ class S3Store(BaseStore): buckets: dict[BucketName, S3Bucket] = CrossRegionAttribute(default=dict) global_bucket_map: dict[BucketName, AccountId] = CrossAccountAttribute(default=dict) aws_managed_kms_key_id: SSEKMSKeyId = LocalAttribute(default=str) - - # static tagging service instance - TAGS: TaggingService = CrossAccountAttribute(default=TaggingService) + tags: Tags = LocalAttribute(default=Tags) class BucketCorsIndex: diff --git a/localstack-core/localstack/services/s3/notifications.py b/localstack-core/localstack/services/s3/notifications.py index 48ece2ab9e788..a0035398fc35c 100644 --- a/localstack-core/localstack/services/s3/notifications.py +++ b/localstack-core/localstack/services/s3/notifications.py @@ -6,7 +6,7 @@ import re from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple, TypedDict, Union +from typing import TypedDict from urllib.parse import quote from botocore.exceptions import ClientError @@ -21,6 +21,7 @@ Event, EventBridgeConfiguration, EventList, + InvalidArgument, LambdaFunctionArn, LambdaFunctionConfiguration, NotificationConfiguration, @@ -34,8 +35,8 @@ TopicConfiguration, ) from localstack.aws.connect import connect_to +from localstack.services.s3.exceptions import MalformedXML from localstack.services.s3.models import S3Bucket, S3DeleteMarker, S3Object -from localstack.services.s3.utils import _create_invalid_argument_exc from localstack.utils.aws import arns from localstack.utils.aws.arns import ARN_PARTITION_REGEX, get_partition, parse_arn, s3_bucket_arn from localstack.utils.aws.client_types import ServicePrincipal @@ -64,8 +65,8 @@ class S3NotificationContent(TypedDict): s3SchemaVersion: str configurationId: NotificationId - bucket: Dict[str, str] # todo - object: Dict[str, Union[str, int]] # todo + bucket: dict[str, str] # todo + object: dict[str, str | int] # todo class EventRecord(TypedDict): @@ -74,14 +75,14 @@ class EventRecord(TypedDict): awsRegion: str eventTime: str eventName: str - userIdentity: Dict[str, str] - requestParameters: Dict[str, str] - responseElements: Dict[str, str] + userIdentity: dict[str, str] + requestParameters: dict[str, str] + responseElements: dict[str, str] s3: S3NotificationContent class Notification(TypedDict): - Records: List[EventRecord] + Records: list[EventRecord] @dataclass @@ -101,15 +102,15 @@ class S3EventNotificationContext: key_etag: str key_version_id: str key_expiry: datetime.datetime - key_storage_class: Optional[StorageClass] + key_storage_class: StorageClass | None @classmethod - def from_request_context_native( + def from_request_context( cls, request_context: RequestContext, s3_bucket: S3Bucket, s3_object: S3Object | S3DeleteMarker, - ) -> "S3EventNotificationContext": + ) -> S3EventNotificationContext: """ Create an S3EventNotificationContext from a RequestContext. The key is not always present in the request context depending on the event type. In that case, we can use @@ -165,7 +166,7 @@ class BucketVerificationContext: request_id: str bucket_name: str region: str - configuration: Dict + configuration: dict skip_destination_validation: bool @@ -185,7 +186,7 @@ def _matching_event(events: EventList, event_name: str) -> bool: def _matching_filter( - notification_filter: Optional[NotificationConfigurationFilter], key_name: str + notification_filter: NotificationConfigurationFilter | None, key_name: str ) -> bool: """ See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-filtering.html @@ -213,11 +214,11 @@ def _matching_filter( class BaseNotifier: service_name: str - def notify(self, ctx: S3EventNotificationContext, config: Dict): + def notify(self, ctx: S3EventNotificationContext, config: dict): raise NotImplementedError @staticmethod - def should_notify(ctx: S3EventNotificationContext, config: Dict) -> bool: + def should_notify(ctx: S3EventNotificationContext, config: dict) -> bool: """ Helper method checking if the event should be notified to the configured notifiers from the configuration :param ctx: S3EventNotificationContext @@ -229,12 +230,12 @@ def should_notify(ctx: S3EventNotificationContext, config: Dict) -> bool: ) @staticmethod - def _get_arn_value_and_name(notifier_configuration: Dict) -> Tuple[str, str]: + def _get_arn_value_and_name(notifier_configuration: dict) -> tuple[str, str]: raise NotImplementedError def validate_configuration_for_notifier( self, - configurations: List[Dict], + configurations: list[dict], skip_destination_validation: bool, context: RequestContext, bucket_name: str, @@ -272,26 +273,29 @@ def _validate_notification(self, verification_ctx: BucketVerificationContext): arn, argument_name = self._get_arn_value_and_name(configuration) if not re.match(f"{ARN_PARTITION_REGEX}:{self.service_name}:", arn): - raise _create_invalid_argument_exc( - "The ARN could not be parsed", name=argument_name, value=arn + raise InvalidArgument( + "The ARN could not be parsed", + ArgumentName=argument_name, + ArgumentValue=arn, ) + if not verification_ctx.skip_destination_validation: self._verify_target(arn, verification_ctx) if filter_rules := configuration.get("Filter", {}).get("Key", {}).get("FilterRules"): for rule in filter_rules: - rule["Name"] = rule["Name"].capitalize() - if rule["Name"] not in ["Suffix", "Prefix"]: - raise _create_invalid_argument_exc( + if "Name" not in rule or "Value" not in rule: + raise MalformedXML() + + if rule["Name"].lower() not in ["suffix", "prefix"]: + raise InvalidArgument( "filter rule name must be either prefix or suffix", - rule["Name"], - rule["Value"], - ) - if not rule["Value"]: - raise _create_invalid_argument_exc( - "filter value cannot be empty", rule["Name"], rule["Value"] + ArgumentName="FilterRule.Name", + ArgumentValue=rule["Name"], ) + rule["Name"] = rule["Name"].capitalize() + @staticmethod def _get_test_payload(verification_ctx: BucketVerificationContext): return { @@ -381,8 +385,8 @@ class SqsNotifier(BaseNotifier): service_name = "sqs" @staticmethod - def _get_arn_value_and_name(queue_configuration: QueueConfiguration) -> Tuple[QueueArn, str]: - return queue_configuration.get("QueueArn", ""), "QueueArn" + def _get_arn_value_and_name(queue_configuration: QueueConfiguration) -> tuple[QueueArn, str]: + return queue_configuration.get("QueueArn", ""), "Queue" def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationContext) -> None: if not is_api_enabled("sqs"): @@ -402,13 +406,20 @@ def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationCo queue_url = sqs_client.get_queue_url( QueueName=arn_data["resource"], QueueOwnerAWSAccountId=arn_data["account"] )["QueueUrl"] - except ClientError: - LOG.exception("Could not validate the notification destination %s", target_arn) - raise _create_invalid_argument_exc( + except ClientError as e: + code = e.response["Error"]["Code"] + LOG.error( + "Could not validate the notification destination %s: %s", + target_arn, + code, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) + raise InvalidArgument( "Unable to validate the following destination configurations", - name=target_arn, - value="The destination queue does not exist", + ArgumentName=target_arn, + ArgumentValue="The destination queue does not exist", ) + # send test event with the request metadata for permissions # https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-event-types-and-destinations.html#supported-notification-event-types sqs_client = connect_to(region_name=arn_data["region"]).sqs.request_metadata( @@ -424,10 +435,10 @@ def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationCo verification_ctx.bucket_name, target_arn, ) - raise _create_invalid_argument_exc( + raise InvalidArgument( "Unable to validate the following destination configurations", - name=target_arn, - value="Permissions on the destination queue do not allow S3 to publish notifications from this bucket", + ArgumentName=target_arn, + ArgumentValue="Permissions on the destination queue do not allow S3 to publish notifications from this bucket", ) from e def notify(self, ctx: S3EventNotificationContext, config: QueueConfiguration): @@ -454,10 +465,11 @@ def notify(self, ctx: S3EventNotificationContext, config: QueueConfiguration): MessageSystemAttributes=system_attributes, ) except Exception: - LOG.exception( + LOG.error( 'Unable to send notification for S3 bucket "%s" to SQS queue "%s"', ctx.bucket_name, parsed_arn["resource"], + exc_info=LOG.isEnabledFor(logging.DEBUG), ) @@ -483,10 +495,10 @@ def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationCo try: sns_client.get_topic_attributes(TopicArn=target_arn) except ClientError: - raise _create_invalid_argument_exc( + raise InvalidArgument( "Unable to validate the following destination configurations", - name=target_arn, - value="The destination topic does not exist", + ArgumentName=target_arn, + ArgumentValue="The destination topic does not exist", ) sns_client = connect_to(region_name=arn_data["region"]).sns.request_metadata( @@ -506,10 +518,10 @@ def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationCo verification_ctx.bucket_name, target_arn, ) - raise _create_invalid_argument_exc( + raise InvalidArgument( "Unable to validate the following destination configurations", - name=target_arn, - value="Permissions on the destination topic do not allow S3 to publish notifications from this bucket", + ArgumentName=target_arn, + ArgumentValue="Permissions on the destination topic do not allow S3 to publish notifications from this bucket", ) from e def notify(self, ctx: S3EventNotificationContext, config: TopicConfiguration): @@ -536,10 +548,11 @@ def notify(self, ctx: S3EventNotificationContext, config: TopicConfiguration): Subject="Amazon S3 Notification", ) except Exception: - LOG.exception( + LOG.error( 'Unable to send notification for S3 bucket "%s" to SNS topic "%s"', ctx.bucket_name, topic_arn, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) @@ -549,7 +562,7 @@ class LambdaNotifier(BaseNotifier): @staticmethod def _get_arn_value_and_name( lambda_configuration: LambdaFunctionConfiguration, - ) -> Tuple[LambdaFunctionArn, str]: + ) -> tuple[LambdaFunctionArn, str]: return lambda_configuration.get("LambdaFunctionArn", ""), "LambdaFunctionArn" def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationContext) -> None: @@ -567,10 +580,10 @@ def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationCo try: lambda_client.get_function(FunctionName=target_arn) except ClientError: - raise _create_invalid_argument_exc( + raise InvalidArgument( "Unable to validate the following destination configurations", - name=target_arn, - value="The destination Lambda does not exist", + ArgumentName=target_arn, + ArgumentValue="The destination Lambda does not exist", ) lambda_client = connect_to(region_name=arn_data["region"]).lambda_.request_metadata( source_arn=s3_bucket_arn(verification_ctx.bucket_name, region=verification_ctx.region), @@ -579,10 +592,10 @@ def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationCo try: lambda_client.invoke(FunctionName=target_arn, InvocationType=InvocationType.DryRun) except ClientError as e: - raise _create_invalid_argument_exc( + raise InvalidArgument( "Unable to validate the following destination configurations", - name=f"{target_arn}, null", - value=f"Not authorized to invoke function [{target_arn}]", + ArgumentName=f"{target_arn}, null", + ArgumentValue=f"Not authorized to invoke function [{target_arn}]", ) from e def notify(self, ctx: S3EventNotificationContext, config: LambdaFunctionConfiguration): @@ -604,10 +617,11 @@ def notify(self, ctx: S3EventNotificationContext, config: LambdaFunctionConfigur Payload=payload, ) except Exception: - LOG.exception( + LOG.error( 'Unable to send notification for S3 bucket "%s" to Lambda function "%s".', ctx.bucket_name, lambda_arn, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) @@ -698,14 +712,14 @@ def _get_event_payload( return entry @staticmethod - def should_notify(ctx: S3EventNotificationContext, config: Dict) -> bool: + def should_notify(ctx: S3EventNotificationContext, config: dict) -> bool: # Events are always passed to EventBridge, you can route the event in EventBridge # See https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html return True def validate_configuration_for_notifier( self, - configurations: List[Dict], + configurations: list[dict], skip_destination_validation: bool, context: RequestContext, bucket_name: str, @@ -729,8 +743,10 @@ def notify(self, ctx: S3EventNotificationContext, config: EventBridgeConfigurati try: events_client.put_events(Entries=[entry]) except Exception: - LOG.exception( - 'Unable to send notification for S3 bucket "%s" to EventBridge', ctx.bucket_name + LOG.error( + 'Unable to send notification for S3 bucket "%s" to EventBridge', + ctx.bucket_name, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) diff --git a/localstack-core/localstack/services/s3/presigned_url.py b/localstack-core/localstack/services/s3/presigned_url.py index e696e82e2c2dc..b782391bc5721 100644 --- a/localstack-core/localstack/services/s3/presigned_url.py +++ b/localstack-core/localstack/services/s3/presigned_url.py @@ -6,8 +6,9 @@ import re import time from collections import namedtuple +from collections.abc import Mapping from functools import cache, cached_property -from typing import Mapping, Optional, TypedDict +from typing import TypedDict from urllib import parse as urlparse from botocore.auth import HmacV1QueryAuth, S3SigV4QueryAuth @@ -19,7 +20,7 @@ from botocore.utils import percent_encode_sequence from werkzeug.datastructures import Headers, ImmutableMultiDict -from localstack import config +from localstack import config, constants from localstack.aws.accounts import get_account_id_from_access_key_id from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api.s3 import ( @@ -34,25 +35,25 @@ from localstack.aws.chain import HandlerChain from localstack.aws.protocol.op_router import RestServiceOperationRouter from localstack.aws.spec import get_service_catalog +from localstack.constants import AWS_REGION_US_EAST_1 from localstack.http import Request, Response from localstack.http.request import get_raw_path from localstack.services.s3.constants import ( DEFAULT_PRE_SIGNED_ACCESS_KEY_ID, DEFAULT_PRE_SIGNED_SECRET_ACCESS_KEY, + S3_HOST_ID, SIGNATURE_V2_PARAMS, SIGNATURE_V4_PARAMS, ) from localstack.services.s3.utils import ( - S3_VIRTUAL_HOST_FORWARDED_HEADER, - _create_invalid_argument_exc, capitalize_header_name_from_snake_case, extract_bucket_name_and_key_from_headers_and_path, - forwarded_from_virtual_host_addressed_request, is_bucket_name_valid, is_presigned_url_request, uses_host_addressing, ) from localstack.utils.aws.arns import get_partition +from localstack.utils.aws.request_context import mock_aws_request_headers from localstack.utils.strings import to_bytes LOG = logging.getLogger(__name__) @@ -85,8 +86,6 @@ "x-amz-content-sha256", ] -FAKE_HOST_ID = "9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg=" - HOST_COMBINATION_REGEX = r"^(.*)(:[\d]{0,6})" PORT_REPLACEMENT = [":80", ":443", f":{config.GATEWAY_LISTEN[0].port}", ""] @@ -105,7 +104,7 @@ class NotValidSigV4SignatureContext(TypedDict): canonical_request: str -FindSigV4Result = tuple["S3SigV4SignatureContext", Optional[NotValidSigV4SignatureContext]] +FindSigV4Result = tuple["S3SigV4SignatureContext", NotValidSigV4SignatureContext | None] class HmacV1QueryAuthValidation(HmacV1QueryAuth): @@ -156,7 +155,7 @@ def create_signature_does_not_match_sig_v2( "The request signature we calculated does not match the signature you provided. Check your key and signing method." ) ex.AWSAccessKeyId = access_key_id - ex.HostId = FAKE_HOST_ID + ex.HostId = S3_HOST_ID ex.SignatureProvided = request_signature ex.StringToSign = string_to_sign ex.StringToSignBytes = to_bytes(string_to_sign).hex(sep=" ", bytes_per_sep=2).upper() @@ -194,8 +193,8 @@ def __call__(self, _: HandlerChain, context: RequestContext, __: Response): return try: if not is_presigned_url_request(context): - # validate headers, as some can raise ValueError in Moto - _validate_headers_for_moto(context.request.headers) + # validate headers + _validate_streaming_headers(context.request.headers) return # will raise exceptions if the url is not valid, except if S3_SKIP_SIGNATURE_VALIDATION is True # will still try to validate it and log if there's an error @@ -209,7 +208,7 @@ def __call__(self, _: HandlerChain, context: RequestContext, __: Response): elif is_valid_sig_v4(query_arg_set): validate_presigned_url_s3v4(context) - _validate_headers_for_moto(context.request.headers) + _validate_streaming_headers(context.request.headers) LOG.debug("Valid presign url.") # TODO: set the Authorization with the data from the pre-signed query string @@ -248,7 +247,10 @@ def get_credentials_from_parameters(parameters: dict, region: str) -> PreSignedC # fallback to the hardcoded value access_key_id = DEFAULT_PRE_SIGNED_ACCESS_KEY_ID - if not (secret_access_key := get_secret_access_key_from_access_key_id(access_key_id, region)): + if access_key_id in (constants.INTERNAL_AWS_ACCESS_KEY_ID, config.INTERNAL_RESOURCE_ACCOUNT): + secret_access_key = constants.INTERNAL_AWS_SECRET_ACCESS_KEY + + elif not (secret_access_key := get_secret_access_key_from_access_key_id(access_key_id, region)): # if we could not retrieve the secret access key, it means the access key was not registered in LocalStack, # fallback to hardcoded necessary secret access key secret_access_key = DEFAULT_PRE_SIGNED_SECRET_ACCESS_KEY @@ -258,7 +260,7 @@ def get_credentials_from_parameters(parameters: dict, region: str) -> PreSignedC @cache -def get_secret_access_key_from_access_key_id(access_key_id: str, region: str) -> Optional[str]: +def get_secret_access_key_from_access_key_id(access_key_id: str, region: str) -> str | None: """ We need to retrieve the internal secret access key in order to also sign the request on our side to validate it For now, we need to access Moto internals, as they are no public APIs to retrieve it for obvious reasons. @@ -299,7 +301,7 @@ def is_valid_sig_v2(query_args: set) -> bool: LOG.info("Presign signature calculation failed") raise AccessDenied( "Query-string authentication requires the Signature, Expires and AWSAccessKeyId parameters", - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, ) return True @@ -317,7 +319,7 @@ def is_valid_sig_v4(query_args: set) -> bool: LOG.info("Presign signature calculation failed") raise AuthorizationQueryParametersError( "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.", - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, ) return True @@ -351,7 +353,7 @@ def validate_presigned_url_s3(context: RequestContext) -> None: ) else: raise AccessDenied( - "Request has expired", HostId=FAKE_HOST_ID, Expires=expires, ServerTime=time.time() + "Request has expired", HostId=S3_HOST_ID, Expires=expires, ServerTime=time.time() ) auth_signer = HmacV1QueryAuthValidation(credentials=signing_credentials, expires=expires) @@ -377,6 +379,14 @@ def validate_presigned_url_s3(context: RequestContext) -> None: ) raise ex + # we need to add a new Authorization header to the request so that the request has the right account id + if not headers.get("Authorization"): + headers["Authorization"] = mock_aws_request_headers( + "s3", + aws_access_key_id=credentials.access_key_id, + region_name=AWS_REGION_US_EAST_1, + )["Authorization"] + add_headers_to_original_request(context, headers) @@ -450,7 +460,7 @@ def validate_presigned_url_s3v4(context: RequestContext) -> None: else: raise AccessDenied( "There were headers present in the request which were not signed", - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, HeadersNotSigned=", ".join(sigv4_context.missing_signed_headers), ) @@ -472,7 +482,7 @@ def validate_presigned_url_s3v4(context: RequestContext) -> None: x_amz_expires = int(query_parameters["X-Amz-Expires"]) x_amz_expires_dt = datetime.timedelta(seconds=x_amz_expires) expiration_time = x_amz_date + x_amz_expires_dt - expiration_time = expiration_time.replace(tzinfo=datetime.timezone.utc) + expiration_time = expiration_time.replace(tzinfo=datetime.UTC) if is_expired(expiration_time): if config.S3_SKIP_SIGNATURE_VALIDATION: @@ -482,7 +492,7 @@ def validate_presigned_url_s3v4(context: RequestContext) -> None: else: raise AccessDenied( "Request has expired", - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, Expires=expiration_time.timestamp(), ServerTime=time.time(), X_Amz_Expires=x_amz_expires, @@ -568,34 +578,21 @@ def __init__(self, context: RequestContext): self._query_parameters ) - if forwarded_from_virtual_host_addressed_request(self._headers): - # FIXME: maybe move this so it happens earlier in the chain when using virtual host? - if not is_bucket_name_valid(self._bucket): - raise InvalidBucketName(BucketName=self._bucket) - netloc = self._headers.get(S3_VIRTUAL_HOST_FORWARDED_HEADER) - self.host = netloc - self._original_host = netloc - self.signed_headers["host"] = netloc - # the request comes from the Virtual Host router, we need to remove the bucket from the path + netloc = urlparse.urlparse(self.request.url).netloc + self.host = netloc + self._original_host = netloc + if (host_addressed := uses_host_addressing(self._headers)) and not is_bucket_name_valid( + self._bucket + ): + raise InvalidBucketName(BucketName=self._bucket) + + if not host_addressed and not self.request.path.startswith(f"/{self._bucket}"): + # if in path style, check that the path starts with the bucket + # our path has been sanitized, we should use the un-sanitized one splitted_path = self.request.path.split("/", maxsplit=2) - self.path = f"/{splitted_path[-1]}" - + self.path = f"/{self._bucket}/{splitted_path[-1]}" else: - netloc = urlparse.urlparse(self.request.url).netloc - self.host = netloc - self._original_host = netloc - if (host_addressed := uses_host_addressing(self._headers)) and not is_bucket_name_valid( - self._bucket - ): - raise InvalidBucketName(BucketName=self._bucket) - - if not host_addressed and not self.request.path.startswith(f"/{self._bucket}"): - # if in path style, check that the path starts with the bucket - # our path has been sanitized, we should use the un-sanitized one - splitted_path = self.request.path.split("/", maxsplit=2) - self.path = f"/{self._bucket}/{splitted_path[-1]}" - else: - self.path = self.request.path + self.path = self.request.path # we need to URL encode the path, as the key needs to be urlencoded for the signature to match self.path = urlparse.quote(self.path) @@ -714,7 +711,7 @@ def _get_region_from_x_amz_credential(credential: str) -> str: if not (split_creds := credential.split("/")) or len(split_creds) != 5: raise AuthorizationQueryParametersError( 'Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting "/YYYYMMDD/REGION/SERVICE/aws4_request".', - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, ) return split_creds[2] @@ -737,16 +734,13 @@ def add_headers_to_original_request(context: RequestContext, headers: Mapping[st context.request.headers.add(header, value) -def _validate_headers_for_moto(headers: Headers) -> None: +def _validate_streaming_headers(headers: Headers) -> None: """ - The headers can contain values that do not have the right type, and it will throw Exception when passed to Moto + The headers can contain values that do not have the right type, and should be validated. Validate them before it get passed :param headers: request headers """ if headers.get("x-amz-content-sha256", None) == "STREAMING-AWS4-HMAC-SHA256-PAYLOAD": - # this is sign that this is a SigV4 request, with payload encoded - # we do not support payload encoding yet - # moto parses it to an int, it would raise a 500 content_length = headers.get("x-amz-decoded-content-length") if not content_length: raise SignatureDoesNotMatch('"X-Amz-Decoded-Content-Length" header is missing') @@ -771,13 +765,12 @@ def validate_post_policy( :return: None """ if not request_form.get("key"): - ex: InvalidArgument = _create_invalid_argument_exc( - message="Bucket POST must contain a field named 'key'. If it is specified, please check the order of the fields.", - name="key", - value="", - host_id=FAKE_HOST_ID, + raise InvalidArgument( + "Bucket POST must contain a field named 'key'. If it is specified, please check the order of the fields.", + ArgumentName="key", + ArgumentValue="", + HostId=S3_HOST_ID, ) - raise ex form_dict = {k.lower(): v for k, v in request_form.items()} @@ -792,7 +785,7 @@ def validate_post_policy( if not is_v2 and not is_v4: ex: AccessDenied = AccessDenied("Access Denied") - ex.HostId = FAKE_HOST_ID + ex.HostId = S3_HOST_ID raise ex try: @@ -811,7 +804,7 @@ def validate_post_policy( if expiration := policy_decoded.get("expiration"): if is_expired(_parse_policy_expiration_date(expiration)): ex: AccessDenied = AccessDenied("Invalid according to Policy: Policy expired.") - ex.HostId = FAKE_HOST_ID + ex.HostId = S3_HOST_ID raise ex # TODO: validate the signature @@ -833,7 +826,7 @@ def validate_post_policy( str_condition = str(condition).replace("'", '"') raise AccessDenied( f"Invalid according to Policy: Policy Condition failed: {str_condition}", - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, ) @@ -886,7 +879,7 @@ def _verify_condition(condition: list | dict, form: dict, additional_policy_meta "Your proposed upload exceeds the maximum allowed size", ProposedSize=size, MaxSizeAllowed=end, - HostId=FAKE_HOST_ID, + HostId=S3_HOST_ID, ) else: return True @@ -905,7 +898,7 @@ def _parse_policy_expiration_date(expiration_string: str) -> datetime.datetime: dt = datetime.datetime.strptime(expiration_string, POLICY_EXPIRATION_FORMAT2) # both date formats assume a UTC timezone ('Z' suffix), but it's not parsed as tzinfo into the datetime object - dt = dt.replace(tzinfo=datetime.timezone.utc) + dt = dt.replace(tzinfo=datetime.UTC) return dt @@ -931,13 +924,12 @@ def _is_match_with_signature_fields( if argument_name == "Awsaccesskeyid": argument_name = "AWSAccessKeyId" - ex: InvalidArgument = _create_invalid_argument_exc( - message=f"Bucket POST must contain a field named '{argument_name}'. If it is specified, please check the order of the fields.", - name=argument_name, - value="", - host_id=FAKE_HOST_ID, + raise InvalidArgument( + f"Bucket POST must contain a field named '{argument_name}'. If it is specified, please check the order of the fields.", + ArgumentName=argument_name, + ArgumentValue="", + HostId=S3_HOST_ID, ) - raise ex return True return False diff --git a/localstack-core/localstack/services/s3/provider.py b/localstack-core/localstack/services/s3/provider.py index 0c6b41e5974f0..2413fa5a33e10 100644 --- a/localstack-core/localstack/services/s3/provider.py +++ b/localstack-core/localstack/services/s3/provider.py @@ -1,4 +1,5 @@ import base64 +import contextlib import copy import datetime import json @@ -8,7 +9,8 @@ from inspect import signature from io import BytesIO from operator import itemgetter -from typing import IO, Optional, Union +from threading import RLock +from typing import IO from urllib import parse as urlparse from zoneinfo import ZoneInfo @@ -23,6 +25,7 @@ AccountId, AnalyticsConfiguration, AnalyticsId, + AuthorizationHeaderMalformed, BadDigest, Body, Bucket, @@ -30,6 +33,7 @@ BucketAlreadyOwnedByYou, BucketCannedACL, BucketLifecycleConfiguration, + BucketLocationConstraint, BucketLoggingStatus, BucketName, BucketNotEmpty, @@ -114,7 +118,6 @@ InvalidArgument, InvalidBucketName, InvalidDigest, - InvalidLocationConstraint, InvalidObjectState, InvalidPartNumber, InvalidPartOrder, @@ -208,6 +211,7 @@ SSECustomerKeyMD5, StartAfter, StorageClass, + Tag, Tagging, Token, TransitionDefaultMinimumObjectSize, @@ -226,7 +230,7 @@ preprocess_request, serve_custom_service_request_handlers, ) -from localstack.constants import AWS_REGION_US_EAST_1 +from localstack.constants import AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1 from localstack.services.edge import ROUTER from localstack.services.plugins import ServiceLifecycleHook from localstack.services.s3.codec import AwsChunkedDecoder @@ -235,6 +239,7 @@ ARCHIVES_STORAGE_CLASSES, CHECKSUM_ALGORITHMS, DEFAULT_BUCKET_ENCRYPTION, + S3_HOST_ID, ) from localstack.services.s3.cors import S3CorsHandler, s3_cors_request_handler from localstack.services.s3.exceptions import ( @@ -271,12 +276,18 @@ base_64_content_md5_to_etag, create_redirect_for_post_request, create_s3_kms_managed_key_for_region, + decode_continuation_token, + decode_user_metadata, + encode_continuation_token, + encode_user_metadata, etag_to_base_64_content_md5, extract_bucket_key_version_id_from_copy_source, generate_safe_version_id, + get_bucket_location_xml, get_canned_acl, get_class_attrs_from_spec_class, get_failed_precondition_copy_source, + get_failed_upload_part_copy_source_preconditions, get_full_default_bucket_location, get_kms_key_arn, get_lifecycle_rule_from_object, @@ -287,6 +298,8 @@ get_s3_checksum_algorithm_from_trailing_headers, get_system_metadata_from_request, get_unique_key_id, + get_url_encoded_object_location, + header_name_from_capitalized_param, is_bucket_name_valid, is_version_older_than_other, parse_copy_source_range_header, @@ -299,6 +312,7 @@ validate_dict_fields, validate_failed_precondition, validate_kms_key_id, + validate_location_constraint, validate_tag_set, ) from localstack.services.s3.validation import ( @@ -309,6 +323,7 @@ validate_canned_acl, validate_checksum_value, validate_cors_configuration, + validate_encoding_type, validate_inventory_configuration, validate_lifecycle_configuration, validate_object_key, @@ -318,6 +333,7 @@ from localstack.services.s3.website_hosting import register_website_hosting_routes from localstack.state import AssetDirectory, StateVisitor from localstack.utils.aws.arns import s3_bucket_name +from localstack.utils.aws.aws_stack import get_valid_regions_for_service from localstack.utils.collections import select_from_typed_dict from localstack.utils.strings import short_uid, to_bytes, to_str @@ -337,6 +353,8 @@ def __init__(self, storage_backend: S3ObjectStore = None) -> None: self._storage_backend = storage_backend or EphemeralS3ObjectStore(DEFAULT_S3_TMP_DIR) self._notification_dispatcher = NotificationDispatcher() self._cors_handler = S3CorsHandler(BucketCorsIndex()) + # TODO: add lock for keys for PutObject, only way to support precondition writes for versioned buckets + self._preconditions_locks = defaultdict(lambda: defaultdict(RLock)) # runtime cache of Lifecycle Expiration headers, as they need to be calculated everytime we fetch an object # in case the rules have changed @@ -381,7 +399,7 @@ def _notify( """ if s3_bucket.notification_configuration: if not s3_notif_ctx: - s3_notif_ctx = S3EventNotificationContext.from_request_context_native( + s3_notif_ctx = S3EventNotificationContext.from_request_context( context, s3_bucket=s3_bucket, s3_object=s3_object, @@ -444,19 +462,20 @@ def _get_cross_account_bucket( f"The value of the expected bucket owner parameter must be an AWS Account ID... [{expected_bucket_owner}]", ) - store = self.get_store(context.account_id, context.region) - if not (s3_bucket := store.buckets.get(bucket_name)): - if not (account_id := store.global_bucket_map.get(bucket_name)): + request_store = self.get_store(context.account_id, context.region) + if not (s3_bucket := request_store.buckets.get(bucket_name)): + if not (bucket_account_id := request_store.global_bucket_map.get(bucket_name)): raise NoSuchBucket("The specified bucket does not exist", BucketName=bucket_name) - store = self.get_store(account_id, context.region) - if not (s3_bucket := store.buckets.get(bucket_name)): + bucket_account_store = self.get_store(bucket_account_id, context.region) + if not (s3_bucket := bucket_account_store.buckets.get(bucket_name)): raise NoSuchBucket("The specified bucket does not exist", BucketName=bucket_name) if expected_bucket_owner and s3_bucket.bucket_account_id != expected_bucket_owner: raise AccessDenied("Access Denied") - return store, s3_bucket + regional_bucket_store = self.get_store(s3_bucket.bucket_account_id, s3_bucket.bucket_region) + return regional_bucket_store, s3_bucket @staticmethod def get_store(account_id: str, region_name: str) -> S3Store: @@ -469,39 +488,35 @@ def create_bucket( context: RequestContext, request: CreateBucketRequest, ) -> CreateBucketOutput: + if context.region == "aws-global": + # TODO: extend this logic to probably all the provider, and maybe all services. S3 is the most impacted + # right now so this will help users to properly set a region in their config + # See the `TestS3.test_create_bucket_aws_global` test + raise AuthorizationHeaderMalformed( + f"The authorization header is malformed; the region 'aws-global' is wrong; expecting '{AWS_REGION_US_EAST_1}'", + HostId=S3_HOST_ID, + Region=AWS_REGION_US_EAST_1, + ) + bucket_name = request["Bucket"] if not is_bucket_name_valid(bucket_name): raise InvalidBucketName("The specified bucket is not valid.", BucketName=bucket_name) - # the XML parser returns an empty dict if the body contains the following: - # - # but it also returns an empty dict if the body is fully empty. We need to differentiate the 2 cases by checking - # if the body is empty or not - if context.request.data and ( - (create_bucket_configuration := request.get("CreateBucketConfiguration")) is not None - ): - if not (bucket_region := create_bucket_configuration.get("LocationConstraint")): - raise MalformedXML() - - if context.region == AWS_REGION_US_EAST_1: - if bucket_region == "us-east-1": - raise InvalidLocationConstraint( - "The specified location-constraint is not valid", - LocationConstraint=bucket_region, - ) - elif context.region != bucket_region: - raise CommonServiceException( - code="IllegalLocationConstraintException", - message=f"The {bucket_region} location constraint is incompatible for the region specific endpoint this request was sent to.", - ) - else: + create_bucket_configuration = request.get("CreateBucketConfiguration") or {} + + bucket_tags = create_bucket_configuration.get("Tags", []) + if bucket_tags: + validate_tag_set(bucket_tags, type_set="create-bucket") + + location_constraint = create_bucket_configuration.get("LocationConstraint", "") + validate_location_constraint(context.region, location_constraint) + + bucket_region = location_constraint + if not location_constraint: bucket_region = AWS_REGION_US_EAST_1 - if context.region != bucket_region: - raise CommonServiceException( - code="IllegalLocationConstraintException", - message="The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.", - ) + if location_constraint == BucketLocationConstraint.EU: + bucket_region = AWS_REGION_EU_WEST_1 store = self.get_store(context.account_id, bucket_region) @@ -510,15 +525,20 @@ def create_bucket( if existing_bucket_owner != context.account_id: raise BucketAlreadyExists() - # if the existing bucket has the same owner, the behaviour will depend on the region - if bucket_region != "us-east-1": + # if the existing bucket has the same owner, the behaviour will depend on the region and if the request has + # tags + if bucket_region != AWS_REGION_US_EAST_1 or bucket_tags: raise BucketAlreadyOwnedByYou( "Your previous request to create the named bucket succeeded and you already own it.", BucketName=bucket_name, ) else: + existing_bucket = store.buckets[bucket_name] # CreateBucket is idempotent in us-east-1 - return CreateBucketOutput(Location=f"/{bucket_name}") + return CreateBucketOutput( + Location=f"/{bucket_name}", + BucketArn=existing_bucket.bucket_arn, + ) if ( object_ownership := request.get("ObjectOwnership") @@ -530,6 +550,7 @@ def create_bucket( # see https://docs.aws.amazon.com/AmazonS3/latest/API/API_Owner.html owner = get_owner_for_account_id(context.account_id) acl = get_access_control_policy_for_new_resource_request(request, owner=owner) + s3_bucket = S3Bucket( name=bucket_name, account_id=context.account_id, @@ -537,11 +558,16 @@ def create_bucket( owner=owner, acl=acl, object_ownership=request.get("ObjectOwnership"), - object_lock_enabled_for_bucket=request.get("ObjectLockEnabledForBucket"), + object_lock_enabled_for_bucket=request.get("ObjectLockEnabledForBucket") or False, + location_constraint=location_constraint, ) store.buckets[bucket_name] = s3_bucket store.global_bucket_map[bucket_name] = s3_bucket.bucket_account_id + if bucket_tags: + store.tags.update_tags( + s3_bucket.bucket_arn, {tag["Key"]: tag["Value"] for tag in bucket_tags} + ) self._cors_handler.invalidate_cache() self._storage_backend.create_bucket(bucket_name) @@ -551,7 +577,7 @@ def create_bucket( if bucket_region == "us-east-1" else get_full_default_bucket_location(bucket_name) ) - response = CreateBucketOutput(Location=location) + response = CreateBucketOutput(Location=location, BucketArn=s3_bucket.bucket_arn) return response def delete_bucket( @@ -577,8 +603,10 @@ def delete_bucket( store.global_bucket_map.pop(bucket) self._cors_handler.invalidate_cache() self._expiration_cache.pop(bucket, None) + self._preconditions_locks.pop(bucket, None) # clean up the storage backend self._storage_backend.delete_bucket(bucket) + store.tags.delete_all_tags(s3_bucket.bucket_arn) def list_buckets( self, @@ -589,6 +617,13 @@ def list_buckets( bucket_region: BucketRegion = None, **kwargs, ) -> ListBucketsOutput: + if bucket_region and not config.ALLOW_NONSTANDARD_REGIONS: + if bucket_region not in get_valid_regions_for_service(self.service): + raise InvalidArgument( + f"Argument value {bucket_region} is not a valid AWS Region", + ArgumentName="bucket-region", + ) + owner = get_owner_for_account_id(context.account_id) store = self.get_store(context.account_id, context.region) @@ -621,6 +656,7 @@ def list_buckets( Name=bucket.name, CreationDate=bucket.creation_date, BucketRegion=bucket.bucket_region, + BucketArn=bucket.bucket_arn, ) buckets.append(output_bucket) count += 1 @@ -636,6 +672,16 @@ def head_bucket( expected_bucket_owner: AccountId = None, **kwargs, ) -> HeadBucketOutput: + if context.region == "aws-global": + # TODO: extend this logic to probably all the provider, and maybe all services. S3 is the most impacted + # right now so this will help users to properly set a region in their config + # See the `TestS3.test_create_bucket_aws_global` test + raise AuthorizationHeaderMalformed( + f"The authorization header is malformed; the region 'aws-global' is wrong; expecting '{AWS_REGION_US_EAST_1}'", + HostId=S3_HOST_ID, + Region=AWS_REGION_US_EAST_1, + ) + store = self.get_store(context.account_id, context.region) if not (s3_bucket := store.buckets.get(bucket)): if not (account_id := store.global_bucket_map.get(bucket)): @@ -649,7 +695,9 @@ def head_bucket( # TODO: this call is also used to check if the user has access/authorization for the bucket # it can return 403 - return HeadBucketOutput(BucketRegion=s3_bucket.bucket_region) + return HeadBucketOutput( + BucketRegion=s3_bucket.bucket_region, BucketArn=s3_bucket.bucket_arn + ) def get_bucket_location( self, @@ -670,17 +718,10 @@ def get_bucket_location( """ store, s3_bucket = self._get_cross_account_bucket(context, bucket) - location_constraint = ( - '\n' - '{{location}}' + return GetBucketLocationOutput( + LocationConstraint=get_bucket_location_xml(s3_bucket.location_constraint) ) - location = s3_bucket.bucket_region if s3_bucket.bucket_region != "us-east-1" else "" - location_constraint = location_constraint.replace("{{location}}", location) - - response = GetBucketLocationOutput(LocationConstraint=location_constraint) - return response - @handler("PutObject", expand=False) def put_object( self, @@ -726,7 +767,15 @@ def put_object( if not system_metadata.get("ContentType"): system_metadata["ContentType"] = "binary/octet-stream" + user_metadata = decode_user_metadata(request.get("Metadata")) + version_id = generate_version_id(s3_bucket.versioning_status) + if version_id != "null": + # if we are in a versioned bucket, we need to lock around the full key (all the versions) + # because object versions have locks per version + precondition_lock = self._preconditions_locks[bucket_name][key] + else: + precondition_lock = contextlib.nullcontext() etag_content_md5 = "" if content_md5 := request.get("ContentMD5"): @@ -770,7 +819,7 @@ def put_object( version_id=version_id, storage_class=storage_class, expires=request.get("Expires"), - user_metadata=request.get("Metadata"), + user_metadata=user_metadata, system_metadata=system_metadata, checksum_algorithm=checksum_algorithm, checksum_value=checksum_value, @@ -809,7 +858,10 @@ def put_object( if encodings: s3_object.system_metadata["ContentEncoding"] = ",".join(encodings) - with self._storage_backend.open(bucket_name, s3_object, mode="w") as s3_stored_object: + with ( + precondition_lock, + self._storage_backend.open(bucket_name, s3_object, mode="w") as s3_stored_object, + ): # as we are inside the lock here, if multiple concurrent requests happen for the same object, it's the first # one to finish to succeed, and subsequent will raise exceptions. Once the first write finishes, we're # opening the lock and other requests can check this condition @@ -854,9 +906,9 @@ def put_object( # in case we are overriding an object, delete the tags entry key_id = get_unique_key_id(bucket_name, key, version_id) - store.TAGS.tags.pop(key_id, None) + store.tags.delete_all_tags(key_id) if tagging: - store.TAGS.tags[key_id] = tagging + store.tags.update_tags(key_id, tagging) # RequestCharged: Optional[RequestCharged] # TODO response = PutObjectOutput( @@ -867,14 +919,14 @@ def put_object( if s3_object.checksum_algorithm: response[f"Checksum{s3_object.checksum_algorithm}"] = s3_object.checksum_value - response["ChecksumType"] = getattr(s3_object, "checksum_type", ChecksumType.FULL_OBJECT) + response["ChecksumType"] = s3_object.checksum_type if s3_bucket.lifecycle_rules: if expiration_header := self._get_expiration_header( s3_bucket.lifecycle_rules, bucket_name, s3_object, - store.TAGS.tags.get(key_id, {}), + store.tags.get_tags(key_id), ): # TODO: we either apply the lifecycle to existing objects when we set the new rules, or we need to # apply them everytime we get/head an object @@ -909,15 +961,6 @@ def get_object( version_id=version_id, http_method="GET", ) - if s3_object.expires and s3_object.expires < datetime.datetime.now( - tz=s3_object.expires.tzinfo - ): - # TODO: old behaviour was deleting key instantly if expired. AWS cleans up only once a day generally - # you can still HeadObject on it and you get the expiry time until scheduled deletion - kwargs = {"Key": object_key} - if version_id: - kwargs["VersionId"] = version_id - raise NoSuchKey("The specified key does not exist.", **kwargs) if s3_object.storage_class in ARCHIVES_STORAGE_CLASSES and not s3_object.restore: raise InvalidObjectState( @@ -929,15 +972,13 @@ def get_object( validate_kms_key_id(kms_key=s3_object.kms_key_id, bucket=s3_bucket) sse_c_key_md5 = request.get("SSECustomerKeyMD5") - # we're using getattr access because when restoring, the field might not exist - # TODO: cleanup at next major release - if sse_key_hash := getattr(s3_object, "sse_key_hash", None): - if sse_key_hash and not sse_c_key_md5: + if s3_object.sse_key_hash: + if s3_object.sse_key_hash and not sse_c_key_md5: raise InvalidRequest( "The object was stored using a form of Server Side Encryption. " "The correct parameters must be provided to retrieve the object." ) - elif sse_key_hash != sse_c_key_md5: + elif s3_object.sse_key_hash != sse_c_key_md5: raise AccessDenied( "Requests specifying Server Side Encryption with Customer provided keys must provide the correct secret key." ) @@ -981,7 +1022,7 @@ def get_object( **s3_object.get_system_metadata_fields(), ) if s3_object.user_metadata: - response["Metadata"] = s3_object.user_metadata + response["Metadata"] = encode_user_metadata(s3_object.user_metadata) if s3_object.parts and request.get("PartNumber"): response["PartsCount"] = len(s3_object.parts) @@ -1000,7 +1041,7 @@ def get_object( if checksum_algorithm := s3_object.checksum_algorithm: if (request.get("ChecksumMode") or "").upper() == "ENABLED": checksum_value = s3_object.checksum_value - checksum_type = getattr(s3_object, "checksum_type", ChecksumType.FULL_OBJECT) + checksum_type = s3_object.checksum_type if range_data: s3_stored_object.seek(range_data.begin) @@ -1012,7 +1053,7 @@ def get_object( response["StatusCode"] = 206 if checksum_value: if s3_object.parts and part_number and checksum_type == ChecksumType.COMPOSITE: - part_data = s3_object.parts[part_number] + part_data = s3_object.parts[str(part_number)] checksum_key = f"Checksum{checksum_algorithm.upper()}" response[checksum_key] = part_data.get(checksum_key) response["ChecksumType"] = ChecksumType.COMPOSITE @@ -1030,11 +1071,10 @@ def get_object( add_encryption_to_response(response, s3_object=s3_object) - if object_tags := store.TAGS.tags.get( - get_unique_key_id(bucket_name, object_key, version_id) - ): - response["TagCount"] = len(object_tags) + object_tags = store.tags.get_tags(get_unique_key_id(bucket_name, object_key, version_id)) + if tag_count := len(object_tags): + response["TagCount"] = tag_count if s3_object.is_current and s3_bucket.lifecycle_rules: if expiration_header := self._get_expiration_header( s3_bucket.lifecycle_rules, @@ -1063,6 +1103,17 @@ def get_object( for request_param, response_param in ALLOWED_HEADER_OVERRIDES.items(): if request_param_value := request.get(request_param): + if isinstance(request_param_value, str): + try: + request_param_value.encode("latin-1") + except UnicodeEncodeError: + raise InvalidArgument( + "Header value cannot be represented using ISO-8859-1.", + ArgumentName=header_name_from_capitalized_param(request_param), + ArgumentValue=request_param_value, + HostId=S3_HOST_ID, + ) + response[response_param] = request_param_value return response @@ -1109,14 +1160,14 @@ def head_object( **s3_object.get_system_metadata_fields(), ) if s3_object.user_metadata: - response["Metadata"] = s3_object.user_metadata + response["Metadata"] = encode_user_metadata(s3_object.user_metadata) checksum_value = None checksum_type = None if checksum_algorithm := s3_object.checksum_algorithm: if (request.get("ChecksumMode") or "").upper() == "ENABLED": checksum_value = s3_object.checksum_value - checksum_type = getattr(s3_object, "checksum_type", ChecksumType.FULL_OBJECT) + checksum_type = s3_object.checksum_type if s3_object.parts and request.get("PartNumber"): response["PartsCount"] = len(s3_object.parts) @@ -1146,7 +1197,7 @@ def head_object( response["StatusCode"] = 206 if checksum_value: if s3_object.parts and part_number and checksum_type == ChecksumType.COMPOSITE: - part_data = s3_object.parts[part_number] + part_data = s3_object.parts[str(part_number)] checksum_key = f"Checksum{checksum_algorithm.upper()}" response[checksum_key] = part_data.get(checksum_key) response["ChecksumType"] = ChecksumType.COMPOSITE @@ -1161,12 +1212,14 @@ def head_object( response["ChecksumType"] = checksum_type add_encryption_to_response(response, s3_object=s3_object) + object_tags = store.tags.get_tags( + get_unique_key_id(bucket_name, object_key, s3_object.version_id) + ) + if tag_count := len(object_tags): + response["TagCount"] = tag_count # if you specify the VersionId, AWS won't return the Expiration header, even if that's the current version if not version_id and s3_bucket.lifecycle_rules: - object_tags = store.TAGS.tags.get( - get_unique_key_id(bucket_name, object_key, s3_object.version_id) - ) if expiration_header := self._get_expiration_header( s3_bucket.lifecycle_rules, bucket_name, @@ -1249,7 +1302,7 @@ def delete_object( if found_object: self._storage_backend.remove(bucket, found_object) self._notify(context, s3_bucket=s3_bucket, s3_object=found_object) - store.TAGS.tags.pop(get_unique_key_id(bucket, key, version_id), None) + store.tags.delete_all_tags(get_unique_key_id(bucket, key, version_id)) return DeleteObjectOutput() @@ -1257,7 +1310,7 @@ def delete_object( delete_marker_id = generate_version_id(s3_bucket.versioning_status) delete_marker = S3DeleteMarker(key=key, version_id=delete_marker_id) s3_bucket.objects.set(key, delete_marker) - s3_notif_ctx = S3EventNotificationContext.from_request_context_native( + s3_notif_ctx = S3EventNotificationContext.from_request_context( context, s3_bucket=s3_bucket, s3_object=delete_marker, @@ -1287,9 +1340,13 @@ def delete_object( response["DeleteMarker"] = True else: self._storage_backend.remove(bucket, s3_object) - store.TAGS.tags.pop(get_unique_key_id(bucket, key, version_id), None) + store.tags.delete_all_tags(get_unique_key_id(bucket, key, version_id)) self._notify(context, s3_bucket=s3_bucket, s3_object=s3_object) + if key not in s3_bucket.objects: + # we clean up keys that do not have any object versions in them anymore + self._preconditions_locks[bucket].pop(key, None) + return response def delete_objects( @@ -1323,6 +1380,7 @@ def delete_objects( errors = [] to_remove = [] + versioned_keys = set() for to_delete_object in objects: object_key = to_delete_object.get("Key") version_id = to_delete_object.get("VersionId") @@ -1342,7 +1400,7 @@ def delete_objects( if found_object: to_remove.append(found_object) self._notify(context, s3_bucket=s3_bucket, s3_object=found_object) - store.TAGS.tags.pop(get_unique_key_id(bucket, object_key, version_id), None) + store.tags.delete_all_tags(get_unique_key_id(bucket, object_key, version_id)) # small hack to not create a fake object for nothing elif s3_bucket.notification_configuration: # DeleteObjects is a bit weird, even if the object didn't exist, S3 will trigger a notification @@ -1360,7 +1418,7 @@ def delete_objects( delete_marker_id = generate_version_id(s3_bucket.versioning_status) delete_marker = S3DeleteMarker(key=object_key, version_id=delete_marker_id) s3_bucket.objects.set(object_key, delete_marker) - s3_notif_ctx = S3EventNotificationContext.from_request_context_native( + s3_notif_ctx = S3EventNotificationContext.from_request_context( context, s3_bucket=s3_bucket, s3_object=delete_marker, @@ -1403,6 +1461,8 @@ def delete_objects( continue s3_bucket.objects.pop(object_key=object_key, version_id=version_id) + versioned_keys.add(object_key) + if not quiet: deleted_object = DeletedObject( Key=object_key, @@ -1418,7 +1478,12 @@ def delete_objects( to_remove.append(found_object) self._notify(context, s3_bucket=s3_bucket, s3_object=found_object) - store.TAGS.tags.pop(get_unique_key_id(bucket, object_key, version_id), None) + store.tags.delete_all_tags(get_unique_key_id(bucket, object_key, version_id)) + + for versioned_key in versioned_keys: + # we clean up keys that do not have any object versions in them anymore + if versioned_key not in s3_bucket.objects: + self._preconditions_locks[bucket].pop(versioned_key, None) # TODO: request charged self._storage_backend.remove(bucket, to_remove) @@ -1440,6 +1505,25 @@ def copy_object( # request_payer: RequestPayer = None, # TODO: dest_bucket = request["Bucket"] dest_key = request["Key"] + + if_match = request.get("IfMatch") + if_none_match = request.get("IfNoneMatch") + + if if_none_match and if_match: + raise NotImplementedException( + "A header you provided implies functionality that is not implemented", + Header="If-Match,If-None-Match", + additionalMessage="Multiple conditional request headers present in the request", + ) + + elif (if_none_match and if_none_match != "*") or (if_match and if_match == "*"): + header_name = "If-None-Match" if if_none_match else "If-Match" + raise NotImplementedException( + "A header you provided implies functionality that is not implemented", + Header=header_name, + additionalMessage=f"We don't accept the provided value of {header_name} header for this API", + ) + validate_object_key(dest_key) store, dest_s3_bucket = self._get_cross_account_bucket(context, dest_bucket) @@ -1533,7 +1617,7 @@ def copy_object( tagging = parse_tagging_header(tagging) if metadata_directive == "REPLACE": - user_metadata = request.get("Metadata") + user_metadata = decode_user_metadata(request.get("Metadata")) system_metadata = get_system_metadata_from_request(request) if not system_metadata.get("ContentType"): system_metadata["ContentType"] = "binary/octet-stream" @@ -1542,6 +1626,12 @@ def copy_object( system_metadata = src_s3_object.system_metadata dest_version_id = generate_version_id(dest_s3_bucket.versioning_status) + if dest_version_id != "null": + # if we are in a versioned bucket, we need to lock around the full key (all the versions) + # because object versions have locks per version + precondition_lock = self._preconditions_locks[dest_bucket][dest_key] + else: + precondition_lock = contextlib.nullcontext() encryption_parameters = get_encryption_parameters_from_request_and_bucket( request, @@ -1581,12 +1671,25 @@ def copy_object( owner=dest_s3_bucket.owner, ) - with self._storage_backend.copy( - src_bucket=src_bucket, - src_object=src_s3_object, - dest_bucket=dest_bucket, - dest_object=s3_object, - ) as s3_stored_object: + with ( + precondition_lock, + self._storage_backend.copy( + src_bucket=src_bucket, + src_object=src_s3_object, + dest_bucket=dest_bucket, + dest_object=s3_object, + ) as s3_stored_object, + ): + # Check destination write preconditions inside the lock to prevent race conditions. + if if_none_match and object_exists_for_precondition_write(dest_s3_bucket, dest_key): + raise PreconditionFailed( + "At least one of the pre-conditions you specified did not hold", + Condition="If-None-Match", + ) + + elif if_match: + verify_object_equality_precondition_write(dest_s3_bucket, dest_key, if_match) + s3_object.checksum_value = s3_stored_object.checksum or src_s3_object.checksum_value s3_object.etag = s3_stored_object.etag or src_s3_object.etag @@ -1595,11 +1698,13 @@ def copy_object( dest_key_id = get_unique_key_id(dest_bucket, dest_key, dest_version_id) if (request.get("TaggingDirective")) == "REPLACE": - store.TAGS.tags[dest_key_id] = tagging or {} + store.tags.delete_all_tags(dest_key_id) + store.tags.update_tags(dest_key_id, tagging or {}) else: src_key_id = get_unique_key_id(src_bucket, src_key, src_s3_object.version_id) - src_tags = store.TAGS.tags.get(src_key_id, {}) - store.TAGS.tags[dest_key_id] = copy.copy(src_tags) + src_tags = store.tags.get_tags(src_key_id) + store.tags.delete_all_tags(dest_key_id) + store.tags.update_tags(dest_key_id, src_tags) copy_object_result = CopyObjectResult( ETag=s3_object.quoted_etag, @@ -1609,6 +1714,7 @@ def copy_object( copy_object_result[f"Checksum{s3_object.checksum_algorithm.upper()}"] = ( s3_object.checksum_value ) + copy_object_result["ChecksumType"] = s3_object.checksum_type response = CopyObjectOutput( CopyObjectResult=copy_object_result, @@ -1652,6 +1758,7 @@ def list_objects( **kwargs, ) -> ListObjectsOutput: store, s3_bucket = self._get_cross_account_bucket(context, bucket) + validate_encoding_type(encoding_type) common_prefixes = set() count = 0 @@ -1660,7 +1767,7 @@ def list_objects( max_keys = max_keys or 1000 prefix = prefix or "" delimiter = delimiter or "" - if encoding_type: + if encoding_type == EncodingType.url: prefix = urlparse.quote(prefix) delimiter = urlparse.quote(delimiter) @@ -1707,9 +1814,7 @@ def list_objects( if s3_object.checksum_algorithm: object_data["ChecksumAlgorithm"] = [s3_object.checksum_algorithm] - object_data["ChecksumType"] = getattr( - s3_object, "checksum_type", ChecksumType.FULL_OBJECT - ) + object_data["ChecksumType"] = s3_object.checksum_type s3_objects.append(object_data) @@ -1771,6 +1876,7 @@ def list_objects_v2( "The continuation token provided is incorrect", ArgumentName="continuation-token", ) + validate_encoding_type(encoding_type) common_prefixes = set() count = 0 @@ -1779,14 +1885,14 @@ def list_objects_v2( max_keys = max_keys or 1000 prefix = prefix or "" delimiter = delimiter or "" - if encoding_type: + start_after = start_after or "" + decoded_continuation_token = decode_continuation_token(continuation_token) + + if encoding_type == EncodingType.url: prefix = urlparse.quote(prefix) delimiter = urlparse.quote(delimiter) - decoded_continuation_token = ( - to_str(base64.urlsafe_b64decode(continuation_token.encode())) - if continuation_token - else None - ) + start_after = urlparse.quote(start_after) + decoded_continuation_token = urlparse.quote(decoded_continuation_token) s3_objects: list[Object] = [] @@ -1822,7 +1928,7 @@ def list_objects_v2( # After skipping all entries, verify we're not over the MaxKeys before adding a new entry if count >= max_keys: is_truncated = True - next_continuation_token = to_str(base64.urlsafe_b64encode(s3_object.key.encode())) + next_continuation_token = encode_continuation_token(s3_object.key) break # if we found a new CommonPrefix, add it to the CommonPrefixes @@ -1844,9 +1950,7 @@ def list_objects_v2( if s3_object.checksum_algorithm: object_data["ChecksumAlgorithm"] = [s3_object.checksum_algorithm] - object_data["ChecksumType"] = getattr( - s3_object, "checksum_type", ChecksumType.FULL_OBJECT - ) + object_data["ChecksumType"] = s3_object.checksum_type s3_objects.append(object_data) @@ -1905,6 +2009,7 @@ def list_object_versions( ArgumentName="version-id-marker", ArgumentValue=version_id_marker, ) + validate_encoding_type(encoding_type) store, s3_bucket = self._get_cross_account_bucket(context, bucket) common_prefixes = set() @@ -1915,7 +2020,7 @@ def list_object_versions( max_keys = max_keys or 1000 prefix = prefix or "" delimiter = delimiter or "" - if encoding_type: + if encoding_type == EncodingType.url: prefix = urlparse.quote(prefix) delimiter = urlparse.quote(delimiter) version_key_marker_found = False @@ -1996,9 +2101,7 @@ def list_object_versions( if version.checksum_algorithm: object_version["ChecksumAlgorithm"] = [version.checksum_algorithm] - object_version["ChecksumType"] = getattr( - version, "checksum_type", ChecksumType.FULL_OBJECT - ) + object_version["ChecksumType"] = version.checksum_type object_versions.append(object_version) @@ -2075,7 +2178,7 @@ def get_object_attributes( object_attrs = request.get("ObjectAttributes", []) response = GetObjectAttributesOutput() - object_checksum_type = getattr(s3_object, "checksum_type", ChecksumType.FULL_OBJECT) + object_checksum_type = s3_object.checksum_type if "ETag" in object_attrs: response["ETag"] = s3_object.etag if "StorageClass" in object_attrs: @@ -2111,18 +2214,11 @@ def get_object_attributes( max_parts = request.get("MaxParts") or 1000 parts = [] - all_parts = sorted(s3_object.parts.items()) + all_parts = sorted( + (int(part_number), part) for part_number, part in s3_object.parts.items() + ) last_part_number, last_part = all_parts[-1] - # TODO: remove this backward compatibility hack needed for state created with <= 4.5 - # the parts would only be a tuple and would not store the proper state for 4.5 and earlier, so we need - # to return early - if isinstance(last_part, tuple): - response["ObjectParts"] = GetObjectAttributesParts( - TotalPartsCount=len(s3_object.parts) - ) - return response - for part_number, part in all_parts: if part_number <= part_number_marker: continue @@ -2188,7 +2284,7 @@ def restore_object( # TODO: add a way to transition from ongoing-request=true to false? for now it is instant s3_object.restore = f'ongoing-request="false", expiry-date="{restore_expiration_date}"' - s3_notif_ctx_initiated = S3EventNotificationContext.from_request_context_native( + s3_notif_ctx_initiated = S3EventNotificationContext.from_request_context( context, s3_bucket=s3_bucket, s3_object=s3_object, @@ -2236,10 +2332,12 @@ def create_multipart_upload( if not system_metadata.get("ContentType"): system_metadata["ContentType"] = "binary/octet-stream" + user_metadata = decode_user_metadata(request.get("Metadata")) + checksum_algorithm = request.get("ChecksumAlgorithm") if checksum_algorithm and checksum_algorithm not in CHECKSUM_ALGORITHMS: raise InvalidRequest( - "Checksum algorithm provided is unsupported. Please try again with any of the valid types: [CRC32, CRC32C, SHA1, SHA256]" + "Checksum algorithm provided is unsupported. Please try again with any of the valid types: [CRC32, CRC32C, CRC64NVME, SHA1, SHA256]" ) if not (checksum_type := request.get("ChecksumType")) and checksum_algorithm: @@ -2284,12 +2382,16 @@ def create_multipart_upload( acl = get_access_control_policy_for_new_resource_request(request, owner=s3_bucket.owner) - # validate encryption values + initiator = get_owner_for_account_id(context.account_id) + # This is weird, but for all other operations, AWS does not return a DisplayName anymore except for the + # `initiator` field in Multipart related operation. We will probably remove this soon once AWS changes that + initiator["DisplayName"] = "webfile" + s3_multipart = S3Multipart( key=key, storage_class=storage_class, expires=request.get("Expires"), - user_metadata=request.get("Metadata"), + user_metadata=user_metadata, system_metadata=system_metadata, checksum_algorithm=checksum_algorithm, checksum_type=checksum_type, @@ -2303,7 +2405,7 @@ def create_multipart_upload( website_redirect_location=request.get("WebsiteRedirectLocation"), expiration=None, # TODO, from lifecycle, or should it be updated with config? acl=acl, - initiator=get_owner_for_account_id(context.account_id), + initiator=initiator, tagging=tagging, owner=s3_bucket.owner, precondition=object_exists_for_precondition_write(s3_bucket, key), @@ -2468,7 +2570,7 @@ def upload_part( CalculatedDigest=calculated_md5, ) - s3_multipart.parts[part_number] = s3_part + s3_multipart.parts[str(part_number)] = s3_part response = UploadPartOutput( ETag=s3_part.quoted_etag, @@ -2492,10 +2594,6 @@ def upload_part_copy( request: UploadPartCopyRequest, ) -> UploadPartCopyOutput: # TODO: handle following parameters: - # CopySourceIfMatch: Optional[CopySourceIfMatch] - # CopySourceIfModifiedSince: Optional[CopySourceIfModifiedSince] - # CopySourceIfNoneMatch: Optional[CopySourceIfNoneMatch] - # CopySourceIfUnmodifiedSince: Optional[CopySourceIfUnmodifiedSince] # SSECustomerAlgorithm: Optional[SSECustomerAlgorithm] # SSECustomerKey: Optional[SSECustomerKey] # SSECustomerKeyMD5: Optional[SSECustomerKeyMD5] @@ -2554,10 +2652,18 @@ def upload_part_copy( source_range = request.get("CopySourceRange") # TODO implement copy source IF - range_data: Optional[ObjectRange] = None + range_data: ObjectRange | None = None if source_range: range_data = parse_copy_source_range_header(source_range, src_s3_object.size) + if precondition := get_failed_upload_part_copy_source_preconditions( + request, src_s3_object.last_modified, src_s3_object.etag + ): + raise PreconditionFailed( + "At least one of the pre-conditions you specified did not hold", + Condition=precondition, + ) + s3_part = S3Part(part_number=part_number) if s3_multipart.checksum_algorithm: s3_part.checksum_algorithm = s3_multipart.checksum_algorithm @@ -2565,7 +2671,7 @@ def upload_part_copy( stored_multipart = self._storage_backend.get_multipart(dest_bucket, s3_multipart) stored_multipart.copy_from_object(s3_part, src_bucket, src_s3_object, range_data) - s3_multipart.parts[part_number] = s3_part + s3_multipart.parts[str(part_number)] = s3_part # TODO: return those fields # RequestCharged: Optional[RequestCharged] @@ -2631,6 +2737,7 @@ def complete_multipart_upload( ) elif if_none_match: + # TODO: improve concurrency mechanism for `if_none_match` and `if_match` if if_none_match != "*": raise NotImplementedException( "A header you provided implies functionality that is not implemented", @@ -2675,7 +2782,7 @@ def complete_multipart_upload( ) mpu_checksum_algorithm = s3_multipart.checksum_algorithm - mpu_checksum_type = getattr(s3_multipart, "checksum_type", None) + mpu_checksum_type = s3_multipart.checksum_type if checksum_type and checksum_type != mpu_checksum_type: raise InvalidRequest( @@ -2726,7 +2833,7 @@ def complete_multipart_upload( stored_multipart = self._storage_backend.get_multipart(bucket, s3_multipart) stored_multipart.complete_multipart( - [s3_multipart.parts.get(part_number) for part_number in parts_numbers] + [s3_multipart.parts.get(str(part_number)) for part_number in parts_numbers] ) if not s3_multipart.checksum_algorithm and s3_multipart.object.checksum_algorithm: with self._storage_backend.open( @@ -2744,9 +2851,9 @@ def complete_multipart_upload( s3_bucket.multiparts.pop(s3_multipart.id, None) key_id = get_unique_key_id(bucket, key, version_id) - store.TAGS.tags.pop(key_id, None) + store.tags.delete_all_tags(key_id) if s3_multipart.tagging: - store.TAGS.tags[key_id] = s3_multipart.tagging + store.tags.update_tags(key_id, s3_multipart.tagging) # RequestCharged: Optional[RequestCharged] TODO @@ -2754,7 +2861,7 @@ def complete_multipart_upload( Bucket=bucket, Key=key, ETag=s3_object.quoted_etag, - Location=f"{get_full_default_bucket_location(bucket)}{key}", + Location=get_url_encoded_object_location(bucket, key), ) if s3_object.version_id: @@ -2837,7 +2944,9 @@ def list_parts( max_parts = max_parts or 1000 parts = [] - all_parts = sorted(s3_multipart.parts.items()) + all_parts = sorted( + (int(part_number), part) for part_number, part in s3_multipart.parts.items() + ) last_part_number = all_parts[-1][0] if all_parts else None for part_number, part in all_parts: if part_number <= part_number_marker: @@ -2863,7 +2972,7 @@ def list_parts( Key=key, UploadId=upload_id, Initiator=s3_multipart.initiator, - Owner=s3_multipart.initiator, + Owner=s3_multipart.object.owner, StorageClass=s3_multipart.object.storage_class, IsTruncated=is_truncated, MaxParts=max_parts, @@ -2879,7 +2988,7 @@ def list_parts( response["PartNumberMarker"] = part_number_marker if s3_multipart.checksum_algorithm: response["ChecksumAlgorithm"] = s3_multipart.object.checksum_algorithm - response["ChecksumType"] = getattr(s3_multipart, "checksum_type", None) + response["ChecksumType"] = s3_multipart.checksum_type # AbortDate: Optional[AbortDate] TODO: lifecycle # AbortRuleId: Optional[AbortRuleId] TODO: lifecycle @@ -2902,6 +3011,7 @@ def list_multipart_uploads( **kwargs, ) -> ListMultipartUploadsOutput: store, s3_bucket = self._get_cross_account_bucket(context, bucket) + validate_encoding_type(encoding_type) common_prefixes = set() count = 0 @@ -2909,7 +3019,7 @@ def list_multipart_uploads( max_uploads = max_uploads or 1000 prefix = prefix or "" delimiter = delimiter or "" - if encoding_type: + if encoding_type == EncodingType.url: prefix = urlparse.quote(prefix) delimiter = urlparse.quote(delimiter) upload_id_marker_found = False @@ -2978,12 +3088,12 @@ def list_multipart_uploads( Key=multipart.object.key, Initiated=multipart.initiated, StorageClass=multipart.object.storage_class, - Owner=multipart.initiator, # TODO: check the difference + Owner=multipart.object.owner, Initiator=multipart.initiator, ) if multipart.checksum_algorithm: multipart_upload["ChecksumAlgorithm"] = multipart.checksum_algorithm - multipart_upload["ChecksumType"] = getattr(multipart, "checksum_type", None) + multipart_upload["ChecksumType"] = multipart.checksum_type uploads.append(multipart_upload) @@ -3175,11 +3285,12 @@ def put_bucket_tagging( if "TagSet" not in tagging: raise MalformedXML() - validate_tag_set(tagging["TagSet"], type_set="bucket") + tag_set = tagging["TagSet"] or [] + validate_tag_set(tag_set, type_set="bucket") # remove the previous tags before setting the new ones, it overwrites the whole TagSet - store.TAGS.tags.pop(s3_bucket.bucket_arn, None) - store.TAGS.tag_resource(s3_bucket.bucket_arn, tags=tagging["TagSet"]) + store.tags.delete_all_tags(s3_bucket.bucket_arn) + store.tags.update_tags(s3_bucket.bucket_arn, {tag["Key"]: tag["Value"] for tag in tag_set}) def get_bucket_tagging( self, @@ -3189,7 +3300,8 @@ def get_bucket_tagging( **kwargs, ) -> GetBucketTaggingOutput: store, s3_bucket = self._get_cross_account_bucket(context, bucket) - tag_set = store.TAGS.list_tags_for_resource(s3_bucket.bucket_arn, root_name="Tags")["Tags"] + tags = store.tags.get_tags(s3_bucket.bucket_arn) + tag_set = [{"Key": key, "Value": value} for key, value in dict(tags).items()] if not tag_set: raise NoSuchTagSet( "The TagSet does not exist", @@ -3207,7 +3319,10 @@ def delete_bucket_tagging( ) -> None: store, s3_bucket = self._get_cross_account_bucket(context, bucket) - store.TAGS.tags.pop(s3_bucket.bucket_arn, None) + # This operation doesn't remove the tags from the store like deleting a resource does + # it just sets them as empty. + store.tags.delete_all_tags(s3_bucket.bucket_arn) + store.tags.update_tags(s3_bucket.bucket_arn, {}) def put_object_tagging( self, @@ -3229,12 +3344,13 @@ def put_object_tagging( if "TagSet" not in tagging: raise MalformedXML() - validate_tag_set(tagging["TagSet"], type_set="object") + tag_set = tagging["TagSet"] or [] + validate_tag_set(tag_set, type_set="object") key_id = get_unique_key_id(bucket, key, s3_object.version_id) # remove the previous tags before setting the new ones, it overwrites the whole TagSet - store.TAGS.tags.pop(key_id, None) - store.TAGS.tag_resource(key_id, tags=tagging["TagSet"]) + store.tags.delete_all_tags(key_id) + store.tags.update_tags(key_id, {tag["Key"]: tag["Value"] for tag in tag_set}) response = PutObjectTaggingOutput() if s3_object.version_id: response["VersionId"] = s3_object.version_id @@ -3254,7 +3370,6 @@ def get_object_tagging( **kwargs, ) -> GetObjectTaggingOutput: store, s3_bucket = self._get_cross_account_bucket(context, bucket) - try: s3_object = s3_bucket.get_object(key=key, version_id=version_id) except NoSuchKey as e: @@ -3278,10 +3393,10 @@ def get_object_tagging( e.Key = f"{bucket}/{key}" raise e - tag_set = store.TAGS.list_tags_for_resource( - get_unique_key_id(bucket, key, s3_object.version_id) - )["Tags"] - response = GetObjectTaggingOutput(TagSet=tag_set) + object_tags = store.tags.get_tags(get_unique_key_id(bucket, key, s3_object.version_id)) + response = GetObjectTaggingOutput( + TagSet=[Tag(Key=key, Value=value) for key, value in object_tags.items()] + ) if s3_object.version_id: response["VersionId"] = s3_object.version_id @@ -3300,7 +3415,7 @@ def delete_object_tagging( s3_object = s3_bucket.get_object(key=key, version_id=version_id, http_method="DELETE") - store.TAGS.tags.pop(get_unique_key_id(bucket, key, version_id), None) + store.tags.delete_all_tags(get_unique_key_id(bucket, key, s3_object.version_id)) response = DeleteObjectTaggingOutput() if s3_object.version_id: response["VersionId"] = s3_object.version_id @@ -3370,12 +3485,7 @@ def get_bucket_lifecycle_configuration( return GetBucketLifecycleConfigurationOutput( Rules=s3_bucket.lifecycle_rules, - # TODO: remove for next major version, safe access to new attribute - TransitionDefaultMinimumObjectSize=getattr( - s3_bucket, - "transition_default_minimum_object_size", - TransitionDefaultMinimumObjectSize.all_storage_classes_128K, - ), + TransitionDefaultMinimumObjectSize=s3_bucket.transition_default_minimum_object_size, ) def put_bucket_lifecycle_configuration( @@ -3861,7 +3971,9 @@ def put_object_retention( if retention and retention["RetainUntilDate"] < datetime.datetime.now(datetime.UTC): # weirdly, this date is format as following: Tue Dec 31 16:00:00 PST 2019 # it contains the timezone as PST, even if you target a bucket in Europe or Asia - pst_datetime = retention["RetainUntilDate"].astimezone(tz=ZoneInfo("US/Pacific")) + pst_datetime = retention["RetainUntilDate"].astimezone( + tz=ZoneInfo("America/Los_Angeles") + ) raise InvalidArgument( "The retain until date must be in the future!", ArgumentName="RetainUntilDate", @@ -4414,7 +4526,8 @@ def post_object( system_metadata = {} for system_metadata_field in post_system_settable_headers: if field_value := form.get(system_metadata_field): - system_metadata[system_metadata_field.replace("-", "")] = field_value + system_key = system_metadata_field.replace("-", "") + system_metadata[system_key] = field_value if not system_metadata.get("ContentType"): system_metadata["ContentType"] = "binary/octet-stream" @@ -4491,9 +4604,9 @@ def post_object( # in case we are overriding an object, delete the tags entry key_id = get_unique_key_id(bucket, object_key, version_id) - store.TAGS.tags.pop(key_id, None) + store.tags.delete_all_tags(key_id) if tagging: - store.TAGS.tags[key_id] = tagging + store.tags.update_tags(key_id, tagging) response = PostResponse() # hacky way to set the etag in the headers as well: two locations for one value @@ -4520,7 +4633,8 @@ def post_object( response["StatusCode"] = 204 response["LocationHeader"] = response.get( - "LocationHeader", f"{get_full_default_bucket_location(bucket)}{object_key}" + "LocationHeader", + get_url_encoded_object_location(bucket, object_key), ) if s3_bucket.versioning_status == "Enabled": @@ -4535,7 +4649,7 @@ def post_object( s3_bucket.lifecycle_rules, bucket, s3_object, - store.TAGS.tags.get(key_id, {}), + store.tags.get_tags(key_id), ): # TODO: we either apply the lifecycle to existing objects when we set the new rules, or we need to # apply them everytime we get/head an object @@ -4805,7 +4919,7 @@ def get_part_range(s3_object: S3Object, part_number: PartNumber) -> ObjectRange: content_length=s3_object.size, content_range=f"bytes 0-{s3_object.size - 1}/{s3_object.size}", ) - elif not (part_data := s3_object.parts.get(part_number)): + elif not (part_data := s3_object.parts.get(str(part_number))): raise InvalidPartNumber( "The requested partnumber is not satisfiable", PartNumberRequested=part_number, @@ -4829,14 +4943,12 @@ def get_part_range(s3_object: S3Object, part_number: PartNumber) -> ObjectRange: def get_acl_headers_from_request( - request: Union[ - PutObjectRequest, - CreateMultipartUploadRequest, - CopyObjectRequest, - CreateBucketRequest, - PutBucketAclRequest, - PutObjectAclRequest, - ], + request: PutObjectRequest + | CreateMultipartUploadRequest + | CopyObjectRequest + | CreateBucketRequest + | PutBucketAclRequest + | PutObjectAclRequest, ) -> list[tuple[str, str]]: permission_keys = [ "GrantFullControl", @@ -4854,7 +4966,7 @@ def get_acl_headers_from_request( def get_access_control_policy_from_acl_request( - request: Union[PutBucketAclRequest, PutObjectAclRequest], + request: PutBucketAclRequest | PutObjectAclRequest, owner: Owner, request_body: bytes, ) -> AccessControlPolicy: @@ -4903,9 +5015,10 @@ def get_access_control_policy_from_acl_request( def get_access_control_policy_for_new_resource_request( - request: Union[ - PutObjectRequest, CreateMultipartUploadRequest, CopyObjectRequest, CreateBucketRequest - ], + request: PutObjectRequest + | CreateMultipartUploadRequest + | CopyObjectRequest + | CreateBucketRequest, owner: Owner, ) -> AccessControlPolicy: # TODO: this is basic ACL, not taking into account Bucket settings. Revisit once we really implement ACLs. diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py index de1573274b2b8..3737dec8f863f 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py @@ -3,7 +3,7 @@ import re from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -22,356 +22,356 @@ class S3BucketProperties(TypedDict): - AccelerateConfiguration: Optional[AccelerateConfiguration] - AccessControl: Optional[str] - AnalyticsConfigurations: Optional[list[AnalyticsConfiguration]] - Arn: Optional[str] - BucketEncryption: Optional[BucketEncryption] - BucketName: Optional[str] - CorsConfiguration: Optional[CorsConfiguration] - DomainName: Optional[str] - DualStackDomainName: Optional[str] - IntelligentTieringConfigurations: Optional[list[IntelligentTieringConfiguration]] - InventoryConfigurations: Optional[list[InventoryConfiguration]] - LifecycleConfiguration: Optional[LifecycleConfiguration] - LoggingConfiguration: Optional[LoggingConfiguration] - MetricsConfigurations: Optional[list[MetricsConfiguration]] - NotificationConfiguration: Optional[NotificationConfiguration] - ObjectLockConfiguration: Optional[ObjectLockConfiguration] - ObjectLockEnabled: Optional[bool] - OwnershipControls: Optional[OwnershipControls] - PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] - RegionalDomainName: Optional[str] - ReplicationConfiguration: Optional[ReplicationConfiguration] - Tags: Optional[list[Tag]] - VersioningConfiguration: Optional[VersioningConfiguration] - WebsiteConfiguration: Optional[WebsiteConfiguration] - WebsiteURL: Optional[str] + AccelerateConfiguration: AccelerateConfiguration | None + AccessControl: str | None + AnalyticsConfigurations: list[AnalyticsConfiguration] | None + Arn: str | None + BucketEncryption: BucketEncryption | None + BucketName: str | None + CorsConfiguration: CorsConfiguration | None + DomainName: str | None + DualStackDomainName: str | None + IntelligentTieringConfigurations: list[IntelligentTieringConfiguration] | None + InventoryConfigurations: list[InventoryConfiguration] | None + LifecycleConfiguration: LifecycleConfiguration | None + LoggingConfiguration: LoggingConfiguration | None + MetricsConfigurations: list[MetricsConfiguration] | None + NotificationConfiguration: NotificationConfiguration | None + ObjectLockConfiguration: ObjectLockConfiguration | None + ObjectLockEnabled: bool | None + OwnershipControls: OwnershipControls | None + PublicAccessBlockConfiguration: PublicAccessBlockConfiguration | None + RegionalDomainName: str | None + ReplicationConfiguration: ReplicationConfiguration | None + Tags: list[Tag] | None + VersioningConfiguration: VersioningConfiguration | None + WebsiteConfiguration: WebsiteConfiguration | None + WebsiteURL: str | None class AccelerateConfiguration(TypedDict): - AccelerationStatus: Optional[str] + AccelerationStatus: str | None class TagFilter(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class Destination(TypedDict): - BucketArn: Optional[str] - Format: Optional[str] - BucketAccountId: Optional[str] - Prefix: Optional[str] + BucketArn: str | None + Format: str | None + BucketAccountId: str | None + Prefix: str | None class DataExport(TypedDict): - Destination: Optional[Destination] - OutputSchemaVersion: Optional[str] + Destination: Destination | None + OutputSchemaVersion: str | None class StorageClassAnalysis(TypedDict): - DataExport: Optional[DataExport] + DataExport: DataExport | None class AnalyticsConfiguration(TypedDict): - Id: Optional[str] - StorageClassAnalysis: Optional[StorageClassAnalysis] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Id: str | None + StorageClassAnalysis: StorageClassAnalysis | None + Prefix: str | None + TagFilters: list[TagFilter] | None class ServerSideEncryptionByDefault(TypedDict): - SSEAlgorithm: Optional[str] - KMSMasterKeyID: Optional[str] + SSEAlgorithm: str | None + KMSMasterKeyID: str | None class ServerSideEncryptionRule(TypedDict): - BucketKeyEnabled: Optional[bool] - ServerSideEncryptionByDefault: Optional[ServerSideEncryptionByDefault] + BucketKeyEnabled: bool | None + ServerSideEncryptionByDefault: ServerSideEncryptionByDefault | None class BucketEncryption(TypedDict): - ServerSideEncryptionConfiguration: Optional[list[ServerSideEncryptionRule]] + ServerSideEncryptionConfiguration: list[ServerSideEncryptionRule] | None class CorsRule(TypedDict): - AllowedMethods: Optional[list[str]] - AllowedOrigins: Optional[list[str]] - AllowedHeaders: Optional[list[str]] - ExposedHeaders: Optional[list[str]] - Id: Optional[str] - MaxAge: Optional[int] + AllowedMethods: list[str] | None + AllowedOrigins: list[str] | None + AllowedHeaders: list[str] | None + ExposedHeaders: list[str] | None + Id: str | None + MaxAge: int | None class CorsConfiguration(TypedDict): - CorsRules: Optional[list[CorsRule]] + CorsRules: list[CorsRule] | None class Tiering(TypedDict): - AccessTier: Optional[str] - Days: Optional[int] + AccessTier: str | None + Days: int | None class IntelligentTieringConfiguration(TypedDict): - Id: Optional[str] - Status: Optional[str] - Tierings: Optional[list[Tiering]] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Id: str | None + Status: str | None + Tierings: list[Tiering] | None + Prefix: str | None + TagFilters: list[TagFilter] | None class InventoryConfiguration(TypedDict): - Destination: Optional[Destination] - Enabled: Optional[bool] - Id: Optional[str] - IncludedObjectVersions: Optional[str] - ScheduleFrequency: Optional[str] - OptionalFields: Optional[list[str]] - Prefix: Optional[str] + Destination: Destination | None + Enabled: bool | None + Id: str | None + IncludedObjectVersions: str | None + ScheduleFrequency: str | None + OptionalFields: list[str] | None + Prefix: str | None class AbortIncompleteMultipartUpload(TypedDict): - DaysAfterInitiation: Optional[int] + DaysAfterInitiation: int | None class NoncurrentVersionExpiration(TypedDict): - NoncurrentDays: Optional[int] - NewerNoncurrentVersions: Optional[int] + NoncurrentDays: int | None + NewerNoncurrentVersions: int | None class NoncurrentVersionTransition(TypedDict): - StorageClass: Optional[str] - TransitionInDays: Optional[int] - NewerNoncurrentVersions: Optional[int] + StorageClass: str | None + TransitionInDays: int | None + NewerNoncurrentVersions: int | None class Transition(TypedDict): - StorageClass: Optional[str] - TransitionDate: Optional[str] - TransitionInDays: Optional[int] + StorageClass: str | None + TransitionDate: str | None + TransitionInDays: int | None class Rule(TypedDict): - Status: Optional[str] - AbortIncompleteMultipartUpload: Optional[AbortIncompleteMultipartUpload] - ExpirationDate: Optional[str] - ExpirationInDays: Optional[int] - ExpiredObjectDeleteMarker: Optional[bool] - Id: Optional[str] - NoncurrentVersionExpiration: Optional[NoncurrentVersionExpiration] - NoncurrentVersionExpirationInDays: Optional[int] - NoncurrentVersionTransition: Optional[NoncurrentVersionTransition] - NoncurrentVersionTransitions: Optional[list[NoncurrentVersionTransition]] - ObjectSizeGreaterThan: Optional[str] - ObjectSizeLessThan: Optional[str] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] - Transition: Optional[Transition] - Transitions: Optional[list[Transition]] + Status: str | None + AbortIncompleteMultipartUpload: AbortIncompleteMultipartUpload | None + ExpirationDate: str | None + ExpirationInDays: int | None + ExpiredObjectDeleteMarker: bool | None + Id: str | None + NoncurrentVersionExpiration: NoncurrentVersionExpiration | None + NoncurrentVersionExpirationInDays: int | None + NoncurrentVersionTransition: NoncurrentVersionTransition | None + NoncurrentVersionTransitions: list[NoncurrentVersionTransition] | None + ObjectSizeGreaterThan: str | None + ObjectSizeLessThan: str | None + Prefix: str | None + TagFilters: list[TagFilter] | None + Transition: Transition | None + Transitions: list[Transition] | None class LifecycleConfiguration(TypedDict): - Rules: Optional[list[Rule]] + Rules: list[Rule] | None class LoggingConfiguration(TypedDict): - DestinationBucketName: Optional[str] - LogFilePrefix: Optional[str] + DestinationBucketName: str | None + LogFilePrefix: str | None class MetricsConfiguration(TypedDict): - Id: Optional[str] - AccessPointArn: Optional[str] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Id: str | None + AccessPointArn: str | None + Prefix: str | None + TagFilters: list[TagFilter] | None class EventBridgeConfiguration(TypedDict): - EventBridgeEnabled: Optional[bool] + EventBridgeEnabled: bool | None class FilterRule(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class S3KeyFilter(TypedDict): - Rules: Optional[list[FilterRule]] + Rules: list[FilterRule] | None class NotificationFilter(TypedDict): - S3Key: Optional[S3KeyFilter] + S3Key: S3KeyFilter | None class LambdaConfiguration(TypedDict): - Event: Optional[str] - Function: Optional[str] - Filter: Optional[NotificationFilter] + Event: str | None + Function: str | None + Filter: NotificationFilter | None class QueueConfiguration(TypedDict): - Event: Optional[str] - Queue: Optional[str] - Filter: Optional[NotificationFilter] + Event: str | None + Queue: str | None + Filter: NotificationFilter | None class TopicConfiguration(TypedDict): - Event: Optional[str] - Topic: Optional[str] - Filter: Optional[NotificationFilter] + Event: str | None + Topic: str | None + Filter: NotificationFilter | None class NotificationConfiguration(TypedDict): - EventBridgeConfiguration: Optional[EventBridgeConfiguration] - LambdaConfigurations: Optional[list[LambdaConfiguration]] - QueueConfigurations: Optional[list[QueueConfiguration]] - TopicConfigurations: Optional[list[TopicConfiguration]] + EventBridgeConfiguration: EventBridgeConfiguration | None + LambdaConfigurations: list[LambdaConfiguration] | None + QueueConfigurations: list[QueueConfiguration] | None + TopicConfigurations: list[TopicConfiguration] | None class DefaultRetention(TypedDict): - Days: Optional[int] - Mode: Optional[str] - Years: Optional[int] + Days: int | None + Mode: str | None + Years: int | None class ObjectLockRule(TypedDict): - DefaultRetention: Optional[DefaultRetention] + DefaultRetention: DefaultRetention | None class ObjectLockConfiguration(TypedDict): - ObjectLockEnabled: Optional[str] - Rule: Optional[ObjectLockRule] + ObjectLockEnabled: str | None + Rule: ObjectLockRule | None class OwnershipControlsRule(TypedDict): - ObjectOwnership: Optional[str] + ObjectOwnership: str | None class OwnershipControls(TypedDict): - Rules: Optional[list[OwnershipControlsRule]] + Rules: list[OwnershipControlsRule] | None class PublicAccessBlockConfiguration(TypedDict): - BlockPublicAcls: Optional[bool] - BlockPublicPolicy: Optional[bool] - IgnorePublicAcls: Optional[bool] - RestrictPublicBuckets: Optional[bool] + BlockPublicAcls: bool | None + BlockPublicPolicy: bool | None + IgnorePublicAcls: bool | None + RestrictPublicBuckets: bool | None class DeleteMarkerReplication(TypedDict): - Status: Optional[str] + Status: str | None class AccessControlTranslation(TypedDict): - Owner: Optional[str] + Owner: str | None class EncryptionConfiguration(TypedDict): - ReplicaKmsKeyID: Optional[str] + ReplicaKmsKeyID: str | None class ReplicationTimeValue(TypedDict): - Minutes: Optional[int] + Minutes: int | None class Metrics(TypedDict): - Status: Optional[str] - EventThreshold: Optional[ReplicationTimeValue] + Status: str | None + EventThreshold: ReplicationTimeValue | None class ReplicationTime(TypedDict): - Status: Optional[str] - Time: Optional[ReplicationTimeValue] + Status: str | None + Time: ReplicationTimeValue | None class ReplicationDestination(TypedDict): - Bucket: Optional[str] - AccessControlTranslation: Optional[AccessControlTranslation] - Account: Optional[str] - EncryptionConfiguration: Optional[EncryptionConfiguration] - Metrics: Optional[Metrics] - ReplicationTime: Optional[ReplicationTime] - StorageClass: Optional[str] + Bucket: str | None + AccessControlTranslation: AccessControlTranslation | None + Account: str | None + EncryptionConfiguration: EncryptionConfiguration | None + Metrics: Metrics | None + ReplicationTime: ReplicationTime | None + StorageClass: str | None class ReplicationRuleAndOperator(TypedDict): - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Prefix: str | None + TagFilters: list[TagFilter] | None class ReplicationRuleFilter(TypedDict): - And: Optional[ReplicationRuleAndOperator] - Prefix: Optional[str] - TagFilter: Optional[TagFilter] + And: ReplicationRuleAndOperator | None + Prefix: str | None + TagFilter: TagFilter | None class ReplicaModifications(TypedDict): - Status: Optional[str] + Status: str | None class SseKmsEncryptedObjects(TypedDict): - Status: Optional[str] + Status: str | None class SourceSelectionCriteria(TypedDict): - ReplicaModifications: Optional[ReplicaModifications] - SseKmsEncryptedObjects: Optional[SseKmsEncryptedObjects] + ReplicaModifications: ReplicaModifications | None + SseKmsEncryptedObjects: SseKmsEncryptedObjects | None class ReplicationRule(TypedDict): - Destination: Optional[ReplicationDestination] - Status: Optional[str] - DeleteMarkerReplication: Optional[DeleteMarkerReplication] - Filter: Optional[ReplicationRuleFilter] - Id: Optional[str] - Prefix: Optional[str] - Priority: Optional[int] - SourceSelectionCriteria: Optional[SourceSelectionCriteria] + Destination: ReplicationDestination | None + Status: str | None + DeleteMarkerReplication: DeleteMarkerReplication | None + Filter: ReplicationRuleFilter | None + Id: str | None + Prefix: str | None + Priority: int | None + SourceSelectionCriteria: SourceSelectionCriteria | None class ReplicationConfiguration(TypedDict): - Role: Optional[str] - Rules: Optional[list[ReplicationRule]] + Role: str | None + Rules: list[ReplicationRule] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class VersioningConfiguration(TypedDict): - Status: Optional[str] + Status: str | None class RedirectRule(TypedDict): - HostName: Optional[str] - HttpRedirectCode: Optional[str] - Protocol: Optional[str] - ReplaceKeyPrefixWith: Optional[str] - ReplaceKeyWith: Optional[str] + HostName: str | None + HttpRedirectCode: str | None + Protocol: str | None + ReplaceKeyPrefixWith: str | None + ReplaceKeyWith: str | None class RoutingRuleCondition(TypedDict): - HttpErrorCodeReturnedEquals: Optional[str] - KeyPrefixEquals: Optional[str] + HttpErrorCodeReturnedEquals: str | None + KeyPrefixEquals: str | None class RoutingRule(TypedDict): - RedirectRule: Optional[RedirectRule] - RoutingRuleCondition: Optional[RoutingRuleCondition] + RedirectRule: RedirectRule | None + RoutingRuleCondition: RoutingRuleCondition | None class RedirectAllRequestsTo(TypedDict): - HostName: Optional[str] - Protocol: Optional[str] + HostName: str | None + Protocol: str | None class WebsiteConfiguration(TypedDict): - ErrorDocument: Optional[str] - IndexDocument: Optional[str] - RedirectAllRequestsTo: Optional[RedirectAllRequestsTo] - RoutingRules: Optional[list[RoutingRule]] + ErrorDocument: str | None + IndexDocument: str | None + RedirectAllRequestsTo: RedirectAllRequestsTo | None + RoutingRules: list[RoutingRule] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py index d79e772ca7a65..c08b38facd9c3 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class S3BucketProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::S3::Bucket" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.s3.resource_providers.aws_s3_bucket import S3BucketProvider diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py index 78c5db3544efa..fa585d7f5fdec 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,9 +17,9 @@ class S3BucketPolicyProperties(TypedDict): - Bucket: Optional[str] - PolicyDocument: Optional[dict] - Id: Optional[str] + Bucket: str | None + PolicyDocument: dict | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py index 1589f69b38ad6..3d0f58bdd4e19 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class S3BucketPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::S3::BucketPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.s3.resource_providers.aws_s3_bucketpolicy import ( diff --git a/localstack-core/localstack/services/s3/storage/core.py b/localstack-core/localstack/services/s3/storage/core.py index d925f3cfc2b7e..7559c0090543c 100644 --- a/localstack-core/localstack/services/s3/storage/core.py +++ b/localstack-core/localstack/services/s3/storage/core.py @@ -1,8 +1,9 @@ import abc +from collections.abc import Iterable, Iterator from io import RawIOBase -from typing import IO, Iterable, Iterator, Literal, Optional +from typing import IO, Literal -from localstack.aws.api.s3 import BucketName, PartNumber +from localstack.aws.api.s3 import BucketName, PartNumber, Parts from localstack.services.s3.models import S3Multipart, S3Object, S3Part from localstack.services.s3.utils import ObjectRange @@ -108,7 +109,7 @@ def last_modified(self) -> int: @property @abc.abstractmethod - def checksum(self) -> Optional[str]: + def checksum(self) -> str | None: if not self.s3_object.checksum_algorithm: return None @@ -157,7 +158,7 @@ def remove_part(self, s3_part: S3Part): pass @abc.abstractmethod - def complete_multipart(self, parts: list[PartNumber]) -> None: + def complete_multipart(self, parts: list[Parts]) -> None: pass @abc.abstractmethod @@ -170,7 +171,7 @@ def copy_from_object( s3_part: S3Part, src_bucket: BucketName, src_s3_object: S3Object, - range_data: Optional[ObjectRange], + range_data: ObjectRange | None, ) -> None: pass diff --git a/localstack-core/localstack/services/s3/storage/ephemeral.py b/localstack-core/localstack/services/s3/storage/ephemeral.py index 64fc3440d7996..ebc21c61215b2 100644 --- a/localstack-core/localstack/services/s3/storage/ephemeral.py +++ b/localstack-core/localstack/services/s3/storage/ephemeral.py @@ -4,11 +4,12 @@ import threading import time from collections import defaultdict +from collections.abc import Iterator from io import BytesIO, UnsupportedOperation from shutil import rmtree from tempfile import SpooledTemporaryFile, mkdtemp from threading import RLock -from typing import IO, Iterator, Literal, Optional, TypedDict +from typing import IO, Literal, TypedDict from readerwriterlock import rwlock @@ -61,9 +62,9 @@ class EphemeralS3StoredObject(S3StoredObject): file: LockedSpooledTemporaryFile size: int _pos: int - etag: Optional[str] - checksum_hash: Optional[ChecksumHash] - _checksum: Optional[str] + etag: str | None + checksum_hash: ChecksumHash | None + _checksum: str | None _lock: rwlock.Lockable def __init__( @@ -202,7 +203,7 @@ def last_modified(self) -> int: return self.file.internal_last_modified @property - def checksum(self) -> Optional[str]: + def checksum(self) -> str | None: """ Return the object checksum base64 encoded, if the S3Object has a checksum algorithm. If the checksum hasn't been calculated, this method will iterate over the file again to recalculate it. @@ -323,7 +324,7 @@ def copy_from_object( s3_part: S3Part, src_bucket: BucketName, src_s3_object: S3Object, - range_data: Optional[ObjectRange], + range_data: ObjectRange | None, ) -> None: """ Create and add an EphemeralS3StoredObject to the Multipart collection, with an S3Object as input. This will diff --git a/localstack-core/localstack/services/s3/utils.py b/localstack-core/localstack/services/s3/utils.py index 8592de4712594..7114afd748837 100644 --- a/localstack-core/localstack/services/s3/utils.py +++ b/localstack-core/localstack/services/s3/utils.py @@ -7,9 +7,10 @@ import re import time import zlib +from collections.abc import Mapping from enum import StrEnum from secrets import token_bytes -from typing import Any, Dict, Literal, NamedTuple, Optional, Protocol, Tuple, Union +from typing import Any, Literal, NamedTuple, Protocol from urllib import parse as urlparser from zoneinfo import ZoneInfo @@ -33,6 +34,7 @@ Grantee, HeadObjectRequest, InvalidArgument, + InvalidLocationConstraint, InvalidRange, InvalidTag, LifecycleExpiration, @@ -50,24 +52,32 @@ SSEKMSKeyId, TaggingHeader, TagSet, + UploadPartCopyRequest, UploadPartRequest, ) from localstack.aws.api.s3 import Type as GranteeType from localstack.aws.chain import HandlerChain from localstack.aws.connect import connect_to +from localstack.constants import AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1 from localstack.http import Response from localstack.services.s3 import checksums from localstack.services.s3.constants import ( ALL_USERS_ACL_GRANTEE, AUTHENTICATED_USERS_ACL_GRANTEE, + BUCKET_LOCATION_CONSTRAINTS, CHECKSUM_ALGORITHMS, + EU_WEST_1_LOCATION_CONSTRAINTS, LOG_DELIVERY_ACL_GRANTEE, - S3_VIRTUAL_HOST_FORWARDED_HEADER, SIGNATURE_V2_PARAMS, SIGNATURE_V4_PARAMS, SYSTEM_METADATA_SETTABLE_HEADERS, ) -from localstack.services.s3.exceptions import InvalidRequest, MalformedXML +from localstack.services.s3.exceptions import ( + IllegalLocationConstraintException, + InvalidRequest, + MalformedXML, +) +from localstack.services.s3.headers import decode_header_rfc2047, encode_header_rfc2047 from localstack.utils.aws import arns from localstack.utils.aws.arns import parse_arn from localstack.utils.objects import singleton_factory @@ -136,14 +146,13 @@ def get_owner_for_account_id(account_id: str): :return: the Owner object containing the DisplayName and owner ID """ return Owner( - DisplayName="webfile", # only in certain regions, see above ID="75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a", ) def extract_bucket_key_version_id_from_copy_source( copy_source: CopySource, -) -> tuple[BucketName, ObjectKey, Optional[ObjectVersionId]]: +) -> tuple[BucketName, ObjectKey, ObjectVersionId | None]: """ Utility to parse bucket name, object key and optionally its versionId. It accepts the CopySource format: - ?versionId=, used for example in CopySource for CopyObject @@ -403,6 +412,46 @@ def parse_copy_source_range_header(copy_source_range: str, object_size: int) -> ) +def get_failed_upload_part_copy_source_preconditions( + request: UploadPartCopyRequest, last_modified: datetime.datetime, etag: ETag +) -> str | None: + """ + Utility which parses the conditions from a S3 UploadPartCopy request. + Note: The order in which these conditions are checked if used in conjunction matters + + :param UploadPartCopyRequest request: The S3 UploadPartCopy request. + :param datetime last_modified: The time the source object was last modified. + :param ETag etag: The ETag of the source object. + + :returns: The name of the failed precondition. + """ + if_match = request.get("CopySourceIfMatch") + if_none_match = request.get("CopySourceIfNoneMatch") + if_unmodified_since = request.get("CopySourceIfUnmodifiedSince") + if_modified_since = request.get("CopySourceIfModifiedSince") + last_modified = second_resolution_datetime(last_modified) + + if if_match: + if if_match.strip('"') != etag.strip('"'): + return "x-amz-copy-source-If-Match" + if if_modified_since and if_modified_since > last_modified: + return "x-amz-copy-source-If-Modified-Since" + # CopySourceIfMatch is unaffected by CopySourceIfUnmodifiedSince so return early + if if_unmodified_since: + return None + + if if_unmodified_since and second_resolution_datetime(if_unmodified_since) < last_modified: + return "x-amz-copy-source-If-Unmodified-Since" + + if if_none_match and if_none_match.strip('"') == etag.strip('"'): + return "x-amz-copy-source-If-None-Match" + + if if_modified_since and last_modified <= second_resolution_datetime( + if_modified_since + ) < datetime.datetime.now(tz=_gmt_zone_info): + return "x-amz-copy-source-If-Modified-Since" + + def get_full_default_bucket_location(bucket_name: BucketName) -> str: host_definition = localstack_host() if host_definition.host != constants.LOCALHOST_HOSTNAME: @@ -413,6 +462,10 @@ def get_full_default_bucket_location(bucket_name: BucketName) -> str: return f"{config.get_protocol()}://{bucket_name}.s3.{host_definition.host_and_port()}/" +def get_url_encoded_object_location(bucket_name: BucketName, object_key: str) -> str: + return f"{get_full_default_bucket_location(bucket_name)}{urlparser.quote(object_key)}" + + def etag_to_base_64_content_md5(etag: ETag) -> str: """ Convert an ETag, representing a MD5 hexdigest (might be quoted), to its base64 encoded representation @@ -482,7 +535,7 @@ def is_valid_canonical_id(canonical_id: str) -> bool: return False -def uses_host_addressing(headers: Dict[str, str]) -> str | None: +def uses_host_addressing(headers: Mapping[str, str]) -> str | None: """ Determines if the request is targeting S3 with virtual host addressing :param headers: the request headers @@ -511,18 +564,23 @@ def get_system_metadata_from_request(request: dict) -> Metadata: return metadata -def forwarded_from_virtual_host_addressed_request(headers: dict[str, str]) -> bool: - """ - Determines if the request was forwarded from a v-host addressing style into a path one - """ - # we can assume that the host header we are receiving here is actually the header we originally received - # from the client (because the edge service is forwarding the request in memory) - return S3_VIRTUAL_HOST_FORWARDED_HEADER in headers +def encode_user_metadata(metadata: Metadata) -> Metadata: + """Encode the user metadata in the RFC 2047 format if necessary so that it can be returned in HTTP headers""" + return {k: encode_header_rfc2047(v) for k, v in metadata.items()} + + +def decode_user_metadata(metadata: Metadata | None) -> Metadata: + """Decode the user metadata if provided in the RFC2047 format, or leave as is if not. AWS also lowercase the + metadata key""" + if not metadata: + return {} + + return {k.lower(): decode_header_rfc2047(v) for k, v in metadata.items()} def extract_bucket_name_and_key_from_headers_and_path( headers: dict[str, str], path: str -) -> tuple[Optional[str], Optional[str]]: +) -> tuple[str | None, str | None]: """ Extract the bucket name and the object key from a request headers and path. This works with both virtual host and path style requests. @@ -556,7 +614,7 @@ def normalize_bucket_name(bucket_name): return bucket_name -def get_bucket_and_key_from_s3_uri(s3_uri: str) -> Tuple[str, str]: +def get_bucket_and_key_from_s3_uri(s3_uri: str) -> tuple[str, str]: """ Extracts the bucket name and key from s3 uri """ @@ -564,7 +622,7 @@ def get_bucket_and_key_from_s3_uri(s3_uri: str) -> Tuple[str, str]: return output_bucket, output_key -def get_bucket_and_key_from_presign_url(presign_url: str) -> Tuple[str, str]: +def get_bucket_and_key_from_presign_url(presign_url: str) -> tuple[str, str]: """ Extracts the bucket name and key from s3 presign url """ @@ -574,22 +632,15 @@ def get_bucket_and_key_from_presign_url(presign_url: str) -> Tuple[str, str]: return bucket, key -def _create_invalid_argument_exc( - message: Union[str, None], name: str, value: str, host_id: str = None -) -> InvalidArgument: - ex = InvalidArgument(message) - ex.ArgumentName = name - ex.ArgumentValue = value - if host_id: - ex.HostId = host_id - return ex - - def capitalize_header_name_from_snake_case(header_name: str) -> str: return "-".join([part.capitalize() for part in header_name.split("-")]) -def get_kms_key_arn(kms_key: str, account_id: str, bucket_region: str) -> Optional[str]: +def header_name_from_capitalized_param(param_name: str) -> str: + return "-".join(re.findall("[A-Z][^A-Z]*", param_name)).lower() + + +def get_kms_key_arn(kms_key: str, account_id: str, bucket_region: str) -> str | None: """ In S3, the KMS key can be passed as a KeyId or a KeyArn. This method allows to always get the KeyArn from either. It can also validate if the key is in the same region, and raise an exception. @@ -681,6 +732,10 @@ def str_to_rfc_1123_datetime(value: str) -> datetime.datetime: return datetime.datetime.strptime(value, RFC1123).replace(tzinfo=_gmt_zone_info) +def second_resolution_datetime(src: datetime.datetime) -> datetime.datetime: + return src.replace(microsecond=0) + + def add_expiration_days_to_datetime(user_datatime: datetime.datetime, exp_days: int) -> str: """ This adds expiration days to a datetime, rounding to the next day at midnight UTC. @@ -762,7 +817,7 @@ def _match_lifecycle_filter( def parse_expiration_header( expiration_header: str, -) -> tuple[Optional[datetime.datetime], Optional[str]]: +) -> tuple[datetime.datetime | None, str | None]: try: header_values = dict( (p.strip('"') for p in v.split("=")) for v in expiration_header.split('", ') @@ -816,13 +871,20 @@ def parse_tagging_header(tagging_header: TaggingHeader) -> dict: ) -def validate_tag_set(tag_set: TagSet, type_set: Literal["bucket", "object"] = "bucket"): +def validate_tag_set( + tag_set: TagSet, type_set: Literal["bucket", "object", "create-bucket"] = "bucket" +): keys = set() for tag in tag_set: if set(tag) != {"Key", "Value"}: raise MalformedXML() key = tag["Key"] + value = tag["Value"] + + if key is None or value is None: + raise MalformedXML() + if key in keys: raise InvalidTag( "Cannot provide multiple Tags with the same key", @@ -832,11 +894,15 @@ def validate_tag_set(tag_set: TagSet, type_set: Literal["bucket", "object"] = "b if key.startswith("aws:"): if type_set == "bucket": message = "System tags cannot be added/updated by requester" - else: + elif type_set == "object": message = "Your TagKey cannot be prefixed with aws:" + else: + message = 'User-defined tag keys can\'t start with "aws:". This prefix is reserved for system tags. Remove "aws:" from your tag keys and try again.' raise InvalidTag( message, - TagKey=key, + # weirdly, AWS does not return the `TagKey` field here, but it does if the TagKey does not match the + # regex in the next step + TagKey=key if type_set != "create-bucket" else None, ) if not TAG_REGEX.match(key): @@ -844,14 +910,35 @@ def validate_tag_set(tag_set: TagSet, type_set: Literal["bucket", "object"] = "b "The TagKey you have provided is invalid", TagKey=key, ) - elif not TAG_REGEX.match(tag["Value"]): + elif not TAG_REGEX.match(value): raise InvalidTag( - "The TagValue you have provided is invalid", TagKey=key, TagValue=tag["Value"] + "The TagValue you have provided is invalid", TagKey=key, TagValue=value ) keys.add(key) +def validate_location_constraint(context_region: str, location_constraint: str) -> None: + if location_constraint: + if context_region == AWS_REGION_US_EAST_1: + if ( + not config.ALLOW_NONSTANDARD_REGIONS + and location_constraint not in BUCKET_LOCATION_CONSTRAINTS + ): + raise InvalidLocationConstraint( + "The specified location-constraint is not valid", + LocationConstraint=location_constraint, + ) + elif context_region == AWS_REGION_EU_WEST_1: + if location_constraint not in EU_WEST_1_LOCATION_CONSTRAINTS: + raise IllegalLocationConstraintException(location_constraint) + elif context_region != location_constraint: + raise IllegalLocationConstraintException(location_constraint) + else: + if context_region != AWS_REGION_US_EAST_1: + raise IllegalLocationConstraintException("unspecified") + + def get_unique_key_id( bucket: BucketName, object_key: ObjectKey, version_id: ObjectVersionId ) -> str: @@ -878,7 +965,7 @@ def get_retention_from_now(days: int = None, years: int = None) -> datetime.date def get_failed_precondition_copy_source( request: CopyObjectRequest, last_modified: datetime.datetime, etag: ETag -) -> Optional[str]: +) -> str | None: """ Validate if the source object LastModified and ETag matches a precondition, and if it does, return the failed precondition @@ -888,6 +975,7 @@ def get_failed_precondition_copy_source( :param etag: source object ETag :return str: the failed precondition to raise """ + last_modified = second_resolution_datetime(last_modified) if (cs_if_match := request.get("CopySourceIfMatch")) and etag.strip('"') != cs_if_match.strip( '"' ): @@ -895,7 +983,7 @@ def get_failed_precondition_copy_source( elif ( cs_if_unmodified_since := request.get("CopySourceIfUnmodifiedSince") - ) and last_modified > cs_if_unmodified_since: + ) and last_modified > second_resolution_datetime(cs_if_unmodified_since): return "x-amz-copy-source-If-Unmodified-Since" elif (cs_if_none_match := request.get("CopySourceIfNoneMatch")) and etag.strip( @@ -905,7 +993,9 @@ def get_failed_precondition_copy_source( elif ( cs_if_modified_since := request.get("CopySourceIfModifiedSince") - ) and last_modified < cs_if_modified_since < datetime.datetime.now(tz=_gmt_zone_info): + ) and last_modified <= second_resolution_datetime(cs_if_modified_since) < datetime.datetime.now( + tz=_gmt_zone_info + ): return "x-amz-copy-source-If-Modified-Since" @@ -923,13 +1013,13 @@ def validate_failed_precondition( """ precondition_failed = None # last_modified needs to be rounded to a second so that strict equality can be enforced from a RFC1123 header - last_modified = last_modified.replace(microsecond=0) + last_modified = second_resolution_datetime(last_modified) if (if_match := request.get("IfMatch")) and etag != if_match.strip('"'): precondition_failed = "If-Match" elif ( if_unmodified_since := request.get("IfUnmodifiedSince") - ) and last_modified > if_unmodified_since: + ) and last_modified > second_resolution_datetime(if_unmodified_since): precondition_failed = "If-Unmodified-Since" if precondition_failed: @@ -940,7 +1030,9 @@ def validate_failed_precondition( if ((if_none_match := request.get("IfNoneMatch")) and etag == if_none_match.strip('"')) or ( (if_modified_since := request.get("IfModifiedSince")) - and last_modified <= if_modified_since < datetime.datetime.now(tz=_gmt_zone_info) + and last_modified + <= second_resolution_datetime(if_modified_since) + < datetime.datetime.now(tz=_gmt_zone_info) ): raise CommonServiceException( message="Not Modified", @@ -1019,7 +1111,7 @@ def create_redirect_for_post_request( return urlparser.urlunparse(newparts) -def parse_post_object_tagging_xml(tagging: str) -> Optional[dict]: +def parse_post_object_tagging_xml(tagging: str) -> dict | None: try: tag_set = {} tags = xmltodict.parse(tagging) @@ -1064,3 +1156,39 @@ def is_version_older_than_other(version_id: str, other: str): See `generate_safe_version_id` """ return base64.b64decode(version_id, altchars=b"._") < base64.b64decode(other, altchars=b"._") + + +def get_bucket_location_xml(location_constraint: str) -> str: + """ + Returns the formatted XML for the GetBucketLocation operation. + + :param location_constraint: The location constraint to return in the XML. It can be an empty string when + it's not specified in the bucket configuration. + :return: The XML response. + """ + + return ( + '\n' + '" if not location_constraint else f">{location_constraint}") + ) + + +def encode_continuation_token(value: str) -> str: + """ + :param value: a string value to be encoded + :return: a base64 encoded S3 ContinuationMarker + """ + return base64.b64encode(value.encode(), altchars=b"._").decode("ascii") + + +def decode_continuation_token(value: str | None) -> str: + """ + Pendant to ``encode_continuation_token``, will decode the value back to its original form + :param value: a ContinuationMarker value + :return: a string from the base64 decoded value + """ + if value is None: + return "" + + return base64.b64decode(value, altchars=b"._").decode("ascii") diff --git a/localstack-core/localstack/services/s3/validation.py b/localstack-core/localstack/services/s3/validation.py index 884b9f6cd11ba..ee012c899f3fc 100644 --- a/localstack-core/localstack/services/s3/validation.py +++ b/localstack-core/localstack/services/s3/validation.py @@ -15,6 +15,7 @@ BucketName, ChecksumAlgorithm, CORSConfiguration, + EncodingType, Grant, Grantee, Grants, @@ -38,7 +39,6 @@ from localstack.services.s3 import constants as s3_constants from localstack.services.s3.exceptions import InvalidRequest, MalformedACLError, MalformedXML from localstack.services.s3.utils import ( - _create_invalid_argument_exc, get_class_attrs_from_spec_class, get_permission_header_name, is_bucket_name_valid, @@ -87,8 +87,11 @@ def validate_canned_acl(canned_acl: str) -> None: Validate the canned ACL value, or raise an Exception """ if canned_acl and canned_acl not in VALID_CANNED_ACLS: - ex = _create_invalid_argument_exc(None, "x-amz-acl", canned_acl) - raise ex + raise InvalidArgument( + None, + ArgumentName="x-amz-acl", + ArgumentValue=canned_acl, + ) def parse_grants_in_headers(permission: Permission, grantees: str) -> Grants: @@ -98,16 +101,18 @@ def parse_grants_in_headers(permission: Permission, grantees: str) -> Grants: grantee_type, grantee_id = seralized_grantee.split("=") grantee_id = grantee_id.strip('"') if grantee_type not in ("uri", "id", "emailAddress"): - ex = _create_invalid_argument_exc( + raise InvalidArgument( "Argument format not recognized", - get_permission_header_name(permission), - seralized_grantee, + ArgumentName=get_permission_header_name(permission), + ArgumentValue=seralized_grantee, ) - raise ex elif grantee_type == "uri": if grantee_id not in s3_constants.VALID_ACL_PREDEFINED_GROUPS: - ex = _create_invalid_argument_exc("Invalid group uri", "uri", grantee_id) - raise ex + raise InvalidArgument( + "Invalid group uri", + ArgumentName="uri", + ArgumentValue=grantee_id, + ) grantee = Grantee( Type=GranteeType.Group, URI=grantee_id, @@ -115,8 +120,11 @@ def parse_grants_in_headers(permission: Permission, grantees: str) -> Grants: elif grantee_type == "id": if not is_valid_canonical_id(grantee_id): - ex = _create_invalid_argument_exc("Invalid id", "id", grantee_id) - raise ex + raise InvalidArgument( + "Invalid id", + ArgumentName="id", + ArgumentValue=grantee_id, + ) grantee = Grantee( Type=GranteeType.CanonicalUser, ID=grantee_id, @@ -141,8 +149,11 @@ def validate_acl_acp(acp: AccessControlPolicy) -> None: ) if not is_valid_canonical_id(owner_id := acp["Owner"].get("ID", "")): - ex = _create_invalid_argument_exc("Invalid id", "CanonicalUser/ID", owner_id) - raise ex + raise InvalidArgument( + "Invalid id", + ArgumentName="CanonicalUser/ID", + ArgumentValue=owner_id, + ) for grant in acp["Grants"]: if grant.get("Permission") not in s3_constants.VALID_GRANTEE_PERMISSIONS: @@ -165,18 +176,24 @@ def validate_acl_acp(acp: AccessControlPolicy) -> None: and (grant_uri := grantee.get("URI", "")) not in s3_constants.VALID_ACL_PREDEFINED_GROUPS ): - ex = _create_invalid_argument_exc("Invalid group uri", "Group/URI", grant_uri) - raise ex + raise InvalidArgument( + "Invalid group uri", + ArgumentName="Group/URI", + ArgumentValue=grant_uri, + ) elif grant_type == GranteeType.AmazonCustomerByEmail: # TODO: add validation here continue elif grant_type == GranteeType.CanonicalUser and not is_valid_canonical_id( - (grantee_id := grantee.get("ID", "")) + grantee_id := grantee.get("ID", "") ): - ex = _create_invalid_argument_exc("Invalid id", "CanonicalUser/ID", grantee_id) - raise ex + raise InvalidArgument( + "Invalid id", + ArgumentName="CanonicalUser/ID", + ArgumentValue=grantee_id, + ) def validate_lifecycle_configuration(lifecycle_conf: BucketLifecycleConfiguration) -> None: @@ -242,12 +259,12 @@ def validate_website_configuration(website_config: WebsiteConfiguration) -> None """ if redirect_all_req := website_config.get("RedirectAllRequestsTo", {}): if len(website_config) > 1: - ex = _create_invalid_argument_exc( - message="RedirectAllRequestsTo cannot be provided in conjunction with other Routing Rules.", - name="RedirectAllRequestsTo", - value="not null", + raise InvalidArgument( + "RedirectAllRequestsTo cannot be provided in conjunction with other Routing Rules.", + ArgumentName="RedirectAllRequestsTo", + ArgumentValue="not null", ) - raise ex + if "HostName" not in redirect_all_req: raise MalformedXML() @@ -261,20 +278,18 @@ def validate_website_configuration(website_config: WebsiteConfiguration) -> None # required # https://docs.aws.amazon.com/AmazonS3/latest/API/API_IndexDocument.html if not (index_configuration := website_config.get("IndexDocument")): - ex = _create_invalid_argument_exc( - message="A value for IndexDocument Suffix must be provided if RedirectAllRequestsTo is empty", - name="IndexDocument", - value="null", + raise InvalidArgument( + "A value for IndexDocument Suffix must be provided if RedirectAllRequestsTo is empty", + ArgumentName="IndexDocument", + ArgumentValue="null", ) - raise ex if not (index_suffix := index_configuration.get("Suffix")) or "/" in index_suffix: - ex = _create_invalid_argument_exc( - message="The IndexDocument Suffix is not well formed", - name="IndexDocument", - value=index_suffix or None, + raise InvalidArgument( + "The IndexDocument Suffix is not well formed", + ArgumentName="IndexDocument", + ArgumentValue=index_suffix or None, ) - raise ex if "ErrorDocument" in website_config and not website_config.get("ErrorDocument", {}).get("Key"): raise MalformedXML() @@ -453,13 +468,11 @@ def validate_sse_c( raise InvalidArgument( "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.", ArgumentName="x-amz-server-side-encryption", - ArgumentValue="null", ) elif not encryption_key and algorithm: raise InvalidArgument( "Requests specifying Server Side Encryption with Customer provided keys must provide an appropriate secret key.", ArgumentName="x-amz-server-side-encryption", - ArgumentValue="null", ) if algorithm != "AES256": @@ -474,7 +487,6 @@ def validate_sse_c( raise InvalidArgument( "The secret key was invalid for the specified algorithm.", ArgumentName="x-amz-server-side-encryption", - ArgumentValue="null", ) sse_customer_key_md5 = base64.b64encode(hashlib.md5(sse_customer_key).digest()).decode("utf-8") @@ -483,7 +495,6 @@ def validate_sse_c( "The calculated MD5 hash of the key did not match the hash that was provided.", # weirdly, the argument name is wrong, it should be `x-amz-server-side-encryption-customer-key-MD5` ArgumentName="x-amz-server-side-encryption", - ArgumentValue="null", ) @@ -506,3 +517,12 @@ def validate_checksum_value(checksum_value: str, checksum_algorithm: ChecksumAlg valid_length = 0 return len(checksum) == valid_length + + +def validate_encoding_type(encoding_type: EncodingType): + if encoding_type is not None and not encoding_type == EncodingType.url: + raise InvalidArgument( + "Invalid Encoding Method specified in Request", + ArgumentName="encoding-type", + ArgumentValue=encoding_type, + ) diff --git a/localstack-core/localstack/services/s3/website_hosting.py b/localstack-core/localstack/services/s3/website_hosting.py index 141dc4e935105..10fac49d33539 100644 --- a/localstack-core/localstack/services/s3/website_hosting.py +++ b/localstack-core/localstack/services/s3/website_hosting.py @@ -1,7 +1,7 @@ import logging import re +from collections.abc import Callable from functools import wraps -from typing import Callable, Dict, Optional, Union from urllib.parse import urlparse from werkzeug.datastructures import Headers @@ -32,8 +32,8 @@ class NoSuchKeyFromErrorDocument(NoSuchKey): code: str = "NoSuchKey" sender_fault: bool = False status_code: int = 404 - Key: Optional[ObjectKey] - ErrorDocumentKey: Optional[ObjectKey] + Key: ObjectKey | None + ErrorDocumentKey: ObjectKey | None class S3WebsiteHostingHandler: @@ -93,8 +93,10 @@ def __call__( return Response(response_body, status=e.response["ResponseMetadata"]["HTTPStatusCode"]) except Exception: - LOG.exception( - "Exception encountered while trying to serve s3-website at %s", request.url + LOG.error( + "Exception encountered while trying to serve s3-website at %s", + request.url, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) return Response(_create_500_error_string(), status=500) @@ -223,7 +225,7 @@ def _return_error_document( ) @staticmethod - def _get_response_headers_from_object(get_object_response: GetObjectOutput) -> Dict[str, str]: + def _get_response_headers_from_object(get_object_response: GetObjectOutput) -> dict[str, str]: """ Only return some headers from the S3 Object :param get_object_response: the response from S3.GetObject @@ -248,7 +250,7 @@ def _check_if_headers(headers: Headers, s3_object: GetObjectOutput) -> bool: @staticmethod def _find_matching_rule( routing_rules: RoutingRules, object_key: ObjectKey, error_code: int = None - ) -> Union[RoutingRule, None]: + ) -> RoutingRule | None: """ Iterate over the routing rules set in the configuration, and return the first that match the key name and/or the error code (in the 4XX range). diff --git a/localstack-core/localstack/services/s3control/exceptions.py b/localstack-core/localstack/services/s3control/exceptions.py new file mode 100644 index 0000000000000..98d7ae36e109d --- /dev/null +++ b/localstack-core/localstack/services/s3control/exceptions.py @@ -0,0 +1,6 @@ +from localstack.aws.api import CommonServiceException + + +class NoSuchResource(CommonServiceException): + def __init__(self, message=None): + super().__init__("NoSuchResource", status_code=404, message=message) diff --git a/localstack-core/localstack/services/s3control/provider.py b/localstack-core/localstack/services/s3control/provider.py index f4057b0adc0bc..3cca0f9477c02 100644 --- a/localstack-core/localstack/services/s3control/provider.py +++ b/localstack-core/localstack/services/s3control/provider.py @@ -1,5 +1,74 @@ -from localstack.aws.api.s3control import S3ControlApi +from localstack.aws.api import RequestContext +from localstack.aws.api.s3control import ( + AccountId, + ListTagsForResourceResult, + S3ControlApi, + S3ResourceArn, + Tag, + TagKeyList, + TagList, + TagResourceResult, + UntagResourceResult, +) +from localstack.services.s3.models import S3Store, s3_stores +from localstack.services.s3control.validation import validate_arn_for_tagging, validate_tags +from localstack.state import StateVisitor class S3ControlProvider(S3ControlApi): - pass + def accept_state_visitor(self, visitor: StateVisitor): + from moto.s3control.models import s3control_backends + + visitor.visit(s3control_backends) + + """ + S3Control is a management interface for S3, and can access some of its internals with no public API + This requires us to access the s3 stores directly + """ + + @staticmethod + def get_s3_store(account_id: str, region: str) -> S3Store: + return s3_stores[account_id][region] + + def tag_resource( + self, + context: RequestContext, + account_id: AccountId, + resource_arn: S3ResourceArn, + tags: TagList, + **kwargs, + ) -> TagResourceResult: + # Currently S3Control only supports tagging buckets + validate_arn_for_tagging(resource_arn, context.partition, account_id, context.region) + validate_tags(tags) + + store = self.get_s3_store(account_id, context.region) + store.tags.update_tags(resource_arn, {tag["Key"]: tag["Value"] for tag in tags}) + return TagResourceResult() + + def untag_resource( + self, + context: RequestContext, + account_id: AccountId, + resource_arn: S3ResourceArn, + tag_keys: TagKeyList, + **kwargs, + ) -> UntagResourceResult: + # Currently S3Control only supports tagging buckets + validate_arn_for_tagging(resource_arn, context.partition, account_id, context.region) + + store = self.get_s3_store(account_id, context.region) + store.tags.delete_tags(resource_arn, tag_keys) + return TagResourceResult() + + def list_tags_for_resource( + self, context: RequestContext, account_id: AccountId, resource_arn: S3ResourceArn, **kwargs + ) -> ListTagsForResourceResult: + # Currently S3Control only supports tagging buckets + validate_arn_for_tagging(resource_arn, context.partition, account_id, context.region) + + store = self.get_s3_store(account_id, context.region) + tags = store.tags.get_tags(resource_arn) + return ListTagsForResourceResult( + Tags=[Tag(Key=key, Value=value) for key, value in tags.items()] + ) diff --git a/localstack-core/localstack/services/s3control/validation.py b/localstack-core/localstack/services/s3control/validation.py new file mode 100644 index 0000000000000..9f99ffcc17718 --- /dev/null +++ b/localstack-core/localstack/services/s3control/validation.py @@ -0,0 +1,80 @@ +from localstack.aws.api.s3 import InvalidTag +from localstack.aws.api.s3control import Tag, TagList +from localstack.aws.forwarder import NotImplementedAvoidFallbackError +from localstack.services.s3.exceptions import MalformedXML +from localstack.services.s3.models import s3_stores +from localstack.services.s3.utils import TAG_REGEX +from localstack.services.s3control.exceptions import NoSuchResource + + +def validate_arn_for_tagging( + resource_arn: str, partition: str, account_id: str, region: str +) -> None: + """ + Validates the resource ARN for the resource being tagged. + + :param resource_arn: The ARN of the resource being tagged. + :param partition: The partition the request is originating from. + :param account_id: The account ID of the target resource. + :param region: The region the request is originating from. + :return: None + """ + + s3_prefix = f"arn:{partition}:s3:::" + if not resource_arn.startswith(s3_prefix): + # Moto does not support Tagging operations for S3 Control, so we should not forward those operations back + # to it + raise NotImplementedAvoidFallbackError( + "LocalStack only support Bucket tagging operations for S3Control" + ) + + store = s3_stores[account_id][region] + bucket_name = resource_arn.removeprefix(s3_prefix) + if bucket_name not in store.global_bucket_map: + raise NoSuchResource("The specified resource doesn't exist.") + + +def validate_tags(tags: TagList): + """ + Validate the tags provided. This is the same function as S3, but with different error messages + :param tags: a TagList object + :raises MalformedXML if the object does not conform to the schema + :raises InvalidTag if the tag key or value are outside the set of validations defined by S3 and S3Control + :return: None + """ + keys = set() + for tag in tags: + tag: Tag + if set(tag) != {"Key", "Value"}: + raise MalformedXML() + + key = tag["Key"] + value = tag["Value"] + + if key is None or value is None: + raise MalformedXML() + + if key in keys: + raise InvalidTag( + "There are duplicate tag keys in your request. Remove the duplicate tag keys and try again.", + TagKey=key, + ) + + if key.startswith("aws:"): + raise InvalidTag( + 'User-defined tag keys can\'t start with "aws:". This prefix is reserved for system tags. Remove "aws:" from your tag keys and try again.', + ) + + if not TAG_REGEX.match(key): + raise InvalidTag( + "This request contains a tag key or value that isn't valid. Valid characters include the following: [a-zA-Z+-=._:/]. Tag keys can contain up to 128 characters. Tag values can contain up to 256 characters.", + TagKey=key, + ) + elif not TAG_REGEX.match(value): + raise InvalidTag( + "This request contains a tag key or value that isn't valid. Valid characters include the following: [a-zA-Z+-=._:/]. Tag keys can contain up to 128 characters. Tag values can contain up to 256 characters.", + TagKey=key, + TagValue=value, + ) + + keys.add(key) diff --git a/localstack-core/localstack/services/scheduler/provider.py b/localstack-core/localstack/services/scheduler/provider.py index 63177c01fda30..3ba12874d8727 100644 --- a/localstack-core/localstack/services/scheduler/provider.py +++ b/localstack-core/localstack/services/scheduler/provider.py @@ -1,11 +1,12 @@ import logging import re -from moto.scheduler.models import EventBridgeSchedulerBackend +from moto.scheduler.models import EventBridgeSchedulerBackend, scheduler_backends from localstack.aws.api.scheduler import SchedulerApi, ValidationException from localstack.services.events.rule import RULE_SCHEDULE_CRON_REGEX, RULE_SCHEDULE_RATE_REGEX from localstack.services.plugins import ServiceLifecycleHook +from localstack.state import StateVisitor from localstack.utils.patch import patch LOG = logging.getLogger(__name__) @@ -17,7 +18,8 @@ class SchedulerProvider(SchedulerApi, ServiceLifecycleHook): - pass + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(scheduler_backends) def _validate_schedule_expression(schedule_expression: str) -> None: diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py index adfc5316062ab..61b8e3e98fc3f 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,110 +14,110 @@ class SchedulerScheduleProperties(TypedDict): - FlexibleTimeWindow: Optional[FlexibleTimeWindow] - ScheduleExpression: Optional[str] - Target: Optional[Target] - Arn: Optional[str] - Description: Optional[str] - EndDate: Optional[str] - GroupName: Optional[str] - KmsKeyArn: Optional[str] - Name: Optional[str] - ScheduleExpressionTimezone: Optional[str] - StartDate: Optional[str] - State: Optional[str] + FlexibleTimeWindow: FlexibleTimeWindow | None + ScheduleExpression: str | None + Target: Target | None + Arn: str | None + Description: str | None + EndDate: str | None + GroupName: str | None + KmsKeyArn: str | None + Name: str | None + ScheduleExpressionTimezone: str | None + StartDate: str | None + State: str | None class FlexibleTimeWindow(TypedDict): - Mode: Optional[str] - MaximumWindowInMinutes: Optional[float] + Mode: str | None + MaximumWindowInMinutes: float | None class DeadLetterConfig(TypedDict): - Arn: Optional[str] + Arn: str | None class RetryPolicy(TypedDict): - MaximumEventAgeInSeconds: Optional[float] - MaximumRetryAttempts: Optional[float] + MaximumEventAgeInSeconds: float | None + MaximumRetryAttempts: float | None class AwsVpcConfiguration(TypedDict): - Subnets: Optional[list[str]] - AssignPublicIp: Optional[str] - SecurityGroups: Optional[list[str]] + Subnets: list[str] | None + AssignPublicIp: str | None + SecurityGroups: list[str] | None class NetworkConfiguration(TypedDict): - AwsvpcConfiguration: Optional[AwsVpcConfiguration] + AwsvpcConfiguration: AwsVpcConfiguration | None class CapacityProviderStrategyItem(TypedDict): - CapacityProvider: Optional[str] - Base: Optional[float] - Weight: Optional[float] + CapacityProvider: str | None + Base: float | None + Weight: float | None class PlacementConstraint(TypedDict): - Expression: Optional[str] - Type: Optional[str] + Expression: str | None + Type: str | None class PlacementStrategy(TypedDict): - Field: Optional[str] - Type: Optional[str] + Field: str | None + Type: str | None class EcsParameters(TypedDict): - TaskDefinitionArn: Optional[str] - CapacityProviderStrategy: Optional[list[CapacityProviderStrategyItem]] - EnableECSManagedTags: Optional[bool] - EnableExecuteCommand: Optional[bool] - Group: Optional[str] - LaunchType: Optional[str] - NetworkConfiguration: Optional[NetworkConfiguration] - PlacementConstraints: Optional[list[PlacementConstraint]] - PlacementStrategy: Optional[list[PlacementStrategy]] - PlatformVersion: Optional[str] - PropagateTags: Optional[str] - ReferenceId: Optional[str] - Tags: Optional[list[dict]] - TaskCount: Optional[float] + TaskDefinitionArn: str | None + CapacityProviderStrategy: list[CapacityProviderStrategyItem] | None + EnableECSManagedTags: bool | None + EnableExecuteCommand: bool | None + Group: str | None + LaunchType: str | None + NetworkConfiguration: NetworkConfiguration | None + PlacementConstraints: list[PlacementConstraint] | None + PlacementStrategy: list[PlacementStrategy] | None + PlatformVersion: str | None + PropagateTags: str | None + ReferenceId: str | None + Tags: list[dict] | None + TaskCount: float | None class EventBridgeParameters(TypedDict): - DetailType: Optional[str] - Source: Optional[str] + DetailType: str | None + Source: str | None class KinesisParameters(TypedDict): - PartitionKey: Optional[str] + PartitionKey: str | None class SageMakerPipelineParameter(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class SageMakerPipelineParameters(TypedDict): - PipelineParameterList: Optional[list[SageMakerPipelineParameter]] + PipelineParameterList: list[SageMakerPipelineParameter] | None class SqsParameters(TypedDict): - MessageGroupId: Optional[str] + MessageGroupId: str | None class Target(TypedDict): - Arn: Optional[str] - RoleArn: Optional[str] - DeadLetterConfig: Optional[DeadLetterConfig] - EcsParameters: Optional[EcsParameters] - EventBridgeParameters: Optional[EventBridgeParameters] - Input: Optional[str] - KinesisParameters: Optional[KinesisParameters] - RetryPolicy: Optional[RetryPolicy] - SageMakerPipelineParameters: Optional[SageMakerPipelineParameters] - SqsParameters: Optional[SqsParameters] + Arn: str | None + RoleArn: str | None + DeadLetterConfig: DeadLetterConfig | None + EcsParameters: EcsParameters | None + EventBridgeParameters: EventBridgeParameters | None + Input: str | None + KinesisParameters: KinesisParameters | None + RetryPolicy: RetryPolicy | None + SageMakerPipelineParameters: SageMakerPipelineParameters | None + SqsParameters: SqsParameters | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py index b5fc742b5377b..57a87b915c7a9 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SchedulerScheduleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Scheduler::Schedule" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.scheduler.resource_providers.aws_scheduler_schedule import ( diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py index 913ce73707551..ace7a4aeb9767 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class SchedulerScheduleGroupProperties(TypedDict): - Arn: Optional[str] - CreationDate: Optional[str] - LastModificationDate: Optional[str] - Name: Optional[str] - State: Optional[str] - Tags: Optional[list[Tag]] + Arn: str | None + CreationDate: str | None + LastModificationDate: str | None + Name: str | None + State: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py index 2f76e843976f7..a2c649960097e 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SchedulerScheduleGroupProviderPlugin(CloudFormationResourceProviderPlugin) name = "AWS::Scheduler::ScheduleGroup" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.scheduler.resource_providers.aws_scheduler_schedulegroup import ( diff --git a/localstack-core/localstack/services/secretsmanager/provider.py b/localstack-core/localstack/services/secretsmanager/provider.py index 5838732f2c4b0..e48dd53066a64 100644 --- a/localstack-core/localstack/services/secretsmanager/provider.py +++ b/localstack-core/localstack/services/secretsmanager/provider.py @@ -5,7 +5,7 @@ import logging import re import time -from typing import Any, Final, Optional, Union +from typing import Any, Final import moto.secretsmanager.exceptions as moto_exception from botocore.utils import InvalidArnException @@ -65,6 +65,7 @@ ) from localstack.aws.connect import connect_to from localstack.services.moto import call_moto +from localstack.state import StateVisitor from localstack.utils.aws import arns from localstack.utils.patch import patch from localstack.utils.time import today_no_time @@ -105,6 +106,9 @@ def __init__(self): super().__init__() apply_patches() + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(secretsmanager_backends) + @staticmethod def get_moto_backend_for_resource( name_or_arn: str, context: RequestContext @@ -135,7 +139,7 @@ def _validate_secret_id(secret_id: SecretIdType) -> bool: return bool(re.match(r"^[A-Za-z0-9/_+=.@-]+\Z", secret_id)) @staticmethod - def _raise_if_invalid_secret_id(secret_id: Union[SecretIdType, NameType]): + def _raise_if_invalid_secret_id(secret_id: SecretIdType | NameType): # Patches moto's implementation for which secret_ids are not validated, by raising a ValidationException. # Skips this check if the secret_id provided appears to be an arn (starting with 'arn:'). if not re.match( @@ -149,12 +153,10 @@ def _raise_if_invalid_secret_id(secret_id: Union[SecretIdType, NameType]): @staticmethod def _raise_if_missing_client_req_token( - request: Union[ - CreateSecretRequest, - PutSecretValueRequest, - RotateSecretRequest, - UpdateSecretRequest, - ], + request: CreateSecretRequest + | PutSecretValueRequest + | RotateSecretRequest + | UpdateSecretRequest, ): if "ClientRequestToken" not in request: raise InvalidRequestException( @@ -201,8 +203,8 @@ def delete_secret( ) -> DeleteSecretResponse: secret_id: str = request["SecretId"] self._raise_if_invalid_secret_id(secret_id) - recovery_window_in_days: Optional[int] = request.get("RecoveryWindowInDays") - force_delete_without_recovery: Optional[bool] = request.get("ForceDeleteWithoutRecovery") + recovery_window_in_days: int | None = request.get("RecoveryWindowInDays") + force_delete_without_recovery: bool | None = request.get("ForceDeleteWithoutRecovery") backend = SecretsmanagerProvider.get_moto_backend_for_resource(secret_id, context) try: @@ -317,7 +319,8 @@ def put_secret_value( secret_binary = request.get("SecretBinary") if not secret_binary and not secret_string: raise InvalidRequestException("You must provide either SecretString or SecretBinary.") - + if secret_binary: + secret_binary = base64.b64encode(secret_binary) version_stages = request.get("VersionStages", ["AWSCURRENT"]) if not isinstance(version_stages, list): version_stages = [version_stages] @@ -400,6 +403,8 @@ def update_secret( secret_id = request["SecretId"] secret_string = request.get("SecretString") secret_binary = request.get("SecretBinary") + if secret_binary: + secret_binary = base64.b64encode(secret_binary) description = request.get("Description") kms_key_id = request.get("KmsKeyId") client_req_token = request.get("ClientRequestToken") @@ -495,7 +500,7 @@ def moto_smb_get_secret_value(fn, self, secret_id, version_id, version_stage): def moto_smb_create_secret(fn, self, name, *args, **kwargs): # Creating a secret with a SecretId equal to one that is scheduled for # deletion should raise an 'InvalidRequestException'. - secret: Optional[FakeSecret] = self.secrets.get(name) + secret: FakeSecret | None = self.secrets.get(name) if secret is not None and secret.deleted_date is not None: raise InvalidRequestException(AWS_INVALID_REQUEST_MESSAGE_CREATE_WITH_SCHEDULED_DELETION) @@ -523,7 +528,7 @@ def moto_smb_list_secret_version_ids( secret = self.secrets[secret_id] # Patch: output format, report exact createdate instead of current time. - versions: list[SecretVersionsListEntry] = list() + versions: list[SecretVersionsListEntry] = [] for version_id, version in secret.versions.items(): version_stages = version["version_stages"] # Patch: include deprecated versions if include_deprecated is True. @@ -612,6 +617,32 @@ def backend_update_secret( return json.dumps(resp) +@patch(SecretsManagerBackend.tag_resource) +def backend_tag_resource(fn, self, secret_id, tags): + if secret_id not in self.secrets: + raise SecretNotFoundException() + + if self.secrets[secret_id].is_deleted(): + raise InvalidRequestException( + "You can't perform this operation on the secret because it was marked for deletion." + ) + + return fn(self, secret_id, tags) + + +@patch(SecretsManagerBackend.untag_resource) +def backend_untag_resource(fn, self, secret_id, tag_keys): + if secret_id not in self.secrets: + raise SecretNotFoundException() + + if self.secrets[secret_id].is_deleted(): + raise InvalidRequestException( + "You can't perform this operation on the secret because it was marked for deletion." + ) + + return fn(self, secret_id, tag_keys) + + @patch(SecretsManagerResponse.update_secret, pass_target=False) def response_update_secret(self): secret_id = self._get_param("SecretId") diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py index 53784023f67f5..4a99783e2d1ef 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,10 +15,10 @@ class SecretsManagerResourcePolicyProperties(TypedDict): - ResourcePolicy: Optional[dict] - SecretId: Optional[str] - BlockPublicPolicy: Optional[bool] - Id: Optional[str] + ResourcePolicy: dict | None + SecretId: str | None + BlockPublicPolicy: bool | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py index 1571bbfd89afc..19d7425eeedcb 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerResourcePolicyProviderPlugin(CloudFormationResourceProviderP name = "AWS::SecretsManager::ResourcePolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_resourcepolicy import ( diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py index b838450d24a1d..76293c6b2b38f 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,32 +14,32 @@ class SecretsManagerRotationScheduleProperties(TypedDict): - SecretId: Optional[str] - HostedRotationLambda: Optional[HostedRotationLambda] - Id: Optional[str] - RotateImmediatelyOnUpdate: Optional[bool] - RotationLambdaARN: Optional[str] - RotationRules: Optional[RotationRules] + SecretId: str | None + HostedRotationLambda: HostedRotationLambda | None + Id: str | None + RotateImmediatelyOnUpdate: bool | None + RotationLambdaARN: str | None + RotationRules: RotationRules | None class RotationRules(TypedDict): - AutomaticallyAfterDays: Optional[int] - Duration: Optional[str] - ScheduleExpression: Optional[str] + AutomaticallyAfterDays: int | None + Duration: str | None + ScheduleExpression: str | None class HostedRotationLambda(TypedDict): - RotationType: Optional[str] - ExcludeCharacters: Optional[str] - KmsKeyArn: Optional[str] - MasterSecretArn: Optional[str] - MasterSecretKmsKeyArn: Optional[str] - RotationLambdaName: Optional[str] - Runtime: Optional[str] - SuperuserSecretArn: Optional[str] - SuperuserSecretKmsKeyArn: Optional[str] - VpcSecurityGroupIds: Optional[str] - VpcSubnetIds: Optional[str] + RotationType: str | None + ExcludeCharacters: str | None + KmsKeyArn: str | None + MasterSecretArn: str | None + MasterSecretKmsKeyArn: str | None + RotationLambdaName: str | None + Runtime: str | None + SuperuserSecretArn: str | None + SuperuserSecretKmsKeyArn: str | None + VpcSecurityGroupIds: str | None + VpcSubnetIds: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py index dd680bd788d1f..faef8a8c5698d 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerRotationScheduleProviderPlugin(CloudFormationResourceProvide name = "AWS::SecretsManager::RotationSchedule" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_rotationschedule import ( diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py index d53dbd2e9aefe..4c1d3c31465a8 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py @@ -6,7 +6,7 @@ import random import string from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -20,37 +20,37 @@ class SecretsManagerSecretProperties(TypedDict): - Description: Optional[str] - GenerateSecretString: Optional[GenerateSecretString] - Id: Optional[str] - KmsKeyId: Optional[str] - Name: Optional[str] - ReplicaRegions: Optional[list[ReplicaRegion]] - SecretString: Optional[str] - Tags: Optional[list[Tag]] + Description: str | None + GenerateSecretString: GenerateSecretString | None + Id: str | None + KmsKeyId: str | None + Name: str | None + ReplicaRegions: list[ReplicaRegion] | None + SecretString: str | None + Tags: list[Tag] | None class GenerateSecretString(TypedDict): - ExcludeCharacters: Optional[str] - ExcludeLowercase: Optional[bool] - ExcludeNumbers: Optional[bool] - ExcludePunctuation: Optional[bool] - ExcludeUppercase: Optional[bool] - GenerateStringKey: Optional[str] - IncludeSpace: Optional[bool] - PasswordLength: Optional[int] - RequireEachIncludedType: Optional[bool] - SecretStringTemplate: Optional[str] + ExcludeCharacters: str | None + ExcludeLowercase: bool | None + ExcludeNumbers: bool | None + ExcludePunctuation: bool | None + ExcludeUppercase: bool | None + GenerateStringKey: str | None + IncludeSpace: bool | None + PasswordLength: int | None + RequireEachIncludedType: bool | None + SecretStringTemplate: str | None class ReplicaRegion(TypedDict): - Region: Optional[str] - KmsKeyId: Optional[str] + Region: str | None + KmsKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py index 4c85279d0d81f..8cd82e4510797 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerSecretProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SecretsManager::Secret" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_secret import ( diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py index 27f8682c0a51f..e58c7d049699f 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class SecretsManagerSecretTargetAttachmentProperties(TypedDict): - SecretId: Optional[str] - TargetId: Optional[str] - TargetType: Optional[str] - Id: Optional[str] + SecretId: str | None + TargetId: str | None + TargetType: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py index f84e773ee3faf..5c7295f1f73ba 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerSecretTargetAttachmentProviderPlugin(CloudFormationResourceP name = "AWS::SecretsManager::SecretTargetAttachment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_secrettargetattachment import ( diff --git a/localstack-core/localstack/services/ses/models.py b/localstack-core/localstack/services/ses/models.py index 2560f872410da..666d5b2f08d06 100644 --- a/localstack-core/localstack/services/ses/models.py +++ b/localstack-core/localstack/services/ses/models.py @@ -1,3 +1,4 @@ +from enum import StrEnum from typing import TypedDict from localstack.aws.api.ses import Address, Destination, Subject, TemplateData, TemplateName @@ -8,7 +9,7 @@ class SentEmailBody(TypedDict): text_part: str -class SentEmail(TypedDict): +class SentEmail(TypedDict, total=False): Id: str Region: str Timestamp: str @@ -19,3 +20,9 @@ class SentEmail(TypedDict): Template: TemplateName TemplateData: TemplateData Body: SentEmailBody + + +class EmailType(StrEnum): + TEMPLATED = "templated" + RAW = "raw" + EMAIL = "email" diff --git a/localstack-core/localstack/services/ses/provider.py b/localstack-core/localstack/services/ses/provider.py index ca87c457c5818..4a15773b91e8f 100644 --- a/localstack-core/localstack/services/ses/provider.py +++ b/localstack-core/localstack/services/ses/provider.py @@ -4,8 +4,8 @@ import os import re from collections import defaultdict -from datetime import date, datetime, time, timezone -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from datetime import UTC, date, datetime, time +from typing import TYPE_CHECKING, Any from botocore.exceptions import ClientError from moto.ses import ses_backends @@ -26,10 +26,13 @@ DeleteConfigurationSetResponse, DeleteTemplateResponse, Destination, + Enabled, EventDestination, EventDestinationDoesNotExistException, EventDestinationName, + EventType, GetIdentityVerificationAttributesResponse, + Identity, IdentityList, IdentityVerificationAttributes, InvalidSNSDestinationException, @@ -40,12 +43,14 @@ MessageRejected, MessageTagList, NextToken, + NotificationType, RawMessage, ReceiptRuleSetName, SendEmailResponse, SendRawEmailResponse, SendTemplatedEmailResponse, SesApi, + SetIdentityHeadersInNotificationsEnabledResponse, TemplateData, TemplateName, VerificationAttributes, @@ -56,7 +61,8 @@ from localstack.http import Resource, Response from localstack.services.moto import call_moto from localstack.services.plugins import ServiceLifecycleHook -from localstack.services.ses.models import SentEmail, SentEmailBody +from localstack.services.ses.models import EmailType, SentEmail, SentEmailBody +from localstack.state import StateVisitor from localstack.utils.aws import arns from localstack.utils.files import mkdir from localstack.utils.strings import long_uid, to_str @@ -69,7 +75,7 @@ # Keep record of all sent emails # These can be retrieved via a service endpoint -EMAILS: Dict[MessageId, Dict[str, Any]] = {} +EMAILS: dict[MessageId, dict[str, Any]] = {} # Endpoint to access all the sent emails # (relative to LocalStack internal HTTP resources base endpoint) @@ -112,7 +118,7 @@ def _serialize(obj): LOGGER.debug("Email saved at: %s", path) -def recipients_from_destination(destination: Destination) -> List[str]: +def recipients_from_destination(destination: Destination) -> list[str]: """Get list of recipient email addresses from a Destination object.""" return ( destination.get("ToAddresses", []) @@ -172,11 +178,16 @@ def register_ses_api_resource(): class SesProvider(SesApi, ServiceLifecycleHook): + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(ses_backends) + # # Lifecycle Hooks # def on_after_init(self): + self._apply_patches() + # Allow sent emails to be retrieved from the SES emails endpoint register_ses_api_resource() @@ -184,7 +195,7 @@ def on_after_init(self): # Helpers # - def get_source_from_raw(self, raw_data: str) -> Optional[str]: + def get_source_from_raw(self, raw_data: str) -> str | None: """Given a raw representation of email, return the source/from field.""" entities = raw_data.split("\n") for entity in entities: @@ -192,6 +203,12 @@ def get_source_from_raw(self, raw_data: str) -> Optional[str]: return entity.replace("From:", "").strip() return None + def _apply_patches(self) -> None: + # Suppress Moto's validation of receipt rule actions. These validations use Moto's implementation of S3, Lambda + # and SQS, which fail because these services have been internalised in LocalStack. + # Besides, AWS does not run the same validations as evidenced by our AWS-validated tests. + SESBackend._validate_receipt_rule_actions = lambda *_: None + # # Implementations for SES operations # @@ -210,7 +227,9 @@ def create_configuration_set_event_destination( emitter = SNSEmitter(context) emitter.emit_create_configuration_set_event_destination_test_message(sns_topic_arn) - # only register the event destiation if emitting the message worked + # FIXME: Moto stores the Event Destinations as a single value when it should be a list + # it only considers the last Event Destination created, when AWS is able to store multiple configurations + # only register the event destination if emitting the message worked try: result = call_moto(context) except CommonServiceException as e: @@ -269,6 +288,9 @@ def delete_configuration_set_event_destination( # FIXME: inconsistent state LOGGER.warning("inconsistent state encountered in ses backend") + # FIXME: Moto stores the Event Destinations as a single value when it should be a list + # it only considers the last Event Destination created, when AWS is able to store multiple configurations + # don't pop the whole value which should be a list but is currently a dict backend.config_set_event_destination.pop(configuration_set_name) return DeleteConfigurationSetEventDestinationResponse() @@ -358,25 +380,21 @@ def send_email( response = call_moto(context) backend = get_ses_backend(context) - emitter = SNSEmitter(context) - recipients = recipients_from_destination(destination) - - for event_destination in backend.config_set_event_destination.values(): - if not event_destination["Enabled"]: - continue - sns_destination_arn = event_destination.get("SNSDestination") - if not sns_destination_arn: - continue - - payload = SNSPayload( + if event_destinations := backend.config_set_event_destination.get(configuration_set_name): + recipients = recipients_from_destination(destination) + payload = EventDestinationPayload( message_id=response["MessageId"], sender_email=source, destination_addresses=recipients, tags=tags, ) - emitter.emit_send_event(payload, sns_destination_arn) - emitter.emit_delivery_event(payload, sns_destination_arn) + notify_event_destinations( + context=context, + event_destinations=event_destinations, + payload=payload, + email_type=EmailType.EMAIL, + ) text_part = message["Body"].get("Text", {}).get("Data") html_part = message["Body"].get("Html", {}).get("Data") @@ -414,25 +432,21 @@ def send_templated_email( response = call_moto(context) backend = get_ses_backend(context) - emitter = SNSEmitter(context) - recipients = recipients_from_destination(destination) - - for event_destination in backend.config_set_event_destination.values(): - if not event_destination["Enabled"]: - continue - sns_destination_arn = event_destination.get("SNSDestination") - if not sns_destination_arn: - continue - - payload = SNSPayload( + if event_destinations := backend.config_set_event_destination.get(configuration_set_name): + recipients = recipients_from_destination(destination) + payload = EventDestinationPayload( message_id=response["MessageId"], sender_email=source, destination_addresses=recipients, tags=tags, ) - emitter.emit_send_event(payload, sns_destination_arn, emit_source_arn=False) - emitter.emit_delivery_event(payload, sns_destination_arn) + notify_event_destinations( + context=context, + event_destinations=event_destinations, + payload=payload, + email_type=EmailType.TEMPLATED, + ) save_for_retrospection( SentEmail( @@ -477,23 +491,19 @@ def send_raw_email( backend = get_ses_backend(context) message = backend.send_raw_email(source, destinations, raw_data) - emitter = SNSEmitter(context) - for event_destination in backend.config_set_event_destination.values(): - if not event_destination["Enabled"]: - continue - - sns_destination_arn = event_destination.get("SNSDestination") - if not sns_destination_arn: - continue - - payload = SNSPayload( + if event_destinations := backend.config_set_event_destination.get(configuration_set_name): + payload = EventDestinationPayload( message_id=message.id, sender_email=source, destination_addresses=destinations, tags=tags, ) - emitter.emit_send_event(payload, sns_destination_arn) - emitter.emit_delivery_event(payload, sns_destination_arn) + notify_event_destinations( + context=context, + event_destinations=event_destinations, + payload=payload, + email_type=EmailType.RAW, + ) save_for_retrospection( SentEmail( @@ -519,18 +529,56 @@ def clone_receipt_rule_set( backend.create_receipt_rule_set(rule_set_name) original_rule_set = backend.describe_receipt_rule_set(original_rule_set_name) - for rule in original_rule_set: - backend.create_receipt_rule(rule_set_name, rule) + after = None + for rule in original_rule_set.rules: + backend.create_receipt_rule(rule_set_name, rule, after) + after = rule["Name"] return CloneReceiptRuleSetResponse() + @handler("SetIdentityHeadersInNotificationsEnabled") + def set_identity_headers_in_notifications_enabled( + self, + context: RequestContext, + identity: Identity, + notification_type: NotificationType, + enabled: Enabled, + **kwargs, + ) -> SetIdentityHeadersInNotificationsEnabledResponse: + """ + Sets whether Amazon SES includes the original email headers in the Amazon SNS notifications + for a specified identity and notification type. + """ + # Validate notification_type + if notification_type not in ( + NotificationType.Bounce, + NotificationType.Complaint, + NotificationType.Delivery, + ): + raise InvalidParameterValue( + f"Invalid notification type: {notification_type}. " + "Valid values are: Bounce, Complaint, Delivery." + ) + + backend = get_ses_backend(context) + if identity not in backend.email_identities: + raise MessageRejected(f"Identity {identity} is not verified or does not exist.") + + # Store the setting in the backend + if not hasattr(backend, "identity_headers_in_notifications_enabled"): + backend.identity_headers_in_notifications_enabled = {} + backend.identity_headers_in_notifications_enabled.setdefault(identity, {})[ + notification_type + ] = enabled + return SetIdentityHeadersInNotificationsEnabledResponse() + @dataclasses.dataclass(frozen=True) -class SNSPayload: +class EventDestinationPayload: message_id: str sender_email: Address destination_addresses: AddressList - tags: Optional[MessageTagList] + tags: MessageTagList | None class SNSEmitter: @@ -558,9 +606,9 @@ def emit_create_configuration_set_event_destination_test_message( ) def emit_send_event( - self, payload: SNSPayload, sns_topic_arn: str, emit_source_arn: bool = True + self, payload: EventDestinationPayload, sns_topic_arn: str, emit_source_arn: bool = True ): - now = datetime.now(tz=timezone.utc) + now = datetime.now(tz=UTC) tags = defaultdict(list) for every in payload.tags or []: @@ -592,10 +640,10 @@ def emit_send_event( Subject="Amazon SES Email Event Notification", ) except ClientError: - LOGGER.exception("sending SNS message") + LOGGER.error("sending SNS message", exc_info=LOGGER.isEnabledFor(logging.DEBUG)) - def emit_delivery_event(self, payload: SNSPayload, sns_topic_arn: str): - now = datetime.now(tz=timezone.utc) + def emit_delivery_event(self, payload: EventDestinationPayload, sns_topic_arn: str): + now = datetime.now(tz=UTC) tags = defaultdict(list) for every in payload.tags or []: @@ -625,7 +673,7 @@ def emit_delivery_event(self, payload: SNSPayload, sns_topic_arn: str): Subject="Amazon SES Email Event Notification", ) except ClientError: - LOGGER.exception("sending SNS message") + LOGGER.error("sending SNS message", exc_info=LOGGER.isEnabledFor(logging.DEBUG)) @staticmethod def _client_for_topic(topic_arn: str) -> "SNSClient": @@ -640,6 +688,35 @@ def _client_for_topic(topic_arn: str) -> "SNSClient": ).sns +def notify_event_destinations( + context: RequestContext, + # FIXME: Moto stores the Event Destinations as a single value when it should be a list + event_destinations: EventDestination | list[EventDestination], + payload: EventDestinationPayload, + email_type: EmailType, +): + emitter = SNSEmitter(context) + + if not isinstance(event_destinations, list): + event_destinations = [event_destinations] + + for event_destination in event_destinations: + if not event_destination["Enabled"]: + continue + + sns_destination_arn = event_destination.get("SNSDestination", {}).get("TopicARN") + if not sns_destination_arn: + continue + + matching_event_types = event_destination.get("MatchingEventTypes") or [] + if EventType.send in matching_event_types: + emitter.emit_send_event( + payload, sns_destination_arn, emit_source_arn=email_type != EmailType.TEMPLATED + ) + if EventType.delivery in matching_event_types: + emitter.emit_delivery_event(payload, sns_destination_arn) + + class InvalidParameterValue(CommonServiceException): def __init__(self, message=None): super().__init__( diff --git a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py index 5baeb44cd6a82..ed1ece331f726 100644 --- a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py +++ b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,41 +14,41 @@ class SESEmailIdentityProperties(TypedDict): - EmailIdentity: Optional[str] - ConfigurationSetAttributes: Optional[ConfigurationSetAttributes] - DkimAttributes: Optional[DkimAttributes] - DkimDNSTokenName1: Optional[str] - DkimDNSTokenName2: Optional[str] - DkimDNSTokenName3: Optional[str] - DkimDNSTokenValue1: Optional[str] - DkimDNSTokenValue2: Optional[str] - DkimDNSTokenValue3: Optional[str] - DkimSigningAttributes: Optional[DkimSigningAttributes] - FeedbackAttributes: Optional[FeedbackAttributes] - MailFromAttributes: Optional[MailFromAttributes] + EmailIdentity: str | None + ConfigurationSetAttributes: ConfigurationSetAttributes | None + DkimAttributes: DkimAttributes | None + DkimDNSTokenName1: str | None + DkimDNSTokenName2: str | None + DkimDNSTokenName3: str | None + DkimDNSTokenValue1: str | None + DkimDNSTokenValue2: str | None + DkimDNSTokenValue3: str | None + DkimSigningAttributes: DkimSigningAttributes | None + FeedbackAttributes: FeedbackAttributes | None + MailFromAttributes: MailFromAttributes | None class ConfigurationSetAttributes(TypedDict): - ConfigurationSetName: Optional[str] + ConfigurationSetName: str | None class DkimSigningAttributes(TypedDict): - DomainSigningPrivateKey: Optional[str] - DomainSigningSelector: Optional[str] - NextSigningKeyLength: Optional[str] + DomainSigningPrivateKey: str | None + DomainSigningSelector: str | None + NextSigningKeyLength: str | None class DkimAttributes(TypedDict): - SigningEnabled: Optional[bool] + SigningEnabled: bool | None class MailFromAttributes(TypedDict): - BehaviorOnMxFailure: Optional[str] - MailFromDomain: Optional[str] + BehaviorOnMxFailure: str | None + MailFromDomain: str | None class FeedbackAttributes(TypedDict): - EmailForwardingEnabled: Optional[bool] + EmailForwardingEnabled: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py index ca75f6be6c340..d80b72231e357 100644 --- a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py +++ b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SESEmailIdentityProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SES::EmailIdentity" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ses.resource_providers.aws_ses_emailidentity import ( diff --git a/localstack-core/localstack/services/sns/constants.py b/localstack-core/localstack/services/sns/constants.py index 04b5f05293818..2ef391b1ebb0a 100644 --- a/localstack-core/localstack/services/sns/constants.py +++ b/localstack-core/localstack/services/sns/constants.py @@ -1,5 +1,8 @@ import re from string import ascii_letters, digits +from typing import get_args + +from localstack.services.sns.models import SnsApplicationPlatforms SNS_PROTOCOLS = [ "http", @@ -13,7 +16,7 @@ "firehose", ] -VALID_SUBSCRIPTION_ATTR_NAME = [ +VALID_SUBSCRIPTION_ATTR_NAME: list[str] = [ "DeliveryPolicy", "FilterPolicy", "FilterPolicyScope", @@ -22,9 +25,24 @@ "SubscriptionRoleArn", ] + +VALID_POLICY_ACTIONS = [ + "GetTopicAttributes", + "SetTopicAttributes", + "AddPermission", + "RemovePermission", + "DeleteTopic", + "Subscribe", + "ListSubscriptionsByTopic", + "Publish", + "Receive", +] + MSG_ATTR_NAME_REGEX = re.compile(r"^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9_\-.]+$") ATTR_TYPE_REGEX = re.compile(r"^(String|Number|Binary)\..+$") VALID_MSG_ATTR_NAME_CHARS = set(ascii_letters + digits + "." + "-" + "_") +E164_REGEX = re.compile(r"^\+?[1-9]\d{1,14}$") +BATCH_ENTRY_ID_REGEX = re.compile(r"^[a-zA-Z0-9_-]+$") GCM_URL = "https://fcm.googleapis.com/fcm/send" @@ -33,9 +51,14 @@ PLATFORM_ENDPOINT_MSGS_ENDPOINT = "/_aws/sns/platform-endpoint-messages" SMS_MSGS_ENDPOINT = "/_aws/sns/sms-messages" SUBSCRIPTION_TOKENS_ENDPOINT = "/_aws/sns/subscription-tokens" +SMS_PHONE_NUMBER_OPT_OUT_ENDPOINT = "/_aws/sns/phone-opt-outs" # we add hex chars to respect the format of AWS with certificate ID, hardcoded for now # we could parametrize the certificate ID in the future SNS_CERT_ENDPOINT = "/_aws/sns/SimpleNotificationService-6c6f63616c737461636b69736e696365.pem" DUMMY_SUBSCRIPTION_PRINCIPAL = "arn:{partition}:iam::{account_id}:user/DummySNSPrincipal" + +VALID_APPLICATION_PLATFORMS = list(get_args(SnsApplicationPlatforms)) + +MAXIMUM_MESSAGE_LENGTH = 262144 diff --git a/localstack-core/localstack/services/sns/executor.py b/localstack-core/localstack/services/sns/executor.py index ce4f8850d6e3e..9aae0f005d75b 100644 --- a/localstack-core/localstack/services/sns/executor.py +++ b/localstack-core/localstack/services/sns/executor.py @@ -18,7 +18,10 @@ def _worker(work_queue: queue.Queue): del work_item except Exception: - LOG.exception("Exception in worker") + LOG.error( + "Exception in worker", + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) class _WorkItem: @@ -31,7 +34,11 @@ def run(self): try: self.fn(*self.args, **self.kwargs) except Exception: - LOG.exception("Unhandled Exception in while running %s", self.fn.__name__) + LOG.error( + "Unhandled Exception in while running %s", + self.fn.__name__, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) class TopicPartitionedThreadPoolExecutor: diff --git a/localstack-core/localstack/services/sns/models.py b/localstack-core/localstack/services/sns/models.py index a4e660e243207..ebb7b7a0f9627 100644 --- a/localstack-core/localstack/services/sns/models.py +++ b/localstack-core/localstack/services/sns/models.py @@ -2,18 +2,36 @@ import time from dataclasses import dataclass, field from enum import StrEnum -from typing import Dict, List, Literal, Optional, TypedDict, Union +from typing import Any, Literal, TypedDict from localstack.aws.api.sns import ( + Endpoint, MessageAttributeMap, + PhoneNumber, + PlatformApplication, PublishBatchRequestEntry, + TopicAttributesMap, subscriptionARN, topicARN, ) -from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute -from localstack.utils.aws.arns import parse_arn +from localstack.services.stores import ( + AccountRegionBundle, + BaseStore, + CrossRegionAttribute, + LocalAttribute, +) from localstack.utils.objects import singleton_factory from localstack.utils.strings import long_uid +from localstack.utils.tagging import Tags + + +class Topic(TypedDict, total=True): + arn: str + name: str + attributes: TopicAttributesMap + data_protection_policy: str | None + subscriptions: list[str] + SnsProtocols = Literal[ "http", "https", "email", "email-json", "sms", "sqs", "application", "lambda", "firehose" @@ -23,39 +41,47 @@ "APNS", "APNS_SANDBOX", "ADM", "FCM", "Baidu", "GCM", "MPNS", "WNS" ] + +class EndpointAttributeNames(StrEnum): + CUSTOM_USER_DATA = "CustomUserData" + Token = "Token" + ENABLED = "Enabled" + + +SMS_ATTRIBUTE_NAMES = [ + "DeliveryStatusIAMRole", + "DeliveryStatusSuccessSamplingRate", + "DefaultSenderID", + "DefaultSMSType", + "UsageReportS3Bucket", +] +SMS_TYPES = ["Promotional", "Transactional"] +SMS_DEFAULT_SENDER_REGEX = r"^(?=[A-Za-z0-9]{1,11}$)(?=.*[A-Za-z])[A-Za-z0-9]+$" SnsMessageProtocols = Literal[SnsProtocols, SnsApplicationPlatforms] -def create_default_sns_topic_policy(topic_arn: str) -> dict: +class SnsSubscription(TypedDict, total=False): """ - Creates the default SNS topic policy for the given topic ARN. - - :param topic_arn: The topic arn - :return: A policy document + In SNS, Subscription can be represented with only TopicArn, Endpoint, Protocol, SubscriptionArn and Owner, for + example in ListSubscriptions. However, when getting a subscription with GetSubscriptionAttributes, it will return + the Subscription object merged with its own attributes. + This represents this merged object, for internal use and in GetSubscriptionAttributes + https://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html """ - return { - "Version": "2008-10-17", - "Id": "__default_policy_ID", - "Statement": [ - { - "Sid": "__default_statement_ID", - "Effect": "Allow", - "Principal": {"AWS": "*"}, - "Action": [ - "SNS:GetTopicAttributes", - "SNS:SetTopicAttributes", - "SNS:AddPermission", - "SNS:RemovePermission", - "SNS:DeleteTopic", - "SNS:Subscribe", - "SNS:ListSubscriptionsByTopic", - "SNS:Publish", - ], - "Resource": topic_arn, - "Condition": {"StringEquals": {"AWS:SourceOwner": parse_arn(topic_arn)["account"]}}, - } - ], - } + + TopicArn: topicARN + Endpoint: str + Protocol: SnsProtocols + SubscriptionArn: subscriptionARN + PendingConfirmation: Literal["true", "false"] + Owner: str | None + SubscriptionPrincipal: str | None + FilterPolicy: str | None + FilterPolicyScope: Literal["MessageAttributes", "MessageBody"] + RawMessageDelivery: Literal["true", "false"] + ConfirmationWasAuthenticated: Literal["true", "false"] + SubscriptionRoleArn: str | None + DeliveryPolicy: str | None @singleton_factory @@ -80,18 +106,18 @@ class SnsMessageType(StrEnum): @dataclass class SnsMessage: type: SnsMessageType - message: Union[ - str, Dict - ] # can be Dict if after being JSON decoded for validation if structure is `json` - message_attributes: Optional[MessageAttributeMap] = None - message_structure: Optional[str] = None - subject: Optional[str] = None - message_deduplication_id: Optional[str] = None - message_group_id: Optional[str] = None - token: Optional[str] = None + message: ( + str | dict + ) # can be Dict if after being JSON decoded for validation if structure is `json` + message_attributes: MessageAttributeMap | None = None + message_structure: str | None = None + subject: str | None = None + message_deduplication_id: str | None = None + message_group_id: str | None = None + token: str | None = None message_id: str = field(default_factory=long_uid) - is_fifo: Optional[bool] = False - sequencer_number: Optional[str] = None + is_fifo: bool | None = False + sequencer_number: str | None = None def __post_init__(self): if self.message_attributes is None: @@ -126,60 +152,52 @@ def from_batch_entry(cls, entry: PublishBatchRequestEntry, is_fifo=False) -> "Sn ) -class SnsSubscription(TypedDict, total=False): - """ - In SNS, Subscription can be represented with only TopicArn, Endpoint, Protocol, SubscriptionArn and Owner, for - example in ListSubscriptions. However, when getting a subscription with GetSubscriptionAttributes, it will return - the Subscription object merged with its own attributes. - This represents this merged object, for internal use and in GetSubscriptionAttributes - https://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html - """ +@dataclass +class PlatformEndpoint: + platform_application_arn: str + platform_endpoint: Endpoint - TopicArn: topicARN - Endpoint: str - Protocol: SnsProtocols - SubscriptionArn: subscriptionARN - PendingConfirmation: Literal["true", "false"] - Owner: Optional[str] - SubscriptionPrincipal: Optional[str] - FilterPolicy: Optional[str] - FilterPolicyScope: Literal["MessageAttributes", "MessageBody"] - RawMessageDelivery: Literal["true", "false"] - ConfirmationWasAuthenticated: Literal["true", "false"] - SubscriptionRoleArn: Optional[str] - DeliveryPolicy: Optional[str] + +@dataclass +class PlatformApplicationDetails: + platform_application: PlatformApplication + # maps all Endpoints of the PlatformApplication, from their Token to their ARN + platform_endpoints: dict[str, str] class SnsStore(BaseStore): - # maps topic ARN to subscriptions ARN - topic_subscriptions: Dict[str, List[str]] = LocalAttribute(default=dict) + # maps topic ARN to Topic + topics: dict[str, Topic] = LocalAttribute(default=dict) # maps subscription ARN to SnsSubscription - subscriptions: Dict[str, SnsSubscription] = LocalAttribute(default=dict) + subscriptions: dict[str, SnsSubscription] = LocalAttribute(default=dict) + + # filter policy are stored as JSON string in subscriptions, store the decoded result Dict + subscription_filter_policy: dict[subscriptionARN, dict[str, Any] | None] = LocalAttribute( + default=dict + ) # maps confirmation token to subscription ARN - subscription_tokens: Dict[str, str] = LocalAttribute(default=dict) + subscription_tokens: dict[str, str] = LocalAttribute(default=dict) + + # maps platform application arns to platform applications + platform_applications: dict[str, PlatformApplicationDetails] = LocalAttribute(default=dict) - # maps topic ARN to list of tags - sns_tags: Dict[str, List[Dict]] = LocalAttribute(default=dict) + # maps endpoint arns to platform endpoints + platform_endpoints: dict[str, PlatformEndpoint] = LocalAttribute(default=dict) # cache of topic ARN to platform endpoint messages (used primarily for testing) - platform_endpoint_messages: Dict[str, List[Dict]] = LocalAttribute(default=dict) + platform_endpoint_messages: dict[str, list[dict[str, Any]]] = LocalAttribute(default=dict) + + # topic/subscription independent default values for sending sms messages + sms_attributes: dict[str, str] = LocalAttribute(default=dict) # list of sent SMS messages - sms_messages: List[Dict] = LocalAttribute(default=list) + sms_messages: list[dict[str, Any]] = LocalAttribute(default=list) - # filter policy are stored as JSON string in subscriptions, store the decoded result Dict - subscription_filter_policy: Dict[subscriptionARN, Dict] = LocalAttribute(default=dict) - - def get_topic_subscriptions(self, topic_arn: str) -> List[SnsSubscription]: - topic_subscriptions = self.topic_subscriptions.get(topic_arn, []) - subscriptions = [ - subscription - for subscription_arn in topic_subscriptions - if (subscription := self.subscriptions.get(subscription_arn)) - ] - return subscriptions + tags: Tags = CrossRegionAttribute(default=Tags) + + PHONE_NUMBERS_OPTED_OUT: set[PhoneNumber] = CrossRegionAttribute(default=set) sns_stores = AccountRegionBundle("sns", SnsStore) diff --git a/localstack-core/localstack/services/sns/provider.py b/localstack-core/localstack/services/sns/provider.py index e5d166ef3c72c..fb906be0bb817 100644 --- a/localstack-core/localstack/services/sns/provider.py +++ b/localstack-core/localstack/services/sns/provider.py @@ -1,45 +1,60 @@ -import base64 +import contextlib import copy import functools import json import logging -from typing import Dict, List -from uuid import uuid4 +import re from botocore.utils import InvalidArnException -from moto.core.utils import camelcase_to_pascal, underscores_to_camelcase -from moto.sns import sns_backends -from moto.sns.models import MAXIMUM_MESSAGE_LENGTH, SNSBackend, Topic -from moto.sns.utils import is_e164 +from rolo import Request, Router, route from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api.sns import ( + ActionsList, AmazonResourceName, BatchEntryIdsNotDistinctException, + CheckIfPhoneNumberIsOptedOutResponse, ConfirmSubscriptionResponse, CreateEndpointResponse, CreatePlatformApplicationResponse, CreateTopicResponse, + DelegatesList, + Endpoint, EndpointDisabledException, + GetDataProtectionPolicyResponse, + GetEndpointAttributesResponse, + GetPlatformApplicationAttributesResponse, + GetSMSAttributesResponse, GetSubscriptionAttributesResponse, GetTopicAttributesResponse, + InvalidBatchEntryIdException, InvalidParameterException, InvalidParameterValueException, + ListEndpointsByPlatformApplicationResponse, + ListPhoneNumbersOptedOutResponse, + ListPlatformApplicationsResponse, + ListString, ListSubscriptionsByTopicResponse, ListSubscriptionsResponse, ListTagsForResourceResponse, + ListTopicsResponse, MapStringToString, MessageAttributeMap, NotFoundException, + OptInPhoneNumberResponse, + PhoneNumber, + PlatformApplication, PublishBatchRequestEntryList, PublishBatchResponse, PublishBatchResultEntry, PublishResponse, + SetSMSAttributesResponse, SnsApi, String, SubscribeResponse, Subscription, SubscriptionAttributesMap, + Tag, TagKeyList, TagList, TagResourceResponse, @@ -49,26 +64,55 @@ attributeName, attributeValue, authenticateOnUnsubscribe, - boolean, + endpoint, + label, + message, messageStructure, nextToken, + protocol, + string, + subject, subscriptionARN, topicARN, topicName, ) from localstack.constants import AWS_REGION_US_EAST_1, DEFAULT_AWS_ACCOUNT_ID -from localstack.http import Request, Response, Router, route +from localstack.http import Response from localstack.services.edge import ROUTER -from localstack.services.moto import call_moto from localstack.services.plugins import ServiceLifecycleHook -from localstack.services.sns import constants as sns_constants +from localstack.services.sns.analytics import internal_api_calls from localstack.services.sns.certificate import SNS_SERVER_CERT +from localstack.services.sns.constants import ( + ATTR_TYPE_REGEX, + BATCH_ENTRY_ID_REGEX, + DUMMY_SUBSCRIPTION_PRINCIPAL, + E164_REGEX, + MAXIMUM_MESSAGE_LENGTH, + MSG_ATTR_NAME_REGEX, + PLATFORM_ENDPOINT_MSGS_ENDPOINT, + SMS_MSGS_ENDPOINT, + SMS_PHONE_NUMBER_OPT_OUT_ENDPOINT, + SNS_CERT_ENDPOINT, + SNS_PROTOCOLS, + SUBSCRIPTION_TOKENS_ENDPOINT, + VALID_APPLICATION_PLATFORMS, + VALID_MSG_ATTR_NAME_CHARS, + VALID_POLICY_ACTIONS, + VALID_SUBSCRIPTION_ATTR_NAME, +) from localstack.services.sns.filter import FilterPolicyValidator from localstack.services.sns.models import ( + SMS_ATTRIBUTE_NAMES, + SMS_DEFAULT_SENDER_REGEX, + SMS_TYPES, + EndpointAttributeNames, + PlatformApplicationDetails, + PlatformEndpoint, SnsMessage, SnsMessageType, SnsStore, SnsSubscription, + Topic, sns_stores, ) from localstack.services.sns.publisher import ( @@ -76,48 +120,47 @@ SnsBatchPublishContext, SnsPublishContext, ) +from localstack.services.sns.utils import ( + create_default_topic_policy, + create_platform_endpoint_arn, + create_subscription_arn, + encode_subscription_token_with_region, + get_next_page_token_from_arn, + get_region_from_subscription_token, + get_topic_subscriptions, + is_valid_e164_number, + parse_and_validate_platform_application_arn, + parse_and_validate_topic_arn, + validate_subscription_attribute, +) +from localstack.state import StateVisitor from localstack.utils.aws.arns import ( - ArnData, extract_account_id_from_arn, extract_region_from_arn, get_partition, parse_arn, + sns_platform_application_arn, + sns_topic_arn, ) from localstack.utils.collections import PaginatedList, select_from_typed_dict -from localstack.utils.strings import short_uid, to_bytes, to_str - -from .analytics import internal_api_calls +from localstack.utils.strings import to_bytes # set up logger LOG = logging.getLogger(__name__) +SNS_TOPIC_NAME_PATTERN_FIFO = r"^[a-zA-Z0-9_-]{1,256}\.fifo$" +SNS_TOPIC_NAME_PATTERN = r"^[a-zA-Z0-9_-]{1,256}$" -class SnsProvider(SnsApi, ServiceLifecycleHook): - """ - Provider class for AWS Simple Notification Service. - - AWS supports following operations in a cross-account setup: - - GetTopicAttributes - - SetTopicAttributes - - AddPermission - - RemovePermission - - Publish - - Subscribe - - ListSubscriptionByTopic - - DeleteTopic - """ - - @route(sns_constants.SNS_CERT_ENDPOINT, methods=["GET"]) - def get_signature_cert_pem_file(self, request: Request): - # see http://sns-public-resources.s3.amazonaws.com/SNS_Message_Signing_Release_Note_Jan_25_2011.pdf - # see https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html - return Response(self._signature_cert_pem, 200) +class SnsProvider(SnsApi, ServiceLifecycleHook): def __init__(self) -> None: super().__init__() self._publisher = PublishDispatcher() self._signature_cert_pem: str = SNS_SERVER_CERT + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(sns_stores) + def on_before_stop(self): self._publisher.shutdown() @@ -127,299 +170,329 @@ def on_after_init(self): # add the route to serve the certificate used to validate message signatures ROUTER.add(self.get_signature_cert_pem_file) - @staticmethod - def get_store(account_id: str, region_name: str) -> SnsStore: - return sns_stores[account_id][region_name] + @route(SNS_CERT_ENDPOINT, methods=["GET"]) + def get_signature_cert_pem_file(self, request: Request): + # see http://sns-public-resources.s3.amazonaws.com/SNS_Message_Signing_Release_Note_Jan_25_2011.pdf + # see https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html + return Response(self._signature_cert_pem, 200) - @staticmethod - def get_moto_backend(account_id: str, region_name: str) -> SNSBackend: - return sns_backends[account_id][region_name] + # Tag Utils - @staticmethod - def _get_topic(arn: str, context: RequestContext) -> Topic: + def _check_matching_tags( + self, context: RequestContext, topic_arn: str, tags: TagList | None + ) -> bool: """ - :param arn: the Topic ARN - :param context: the RequestContext of the request - :param multiregion: if the request can fetch the topic across regions or not (ex. Publish cannot publish to a - topic in a different region than the request) - :return: the Moto model Topic + Checks if a topic to be created doesn't already exist with different tags + :param context: The context of the original request + :param topic_arn: Arn of the topic + :param tags: Tags to be checked + :return: False if there is a mismatch in tags, True otherwise """ - arn_data = parse_and_validate_topic_arn(arn) - if context.region != arn_data["region"]: - raise InvalidParameterException("Invalid parameter: TopicArn") - - try: - return sns_backends[arn_data["account"]][context.region].topics[arn] - except KeyError: - raise NotFoundException("Topic does not exist") + store = self.get_store(context.account_id, context.region) + existing_tags = self._list_resource_tags(context, resource_arn=topic_arn) + # if this is none there is nothing to check + if topic_arn in store.topics: + if tags is None: + tags = [] + for tag in tags: + # this means topic already created with empty tags and when we try to create it + # again with other tag value then it should fail according to aws documentation. + if existing_tags is not None and tag not in existing_tags: + return False + return True + + def _list_resource_tags(self, context: RequestContext, resource_arn: str) -> TagList: + store = self.get_store(context.account_id, context.region) + tags = store.tags.get_tags(resource_arn) + return [Tag(Key=key, Value=value) for key, value in tags.items()] - def get_topic_attributes( - self, context: RequestContext, topic_arn: topicARN, **kwargs - ) -> GetTopicAttributesResponse: - # get the Topic from moto manually first, because Moto does not handle well the case where the ARN is malformed - # (raises ValueError: not enough values to unpack (expected 6, got 1)) - moto_topic_model = self._get_topic(topic_arn, context) - moto_response: GetTopicAttributesResponse = call_moto(context) - # TODO: fix some attributes by moto, see snapshot - # DeliveryPolicy - # EffectiveDeliveryPolicy - # Policy.Statement..Action -> SNS:Receive is added by moto but not returned in AWS - # TODO: very hacky way to get the attributes we need instead of a moto patch - # see the attributes we need: https://docs.aws.amazon.com/sns/latest/dg/sns-topic-attributes.html - # would need more work to have the proper format out of moto, maybe extract the model to our store - attributes = moto_response["Attributes"] - for attr in vars(moto_topic_model): - if "_feedback" in attr: - key = camelcase_to_pascal(underscores_to_camelcase(attr)) - attributes[key] = getattr(moto_topic_model, attr) - elif attr == "signature_version": - attributes["SignatureVersion"] = moto_topic_model.signature_version - elif attr == "archive_policy": - attributes["ArchivePolicy"] = moto_topic_model.archive_policy - - return moto_response + def _tag_resource(self, context: RequestContext, resource_arn: str, tags: TagList) -> None: + store = self.get_store(context.account_id, context.region) + store.tags.update_tags(resource_arn, {tag["Key"]: tag["Value"] for tag in tags}) - def set_topic_attributes( - self, - context: RequestContext, - topic_arn: topicARN, - attribute_name: attributeName, - attribute_value: attributeValue | None = None, - **kwargs, + def _untag_resource( + self, context: RequestContext, resource_arn: str, tag_keys: TagKeyList ) -> None: - # validate the topic first - self._get_topic(topic_arn, context) - call_moto(context) + store = self.get_store(context.account_id, context.region) + store.tags.delete_tags(resource_arn, tag_keys) - def publish_batch( + def _remove_resource_tags(self, context: RequestContext, resource_arn: str) -> None: + store = self.get_store(context.account_id, context.region) + store.tags.delete_all_tags(resource_arn) + + ## Topic Operations + + def create_topic( self, context: RequestContext, - topic_arn: topicARN, - publish_batch_request_entries: PublishBatchRequestEntryList, + name: topicName, + attributes: TopicAttributesMap | None = None, + tags: TagList | None = None, + data_protection_policy: attributeValue | None = None, **kwargs, - ) -> PublishBatchResponse: - if len(publish_batch_request_entries) > 10: - raise TooManyEntriesInBatchRequestException( - "The batch request contains more entries than permissible." - ) - - parsed_arn = parse_and_validate_topic_arn(topic_arn) - store = self.get_store(account_id=parsed_arn["account"], region_name=context.region) - moto_topic = self._get_topic(topic_arn, context) - - ids = [entry["Id"] for entry in publish_batch_request_entries] - if len(set(ids)) != len(publish_batch_request_entries): - raise BatchEntryIdsNotDistinctException( - "Two or more batch entries in the request have the same Id." - ) - - response: PublishBatchResponse = {"Successful": [], "Failed": []} + ) -> CreateTopicResponse: + store = self.get_store(context.account_id, context.region) + topic_arn = sns_topic_arn( + topic_name=name, region_name=context.region, account_id=context.account_id + ) + attributes = dict(attributes) if attributes else {} + if attributes.get("FifoTopic") and attributes["FifoTopic"].lower() == "true": + pattern = SNS_TOPIC_NAME_PATTERN_FIFO + else: + # AWS does not seem to save explicit settings of fifo = false + attributes.pop("FifoTopic", None) + pattern = SNS_TOPIC_NAME_PATTERN + + if not re.match(pattern, name): + raise InvalidParameterException("Invalid parameter: Topic Name") + + if existing_topic := store.topics.get(topic_arn): + existing_attrs = existing_topic["attributes"] + # TODO: validate attribute names + for k, v in attributes.items(): + # special case for FifoTopic + if k == "FifoTopic" and v == "false" and "FifoTopic" not in existing_attrs: + continue + + if not existing_attrs.get(k) or not existing_attrs.get(k) == v: + raise InvalidParameterException( + "Invalid parameter: Attributes Reason: Topic already exists with different attributes" + ) + tag_resource_success = self._check_matching_tags(context, topic_arn, tags) + if not tag_resource_success: + raise InvalidParameterException( + "Invalid parameter: Tags Reason: Topic already exists with different tags" + ) + return CreateTopicResponse(TopicArn=topic_arn) - # TODO: write AWS validated tests with FilterPolicy and batching - # TODO: find a scenario where we can fail to send a message synchronously to be able to report it - # right now, it seems that AWS fails the whole publish if something is wrong in the format of 1 message + attributes["EffectiveDeliveryPolicy"] = _create_default_effective_delivery_policy() - total_batch_size = 0 - message_contexts = [] - for entry_index, entry in enumerate(publish_batch_request_entries, start=1): - message_payload = entry.get("Message") - message_attributes = entry.get("MessageAttributes", {}) - if message_attributes: - # if a message contains non-valid message attributes - # will fail for the first non-valid message encountered, and raise ParameterValueInvalid - validate_message_attributes(message_attributes, position=entry_index) + topic = _create_topic( + name=name, + attributes=attributes, + data_protection_policy=data_protection_policy, + context=context, + ) + if tags: + self._tag_resource(context, resource_arn=topic_arn, tags=tags) - total_batch_size += get_total_publish_size(message_payload, message_attributes) + store.topics[topic_arn] = topic - # TODO: WRITE AWS VALIDATED - if entry.get("MessageStructure") == "json": - try: - message = json.loads(message_payload) - # Keys in the JSON object that correspond to supported transport protocols must have - # simple JSON string values. - # Non-string values will cause the key to be ignored. - message = { - key: field for key, field in message.items() if isinstance(field, str) - } - if "default" not in message: - raise InvalidParameterException( - "Invalid parameter: Message Structure - No default entry in JSON message body" - ) - entry["Message"] = message # noqa - except json.JSONDecodeError: - raise InvalidParameterException( - "Invalid parameter: Message Structure - JSON message body failed to parse" - ) + return CreateTopicResponse(TopicArn=topic_arn) - if is_fifo := (".fifo" in topic_arn): - if not all("MessageGroupId" in entry for entry in publish_batch_request_entries): - raise InvalidParameterException( - "Invalid parameter: The MessageGroupId parameter is required for FIFO topics" - ) - if moto_topic.content_based_deduplication == "false": - if not all( - "MessageDeduplicationId" in entry for entry in publish_batch_request_entries - ): - raise InvalidParameterException( - "Invalid parameter: The topic should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly", - ) + def get_topic_attributes( + self, context: RequestContext, topic_arn: topicARN, **kwargs + ) -> GetTopicAttributesResponse: + topic: Topic = self._get_topic(arn=topic_arn, context=context) + if topic: + attributes = topic["attributes"] + return GetTopicAttributesResponse(Attributes=attributes) + else: + raise NotFoundException("Topic does not exist") - msg_ctx = SnsMessage.from_batch_entry(entry, is_fifo=is_fifo) - message_contexts.append(msg_ctx) - success = PublishBatchResultEntry( - Id=entry["Id"], - MessageId=msg_ctx.message_id, - ) - if is_fifo: - success["SequenceNumber"] = msg_ctx.sequencer_number - response["Successful"].append(success) + def delete_topic(self, context: RequestContext, topic_arn: topicARN, **kwargs) -> None: + # This also deletes all subscriptions for the topic. In AWS, this is not immediately the case; + # the subs still exist for a certain period of time (~48h), detached, after which they are garbage collected + arn_data = parse_and_validate_topic_arn(topic_arn) + if context.region != arn_data["region"]: + raise InvalidParameterException("Invalid parameter: TopicArn") - if total_batch_size > MAXIMUM_MESSAGE_LENGTH: - raise CommonServiceException( - code="BatchRequestTooLong", - message="The length of all the messages put together is more than the limit.", - sender_fault=True, - ) + store = self.get_store(context.account_id, context.region) + self._remove_resource_tags(context, topic_arn) + store.topics.pop(topic_arn, None) - publish_ctx = SnsBatchPublishContext( - messages=message_contexts, - store=store, - request_headers=context.request.headers, - topic_attributes=vars(moto_topic), + def list_topics( + self, context: RequestContext, next_token: nextToken | None = None, **kwargs + ) -> ListTopicsResponse: + store = self.get_store(context.account_id, context.region) + topics = [{"TopicArn": t["arn"]} for t in list(store.topics.values())] + topics = PaginatedList(topics) + page, nxt = topics.get_page( + token_generator=lambda x: get_next_page_token_from_arn(x["TopicArn"]), + next_token=next_token, + page_size=100, ) - self._publisher.publish_batch_to_topic(publish_ctx, topic_arn) - - return response + topics = {"Topics": page, "NextToken": nxt} + return ListTopicsResponse(**topics) - def set_subscription_attributes( + def set_topic_attributes( self, context: RequestContext, - subscription_arn: subscriptionARN, + topic_arn: topicARN, attribute_name: attributeName, - attribute_value: attributeValue = None, + attribute_value: attributeValue | None = None, **kwargs, ) -> None: - store = self.get_store(account_id=context.account_id, region_name=context.region) - sub = store.subscriptions.get(subscription_arn) - if not sub: - raise NotFoundException("Subscription does not exist") - - validate_subscription_attribute( - attribute_name=attribute_name, - attribute_value=attribute_value, - topic_arn=sub["TopicArn"], - endpoint=sub["Endpoint"], - ) - if attribute_name == "RawMessageDelivery": - attribute_value = attribute_value.lower() + topic: Topic = self._get_topic(arn=topic_arn, context=context) + if attribute_name == "FifoTopic": + raise InvalidParameterException("Invalid parameter: AttributeName") + topic["attributes"][attribute_name] = attribute_value - elif attribute_name == "FilterPolicy": - filter_policy = json.loads(attribute_value) if attribute_value else None - if filter_policy: - validator = FilterPolicyValidator( - scope=sub.get("FilterPolicyScope", "MessageAttributes"), - is_subscribe_call=False, - ) - validator.validate_filter_policy(filter_policy) + ## Subscribe operations - store.subscription_filter_policy[subscription_arn] = filter_policy - - sub[attribute_name] = attribute_value - - def confirm_subscription( + def subscribe( self, context: RequestContext, topic_arn: topicARN, - token: String, - authenticate_on_unsubscribe: authenticateOnUnsubscribe = None, + protocol: protocol, + endpoint: endpoint | None = None, + attributes: SubscriptionAttributesMap | None = None, + return_subscription_arn: bool | None = None, **kwargs, - ) -> ConfirmSubscriptionResponse: - # TODO: validate format on the token (seems to be 288 hex chars) - # this request can come from any http client, it might not be signed (we would need to implement - # `authenticate_on_unsubscribe` to force a signing client to do this request. - # so, the region and account_id might not be in the request. Use the ones from the topic_arn - try: - parsed_arn = parse_arn(topic_arn) - except InvalidArnException: - raise InvalidParameterException("Invalid parameter: Topic") + ) -> SubscribeResponse: + parsed_topic_arn = parse_and_validate_topic_arn(topic_arn) + if context.region != parsed_topic_arn["region"]: + raise InvalidParameterException("Invalid parameter: TopicArn") - store = self.get_store(account_id=parsed_arn["account"], region_name=parsed_arn["region"]) + store = self.get_store(account_id=parsed_topic_arn["account"], region=context.region) - # it seems SNS is able to know what the region of the topic should be, even though a wrong topic is accepted - if parsed_arn["region"] != get_region_from_subscription_token(token): - raise InvalidParameterException("Invalid parameter: Topic") + topic = self._get_topic(arn=topic_arn, context=context) + topic_subscriptions = topic["subscriptions"] + if not endpoint: + # TODO: check AWS behaviour (because endpoint is optional) + raise NotFoundException("Endpoint not specified in subscription") + if protocol not in SNS_PROTOCOLS: + raise InvalidParameterException( + f"Invalid parameter: Amazon SNS does not support this protocol string: {protocol}" + ) + elif protocol in ["http", "https"] and not endpoint.startswith(f"{protocol}://"): + raise InvalidParameterException( + "Invalid parameter: Endpoint must match the specified protocol" + ) + elif protocol == "sms" and not is_valid_e164_number(endpoint): + raise InvalidParameterException(f"Invalid SMS endpoint: {endpoint}") - subscription_arn = store.subscription_tokens.get(token) - if not subscription_arn: - raise InvalidParameterException("Invalid parameter: Token") + elif protocol == "sqs": + try: + parse_arn(endpoint) + except InvalidArnException: + raise InvalidParameterException("Invalid parameter: SQS endpoint ARN") - subscription = store.subscriptions.get(subscription_arn) - if not subscription: - # subscription could have been deleted in the meantime - raise InvalidParameterException("Invalid parameter: Token") + elif protocol == "application": + # TODO: Validate exact behaviour + try: + parse_arn(endpoint) + except InvalidArnException: + raise InvalidParameterException("Invalid parameter: ApplicationEndpoint ARN") - # ConfirmSubscription is idempotent - if subscription.get("PendingConfirmation") == "false": - return ConfirmSubscriptionResponse(SubscriptionArn=subscription_arn) + if ".fifo" in endpoint and ".fifo" not in topic_arn: + # TODO: move to sqs protocol block if possible + raise InvalidParameterException( + "Invalid parameter: Invalid parameter: Endpoint Reason: FIFO SQS Queues can not be subscribed to standard SNS topics" + ) - subscription["PendingConfirmation"] = "false" - subscription["ConfirmationWasAuthenticated"] = "true" + sub_attributes = copy.deepcopy(attributes) if attributes else None + if sub_attributes: + for attr_name, attr_value in sub_attributes.items(): + validate_subscription_attribute( + attribute_name=attr_name, + attribute_value=attr_value, + topic_arn=topic_arn, + endpoint=endpoint, + is_subscribe_call=True, + ) + if raw_msg_delivery := sub_attributes.get("RawMessageDelivery"): + sub_attributes["RawMessageDelivery"] = raw_msg_delivery.lower() - return ConfirmSubscriptionResponse(SubscriptionArn=subscription_arn) + # An endpoint may only be subscribed to a topic once. Subsequent + # subscribe calls do nothing (subscribe is idempotent), except if its attributes are different. + for existing_topic_subscription in topic_subscriptions: + sub = store.subscriptions.get(existing_topic_subscription, {}) + if sub.get("Endpoint") == endpoint: + if sub_attributes: + # validate the subscription attributes aren't different + for attr in VALID_SUBSCRIPTION_ATTR_NAME: + # if a new attribute is present and different from an existent one, raise + if (new_attr := sub_attributes.get(attr)) and sub.get(attr) != new_attr: + raise InvalidParameterException( + "Invalid parameter: Attributes Reason: Subscription already exists with different attributes" + ) - def untag_resource( - self, - context: RequestContext, - resource_arn: AmazonResourceName, - tag_keys: TagKeyList, - **kwargs, - ) -> UntagResourceResponse: - call_moto(context) - # TODO: probably get the account_id and region from the `resource_arn` - store = self.get_store(context.account_id, context.region) - existing_tags = store.sns_tags.setdefault(resource_arn, []) - store.sns_tags[resource_arn] = [t for t in existing_tags if t["Key"] not in tag_keys] - return UntagResourceResponse() + return SubscribeResponse(SubscriptionArn=sub["SubscriptionArn"]) + principal = DUMMY_SUBSCRIPTION_PRINCIPAL.format( + partition=get_partition(context.region), account_id=context.account_id + ) + subscription_arn = create_subscription_arn(topic_arn) + subscription = SnsSubscription( + # http://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html + TopicArn=topic_arn, + Endpoint=endpoint, + Protocol=protocol, + SubscriptionArn=subscription_arn, + PendingConfirmation="true", + Owner=context.account_id, + RawMessageDelivery="false", # default value, will be overridden if set + FilterPolicyScope="MessageAttributes", # default value, will be overridden if set + SubscriptionPrincipal=principal, # dummy value, could be fetched with a call to STS? + ) + if sub_attributes: + subscription.update(sub_attributes) + if "FilterPolicy" in sub_attributes: + filter_policy = ( + json.loads(sub_attributes["FilterPolicy"]) + if sub_attributes["FilterPolicy"] + else None + ) + if filter_policy: + validator = FilterPolicyValidator( + scope=subscription.get("FilterPolicyScope", "MessageAttributes"), + is_subscribe_call=True, + ) + validator.validate_filter_policy(filter_policy) - def list_tags_for_resource( - self, context: RequestContext, resource_arn: AmazonResourceName, **kwargs - ) -> ListTagsForResourceResponse: - # TODO: probably get the account_id and region from the `resource_arn` - store = self.get_store(context.account_id, context.region) - tags = store.sns_tags.setdefault(resource_arn, []) - return ListTagsForResourceResponse(Tags=tags) + store.subscription_filter_policy[subscription_arn] = filter_policy - def create_platform_application( - self, - context: RequestContext, - name: String, - platform: String, - attributes: MapStringToString, - **kwargs, - ) -> CreatePlatformApplicationResponse: - # TODO: validate platform - # see https://docs.aws.amazon.com/cli/latest/reference/sns/create-platform-application.html - # list of possible values: ADM, Baidu, APNS, APNS_SANDBOX, GCM, MPNS, WNS - # each platform has a specific way to handle credentials - # this can also be used for dispatching message to the right platform - return call_moto(context) + store.subscriptions[subscription_arn] = subscription - def create_platform_endpoint( - self, - context: RequestContext, - platform_application_arn: String, - token: String, - custom_user_data: String = None, - attributes: MapStringToString = None, - **kwargs, - ) -> CreateEndpointResponse: - # TODO: support mobile app events - # see https://docs.aws.amazon.com/sns/latest/dg/application-event-notifications.html - return call_moto(context) + topic_subscriptions.append(subscription_arn) + + # store the token and subscription arn + # TODO: the token is a 288 hex char string + subscription_token = encode_subscription_token_with_region(region=context.region) + store.subscription_tokens[subscription_token] = subscription_arn + + response_subscription_arn = subscription_arn + # Send out confirmation message for HTTP(S), fix for https://github.com/localstack/localstack/issues/881 + if protocol in ["http", "https"]: + message_ctx = SnsMessage( + type=SnsMessageType.SubscriptionConfirmation, + token=subscription_token, + message=f"You have chosen to subscribe to the topic {topic_arn}.\nTo confirm the subscription, visit the SubscribeURL included in this message.", + ) + publish_ctx = SnsPublishContext( + message=message_ctx, + store=store, + request_headers=context.request.headers, + topic_attributes=topic["attributes"], + ) + self._publisher.publish_to_topic_subscriber( + ctx=publish_ctx, + topic_arn=topic_arn, + subscription_arn=subscription_arn, + ) + if not return_subscription_arn: + response_subscription_arn = "pending confirmation" + + elif protocol not in ["email", "email-json"]: + # Only HTTP(S) and email subscriptions are not auto validated + # Except if the endpoint and the topic are not in the same AWS account, then you'd need to manually confirm + # the subscription with the token + # TODO: revisit for multi-account + # TODO: test with AWS for email & email-json confirmation message + # we need to add the following check: + # if parsed_topic_arn["account"] == endpoint account (depending on the type, SQS, lambda, parse the arn) + subscription["PendingConfirmation"] = "false" + subscription["ConfirmationWasAuthenticated"] = "true" + + return SubscribeResponse(SubscriptionArn=response_subscription_arn) def unsubscribe( self, context: RequestContext, subscription_arn: subscriptionARN, **kwargs ) -> None: + if subscription_arn is None: + raise InvalidParameterException( + "Invalid parameter: SubscriptionArn Reason: no value for required parameter", + ) count = len(subscription_arn.split(":")) try: parsed_arn = parse_arn(subscription_arn) @@ -432,12 +505,12 @@ def unsubscribe( account_id = parsed_arn["account"] region_name = parsed_arn["region"] - store = self.get_store(account_id=account_id, region_name=region_name) + store = self.get_store(account_id=account_id, region=region_name) if count == 6 and subscription_arn not in store.subscriptions: raise InvalidParameterException("Invalid parameter: SubscriptionId") - moto_sns_backend = self.get_moto_backend(account_id, region_name) - moto_sns_backend.unsubscribe(subscription_arn) + # TODO: here was a moto_backend.unsubscribe call, check correct functionality and remove this comment + # before switching to v2 for production # pop the subscription at the end, to avoid race condition by iterating over the topic subscriptions subscription = store.subscriptions.get(subscription_arn) @@ -457,12 +530,12 @@ def unsubscribe( token=subscription_token, message=f"You have chosen to deactivate subscription {subscription_arn}.\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message.", ) - moto_topic = moto_sns_backend.topics.get(subscription["TopicArn"]) publish_ctx = SnsPublishContext( message=message_ctx, store=store, request_headers=context.request.headers, - topic_attributes=vars(moto_topic), + # TODO: add the topic attributes once we ported them from moto to LocalStack + # topic_attributes=vars(moto_topic), ) self._publisher.publish_to_topic_subscriber( publish_ctx, @@ -470,14 +543,15 @@ def unsubscribe( subscription_arn=subscription_arn, ) - store.topic_subscriptions[subscription["TopicArn"]].remove(subscription_arn) + with contextlib.suppress(KeyError): + store.topics[subscription["TopicArn"]]["subscriptions"].remove(subscription_arn) store.subscription_filter_policy.pop(subscription_arn, None) store.subscriptions.pop(subscription_arn, None) def get_subscription_attributes( self, context: RequestContext, subscription_arn: subscriptionARN, **kwargs ) -> GetSubscriptionAttributesResponse: - store = self.get_store(account_id=context.account_id, region_name=context.region) + store = self.get_store(account_id=context.account_id, region=context.region) sub = store.subscriptions.get(subscription_arn) if not sub: raise NotFoundException("Subscription does not exist") @@ -491,13 +565,93 @@ def get_subscription_attributes( attributes = {k: v for k, v in sub.items() if k not in removed_attrs} return GetSubscriptionAttributesResponse(Attributes=attributes) + def set_subscription_attributes( + self, + context: RequestContext, + subscription_arn: subscriptionARN, + attribute_name: attributeName, + attribute_value: attributeValue = None, + **kwargs, + ) -> None: + store = self.get_store(account_id=context.account_id, region=context.region) + sub = store.subscriptions.get(subscription_arn) + if not sub: + raise NotFoundException("Subscription does not exist") + + validate_subscription_attribute( + attribute_name=attribute_name, + attribute_value=attribute_value, + topic_arn=sub["TopicArn"], + endpoint=sub["Endpoint"], + ) + if attribute_name == "RawMessageDelivery": + attribute_value = attribute_value.lower() + + elif attribute_name == "FilterPolicy": + filter_policy = json.loads(attribute_value) if attribute_value else None + if filter_policy: + validator = FilterPolicyValidator( + scope=sub.get("FilterPolicyScope", "MessageAttributes"), + is_subscribe_call=False, + ) + validator.validate_filter_policy(filter_policy) + + store.subscription_filter_policy[subscription_arn] = filter_policy + + sub[attribute_name] = attribute_value + + def confirm_subscription( + self, + context: RequestContext, + topic_arn: topicARN, + token: String, + authenticate_on_unsubscribe: authenticateOnUnsubscribe = None, + **kwargs, + ) -> ConfirmSubscriptionResponse: + # TODO: validate format on the token (seems to be 288 hex chars) + # this request can come from any http client, it might not be signed (we would need to implement + # `authenticate_on_unsubscribe` to force a signing client to do this request. + # so, the region and account_id might not be in the request. Use the ones from the topic_arn + try: + parsed_arn = parse_arn(topic_arn) + except InvalidArnException: + raise InvalidParameterException("Invalid parameter: Topic") + + store = self.get_store(account_id=parsed_arn["account"], region=parsed_arn["region"]) + + # it seems SNS is able to know what the region of the topic should be, even though a wrong topic is accepted + if parsed_arn["region"] != get_region_from_subscription_token(token): + raise InvalidParameterException("Invalid parameter: Topic") + + subscription_arn = store.subscription_tokens.get(token) + if not subscription_arn: + raise InvalidParameterException("Invalid parameter: Token") + + subscription = store.subscriptions.get(subscription_arn) + if not subscription: + # subscription could have been deleted in the meantime + raise InvalidParameterException("Invalid parameter: Token") + + # ConfirmSubscription is idempotent + if subscription.get("PendingConfirmation") == "false": + return ConfirmSubscriptionResponse(SubscriptionArn=subscription_arn) + + subscription["PendingConfirmation"] = "false" + subscription["ConfirmationWasAuthenticated"] = "true" + + return ConfirmSubscriptionResponse(SubscriptionArn=subscription_arn) + def list_subscriptions( self, context: RequestContext, next_token: nextToken = None, **kwargs ) -> ListSubscriptionsResponse: store = self.get_store(context.account_id, context.region) - subscriptions = [ - select_from_typed_dict(Subscription, sub) for sub in list(store.subscriptions.values()) - ] + subscriptions = [] + for s in list(store.subscriptions.values()): + sub = select_from_typed_dict(Subscription, s) + if s["PendingConfirmation"] == "true": + sub["SubscriptionArn"] = "PendingConfirmation" + subscriptions.append(sub) + paginated_subscriptions = PaginatedList(subscriptions) page, next_token = paginated_subscriptions.get_page( token_generator=lambda x: get_next_page_token_from_arn(x["SubscriptionArn"]), @@ -513,11 +667,10 @@ def list_subscriptions( def list_subscriptions_by_topic( self, context: RequestContext, topic_arn: topicARN, next_token: nextToken = None, **kwargs ) -> ListSubscriptionsByTopicResponse: - self._get_topic(topic_arn, context) + self._get_topic(topic_arn, context) # for validation purposes only parsed_topic_arn = parse_and_validate_topic_arn(topic_arn) store = self.get_store(parsed_topic_arn["account"], parsed_topic_arn["region"]) - sns_subscriptions = store.get_topic_subscriptions(topic_arn) - subscriptions = [select_from_typed_dict(Subscription, sub) for sub in sns_subscriptions] + subscriptions = get_topic_subscriptions(store, topic_arn) paginated_subscriptions = PaginatedList(subscriptions) page, next_token = paginated_subscriptions.get_page( @@ -531,18 +684,22 @@ def list_subscriptions_by_topic( response["NextToken"] = next_token return response + # + # Publish + # + def publish( self, context: RequestContext, - message: String, - topic_arn: topicARN = None, - target_arn: String = None, - phone_number: String = None, - subject: String = None, - message_structure: messageStructure = None, - message_attributes: MessageAttributeMap = None, - message_deduplication_id: String = None, - message_group_id: String = None, + message: message, + topic_arn: topicARN | None = None, + target_arn: String | None = None, + phone_number: PhoneNumber | None = None, + subject: subject | None = None, + message_structure: messageStructure | None = None, + message_attributes: MessageAttributeMap | None = None, + message_deduplication_id: String | None = None, + message_group_id: String | None = None, **kwargs, ) -> PublishResponse: if subject == "": @@ -552,29 +709,29 @@ def publish( # TODO: check for topic + target + phone number at the same time? # TODO: more validation on phone, it might be opted out? - if phone_number and not is_e164(phone_number): + if phone_number and not is_valid_e164_number(phone_number): raise InvalidParameterException( f"Invalid parameter: PhoneNumber Reason: {phone_number} is not valid to publish to" ) if message_attributes: - validate_message_attributes(message_attributes) + _validate_message_attributes(message_attributes) - if get_total_publish_size(message, message_attributes) > MAXIMUM_MESSAGE_LENGTH: + if _get_total_publish_size(message, message_attributes) > MAXIMUM_MESSAGE_LENGTH: raise InvalidParameterException("Invalid parameter: Message too long") # for compatibility reasons, AWS allows users to use either TargetArn or TopicArn for publishing to a topic # use any of them for topic validation topic_or_target_arn = topic_arn or target_arn - topic_model = None + topic = None if is_fifo := (topic_or_target_arn and ".fifo" in topic_or_target_arn): if not message_group_id: raise InvalidParameterException( "Invalid parameter: The MessageGroupId parameter is required for FIFO topics", ) - topic_model = self._get_topic(topic_or_target_arn, context) - if topic_model.content_based_deduplication == "false": + topic = self._get_topic(topic_or_target_arn, context) + if topic["attributes"]["ContentBasedDeduplication"] == "false": if not message_deduplication_id: raise InvalidParameterException( "Invalid parameter: The topic should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly", @@ -584,10 +741,7 @@ def publish( raise InvalidParameterException( "Invalid parameter: MessageDeduplicationId Reason: The request includes MessageDeduplicationId parameter that is not valid for this topic type" ) - elif message_group_id: - raise InvalidParameterException( - "Invalid parameter: MessageGroupId Reason: The request includes MessageGroupId parameter that is not valid for this topic type" - ) + is_endpoint_publish = target_arn and ":endpoint/" in target_arn if message_structure == "json": try: @@ -610,20 +764,24 @@ def publish( if not phone_number: # use the account to get the store from the TopicArn (you can only publish in the same region as the topic) parsed_arn = parse_and_validate_topic_arn(topic_or_target_arn) - store = self.get_store(account_id=parsed_arn["account"], region_name=context.region) - moto_sns_backend = self.get_moto_backend(parsed_arn["account"], context.region) + store = self.get_store(account_id=parsed_arn["account"], region=context.region) if is_endpoint_publish: - if not (platform_endpoint := moto_sns_backend.platform_endpoints.get(target_arn)): + if not (platform_endpoint := store.platform_endpoints.get(target_arn)): raise InvalidParameterException( "Invalid parameter: TargetArn Reason: No endpoint found for the target arn specified" ) - elif not platform_endpoint.enabled: + elif ( + not platform_endpoint.platform_endpoint["Attributes"] + .get("Enabled", "false") + .lower() + == "true" + ): raise EndpointDisabledException("Endpoint is disabled") else: - topic_model = self._get_topic(topic_or_target_arn, context) + topic = self._get_topic(topic_or_target_arn, context) else: # use the store from the request context - store = self.get_store(account_id=context.account_id, region_name=context.region) + store = self.get_store(account_id=context.account_id, region=context.region) message_ctx = SnsMessage( type=SnsMessageType.Notification, @@ -649,7 +807,7 @@ def publish( # beware if the subscription is FIFO, the order might not be guaranteed. # 2 quick call to this method in succession might not be executed in order in the executor? # TODO: test how this behaves in a FIFO context with a lot of threads. - publish_ctx.topic_attributes |= vars(topic_model) + publish_ctx.topic_attributes |= topic["attributes"] self._publisher.publish_to_topic(publish_ctx, topic_or_target_arn) if is_fifo: @@ -659,300 +817,583 @@ def publish( return PublishResponse(MessageId=message_ctx.message_id) - def subscribe( + def publish_batch( self, context: RequestContext, topic_arn: topicARN, - protocol: String, - endpoint: String = None, - attributes: SubscriptionAttributesMap = None, - return_subscription_arn: boolean = None, + publish_batch_request_entries: PublishBatchRequestEntryList, **kwargs, - ) -> SubscribeResponse: - # TODO: check validation ordering - parsed_topic_arn = parse_and_validate_topic_arn(topic_arn) - if context.region != parsed_topic_arn["region"]: - raise InvalidParameterException("Invalid parameter: TopicArn") + ) -> PublishBatchResponse: + if len(publish_batch_request_entries) > 10: + raise TooManyEntriesInBatchRequestException( + "The batch request contains more entries than permissible." + ) - store = self.get_store(account_id=parsed_topic_arn["account"], region_name=context.region) + parsed_arn = parse_and_validate_topic_arn(topic_arn) + store = self.get_store(account_id=parsed_arn["account"], region=context.region) + topic = self._get_topic(topic_arn, context) + ids = [entry["Id"] for entry in publish_batch_request_entries] + if len(set(ids)) != len(publish_batch_request_entries): + raise BatchEntryIdsNotDistinctException( + "Two or more batch entries in the request have the same Id." + ) - if topic_arn not in store.topic_subscriptions: - raise NotFoundException("Topic does not exist") + # Validate each entry ID + for entry_id in ids: + if len(entry_id) > 80: + raise InvalidBatchEntryIdException( + f"The Id of a batch entry in the batch request is too long: {entry_id}" + ) + if not BATCH_ENTRY_ID_REGEX.match(entry_id): + raise InvalidBatchEntryIdException( + f"The Id of a batch entry in the batch request contains an impermissible character: {entry_id}" + ) - if not endpoint: - # TODO: check AWS behaviour (because endpoint is optional) - raise NotFoundException("Endpoint not specified in subscription") - if protocol not in sns_constants.SNS_PROTOCOLS: - raise InvalidParameterException( - f"Invalid parameter: Amazon SNS does not support this protocol string: {protocol}" + response: PublishBatchResponse = {"Successful": [], "Failed": []} + + # TODO: write AWS validated tests with FilterPolicy and batching + # TODO: find a scenario where we can fail to send a message synchronously to be able to report it + # right now, it seems that AWS fails the whole publish if something is wrong in the format of 1 message + + total_batch_size = 0 + message_contexts = [] + for entry_index, entry in enumerate(publish_batch_request_entries, start=1): + message_payload = entry.get("Message") + message_attributes = entry.get("MessageAttributes", {}) + if message_attributes: + # if a message contains non-valid message attributes, it + # will fail for the first non-valid message encountered, and raise ParameterValueInvalid + _validate_message_attributes(message_attributes, position=entry_index) + + total_batch_size += _get_total_publish_size(message_payload, message_attributes) + + # TODO: WRITE AWS VALIDATED + if entry.get("MessageStructure") == "json": + try: + message = json.loads(message_payload) + # Keys in the JSON object that correspond to supported transport protocols must have + # simple JSON string values. + # Non-string values will cause the key to be ignored. + message = { + key: field for key, field in message.items() if isinstance(field, str) + } + if "default" not in message: + raise InvalidParameterException( + "Invalid parameter: Message Structure - No default entry in JSON message body" + ) + entry["Message"] = message # noqa + except json.JSONDecodeError: + raise InvalidParameterException( + "Invalid parameter: Message Structure - JSON message body failed to parse" + ) + + if is_fifo := (topic_arn.endswith(".fifo")): + if not all("MessageGroupId" in entry for entry in publish_batch_request_entries): + raise InvalidParameterException( + "Invalid parameter: The MessageGroupId parameter is required for FIFO topics" + ) + if topic["attributes"]["ContentBasedDeduplication"] == "false": + if not all( + "MessageDeduplicationId" in entry for entry in publish_batch_request_entries + ): + raise InvalidParameterException( + "Invalid parameter: The topic should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly", + ) + + msg_ctx = SnsMessage.from_batch_entry(entry, is_fifo=is_fifo) + message_contexts.append(msg_ctx) + success = PublishBatchResultEntry( + Id=entry["Id"], + MessageId=msg_ctx.message_id, ) - elif protocol in ["http", "https"] and not endpoint.startswith(f"{protocol}://"): + if is_fifo: + success["SequenceNumber"] = msg_ctx.sequencer_number + response["Successful"].append(success) + + if total_batch_size > MAXIMUM_MESSAGE_LENGTH: + raise CommonServiceException( + code="BatchRequestTooLong", + message="The length of all the messages put together is more than the limit.", + sender_fault=True, + ) + + publish_ctx = SnsBatchPublishContext( + messages=message_contexts, + store=store, + request_headers=context.request.headers, + topic_attributes=topic["attributes"], + ) + self._publisher.publish_batch_to_topic(publish_ctx, topic_arn) + + return response + + # + # PlatformApplications + # + def create_platform_application( + self, + context: RequestContext, + name: String, + platform: String, + attributes: MapStringToString, + **kwargs, + ) -> CreatePlatformApplicationResponse: + _validate_platform_application_name(name) + if platform not in VALID_APPLICATION_PLATFORMS: raise InvalidParameterException( - "Invalid parameter: Endpoint must match the specified protocol" + f"Invalid parameter: Platform Reason: {platform} is not supported" ) - elif protocol == "sms" and not is_e164(endpoint): - raise InvalidParameterException(f"Invalid SMS endpoint: {endpoint}") - elif protocol == "sqs": - try: - parse_arn(endpoint) - except InvalidArnException: - raise InvalidParameterException("Invalid parameter: SQS endpoint ARN") + _validate_platform_application_attributes(attributes) - elif protocol == "application": - # TODO: this is taken from moto, validate it - moto_backend = self.get_moto_backend( - account_id=parsed_topic_arn["account"], region_name=context.region + # attribute validation specific to create_platform_application + if "PlatformCredential" in attributes and "PlatformPrincipal" not in attributes: + raise InvalidParameterException( + "Invalid parameter: Attributes Reason: PlatformCredential attribute provided without PlatformPrincipal" ) - if endpoint not in moto_backend.platform_endpoints: - raise NotFoundException("Endpoint does not exist") - if ".fifo" in endpoint and ".fifo" not in topic_arn: + elif "PlatformPrincipal" in attributes and "PlatformCredential" not in attributes: raise InvalidParameterException( - "Invalid parameter: Invalid parameter: Endpoint Reason: FIFO SQS Queues can not be subscribed to standard SNS topics" + "Invalid parameter: Attributes Reason: PlatformPrincipal attribute provided without PlatformCredential" ) - sub_attributes = copy.deepcopy(attributes) if attributes else None - if sub_attributes: - for attr_name, attr_value in sub_attributes.items(): - validate_subscription_attribute( - attribute_name=attr_name, - attribute_value=attr_value, - topic_arn=topic_arn, - endpoint=endpoint, - is_subscribe_call=True, + store = self.get_store(context.account_id, context.region) + # We are not validating the access data here like AWS does (against ADM and the like) + attributes.pop("PlatformPrincipal") + attributes.pop("PlatformCredential") + _attributes = {"Enabled": "true"} + _attributes.update(attributes) + application_arn = sns_platform_application_arn( + platform_application_name=name, + platform=platform, + account_id=context.account_id, + region_name=context.region, + ) + platform_application_details = PlatformApplicationDetails( + platform_application=PlatformApplication( + PlatformApplicationArn=application_arn, + Attributes=_attributes, + ), + platform_endpoints={}, + ) + store.platform_applications[application_arn] = platform_application_details + + return platform_application_details.platform_application + + def delete_platform_application( + self, context: RequestContext, platform_application_arn: String, **kwargs + ) -> None: + store = self.get_store(context.account_id, context.region) + store.platform_applications.pop(platform_application_arn, None) + # TODO: if the platform had endpoints, should we remove them from the store? There is no way to list + # endpoints without an application, so this is impossible to check the state of AWS here + + def list_platform_applications( + self, context: RequestContext, next_token: String | None = None, **kwargs + ) -> ListPlatformApplicationsResponse: + store = self.get_store(context.account_id, context.region) + platform_applications = store.platform_applications.values() + paginated_applications = PaginatedList(platform_applications) + page, token = paginated_applications.get_page( + token_generator=lambda x: get_next_page_token_from_arn(x["PlatformApplicationArn"]), + page_size=100, + next_token=next_token, + ) + + response = ListPlatformApplicationsResponse( + PlatformApplications=[platform_app.platform_application for platform_app in page] + ) + if token: + response["NextToken"] = token + return response + + def get_platform_application_attributes( + self, context: RequestContext, platform_application_arn: String, **kwargs + ) -> GetPlatformApplicationAttributesResponse: + platform_application = self._get_platform_application(platform_application_arn, context) + attributes = platform_application["Attributes"] + return GetPlatformApplicationAttributesResponse(Attributes=attributes) + + def set_platform_application_attributes( + self, + context: RequestContext, + platform_application_arn: String, + attributes: MapStringToString, + **kwargs, + ) -> None: + parse_and_validate_platform_application_arn(platform_application_arn) + _validate_platform_application_attributes(attributes) + + platform_application = self._get_platform_application(platform_application_arn, context) + platform_application["Attributes"].update(attributes) + + # + # Platform Endpoints + # + + def create_platform_endpoint( + self, + context: RequestContext, + platform_application_arn: String, + token: String, + custom_user_data: String | None = None, + attributes: MapStringToString | None = None, + **kwargs, + ) -> CreateEndpointResponse: + store = self.get_store(context.account_id, context.region) + application = store.platform_applications.get(platform_application_arn) + if not application: + raise NotFoundException("PlatformApplication does not exist") + endpoint_arn = application.platform_endpoints.get(token, {}) + attributes = attributes or {} + _validate_endpoint_attributes(attributes, allow_empty=True) + # CustomUserData can be specified both in attributes and as parameter. Attributes take precedence + if custom_user_data: + attributes.setdefault(EndpointAttributeNames.CUSTOM_USER_DATA, custom_user_data) + _attributes = {"Enabled": "true", "Token": token, **attributes} + if endpoint_arn and ( + platform_endpoint_details := store.platform_endpoints.get(endpoint_arn) + ): + # endpoint for that application with that particular token already exists + if not platform_endpoint_details.platform_endpoint["Attributes"] == _attributes: + raise InvalidParameterException( + f"Invalid parameter: Token Reason: Endpoint {endpoint_arn} already exists with the same Token, but different attributes." + ) + else: + return CreateEndpointResponse(EndpointArn=endpoint_arn) + + endpoint_arn = create_platform_endpoint_arn(platform_application_arn) + platform_endpoint = PlatformEndpoint( + platform_application_arn=endpoint_arn, + platform_endpoint=Endpoint( + Attributes=_attributes, + EndpointArn=endpoint_arn, + ), + ) + store.platform_endpoints[endpoint_arn] = platform_endpoint + application.platform_endpoints[token] = endpoint_arn + + return CreateEndpointResponse(EndpointArn=endpoint_arn) + + def delete_endpoint(self, context: RequestContext, endpoint_arn: String, **kwargs) -> None: + store = self.get_store(context.account_id, context.region) + platform_endpoint_details = store.platform_endpoints.pop(endpoint_arn, None) + if platform_endpoint_details: + platform_application = store.platform_applications.get( + platform_endpoint_details.platform_application_arn + ) + if platform_application: + platform_endpoint = platform_endpoint_details.platform_endpoint + platform_application.platform_endpoints.pop( + platform_endpoint["Attributes"]["Token"], None ) - if raw_msg_delivery := sub_attributes.get("RawMessageDelivery"): - sub_attributes["RawMessageDelivery"] = raw_msg_delivery.lower() - # An endpoint may only be subscribed to a topic once. Subsequent - # subscribe calls do nothing (subscribe is idempotent), except if its attributes are different. - for existing_topic_subscription in store.topic_subscriptions.get(topic_arn, []): - sub = store.subscriptions.get(existing_topic_subscription, {}) - if sub.get("Endpoint") == endpoint: - if sub_attributes: - # validate the subscription attributes aren't different - for attr in sns_constants.VALID_SUBSCRIPTION_ATTR_NAME: - # if a new attribute is present and different from an existent one, raise - if (new_attr := sub_attributes.get(attr)) and sub.get(attr) != new_attr: - raise InvalidParameterException( - "Invalid parameter: Attributes Reason: Subscription already exists with different attributes" - ) + def list_endpoints_by_platform_application( + self, + context: RequestContext, + platform_application_arn: String, + next_token: String | None = None, + **kwargs, + ) -> ListEndpointsByPlatformApplicationResponse: + store = self.get_store(context.account_id, context.region) + platform_application = store.platform_applications.get(platform_application_arn) + if not platform_application: + raise NotFoundException("PlatformApplication does not exist") + endpoint_arns = platform_application.platform_endpoints.values() + paginated_endpoint_arns = PaginatedList(endpoint_arns) + page, token = paginated_endpoint_arns.get_page( + token_generator=lambda x: get_next_page_token_from_arn(x), + page_size=100, + next_token=next_token, + ) - return SubscribeResponse(SubscriptionArn=sub["SubscriptionArn"]) + response = ListEndpointsByPlatformApplicationResponse( + Endpoints=[ + store.platform_endpoints[endpoint_arn].platform_endpoint + for endpoint_arn in page + if endpoint_arn in store.platform_endpoints + ] + ) + if token: + response["NextToken"] = token + return response - principal = sns_constants.DUMMY_SUBSCRIPTION_PRINCIPAL.format( - partition=get_partition(context.region), account_id=context.account_id + def get_endpoint_attributes( + self, context: RequestContext, endpoint_arn: String, **kwargs + ) -> GetEndpointAttributesResponse: + store = self.get_store(context.account_id, context.region) + platform_endpoint_details = store.platform_endpoints.get(endpoint_arn) + if not platform_endpoint_details: + raise NotFoundException("Endpoint does not exist") + attributes = platform_endpoint_details.platform_endpoint["Attributes"] + return GetEndpointAttributesResponse(Attributes=attributes) + + def set_endpoint_attributes( + self, context: RequestContext, endpoint_arn: String, attributes: MapStringToString, **kwargs + ) -> None: + store = self.get_store(context.account_id, context.region) + platform_endpoint_details = store.platform_endpoints.get(endpoint_arn) + if not platform_endpoint_details: + raise NotFoundException("Endpoint does not exist") + _validate_endpoint_attributes(attributes) + attributes = attributes or {} + platform_endpoint_details.platform_endpoint["Attributes"].update(attributes) + + # + # Sms operations + # + + def set_sms_attributes( + self, context: RequestContext, attributes: MapStringToString, **kwargs + ) -> SetSMSAttributesResponse: + store = self.get_store(context.account_id, context.region) + _validate_sms_attributes(attributes) + _set_sms_attribute_default(store) + store.sms_attributes.update(attributes or {}) + return SetSMSAttributesResponse() + + def get_sms_attributes( + self, context: RequestContext, attributes: ListString | None = None, **kwargs + ) -> GetSMSAttributesResponse: + store = self.get_store(context.account_id, context.region) + _set_sms_attribute_default(store) + store_attributes = store.sms_attributes + return_attributes = {} + for k, v in store_attributes.items(): + if not attributes or k in attributes: + return_attributes[k] = store_attributes[k] + + return GetSMSAttributesResponse(attributes=return_attributes) + + # + # Phone number operations + # + + def check_if_phone_number_is_opted_out( + self, context: RequestContext, phone_number: PhoneNumber, **kwargs + ) -> CheckIfPhoneNumberIsOptedOutResponse: + store = sns_stores[context.account_id][context.region] + return CheckIfPhoneNumberIsOptedOutResponse( + isOptedOut=phone_number in store.PHONE_NUMBERS_OPTED_OUT ) - subscription_arn = create_subscription_arn(topic_arn) - subscription = SnsSubscription( - # http://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html - TopicArn=topic_arn, - Endpoint=endpoint, - Protocol=protocol, - SubscriptionArn=subscription_arn, - PendingConfirmation="true", - Owner=context.account_id, - RawMessageDelivery="false", # default value, will be overridden if set - FilterPolicyScope="MessageAttributes", # default value, will be overridden if set - SubscriptionPrincipal=principal, # dummy value, could be fetched with a call to STS? + + def list_phone_numbers_opted_out( + self, context: RequestContext, next_token: string | None = None, **kwargs + ) -> ListPhoneNumbersOptedOutResponse: + store = self.get_store(context.account_id, context.region) + numbers_opted_out = PaginatedList(store.PHONE_NUMBERS_OPTED_OUT) + page, nxt = numbers_opted_out.get_page( + token_generator=lambda x: x, + next_token=next_token, + page_size=100, ) - if sub_attributes: - subscription.update(sub_attributes) - if "FilterPolicy" in sub_attributes: - filter_policy = ( - json.loads(sub_attributes["FilterPolicy"]) - if sub_attributes["FilterPolicy"] - else None - ) - if filter_policy: - validator = FilterPolicyValidator( - scope=subscription.get("FilterPolicyScope", "MessageAttributes"), - is_subscribe_call=True, - ) - validator.validate_filter_policy(filter_policy) + phone_numbers = {"phoneNumbers": page, "nextToken": nxt} + return ListPhoneNumbersOptedOutResponse(**phone_numbers) - store.subscription_filter_policy[subscription_arn] = filter_policy + def opt_in_phone_number( + self, context: RequestContext, phone_number: PhoneNumber, **kwargs + ) -> OptInPhoneNumberResponse: + _validate_phone_number(phone_number) + store = self.get_store(context.account_id, context.region) + if phone_number in store.PHONE_NUMBERS_OPTED_OUT: + store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) + return OptInPhoneNumberResponse() - store.subscriptions[subscription_arn] = subscription + # + # Permission operations + # - topic_subscriptions = store.topic_subscriptions.setdefault(topic_arn, []) - topic_subscriptions.append(subscription_arn) + def add_permission( + self, + context: RequestContext, + topic_arn: topicARN, + label: label, + aws_account_id: DelegatesList, + action_name: ActionsList, + **kwargs, + ) -> None: + topic: Topic = self._get_topic(topic_arn, context) + policy = json.loads(topic["attributes"]["Policy"]) + statement = next( + (statement for statement in policy["Statement"] if statement["Sid"] == label), + None, + ) - # store the token and subscription arn - # TODO: the token is a 288 hex char string - subscription_token = encode_subscription_token_with_region(region=context.region) - store.subscription_tokens[subscription_token] = subscription_arn + if statement: + raise InvalidParameterException("Invalid parameter: Statement already exists") - response_subscription_arn = subscription_arn - # Send out confirmation message for HTTP(S), fix for https://github.com/localstack/localstack/issues/881 - if protocol in ["http", "https"]: - message_ctx = SnsMessage( - type=SnsMessageType.SubscriptionConfirmation, - token=subscription_token, - message=f"You have chosen to subscribe to the topic {topic_arn}.\nTo confirm the subscription, visit the SubscribeURL included in this message.", - ) - publish_ctx = SnsPublishContext( - message=message_ctx, - store=store, - request_headers=context.request.headers, - topic_attributes=vars(self._get_topic(topic_arn, context)), - ) - self._publisher.publish_to_topic_subscriber( - ctx=publish_ctx, - topic_arn=topic_arn, - subscription_arn=subscription_arn, + if any(action not in VALID_POLICY_ACTIONS for action in action_name): + raise InvalidParameterException( + "Invalid parameter: Policy statement action out of service scope!" ) - if not return_subscription_arn: - response_subscription_arn = "pending confirmation" - elif protocol not in ["email", "email-json"]: - # Only HTTP(S) and email subscriptions are not auto validated - # Except if the endpoint and the topic are not in the same AWS account, then you'd need to manually confirm - # the subscription with the token - # TODO: revisit for multi-account - # TODO: test with AWS for email & email-json confirmation message - # we need to add the following check: - # if parsed_topic_arn["account"] == endpoint account (depending on the type, SQS, lambda, parse the arn) - subscription["PendingConfirmation"] = "false" - subscription["ConfirmationWasAuthenticated"] = "true" + principals = [ + f"arn:{get_partition(context.region)}:iam::{account_id}:root" + for account_id in aws_account_id + ] + actions = [f"SNS:{action}" for action in action_name] + + statement = { + "Sid": label, + "Effect": "Allow", + "Principal": {"AWS": principals[0] if len(principals) == 1 else principals}, + "Action": actions[0] if len(actions) == 1 else actions, + "Resource": topic_arn, + } - return SubscribeResponse(SubscriptionArn=response_subscription_arn) + policy["Statement"].append(statement) + topic["attributes"]["Policy"] = json.dumps(policy) + + def remove_permission( + self, context: RequestContext, topic_arn: topicARN, label: label, **kwargs + ) -> None: + topic = self._get_topic(topic_arn, context) + policy = json.loads(topic["attributes"]["Policy"]) + statements = policy["Statement"] + statements = [statement for statement in statements if statement["Sid"] != label] + policy["Statement"] = statements + topic["attributes"]["Policy"] = json.dumps(policy) + + # + # Data Protection Policy operations + # + + def get_data_protection_policy( + self, context: RequestContext, resource_arn: topicARN, **kwargs + ) -> GetDataProtectionPolicyResponse: + topic = self._get_topic(resource_arn, context) + return GetDataProtectionPolicyResponse( + DataProtectionPolicy=topic.get("data_protection_policy") + ) + + def put_data_protection_policy( + self, + context: RequestContext, + resource_arn: topicARN, + data_protection_policy: attributeValue, + **kwargs, + ) -> None: + topic = self._get_topic(resource_arn, context) + topic["data_protection_policy"] = data_protection_policy + + def list_tags_for_resource( + self, context: RequestContext, resource_arn: AmazonResourceName, **kwargs + ) -> ListTagsForResourceResponse: + tags = self._list_resource_tags(context, resource_arn) + return ListTagsForResourceResponse(Tags=tags) def tag_resource( self, context: RequestContext, resource_arn: AmazonResourceName, tags: TagList, **kwargs ) -> TagResourceResponse: - # each tag key must be unique - # https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html#tag-best-practices unique_tag_keys = {tag["Key"] for tag in tags} if len(unique_tag_keys) < len(tags): raise InvalidParameterException("Invalid parameter: Duplicated keys are not allowed.") - - call_moto(context) - store = self.get_store(context.account_id, context.region) - existing_tags = store.sns_tags.get(resource_arn, []) - - def existing_tag_index(_item): - for idx, tag in enumerate(existing_tags): - if _item["Key"] == tag["Key"]: - return idx - return None - - for item in tags: - existing_index = existing_tag_index(item) - if existing_index is None: - existing_tags.append(item) - else: - existing_tags[existing_index] = item - - store.sns_tags[resource_arn] = existing_tags + self._tag_resource(context, resource_arn, tags) return TagResourceResponse() - def delete_topic(self, context: RequestContext, topic_arn: topicARN, **kwargs) -> None: - parsed_arn = parse_and_validate_topic_arn(topic_arn) - if context.region != parsed_arn["region"]: - raise InvalidParameterException("Invalid parameter: TopicArn") - - call_moto(context) - store = self.get_store(account_id=parsed_arn["account"], region_name=context.region) - topic_subscriptions = store.topic_subscriptions.pop(topic_arn, []) - for topic_sub in topic_subscriptions: - store.subscriptions.pop(topic_sub, None) - - store.sns_tags.pop(topic_arn, None) - - def create_topic( + def untag_resource( self, context: RequestContext, - name: topicName, - attributes: TopicAttributesMap = None, - tags: TagList = None, - data_protection_policy: attributeValue = None, + resource_arn: AmazonResourceName, + tag_keys: TagKeyList, **kwargs, - ) -> CreateTopicResponse: - moto_response = call_moto(context) - store = self.get_store(account_id=context.account_id, region_name=context.region) - topic_arn = moto_response["TopicArn"] - tag_resource_success = extract_tags(topic_arn, tags, True, store) - if not tag_resource_success: - raise InvalidParameterException( - "Invalid parameter: Tags Reason: Topic already exists with different tags" - ) - if tags: - self.tag_resource(context=context, resource_arn=topic_arn, tags=tags) - store.topic_subscriptions.setdefault(topic_arn, []) - return CreateTopicResponse(TopicArn=topic_arn) - - -def is_raw_message_delivery(susbcriber): - return susbcriber.get("RawMessageDelivery") in ("true", True, "True") - + ) -> UntagResourceResponse: + self._untag_resource(context, resource_arn, tag_keys) + return UntagResourceResponse() -def validate_subscription_attribute( - attribute_name: str, - attribute_value: str, - topic_arn: str, - endpoint: str, - is_subscribe_call: bool = False, -) -> None: - """ - Validate the subscription attribute to be set. See: - https://docs.aws.amazon.com/sns/latest/api/API_SetSubscriptionAttributes.html - :param attribute_name: the subscription attribute name, must be in VALID_SUBSCRIPTION_ATTR_NAME - :param attribute_value: the subscription attribute value - :param topic_arn: the topic_arn of the subscription, needed to know if it is FIFO - :param endpoint: the subscription endpoint (like an SQS queue ARN) - :param is_subscribe_call: the error message is different if called from Subscribe or SetSubscriptionAttributes - :raises InvalidParameterException - :return: - """ - error_prefix = ( - "Invalid parameter: Attributes Reason: " if is_subscribe_call else "Invalid parameter: " - ) - if attribute_name not in sns_constants.VALID_SUBSCRIPTION_ATTR_NAME: - raise InvalidParameterException(f"{error_prefix}AttributeName") + @staticmethod + def get_store(account_id: str, region: str) -> SnsStore: + return sns_stores[account_id][region] - if attribute_name == "FilterPolicy": + @staticmethod + def _get_topic(arn: str, context: RequestContext, multi_region: bool = False) -> Topic: + """ + :param arn: the Topic ARN + :param context: the RequestContext of the request + :return: the model Topic + """ + arn_data = parse_and_validate_topic_arn(arn) + if not multi_region and context.region != arn_data["region"]: + raise InvalidParameterException("Invalid parameter: TopicArn") try: - json.loads(attribute_value or "{}") - except json.JSONDecodeError: - raise InvalidParameterException(f"{error_prefix}FilterPolicy: failed to parse JSON.") - elif attribute_name == "FilterPolicyScope": - if attribute_value not in ("MessageAttributes", "MessageBody"): - raise InvalidParameterException( - f"{error_prefix}FilterPolicyScope: Invalid value [{attribute_value}]. " - f"Please use either MessageBody or MessageAttributes" - ) - elif attribute_name == "RawMessageDelivery": - # TODO: only for SQS and https(s) subs, + firehose - if attribute_value.lower() not in ("true", "false"): - raise InvalidParameterException( - f"{error_prefix}RawMessageDelivery: Invalid value [{attribute_value}]. " - f"Must be true or false." - ) + store = SnsProvider.get_store(arn_data["account"], arn_data["region"]) + return store.topics[arn] + except KeyError: + raise NotFoundException("Topic does not exist") - elif attribute_name == "RedrivePolicy": - try: - dlq_target_arn = json.loads(attribute_value).get("deadLetterTargetArn", "") - except json.JSONDecodeError: - raise InvalidParameterException(f"{error_prefix}RedrivePolicy: failed to parse JSON.") + @staticmethod + def _get_platform_application( + platform_application_arn: str, context: RequestContext + ) -> PlatformApplication: + parse_and_validate_platform_application_arn(platform_application_arn) try: - parsed_arn = parse_arn(dlq_target_arn) - except InvalidArnException: - raise InvalidParameterException( - f"{error_prefix}RedrivePolicy: deadLetterTargetArn is an invalid arn" - ) + store = SnsProvider.get_store(context.account_id, context.region) + return store.platform_applications[platform_application_arn].platform_application + except KeyError: + raise NotFoundException("PlatformApplication does not exist") - if topic_arn.endswith(".fifo"): - if endpoint.endswith(".fifo") and ( - not parsed_arn["resource"].endswith(".fifo") or "sqs" not in parsed_arn["service"] - ): - raise InvalidParameterException( - f"{error_prefix}RedrivePolicy: must use a FIFO queue as DLQ for a FIFO Subscription to a FIFO Topic." - ) + +def _create_topic( + name: str, attributes: dict, data_protection_policy: str, context: RequestContext +) -> Topic: + topic_arn = sns_topic_arn( + topic_name=name, region_name=context.region, account_id=context.account_id + ) + topic: Topic = { + "name": name, + "arn": topic_arn, + "attributes": {}, + "subscriptions": [], + "data_protection_policy": data_protection_policy, + } + attrs = _default_attributes(topic, context) + attrs.update(attributes or {}) + topic["attributes"] = attrs + + return topic + + +def _default_attributes(topic: Topic, context: RequestContext) -> TopicAttributesMap: + default_attributes = { + "DisplayName": "", + "Owner": context.account_id, + "Policy": create_default_topic_policy(topic["arn"]), + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": topic["arn"], + } + if topic["name"].endswith(".fifo"): + default_attributes.update( + { + "ContentBasedDeduplication": "false", + "FifoTopic": "false", + } + ) + return default_attributes + + +def _create_default_effective_delivery_policy(): + return json.dumps( + { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear", + }, + "disableSubscriptionOverrides": False, + "defaultRequestPolicy": {"headerContentType": "text/plain; charset=UTF-8"}, + } + } + ) -def validate_message_attributes( +def _validate_message_attributes( message_attributes: MessageAttributeMap, position: int | None = None ) -> None: """ @@ -969,7 +1410,7 @@ def validate_message_attributes( raise InvalidParameterValueException( "Length of message attribute name must be less than 256 bytes." ) - validate_message_attribute_name(attr_name) + _validate_message_attribute_name(attr_name) # `DataType` is a required field for MessageAttributeValue if (data_type := attr.get("DataType")) is None: if position: @@ -987,7 +1428,7 @@ def validate_message_attributes( "String", "Number", "Binary", - ) and not sns_constants.ATTR_TYPE_REGEX.match(data_type): + ) and not ATTR_TYPE_REGEX.match(data_type): raise InvalidParameterValueException( f"The message attribute '{attr_name}' has an invalid message attribute type, the set of supported type prefixes is Binary, Number, and String." ) @@ -1008,14 +1449,14 @@ def validate_message_attributes( ) -def validate_message_attribute_name(name: str) -> None: +def _validate_message_attribute_name(name: str) -> None: """ Validate the message attribute name with the specification of AWS. The message attribute name can contain the following characters: A-Z, a-z, 0-9, underscore(_), hyphen(-), and period (.). The name must not start or end with a period, and it should not have successive periods. :param name: message attribute name :raises InvalidParameterValueException: if the name does not conform to the spec """ - if not sns_constants.MSG_ATTR_NAME_REGEX.match(name): + if not MSG_ATTR_NAME_REGEX.match(name): # find the proper exception if name[0] == ".": raise InvalidParameterValueException( @@ -1027,7 +1468,7 @@ def validate_message_attribute_name(name: str) -> None: ) for idx, char in enumerate(name): - if char not in sns_constants.VALID_MSG_ATTR_NAME_CHARS: + if char not in VALID_MSG_ATTR_NAME_CHARS: # change prefix from 0x to #x, without capitalizing the x hex_char = "#x" + hex(ord(char)).upper()[2:] raise InvalidParameterValueException( @@ -1040,72 +1481,72 @@ def validate_message_attribute_name(name: str) -> None: ) -def extract_tags( - topic_arn: str, tags: TagList, is_create_topic_request: bool, store: SnsStore -) -> bool: - existing_tags = list(store.sns_tags.get(topic_arn, [])) - # if this is none there is nothing to check - if topic_arn in store.topic_subscriptions: - if tags is None: - tags = [] - for tag in tags: - # this means topic already created with empty tags and when we try to create it - # again with other tag value then it should fail according to aws documentation. - if is_create_topic_request and existing_tags is not None and tag not in existing_tags: - return False - return True - - -def parse_and_validate_topic_arn(topic_arn: str | None) -> ArnData: - topic_arn = topic_arn or "" - try: - return parse_arn(topic_arn) - except InvalidArnException: - count = len(topic_arn.split(":")) - raise InvalidParameterException( - f"Invalid parameter: TopicArn Reason: An ARN must have at least 6 elements, not {count}" - ) +def _validate_platform_application_name(name: str) -> None: + reason = "" + if not name: + reason = "cannot be empty" + elif not re.match(r"^.{0,256}$", name): + reason = "must be at most 256 characters long" + elif not re.match(r"^[A-Za-z0-9._-]+$", name): + reason = "must contain only characters 'a'-'z', 'A'-'Z', '0'-'9', '_', '-', and '.'" + if reason: + LOG.debug("SNS Platform Application Name rejected due to reason: %s", reason) + raise InvalidParameterException(f"Invalid parameter: {name} Reason: {reason}") -def create_subscription_arn(topic_arn: str) -> str: - # This is the format of a Subscription ARN - # arn:aws:sns:us-west-2:123456789012:my-topic:8a21d249-4329-4871-acc6-7be709c6ea7f - return f"{topic_arn}:{uuid4()}" +def _validate_platform_application_attributes(attributes: dict) -> None: + _check_empty_attributes(attributes) -def encode_subscription_token_with_region(region: str) -> str: - """ - Create a 64 characters Subscription Token with the region encoded - :param region: - :return: a subscription token with the region encoded - """ - return ((region.encode() + b"/").hex() + short_uid() * 8)[:64] +def _check_empty_attributes(attributes: dict) -> None: + if not attributes: + raise CommonServiceException( + code="ValidationError", + message="1 validation error detected: Value null at 'attributes' failed to satisfy constraint: Member must not be null", + sender_fault=True, + ) -def get_region_from_subscription_token(token: str) -> str: - """ - Try to decode and return the region from a subscription token - :param token: - :return: the region if able to decode it - :raises: InvalidParameterException if the token is invalid - """ - try: - region = token.split("2f", maxsplit=1)[0] - return bytes.fromhex(region).decode("utf-8") - except (IndexError, ValueError, TypeError, UnicodeDecodeError): - raise InvalidParameterException("Invalid parameter: Token") +def _validate_endpoint_attributes(attributes: dict, allow_empty: bool = False) -> None: + if not allow_empty: + _check_empty_attributes(attributes) + for key in attributes: + if key not in EndpointAttributeNames: + raise InvalidParameterException( + f"Invalid parameter: Attributes Reason: Invalid attribute name: {key}" + ) + if len(attributes.get(EndpointAttributeNames.CUSTOM_USER_DATA, "")) > 2048: + raise InvalidParameterException( + "Invalid parameter: Attributes Reason: Invalid value for attribute: CustomUserData: must be at most 2048 bytes long in UTF-8 encoding" + ) -def get_next_page_token_from_arn(resource_arn: str) -> str: - return to_str(base64.b64encode(to_bytes(resource_arn))) +def _validate_sms_attributes(attributes: dict) -> None: + for k, v in attributes.items(): + if k not in SMS_ATTRIBUTE_NAMES: + raise InvalidParameterException(f"{k} is not a valid attribute") + default_send_id = attributes.get("DefaultSendID") + if default_send_id and not re.match(SMS_DEFAULT_SENDER_REGEX, default_send_id): + raise InvalidParameterException("DefaultSendID is not a valid attribute") + sms_type = attributes.get("DefaultSMSType") + if sms_type and sms_type not in SMS_TYPES: + raise InvalidParameterException("DefaultSMSType is invalid") -def _get_byte_size(payload: str | bytes) -> int: - # Calculate the real length of the byte object if the object is a string - return len(to_bytes(payload)) +def _set_sms_attribute_default(store: SnsStore) -> None: + # TODO: don't call this on every sms attribute crud api call + store.sms_attributes.setdefault("MonthlySpendLimit", "1") -def get_total_publish_size( + +def _validate_phone_number(phone_number: str): + if not re.match(E164_REGEX, phone_number): + raise InvalidParameterException( + "Invalid parameter: PhoneNumber Reason: input incorrectly formatted" + ) + + +def _get_total_publish_size( message_body: str, message_attributes: MessageAttributeMap | None ) -> int: size = _get_byte_size(message_body) @@ -1123,32 +1564,18 @@ def get_total_publish_size( return size -def register_sns_api_resource(router: Router): +def _get_byte_size(payload: str | bytes) -> int: + # Calculate the real length of the byte object if the object is a string + return len(to_bytes(payload)) + + +def _register_sns_api_resource(router: Router): """Register the retrospection endpoints as internal LocalStack endpoints.""" router.add(SNSServicePlatformEndpointMessagesApiResource()) router.add(SNSServiceSMSMessagesApiResource()) router.add(SNSServiceSubscriptionTokenApiResource()) -def _format_messages(sent_messages: List[Dict[str, str]], validated_keys: List[str]): - """ - This method format the messages to be more readable and undo the format change that was needed for Moto - Should be removed once we refactor SNS. - """ - formatted_messages = [] - for sent_message in sent_messages: - msg = { - key: json.dumps(value) - if key == "Message" and sent_message.get("MessageStructure") == "json" - else value - for key, value in sent_message.items() - if key in validated_keys - } - formatted_messages.append(msg) - - return formatted_messages - - class SNSInternalResource: resource_type: str """Base class with helper to properly track usage of internal endpoints""" @@ -1191,7 +1618,7 @@ class SNSServicePlatformEndpointMessagesApiResource(SNSInternalResource): "MessageId", ] - @route(sns_constants.PLATFORM_ENDPOINT_MSGS_ENDPOINT, methods=["GET"]) + @route(PLATFORM_ENDPOINT_MSGS_ENDPOINT, methods=["GET"]) @count_usage def on_get(self, request: Request): filter_endpoint_arn = request.args.get("endpointArn") @@ -1223,7 +1650,7 @@ def on_get(self, request: Request): "region": region, } - @route(sns_constants.PLATFORM_ENDPOINT_MSGS_ENDPOINT, methods=["DELETE"]) + @route(PLATFORM_ENDPOINT_MSGS_ENDPOINT, methods=["DELETE"]) @count_usage def on_delete(self, request: Request) -> Response: filter_endpoint_arn = request.args.get("endpointArn") @@ -1246,6 +1673,33 @@ def on_delete(self, request: Request) -> Response: return Response("", status=204) +def register_sns_api_resource(router: Router): + """Register the retrospection endpoints as internal LocalStack endpoints.""" + router.add(SNSServicePlatformEndpointMessagesApiResource()) + router.add(SNSServiceSMSMessagesApiResource()) + router.add(SNSServiceSubscriptionTokenApiResource()) + router.add(SNSServiceSMSPhoneOptOutResource()) + + +def _format_messages(sent_messages: list[dict[str, str]], validated_keys: list[str]): + """ + This method format the messages to be more readable and undo the format change that was needed for Moto + Should be removed once we refactor SNS. + """ + formatted_messages = [] + for sent_message in sent_messages: + msg = { + key: json.dumps(value) + if key == "Message" and sent_message.get("MessageStructure") == "json" + else value + for key, value in sent_message.items() + if key in validated_keys + } + formatted_messages.append(msg) + + return formatted_messages + + class SNSServiceSMSMessagesApiResource(SNSInternalResource): resource_type = "sms-message" """Provides a REST API for retrospective access to SMS messages sent via SNS. @@ -1269,7 +1723,7 @@ class SNSServiceSMSMessagesApiResource(SNSInternalResource): "Subject", ] - @route(sns_constants.SMS_MSGS_ENDPOINT, methods=["GET"]) + @route(SMS_MSGS_ENDPOINT, methods=["GET"]) @count_usage def on_get(self, request: Request): account_id = request.args.get("accountId", DEFAULT_AWS_ACCOUNT_ID) @@ -1296,7 +1750,7 @@ def on_get(self, request: Request): "region": region, } - @route(sns_constants.SMS_MSGS_ENDPOINT, methods=["DELETE"]) + @route(SMS_MSGS_ENDPOINT, methods=["DELETE"]) @count_usage def on_delete(self, request: Request) -> Response: account_id = request.args.get("accountId", DEFAULT_AWS_ACCOUNT_ID) @@ -1325,7 +1779,7 @@ class SNSServiceSubscriptionTokenApiResource(SNSInternalResource): - GET `subscription_arn`: `subscriptionArn`resource in SNS for which you want the SubscriptionToken """ - @route(f"{sns_constants.SUBSCRIPTION_TOKENS_ENDPOINT}/", methods=["GET"]) + @route(f"{SUBSCRIPTION_TOKENS_ENDPOINT}/", methods=["GET"]) @count_usage def on_get(self, _request: Request, subscription_arn: str): try: @@ -1357,3 +1811,27 @@ def on_get(self, _request: Request, subscription_arn: str): } ) return response + + +class SNSServiceSMSPhoneOptOutResource(SNSInternalResource): + resource_type = "phone-number-opt-out" + """Provides a REST API for adding phone numbers to the opt out list for testing purposes. + In AWS this seems to be handled by pin-point, which is scheduled for deprecation. + + This is registered as a LocalStack internal HTTP resource. + + This endpoint accepts: + - POST data `phoneNumber`: phone number to be opted out in SNS + - POST data `accountId`: account ID + """ + + @route(SMS_PHONE_NUMBER_OPT_OUT_ENDPOINT, methods=["POST"]) + @count_usage + def on_post(self, request: Request): + data = json.loads(request.data) or {} + account_id = data.get("accountId", DEFAULT_AWS_ACCOUNT_ID) + region = AWS_REGION_US_EAST_1 # opt-out list is account-wide + opt_out_phone_number = data.get("phoneNumber") + store: SnsStore = sns_stores[account_id][region] + if opt_out_phone_number: + store.PHONE_NUMBERS_OPTED_OUT.add(opt_out_phone_number) diff --git a/localstack-core/localstack/services/sns/publisher.py b/localstack-core/localstack/services/sns/publisher.py index 9510885f51431..f3589df6fc20d 100644 --- a/localstack-core/localstack/services/sns/publisher.py +++ b/localstack-core/localstack/services/sns/publisher.py @@ -9,7 +9,6 @@ import traceback from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field -from typing import Dict, List, Tuple, Union import requests from cryptography.hazmat.primitives import hashes @@ -17,7 +16,7 @@ from localstack import config from localstack.aws.api.lambda_ import InvocationType -from localstack.aws.api.sns import MessageAttributeMap +from localstack.aws.api.sns import MessageAttributeMap, TopicAttributesMap from localstack.aws.connect import connect_to from localstack.config import external_service_url from localstack.services.sns import constants as sns_constants @@ -31,6 +30,7 @@ SnsStore, SnsSubscription, ) +from localstack.services.sns.utils import get_topic_subscriptions, snake_to_pascal_case from localstack.utils.aws.arns import ( PARTITION_NAMES, extract_account_id_from_arn, @@ -61,9 +61,9 @@ class SnsPublishContext: @dataclass class SnsBatchPublishContext: - messages: List[SnsMessage] + messages: list[SnsMessage] store: SnsStore - request_headers: Dict[str, str] + request_headers: dict[str, str] topic_attributes: dict[str, str] = field(default_factory=dict) @@ -90,9 +90,10 @@ def publish(self, context: SnsPublishContext, subscriber: SnsSubscription): try: self._publish(context=context, subscriber=subscriber) except Exception: - LOG.exception( + LOG.error( "An internal error occurred while trying to send the SNS message %s", context.message, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) return @@ -110,7 +111,7 @@ def prepare_message( self, message_context: SnsMessage, subscriber: SnsSubscription, - topic_attributes: dict[str, str] = None, + topic_attributes: TopicAttributesMap = None, ) -> str: """ Returns the message formatted in the base SNS message format. The base SNS message format is shared amongst @@ -148,9 +149,10 @@ def publish(self, context: SnsPublishContext, endpoint: str): try: self._publish(context=context, endpoint=endpoint) except Exception: - LOG.exception( + LOG.error( "An internal error occurred while trying to send the SNS message %s", context.message, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) return @@ -231,7 +233,7 @@ def prepare_message( self, message_context: SnsMessage, subscriber: SnsSubscription, - topic_attributes: dict[str, str] = None, + topic_attributes: TopicAttributesMap = None, ) -> str: """ You can see Lambda SNS Event format here: https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html @@ -253,9 +255,11 @@ def prepare_message( "UnsubscribeUrl": unsubscribe_url, "MessageAttributes": message_attributes, } - + # TODO: remove v1 "signature_version" access once v1 is retired signature_version = ( - topic_attributes.get("signature_version", "1") if topic_attributes else "1" + topic_attributes.get("signature_version", topic_attributes.get("SignatureVersion", "1")) + if topic_attributes + else "1" ) canonical_string = compute_canonical_string(event_payload, message_context.type) signature = get_message_signature(canonical_string, signature_version=signature_version) @@ -296,7 +300,10 @@ def _publish(self, context: SnsPublishContext, subscriber: SnsSubscription): ) kwargs = self.get_sqs_kwargs(msg_context=message_context, subscriber=subscriber) except Exception: - LOG.exception("An internal error occurred while trying to format the message for SQS") + LOG.error( + "An internal error occurred while trying to format the message for SQS", + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) return try: queue_url: str = sqs_queue_url_for_arn(subscriber["Endpoint"]) @@ -336,19 +343,29 @@ def get_sqs_kwargs(msg_context: SnsMessage, subscriber: SnsSubscription): # SNS now allows regular non-fifo subscriptions to FIFO topics. Validate that the subscription target is fifo # before passing the FIFO-only parameters - if subscriber["Endpoint"].endswith(".fifo"): - if msg_context.message_group_id: - kwargs["MessageGroupId"] = msg_context.message_group_id - if msg_context.message_deduplication_id: - kwargs["MessageDeduplicationId"] = msg_context.message_deduplication_id - elif subscriber["TopicArn"].endswith(".fifo"): - # Amazon SNS uses the message body provided to generate a unique hash value to use as the deduplication - # ID for each message, so you don't need to set a deduplication ID when you send each message. - # https://docs.aws.amazon.com/sns/latest/dg/fifo-message-dedup.html - content = msg_context.message_content("sqs") - kwargs["MessageDeduplicationId"] = hashlib.sha256( - content.encode("utf-8") - ).hexdigest() + + # SNS will only forward the `MessageGroupId` for Fair Queues in some scenarios: + # - non-FIFO SNS topic to Fair Queue + # - FIFO topic to FIFO queue + # It will NOT forward it with FIFO topic to regular Queue (possibly used for internal grouping without relying + # on SQS capabilities) + if subscriber["TopicArn"].endswith(".fifo"): + if subscriber["Endpoint"].endswith(".fifo"): + if msg_context.message_group_id: + kwargs["MessageGroupId"] = msg_context.message_group_id + if msg_context.message_deduplication_id: + kwargs["MessageDeduplicationId"] = msg_context.message_deduplication_id + else: + # SNS uses the message body provided to generate a unique hash value to use as the deduplication ID + # for each message, so you don't need to set a deduplication ID when you send each message. + # https://docs.aws.amazon.com/sns/latest/dg/fifo-message-dedup.html + content = msg_context.message_content("sqs") + kwargs["MessageDeduplicationId"] = hashlib.sha256( + content.encode("utf-8") + ).hexdigest() + + elif msg_context.message_group_id: + kwargs["MessageGroupId"] = msg_context.message_group_id # TODO: for message deduplication, we are using the underlying features of the SQS queue # however, SQS queue only deduplicate at the Queue level, where the SNS topic deduplicate on the topic level @@ -544,7 +561,10 @@ def _get_content_type(subscriber: SnsSubscription, topic_attributes: dict) -> st ): return sub_content_type - if json_topic_delivery_policy := topic_attributes.get("delivery_policy"): + # TODO: remove lower case access once legacy v1 provider is removed + if json_topic_delivery_policy := topic_attributes.get( + "delivery_policy", topic_attributes.get("DeliveryPolicy") + ): topic_delivery_policy = json.loads(json_topic_delivery_policy) if not ( topic_content_type := topic_delivery_policy.get(subscriber["Protocol"].lower()) @@ -601,7 +621,7 @@ def prepare_message( self, message_context: SnsMessage, subscriber: SnsSubscription, - topic_attributes: dict[str, str] = None, + topic_attributes: TopicAttributesMap = None, ) -> str: return message_context.message_content(subscriber["Protocol"]) @@ -644,8 +664,8 @@ def prepare_message( self, message_context: SnsMessage, subscriber: SnsSubscription, - topic_attributes: dict[str, str] = None, - ) -> dict[str, str]: + topic_attributes: TopicAttributesMap = None, + ) -> dict[str, str | MessageAttributeMap | None]: endpoint_arn = subscriber["Endpoint"] platform_type = get_platform_type_from_endpoint_arn(endpoint_arn) return { @@ -705,8 +725,8 @@ def prepare_message( self, message_context: SnsMessage, subscriber: SnsSubscription, - topic_attributes: dict[str, str] = None, - ) -> dict: + topic_attributes: TopicAttributesMap = None, + ) -> dict[str, str | MessageAttributeMap | None]: return { "PhoneNumber": subscriber["Endpoint"], "TopicArn": subscriber["TopicArn"], @@ -818,7 +838,7 @@ def _publish(self, context: SnsPublishContext, endpoint: str): # TODO: see about delivery log for individual endpoint message, need credentials for testing # store_delivery_log(subscriber, context, success=True) - def prepare_message(self, message_context: SnsMessage, endpoint: str) -> Union[str, Dict]: + def prepare_message(self, message_context: SnsMessage, endpoint: str) -> str | dict: platform_type = get_platform_type_from_endpoint_arn(endpoint) return { "TargetArn": endpoint, @@ -858,11 +878,11 @@ def get_application_platform_arn_from_endpoint_arn(endpoint_arn: str) -> str: parsed_arn = parse_arn(endpoint_arn) _, platform_type, app_name, _ = parsed_arn["resource"].split("/") - base_arn = f"arn:aws:sns:{parsed_arn['region']}:{parsed_arn['account']}" + base_arn = f"arn:{parsed_arn['partition']}:sns:{parsed_arn['region']}:{parsed_arn['account']}" return f"{base_arn}:app/{platform_type}/{app_name}" -def get_attributes_for_application_endpoint(endpoint_arn: str) -> Tuple[Dict, Dict]: +def get_attributes_for_application_endpoint(endpoint_arn: str) -> tuple[dict, dict]: """ Retrieve the attributes necessary to send a message directly to the platform (credentials and token) :param endpoint_arn: @@ -883,7 +903,7 @@ def get_attributes_for_application_endpoint(endpoint_arn: str) -> Tuple[Dict, Di def send_message_to_gcm( - context: SnsPublishContext, app_attributes: Dict[str, str], endpoint_attributes: Dict[str, str] + context: SnsPublishContext, app_attributes: dict[str, str], endpoint_attributes: dict[str, str] ) -> None: """ Send the message directly to GCM, with the credentials used when creating the PlatformApplication and the Endpoint @@ -995,7 +1015,10 @@ def create_sns_message_body( # FIFO topics do not add the signature in the message if not subscriber.get("TopicArn", "").endswith(".fifo"): signature_version = ( - topic_attributes.get("signature_version", "1") if topic_attributes else "1" + # we allow for both casings, depending on v1 or v2 provider + topic_attributes.get("signature_version", topic_attributes.get("SignatureVersion", "1")) + if topic_attributes + else "1" ) canonical_string = compute_canonical_string(data, message_type) signature = get_message_signature(canonical_string, signature_version=signature_version) @@ -1014,7 +1037,7 @@ def create_sns_message_body( def prepare_message_attributes( message_attributes: MessageAttributeMap, -) -> Dict[str, Dict[str, str]]: +) -> dict[str, dict[str, str]]: attributes = {} if not message_attributes: return attributes @@ -1072,7 +1095,6 @@ def store_delivery_log( # SMS is a special case: https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html # seems like you need to configure on the Console, leave it on by default now in LocalStack protocol = subscriber.get("Protocol") - if protocol != "sms": if protocol not in available_delivery_logs_services or not topic_attributes: # this service does not have DeliveryLogs feature, return @@ -1087,7 +1109,11 @@ def store_delivery_log( # TODO: on purpose not using walrus operator to show that we get the RoleArn here for CloudWatch role_arn = topic_attributes.get(topic_attribute) if not role_arn: - return + # TODO: remove snake case access once v1 is completely obsolete + topic_attribute = snake_to_pascal_case(topic_attribute) + role_arn = topic_attributes.get(topic_attribute) + if not role_arn: + return if not is_api_enabled("logs"): LOG.warning( @@ -1220,7 +1246,7 @@ def _should_publish( ) def publish_to_topic(self, ctx: SnsPublishContext, topic_arn: str) -> None: - subscriptions = ctx.store.get_topic_subscriptions(topic_arn) + subscriptions = get_topic_subscriptions(ctx.store, topic_arn) for subscriber in subscriptions: if self._should_publish(ctx.store.subscription_filter_policy, ctx.message, subscriber): notifier = self.topic_notifiers[subscriber["Protocol"]] @@ -1235,7 +1261,7 @@ def publish_to_topic(self, ctx: SnsPublishContext, topic_arn: str) -> None: self._submit_notification(notifier, ctx, subscriber) def publish_batch_to_topic(self, ctx: SnsBatchPublishContext, topic_arn: str) -> None: - subscriptions = ctx.store.get_topic_subscriptions(topic_arn) + subscriptions = get_topic_subscriptions(ctx.store, topic_arn) for subscriber in subscriptions: protocol = subscriber["Protocol"] notifier = self.batch_topic_notifiers.get(protocol) diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py index 650df889dff02..e98c24012fe5f 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack import config @@ -18,18 +18,18 @@ class SNSSubscriptionProperties(TypedDict): - Protocol: Optional[str] - TopicArn: Optional[str] - DeliveryPolicy: Optional[dict] - Endpoint: Optional[str] - FilterPolicy: Optional[dict] - FilterPolicyScope: Optional[str] - Id: Optional[str] - RawMessageDelivery: Optional[bool] - RedrivePolicy: Optional[dict] - Region: Optional[str] - ReplayPolicy: Optional[dict] - SubscriptionRoleArn: Optional[str] + Protocol: str | None + TopicArn: str | None + DeliveryPolicy: dict | None + Endpoint: str | None + FilterPolicy: dict | None + FilterPolicyScope: str | None + Id: str | None + RawMessageDelivery: bool | None + RedrivePolicy: dict | None + Region: str | None + ReplayPolicy: dict | None + SubscriptionRoleArn: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py index 01e23a1f30aed..f0be907619d54 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SNSSubscriptionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SNS::Subscription" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sns.resource_providers.aws_sns_subscription import ( diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py index 7bc6720fd63f5..c3f8c5bae5279 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,27 +16,27 @@ class SNSTopicProperties(TypedDict): - ContentBasedDeduplication: Optional[bool] - DataProtectionPolicy: Optional[dict] - DisplayName: Optional[str] - FifoTopic: Optional[bool] - KmsMasterKeyId: Optional[str] - SignatureVersion: Optional[str] - Subscription: Optional[list[Subscription]] - Tags: Optional[list[Tag]] - TopicArn: Optional[str] - TopicName: Optional[str] - TracingConfig: Optional[str] + ContentBasedDeduplication: bool | None + DataProtectionPolicy: dict | None + DisplayName: str | None + FifoTopic: bool | None + KmsMasterKeyId: str | None + SignatureVersion: str | None + Subscription: list[Subscription] | None + Tags: list[Tag] | None + TopicArn: str | None + TopicName: str | None + TracingConfig: str | None class Subscription(TypedDict): - Endpoint: Optional[str] - Protocol: Optional[str] + Endpoint: str | None + Protocol: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py index de6a26a9482c5..b7d9b1d484d67 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SNSTopicProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SNS::Topic" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sns.resource_providers.aws_sns_topic import SNSTopicProvider diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py index 412a22a150a96..bbe17c09466a4 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -14,13 +14,13 @@ ResourceProvider, ResourceRequest, ) -from localstack.services.sns.models import create_default_sns_topic_policy +from localstack.services.sns.provider import create_default_topic_policy class SNSTopicPolicyProperties(TypedDict): - PolicyDocument: Optional[dict | str] - Topics: Optional[list[str]] - Id: Optional[str] + PolicyDocument: dict | str | None + Topics: list[str] | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -99,7 +99,7 @@ def delete( sns.set_topic_attributes( TopicArn=topic_arn, AttributeName="Policy", - AttributeValue=json.dumps(create_default_sns_topic_policy(topic_arn)), + AttributeValue=create_default_topic_policy(topic_arn), ) except ClientError as err: diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py index 9fbe0afe2d7e4..fc1faeff714f8 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SNSTopicPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SNS::TopicPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sns.resource_providers.aws_sns_topicpolicy import ( diff --git a/localstack-core/localstack/services/sns/utils.py b/localstack-core/localstack/services/sns/utils.py new file mode 100644 index 0000000000000..cfad0431b5b2f --- /dev/null +++ b/localstack-core/localstack/services/sns/utils.py @@ -0,0 +1,184 @@ +import base64 +import json +from uuid import uuid4 + +from botocore.utils import InvalidArnException + +from localstack.aws.api.sns import InvalidParameterException +from localstack.services.sns.constants import E164_REGEX, VALID_SUBSCRIPTION_ATTR_NAME +from localstack.services.sns.models import SnsStore, SnsSubscription +from localstack.utils.aws.arns import ArnData, parse_arn +from localstack.utils.strings import short_uid, to_bytes, to_str + + +def parse_and_validate_topic_arn(topic_arn: str | None) -> ArnData: + return _parse_and_validate_arn(topic_arn, "Topic") + + +def parse_and_validate_platform_application_arn(platform_application_arn: str | None) -> ArnData: + return _parse_and_validate_arn(platform_application_arn, "PlatformApplication") + + +def _parse_and_validate_arn(arn: str | None, resource_type: str) -> ArnData: + arn = arn or "" + try: + return parse_arn(arn) + except InvalidArnException: + count = len(arn.split(":")) + raise InvalidParameterException( + f"Invalid parameter: {resource_type}Arn Reason: An ARN must have at least 6 elements, not {count}" + ) + + +def is_valid_e164_number(number: str) -> bool: + return E164_REGEX.match(number) is not None + + +def snake_to_pascal_case(snake_case_string: str) -> str: + return "".join(word.capitalize() for word in snake_case_string.split("_")) + + +def validate_subscription_attribute( + attribute_name: str, + attribute_value: str, + topic_arn: str, + endpoint: str, + is_subscribe_call: bool = False, +) -> None: + """ + Validate the subscription attribute to be set. See: + https://docs.aws.amazon.com/sns/latest/api/API_SetSubscriptionAttributes.html + :param attribute_name: the subscription attribute name, must be in VALID_SUBSCRIPTION_ATTR_NAME + :param attribute_value: the subscription attribute value + :param topic_arn: the topic_arn of the subscription, needed to know if it is FIFO + :param endpoint: the subscription endpoint (like an SQS queue ARN) + :param is_subscribe_call: the error message is different if called from Subscribe or SetSubscriptionAttributes + :raises InvalidParameterException + :return: + """ + error_prefix = ( + "Invalid parameter: Attributes Reason: " if is_subscribe_call else "Invalid parameter: " + ) + if attribute_name not in VALID_SUBSCRIPTION_ATTR_NAME: + raise InvalidParameterException(f"{error_prefix}AttributeName") + + if attribute_name == "FilterPolicy": + try: + json.loads(attribute_value or "{}") + except json.JSONDecodeError: + raise InvalidParameterException(f"{error_prefix}FilterPolicy: failed to parse JSON.") + elif attribute_name == "FilterPolicyScope": + if attribute_value not in ("MessageAttributes", "MessageBody"): + raise InvalidParameterException( + f"{error_prefix}FilterPolicyScope: Invalid value [{attribute_value}]. " + f"Please use either MessageBody or MessageAttributes" + ) + elif attribute_name == "RawMessageDelivery": + # TODO: only for SQS and https(s) subs, + firehose + if attribute_value.lower() not in ("true", "false"): + raise InvalidParameterException( + f"{error_prefix}RawMessageDelivery: Invalid value [{attribute_value}]. " + f"Must be true or false." + ) + + elif attribute_name == "RedrivePolicy": + try: + dlq_target_arn = json.loads(attribute_value).get("deadLetterTargetArn", "") + except json.JSONDecodeError: + raise InvalidParameterException(f"{error_prefix}RedrivePolicy: failed to parse JSON.") + try: + parsed_arn = parse_arn(dlq_target_arn) + except InvalidArnException: + raise InvalidParameterException( + f"{error_prefix}RedrivePolicy: deadLetterTargetArn is an invalid arn" + ) + + if topic_arn.endswith(".fifo"): + if endpoint.endswith(".fifo") and ( + not parsed_arn["resource"].endswith(".fifo") or "sqs" not in parsed_arn["service"] + ): + raise InvalidParameterException( + f"{error_prefix}RedrivePolicy: must use a FIFO queue as DLQ for a FIFO Subscription to a FIFO Topic." + ) + + +def create_subscription_arn(topic_arn: str) -> str: + # This is the format of a Subscription ARN + # arn:aws:sns:us-west-2:123456789012:my-topic:8a21d249-4329-4871-acc6-7be709c6ea7f + return f"{topic_arn}:{uuid4()}" + + +def create_platform_endpoint_arn( + platform_application_arn: str, +) -> str: + # This is the format of an Endpoint Arn + # arn:aws:sns:us-west-2:1234567890:endpoint/GCM/MyApplication/12345678-abcd-9012-efgh-345678901234 + return f"{platform_application_arn.replace('app', 'endpoint', 1)}/{uuid4()}" + + +def encode_subscription_token_with_region(region: str) -> str: + """ + Create a 64 characters Subscription Token with the region encoded + :param region: + :return: a subscription token with the region encoded + """ + return ((region.encode() + b"/").hex() + short_uid() * 8)[:64] + + +def get_next_page_token_from_arn(resource_arn: str) -> str: + return to_str(base64.b64encode(to_bytes(resource_arn))) + + +def get_region_from_subscription_token(token: str) -> str: + """ + Try to decode and return the region from a subscription token + :param token: + :return: the region if able to decode it + :raises: InvalidParameterException if the token is invalid + """ + try: + region = token.split("2f", maxsplit=1)[0] + return bytes.fromhex(region).decode("utf-8") + except (IndexError, ValueError, TypeError, UnicodeDecodeError): + raise InvalidParameterException("Invalid parameter: Token") + + +def get_topic_subscriptions(store: SnsStore, topic_arn: str) -> list[SnsSubscription]: + # TODO: delete this once the legacy v1 implementation has been removed + if hasattr(store, "topic_subscriptions"): + sub_arns = store.topic_subscriptions.get(topic_arn, []) + else: + sub_arns: list[str] = store.topics[topic_arn].get("subscriptions", []) + + subscriptions = [store.subscriptions[k] for k in sub_arns if k in store.subscriptions] + return subscriptions + + +def create_default_topic_policy(topic_arn: str) -> str: + return json.dumps( + { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Effect": "Allow", + "Sid": "__default_statement_ID", + "Principal": {"AWS": "*"}, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish", + ], + "Resource": topic_arn, + "Condition": { + "StringEquals": {"AWS:SourceOwner": parse_arn(topic_arn)["account"]} + }, + } + ], + } + ) diff --git a/localstack-core/localstack/services/sqs/constants.py b/localstack-core/localstack/services/sqs/constants.py index 0cdc49b8eccdb..40664d122b601 100644 --- a/localstack-core/localstack/services/sqs/constants.py +++ b/localstack-core/localstack/services/sqs/constants.py @@ -9,7 +9,7 @@ ATTR_NAME_CHAR_REGEX = "^[\u00c0-\u017fa-zA-Z0-9_.-]*$" ATTR_NAME_PREFIX_SUFFIX_REGEX = r"^(?!(aws\.|amazon\.|\.)).*(??@[\\]^_`{|}~-]*$" +FIFO_MSG_REGEX = "^[0-9a-zA-z!\"#$%&'()*+,./:;<=>?@[\\]^_`{|}~-]{1,128}$" DEDUPLICATION_INTERVAL_IN_SEC = 5 * 60 @@ -18,7 +18,14 @@ RECENTLY_DELETED_TIMEOUT = 60 # the default maximum message size in SQS -DEFAULT_MAXIMUM_MESSAGE_SIZE = 262144 +DEFAULT_MAXIMUM_MESSAGE_SIZE = 1048576 +DYNAMIC_ATTRIBUTES = [ + # these attributes are not set once, but calculated dynamically, therefore we do not to store them + # internally with the other attributes, and only include them in the necessary responses. + QueueAttributeName.ApproximateNumberOfMessages, + QueueAttributeName.ApproximateNumberOfMessagesDelayed, + QueueAttributeName.ApproximateNumberOfMessagesNotVisible, +] INTERNAL_QUEUE_ATTRIBUTES = [ # these attributes cannot be changed by set_queue_attributes and should # therefore be ignored when comparing queue attributes for create_queue @@ -31,6 +38,12 @@ QueueAttributeName.QueueArn, ] +# +# If these attributes are set to their default values, they are effectively +# deleted from the queue attributes and not returned in future calls to get_queue_attributes() +# +DELETE_IF_DEFAULT = {"KmsMasterKeyId": "", "KmsDataKeyReusePeriodSeconds": "300"} + INVALID_STANDARD_QUEUE_ATTRIBUTES = [ QueueAttributeName.FifoQueue, QueueAttributeName.ContentBasedDeduplication, diff --git a/localstack-core/localstack/services/sqs/developer_api.py b/localstack-core/localstack/services/sqs/developer_api.py new file mode 100644 index 0000000000000..d9571192b8d5c --- /dev/null +++ b/localstack-core/localstack/services/sqs/developer_api.py @@ -0,0 +1,205 @@ +import logging +from typing import Literal + +from werkzeug import Request as WerkzeugRequest + +from localstack.aws.api import CommonServiceException, ServiceException +from localstack.aws.api.sqs import ( + Message, + QueueAttributeName, + QueueDoesNotExist, + ReceiveMessageResult, +) +from localstack.aws.protocol.parser import create_parser +from localstack.aws.protocol.serializer import aws_response_serializer +from localstack.aws.spec import load_service +from localstack.http import Request, route +from localstack.services.sqs.models import ( + FifoQueue, + SqsMessage, + SqsQueue, + StandardQueue, + sqs_stores, + to_sqs_api_message, +) +from localstack.services.sqs.utils import ( + parse_queue_url, +) +from localstack.utils.aws.request_context import extract_region_from_headers + +LOG = logging.getLogger(__name__) + + +class InvalidAddress(ServiceException): + code = "InvalidAddress" + message = "The address https://queue.amazonaws.com/ is not valid for this endpoint." + sender_fault = True + status_code = 404 + + +def get_sqs_protocol(request: Request) -> Literal["query", "json"]: + content_type = request.headers.get("Content-Type") + return "json" if content_type == "application/x-amz-json-1.0" else "query" + + +def sqs_auto_protocol_aws_response_serializer(service_name: str, operation: str): + def _decorate(fn): + def _proxy(*args, **kwargs): + # extract request from function invocation (decorator can be used for methods as well as for functions). + if len(args) > 0 and isinstance(args[0], WerkzeugRequest): + # function + request = args[0] + elif len(args) > 1 and isinstance(args[1], WerkzeugRequest): + # method (arg[0] == self) + request = args[1] + elif "request" in kwargs: + request = kwargs["request"] + else: + raise ValueError(f"could not find Request in signature of function {fn}") + + protocol = get_sqs_protocol(request) + return aws_response_serializer(service_name, operation, protocol)(fn)(*args, **kwargs) + + return _proxy + + return _decorate + + +class SqsDeveloperApi: + """ + A set of SQS developer tool endpoints: + + - ``/_aws/sqs/messages``: list SQS messages without side effects, compatible with ``ReceiveMessage``. + """ + + def __init__(self, stores=None): + self.stores = stores or sqs_stores + + @route("/_aws/sqs/messages", methods=["GET", "POST"]) + @sqs_auto_protocol_aws_response_serializer("sqs", "ReceiveMessage") + def list_messages(self, request: Request) -> ReceiveMessageResult: + """ + This endpoint expects a ``QueueUrl`` request parameter (either as query arg or form parameter), similar to + the ``ReceiveMessage`` operation. It will parse the Queue URL generated by one of the SQS endpoint strategies. + """ + + if "x-amz-" in request.mimetype or "x-www-form-urlencoded" in request.mimetype: + # only parse the request using a parser if it comes from an AWS client + protocol = get_sqs_protocol(request) + operation, service_request = create_parser( + load_service("sqs", protocol=protocol) + ).parse(request) + if operation.name != "ReceiveMessage": + raise CommonServiceException( + "InvalidRequest", "This endpoint only accepts ReceiveMessage calls" + ) + else: + service_request = dict(request.values) + + if not service_request.get("QueueUrl"): + raise QueueDoesNotExist() + + try: + account_id, region, queue_name = parse_queue_url(service_request.get("QueueUrl")) + except ValueError: + LOG.error( + "Error while parsing Queue URL from request values: %s", + service_request.get, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) + raise InvalidAddress() + + if not region: + region = extract_region_from_headers(request.headers) + + return self._get_and_serialize_messages(request, region, account_id, queue_name) + + @route("/_aws/sqs/messages///") + @sqs_auto_protocol_aws_response_serializer("sqs", "ReceiveMessage") + def list_messages_for_queue_url( + self, request: Request, region: str, account_id: str, queue_name: str + ) -> ReceiveMessageResult: + """ + This endpoint extracts the region, account_id, and queue_name directly from the URL rather than requiring the + QueueUrl as parameter. + """ + return self._get_and_serialize_messages(request, region, account_id, queue_name) + + def _get_and_serialize_messages( + self, + request: Request, + region: str, + account_id: str, + queue_name: str, + ) -> ReceiveMessageResult: + show_invisible = request.values.get("ShowInvisible", "").lower() in ["true", "1"] + show_delayed = request.values.get("ShowDelayed", "").lower() in ["true", "1"] + + try: + store = self.stores[account_id][region] + queue = store.queues[queue_name] + except KeyError: + LOG.info( + "no queue named %s in region %s and account %s", queue_name, region, account_id + ) + raise QueueDoesNotExist() + + messages = self._collect_messages( + queue, show_invisible=show_invisible, show_delayed=show_delayed + ) + + return ReceiveMessageResult(Messages=messages) + + def _collect_messages( + self, queue: SqsQueue, show_invisible: bool = False, show_delayed: bool = False + ) -> list[Message]: + """ + Retrieves from a given SqsQueue all visible messages without causing any side effects (not setting any + receive timestamps, receive counts, or visibility state). + + :param queue: the queue + :param show_invisible: show invisible messages as well + :param show_delayed: show delayed messages as well + :return: a list of messages + """ + receipt_handle = "SQS/BACKDOOR/ACCESS" # dummy receipt handle + + sqs_messages: list[SqsMessage] = [] + + if show_invisible: + sqs_messages.extend(queue.inflight) + + if isinstance(queue, StandardQueue): + sqs_messages.extend(queue.visible.queue) + elif isinstance(queue, FifoQueue): + if show_invisible: + for inflight_group in queue.inflight_groups: + # messages that have been received are held in ``queue.inflight``, even for FIFO queues. however, + # for fifo queues, messages that are in the same message group as messages that have been + # received, are also considered invisible, and are held here in ``inflight_group.messages``. + for sqs_message in inflight_group.messages: + sqs_messages.append(sqs_message) + + for message_group in queue.message_group_queue.queue: + # these are all messages of message groups that are visible + for sqs_message in message_group.messages: + sqs_messages.append(sqs_message) + else: + raise ValueError(f"unknown queue type {type(queue)}") + + if show_delayed: + sqs_messages.extend(queue.delayed) + + messages = [] + + for sqs_message in sqs_messages: + message: Message = to_sqs_api_message(sqs_message, [QueueAttributeName.All], ["All"]) + # these are all non-standard fields so we squelch the linter + if show_invisible: + message["Attributes"]["IsVisible"] = str(sqs_message.is_visible).lower() # noqa + if show_delayed: + message["Attributes"]["IsDelayed"] = str(sqs_message.is_delayed).lower() # noqa + messages.append(message) + message["ReceiptHandle"] = receipt_handle + + return messages diff --git a/localstack-core/localstack/services/sqs/models.py b/localstack-core/localstack/services/sqs/models.py index 8e7352bd28172..4f0de6365dea3 100644 --- a/localstack-core/localstack/services/sqs/models.py +++ b/localstack-core/localstack/services/sqs/models.py @@ -1,3 +1,4 @@ +import copy import hashlib import heapq import inspect @@ -8,7 +9,6 @@ import time from datetime import datetime from queue import Empty -from typing import Dict, Optional, Set from localstack import config from localstack.aws.api import RequestContext @@ -16,6 +16,7 @@ AttributeNameList, InvalidAttributeName, Message, + MessageAttributeNameList, MessageSystemAttributeName, QueueAttributeMap, QueueAttributeName, @@ -23,6 +24,7 @@ TagMap, ) from localstack.services.sqs import constants as sqs_constants +from localstack.services.sqs.constants import DYNAMIC_ATTRIBUTES from localstack.services.sqs.exceptions import ( InvalidAttributeValue, InvalidParameterValueException, @@ -30,16 +32,20 @@ ) from localstack.services.sqs.queue import InterruptiblePriorityQueue, InterruptibleQueue from localstack.services.sqs.utils import ( + create_message_attribute_hash, encode_move_task_handle, encode_receipt_handle, extract_receipt_handle_info, global_message_sequence, guess_endpoint_strategy_and_host, is_message_deduplication_id_required, + message_filter_attributes, + message_filter_message_attributes, ) from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute from localstack.utils.aws.arns import get_partition from localstack.utils.strings import long_uid +from localstack.utils.tagging import Tags from localstack.utils.time import now from localstack.utils.urls import localstack_host @@ -51,18 +57,16 @@ class SqsMessage: message: Message created: float - visibility_timeout: int + visibility_timeout: int | None receive_count: int - delay_seconds: Optional[int] - receipt_handles: Set[str] - last_received: Optional[float] - first_received: Optional[float] - visibility_deadline: Optional[float] + delay_seconds: int | None + receipt_handles: set[str] + last_received: float | None + first_received: float | None + visibility_deadline: float | None deleted: bool priority: float - message_deduplication_id: str - message_group_id: str - sequence_number: str + sequence_number: str | None def __init__( self, @@ -80,6 +84,7 @@ def __init__( self.delay_seconds = None self.last_received = None self.first_received = None + self.visibility_timeout = None self.visibility_deadline = None self.deleted = False self.priority = priority @@ -104,15 +109,15 @@ def __init__( ) @property - def message_group_id(self) -> Optional[str]: + def message_group_id(self) -> str | None: return self.message["Attributes"].get(MessageSystemAttributeName.MessageGroupId) @property - def message_deduplication_id(self) -> Optional[str]: + def message_deduplication_id(self) -> str | None: return self.message["Attributes"].get(MessageSystemAttributeName.MessageDeduplicationId) @property - def dead_letter_queue_source_arn(self) -> Optional[str]: + def dead_letter_queue_source_arn(self) -> str | None: return self.message["Attributes"].get(MessageSystemAttributeName.DeadLetterQueueSourceArn) @property @@ -154,7 +159,7 @@ def is_visible(self) -> bool: """ Returns false if the message has a visibility deadline that is in the future. - :return: whether the message is visibile or not. + :return: whether the message is visible or not. """ if self.visibility_deadline is None: return True @@ -191,6 +196,41 @@ def __repr__(self): return f"SqsMessage(id={self.message_id},group={self.message_group_id})" +def to_sqs_api_message( + standard_message: SqsMessage, + attribute_names: AttributeNameList = None, + message_attribute_names: MessageAttributeNameList = None, +) -> Message: + """ + Utility function to convert an SQS message from LocalStack's internal representation to the AWS API + concept 'Message', which is the format returned by the ``ReceiveMessage`` operation. + + :param standard_message: A LocalStack SQS message + :param attribute_names: the attribute name list to filter + :param message_attribute_names: the message attribute names to filter + :return: a copy of the original Message with updated message attributes and MD5 attribute hash sums + """ + # prepare message for receiver + message = copy.deepcopy(standard_message.message) + + # update system attributes of the message copy + message["Attributes"][MessageSystemAttributeName.ApproximateFirstReceiveTimestamp] = str( + int((standard_message.first_received or 0) * 1000) + ) + + # filter attributes for receiver + message_filter_attributes(message, attribute_names) + message_filter_message_attributes(message, message_attribute_names) + if message.get("MessageAttributes"): + message["MD5OfMessageAttributes"] = create_message_attribute_hash( + message["MessageAttributes"] + ) + else: + # delete the value that was computed when creating the message + message.pop("MD5OfMessageAttributes", None) + return message + + class ReceiveMessageResult: """ Object to communicate the result of a "receive messages" operation between the SqsProvider and @@ -230,28 +270,36 @@ class MessageMoveTask: # configurable fields source_arn: str """The arn of the DLQ the messages are currently in.""" - destination_arn: str | None = None + destination_arn: str | None """If the DestinationArn is not specified, the original source arn will be used as target.""" - max_number_of_messages_per_second: int | None = None + max_number_of_messages_per_second: int | None # dynamic fields task_id: str - status: str = MessageMoveTaskStatus.CREATED - started_timestamp: datetime | None = None - approximate_number_of_messages_moved: int | None = None - approximate_number_of_messages_to_move: int | None = None - failure_reason: str | None = None + status: str + started_timestamp: datetime | None + approximate_number_of_messages_moved: int | None + approximate_number_of_messages_to_move: int | None + failure_reason: str | None cancel_event: threading.Event def __init__( - self, source_arn: str, destination_arn: str, max_number_of_messages_per_second: int = None + self, + source_arn: str, + destination_arn: str, + max_number_of_messages_per_second: int | None = None, ): self.task_id = long_uid() self.source_arn = source_arn self.destination_arn = destination_arn self.max_number_of_messages_per_second = max_number_of_messages_per_second self.cancel_event = threading.Event() + self.status = MessageMoveTaskStatus.CREATED + self.started_timestamp = None + self.approximate_number_of_messages_moved = None + self.approximate_number_of_messages_to_move = None + self.failure_reason = None def mark_started(self): self.started_timestamp = datetime.utcnow() @@ -272,11 +320,13 @@ class SqsQueue: tags: TagMap purge_in_progress: bool - purge_timestamp: Optional[float] + purge_timestamp: float | None - delayed: Set[SqsMessage] - inflight: Set[SqsMessage] - receipts: Dict[str, SqsMessage] + delayed: set[SqsMessage] + # Simulating an ordered set in python. Only the keys are used and of interest. + inflight: dict[SqsMessage, None] + receipts: dict[str, SqsMessage] + mutex: threading.RLock def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None: self.name = name @@ -287,7 +337,7 @@ def __init__(self, name: str, region: str, account_id: str, attributes=None, tag self.tags = tags or {} self.delayed = set() - self.inflight = set() + self.inflight = {} self.receipts = {} self.attributes = self.default_attributes() @@ -306,15 +356,6 @@ def shutdown(self): def default_attributes(self) -> QueueAttributeMap: return { - QueueAttributeName.ApproximateNumberOfMessages: lambda: str( - self.approx_number_of_messages - ), - QueueAttributeName.ApproximateNumberOfMessagesNotVisible: lambda: str( - self.approx_number_of_messages_not_visible - ), - QueueAttributeName.ApproximateNumberOfMessagesDelayed: lambda: str( - self.approx_number_of_messages_delayed - ), QueueAttributeName.CreatedTimestamp: str(now()), QueueAttributeName.DelaySeconds: "0", QueueAttributeName.LastModifiedTimestamp: str(now()), @@ -393,13 +434,13 @@ def url(self, context: RequestContext) -> str: ) @property - def redrive_policy(self) -> Optional[dict]: + def redrive_policy(self) -> dict | None: if policy_document := self.attributes.get(QueueAttributeName.RedrivePolicy): return json.loads(policy_document) return None @property - def max_receive_count(self) -> Optional[int]: + def max_receive_count(self) -> int | None: """ Returns the maxReceiveCount attribute of the redrive policy. If no redrive policy is set, then it returns None. @@ -433,15 +474,15 @@ def maximum_message_size(self) -> int: return int(self.attributes[QueueAttributeName.MaximumMessageSize]) @property - def approx_number_of_messages(self) -> int: + def approximate_number_of_messages(self) -> int: raise NotImplementedError @property - def approx_number_of_messages_not_visible(self) -> int: + def approximate_number_of_messages_not_visible(self) -> int: return len(self.inflight) @property - def approx_number_of_messages_delayed(self) -> int: + def approximate_number_of_messages_delayed(self) -> int: return len(self.delayed) def validate_receipt_handle(self, receipt_handle: str): @@ -474,7 +515,7 @@ def update_visibility_timeout(self, receipt_handle: str, visibility_timeout: int ) # Terminating the visibility timeout for a message # https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html#terminating-message-visibility-timeout - self.inflight.remove(standard_message) + del self.inflight[standard_message] self._put_message(standard_message) def remove(self, receipt_handle: str): @@ -567,9 +608,17 @@ def requeue_inflight_messages(self): standard_message, self.arn, ) - self.inflight.remove(standard_message) + del self.inflight[standard_message] self._put_message(standard_message) + def add_inflight_message(self, message: SqsMessage): + """ + We are simulating an ordered set with a dict. When a value is added, it is added as key to the dict, which + is all we need. Hence all "values" in this ordered set are None + :param message: The message to put in flight + """ + self.inflight[message] = None + def enqueue_delayed_messages(self): if not self.delayed: return @@ -676,9 +725,9 @@ def get_queue_attributes(self, attribute_names: AttributeNameList = None) -> dic return {} if QueueAttributeName.All in attribute_names: - attribute_names = self.attributes.keys() + attribute_names = list(self.attributes.keys()) + DYNAMIC_ATTRIBUTES - result: Dict[QueueAttributeName, str] = {} + result: dict[QueueAttributeName, str] = {} for attr in attribute_names: try: @@ -686,13 +735,18 @@ def get_queue_attributes(self, attribute_names: AttributeNameList = None) -> dic except AttributeError: raise InvalidAttributeName(f"Unknown Attribute {attr}.") - value = self.attributes.get(attr) - if callable(value): - func = value - value = func() - if value is not None: - result[attr] = value - elif value == "False" or value == "True": + # The approximate_* attributes are calculated on the spot when accessed. + # We have a @property for each of those which calculates the value. + match attr: + case QueueAttributeName.ApproximateNumberOfMessages: + value = str(self.approximate_number_of_messages) + case QueueAttributeName.ApproximateNumberOfMessagesDelayed: + value = str(self.approximate_number_of_messages_delayed) + case QueueAttributeName.ApproximateNumberOfMessagesNotVisible: + value = str(self.approximate_number_of_messages_not_visible) + case _: + value = self.attributes.get(attr) + if value == "False" or value == "True": result[attr] = value.lower() elif value is not None: result[attr] = value @@ -740,7 +794,6 @@ def _pre_delete_checks(self, standard_message: SqsMessage, receipt_handle: str) class StandardQueue(SqsQueue): visible: InterruptiblePriorityQueue[SqsMessage] - inflight: Set[SqsMessage] def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None: super().__init__(name, region, account_id, attributes, tags) @@ -752,7 +805,7 @@ def clear(self): self.visible.queue.clear() @property - def approx_number_of_messages(self): + def approximate_number_of_messages(self): return self.visible.qsize() def shutdown(self): @@ -771,13 +824,12 @@ def put( f"Value {message_deduplication_id} for parameter MessageDeduplicationId is invalid. Reason: The " f"request includes a parameter that is not valid for this queue type." ) - if isinstance(message_group_id, str): - raise InvalidParameterValueException( - f"Value {message_group_id} for parameter MessageGroupId is invalid. Reason: The request include " - f"parameter that is not valid for this queue type." - ) - standard_message = SqsMessage(time.time(), message) + standard_message = SqsMessage( + time.time(), + message, + message_group_id=message_group_id, + ) if visibility_timeout is not None: standard_message.visibility_timeout = visibility_timeout @@ -885,13 +937,13 @@ def receive( if message.visibility_timeout == 0: self.visible.put_nowait(message) else: - self.inflight.add(message) + self.add_inflight_message(message) return result def _on_remove_message(self, message: SqsMessage): try: - self.inflight.remove(message) + del self.inflight[message] except KeyError: # this likely means the message was removed with an expired receipt handle unfortunately this # means we need to scan the queue for the element and remove it from there, and then re-heapify @@ -960,7 +1012,7 @@ class FifoQueue(SqsQueue): TODO: raise exceptions when trying to remove a message with an expired receipt handle """ - deduplication: Dict[str, SqsMessage] + deduplication: dict[str, SqsMessage] message_groups: dict[str, MessageGroup] inflight_groups: set[MessageGroup] message_group_queue: InterruptibleQueue @@ -979,7 +1031,7 @@ def __init__(self, name: str, region: str, account_id: str, attributes=None, tag self.deduplication_scope = self.attributes[QueueAttributeName.DeduplicationScope] @property - def approx_number_of_messages(self): + def approximate_number_of_messages(self): n = 0 for message_group in self.message_groups.values(): n += len(message_group.messages) @@ -1010,13 +1062,12 @@ def default_attributes(self) -> QueueAttributeMap: } def update_delay_seconds(self, value: int): - super(FifoQueue, self).update_delay_seconds(value) + super().update_delay_seconds(value) for message in self.delayed: message.delay_seconds = value def _pre_delete_checks(self, message: SqsMessage, receipt_handle: str) -> None: - _, _, _, last_received = extract_receipt_handle_info(receipt_handle) - if time.time() - float(last_received) > message.visibility_timeout: + if message.is_visible: raise InvalidParameterValueException( f"Value {receipt_handle} for parameter ReceiptHandle is invalid. Reason: The receipt handle has expired." ) @@ -1067,10 +1118,8 @@ def put( # use the attribute from the queue fifo_message.visibility_timeout = self.visibility_timeout - if delay_seconds is not None: - fifo_message.delay_seconds = delay_seconds - else: - fifo_message.delay_seconds = self.delay_seconds + # FIFO queues always use the queue level setting for 'DelaySeconds' + fifo_message.delay_seconds = self.delay_seconds original_message = self.deduplication.get(dedup_id) if ( @@ -1114,6 +1163,26 @@ def _put_message(self, message: SqsMessage): elif previously_empty: self.message_group_queue.put_nowait(message_group) + def requeue_inflight_messages(self): + if not self.inflight: + return + + with self.mutex: + messages = list(self.inflight) + for standard_message in messages: + # in fifo, an invisible message blocks potentially visible messages afterwards + # this can happen for example if multiple message of the same group are received at once, then one + # message of this batch has its visibility timeout extended + if not standard_message.is_visible: + return + LOG.debug( + "re-queueing inflight messages %s into queue %s", + standard_message, + self.arn, + ) + del self.inflight[standard_message] + self._put_message(standard_message) + def remove_expired_messages(self): with self.mutex: retention_period = self.message_retention_period @@ -1159,7 +1228,7 @@ def receive( timeout = wait_time_seconds or 0 start = time.time() - received_groups: Set[MessageGroup] = set() + received_groups: set[MessageGroup] = set() # collect messages over potentially multiple groups while True: @@ -1243,8 +1312,7 @@ def receive( if message.visibility_timeout == 0: self._put_message(message) else: - self.inflight.add(message) - + self.add_inflight_message(message) return result def _on_remove_message(self, message: SqsMessage): @@ -1253,7 +1321,7 @@ def _on_remove_message(self, message: SqsMessage): with self.mutex: try: - self.inflight.remove(message) + del self.inflight[message] except KeyError: # in FIFO queues, this should not happen, as expired receipt handles cannot be used to # delete a message. @@ -1315,13 +1383,15 @@ def clear(self): class SqsStore(BaseStore): - queues: Dict[str, SqsQueue] = LocalAttribute(default=dict) + queues: dict[str, FifoQueue | StandardQueue] = LocalAttribute(default=dict) - deleted: Dict[str, float] = LocalAttribute(default=dict) + deleted: dict[str, float] = LocalAttribute(default=dict) - move_tasks: Dict[str, MessageMoveTask] = LocalAttribute(default=dict) + move_tasks: dict[str, MessageMoveTask] = LocalAttribute(default=dict) """Maps task IDs to their ``MoveMessageTask`` object. Task IDs can be found by decoding a task handle.""" + tags: Tags = LocalAttribute(default=Tags) + def expire_deleted(self): for k in list(self.deleted.keys()): if self.deleted[k] <= (time.time() - sqs_constants.RECENTLY_DELETED_TIMEOUT): diff --git a/localstack-core/localstack/services/sqs/provider.py b/localstack-core/localstack/services/sqs/provider.py index 0dfcc41a047d2..0058e6828a267 100644 --- a/localstack-core/localstack/services/sqs/provider.py +++ b/localstack-core/localstack/services/sqs/provider.py @@ -1,21 +1,17 @@ -import copy -import hashlib import json import logging import re import threading import time +from collections.abc import Iterable from concurrent.futures.thread import ThreadPoolExecutor from itertools import islice -from typing import Dict, Iterable, List, Literal, Optional, Tuple from botocore.utils import InvalidArnException -from moto.sqs.models import BINARY_TYPE_FIELD_INDEX, STRING_TYPE_FIELD_INDEX -from moto.sqs.models import Message as MotoMessage from werkzeug import Request as WerkzeugRequest from localstack import config -from localstack.aws.api import CommonServiceException, RequestContext, ServiceException +from localstack.aws.api import RequestContext, ServiceException from localstack.aws.api.sqs import ( ActionNameList, AttributeNameList, @@ -69,11 +65,8 @@ Token, TooManyEntriesInBatchRequest, ) -from localstack.aws.protocol.parser import create_parser -from localstack.aws.protocol.serializer import aws_response_serializer from localstack.aws.spec import load_service from localstack.config import SQS_DISABLE_MAX_NUMBER_OF_MESSAGE_LIMIT -from localstack.http import Request, route from localstack.services.edge import ROUTER from localstack.services.plugins import ServiceLifecycleHook from localstack.services.sqs import constants as sqs_constants @@ -83,6 +76,7 @@ HEADER_LOCALSTACK_SQS_OVERRIDE_WAIT_TIME_SECONDS, MAX_RESULT_LIMIT, ) +from localstack.services.sqs.developer_api import SqsDeveloperApi from localstack.services.sqs.exceptions import ( InvalidParameterValueException, MissingRequiredParameterException, @@ -96,8 +90,10 @@ SqsStore, StandardQueue, sqs_stores, + to_sqs_api_message, ) from localstack.services.sqs.utils import ( + create_message_attribute_hash, decode_move_task_handle, generate_message_id, is_fifo_queue, @@ -105,8 +101,8 @@ parse_queue_url, ) from localstack.services.stores import AccountRegionBundle +from localstack.state import StateVisitor from localstack.utils.aws.arns import parse_arn -from localstack.utils.aws.request_context import extract_region_from_headers from localstack.utils.bootstrap import is_api_enabled from localstack.utils.cloudwatch.cloudwatch_util import ( SqsMetricBatchData, @@ -126,13 +122,6 @@ _STORE_LOCK = threading.RLock() -class InvalidAddress(ServiceException): - code = "InvalidAddress" - message = "The address https://queue.amazonaws.com/ is not valid for this endpoint." - sender_fault = True - status_code = 404 - - def assert_queue_name(queue_name: str, fifo: bool = False): if queue_name.endswith(".fifo"): if not fifo: @@ -310,7 +299,7 @@ class CloudwatchPublishWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None def publish_approximate_cloudwatch_metrics(self): """Publishes the metrics for ApproximateNumberOfMessagesVisible, ApproximateNumberOfMessagesNotVisible @@ -332,21 +321,21 @@ def publish_approximate_cloudwatch_metrics(self): SqsMetricBatchData( QueueName=queue.name, MetricName="ApproximateNumberOfMessagesVisible", - Value=queue.approx_number_of_messages, + Value=queue.approximate_number_of_messages, ) ) batch_data.append( SqsMetricBatchData( QueueName=queue.name, MetricName="ApproximateNumberOfMessagesNotVisible", - Value=queue.approx_number_of_messages_not_visible, + Value=queue.approximate_number_of_messages_not_visible, ) ) batch_data.append( SqsMetricBatchData( QueueName=queue.name, MetricName="ApproximateNumberOfMessagesDelayed", - Value=queue.approx_number_of_messages_delayed, + Value=queue.approximate_number_of_messages_delayed, ) ) @@ -391,31 +380,39 @@ class QueueUpdateWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None self.mutex = threading.RLock() def iter_queues(self) -> Iterable[SqsQueue]: for account_id, region, store in sqs_stores.iter_stores(): - for queue in store.queues.values(): - yield queue + yield from store.queues.values() def do_update_all_queues(self): for queue in self.iter_queues(): try: queue.requeue_inflight_messages() except Exception: - LOG.exception("error re-queueing inflight messages") + LOG.error( + "error re-queueing inflight messages", + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) try: queue.enqueue_delayed_messages() except Exception: - LOG.exception("error enqueueing delayed messages") + LOG.error( + "error enqueueing delayed messages", + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) if config.SQS_ENABLE_MESSAGE_RETENTION_PERIOD: try: queue.remove_expired_messages() except Exception: - LOG.exception("error removing expired messages") + LOG.error( + "error removing expired messages", + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) def start(self): with self.mutex: @@ -460,7 +457,7 @@ class MessageMoveTaskManager: def __init__(self, stores: AccountRegionBundle[SqsStore] = None) -> None: self.stores = stores or sqs_stores self.mutex = threading.RLock() - self.move_tasks: dict[str, MessageMoveTask] = dict() + self.move_tasks: dict[str, MessageMoveTask] = {} self.executor = ThreadPoolExecutor(max_workers=100, thread_name_prefix="sqs-move-message") def submit(self, move_task: MessageMoveTask): @@ -468,7 +465,7 @@ def submit(self, move_task: MessageMoveTask): try: source_queue = self._get_queue_by_arn(move_task.source_arn) move_task.approximate_number_of_messages_to_move = ( - source_queue.approx_number_of_messages + source_queue.approximate_number_of_messages ) move_task.approximate_number_of_messages_moved = 0 move_task.mark_started() @@ -621,176 +618,15 @@ def check_attributes(message_attributes: MessageBodyAttributeMap): raise InvalidParameterValueException(e.args[0]) -def check_fifo_id(fifo_id, parameter): - if not fifo_id: +def check_fifo_id(fifo_id: str | None, parameter: str): + if fifo_id is None: return - if len(fifo_id) > 128: - raise InvalidParameterValueException( - f"Value {fifo_id} for parameter {parameter} is invalid. Reason: {parameter} can only include alphanumeric and punctuation characters. 1 to 128 in length." - ) if not re.match(sqs_constants.FIFO_MSG_REGEX, fifo_id): raise InvalidParameterValueException( - "Invalid characters found. Deduplication ID and group ID can only contain" - "alphanumeric characters as well as TODO" - ) - - -def get_sqs_protocol(request: Request) -> Literal["query", "json"]: - content_type = request.headers.get("Content-Type") - return "json" if content_type == "application/x-amz-json-1.0" else "query" - - -def sqs_auto_protocol_aws_response_serializer(service_name: str, operation: str): - def _decorate(fn): - def _proxy(*args, **kwargs): - # extract request from function invocation (decorator can be used for methods as well as for functions). - if len(args) > 0 and isinstance(args[0], WerkzeugRequest): - # function - request = args[0] - elif len(args) > 1 and isinstance(args[1], WerkzeugRequest): - # method (arg[0] == self) - request = args[1] - elif "request" in kwargs: - request = kwargs["request"] - else: - raise ValueError(f"could not find Request in signature of function {fn}") - - protocol = get_sqs_protocol(request) - return aws_response_serializer(service_name, operation, protocol)(fn)(*args, **kwargs) - - return _proxy - - return _decorate - - -class SqsDeveloperEndpoints: - """ - A set of SQS developer tool endpoints: - - - ``/_aws/sqs/messages``: list SQS messages without side effects, compatible with ``ReceiveMessage``. - """ - - def __init__(self, stores=None): - self.stores = stores or sqs_stores - - @route("/_aws/sqs/messages", methods=["GET", "POST"]) - @sqs_auto_protocol_aws_response_serializer("sqs", "ReceiveMessage") - def list_messages(self, request: Request) -> ReceiveMessageResult: - """ - This endpoint expects a ``QueueUrl`` request parameter (either as query arg or form parameter), similar to - the ``ReceiveMessage`` operation. It will parse the Queue URL generated by one of the SQS endpoint strategies. - """ - - if "x-amz-" in request.mimetype or "x-www-form-urlencoded" in request.mimetype: - # only parse the request using a parser if it comes from an AWS client - protocol = get_sqs_protocol(request) - operation, service_request = create_parser( - load_service("sqs", protocol=protocol) - ).parse(request) - if operation.name != "ReceiveMessage": - raise CommonServiceException( - "InvalidRequest", "This endpoint only accepts ReceiveMessage calls" - ) - else: - service_request = dict(request.values) - - if not service_request.get("QueueUrl"): - raise QueueDoesNotExist() - - try: - account_id, region, queue_name = parse_queue_url(service_request.get("QueueUrl")) - except ValueError: - LOG.exception( - "Error while parsing Queue URL from request values: %s", service_request.get - ) - raise InvalidAddress() - - if not region: - region = extract_region_from_headers(request.headers) - - return self._get_and_serialize_messages(request, region, account_id, queue_name) - - @route("/_aws/sqs/messages///") - @sqs_auto_protocol_aws_response_serializer("sqs", "ReceiveMessage") - def list_messages_for_queue_url( - self, request: Request, region: str, account_id: str, queue_name: str - ) -> ReceiveMessageResult: - """ - This endpoint extracts the region, account_id, and queue_name directly from the URL rather than requiring the - QueueUrl as parameter. - """ - return self._get_and_serialize_messages(request, region, account_id, queue_name) - - def _get_and_serialize_messages( - self, - request: Request, - region: str, - account_id: str, - queue_name: str, - ) -> ReceiveMessageResult: - show_invisible = request.values.get("ShowInvisible", "").lower() in ["true", "1"] - show_delayed = request.values.get("ShowDelayed", "").lower() in ["true", "1"] - - try: - store = SqsProvider.get_store(account_id, region) - queue = store.queues[queue_name] - except KeyError: - LOG.info( - "no queue named %s in region %s and account %s", queue_name, region, account_id - ) - raise QueueDoesNotExist() - - messages = self._collect_messages( - queue, show_invisible=show_invisible, show_delayed=show_delayed + f"Value {fifo_id} for parameter {parameter} is invalid. " + f"Reason: {parameter} can only include alphanumeric and punctuation characters. 1 to 128 in length." ) - return ReceiveMessageResult(Messages=messages) - - def _collect_messages( - self, queue: SqsQueue, show_invisible: bool = False, show_delayed: bool = False - ) -> List[Message]: - """ - Retrieves from a given SqsQueue all visible messages without causing any side effects (not setting any - receive timestamps, receive counts, or visibility state). - - :param queue: the queue - :param show_invisible: show invisible messages as well - :param show_delayed: show delayed messages as well - :return: a list of messages - """ - receipt_handle = "SQS/BACKDOOR/ACCESS" # dummy receipt handle - - sqs_messages: List[SqsMessage] = [] - - if show_invisible: - sqs_messages.extend(queue.inflight) - - if isinstance(queue, StandardQueue): - sqs_messages.extend(queue.visible.queue) - elif isinstance(queue, FifoQueue): - for message_group in queue.message_groups.values(): - for sqs_message in message_group.messages: - sqs_messages.append(sqs_message) - else: - raise ValueError(f"unknown queue type {type(queue)}") - - if show_delayed: - sqs_messages.extend(queue.delayed) - - messages = [] - - for sqs_message in sqs_messages: - message: Message = to_sqs_api_message(sqs_message, [QueueAttributeName.All], ["All"]) - # these are all non-standard fields so we squelch the linter - if show_invisible: - message["Attributes"]["IsVisible"] = str(sqs_message.is_visible).lower() # noqa - if show_delayed: - message["Attributes"]["IsDelayed"] = str(sqs_message.is_delayed).lower() # noqa - messages.append(message) - message["ReceiptHandle"] = receipt_handle - - return messages - class SqsProvider(SqsApi, ServiceLifecycleHook): """ @@ -815,7 +651,7 @@ class SqsProvider(SqsApi, ServiceLifecycleHook): - UntagQueue """ - queues: Dict[str, SqsQueue] + queues: dict[str, SqsQueue] def __init__(self) -> None: super().__init__() @@ -824,13 +660,33 @@ def __init__(self) -> None: self._router_rules = [] self._init_cloudwatch_metrics_reporting() + def accept_state_visitor(self, visitor: StateVisitor): + visitor.visit(sqs_stores) + @staticmethod def get_store(account_id: str, region: str) -> SqsStore: return sqs_stores[account_id][region] + def on_after_init(self): + # this configuration increases the processing power for Query protocol requests, which are form-encoded and by + # default are limited to 500kb payload size by Werkzeug. we make sure we only *increase* the limit if it's + # already set, and if it's already set to unlimited we leave it. + from rolo import Request as RoloRequest + + # needed for the webserver integration (webservers create Werkzeug request objects) + if WerkzeugRequest.max_form_memory_size is not None: + WerkzeugRequest.max_form_memory_size = max( + WerkzeugRequest.max_form_memory_size, sqs_constants.DEFAULT_MAXIMUM_MESSAGE_SIZE * 2 + ) + # needed for internal/proxy requests (which create rolo request objects) + if RoloRequest.max_form_memory_size is not None: + RoloRequest.max_form_memory_size = max( + RoloRequest.max_form_memory_size, sqs_constants.DEFAULT_MAXIMUM_MESSAGE_SIZE * 2 + ) + def on_before_start(self): query_api.register(ROUTER) - self._router_rules = ROUTER.add(SqsDeveloperEndpoints()) + self._router_rules = ROUTER.add(SqsDeveloperApi()) self._queue_update_worker.start() self._start_cloudwatch_metrics_reporting() @@ -881,8 +737,8 @@ def _require_queue_by_arn(self, context: RequestContext, queue_arn: str) -> SqsQ def _resolve_queue( self, context: RequestContext, - queue_name: Optional[str] = None, - queue_url: Optional[str] = None, + queue_name: str | None = None, + queue_url: str | None = None, ) -> SqsQueue: """ Determines the name of the queue from available information (request context, queue URL) to return the respective queue, @@ -956,6 +812,8 @@ def create_queue( queue = StandardQueue( queue_name, context.region, context.account_id, attributes, tags ) + if tags: + self._tag_queue(queue, tags) LOG.debug("creating queue key=%s attributes=%s tags=%s", queue_name, attributes, tags) @@ -1082,6 +940,7 @@ def delete_queue(self, context: RequestContext, queue_url: String, **kwargs) -> store.queues[queue.name].shutdown() del store.queues[queue.name] store.deleted[queue.name] = time.time() + self._remove_all_queue_tags(queue) def get_queue_attributes( self, @@ -1125,7 +984,7 @@ def send_message( MD5OfMessageBody=message["MD5OfBody"], MD5OfMessageAttributes=message.get("MD5OfMessageAttributes"), SequenceNumber=queue_item.sequence_number, - MD5OfMessageSystemAttributes=_create_message_attribute_hash(message_system_attributes), + MD5OfMessageSystemAttributes=create_message_attribute_hash(message_system_attributes), ) def send_message_batch( @@ -1171,7 +1030,7 @@ def send_message_batch( MessageId=message.get("MessageId"), MD5OfMessageBody=message.get("MD5OfBody"), MD5OfMessageAttributes=message.get("MD5OfMessageAttributes"), - MD5OfMessageSystemAttributes=_create_message_attribute_hash( + MD5OfMessageSystemAttributes=create_message_attribute_hash( message.get("message_system_attributes") ), SequenceNumber=queue_item.sequence_number, @@ -1225,7 +1084,7 @@ def _put_message( MD5OfBody=md5(message_body), Body=message_body, Attributes=self._create_message_attributes(context, message_system_attributes), - MD5OfMessageAttributes=_create_message_attribute_hash(message_attributes), + MD5OfMessageAttributes=create_message_attribute_hash(message_attributes), MessageAttributes=message_attributes, ) if self._cloudwatch_dispatcher: @@ -1274,7 +1133,7 @@ def receive_message( num = override elif num == -1: # backdoor to get all messages - num = queue.approx_number_of_messages + num = queue.approximate_number_of_messages elif ( num < 1 or num > MAX_NUMBER_OF_MESSAGES ) and not SQS_DISABLE_MAX_NUMBER_OF_MESSAGE_LIMIT: @@ -1417,7 +1276,11 @@ def set_queue_attributes( for k, v in attributes.items(): if k in sqs_constants.INTERNAL_QUEUE_ATTRIBUTES: raise InvalidAttributeName(f"Unknown Attribute {k}.") - queue.attributes[k] = v + if k in sqs_constants.DELETE_IF_DEFAULT and v == sqs_constants.DELETE_IF_DEFAULT[k]: + if k in queue.attributes: + del queue.attributes[k] + else: + queue.attributes[k] = v # Special cases if queue.attributes.get(QueueAttributeName.Policy) == "": @@ -1625,28 +1488,24 @@ def cancel_message_move_task( ) def tag_queue(self, context: RequestContext, queue_url: String, tags: TagMap, **kwargs) -> None: - queue = self._resolve_queue(context, queue_url=queue_url) - if not tags: return - for k, v in tags.items(): - queue.tags[k] = v + queue = self._resolve_queue(context, queue_url=queue_url) + self._tag_queue(queue, tags) def list_queue_tags( self, context: RequestContext, queue_url: String, **kwargs ) -> ListQueueTagsResult: queue = self._resolve_queue(context, queue_url=queue_url) - return ListQueueTagsResult(Tags=(queue.tags if queue.tags else None)) + tags = self._get_queue_tags(queue) + return ListQueueTagsResult(Tags=tags if tags else None) def untag_queue( self, context: RequestContext, queue_url: String, tag_keys: TagKeyList, **kwargs ) -> None: queue = self._resolve_queue(context, queue_url=queue_url) - - for k in tag_keys: - if k in queue.tags: - del queue.tags[k] + self._untag_queue(queue, tag_keys) def add_permission( self, @@ -1674,8 +1533,8 @@ def _create_message_attributes( self, context: RequestContext, message_system_attributes: MessageBodySystemAttributeMap = None, - ) -> Dict[MessageSystemAttributeName, str]: - result: Dict[MessageSystemAttributeName, str] = { + ) -> dict[MessageSystemAttributeName, str]: + result: dict[MessageSystemAttributeName, str] = { MessageSystemAttributeName.SenderId: context.account_id, # not the account ID in AWS MessageSystemAttributeName.SentTimestamp: str(now(millis=True)), } @@ -1709,7 +1568,7 @@ def _validate_actions(self, actions: ActionNameList): def _assert_batch( self, - batch: List, + batch: list, *, require_fifo_queue_params: bool = False, require_message_deduplication_id: bool = False, @@ -1745,7 +1604,7 @@ def _assert_batch( else: visited.add(entry_id) - def _assert_valid_batch_size(self, batch: List, max_message_size: int): + def _assert_valid_batch_size(self, batch: list, max_message_size: int): batch_message_size = sum( _message_body_size(entry.get("MessageBody")) + _message_attributes_size(entry.get("MessageAttributes")) @@ -1756,7 +1615,7 @@ def _assert_valid_batch_size(self, batch: List, max_message_size: int): error += f" You have sent {batch_message_size} bytes." raise BatchRequestTooLong(error) - def _assert_valid_message_ids(self, batch: List): + def _assert_valid_message_ids(self, batch: list): batch_id_regex = r"^[\w-]{1,80}$" for message in batch: if not re.match(batch_id_regex, message.get("Id", "")): @@ -1784,38 +1643,26 @@ def _stop_cloudwatch_metrics_reporting(self): self._cloudwatch_publish_worker.stop() self._cloudwatch_dispatcher.shutdown() + def _get_queue_tags(self, queue: SqsQueue) -> TagMap: + store = self.get_store(queue.account_id, queue.region) + return store.tags.get_tags(queue.arn) -# Method from moto's attribute_md5 of moto/sqs/models.py, separated from the Message Object -def _create_message_attribute_hash(message_attributes) -> Optional[str]: - # To avoid the need to check for dict conformity everytime we invoke this function - if not isinstance(message_attributes, dict): - return - hash = hashlib.md5() - - for attrName in sorted(message_attributes.keys()): - attr_value = message_attributes[attrName] - # Encode name - MotoMessage.update_binary_length_and_value(hash, MotoMessage.utf8(attrName)) - # Encode data type - MotoMessage.update_binary_length_and_value(hash, MotoMessage.utf8(attr_value["DataType"])) - # Encode transport type and value - if attr_value.get("StringValue"): - hash.update(bytearray([STRING_TYPE_FIELD_INDEX])) - MotoMessage.update_binary_length_and_value( - hash, MotoMessage.utf8(attr_value.get("StringValue")) - ) - elif attr_value.get("BinaryValue"): - hash.update(bytearray([BINARY_TYPE_FIELD_INDEX])) - decoded_binary_value = attr_value.get("BinaryValue") - MotoMessage.update_binary_length_and_value(hash, decoded_binary_value) - # string_list_value, binary_list_value type is not implemented, reserved for the future use. - # See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html - return hash.hexdigest() + def _tag_queue(self, queue: SqsQueue, tags: TagMap) -> None: + store = self.get_store(queue.account_id, queue.region) + store.tags.update_tags(queue.arn, tags) + + def _untag_queue(self, queue: SqsQueue, tag_keys: TagKeyList) -> None: + store = self.get_store(queue.account_id, queue.region) + store.tags.delete_tags(queue.arn, tag_keys) + + def _remove_all_queue_tags(self, queue: SqsQueue) -> None: + store = self.get_store(queue.account_id, queue.region) + store.tags.delete_all_tags(queue.arn) def resolve_queue_location( - context: RequestContext, queue_name: Optional[str] = None, queue_url: Optional[str] = None -) -> Tuple[str, Optional[str], str]: + context: RequestContext, queue_name: str | None = None, queue_url: str | None = None +) -> tuple[str, str | None, str]: """ Resolves a queue location from the given information. @@ -1837,106 +1684,6 @@ def resolve_queue_location( return context.account_id, context.region, queue_name -def to_sqs_api_message( - standard_message: SqsMessage, - attribute_names: AttributeNameList = None, - message_attribute_names: MessageAttributeNameList = None, -) -> Message: - """ - Utility function to convert an SQS message from LocalStack's internal representation to the AWS API - concept 'Message', which is the format returned by the ``ReceiveMessage`` operation. - - :param standard_message: A LocalStack SQS message - :param attribute_names: the attribute name list to filter - :param message_attribute_names: the message attribute names to filter - :return: a copy of the original Message with updated message attributes and MD5 attribute hash sums - """ - # prepare message for receiver - message = copy.deepcopy(standard_message.message) - - # update system attributes of the message copy - message["Attributes"][MessageSystemAttributeName.ApproximateFirstReceiveTimestamp] = str( - int((standard_message.first_received or 0) * 1000) - ) - - # filter attributes for receiver - message_filter_attributes(message, attribute_names) - message_filter_message_attributes(message, message_attribute_names) - if message.get("MessageAttributes"): - message["MD5OfMessageAttributes"] = _create_message_attribute_hash( - message["MessageAttributes"] - ) - else: - # delete the value that was computed when creating the message - message.pop("MD5OfMessageAttributes", None) - return message - - -def message_filter_attributes(message: Message, names: Optional[AttributeNameList]): - """ - Utility function filter from the given message (in-place) the system attributes from the given list. It will - apply all rules according to: - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message. - - :param message: The message to filter (it will be modified) - :param names: the attributes names/filters - """ - if "Attributes" not in message: - return - - if not names: - del message["Attributes"] - return - - if QueueAttributeName.All in names: - return - - for k in list(message["Attributes"].keys()): - if k not in names: - del message["Attributes"][k] - - -def message_filter_message_attributes(message: Message, names: Optional[MessageAttributeNameList]): - """ - Utility function filter from the given message (in-place) the message attributes from the given list. It will - apply all rules according to: - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message. - - :param message: The message to filter (it will be modified) - :param names: the attributes names/filters (can be 'All', '.*', '*' or prefix filters like 'Foo.*') - """ - if not message.get("MessageAttributes"): - return - - if not names: - del message["MessageAttributes"] - return - - if "All" in names or ".*" in names or "*" in names: - return - - attributes = message["MessageAttributes"] - matched = [] - - keys = [name for name in names if ".*" not in name] - prefixes = [name.split(".*")[0] for name in names if ".*" in name] - - # match prefix filters - for k in attributes: - if k in keys: - matched.append(k) - continue - - for prefix in prefixes: - if k.startswith(prefix): - matched.append(k) - break - if matched: - message["MessageAttributes"] = {k: attributes[k] for k in matched} - else: - message.pop("MessageAttributes") - - def extract_message_count_from_headers(context: RequestContext) -> int | None: if override := context.request.headers.get( HEADER_LOCALSTACK_SQS_OVERRIDE_MESSAGE_COUNT, default=None, type=int diff --git a/localstack-core/localstack/services/sqs/query_api.py b/localstack-core/localstack/services/sqs/query_api.py index 6d5a33ee4bd5d..83bd0532ab547 100644 --- a/localstack-core/localstack/services/sqs/query_api.py +++ b/localstack-core/localstack/services/sqs/query_api.py @@ -4,7 +4,6 @@ to make the request.""" import logging -from typing import Dict, Optional, Tuple from urllib.parse import urlencode from botocore.exceptions import ClientError @@ -36,7 +35,7 @@ service = load_service("sqs-query") parser = create_parser(service) -serializer = create_serializer(service) +serializer = create_serializer(service, "query") @route( @@ -167,7 +166,11 @@ def handle_request(request: Request, region: str) -> Response: op = service.operation_model(service.operation_names[0]) return serializer.serialize_error_to_response(e, op, request.headers, request_id) except Exception as e: - LOG.exception("exception") + LOG.error( + "Internal Server exception while executing SQS Query operation: '%s'", + e, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) op = service.operation_model(service.operation_names[0]) return serializer.serialize_error_to_response( CommonServiceException( @@ -179,7 +182,7 @@ def handle_request(request: Request, region: str) -> Response: ) -def try_call_sqs(request: Request, region: str) -> Tuple[Dict, OperationModel]: +def try_call_sqs(request: Request, region: str) -> tuple[dict, OperationModel]: action = request.values.get("Action") if not action: raise UnknownOperationException() @@ -209,7 +212,7 @@ def try_call_sqs(request: Request, region: str) -> Tuple[Dict, OperationModel]: # Extract from auth header to allow cross-account operations # TODO: permissions encoded in URL as AUTHPARAMS cannot be accounted for in this method, which is not a big # problem yet since we generally don't enforce permissions. - account_id: Optional[str] = extract_access_key_id_from_auth_header(headers) + account_id: str | None = extract_access_key_id_from_auth_header(headers) client = connect_to( region_name=region, diff --git a/localstack-core/localstack/services/sqs/queue.py b/localstack-core/localstack/services/sqs/queue.py index dc3b5e8d88f70..ea250191c083b 100644 --- a/localstack-core/localstack/services/sqs/queue.py +++ b/localstack-core/localstack/services/sqs/queue.py @@ -2,15 +2,15 @@ from queue import Empty, PriorityQueue, Queue -class InterruptibleQueue(Queue): +class InterruptibleQueue[T](Queue): # is_shutdown is used to check whether we have triggered a shutdown of the Queue is_shutdown: bool - def __init__(self, maxsize=0): + def __init__(self, maxsize: int = 0): super().__init__(maxsize) self.is_shutdown = False - def get(self, block=True, timeout=None): + def get(self, block: bool = True, timeout: float | None = None) -> T: with self.not_empty: if self.is_shutdown: raise Empty @@ -35,7 +35,7 @@ def get(self, block=True, timeout=None): self.not_full.notify() return item - def shutdown(self): + def shutdown(self) -> None: """ `shutdown` signals to stop all current and future `Queue.get` calls from executing. @@ -46,5 +46,5 @@ def shutdown(self): self.not_empty.notify_all() -class InterruptiblePriorityQueue(PriorityQueue, InterruptibleQueue): +class InterruptiblePriorityQueue[T](PriorityQueue[T], InterruptibleQueue[T]): pass diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py index 52b39da351d96..bf3914c278e76 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py +++ b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py @@ -2,45 +2,17 @@ from __future__ import annotations import json -from pathlib import Path -from typing import Optional, TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( OperationStatus, ProgressEvent, - ResourceProvider, ResourceRequest, ) - - -class SQSQueueProperties(TypedDict): - Arn: Optional[str] - ContentBasedDeduplication: Optional[bool] - DeduplicationScope: Optional[str] - DelaySeconds: Optional[int] - FifoQueue: Optional[bool] - FifoThroughputLimit: Optional[str] - KmsDataKeyReusePeriodSeconds: Optional[int] - KmsMasterKeyId: Optional[str] - MaximumMessageSize: Optional[int] - MessageRetentionPeriod: Optional[int] - QueueName: Optional[str] - QueueUrl: Optional[str] - ReceiveMessageWaitTimeSeconds: Optional[int] - RedriveAllowPolicy: Optional[dict | str] - RedrivePolicy: Optional[dict | str] - SqsManagedSseEnabled: Optional[bool] - Tags: Optional[list[Tag]] - VisibilityTimeout: Optional[int] - - -class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] - - -REPEATED_INVOCATION = "repeated_invocation" +from localstack.services.sqs.resource_providers.generated.aws_sqs_queue_base import ( + SQSQueueProperties, + SQSQueueProviderBase, +) _queue_attribute_list = [ "ContentBasedDeduplication", @@ -60,9 +32,35 @@ class Tag(TypedDict): ] -class SQSQueueProvider(ResourceProvider[SQSQueueProperties]): - TYPE = "AWS::SQS::Queue" # Autogenerated. Don't change - SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change +class SQSQueueProvider(SQSQueueProviderBase): + # Values used when a property is removed from a template and needs to be set to its default. + # If AWS changes their defaults in the future, our parity tests should break. + DEFAULT_ATTRIBUTE_VALUES = { + "ReceiveMessageWaitTimeSeconds": "0", + "DelaySeconds": "0", + "KmsMasterKeyId": "", + "RedrivePolicy": "", + "MessageRetentionPeriod": "345600", + "MaximumMessageSize": "262144", # Note: CloudFormation sets this to 256KB on update, but 1MB on create + "VisibilityTimeout": "30", + "KmsDataKeyReusePeriodSeconds": "300", + # Note that "SqsManagedSseEnabled" is deliberately omitted from this list, since AWS + # doesn't seem to reset it to a default. + } + + # Similar for FIFO, but only applied when FifoQueue is true (these can't be set otherwise) + DEFAULT_FIFO_ATTRIBUTE_VALUES = { + "ContentBasedDeduplication": "false", + "DeduplicationScope": "messageGroup", + "FifoThroughputLimit": "perMessageGroupId", + } + + # Private method for creating a unique queue name, if none is specified. + def _autogenerated_queue_name(self, request: ResourceRequest[SQSQueueProperties]) -> str: + queue_name = util.generate_default_name(request.stack_name, request.logical_resource_id) + if self._is_true_property(request.desired_state.get("FifoQueue")): + queue_name = f"{queue_name[:-5]}.fifo" + return queue_name def create( self, @@ -74,8 +72,6 @@ def create( Primary identifier fields: - /properties/QueueUrl - - Create-only properties: - /properties/FifoQueue - /properties/QueueName @@ -92,26 +88,13 @@ def create( - sqs:TagQueue """ - # TODO: validations + # TODO: validations - what validations are needed? model = request.desired_state sqs = request.aws_client_factory.sqs - if model.get("FifoQueue", False): - model["FifoQueue"] = model["FifoQueue"] - - queue_name = model.get("QueueName") - if not queue_name: - # TODO: verify patterns here - if model.get("FifoQueue"): - queue_name = util.generate_default_name( - request.stack_name, request.logical_resource_id - )[:-5] - queue_name = f"{queue_name}.fifo" - else: - queue_name = util.generate_default_name( - request.stack_name, request.logical_resource_id - ) - model["QueueName"] = queue_name + # if no QueueName is specified, automatically generate one + if not model.get("QueueName"): + model["QueueName"] = self._autogenerated_queue_name(request) attributes = self._compile_sqs_queue_attributes(model) result = request.aws_client_factory.sqs.create_queue( @@ -184,38 +167,30 @@ def update( """ sqs = request.aws_client_factory.sqs model = request.desired_state + prev_model = request.previous_state assert request.previous_state is not None - should_replace = ( - request.desired_state.get("QueueName", request.previous_state["QueueName"]) - != request.previous_state["QueueName"] - ) or ( - request.desired_state.get("FifoQueue", request.previous_state.get("FifoQueue")) - != request.previous_state.get("FifoQueue") + queue_url = prev_model["QueueUrl"] + self._populate_missing_attributes_with_defaults(model) + sqs.set_queue_attributes( + QueueUrl=queue_url, Attributes=self._compile_sqs_queue_attributes(model) ) - if not should_replace: - return ProgressEvent(OperationStatus.SUCCESS, resource_model=request.previous_state) - - # TODO: copied from the create handler, extract? - if model.get("FifoQueue"): - queue_name = util.generate_default_name( - request.stack_name, request.logical_resource_id - )[:-5] - queue_name = f"{queue_name}.fifo" - else: - queue_name = util.generate_default_name(request.stack_name, request.logical_resource_id) - - # replacement (TODO: find out if we should handle this in the provider or outside of it) - # delete old queue - sqs.delete_queue(QueueUrl=request.previous_state["QueueUrl"]) - # create new queue (TODO: re-use create logic to make this more robust, e.g. for - # auto-generated queue names) - model["QueueUrl"] = sqs.create_queue(QueueName=queue_name)["QueueUrl"] - model["Arn"] = sqs.get_queue_attributes( - QueueUrl=model["QueueUrl"], AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] + (tags_to_remove, tags_to_add_or_update) = util.resource_tags_to_remove_or_update( + prev_model.get("Tags", []), model.get("Tags", []) + ) + sqs.untag_queue(QueueUrl=queue_url, TagKeys=tags_to_remove) + sqs.tag_queue(QueueUrl=queue_url, Tags=tags_to_add_or_update) + + model["QueueUrl"] = queue_url + model["Arn"] = request.previous_state["Arn"] + + # For QueueName and FifoQueue, always use the value from the previous model. These fields + # are create-only, so they cannot be changed via an update (even though they might be omitted) + model["QueueName"] = prev_model.get("QueueName") + model["FifoQueue"] = prev_model.get("FifoQueue", False) + return ProgressEvent(OperationStatus.SUCCESS, resource_model=model) def _compile_sqs_queue_attributes(self, properties: SQSQueueProperties) -> dict[str, str]: @@ -250,6 +225,30 @@ def _compile_sqs_queue_attributes(self, properties: SQSQueueProperties) -> dict[ return result + def _populate_missing_attributes_with_defaults(self, properties: SQSQueueProperties) -> None: + """ + For any attribute that is missing from the desired state, populate it with the default value. + This is the only way to remove an attribute from an existing SQS queue's configuration. + :param properties: the properties passed from cloudformation + """ + for k, v in self.DEFAULT_ATTRIBUTE_VALUES.items(): + properties.setdefault(k, v) + + if self._is_true_property(properties.get("FifoQueue")): + for k, v in self.DEFAULT_FIFO_ATTRIBUTE_VALUES.items(): + properties.setdefault(k, v) + + def _is_true_property(self, property_value: str | None) -> bool: + """ + Detect whether a 'true' value is passed in a property. If it's None (property was omitted) or False, or any + type of string (e.g. a typo such as "Fasle"), then it's not a true value. This extra check is needed because + the CloudFormation engine doesn't fully validate booleans properties before passing them to the resource provider. + """ + return ( + property_value == True # noqa: E712 + or (isinstance(property_value, str) and property_value.lower() == "true") + ) + def list( self, request: ResourceRequest[SQSQueueProperties], diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queueinlinepolicy.py b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queueinlinepolicy.py new file mode 100644 index 0000000000000..3ef15877a8da1 --- /dev/null +++ b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queueinlinepolicy.py @@ -0,0 +1,76 @@ +# LocalStack Resource Provider Scaffolding v2 +import json + +from localstack.services.cloudformation.resource_provider import ( + OperationStatus, + ProgressEvent, + ResourceRequest, +) +from localstack.services.sqs.resource_providers.generated.aws_sqs_queueinlinepolicy_base import ( + SQSQueueInlinePolicyProperties, + SQSQueueInlinePolicyProviderBase, +) + + +class SQSQueueInlinePolicyProvider(SQSQueueInlinePolicyProviderBase): + def create( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + model = request.desired_state + sqs = request.aws_client_factory.sqs + + queue = model.get("Queue") + policy = model.get("PolicyDocument") + sqs.set_queue_attributes(QueueUrl=queue, Attributes={"Policy": json.dumps(policy)}) + + return ProgressEvent( + status=OperationStatus.SUCCESS, + resource_model=model, + custom_context=request.custom_context, + ) + + def read( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + raise NotImplementedError + + def delete( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + model = request.desired_state + sqs = request.aws_client_factory.sqs + + queue = model.get("Queue") + sqs.set_queue_attributes(QueueUrl=queue, Attributes={"Policy": ""}) + + return ProgressEvent(status=OperationStatus.SUCCESS, resource_model={}) + + def update( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + model = request.desired_state + sqs = request.aws_client_factory.sqs + + queue = model.get("Queue") + policy = model.get("PolicyDocument") + sqs.set_queue_attributes(QueueUrl=queue, Attributes={"Policy": json.dumps(policy)}) + + return ProgressEvent( + status=OperationStatus.SUCCESS, + resource_model=model, + custom_context=request.custom_context, + ) + + def list( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + """ + List available resources of this type + + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py index cc7bdecfa9254..397eb6719e9a3 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py +++ b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py @@ -1,49 +1,25 @@ # LocalStack Resource Provider Scaffolding v2 -from __future__ import annotations - import json -from pathlib import Path -from typing import Optional, TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( OperationStatus, ProgressEvent, - ResourceProvider, ResourceRequest, ) +from localstack.services.sqs.resource_providers.generated.aws_sqs_queuepolicy_base import ( + SQSQueuePolicyProperties, + SQSQueuePolicyProviderBase, +) -class SQSQueuePolicyProperties(TypedDict): - PolicyDocument: Optional[dict] - Queues: Optional[list[str]] - Id: Optional[str] - - -REPEATED_INVOCATION = "repeated_invocation" - - -class SQSQueuePolicyProvider(ResourceProvider[SQSQueuePolicyProperties]): - TYPE = "AWS::SQS::QueuePolicy" # Autogenerated. Don't change - SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change - +class SQSQueuePolicyProvider(SQSQueuePolicyProviderBase): def create( self, request: ResourceRequest[SQSQueuePolicyProperties], ) -> ProgressEvent[SQSQueuePolicyProperties]: """ Create a new resource. - - Primary identifier fields: - - /properties/Id - - Required properties: - - PolicyDocument - - Queues - - Read-only properties: - - /properties/Id - """ model = request.desired_state sqs = request.aws_client_factory.sqs @@ -100,11 +76,31 @@ def update( """ model = request.desired_state sqs = request.aws_client_factory.sqs - for queue in model.get("Queues", []): + + # handle new/updated queues + desired_queues = model.get("Queues", []) + for queue in desired_queues: policy = json.dumps(model["PolicyDocument"]) sqs.set_queue_attributes(QueueUrl=queue, Attributes={"Policy": policy}) + # handle queues no longer in the desired state + previous_queues = request.previous_state.get("Queues", []) + outdated_queues = set(previous_queues) - set(desired_queues) + for queue in outdated_queues: + sqs.set_queue_attributes(QueueUrl=queue, Attributes={"Policy": ""}) + + model["Id"] = request.previous_state["Id"] + return ProgressEvent( status=OperationStatus.SUCCESS, - resource_model=request.desired_state, + resource_model=model, ) + + def list( + self, + request: ResourceRequest[SQSQueuePolicyProperties], + ) -> ProgressEvent[SQSQueuePolicyProperties]: + """ + List available resources of this type + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.schema.json b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.schema.json deleted file mode 100644 index 654910643709d..0000000000000 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.schema.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "typeName": "AWS::SQS::QueuePolicy", - "description": "Resource Type definition for AWS::SQS::QueuePolicy", - "additionalProperties": false, - "properties": { - "Id": { - "type": "string" - }, - "PolicyDocument": { - "type": "object" - }, - "Queues": { - "type": "array", - "uniqueItems": false, - "items": { - "type": "string" - } - } - }, - "required": [ - "PolicyDocument", - "Queues" - ], - "primaryIdentifier": [ - "/properties/Id" - ], - "readOnlyProperties": [ - "/properties/Id" - ] -} diff --git a/localstack-core/localstack/utils/lambda_debug_mode/__init__.py b/localstack-core/localstack/services/sqs/resource_providers/generated/__init__.py similarity index 100% rename from localstack-core/localstack/utils/lambda_debug_mode/__init__.py rename to localstack-core/localstack/services/sqs/resource_providers/generated/__init__.py diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.schema.json b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue.schema.json similarity index 97% rename from localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.schema.json rename to localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue.schema.json index 0756d0bfb2b07..e5d96f59901bb 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.schema.json +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue.schema.json @@ -123,8 +123,13 @@ "taggable": true, "tagOnCreate": true, "tagUpdatable": true, - "cloudFormationSystemTags": true, - "tagProperty": "/properties/Tags" + "cloudFormationSystemTags": false, + "tagProperty": "/properties/Tags", + "permissions": [ + "sqs:TagQueue", + "sqs:UntagQueue", + "sqs:ListQueueTags" + ] }, "handlers": { "create": { diff --git a/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue_base.py b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue_base.py new file mode 100644 index 0000000000000..289d1679531ce --- /dev/null +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue_base.py @@ -0,0 +1,139 @@ +# LocalStack Resource Provider Base Class Scaffolding v2 +# +# AUTOGENERATED FILE - DO NOT EDIT +# + +from __future__ import annotations + +from abc import ABC, abstractmethod +from pathlib import Path +from typing import TypedDict + +import localstack.services.cloudformation.provider_utils as util +from localstack.services.cloudformation.resource_provider import ( + ProgressEvent, + ResourceProvider, + ResourceRequest, +) + + +class SQSQueueProperties(TypedDict): + Arn: str | None + ContentBasedDeduplication: bool | None + DeduplicationScope: str | None + DelaySeconds: int | None + FifoQueue: bool | None + FifoThroughputLimit: str | None + KmsDataKeyReusePeriodSeconds: int | None + KmsMasterKeyId: str | None + MaximumMessageSize: int | None + MessageRetentionPeriod: int | None + QueueName: str | None + QueueUrl: str | None + ReceiveMessageWaitTimeSeconds: int | None + RedriveAllowPolicy: dict | str | None + RedrivePolicy: dict | str | None + SqsManagedSseEnabled: bool | None + Tags: list[Tag] | None + VisibilityTimeout: int | None + + +class Tag(TypedDict): + Key: str | None + Value: str | None + + +REPEATED_INVOCATION = "repeated_invocation" + + +class SQSQueueProviderBase(ResourceProvider[SQSQueueProperties], ABC): + TYPE = "AWS::SQS::Queue" # Autogenerated. Don't change + SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change + + @abstractmethod + def create( + self, + request: ResourceRequest[SQSQueueProperties], + ) -> ProgressEvent[SQSQueueProperties]: + """ + Create a new resource. + + Primary identifier fields: + - /properties/QueueUrl + + + + Create-only properties: + - /properties/FifoQueue + - /properties/QueueName + + Read-only properties: + - /properties/QueueUrl + - /properties/Arn + + IAM permissions required: + - sqs:CreateQueue + - sqs:GetQueueUrl + - sqs:GetQueueAttributes + - sqs:ListQueueTags + - sqs:TagQueue + + """ + raise NotImplementedError + + @abstractmethod + def read( + self, + request: ResourceRequest[SQSQueueProperties], + ) -> ProgressEvent[SQSQueueProperties]: + """ + Fetch resource information + + IAM permissions required: + - sqs:GetQueueAttributes + - sqs:ListQueueTags + """ + raise NotImplementedError + + @abstractmethod + def delete( + self, + request: ResourceRequest[SQSQueueProperties], + ) -> ProgressEvent[SQSQueueProperties]: + """ + Delete a resource + + IAM permissions required: + - sqs:DeleteQueue + - sqs:GetQueueAttributes + """ + raise NotImplementedError + + @abstractmethod + def update( + self, + request: ResourceRequest[SQSQueueProperties], + ) -> ProgressEvent[SQSQueueProperties]: + """ + Update a resource + + IAM permissions required: + - sqs:SetQueueAttributes + - sqs:GetQueueAttributes + - sqs:ListQueueTags + - sqs:TagQueue + - sqs:UntagQueue + """ + raise NotImplementedError + + @abstractmethod + def list( + self, + request: ResourceRequest[SQSQueueProperties], + ) -> ProgressEvent[SQSQueueProperties]: + """ + List available resources of this type + IAM permissions required: + - sqs:ListQueues + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue_plugin.py similarity index 80% rename from localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py rename to localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue_plugin.py index 45c892bc5dade..0da1d17979fdb 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queue_plugin.py @@ -1,4 +1,6 @@ -from typing import Optional, Type +# +# AUTOGENERATED FILE - DO NOT EDIT +# from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, @@ -10,7 +12,7 @@ class SQSQueueProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SQS::Queue" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sqs.resource_providers.aws_sqs_queue import SQSQueueProvider diff --git a/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy.schema.json b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy.schema.json new file mode 100644 index 0000000000000..6093c0943f49f --- /dev/null +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy.schema.json @@ -0,0 +1,60 @@ +{ + "typeName": "AWS::SQS::QueueInlinePolicy", + "description": "Schema for SQS QueueInlinePolicy", + "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-sqs.git", + "properties": { + "PolicyDocument": { + "description": "A policy document that contains permissions to add to the specified SQS queue", + "type": "object" + }, + "Queue": { + "description": "The URL of the SQS queue.", + "type": "string" + } + }, + "additionalProperties": false, + "tagging": { + "taggable": false, + "tagOnCreate": false, + "tagUpdatable": false, + "cloudFormationSystemTags": false + }, + "required": [ + "PolicyDocument", + "Queue" + ], + "primaryIdentifier": [ + "/properties/Queue" + ], + "createOnlyProperties": [ + "/properties/Queue" + ], + "handlers": { + "create": { + "permissions": [ + "sqs:SetQueueAttributes", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + }, + "read": { + "permissions": [ + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + }, + "delete": { + "permissions": [ + "sqs:SetQueueAttributes", + "sqs:GetQueueAttributes" + ] + }, + "update": { + "permissions": [ + "sqs:SetQueueAttributes", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + } + } +} diff --git a/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy_base.py b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy_base.py new file mode 100644 index 0000000000000..f336f341ae1cc --- /dev/null +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy_base.py @@ -0,0 +1,112 @@ +# LocalStack Resource Provider Base Class Scaffolding v2 +# +# AUTOGENERATED FILE - DO NOT EDIT +# + +from __future__ import annotations + +from abc import ABC, abstractmethod +from pathlib import Path +from typing import TypedDict + +import localstack.services.cloudformation.provider_utils as util +from localstack.services.cloudformation.resource_provider import ( + ProgressEvent, + ResourceProvider, + ResourceRequest, +) + + +class SQSQueueInlinePolicyProperties(TypedDict): + PolicyDocument: dict | None + Queue: str | None + + +REPEATED_INVOCATION = "repeated_invocation" + + +class SQSQueueInlinePolicyProviderBase(ResourceProvider[SQSQueueInlinePolicyProperties], ABC): + TYPE = "AWS::SQS::QueueInlinePolicy" # Autogenerated. Don't change + SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change + + @abstractmethod + def create( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + """ + Create a new resource. + + Primary identifier fields: + - /properties/Queue + + Required properties: + - PolicyDocument + - Queue + + Create-only properties: + - /properties/Queue + + + + IAM permissions required: + - sqs:SetQueueAttributes + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + + """ + raise NotImplementedError + + @abstractmethod + def read( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + """ + Fetch resource information + + IAM permissions required: + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + """ + raise NotImplementedError + + @abstractmethod + def delete( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + """ + Delete a resource + + IAM permissions required: + - sqs:SetQueueAttributes + - sqs:GetQueueAttributes + """ + raise NotImplementedError + + @abstractmethod + def update( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + """ + Update a resource + + IAM permissions required: + - sqs:SetQueueAttributes + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + """ + raise NotImplementedError + + @abstractmethod + def list( + self, + request: ResourceRequest[SQSQueueInlinePolicyProperties], + ) -> ProgressEvent[SQSQueueInlinePolicyProperties]: + """ + List available resources of this type + + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy_plugin.py b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy_plugin.py new file mode 100644 index 0000000000000..bcc7af85d1916 --- /dev/null +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queueinlinepolicy_plugin.py @@ -0,0 +1,22 @@ +# +# AUTOGENERATED FILE - DO NOT EDIT +# + +from localstack.services.cloudformation.resource_provider import ( + CloudFormationResourceProviderPlugin, + ResourceProvider, +) + + +class SQSQueueInlinePolicyProviderPlugin(CloudFormationResourceProviderPlugin): + name = "AWS::SQS::QueueInlinePolicy" + + def __init__(self): + self.factory: type[ResourceProvider] | None = None + + def load(self): + from localstack.services.sqs.resource_providers.aws_sqs_queueinlinepolicy import ( + SQSQueueInlinePolicyProvider, + ) + + self.factory = SQSQueueInlinePolicyProvider diff --git a/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy.schema.json b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy.schema.json new file mode 100644 index 0000000000000..5924a0637eef7 --- /dev/null +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy.schema.json @@ -0,0 +1,61 @@ +{ + "typeName": "AWS::SQS::QueuePolicy", + "description": "Resource Type definition for AWS::SQS::QueuePolicy", + "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-sqs.git", + "additionalProperties": false, + "properties": { + "Id": { + "type": "string", + "description": "The provider-assigned unique ID for this managed resource." + }, + "PolicyDocument": { + "type": [ + "object", + "string" + ], + "description": "A policy document that contains the permissions for the specified Amazon SQS queues. For more information about Amazon SQS policies, see Creating Custom Policies Using the Access Policy Language in the Amazon Simple Queue Service Developer Guide." + }, + "Queues": { + "type": "array", + "description": "The URLs of the queues to which you want to add the policy. You can use the Ref function to specify an AWS::SQS::Queue resource.", + "uniqueItems": false, + "insertionOrder": false, + "items": { + "type": "string" + } + } + }, + "required": [ + "PolicyDocument", + "Queues" + ], + "primaryIdentifier": [ + "/properties/Id" + ], + "readOnlyProperties": [ + "/properties/Id" + ], + "tagging": { + "taggable": false, + "tagOnCreate": false, + "tagUpdatable": false, + "cloudFormationSystemTags": false + }, + "handlers": { + "create": { + "permissions": [ + "sqs:SetQueueAttributes" + ] + }, + "update": { + "permissions": [ + "sqs:SetQueueAttributes" + ] + }, + "delete": { + "permissions": [ + "sqs:SetQueueAttributes" + ] + } + } +} diff --git a/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy_base.py b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy_base.py new file mode 100644 index 0000000000000..c057ce766d17d --- /dev/null +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy_base.py @@ -0,0 +1,106 @@ +# LocalStack Resource Provider Base Class Scaffolding v2 +# +# AUTOGENERATED FILE - DO NOT EDIT +# + +from __future__ import annotations + +from abc import ABC, abstractmethod +from pathlib import Path +from typing import TypedDict + +import localstack.services.cloudformation.provider_utils as util +from localstack.services.cloudformation.resource_provider import ( + ProgressEvent, + ResourceProvider, + ResourceRequest, +) + + +class SQSQueuePolicyProperties(TypedDict): + PolicyDocument: dict | str | None + Queues: list[str] | None + Id: str | None + + +REPEATED_INVOCATION = "repeated_invocation" + + +class SQSQueuePolicyProviderBase(ResourceProvider[SQSQueuePolicyProperties], ABC): + TYPE = "AWS::SQS::QueuePolicy" # Autogenerated. Don't change + SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change + + @abstractmethod + def create( + self, + request: ResourceRequest[SQSQueuePolicyProperties], + ) -> ProgressEvent[SQSQueuePolicyProperties]: + """ + Create a new resource. + + Primary identifier fields: + - /properties/Id + + Required properties: + - PolicyDocument + - Queues + + + + Read-only properties: + - /properties/Id + + IAM permissions required: + - sqs:SetQueueAttributes + + """ + raise NotImplementedError + + @abstractmethod + def read( + self, + request: ResourceRequest[SQSQueuePolicyProperties], + ) -> ProgressEvent[SQSQueuePolicyProperties]: + """ + Fetch resource information + + + """ + raise NotImplementedError + + @abstractmethod + def delete( + self, + request: ResourceRequest[SQSQueuePolicyProperties], + ) -> ProgressEvent[SQSQueuePolicyProperties]: + """ + Delete a resource + + IAM permissions required: + - sqs:SetQueueAttributes + """ + raise NotImplementedError + + @abstractmethod + def update( + self, + request: ResourceRequest[SQSQueuePolicyProperties], + ) -> ProgressEvent[SQSQueuePolicyProperties]: + """ + Update a resource + + IAM permissions required: + - sqs:SetQueueAttributes + """ + raise NotImplementedError + + @abstractmethod + def list( + self, + request: ResourceRequest[SQSQueuePolicyProperties], + ) -> ProgressEvent[SQSQueuePolicyProperties]: + """ + List available resources of this type + + """ + raise NotImplementedError diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy_plugin.py similarity index 82% rename from localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py rename to localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy_plugin.py index fc6ce346cf5e3..1b05883131c65 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py +++ b/localstack-core/localstack/services/sqs/resource_providers/generated/aws_sqs_queuepolicy_plugin.py @@ -1,4 +1,6 @@ -from typing import Optional, Type +# +# AUTOGENERATED FILE - DO NOT EDIT +# from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, @@ -10,7 +12,7 @@ class SQSQueuePolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SQS::QueuePolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sqs.resource_providers.aws_sqs_queuepolicy import ( diff --git a/localstack-core/localstack/services/sqs/utils.py b/localstack-core/localstack/services/sqs/utils.py index a280128ad7b66..ff78955f0ce46 100644 --- a/localstack-core/localstack/services/sqs/utils.py +++ b/localstack-core/localstack/services/sqs/utils.py @@ -1,12 +1,20 @@ import base64 +import hashlib import itertools import json import re +import struct import time -from typing import Literal, NamedTuple, Optional, Tuple +from typing import Any, Literal, NamedTuple from urllib.parse import urlparse -from localstack.aws.api.sqs import QueueAttributeName, ReceiptHandleIsInvalid +from localstack.aws.api.sqs import ( + AttributeNameList, + Message, + MessageAttributeNameList, + QueueAttributeName, + ReceiptHandleIsInvalid, +) from localstack.services.sqs.constants import ( DOMAIN_STRATEGY_URL_REGEX, LEGACY_STRATEGY_URL_REGEX, @@ -22,6 +30,11 @@ PATH_ENDPOINT = re.compile(PATH_STRATEGY_URL_REGEX) LEGACY_ENDPOINT = re.compile(LEGACY_STRATEGY_URL_REGEX) +STRING_TYPE_FIELD_INDEX = 1 +BINARY_TYPE_FIELD_INDEX = 2 +STRING_LIST_TYPE_FIELD_INDEX = 3 +BINARY_LIST_TYPE_FIELD_INDEX = 4 + def is_sqs_queue_url(url: str) -> bool: return any( @@ -36,7 +49,7 @@ def is_sqs_queue_url(url: str) -> bool: def guess_endpoint_strategy_and_host( host: str, -) -> Tuple[Literal["standard", "domain", "path"], str]: +) -> tuple[Literal["standard", "domain", "path"], str]: """ This method is used for the dynamic endpoint strategy. It heuristically determines a tuple where the first element is the endpoint strategy, and the second is the part of the host after the endpoint prefix and region. @@ -77,7 +90,7 @@ def is_fifo_queue(queue): return "true" == queue.attributes.get(QueueAttributeName.FifoQueue, "false").lower() -def parse_queue_url(queue_url: str) -> Tuple[str, Optional[str], str]: +def parse_queue_url(queue_url: str) -> tuple[str, str | None, str]: """ Parses an SQS Queue URL and returns a triple of account_id, region and queue_name. @@ -184,3 +197,109 @@ def global_message_sequence(): def generate_message_id(): return long_uid() + + +def message_filter_attributes(message: Message, names: AttributeNameList | None): + """ + Utility function filter from the given message (in-place) the system attributes from the given list. It will + apply all rules according to: + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message. + + :param message: The message to filter (it will be modified) + :param names: the attributes names/filters + """ + if "Attributes" not in message: + return + + if not names: + del message["Attributes"] + return + + if QueueAttributeName.All in names: + return + + for k in list(message["Attributes"].keys()): + if k not in names: + del message["Attributes"][k] + + +def message_filter_message_attributes(message: Message, names: MessageAttributeNameList | None): + """ + Utility function filter from the given message (in-place) the message attributes from the given list. It will + apply all rules according to: + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message. + + :param message: The message to filter (it will be modified) + :param names: the attributes names/filters (can be 'All', '.*', '*' or prefix filters like 'Foo.*') + """ + if not message.get("MessageAttributes"): + return + + if not names: + del message["MessageAttributes"] + return + + if "All" in names or ".*" in names or "*" in names: + return + + attributes = message["MessageAttributes"] + matched = [] + + keys = [name for name in names if ".*" not in name] + prefixes = [name.split(".*")[0] for name in names if ".*" in name] + + # match prefix filters + for k in attributes: + if k in keys: + matched.append(k) + continue + + for prefix in prefixes: + if k.startswith(prefix): + matched.append(k) + break + if matched: + message["MessageAttributes"] = {k: attributes[k] for k in matched} + else: + message.pop("MessageAttributes") + + +def _utf8(value: Any) -> bytes: # type: ignore[misc] + if isinstance(value, str): + return value.encode("utf-8") + return value + + +def _update_binary_length_and_value(md5: Any, value: bytes) -> None: # type: ignore[misc] + length_bytes = struct.pack("!I".encode("ascii"), len(value)) + md5.update(length_bytes) + md5.update(value) + + +def create_message_attribute_hash(message_attributes) -> str | None: + """ + Method from moto's attribute_md5 of moto/sqs/models.py, separated from the Message Object. + """ + # To avoid the need to check for dict conformity everytime we invoke this function + if not isinstance(message_attributes, dict): + return + + hash = hashlib.md5() + + for attrName in sorted(message_attributes.keys()): + attr_value = message_attributes[attrName] + # Encode name + _update_binary_length_and_value(hash, _utf8(attrName)) + # Encode data type + _update_binary_length_and_value(hash, _utf8(attr_value["DataType"])) + # Encode transport type and value + if attr_value.get("StringValue"): + hash.update(bytearray([STRING_TYPE_FIELD_INDEX])) + _update_binary_length_and_value(hash, _utf8(attr_value.get("StringValue"))) + elif attr_value.get("BinaryValue"): + hash.update(bytearray([BINARY_TYPE_FIELD_INDEX])) + decoded_binary_value = attr_value.get("BinaryValue") + _update_binary_length_and_value(hash, decoded_binary_value) + # string_list_value, binary_list_value type is not implemented, reserved for the future use. + # See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html + return hash.hexdigest() diff --git a/localstack-core/localstack/services/ssm/provider.py b/localstack-core/localstack/services/ssm/provider.py index 7787daa091383..6c1d0c7d1a2fa 100644 --- a/localstack-core/localstack/services/ssm/provider.py +++ b/localstack-core/localstack/services/ssm/provider.py @@ -3,7 +3,6 @@ import logging import time from abc import ABC -from typing import Dict, Optional from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api.ssm import ( @@ -79,6 +78,7 @@ ) from localstack.aws.connect import connect_to from localstack.services.moto import call_moto, call_moto_with_request +from localstack.state import StateVisitor from localstack.utils.aws.arns import extract_resource_from_arn, is_arn from localstack.utils.bootstrap import is_api_enabled from localstack.utils.collections import remove_attributes @@ -106,6 +106,11 @@ def __init__(self): # TODO: check if _normalize_name(..) calls are still required here class SsmProvider(SsmApi, ABC): + def accept_state_visitor(self, visitor: StateVisitor): + from moto.ssm.models import ssm_backends + + visitor.visit(ssm_backends) + def get_parameters( self, context: RequestContext, @@ -339,7 +344,7 @@ def describe_maintenance_window_tasks( # utility methods below @staticmethod - def _denormalize_param_name_in_response(param_result: Dict, param_name: str): + def _denormalize_param_name_in_response(param_result: dict, param_name: str): result_name = param_result["Name"] if result_name != param_name and result_name.lstrip("/") == param_name.lstrip("/"): param_result["Name"] = param_name @@ -367,13 +372,13 @@ def _normalize_name(param_name: ParameterName, validate=False) -> ParameterName: param_name = param_name.strip("/") param_name = param_name.replace("//", "/") if "/" in param_name: - param_name = "/%s" % param_name + param_name = f"/{param_name}" return param_name @staticmethod def _get_secrets_information( account_id: str, region_name: str, name: ParameterName, resource_name: str - ) -> Optional[GetParameterResult]: + ) -> GetParameterResult | None: client = connect_to(aws_access_key_id=account_id, region_name=region_name).secretsmanager try: secret_info = client.get_secret_value(SecretId=resource_name) diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py index 974a6b0676242..8200b62c7885e 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,23 +14,23 @@ class SSMMaintenanceWindowProperties(TypedDict): - AllowUnassociatedTargets: Optional[bool] - Cutoff: Optional[int] - Duration: Optional[int] - Name: Optional[str] - Schedule: Optional[str] - Description: Optional[str] - EndDate: Optional[str] - Id: Optional[str] - ScheduleOffset: Optional[int] - ScheduleTimezone: Optional[str] - StartDate: Optional[str] - Tags: Optional[list[Tag]] + AllowUnassociatedTargets: bool | None + Cutoff: int | None + Duration: int | None + Name: str | None + Schedule: str | None + Description: str | None + EndDate: str | None + Id: str | None + ScheduleOffset: int | None + ScheduleTimezone: str | None + StartDate: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py index c7f5ef1c2e50a..8d4fe0b2aa855 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMMaintenanceWindowProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SSM::MaintenanceWindow" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_maintenancewindow import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py index a6f8ef6029dbf..4cef768c14fc8 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class SSMMaintenanceWindowTargetProperties(TypedDict): - ResourceType: Optional[str] - Targets: Optional[list[Targets]] - WindowId: Optional[str] - Description: Optional[str] - Id: Optional[str] - Name: Optional[str] - OwnerInformation: Optional[str] + ResourceType: str | None + Targets: list[Targets] | None + WindowId: str | None + Description: str | None + Id: str | None + Name: str | None + OwnerInformation: str | None class Targets(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py index c16b5208eff20..b3f9bcd876553 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMMaintenanceWindowTargetProviderPlugin(CloudFormationResourceProviderPlu name = "AWS::SSM::MaintenanceWindowTarget" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_maintenancewindowtarget import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py index 01b2f165a9aaa..5f8a33d8c7f78 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,80 +14,80 @@ class SSMMaintenanceWindowTaskProperties(TypedDict): - Priority: Optional[int] - TaskArn: Optional[str] - TaskType: Optional[str] - WindowId: Optional[str] - CutoffBehavior: Optional[str] - Description: Optional[str] - Id: Optional[str] - LoggingInfo: Optional[LoggingInfo] - MaxConcurrency: Optional[str] - MaxErrors: Optional[str] - Name: Optional[str] - ServiceRoleArn: Optional[str] - Targets: Optional[list[Target]] - TaskInvocationParameters: Optional[TaskInvocationParameters] - TaskParameters: Optional[dict] + Priority: int | None + TaskArn: str | None + TaskType: str | None + WindowId: str | None + CutoffBehavior: str | None + Description: str | None + Id: str | None + LoggingInfo: LoggingInfo | None + MaxConcurrency: str | None + MaxErrors: str | None + Name: str | None + ServiceRoleArn: str | None + Targets: list[Target] | None + TaskInvocationParameters: TaskInvocationParameters | None + TaskParameters: dict | None class Target(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class MaintenanceWindowStepFunctionsParameters(TypedDict): - Input: Optional[str] - Name: Optional[str] + Input: str | None + Name: str | None class CloudWatchOutputConfig(TypedDict): - CloudWatchLogGroupName: Optional[str] - CloudWatchOutputEnabled: Optional[bool] + CloudWatchLogGroupName: str | None + CloudWatchOutputEnabled: bool | None class NotificationConfig(TypedDict): - NotificationArn: Optional[str] - NotificationEvents: Optional[list[str]] - NotificationType: Optional[str] + NotificationArn: str | None + NotificationEvents: list[str] | None + NotificationType: str | None class MaintenanceWindowRunCommandParameters(TypedDict): - CloudWatchOutputConfig: Optional[CloudWatchOutputConfig] - Comment: Optional[str] - DocumentHash: Optional[str] - DocumentHashType: Optional[str] - DocumentVersion: Optional[str] - NotificationConfig: Optional[NotificationConfig] - OutputS3BucketName: Optional[str] - OutputS3KeyPrefix: Optional[str] - Parameters: Optional[dict] - ServiceRoleArn: Optional[str] - TimeoutSeconds: Optional[int] + CloudWatchOutputConfig: CloudWatchOutputConfig | None + Comment: str | None + DocumentHash: str | None + DocumentHashType: str | None + DocumentVersion: str | None + NotificationConfig: NotificationConfig | None + OutputS3BucketName: str | None + OutputS3KeyPrefix: str | None + Parameters: dict | None + ServiceRoleArn: str | None + TimeoutSeconds: int | None class MaintenanceWindowLambdaParameters(TypedDict): - ClientContext: Optional[str] - Payload: Optional[str] - Qualifier: Optional[str] + ClientContext: str | None + Payload: str | None + Qualifier: str | None class MaintenanceWindowAutomationParameters(TypedDict): - DocumentVersion: Optional[str] - Parameters: Optional[dict] + DocumentVersion: str | None + Parameters: dict | None class TaskInvocationParameters(TypedDict): - MaintenanceWindowAutomationParameters: Optional[MaintenanceWindowAutomationParameters] - MaintenanceWindowLambdaParameters: Optional[MaintenanceWindowLambdaParameters] - MaintenanceWindowRunCommandParameters: Optional[MaintenanceWindowRunCommandParameters] - MaintenanceWindowStepFunctionsParameters: Optional[MaintenanceWindowStepFunctionsParameters] + MaintenanceWindowAutomationParameters: MaintenanceWindowAutomationParameters | None + MaintenanceWindowLambdaParameters: MaintenanceWindowLambdaParameters | None + MaintenanceWindowRunCommandParameters: MaintenanceWindowRunCommandParameters | None + MaintenanceWindowStepFunctionsParameters: MaintenanceWindowStepFunctionsParameters | None class LoggingInfo(TypedDict): - Region: Optional[str] - S3Bucket: Optional[str] - S3Prefix: Optional[str] + Region: str | None + S3Bucket: str | None + S3Prefix: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py index 494b10f07bd48..906565014d2c9 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMMaintenanceWindowTaskProviderPlugin(CloudFormationResourceProviderPlugi name = "AWS::SSM::MaintenanceWindowTask" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_maintenancewindowtask import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py index 95ea2ecb4d214..e6ee2b967eac6 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,15 +14,15 @@ class SSMParameterProperties(TypedDict): - Type: Optional[str] - Value: Optional[str] - AllowedPattern: Optional[str] - DataType: Optional[str] - Description: Optional[str] - Name: Optional[str] - Policies: Optional[str] - Tags: Optional[dict] - Tier: Optional[str] + Type: str | None + Value: str | None + AllowedPattern: str | None + DataType: str | None + Description: str | None + Name: str | None + Policies: str | None + Tags: dict | None + Tier: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py index e75f657f22100..5cda5c985867a 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMParameterProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SSM::Parameter" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_parameter import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py index 7c3623c981eee..927cbdcbc962d 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,52 +14,52 @@ class SSMPatchBaselineProperties(TypedDict): - Name: Optional[str] - ApprovalRules: Optional[RuleGroup] - ApprovedPatches: Optional[list[str]] - ApprovedPatchesComplianceLevel: Optional[str] - ApprovedPatchesEnableNonSecurity: Optional[bool] - Description: Optional[str] - GlobalFilters: Optional[PatchFilterGroup] - Id: Optional[str] - OperatingSystem: Optional[str] - PatchGroups: Optional[list[str]] - RejectedPatches: Optional[list[str]] - RejectedPatchesAction: Optional[str] - Sources: Optional[list[PatchSource]] - Tags: Optional[list[Tag]] + Name: str | None + ApprovalRules: RuleGroup | None + ApprovedPatches: list[str] | None + ApprovedPatchesComplianceLevel: str | None + ApprovedPatchesEnableNonSecurity: bool | None + Description: str | None + GlobalFilters: PatchFilterGroup | None + Id: str | None + OperatingSystem: str | None + PatchGroups: list[str] | None + RejectedPatches: list[str] | None + RejectedPatchesAction: str | None + Sources: list[PatchSource] | None + Tags: list[Tag] | None class PatchFilter(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class PatchFilterGroup(TypedDict): - PatchFilters: Optional[list[PatchFilter]] + PatchFilters: list[PatchFilter] | None class Rule(TypedDict): - ApproveAfterDays: Optional[int] - ApproveUntilDate: Optional[dict] - ComplianceLevel: Optional[str] - EnableNonSecurity: Optional[bool] - PatchFilterGroup: Optional[PatchFilterGroup] + ApproveAfterDays: int | None + ApproveUntilDate: dict | None + ComplianceLevel: str | None + EnableNonSecurity: bool | None + PatchFilterGroup: PatchFilterGroup | None class RuleGroup(TypedDict): - PatchRules: Optional[list[Rule]] + PatchRules: list[Rule] | None class PatchSource(TypedDict): - Configuration: Optional[str] - Name: Optional[str] - Products: Optional[list[str]] + Configuration: str | None + Name: str | None + Products: list[str] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py index 3991ae2eec102..3282273f9fbaf 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMPatchBaselineProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SSM::PatchBaseline" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_patchbaseline import ( diff --git a/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py b/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py index 61c7d073abb19..2340d3f49aa00 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py +++ b/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py @@ -1,11 +1,10 @@ import ast -from typing import Optional from antlr4 import ParserRuleContext from antlr4.tree.Tree import ParseTree, TerminalNodeImpl -def is_production(pt: ParseTree, rule_index: Optional[int] = None) -> Optional[ParserRuleContext]: +def is_production(pt: ParseTree, rule_index: int | None = None) -> ParserRuleContext | None: if isinstance(pt, ParserRuleContext): prc = pt.getRuleContext() # noqa if rule_index is not None: @@ -14,7 +13,7 @@ def is_production(pt: ParseTree, rule_index: Optional[int] = None) -> Optional[P return None -def is_terminal(pt: ParseTree, token_type: Optional[int] = None) -> Optional[TerminalNodeImpl]: +def is_terminal(pt: ParseTree, token_type: int | None = None) -> TerminalNodeImpl | None: if isinstance(pt, TerminalNodeImpl): if token_type is not None: return pt if pt.getSymbol().type == token_type else None @@ -22,7 +21,7 @@ def is_terminal(pt: ParseTree, token_type: Optional[int] = None) -> Optional[Ter return None -def from_string_literal(parser_rule_context: ParserRuleContext) -> Optional[str]: +def from_string_literal(parser_rule_context: ParserRuleContext) -> str | None: string_literal = parser_rule_context.getText() if string_literal.startswith('"') and string_literal.endswith('"'): string_literal = string_literal[1:-1] diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py index 494fb10db595d..326a3d468d35f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py @@ -15,7 +15,7 @@ def __init__(self, declaration_bindings: list[AssignDeclBinding]): self.declaration_bindings = declaration_bindings def _eval_body(self, env: Environment) -> None: - declarations: dict[str, Any] = dict() + declarations: dict[str, Any] = {} for declaration_binding in self.declaration_bindings: declaration_binding.eval(env=env) binding: dict[str, Any] = env.stack.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py index 8695bfea82678..cbe48103785ba 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py @@ -15,5 +15,5 @@ def __init__(self, binding: AssignTemplateBinding): self.binding = binding def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) self.binding.eval(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py index b2ff0a71ec733..097fa3403fb92 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py @@ -13,7 +13,7 @@ def __init__(self, values: list[AssignTemplateValue]): self.values = values def _eval_body(self, env: Environment) -> None: - arr = list() + arr = [] for value in self.values: value.eval(env) arr.append(env.stack.pop()) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py index 2b4c451595e9b..c4644cdaeb146 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py @@ -16,6 +16,6 @@ def __init__(self, bindings: list[AssignTemplateBinding]): self.bindings = bindings def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) for binding in self.bindings: binding.eval(env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py index 44705370da1cd..872f14c17c707 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.assign.assign_decl import AssignDecl from localstack.services.stepfunctions.asl.component.common.catch.catcher_outcome import ( @@ -34,19 +34,19 @@ class CatcherDecl(EvalComponent): error_equals: Final[ErrorEqualsDecl] next_decl: Final[Next] - result_path: Final[Optional[ResultPath]] - assign: Final[Optional[AssignDecl]] - output: Final[Optional[Output]] - comment: Final[Optional[Comment]] + result_path: Final[ResultPath | None] + assign: Final[AssignDecl | None] + output: Final[Output | None] + comment: Final[Comment | None] def __init__( self, error_equals: ErrorEqualsDecl, next_decl: Next, - result_path: Optional[ResultPath], - assign: Optional[AssignDecl], - output: Optional[Output], - comment: Optional[Comment], + result_path: ResultPath | None, + assign: AssignDecl | None, + output: Output | None, + comment: Comment | None, ): self.error_equals = error_equals self.next_decl = next_decl diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py index 6d4ed3954ad1f..14155be70b7e9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.error_name.error_name import ErrorName @@ -10,7 +10,7 @@ class CustomErrorName(ErrorName): States MAY report errors with other names, which MUST NOT begin with the prefix "States.". """ - def __init__(self, error_name: Optional[str]): + def __init__(self, error_name: str | None): if error_name is not None and error_name.startswith(ILLEGAL_CUSTOM_ERROR_PREFIX): raise ValueError( f"Custom Error Names MUST NOT begin with the prefix 'States.', got '{error_name}'." diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py index 50e09e290aa4f..443c1daff7f54 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py @@ -1,18 +1,18 @@ from __future__ import annotations import abc -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.component import Component class ErrorName(Component, abc.ABC): - error_name: Final[Optional[str]] + error_name: Final[str | None] - def __init__(self, error_name: Optional[str]): + def __init__(self, error_name: str | None): self.error_name = error_name - def matches(self, error_name: Optional[str]) -> bool: + def matches(self, error_name: str | None) -> bool: return self.error_name == error_name def __eq__(self, other): diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py index 4624ea025395b..2320cd5ad9f23 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( EvaluationFailedEventDetails, @@ -16,16 +16,16 @@ class FailureEvent: state_name: Final[str] source_event_id: Final[int] - error_name: Final[Optional[ErrorName]] + error_name: Final[ErrorName | None] event_type: Final[HistoryEventType] - event_details: Final[Optional[EventDetails]] + event_details: Final[EventDetails | None] def __init__( self, env: Environment, - error_name: Optional[ErrorName], + error_name: ErrorName | None, event_type: HistoryEventType, - event_details: Optional[EventDetails] = None, + event_details: EventDetails | None = None, ): self.state_name = env.next_state_name self.source_event_id = env.event_history_context.source_event_id @@ -40,7 +40,7 @@ class FailureEventException(Exception): def __init__(self, failure_event: FailureEvent): self.failure_event = failure_event - def extract_error_cause_pair(self) -> Optional[tuple[Optional[str], Optional[str]]]: + def extract_error_cause_pair(self) -> tuple[str | None, str | None] | None: if self.failure_event.event_details is None: return None @@ -54,7 +54,7 @@ def extract_error_cause_pair(self) -> Optional[tuple[Optional[str], Optional[str cause = failure_event_spec["cause"] return error, cause - def get_evaluation_failed_event_details(self) -> Optional[EvaluationFailedEventDetails]: + def get_evaluation_failed_event_details(self) -> EvaluationFailedEventDetails | None: original_failed_event_details = self.failure_event.event_details[ "evaluationFailedEventDetails" ] @@ -80,7 +80,7 @@ def get_evaluation_failed_event_details(self) -> Optional[EvaluationFailedEventD return evaluation_failed_event_details - def get_execution_failed_event_details(self) -> Optional[ExecutionFailedEventDetails]: + def get_execution_failed_event_details(self) -> ExecutionFailedEventDetails | None: maybe_error_cause_pair = self.extract_error_cause_pair() if maybe_error_cause_pair is None: return None diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py index 9dcda9350ffcd..8cc6cff7c67d8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py @@ -40,7 +40,7 @@ def _error_name(error_name: StatesErrorNameType) -> str: def _reverse_error_name_lookup() -> dict[str, StatesErrorNameType]: - lookup: dict[str, StatesErrorNameType] = dict() + lookup: dict[str, StatesErrorNameType] = {} for error_name in StatesErrorNameType: error_text: str = _error_name(error_name) lookup[error_text] = error_name diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py index 3833f14c0abdc..dbdc0f581ec74 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.jsonata.jsonata_template_value import ( JSONataTemplateValue, @@ -17,7 +17,7 @@ def __init__(self, identifier: str, value: JSONataTemplateValue): self.identifier = identifier self.value = value - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return self.identifier def _eval_body(self, env: Environment) -> None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py index 552b168299e2a..38e01dda43dde 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py @@ -13,7 +13,7 @@ def __init__(self, values: list[JSONataTemplateValue]): self.values = values def _eval_body(self, env: Environment) -> None: - arr = list() + arr = [] for value in self.values: value.eval(env) arr.append(env.stack.pop()) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py index 81b1c19a00c53..e3e1a09b5847c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py @@ -16,6 +16,6 @@ def __init__(self, bindings: list[JSONataTemplateBinding]): self.bindings = bindings def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) for binding in self.bindings: binding.eval(env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py index 8c0d4e6cbb4e7..4adc2c01254d2 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( @@ -22,14 +22,14 @@ class InputPath(EvalComponent): - string_sampler: Final[Optional[StringSampler]] + string_sampler: Final[StringSampler | None] - def __init__(self, string_sampler: Optional[StringSampler]): + def __init__(self, string_sampler: StringSampler | None): self.string_sampler = string_sampler def _eval_body(self, env: Environment) -> None: if self.string_sampler is None: - env.stack.append(dict()) + env.stack.append({}) return if isinstance(self.string_sampler, StringJsonPath): # JsonPaths are sampled from a given state, hence pass the state's input. diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py index b40586aa8e716..f9d853e1f26d5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( @@ -21,14 +21,14 @@ class OutputPath(EvalComponent): - string_sampler: Final[Optional[StringSampler]] + string_sampler: Final[StringSampler | None] - def __init__(self, string_sampler: Optional[StringSampler]): + def __init__(self, string_sampler: StringSampler | None): self.string_sampler = string_sampler def _eval_body(self, env: Environment) -> None: if self.string_sampler is None: - env.states.reset(input_value=dict()) + env.states.reset(input_value={}) return try: self.string_sampler.eval(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py index bfcb3f2cfe91d..f08fdf1637fd1 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py @@ -1,5 +1,5 @@ import copy -from typing import Final, Optional +from typing import Final from jsonpath_ng import parse @@ -10,15 +10,15 @@ class ResultPath(EvalComponent): DEFAULT_PATH: Final[str] = "$" - result_path_src: Final[Optional[str]] + result_path_src: Final[str | None] - def __init__(self, result_path_src: Optional[str]): + def __init__(self, result_path_src: str | None): self.result_path_src = result_path_src def _eval_body(self, env: Environment) -> None: state_input = env.states.get_input() - # Discard task output if there is one, and set the output ot be the state's input. + # Discard task output if there is one, and set the output to be the state's input. if self.result_path_src is None: env.stack.clear() env.stack.append(state_input) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py index 3c9f7a3bdf9b0..86d78d4697e2b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py @@ -11,7 +11,7 @@ def __init__(self, payload_values: list[PayloadValue]): self.payload_values: Final[list[PayloadValue]] = payload_values def _eval_body(self, env: Environment) -> None: - arr = list() + arr = [] for payload_value in self.payload_values: payload_value.eval(env) arr.append(env.stack.pop()) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py index 1b7d7fb527634..daef4e7bc9605 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py @@ -1,5 +1,5 @@ import abc -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.common.payload.payloadvalue.payload_value import ( PayloadValue, @@ -16,7 +16,7 @@ class PayloadBinding(PayloadValue, abc.ABC): def __init__(self, field: str): self.field = field - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return self.field @abc.abstractmethod @@ -36,7 +36,7 @@ def __init__(self, field: str, string_expression_simple: StringExpressionSimple) super().__init__(field=field) self.string_expression_simple = string_expression_simple - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return f"{self.field}.$" def _eval_val(self, env: Environment) -> Any: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py index dce0cc10b2cc1..0279a85ea5cb4 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py @@ -14,6 +14,6 @@ def __init__(self, payload_bindings: list[PayloadBinding]): self.payload_bindings: Final[list[PayloadBinding]] = payload_bindings def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) for payload_binding in self.payload_bindings: payload_binding.eval(env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py index 108a4f97790e5..f4871cbd3a67a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py @@ -1,7 +1,7 @@ from __future__ import annotations import time -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.error_name.error_equals_decl import ( @@ -38,17 +38,17 @@ class RetrierDecl(EvalComponent): backoff_rate: Final[BackoffRateDecl] max_delay_seconds: Final[MaxDelaySecondsDecl] jitter_strategy: Final[JitterStrategyDecl] - comment: Final[Optional[Comment]] + comment: Final[Comment | None] def __init__( self, error_equals: ErrorEqualsDecl, - interval_seconds: Optional[IntervalSecondsDecl] = None, - max_attempts: Optional[MaxAttemptsDecl] = None, - backoff_rate: Optional[BackoffRateDecl] = None, - max_delay_seconds: Optional[MaxDelaySecondsDecl] = None, - jitter_strategy: Optional[JitterStrategyDecl] = None, - comment: Optional[Comment] = None, + interval_seconds: IntervalSecondsDecl | None = None, + max_attempts: MaxAttemptsDecl | None = None, + backoff_rate: BackoffRateDecl | None = None, + max_delay_seconds: MaxDelaySecondsDecl | None = None, + jitter_strategy: JitterStrategyDecl | None = None, + comment: Comment | None = None, ): self.error_equals = error_equals self.interval_seconds = interval_seconds or IntervalSecondsDecl() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py index 3f4be28c7e14c..c784e9e48f45a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py @@ -1,6 +1,6 @@ import abc import copy -from typing import Any, Final, Optional +from typing import Any, Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.events.utils import to_json_str @@ -46,7 +46,7 @@ class StringExpression(EvalComponent, abc.ABC): def __init__(self, literal_value: str): self.literal_value = literal_value - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return None @@ -129,7 +129,7 @@ def _eval_body(self, env: Environment) -> None: expression_variable_references: set[VariableReference] = ( extract_jsonata_variable_references(self.expression) ) - variable_declarations_list = list() + variable_declarations_list = [] if self.query_language_mode == QueryLanguageMode.JSONata: # Sample $states values into expression. states_variable_declarations: VariableDeclarations = ( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py index 03ae1a6ba2e33..fb90f963e4ad8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py @@ -1,5 +1,5 @@ import abc -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( ExecutionFailedEventDetails, @@ -44,13 +44,13 @@ def _eval_body(self, env: Environment) -> None: class TimeoutSeconds(Timeout): DEFAULT_TIMEOUT_SECONDS: Final[int] = 99999999 - def __init__(self, timeout_seconds: int, is_default: Optional[bool] = None): + def __init__(self, timeout_seconds: int, is_default: bool | None = None): if not isinstance(timeout_seconds, int) and timeout_seconds <= 0: raise ValueError( f"Expected non-negative integer for TimeoutSeconds, got '{timeout_seconds}' instead." ) self.timeout_seconds: Final[int] = timeout_seconds - self.is_default: Optional[bool] = is_default + self.is_default: bool | None = is_default def is_default_value(self) -> bool: if self.is_default is not None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py index cd7940208f5cc..3db4e599e3273 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py @@ -1,6 +1,5 @@ import abc import logging -from typing import Optional from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( FailureEventException, @@ -14,7 +13,7 @@ class EvalComponent(Component, abc.ABC): - __heap_key: Optional[str] = None + __heap_key: str | None = None @property def heap_key(self) -> str: @@ -82,5 +81,5 @@ def eval(self, env: Environment) -> None: def _eval_body(self, env: Environment) -> None: raise NotImplementedError() - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return self.__class__.__name__ diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py index 6438471c8becb..351331f8b41db 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py @@ -1,5 +1,5 @@ import abc -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.common.string.string_expression import ( StringVariableSample, @@ -32,9 +32,9 @@ def _eval_body(self, env: Environment) -> None: class ArgumentLiteral(Argument): - definition_value: Final[Optional[Any]] + definition_value: Final[Any | None] - def __init__(self, definition_value: Optional[Any]): + def __init__(self, definition_value: Any | None): self.definition_value = definition_value def _eval_argument(self, env: Environment) -> Any: @@ -97,7 +97,7 @@ def __init__(self, arguments: list[Argument]): self.size = len(arguments) def _eval_argument(self, env: Environment) -> Any: - values = list() + values = [] for argument in self.arguments: argument.eval(env=env) argument_value = env.stack.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py index a12b2780c0faf..b718988e0fcd9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py @@ -60,7 +60,7 @@ def _eval_body(self, env: Environment) -> None: @staticmethod def _to_chunks(array: list, chunk_size: int): - chunks = list() + chunks = [] for i in range(0, len(array), chunk_size): chunks.append(array[i : i + chunk_size]) return chunks diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py index 86e8b50050518..e446a44dc84ee 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py @@ -95,7 +95,7 @@ def _to_str_repr(value: Any) -> str: value_parts: list[str] = list(map(StringFormat._to_str_repr, value)) return f"[{', '.join(value_parts)}]" elif isinstance(value, dict): - dict_items = list() + dict_items = [] for d_key, d_value in value.items(): d_value_lit = StringFormat._to_str_repr(d_value) dict_items.append(f"{d_key}={d_value_lit}") diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py index 5cce091f0fd85..50d98145dece8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py @@ -24,7 +24,7 @@ def __init__(self, argument_list: ArgumentList): def _eval_body(self, env: Environment) -> None: self.argument_list.eval(env=env) - values: list[Any] = list() + values: list[Any] = [] for _ in range(self.argument_list.size): values.append(env.stack.pop()) values.reverse() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py index 8b71a07fbd122..9ea03ed3c7437 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py @@ -41,7 +41,7 @@ def _eval_body(self, env: Environment) -> None: # TODO: investigate behaviour for incorrect number of arguments in string format. self.argument_list.eval(env=env) - values: list[Any] = list() + values: list[Any] = [] for _ in range(self.argument_list.size): values.append(env.stack.pop()) string_format: str = values.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py index 8602aed713e63..435dd446a98c8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.jsonata.jsonata import ( VariableDeclarations, @@ -75,9 +75,9 @@ def get_intrinsic_functions_declarations( variable_references: set[VariableReference], ) -> VariableDeclarations: - declarations: list[str] = list() + declarations: list[str] = [] for variable_reference in variable_references: - declaration: Optional[VariableDeclarations] = _DECLARATION_BY_VARIABLE_REFERENCE.get( + declaration: VariableDeclarations | None = _DECLARATION_BY_VARIABLE_REFERENCE.get( variable_reference ) if declaration: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/member.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/member.py index 2e66657a2b59f..d5d8c2f3062f1 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/member.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/member.py @@ -13,4 +13,4 @@ def __init__(self, identifier: str): class DollarMember(IdentifiedMember): def __init__(self): - super(DollarMember, self).__init__(identifier="$") + super().__init__(identifier="$") diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py b/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py index e86a5cd076620..330f2a3e599b9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py @@ -1,6 +1,6 @@ import logging import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( ExecutionAbortedEventDetails, @@ -47,18 +47,18 @@ class Program(EvalComponent): query_language: Final[QueryLanguage] start_at: Final[StartAt] states: Final[States] - timeout_seconds: Final[Optional[TimeoutSeconds]] - comment: Final[Optional[Comment]] - version: Final[Optional[Version]] + timeout_seconds: Final[TimeoutSeconds | None] + comment: Final[Comment | None] + version: Final[Version | None] def __init__( self, query_language: QueryLanguage, start_at: StartAt, states: States, - timeout_seconds: Optional[TimeoutSeconds], - comment: Optional[Comment] = None, - version: Optional[Version] = None, + timeout_seconds: TimeoutSeconds | None, + comment: Comment | None = None, + version: Version | None = None, ): self.query_language = query_language self.start_at = start_at @@ -68,7 +68,7 @@ def __init__( self.version = version def _get_state(self, state_name: str) -> CommonStateField: - state: Optional[CommonStateField] = self.states.states.get(state_name, None) + state: CommonStateField | None = self.states.states.get(state_name, None) if state is None: raise ValueError(f"No such state {state}.") return state @@ -111,7 +111,7 @@ def _eval_body(self, env: Environment) -> None: program_state: ProgramState = env.program_state() if isinstance(program_state, ProgramError): exec_failed_event_details = select_from_typed_dict( - typed_dict=ExecutionFailedEventDetails, obj=program_state.error or dict() + typed_dict=ExecutionFailedEventDetails, obj=program_state.error or {} ) env.event_manager.add_event( context=env.event_history_context, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py b/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py index 61fa0a8c3f757..fcda4338e4763 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py @@ -4,4 +4,4 @@ class States(Component): def __init__(self): - self.states: dict[str, CommonStateField] = dict() + self.states: dict[str, CommonStateField] = {} diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py index 7e7004b27e31d..3c63a235aef63 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py @@ -2,10 +2,9 @@ import abc import datetime -import json import logging from abc import ABC -from typing import Final, Optional, Union +from typing import Any, Final from localstack.aws.api.stepfunctions import ( ExecutionFailedEventDetails, @@ -74,27 +73,27 @@ class CommonStateField(EvalComponent, ABC): continue_with: ContinueWith # Holds a human-readable description of the state. - comment: Optional[Comment] + comment: Comment | None # A path that selects a portion of the state's input to be passed to the state's state_task for processing. # If omitted, it has the value $ which designates the entire input. - input_path: Optional[InputPath] + input_path: InputPath | None # A path that selects a portion of the state's output to be passed to the next state. # If omitted, it has the value $ which designates the entire output. - output_path: Optional[OutputPath] + output_path: OutputPath | None - assign_decl: Optional[AssignDecl] + assign_decl: AssignDecl | None - output: Optional[Output] + output: Output | None state_entered_event_type: Final[HistoryEventType] - state_exited_event_type: Final[Optional[HistoryEventType]] + state_exited_event_type: Final[HistoryEventType | None] def __init__( self, state_entered_event_type: HistoryEventType, - state_exited_event_type: Optional[HistoryEventType], + state_exited_event_type: HistoryEventType | None, ): self.state_entered_event_type = state_entered_event_type self.state_exited_event_type = state_exited_event_type @@ -135,7 +134,7 @@ def _set_next(self, env: Environment) -> None: else: LOG.error("Could not handle ContinueWith type of '%s'.", type(self.continue_with)) - def _is_language_query_jsonpath(self) -> bool: + def is_jsonpath_query_language(self) -> bool: return self.query_language.query_language_mode == QueryLanguageMode.JSONPath def _get_state_entered_event_details(self, env: Environment) -> StateEnteredEventDetails: @@ -163,7 +162,7 @@ def _get_state_exited_event_details(self, env: Environment) -> StateExitedEventD event_details["assignedVariablesDetails"] = {"truncated": False} # noqa return event_details - def _verify_size_quota(self, env: Environment, value: Union[str, json]) -> None: + def _verify_size_quota(self, env: Environment, value: str | Any) -> None: is_within: bool = is_within_size_quota(value) if is_within: return @@ -215,7 +214,7 @@ def _eval_body(self, env: Environment) -> None: ), ) env.states.context_object.context_object_data["State"] = StateData( - EnteredTime=datetime.datetime.now(tz=datetime.timezone.utc).isoformat(), Name=self.name + EnteredTime=datetime.datetime.now(tz=datetime.UTC).isoformat(), Name=self.name ) self._eval_state_input(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py index a946eec561292..bf12b6c628174 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.assign.assign_decl import AssignDecl from localstack.services.stepfunctions.asl.component.common.comment import Comment @@ -12,19 +12,19 @@ class ChoiceRule(EvalComponent): - comparison: Final[Optional[Comparison]] - next_stmt: Final[Optional[Next]] - comment: Final[Optional[Comment]] - assign: Final[Optional[AssignDecl]] - output: Final[Optional[Output]] + comparison: Final[Comparison | None] + next_stmt: Final[Next | None] + comment: Final[Comment | None] + assign: Final[AssignDecl | None] + output: Final[Output | None] def __init__( self, - comparison: Optional[Comparison], - next_stmt: Optional[Next], - comment: Optional[Comment], - assign: Optional[AssignDecl], - output: Optional[Output], + comparison: Comparison | None, + next_stmt: Next | None, + comment: Comment | None, + assign: AssignDecl | None, + output: Output | None, ): self.comparison = comparison self.next_stmt = next_stmt diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py index d70065dc56a92..ed7190548ecf7 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py @@ -72,7 +72,7 @@ class ComparisonCompositeSingle(ComparisonComposite, abc.ABC): rule: Final[ChoiceRule] def __init__(self, operator: ComparisonComposite.ChoiceOp, rule: ChoiceRule): - super(ComparisonCompositeSingle, self).__init__(operator=operator) + super().__init__(operator=operator) self.rule = rule @@ -80,15 +80,13 @@ class ComparisonCompositeMulti(ComparisonComposite, abc.ABC): rules: Final[list[ChoiceRule]] def __init__(self, operator: ComparisonComposite.ChoiceOp, rules: list[ChoiceRule]): - super(ComparisonCompositeMulti, self).__init__(operator=operator) + super().__init__(operator=operator) self.rules = rules class ComparisonCompositeNot(ComparisonCompositeSingle): def __init__(self, rule: ChoiceRule): - super(ComparisonCompositeNot, self).__init__( - operator=ComparisonComposite.ChoiceOp.Not, rule=rule - ) + super().__init__(operator=ComparisonComposite.ChoiceOp.Not, rule=rule) def _eval_body(self, env: Environment) -> None: self.rule.eval(env) @@ -99,9 +97,7 @@ def _eval_body(self, env: Environment) -> None: class ComparisonCompositeAnd(ComparisonCompositeMulti): def __init__(self, rules: list[ChoiceRule]): - super(ComparisonCompositeAnd, self).__init__( - operator=ComparisonComposite.ChoiceOp.And, rules=rules - ) + super().__init__(operator=ComparisonComposite.ChoiceOp.And, rules=rules) def _eval_body(self, env: Environment) -> None: res = True @@ -116,9 +112,7 @@ def _eval_body(self, env: Environment) -> None: class ComparisonCompositeOr(ComparisonCompositeMulti): def __init__(self, rules: list[ChoiceRule]): - super(ComparisonCompositeOr, self).__init__( - operator=ComparisonComposite.ChoiceOp.Or, rules=rules - ) + super().__init__(operator=ComparisonComposite.ChoiceOp.Or, rules=rules) def _eval_body(self, env: Environment) -> None: res = False diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py index e998ae1a50a0c..33885eda4ea8e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py @@ -1,6 +1,6 @@ import datetime import logging -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_operator_type import ( ComparisonOperatorType, @@ -88,7 +88,7 @@ def impl_name() -> str: return str(ComparisonOperatorType.IsTimestamp) @staticmethod - def string_to_timestamp(string: str) -> Optional[datetime.datetime]: + def string_to_timestamp(string: str) -> datetime.datetime | None: try: return datetime.datetime.strptime(string, IsTimestamp.TIMESTAMP_FORMAT) except Exception: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py index 99d21029a3fc3..2f4bd50b26eac 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.aws.api.stepfunctions import HistoryEventType from localstack.services.stepfunctions.asl.component.common.flow.end import End from localstack.services.stepfunctions.asl.component.common.flow.next import Next @@ -16,10 +14,10 @@ class StateChoice(CommonStateField): choices_decl: ChoicesDecl - default_state: Optional[DefaultDecl] + default_state: DefaultDecl | None def __init__(self): - super(StateChoice, self).__init__( + super().__init__( state_entered_event_type=HistoryEventType.ChoiceStateEntered, state_exited_event_type=HistoryEventType.ChoiceStateExited, ) @@ -27,7 +25,7 @@ def __init__(self): self._next_state_name = None def from_state_props(self, state_props: StateProps) -> None: - super(StateChoice, self).from_state_props(state_props) + super().from_state_props(state_props) self.choices_decl = state_props.get(ChoicesDecl) self.default_state = state_props.get(DefaultDecl) @@ -68,7 +66,7 @@ def _eval_state_output(self, env: Environment) -> None: self.output.eval(env=env) # Handle legacy output sequences if in JsonPath mode. - if self._is_language_query_jsonpath(): + if self.is_jsonpath_query_language(): if self.output_path: self.output_path.eval(env=env) else: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py index c32150cb3eb12..ee8025370db6d 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py @@ -3,7 +3,7 @@ import logging import threading from threading import Thread -from typing import Any, Optional +from typing import Any from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.catch.catch_decl import CatchDecl @@ -44,7 +44,7 @@ class ExecutionState(CommonStateField, abc.ABC): def __init__( self, state_entered_event_type: HistoryEventType, - state_exited_event_type: Optional[HistoryEventType], + state_exited_event_type: HistoryEventType | None, ): super().__init__( state_entered_event_type=state_entered_event_type, @@ -54,20 +54,20 @@ def __init__( # Specifies where (in the input) to place the results of executing the state_task that's specified in Resource. # The input is then filtered as specified by the OutputPath field (if present) before being used as the # state's output. - self.result_path: Optional[ResultPath] = None + self.result_path: ResultPath | None = None # ResultSelector (Optional) # Pass a collection of key value pairs, where the values are static or selected from the result. - self.result_selector: Optional[ResultSelector] = None + self.result_selector: ResultSelector | None = None # Retry (Optional) # An array of objects, called Retriers, that define a retry policy if the state encounters runtime errors. - self.retry: Optional[RetryDecl] = None + self.retry: RetryDecl | None = None # Catch (Optional) # An array of objects, called Catchers, that define a fallback state. This state is executed if the state # encounters runtime errors and its retry policy is exhausted or isn't defined. - self.catch: Optional[CatchDecl] = None + self.catch: CatchDecl | None = None # TimeoutSeconds (Optional) # If the state_task runs longer than the specified seconds, this state fails with a States.Timeout error name. @@ -93,7 +93,7 @@ def __init__( # HeartbeatSecondsPath. When resolved, the reference path must select fields whose values are positive integers. # A Task state cannot include both HeartbeatSeconds and HeartbeatSecondsPath # HeartbeatSeconds and HeartbeatSecondsPath fields are encoded by the Heartbeat type. - self.heartbeat: Optional[Heartbeat] = None + self.heartbeat: Heartbeat | None = None def from_state_props(self, state_props: StateProps) -> None: super().from_state_props(state_props=state_props) @@ -170,8 +170,8 @@ def _evaluate_with_timeout(self, env: Environment) -> None: frame: Environment = env.open_frame() frame.states.reset(input_value=env.states.get_input()) frame.stack = copy.deepcopy(env.stack) - execution_outputs: list[Any] = list() - execution_exceptions: list[Optional[Exception]] = [None] + execution_outputs: list[Any] = [] + execution_exceptions: list[Exception | None] = [None] terminated_event = threading.Event() def _exec_and_notify(): @@ -200,7 +200,7 @@ def _exec_and_notify(): execution_output = execution_outputs.pop() env.stack.append(execution_output) - if not self._is_language_query_jsonpath(): + if not self.is_jsonpath_query_language(): env.states.set_result(execution_output) if self.assign_decl: @@ -251,7 +251,6 @@ def _eval_state(self, env: Environment) -> None: ) error_output = self._construct_error_output_value(failure_event=failure_event) env.states.set_error_output(error_output) - env.states.set_result(error_output) if self.retry: retry_outcome: RetryOutcome = self._handle_retry( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py index ed8e325034c56..a52420acdb6de 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py @@ -1,5 +1,5 @@ import copy -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.parargs import Parargs from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent @@ -26,15 +26,15 @@ class ItemReader(EvalComponent): resource_eval: Final[ResourceEval] - parargs: Final[Optional[Parargs]] - reader_config: Final[Optional[ReaderConfig]] - resource_output_transformer: Optional[ResourceOutputTransformer] + parargs: Final[Parargs | None] + reader_config: Final[ReaderConfig | None] + resource_output_transformer: ResourceOutputTransformer | None def __init__( self, resource: Resource, - parargs: Optional[Parargs], - reader_config: Optional[ReaderConfig], + parargs: Parargs | None, + reader_config: ReaderConfig | None, ): self.resource_eval = resource_eval_for(resource=resource) self.parargs = parargs @@ -65,7 +65,7 @@ def _eval_body(self, env: Environment) -> None: if self.parargs: self.parargs.eval(env=env) else: - env.stack.append(dict()) + env.stack.append({}) self.resource_eval.eval_resource(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py index fff888b474b5a..58fbbc390bd96 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py @@ -1,4 +1,4 @@ -from typing import Final, Optional, TypedDict +from typing import Final, TypedDict from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_reader.reader_config.csv_header_location import ( @@ -34,7 +34,7 @@ class CSVHeaderLocationOutput(str): class ReaderConfigOutput(TypedDict): InputType: InputTypeOutput CSVHeaderLocation: CSVHeaderLocationOutput - CSVHeaders: Optional[CSVHeadersOutput] + CSVHeaders: CSVHeadersOutput | None MaxItemsValue: MaxItemsValueOutput @@ -42,14 +42,14 @@ class ReaderConfig(EvalComponent): input_type: Final[InputType] max_items_decl: Final[MaxItemsDecl] csv_header_location: Final[CSVHeaderLocation] - csv_headers: Optional[CSVHeaders] + csv_headers: CSVHeaders | None def __init__( self, input_type: InputType, csv_header_location: CSVHeaderLocation, - csv_headers: Optional[CSVHeaders], - max_items_decl: Optional[MaxItemsDecl], + csv_headers: CSVHeaders | None, + max_items_decl: MaxItemsDecl | None, ): self.input_type = input_type self.max_items_decl = max_items_decl or MaxItemsInt() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py index 262c4f00ca540..d37ca8bbaeb7c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Callable, Final +from collections.abc import Callable +from typing import Final from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_reader.resource_eval.resource_eval import ( ResourceEval, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py index 065aacdbc56fc..a67b7489bd362 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py @@ -59,9 +59,9 @@ def _eval_body(self, env: Environment) -> None: ) raise FailureEventException(failure_event=failure_event) - transformed_outputs = list() + transformed_outputs = [] for row in csv_reader_slice: - transformed_output = dict() + transformed_output = {} for i, header in enumerate(headers): transformed_output[header] = row[i] if i < len(row) else "" transformed_outputs.append( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py index 841a9db4f453a..78db0aa23a15a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py @@ -2,7 +2,7 @@ import abc import json -from typing import Any, Final, Optional +from typing import Any, Final from localstack.aws.api.stepfunctions import ( HistoryEventType, @@ -50,8 +50,8 @@ class DistributedIterationComponentEvalInput(InlineIterationComponentEvalInput): - item_reader: Final[Optional[ItemReader]] - label: Final[Optional[str]] + item_reader: Final[ItemReader | None] + label: Final[str | None] map_run_record: Final[MapRunRecord] def __init__( @@ -59,12 +59,12 @@ def __init__( state_name: str, max_concurrency: int, input_items: list[json], - parameters: Optional[Parameters], - item_selector: Optional[ItemSelector], - item_reader: Optional[ItemReader], + parameters: Parameters | None, + item_selector: ItemSelector | None, + item_reader: ItemReader | None, tolerated_failure_count: int, tolerated_failure_percentage: float, - label: Optional[str], + label: str | None, map_run_record: MapRunRecord, ): super().__init__( @@ -118,7 +118,7 @@ def _map_run( job_pool.await_jobs() - worker_exception: Optional[Exception] = job_pool.get_worker_exception() + worker_exception: Exception | None = job_pool.get_worker_exception() if worker_exception is not None: raise worker_exception diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py index 3eb020678142c..88cb25730ae4e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py @@ -3,7 +3,7 @@ import abc import json import threading -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.flow.start_at import StartAt @@ -38,16 +38,16 @@ class InlineIterationComponentEvalInput: state_name: Final[str] max_concurrency: Final[int] input_items: Final[list[json]] - parameters: Final[Optional[Parameters]] - item_selector: Final[Optional[ItemSelector]] + parameters: Final[Parameters | None] + item_selector: Final[ItemSelector | None] def __init__( self, state_name: str, max_concurrency: int, input_items: list[json], - parameters: Optional[Parameters], - item_selector: Optional[ItemSelector], + parameters: Parameters | None, + item_selector: ItemSelector | None, ): self.state_name = state_name self.max_concurrency = max_concurrency @@ -65,7 +65,7 @@ def __init__( start_at: StartAt, states: States, processor_config: ProcessorConfig, - comment: Optional[Comment], + comment: Comment | None, ): super().__init__( query_language=query_language, start_at=start_at, states=states, comment=comment @@ -105,7 +105,7 @@ def _eval_body(self, env: Environment) -> None: job_pool.await_jobs() - worker_exception: Optional[Exception] = job_pool.get_worker_exception() + worker_exception: Exception | None = job_pool.get_worker_exception() if worker_exception is not None: raise worker_exception diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py index bde4c49bdf073..1034d4c344fa5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py @@ -1,5 +1,5 @@ import logging -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( FailureEventException, @@ -44,8 +44,8 @@ def __init__( job_pool: JobPool, env: Environment, item_reader: ItemReader, - parameters: Optional[Parameters], - item_selector: Optional[ItemSelector], + parameters: Parameters | None, + item_selector: ItemSelector | None, map_run_record: MapRunRecord, ): super().__init__( @@ -124,7 +124,7 @@ def _eval_job(self, env: Environment, job: Job) -> None: self._map_run_record.item_counter.running.offset(-1) job.job_output = job_output - def _eval_pool(self, job: Optional[Job], worker_frame: Environment) -> None: + def _eval_pool(self, job: Job | None, worker_frame: Environment) -> None: if job is None: self._env.delete_frame(worker_frame) return diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py index 2562108ebac80..f8f16fcc225b5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py @@ -1,5 +1,5 @@ import logging -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.parargs import Parameters from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_selector import ( @@ -17,16 +17,16 @@ class InlineItemProcessorWorker(IterationWorker): - _parameters: Final[Optional[Parameters]] - _item_selector: Final[Optional[ItemSelector]] + _parameters: Final[Parameters | None] + _item_selector: Final[ItemSelector | None] def __init__( self, work_name: str, job_pool: JobPool, env: Environment, - item_selector: Optional[ItemSelector], - parameters: Optional[Parameters], + item_selector: ItemSelector | None, + parameters: Parameters | None, ): super().__init__(work_name=work_name, job_pool=job_pool, env=env) self._item_selector = item_selector diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py index 52599e7abf489..7769816fed5b0 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py @@ -2,7 +2,7 @@ import datetime import threading from collections import OrderedDict -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( Arn, @@ -98,7 +98,7 @@ class MapRunRecord: item_counter: Final[ItemCounter] start_date: Timestamp status: MapRunStatus - stop_date: Optional[Timestamp] + stop_date: Timestamp | None # TODO: add support for failure toleration fields. tolerated_failure_count: int tolerated_failure_percentage: float @@ -110,7 +110,7 @@ def __init__( max_concurrency: int, tolerated_failure_count: int, tolerated_failure_percentage: float, - label: Optional[str], + label: str | None, ): self.update_event = threading.Event() ( @@ -123,7 +123,7 @@ def __init__( self.max_concurrency = max_concurrency self.execution_counter = ExecutionCounter() self.item_counter = ItemCounter() - self.start_date = datetime.datetime.now(tz=datetime.timezone.utc) + self.start_date = datetime.datetime.now(tz=datetime.UTC) self.status = MapRunStatus.RUNNING self.stop_date = None self.tolerated_failure_count = tolerated_failure_count @@ -131,7 +131,7 @@ def __init__( @staticmethod def _generate_map_run_arns( - state_machine_arn: Arn, label: Optional[str] + state_machine_arn: Arn, label: str | None ) -> tuple[LongArn, LongArn]: # Generate a new MapRunArn given the StateMachineArn, such that: # inp: arn:aws:states::111111111111:stateMachine: @@ -144,7 +144,7 @@ def _generate_map_run_arns( def set_stop(self, status: MapRunStatus): self.status = status - self.stop_date = datetime.datetime.now(tz=datetime.timezone.utc) + self.stop_date = datetime.datetime.now(tz=datetime.UTC) def describe(self) -> DescribeMapRunOutput: describe_output = DescribeMapRunOutput( @@ -176,9 +176,9 @@ def list_item(self) -> MapRunListItem: def update( self, - max_concurrency: Optional[int], - tolerated_failure_count: Optional[int], - tolerated_failure_percentage: Optional[float], + max_concurrency: int | None, + tolerated_failure_count: int | None, + tolerated_failure_percentage: float | None, ) -> None: if max_concurrency is not None: self.max_concurrency = max_concurrency @@ -198,7 +198,7 @@ def __init__(self): def add(self, map_run_record: MapRunRecord) -> None: self._pool[map_run_record.map_run_arn] = map_run_record - def get(self, map_run_arn: LongArn) -> Optional[MapRunRecord]: + def get(self, map_run_arn: LongArn) -> MapRunRecord | None: return self._pool.get(map_run_arn) def get_all(self) -> list[MapRunRecord]: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py index 92e1be15ccd64..edfb073231985 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py @@ -1,7 +1,7 @@ from __future__ import annotations import abc -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.flow.start_at import StartAt @@ -18,14 +18,14 @@ class IterationComponent(EvalComponent, abc.ABC): _query_language: Final[QueryLanguage] _start_at: Final[StartAt] _states: Final[States] - _comment: Final[Optional[Comment]] + _comment: Final[Comment | None] def __init__( self, query_language: QueryLanguage, start_at: StartAt, states: States, - comment: Optional[Comment], + comment: Comment | None, ): self._query_language = query_language self._start_at = start_at diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py index b26b87ec1437e..6a5250c31202a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.flow.start_at import StartAt @@ -11,7 +11,7 @@ class IterationDecl(Component): - comment: Final[Optional[Comment]] + comment: Final[Comment | None] query_language: Final[QueryLanguage] start_at: Final[StartAt] states: Final[States] @@ -19,7 +19,7 @@ class IterationDecl(Component): def __init__( self, - comment: Optional[Comment], + comment: Comment | None, query_language: QueryLanguage, start_at: StartAt, states: States, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py index 1603149ca0b57..e137a02e55567 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py @@ -1,6 +1,6 @@ import abc import logging -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, MapIterationEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( @@ -169,7 +169,7 @@ def _eval_job(self, env: Environment, job: Job) -> None: finally: job.job_output = job_output - def _eval_pool(self, job: Optional[Job], worker_frame: Environment) -> None: + def _eval_pool(self, job: Job | None, worker_frame: Environment) -> None: # Note: the frame has to be closed before the job, to ensure the owner environment is correctly updated # before the evaluation continues; map states await for job termination not workers termination. if job is None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py index 583ab6e666473..9b6fce1a861f7 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( FailureEventException, ) @@ -35,9 +33,9 @@ def __init__( work_name: str, job_pool: JobPool, env: Environment, - parameters: Optional[Parameters], + parameters: Parameters | None, map_run_record: MapRunRecord, - item_selector: Optional[ItemSelector], + item_selector: ItemSelector | None, ): super().__init__( work_name=work_name, @@ -99,7 +97,7 @@ def _eval_job(self, env: Environment, job: Job) -> None: self._map_run_record.item_counter.running.offset(-1) job.job_output = job_output - def _eval_pool(self, job: Optional[Job], worker_frame: Environment) -> None: + def _eval_pool(self, job: Job | None, worker_frame: Environment) -> None: # Note: the frame has to be closed before the job, to ensure the owner environment is correctly updated # before the evaluation continues; map states await for job termination not workers termination. if job is None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py index 6100e412df44c..2f73f57c2b6fb 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from typing import Optional from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.iteration.inline_iteration_component import ( InlineIterationComponent, @@ -26,7 +25,7 @@ class InlineIteratorEvalInput(InlineIterationComponentEvalInput): class InlineIterator(InlineIterationComponent): - _eval_input: Optional[InlineIteratorEvalInput] + _eval_input: InlineIteratorEvalInput | None def _create_worker( self, env: Environment, eval_input: InlineIteratorEvalInput, job_pool: JobPool diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py index 45db68a00e8b1..f21c68962e4bc 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py @@ -1,5 +1,5 @@ import logging -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.parargs import Parameters from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_selector import ( @@ -17,16 +17,16 @@ class InlineIteratorWorker(IterationWorker): - _parameters: Final[Optional[Parameters]] - _item_selector: Final[Optional[ItemSelector]] + _parameters: Final[Parameters | None] + _item_selector: Final[ItemSelector | None] def __init__( self, work_name: str, job_pool: JobPool, env: Environment, - item_selector: Optional[ItemSelector], - parameters: Optional[Parameters], + item_selector: ItemSelector | None, + parameters: Parameters | None, ): super().__init__(work_name=work_name, job_pool=job_pool, env=env) self._item_selector = item_selector diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py index 1ef24a6e17593..7cf76f1ef17b9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py @@ -1,7 +1,7 @@ import copy import logging import threading -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.program.program import Program from localstack.services.stepfunctions.asl.utils.encoding import to_json_str @@ -12,10 +12,10 @@ class Job: job_index: Final[int] job_program: Final[Program] - job_input: Final[Optional[Any]] - job_output: Optional[Any] + job_input: Final[Any | None] + job_output: Any | None - def __init__(self, job_index: int, job_program: Program, job_input: Optional[Any]): + def __init__(self, job_index: int, job_program: Program, job_input: Any | None): self.job_index = job_index self.job_program = job_program self.job_input = job_input @@ -24,9 +24,9 @@ def __init__(self, job_index: int, job_program: Program, job_input: Optional[Any class JobClosed: job_index: Final[int] - job_output: Optional[Any] + job_output: Any | None - def __init__(self, job_index: int, job_output: Optional[Any]): + def __init__(self, job_index: int, job_output: Any | None): self.job_index = job_index self.job_output = job_output @@ -37,7 +37,7 @@ def __hash__(self): class JobPool: _mutex: Final[threading.Lock] _termination_event: Final[threading.Event] - _worker_exception: Optional[Exception] + _worker_exception: Exception | None _jobs_number: Final[int] _open_jobs: Final[list[Job]] @@ -56,7 +56,7 @@ def __init__(self, job_program: Program, job_inputs: list[Any]): self._open_jobs.reverse() self._closed_jobs = set() - def next_job(self) -> Optional[Any]: + def next_job(self) -> Any | None: with self._mutex: if self._worker_exception is not None: return None @@ -72,7 +72,7 @@ def _notify_on_termination(self) -> None: if self._is_terminated(): self._termination_event.set() - def get_worker_exception(self) -> Optional[Exception]: + def get_worker_exception(self) -> Exception | None: return self._worker_exception def close_job(self, job: Job) -> None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py index 178c9653c83c6..6ce02f26abffc 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py @@ -1,7 +1,8 @@ from __future__ import annotations import json -from typing import Callable, Final +from collections.abc import Callable +from typing import Final from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.result_writer.resource_eval.resource_eval import ( ResourceEval, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py index ea0aebac7751d..80a0bbbbbff8d 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py @@ -1,5 +1,4 @@ import copy -from typing import Optional from localstack.aws.api.stepfunctions import ( EvaluationFailedEventDetails, @@ -99,31 +98,31 @@ class StateMap(ExecutionState): - items: Optional[Items] - items_path: Optional[ItemsPath] + items: Items | None + items_path: ItemsPath | None iteration_component: IterationComponent - item_reader: Optional[ItemReader] - item_selector: Optional[ItemSelector] - parameters: Optional[Parameters] + item_reader: ItemReader | None + item_selector: ItemSelector | None + parameters: Parameters | None max_concurrency_decl: MaxConcurrencyDecl tolerated_failure_count_decl: ToleratedFailureCountDecl tolerated_failure_percentage_decl: ToleratedFailurePercentage - result_path: Optional[ResultPath] + result_path: ResultPath | None result_selector: ResultSelector - retry: Optional[RetryDecl] - catch: Optional[CatchDecl] - label: Optional[Label] - result_writer: Optional[ResultWriter] + retry: RetryDecl | None + catch: CatchDecl | None + label: Label | None + result_writer: ResultWriter | None def __init__(self): - super(StateMap, self).__init__( + super().__init__( state_entered_event_type=HistoryEventType.MapStateEntered, state_exited_event_type=HistoryEventType.MapStateExited, ) def from_state_props(self, state_props: StateProps) -> None: - super(StateMap, self).from_state_props(state_props) - if self._is_language_query_jsonpath(): + super().from_state_props(state_props) + if self.is_jsonpath_query_language(): self.items = None self.items_path = state_props.get(ItemsPath) or ItemsPath( string_sampler=StringJsonPath(JSONPATH_ROOT_PATH) @@ -312,7 +311,6 @@ def _eval_state(self, env: Environment) -> None: failure_event: FailureEvent = self._from_error(env=env, ex=ex) error_output = self._construct_error_output_value(failure_event=failure_event) env.states.set_error_output(error_output) - env.states.set_result(error_output) if self.retry: retry_outcome: RetryOutcome = self._handle_retry( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py index 51ef19322cf5e..448f548c2707f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py @@ -1,7 +1,7 @@ import abc import logging import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import Timestamp from localstack.services.stepfunctions.asl.component.program.program import Program @@ -18,7 +18,7 @@ def on_terminated(self, env: Environment): ... _branch_worker_comm: Final[BranchWorkerComm] _program: Final[Program] - _worker_thread: Optional[threading.Thread] + _worker_thread: threading.Thread | None env: Final[Environment] def __init__(self, branch_worker_comm: BranchWorkerComm, program: Program, env: Environment): @@ -43,7 +43,7 @@ def start(self): TMP_THREADS.append(self._worker_thread) self._worker_thread.start() - def stop(self, stop_date: Timestamp, cause: Optional[str], error: Optional[str]) -> None: + def stop(self, stop_date: Timestamp, cause: str | None, error: str | None) -> None: env = self.env if env: try: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py index d9c268e776f66..1fe296550de3c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py @@ -1,6 +1,6 @@ import datetime import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ExecutionFailedEventDetails, HistoryEventType from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( @@ -26,7 +26,7 @@ class BranchWorkerPool(BranchWorker.BranchWorkerComm): _termination_event: Final[threading.Event] _active_workers_num: int - _terminated_with_error: Optional[ExecutionFailedEventDetails] + _terminated_with_error: ExecutionFailedEventDetails | None def __init__(self, workers_num: int): self._mutex = threading.Lock() @@ -42,7 +42,7 @@ def on_terminated(self, env: Environment): end_program_state: ProgramState = env.program_state() if isinstance(end_program_state, ProgramError): self._terminated_with_error = select_from_typed_dict( - typed_dict=ExecutionFailedEventDetails, obj=end_program_state.error or dict() + typed_dict=ExecutionFailedEventDetails, obj=end_program_state.error or {} ) self._termination_event.set() else: @@ -53,7 +53,7 @@ def on_terminated(self, env: Environment): def wait(self): self._termination_event.wait() - def get_exit_event_details(self) -> Optional[ExecutionFailedEventDetails]: + def get_exit_event_details(self) -> ExecutionFailedEventDetails | None: return self._terminated_with_error @@ -67,7 +67,7 @@ def _eval_body(self, env: Environment) -> None: branch_worker_pool = BranchWorkerPool(workers_num=len(self.programs)) - branch_workers: list[BranchWorker] = list() + branch_workers: list[BranchWorker] = [] for program in self.programs: # Environment frame for this sub process. env_frame: Environment = env.open_inner_frame() @@ -84,7 +84,7 @@ def _eval_body(self, env: Environment) -> None: branch_worker_pool.wait() # Propagate exception if parallel task failed. - exit_event_details: Optional[ExecutionFailedEventDetails] = ( + exit_event_details: ExecutionFailedEventDetails | None = ( branch_worker_pool.get_exit_event_details() ) if exit_event_details is not None: @@ -103,7 +103,7 @@ def _eval_body(self, env: Environment) -> None: ) # Collect the results and return. - result_list = list() + result_list = [] for worker in branch_workers: env_frame = worker.env diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py index ce7c5c42d4109..a10a3f8358fd3 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py @@ -1,5 +1,4 @@ import copy -from typing import Optional from localstack.aws.api.stepfunctions import HistoryEventType from localstack.services.stepfunctions.asl.component.common.catch.catch_outcome import CatchOutcome @@ -25,7 +24,7 @@ class StateParallel(ExecutionState): # machine object must have fields named States and StartAt, whose meanings are exactly # like those in the top level of a state machine. branches: BranchesDecl - parargs: Optional[Parargs] + parargs: Parargs | None def __init__(self): super().__init__( @@ -34,7 +33,7 @@ def __init__(self): ) def from_state_props(self, state_props: StateProps) -> None: - super(StateParallel, self).from_state_props(state_props) + super().from_state_props(state_props) self.branches = state_props.get( typ=BranchesDecl, raise_on_missing=ValueError(f"Missing Branches definition in props '{state_props}'."), diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py index 9f59414b844ab..f88dc1d950276 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py @@ -1,32 +1,32 @@ import json from json import JSONDecodeError -from typing import IO, Any, Final, Optional, Union +from typing import IO, Any, Final from localstack.aws.api.lambda_ import InvocationResponse from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import ( StateCredentials, ) -from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.mock_eval_utils import ( - eval_mocked_response, +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import ( + eval_local_mocked_response, ) from localstack.services.stepfunctions.asl.eval.environment import Environment from localstack.services.stepfunctions.asl.utils.boto_client import boto_client_for from localstack.services.stepfunctions.asl.utils.encoding import to_json_str -from localstack.services.stepfunctions.mocking.mock_config import MockedResponse +from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse from localstack.utils.collections import select_from_typed_dict from localstack.utils.strings import to_bytes class LambdaFunctionErrorException(Exception): - function_error: Final[Optional[str]] + function_error: Final[str | None] payload: Final[str] - def __init__(self, function_error: Optional[str], payload: str): + def __init__(self, function_error: str | None, payload: str): self.function_error = function_error self.payload = payload -def _from_payload(payload_streaming_body: IO[bytes]) -> Union[json, str]: +def _from_payload(payload_streaming_body: IO[bytes]) -> Any | str: """ This method extracts the lambda payload. The payload may be a string or a JSON stringified object. In the first case, this function converts the output into a UTF-8 string, otherwise it parses the @@ -42,9 +42,9 @@ def _from_payload(payload_streaming_body: IO[bytes]) -> Union[json, str]: return decoded_data -def _mocked_invoke_lambda_function(env: Environment) -> InvocationResponse: - mocked_response: MockedResponse = env.get_current_mocked_response() - eval_mocked_response(env=env, mocked_response=mocked_response) +def _local_mocked_invoke_lambda_function(env: Environment) -> InvocationResponse: + mocked_response: LocalMockedResponse = env.get_current_local_mocked_response() + eval_local_mocked_response(env=env, mocked_response=mocked_response) invocation_resp: InvocationResponse = env.stack.pop() return invocation_resp @@ -68,14 +68,14 @@ def _invoke_lambda_function( def execute_lambda_function_integration( env: Environment, parameters: dict, region: str, state_credentials: StateCredentials ) -> None: - if env.is_mocked_mode(): - invocation_response: InvocationResponse = _mocked_invoke_lambda_function(env=env) + if env.is_local_mocked_mode(): + invocation_response: InvocationResponse = _local_mocked_invoke_lambda_function(env=env) else: invocation_response: InvocationResponse = _invoke_lambda_function( parameters=parameters, region=region, state_credentials=state_credentials ) - function_error: Optional[str] = invocation_response.get("FunctionError") + function_error: str | None = invocation_response.get("FunctionError") if function_error: payload_json = invocation_response["Payload"] payload_str = json.dumps(payload_json, separators=(",", ":")) @@ -85,12 +85,12 @@ def execute_lambda_function_integration( env.stack.append(response) -def to_payload_type(payload: Any) -> Optional[bytes]: +def to_payload_type(payload: Any) -> bytes | None: if isinstance(payload, bytes): return payload if payload is None: - str_value = to_json_str(dict()) + str_value = to_json_str({}) elif isinstance(payload, str): try: json.loads(payload) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/mock_eval_utils.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/local_mock_eval_utils.py similarity index 69% rename from localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/mock_eval_utils.py rename to localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/local_mock_eval_utils.py index aa8a9c423f433..2ed6e2287e7a2 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/mock_eval_utils.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/local_mock_eval_utils.py @@ -10,14 +10,16 @@ ) from localstack.services.stepfunctions.asl.eval.environment import Environment from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails -from localstack.services.stepfunctions.mocking.mock_config import ( - MockedResponse, - MockedResponseReturn, - MockedResponseThrow, +from localstack.services.stepfunctions.local_mocking.mock_config import ( + LocalMockedResponse, + LocalMockedResponseReturn, + LocalMockedResponseThrow, ) -def _eval_mocked_response_throw(env: Environment, mocked_response: MockedResponseThrow) -> None: +def _eval_mocked_response_throw( + env: Environment, mocked_response: LocalMockedResponseThrow +) -> None: task_failed_event_details = TaskFailedEventDetails( error=mocked_response.error, cause=mocked_response.cause ) @@ -31,15 +33,17 @@ def _eval_mocked_response_throw(env: Environment, mocked_response: MockedRespons raise FailureEventException(failure_event=failure_event) -def _eval_mocked_response_return(env: Environment, mocked_response: MockedResponseReturn) -> None: +def _eval_mocked_response_return( + env: Environment, mocked_response: LocalMockedResponseReturn +) -> None: payload_copy = copy.deepcopy(mocked_response.payload) env.stack.append(payload_copy) -def eval_mocked_response(env: Environment, mocked_response: MockedResponse) -> None: - if isinstance(mocked_response, MockedResponseReturn): +def eval_local_mocked_response(env: Environment, mocked_response: LocalMockedResponse) -> None: + if isinstance(mocked_response, LocalMockedResponseReturn): _eval_mocked_response_return(env=env, mocked_response=mocked_response) - elif isinstance(mocked_response, MockedResponseThrow): + elif isinstance(mocked_response, LocalMockedResponseThrow): _eval_mocked_response_throw(env=env, mocked_response=mocked_response) else: raise RuntimeError(f"Invalid MockedResponse type '{type(mocked_response)}'") diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py index ce1d4288d5a5c..4bee75df0ee3f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py @@ -2,7 +2,7 @@ import abc from itertools import takewhile -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent from localstack.services.stepfunctions.asl.eval.environment import Environment @@ -33,7 +33,7 @@ def __init__( account: str, task_type: str, name: str, - option: Optional[str], + option: str | None, ): self.arn = arn self.partition = partition @@ -137,7 +137,7 @@ class ServiceResource(Resource): service_name: Final[str] api_name: Final[str] api_action: Final[str] - condition: Final[Optional[str]] + condition: Final[str | None] def __init__(self, resource_arn: ResourceARN): super().__init__(resource_arn=resource_arn) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py index c385368c25dc2..1768833567680 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py @@ -4,7 +4,7 @@ import copy import json import logging -from typing import Any, Final, Optional, Union +from typing import Any, Final from botocore.model import ListShape, OperationModel, Shape, StringShape, StructureShape from botocore.response import StreamingBody @@ -33,8 +33,8 @@ from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import ( StateCredentials, ) -from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.mock_eval_utils import ( - eval_mocked_response, +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import ( + eval_local_mocked_response, ) from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.resource import ( ResourceRuntimePart, @@ -47,7 +47,7 @@ from localstack.services.stepfunctions.asl.eval.environment import Environment from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails from localstack.services.stepfunctions.asl.utils.encoding import to_json_str -from localstack.services.stepfunctions.mocking.mock_config import MockedResponse +from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse from localstack.services.stepfunctions.quotas import is_within_size_quota from localstack.utils.strings import camel_to_snake_case, snake_to_camel_case, to_bytes, to_str @@ -147,8 +147,8 @@ def _to_boto_request(self, parameters: dict, structure_shape: StructureShape) -> parameters_bind_keys: list[str] = list(parameters.keys()) for parameter_key in parameters_bind_keys: norm_parameter_key = camel_to_snake_case(parameter_key) - norm_member_bind: Optional[tuple[str, Optional[StructureShape]]] = ( - norm_member_binds.get(norm_parameter_key) + norm_member_bind: tuple[str, StructureShape | None] | None = norm_member_binds.get( + norm_parameter_key ) if norm_member_bind is not None: norm_member_bind_key, norm_member_bind_shape = norm_member_bind @@ -203,19 +203,19 @@ def _from_boto_response(self, response: Any, structure_shape: StructureShape) -> response[norm_response_key] = response_value - def _get_boto_service_name(self, boto_service_name: Optional[str] = None) -> str: + def _get_boto_service_name(self, boto_service_name: str | None = None) -> str: api_name = boto_service_name or self.resource.api_name return self._SERVICE_NAME_SFN_TO_BOTO_OVERRIDES.get(api_name, api_name) - def _get_boto_service_action(self, service_action_name: Optional[str] = None) -> str: + def _get_boto_service_action(self, service_action_name: str | None = None) -> str: api_action = service_action_name or self.resource.api_action return camel_to_snake_case(api_action) def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: boto_service_name = self._get_boto_service_name(boto_service_name=boto_service_name) service_action_name = self._get_boto_service_action(service_action_name=service_action_name) @@ -228,8 +228,8 @@ def _normalise_parameters( def _normalise_response( self, response: Any, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: boto_service_name = self._get_boto_service_name(boto_service_name=boto_service_name) service_action_name = self._get_boto_service_action(service_action_name=service_action_name) @@ -239,7 +239,7 @@ def _normalise_response( if output_shape is not None: self._from_boto_response(response, output_shape) # noqa - def _verify_size_quota(self, env: Environment, value: Union[str, json]) -> None: + def _verify_size_quota(self, env: Environment, value: str | json) -> None: is_within: bool = is_within_size_quota(value) if is_within: return @@ -356,9 +356,9 @@ def _eval_execution(self, env: Environment) -> None: normalised_parameters = copy.deepcopy(raw_parameters) self._normalise_parameters(normalised_parameters) - if env.is_mocked_mode(): - mocked_response: MockedResponse = env.get_current_mocked_response() - eval_mocked_response(env=env, mocked_response=mocked_response) + if env.is_local_mocked_mode(): + mocked_response: LocalMockedResponse = env.get_current_local_mocked_response() + eval_local_mocked_response(env=env, mocked_response=mocked_response) else: self._eval_service_task( env=env, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py index b4d8c660a8f81..ea9f7ac1f1687 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py @@ -4,7 +4,7 @@ import json import logging from json import JSONDecodeError -from typing import Any, Final, Optional, TypedDict +from typing import Any, Final, TypedDict from urllib.parse import urlencode, urljoin, urlparse import requests @@ -76,13 +76,13 @@ class AuthType(str): class TaskParameters(TypedDict): ApiEndpoint: ApiEndpoint Method: Method - Headers: Optional[Headers] - Stage: Optional[Stage] - Path: Optional[Path] - QueryParameters: Optional[QueryParameters] - RequestBody: Optional[RequestBody] - AllowNullValues: Optional[AllowNullValues] - AuthType: Optional[AuthType] + Headers: Headers | None + Stage: Stage | None + Path: Path | None + QueryParameters: QueryParameters | None + RequestBody: RequestBody | None + AllowNullValues: AllowNullValues | None + AuthType: AuthType | None class InvokeOutput(TypedDict): @@ -131,14 +131,14 @@ class StateTaskServiceApiGateway(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return self._SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: # ApiGateway does not support botocore request relay. pass @@ -146,14 +146,14 @@ def _normalise_parameters( def _normalise_response( self, response: Any, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: # ApiGateway does not support botocore request relay. pass @staticmethod - def _query_parameters_of(parameters: TaskParameters) -> Optional[str]: + def _query_parameters_of(parameters: TaskParameters) -> str | None: query_str = None query_parameters = parameters.get("QueryParameters") # TODO: add support for AllowNullValues. @@ -167,8 +167,8 @@ def _query_parameters_of(parameters: TaskParameters) -> Optional[str]: return query_str @staticmethod - def _headers_of(parameters: TaskParameters) -> Optional[dict]: - headers = parameters.get("Headers", dict()) + def _headers_of(parameters: TaskParameters) -> dict | None: + headers = parameters.get("Headers", {}) if headers: for key in headers.keys(): # TODO: the following check takes place at parse time. @@ -239,8 +239,8 @@ def _invoke_output_of(response: Response) -> InvokeOutput: response_body = response.json() except JSONDecodeError: response_body = response.text - if response_body == json.dumps(dict()): - response_body = dict() + if response_body == json.dumps({}): + response_body = {} # since we are not using a case-insensitive dict, and we want to remove a header, for server # compatibility we should consider both casing variants diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py index aff2642e29710..04fbc7bf5bbea 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py @@ -132,7 +132,7 @@ def _eval_service_task( region=resource_runtime_part.region, state_credentials=state_credentials, ) - response = getattr(api_client, api_action)(**normalised_parameters) or dict() + response = getattr(api_client, api_action)(**normalised_parameters) or {} if response: response.pop("ResponseMetadata", None) env.stack.append(response) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py index bc83e1f327121..1ed16a236ce4e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from botocore.exceptions import ClientError from moto.batch.utils import JobStatus @@ -62,7 +63,7 @@ class StateTaskServiceBatch(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) @staticmethod @@ -70,12 +71,12 @@ def _attach_aws_environment_variables(parameters: dict) -> None: # Attaches to the ContainerOverrides environment variables the AWS managed flags. container_overrides = parameters.get("ContainerOverrides") if container_overrides is None: - container_overrides = dict() + container_overrides = {} parameters["ContainerOverrides"] = container_overrides environment = container_overrides.get("Environment") if environment is None: - environment = list() + environment = [] container_overrides["Environment"] = environment environment.append( @@ -117,7 +118,7 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: "Proxy: null", ] ) - cause = f"Error executing request, Exception : {error_message}, RequestId: {request_id} ({response_details})" + cause = f"{error_message} ({response_details})" return FailureEvent( env=env, error_name=CustomErrorName(error_name), @@ -139,7 +140,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: batch_client = boto_client_for( service="batch", region=resource_runtime_part.region, @@ -148,7 +149,7 @@ def _build_sync_resolver( submission_output: dict = env.stack.pop() job_id = submission_output["JobId"] - def _sync_resolver() -> Optional[dict]: + def _sync_resolver() -> dict | None: describe_jobs_response = batch_client.describe_jobs(jobs=[job_id]) describe_jobs = describe_jobs_response["jobs"] if describe_jobs: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py index bed6e8b78fdd5..bec4b68e9c838 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py @@ -2,7 +2,8 @@ import json import threading import time -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from localstack.aws.api.stepfunctions import ( HistoryEventExecutionDataDetails, @@ -67,7 +68,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: raise RuntimeError( f"Unsupported .sync callback procedure in resource {self.resource.resource_arn}" ) @@ -78,7 +79,7 @@ def _build_sync2_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: raise RuntimeError( f"Unsupported .sync2 callback procedure in resource {self.resource.resource_arn}" ) @@ -88,9 +89,9 @@ def _eval_wait_for_task_token( env: Environment, timeout_seconds: int, callback_endpoint: CallbackEndpoint, - heartbeat_endpoint: Optional[HeartbeatEndpoint], + heartbeat_endpoint: HeartbeatEndpoint | None, ) -> CallbackOutcome: - outcome: Optional[CallbackOutcome] + outcome: CallbackOutcome | None if heartbeat_endpoint is not None: outcome = self._wait_for_task_token_heartbeat( env, callback_endpoint, heartbeat_endpoint @@ -104,12 +105,12 @@ def _eval_wait_for_task_token( def _eval_sync( self, env: Environment, - sync_resolver: Callable[[], Optional[Any]], - timeout_seconds: Optional[int], - callback_endpoint: Optional[CallbackEndpoint], - heartbeat_endpoint: Optional[HeartbeatEndpoint], + sync_resolver: Callable[[], Any | None], + timeout_seconds: int | None, + callback_endpoint: CallbackEndpoint | None, + heartbeat_endpoint: HeartbeatEndpoint | None, ) -> CallbackOutcome | Any: - callback_output: Optional[CallbackOutcome] = None + callback_output: CallbackOutcome | None = None # Listen for WaitForTaskToken signals if an endpoint is provided. if callback_endpoint is not None: @@ -134,7 +135,7 @@ def _local_update_wait_for_task_token(): # an exception in this thread will invalidate env, and therefore the worker thread. # hence why here there are no explicit stopping logic for thread_wait_for_task_token. - sync_result: Optional[Any] = None + sync_result: Any | None = None while env.is_running(): sync_result = sync_resolver() if callback_output or sync_result: @@ -154,7 +155,7 @@ def _eval_integration_pattern( task_output = env.stack.pop() # Initialise the waitForTaskToken Callback endpoint for this task if supported. - callback_endpoint: Optional[CallbackEndpoint] = None + callback_endpoint: CallbackEndpoint | None = None if ResourceCondition.WaitForTaskToken in self._supported_integration_patterns: callback_id = env.states.context_object.context_object_data["Task"]["Token"] callback_endpoint = env.callback_pool_manager.get(callback_id) @@ -164,7 +165,7 @@ def _eval_integration_pattern( timeout_seconds = env.stack.pop() # Setup resources for heartbeat workloads if necessary. - heartbeat_endpoint: Optional[HeartbeatEndpoint] = None + heartbeat_endpoint: HeartbeatEndpoint | None = None if self.heartbeat: self.heartbeat.eval(env=env) heartbeat_seconds = env.stack.pop() @@ -216,7 +217,8 @@ def _eval_integration_pattern( # finished, ensure all waiting # threads on this endpoint (or task) will stop. This is in an effort to # release resources sooner than when these would eventually synchronise with the updated environment # state of this task. - callback_endpoint.interrupt_all() + if callback_endpoint: + callback_endpoint.interrupt_all() # Handle Callback outcome types. if isinstance(outcome, CallbackOutcomeTimedOut): @@ -240,7 +242,7 @@ def _wait_for_task_token_timeout( # noqa self, timeout_seconds: int, callback_endpoint: CallbackEndpoint, - ) -> Optional[CallbackOutcome]: + ) -> CallbackOutcome | None: # Awaits a callback notification and returns the outcome received. # If the operation times out or is interrupted it returns None. @@ -249,7 +251,7 @@ def _wait_for_task_token_timeout( # noqa # discarded by the main process. # Note: although this is the same timeout value, this can only decay strictly after the first timeout # started as it is invoked strictly later. - outcome: Optional[CallbackOutcome] = callback_endpoint.wait(timeout=timeout_seconds) + outcome: CallbackOutcome | None = callback_endpoint.wait(timeout=timeout_seconds) return outcome def _wait_for_task_token_heartbeat( # noqa @@ -257,7 +259,7 @@ def _wait_for_task_token_heartbeat( # noqa env: Environment, callback_endpoint: CallbackEndpoint, heartbeat_endpoint: HeartbeatEndpoint, - ) -> Optional[CallbackOutcome]: + ) -> CallbackOutcome | None: outcome = None while ( env.is_running() @@ -285,7 +287,7 @@ def _get_callback_outcome_failure_event( self, env: Environment, ex: CallbackOutcomeFailureError ) -> FailureEvent: callback_outcome_failure: CallbackOutcomeFailure = ex.callback_outcome_failure - error: Optional[str] = callback_outcome_failure.error + error: str | None = callback_outcome_failure.error return FailureEvent( env=env, error_name=CustomErrorName(error_name=error), @@ -345,7 +347,7 @@ def _after_eval_execution( ) ), ) - if not env.is_mocked_mode(): + if not env.is_local_mocked_mode() and not env.is_test_state_mocked_mode(): self._eval_integration_pattern( env=env, resource_runtime_part=resource_runtime_part, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py index 9fb484abc6362..95ebfd4edb8fc 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from botocore.exceptions import ClientError @@ -76,7 +76,7 @@ class StateTaskServiceDynamoDB(StateTaskService): - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) @staticmethod diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py index 3b3473aaa848c..2a4395872e877 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import ( StateCredentials, @@ -42,7 +43,7 @@ class StateTaskServiceEcs(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _before_eval_execution( @@ -102,7 +103,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: ecs_client = boto_client_for( service="ecs", region=resource_runtime_part.region, @@ -112,7 +113,7 @@ def _build_sync_resolver( task_arn: str = submission_output["Tasks"][0]["TaskArn"] cluster_arn: str = submission_output["Tasks"][0]["ClusterArn"] - def _sync_resolver() -> Optional[dict]: + def _sync_resolver() -> dict | None: describe_tasks_output = ecs_client.describe_tasks(cluster=cluster_arn, tasks=[task_arn]) last_status: str = describe_tasks_output["tasks"][0]["lastStatus"] diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py index 19640f84ab02f..32416bc7b241d 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py @@ -1,5 +1,5 @@ import json -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( @@ -33,9 +33,9 @@ class SfnFailedEntryCountException(RuntimeError): - cause: Final[Optional[dict]] + cause: Final[dict | None] - def __init__(self, cause: Optional[dict]): + def __init__(self, cause: dict | None): super().__init__(json.dumps(cause)) self.cause = cause @@ -44,7 +44,7 @@ class StateTaskServiceEvents(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py index f66a00e26d4ef..a5bf7a5b44642 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final import boto3 from botocore.exceptions import ClientError @@ -73,7 +74,7 @@ ] # The type of (sync)handler builder function for StateTaskServiceGlue objects. _API_ACTION_HANDLER_BUILDER_TYPE = Callable[ - [Environment, ResourceRuntimePart, dict, StateCredentials], Callable[[], Optional[Any]] + [Environment, ResourceRuntimePart, dict, StateCredentials], Callable[[], Any | None] ] @@ -81,7 +82,7 @@ class StateTaskServiceGlue(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _get_api_action_handler(self) -> _API_ACTION_HANDLER_TYPE: @@ -174,7 +175,7 @@ def _sync_to_start_job_run( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: # Poll the job run state from glue, using GetJobRun until the job has terminated. Hence, append the output # of GetJobRun to the state. @@ -188,7 +189,7 @@ def _sync_to_start_job_run( resource_runtime_part=resource_runtime_part, state_credentials=state_credentials ) - def _sync_resolver() -> Optional[Any]: + def _sync_resolver() -> Any | None: # Sample GetJobRun until completion. get_job_run_response: dict = glue_client.get_job_run(JobName=job_name, RunId=job_run_id) job_run: dict = get_job_run_response["JobRun"] @@ -232,7 +233,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: sync_resolver_builder = self._get_api_action_sync_builder_handler() sync_resolver = sync_resolver_builder( env, resource_runtime_part, normalised_parameters, state_credentials diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py index 8feebfa1cdc29..4a2b67d208b6e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py @@ -1,6 +1,6 @@ import json import logging -from typing import Final, Optional +from typing import Final from botocore.exceptions import ClientError @@ -50,7 +50,7 @@ class StateTaskServiceLambda(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) @staticmethod @@ -105,8 +105,8 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: # Run Payload value casting before normalisation. if "Payload" in parameters: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py index 33bafc723a00e..9fcd18163fde3 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py @@ -1,5 +1,6 @@ import json -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from botocore.exceptions import ClientError @@ -52,7 +53,7 @@ class StateTaskServiceSfn(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: @@ -91,8 +92,8 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: if service_action_name is None: if self._get_boto_service_action() == "start_execution": @@ -115,7 +116,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: sfn_client = boto_client_for( service="stepfunctions", region=resource_runtime_part.region, @@ -124,7 +125,7 @@ def _build_sync_resolver( submission_output: dict = env.stack.pop() execution_arn: str = submission_output["ExecutionArn"] - def _sync_resolver() -> Optional[Any]: + def _sync_resolver() -> Any | None: describe_execution_output = sfn_client.describe_execution(executionArn=execution_arn) describe_execution_output: DescribeExecutionOutput = select_from_typed_dict( DescribeExecutionOutput, describe_execution_output @@ -176,7 +177,7 @@ def _build_sync2_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: sfn_client = boto_client_for( region=resource_runtime_part.region, service="stepfunctions", @@ -185,7 +186,7 @@ def _build_sync2_resolver( submission_output: dict = env.stack.pop() execution_arn: str = submission_output["ExecutionArn"] - def _sync2_resolver() -> Optional[Any]: + def _sync2_resolver() -> Any | None: describe_execution_output = sfn_client.describe_execution(executionArn=execution_arn) describe_execution_output: DescribeExecutionOutput = select_from_typed_dict( DescribeExecutionOutput, describe_execution_output diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py index 45c6693d0dafd..e230cd418b898 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from botocore.exceptions import ClientError @@ -46,7 +46,7 @@ class StateTaskServiceSns(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py index 836cb8ad1b95b..621a43a1a651d 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py @@ -1,4 +1,4 @@ -from typing import Any, Final, Optional +from typing import Any, Final from botocore.exceptions import ClientError @@ -45,7 +45,7 @@ class StateTaskServiceSqs(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: @@ -70,8 +70,8 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: def _normalise_response( self, response: Any, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: super()._normalise_response( response=response, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py index 79c5f496d7bf8..9a6ab7afb792f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py @@ -31,17 +31,17 @@ class StateTask(ExecutionState, abc.ABC): resource: Resource - parargs: Optional[Parargs] - credentials: Optional[Credentials] + parargs: Parargs | None + credentials: Credentials | None def __init__(self): - super(StateTask, self).__init__( + super().__init__( state_entered_event_type=HistoryEventType.TaskStateEntered, state_exited_event_type=HistoryEventType.TaskStateExited, ) def from_state_props(self, state_props: StateProps) -> None: - super(StateTask, self).from_state_props(state_props) + super().from_state_props(state_props) self.resource = state_props.get(Resource) self.parargs = state_props.get(Parargs) self.credentials = state_props.get(Credentials) @@ -51,7 +51,7 @@ def _get_supported_parameters(self) -> Optional[set[str]]: # noqa def _eval_parameters(self, env: Environment) -> dict: # Eval raw parameters. - parameters = dict() + parameters = {} if self.parargs is not None: self.parargs.eval(env=env) parameters = env.stack.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py index d33fc290b611e..e7ed09aad112e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py @@ -1,6 +1,6 @@ import json import logging -from typing import Union +from typing import Any from botocore.exceptions import ClientError @@ -94,7 +94,7 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: ), ) - def _verify_size_quota(self, env: Environment, value: Union[str, json]) -> None: + def _verify_size_quota(self, env: Environment, value: str | Any) -> None: is_within: bool = is_within_size_quota(value=value) if is_within: return diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py index 608b27f2044fc..607434657cec5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( CustomErrorName, @@ -9,6 +7,9 @@ FailureEventException, ) from localstack.services.stepfunctions.asl.component.state.state import CommonStateField +from localstack.services.stepfunctions.asl.component.state.state_continue_with import ( + ContinueWithEnd, +) from localstack.services.stepfunctions.asl.component.state.state_fail.cause_decl import CauseDecl from localstack.services.stepfunctions.asl.component.state.state_fail.error_decl import ErrorDecl from localstack.services.stepfunctions.asl.component.state.state_props import StateProps @@ -22,13 +23,14 @@ def __init__(self): state_entered_event_type=HistoryEventType.FailStateEntered, state_exited_event_type=None, ) - self.cause: Optional[CauseDecl] = None - self.error: Optional[ErrorDecl] = None + self.cause: CauseDecl | None = None + self.error: ErrorDecl | None = None def from_state_props(self, state_props: StateProps) -> None: - super(StateFail, self).from_state_props(state_props) + super().from_state_props(state_props) self.cause = state_props.get(CauseDecl) self.error = state_props.get(ErrorDecl) + self.continue_with = ContinueWithEnd() def _eval_state(self, env: Environment) -> None: task_failed_event_details = TaskFailedEventDetails() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py index 3a13b935b73ac..9a522df47157d 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.aws.api.stepfunctions import ( HistoryEventType, ) @@ -13,7 +11,7 @@ class StatePass(CommonStateField): def __init__(self): - super(StatePass, self).__init__( + super().__init__( state_entered_event_type=HistoryEventType.PassStateEntered, state_exited_event_type=HistoryEventType.PassStateExited, ) @@ -21,20 +19,20 @@ def __init__(self): # Result (Optional) # Refers to the output of a virtual state_task that is passed on to the next state. If you include the ResultPath # field in your state machine definition, Result is placed as specified by ResultPath and passed on to the - self.result: Optional[Result] = None + self.result: Result | None = None # ResultPath (Optional) # Specifies where to place the output (relative to the input) of the virtual state_task specified in Result. The input # is further filtered as specified by the OutputPath field (if present) before being used as the state's output. - self.result_path: Optional[ResultPath] = None + self.result_path: ResultPath | None = None # Parameters (Optional) # Creates a collection of key-value pairs that will be passed as input. You can specify Parameters as a static # value or select from the input using a path. - self.parameters: Optional[Parameters] = None + self.parameters: Parameters | None = None def from_state_props(self, state_props: StateProps) -> None: - super(StatePass, self).from_state_props(state_props) + super().from_state_props(state_props) self.result = state_props.get(Result) self.result_path = state_props.get(ResultPath) or ResultPath( result_path_src=ResultPath.DEFAULT_PATH @@ -48,7 +46,7 @@ def _eval_state(self, env: Environment) -> None: if self.result: self.result.eval(env=env) - if not self._is_language_query_jsonpath(): + if not self.is_jsonpath_query_language(): output_value = env.stack[-1] env.states.set_result(output_value) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py index f6423e3e221d9..1c485c22982d1 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py @@ -17,7 +17,7 @@ def __init__(self): ) def from_state_props(self, state_props: StateProps) -> None: - super(StateSucceed, self).from_state_props(state_props) + super().from_state_props(state_props) # TODO: assert all other fields are undefined? # No Next or End field: Succeed states are terminal states. diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py index 958377cbcc7e8..6abbc474c723e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py @@ -19,7 +19,7 @@ def __init__(self): ) def from_state_props(self, state_props: StateProps) -> None: - super(StateWait, self).from_state_props(state_props) + super().from_state_props(state_props) self.wait_function = state_props.get( typ=WaitFunction, raise_on_missing=ValueError(f"Undefined WaitFunction for StateWait: '{self}'."), diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py index f26583bf77d10..9b771fa8784f7 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py @@ -1,6 +1,6 @@ import datetime import re -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ExecutionFailedEventDetails, HistoryEventType from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( @@ -47,7 +47,7 @@ def _is_valid_timestamp_pattern(timestamp: str) -> bool: return re.match(TIMESTAMP_PATTERN, timestamp) is not None @staticmethod - def _from_timestamp_string(timestamp: str) -> Optional[datetime]: + def _from_timestamp_string(timestamp: str) -> datetime.datetime | None: if not Timestamp._is_valid_timestamp_pattern(timestamp): return None try: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py new file mode 100644 index 0000000000000..8541431af54d3 --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py @@ -0,0 +1,115 @@ +import abc +import copy + +from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent +from localstack.services.stepfunctions.asl.component.state.state import CommonStateField +from localstack.services.stepfunctions.asl.component.state.state_continue_with import ( + ContinueWithNext, +) +from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment +from localstack.services.stepfunctions.asl.utils.encoding import to_json_str +from localstack.services.stepfunctions.backend.test_state.test_state_mock import ( + TestStateResponseReturn, + TestStateResponseThrow, + eval_mocked_response_throw, +) + + +class MockedBaseState[T: CommonStateField](abc.ABC): + is_single_state: bool + _wrapped: T + + def __init__(self, wrapped: T): + super().__init__() + self._wrapped = wrapped + self.apply_patches() + + def apply_patches(self): + self._apply_patches() + + original_eval_body = self._wrapped._eval_body + self._wrapped._eval_body = self.wrap_with_post_return( + original_eval_body, self.stop_execution + ) + + @abc.abstractmethod + def _apply_patches(self): ... + + @classmethod + def wrap(cls, state: T, is_single_state: bool = False) -> T: + cls.is_single_state = is_single_state + cls._wrapped = state + return cls(state)._wrapped + + def __getattr__(self, attr: str): + return getattr(self._wrapped, attr) + + @classmethod + def before_mock(self, env: TestStateEnvironment): + return + + @classmethod + def do_mock(self, env: TestStateEnvironment): + mocked_response = env.mock.get_next_result() + if not mocked_response: + return + + if isinstance(mocked_response, TestStateResponseThrow): + eval_mocked_response_throw(env, mocked_response) + return + + if isinstance(mocked_response, TestStateResponseReturn): + result_copy = copy.deepcopy(mocked_response.payload) + env.stack.append(result_copy) + + @classmethod + def after_mock(self, env: TestStateEnvironment): + return + + @classmethod + def wrap_with_mock(cls, original_method): + def wrapper(env: TestStateEnvironment, *args, **kwargs): + if not env.mock.is_mocked(): + original_method(env, *args, **kwargs) + return + + cls.before_mock(env) + try: + cls.do_mock(env) + finally: + cls.after_mock(env) + + return wrapper + + @staticmethod + def wrap_with_post_return(method, post_return_fn): + def wrapper(env: TestStateEnvironment, *args, **kwargs): + try: + method(env, *args, **kwargs) + finally: + post_return_fn(env) + + return wrapper + + @staticmethod + def _eval_with_inspect(component: EvalComponent, key: str): + if not component: + return + + eval_body_fn = component._eval_body + + def _update(env: TestStateEnvironment, *args, **kwargs): + # if inspectionData already populated, don't execute again + if key in env.inspection_data: + return + + eval_body_fn(env, *args, **kwargs) + result = env.stack[-1] + env.inspection_data[key] = to_json_str(result) + + component._eval_body = MockedBaseState.wrap_with_post_return(eval_body_fn, _update) + + def stop_execution(self, env: TestStateEnvironment): + if isinstance(self._wrapped.continue_with, ContinueWithNext): + if next_state := self._wrapped.continue_with.next_state: + env.set_choice_selected(next_state.name) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/common.py b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/common.py new file mode 100644 index 0000000000000..d56b009d7bee4 --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/common.py @@ -0,0 +1,89 @@ +from localstack.services.stepfunctions.asl.component.state.state import CommonStateField +from localstack.services.stepfunctions.asl.component.state.state_choice.state_choice import ( + StateChoice, +) +from localstack.services.stepfunctions.asl.component.state.state_continue_with import ( + ContinueWithEnd, +) +from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail +from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass +from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import ( + StateSucceed, +) +from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import ( + MockedBaseState, +) +from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment + + +class MockedCommonState(MockedBaseState[CommonStateField]): + def add_inspection_data(self, env: TestStateEnvironment): + state = self._wrapped + + if state.is_jsonpath_query_language(): + self._add_jsonpath_inspection_data(env) + + def _add_jsonpath_inspection_data(self, env: TestStateEnvironment): + + state = self._wrapped + + if not isinstance(state, StatePass): + if not self.is_single_state: + return + + if "afterInputPath" not in env.inspection_data: + env.inspection_data["afterInputPath"] = env.states.get_input() + return + + # If not a terminal state, only populate inspection data from pre-processor. + if not isinstance(self._wrapped.continue_with, ContinueWithEnd): + return + + if state.result: + # TODO: investigate interactions between these inspectionData field types. + # i.e parity tests shows that if "Result" is defined, 'afterInputPath' and 'afterParameters' + # cannot be present in the inspection data. + env.inspection_data.pop("afterInputPath", None) + env.inspection_data.pop("afterParameters", None) + + if "afterResultSelector" not in env.inspection_data: + env.inspection_data["afterResultSelector"] = state.result.result_obj + + if "afterResultPath" not in env.inspection_data: + env.inspection_data["afterResultPath"] = env.inspection_data.get( + "afterResultSelector", env.states.get_input() + ) + return + + if "afterInputPath" not in env.inspection_data: + env.inspection_data["afterInputPath"] = env.states.get_input() + + if "afterParameters" not in env.inspection_data: + env.inspection_data["afterParameters"] = env.inspection_data.get( + "afterInputPath", env.states.get_input() + ) + + if "afterResultSelector" not in env.inspection_data: + env.inspection_data["afterResultSelector"] = env.inspection_data["afterParameters"] + + if "afterResultPath" not in env.inspection_data: + env.inspection_data["afterResultPath"] = env.inspection_data.get( + "afterResultSelector", env.states.get_input() + ) + + def _apply_patches(self): + if not isinstance(self._wrapped, (StatePass, StateFail, StateChoice, StateSucceed)): + raise ValueError("Needs to be a Pass, Fail, Choice, or Succeed state.") + + original_eval_body = self.wrap_with_mock(self._wrapped._eval_body) + + def mock_eval_execution(env: TestStateEnvironment): + original_eval_body(env) + env.set_choice_selected(env.next_state_name) + + mock_eval_execution = self.wrap_with_post_return( + method=mock_eval_execution, + post_return_fn=self.add_inspection_data, + ) + + self._wrapped._eval_body = mock_eval_execution diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/execution.py b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/execution.py new file mode 100644 index 0000000000000..831f07b481f3d --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/execution.py @@ -0,0 +1,139 @@ +from collections.abc import Callable +from functools import partial + +from localstack.services.stepfunctions.asl.component.common.catch.catcher_outcome import ( + CatcherOutcomeCaught, +) +from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( + FailureEvent, +) +from localstack.services.stepfunctions.asl.component.common.query_language import ( + QueryLanguageMode, +) +from localstack.services.stepfunctions.asl.component.common.retry.retrier_decl import RetrierDecl +from localstack.services.stepfunctions.asl.component.common.retry.retrier_outcome import ( + RetrierOutcome, +) +from localstack.services.stepfunctions.asl.component.common.retry.retry_outcome import RetryOutcome +from localstack.services.stepfunctions.asl.component.state.state_execution.execute_state import ( + ExecutionState, +) +from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import ( + MockedBaseState, +) +from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment +from localstack.services.stepfunctions.asl.utils.encoding import to_json_str + + +class MockedStateExecution(MockedBaseState[ExecutionState]): + def add_inspection_data(self, env: TestStateEnvironment): + if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath: + if "afterResultSelector" not in env.inspection_data: + # HACK: A DistributedItemProcessorEvalInput is added to the stack and never popped off + # during an error case. So we need to check the inspected value is correct before + # adding it to our inspectionData. + if isinstance(env.stack[-1], (dict, str, int, float, list)): + env.inspection_data["afterResultSelector"] = to_json_str(env.stack[-1]) + + if catch := self._wrapped.catch: + for ind, catcher in enumerate(catch.catchers): + original_fn = catcher._eval_body + catcher._eval_body = self.with_catch_state_id(original_fn, ind) + + if retry := self._wrapped.retry: + for ind, retrier in enumerate(retry.retriers): + original_fn = retrier._eval_body + retrier._eval_body = self.with_retry_state_id(retrier, ind) + + def _apply_patches(self): + if not isinstance(self._wrapped, ExecutionState): + raise ValueError("Can only apply MockedStateExecution patches to an ExecutionState") + state = self._wrapped + + if state.query_language.query_language_mode == QueryLanguageMode.JSONPath: + self._eval_with_inspect(self._wrapped.input_path, "afterInputPath") + self._eval_with_inspect(self._wrapped.result_path, "afterResultPath") + + self._eval_with_inspect(self._wrapped.result_selector, "afterResultSelector") + original_eval_execution = self._wrapped._eval_execution + + if self._wrapped.catch: + original_fn = self._wrapped._handle_catch + self._wrapped._handle_catch = partial(self._handle_catch, original_fn) + + if self._wrapped.retry: + original_fn = self._wrapped._handle_retry + self._wrapped._handle_retry = partial(self._handle_retry, original_fn) + + self._wrapped._eval_execution = self.wrap_with_post_return( + method=original_eval_execution, + post_return_fn=self.add_inspection_data, + ) + + @staticmethod + def with_catch_state_id( + original_eval_body: Callable[[TestStateEnvironment], None], state_id: int + ) -> Callable[[TestStateEnvironment], None]: + def _wrapped(env: TestStateEnvironment): + original_eval_body(env) + + if isinstance(env.stack[-1], CatcherOutcomeCaught): + if not (error_details := env.inspection_data.get("errorDetails")): + error_details = env.inspection_data["errorDetails"] = {} + + error_details["catchIndex"] = state_id + + return _wrapped + + @staticmethod + def with_retry_state_id( + retrier: RetrierDecl, state_id: int + ) -> Callable[[TestStateEnvironment], None]: + original_retrier_eval_body = retrier._eval_body + + def _wrapped(env: TestStateEnvironment): + if (retry_count := env.mock._state_configuration.get("retrierRetryCount", 0)) > 0: + retrier.max_attempts._store_attempt_number(env, retry_count - 1) + + original_retrier_eval_body(env) + + if not (error_details := env.inspection_data.get("errorDetails")): + error_details = env.inspection_data["errorDetails"] = {} + + error_details["retryIndex"] = state_id + if env.stack[-1] == RetrierOutcome.Executed: + # TODO(gregfurman): Ideally, retryBackoffIntervalSeconds should be written to inspectionData + # within the retrier.backoff_rate decleration (perhaps at _access_next_multiplier). + rate = retrier.backoff_rate.rate + interval = retrier.interval_seconds.seconds + error_details["retryBackoffIntervalSeconds"] = int(interval * (rate**retry_count)) + + return _wrapped + + @staticmethod + def _handle_catch( + original_handle_catch: Callable[[TestStateEnvironment, FailureEvent], None], + env: TestStateEnvironment, + failure_event: FailureEvent, + ) -> None: + original_handle_catch(env, failure_event) + + spec: dict[str, str] = ExecutionState._construct_error_output_value(failure_event) + error, cause = spec.get("Error"), spec.get("Cause") + + env.set_caught_error(env.next_state_name, error, cause) + + @staticmethod + def _handle_retry( + original_handle_retry: Callable[[TestStateEnvironment, FailureEvent], RetryOutcome], + env: TestStateEnvironment, + failure_event: FailureEvent, + ) -> RetryOutcome: + res = original_handle_retry(env, failure_event) + + spec: dict[str, str] = ExecutionState._construct_error_output_value(failure_event) + error, cause = spec.get("Error"), spec.get("Cause") + + if res == RetryOutcome.CanRetry: + env.set_retriable_error(error, cause) + return res diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/map.py b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/map.py new file mode 100644 index 0000000000000..9c76e934cc336 --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/map.py @@ -0,0 +1,74 @@ +from localstack.services.stepfunctions.asl.component.common.error_name.states_error_name_type import ( + StatesErrorNameType, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import ( + StateMap, +) +from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import ( + MockedBaseState, +) +from localstack.services.stepfunctions.asl.component.test_state.state.execution import ( + MockedStateExecution, +) +from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment +from localstack.services.stepfunctions.backend.test_state.test_state_mock import ( + TestStateResponseThrow, +) + + +class MockedStateMap(MockedBaseState[StateMap]): + def add_inspection_data(self, env: TestStateEnvironment): + if tolerated_failure_percentage := env.inspection_data.get("toleratedFailurePercentage"): + env.inspection_data["toleratedFailurePercentage"] = float(tolerated_failure_percentage) + + if tolerated_failure_count := env.inspection_data.get("toleratedFailureCount"): + env.inspection_data["toleratedFailureCount"] = int(tolerated_failure_count) + + @classmethod + def before_mock(cls, env: TestStateEnvironment): + if not env.mock or not env.mock._state_configuration: + return + + if not cls._wrapped.catch and not cls._wrapped.retry: + return + + if failure_count := env.mock._state_configuration.get("mapIterationFailureCount"): + max_failure_count = ( + cls._wrapped.tolerated_failure_count_decl._eval_tolerated_failure_count(env) + ) + if failure_count > max_failure_count: + error_response = TestStateResponseThrow( + error=StatesErrorNameType.StatesExceedToleratedFailureThreshold.to_name(), + cause="The specified tolerated failure threshold was exceeded", + ) + env.mock.add_result(error_response) + return + + def _apply_patches(self): + self._wrapped = MockedStateExecution.wrap(self._wrapped) + + if self._wrapped.is_jsonpath_query_language(): + self._eval_with_inspect(self._wrapped.items_path, "afterInputPath") + self._eval_with_inspect(self._wrapped.item_selector, "afterItemsSelector") + + original_eval_max_concurrency = self._wrapped.max_concurrency_decl._eval_max_concurrency + original_iteration_component_eval_body = self._wrapped.iteration_component._eval_body + original_eval_execution = self._wrapped._eval_execution + + # HACK(gregfurman): Ideally we should be using the "$$.Map.Item.Index" to access each item of the + # mocked result list. This is turning out to be quite complicated, so instead just patch the + # StateMap's max concurrency decleration to always eval to '1' -- making the map run in serial. + def mock_max_concurrency(env: TestStateEnvironment) -> int: + # always set concurrency to 1 but inspection data is accurate to original + env.inspection_data["maxConcurrency"] = original_eval_max_concurrency(env) + return 1 + + self._wrapped._eval_execution = self.wrap_with_post_return( + method=original_eval_execution, + post_return_fn=self.add_inspection_data, + ) + + self._wrapped.max_concurrency_decl._eval_max_concurrency = mock_max_concurrency + self._wrapped.iteration_component._eval_body = self.wrap_with_mock( + original_iteration_component_eval_body + ) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/parallel.py b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/parallel.py new file mode 100644 index 0000000000000..caefc6e7fff96 --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/parallel.py @@ -0,0 +1,33 @@ +from localstack.services.stepfunctions.asl.component.state.state_execution.state_parallel.state_parallel import ( + StateParallel, +) +from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import ( + MockedBaseState, +) +from localstack.services.stepfunctions.asl.component.test_state.state.execution import ( + MockedStateExecution, +) +from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment + + +class MockedStateParallel(MockedBaseState[StateParallel]): + def _apply_patches(self): + self._wrapped = MockedStateExecution.wrap(self._wrapped) + + original_branches_eval_body = self._wrapped.branches._eval_body + original_eval_execution = self._wrapped._eval_execution + + self._wrapped._eval_execution = self.wrap_with_post_return( + method=original_eval_execution, + post_return_fn=self.add_inspection_data, + ) + + self._wrapped.branches._eval_body = self.wrap_with_mock(original_branches_eval_body) + + def add_inspection_data(self, env: TestStateEnvironment): + if self._wrapped.is_jsonpath_query_language(): + # AWS does not include afterInputPath in inspection data for Parallel states. + env.inspection_data.pop("afterInputPath", None) + else: + # AWS does not include afterArguments in inspection data for Parallel states. + env.inspection_data.pop("afterArguments", None) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/task.py b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/task.py new file mode 100644 index 0000000000000..6cfbb147fa8ac --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/asl/component/test_state/state/task.py @@ -0,0 +1,44 @@ +from localstack.services.stepfunctions.asl.component.common.query_language import ( + QueryLanguageMode, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service import ( + StateTaskService, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import ( + StateTask, +) +from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import ( + MockedBaseState, +) +from localstack.services.stepfunctions.asl.component.test_state.state.execution import ( + MockedStateExecution, +) +from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment +from localstack.services.stepfunctions.asl.utils.encoding import to_json_str + + +class MockedStateTask(MockedBaseState[StateTask]): + def add_inspection_data(self, env: TestStateEnvironment): + if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath: + if "afterParameters" not in env.inspection_data: + env.inspection_data["afterParameters"] = to_json_str(env.states.get_input()) + + def _apply_patches(self): + self._wrapped = MockedStateExecution.wrap(self._wrapped) + + if self._wrapped.query_language.query_language_mode == QueryLanguageMode.JSONPath: + self._eval_with_inspect(self._wrapped.parargs, "afterParameters") + + if isinstance(self._wrapped, StateTaskService): + self._wrapped._eval_service_task = self.wrap_with_mock(self._wrapped._eval_service_task) + + original_eval_execution = self._wrapped._eval_execution + + def mock_eval_execution(env: TestStateEnvironment, *args, **kwargs): + original_eval_execution(env, *args, **kwargs) + result = to_json_str(env.stack[-1]) + env.inspection_data["result"] = result + + self._wrapped._eval_execution = self.wrap_with_post_return( + mock_eval_execution, self.add_inspection_data + ) diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py b/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py index c5c27a05f4723..5a21ac217df04 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py @@ -1,7 +1,7 @@ import abc from collections import OrderedDict from threading import Event, Lock -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ActivityDoesNotExist, Arn from localstack.services.stepfunctions.backend.activity import Activity, ActivityTask @@ -26,10 +26,10 @@ def __init__(self, callback_id: CallbackId, output: str): class CallbackOutcomeFailure(CallbackOutcome): - error: Final[Optional[str]] - cause: Final[Optional[str]] + error: Final[str | None] + cause: Final[str | None] - def __init__(self, callback_id: CallbackId, error: Optional[str], cause: Optional[str]): + def __init__(self, callback_id: CallbackId, error: str | None, cause: str | None): super().__init__(callback_id=callback_id) self.error = error self.cause = cause @@ -85,20 +85,20 @@ class HeartbeatTimedOut(CallbackConsumerError): class ActivityTaskStartOutcome: - worker_name: Optional[str] + worker_name: str | None - def __init__(self, worker_name: Optional[str] = None): + def __init__(self, worker_name: str | None = None): self.worker_name = worker_name class ActivityTaskStartEndpoint: _next_activity_task_start_event: Final[Event] - _outcome: Optional[ActivityTaskStartOutcome] + _outcome: ActivityTaskStartOutcome | None def __init__(self): self._next_activity_task_start_event = Event() - def wait(self, timeout_seconds: float) -> Optional[ActivityTaskStartOutcome]: + def wait(self, timeout_seconds: float) -> ActivityTaskStartOutcome | None: self._next_activity_task_start_event.wait(timeout=timeout_seconds) return self._outcome @@ -110,9 +110,9 @@ def notify(self, activity_task: ActivityTaskStartOutcome) -> None: class CallbackEndpoint: callback_id: Final[CallbackId] _notify_event: Final[Event] - _outcome: Optional[CallbackOutcome] - consumer_error: Optional[CallbackConsumerError] - _heartbeat_endpoint: Optional[HeartbeatEndpoint] + _outcome: CallbackOutcome | None + consumer_error: CallbackConsumerError | None + _heartbeat_endpoint: HeartbeatEndpoint | None def __init__(self, callback_id: CallbackId): self.callback_id = callback_id @@ -144,11 +144,11 @@ def notify_heartbeat(self) -> bool: self._heartbeat_endpoint.notify() return True - def wait(self, timeout: Optional[float] = None) -> Optional[CallbackOutcome]: + def wait(self, timeout: float | None = None) -> CallbackOutcome | None: self._notify_event.wait(timeout=timeout) return self._outcome - def get_outcome(self) -> Optional[CallbackOutcome]: + def get_outcome(self) -> CallbackOutcome | None: return self._outcome def report(self, consumer_error: CallbackConsumerError) -> None: @@ -170,7 +170,7 @@ def get_activity_input(self) -> str: def get_activity_task_start_endpoint(self) -> ActivityTaskStartEndpoint: return self._activity_task_start_endpoint - def notify_activity_task_start(self, worker_name: Optional[str]) -> None: + def notify_activity_task_start(self, worker_name: str | None) -> None: self._activity_task_start_endpoint.notify(ActivityTaskStartOutcome(worker_name=worker_name)) @@ -196,7 +196,7 @@ def __init__(self, activity_store: dict[Arn, Activity]): self._activity_store = activity_store self._pool = OrderedDict() - def get(self, callback_id: CallbackId) -> Optional[CallbackEndpoint]: + def get(self, callback_id: CallbackId) -> CallbackEndpoint | None: return self._pool.get(callback_id) def add(self, callback_id: CallbackId) -> CallbackEndpoint: @@ -212,7 +212,7 @@ def add_activity_task( if callback_id in self._pool: raise ValueError("Duplicate callback token id value.") - maybe_activity: Optional[Activity] = self._activity_store.get(activity_arn) + maybe_activity: Activity | None = self._activity_store.get(activity_arn) if maybe_activity is None: raise ActivityDoesNotExist() @@ -232,7 +232,7 @@ def notify(self, callback_id: CallbackId, outcome: CallbackOutcome) -> bool: if callback_endpoint is None: return False - consumer_error: Optional[CallbackConsumerError] = callback_endpoint.consumer_error + consumer_error: CallbackConsumerError | None = callback_endpoint.consumer_error if consumer_error is not None: raise CallbackNotifyConsumerError(callback_consumer_error=consumer_error) @@ -244,7 +244,7 @@ def heartbeat(self, callback_id: CallbackId) -> bool: if callback_endpoint is None: return False - consumer_error: Optional[CallbackConsumerError] = callback_endpoint.consumer_error + consumer_error: CallbackConsumerError | None = callback_endpoint.consumer_error if consumer_error is not None: raise CallbackNotifyConsumerError(callback_consumer_error=consumer_error) diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py b/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py index ecb90be5b8d07..b4f997422b978 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py @@ -3,7 +3,7 @@ import copy import logging import threading -from typing import Any, Final, Optional +from typing import Any, Final, Optional, Self from localstack.aws.api.stepfunctions import ( Arn, @@ -34,31 +34,37 @@ from localstack.services.stepfunctions.asl.eval.states import ContextObjectData, States from localstack.services.stepfunctions.asl.eval.variable_store import VariableStore from localstack.services.stepfunctions.backend.activity import Activity -from localstack.services.stepfunctions.mocking.mock_config import MockedResponse, MockTestCase +from localstack.services.stepfunctions.local_mocking.mock_config import ( + LocalMockedResponse, + LocalMockTestCase, +) LOG = logging.getLogger(__name__) class Environment: _state_mutex: Final[threading.RLock()] - _program_state: Optional[ProgramState] + _program_state: ProgramState | None program_state_event: Final[threading.Event()] event_manager: EventManager event_history_context: Final[EventHistoryContext] - cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] + cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] aws_execution_details: Final[AWSExecutionDetails] execution_type: Final[StateMachineType] callback_pool_manager: CallbackPoolManager map_run_record_pool_manager: MapRunRecordPoolManager activity_store: Final[dict[Arn, Activity]] - mock_test_case: Optional[MockTestCase] = None + local_mock_test_case: LocalMockTestCase | None = None + next_local_mock_invocation_number: dict[ + str, int + ] # Tracks invocation count per state for mock cycling _frames: Final[list[Environment]] _is_frame: bool = False - heap: dict[str, Any] = dict() - stack: list[Any] = list() + heap: dict[str, Any] = {} + stack: list[Any] = [] states: Final[States] variable_store: Final[VariableStore] @@ -68,12 +74,12 @@ def __init__( execution_type: StateMachineType, context: ContextObjectData, event_history_context: EventHistoryContext, - cloud_watch_logging_session: Optional[CloudWatchLoggingSession], + cloud_watch_logging_session: CloudWatchLoggingSession | None, activity_store: dict[Arn, Activity], - variable_store: Optional[VariableStore] = None, - mock_test_case: Optional[MockTestCase] = None, + variable_store: VariableStore | None = None, + local_mock_test_case: LocalMockTestCase | None = None, ): - super(Environment, self).__init__() + super().__init__() self._state_mutex = threading.RLock() self._program_state = None self.program_state_event = threading.Event() @@ -89,21 +95,22 @@ def __init__( self.activity_store = activity_store - self.mock_test_case = mock_test_case + self.local_mock_test_case = local_mock_test_case + self.next_local_mock_invocation_number = {} - self._frames = list() + self._frames = [] self._is_frame = False - self.heap = dict() - self.stack = list() + self.heap = {} + self.stack = [] self.states = States(context=context) self.variable_store = variable_store or VariableStore() @classmethod def as_frame_of( - cls, env: Environment, event_history_frame_cache: Optional[EventHistoryContext] = None - ) -> Environment: - return Environment.as_inner_frame_of( + cls, env: Self, event_history_frame_cache: EventHistoryContext | None = None + ) -> Self: + return cls.as_inner_frame_of( env=env, variable_store=env.variable_store, event_history_frame_cache=event_history_frame_cache, @@ -112,10 +119,10 @@ def as_frame_of( @classmethod def as_inner_frame_of( cls, - env: Environment, + env: Self, variable_store: VariableStore, - event_history_frame_cache: Optional[EventHistoryContext] = None, - ) -> Environment: + event_history_frame_cache: EventHistoryContext | None = None, + ) -> Self: # Construct the frame's context object data. context = ContextObjectData( Execution=env.states.context_object.context_object_data["Execution"], @@ -138,8 +145,11 @@ def as_inner_frame_of( cloud_watch_logging_session=env.cloud_watch_logging_session, activity_store=env.activity_store, variable_store=variable_store, - mock_test_case=env.mock_test_case, ) + frame.local_mock_test_case = env.local_mock_test_case + frame.next_local_mock_invocation_number = ( + env.next_local_mock_invocation_number + ) # Share counter with parent frame._is_frame = True frame.event_manager = env.event_manager if "State" in env.states.context_object.context_object_data: @@ -148,13 +158,13 @@ def as_inner_frame_of( ) frame.callback_pool_manager = env.callback_pool_manager frame.map_run_record_pool_manager = env.map_run_record_pool_manager - frame.heap = dict() + frame.heap = {} frame._program_state = copy.deepcopy(env._program_state) return frame @property - def next_state_name(self) -> Optional[str]: - next_state_name: Optional[str] = None + def next_state_name(self) -> str | None: + next_state_name: str | None = None program_state = self._program_state if isinstance(program_state, ProgramRunning): next_state_name = program_state.next_state_name @@ -173,8 +183,8 @@ def next_state_name(self, next_state_name: str) -> None: ) @property - def next_field_name(self) -> Optional[str]: - next_field_name: Optional[str] = None + def next_field_name(self) -> str | None: + next_field_name: str | None = None program_state = self._program_state if isinstance(program_state, ProgramRunning): next_field_name = program_state.next_field_name @@ -220,7 +230,7 @@ def set_timed_out(self) -> None: self.program_state_event.set() self.program_state_event.clear() - def set_stop(self, stop_date: Timestamp, cause: Optional[str], error: Optional[str]) -> None: + def set_stop(self, stop_date: Timestamp, cause: str | None, error: str | None) -> None: with self._state_mutex: if isinstance(self._program_state, ProgramRunning): self._program_state = ProgramStopped(stop_date=stop_date, cause=cause, error=error) @@ -229,16 +239,14 @@ def set_stop(self, stop_date: Timestamp, cause: Optional[str], error: Optional[s self.program_state_event.set() self.program_state_event.clear() - def open_frame( - self, event_history_context: Optional[EventHistoryContext] = None - ) -> Environment: + def open_frame(self, event_history_context: EventHistoryContext | None = None) -> Environment: with self._state_mutex: frame = self.as_frame_of(env=self, event_history_frame_cache=event_history_context) self._frames.append(frame) return frame def open_inner_frame( - self, event_history_context: Optional[EventHistoryContext] = None + self, event_history_context: EventHistoryContext | None = None ) -> Environment: with self._state_mutex: variable_store = VariableStore.as_inner_scope_of( @@ -269,32 +277,41 @@ def is_frame(self) -> bool: def is_standard_workflow(self) -> bool: return self.execution_type == StateMachineType.STANDARD - def is_mocked_mode(self) -> bool: + def is_test_state_mocked_mode(self) -> bool: + return False + + def is_local_mocked_mode(self) -> bool: """ - Returns True if the state machine is running in mock mode and the current - state has a defined mock configuration in the target environment or frame; - otherwise, returns False. + Returns True if: + - the state machine is running in Step Functions Local mode + - the current state has a defined Local mock configuration in the target environment or frame + + Otherwise, returns False. """ return ( - self.mock_test_case is not None - and self.next_state_name in self.mock_test_case.state_mocked_responses + self.local_mock_test_case is not None + and self.next_state_name in self.local_mock_test_case.state_mocked_responses ) - def get_current_mocked_response(self) -> MockedResponse: - if not self.is_mocked_mode(): + def get_current_local_mocked_response(self) -> LocalMockedResponse: + if not self.is_local_mocked_mode(): raise RuntimeError( "Cannot retrieve mocked response: execution is not operating in mocked mode" ) state_name = self.next_state_name - state_mocked_responses: Optional = self.mock_test_case.state_mocked_responses.get( + state_mocked_responses: Optional = self.local_mock_test_case.state_mocked_responses.get( state_name ) if state_mocked_responses is None: - raise RuntimeError(f"No mocked response definition for state '{state_name}'") - retry_count = self.states.context_object.context_object_data["State"]["RetryCount"] - if len(state_mocked_responses.mocked_responses) <= retry_count: + raise RuntimeError(f"No Local mocked response definition for state '{state_name}'") + + # Get and increment the invocation counter for this state + invocation_number = self.next_local_mock_invocation_number.get(state_name, 0) + self.next_local_mock_invocation_number[state_name] = invocation_number + 1 + + if len(state_mocked_responses.mocked_responses) <= invocation_number: raise RuntimeError( - f"No mocked response definition for state '{state_name}' " - f"and retry number '{retry_count}'" + f"No Local mocked response definition for state '{state_name}' " + f"and invocation number '{invocation_number}'" ) - return state_mocked_responses.mocked_responses[retry_count] + return state_mocked_responses.mocked_responses[invocation_number] diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py b/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py index d053ae70e2187..badb613f23dca 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py @@ -1,4 +1,4 @@ -from typing import Any, Final, Optional +from typing import Any, Final from localstack.aws.api.stepfunctions import Arn, Definition, LongArn, StateMachineType @@ -18,12 +18,10 @@ class ExecutionDetails: arn: Final[LongArn] name: Final[str] role_arn: Final[Arn] - inpt: Final[Optional[Any]] + inpt: Final[Any | None] start_time: Final[str] - def __init__( - self, arn: LongArn, name: str, role_arn: Arn, inpt: Optional[Any], start_time: str - ): + def __init__(self, arn: LongArn, name: str, role_arn: Arn, inpt: Any | None, start_time: str): self.arn = arn self.name = name self.role_arn = role_arn diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py b/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py index 8a9ea31a47287..09cd0b64a8a5b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py @@ -4,7 +4,7 @@ import datetime import logging import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( HistoryEvent, @@ -61,19 +61,19 @@ class EventManager: _mutex: Final[threading.Lock] _event_id_gen: EventIdGenerator _history_event_list: Final[HistoryEventList] - _cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] + _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] - def __init__(self, cloud_watch_logging_session: Optional[CloudWatchLoggingSession] = None): + def __init__(self, cloud_watch_logging_session: CloudWatchLoggingSession | None = None): self._mutex = threading.Lock() self._event_id_gen = EventIdGenerator() - self._history_event_list = list() + self._history_event_list = [] self._cloud_watch_logging_session = cloud_watch_logging_session def add_event( self, context: EventHistoryContext, event_type: HistoryEventType, - event_details: Optional[EventDetails] = None, + event_details: EventDetails | None = None, timestamp: Timestamp = None, update_source_event_id: bool = True, ) -> int: @@ -105,7 +105,7 @@ def add_event( @staticmethod def _get_current_timestamp() -> datetime.datetime: - return datetime.datetime.now(tz=datetime.timezone.utc) + return datetime.datetime.now(tz=datetime.UTC) @staticmethod def _create_history_event( @@ -113,7 +113,7 @@ def _create_history_event( source_event_id: int, event_type: HistoryEventType, timestamp: datetime.datetime, - event_details: Optional[EventDetails], + event_details: EventDetails | None, ) -> HistoryEvent: history_event = HistoryEvent() if event_details is not None: @@ -130,7 +130,7 @@ def _publish_history_event( source_event_id: int, event_type: HistoryEventType, timestamp: datetime.datetime, - event_details: Optional[EventDetails], + event_details: EventDetails | None, ): history_event = self._create_history_event( event_id=event_id, @@ -154,7 +154,7 @@ def _create_history_log( event_type: HistoryEventType, timestamp: datetime.datetime, execution_arn: LongArn, - event_details: Optional[EventDetails], + event_details: EventDetails | None, include_execution_data: bool, ) -> HistoryLog: log = HistoryLog( @@ -184,7 +184,7 @@ def _publish_history_log( source_event_id: int, event_type: HistoryEventType, timestamp: datetime.datetime, - event_details: Optional[EventDetails], + event_details: EventDetails | None, ): # No logging session for this execution. if self._cloud_watch_logging_session is None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py b/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py index de504ad2a8255..4471c64d3a062 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py @@ -2,7 +2,7 @@ import logging from datetime import datetime -from typing import Final, NotRequired, Optional, TypedDict +from typing import Final, NotRequired, TypedDict from botocore.client import BaseClient from botocore.exceptions import ClientError @@ -111,7 +111,7 @@ def __init__( @staticmethod def extract_log_arn_parts_from( logging_configuration: LoggingConfiguration, - ) -> Optional[tuple[str, str, str]]: + ) -> tuple[str, str, str] | None: # Returns a tuple with: account_id, region, and log group name if the logging configuration # specifies a valid cloud watch log group arn, none otherwise. @@ -155,7 +155,7 @@ def extract_log_arn_parts_from( def from_logging_configuration( state_machine_arn: LongArn, logging_configuration: LoggingConfiguration, - ) -> Optional[CloudWatchLoggingConfiguration]: + ) -> CloudWatchLoggingConfiguration | None: log_level = logging_configuration.get("level", LogLevel.OFF) if log_level == LogLevel.OFF: return None diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py b/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py index 00f3af00cb82f..f904964fc662b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py @@ -1,5 +1,5 @@ import abc -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ExecutionFailedEventDetails, Timestamp @@ -12,16 +12,16 @@ class ProgramEnded(ProgramState): class ProgramStopped(ProgramState): - def __init__(self, stop_date: Timestamp, error: Optional[str], cause: Optional[str]): + def __init__(self, stop_date: Timestamp, error: str | None, cause: str | None): super().__init__() self.stop_date: Timestamp = stop_date - self.error: Optional[str] = error - self.cause: Optional[str] = cause + self.error: str | None = error + self.cause: str | None = cause class ProgramRunning(ProgramState): - _next_state_name: Optional[str] - _next_field_name: Optional[str] + _next_state_name: str | None + _next_field_name: str | None def __init__(self): super().__init__() @@ -53,9 +53,9 @@ def next_field_name(self, next_field_name) -> None: class ProgramError(ProgramState): - error: Final[Optional[ExecutionFailedEventDetails]] + error: Final[ExecutionFailedEventDetails | None] - def __init__(self, error: Optional[ExecutionFailedEventDetails]): + def __init__(self, error: ExecutionFailedEventDetails | None): super().__init__() self.error = error diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/states.py b/localstack-core/localstack/services/stepfunctions/asl/eval/states.py index 295e4149344e7..85aa0711fc1ec 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/states.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/states.py @@ -1,5 +1,5 @@ import copy -from typing import Any, Final, NotRequired, Optional, TypedDict +from typing import Any, Final, NotRequired, TypedDict from localstack.services.stepfunctions.asl.jsonata.jsonata import ( VariableDeclarations, @@ -18,7 +18,7 @@ class ExecutionData(TypedDict): Id: str - Input: Optional[Any] + Input: Any | None Name: str RoleArn: str StartTime: str # Format: ISO 8601. @@ -43,7 +43,7 @@ class ItemData(TypedDict): # Contains the index number for the array item that is being currently processed. Index: int # Contains the array item being processed. - Value: Optional[Any] + Value: Any | None class MapData(TypedDict): @@ -73,8 +73,8 @@ def update_task_token(self) -> str: class StatesData(TypedDict): input: Any context: ContextObjectData - result: NotRequired[Optional[Any]] - errorOutput: NotRequired[Optional[Any]] + result: NotRequired[Any | None] + errorOutput: NotRequired[Any | None] class States: @@ -82,12 +82,12 @@ class States: context_object: Final[ContextObject] def __init__(self, context: ContextObjectData): - input_value = context["Execution"]["Input"] + input_value = context.get("Execution", {}).get("Input", {}) self._states_data = StatesData(input=input_value, context=context) self.context_object = ContextObject(context_object=context) @staticmethod - def _extract(query: Optional[str], data: Any) -> Any: + def _extract(query: str | None, data: Any) -> Any: if query is None: result = data else: @@ -100,7 +100,7 @@ def extract(self, query: str) -> Any: jsonpath_states_query = "$." + query[1:] return self._extract(jsonpath_states_query, self._states_data) - def get_input(self, query: Optional[str] = None) -> Any: + def get_input(self, query: str | None = None) -> Any: return self._extract(query, self._states_data["input"]) def reset(self, input_value: Any) -> None: @@ -109,10 +109,10 @@ def reset(self, input_value: Any) -> None: self._states_data["result"] = None self._states_data["errorOutput"] = None - def get_context(self, query: Optional[str] = None) -> Any: + def get_context(self, query: str | None = None) -> Any: return self._extract(query, self._states_data["context"]) - def get_result(self, query: Optional[str] = None) -> Any: + def get_result(self, query: str | None = None) -> Any: if "result" not in self._states_data: raise RuntimeError("Illegal access to $states.result") return self._extract(query, self._states_data["result"]) @@ -121,7 +121,7 @@ def set_result(self, result: Any) -> Any: clone_result = copy.deepcopy(result) self._states_data["result"] = clone_result - def get_error_output(self, query: Optional[str] = None) -> Any: + def get_error_output(self, query: str | None = None) -> Any: if "errorOutput" not in self._states_data: raise RuntimeError("Illegal access to $states.errorOutput") return self._extract(query, self._states_data["errorOutput"]) @@ -131,7 +131,7 @@ def set_error_output(self, error_output: Any) -> None: self._states_data["errorOutput"] = clone_error_output def to_variable_declarations( - self, variable_references: Optional[set[VariableReference]] = None + self, variable_references: set[VariableReference] | None = None ) -> VariableDeclarations: if not variable_references or _STATES_PREFIX in variable_references: return encode_jsonata_variable_declarations( @@ -143,7 +143,7 @@ def to_variable_declarations( "result": _STATES_RESULT_PREFIX, "errorOutput": _STATES_ERROR_OUTPUT_PREFIX, } - sub_states = dict() + sub_states = {} for variable_reference in variable_references: if not candidate_sub_states: break diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py b/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py index 8db4b0e427cac..bb65c15fb7bde 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional +from typing import Self from localstack.aws.api.stepfunctions import Arn, InspectionData, StateMachineType from localstack.services.stepfunctions.asl.eval.environment import Environment @@ -16,14 +16,19 @@ ) from localstack.services.stepfunctions.asl.eval.states import ContextObjectData from localstack.services.stepfunctions.asl.eval.test_state.program_state import ( + ProgramCaughtError, ProgramChoiceSelected, + ProgramRetriable, ) from localstack.services.stepfunctions.asl.eval.variable_store import VariableStore +from localstack.services.stepfunctions.asl.utils.encoding import to_json_str from localstack.services.stepfunctions.backend.activity import Activity +from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock class TestStateEnvironment(Environment): inspection_data: InspectionData + mock: TestStateMock def __init__( self, @@ -32,7 +37,9 @@ def __init__( context: ContextObjectData, event_history_context: EventHistoryContext, activity_store: dict[Arn, Activity], - cloud_watch_logging_session: Optional[CloudWatchLoggingSession] = None, + cloud_watch_logging_session: CloudWatchLoggingSession | None = None, + variable_store: VariableStore | None = None, + mock: TestStateMock | None = None, ): super().__init__( aws_execution_details=aws_execution_details, @@ -41,30 +48,46 @@ def __init__( event_history_context=event_history_context, cloud_watch_logging_session=cloud_watch_logging_session, activity_store=activity_store, + variable_store=variable_store, ) self.inspection_data = InspectionData() + variables = variable_store.to_dict() + if variables: + self.inspection_data["variables"] = to_json_str(variables) + self.mock = mock + + def is_test_state_mocked_mode(self) -> bool: + return self.mock.is_mocked() + @classmethod def as_frame_of( cls, - env: TestStateEnvironment, - event_history_frame_cache: Optional[EventHistoryContext] = None, - ) -> Environment: - frame = super().as_frame_of(env=env, event_history_frame_cache=event_history_frame_cache) - frame.inspection_data = env.inspection_data - return frame + env: Self, + event_history_frame_cache: EventHistoryContext | None = None, + ) -> Self: + if (mocked_context := env.mock.get_context()) is not None: + env.states.context_object.context_object_data = mocked_context + return cls.as_inner_frame_of( + env=env, + variable_store=env.variable_store, + event_history_frame_cache=event_history_frame_cache, + ) + + @classmethod def as_inner_frame_of( cls, - env: TestStateEnvironment, + env: Self, variable_store: VariableStore, - event_history_frame_cache: Optional[EventHistoryContext] = None, - ) -> Environment: + event_history_frame_cache: EventHistoryContext | None = None, + ) -> Self: frame = super().as_inner_frame_of( env=env, event_history_frame_cache=event_history_frame_cache, variable_store=variable_store, ) frame.inspection_data = env.inspection_data + frame.mock = env.mock return frame def set_choice_selected(self, next_state_name: str) -> None: @@ -73,5 +96,24 @@ def set_choice_selected(self, next_state_name: str) -> None: self._program_state = ProgramChoiceSelected(next_state_name=next_state_name) self.program_state_event.set() self.program_state_event.clear() - else: - raise RuntimeError("Cannot set choice selected for non running ProgramState.") + + def set_caught_error(self, next_state_name: str, error: str, cause: str) -> None: + with self._state_mutex: + if isinstance(self._program_state, ProgramRunning): + self._program_state = ProgramCaughtError( + next_state_name=next_state_name, + error=error, + cause=cause, + ) + self.program_state_event.set() + self.program_state_event.clear() + + def set_retriable_error(self, error: str, cause: str) -> None: + with self._state_mutex: + if isinstance(self._program_state, ProgramRunning): + self._program_state = ProgramRetriable( + error=error, + cause=cause, + ) + self.program_state_event.set() + self.program_state_event.clear() diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/program_state.py b/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/program_state.py index d9576ceda285b..a7755633d3198 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/program_state.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/program_state.py @@ -9,3 +9,25 @@ class ProgramChoiceSelected(ProgramState): def __init__(self, next_state_name: str): super().__init__() self.next_state_name = next_state_name + + +class ProgramCaughtError(ProgramState): + next_state_name: Final[str] + error: Final[str] + cause: Final[str] + + def __init__(self, next_state_name: str, error: str, cause: str): + super().__init__() + self.next_state_name = next_state_name + self.error = error + self.cause = cause + + +class ProgramRetriable(ProgramState): + error: Final[str] + cause: Final[str] + + def __init__(self, error: str, cause: str): + super().__init__() + self.error = error + self.cause = cause diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py b/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py index 055fb9355ca5c..a17d557e990f7 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.jsonata.jsonata import ( VariableDeclarations, @@ -51,16 +51,20 @@ class VariableStore: _declaration_tracing: Final[set[str]] - _outer_variable_declaration_cache: Optional[VariableDeclarations] - _variable_declarations_cache: Optional[VariableDeclarations] + _outer_variable_declaration_cache: VariableDeclarations | None + _variable_declarations_cache: VariableDeclarations | None - def __init__(self): - self._outer_scope = dict() - self._inner_scope = dict() + def __init__(self, variables: dict | None = None): + self._outer_scope = {} + self._inner_scope = {} self._declaration_tracing = set() self._outer_variable_declaration_cache = None self._variable_declarations_cache = None + if variables: + for key, value in variables.items(): + self.set(key, value) + @classmethod def as_inner_scope_of(cls, outer_variable_store: VariableStore) -> VariableStore: inner_variable_store = cls() @@ -73,7 +77,7 @@ def reset_tracing(self) -> None: # TODO: add typing when this available in service init. def get_assigned_variables(self) -> dict[str, str]: - assigned_variables: dict[str, str] = dict() + assigned_variables: dict[str, str] = {} for traced_declaration_identifier in self._declaration_tracing: traced_declaration_value = self.get(traced_declaration_identifier) if isinstance(traced_declaration_value, str): @@ -85,6 +89,14 @@ def get_assigned_variables(self) -> dict[str, str]: assigned_variables[traced_declaration_identifier] = traced_declaration_value_json_str return assigned_variables + def to_dict(self) -> dict[str, str]: + assigned_variables: dict[str, str] = {} + for traced_declaration_identifier in self._declaration_tracing: + assigned_variables[traced_declaration_identifier] = self.get( + traced_declaration_identifier + ) + return assigned_variables + def get(self, variable_identifier: VariableIdentifier) -> VariableValue: if variable_identifier in self._inner_scope: return self._inner_scope[variable_identifier] diff --git a/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py b/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py index 1fa837f68815e..fe327bd829127 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py +++ b/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py @@ -2,11 +2,12 @@ import json import re +from collections.abc import Callable from pathlib import Path -from typing import Any, Callable, Final, Optional +from typing import Any, Final import jpype -import jpype.imports +import jpype.imports # noqa # Required for JVM Java class imports from localstack.services.stepfunctions.asl.utils.encoding import to_json_str from localstack.services.stepfunctions.packages import jpype_jsonata_package @@ -16,9 +17,29 @@ VariableReference = str VariableDeclarations = str -_PATTERN_VARIABLE_REFERENCE: Final[re.Pattern] = re.compile( - r"\$\$|\$[a-zA-Z0-9_$]+(?:\.[a-zA-Z0-9_][a-zA-Z0-9_$]*)*|\$" + +# TODO: move the extraction logic to a formal ANTLR-base parser, as done with legacy +# Intrinsic Functions in package localstack.services.stepfunctions.asl.antlr +# with grammars ASLIntrinsicLexer and ASLIntrinsicParser, later used by upstream +# logics such as in: +# localstack.services.stepfunctions.asl.parse.intrinsic.preprocessor.Preprocessor +_PATTERN_VARIABLE_REFERENCE = re.compile( + # 1) Non-capturing branch for JSONata regex literal + # /.../ (slash delimited), allowing escaped slashes \/ + r"(?:\/(?:\\.|[^\\/])*\/[a-zA-Z]*)" + r"|" + # 2) Non-capturing branch for JSONata string literal: + # "..." (double quotes) or '...' (single quotes), + # allowing escapes + r"(?:\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\')" + r"|" + # 3) Capturing branch for $identifier[.prop…] + # Requires at least one identifier character after $, so bare $ (the + # JSONata context variable used in filter predicates like [$ = 1]) is + # never captured. $$ is captured but filtered out downstream. + r"(\$[A-Za-z0-9_$]+(?:\.[A-Za-z0-9_][A-Za-z0-9_$]*)*)" ) + _ILLEGAL_VARIABLE_REFERENCES: Final[set[str]] = {"$", "$$"} _VARIABLE_REFERENCE_ASSIGNMENT_OPERATOR: Final[str] = ":=" _VARIABLE_REFERENCE_ASSIGNMENT_STOP_SYMBOL: Final[str] = ";" @@ -28,9 +49,9 @@ class JSONataException(Exception): error: Final[str] - details: Optional[str] + details: str | None - def __init__(self, error: str, details: Optional[str]): + def __init__(self, error: str, details: str | None): self.error = error self.details = details @@ -88,7 +109,7 @@ def eval_jsonata(self, jsonata_expression: JSONataExpression) -> Any: # Lazy initialization of the `eval_jsonata` function pointer. # This ensures the JVM is only started when JSONata functionality is needed. -_eval_jsonata: Optional[Callable[[JSONataExpression], Any]] = None +_eval_jsonata: Callable[[JSONataExpression], Any] | None = None def eval_jsonata_expression(jsonata_expression: JSONataExpression) -> Any: @@ -111,19 +132,23 @@ def extract_jsonata_variable_references( ) -> set[VariableReference]: if not jsonata_expression: return set() - variable_references: list[VariableReference] = _PATTERN_VARIABLE_REFERENCE.findall( - jsonata_expression - ) + # Extract all recognised patterns. + all_references: list[Any] = _PATTERN_VARIABLE_REFERENCE.findall(jsonata_expression) + # Filter non-empty patterns (this includes consumed blocks such as jsonata + # regular expressions, delimited between non-escaped slashes). + variable_references: set[VariableReference] = { + reference for reference in all_references if reference and isinstance(reference, str) + } for variable_reference in variable_references: if variable_reference in _ILLEGAL_VARIABLE_REFERENCES: raise IllegalJSONataVariableReference(variable_reference=variable_reference) - return set(variable_references) + return variable_references def encode_jsonata_variable_declarations( bindings: dict[VariableReference, Any], ) -> VariableDeclarations: - declarations_parts: list[str] = list() + declarations_parts: list[str] = [] for variable_reference, value in bindings.items(): if isinstance(value, str): value_str_lit = f'"{value}"' diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py b/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py index 29c9c93f53bf5..30f9a07883072 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py @@ -15,7 +15,7 @@ class SyntaxErrorListener(ErrorListener): def __init__(self): super().__init__() - self.errors = list() + self.errors = [] def syntaxError(self, recognizer, offending_symbol, line, column, message, exception): log_parts = [f"line {line}:{column}"] diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py b/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py index c25f0345b1b0d..63b3dbeb5423c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py @@ -1,5 +1,4 @@ import re -from typing import Optional from antlr4.tree.Tree import ParseTree, TerminalNodeImpl @@ -86,9 +85,9 @@ def visitFunc_arg_bool(self, ctx: ASLIntrinsicParser.Func_arg_boolContext) -> Ar return ArgumentLiteral(definition_value=bool_val) def visitFunc_arg_list(self, ctx: ASLIntrinsicParser.Func_arg_listContext) -> ArgumentList: - arguments: list[Argument] = list() + arguments: list[Argument] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, Argument): arguments.append(cmp) return ArgumentList(arguments=arguments) diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py b/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py index 93132888e920b..94aaa897b5fa7 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py @@ -1,19 +1,23 @@ import json import logging -from typing import Any, Optional +from typing import Any from antlr4 import ParserRuleContext from antlr4.tree.Tree import ParseTree, TerminalNodeImpl from localstack.services.stepfunctions.asl.antlr.runtime.ASLLexer import ASLLexer from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser -from localstack.services.stepfunctions.asl.antlr.runtime.ASLParserVisitor import ASLParserVisitor +from localstack.services.stepfunctions.asl.antlr.runtime.ASLParserVisitor import ( + ASLParserVisitor, +) from localstack.services.stepfunctions.asl.antlt4utils.antlr4utils import ( from_string_literal, is_production, is_terminal, ) -from localstack.services.stepfunctions.asl.component.common.assign.assign_decl import AssignDecl +from localstack.services.stepfunctions.asl.component.common.assign.assign_decl import ( + AssignDecl, +) from localstack.services.stepfunctions.asl.component.common.assign.assign_decl_binding import ( AssignDeclBinding, ) @@ -36,9 +40,15 @@ AssignTemplateValueTerminalLit, AssignTemplateValueTerminalStringJSONata, ) -from localstack.services.stepfunctions.asl.component.common.catch.catch_decl import CatchDecl -from localstack.services.stepfunctions.asl.component.common.catch.catcher_decl import CatcherDecl -from localstack.services.stepfunctions.asl.component.common.catch.catcher_props import CatcherProps +from localstack.services.stepfunctions.asl.component.common.catch.catch_decl import ( + CatchDecl, +) +from localstack.services.stepfunctions.asl.component.common.catch.catcher_decl import ( + CatcherDecl, +) +from localstack.services.stepfunctions.asl.component.common.catch.catcher_props import ( + CatcherProps, +) from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( CustomErrorName, @@ -46,7 +56,9 @@ from localstack.services.stepfunctions.asl.component.common.error_name.error_equals_decl import ( ErrorEqualsDecl, ) -from localstack.services.stepfunctions.asl.component.common.error_name.error_name import ErrorName +from localstack.services.stepfunctions.asl.component.common.error_name.error_name import ( + ErrorName, +) from localstack.services.stepfunctions.asl.component.common.error_name.states_error_name import ( StatesErrorName, ) @@ -79,10 +91,18 @@ Parameters, Parargs, ) -from localstack.services.stepfunctions.asl.component.common.path.input_path import InputPath -from localstack.services.stepfunctions.asl.component.common.path.items_path import ItemsPath -from localstack.services.stepfunctions.asl.component.common.path.output_path import OutputPath -from localstack.services.stepfunctions.asl.component.common.path.result_path import ResultPath +from localstack.services.stepfunctions.asl.component.common.path.input_path import ( + InputPath, +) +from localstack.services.stepfunctions.asl.component.common.path.items_path import ( + ItemsPath, +) +from localstack.services.stepfunctions.asl.component.common.path.output_path import ( + OutputPath, +) +from localstack.services.stepfunctions.asl.component.common.path.result_path import ( + ResultPath, +) from localstack.services.stepfunctions.asl.component.common.payload.payloadvalue.payload_value import ( PayloadValue, ) @@ -116,7 +136,9 @@ QueryLanguage, QueryLanguageMode, ) -from localstack.services.stepfunctions.asl.component.common.result_selector import ResultSelector +from localstack.services.stepfunctions.asl.component.common.result_selector import ( + ResultSelector, +) from localstack.services.stepfunctions.asl.component.common.retry.backoff_rate_decl import ( BackoffRateDecl, ) @@ -133,9 +155,15 @@ from localstack.services.stepfunctions.asl.component.common.retry.max_delay_seconds_decl import ( MaxDelaySecondsDecl, ) -from localstack.services.stepfunctions.asl.component.common.retry.retrier_decl import RetrierDecl -from localstack.services.stepfunctions.asl.component.common.retry.retrier_props import RetrierProps -from localstack.services.stepfunctions.asl.component.common.retry.retry_decl import RetryDecl +from localstack.services.stepfunctions.asl.component.common.retry.retrier_decl import ( + RetrierDecl, +) +from localstack.services.stepfunctions.asl.component.common.retry.retrier_props import ( + RetrierProps, +) +from localstack.services.stepfunctions.asl.component.common.retry.retry_decl import ( + RetryDecl, +) from localstack.services.stepfunctions.asl.component.common.string.string_expression import ( StringContextPath, StringExpression, @@ -291,15 +319,23 @@ Error, ErrorPath, ) -from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail -from localstack.services.stepfunctions.asl.component.state.state_pass.result import Result -from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass +from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import ( + StateFail, +) +from localstack.services.stepfunctions.asl.component.state.state_pass.result import ( + Result, +) +from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import ( + StatePass, +) from localstack.services.stepfunctions.asl.component.state.state_props import StateProps from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import ( StateSucceed, ) from localstack.services.stepfunctions.asl.component.state.state_type import StateType -from localstack.services.stepfunctions.asl.component.state.state_wait.state_wait import StateWait +from localstack.services.stepfunctions.asl.component.state.state_wait.state_wait import ( + StateWait, +) from localstack.services.stepfunctions.asl.component.state.state_wait.wait_function.seconds import ( Seconds, SecondsJSONata, @@ -311,18 +347,24 @@ Timestamp, TimestampPath, ) -from localstack.services.stepfunctions.asl.parse.intrinsic.intrinsic_parser import IntrinsicParser +from localstack.services.stepfunctions.asl.parse.intrinsic.intrinsic_parser import ( + IntrinsicParser, +) from localstack.services.stepfunctions.asl.parse.typed_props import TypedProps LOG = logging.getLogger(__name__) class Preprocessor(ASLParserVisitor): - _query_language_per_scope: list[QueryLanguage] = list() + def __init__(self): + self._query_language_per_scope: list[QueryLanguage] = [] def _get_current_query_language(self) -> QueryLanguage: return self._query_language_per_scope[-1] + def _get_top_level_query_language(self) -> QueryLanguage: + return self._query_language_per_scope[0] + def _open_query_language_scope(self, parse_tree: ParseTree) -> None: production = is_production(parse_tree) if production is None: @@ -347,11 +389,11 @@ def _open_query_language_scope(self, parse_tree: ParseTree) -> None: query_language = QueryLanguage() # Otherwise, check for logical conflicts and add the latest or inherited value to as the next scope. else: - current_query_language = self._get_current_query_language() + top_query_language = self._get_top_level_query_language() if query_language is None: - query_language = current_query_language + query_language = top_query_language if ( - current_query_language.query_language_mode == QueryLanguageMode.JSONata + top_query_language.query_language_mode == QueryLanguageMode.JSONata and query_language.query_language_mode == QueryLanguageMode.JSONPath ): raise ValueError( @@ -376,7 +418,7 @@ def _raise_if_query_language_is_not( ) @staticmethod - def _inner_string_of(parser_rule_context: ParserRuleContext) -> Optional[str]: + def _inner_string_of(parser_rule_context: ParserRuleContext) -> str | None: if is_terminal(parser_rule_context, ASLLexer.NULL): return None inner_str = parser_rule_context.getText() @@ -408,7 +450,7 @@ def visitStartat_decl(self, ctx: ASLParser.Startat_declContext) -> StartAt: def visitStates_decl(self, ctx: ASLParser.States_declContext) -> States: states = States() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, CommonStateField): # TODO move check to setter or checker layer? if cmp.name in states.states: @@ -429,7 +471,7 @@ def visitResource_decl(self, ctx: ASLParser.Resource_declContext) -> Resource: def visitEnd_decl(self, ctx: ASLParser.End_declContext) -> End: bool_child: ParseTree = ctx.children[-1] - bool_term: Optional[TerminalNodeImpl] = is_terminal(bool_child) + bool_term: TerminalNodeImpl | None = is_terminal(bool_child) if bool_term is None: raise ValueError(f"Could not derive End from declaration context '{ctx.getText()}'") bool_term_rule: int = bool_term.getSymbol().type @@ -448,7 +490,7 @@ def visitResult_path_decl(self, ctx: ASLParser.Result_path_declContext) -> Resul return ResultPath(result_path_src=inner_str) def visitInput_path_decl(self, ctx: ASLParser.Input_path_declContext) -> InputPath: - string_sampler: Optional[StringSampler] = None + string_sampler: StringSampler | None = None if not is_terminal(pt=ctx.children[-1], token_type=ASLLexer.NULL): string_sampler: StringSampler = self.visitString_sampler(ctx.string_sampler()) return InputPath(string_sampler=string_sampler) @@ -457,7 +499,7 @@ def visitOutput_path_decl(self, ctx: ASLParser.Output_path_declContext) -> Outpu self._raise_if_query_language_is_not( query_language_mode=QueryLanguageMode.JSONPath, ctx=ctx ) - string_sampler: Optional[StringSampler] = None + string_sampler: StringSampler | None = None if is_production(ctx.children[-1], ASLParser.RULE_string_sampler): string_sampler: StringSampler = self.visitString_sampler(ctx.children[-1]) return OutputPath(string_sampler=string_sampler) @@ -527,7 +569,7 @@ def visitResult_selector_decl( def visitBranches_decl(self, ctx: ASLParser.Branches_declContext) -> BranchesDecl: programs: list[Program] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, Program): programs.append(cmp) return BranchesDecl(programs=programs) @@ -536,7 +578,7 @@ def visitState_decl_body(self, ctx: ASLParser.State_decl_bodyContext) -> StatePr self._open_query_language_scope(ctx) state_props = StateProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) state_props.add(cmp) if state_props.get(QueryLanguage) is None: state_props.add(self._get_current_query_language()) @@ -583,7 +625,7 @@ def _common_state_field_of(state_props: StateProps) -> CommonStateField: def visitCondition_lit(self, ctx: ASLParser.Condition_litContext) -> ConditionJSONataLit: self._raise_if_query_language_is_not(query_language_mode=QueryLanguageMode.JSONata, ctx=ctx) bool_child: ParseTree = ctx.children[-1] - bool_term: Optional[TerminalNodeImpl] = is_terminal(bool_child) + bool_term: TerminalNodeImpl | None = is_terminal(bool_child) if bool_term is None: raise ValueError( f"Could not derive boolean literal from declaration context '{ctx.getText()}'." @@ -651,7 +693,7 @@ def visitChoice_operator( self._raise_if_query_language_is_not( query_language_mode=QueryLanguageMode.JSONPath, ctx=ctx ) - pt: Optional[TerminalNodeImpl] = is_terminal(ctx.children[0]) + pt: TerminalNodeImpl | None = is_terminal(ctx.children[0]) if not pt: raise ValueError(f"Could not derive ChoiceOperator in block '{ctx.getText()}'.") return ComparisonComposite.ChoiceOp(pt.symbol.type) @@ -660,9 +702,9 @@ def visitComparison_composite( self, ctx: ASLParser.Comparison_compositeContext ) -> ComparisonComposite: choice_op: ComparisonComposite.ChoiceOp = self.visit(ctx.choice_operator()) - rules: list[ChoiceRule] = list() + rules: list[ChoiceRule] = [] for child in ctx.children[1:]: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if not cmp: continue elif isinstance(cmp, ChoiceRule): @@ -685,7 +727,7 @@ def visitChoice_rule_comparison_composite( ) -> ChoiceRule: composite_stmts = ComparisonCompositeProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) composite_stmts.add(cmp) return ChoiceRule( comparison=composite_stmts.get( @@ -705,7 +747,7 @@ def visitChoice_rule_comparison_variable( ) -> ChoiceRule: comparison_stmts = StateProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) comparison_stmts.add(cmp) if self._is_query_language(query_language_mode=QueryLanguageMode.JSONPath): variable: Variable = comparison_stmts.get( @@ -746,9 +788,9 @@ def visitChoice_rule_comparison_variable( ) def visitChoices_decl(self, ctx: ASLParser.Choices_declContext) -> ChoicesDecl: - rules: list[ChoiceRule] = list() + rules: list[ChoiceRule] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if not cmp: continue elif isinstance(cmp, ChoiceRule): @@ -966,7 +1008,7 @@ def visitCsv_header_location_decl( return CSVHeaderLocation(csv_header_location_value=value) def visitCsv_headers_decl(self, ctx: ASLParser.Csv_headers_declContext) -> CSVHeaders: - csv_headers: list[str] = list() + csv_headers: list[str] = [] for child in ctx.children[3:-1]: maybe_str = is_production(pt=child, rule_index=ASLParser.RULE_string_literal) if maybe_str is not None: @@ -1074,9 +1116,9 @@ def visitResult_writer_decl(self, ctx: ASLParser.Result_writer_declContext) -> R return ResultWriter(resource=resource, parargs=parargs) def visitRetry_decl(self, ctx: ASLParser.Retry_declContext) -> RetryDecl: - retriers: list[RetrierDecl] = list() + retriers: list[RetrierDecl] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, RetrierDecl): retriers.append(cmp) return RetryDecl(retriers=retriers) @@ -1084,7 +1126,7 @@ def visitRetry_decl(self, ctx: ASLParser.Retry_declContext) -> RetryDecl: def visitRetrier_decl(self, ctx: ASLParser.Retrier_declContext) -> RetrierDecl: props = RetrierProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) props.add(cmp) return RetrierDecl.from_retrier_props(props=props) @@ -1092,7 +1134,7 @@ def visitRetrier_stmt(self, ctx: ASLParser.Retrier_stmtContext): return self.visit(ctx.children[0]) def visitError_equals_decl(self, ctx: ASLParser.Error_equals_declContext) -> ErrorEqualsDecl: - error_names: list[ErrorName] = list() + error_names: list[ErrorName] = [] for child in ctx.children: cmp = self.visit(child) if isinstance(cmp, ErrorName): @@ -1103,7 +1145,7 @@ def visitError_name(self, ctx: ASLParser.Error_nameContext) -> ErrorName: pt = ctx.children[0] # Case: StatesErrorName. - prc: Optional[ParserRuleContext] = is_production( + prc: ParserRuleContext | None = is_production( pt=pt, rule_index=ASLParser.RULE_states_error_name ) if prc: @@ -1114,7 +1156,7 @@ def visitError_name(self, ctx: ASLParser.Error_nameContext) -> ErrorName: return CustomErrorName(error_name=error_name) def visitStates_error_name(self, ctx: ASLParser.States_error_nameContext) -> StatesErrorName: - pt: Optional[TerminalNodeImpl] = is_terminal(ctx.children[0]) + pt: TerminalNodeImpl | None = is_terminal(ctx.children[0]) if not pt: raise ValueError(f"Could not derive ErrorName in block '{ctx.getText()}'.") states_error_name_type = StatesErrorNameType(pt.symbol.type) @@ -1140,15 +1182,15 @@ def visitJitter_strategy_decl( self, ctx: ASLParser.Jitter_strategy_declContext ) -> JitterStrategyDecl: last_child: ParseTree = ctx.children[-1] - strategy_child: Optional[TerminalNodeImpl] = is_terminal(last_child) + strategy_child: TerminalNodeImpl | None = is_terminal(last_child) strategy_value = strategy_child.getSymbol().type jitter_strategy = JitterStrategy(strategy_value) return JitterStrategyDecl(jitter_strategy=jitter_strategy) def visitCatch_decl(self, ctx: ASLParser.Catch_declContext) -> CatchDecl: - catchers: list[CatcherDecl] = list() + catchers: list[CatcherDecl] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, CatcherDecl): catchers.append(cmp) return CatchDecl(catchers=catchers) @@ -1156,7 +1198,7 @@ def visitCatch_decl(self, ctx: ASLParser.Catch_declContext) -> CatchDecl: def visitCatcher_decl(self, ctx: ASLParser.Catcher_declContext) -> CatcherDecl: props = CatcherProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) props.add(cmp) if self._is_query_language(QueryLanguageMode.JSONPath) and not props.get(ResultPath): props.add(CatcherDecl.DEFAULT_RESULT_PATH) @@ -1172,7 +1214,7 @@ def visitPayload_value_int(self, ctx: ASLParser.Payload_value_intContext) -> Pay def visitPayload_value_bool(self, ctx: ASLParser.Payload_value_boolContext) -> PayloadValueBool: bool_child: ParseTree = ctx.children[0] - bool_term: Optional[TerminalNodeImpl] = is_terminal(bool_child) + bool_term: TerminalNodeImpl | None = is_terminal(bool_child) if bool_term is None: raise ValueError( f"Could not derive PayloadValueBool from declaration context '{ctx.getText()}'." @@ -1208,17 +1250,17 @@ def visitPayload_binding_value( return PayloadBindingValue(field=string_literal.literal_value, payload_value=payload_value) def visitPayload_arr_decl(self, ctx: ASLParser.Payload_arr_declContext) -> PayloadArr: - payload_values: list[PayloadValue] = list() + payload_values: list[PayloadValue] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, PayloadValue): payload_values.append(cmp) return PayloadArr(payload_values=payload_values) def visitPayload_tmpl_decl(self, ctx: ASLParser.Payload_tmpl_declContext) -> PayloadTmpl: - payload_bindings: list[PayloadBinding] = list() + payload_bindings: list[PayloadBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, PayloadBinding): payload_bindings.append(cmp) return PayloadTmpl(payload_bindings=payload_bindings) @@ -1231,7 +1273,7 @@ def visitProgram_decl(self, ctx: ASLParser.Program_declContext) -> Program: self._open_query_language_scope(ctx) props = TypedProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) props.add(cmp) if props.get(QueryLanguage) is None: props.add(self._get_current_query_language()) @@ -1312,9 +1354,9 @@ def visitAssign_template_value(self, ctx: ASLParser.Assign_template_valueContext def visitAssign_template_value_array( self, ctx: ASLParser.Assign_template_value_arrayContext ) -> AssignTemplateValueArray: - values: list[AssignTemplateValue] = list() + values: list[AssignTemplateValue] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, AssignTemplateValue): values.append(cmp) return AssignTemplateValueArray(values=values) @@ -1322,9 +1364,9 @@ def visitAssign_template_value_array( def visitAssign_template_value_object( self, ctx: ASLParser.Assign_template_value_objectContext ) -> AssignTemplateValueObject: - bindings: list[AssignTemplateBinding] = list() + bindings: list[AssignTemplateBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, AssignTemplateBinding): bindings.append(cmp) return AssignTemplateValueObject(bindings=bindings) @@ -1359,9 +1401,9 @@ def visitAssign_decl_binding( def visitAssign_decl_body( self, ctx: ASLParser.Assign_decl_bodyContext ) -> list[AssignDeclBinding]: - bindings: list[AssignDeclBinding] = list() + bindings: list[AssignDeclBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, AssignDeclBinding): bindings.append(cmp) return bindings @@ -1414,9 +1456,9 @@ def visitJsonata_template_value( def visitJsonata_template_value_array( self, ctx: ASLParser.Jsonata_template_value_arrayContext ) -> JSONataTemplateValueArray: - values: list[JSONataTemplateValue] = list() + values: list[JSONataTemplateValue] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, JSONataTemplateValue): values.append(cmp) return JSONataTemplateValueArray(values=values) @@ -1424,9 +1466,9 @@ def visitJsonata_template_value_array( def visitJsonata_template_value_object( self, ctx: ASLParser.Jsonata_template_value_objectContext ) -> JSONataTemplateValueObject: - bindings: list[JSONataTemplateBinding] = list() + bindings: list[JSONataTemplateBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, JSONataTemplateBinding): bindings.append(cmp) return JSONataTemplateValueObject(bindings=bindings) @@ -1507,5 +1549,6 @@ def visitString_intrinsic_function( intrinsic_function_derivation = ctx.STRINGINTRINSICFUNC().getText()[1:-1] function, _ = IntrinsicParser.parse(intrinsic_function_derivation) return StringIntrinsicFunction( - intrinsic_function_derivation=intrinsic_function_derivation, function=function + intrinsic_function_derivation=intrinsic_function_derivation, + function=function, ) diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py b/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py index d4c4b8b3ef582..3616984ea08fa 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py @@ -15,7 +15,9 @@ class TestStateAmazonStateLanguageParser(AmazonStateLanguageParser): @staticmethod - def parse(definition: str) -> tuple[EvalComponent, ParserRuleContext]: + def parse( + definition: str, state_name: str | None = None + ) -> tuple[EvalComponent, ParserRuleContext]: # Attempt to build the AST and look out for syntax errors. syntax_error_listener = SyntaxErrorListener() @@ -25,15 +27,14 @@ def parse(definition: str) -> tuple[EvalComponent, ParserRuleContext]: parser = ASLParser(stream) parser.removeErrorListeners() parser.addErrorListener(syntax_error_listener) - # Unlike the main Program parser, TestState parsing occurs at a state declaration level. - tree = parser.state_decl_body() + tree = parser.state_machine() if state_name else parser.state_decl_body() errors = syntax_error_listener.errors if errors: raise ASLParserException(errors=errors) # Attempt to preprocess the AST into evaluation components. preprocessor = TestStatePreprocessor() - test_state_program = preprocessor.visit(tree) + test_state_program = preprocessor.to_test_state_program(tree, state_name) return test_state_program, tree diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py b/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py index 0565f74a67a55..8b8e99b6319d5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py @@ -1,9 +1,19 @@ import enum from typing import Final +from antlr4.tree.Tree import ParseTree + from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser -from localstack.services.stepfunctions.asl.component.common.parargs import Parameters +from localstack.services.stepfunctions.asl.antlt4utils.antlr4utils import ( + is_production, +) +from localstack.services.stepfunctions.asl.component.common.parargs import ( + ArgumentsJSONataTemplateValueObject, + ArgumentsStringJSONata, + Parameters, +) from localstack.services.stepfunctions.asl.component.common.path.input_path import InputPath +from localstack.services.stepfunctions.asl.component.common.path.items_path import ItemsPath from localstack.services.stepfunctions.asl.component.common.path.result_path import ResultPath from localstack.services.stepfunctions.asl.component.common.query_language import QueryLanguage from localstack.services.stepfunctions.asl.component.common.result_selector import ResultSelector @@ -11,13 +21,49 @@ from localstack.services.stepfunctions.asl.component.state.state_choice.state_choice import ( StateChoice, ) -from localstack.services.stepfunctions.asl.component.state.state_execution.execute_state import ( - ExecutionState, +from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.max_concurrency import ( + MaxConcurrency, + MaxConcurrencyJSONata, + MaxConcurrencyPath, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import ( + StateMap, ) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.tolerated_failure import ( + ToleratedFailureCountInt, + ToleratedFailureCountPath, + ToleratedFailureCountStringJSONata, + ToleratedFailurePercentage, + ToleratedFailurePercentagePath, + ToleratedFailurePercentageStringJSONata, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_parallel.state_parallel import ( + StateParallel, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import ( + StateTask, +) +from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail from localstack.services.stepfunctions.asl.component.state.state_pass.result import Result +from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass +from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import ( + StateSucceed, +) from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import ( TestStateProgram, ) +from localstack.services.stepfunctions.asl.component.test_state.state.common import ( + MockedCommonState, +) +from localstack.services.stepfunctions.asl.component.test_state.state.map import ( + MockedStateMap, +) +from localstack.services.stepfunctions.asl.component.test_state.state.parallel import ( + MockedStateParallel, +) +from localstack.services.stepfunctions.asl.component.test_state.state.task import ( + MockedStateTask, +) from localstack.services.stepfunctions.asl.component.test_state.state.test_state_state_props import ( TestStateStateProps, ) @@ -30,63 +76,103 @@ class InspectionDataKey(enum.Enum): INPUT = "input" AFTER_INPUT_PATH = "afterInputPath" AFTER_PARAMETERS = "afterParameters" + AFTER_ARGUMENTS = "afterArguments" RESULT = "result" AFTER_RESULT_SELECTOR = "afterResultSelector" AFTER_RESULT_PATH = "afterResultPath" + AFTER_ITEMS_PATH = "afterItemsPath" REQUEST = "request" RESPONSE = "response" - -def _decorated_updated_choice_inspection_data(method): - def wrapper(env: TestStateEnvironment, *args, **kwargs): - method(env, *args, **kwargs) - env.set_choice_selected(env.next_state_name) - - return wrapper + MAX_CONCURRENCY = "maxConcurrency" + TOLERATED_FAILURE_COUNT = "toleratedFailureCount" + TOLERATED_FAILURE_PERCENTAGE = "toleratedFailurePercentage" def _decorated_updates_inspection_data(method, inspection_data_key: InspectionDataKey): def wrapper(env: TestStateEnvironment, *args, **kwargs): method(env, *args, **kwargs) - result = to_json_str(env.stack[-1]) + result = env.stack[-1] + if not isinstance(result, (int, float)): + result = to_json_str(result) # We know that the enum value used here corresponds to a supported inspection data field by design. env.inspection_data[inspection_data_key.value] = result # noqa return wrapper -def _decorate_state_field(state_field: CommonStateField) -> None: - if isinstance(state_field, ExecutionState): - state_field._eval_execution = _decorated_updates_inspection_data( - # As part of the decoration process, we intentionally access this protected member - # to facilitate the decorator's functionality. - method=state_field._eval_execution, # noqa - inspection_data_key=InspectionDataKey.RESULT, - ) - elif isinstance(state_field, StateChoice): - state_field._eval_body = _decorated_updated_choice_inspection_data( - # As part of the decoration process, we intentionally access this protected member - # to facilitate the decorator's functionality. - method=state_field._eval_body # noqa - ) +def _decorate_state_field(state_field: CommonStateField, is_single_state: bool = False) -> None: + if isinstance(state_field, StateMap): + MockedStateMap.wrap(state_field, is_single_state) + elif isinstance(state_field, StateParallel): + MockedStateParallel.wrap(state_field, is_single_state) + elif isinstance(state_field, StateTask): + MockedStateTask.wrap(state_field, is_single_state) + elif isinstance(state_field, (StateChoice, StatePass, StateFail, StateSucceed)): + MockedCommonState.wrap(state_field, is_single_state) + + +def find_state(state_name: str, states: dict[str, CommonStateField]) -> CommonStateField | None: + if state_name in states: + return states[state_name] + + for state in states.values(): + if isinstance(state, StateMap): + found_state = find_state(state_name, state.iteration_component._states.states) + if found_state: + return found_state + elif isinstance(state, StateParallel): + for program in state.branches.programs: + found_state = find_state(state_name, program.states.states) + if found_state: + return found_state class TestStatePreprocessor(Preprocessor): - STATE_NAME: Final[str] = "TestState" + STATE_NAME: Final[str] = "StateName" + _state_name_stack: list[str] = [] + + def to_test_state_program( + self, tree: ParseTree, state_name: str | None = None + ) -> TestStateProgram: + if is_production(tree, ASLParser.RULE_state_machine): + # full definition passed in + program = self.visitState_machine(ctx=tree) + state_field = find_state(state_name, program.states.states) + _decorate_state_field(state_field, False) + return TestStateProgram(state_field) - def visitState_decl_body(self, ctx: ASLParser.State_decl_bodyContext) -> TestStateProgram: + if is_production(tree, ASLParser.RULE_state_decl_body): + # single state case + state_props = self.visitState_decl_body(ctx=tree) + state_field = self._common_state_field_of(state_props=state_props) + _decorate_state_field(state_field, True) + return TestStateProgram(state_field) + + return super().visit(tree) + + def visitState_decl(self, ctx: ASLParser.State_declContext) -> CommonStateField: + # if we are parsing a full state machine, we need to record the state_name prior to stepping + # into the state body definition. + state_name = self._inner_string_of(parser_rule_context=ctx.string_literal()) + self._state_name_stack.append(state_name) + state_props: TestStateStateProps = self.visit(ctx.state_decl_body()) + state_field = self._common_state_field_of(state_props=state_props) + return state_field + + def visitState_decl_body(self, ctx: ASLParser.State_decl_bodyContext) -> TestStateStateProps: self._open_query_language_scope(ctx) state_props = TestStateStateProps() - state_props.name = self.STATE_NAME + state_props.name = ( + self._state_name_stack.pop(-1) if self._state_name_stack else self.STATE_NAME + ) for child in ctx.children: cmp = self.visit(child) state_props.add(cmp) - state_field = self._common_state_field_of(state_props=state_props) if state_props.get(QueryLanguage) is None: state_props.add(self._get_current_query_language()) - _decorate_state_field(state_field) self._close_query_language_scope() - return TestStateProgram(state_field) + return state_props def visitInput_path_decl(self, ctx: ASLParser.Input_path_declContext) -> InputPath: input_path: InputPath = super().visitInput_path_decl(ctx=ctx) @@ -129,3 +215,121 @@ def visitResult_decl(self, ctx: ASLParser.Result_declContext) -> Result: inspection_data_key=InspectionDataKey.RESULT, # noqa ) return result + + def visitMax_concurrency_int(self, ctx: ASLParser.Max_concurrency_intContext) -> MaxConcurrency: + max_concurrency: MaxConcurrency = super().visitMax_concurrency_int(ctx) + max_concurrency._eval_body = _decorated_updates_inspection_data( + method=max_concurrency._eval_body, + inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa + ) + return max_concurrency + + def visitMax_concurrency_jsonata( + self, ctx: ASLParser.Max_concurrency_jsonataContext + ) -> MaxConcurrencyJSONata: + max_concurrency_jsonata: MaxConcurrencyJSONata = super().visitMax_concurrency_jsonata(ctx) + max_concurrency_jsonata._eval_body = _decorated_updates_inspection_data( + method=max_concurrency_jsonata._eval_body, + inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa + ) + return max_concurrency_jsonata + + def visitMax_concurrency_path( + self, ctx: ASLParser.Max_concurrency_declContext + ) -> MaxConcurrencyPath: + max_concurrency_path: MaxConcurrencyPath = super().visitMax_concurrency_path(ctx) + max_concurrency_path._eval_body = _decorated_updates_inspection_data( + method=max_concurrency_path._eval_body, + inspection_data_key=InspectionDataKey.MAX_CONCURRENCY, # noqa + ) + return max_concurrency_path + + def visitTolerated_failure_count_int(self, ctx) -> ToleratedFailureCountInt: + tolerated_failure_count: ToleratedFailureCountInt = ( + super().visitTolerated_failure_count_int(ctx) + ) + tolerated_failure_count._eval_body = _decorated_updates_inspection_data( + method=tolerated_failure_count._eval_body, + inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT, + ) + return tolerated_failure_count + + def visitTolerated_failure_count_path(self, ctx) -> ToleratedFailureCountPath: + tolerated_failure_count_path: ToleratedFailureCountPath = ( + super().visitTolerated_failure_count_path(ctx) + ) + tolerated_failure_count_path._eval_body = _decorated_updates_inspection_data( + method=tolerated_failure_count_path._eval_body, + inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT, + ) + return tolerated_failure_count_path + + def visitTolerated_failure_count_string_jsonata( + self, ctx + ) -> ToleratedFailureCountStringJSONata: + tolerated_failure_count_jsonata: ToleratedFailureCountStringJSONata = ( + super().visitTolerated_failure_count_string_jsonata(ctx) + ) + tolerated_failure_count_jsonata._eval_body = _decorated_updates_inspection_data( + method=tolerated_failure_count_jsonata._eval_body, + inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_COUNT, + ) + return tolerated_failure_count_jsonata + + def visitTolerated_failure_percentage_number(self, ctx) -> ToleratedFailurePercentage: + tolerated_failure_percentage: ToleratedFailurePercentage = ( + super().visitTolerated_failure_percentage_number(ctx) + ) + tolerated_failure_percentage._eval_body = _decorated_updates_inspection_data( + method=tolerated_failure_percentage._eval_body, + inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE, + ) + return tolerated_failure_percentage + + def visitTolerated_failure_percentage_path(self, ctx) -> ToleratedFailurePercentagePath: + tolerated_failure_percentage_path: ToleratedFailurePercentagePath = ( + super().visitTolerated_failure_percentage_path(ctx) + ) + tolerated_failure_percentage_path._eval_body = _decorated_updates_inspection_data( + method=tolerated_failure_percentage_path._eval_body, + inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE, + ) + return tolerated_failure_percentage_path + + def visitTolerated_failure_percentage_string_jsonata( + self, ctx + ) -> ToleratedFailurePercentageStringJSONata: + tolerated_failure_percentage_jsonata: ToleratedFailurePercentageStringJSONata = ( + super().visitTolerated_failure_percentage_string_jsonata(ctx) + ) + tolerated_failure_percentage_jsonata._eval_body = _decorated_updates_inspection_data( + method=tolerated_failure_percentage_jsonata._eval_body, + inspection_data_key=InspectionDataKey.TOLERATED_FAILURE_PERCENTAGE, + ) + return tolerated_failure_percentage_jsonata + + def visitItems_path_decl(self, ctx) -> ItemsPath: + items_path: ItemsPath = super().visitItems_path_decl(ctx) + items_path._eval_body = _decorated_updates_inspection_data( + method=items_path._eval_body, + inspection_data_key=InspectionDataKey.AFTER_ITEMS_PATH, + ) + return items_path + + def visitArguments_string_jsonata(self, ctx): + arguments: ArgumentsStringJSONata = super().visitArguments_string_jsonata(ctx) + arguments._eval_body = _decorated_updates_inspection_data( + method=arguments._eval_body, + inspection_data_key=InspectionDataKey.AFTER_ARGUMENTS, + ) + return arguments + + def visitArguments_jsonata_template_value_object(self, ctx): + arguments: ArgumentsJSONataTemplateValueObject = ( + super().visitArguments_jsonata_template_value_object(ctx) + ) + arguments._eval_body = _decorated_updates_inspection_data( + method=arguments._eval_body, + inspection_data_key=InspectionDataKey.AFTER_ARGUMENTS, + ) + return arguments diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py b/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py index fda0913ec86b2..a63bce525e156 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from typing import Any, Optional +from typing import Any class TypedProps: @@ -18,7 +18,7 @@ def _add(self, typ: type, instance: Any) -> None: ) self._instance_by_type[typ] = instance - def get(self, typ: type, raise_on_missing: Optional[Exception] = None) -> Optional[Any]: + def get(self, typ: type, raise_on_missing: Exception | None = None) -> Any | None: if raise_on_missing and typ not in self._instance_by_type: raise raise_on_missing return self._instance_by_type.get(typ) diff --git a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py index 6c4514183bfa3..e0feefb65c04b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py @@ -16,7 +16,7 @@ class VariableNamesIntrinsicStaticAnalyser(IntrinsicStaticAnalyser): def __init__(self): super().__init__() - self._variable_names = list() + self._variable_names = [] @staticmethod def process_and_get(definition: str) -> VariableNameList: diff --git a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py index 79cb80196b54d..37a107dbf3a37 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py @@ -1,12 +1,51 @@ -from typing import Final +import json +from typing import Any, Final +# Botocore shape classes to drive validation +from botocore.model import ( + ListShape, + MapShape, + Shape, + StringShape, + StructureShape, +) + +from localstack.aws.api.stepfunctions import ( + Definition, + InvalidDefinition, + MockInput, + MockResponseValidationMode, + StateName, + TestStateConfiguration, + TestStateInput, + ValidationException, +) from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser -from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.resource import ( - ActivityResource, - Resource, - ServiceResource, +from localstack.services.stepfunctions.asl.component.state.state import CommonStateField +from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import ( + StateMap, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_parallel.state_parallel import ( + StateParallel, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service import ( + StateTaskService, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service_api_gateway import ( + StateTaskServiceApiGateway, +) +from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import ( + StateTask, +) +from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail +from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass +from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import ( + StateSucceed, ) from localstack.services.stepfunctions.asl.component.state.state_type import StateType +from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import ( + TestStateProgram, +) from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import ( TestStateAmazonStateLanguageParser, ) @@ -14,6 +53,11 @@ class TestStateStaticAnalyser(StaticAnalyser): + state_name: StateName | None + + def __init__(self, state_name: StateName | None = None): + self.state_name = state_name + _SUPPORTED_STATE_TYPES: Final[set[StateType]] = { StateType.Task, StateType.Pass, @@ -21,10 +65,260 @@ class TestStateStaticAnalyser(StaticAnalyser): StateType.Choice, StateType.Succeed, StateType.Fail, + StateType.Map, + StateType.Parallel, } - def analyse(self, definition) -> None: - _, parser_rule_context = TestStateAmazonStateLanguageParser.parse(definition) + @staticmethod + def is_state_in_definition(definition: Definition, state_name: StateName) -> bool: + test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name) + if not isinstance(test_program, TestStateProgram): + raise ValueError("expected parsed EvalComponent to be of type TestStateProgram") + + return test_program.test_state is not None + + @staticmethod + def validate_role_arn_required( + mock_input: MockInput, definition: Definition, state_name: StateName + ) -> None: + test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name) + test_state = test_program.test_state + if isinstance(test_state, StateTask) and mock_input is None: + raise ValidationException("RoleArn must be specified when testing a Task state") + + @staticmethod + def validate_state_configuration( + state_configuration: TestStateConfiguration | None, mock_input: MockInput + ): + if state_configuration is None: + return + + if "mapIterationFailureCount" not in state_configuration: + return + + if not mock_input: + raise ValidationException( + "TestState does not support MapIterationFailureCount when a mock is not specified." + ) + + mock_result_raw = mock_input.get("result") + if not mock_result_raw: + return + + mock_result = json.loads(mock_result_raw) + map_iteration_failure_count = state_configuration["mapIterationFailureCount"] + if isinstance(mock_result, list) and map_iteration_failure_count > len(mock_result): + raise ValidationException( + "Map iteration failure count must be less than or equal to the number of Map iterations" + ) + + @staticmethod + def validate_mock(test_state_input: TestStateInput) -> None: + test_program, _ = TestStateAmazonStateLanguageParser.parse( + test_state_input.get("definition"), test_state_input.get("stateName") + ) + test_state = test_program.test_state + mock_input = test_state_input.get("mock") + + TestStateStaticAnalyser.validate_test_state_allows_mocking( + mock_input=mock_input, test_state=test_state + ) + + if mock_input is None: + return + + if test_state_input.get("revealSecrets"): + raise ValidationException( + "TestState does not support RevealSecrets when a mock is specified." + ) + + if {"result", "errorOutput"} <= mock_input.keys(): + raise ValidationException( + "A test mock should have only one of the following fields: [result, errorOutput]." + ) + + mock_result_raw = mock_input.get("result") + if mock_result_raw is None: + return + try: + mock_result = json.loads(mock_result_raw) + except json.JSONDecodeError: + raise ValidationException("Mocked result must be valid JSON") + + if isinstance(test_state, StateMap): + TestStateStaticAnalyser.validate_mock_result_matches_map_definition( + mock_result=mock_result, test_state=test_state + ) + + if isinstance(test_state, StateParallel): + TestStateStaticAnalyser.validate_mock_result_matches_parallel_definition( + mock_result=mock_result, test_state=test_state + ) + + if isinstance(test_state, StateTaskService): + field_validation_mode = mock_input.get( + "fieldValidationMode", MockResponseValidationMode.STRICT + ) + TestStateStaticAnalyser.validate_mock_result_matches_api_shape( + mock_result=mock_result, + field_validation_mode=field_validation_mode, + test_state=test_state, + ) + + @staticmethod + def validate_test_state_allows_mocking( + mock_input: MockInput, test_state: CommonStateField + ) -> None: + if mock_input is None and isinstance(test_state, (StateMap, StateParallel)): + # This is a literal message when a Map or Parallel state is not accompanied by a mock in a test state request. + # The message is the same for both cases and is not parametrised anyhow. + raise InvalidDefinition( + "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]" + ) + + if mock_input is not None and isinstance(test_state, (StatePass, StateFail, StateSucceed)): + raise ValidationException( + f"State type '{test_state.state_type.name}' is not supported when a mock is specified" + ) + + @staticmethod + def validate_mock_result_matches_map_definition(mock_result: Any, test_state: StateMap): + if test_state.result_writer is not None and not isinstance(mock_result, dict): + raise ValidationException("Mocked result must be a JSON object.") + + if test_state.result_writer is None and not isinstance(mock_result, list): + raise ValidationException("Mocked result must be an array.") + + @staticmethod + def validate_mock_result_matches_parallel_definition( + mock_result: Any, test_state: StateParallel + ): + if not isinstance(mock_result, list): + raise ValidationException("Mocked result must be an array.") + + if len(mock_result) != len(test_state.branches.programs): + raise ValidationException( + "Mocked result must contain the same number of items as number of Parallel branches." + ) + + @staticmethod + def validate_mock_result_matches_api_shape( + mock_result: Any, + field_validation_mode: MockResponseValidationMode, + test_state: StateTaskService, + ): + # apigateway:invoke: has no equivalent in the AWS SDK service integration. + # Hence, the validation against botocore doesn't apply. + # See the note in https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html + # TODO do custom validation for apigateway:invoke: + if isinstance(test_state, StateTaskServiceApiGateway): + return + + if field_validation_mode == MockResponseValidationMode.NONE: + return + + boto_service_name = test_state._get_boto_service_name() + service_action_name = test_state._get_boto_service_action() + output_shape = test_state._get_boto_operation_model( + boto_service_name=boto_service_name, service_action_name=service_action_name + ).output_shape + + # If the operation has no output, there's nothing to validate + if output_shape is None: + return + + def _raise_type_error(expected_type: str, field_name: str) -> None: + raise ValidationException( + f"Mock result schema validation error: Field '{field_name}' must be {expected_type}" + ) + + def _validate_value(value: Any, shape: Shape, field_name: str | None = None) -> None: + # Document type accepts any JSON value + if shape.type_name == "document": + return + + if isinstance(shape, StructureShape): + if not isinstance(value, dict): + # this is a defensive check, the mock result is loaded from JSON before, so should always be a dict + raise ValidationException( + f"Mock result must be a valid JSON object, but got '{type(value)}' instead" + ) + # Build a mapping from SFN-normalised member keys -> botocore member shapes + members = shape.members + sfn_key_to_member_shape: dict[str, Shape] = { + StateTaskService._to_sfn_cased(member_key): member_shape + for member_key, member_shape in members.items() + } + if field_validation_mode == MockResponseValidationMode.STRICT: + # Ensure required members are present, using SFN-normalised keys + for required_key in shape.required_members: + sfn_required_key = StateTaskService._to_sfn_cased(required_key) + if sfn_required_key not in value: + raise ValidationException( + f"Mock result schema validation error: Required field '{sfn_required_key}' is missing" + ) + # Validate present fields (match SFN-normalised keys to member shapes) + for mock_field_name, mock_field_value in value.items(): + member_shape = sfn_key_to_member_shape.get(mock_field_name) + if member_shape is None: + # Fields that are present in mock but are not in the API spec should not raise validation errors - forward compatibility + continue + _validate_value(mock_field_value, member_shape, mock_field_name) + return + + if isinstance(shape, ListShape): + if not isinstance(value, list): + _raise_type_error("an array", field_name) + member_shape = shape.member + for list_item in value: + _validate_value(list_item, member_shape, field_name) + return + + if isinstance(shape, MapShape): + if not isinstance(value, dict): + _raise_type_error("an object", field_name) + value_shape = shape.value + for _, map_item_value in value.items(): + _validate_value(map_item_value, value_shape, field_name) + return + + # Primitive shapes and others + type_name = shape.type_name + match type_name: + case "string" | "timestamp": + if not isinstance(value, str): + _raise_type_error("a string", field_name) + # Validate enum if present + if isinstance(shape, StringShape): + enum = getattr(shape, "enum", None) + if enum and value not in enum: + raise ValidationException( + f"Mock result schema validation error: Field '{field_name}' is not an expected value" + ) + + case "integer" | "long": + if not isinstance(value, int) or isinstance(value, bool): + _raise_type_error("a number", field_name) + + case "float" | "double": + if not (isinstance(value, (int, float)) or isinstance(value, bool)): + _raise_type_error("a number", field_name) + + case "boolean": + if not isinstance(value, bool): + _raise_type_error("a boolean", field_name) + + case "blob": + if not (isinstance(value, (str, bytes))): + _raise_type_error("a string", field_name) + + # Perform validation against the output shape + _validate_value(mock_result, output_shape) + + def analyse(self, definition: str) -> None: + _, parser_rule_context = TestStateAmazonStateLanguageParser.parse( + definition, self.state_name + ) self.visit(parser_rule_context) def visitState_type(self, ctx: ASLParser.State_typeContext) -> None: @@ -32,18 +326,3 @@ def visitState_type(self, ctx: ASLParser.State_typeContext) -> None: state_type = StateType(state_type_value) if state_type not in self._SUPPORTED_STATE_TYPES: raise ValueError(f"Unsupported state type for TestState runs '{state_type}'.") - - def visitResource_decl(self, ctx: ASLParser.Resource_declContext) -> None: - resource_str: str = ctx.string_literal().getText()[1:-1] - resource = Resource.from_resource_arn(resource_str) - - if isinstance(resource, ActivityResource): - raise ValueError( - f"ActivityResources are not supported for TestState runs {resource_str}." - ) - - if isinstance(resource, ServiceResource): - if resource.condition is not None: - raise ValueError( - f"Service integration patterns are not supported for TestState runs {resource_str}." - ) diff --git a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py index 93edc9a06a97f..cbfd66bddff28 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py @@ -30,7 +30,7 @@ def process_and_get(definition: str) -> VariableReferences: def __init__(self): super().__init__() - self._fringe_state_names = list() + self._fringe_state_names = [] self._variable_references = OrderedDict() def get_variable_references(self) -> VariableReferences: @@ -54,7 +54,7 @@ def _put_variable_reference(self, variable_reference: VariableReference) -> None def _put_variable_name(self, variable_name: VariableName) -> None: state_name = self._fringe_state_names[-1] - variable_name_list: VariableNameList = self._variable_references.get(state_name, list()) + variable_name_list: VariableNameList = self._variable_references.get(state_name, []) if variable_name in variable_name_list: return variable_name_list.append(variable_name) diff --git a/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py b/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py index 893db6cc28f44..1ea102727a29c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py @@ -1,7 +1,7 @@ import datetime import json from json import JSONEncoder -from typing import Any, Optional +from typing import Any class _DateTimeEncoder(JSONEncoder): @@ -12,5 +12,5 @@ def default(self, obj): return str(obj) -def to_json_str(obj: Any, separators: Optional[tuple[str, str]] = None) -> str: +def to_json_str(obj: Any, separators: tuple[str, str] | None = None) -> str: return json.dumps(obj, cls=_DateTimeEncoder, separators=separators) diff --git a/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py b/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py index 2447458683daf..592f09c76cf48 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py @@ -1,5 +1,5 @@ import re -from typing import Any, Final, Optional +from typing import Any, Final from jsonpath_ng.ext import parse from jsonpath_ng.jsonpath import Index @@ -24,7 +24,7 @@ def _contains_slice_or_wildcard_array(path: str) -> bool: class NoSuchJsonPathError(Exception): json_path: Final[str] data: Final[Any] - _message: Optional[str] + _message: str | None def __init__(self, json_path: str, data: Any): self.json_path = json_path diff --git a/localstack-core/localstack/services/stepfunctions/backend/activity.py b/localstack-core/localstack/services/stepfunctions/backend/activity.py index 8800dbd3fa122..cabb8f98cd420 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/activity.py +++ b/localstack-core/localstack/services/stepfunctions/backend/activity.py @@ -1,6 +1,6 @@ import datetime from collections import deque -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( ActivityListItem, @@ -26,16 +26,16 @@ class Activity: creation_date: Final[Timestamp] _tasks: Final[deque[ActivityTask]] - def __init__(self, arn: Arn, name: Name, creation_date: Optional[Timestamp] = None): + def __init__(self, arn: Arn, name: Name, creation_date: Timestamp | None = None): self.arn = arn self.name = name - self.creation_date = creation_date or datetime.datetime.now(tz=datetime.timezone.utc) + self.creation_date = creation_date or datetime.datetime.now(tz=datetime.UTC) self._tasks = deque() def add_task(self, task: ActivityTask): self._tasks.append(task) - def get_task(self) -> Optional[ActivityTask]: + def get_task(self) -> ActivityTask | None: return self._tasks.popleft() def to_describe_activity_output(self) -> DescribeActivityOutput: diff --git a/localstack-core/localstack/services/stepfunctions/backend/alias.py b/localstack-core/localstack/services/stepfunctions/backend/alias.py index 155890abf4cb3..b66574c9a4128 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/alias.py +++ b/localstack-core/localstack/services/stepfunctions/backend/alias.py @@ -4,7 +4,7 @@ import datetime import random import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( AliasDescription, @@ -20,9 +20,9 @@ class Alias: _mutex: Final[threading.Lock] - update_date: Optional[datetime.datetime] + update_date: datetime.datetime | None name: Final[CharacterRestrictedName] - _description: Optional[AliasDescription] + _description: AliasDescription | None _routing_configuration_list: RoutingConfigurationList _state_machine_version_arns: list[Arn] _execution_probability_distribution: list[int] @@ -34,7 +34,7 @@ def __init__( self, state_machine_arn: Arn, name: CharacterRestrictedName, - description: Optional[AliasDescription], + description: AliasDescription | None, routing_configuration_list: RoutingConfigurationList, ): self._mutex = threading.Lock() @@ -66,7 +66,7 @@ def is_idempotent(self, other: Alias) -> bool: @staticmethod def _get_mutex_date() -> datetime.datetime: - return datetime.datetime.now(tz=datetime.timezone.utc) + return datetime.datetime.now(tz=datetime.UTC) def get_routing_configuration_list(self) -> RoutingConfigurationList: return copy.deepcopy(self._routing_configuration_list) @@ -77,7 +77,7 @@ def is_router_for(self, state_machine_version_arn: Arn) -> bool: def update( self, - description: Optional[AliasDescription], + description: AliasDescription | None, routing_configuration_list: RoutingConfigurationList, ) -> None: with self._mutex: @@ -88,8 +88,8 @@ def update( if routing_configuration_list: self._routing_configuration_list = routing_configuration_list - self._state_machine_version_arns = list() - self._execution_probability_distribution = list() + self._state_machine_version_arns = [] + self._execution_probability_distribution = [] for routing_configuration in routing_configuration_list: self._state_machine_version_arns.append( routing_configuration["stateMachineVersionArn"] diff --git a/localstack-core/localstack/services/stepfunctions/backend/execution.py b/localstack-core/localstack/services/stepfunctions/backend/execution.py index 552497557193f..5440afa0a8ea9 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/execution.py +++ b/localstack-core/localstack/services/stepfunctions/backend/execution.py @@ -1,9 +1,8 @@ from __future__ import annotations import datetime -import json import logging -from typing import Final, Optional +from typing import Any, Final from localstack.aws.api.events import PutEventsRequestEntry from localstack.aws.api.stepfunctions import ( @@ -59,7 +58,7 @@ StateMachineInstance, StateMachineVersion, ) -from localstack.services.stepfunctions.mocking.mock_config import MockTestCase +from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockTestCase LOG = logging.getLogger(__name__) @@ -72,7 +71,7 @@ def __init__(self, execution: Execution): def _reflect_execution_status(self): exit_program_state: ProgramState = self.execution.exec_worker.env.program_state() - self.execution.stop_date = datetime.datetime.now(tz=datetime.timezone.utc) + self.execution.stop_date = datetime.datetime.now(tz=datetime.UTC) if isinstance(exit_program_state, ProgramEnded): self.execution.exec_status = ExecutionStatus.SUCCEEDED self.execution.output = self.execution.exec_worker.env.states.get_input() @@ -105,27 +104,27 @@ class Execution: state_machine: Final[StateMachineInstance] state_machine_arn: Final[Arn] - state_machine_version_arn: Final[Optional[Arn]] - state_machine_alias_arn: Final[Optional[Arn]] + state_machine_version_arn: Final[Arn | None] + state_machine_alias_arn: Final[Arn | None] - mock_test_case: Final[Optional[MockTestCase]] + local_mock_test_case: Final[LocalMockTestCase | None] start_date: Final[Timestamp] - input_data: Final[Optional[json]] - input_details: Final[Optional[CloudWatchEventsExecutionDataDetails]] - trace_header: Final[Optional[TraceHeader]] - _cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] + input_data: Final[dict[str, Any] | None] + input_details: Final[CloudWatchEventsExecutionDataDetails | None] + trace_header: Final[TraceHeader | None] + _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] - exec_status: Optional[ExecutionStatus] - stop_date: Optional[Timestamp] + exec_status: ExecutionStatus | None + stop_date: Timestamp | None - output: Optional[json] - output_details: Optional[CloudWatchEventsExecutionDataDetails] + output: dict[str, Any] | None + output_details: CloudWatchEventsExecutionDataDetails | None - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None - exec_worker: Optional[ExecutionWorker] + exec_worker: ExecutionWorker | None _activity_store: dict[Arn, Activity] @@ -139,12 +138,12 @@ def __init__( region_name: str, state_machine: StateMachineInstance, start_date: Timestamp, - cloud_watch_logging_session: Optional[CloudWatchLoggingSession], + cloud_watch_logging_session: CloudWatchLoggingSession | None, activity_store: dict[Arn, Activity], - input_data: Optional[json] = None, - trace_header: Optional[TraceHeader] = None, - state_machine_alias_arn: Optional[Arn] = None, - mock_test_case: Optional[MockTestCase] = None, + input_data: dict[str, Any] | None = None, + trace_header: TraceHeader | None = None, + state_machine_alias_arn: Arn | None = None, + local_mock_test_case: LocalMockTestCase | None = None, ): self.name = name self.sm_type = sm_type @@ -173,7 +172,7 @@ def __init__( self.error = None self.cause = None self._activity_store = activity_store - self.mock_test_case = mock_test_case + self.local_mock_test_case = local_mock_test_case def _get_events_client(self): return connect_to(aws_access_key_id=self.account_id, region_name=self.region_name).events @@ -258,7 +257,7 @@ def to_execution_list_item(self) -> ExecutionListItem: def to_history_output(self) -> GetExecutionHistoryOutput: env = self.exec_worker.env - event_history: HistoryEventList = list() + event_history: HistoryEventList = [] if env is not None: # The execution has not started yet. event_history: HistoryEventList = env.event_manager.get_event_history() @@ -267,9 +266,7 @@ def to_history_output(self) -> GetExecutionHistoryOutput: @staticmethod def _to_serialized_date(timestamp: datetime.datetime) -> str: """See test in tests.aws.services.stepfunctions.v2.base.test_base.TestSnfBase.test_execution_dateformat""" - return ( - f"{timestamp.astimezone(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}Z" - ) + return f"{timestamp.astimezone(datetime.UTC).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}Z" def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication: return BaseExecutionWorkerCommunication(self) @@ -306,7 +303,7 @@ def _get_start_execution_worker(self) -> ExecutionWorker: exec_comm=self._get_start_execution_worker_comm(), cloud_watch_logging_session=self._cloud_watch_logging_session, activity_store=self._activity_store, - mock_test_case=self.mock_test_case, + local_mock_test_case=self.local_mock_test_case, ) def start(self) -> None: @@ -318,14 +315,14 @@ def start(self) -> None: self.publish_execution_status_change_event() self.exec_worker.start() - def stop(self, stop_date: datetime.datetime, error: Optional[str], cause: Optional[str]): - exec_worker: Optional[ExecutionWorker] = self.exec_worker + def stop(self, stop_date: datetime.datetime, error: str | None, cause: str | None): + exec_worker: ExecutionWorker | None = self.exec_worker if exec_worker: exec_worker.stop(stop_date=stop_date, cause=cause, error=error) def publish_execution_status_change_event(self): input_value = ( - dict() if not self.input_data else to_json_str(self.input_data, separators=(",", ":")) + {} if not self.input_data else to_json_str(self.input_data, separators=(",", ":")) ) output_value = ( None if self.output is None else to_json_str(self.output, separators=(",", ":")) @@ -358,10 +355,11 @@ def publish_execution_status_change_event(self): try: self._get_events_client().put_events(Entries=[entry]) except Exception: - LOG.exception( + LOG.error( "Unable to send notification of Entry='%s' for Step Function execution with Arn='%s' to EventBridge.", entry, self.exec_arn, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) @@ -380,7 +378,7 @@ def _reflect_execution_status(self) -> None: class SyncExecution(Execution): - sync_execution_status: Optional[SyncExecutionStatus] = None + sync_execution_status: SyncExecutionStatus | None = None def _get_start_execution_worker(self) -> SyncExecutionWorker: return SyncExecutionWorker( @@ -392,7 +390,7 @@ def _get_start_execution_worker(self) -> SyncExecutionWorker: exec_comm=self._get_start_execution_worker_comm(), cloud_watch_logging_session=self._cloud_watch_logging_session, activity_store=self._activity_store, - mock_test_case=self.mock_test_case, + local_mock_test_case=self.local_mock_test_case, ) def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication: diff --git a/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py b/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py index c2d14c2085295..24413272d2262 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py +++ b/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py @@ -1,6 +1,6 @@ import datetime from threading import Thread -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( Arn, @@ -29,31 +29,31 @@ from localstack.services.stepfunctions.backend.execution_worker_comm import ( ExecutionWorkerCommunication, ) -from localstack.services.stepfunctions.mocking.mock_config import MockTestCase +from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockTestCase from localstack.utils.common import TMP_THREADS class ExecutionWorker: _evaluation_details: Final[EvaluationDetails] _execution_communication: Final[ExecutionWorkerCommunication] - _cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] - _mock_test_case: Final[Optional[MockTestCase]] + _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] + _local_mock_test_case: Final[LocalMockTestCase | None] _activity_store: dict[Arn, Activity] - env: Optional[Environment] + env: Environment | None def __init__( self, evaluation_details: EvaluationDetails, exec_comm: ExecutionWorkerCommunication, - cloud_watch_logging_session: Optional[CloudWatchLoggingSession], + cloud_watch_logging_session: CloudWatchLoggingSession | None, activity_store: dict[Arn, Activity], - mock_test_case: Optional[MockTestCase] = None, + local_mock_test_case: LocalMockTestCase | None = None, ): self._evaluation_details = evaluation_details self._execution_communication = exec_comm self._cloud_watch_logging_session = cloud_watch_logging_session - self._mock_test_case = mock_test_case + self._local_mock_test_case = local_mock_test_case self._activity_store = activity_store self.env = None @@ -82,7 +82,7 @@ def _get_evaluation_environment(self) -> Environment: event_history_context=EventHistoryContext.of_program_start(), cloud_watch_logging_session=self._cloud_watch_logging_session, activity_store=self._activity_store, - mock_test_case=self._mock_test_case, + local_mock_test_case=self._local_mock_test_case, ) def _execution_logic(self): @@ -113,7 +113,7 @@ def start(self): TMP_THREADS.append(execution_logic_thread) execution_logic_thread.start() - def stop(self, stop_date: datetime.datetime, error: Optional[str], cause: Optional[str]): + def stop(self, stop_date: datetime.datetime, error: str | None, cause: str | None): self.env.set_stop(stop_date=stop_date, cause=cause, error=error) diff --git a/localstack-core/localstack/services/stepfunctions/backend/store.py b/localstack-core/localstack/services/stepfunctions/backend/models.py similarity index 100% rename from localstack-core/localstack/services/stepfunctions/backend/store.py rename to localstack-core/localstack/services/stepfunctions/backend/models.py diff --git a/localstack-core/localstack/services/stepfunctions/backend/state_machine.py b/localstack-core/localstack/services/stepfunctions/backend/state_machine.py index 71c82f55c881c..d64e142bbe973 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/state_machine.py +++ b/localstack-core/localstack/services/stepfunctions/backend/state_machine.py @@ -4,7 +4,7 @@ import datetime import json from collections import OrderedDict -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( Arn, @@ -37,15 +37,15 @@ class StateMachineInstance: name: Name arn: Arn - revision_id: Optional[RevisionId] + revision_id: RevisionId | None definition: Definition role_arn: Arn create_date: datetime.datetime sm_type: StateMachineType logging_config: LoggingConfiguration - cloud_watch_logging_configuration: Optional[CloudWatchLoggingConfiguration] - tags: Optional[TagList] - tracing_config: Optional[TracingConfiguration] + cloud_watch_logging_configuration: CloudWatchLoggingConfiguration | None + tags: TagList | None + tracing_config: TracingConfiguration | None def __init__( self, @@ -54,18 +54,18 @@ def __init__( definition: Definition, role_arn: Arn, logging_config: LoggingConfiguration, - cloud_watch_logging_configuration: Optional[CloudWatchLoggingConfiguration] = None, - create_date: Optional[datetime.datetime] = None, - sm_type: Optional[StateMachineType] = None, - tags: Optional[TagList] = None, - tracing_config: Optional[TracingConfiguration] = None, + cloud_watch_logging_configuration: CloudWatchLoggingConfiguration | None = None, + create_date: datetime.datetime | None = None, + sm_type: StateMachineType | None = None, + tags: TagList | None = None, + tracing_config: TracingConfiguration | None = None, ): self.name = name self.arn = arn self.revision_id = None self.definition = definition self.role_arn = role_arn - self.create_date = create_date or datetime.datetime.now(tz=datetime.timezone.utc) + self.create_date = create_date or datetime.datetime.now(tz=datetime.UTC) self.sm_type = sm_type or StateMachineType.STANDARD self.logging_config = logging_config self.cloud_watch_logging_configuration = cloud_watch_logging_configuration @@ -106,7 +106,7 @@ def __init__( arn: Arn, definition: Definition, role_arn: Arn, - create_date: Optional[datetime.datetime] = None, + create_date: datetime.datetime | None = None, ): super().__init__( name, @@ -125,7 +125,7 @@ def itemise(self): class TagManager: - _tags: Final[dict[str, Optional[str]]] + _tags: Final[dict[str, str | None]] def __init__(self): self._tags = OrderedDict() @@ -154,7 +154,7 @@ def remove_all(self, keys: TagKeyList): self._tags.pop(key, None) def to_tag_list(self) -> TagList: - tag_list = list() + tag_list = [] for key, value in self._tags.items(): tag_list.append(Tag(key=key, value=value)) return tag_list @@ -173,11 +173,11 @@ def __init__( definition: Definition, role_arn: Arn, logging_config: LoggingConfiguration, - cloud_watch_logging_configuration: Optional[CloudWatchLoggingConfiguration], - create_date: Optional[datetime.datetime] = None, - sm_type: Optional[StateMachineType] = None, - tags: Optional[TagList] = None, - tracing_config: Optional[TracingConfiguration] = None, + cloud_watch_logging_configuration: CloudWatchLoggingConfiguration | None, + create_date: datetime.datetime | None = None, + sm_type: StateMachineType | None = None, + tags: TagList | None = None, + tracing_config: TracingConfiguration | None = None, ): super().__init__( name, @@ -191,7 +191,7 @@ def __init__( tags, tracing_config, ) - self.versions = dict() + self.versions = {} self._version_number = 0 self.tag_manager = TagManager() if tags: @@ -200,10 +200,10 @@ def __init__( def create_revision( self, - definition: Optional[str], - role_arn: Optional[Arn], - logging_configuration: Optional[LoggingConfiguration], - ) -> Optional[RevisionId]: + definition: str | None, + role_arn: Arn | None, + logging_configuration: LoggingConfiguration | None, + ) -> RevisionId | None: update_definition = definition and json.loads(definition) != json.loads(self.definition) if update_definition: self.definition = definition @@ -228,7 +228,7 @@ def create_revision( return self.revision_id - def create_version(self, description: Optional[str]) -> Optional[StateMachineVersion]: + def create_version(self, description: str | None) -> StateMachineVersion | None: if self.revision_id not in self.versions: self._version_number += 1 version = StateMachineVersion( @@ -259,10 +259,10 @@ def itemise(self) -> StateMachineListItem: class StateMachineVersion(StateMachineInstance): source_arn: Arn version: int - description: Optional[str] + description: str | None def __init__( - self, state_machine_revision: StateMachineRevision, version: int, description: Optional[str] + self, state_machine_revision: StateMachineRevision, version: int, description: str | None ): version_arn = f"{state_machine_revision.arn}:{version}" super().__init__( @@ -270,7 +270,7 @@ def __init__( arn=version_arn, definition=state_machine_revision.definition, role_arn=state_machine_revision.role_arn, - create_date=datetime.datetime.now(tz=datetime.timezone.utc), + create_date=datetime.datetime.now(tz=datetime.UTC), sm_type=state_machine_revision.sm_type, logging_config=state_machine_revision.logging_config, cloud_watch_logging_configuration=state_machine_revision.cloud_watch_logging_configuration, diff --git a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py index cc200f09b29c6..7631ec5f1e7d9 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py +++ b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py @@ -2,7 +2,6 @@ import logging import threading -from typing import Optional from localstack.aws.api.stepfunctions import ( Arn, @@ -13,14 +12,18 @@ TestStateOutput, Timestamp, ) -from localstack.services.stepfunctions.asl.eval.evaluation_details import EvaluationDetails +from localstack.services.stepfunctions.asl.eval.evaluation_details import ( + EvaluationDetails, +) from localstack.services.stepfunctions.asl.eval.program_state import ( ProgramEnded, ProgramError, ProgramState, ) from localstack.services.stepfunctions.asl.eval.test_state.program_state import ( + ProgramCaughtError, ProgramChoiceSelected, + ProgramRetriable, ) from localstack.services.stepfunctions.asl.utils.encoding import to_json_str from localstack.services.stepfunctions.backend.activity import Activity @@ -32,13 +35,17 @@ from localstack.services.stepfunctions.backend.test_state.execution_worker import ( TestStateExecutionWorker, ) +from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock LOG = logging.getLogger(__name__) class TestStateExecution(Execution): - exec_worker: Optional[TestStateExecutionWorker] - next_state: Optional[str] + exec_worker: TestStateExecutionWorker | None + next_state: str | None + state_name: str | None + mock: TestStateMock | None + variables: dict | None class TestCaseExecutionWorkerCommunication(BaseExecutionWorkerCommunication): _execution: TestStateExecution @@ -49,6 +56,16 @@ def terminated(self) -> None: self.execution.exec_status = ExecutionStatus.SUCCEEDED self.execution.output = self.execution.exec_worker.env.states.get_input() self.execution.next_state = exit_program_state.next_state_name + elif isinstance(exit_program_state, ProgramCaughtError): + self.execution.exec_status = ExecutionStatus.SUCCEEDED + self.execution.error = exit_program_state.error + self.execution.cause = exit_program_state.cause + self.execution.output = self.execution.exec_worker.env.states.get_input() + self.execution.next_state = exit_program_state.next_state_name + elif isinstance(exit_program_state, ProgramRetriable): + self.execution.exec_status = ExecutionStatus.SUCCEEDED + self.execution.error = exit_program_state.error + self.execution.cause = exit_program_state.cause else: self._reflect_execution_status() @@ -62,7 +79,10 @@ def __init__( state_machine: StateMachineInstance, start_date: Timestamp, activity_store: dict[Arn, Activity], - input_data: Optional[dict] = None, + state_name: str | None = None, + input_data: dict | None = None, + mock: TestStateMock | None = None, + variables: dict | None = None, ): super().__init__( name=name, @@ -80,6 +100,9 @@ def __init__( ) self._execution_terminated_event = threading.Event() self.next_state = None + self.state_name = state_name + self.mock = mock + self.variables = variables def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication: return self.TestCaseExecutionWorkerCommunication(self) @@ -94,6 +117,9 @@ def _get_start_execution_worker(self) -> TestStateExecutionWorker: exec_comm=self._get_start_execution_worker_comm(), cloud_watch_logging_session=self._cloud_watch_logging_session, activity_store=self._activity_store, + state_name=self.state_name, + mock=self.mock, + variables=self.variables, ) def publish_execution_status_change_event(self): @@ -118,6 +144,21 @@ def to_test_state_output(self, inspection_level: InspectionLevel) -> TestStateOu test_state_output = TestStateOutput( status=TestExecutionStatus.SUCCEEDED, nextState=self.next_state, output=output_str ) + elif isinstance(exit_program_state, ProgramCaughtError): + output_str = to_json_str(self.output) + test_state_output = TestStateOutput( + status=TestExecutionStatus.CAUGHT_ERROR, + nextState=self.next_state, + output=output_str, + error=exit_program_state.error, + cause=exit_program_state.cause, + ) + elif isinstance(exit_program_state, ProgramRetriable): + test_state_output = TestStateOutput( + status=TestExecutionStatus.RETRIABLE, + error=exit_program_state.error, + cause=exit_program_state.cause, + ) else: # TODO: handle other statuses LOG.warning( diff --git a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py index b70c7d41bd6a3..32de04fef6b65 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py +++ b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py @@ -1,28 +1,61 @@ -from typing import Optional - +from localstack.aws.api.stepfunctions import Arn, StateName from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent from localstack.services.stepfunctions.asl.eval.environment import Environment +from localstack.services.stepfunctions.asl.eval.evaluation_details import EvaluationDetails from localstack.services.stepfunctions.asl.eval.event.event_manager import ( EventHistoryContext, ) +from localstack.services.stepfunctions.asl.eval.event.logging import ( + CloudWatchLoggingSession, +) from localstack.services.stepfunctions.asl.eval.states import ( ContextObjectData, ExecutionData, StateMachineData, ) from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment +from localstack.services.stepfunctions.asl.eval.variable_store import VariableStore from localstack.services.stepfunctions.asl.parse.test_state.asl_parser import ( TestStateAmazonStateLanguageParser, ) +from localstack.services.stepfunctions.backend.activity import Activity from localstack.services.stepfunctions.backend.execution_worker import SyncExecutionWorker +from localstack.services.stepfunctions.backend.execution_worker_comm import ( + ExecutionWorkerCommunication, +) +from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock class TestStateExecutionWorker(SyncExecutionWorker): - env: Optional[TestStateEnvironment] + env: TestStateEnvironment | None + state_name: str | None = None + mock: TestStateMock | None + variables: dict | None + + def __init__( + self, + evaluation_details: EvaluationDetails, + exec_comm: ExecutionWorkerCommunication, + cloud_watch_logging_session: CloudWatchLoggingSession | None, + activity_store: dict[Arn, Activity], + state_name: StateName | None = None, + mock: TestStateMock | None = None, + variables: dict | None = None, + ): + super().__init__( + evaluation_details, + exec_comm, + cloud_watch_logging_session, + activity_store, + local_mock_test_case=None, # local mock is only applicable to SFN Local, but not for TestState + ) + self.state_name = state_name + self.mock = mock + self.variables = variables def _get_evaluation_entrypoint(self) -> EvalComponent: return TestStateAmazonStateLanguageParser.parse( - self._evaluation_details.state_machine_details.definition + self._evaluation_details.state_machine_details.definition, self.state_name )[0] def _get_evaluation_environment(self) -> Environment: @@ -45,4 +78,6 @@ def _get_evaluation_environment(self) -> Environment: event_history_context=EventHistoryContext.of_program_start(), cloud_watch_logging_session=self._cloud_watch_logging_session, activity_store=self._activity_store, + variable_store=VariableStore(self.variables), + mock=self.mock, ) diff --git a/localstack-core/localstack/services/stepfunctions/backend/test_state/test_state_mock.py b/localstack-core/localstack/services/stepfunctions/backend/test_state/test_state_mock.py new file mode 100644 index 0000000000000..c4822fddc65a6 --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/backend/test_state/test_state_mock.py @@ -0,0 +1,127 @@ +import copy +import json +from typing import Final + +from pydantic import ( + ValidationError, +) + +from localstack.aws.api.stepfunctions import ( + HistoryEventType, + MockInput, + TaskFailedEventDetails, + TestStateConfiguration, +) +from localstack.services.stepfunctions.asl.component.common.error_name.error_name import ErrorName +from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( + FailureEvent, + FailureEventException, +) +from localstack.services.stepfunctions.asl.component.state.state_type import StateType +from localstack.services.stepfunctions.asl.eval.environment import Environment +from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails +from localstack.services.stepfunctions.asl.eval.states import ( + ContextObjectData, +) +from localstack.services.stepfunctions.test_state.mock_config import ( + TestStateContextObjectValidator, + TestStateMockedResponse, + TestStateResponseReturn, + TestStateResponseThrow, +) + + +def eval_mocked_response_throw(env: Environment, mocked_response: TestStateResponseThrow) -> None: + task_failed_event_details = TaskFailedEventDetails( + error=mocked_response.error, cause=mocked_response.cause + ) + error_name = ErrorName(mocked_response.error) + failure_event = FailureEvent( + env=env, + error_name=error_name, + event_type=HistoryEventType.TaskFailed, # TODO(gregfurman): Should this be state specific? + event_details=EventDetails(taskFailedEventDetails=task_failed_event_details), + ) + raise FailureEventException(failure_event=failure_event) + + +class TestStateMock: + _mock_input: MockInput | None + _state_configuration: TestStateConfiguration | None + _result_stack: Final[list[TestStateMockedResponse]] + _context: Final[ContextObjectData | None] + + def __init__( + self, + mock_input: MockInput | None, + state_configuration: TestStateConfiguration | None, + context: str | None, + ): + self._mock_input = mock_input + self._state_configuration = state_configuration + self._result_stack = [] + self._context = None + + if not mock_input: + return + + self._context = None if context is None else self.parse_context(context) + + if mock_result_raw := mock_input.get("result"): + mock = json.loads(mock_result_raw) + self._result_stack.append(TestStateResponseReturn(mock)) + return + + if mock_error_output := mock_input.get("errorOutput"): + mock = copy.deepcopy(mock_error_output) + self._result_stack.append(TestStateResponseThrow(**mock)) + return + + def is_mocked(self): + if self._mock_input or self._state_configuration: + return True + + return False + + def add_result(self, result: TestStateMockedResponse): + mock = copy.deepcopy(result) + self._result_stack.append(mock) + + def get_next_result(self) -> TestStateMockedResponse: + if not self._result_stack: + return None + return self._result_stack.pop() + + def get_context(self) -> ContextObjectData | None: + if self._context is not None: + return copy.deepcopy(self._context) + return None + + @staticmethod + def parse_context(context: str, state_type: StateType = None) -> ContextObjectData: + """Parse and validate context JSON string.""" + try: + validation_result = TestStateContextObjectValidator.model_validate_json(context) + return validation_result.model_dump(exclude_unset=True, exclude_none=True) + except ValidationError as e: + error = e.errors()[0] + path_str = ".".join(str(x) for x in error["loc"]) + + match error: + case {"type": "extra_forbidden", "loc": ("Map",)}: + raise ValueError("'Map' field is not supported when mocking a Context object") + + case {"type": "extra_forbidden", "loc": (*_, forbidden_key)}: + raise ValueError(f"Field '{forbidden_key}' is not allowed") + + case {"type": t} if t in ("string_type", "int_type", "dict_type", "model_type"): + expected_map = { + "string_type": "string", + "int_type": "integer", + "dict_type": "object", + "model_type": "object", + } + expected = expected_map.get(t, "valid type") + raise ValueError(f"{path_str} must be a {expected}") + case _: + raise ValueError(f"{error['msg']}") diff --git a/localstack-core/localstack/services/stepfunctions/local_mocking/__init__.py b/localstack-core/localstack/services/stepfunctions/local_mocking/__init__.py new file mode 100644 index 0000000000000..168f86b9bc63a --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/local_mocking/__init__.py @@ -0,0 +1,9 @@ +""" +local_mocking +------------- + +The implementation of the Step Functions Local Mocking + +Note that Step Functions Local is different from TestState API mocks. +TestState API mocking works differently and is implemented separately. +""" diff --git a/localstack-core/localstack/services/stepfunctions/mocking/mock_config.py b/localstack-core/localstack/services/stepfunctions/local_mocking/mock_config.py similarity index 83% rename from localstack-core/localstack/services/stepfunctions/mocking/mock_config.py rename to localstack-core/localstack/services/stepfunctions/local_mocking/mock_config.py index 25f71acee35d5..62cedf0480c9d 100644 --- a/localstack-core/localstack/services/stepfunctions/mocking/mock_config.py +++ b/localstack-core/localstack/services/stepfunctions/local_mocking/mock_config.py @@ -1,7 +1,7 @@ import abc -from typing import Any, Final, Optional +from typing import Any, Final -from localstack.services.stepfunctions.mocking.mock_config_file import ( +from localstack.services.stepfunctions.local_mocking.mock_config_file import ( RawMockConfig, RawResponseModel, RawTestCase, @@ -9,7 +9,7 @@ ) -class MockedResponse(abc.ABC): +class LocalMockedResponse(abc.ABC): range_start: Final[int] range_end: Final[int] @@ -28,7 +28,7 @@ def __init__(self, range_start: int, range_end: int): self.range_end = range_end -class MockedResponseReturn(MockedResponse): +class LocalMockedResponseReturn(LocalMockedResponse): payload: Final[Any] def __init__(self, range_start: int, range_end: int, payload: Any): @@ -36,7 +36,7 @@ def __init__(self, range_start: int, range_end: int, payload: Any): self.payload = payload -class MockedResponseThrow(MockedResponse): +class LocalMockedResponseThrow(LocalMockedResponse): error: Final[str] cause: Final[str] @@ -49,14 +49,17 @@ def __init__(self, range_start: int, range_end: int, error: str, cause: str): class StateMockedResponses: state_name: Final[str] mocked_response_name: Final[str] - mocked_responses: Final[list[MockedResponse]] + mocked_responses: Final[list[LocalMockedResponse]] def __init__( - self, state_name: str, mocked_response_name: str, mocked_responses: list[MockedResponse] + self, + state_name: str, + mocked_response_name: str, + mocked_responses: list[LocalMockedResponse], ): self.state_name = state_name self.mocked_response_name = mocked_response_name - self.mocked_responses = list() + self.mocked_responses = [] last_range_end: int = -1 mocked_responses_sorted = sorted(mocked_responses, key=lambda mr: mr.range_start) for mocked_response in mocked_responses_sorted: @@ -74,7 +77,7 @@ def __init__( last_range_end = mocked_response.range_end -class MockTestCase: +class LocalMockTestCase: state_machine_name: Final[str] test_case_name: Final[str] state_mocked_responses: Final[dict[str, StateMockedResponses]] @@ -87,7 +90,7 @@ def __init__( ): self.state_machine_name = state_machine_name self.test_case_name = test_case_name - self.state_mocked_responses = dict() + self.state_mocked_responses = {} for state_mocked_response in state_mocked_responses_list: state_name = state_mocked_response.state_name if state_name in self.state_mocked_responses: @@ -127,13 +130,15 @@ def _parse_mocked_response_range(string_definition: str) -> tuple[int, int]: def _mocked_response_from_raw( raw_response_model_range: str, raw_response_model: RawResponseModel -) -> MockedResponse: +) -> LocalMockedResponse: range_start, range_end = _parse_mocked_response_range(raw_response_model_range) if raw_response_model.Return: payload = raw_response_model.Return.model_dump() - return MockedResponseReturn(range_start=range_start, range_end=range_end, payload=payload) + return LocalMockedResponseReturn( + range_start=range_start, range_end=range_end, payload=payload + ) throw_definition = raw_response_model.Throw - return MockedResponseThrow( + return LocalMockedResponseThrow( range_start=range_start, range_end=range_end, error=throw_definition.Error, @@ -143,17 +148,17 @@ def _mocked_response_from_raw( def _mocked_responses_from_raw( mocked_response_name: str, raw_mock_config: RawMockConfig -) -> list[MockedResponse]: - raw_response_models: Optional[dict[str, RawResponseModel]] = ( - raw_mock_config.MockedResponses.get(mocked_response_name) +) -> list[LocalMockedResponse]: + raw_response_models: dict[str, RawResponseModel] | None = raw_mock_config.MockedResponses.get( + mocked_response_name ) if not raw_response_models: raise RuntimeError( f"No definitions for mocked response '{mocked_response_name}' in the mock configuration file." ) - mocked_responses: list[MockedResponse] = list() + mocked_responses: list[LocalMockedResponse] = [] for raw_response_model_range, raw_response_model in raw_response_models.items(): - mocked_response: MockedResponse = _mocked_response_from_raw( + mocked_response: LocalMockedResponse = _mocked_response_from_raw( raw_response_model_range=raw_response_model_range, raw_response_model=raw_response_model ) mocked_responses.append(mocked_response) @@ -175,7 +180,7 @@ def _state_mocked_responses_from_raw( def _mock_test_case_from_raw( state_machine_name: str, test_case_name: str, raw_mock_config: RawMockConfig -) -> MockTestCase: +) -> LocalMockTestCase: state_machine = raw_mock_config.StateMachines.get(state_machine_name) if not state_machine: raise RuntimeError( @@ -187,7 +192,7 @@ def _mock_test_case_from_raw( f"No definitions for test case '{test_case_name}' and " f"state machine '{state_machine_name}' in the mock configuration file." ) - state_mocked_responses_list: list[StateMockedResponses] = list() + state_mocked_responses_list: list[StateMockedResponses] = [] for state_name, mocked_response_name in test_case.root.items(): state_mocked_responses = _state_mocked_responses_from_raw( state_name=state_name, @@ -195,18 +200,20 @@ def _mock_test_case_from_raw( raw_mock_config=raw_mock_config, ) state_mocked_responses_list.append(state_mocked_responses) - return MockTestCase( + return LocalMockTestCase( state_machine_name=state_machine_name, test_case_name=test_case_name, state_mocked_responses_list=state_mocked_responses_list, ) -def load_mock_test_case_for(state_machine_name: str, test_case_name: str) -> Optional[MockTestCase]: - raw_mock_config: Optional[RawMockConfig] = _load_sfn_raw_mock_config() +def load_local_mock_test_case_for( + state_machine_name: str, test_case_name: str +) -> LocalMockTestCase | None: + raw_mock_config: RawMockConfig | None = _load_sfn_raw_mock_config() if raw_mock_config is None: return None - mock_test_case: MockTestCase = _mock_test_case_from_raw( + mock_test_case: LocalMockTestCase = _mock_test_case_from_raw( state_machine_name=state_machine_name, test_case_name=test_case_name, raw_mock_config=raw_mock_config, diff --git a/localstack-core/localstack/services/stepfunctions/mocking/mock_config_file.py b/localstack-core/localstack/services/stepfunctions/local_mocking/mock_config_file.py similarity index 92% rename from localstack-core/localstack/services/stepfunctions/mocking/mock_config_file.py rename to localstack-core/localstack/services/stepfunctions/local_mocking/mock_config_file.py index 145ffd20750a2..183116f9e9c6f 100644 --- a/localstack-core/localstack/services/stepfunctions/mocking/mock_config_file.py +++ b/localstack-core/localstack/services/stepfunctions/local_mocking/mock_config_file.py @@ -2,7 +2,7 @@ import os from functools import lru_cache from json import JSONDecodeError -from typing import Any, Dict, Final, Optional +from typing import Any, Final, Optional from pydantic import BaseModel, RootModel, ValidationError, model_validator @@ -44,8 +44,8 @@ class RawResponseModel(BaseModel): model_config = {"frozen": True} - Return: Optional[RawReturnResponse] = None - Throw: Optional[RawThrowResponse] = None + Return: RawReturnResponse | None = None + Throw: RawThrowResponse | None = None @model_validator(mode="before") def validate_response(cls, data: dict) -> dict: @@ -56,7 +56,7 @@ def validate_response(cls, data: dict) -> dict: return data -class RawTestCase(RootModel[Dict[str, str]]): +class RawTestCase(RootModel[dict[str, str]]): """ Represents an individual test case. The keys are state names (e.g., 'LambdaState', 'SQSState') @@ -73,7 +73,7 @@ class RawStateMachine(BaseModel): model_config = {"frozen": True} - TestCases: Dict[str, RawTestCase] + TestCases: dict[str, RawTestCase] class RawMockConfig(BaseModel): @@ -86,8 +86,8 @@ class RawMockConfig(BaseModel): model_config = {"frozen": True} - StateMachines: Dict[str, RawStateMachine] - MockedResponses: Dict[str, Dict[str, RawResponseModel]] + StateMachines: dict[str, RawStateMachine] + MockedResponses: dict[str, dict[str, RawResponseModel]] @lru_cache(maxsize=1) @@ -119,11 +119,11 @@ def _read_sfn_raw_mock_config(file_path: str, modified_epoch: int) -> Optional[R - Logging is used to capture warnings if file access or parsing fails. """ try: - with open(file_path, "r") as df: + with open(file_path) as df: mock_config_str = df.read() mock_config: RawMockConfig = RawMockConfig.model_validate_json(mock_config_str) return mock_config - except (OSError, IOError) as file_error: + except OSError as file_error: LOG.error("Failed to open mock configuration file '%s'. Error: %s", file_path, file_error) return None except ValidationError as validation_error: @@ -168,7 +168,7 @@ def _read_sfn_raw_mock_config(file_path: str, modified_epoch: int) -> Optional[R return None -def _load_sfn_raw_mock_config() -> Optional[RawMockConfig]: +def _load_sfn_raw_mock_config() -> RawMockConfig | None: configuration_file_path = config.SFN_MOCK_CONFIG if not configuration_file_path: return None diff --git a/localstack-core/localstack/services/stepfunctions/packages.py b/localstack-core/localstack/services/stepfunctions/packages.py index b96f7a8d775f0..e9b853682c53b 100644 --- a/localstack-core/localstack/services/stepfunctions/packages.py +++ b/localstack-core/localstack/services/stepfunctions/packages.py @@ -1,6 +1,6 @@ -from localstack.packages import Package, PackageInstaller +from localstack.packages import InstallTarget, Package, PackageInstaller from localstack.packages.core import MavenPackageInstaller -from localstack.packages.java import JavaInstallerMixin +from localstack.packages.java import JavaInstallerMixin, java_package JSONATA_DEFAULT_VERSION = "0.9.7" JACKSON_DEFAULT_VERSION = "2.16.2" @@ -12,9 +12,6 @@ class JSONataPackage(Package): def __init__(self): super().__init__("JSONataLibs", JSONATA_DEFAULT_VERSION) - # Match the dynamodb-local JRE version to reduce the LocalStack image size by sharing the same JRE version - self.java_version = "21" - def get_versions(self) -> list[str]: return list(JSONATA_JACKSON_VERSION_STORE.keys()) @@ -25,6 +22,10 @@ def _get_installer(self, version: str) -> PackageInstaller: class JSONataPackageInstaller(JavaInstallerMixin, MavenPackageInstaller): def __init__(self, version: str): jackson_version = JSONATA_JACKSON_VERSION_STORE[version] + + # Match the dynamodb-local JRE version to reduce the LocalStack image size by sharing the same JRE version + self.java_version = "21" + super().__init__( f"pkg:maven/com.dashjoin/jsonata@{version}", # jackson-databind is imported in jsonata.py as "from com.fasterxml.jackson.databind import ObjectMapper" @@ -35,5 +36,13 @@ def __init__(self, version: str): f"pkg:maven/com.fasterxml.jackson.core/jackson-databind@{jackson_version}", ) + def _prepare_installation(self, target: InstallTarget) -> None: + # override to install correct java version + java_package.get_installer(self.java_version).install(target) + + def get_java_home(self) -> str | None: + """Override to use the specific Java version""" + return java_package.get_installer(self.java_version).get_java_home() + jpype_jsonata_package = JSONataPackage() diff --git a/localstack-core/localstack/services/stepfunctions/provider.py b/localstack-core/localstack/services/stepfunctions/provider.py index 2202014eb0b90..08d0459fd26bd 100644 --- a/localstack-core/localstack/services/stepfunctions/provider.py +++ b/localstack-core/localstack/services/stepfunctions/provider.py @@ -4,7 +4,7 @@ import logging import re import time -from typing import Final, Optional +from typing import Final from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api.stepfunctions import ( @@ -63,7 +63,6 @@ Publish, PublishStateMachineVersionOutput, ResourceNotFound, - RevealSecrets, ReverseOrder, RevisionId, RoutingConfigurationList, @@ -89,6 +88,7 @@ TaskDoesNotExist, TaskTimedOut, TaskToken, + TestStateInput, TestStateOutput, ToleratedFailureCount, ToleratedFailurePercentage, @@ -140,19 +140,20 @@ from localstack.services.stepfunctions.backend.activity import Activity, ActivityTask from localstack.services.stepfunctions.backend.alias import Alias from localstack.services.stepfunctions.backend.execution import Execution, SyncExecution +from localstack.services.stepfunctions.backend.models import SFNStore, sfn_stores from localstack.services.stepfunctions.backend.state_machine import ( StateMachineInstance, StateMachineRevision, StateMachineVersion, TestStateMachine, ) -from localstack.services.stepfunctions.backend.store import SFNStore, sfn_stores from localstack.services.stepfunctions.backend.test_state.execution import ( TestStateExecution, ) -from localstack.services.stepfunctions.mocking.mock_config import ( - MockTestCase, - load_mock_test_case_for, +from localstack.services.stepfunctions.backend.test_state.test_state_mock import TestStateMock +from localstack.services.stepfunctions.local_mocking.mock_config import ( + LocalMockTestCase, + load_local_mock_test_case_for, ) from localstack.services.stepfunctions.stepfunctions_utils import ( assert_pagination_parameters_valid, @@ -160,6 +161,7 @@ normalise_max_results, ) from localstack.state import StateVisitor +from localstack.utils.aws import arns from localstack.utils.aws.arns import ( ARN_PARTITION_REGEX, stepfunctions_activity_arn, @@ -275,7 +277,7 @@ def _validate_state_machine_alias_name(name: CharacterRestrictedName) -> None: ) def _get_execution(self, context: RequestContext, execution_arn: Arn) -> Execution: - execution: Optional[Execution] = self.get_store(context).executions.get(execution_arn) + execution: Execution | None = self.get_store(context).executions.get(execution_arn) if not execution: raise ExecutionDoesNotExist(f"Execution Does Not Exist: '{execution_arn}'") return execution @@ -283,7 +285,7 @@ def _get_execution(self, context: RequestContext, execution_arn: Arn) -> Executi def _get_executions( self, context: RequestContext, - execution_status: Optional[ExecutionStatus] = None, + execution_status: ExecutionStatus | None = None, ): store = self.get_store(context) execution: list[Execution] = list(store.executions.values()) @@ -297,9 +299,7 @@ def _get_executions( return execution def _get_activity(self, context: RequestContext, activity_arn: Arn) -> Activity: - maybe_activity: Optional[Activity] = self.get_store(context).activities.get( - activity_arn, None - ) + maybe_activity: Activity | None = self.get_store(context).activities.get(activity_arn, None) if maybe_activity is None: raise ActivityDoesNotExist(f"Activity Does Not Exist: '{activity_arn}'") return maybe_activity @@ -312,7 +312,7 @@ def _idempotent_revision( state_machine_type: StateMachineType, logging_configuration: LoggingConfiguration, tracing_configuration: TracingConfiguration, - ) -> Optional[StateMachineRevision]: + ) -> StateMachineRevision | None: # CreateStateMachine's idempotency check is based on the state machine name, definition, type, # LoggingConfiguration and TracingConfiguration. # If a following request has a different roleArn or tags, Step Functions will ignore these differences and @@ -338,11 +338,11 @@ def _idempotent_revision( def _idempotent_start_execution( self, - execution: Optional[Execution], + execution: Execution | None, state_machine: StateMachineInstance, name: Name, input_data: SensitiveData, - ) -> Optional[Execution]: + ) -> Execution | None: # StartExecution is idempotent for STANDARD workflows. For a STANDARD workflow, # if you call StartExecution with the same name and input as a running execution, # the call succeeds and return the same response as the original request. @@ -366,9 +366,7 @@ def _idempotent_start_execution( message=f"Execution Already Exists: '{execution.exec_arn}'", ) - def _revision_by_name( - self, context: RequestContext, name: str - ) -> Optional[StateMachineInstance]: + def _revision_by_name(self, context: RequestContext, name: str) -> StateMachineInstance | None: state_machines: list[StateMachineInstance] = list( self.get_store(context).state_machines.values() ) @@ -445,7 +443,7 @@ def create_state_machine( # CreateStateMachine is an idempotent API. Subsequent requests won't create a duplicate resource if it was # already created. - idem_state_machine: Optional[StateMachineRevision] = self._idempotent_revision( + idem_state_machine: StateMachineRevision | None = self._idempotent_revision( context=context, name=state_machine_name, definition=state_machine_definition, @@ -460,7 +458,7 @@ def create_state_machine( ) # Assert this state machine name is unique. - state_machine_with_name: Optional[StateMachineRevision] = self._revision_by_name( + state_machine_with_name: StateMachineRevision | None = self._revision_by_name( context=context, name=state_machine_name ) if state_machine_with_name is not None: @@ -690,9 +688,7 @@ def describe_state_machine_alias( self, context: RequestContext, state_machine_alias_arn: Arn, **kwargs ) -> DescribeStateMachineAliasOutput: self._validate_state_machine_alias_arn(state_machine_alias_arn=state_machine_alias_arn) - alias: Optional[Alias] = self.get_store(context=context).aliases.get( - state_machine_alias_arn - ) + alias: Alias | None = self.get_store(context=context).aliases.get(state_machine_alias_arn) if alias is None: # TODO: assemble the correct exception raise ValidationException() @@ -778,16 +774,16 @@ def _get_state_machine_arn(state_machine_arn: str) -> str: return state_machine_arn.split("#")[0] @staticmethod - def _get_mock_test_case( + def _get_local_mock_test_case( state_machine_arn: str, state_machine_name: str - ) -> Optional[MockTestCase]: + ) -> LocalMockTestCase | None: """Extract and load a mock test case from a state machine ARN if present.""" parts = state_machine_arn.split("#") if len(parts) != 2: return None mock_test_case_name = parts[1] - mock_test_case = load_mock_test_case_for( + mock_test_case = load_local_mock_test_case_for( state_machine_name=state_machine_name, test_case_name=mock_test_case_name ) if mock_test_case is None: @@ -813,9 +809,9 @@ def start_execution( base_arn = self._get_state_machine_arn(state_machine_arn) store = self.get_store(context=context) - alias: Optional[Alias] = store.aliases.get(base_arn) + alias: Alias | None = store.aliases.get(base_arn) alias_sample_state_machine_version_arn = alias.sample() if alias is not None else None - unsafe_state_machine: Optional[StateMachineInstance] = store.state_machines.get( + unsafe_state_machine: StateMachineInstance | None = store.state_machines.get( alias_sample_state_machine_version_arn or base_arn ) if not unsafe_state_machine: @@ -825,7 +821,7 @@ def start_execution( state_machine_clone = copy.deepcopy(unsafe_state_machine) if input is None: - input_data = dict() + input_data = {} else: try: input_data = json.loads(input) @@ -864,7 +860,9 @@ def start_execution( configuration=state_machine_clone.cloud_watch_logging_configuration, ) - mock_test_case = self._get_mock_test_case(state_machine_arn, state_machine_clone.name) + local_mock_test_case = self._get_local_mock_test_case( + state_machine_arn, state_machine_clone.name + ) execution = Execution( name=exec_name, @@ -875,12 +873,12 @@ def start_execution( region_name=context.region, state_machine=state_machine_clone, state_machine_alias_arn=alias.state_machine_alias_arn if alias is not None else None, - start_date=datetime.datetime.now(tz=datetime.timezone.utc), + start_date=datetime.datetime.now(tz=datetime.UTC), cloud_watch_logging_session=cloud_watch_logging_session, input_data=input_data, trace_header=trace_header, activity_store=self.get_store(context).activities, - mock_test_case=mock_test_case, + local_mock_test_case=local_mock_test_case, ) store.executions[exec_arn] = execution @@ -901,7 +899,7 @@ def start_sync_execution( self._validate_state_machine_arn(state_machine_arn) base_arn = self._get_state_machine_arn(state_machine_arn) - unsafe_state_machine: Optional[StateMachineInstance] = self.get_store( + unsafe_state_machine: StateMachineInstance | None = self.get_store( context ).state_machines.get(base_arn) if not unsafe_state_machine: @@ -914,7 +912,7 @@ def start_sync_execution( state_machine_clone = copy.deepcopy(unsafe_state_machine) if input is None: - input_data = dict() + input_data = {} else: try: input_data = json.loads(input) @@ -940,7 +938,9 @@ def start_sync_execution( configuration=state_machine_clone.cloud_watch_logging_configuration, ) - mock_test_case = self._get_mock_test_case(state_machine_arn, state_machine_clone.name) + local_mock_test_case = self._get_local_mock_test_case( + state_machine_arn, state_machine_clone.name + ) execution = SyncExecution( name=exec_name, @@ -950,12 +950,12 @@ def start_sync_execution( account_id=context.account_id, region_name=context.region, state_machine=state_machine_clone, - start_date=datetime.datetime.now(tz=datetime.timezone.utc), + start_date=datetime.datetime.now(tz=datetime.UTC), cloud_watch_logging_session=cloud_watch_logging_session, input_data=input_data, trace_header=trace_header, activity_store=self.get_store(context).activities, - mock_test_case=mock_test_case, + local_mock_test_case=local_mock_test_case, ) self.get_store(context).executions[exec_arn] = execution @@ -980,7 +980,7 @@ def describe_execution( @staticmethod def _list_execution_filter( - ex: Execution, state_machine_arn: str, status_filter: Optional[str] + ex: Execution, state_machine_arn: str, status_filter: str | None ) -> bool: state_machine_reference_arn_set = {ex.state_machine_arn, ex.state_machine_version_arn} if state_machine_arn not in state_machine_reference_arn_set: @@ -1105,7 +1105,7 @@ def list_state_machine_aliases( if not isinstance(state_machine_revision, StateMachineRevision): raise InvalidArn(f"Invalid arn: {state_machine_arn}") - state_machine_aliases: StateMachineAliasList = list() + state_machine_aliases: StateMachineAliasList = [] valid_token_found = next_token is None for alias in state_machine_revision.aliases: @@ -1149,7 +1149,7 @@ def list_state_machine_versions( if not isinstance(state_machine_revision, StateMachineRevision): raise InvalidArn(f"Invalid arn: {state_machine_arn}") - state_machine_version_items = list() + state_machine_version_items = [] for version_arn in state_machine_revision.versions.values(): state_machine_version = state_machines[version_arn] if isinstance(state_machine_version, StateMachineVersion): @@ -1245,7 +1245,7 @@ def delete_state_machine_version( if ( state_machine_revision := state_machines.get(state_machine_version.source_arn) ) and isinstance(state_machine_revision, StateMachineRevision): - referencing_alias_names: list[str] = list() + referencing_alias_names: list[str] = [] for alias in state_machine_revision.aliases: if alias.is_router_for(state_machine_version_arn=state_machine_version_arn): referencing_alias_names.append(alias.name) @@ -1275,7 +1275,7 @@ def stop_execution( if execution.sm_type != StateMachineType.STANDARD: self._raise_resource_type_not_in_context(resource_type=execution.sm_type) - stop_date = datetime.datetime.now(tz=datetime.timezone.utc) + stop_date = datetime.datetime.now(tz=datetime.UTC) execution.stop(stop_date=stop_date, cause=cause, error=error) return StopExecutionOutput(stopDate=stop_date) @@ -1328,9 +1328,7 @@ def update_state_machine( target_revision_id = revision_id or state_machine.revision_id version_arn = state_machine.versions[target_revision_id] - update_output = UpdateStateMachineOutput( - updateDate=datetime.datetime.now(tz=datetime.timezone.utc) - ) + update_output = UpdateStateMachineOutput(updateDate=datetime.datetime.now(tz=datetime.UTC)) if revision_id is not None: update_output["revisionId"] = revision_id if version_arn is not None: @@ -1437,7 +1435,7 @@ def describe_map_run( ) -> DescribeMapRunOutput: store = self.get_store(context) for execution in store.executions.values(): - map_run_record: Optional[MapRunRecord] = ( + map_run_record: MapRunRecord | None = ( execution.exec_worker.env.map_run_record_pool_manager.get(map_run_arn) ) if map_run_record is not None: @@ -1477,7 +1475,7 @@ def update_map_run( # TODO: investigate behaviour of empty requests. store = self.get_store(context) for execution in store.executions.values(): - map_run_record: Optional[MapRunRecord] = ( + map_run_record: MapRunRecord | None = ( execution.exec_worker.env.map_run_record_pool_manager.get(map_run_arn) ) if map_run_record is not None: @@ -1493,48 +1491,101 @@ def update_map_run( raise ResourceNotFound() def test_state( - self, - context: RequestContext, - definition: Definition, - role_arn: Arn = None, - input: SensitiveData = None, - inspection_level: InspectionLevel = None, - reveal_secrets: RevealSecrets = None, - variables: SensitiveData = None, - **kwargs, + self, context: RequestContext, request: TestStateInput, **kwargs ) -> TestStateOutput: + state_name = request.get("stateName") + definition = request["definition"] + StepFunctionsProvider._validate_definition( - definition=definition, static_analysers=[TestStateStaticAnalyser()] + definition=definition, + static_analysers=[TestStateStaticAnalyser(state_name)], ) - name: Optional[Name] = f"TestState-{short_uid()}" + # if StateName is present, we need to ensure the state being referenced exists in full definition. + if state_name and not TestStateStaticAnalyser.is_state_in_definition( + definition=definition, state_name=state_name + ): + raise ValidationException("State not found in definition") + + mock_input = request.get("mock") + state_configuration = request.get("stateConfiguration") + + TestStateStaticAnalyser.validate_state_configuration(state_configuration, mock_input) + TestStateStaticAnalyser.validate_mock(test_state_input=request) + + if state_context := request.get("context"): + # TODO: Add validation ensuring only present if 'mock' is specified + # An error occurred (ValidationException) when calling the TestState operation: State type 'Pass' is not supported when a mock is specified + pass + + try: + state_mock = TestStateMock( + mock_input=mock_input, + state_configuration=state_configuration, + context=state_context, + ) + except ValueError as e: + LOG.error(e) + raise ValidationException(f"Invalid Context object provided: {e}") + + name: Name | None = f"TestState-{short_uid()}" arn = stepfunctions_state_machine_arn( name=name, account_id=context.account_id, region_name=context.region ) + role_arn = request.get("roleArn") + if role_arn is None: + TestStateStaticAnalyser.validate_role_arn_required( + mock_input=mock_input, definition=definition, state_name=state_name + ) + # HACK: Added dummy role ARN because it is a required field in Execution. + # To allow optional roleArn for the test state but preserve the mandatory one for regular executions + # we likely need to remove inheritance TestStateExecution(Execution) in favor of composition. + # TestState execution starts to have too many simplifications compared to a regular execution + # which renders the inheritance mechanism harmful. + # TODO make role_arn optional in TestStateExecution + role_arn = arns.iam_role_arn( + role_name=f"RoleFor-{name}", + account_id=context.account_id, + region_name=context.region, + ) + state_machine = TestStateMachine( name=name, arn=arn, role_arn=role_arn, - definition=definition, + definition=request["definition"], ) - exec_arn = stepfunctions_standard_execution_arn(state_machine.arn, name) - input_json = json.loads(input) + # HACK(gregfurman): The ARN that gets generated has a duplicate 'name' field in the + # resource ARN. Just replace this duplication and extract the execution ID. + exec_arn = stepfunctions_express_execution_arn(state_machine.arn, name) + exec_arn = exec_arn.replace(f":{name}:{name}:", f":{name}:", 1) + _, exec_name = exec_arn.rsplit(":", 1) + + if input_json := request.get("input", {}): + input_json = json.loads(input_json) + + if variables_json := request.get("variables"): + variables_json = json.loads(variables_json) + execution = TestStateExecution( - name=name, + name=exec_name, role_arn=role_arn, exec_arn=exec_arn, account_id=context.account_id, region_name=context.region, state_machine=state_machine, - start_date=datetime.datetime.now(tz=datetime.timezone.utc), + start_date=datetime.datetime.now(tz=datetime.UTC), input_data=input_json, + state_name=state_name, activity_store=self.get_store(context).activities, + mock=state_mock, + variables=variables_json, ) execution.start() test_state_output = execution.to_test_state_output( - inspection_level=inspection_level or InspectionLevel.INFO + inspection_level=request.get("inspectionLevel", InspectionLevel.INFO) ) return test_state_output @@ -1591,7 +1642,7 @@ def _send_activity_task_started( self, context: RequestContext, task_token: TaskToken, - worker_name: Optional[Name], + worker_name: Name | None, ) -> None: executions: list[Execution] = self._get_executions(context) for execution in executions: @@ -1604,7 +1655,7 @@ def _send_activity_task_started( raise InvalidToken() @staticmethod - def _pull_activity_task(activity: Activity) -> Optional[ActivityTask]: + def _pull_activity_task(activity: Activity) -> ActivityTask | None: seconds_left = 60 while seconds_left > 0: try: @@ -1624,7 +1675,7 @@ def get_activity_task( self._validate_activity_arn(activity_arn) activity = self._get_activity(context=context, activity_arn=activity_arn) - maybe_task: Optional[ActivityTask] = self._pull_activity_task(activity=activity) + maybe_task: ActivityTask | None = self._pull_activity_task(activity=activity) if maybe_task is not None: self._send_activity_task_started( context, maybe_task.task_token, worker_name=worker_name @@ -1645,13 +1696,13 @@ def validate_state_machine_definition( state_machine_type: StateMachineType = request.get("type", StateMachineType.STANDARD) definition: str = request["definition"] - static_analysers = list() + static_analysers = [] if state_machine_type == StateMachineType.STANDARD: static_analysers.append(StaticAnalyser()) else: static_analysers.append(ExpressStaticAnalyser()) - diagnostics: ValidateStateMachineDefinitionDiagnosticList = list() + diagnostics: ValidateStateMachineDefinitionDiagnosticList = [] try: StepFunctionsProvider._validate_definition( definition=definition, static_analysers=static_analysers diff --git a/localstack-core/localstack/services/stepfunctions/quotas.py b/localstack-core/localstack/services/stepfunctions/quotas.py index bf55f8256f51a..e8af8465e086a 100644 --- a/localstack-core/localstack/services/stepfunctions/quotas.py +++ b/localstack-core/localstack/services/stepfunctions/quotas.py @@ -1,12 +1,11 @@ -import json -from typing import Final, Union +from typing import Any, Final from localstack.services.stepfunctions.asl.utils.encoding import to_json_str MAX_STATE_SIZE_UTF8_BYTES: Final[int] = 256 * 1024 # 256 KB of data as a UTF-8 encoded string. -def is_within_size_quota(value: Union[str, json]) -> bool: +def is_within_size_quota(value: str | Any) -> bool: item_str = value if isinstance(value, str) else to_json_str(value) item_bytes = item_str.encode("utf-8") len_item_bytes = len(item_bytes) diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py index bea92e160ec03..766c8c196d6ab 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class StepFunctionsActivityProperties(TypedDict): - Name: Optional[str] - Arn: Optional[str] - Tags: Optional[list[TagsEntry]] + Name: str | None + Arn: str | None + Tags: list[TagsEntry] | None class TagsEntry(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py index b8f8891464a39..56afaee0a28a1 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class StepFunctionsActivityProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::StepFunctions::Activity" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.stepfunctions.resource_providers.aws_stepfunctions_activity import ( diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py index a1dd521ab5d4a..449574adfca49 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py @@ -4,7 +4,7 @@ import json import re from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -18,48 +18,48 @@ class StepFunctionsStateMachineProperties(TypedDict): - RoleArn: Optional[str] - Arn: Optional[str] - Definition: Optional[dict] - DefinitionS3Location: Optional[S3Location] - DefinitionString: Optional[str] - DefinitionSubstitutions: Optional[dict] - LoggingConfiguration: Optional[LoggingConfiguration] - Name: Optional[str] - StateMachineName: Optional[str] - StateMachineRevisionId: Optional[str] - StateMachineType: Optional[str] - Tags: Optional[list[TagsEntry]] - TracingConfiguration: Optional[TracingConfiguration] + RoleArn: str | None + Arn: str | None + Definition: dict | None + DefinitionS3Location: S3Location | None + DefinitionString: str | None + DefinitionSubstitutions: dict | None + LoggingConfiguration: LoggingConfiguration | None + Name: str | None + StateMachineName: str | None + StateMachineRevisionId: str | None + StateMachineType: str | None + Tags: list[TagsEntry] | None + TracingConfiguration: TracingConfiguration | None class CloudWatchLogsLogGroup(TypedDict): - LogGroupArn: Optional[str] + LogGroupArn: str | None class LogDestination(TypedDict): - CloudWatchLogsLogGroup: Optional[CloudWatchLogsLogGroup] + CloudWatchLogsLogGroup: CloudWatchLogsLogGroup | None class LoggingConfiguration(TypedDict): - Destinations: Optional[list[LogDestination]] - IncludeExecutionData: Optional[bool] - Level: Optional[str] + Destinations: list[LogDestination] | None + IncludeExecutionData: bool | None + Level: str | None class TracingConfiguration(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class S3Location(TypedDict): - Bucket: Optional[str] - Key: Optional[str] - Version: Optional[str] + Bucket: str | None + Key: str | None + Version: str | None class TagsEntry(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py index 744ff8120e5f6..8683d169a4bf7 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class StepFunctionsStateMachineProviderPlugin(CloudFormationResourceProviderPlug name = "AWS::StepFunctions::StateMachine" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.stepfunctions.resource_providers.aws_stepfunctions_statemachine import ( diff --git a/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py b/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py index 95133b4ed47e8..6b1850deb29a2 100644 --- a/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py +++ b/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py @@ -1,6 +1,5 @@ import base64 import logging -from typing import Dict from localstack.aws.api.stepfunctions import ValidationException from localstack.aws.connect import connect_to @@ -11,7 +10,7 @@ LOG = logging.getLogger(__name__) -def await_sfn_execution_result(execution_arn: str, timeout_secs: int = 60) -> Dict: +def await_sfn_execution_result(execution_arn: str, timeout_secs: int = 60) -> dict: """Wait until the given SFN execution ARN is no longer in RUNNING status, then return execution result.""" arn_data = parse_arn(execution_arn) diff --git a/localstack-core/localstack/services/stepfunctions/test_state/mock_config.py b/localstack-core/localstack/services/stepfunctions/test_state/mock_config.py new file mode 100644 index 0000000000000..6731a44cf78fc --- /dev/null +++ b/localstack-core/localstack/services/stepfunctions/test_state/mock_config.py @@ -0,0 +1,47 @@ +import abc +from typing import Any, Final + +from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr, create_model + +from localstack.services.stepfunctions.asl.eval.states import ( + ExecutionData, + StateData, + StateMachineData, + TaskData, +) + + +class TestStateMockedResponse(abc.ABC): + pass + + +class TestStateResponseReturn(TestStateMockedResponse): + payload: Final[Any] + + def __init__(self, payload: Any): + self.payload = payload + + +class TestStateResponseThrow(TestStateMockedResponse): + error: Final[str] + cause: Final[str] + + def __init__(self, error: str, cause: str): + self.error = error + self.cause = cause + + +def _to_strict_model(name: str, source: type): + type_map = {str: StrictStr, int: StrictInt} + fields = {k: (type_map.get(v, v) | None, None) for k, v in source.__annotations__.items()} + return create_model(name, __config__=ConfigDict(extra="forbid"), **fields) + + +TestStateContextObjectValidator: Final[type[BaseModel]] = create_model( + "ContextValidator", + __config__=ConfigDict(extra="forbid"), + Execution=(_to_strict_model("Execution", ExecutionData) | None, None), + State=(_to_strict_model("State", StateData) | None, None), + StateMachine=(_to_strict_model("StateMachine", StateMachineData) | None, None), + Task=(_to_strict_model("Task", TaskData) | None, None), +) diff --git a/localstack-core/localstack/services/stores.py b/localstack-core/localstack/services/stores.py index af4d7d1b8b068..ad9a566b83205 100644 --- a/localstack-core/localstack/services/stores.py +++ b/localstack-core/localstack/services/stores.py @@ -30,9 +30,9 @@ class SqsStore(BaseStore): """ import re -from collections.abc import Callable +from collections.abc import Callable, Iterator from threading import RLock -from typing import Any, Generic, Iterator, Type, TypeVar, Union +from typing import Any, TypeVar from localstack import config from localstack.utils.aws.aws_stack import get_valid_regions_for_service @@ -52,7 +52,7 @@ class LocalAttribute: Descriptor protocol for marking store attributes as local to a region. """ - def __init__(self, default: Union[Callable, int, float, str, bool, None]): + def __init__(self, default: Callable | int | float | str | bool | None): """ :param default: Default value assigned to the local attribute. Must be a scalar or a callable. @@ -81,7 +81,7 @@ class CrossRegionAttribute: Descriptor protocol for marking store attributes as shared across all regions. """ - def __init__(self, default: Union[Callable, int, float, str, bool, None]): + def __init__(self, default: Callable | int | float | str | bool | None): """ :param default: The default value assigned to the cross-region attribute. This must be a scalar or a callable. @@ -122,7 +122,7 @@ class CrossAccountAttribute: This should be used for resources that are identified by ARNs. """ - def __init__(self, default: Union[Callable, int, float, str, bool, None]): + def __init__(self, default: Callable | int | float | str | bool | None): """ :param default: The default value assigned to the cross-account attribute. This must be a scalar or a callable. @@ -190,7 +190,7 @@ def __repr__(self): # -class RegionBundle(dict, Generic[BaseStoreType]): +class RegionBundle[BaseStoreType](dict): """ Encapsulation for stores across all regions for a specific AWS account ID. """ @@ -198,7 +198,7 @@ class RegionBundle(dict, Generic[BaseStoreType]): def __init__( self, service_name: str, - store: Type[BaseStoreType], + store: type[BaseStoreType], account_id: str, validate: bool = True, lock: RLock = None, @@ -238,7 +238,7 @@ def __getitem__(self, region_name) -> BaseStoreType: store_obj._global = self._global store_obj._universal = self._universal - store_obj.service_name = self.service_name + store_obj._service_name = self.service_name store_obj._account_id = self.account_id store_obj._region_name = region_name @@ -281,12 +281,12 @@ def reset(self, _reset_universal: bool = False): self.clear() -class AccountRegionBundle(dict, Generic[BaseStoreType]): +class AccountRegionBundle[BaseStoreType](dict): """ Encapsulation for all stores for all AWS account IDs. """ - def __init__(self, service_name: str, store: Type[BaseStoreType], validate: bool = True): + def __init__(self, service_name: str, store: type[BaseStoreType], validate: bool = True): """ :param service_name: Name of the service. Must be a valid service defined in botocore. :param store: Class definition of the Store diff --git a/localstack-core/localstack/services/sts/models.py b/localstack-core/localstack/services/sts/models.py index de28b1e723647..ded792fd5665e 100644 --- a/localstack-core/localstack/services/sts/models.py +++ b/localstack-core/localstack/services/sts/models.py @@ -1,4 +1,4 @@ -from typing import TypedDict +from typing import Any, TypedDict from localstack.aws.api.sts import Tag from localstack.services.stores import AccountRegionBundle, BaseStore, CrossRegionAttribute @@ -10,7 +10,7 @@ class SessionConfig(TypedDict): # list of lowercase transitive tag keys transitive_tags: list[str] # other stored context variables - iam_context: dict[str, str | list[str]] + iam_context: dict[str, Any] class STSStore(BaseStore): diff --git a/localstack-core/localstack/services/sts/provider.py b/localstack-core/localstack/services/sts/provider.py index b53e7b0a1684e..f2f56ad6eb950 100644 --- a/localstack-core/localstack/services/sts/provider.py +++ b/localstack-core/localstack/services/sts/provider.py @@ -1,6 +1,7 @@ import logging +import re -from localstack.aws.api import RequestContext, ServiceException +from localstack.aws.api import CommonServiceException, RequestContext, ServiceException from localstack.aws.api.sts import ( AssumeRoleResponse, GetCallerIdentityResponse, @@ -22,6 +23,7 @@ from localstack.services.moto import call_moto from localstack.services.plugins import ServiceLifecycleHook from localstack.services.sts.models import SessionConfig, sts_stores +from localstack.state import StateVisitor from localstack.utils.aws.arns import extract_account_id_from_arn from localstack.utils.aws.request_context import extract_access_key_id_from_auth_header @@ -34,10 +36,27 @@ class InvalidParameterValueError(ServiceException): sender_fault = True +# allows for arn:a:a:::aaaaaaaaaa which would pass the check +ROLE_ARN_REGEX = re.compile(r"^arn:[^:]+:[^:]+:[^:]*:[^:]*:[^:]+$") +# Session name regex as specified in the error response from AWS +SESSION_NAME_REGEX = re.compile(r"^[\w+=,.@-]*$") + + +class ValidationError(CommonServiceException): + def __init__(self, message: str): + super().__init__("ValidationError", message, 400, True) + + class StsProvider(StsApi, ServiceLifecycleHook): def __init__(self): apply_iam_patches() + def accept_state_visitor(self, visitor: StateVisitor): + from moto.sts.models import sts_backends + + visitor.visit(sts_backends) + visitor.visit(sts_stores) + def get_caller_identity(self, context: RequestContext, **kwargs) -> GetCallerIdentityResponse: response = call_moto(context) if "user/moto" in response["Arn"] and "sts" in response["Arn"]: @@ -61,7 +80,16 @@ def assume_role( provided_contexts: ProvidedContextsListType = None, **kwargs, ) -> AssumeRoleResponse: - target_account_id = extract_account_id_from_arn(role_arn) + # verify role arn + if not ROLE_ARN_REGEX.match(role_arn): + raise ValidationError(f"{role_arn} is invalid") + + if not SESSION_NAME_REGEX.match(role_session_name): + raise ValidationError( + f"1 validation error detected: Value '{role_session_name}' at 'roleSessionName' failed to satisfy constraint: Member must satisfy regular expression pattern: [\\w+=,.@-]*" + ) + + target_account_id = extract_account_id_from_arn(role_arn) or context.account_id access_key_id = extract_access_key_id_from_auth_header(context.request.headers) store = sts_stores[target_account_id]["us-east-1"] existing_session_config = store.sessions.get(access_key_id, {}) diff --git a/localstack-core/localstack/services/support/provider.py b/localstack-core/localstack/services/support/provider.py index 5a31be07baf6d..6d136ef975280 100644 --- a/localstack-core/localstack/services/support/provider.py +++ b/localstack-core/localstack/services/support/provider.py @@ -1,7 +1,11 @@ from abc import ABC from localstack.aws.api.support import SupportApi +from localstack.state import StateVisitor class SupportProvider(SupportApi, ABC): - pass + def accept_state_visitor(self, visitor: StateVisitor): + from moto.support.models import support_backends + + visitor.visit(support_backends) diff --git a/localstack-core/localstack/services/swf/provider.py b/localstack-core/localstack/services/swf/provider.py index b21f71dfaa915..94bec537b377b 100644 --- a/localstack-core/localstack/services/swf/provider.py +++ b/localstack-core/localstack/services/swf/provider.py @@ -1,7 +1,11 @@ from abc import ABC from localstack.aws.api.swf import SwfApi +from localstack.state import StateVisitor class SWFProvider(SwfApi, ABC): - pass + def accept_state_visitor(self, visitor: StateVisitor): + from moto.swf.models import swf_backends + + visitor.visit(swf_backends) diff --git a/localstack-core/localstack/services/transcribe/packages.py b/localstack-core/localstack/services/transcribe/packages.py index 14faf968c2159..c37bfdb6ab038 100644 --- a/localstack-core/localstack/services/transcribe/packages.py +++ b/localstack-core/localstack/services/transcribe/packages.py @@ -1,5 +1,3 @@ -from typing import List - from localstack.packages import Package from localstack.packages.core import PythonPackageInstaller @@ -13,7 +11,7 @@ def __init__(self, default_version: str = _VOSK_DEFAULT_VERSION): def _get_installer(self, version: str) -> PythonPackageInstaller: return VoskPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [_VOSK_DEFAULT_VERSION] diff --git a/localstack-core/localstack/services/transcribe/provider.py b/localstack-core/localstack/services/transcribe/provider.py index b0d1f62d458ed..9a70031bf732b 100644 --- a/localstack-core/localstack/services/transcribe/provider.py +++ b/localstack-core/localstack/services/transcribe/provider.py @@ -5,7 +5,7 @@ import wave from functools import cache from pathlib import Path -from typing import Any, Tuple +from typing import Any from zipfile import ZipFile from localstack import config @@ -38,6 +38,7 @@ ) from localstack.services.transcribe.models import TranscribeStore, transcribe_stores from localstack.services.transcribe.packages import vosk_package +from localstack.state import StateVisitor from localstack.utils.files import new_tmp_file from localstack.utils.http import download from localstack.utils.run import run @@ -101,6 +102,12 @@ class TranscribeProvider(TranscribeApi): + def accept_state_visitor(self, visitor: StateVisitor) -> None: + from moto.transcribe.models import transcribe_backends + + visitor.visit(transcribe_backends) + visitor.visit(transcribe_stores) + def get_transcription_job( self, context: RequestContext, transcription_job_name: TranscriptionJobName, **kwargs: Any ) -> GetTranscriptionJobResponse: @@ -277,7 +284,7 @@ def download_model(name: str) -> str: # Threads # - def _run_transcription_job(self, args: Tuple[TranscribeStore, str]) -> None: + def _run_transcription_job(self, args: tuple[TranscribeStore, str]) -> None: store, job_name = args job = store.transcription_jobs[job_name] @@ -312,7 +319,7 @@ def _run_transcription_job(self, args: Tuple[TranscribeStore, str]) -> None: job["MediaFormat"] = SUPPORTED_FORMAT_NAMES[format] duration = ffprobe_output["format"]["duration"] - if float(duration) >= MAX_AUDIO_DURATION_SECONDS: + if float(duration) > MAX_AUDIO_DURATION_SECONDS: failure_reason = "Invalid file size: file size too large. Maximum audio duration is 4.000000 hours.Check the length of the file and try your request again." raise RuntimeError() @@ -412,4 +419,9 @@ def _run_transcription_job(self, args: Tuple[TranscribeStore, str]) -> None: job["FailureReason"] = failure_reason or str(exc) job["TranscriptionJobStatus"] = TranscriptionJobStatus.FAILED - LOG.exception("Transcription job %s failed: %s", job_name, job["FailureReason"]) + LOG.error( + "Transcription job %s failed: %s", + job_name, + job["FailureReason"], + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) diff --git a/localstack-core/localstack/state/codecs.py b/localstack-core/localstack/state/codecs.py new file mode 100644 index 0000000000000..1bba0710af60b --- /dev/null +++ b/localstack-core/localstack/state/codecs.py @@ -0,0 +1,61 @@ +"""Factory for encoders and decoders""" + +from localstack import config +from localstack.state import Decoder, Encoder +from localstack.state.pickle import PickleDecoder, PickleEncoder + +ENCODERS = { + "dill": PickleEncoder, +} +"""Encoders that map to the name of ``STATE_SERIALIZATION_BACKEND``.""" + +DECODERS = { + "dill": PickleDecoder, +} +"""Decoders that map to the name of ``STATE_SERIALIZATION_BACKEND``.""" + + +def create_encoder(encoder_type: str) -> Encoder: + cls = ENCODERS.get(encoder_type) + if cls is None: + raise ValueError(f"Unknown encoder type: {encoder_type}") + return cls() + + +def create_decoder(decoder_type: str) -> Decoder: + cls = DECODERS.get(decoder_type, PickleDecoder) + if cls is None: + raise ValueError(f"Unknown decoder type: {decoder_type}") + return cls() + + +def get_default_encoder() -> Encoder: + """ + Gets the default encoder based on the state serialization backend defined in the configuration + ``STATE_SERIALIZATION_BACKEND``. + + If the serialization backend specified in the configuration leads to an error + (such as an invalid backend), a ``PickleEncoder`` is returned as a fallback. + + :return: The default encoder for state serialization. + """ + try: + return create_encoder(config.STATE_SERIALIZATION_BACKEND) + except ValueError: + return PickleEncoder() + + +def get_default_decoder() -> Decoder: + """ + Gets the default decoder based on the state serialization backend defined in the configuration + ``STATE_SERIALIZATION_BACKEND``. + + If the serialization backend specified in the configuration leads to an error + (such as an invalid backend), a ``PickleDecoder`` is returned as a fallback. + + :return: The default decoder for state serialization. + """ + try: + return create_decoder(config.STATE_SERIALIZATION_BACKEND) + except ValueError: + return PickleDecoder() diff --git a/localstack-core/localstack/state/core.py b/localstack-core/localstack/state/core.py index ae41f47b17469..de482ed7a4736 100644 --- a/localstack-core/localstack/state/core.py +++ b/localstack-core/localstack/state/core.py @@ -97,42 +97,48 @@ def __str__(self) -> str: class Encoder: - def encodes(self, obj: Any) -> bytes: + def encodes(self, obj: Any, py_type: type = None) -> bytes: """ Encode an object into bytes. :param obj: the object to encode + :param py_type: the type of the object. needed by some encoders that don't have implicit type knowledge. :return: the encoded object """ b = io.BytesIO() self.encode(obj, b) return b.getvalue() - def encode(self, obj: Any, file: IO[bytes]): + def encode(self, obj: Any, file: IO[bytes], py_type: type = None): """ Encode an object into bytes. :param obj: the object to encode + :param py_type: the type of the object. needed by some encoders that don't have implicit type knowledge. :param file: the file to write the encoded data into """ raise NotImplementedError class Decoder: - def decodes(self, data: bytes) -> Any: + def decodes(self, data: bytes, py_type: type = None) -> Any: """ Decode a previously encoded object. :param data: the encoded object to decode + :param py_type: the type that is expected as return type. Needed by some decoders that don't have implicit + type knowledge. :return: the decoded object """ - return self.decode(io.BytesIO(data)) + return self.decode(io.BytesIO(data), py_type) - def decode(self, file: IO[bytes]) -> Any: + def decode(self, file: IO[bytes], py_type: type = None) -> Any: """ Decode a previously encoded object. :param file: the io object containing the object to decode + :param py_type: the type that is expected as return type. Needed by some decoders that don't have implicit + type knowledge. :return: the decoded object """ raise NotImplementedError diff --git a/localstack-core/localstack/state/inspect.py b/localstack-core/localstack/state/inspect.py index f5b10c6e3e2e4..6bac9d9cc6b74 100644 --- a/localstack-core/localstack/state/inspect.py +++ b/localstack-core/localstack/state/inspect.py @@ -3,7 +3,7 @@ import importlib import logging from functools import singledispatchmethod -from typing import Any, Dict, Optional, TypedDict +from typing import Any, TypedDict from moto.core.base_backend import BackendDict @@ -17,14 +17,14 @@ class ServiceBackend(TypedDict, total=False): """Wrapper of the possible type of backends that a service can use.""" localstack: AccountRegionBundle | None - moto: BackendDict | Dict | None + moto: BackendDict | dict | None class ServiceBackendCollectorVisitor(StateVisitor): """Implementation of StateVisitor meant to collect the backends that a given service use to hold its state.""" store: AccountRegionBundle | None - backend_dict: BackendDict | Dict | None + backend_dict: BackendDict | dict | None def __init__(self) -> None: self.store = None @@ -60,7 +60,7 @@ class ReflectionStateLocator: provider: Any - def __init__(self, provider: Optional[Any] = None, service: Optional[str] = None): + def __init__(self, provider: Any | None = None, service: str | None = None): self.provider = provider self.service = service or provider.service diff --git a/localstack-core/localstack/state/pickle.py b/localstack-core/localstack/state/pickle.py index 1b4535a5f5ca3..04b40a49df330 100644 --- a/localstack-core/localstack/state/pickle.py +++ b/localstack-core/localstack/state/pickle.py @@ -29,20 +29,19 @@ def _recreate(obj_type, obj_queue): """ import inspect -from typing import Any, BinaryIO, Callable, Generic, Type, TypeVar +from collections.abc import Callable +from typing import Any, BinaryIO import dill from dill._dill import MetaCatchingDict from .core import Decoder, Encoder -_T = TypeVar("_T") - PythonPickler = Any """Type placeholder for pickle._Pickler (which has for instance the save_reduce method)""" -def register(cls: Type = None, subclasses: bool = False): +def register(cls: type = None, subclasses: bool = False): """ Decorator to register a custom type or type tree into the dill pickling dispatcher table. @@ -60,14 +59,14 @@ def _wrapper(fn: Any | Callable[[PythonPickler, Any], None]): elif callable(fn): add_dispatch_entry(cls, fn, subclasses=subclasses) else: - raise ValueError("cannot register %s" % fn) + raise ValueError(f"cannot register {fn}") return fn return _wrapper -def reducer(cls: Type, restore: Callable = None, subclasses: bool = False): +def reducer(cls: type, restore: Callable = None, subclasses: bool = False): """ Convenience decorator to simplify the following pattern:: @@ -115,14 +114,14 @@ def _reducer(pickler, obj): def add_dispatch_entry( - cls: Type, fn: Callable[[PythonPickler, Any], None], subclasses: bool = False + cls: type, fn: Callable[[PythonPickler, Any], None], subclasses: bool = False ): Pickler.dispatch_overwrite[cls] = fn if subclasses: Pickler.match_subclasses_of.add(cls) -def remove_dispatch_entry(cls: Type): +def remove_dispatch_entry(cls: type): try: del Pickler.dispatch_overwrite[cls] except KeyError: @@ -136,42 +135,42 @@ def remove_dispatch_entry(cls: Type): def dumps(obj: Any) -> bytes: """ - Pickle an object into bytes using a ``PickleEncoder``. + Pickle an object into bytes using a ``Encoder``. :param obj: the object to pickle :return: the pickled object """ - return PickleEncoder().encodes(obj) + return get_default_encoder().encodes(obj) def dump(obj: Any, file: BinaryIO): """ - Pickle an object into a buffer using a ``PickleEncoder``. + Pickle an object into a buffer using a ``Encoder``. :param obj: the object to pickle :param file: the IO buffer """ - return PickleEncoder().encode(obj, file) + return get_default_encoder().encode(obj, file) def loads(data: bytes) -> Any: """ - Unpickle am object from bytes using a ``PickleDecoder``. + Unpickle am object from bytes using a ``Decoder``. :param data: the pickled object :return: the unpickled object """ - return PickleDecoder().decodes(data) + return get_default_decoder().decodes(data) def load(file: BinaryIO) -> Any: """ - Unpickle am object from a buffer using a ``PickleDecoder``. + Unpickle am object from a buffer using a ``Decoder``. :param file: the buffer containing the pickled object :return: the unpickled object """ - return PickleDecoder().decode(file) + return get_default_decoder().decode(file) class _SuperclassMatchingTypeDict(MetaCatchingDict): @@ -187,7 +186,7 @@ class _SuperclassMatchingTypeDict(MetaCatchingDict): """ - def __init__(self, seq=None, match_subclasses_of: set[Type] = None): + def __init__(self, seq=None, match_subclasses_of: set[type] = None): if seq is not None: super().__init__(seq) else: @@ -212,8 +211,8 @@ class Pickler(dill.Pickler): Custom dill pickler that considers dispatchers and subclass dispatchers registered via ``register``. """ - match_subclasses_of: set[Type] = set() - dispatch_overwrite: dict[Type, Callable] = {} + match_subclasses_of: set[type] = set() + dispatch_overwrite: dict[type, Callable] = {} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -231,12 +230,12 @@ class PickleEncoder(Encoder): extended with custom serializers. """ - pickler_class: Type[dill.Pickler] + pickler_class: type[dill.Pickler] - def __init__(self, pickler_class: Type[dill.Pickler] = None): + def __init__(self, pickler_class: type[dill.Pickler] = None): self.pickler_class = pickler_class or Pickler - def encode(self, obj: Any, file: BinaryIO): + def encode(self, obj: Any, file: BinaryIO, py_type: type = None) -> Any: return self.pickler_class(file).dump(obj) @@ -246,16 +245,28 @@ class PickleDecoder(Decoder): extended with custom serializers. """ - unpickler_class: Type[dill.Unpickler] + unpickler_class: type[dill.Unpickler] - def __init__(self, unpickler_class: Type[dill.Unpickler] = None): + def __init__(self, unpickler_class: type[dill.Unpickler] = None): self.unpickler_class = unpickler_class or dill.Unpickler - def decode(self, file: BinaryIO) -> Any: + def decode(self, file: BinaryIO, py_type=None) -> Any: return self.unpickler_class(file).load() -class ObjectStateReducer(Generic[_T]): +def get_default_encoder() -> Encoder: + from .codecs import get_default_encoder + + return get_default_encoder() + + +def get_default_decoder() -> Decoder: + from .codecs import get_default_decoder + + return get_default_decoder() + + +class ObjectStateReducer[T]: """ A generalization of the following pattern:: @@ -288,7 +299,7 @@ def restore(state: dict): state["this_one_doesnt_serialize"] = restore(state["this_one_serialized"]) """ - cls: _T + cls: T @classmethod def create(cls): @@ -300,7 +311,7 @@ def register(self, subclasses=False): """ add_dispatch_entry(self.cls, self._pickle, subclasses=subclasses) - def _pickle(self, pickler, obj: _T): + def _pickle(self, pickler, obj: T): state = self.get_state(obj) self.prepare(obj, state) return pickler.save_reduce(self._unpickle, (state,), obj=obj) @@ -311,7 +322,7 @@ def _unpickle(self, state: dict) -> dict: self.set_state(obj, state) return obj - def get_state(self, obj: _T) -> Any: + def get_state(self, obj: T) -> Any: """ Return the objects state. Can be overwritten by subclasses to return custom state. @@ -320,7 +331,7 @@ def get_state(self, obj: _T) -> Any: """ return obj.__dict__.copy() - def set_state(self, obj: _T, state: Any): + def set_state(self, obj: T, state: Any): """ Set the state of the object. Can be overwritten by subclasses to set custom state. @@ -329,7 +340,7 @@ def set_state(self, obj: _T, state: Any): """ obj.__dict__.update(state) - def prepare(self, obj: _T, state: Any): + def prepare(self, obj: T, state: Any): """ Can be overwritten by subclasses to prepare the object state for pickling. @@ -338,7 +349,7 @@ def prepare(self, obj: _T, state: Any): """ pass - def restore(self, obj: _T, state: Any): + def restore(self, obj: T, state: Any): """ Can be overwritten by subclasses to modify the object state to restore any previously removed attributes. diff --git a/localstack-core/localstack/testing/aws/asf_utils.py b/localstack-core/localstack/testing/aws/asf_utils.py index 33035496ebf2f..e48435677c7b8 100644 --- a/localstack-core/localstack/testing/aws/asf_utils.py +++ b/localstack-core/localstack/testing/aws/asf_utils.py @@ -3,12 +3,13 @@ import inspect import pkgutil import re +from re import Pattern from types import FunctionType, ModuleType, NoneType, UnionType -from typing import Optional, Pattern, Union, get_args, get_origin +from typing import Union, get_args, get_origin def _import_submodules( - package_name: str, module_regex: Optional[Pattern] = None, recursive: bool = True + package_name: str, module_regex: Pattern | None = None, recursive: bool = True ) -> dict[str, ModuleType]: """ Imports all submodules of the given package with the defined (optional) module_suffix. diff --git a/localstack-core/localstack/testing/aws/cloudformation_utils.py b/localstack-core/localstack/testing/aws/cloudformation_utils.py index 0ef513dec8456..33dd9634b78d2 100644 --- a/localstack-core/localstack/testing/aws/cloudformation_utils.py +++ b/localstack-core/localstack/testing/aws/cloudformation_utils.py @@ -29,7 +29,7 @@ def load_template_file(file_path: str | os.PathLike, *, path_ctx: str | os.PathL elif not file_path_obj.is_absolute(): raise ValueError("Provided path must be absolute if no path_ctx is provided") - return load_file(file_path_obj.absolute()) + return load_file(file_path_obj.absolute(), strict=True) # TODO: TBH this utility really doesn't add anything, probably better to just remove it diff --git a/localstack-core/localstack/testing/aws/lambda_utils.py b/localstack-core/localstack/testing/aws/lambda_utils.py index 764605f46962a..c020cfdbec0a0 100644 --- a/localstack-core/localstack/testing/aws/lambda_utils.py +++ b/localstack-core/localstack/testing/aws/lambda_utils.py @@ -4,21 +4,24 @@ import os import subprocess import zipfile +from collections.abc import Mapping, Sequence from pathlib import Path -from typing import TYPE_CHECKING, Literal, Mapping, Optional, Sequence, overload +from typing import TYPE_CHECKING, Optional, overload from localstack import config +from localstack.services.lambda_.invocation.lambda_models import InitializationType from localstack.services.lambda_.runtimes import RUNTIMES_AGGREGATED from localstack.utils.files import load_file from localstack.utils.platform import Arch, get_arch from localstack.utils.strings import short_uid -from localstack.utils.sync import ShortCircuitWaitException, retry +from localstack.utils.sync import ShortCircuitWaitException, retry, wait_until from localstack.utils.testutil import get_lambda_log_events if TYPE_CHECKING: from mypy_boto3_lambda import LambdaClient from mypy_boto3_lambda.literals import ArchitectureType, PackageTypeType, RuntimeType from mypy_boto3_lambda.type_defs import ( + CapacityProviderConfigTypeDef, DeadLetterConfigTypeDef, EnvironmentTypeDef, EphemeralStorageTypeDef, @@ -176,28 +179,29 @@ def __init__( def create_function( self, *, - FunctionName: Optional[str] = None, - Role: Optional[str] = None, + FunctionName: str | None = None, + Role: str | None = None, Code: Optional["FunctionCodeTypeDef"] = None, Runtime: Optional["RuntimeType"] = None, - Handler: Optional[str] = None, - Description: Optional[str] = None, - Timeout: Optional[int] = None, - MemorySize: Optional[int] = None, - Publish: Optional[bool] = None, + Handler: str | None = None, + Description: str | None = None, + Timeout: int | None = None, + MemorySize: int | None = None, + Publish: bool | None = None, VpcConfig: Optional["VpcConfigTypeDef"] = None, PackageType: Optional["PackageTypeType"] = None, DeadLetterConfig: Optional["DeadLetterConfigTypeDef"] = None, Environment: Optional["EnvironmentTypeDef"] = None, - KMSKeyArn: Optional[str] = None, + KMSKeyArn: str | None = None, TracingConfig: Optional["TracingConfigTypeDef"] = None, - Tags: Optional[Mapping[str, str]] = None, - Layers: Optional[Sequence[str]] = None, - FileSystemConfigs: Optional[Sequence["FileSystemConfigTypeDef"]] = None, + Tags: Mapping[str, str] | None = None, + Layers: Sequence[str] | None = None, + FileSystemConfigs: Sequence["FileSystemConfigTypeDef"] | None = None, ImageConfig: Optional["ImageConfigTypeDef"] = None, - CodeSigningConfigArn: Optional[str] = None, - Architectures: Optional[Sequence["ArchitectureType"]] = None, + CodeSigningConfigArn: str | None = None, + Architectures: Sequence["ArchitectureType"] | None = None, EphemeralStorage: Optional["EphemeralStorageTypeDef"] = None, + CapacityProviderConfig: Optional["CapacityProviderConfigTypeDef"] = None, ) -> "FunctionConfigurationResponseMetadataTypeDef": ... def create_function(self, **kwargs): @@ -214,9 +218,24 @@ def _create_function(): # localstack should normally not require the retries and will just continue here result = retry(_create_function, retries=3, sleep=4) self.function_names.append(result["FunctionArn"]) - self.lambda_client.get_waiter("function_active_v2").wait( - FunctionName=kwargs.get("FunctionName") - ) + + def _is_not_pending(): + # Using custom wait condition instead of the 'function_active_v2' waiter which expects 'Active' state, + # which is not true for lambda managed instances, whose state becomes in ActiveNonInvokable + try: + result = ( + self.lambda_client.get_function(FunctionName=kwargs.get("FunctionName"))[ + "Configuration" + ]["State"] + != "Pending" + ) + LOG.debug("lambda state result: result=%s", result) + return result + except Exception as e: + LOG.error(e) + raise + + wait_until(_is_not_pending) return result @@ -258,9 +277,23 @@ def _concurrency_update_done(): return _concurrency_update_done -def get_invoke_init_type( - client, function_name, qualifier -) -> Literal["on-demand", "provisioned-concurrency"]: +def concurrency_update_failed(client, function_name, qualifier): + """wait fn for ProvisionedConcurrencyConfig 'Status'""" + + def _concurrency_update_failed(): + status = client.get_provisioned_concurrency_config( + FunctionName=function_name, Qualifier=qualifier + )["Status"] + if status == "READY": + # We are expecting a failure and short-circuit upon success + raise ShortCircuitWaitException(f"Concurrency update succeeded: {status=}") + else: + return status == "FAILED" + + return _concurrency_update_failed + + +def get_invoke_init_type(client, function_name, qualifier) -> InitializationType: """check the environment in the lambda for AWS_LAMBDA_INITIALIZATION_TYPE indicating ondemand/provisioned""" invoke_result = client.invoke(FunctionName=function_name, Qualifier=qualifier) return json.load(invoke_result["Payload"]) diff --git a/localstack-core/localstack/testing/aws/util.py b/localstack-core/localstack/testing/aws/util.py index 2fadd02b9b257..a270a016368b5 100644 --- a/localstack-core/localstack/testing/aws/util.py +++ b/localstack-core/localstack/testing/aws/util.py @@ -1,6 +1,6 @@ import functools import os -from typing import Callable, Dict, TypeVar +from collections.abc import Callable import boto3 import botocore @@ -28,6 +28,7 @@ SECONDARY_TEST_AWS_SECRET_ACCESS_KEY, SECONDARY_TEST_AWS_SESSION_TOKEN, TEST_AWS_ACCESS_KEY_ID, + TEST_AWS_ENDPOINT_URL, TEST_AWS_REGION_NAME, TEST_AWS_SECRET_ACCESS_KEY, ) @@ -80,7 +81,7 @@ def is_user_ready(): def create_client_with_keys( service: str, - keys: Dict[str, str], + keys: dict[str, str], region_name: str, client_config: Config = None, ): @@ -172,10 +173,7 @@ def event_handler(request: AWSPreparedRequest, **_): return wrapper_method -T = TypeVar("T", bound=BaseClient) - - -def RequestContextClient(client: T) -> T: +def RequestContextClient[T: BaseClient](client: T) -> T: return _RequestContextClient(client) # noqa @@ -239,7 +237,7 @@ def base_aws_client_factory(session: boto3.Session) -> ClientFactory: # Prevent this fixture from using the region configured in system config config = config.merge(botocore.config.Config(region_name=TEST_AWS_REGION_NAME)) - return ExternalClientFactory(session=session, config=config) + return ExternalClientFactory(session=session, config=config, endpoint=TEST_AWS_ENDPOINT_URL) def base_testing_aws_client(client_factory: ClientFactory) -> ServiceLevelClientFactory: diff --git a/localstack-core/localstack/testing/config.py b/localstack-core/localstack/testing/config.py index f6191e9faa977..8f4b8377e952e 100644 --- a/localstack-core/localstack/testing/config.py +++ b/localstack-core/localstack/testing/config.py @@ -1,5 +1,6 @@ import os +from localstack.config import is_env_true from localstack.constants import DEFAULT_AWS_ACCOUNT_ID # Credentials used in the test suite @@ -9,6 +10,7 @@ TEST_AWS_ACCESS_KEY_ID = os.getenv("TEST_AWS_ACCESS_KEY_ID") or "test" TEST_AWS_SECRET_ACCESS_KEY = os.getenv("TEST_AWS_SECRET_ACCESS_KEY") or "test" TEST_AWS_REGION_NAME = os.getenv("TEST_AWS_REGION_NAME") or "us-east-1" +TEST_AWS_ENDPOINT_URL = os.getenv("TEST_AWS_ENDPOINT_URL") # Secondary test AWS profile - only used for testing against AWS SECONDARY_TEST_AWS_PROFILE = os.getenv("SECONDARY_TEST_AWS_PROFILE") @@ -18,3 +20,6 @@ SECONDARY_TEST_AWS_SECRET_ACCESS_KEY = os.getenv("SECONDARY_TEST_AWS_SECRET_ACCESS_KEY") or "test2" SECONDARY_TEST_AWS_SESSION_TOKEN = os.getenv("SECONDARY_TEST_AWS_SESSION_TOKEN") SECONDARY_TEST_AWS_REGION_NAME = os.getenv("SECONDARY_TEST_AWS_REGION_NAME") or "ap-southeast-1" + +TEST_SKIP_LOCALSTACK_START = is_env_true("TEST_SKIP_LOCALSTACK_START") +TEST_FORCE_LOCALSTACK_START = is_env_true("TEST_FORCE_LOCALSTACK_START") diff --git a/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py b/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py index 745a547f078c3..7ed590e23f42b 100644 --- a/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py +++ b/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py @@ -1,8 +1,10 @@ import json from collections import defaultdict -from typing import Callable, Optional, TypedDict +from collections.abc import Callable, Generator +from typing import TypedDict import pytest +from botocore.exceptions import WaiterError from localstack.aws.api.cloudformation import DescribeChangeSetOutput, StackEvent from localstack.aws.connect import ServiceLevelClientFactory @@ -11,7 +13,7 @@ class NormalizedEvent(TypedDict): - PhysicalResourceId: Optional[str] + PhysicalResourceId: str | None LogicalResourceId: str ResourceType: str ResourceStatus: str @@ -32,23 +34,41 @@ def normalize_event(event: StackEvent) -> NormalizedEvent: @pytest.fixture -def capture_per_resource_events( - aws_client: ServiceLevelClientFactory, -) -> Callable[[str], PerResourceStackEvents]: - def capture(stack_name: str) -> dict: +def capture_resource_state_changes(aws_client: ServiceLevelClientFactory): + def capture(stack_name: str) -> Generator[StackEvent, None, None]: + resource_states: dict[str, str] = {} events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ "StackEvents" ] - per_resource_events = defaultdict(list) for event in events: # TODO: not supported events if event.get("ResourceStatus") in { "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", - "DELETE_IN_PROGRESS", - "DELETE_COMPLETE", }: continue + resource = event["LogicalResourceId"] + status = event["ResourceStatus"] + if resource not in resource_states: + yield event + resource_states[resource] = status + continue + + if status != resource_states[resource]: + yield event + resource_states[resource] = status + + return capture + + +@pytest.fixture +def capture_per_resource_events( + capture_resource_state_changes, +) -> Callable[[str], PerResourceStackEvents]: + def capture(stack_name: str) -> dict: + per_resource_events = defaultdict(list) + events = capture_resource_state_changes(stack_name) + for event in events: if logical_resource_id := event.get("LogicalResourceId"): resource_name = ( logical_resource_id @@ -91,8 +111,8 @@ def capture(stack_name: str) -> dict: def _normalise_describe_change_set_output(value: DescribeChangeSetOutput) -> None: - value.get("Changes", list()).sort( - key=lambda change: change.get("ResourceChange", dict()).get("LogicalResourceId", str()) + value.get("Changes", []).sort( + key=lambda change: change.get("ResourceChange", {}).get("LogicalResourceId", "") ) @@ -107,8 +127,16 @@ def capture_update_process(aws_client_no_retry, cleanups, capture_per_resource_e change_set_name = f"cs-{short_uid()}" def inner( - snapshot, t1: dict | str, t2: dict | str, p1: dict | None = None, p2: dict | None = None - ): + snapshot, + t1: dict | str, + t2: dict | str, + p1: dict | None = None, + p2: dict | None = None, + custom_update_step: Callable[[], None] | None = None, + ) -> str: + """ + :return: stack id + """ snapshot.add_transformer(snapshot.transform.cloudformation_api()) if isinstance(t1, dict): @@ -131,6 +159,11 @@ def inner( ChangeSetName=change_set_name, TemplateBody=t1, ChangeSetType="CREATE", + Capabilities=[ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND", + ], Parameters=[{"ParameterKey": k, "ParameterValue": v} for (k, v) in p1.items()], ) snapshot.match("create-change-set-1", change_set_details) @@ -142,7 +175,7 @@ def inner( cleanups.append( lambda: call_safe( aws_client_no_retry.cloudformation.delete_change_set, - kwargs=dict(ChangeSetName=change_set_id), + kwargs={"ChangeSetName": change_set_id}, ) ) @@ -173,7 +206,7 @@ def inner( # ensure stack deletion cleanups.append( lambda: call_safe( - aws_client_no_retry.cloudformation.delete_stack, kwargs=dict(StackName=stack_id) + aws_client_no_retry.cloudformation.delete_stack, kwargs={"StackName": stack_id} ) ) @@ -182,6 +215,10 @@ def inner( ] snapshot.match("post-create-1-describe", describe) + # run any custom steps if present + if custom_update_step: + custom_update_step() + # update stack change_set_details = aws_client_no_retry.cloudformation.create_change_set( StackName=stack_name, @@ -189,13 +226,24 @@ def inner( TemplateBody=t2, ChangeSetType="UPDATE", Parameters=[{"ParameterKey": k, "ParameterValue": v} for (k, v) in p2.items()], + Capabilities=[ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND", + ], ) snapshot.match("create-change-set-2", change_set_details) stack_id = change_set_details["StackId"] change_set_id = change_set_details["Id"] - aws_client_no_retry.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=change_set_id - ) + try: + aws_client_no_retry.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_id + ) + except WaiterError as e: + desc = aws_client_no_retry.cloudformation.describe_change_set( + ChangeSetName=change_set_id + ) + raise RuntimeError(f"Change set deployment failed: {desc}") from e describe_change_set_with_prop_values = ( aws_client_no_retry.cloudformation.describe_change_set( @@ -239,4 +287,6 @@ def inner( events = capture_per_resource_events(stack_id) snapshot.match("per-resource-events", events) + return stack_id + yield inner diff --git a/tests/aws/services/cloudformation/__init__.py b/localstack-core/localstack/testing/pytest/cloudformation/transformers.py similarity index 100% rename from tests/aws/services/cloudformation/__init__.py rename to localstack-core/localstack/testing/pytest/cloudformation/transformers.py diff --git a/localstack-core/localstack/testing/pytest/container.py b/localstack-core/localstack/testing/pytest/container.py index fd904f6a86233..a962eb3a89ccc 100644 --- a/localstack-core/localstack/testing/pytest/container.py +++ b/localstack-core/localstack/testing/pytest/container.py @@ -2,7 +2,7 @@ import os import shlex import threading -from typing import Callable, Generator, List, Optional +from collections.abc import Callable, Generator import pytest @@ -23,7 +23,7 @@ LOG = logging.getLogger(__name__) ENV_TEST_CONTAINER_MOUNT_SOURCES = "TEST_CONTAINER_MOUNT_SOURCES" -"""Environment variable used to indicate that we should mount LocalStack source files into the container.""" +"""Environment variable used to indicate that we should mount LocalStack source files into the container.""" ENV_TEST_CONTAINER_MOUNT_DEPENDENCIES = "TEST_CONTAINER_MOUNT_DEPENDENCIES" """Environment variable used to indicate that we should mount dependencies into the container.""" @@ -31,14 +31,14 @@ class ContainerFactory: def __init__(self): - self._containers: List[Container] = [] + self._containers: list[Container] = [] def __call__( self, # convenience properties pro: bool = False, - publish: Optional[List[int]] = None, - configurators: Optional[List[ContainerConfigurator]] = None, + publish: list[int] | None = None, + configurators: list[ContainerConfigurator] | None = None, # ContainerConfig properties **kwargs, ) -> Container: @@ -171,7 +171,7 @@ def container_factory() -> Generator[ContainerFactory, None, None]: @pytest.fixture(scope="session") def wait_for_localstack_ready(): - def _wait_for(container: RunningContainer, timeout: Optional[float] = None): + def _wait_for(container: RunningContainer, timeout: float | None = None): container.wait_until_ready(timeout) poll_condition( diff --git a/localstack-core/localstack/testing/pytest/filters.py b/localstack-core/localstack/testing/pytest/filters.py index 2e7f0a8d0a780..8f32827364950 100644 --- a/localstack-core/localstack/testing/pytest/filters.py +++ b/localstack-core/localstack/testing/pytest/filters.py @@ -1,5 +1,3 @@ -from typing import List - import pytest from _pytest.config import Config, PytestPluginManager from _pytest.config.argparsing import Parser @@ -13,7 +11,7 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager): @pytest.hookimpl -def pytest_collection_modifyitems(session: Session, config: Config, items: List[Item]): +def pytest_collection_modifyitems(session: Session, config: Config, items: list[Item]): filter_fixtures_option = config.getoption("--filter-fixtures") if filter_fixtures_option: # TODO: add more sophisticated combinations (=> like pytest -m and -k) diff --git a/localstack-core/localstack/testing/pytest/fixtures.py b/localstack-core/localstack/testing/pytest/fixtures.py index 93f17e84ca7ef..afe2c8345937e 100644 --- a/localstack-core/localstack/testing/pytest/fixtures.py +++ b/localstack-core/localstack/testing/pytest/fixtures.py @@ -6,7 +6,9 @@ import re import textwrap import time -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Unpack +from unittest.mock import MagicMock import botocore.auth import botocore.config @@ -21,7 +23,8 @@ from werkzeug import Request, Response from localstack import config -from localstack.aws.api.ec2 import CreateSecurityGroupRequest +from localstack.aws.api.cloudformation import CreateChangeSetInput, Parameter +from localstack.aws.api.ec2 import CreateSecurityGroupRequest, CreateVpcEndpointRequest, VpcEndpoint from localstack.aws.connect import ServiceLevelClientFactory from localstack.services.stores import ( AccountRegionBundle, @@ -31,7 +34,7 @@ LocalAttribute, ) from localstack.testing.aws.cloudformation_utils import load_template_file, render_template -from localstack.testing.aws.util import get_lambda_logs, is_aws_cloud +from localstack.testing.aws.util import get_lambda_logs, is_aws_cloud, wait_for_user from localstack.testing.config import ( SECONDARY_TEST_AWS_ACCOUNT_ID, SECONDARY_TEST_AWS_REGION_NAME, @@ -231,7 +234,7 @@ def s3_create_bucket(s3_empty_bucket, aws_client): def factory(**kwargs) -> str: if "Bucket" not in kwargs: - kwargs["Bucket"] = "test-bucket-%s" % short_uid() + kwargs["Bucket"] = f"test-bucket-{short_uid()}" if ( "CreateBucketConfiguration" not in kwargs @@ -334,7 +337,7 @@ def sqs_create_queue(aws_client): def factory(**kwargs): if "QueueName" not in kwargs: - kwargs["QueueName"] = "test-queue-%s" % short_uid() + kwargs["QueueName"] = f"test-queue-{short_uid()}" response = aws_client.sqs.create_queue(**kwargs) url = response["QueueUrl"] @@ -356,8 +359,8 @@ def factory(**kwargs): def sqs_receive_messages_delete(aws_client): def factory( queue_url: str, - expected_messages: Optional[int] = None, - wait_time: Optional[int] = 5, + expected_messages: int | None = None, + wait_time: int | None = 5, ): response = aws_client.sqs.receive_message( QueueUrl=queue_url, @@ -502,7 +505,7 @@ def sns_create_topic(aws_client): def _create_topic(**kwargs): if "Name" not in kwargs: - kwargs["Name"] = "test-topic-%s" % short_uid() + kwargs["Name"] = f"test-topic-{short_uid()}" response = aws_client.sns.create_topic(**kwargs) topic_arns.append(response["TopicArn"]) return response @@ -593,7 +596,7 @@ def _allow_sns_topic(sqs_queue_url, sqs_queue_arn, sns_topic_arn) -> None: def sns_create_sqs_subscription(sns_allow_topic_sqs_queue, sqs_get_queue_arn, aws_client): subscriptions = [] - def _factory(topic_arn: str, queue_url: str, **kwargs) -> Dict[str, str]: + def _factory(topic_arn: str, queue_url: str, **kwargs) -> dict[str, str]: queue_arn = sqs_get_queue_arn(queue_url) # connect sns topic to sqs @@ -630,7 +633,7 @@ def sns_create_http_endpoint(sns_create_topic, sns_subscription, aws_client): def _create_http_endpoint( raw_message_delivery: bool = False, - ) -> Tuple[str, str, str, HTTPServer]: + ) -> tuple[str, str, str, HTTPServer]: server = HTTPServer() server.start() http_servers.append(server) @@ -705,7 +708,7 @@ def factory(**kwargs): def transcribe_create_job(s3_bucket, aws_client): job_names = [] - def _create_job(audio_file: str, params: Optional[dict[str, Any]] = None) -> str: + def _create_job(audio_file: str, params: dict[str, Any] | None = None) -> str: s3_key = "test-clip.wav" if not params: @@ -1004,7 +1007,7 @@ def opensearch_document_path(opensearch_endpoint, aws_client): # Cleanup fixtures @pytest.fixture def cleanup_stacks(aws_client): - def _cleanup_stacks(stacks: List[str]) -> None: + def _cleanup_stacks(stacks: list[str]) -> None: stacks = ensure_list(stacks) for stack in stacks: try: @@ -1018,7 +1021,7 @@ def _cleanup_stacks(stacks: List[str]) -> None: @pytest.fixture def cleanup_changesets(aws_client): - def _cleanup_changesets(changesets: List[str]) -> None: + def _cleanup_changesets(changesets: list[str]) -> None: changesets = ensure_list(changesets) for cs in changesets: try: @@ -1039,7 +1042,7 @@ class DeployResult: stack_id: str stack_name: str change_set_name: str - outputs: Dict[str, str] + outputs: dict[str, str] destroy: Callable[[], None] @@ -1084,17 +1087,18 @@ def deploy_cfn_template( def _deploy( *, - is_update: Optional[bool] = False, - stack_name: Optional[str] = None, - change_set_name: Optional[str] = None, - template: Optional[str] = None, - template_path: Optional[str | os.PathLike] = None, - template_mapping: Optional[Dict[str, Any]] = None, - parameters: Optional[Dict[str, str]] = None, - role_arn: Optional[str] = None, - max_wait: Optional[int] = None, - delay_between_polls: Optional[int] = 2, - custom_aws_client: Optional[ServiceLevelClientFactory] = None, + is_update: bool | None = False, + stack_name: str | None = None, + change_set_name: str | None = None, + template: str | None = None, + template_path: str | os.PathLike | None = None, + template_mapping: dict[str, Any] | None = None, + parameters: dict[str, str] | None = None, + role_arn: str | None = None, + max_wait: int | None = None, + delay_between_polls: int | None = 2, + custom_aws_client: ServiceLevelClientFactory | None = None, + raw_parameters: list[Parameter] | None = None, ) -> DeployResult: if is_update: assert stack_name @@ -1110,20 +1114,21 @@ def _deploy( raise RuntimeError(f"Could not find file {os.path.realpath(template_path)}") template_rendered = render_template(template, **(template_mapping or {})) - kwargs = dict( + kwargs = CreateChangeSetInput( StackName=stack_name, ChangeSetName=change_set_name, TemplateBody=template_rendered, Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], ChangeSetType=("UPDATE" if is_update else "CREATE"), - Parameters=[ - { - "ParameterKey": k, - "ParameterValue": v, - } - for (k, v) in (parameters or {}).items() - ], ) + kwargs["Parameters"] = [] + if parameters: + kwargs["Parameters"] = [ + Parameter(ParameterKey=k, ParameterValue=v) for (k, v) in parameters.items() + ] + elif raw_parameters: + kwargs["Parameters"] = raw_parameters + if role_arn is not None: kwargs["RoleARN"] = role_arn @@ -1134,9 +1139,16 @@ def _deploy( change_set_id = response["Id"] stack_id = response["StackId"] - cfn_aws_client.cloudformation.get_waiter(WAITER_CHANGE_SET_CREATE_COMPLETE).wait( - ChangeSetName=change_set_id - ) + try: + cfn_aws_client.cloudformation.get_waiter(WAITER_CHANGE_SET_CREATE_COMPLETE).wait( + ChangeSetName=change_set_id + ) + except botocore.exceptions.WaiterError as e: + change_set = cfn_aws_client.cloudformation.describe_change_set( + ChangeSetName=change_set_id + ) + raise Exception(f"{change_set['Status']}: {change_set.get('StatusReason')}") from e + cfn_aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) stack_waiter = cfn_aws_client.cloudformation.get_waiter( WAITER_STACK_UPDATE_COMPLETE if is_update else WAITER_STACK_CREATE_COMPLETE @@ -1238,7 +1250,7 @@ def is_stack_deleted(aws_client): return _has_stack_status(aws_client.cloudformation, ["DELETE_COMPLETE"]) -def _has_stack_status(cfn_client, statuses: List[str]): +def _has_stack_status(cfn_client, statuses: list[str]): def _has_status(stack_id: str): def _inner(): resp = cfn_client.describe_stacks(StackName=stack_id) @@ -1252,7 +1264,7 @@ def _inner(): @pytest.fixture def is_change_set_finished(aws_client): - def _is_change_set_finished(change_set_id: str, stack_name: Optional[str] = None): + def _is_change_set_finished(change_set_id: str, stack_name: str | None = None): def _inner(): kwargs = {"ChangeSetName": change_set_id} if stack_name: @@ -1415,6 +1427,34 @@ def _create_function(): LOG.debug("Unable to delete log group %s in cleanup", log_group_name) +@pytest.fixture +def lambda_is_function_deleted(aws_client): + """Example usage: + wait_until(lambda_is_function_deleted(function_name)) + wait_until(lambda_is_function_deleted(function_name, Qualifier="my-alias")) + + function_name can be a function name, function ARN, or partial function ARN. + """ + return _lambda_is_function_deleted(aws_client.lambda_) + + +def _lambda_is_function_deleted(lambda_client): + def _is_function_deleted( + function_name: str, + **kwargs, + ) -> Callable[[], bool]: + def _inner() -> bool: + try: + lambda_client.get_function(FunctionName=function_name, **kwargs) + return False + except lambda_client.exceptions.ResourceNotFoundException: + return True + + return _inner + + return _is_function_deleted + + @pytest.fixture def create_echo_http_server(aws_client, create_lambda_function): from localstack.aws.api.lambda_ import Runtime @@ -1508,13 +1548,15 @@ def _create_event_source_mapping(*args, **kwargs): for uuid in uuids: try: aws_client.lambda_.delete_event_source_mapping(UUID=uuid) - except Exception: - LOG.debug("Unable to delete event source mapping %s in cleanup", uuid) + except aws_client.lambda_.exceptions.ResourceNotFoundException: + pass + except Exception as ex: + LOG.debug("Unable to delete event source mapping %s in cleanup: %s", uuid, ex) @pytest.fixture def check_lambda_logs(aws_client): - def _check_logs(func_name: str, expected_lines: List[str] = None) -> List[str]: + def _check_logs(func_name: str, expected_lines: list[str] = None) -> list[str]: if not expected_lines: expected_lines = [] log_events = get_lambda_logs(func_name, logs_client=aws_client.logs) @@ -1983,7 +2025,7 @@ def setup_sender_email_address(ses_verify_identity): email address and verify them. """ - def inner(sender_email_address: Optional[str] = None) -> str: + def inner(sender_email_address: str | None = None) -> str: if is_aws_cloud(): if sender_email_address is None: raise ValueError( @@ -2046,6 +2088,45 @@ def factory(ports=None, ip_protocol: str = "tcp", **kwargs): LOG.debug("Error cleaning up EC2 security group: %s, %s", sg_group_id, e) +@pytest.fixture +def ec2_create_vpc_endpoint(aws_client): + vpc_endpoints = [] + + def _create(**kwargs: Unpack[CreateVpcEndpointRequest]) -> VpcEndpoint: + endpoint = aws_client.ec2.create_vpc_endpoint(**kwargs) + endpoint_id = endpoint["VpcEndpoint"]["VpcEndpointId"] + vpc_endpoints.append(endpoint_id) + + def _check_available() -> VpcEndpoint: + result = aws_client.ec2.describe_vpc_endpoints(VpcEndpointIds=[endpoint_id]) + _endpoint_details = result["VpcEndpoints"][0] + assert _endpoint_details["State"] == "available" + + return _endpoint_details + + return retry(_check_available, retries=30, sleep=5 if is_aws_cloud() else 1) + + yield _create + + try: + aws_client.ec2.delete_vpc_endpoints(VpcEndpointIds=vpc_endpoints) + except Exception as e: + LOG.error("Error cleaning up VPC endpoint: %s, %s", vpc_endpoints, e) + + def wait_for_endpoint_deleted(): + try: + endpoints = aws_client.ec2.describe_vpc_endpoints(VpcEndpointIds=vpc_endpoints) + assert len(endpoints["VpcEndpoints"]) == 0 or all( + endpoint["State"] == "Deleted" for endpoint in endpoints["VpcEndpoints"] + ) + except botocore.exceptions.ClientError: + pass + + # the vpc can't be deleted if an endpoint exists + if is_aws_cloud(): + retry(wait_for_endpoint_deleted, retries=30, sleep=10 if is_aws_cloud() else 1) + + @pytest.fixture def cleanups(): cleanup_fns = [] @@ -2055,8 +2136,28 @@ def cleanups(): for cleanup_callback in cleanup_fns[::-1]: try: cleanup_callback() + except ClientError as e: + http_code = e.response["ResponseMetadata"]["HTTPStatusCode"] + # Covers non-standardized error codes such as NotFoundException, NoSuchEntity (IAM), NoSuchBucket (S3), etc + if http_code == 404: + LOG.warning( + "Failed to execute cleanup because a resource was not found. " + "This cleanup might be unnecessary. %s", + str(e), + exc_info=e, + ) + else: + LOG.warning( + "Failed to execute cleanup due to ClientError: %s", + str(e), + exc_info=e, + ) except Exception as e: - LOG.warning("Failed to execute cleanup", exc_info=e) + LOG.warning( + "Failed to execute cleanup due to unexpected error: %s", + str(e), + exc_info=e, + ) @pytest.fixture(scope="session") @@ -2203,7 +2304,7 @@ def assert_host_customisation(monkeypatch): def asserter( url: str, *, - custom_host: Optional[str] = None, + custom_host: str | None = None, ): if custom_host is not None: assert custom_host in url, f"Could not find `{custom_host}` in `{url}`" @@ -2251,7 +2352,7 @@ def echo_http_server_post(echo_http_server): return f"{echo_http_server}post" -def create_policy_doc(effect: str, actions: List, resource=None) -> Dict: +def create_policy_doc(effect: str, actions: list, resource=None) -> dict: actions = ensure_list(actions) resource = resource or "*" return { @@ -2316,13 +2417,15 @@ def _create_role_with_policy( @pytest.fixture -def create_user_with_policy(create_policy_generated_document, create_user, aws_client): - def _create_user_with_policy(effect, actions, resource=None): +def create_user_with_policy(create_policy_generated_document, create_user, aws_client, region_name): + def _create_user_with_policy(effect, actions, resource=None, user_name=None): policy_arn = create_policy_generated_document(effect, actions, resource=resource) - username = f"user-{short_uid()}" + username = user_name or f"user-{short_uid()}" create_user(UserName=username) aws_client.iam.attach_user_policy(UserName=username, PolicyArn=policy_arn) keys = aws_client.iam.create_access_key(UserName=username)["AccessKey"] + + wait_for_user(keys=keys, region_name=region_name) return username, keys return _create_user_with_policy @@ -2386,7 +2489,10 @@ def factory(**kwargs): yield factory for zone_id in zone_ids[::-1]: - aws_client.route53.delete_hosted_zone(Id=zone_id) + try: + aws_client.route53.delete_hosted_zone(Id=zone_id) + except ClientError as e: + LOG.debug("failed to delete hosted zone %s: %s", zone_id, e) @pytest.fixture @@ -2540,14 +2646,22 @@ def _create_rule(**kwargs): @pytest.fixture def sqs_as_events_target(aws_client, sqs_get_queue_arn): + """ + Fixture that creates an SQS queue and sets it up as a target for EventBridge events. + """ queue_urls = [] - def _sqs_as_events_target(queue_name: str | None = None) -> tuple[str, str]: + def _sqs_as_events_target( + queue_name: str | None = None, custom_aws_client=None + ) -> tuple[str, str]: if not queue_name: queue_name = f"tests-queue-{short_uid()}" - sqs_client = aws_client.sqs + if custom_aws_client: + sqs_client = custom_aws_client.sqs + else: + sqs_client = aws_client.sqs queue_url = sqs_client.create_queue(QueueName=queue_name)["QueueUrl"] - queue_urls.append(queue_url) + queue_urls.append((queue_url, sqs_client)) queue_arn = sqs_get_queue_arn(queue_url) policy = { "Version": "2012-10-17", @@ -2565,17 +2679,98 @@ def _sqs_as_events_target(queue_name: str | None = None) -> tuple[str, str]: sqs_client.set_queue_attributes( QueueUrl=queue_url, Attributes={"Policy": json.dumps(policy)} ) - return queue_url, queue_arn + return queue_url, queue_arn, queue_name yield _sqs_as_events_target - for queue_url in queue_urls: + for queue_url, sqs_client in queue_urls: try: - aws_client.sqs.delete_queue(QueueUrl=queue_url) + sqs_client.delete_queue(QueueUrl=queue_url) except Exception as e: LOG.debug("error cleaning up queue %s: %s", queue_url, e) +@pytest.fixture +def create_role_event_bus_source_to_bus_target(create_iam_role_with_policy): + def _create_role_event_bus_to_bus(): + assume_role_policy_document_bus_source_to_bus_target = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "events.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + + policy_document_bus_source_to_bus_target = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Action": "events:PutEvents", + "Resource": "arn:aws:events:*:*:event-bus/*", + } + ], + } + + role_arn_bus_source_to_bus_target = create_iam_role_with_policy( + RoleDefinition=assume_role_policy_document_bus_source_to_bus_target, + PolicyDefinition=policy_document_bus_source_to_bus_target, + ) + + return role_arn_bus_source_to_bus_target + + yield _create_role_event_bus_to_bus + + +@pytest.fixture +def get_primary_secondary_client( + aws_client_factory, + secondary_aws_client_factory, + region_name, + secondary_region_name, + account_id, + secondary_account_id, +): + def _get_primary_secondary_clients(cross_scenario: str): + """ + Returns primary and secondary AWS clients based on the cross-scenario. + :param cross_scenario: The scenario for cross-region or cross-account testing. + Options: "region", "account", "region_account" + account_region cross scenario is not supported by AWS + :return: A dictionary containing primary and secondary AWS clients, and their respective region and account IDs. + """ + secondary_region = secondary_region_name + secondary_account = secondary_account_id + if cross_scenario not in ["region", "account", "region_account"]: + raise ValueError(f"cross_scenario {cross_scenario} not supported") + + primary_client = aws_client_factory(region_name=region_name) + + if cross_scenario == "region": + secondary_account = account_id + secondary_client = aws_client_factory(region_name=secondary_region_name) + + elif cross_scenario == "account": + secondary_region = region_name + secondary_client = secondary_aws_client_factory(region_name=region_name) + + elif cross_scenario == "region_account": + secondary_client = secondary_aws_client_factory(region_name=secondary_region) + + return { + "primary_aws_client": primary_client, + "secondary_aws_client": secondary_client, + "secondary_region_name": secondary_region, + "secondary_account_id": secondary_account, + } + + return _get_primary_secondary_clients + + @pytest.fixture def clean_up( aws_client, @@ -2598,10 +2793,10 @@ def _clean_up( if rule_name: call_safe(events_client.delete_rule, kwargs=dict(Name=rule_name, Force=True, **kwargs)) if bus_name: - call_safe(events_client.delete_event_bus, kwargs=dict(Name=bus_name)) + call_safe(events_client.delete_event_bus, kwargs={"Name": bus_name}) if queue_url: sqs_client = aws_client.sqs - call_safe(sqs_client.delete_queue, kwargs=dict(QueueUrl=queue_url)) + call_safe(sqs_client.delete_queue, kwargs={"QueueUrl": queue_url}) if log_group_name: logs_client = aws_client.logs @@ -2616,3 +2811,50 @@ def _delete_log_group(): call_safe(_delete_log_group) yield _clean_up + + +@pytest.fixture +def aws_catalog_mock(monkeypatch): + def _mock_catalog(path): + catalog = MagicMock() + monkeypatch.setattr(path, lambda: catalog) + return catalog + + return _mock_catalog + + +@pytest.fixture +def logs_log_group(aws_client): + """Create a log group for testing and clean up afterwards.""" + + log_group_names = [] + + def _create_log_group(): + log_group_name = f"test-log-group-{short_uid()}" + aws_client.logs.create_log_group(logGroupName=log_group_name) + log_group_names.append(log_group_name) + return log_group_name + + yield _create_log_group() + + for group_name in log_group_names: + aws_client.logs.delete_log_group(logGroupName=group_name) + + +@pytest.fixture +def logs_log_stream(logs_log_group, aws_client): + """Create a log stream for testing and clean up afterwards.""" + log_stream_names = [] + + def _create_log_stream(): + log_stream_name = f"test-log-stream-{short_uid()}" + aws_client.logs.create_log_stream( + logGroupName=logs_log_group, logStreamName=log_stream_name + ) + log_stream_names.append(log_stream_name) + return log_stream_name + + yield _create_log_stream() + + for stream_name in log_stream_names: + aws_client.logs.delete_log_stream(logStreamName=stream_name, logGroupName=logs_log_group) diff --git a/localstack-core/localstack/testing/pytest/in_memory_localstack.py b/localstack-core/localstack/testing/pytest/in_memory_localstack.py index d31a570ac4b30..534a8805cc65d 100644 --- a/localstack-core/localstack/testing/pytest/in_memory_localstack.py +++ b/localstack-core/localstack/testing/pytest/in_memory_localstack.py @@ -21,8 +21,8 @@ def pytest_configure(config): from _pytest.config.argparsing import Parser from _pytest.main import Session +import localstack.testing.config as test_config from localstack import config as localstack_config -from localstack.config import is_env_true from localstack.constants import ENV_INTERNAL_TEST_RUN LOG = logging.getLogger(__name__) @@ -53,25 +53,23 @@ def pytest_runtestloop(session: Session): from localstack.testing.aws.util import is_aws_cloud - if is_env_true("TEST_SKIP_LOCALSTACK_START"): + if test_config.TEST_SKIP_LOCALSTACK_START: LOG.info("TEST_SKIP_LOCALSTACK_START is set, not starting localstack") return if is_aws_cloud(): - if not is_env_true("TEST_FORCE_LOCALSTACK_START"): + if not test_config.TEST_FORCE_LOCALSTACK_START: LOG.info("Test running against aws, not starting localstack") return LOG.info("TEST_FORCE_LOCALSTACK_START is set, a Localstack instance will be created.") - from localstack.utils.common import safe_requests - if is_aws_cloud(): localstack_config.DEFAULT_DELAY = 5 localstack_config.DEFAULT_MAX_ATTEMPTS = 60 # configure os.environ[ENV_INTERNAL_TEST_RUN] = "1" - safe_requests.verify_ssl = False + localstack_config.INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE = True from localstack.runtime import current diff --git a/localstack-core/localstack/testing/pytest/marking.py b/localstack-core/localstack/testing/pytest/marking.py index 5afcca6cdc24f..f7479d510f9bd 100644 --- a/localstack-core/localstack/testing/pytest/marking.py +++ b/localstack-core/localstack/testing/pytest/marking.py @@ -3,7 +3,8 @@ """ import os -from typing import TYPE_CHECKING, Callable, List, Optional +from collections.abc import Callable +from typing import TYPE_CHECKING import pytest from _pytest.config import PytestPluginManager @@ -36,13 +37,13 @@ class SkipSnapshotVerifyMarker: def __call__( self, *, - paths: "Optional[List[str]]" = None, - condition: "Optional[Callable[[...], bool]]" = None, + paths: "list[str] | None" = None, + condition: "Callable[[...], bool] | None" = None, ): ... class MultiRuntimeMarker: - def __call__(self, *, scenario: str, runtimes: Optional[List[str]] = None): ... + def __call__(self, *, scenario: str, runtimes: list[str] | None = None): ... class SnapshotMarkers: @@ -58,13 +59,25 @@ class Markers: # test selection acceptance_test = pytest.mark.acceptance_test + """This test is an acceptance test""" skip_offline = pytest.mark.skip_offline + """Test is skipped if offline, as it requires some sort of internet connection to run""" only_on_amd64 = pytest.mark.only_on_amd64 + """Test requires ability of the system to execute amd64 binaries""" only_on_arm64 = pytest.mark.only_on_arm64 + """Test requires ability of the system to execute arm64 binaries""" resource_heavy = pytest.mark.resource_heavy - only_in_docker = pytest.mark.only_in_docker - # Tests to execute when updating snapshots for a new Lambda runtime + """Test is very resource heavy, and might be skipped in CI""" + requires_in_container = pytest.mark.requires_in_container + """Test requires LocalStack to run inside a container""" + requires_in_process = pytest.mark.requires_in_process + """The test and the LS instance have to be run in the same process""" + requires_docker = pytest.mark.requires_docker + """The test requires docker or a compatible container engine - will not work on kubernetes""" lambda_runtime_update = pytest.mark.lambda_runtime_update + """Tests to execute when updating snapshots for a new Lambda runtime""" + skip_k8s = pytest.mark.skip_k8s + """This test will be skipped in k8s environment""" # pytest plugin @@ -82,7 +95,7 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager): ) -def enforce_single_aws_marker(items: List[pytest.Item]): +def enforce_single_aws_marker(items: list[pytest.Item]): """Enforce that each test has exactly one aws compatibility marker""" marker_errors = [] @@ -91,7 +104,7 @@ def enforce_single_aws_marker(items: List[pytest.Item]): if "tests/aws" not in item.fspath.dirname: continue - aws_markers = list() + aws_markers = [] for mark in item.iter_markers(): if mark.name.startswith("aws_"): aws_markers.append(mark.name) @@ -107,7 +120,7 @@ def enforce_single_aws_marker(items: List[pytest.Item]): raise pytest.UsageError(*marker_errors) -def filter_by_markers(config: "Config", items: List[pytest.Item]): +def filter_by_markers(config: "Config", items: list[pytest.Item]): """Filter tests by markers.""" from localstack import config as localstack_config from localstack.utils.bootstrap import in_ci @@ -131,8 +144,8 @@ def filter_by_markers(config: "Config", items: List[pytest.Item]): "Add network connectivity and remove the --offline option when running " "the test." ) - only_in_docker = pytest.mark.skip( - reason="Test requires execution inside Docker (e.g., to install system packages)" + requires_in_container = pytest.mark.skip( + reason="Test requires execution inside a container (e.g., to install system packages)" ) only_on_amd64 = pytest.mark.skip( reason="Test uses features that are currently only supported for AMD64. Skipping in CI." @@ -144,8 +157,8 @@ def filter_by_markers(config: "Config", items: List[pytest.Item]): for item in items: if is_offline and "skip_offline" in item.keywords: item.add_marker(skip_offline) - if not is_in_docker and "only_in_docker" in item.keywords: - item.add_marker(only_in_docker) + if not is_in_docker and "requires_in_container" in item.keywords: + item.add_marker(requires_in_container) if is_in_ci and not is_amd64 and "only_on_amd64" in item.keywords: item.add_marker(only_on_amd64) if is_in_ci and not is_arm64 and "only_on_arm64" in item.keywords: @@ -154,7 +167,7 @@ def filter_by_markers(config: "Config", items: List[pytest.Item]): @pytest.hookimpl def pytest_collection_modifyitems( - session: pytest.Session, config: "Config", items: List[pytest.Item] + session: pytest.Session, config: "Config", items: list[pytest.Item] ) -> None: enforce_single_aws_marker(items) filter_by_markers(config, items) @@ -177,7 +190,7 @@ def pytest_configure(config): ) config.addinivalue_line( "markers", - "only_in_docker: mark the test as running only in Docker (e.g., requires installation of system packages)", + "requires_in_container: mark the test as running only in a container (e.g., requires installation of system packages)", ) config.addinivalue_line( "markers", @@ -208,3 +221,11 @@ def pytest_configure(config): "markers", "multiruntime: parametrize test against multiple Lambda runtimes", ) + config.addinivalue_line( + "markers", + "requires_docker: mark the test as requiring docker (or a compatible container engine) - will not work on kubernetes.", + ) + config.addinivalue_line( + "markers", + "requires_in_process: mark the test as requiring the test to run inside the same process as LocalStack - will not work if tests are run against a running LS container.", + ) diff --git a/localstack-core/localstack/testing/pytest/path_filter.py b/localstack-core/localstack/testing/pytest/path_filter.py index d3e13c0016143..13d562abd6a4f 100644 --- a/localstack-core/localstack/testing/pytest/path_filter.py +++ b/localstack-core/localstack/testing/pytest/path_filter.py @@ -49,7 +49,7 @@ def pytest_collection_modifyitems(config, items): if not os.path.exists(pathfilter_file): raise ValueError(f"Pathfilter file does not exist: {pathfilter_file}") - with open(pathfilter_file, "r") as f: + with open(pathfilter_file) as f: pathfilter_substrings = [line.strip() for line in f.readlines() if line.strip()] if not pathfilter_substrings: diff --git a/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py b/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py index 13a134d269e85..bdd0a99ed2027 100644 --- a/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py +++ b/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py @@ -271,7 +271,7 @@ def _wait_sfn_can_assume_role(): @pytest.fixture def create_state_machine(): - created_state_machine_references = list() + created_state_machine_references = [] def _create_state_machine(target_aws_client, **kwargs): sfn_client = target_aws_client.stepfunctions @@ -299,7 +299,7 @@ def _create_state_machine(target_aws_client, **kwargs): @pytest.fixture def create_state_machine_alias(): - state_machine_alias_arn_and_client = list() + state_machine_alias_arn_and_client = [] def _create_state_machine_alias(target_aws_client, **kwargs): step_functions_client = target_aws_client.stepfunctions @@ -324,7 +324,7 @@ def _create_state_machine_alias(target_aws_client, **kwargs): @pytest.fixture def create_activity(aws_client): - activities_arns: Final[list[str]] = list() + activities_arns: Final[list[str]] = [] def _create_activity(**kwargs): create_output = aws_client.stepfunctions.create_activity(**kwargs) @@ -778,7 +778,7 @@ def _create() -> str: @pytest.fixture def create_cross_account_admin_role_and_policy(create_state_machine, create_state_machine_iam_role): - created = list() + created = [] def _create_role_and_policy(trusting_aws_client, trusted_aws_client, trusted_account_id) -> str: trusting_iam_client = trusting_aws_client.iam diff --git a/localstack-core/localstack/testing/pytest/stepfunctions/utils.py b/localstack-core/localstack/testing/pytest/stepfunctions/utils.py index 401b6173d66f4..78d257b230f09 100644 --- a/localstack-core/localstack/testing/pytest/stepfunctions/utils.py +++ b/localstack-core/localstack/testing/pytest/stepfunctions/utils.py @@ -1,6 +1,7 @@ import json import logging -from typing import Callable, Final, Optional +from collections.abc import Callable +from typing import Final from botocore.exceptions import ClientError from jsonpath_ng.ext import parse @@ -98,10 +99,12 @@ def await_state_machine_alias_is_deleted( stepfunctions_client, state_machine_arn: Arn, state_machine_alias_arn: Arn ): success = poll_condition( - condition=lambda: not _is_state_machine_alias_listed( - stepfunctions_client=stepfunctions_client, - state_machine_arn=state_machine_arn, - state_machine_alias_arn=state_machine_alias_arn, + condition=lambda: ( + not _is_state_machine_alias_listed( + stepfunctions_client=stepfunctions_client, + state_machine_arn=state_machine_arn, + state_machine_alias_arn=state_machine_alias_arn, + ) ), timeout=_DELETION_TIMEOUT_SECS, interval=_get_sampling_interval_seconds(), @@ -154,8 +157,10 @@ def await_state_machine_version_not_listed( stepfunctions_client, state_machine_arn: str, state_machine_version_arn: str ): success = poll_condition( - condition=lambda: not _is_state_machine_version_listed( - stepfunctions_client, state_machine_arn, state_machine_version_arn + condition=lambda: ( + not _is_state_machine_version_listed( + stepfunctions_client, state_machine_arn, state_machine_version_arn + ) ), timeout=_DELETION_TIMEOUT_SECS, interval=_get_sampling_interval_seconds(), @@ -189,7 +194,7 @@ def await_state_machine_version_listed( def await_on_execution_events( stepfunctions_client, execution_arn: str, check_func: Callable[[HistoryEventList], bool] ) -> HistoryEventList: - events: HistoryEventList = list() + events: HistoryEventList = [] def _run_check(): nonlocal events @@ -342,7 +347,7 @@ def _validation_function(log_events: list) -> bool: def _await_on_execution_log_stream_created(target_aws_client, log_group_name: str) -> str: logs_client = target_aws_client.logs - log_stream_name = str() + log_stream_name = "" def _run_check(): nonlocal log_stream_name @@ -376,7 +381,7 @@ def await_on_execution_logs( log_stream_name = _await_on_execution_log_stream_created(target_aws_client, log_group_name) logs_client = target_aws_client.logs - events: HistoryEventList = list() + events: HistoryEventList = [] def _run_check(): nonlocal events @@ -402,8 +407,8 @@ def create_state_machine_with_iam_role( create_state_machine, snapshot, definition: Definition, - logging_configuration: Optional[LoggingConfiguration] = None, - state_machine_name: Optional[str] = None, + logging_configuration: LoggingConfiguration | None = None, + state_machine_name: str | None = None, state_machine_type: StateMachineType = StateMachineType.STANDARD, ): snf_role_arn = create_state_machine_iam_role(target_aws_client=target_aws_client) @@ -567,6 +572,7 @@ def launch_and_record_logs( sfn_snapshot.match("logged_execution_events", logged_execution_events) +# TODO refactor to have fewer positional arguments. Consider converting to a fixture. def create_and_record_execution( target_aws_client, create_state_machine_iam_role, @@ -889,7 +895,7 @@ def create_and_record_events( stepfunctions_client=target_aws_client.stepfunctions, execution_arn=execution_arn ) - stepfunctions_events = list() + stepfunctions_events = [] def _get_events(): received = target_aws_client.sqs.receive_message(QueueUrl=queue_url) @@ -905,7 +911,7 @@ def _get_events(): def record_sqs_events(target_aws_client, queue_url, sfn_snapshot, num_events): - stepfunctions_events = list() + stepfunctions_events = [] def _get_events(): received = target_aws_client.sqs.receive_message(QueueUrl=queue_url) @@ -916,7 +922,7 @@ def _get_events(): return len(stepfunctions_events) == num_events poll_condition(_get_events, timeout=60) - stepfunctions_events.sort(key=lambda e: json.dumps(e.get("detail", dict()))) + stepfunctions_events.sort(key=lambda e: json.dumps(e.get("detail", {}))) sfn_snapshot.match("stepfunctions_events", stepfunctions_events) @@ -931,7 +937,7 @@ def __init__(self, events_jsonpath: str = "$..events"): @staticmethod def _normalise_events(events: list[dict]) -> None: start_idx = None - sublist = list() + sublist = [] in_sublist = False for i, event in enumerate(events): event_type = event.get("type") diff --git a/localstack-core/localstack/testing/pytest/util.py b/localstack-core/localstack/testing/pytest/util.py index 28e88a8dbef24..9785e5e2b13f3 100644 --- a/localstack-core/localstack/testing/pytest/util.py +++ b/localstack-core/localstack/testing/pytest/util.py @@ -1,7 +1,7 @@ import os import pwd +from collections.abc import Callable from multiprocessing import Process, ProcessError -from typing import Callable def run_as_os_user(target: Callable, uid: str | int, gid: str | int = None): diff --git a/localstack-core/localstack/testing/pytest/validation_tracking.py b/localstack-core/localstack/testing/pytest/validation_tracking.py index cb3fd9eb48dae..8cb3b75ba0314 100644 --- a/localstack-core/localstack/testing/pytest/validation_tracking.py +++ b/localstack-core/localstack/testing/pytest/validation_tracking.py @@ -9,7 +9,6 @@ import json import os from pathlib import Path -from typing import Dict, Optional import pytest from pluggy import Result @@ -17,7 +16,7 @@ from localstack.testing.aws.util import is_aws_cloud -durations_key = StashKey[Dict[str, float]]() +durations_key = StashKey[dict[str, float]]() """ Stores phase durations on the test node between execution phases. See https://docs.pytest.org/en/latest/reference/reference.html#pytest.Stash @@ -28,14 +27,14 @@ """ -def find_validation_data_for_item(item: pytest.Item) -> Optional[dict]: +def find_validation_data_for_item(item: pytest.Item) -> dict | None: base_path = os.path.join(item.fspath.dirname, item.fspath.purebasename) snapshot_path = f"{base_path}.validation.json" if not os.path.exists(snapshot_path): return None - with open(snapshot_path, "r") as fd: + with open(snapshot_path) as fd: file_content = json.load(fd) return file_content.get(item.nodeid) diff --git a/localstack-core/localstack/testing/scenario/provisioning.py b/localstack-core/localstack/testing/scenario/provisioning.py index cc384d3046c65..c7e116a24a97f 100644 --- a/localstack-core/localstack/testing/scenario/provisioning.py +++ b/localstack-core/localstack/testing/scenario/provisioning.py @@ -1,9 +1,10 @@ import json import logging import warnings -from contextlib import contextmanager +from collections.abc import Callable +from contextlib import AbstractContextManager, contextmanager from pathlib import Path -from typing import TYPE_CHECKING, Callable, ContextManager, Optional +from typing import TYPE_CHECKING import aws_cdk as cdk from botocore.exceptions import ClientError, WaiterError @@ -77,9 +78,9 @@ def __init__( self, aws_client: ServiceLevelClientFactory, namespace: str, - base_path: Optional[str] = None, - force_synth: Optional[bool] = False, - persist_output: Optional[bool] = False, + base_path: str | None = None, + force_synth: bool | None = False, + persist_output: bool | None = False, ): """ :param namespace: repo-unique identifier for this CDK app. @@ -108,8 +109,8 @@ def get_asset_bucket(self): @contextmanager def provisioner( - self, skip_deployment: Optional[bool] = False, skip_teardown: Optional[bool] = False - ) -> ContextManager["InfraProvisioner"]: + self, skip_deployment: bool | None = False, skip_teardown: bool | None = False + ) -> AbstractContextManager["InfraProvisioner"]: """ :param skip_deployment: Set to True to skip stack creation and re-use existing stack without modifications. Also skips custom setup steps. @@ -136,7 +137,7 @@ def my_fixture(infrastructure_setup): else: LOG.debug("Skipping teardown. Resources and stacks are not deleted.") - def provision(self, skip_deployment: Optional[bool] = False): + def provision(self, skip_deployment: bool | None = False): """ Execute all previously added custom provisioning steps and deploy added CDK stacks via CloudFormation. @@ -299,7 +300,7 @@ def teardown(self): def add_cdk_stack( self, cdk_stack: cdk.Stack, - autoclean_buckets: Optional[bool] = True, + autoclean_buckets: bool | None = True, ): """ Register a CDK stack to be deployed in a later `InfraProvisioner.provision` call. @@ -318,7 +319,7 @@ def add_cdk_stack( is_env_true("TEST_CDK_FORCE_SYNTH") or self.force_synth ) # EXPERIMENTAL / API subject to change if not template_path.exists() or should_update_template: - with open(template_path, "wt") as fd: + with open(template_path, "w") as fd: template_json = cdk.assertions.Template.from_stack(cdk_stack).to_json() json.dump(template_json, fd, indent=2) # add trailing newline for linter and Git compliance diff --git a/localstack-core/localstack/testing/snapshots/transformer_utility.py b/localstack-core/localstack/testing/snapshots/transformer_utility.py index 6e6d35ba70689..ef2fdd13e6034 100644 --- a/localstack-core/localstack/testing/snapshots/transformer_utility.py +++ b/localstack-core/localstack/testing/snapshots/transformer_utility.py @@ -3,7 +3,7 @@ import re from datetime import datetime from json import JSONDecodeError -from typing import Optional, Pattern +from re import Pattern from localstack_snapshot.snapshots.transformer import ( PATTERN_ISO8601, @@ -45,12 +45,16 @@ r"arn:(aws[a-zA-Z-]*)?:([a-zA-Z0-9-_.]+)?:([^:]+)?:(\d{12})?:key/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}" ) +PATTERN_MRK_KEY_ARN = re.compile( + r"arn:(aws[a-zA-Z-]*)?:([a-zA-Z0-9-_.]+)?:([^:]+)?:(\d{12})?:key/mrk-[a-fA-F0-9]{32}" +) + # TODO: split into generic/aws and put into lib class TransformerUtility: @staticmethod def key_value( - key: str, value_replacement: Optional[str] = None, reference_replacement: bool = True + key: str, value_replacement: str | None = None, reference_replacement: bool = True ): """Creates a new KeyValueBasedTransformer. If the key matches, the value will be replaced. @@ -500,7 +504,7 @@ def kinesis_api(): replacement="", ), TransformerUtility.key_value( - "ContinuationSequenceNumber", "" + "ContinuationSequenceNumber", "continuation_sequence_number" ), ] @@ -562,6 +566,8 @@ def kms_api(): """ return [ TransformerUtility.key_value("KeyId"), + TransformerUtility.key_value("KeyMaterialId"), + TransformerUtility.key_value("CurrentKeyMaterialId"), TransformerUtility.jsonpath( jsonpath="$..Signature", value_replacement="", @@ -573,6 +579,7 @@ def kms_api(): TransformerUtility.key_value("CiphertextBlob", reference_replacement=False), TransformerUtility.key_value("Plaintext", reference_replacement=False), RegexTransformer(PATTERN_KEY_ARN, replacement=""), + RegexTransformer(PATTERN_MRK_KEY_ARN, replacement=""), ] @staticmethod @@ -784,6 +791,11 @@ def stepfunctions_api(): "x-amzn-RequestId", replace_reference=False, ), + JsonpathTransformer( + "$..x-amzn-requestid", + "x-amzn-requestid", + replace_reference=False, + ), KeyValueBasedTransformer(_transform_stepfunctions_cause_details, "json-input"), ] @@ -913,16 +925,18 @@ def _change_set_id_transformer(key: str, val: str) -> str: ), RegexTransformer(PATTERN_ISO8601, "date"), KeyValueBasedTransformer( - lambda k, v: (v if isinstance(v, datetime) else None), "datetime", replace_reference=False + lambda k, v: v if isinstance(v, datetime) else None, "datetime", replace_reference=False ), KeyValueBasedTransformer( - lambda k, v: str(v) - if ( - re.compile(r"^.*timestamp.*$", flags=re.IGNORECASE).match(k) - or k in ("creationTime", "ingestionTime") - ) - and not PATTERN_ISO8601.match(str(v)) - else None, + lambda k, v: ( + str(v) + if ( + re.compile(r"^.*timestamp.*$", flags=re.IGNORECASE).match(k) + or k in ("creationTime", "ingestionTime") + ) + and not PATTERN_ISO8601.match(str(v)) + else None + ), "timestamp", replace_reference=False, ), diff --git a/localstack-core/localstack/testing/testselection/matching.py b/localstack-core/localstack/testing/testselection/matching.py index 4bf5e9bfaca2d..6c6d2ab5a360d 100644 --- a/localstack-core/localstack/testing/testselection/matching.py +++ b/localstack-core/localstack/testing/testselection/matching.py @@ -2,7 +2,7 @@ import pathlib import re from collections import defaultdict -from typing import Callable, Iterable, Optional +from collections.abc import Callable, Iterable from localstack.aws.scaffold import is_keyword @@ -84,10 +84,8 @@ def ignore(self): return lambda t: [SENTINEL_NO_TEST] if self.matching_func(t) else [] def service_tests(self, services: list[str]): - return ( - lambda t: [get_test_dir_for_service(svc) for svc in services] - if self.matching_func(t) - else [] + return lambda t: ( + [get_test_dir_for_service(svc) for svc in services] if self.matching_func(t) else [] ) def passthrough(self): @@ -117,7 +115,7 @@ def prefix(prefix: str) -> Matcher: def generic_service_test_matching_rule( changed_file_path: str, - api_dependencies: Optional[dict[str, Iterable[str]]] = None, + api_dependencies: dict[str, Iterable[str]] | None = None, search_patterns: Iterable[str] = DEFAULT_SEARCH_PATTERNS, test_dirs: Iterable[str] = ("tests/aws/services",), ) -> set[str]: @@ -181,7 +179,6 @@ def check_rule_has_matches(rule: MatchingRule, files: Iterable[str]) -> bool: ).passthrough(), # changes in a test file should always at least test that file # CI Matchers.glob(".github/**").full_suite(), - Matchers.glob(".circleci/**").full_suite(), # dependencies / project setup Matchers.glob("requirements*.txt").full_suite(), Matchers.glob("setup.cfg").full_suite(), diff --git a/localstack-core/localstack/testing/testselection/opt_out.py b/localstack-core/localstack/testing/testselection/opt_out.py index 32fdf17b9cc26..48d4726633501 100644 --- a/localstack-core/localstack/testing/testselection/opt_out.py +++ b/localstack-core/localstack/testing/testselection/opt_out.py @@ -1,5 +1,5 @@ import fnmatch -from typing import Iterable +from collections.abc import Iterable OPT_OUT = [] diff --git a/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py b/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py index 81571dfb8b1ca..2affcf8e15ee8 100644 --- a/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py +++ b/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py @@ -24,7 +24,7 @@ def main(): sys.exit(1) testselection_file_path = sys.argv[1] - with open(testselection_file_path, "r") as file: + with open(testselection_file_path) as file: selected_tests = [line.strip() for line in file.readlines() if line.strip()] test_files = [line.strip() for line in sys.stdin] filter_test_files(test_files, selected_tests) diff --git a/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py b/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py index 31af5e5d983b5..ca04c1a13cb70 100644 --- a/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py +++ b/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py @@ -10,8 +10,8 @@ import argparse import os import sys +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from localstack.testing.testselection.git import ( find_merge_base, diff --git a/localstack-core/localstack/testing/testselection/testselection.py b/localstack-core/localstack/testing/testselection/testselection.py index e1a3799cc5c3d..98a5c95d0f012 100644 --- a/localstack-core/localstack/testing/testselection/testselection.py +++ b/localstack-core/localstack/testing/testselection/testselection.py @@ -1,10 +1,10 @@ -from typing import Iterable, Optional +from collections.abc import Iterable from localstack.testing.testselection.matching import MATCHING_RULES, MatchingRule def get_affected_tests_from_changes( - changed_files: Iterable[str], matching_rules: Optional[list[MatchingRule]] = None + changed_files: Iterable[str], matching_rules: list[MatchingRule] | None = None ) -> list[str]: """ Generate test selectors based on the changed files and matching rules to apply to. diff --git a/localstack-core/localstack/utils/analytics/cli.py b/localstack-core/localstack/utils/analytics/cli.py index ebab4f37bb451..a52fe10e3485a 100644 --- a/localstack-core/localstack/utils/analytics/cli.py +++ b/localstack-core/localstack/utils/analytics/cli.py @@ -1,7 +1,6 @@ import datetime import functools from multiprocessing import Process -from typing import List import click @@ -15,7 +14,7 @@ ANALYTICS_API_RESPONSE_TIMEOUT_SECS = 0.5 -def _publish_cmd_as_analytics_event(command_name: str, params: List[str]): +def _publish_cmd_as_analytics_event(command_name: str, params: list[str]): event = Event( name="cli_cmd", payload={"cmd": command_name, "params": params}, @@ -28,7 +27,7 @@ def _publish_cmd_as_analytics_event(command_name: str, params: List[str]): publisher.publish([event]) -def _get_parent_commands(ctx: click.Context) -> List[str]: +def _get_parent_commands(ctx: click.Context) -> list[str]: parent_commands = [] parent = ctx.parent while parent is not None: diff --git a/localstack-core/localstack/utils/analytics/client.py b/localstack-core/localstack/utils/analytics/client.py index 707a5e17e803d..1f7ff8746ed11 100644 --- a/localstack-core/localstack/utils/analytics/client.py +++ b/localstack-core/localstack/utils/analytics/client.py @@ -3,7 +3,7 @@ """ import logging -from typing import Any, Dict, List +from typing import Any import requests @@ -18,10 +18,10 @@ class SessionResponse: - response: Dict[str, Any] + response: dict[str, Any] status: int - def __init__(self, response: Dict[str, Any], status: int = 200): + def __init__(self, response: dict[str, Any], status: int = 200): self.response = response self.status = status @@ -70,7 +70,7 @@ def start_session(self, metadata: ClientMetadata) -> SessionResponse: ) # TODO: naming seems confusing since this doesn't actually append, but directly sends all passed events via HTTP - def append_events(self, events: List[Event]): + def append_events(self, events: list[Event]): # TODO: add compression to append_events # it would maybe be useful to compress analytics data, but it's unclear how that will # affect performance and what the benefit is. need to measure first. @@ -104,7 +104,7 @@ def append_events(self, events: List[Event]): # TODO: Add response type to analytics client return response - def _create_headers(self) -> Dict[str, str]: + def _create_headers(self) -> dict[str, str]: return { "User-Agent": "localstack/" + constants.VERSION, "Localstack-Session-ID": self.localstack_session_id, diff --git a/localstack-core/localstack/utils/analytics/events.py b/localstack-core/localstack/utils/analytics/events.py index 2b30b673b7032..ada536431f35a 100644 --- a/localstack-core/localstack/utils/analytics/events.py +++ b/localstack-core/localstack/utils/analytics/events.py @@ -1,8 +1,8 @@ import abc import dataclasses -from typing import Any, Dict, Union +from typing import Any -EventPayload = Union[Dict[str, Any], Any] # FIXME: better typing +EventPayload = dict[str, Any] | Any # FIXME: better typing @dataclasses.dataclass diff --git a/localstack-core/localstack/utils/analytics/metadata.py b/localstack-core/localstack/utils/analytics/metadata.py index da135c861a323..32ef9b81145de 100644 --- a/localstack-core/localstack/utils/analytics/metadata.py +++ b/localstack-core/localstack/utils/analytics/metadata.py @@ -2,7 +2,6 @@ import logging import os import platform -from typing import Optional from localstack import config from localstack.constants import VERSION @@ -41,7 +40,7 @@ def __repr__(self): k = "*" * len(k) d["api_key"] = k - return "ClientMetadata(%s)" % d + return f"ClientMetadata({d})" def get_version_string() -> str: @@ -57,7 +56,7 @@ def read_client_metadata() -> ClientMetadata: session_id=get_session_id(), machine_id=get_machine_id(), api_key=get_api_key_or_auth_token() or "", # api key should not be None - system=get_system(), + system=get_system_information_summary(), version=get_version_string(), is_ci=os.getenv("CI") is not None, is_docker=config.is_in_docker, @@ -148,7 +147,10 @@ def is_license_activated() -> bool: return licensingv2.get_licensed_environment().activated except Exception: - LOG.exception("Could not determine license activation status") + LOG.error( + "Could not determine license activation status", + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) return False @@ -198,7 +200,7 @@ def _generate_machine_id() -> str: return f"gen_{long_uid()[:12]}" -def get_api_key_or_auth_token() -> Optional[str]: +def get_api_key_or_auth_token() -> str | None: # TODO: this is duplicated code from ext, but should probably migrate that to localstack auth_token = os.environ.get("LOCALSTACK_AUTH_TOKEN", "").strip("'\" ") if auth_token: @@ -213,6 +215,7 @@ def get_api_key_or_auth_token() -> Optional[str]: @singleton_factory def get_system() -> str: + # TODO: candidate for removal try: # try to get the system from the docker socket from localstack.utils.docker_utils import DOCKER_CLIENT @@ -229,6 +232,69 @@ def get_system() -> str: return platform.system().lower() +@singleton_factory +def get_system_information_summary() -> str: + """ + Returns a string that contains three comma-separated values: The operating system, kernel version, + and architecture. We either use the docker socket to resolve the information, if that is not available + we fall back ``platform.uname()``. If we're in docker and we don't have the docker socket available, + we add ``(Container)`` to the operating system type to indicate that we don't have any additional + information. + + Some examples: + + If the Docker socket is available: + - Docker Desktop,5.15.90.1-microsoft-standard-WSL2,x86_64 + - Linux Mint 21.1,5.19.0-32-generic,x86_64 + + If the Docker socket is not available, and we're on the host: + - Windows,10,AMD64 + - Linux,5.19.0-32-generic,x86_64 + + If the Docker socket is not available, and we're in the container: + - Linux(Container),5.19.0-32-generic,x86_64 + + :return: A string representing the system's information + """ + try: + # try to get the system from the docker socket + from localstack.utils.docker_utils import DOCKER_CLIENT + + system = DOCKER_CLIENT.get_system_info() + + return ",".join( + [ + system["OperatingSystem"], + system["KernelVersion"], + system["Architecture"], + ] + ) + except Exception: + if config.DEBUG_ANALYTICS: + LOG.exception( + "Unable to get system information from docker socket, falling back to platform.uname()" + ) + + uname = platform.uname() + + if config.is_in_docker: + return ",".join( + [ + f"{uname.system}(Container)", + uname.release, + uname.machine, + ] + ) + + return ",".join( + [ + uname.system, + uname.release, + uname.machine, + ] + ) + + @hooks.prepare_host() def prepare_host_machine_id(): # lazy-init machine ID into cache on the host, which can then be used in the container diff --git a/localstack-core/localstack/utils/analytics/metrics/counter.py b/localstack-core/localstack/utils/analytics/metrics/counter.py index 42dfa5a673e9c..acf25db4d2615 100644 --- a/localstack-core/localstack/utils/analytics/metrics/counter.py +++ b/localstack-core/localstack/utils/analytics/metrics/counter.py @@ -1,7 +1,7 @@ import threading from collections import defaultdict from dataclasses import dataclass -from typing import Any, Optional, Union +from typing import Any from localstack import config @@ -38,7 +38,7 @@ class LabeledCounterPayload: value: int type: str schema_version: int - labels: dict[str, Union[str, float]] + labels: dict[str, str | float] def as_dict(self) -> dict[str, Any]: payload_dict = { @@ -66,7 +66,7 @@ class ThreadSafeCounter: _count: int def __init__(self): - super(ThreadSafeCounter, self).__init__() + super().__init__() self._mutex = threading.Lock() self._count = 0 @@ -113,11 +113,11 @@ def __init__(self, namespace: str, name: str, schema_version: int = 1): def collect(self) -> list[CounterPayload]: """Collects the metric unless events are disabled.""" if config.DISABLE_EVENTS: - return list() + return [] if self._count == 0: # Return an empty list if the count is 0, as there are no metrics to send to the analytics backend. - return list() + return [] return [ CounterPayload( @@ -140,15 +140,11 @@ class LabeledCounter(Metric): _type: str _labels: list[str] - _label_values: tuple[Optional[Union[str, float]], ...] - _counters_by_label_values: defaultdict[ - tuple[Optional[Union[str, float]], ...], ThreadSafeCounter - ] + _label_values: tuple[str | float | None, ...] + _counters_by_label_values: defaultdict[tuple[str | float | None, ...], ThreadSafeCounter] def __init__(self, namespace: str, name: str, labels: list[str], schema_version: int = 1): - super(LabeledCounter, self).__init__( - namespace=namespace, name=name, schema_version=schema_version - ) + super().__init__(namespace=namespace, name=name, schema_version=schema_version) if not labels: raise ValueError("At least one label is required; the labels list cannot be empty.") @@ -164,7 +160,7 @@ def __init__(self, namespace: str, name: str, labels: list[str], schema_version: self._counters_by_label_values = defaultdict(ThreadSafeCounter) MetricRegistry().register(self) - def labels(self, **kwargs: Union[str, float, None]) -> ThreadSafeCounter: + def labels(self, **kwargs: str | float | None) -> ThreadSafeCounter: """ Create a scoped counter instance with specific label values. @@ -184,7 +180,7 @@ def labels(self, **kwargs: Union[str, float, None]) -> ThreadSafeCounter: def collect(self) -> list[LabeledCounterPayload]: if config.DISABLE_EVENTS: - return list() + return [] payload = [] num_labels = len(self._labels) @@ -200,10 +196,7 @@ def collect(self) -> list[LabeledCounterPayload]: ) # Create labels dictionary - labels_dict = { - label_name: label_value - for label_name, label_value in zip(self._labels, label_values) - } + labels_dict = dict(zip(self._labels, label_values, strict=False)) payload.append( LabeledCounterPayload( diff --git a/localstack-core/localstack/utils/analytics/metrics/registry.py b/localstack-core/localstack/utils/analytics/metrics/registry.py index 50f23c345ad67..f88d838d4dd23 100644 --- a/localstack-core/localstack/utils/analytics/metrics/registry.py +++ b/localstack-core/localstack/utils/analytics/metrics/registry.py @@ -43,7 +43,7 @@ class MetricRegistry: Provides methods for retrieving and collecting metrics. """ - _instance: "MetricRegistry" = None + _instance: MetricRegistry = None _mutex: threading.Lock = threading.Lock() def __new__(cls): @@ -57,7 +57,7 @@ def __new__(cls): def __init__(self): if not hasattr(self, "_registry"): - self._registry = dict() + self._registry = {} @property def registry(self) -> dict[MetricRegistryKey, Metric]: diff --git a/localstack-core/localstack/utils/analytics/publisher.py b/localstack-core/localstack/utils/analytics/publisher.py index 48faf6d293625..7a3b1354b3d04 100644 --- a/localstack-core/localstack/utils/analytics/publisher.py +++ b/localstack-core/localstack/utils/analytics/publisher.py @@ -2,12 +2,10 @@ import atexit import logging import threading -import time -from queue import Full, Queue -from typing import List, Optional from localstack import config -from localstack.utils.threads import start_thread, start_worker_thread +from localstack.utils.batching import AsyncBatcher +from localstack.utils.threads import FuncThread, start_thread, start_worker_thread from .client import AnalyticsClient from .events import Event, EventHandler @@ -21,7 +19,7 @@ class Publisher(abc.ABC): A publisher takes a batch of events and publishes them to a destination. """ - def publish(self, events: List[Event]): + def publish(self, events: list[Event]): raise NotImplementedError def close(self): @@ -35,7 +33,7 @@ def __init__(self, client: AnalyticsClient = None) -> None: super().__init__() self.client = client or AnalyticsClient() - def publish(self, events: List[Event]): + def publish(self, events: list[Event]): self.client.append_events(events) def close(self): @@ -47,144 +45,41 @@ class Printer(Publisher): Publisher that prints serialized events to stdout. """ - def publish(self, events: List[Event]): + def publish(self, events: list[Event]): for event in events: print(event.asdict()) -class PublisherBuffer(EventHandler): - """ - A PublisherBuffer is an EventHandler that collects events into a buffer until a flush condition is - met, and then flushes the buffer to a Publisher. The condition is either a given buffer size or - a time interval, whatever occurs first. The buffer is also flushed when the recorder is stopped - via `close()`. Internally it uses a simple event-loop mechanism to multiplex commands on a - single thread. - """ - - flush_size: int - flush_interval: float - - _cmd_flush = "__FLUSH__" - _cmd_stop = "__STOP__" - - # FIXME: figure out good default values - def __init__( - self, publisher: Publisher, flush_size: int = 20, flush_interval: float = 10, maxsize=0 - ): - self._publisher = publisher - self._queue = Queue(maxsize=maxsize) - self._command_queue = Queue() - - self.flush_size = flush_size - self.flush_interval = flush_interval - - self._last_flush = time.time() - self._stopping = threading.Event() - self._stopped = threading.Event() - - def handle(self, event: Event): - self._queue.put_nowait(event) - self.checked_flush() - - def close(self): - if self._stopping.is_set(): - return - - self._stopping.set() - self._command_queue.put(self._cmd_stop) +class GlobalAnalyticsBus(EventHandler): + _batcher: AsyncBatcher[Event] + _client: AnalyticsClient + _worker_thread: FuncThread | None - def close_sync(self, timeout: Optional[float] = None): - self.close() - return self._stopped.wait(timeout) - - def flush(self): - self._command_queue.put(self._cmd_flush) - self._last_flush = time.time() - - def checked_flush(self): - """ - Runs flush only if a flush condition is met. - """ - if config.DEBUG_ANALYTICS: - LOG.debug( - "analytics queue size: %d, command queue size: %d, time since last flush: %.1fs", - self._queue.qsize(), - self._command_queue.qsize(), - time.time() - self._last_flush, - ) - - if self._queue.qsize() >= self.flush_size: - self.flush() - return - if time.time() - self._last_flush >= self.flush_interval: - self.flush() - return - - def _run_flush_schedule(self, *_): - while True: - if self._stopping.wait(self.flush_interval): - return - self.checked_flush() - - def run(self, *_): - flush_scheduler = start_thread(self._run_flush_schedule, name="analytics-publishbuffer") - - try: - while True: - command = self._command_queue.get() - - if command is self._cmd_flush or command is self._cmd_stop: - try: - self._do_flush() - except Exception: - if config.DEBUG_ANALYTICS: - LOG.exception("error while flushing events") - - if command is self._cmd_stop: - return - finally: - self._stopped.set() - flush_scheduler.stop() - self._publisher.close() - if config.DEBUG_ANALYTICS: - LOG.debug("Exit analytics publisher") - - def _do_flush(self): - queue = self._queue - events = [] - - for _ in range(queue.qsize()): - event = queue.get_nowait() - events.append(event) - - if config.DEBUG_ANALYTICS: - LOG.debug("collected %d events to publish", len(events)) - - self._publisher.publish(events) - - -class GlobalAnalyticsBus(PublisherBuffer): - def __init__( - self, client: AnalyticsClient = None, flush_size=20, flush_interval=10, max_buffer_size=1000 - ) -> None: + def __init__(self, client: AnalyticsClient = None, flush_size=20, flush_interval=10) -> None: self._client = client or AnalyticsClient() self._publisher = AnalyticsClientPublisher(self._client) - - super().__init__( - self._publisher, - flush_size=flush_size, - flush_interval=flush_interval, - maxsize=max_buffer_size, + self._batcher = AsyncBatcher( + self._handle_batch, + max_batch_size=flush_size, + max_flush_interval=flush_interval, ) self._started = False - self._startup_complete = False self._startup_mutex = threading.Lock() - self._buffer_thread = None + self._worker_thread = None self.force_tracking = False # allow class to ignore all other tracking config self.tracking_disabled = False # disables tracking if global config would otherwise track + def _handle_batch(self, batch: list[Event]): + """Method that satisfies the BatchHandler[Event] protocol and is passed to AsyncBatcher.""" + try: + self._publisher.publish(batch) + except Exception: + # currently we're just dropping events if something goes wrong during publishing + if config.DEBUG_ANALYTICS: + LOG.exception("error while publishing analytics events") + @property def is_tracking_disabled(self): if self.force_tracking: @@ -201,44 +96,20 @@ def is_tracking_disabled(self): return False - def _do_flush(self): - if self.tracking_disabled: - # flushing although tracking has been disabled most likely means that _do_start_retry - # has failed, tracking is now disabled, and the system tries to flush the queued - # events. we use this opportunity to shut down the tracker and clear the queue, since - # no tracking should happen from this point on. - if config.DEBUG_ANALYTICS: - LOG.debug("attempting to flush while tracking is disabled, shutting down tracker") - self.close_sync(timeout=10) - self._queue.queue.clear() - return - - super()._do_flush() - - def flush(self): - if not self._startup_complete: - # don't flush until _do_start_retry has completed (command queue would fill up) - return - - super().flush() - def handle(self, event: Event): """ Publish an event to the global analytics event publisher. """ if self.is_tracking_disabled: if config.DEBUG_ANALYTICS: - LOG.debug("skipping event %s", event) + LOG.debug("tracking disabled, skipping event %s", event) return if not self._started: + # we make sure the batching worker is started self._start() - try: - super().handle(event) - except Full: - if config.DEBUG_ANALYTICS: - LOG.warning("event queue is full, dropping event %s", event) + self._batcher.add(event) def _start(self): with self._startup_mutex: @@ -268,12 +139,22 @@ def _do_start_retry(self, *_): if config.DEBUG_ANALYTICS: LOG.exception("error while registering session. disabling tracking") return - finally: - self._startup_complete = True - start_thread(self.run, name="global-analytics-bus") + self._worker_thread = start_thread(self._run, name="global-analytics-bus") + # given the "Global" nature of this class, we register a global atexit hook to make sure all events are flushed + # when localstack shuts down. def _do_close(): self.close_sync(timeout=2) atexit.register(_do_close) + + def _run(self, *_): + # main control loop, simply runs the batcher + self._batcher.run() + + def close_sync(self, timeout=None): + self._batcher.close() + + if self._worker_thread: + self._worker_thread.join(timeout=timeout) diff --git a/localstack-core/localstack/utils/analytics/service_request_aggregator.py b/localstack-core/localstack/utils/analytics/service_request_aggregator.py index f503235201c45..42ba4700c7794 100644 --- a/localstack-core/localstack/utils/analytics/service_request_aggregator.py +++ b/localstack-core/localstack/utils/analytics/service_request_aggregator.py @@ -2,7 +2,7 @@ import logging import threading from collections import Counter -from typing import Dict, List, NamedTuple, Optional +from typing import NamedTuple from localstack import config from localstack.runtime.shutdown import SHUTDOWN_HANDLERS @@ -11,7 +11,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_FLUSH_INTERVAL_SECS = 15 +DEFAULT_FLUSH_INTERVAL_SECS = 60 EVENT_NAME = "aws_request_agg" OPTIONAL_FIELDS = ["err_type"] @@ -20,7 +20,7 @@ class ServiceRequestInfo(NamedTuple): service: str operation: str status_code: int - err_type: Optional[str] = None + err_type: str | None = None class ServiceRequestAggregator: @@ -34,7 +34,7 @@ def __init__(self, flush_interval: float = DEFAULT_FLUSH_INTERVAL_SECS): self._flush_interval = flush_interval self._flush_scheduler = Scheduler() self._mutex = threading.RLock() - self._period_start_time = datetime.datetime.utcnow() + self._period_start_time = datetime.datetime.now(datetime.UTC) self._is_started = False self._is_shutdown = False @@ -101,16 +101,18 @@ def _flush(self): self._emit_payload(analytics_payload) self.counter.clear() finally: - self._period_start_time = datetime.datetime.utcnow() + self._period_start_time = datetime.datetime.now(datetime.UTC) def _create_analytics_payload(self): return { - "period_start_time": self._period_start_time.isoformat() + "Z", - "period_end_time": datetime.datetime.utcnow().isoformat() + "Z", + "period_start_time": self._period_start_time.isoformat().replace("+00:00", "Z"), + "period_end_time": datetime.datetime.now(datetime.UTC) + .isoformat() + .replace("+00:00", "Z"), "api_calls": self._aggregate_api_calls(self.counter), } - def _aggregate_api_calls(self, counter) -> List: + def _aggregate_api_calls(self, counter) -> list: aggregations = [] for api_call_info, count in counter.items(): doc = api_call_info._asdict() @@ -121,5 +123,5 @@ def _aggregate_api_calls(self, counter) -> List: aggregations.append(doc) return aggregations - def _emit_payload(self, analytics_payload: Dict): + def _emit_payload(self, analytics_payload: dict): analytics.log.event(EVENT_NAME, analytics_payload) diff --git a/localstack-core/localstack/utils/archives.py b/localstack-core/localstack/utils/archives.py index e3b3673541a80..08328938e776a 100644 --- a/localstack-core/localstack/utils/archives.py +++ b/localstack-core/localstack/utils/archives.py @@ -8,7 +8,7 @@ import time import zipfile from subprocess import Popen -from typing import IO, Literal, Optional, Union +from typing import IO, Literal from localstack.constants import MAVEN_REPO_URL from localstack.utils.files import load_file, mkdir, new_tmp_file, rm_rf, save_file @@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__) -StrPath = Union[str, os.PathLike] +StrPath = str | os.PathLike def is_zip_file(content): @@ -30,13 +30,13 @@ def is_zip_file(content): return zipfile.is_zipfile(stream) -def get_unzipped_size(zip_file: Union[str, IO[bytes]]): +def get_unzipped_size(zip_file: str | IO[bytes]): """Returns the size of the unzipped file.""" with zipfile.ZipFile(zip_file, "r") as zip_ref: return sum(f.file_size for f in zip_ref.infolist()) -def unzip(path: str, target_dir: str, overwrite: bool = True) -> Optional[Union[str, Popen]]: +def unzip(path: str, target_dir: str, overwrite: bool = True) -> str | Popen | None: from localstack.utils.platform import is_debian use_native_cmd = is_debian() or is_command_available("unzip") @@ -99,7 +99,7 @@ def create_zip_file_python( base_dir: StrPath, zip_file: StrPath, mode: Literal["r", "w", "x", "a"] = "w", - content_root: Optional[str] = None, + content_root: str | None = None, ): with zipfile.ZipFile(zip_file, mode) as zip_file: for root, dirs, files in os.walk(base_dir): @@ -122,7 +122,7 @@ def add_file_to_jar(class_file, class_url, target_jar, base_dir=None): def update_jar_manifest( - jar_file_name: str, parent_dir: str, search: Union[str, re.Pattern], replace: str + jar_file_name: str, parent_dir: str, search: str | re.Pattern, replace: str ): manifest_file_path = "META-INF/MANIFEST.MF" jar_path = os.path.join(parent_dir, jar_file_name) @@ -174,10 +174,10 @@ def upgrade_jar_file(base_dir: str, file_glob: str, maven_asset: str): def download_and_extract( archive_url: str, target_dir: str, - retries: Optional[int] = 0, - sleep: Optional[int] = 3, - tmp_archive: Optional[str] = None, - checksum_url: Optional[str] = None, + retries: int | None = 0, + sleep: int | None = 3, + tmp_archive: str | None = None, + checksum_url: str | None = None, ) -> None: """ Download and extract an archive to a target directory with optional checksum verification. @@ -233,7 +233,7 @@ def download_and_extract( rm_rf(tmp_archive) raise e - if ext == ".zip": + if ext in (".zip", ".whl"): unzip(tmp_archive, target_dir) elif ext in ( ".bz2", @@ -250,7 +250,7 @@ def download_and_extract_with_retry( archive_url, tmp_archive, target_dir, - checksum_url: Optional[str] = None, + checksum_url: str | None = None, ): try: download_and_extract( diff --git a/localstack-core/localstack/utils/asyncio.py b/localstack-core/localstack/utils/asyncio.py index 745dab6abae4d..fd66c1492999f 100644 --- a/localstack-core/localstack/utils/asyncio.py +++ b/localstack-core/localstack/utils/asyncio.py @@ -40,12 +40,12 @@ class AdaptiveThreadPool(DaemonAwareThreadPool): def __init__(self, core_size=None): self.core_size = core_size or self.DEFAULT_CORE_POOL_SIZE - super(AdaptiveThreadPool, self).__init__(max_workers=self.core_size) + super().__init__(max_workers=self.core_size) def submit(self, fn, *args, **kwargs): # if idle threads are available, don't spin new threads if self.has_idle_threads(): - return super(AdaptiveThreadPool, self).submit(fn, *args, **kwargs) + return super().submit(fn, *args, **kwargs) def _run(*tmpargs): return fn(*args, **kwargs) diff --git a/localstack-core/localstack/utils/aws/arns.py b/localstack-core/localstack/utils/aws/arns.py index 5b6f139473bac..01ada4a2f0d60 100644 --- a/localstack-core/localstack/utils/aws/arns.py +++ b/localstack-core/localstack/utils/aws/arns.py @@ -1,7 +1,7 @@ import logging import re from functools import cache -from typing import Optional, TypedDict +from typing import TypedDict from botocore.utils import ArnParser, InvalidArnException @@ -22,12 +22,13 @@ "us-gov-": "aws-us-gov", "us-iso-": "aws-iso", "us-isob-": "aws-iso-b", + "eusc-": "aws-eusc", } PARTITION_NAMES = list(REGION_PREFIX_TO_PARTITION.values()) + [DEFAULT_PARTITION] ARN_PARTITION_REGEX = r"^arn:(" + "|".join(sorted(PARTITION_NAMES)) + ")" -def get_partition(region: Optional[str]) -> str: +def get_partition(region: str | None) -> str: if not region: return DEFAULT_PARTITION if region in PARTITION_NAMES: @@ -65,28 +66,28 @@ def parse_arn(arn: str) -> ArnData: return _arn_parser.parse_arn(arn) -def extract_account_id_from_arn(arn: str) -> Optional[str]: +def extract_account_id_from_arn(arn: str) -> str | None: try: return parse_arn(arn).get("account") except InvalidArnException: return None -def extract_region_from_arn(arn: str) -> Optional[str]: +def extract_region_from_arn(arn: str) -> str | None: try: return parse_arn(arn).get("region") except InvalidArnException: return None -def extract_service_from_arn(arn: str) -> Optional[str]: +def extract_service_from_arn(arn: str) -> str | None: try: return parse_arn(arn).get("service") except InvalidArnException: return None -def extract_resource_from_arn(arn: str) -> Optional[str]: +def extract_resource_from_arn(arn: str) -> str | None: try: return parse_arn(arn).get("resource") except InvalidArnException: @@ -98,8 +99,10 @@ def extract_resource_from_arn(arn: str) -> Optional[str]: # -def _resource_arn(name: str, pattern: str, account_id: str, region_name: str) -> str: - if ":" in name: +def _resource_arn( + name: str, pattern: str, account_id: str, region_name: str, allow_colons=False +) -> str: + if ":" in name and not allow_colons: return name if len(pattern.split("%s")) == 4: return pattern % (get_partition(region_name), account_id, name) @@ -120,7 +123,7 @@ def iam_role_arn(role_name: str, account_id: str, region_name: str) -> str: return role_name if re.match(f"{ARN_PARTITION_REGEX}:iam::", role_name): return role_name - return "arn:%s:iam::%s:role/%s" % (get_partition(region_name), account_id, role_name) + return f"arn:{get_partition(region_name)}:iam::{account_id}:role/{role_name}" def iam_resource_arn(resource: str, account_id: str, role: str = None) -> str: @@ -155,14 +158,14 @@ def secretsmanager_secret_arn( def cloudformation_stack_arn( stack_name: str, stack_id: str, account_id: str, region_name: str ) -> str: - pattern = "arn:%s:cloudformation:%s:%s:stack/%s/{stack_id}".format(stack_id=stack_id) + pattern = f"arn:%s:cloudformation:%s:%s:stack/%s/{stack_id}" return _resource_arn(stack_name, pattern, account_id=account_id, region_name=region_name) def cloudformation_change_set_arn( change_set_name: str, change_set_id: str, account_id: str, region_name: str ) -> str: - pattern = "arn:%s:cloudformation:%s:%s:changeSet/%s/{cs_id}".format(cs_id=change_set_id) + pattern = f"arn:%s:cloudformation:%s:%s:changeSet/%s/{change_set_id}" return _resource_arn(change_set_name, pattern, account_id=account_id, region_name=region_name) @@ -180,13 +183,7 @@ def dynamodb_table_arn(table_name: str, account_id: str, region_name: str) -> st def dynamodb_stream_arn( table_name: str, latest_stream_label: str, account_id: str, region_name: str ) -> str: - return "arn:%s:dynamodb:%s:%s:table/%s/stream/%s" % ( - get_partition(region_name), - region_name, - account_id, - table_name, - latest_stream_label, - ) + return f"arn:{get_partition(region_name)}:dynamodb:{region_name}:{account_id}:table/{table_name}/stream/{latest_stream_label}" # @@ -288,10 +285,17 @@ def lambda_event_source_mapping_arn(uuid: str, account_id: str, region_name: str return _resource_arn(uuid, pattern, account_id=account_id, region_name=region_name) +def capacity_provider_arn(capacity_provider_name: str, account_id: str, region_name: str) -> str: + pattern = "arn:%s:lambda:%s:%s:capacity-provider:%s" + return _resource_arn( + capacity_provider_name, pattern, account_id=account_id, region_name=region_name + ) + + def lambda_function_or_layer_arn( type: str, entity_name: str, - version: Optional[str], + version: str | None, account_id: str, region_name: str, ) -> str: @@ -453,7 +457,7 @@ def s3_bucket_arn(bucket_name_or_arn: str, region="us-east-1") -> str: def sqs_queue_arn(queue_name: str, account_id: str, region_name: str) -> str: queue_name = queue_name.split("/")[-1] - return "arn:%s:sqs:%s:%s:%s" % (get_partition(region_name), region_name, account_id, queue_name) + return f"arn:{get_partition(region_name)}:sqs:{region_name}:{account_id}:{queue_name}" # @@ -462,20 +466,13 @@ def sqs_queue_arn(queue_name: str, account_id: str, region_name: str) -> str: def apigateway_restapi_arn(api_id: str, account_id: str, region_name: str) -> str: - return "arn:%s:apigateway:%s:%s:/restapis/%s" % ( - get_partition(region_name), - region_name, - account_id, - api_id, + return ( + f"arn:{get_partition(region_name)}:apigateway:{region_name}:{account_id}:/restapis/{api_id}" ) def apigateway_invocations_arn(lambda_uri: str, region_name: str) -> str: - return "arn:%s:apigateway:%s:lambda:path/2015-03-31/functions/%s/invocations" % ( - get_partition(region_name), - region_name, - lambda_uri, - ) + return f"arn:{get_partition(region_name)}:apigateway:{region_name}:lambda:path/2015-03-31/functions/{lambda_uri}/invocations" # @@ -487,6 +484,12 @@ def sns_topic_arn(topic_name: str, account_id: str, region_name: str) -> str: return f"arn:{get_partition(region_name)}:sns:{region_name}:{account_id}:{topic_name}" +def sns_platform_application_arn( + platform_application_name: str, platform: str, account_id: str, region_name: str +) -> str: + return f"arn:{get_partition(region_name)}:sns:{region_name}:{account_id}:app/{platform}/{platform_application_name}" + + # # ECR # @@ -555,7 +558,7 @@ def lambda_function_name(name_or_arn: str) -> str: if ":" in name_or_arn: arn = parse_arn(name_or_arn) if arn["service"] != "lambda": - raise ValueError("arn is not a lambda arn %s" % name_or_arn) + raise ValueError(f"arn is not a lambda arn {name_or_arn}") return parse_arn(name_or_arn)["resource"].split(":")[1] else: diff --git a/localstack-core/localstack/utils/aws/aws_responses.py b/localstack-core/localstack/utils/aws/aws_responses.py index 509b7a8a32889..1c1de72e686a2 100644 --- a/localstack-core/localstack/utils/aws/aws_responses.py +++ b/localstack-core/localstack/utils/aws/aws_responses.py @@ -3,7 +3,7 @@ import json import re from binascii import crc32 -from typing import Any, Dict, Optional, Union +from typing import Any from urllib.parse import parse_qs import xmltodict @@ -36,21 +36,19 @@ def requests_error_response_json(message, code=500, error_type="InternalFailure" def requests_error_response_xml( message: str, - code: Optional[int] = 400, - code_string: Optional[str] = "InvalidParameter", - service: Optional[str] = None, - xmlns: Optional[str] = None, + code: int | None = 400, + code_string: str | None = "InvalidParameter", + service: str | None = None, + xmlns: str | None = None, ): response = RequestsResponse() - xmlns = xmlns or "http://%s.amazonaws.com/doc/2010-03-31/" % service - response._content = """ + xmlns = xmlns or f"http://{service}.amazonaws.com/doc/2010-03-31/" + response._content = f""" Sender {code_string} {message} - {req_id} - """.format( - xmlns=xmlns, message=message, code_string=code_string, req_id=short_uid() - ) + {short_uid()} + """ response.status_code = code return response @@ -65,18 +63,13 @@ def requests_error_response_xml_signature_calculation( aws_access_token="temp", ): response = RequestsResponse() - response_template = """ + response_template = f""" {code_string} {message} - {req_id} - {host_id} - """.format( - message=message, - code_string=code_string, - req_id=short_uid(), - host_id=short_uid(), - ) + {short_uid()} + {short_uid()} + """ parsed_response = xmltodict.parse(response_template) response.status_code = code @@ -94,8 +87,8 @@ def requests_error_response_xml_signature_calculation( server_time = datetime.datetime.utcnow().isoformat()[:-4] expires_isoformat = datetime.datetime.fromtimestamp(int(expires)).isoformat()[:-4] parsed_response["Error"]["Code"] = code_string - parsed_response["Error"]["Expires"] = "{}Z".format(expires_isoformat) - parsed_response["Error"]["ServerTime"] = "{}Z".format(server_time) + parsed_response["Error"]["Expires"] = f"{expires_isoformat}Z" + parsed_response["Error"]["ServerTime"] = f"{server_time}Z" set_response_content(response, xmltodict.unparse(parsed_response)) if not signature and not expires and code_string == "AccessDenied": @@ -106,8 +99,8 @@ def requests_error_response_xml_signature_calculation( def requests_error_response( - req_headers: Dict, - message: Union[str, bytes], + req_headers: dict, + message: str | bytes, code: int = 500, error_type: str = "InternalFailure", service: str = None, @@ -121,7 +114,7 @@ def requests_error_response( ) -def is_json_request(req_headers: Dict) -> bool: +def is_json_request(req_headers: dict) -> bool: ctype = req_headers.get("Content-Type", "") accept = req_headers.get("Accept", "") return "json" in ctype or "json" in accept @@ -186,7 +179,7 @@ def set_response_content(response, content, headers=None): response.headers["Content-Length"] = str(len(response._content)) -def create_sqs_system_attributes(headers: Dict[str, str]) -> Dict[str, Any]: +def create_sqs_system_attributes(headers: dict[str, str]) -> dict[str, Any]: system_attributes = {} if "X-Amzn-Trace-Id" in headers: system_attributes["AWSTraceHeader"] = { @@ -196,7 +189,7 @@ def create_sqs_system_attributes(headers: Dict[str, str]) -> Dict[str, Any]: return system_attributes -def parse_query_string(url_or_qs: str, multi_values=False) -> Dict[str, str]: +def parse_query_string(url_or_qs: str, multi_values=False) -> dict[str, str]: url_or_qs = str(url_or_qs or "").strip() # we match if the `url_or_qs` passed is maybe a URL if regex_url_start.match(url_or_qs) and "?" not in url_or_qs: @@ -208,7 +201,7 @@ def parse_query_string(url_or_qs: str, multi_values=False) -> Dict[str, str]: return result -def calculate_crc32(content: Union[str, bytes]) -> int: +def calculate_crc32(content: str | bytes) -> int: return crc32(to_bytes(content)) & 0xFFFFFFFF diff --git a/localstack-core/localstack/utils/aws/aws_stack.py b/localstack-core/localstack/utils/aws/aws_stack.py index 8ca6107337b49..f03bbeb268a05 100644 --- a/localstack-core/localstack/utils/aws/aws_stack.py +++ b/localstack-core/localstack/utils/aws/aws_stack.py @@ -2,7 +2,6 @@ import re import socket from functools import lru_cache -from typing import List, Union import boto3 @@ -20,7 +19,7 @@ CACHE_S3_HOSTNAME_DNS_STATUS = None -@lru_cache() +@lru_cache def get_valid_regions(): session = boto3.Session() valid_regions = set() @@ -32,12 +31,15 @@ def get_valid_regions(): # FIXME: AWS recommends use of SSM parameter store to determine per region availability # https://github.com/aws/aws-sdk/issues/206#issuecomment-1471354853 -@lru_cache() +@lru_cache def get_valid_regions_for_service(service_name): session = boto3.Session() regions = list(session.get_available_regions(service_name)) - regions.extend(session.get_available_regions("cloudwatch", partition_name="aws-us-gov")) - regions.extend(session.get_available_regions("cloudwatch", partition_name="aws-cn")) + for partition in session.get_available_partitions(): + # handle default partition separately for now. + # We use cloudwatch as service here to avoid missing botocore updates preventing service access for non-default partitions + if partition != "aws": + regions.extend(session.get_available_regions("cloudwatch", partition_name=partition)) return regions @@ -46,7 +48,7 @@ def get_boto3_region() -> str: return boto3.session.Session().region_name -def get_local_service_url(service_name_or_port: Union[str, int]) -> str: +def get_local_service_url(service_name_or_port: str | int) -> str: """Return the local service URL for the given service name or port.""" # TODO(srw): we don't need to differentiate on service name any more, so remove the argument if isinstance(service_name_or_port, int): @@ -60,7 +62,7 @@ def get_s3_hostname(): try: assert socket.gethostbyname(S3_VIRTUAL_HOSTNAME) CACHE_S3_HOSTNAME_DNS_STATUS = True - except socket.error: + except OSError: CACHE_S3_HOSTNAME_DNS_STATUS = False if CACHE_S3_HOSTNAME_DNS_STATUS: return S3_VIRTUAL_HOSTNAME @@ -68,7 +70,7 @@ def get_s3_hostname(): def fix_account_id_in_arns( - response, replacement: str, colon_delimiter: str = ":", existing: Union[str, List[str]] = None + response, replacement: str, colon_delimiter: str = ":", existing: str | list[str] = None ): """Fix the account ID in the ARNs returned in the given Flask response or string""" from moto.core import DEFAULT_ACCOUNT_ID @@ -78,13 +80,9 @@ def fix_account_id_in_arns( is_str_obj = is_string_or_bytes(response) content = to_str(response if is_str_obj else response._content) - replacement = r"arn{col}aws{col}\1{col}\2{col}{acc}{col}".format( - col=colon_delimiter, acc=replacement - ) + replacement = rf"arn{colon_delimiter}aws{colon_delimiter}\1{colon_delimiter}\2{colon_delimiter}{replacement}{colon_delimiter}" for acc_id in existing: - regex = r"arn{col}aws{col}([^:%]+){col}([^:%]*){col}{acc}{col}".format( - col=colon_delimiter, acc=acc_id - ) + regex = rf"arn{colon_delimiter}aws{colon_delimiter}([^:%]+){colon_delimiter}([^:%]*){colon_delimiter}{acc_id}{colon_delimiter}" content = re.sub(regex, replacement, content) if not is_str_obj: diff --git a/localstack-core/localstack/utils/aws/client_types.py b/localstack-core/localstack/utils/aws/client_types.py index 1fd9f3a84df5e..15e64576a3d80 100644 --- a/localstack-core/localstack/utils/aws/client_types.py +++ b/localstack-core/localstack/utils/aws/client_types.py @@ -61,11 +61,9 @@ from mypy_boto3_identitystore import IdentityStoreClient from mypy_boto3_iot import IoTClient from mypy_boto3_iot_data import IoTDataPlaneClient - from mypy_boto3_iotanalytics import IoTAnalyticsClient from mypy_boto3_iotwireless import IoTWirelessClient from mypy_boto3_kafka import KafkaClient from mypy_boto3_kinesis import KinesisClient - from mypy_boto3_kinesisanalytics import KinesisAnalyticsClient from mypy_boto3_kinesisanalyticsv2 import KinesisAnalyticsV2Client from mypy_boto3_kms import KMSClient from mypy_boto3_lakeformation import LakeFormationClient @@ -73,7 +71,6 @@ from mypy_boto3_logs import CloudWatchLogsClient from mypy_boto3_managedblockchain import ManagedBlockchainClient from mypy_boto3_mediaconvert import MediaConvertClient - from mypy_boto3_mediastore import MediaStoreClient from mypy_boto3_mq import MQClient from mypy_boto3_mwaa import MWAAClient from mypy_boto3_neptune import NeptuneClient @@ -82,8 +79,6 @@ from mypy_boto3_pi import PIClient from mypy_boto3_pinpoint import PinpointClient from mypy_boto3_pipes import EventBridgePipesClient - from mypy_boto3_qldb import QLDBClient - from mypy_boto3_qldb_session import QLDBSessionClient from mypy_boto3_rds import RDSClient from mypy_boto3_rds_data import RDSDataServiceClient from mypy_boto3_redshift import RedshiftClient @@ -187,13 +182,9 @@ class TypedServiceClientFactory(abc.ABC): identitystore: Union["IdentityStoreClient", "MetadataRequestInjector[IdentityStoreClient]"] iot: Union["IoTClient", "MetadataRequestInjector[IoTClient]"] iot_data: Union["IoTDataPlaneClient", "MetadataRequestInjector[IoTDataPlaneClient]"] - iotanalytics: Union["IoTAnalyticsClient", "MetadataRequestInjector[IoTAnalyticsClient]"] iotwireless: Union["IoTWirelessClient", "MetadataRequestInjector[IoTWirelessClient]"] kafka: Union["KafkaClient", "MetadataRequestInjector[KafkaClient]"] kinesis: Union["KinesisClient", "MetadataRequestInjector[KinesisClient]"] - kinesisanalytics: Union[ - "KinesisAnalyticsClient", "MetadataRequestInjector[KinesisAnalyticsClient]" - ] kinesisanalyticsv2: Union[ "KinesisAnalyticsV2Client", "MetadataRequestInjector[KinesisAnalyticsV2Client]" ] @@ -205,7 +196,6 @@ class TypedServiceClientFactory(abc.ABC): "ManagedBlockchainClient", "MetadataRequestInjector[ManagedBlockchainClient]" ] mediaconvert: Union["MediaConvertClient", "MetadataRequestInjector[MediaConvertClient]"] - mediastore: Union["MediaStoreClient", "MetadataRequestInjector[MediaStoreClient]"] mq: Union["MQClient", "MetadataRequestInjector[MQClient]"] mwaa: Union["MWAAClient", "MetadataRequestInjector[MWAAClient]"] neptune: Union["NeptuneClient", "MetadataRequestInjector[NeptuneClient]"] @@ -214,8 +204,6 @@ class TypedServiceClientFactory(abc.ABC): pi: Union["PIClient", "MetadataRequestInjector[PIClient]"] pinpoint: Union["PinpointClient", "MetadataRequestInjector[PinpointClient]"] pipes: Union["EventBridgePipesClient", "MetadataRequestInjector[EventBridgePipesClient]"] - qldb: Union["QLDBClient", "MetadataRequestInjector[QLDBClient]"] - qldb_session: Union["QLDBSessionClient", "MetadataRequestInjector[QLDBSessionClient]"] rds: Union["RDSClient", "MetadataRequestInjector[RDSClient]"] rds_data: Union["RDSDataServiceClient", "MetadataRequestInjector[RDSDataServiceClient]"] redshift: Union["RedshiftClient", "MetadataRequestInjector[RedshiftClient]"] @@ -282,11 +270,16 @@ class ServicePrincipal(str): """ apigateway = "apigateway" + appsync = "appsync" cloudformation = "cloudformation" dms = "dms" + ecs = "ecs" + ecs_tasks = "ecs-tasks" edgelambda = "edgelambda" + elasticloadbalancing = "elasticloadbalancing" events = "events" firehose = "firehose" + iot = "iot" lambda_ = "lambda" logs = "logs" pipes = "pipes" diff --git a/localstack-core/localstack/utils/aws/dead_letter_queue.py b/localstack-core/localstack/utils/aws/dead_letter_queue.py index 9fdd8c70ec5e3..376a0819070a8 100644 --- a/localstack-core/localstack/utils/aws/dead_letter_queue.py +++ b/localstack-core/localstack/utils/aws/dead_letter_queue.py @@ -1,7 +1,6 @@ import json import logging import uuid -from typing import Dict, List from localstack.aws.connect import connect_to from localstack.utils.aws import arns @@ -34,7 +33,7 @@ def sns_error_to_dead_letter_queue( return _send_to_dead_letter_queue(sns_subscriber["SubscriptionArn"], target_arn, event, error) -def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Dict, error, role: str = None): +def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: dict, error, role: str = None): if not dlq_arn: return LOG.info("Sending failed execution %s to dead letter queue %s", source_arn, dlq_arn) @@ -60,11 +59,7 @@ def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Dict, error except Exception as e: error = e if error or not result_code or result_code >= 400: - msg = "Unable to send message to dead letter queue %s (code %s): %s" % ( - queue_url, - result_code, - error, - ) + msg = f"Unable to send message to dead letter queue {queue_url} (code {result_code}): {error}" if "InvalidMessageContents" in str(error): msg += f" - messages: {messages}" LOG.info(msg) @@ -84,7 +79,7 @@ def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Dict, error return dlq_arn -def _prepare_messages_to_dlq(source_arn: str, event: Dict, error) -> List[Dict]: +def _prepare_messages_to_dlq(source_arn: str, event: dict, error) -> list[dict]: messages = [] custom_attrs = { "RequestID": {"DataType": "String", "StringValue": str(uuid.uuid4())}, @@ -136,7 +131,7 @@ def _prepare_messages_to_dlq(source_arn: str, event: Dict, error) -> List[Dict]: return messages -def message_attributes_to_upper(message_attrs: Dict) -> Dict: +def message_attributes_to_upper(message_attrs: dict) -> dict: """Convert message attribute details (first characters) to upper case (e.g., StringValue, DataType).""" message_attrs = message_attrs or {} for _, attr in message_attrs.items(): diff --git a/localstack-core/localstack/utils/aws/message_forwarding.py b/localstack-core/localstack/utils/aws/message_forwarding.py index ad28c015b9485..32ca4e4eefc17 100644 --- a/localstack-core/localstack/utils/aws/message_forwarding.py +++ b/localstack-core/localstack/utils/aws/message_forwarding.py @@ -3,7 +3,6 @@ import logging import re import uuid -from typing import Dict, Optional from moto.events.models import events_backends @@ -31,10 +30,10 @@ # TODO: refactor/split this. too much here is service specific def send_event_to_target( target_arn: str, - event: Dict, - target_attributes: Dict = None, + event: dict, + target_attributes: dict = None, asynchronous: bool = True, - target: Dict = None, + target: dict = None, role: str = None, source_arn: str = None, source_service: str = None, @@ -155,7 +154,7 @@ def send_event_to_target( LOG.warning('Unsupported Events rule target ARN: "%s"', target_arn) -def auth_keys_from_connection(connection: Dict): +def auth_keys_from_connection(connection: dict): headers = {} auth_type = connection.get("AuthorizationType").upper() @@ -164,9 +163,7 @@ def auth_keys_from_connection(connection: Dict): basic_auth_parameters = auth_parameters.get("BasicAuthParameters", {}) username = basic_auth_parameters.get("Username", "") password = basic_auth_parameters.get("Password", "") - auth = "Basic " + to_str( - base64.b64encode("{}:{}".format(username, password).encode("ascii")) - ) + auth = "Basic " + to_str(base64.b64encode(f"{username}:{password}".encode("ascii"))) headers.update({"authorization": auth}) if auth_type == AUTH_API_KEY: @@ -206,7 +203,7 @@ def auth_keys_from_connection(connection: Dict): token_type = oauth_data.get("token_type", "") access_token = oauth_data.get("access_token", "") - auth_header = "{} {}".format(token_type, access_token) + auth_header = f"{token_type} {access_token}" headers.update({"authorization": auth_header}) return headers @@ -216,7 +213,7 @@ def list_of_parameters_to_object(items): return {item.get("Key"): item.get("Value") for item in items} -def send_event_to_api_destination(target_arn, event, http_parameters: Optional[Dict] = None): +def send_event_to_api_destination(target_arn, event, http_parameters: dict | None = None): """Send an event to an EventBridge API destination See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-api-destinations.html""" @@ -291,7 +288,7 @@ def add_api_destination_authorization(destination, headers, event): return endpoint -def add_target_http_parameters(http_parameters: Dict, endpoint: str, headers: Dict, body): +def add_target_http_parameters(http_parameters: dict, endpoint: str, headers: dict, body): endpoint = add_path_parameters_to_url(endpoint, http_parameters.get("PathParameterValues", [])) # The request should prioritze connection header/query parameters over target params if there is an overlap diff --git a/localstack-core/localstack/utils/aws/request_context.py b/localstack-core/localstack/utils/aws/request_context.py index 9af869aeffa19..23a56c9bb76ab 100644 --- a/localstack-core/localstack/utils/aws/request_context.py +++ b/localstack-core/localstack/utils/aws/request_context.py @@ -4,7 +4,6 @@ import logging import re -from typing import Dict, Optional from rolo import Request as RoloRequest @@ -30,7 +29,7 @@ def get_account_id_from_request(request: RoloRequest) -> str: return get_account_id_from_access_key_id(access_key_id) -def extract_region_from_auth_header(headers) -> Optional[str]: +def extract_region_from_auth_header(headers) -> str | None: auth = headers.get("Authorization") or "" region = re.sub(r".*Credential=[^/]+/[^/]+/([^/]+)/.*", r"\1", auth) if region == auth: @@ -38,12 +37,12 @@ def extract_region_from_auth_header(headers) -> Optional[str]: return region -def extract_account_id_from_auth_header(headers) -> Optional[str]: +def extract_account_id_from_auth_header(headers) -> str | None: if access_key_id := extract_access_key_id_from_auth_header(headers): return get_account_id_from_access_key_id(access_key_id) -def extract_access_key_id_from_auth_header(headers: Dict[str, str]) -> Optional[str]: +def extract_access_key_id_from_auth_header(headers: dict[str, str]) -> str | None: auth = headers.get("Authorization") or "" if auth.startswith("AWS4-"): @@ -67,7 +66,7 @@ def extract_region_from_headers(headers) -> str: return extract_region_from_auth_header(headers) or AWS_REGION_US_EAST_1 -def extract_service_name_from_auth_header(headers: Dict) -> Optional[str]: +def extract_service_name_from_auth_header(headers: dict) -> str | None: try: auth_header = headers.get("authorization", "") credential_scope = auth_header.split(",")[0].split()[1] @@ -79,7 +78,7 @@ def extract_service_name_from_auth_header(headers: Dict) -> Optional[str]: def mock_aws_request_headers( service: str, aws_access_key_id: str, region_name: str, internal: bool = False -) -> Dict[str, str]: +) -> dict[str, str]: """ Returns a mock set of headers that resemble SigV4 signing method. """ diff --git a/localstack-core/localstack/utils/aws/resources.py b/localstack-core/localstack/utils/aws/resources.py index d18a2ca4b2b0e..9dc489abd5ce7 100644 --- a/localstack-core/localstack/utils/aws/resources.py +++ b/localstack-core/localstack/utils/aws/resources.py @@ -86,7 +86,7 @@ def create_api_gateway( resources = resources or [] stage_name = stage_name or "testing" usage_plan_name = usage_plan_name or "Basic Usage" - description = description or 'Test description for API "%s"' % name + description = description or f'Test description for API "{name}"' LOG.info('Creating API resources under API Gateway "%s".', name) api = client.create_rest_api(name=name, description=description) diff --git a/localstack-core/localstack/utils/aws/templating.py b/localstack-core/localstack/utils/aws/templating.py index 4d9ef57897da1..39de5d1a1cf52 100644 --- a/localstack-core/localstack/utils/aws/templating.py +++ b/localstack-core/localstack/utils/aws/templating.py @@ -1,6 +1,6 @@ import json import re -from typing import Any, Dict +from typing import Any import airspeed @@ -44,7 +44,7 @@ def qr(self, *args, **kwargs): class VtlTemplate: """Utility class for rendering Velocity templates""" - def render_vtl(self, template: str, variables: Dict, as_json=False) -> str | dict: + def render_vtl(self, template: str, variables: dict, as_json=False) -> str | dict: """ Render the given VTL template against the dict of variables. Note that this is a potentially mutating operation which may change the values of `variables` in-place. @@ -66,7 +66,7 @@ def render_vtl(self, template: str, variables: Dict, as_json=False) -> str | dic empty_placeholder = " __pLaCe-HoLdEr__ " template = re.sub( r"([^\s]+)#\$({)?(.*)", - r"\1#%s$\2\3" % empty_placeholder, + rf"\1#{empty_placeholder}$\2\3", template, count=re.MULTILINE, ) @@ -125,7 +125,7 @@ def apply(obj, **_): rendered_template = json.loads(rendered_template) return rendered_template - def prepare_namespace(self, variables: Dict[str, Any], source: str = "") -> Dict: + def prepare_namespace(self, variables: dict[str, Any], source: str = "") -> dict: namespace = dict(variables or {}) namespace.setdefault("context", {}) if not namespace.get("util"): diff --git a/localstack-core/localstack/utils/batch_policy.py b/localstack-core/localstack/utils/batch_policy.py deleted file mode 100644 index 9ac5e575f3a49..0000000000000 --- a/localstack-core/localstack/utils/batch_policy.py +++ /dev/null @@ -1,124 +0,0 @@ -import copy -import time -from typing import Generic, List, Optional, TypeVar, overload - -from pydantic import Field -from pydantic.dataclasses import dataclass - -T = TypeVar("T") - -# alias to signify whether a batch policy has been triggered -BatchPolicyTriggered = bool - - -# TODO: Add batching on bytes as well. -@dataclass -class Batcher(Generic[T]): - """ - A utility for collecting items into batches and flushing them when one or more batch policy conditions are met. - - The batch policy can be created to trigger on: - - max_count: Maximum number of items added - - max_window: Maximum time window (in seconds) - - If no limits are specified, the batcher is always in triggered state. - - Example usage: - - import time - - # Triggers when 2 (or more) items are added - batcher = Batcher(max_count=2) - assert batcher.add(["item1", "item2", "item3"]) - assert batcher.flush() == ["item1", "item2", "item3"] - - # Triggers partially when 2 (or more) items are added - batcher = Batcher(max_count=2) - assert batcher.add(["item1", "item2", "item3"]) - assert batcher.flush(partial=True) == ["item1", "item2"] - assert batcher.add("item4") - assert batcher.flush(partial=True) == ["item3", "item4"] - - # Trigger 2 seconds after the first add - batcher = Batcher(max_window=2.0) - assert not batcher.add(["item1", "item2", "item3"]) - time.sleep(2.1) - assert not batcher.add(["item4"]) - assert batcher.flush() == ["item1", "item2", "item3", "item4"] - """ - - max_count: Optional[int] = Field(default=None, description="Maximum number of items", ge=0) - max_window: Optional[float] = Field( - default=None, description="Maximum time window in seconds", ge=0 - ) - - _triggered: bool = Field(default=False, init=False) - _last_batch_time: float = Field(default_factory=time.monotonic, init=False) - _batch: list[T] = Field(default_factory=list, init=False) - - @property - def period(self) -> float: - return time.monotonic() - self._last_batch_time - - def _check_batch_policy(self) -> bool: - """Check if any batch policy conditions are met""" - if self.max_count is not None and len(self._batch) >= self.max_count: - self._triggered = True - elif self.max_window is not None and self.period >= self.max_window: - self._triggered = True - elif not self.max_count and not self.max_window: - # always return true - self._triggered = True - - return self._triggered - - @overload - def add(self, item: T, *, deep_copy: bool = False) -> BatchPolicyTriggered: ... - - @overload - def add(self, items: List[T], *, deep_copy: bool = False) -> BatchPolicyTriggered: ... - - def add(self, item_or_items: T | list[T], *, deep_copy: bool = False) -> BatchPolicyTriggered: - """ - Add an item or list of items to the collected batch. - - Returns: - BatchPolicyTriggered: True if the batch policy was triggered during addition, False otherwise. - """ - if deep_copy: - item_or_items = copy.deepcopy(item_or_items) - - if isinstance(item_or_items, list): - self._batch.extend(item_or_items) - else: - self._batch.append(item_or_items) - - # Check if the last addition triggered the batch policy - return self.is_triggered() - - def flush(self, *, partial=False) -> list[T]: - result = [] - if not partial or not self.max_count: - result = self._batch.copy() - self._batch.clear() - else: - batch_size = min(self.max_count, len(self._batch)) - result = self._batch[:batch_size].copy() - self._batch = self._batch[batch_size:] - - self._last_batch_time = time.monotonic() - self._triggered = False - self._check_batch_policy() - - return result - - def duration_until_next_batch(self) -> float: - if not self.max_window: - return -1 - return max(self.max_window - self.period, -1) - - def get_current_size(self) -> int: - return len(self._batch) - - def is_triggered(self): - return self._triggered or self._check_batch_policy() diff --git a/localstack-core/localstack/utils/batching.py b/localstack-core/localstack/utils/batching.py new file mode 100644 index 0000000000000..500e00e719da8 --- /dev/null +++ b/localstack-core/localstack/utils/batching.py @@ -0,0 +1,258 @@ +import copy +import logging +import threading +import time +from typing import Generic, Protocol, TypeVar, overload + +LOG = logging.getLogger(__name__) + +T = TypeVar("T") + +# alias to signify whether a batch policy has been triggered +BatchPolicyTriggered = bool + + +# TODO: Add batching on bytes as well. +class Batcher(Generic[T]): + """ + A utility for collecting items into batches and flushing them when one or more batch policy conditions are met. + + The batch policy can be created to trigger on: + - max_count: Maximum number of items added + - max_window: Maximum time window (in seconds) + + If no limits are specified, the batcher is always in triggered state. + + Example usage: + + import time + + # Triggers when 2 (or more) items are added + batcher = Batcher(max_count=2) + assert batcher.add(["item1", "item2", "item3"]) + assert batcher.flush() == ["item1", "item2", "item3"] + + # Triggers partially when 2 (or more) items are added + batcher = Batcher(max_count=2) + assert batcher.add(["item1", "item2", "item3"]) + assert batcher.flush(partial=True) == ["item1", "item2"] + assert batcher.add("item4") + assert batcher.flush(partial=True) == ["item3", "item4"] + + # Trigger 2 seconds after the first add + batcher = Batcher(max_window=2.0) + assert not batcher.add(["item1", "item2", "item3"]) + time.sleep(2.1) + assert not batcher.add(["item4"]) + assert batcher.flush() == ["item1", "item2", "item3", "item4"] + """ + + max_count: int | None + """ + Maximum number of items, must be None or positive. + """ + + max_window: float | None + """ + Maximum time window in seconds, must be None or positive. + """ + + _triggered: bool + _last_batch_time: float + _batch: list[T] + + def __init__(self, max_count: int | None = None, max_window: float | None = None): + """ + Initialize a new Batcher instance. + + :param max_count: Maximum number of items that be None or positive. + :param max_window: Maximum time window in seconds that must be None or positive. + """ + self.max_count = max_count + self.max_window = max_window + + self._triggered = False + self._last_batch_time = time.monotonic() + self._batch = [] + + @property + def period(self) -> float: + return time.monotonic() - self._last_batch_time + + def _check_batch_policy(self) -> bool: + """Check if any batch policy conditions are met""" + if self.max_count is not None and len(self._batch) >= self.max_count: + self._triggered = True + elif self.max_window is not None and self.period >= self.max_window: + self._triggered = True + elif not self.max_count and not self.max_window: + # always return true + self._triggered = True + + return self._triggered + + @overload + def add(self, item: T, *, deep_copy: bool = False) -> BatchPolicyTriggered: ... + + @overload + def add(self, items: list[T], *, deep_copy: bool = False) -> BatchPolicyTriggered: ... + + def add(self, item_or_items: T | list[T], *, deep_copy: bool = False) -> BatchPolicyTriggered: + """ + Add an item or list of items to the collected batch. + + Returns: + BatchPolicyTriggered: True if the batch policy was triggered during addition, False otherwise. + """ + if deep_copy: + item_or_items = copy.deepcopy(item_or_items) + + if isinstance(item_or_items, list): + self._batch.extend(item_or_items) + else: + self._batch.append(item_or_items) + + # Check if the last addition triggered the batch policy + return self.is_triggered() + + def flush(self, *, partial=False) -> list[T]: + result = [] + if not partial or not self.max_count: + result = self._batch.copy() + self._batch.clear() + else: + batch_size = min(self.max_count, len(self._batch)) + result = self._batch[:batch_size].copy() + self._batch = self._batch[batch_size:] + + self._last_batch_time = time.monotonic() + self._triggered = False + self._check_batch_policy() + + return result + + def duration_until_next_batch(self) -> float: + if not self.max_window: + return -1 + return max(self.max_window - self.period, -1) + + def get_current_size(self) -> int: + return len(self._batch) + + def is_triggered(self): + return self._triggered or self._check_batch_policy() + + +class BatchHandler(Protocol[T]): + """ + A BatchHandler is a callable that processes a list of items handed down by the AsyncBatcher. + """ + + def __call__(self, batch: list[T]) -> None: ... + + +class AsyncBatcher(Generic[T]): + """ + Class for managing asynchronous batching of items. + + This class allows for efficient buffering and processing of items in batches by + periodically flushing the buffer to a given handler, or by automatically flushing + when the maximum batch size is reached. It is designed to be used in asynchronous + scenarios where the caller does not execute the flushing IO call itself, like with ``Batcher``. + + :ivar max_flush_interval: Maximum time interval in seconds between + automatic flushes, regardless of the batch size. + :ivar max_batch_size: Maximum number of items in a batch. When reached, + the batch is flushed automatically. + :ivar handler: Callable handler that processes each flushed batch. The handler must + be provided during initialization and must accept a list of items as input. + """ + + max_flush_interval: float + max_batch_size: int + handler: BatchHandler[T] + + _buffer: list[T] + _flush_lock: threading.Condition + _closed: bool + + def __init__( + self, + handler: BatchHandler[T], + max_flush_interval: float = 10, + max_batch_size: int = 20, + ): + self.handler = handler + self.max_flush_interval = max_flush_interval + self.max_batch_size = max_batch_size + + self._buffer = [] + self._flush_lock = threading.Condition() + self._closed = False + + def add(self, item: T): + """ + Adds an item to the buffer. + + :param item: the item to add + """ + with self._flush_lock: + if self._closed: + raise ValueError("Batcher is stopped, can no longer add items") + + self._buffer.append(item) + + if len(self._buffer) >= self.max_batch_size: + self._flush_lock.notify_all() + + @property + def current_batch_size(self) -> int: + """ + Returns the current number of items in the buffer waiting to be flushed. + """ + return len(self._buffer) + + def run(self): + """ + Runs the event loop that flushes the buffer to the handler based on the configured rules, and blocks until + ``close()`` is called. This method is meant to be run in a separate thread. + """ + while not self._closed: + with self._flush_lock: + # wait returns once either the condition is notified (in which case wait returns True, indicating that + # something has triggered a flush manually), or the timeout expires (in which case wait returns False) + self._flush_lock.wait(self.max_flush_interval) + + # if _flush_condition was notified because close() was called, we should still make sure we flush the + # last batch + + # perform the flush, if there are any items in the buffer + if not self._buffer: + continue + + batch = self._buffer.copy() + self._buffer.clear() + + # we can call the processor outside the lock so we can continue adding items into the next batch without + # waiting on the processor to return. + try: + self.handler(batch) + except Exception as e: + LOG.error( + "Unhandled exception while processing a batch: %s", + e, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) + + # this marks that the main control loop is done + return + + def close(self): + """ + Triggers a close of the batcher, which will cause one last flush, and then end the main event loop. + """ + with self._flush_lock: + if self._closed: + return + self._closed = True + self._flush_lock.notify_all() diff --git a/localstack-core/localstack/utils/bootstrap.py b/localstack-core/localstack/utils/bootstrap.py index 6d65ef30db0f1..7bb8aa915d703 100644 --- a/localstack-core/localstack/utils/bootstrap.py +++ b/localstack-core/localstack/utils/bootstrap.py @@ -9,8 +9,11 @@ import signal import threading import time +from collections.abc import Callable, Iterable from functools import wraps -from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Union +from typing import Any + +from rich.console import Console from localstack import config, constants from localstack.config import ( @@ -33,8 +36,8 @@ NoSuchImage, NoSuchNetwork, PortMappings, - VolumeDirMount, VolumeMappings, + VolumeMappingSpecification, ) from localstack.utils.container_utils.docker_cmd_client import CmdDockerClient from localstack.utils.docker_utils import DOCKER_CLIENT @@ -47,6 +50,7 @@ from localstack.utils.sync import poll_condition LOG = logging.getLogger(__name__) +console = Console() # Mandatory dependencies of services on other services # - maps from API names to list of other API names that they _explicitly_ depend on: : @@ -66,6 +70,8 @@ "transcribe": ["s3"], # secretsmanager uses lambda for rotation "secretsmanager": ["kms", "lambda"], + # ssm uses secretsmanager for get_parameter + "ssm": ["secretsmanager"], } # Optional dependencies of services on other services @@ -154,7 +160,7 @@ def wrapped(*args, **kwargs): return wrapper -def get_docker_image_details(image_name: str = None) -> Dict[str, str]: +def get_docker_image_details(image_name: str = None) -> dict[str, str]: image_name = image_name or get_docker_image_to_start() try: result = DOCKER_CLIENT.inspect_image(image_name) @@ -172,7 +178,7 @@ def get_docker_image_details(image_name: str = None) -> Dict[str, str]: return result -def get_image_environment_variable(env_name: str) -> Optional[str]: +def get_image_environment_variable(env_name: str) -> str | None: image_name = get_docker_image_to_start() image_info = DOCKER_CLIENT.inspect_image(image_name) image_envs = image_info["Config"]["Env"] @@ -247,7 +253,7 @@ def setup_logging(): # -------------- -def resolve_apis(services: Iterable[str]) -> Set[str]: +def resolve_apis(services: Iterable[str]) -> set[str]: """ Resolves recursively for the given collection of services (e.g., ["serverless", "cognito"]) the list of actual API services that need to be included (e.g., {'dynamodb', 'cloudformation', 'logs', 'kinesis', 'sts', @@ -287,8 +293,8 @@ def resolve_apis(services: Iterable[str]) -> Set[str]: return result -@functools.lru_cache() -def get_enabled_apis() -> Set[str]: +@functools.lru_cache +def get_enabled_apis() -> set[str]: """ Returns the list of APIs that are enabled through the combination of the SERVICES variable and STRICT_SERVICE_LOADING variable. If the SERVICES variable is empty, then it will return all available services. @@ -322,8 +328,8 @@ def is_api_enabled(api: str) -> bool: return api in get_enabled_apis() -@functools.lru_cache() -def get_preloaded_services() -> Set[str]: +@functools.lru_cache +def get_preloaded_services() -> set[str]: """ Returns the list of APIs that are marked to be eager loaded through the combination of SERVICES variable and EAGER_SERVICE_LOADING. If the SERVICES variable is empty, then it will return all available services. @@ -362,8 +368,6 @@ def validate_localstack_config(name: str): # (use exceptions to communicate errors, and return list of warnings) from subprocess import CalledProcessError - from localstack.cli import console - dirname = os.getcwd() compose_file_name = name if os.path.isabs(name) else os.path.join(dirname, name) warns = [] @@ -424,15 +428,13 @@ def validate_localstack_config(name: str): def port_exposed(port): for exposed in docker_ports: - if re.match(r"^([0-9]+-)?%s(-[0-9]+)?$" % port, exposed): + if re.match(rf"^([0-9]+-)?{port}(-[0-9]+)?$", exposed): return True if not port_exposed(edge_port): warns.append( - ( - f"Edge port {edge_port} is not exposed. You may have to add the entry " - 'to the "ports" section of the docker-compose file.' - ) + f"Edge port {edge_port} is not exposed. You may have to add the entry " + 'to the "ports" section of the docker-compose file.' ) # print warning/info messages @@ -443,7 +445,7 @@ def port_exposed(port): return False -def get_docker_image_to_start(): +def get_docker_image_to_start() -> str: image_name = os.environ.get("IMAGE_NAME") if not image_name: image_name = constants.DOCKER_IMAGE_NAME @@ -454,7 +456,7 @@ def get_docker_image_to_start(): def extract_port_flags(user_flags, port_mappings: PortMappings): regex = r"-p\s+([0-9]+)(\-([0-9]+))?:([0-9]+)(\-([0-9]+))?" - matches = re.match(".*%s" % regex, user_flags) + matches = re.match(f".*{regex}", user_flags) if matches: for match in re.findall(regex, user_flags): start = int(match[0]) @@ -543,7 +545,7 @@ def default_gateway_port(cfg: ContainerConfiguration): @staticmethod def gateway_listen( - port: Union[int, Iterable[int], HostAndPort, Iterable[HostAndPort]], + port: int | Iterable[int] | HostAndPort | Iterable[HostAndPort], ): """ Uses the given ports to configure GATEWAY_LISTEN. For instance, ``gateway_listen([4566, 443])`` would @@ -636,7 +638,7 @@ def _cfg(cfg: ContainerConfiguration): return _cfg @staticmethod - def custom_command(cmd: List[str]): + def custom_command(cmd: list[str]): """ Overwrites the container command and unsets the default entrypoint. @@ -651,7 +653,7 @@ def _cfg(cfg: ContainerConfiguration): return _cfg @staticmethod - def env_vars(env_vars: Dict[str, str]): + def env_vars(env_vars: dict[str, str]): def _cfg(cfg: ContainerConfiguration): cfg.env_vars.update(env_vars) @@ -665,14 +667,14 @@ def _cfg(cfg: ContainerConfiguration): return _cfg @staticmethod - def volume(volume: BindMount | VolumeDirMount): + def volume(volume: VolumeMappingSpecification): def _cfg(cfg: ContainerConfiguration): cfg.volumes.add(volume) return _cfg @staticmethod - def cli_params(params: Dict[str, Any]): + def cli_params(params: dict[str, Any]): """ Parse docker CLI parameters and add them to the config. The currently known CLI params are:: @@ -806,7 +808,7 @@ def get_gateway_port(container: Container) -> int: :param container: the localstack container :return: the gateway port reachable from the host """ - candidates: List[int] + candidates: list[int] gateway_listen = container.config.env_vars.get("GATEWAY_LISTEN") if gateway_listen: @@ -999,7 +1001,7 @@ def shutdown(self, timeout: int = 10, remove: bool = True): return raise - def inspect(self) -> Dict[str, Union[Dict, str]]: + def inspect(self) -> dict[str, dict | str]: return self.container_client.inspect_container(container_name_or_id=self.id) def attach(self): @@ -1027,11 +1029,11 @@ def __init__(self, container: Container, callback: Callable[[str], None] = print self.callback = callback self._closed = threading.Event() - self._stream: Optional[CancellableStream] = None + self._stream: CancellableStream | None = None def _can_start_streaming(self): if self._closed.is_set(): - raise IOError("Already stopped") + raise OSError("Already stopped") if not self.container.running_container: return False return self.container.running_container.is_running() @@ -1039,7 +1041,7 @@ def _can_start_streaming(self): def run(self): try: poll_condition(self._can_start_streaming) - except IOError: + except OSError: return self._stream = self.container.running_container.stream_logs() for line in self._stream: @@ -1113,8 +1115,8 @@ def start(self) -> bool: def do_run(self): if self.is_container_running(): - raise ContainerExists( - 'LocalStack container named "%s" is already running' % self.container.name + raise ContainerRunning( + f'LocalStack container named "{self.container.name}" is already running' ) config.dirs.mkdirs() @@ -1150,12 +1152,19 @@ class ContainerExists(Exception): pass +class ContainerRunning(Exception): + pass + + def prepare_docker_start(): # prepare environment for docker start container_name = config.MAIN_CONTAINER_NAME if DOCKER_CLIENT.is_container_running(container_name): - raise ContainerExists('LocalStack container named "%s" is already running' % container_name) + raise ContainerRunning(f'LocalStack container named "{container_name}" is already running') + + if container_name in DOCKER_CLIENT.get_all_container_names(): + raise ContainerExists(f'LocalStack container named "{container_name}" already exists') config.dirs.mkdirs() @@ -1225,7 +1234,7 @@ def prepare_host(console): hooks.prepare_host.run() -def start_infra_in_docker(console, cli_params: Dict[str, Any] = None): +def start_infra_in_docker(console, cli_params: dict[str, Any] = None): prepare_docker_start() # create and prepare container @@ -1300,14 +1309,15 @@ def ensure_container_image(console, container: Container): console.log("download complete") -def start_infra_in_docker_detached(console, cli_params: Dict[str, Any] = None): +def start_infra_in_docker_detached(console, cli_params: dict[str, Any] = None): """ An alternative to start_infra_in_docker where the terminal is not blocked by the follow on the logfile. """ console.log("preparing environment") try: prepare_docker_start() - except ContainerExists as e: + except ContainerRunning as e: + # starting in detached mode is idempotent, return if container is already running console.print(str(e)) return @@ -1329,7 +1339,7 @@ def start_infra_in_docker_detached(console, cli_params: Dict[str, Any] = None): console.log("detaching") -def wait_container_is_ready(timeout: Optional[float] = None): +def wait_container_is_ready(timeout: float | None = None): """Blocks until the localstack main container is running and the ready marker has been printed.""" container_name = config.MAIN_CONTAINER_NAME started = time.time() diff --git a/localstack-core/localstack/utils/catalog/catalog.py b/localstack-core/localstack/utils/catalog/catalog.py new file mode 100644 index 0000000000000..a0a536fd36cff --- /dev/null +++ b/localstack-core/localstack/utils/catalog/catalog.py @@ -0,0 +1,141 @@ +import logging +from abc import abstractmethod +from typing import TypeAlias + +from plux import Plugin + +from localstack.utils.catalog.catalog_loader import RemoteCatalogLoader +from localstack.utils.catalog.common import ( + AwsServiceOperationsSupportInLatest, + AwsServicesSupportInLatest, + AwsServiceSupportAtRuntime, + CloudFormationResourcesSupportAtRuntime, + CloudFormationResourcesSupportInLatest, + LocalstackEmulatorType, +) + +ServiceName = str +ServiceOperations = set[str] +ProviderName = str +CfnResourceName = str +CfnResourceMethodName = str +AwsServicesSupportStatus: TypeAlias = ( + AwsServiceSupportAtRuntime | AwsServicesSupportInLatest | AwsServiceOperationsSupportInLatest +) +CfnResourceSupportStatus: TypeAlias = ( + CloudFormationResourcesSupportInLatest | CloudFormationResourcesSupportAtRuntime +) +CfnResourceCatalog = dict[LocalstackEmulatorType, dict[CfnResourceName, set[CfnResourceMethodName]]] + +LOG = logging.getLogger(__name__) + + +class CatalogPlugin(Plugin): + namespace = "localstack.utils.catalog" + + @staticmethod + def _get_cfn_resources_catalog(cloudformation_resources: dict) -> CfnResourceCatalog: + cfn_resources_catalog = {} + for emulator_type, resources in cloudformation_resources.items(): + cfn_resources_catalog[emulator_type] = {} + for resource_name, resource in resources.items(): + cfn_resources_catalog[emulator_type][resource_name] = set(resource.methods) + return cfn_resources_catalog + + @staticmethod + def _get_services_at_runtime() -> set[ServiceName]: + from localstack.services.plugins import SERVICE_PLUGINS + + return set(SERVICE_PLUGINS.list_available()) + + @staticmethod + def _get_cfn_resources_available_at_runtime() -> set[CfnResourceName]: + from localstack.services.cloudformation.resource_provider import ( + plugin_manager as cfn_plugin_manager, + ) + + return set(cfn_plugin_manager.list_names()) + + @abstractmethod + def get_aws_service_status( + self, service_name: str, operation_name: str | None = None + ) -> AwsServicesSupportStatus | None: + pass + + @abstractmethod + def get_cloudformation_resource_status( + self, resource_name: str, service_name: str, is_pro_resource: bool = False + ) -> CfnResourceSupportStatus | AwsServicesSupportInLatest | None: + pass + + +class AwsCatalogRuntimePlugin(CatalogPlugin): + name = "aws-catalog-runtime-only" + + def get_aws_service_status( + self, service_name: str, operation_name: str | None = None + ) -> AwsServicesSupportStatus | None: + return None + + def get_cloudformation_resource_status( + self, resource_name: str, service_name: str, is_pro_resource: bool = False + ) -> CfnResourceSupportStatus | AwsServicesSupportInLatest | None: + return None + + +class AwsCatalogRemoteStatePlugin(CatalogPlugin): + name = "aws-catalog-remote-state" + current_emulator_type: LocalstackEmulatorType = LocalstackEmulatorType.COMMUNITY + services_in_latest: dict[ServiceName, dict[LocalstackEmulatorType, ServiceOperations]] = {} + services_at_runtime: set[ServiceName] = set() + cfn_resources_in_latest: CfnResourceCatalog = {} + cfn_resources_at_runtime: set[CfnResourceName] = set() + + def __init__(self, remote_catalog_loader: RemoteCatalogLoader | None = None) -> None: + catalog_loader = remote_catalog_loader or RemoteCatalogLoader() + remote_catalog = catalog_loader.get_remote_catalog() + for service_name, emulators in remote_catalog.services.items(): + for emulator_type, service_provider in emulators.items(): + self.services_in_latest.setdefault(service_name, {})[emulator_type] = set( + service_provider.operations + ) + + self.cfn_resources_in_latest = self._get_cfn_resources_catalog( + remote_catalog.cloudformation_resources + ) + self.cfn_resources_at_runtime = self._get_cfn_resources_available_at_runtime() + self.services_at_runtime = self._get_services_at_runtime() + + def get_aws_service_status( + self, service_name: str, operation_name: str | None = None + ) -> AwsServicesSupportStatus | None: + if not self.services_in_latest: + return None + if service_name not in self.services_in_latest: + return AwsServicesSupportInLatest.NOT_SUPPORTED + if self.current_emulator_type not in self.services_in_latest[service_name]: + return AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE + if not operation_name: + return AwsServicesSupportInLatest.SUPPORTED + if operation_name in self.services_in_latest[service_name][self.current_emulator_type]: + return AwsServiceOperationsSupportInLatest.SUPPORTED + for emulator_type in self.services_in_latest[service_name]: + if emulator_type is self.current_emulator_type: + continue + if operation_name in self.services_in_latest[service_name][emulator_type]: + return AwsServiceOperationsSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE + return AwsServiceOperationsSupportInLatest.NOT_SUPPORTED + + def get_cloudformation_resource_status( + self, resource_name: str, service_name: str, is_pro_resource: bool = False + ) -> CfnResourceSupportStatus | AwsServicesSupportInLatest | None: + if resource_name in self.cfn_resources_at_runtime: + return CloudFormationResourcesSupportAtRuntime.AVAILABLE + if service_name in self.services_at_runtime: + if resource_name in self.cfn_resources_in_latest[self.current_emulator_type]: + return CloudFormationResourcesSupportInLatest.SUPPORTED + else: + return CloudFormationResourcesSupportInLatest.NOT_SUPPORTED + if service_name in self.services_in_latest: + return self.get_aws_service_status(service_name, operation_name=None) + return AwsServicesSupportInLatest.NOT_SUPPORTED diff --git a/localstack-core/localstack/utils/catalog/catalog_loader.py b/localstack-core/localstack/utils/catalog/catalog_loader.py new file mode 100644 index 0000000000000..2c2e84628abca --- /dev/null +++ b/localstack-core/localstack/utils/catalog/catalog_loader.py @@ -0,0 +1,119 @@ +import json +import logging +from json import JSONDecodeError +from pathlib import Path + +import requests +from pydantic import BaseModel + +from localstack import config, constants +from localstack.utils.catalog.common import AwsRemoteCatalog +from localstack.utils.http import get_proxies +from localstack.utils.json import FileMappedDocument + +LOG = logging.getLogger(__name__) + +AWS_CATALOG_FILE_NAME = "aws_catalog.json" + + +class RemoteCatalogVersionResponse(BaseModel): + emulator_type: str + version: str + + +class AwsCatalogLoaderException(Exception): + def __init__(self, msg: str, *args): + super().__init__(msg, *args) + + +class RemoteCatalogLoader: + supported_schema_version = "v1" + api_endpoint_catalog = f"{constants.API_ENDPOINT}/license/catalog" + catalog_file_path = Path(config.dirs.cache) / AWS_CATALOG_FILE_NAME + + def get_remote_catalog(self) -> AwsRemoteCatalog: + catalog_doc = FileMappedDocument(self.catalog_file_path) + cached_catalog = AwsRemoteCatalog(**catalog_doc) if catalog_doc else None + if cached_catalog: + cached_catalog_version = cached_catalog.localstack.version + if not self._should_update_cached_catalog(cached_catalog_version): + return cached_catalog + catalog = self._get_catalog_from_platform() + self._save_catalog_to_cache(catalog_doc, catalog) + return catalog + + def _get_latest_localstack_version(self) -> str: + try: + proxies = get_proxies() + response = requests.get( + f"{self.api_endpoint_catalog}/aws/version", + verify=not config.is_env_true("SSL_NO_VERIFY"), + proxies=proxies, + ) + if response.ok: + return RemoteCatalogVersionResponse.model_validate(response.content).version + self._raise_server_error(response) + except requests.exceptions.RequestException as e: + raise AwsCatalogLoaderException( + f"An unexpected network error occurred when trying to fetch latest localstack version: {e}" + ) from e + + def _should_update_cached_catalog(self, current_catalog_version: str) -> bool: + try: + latest_version = self._get_latest_localstack_version() + return latest_version != current_catalog_version + except Exception as e: + LOG.warning( + "Failed to retrieve the latest catalog version, cached catalog update skipped: %s", + e, + ) + return False + + def _save_catalog_to_cache(self, catalog_doc: FileMappedDocument, catalog: AwsRemoteCatalog): + catalog_doc.clear() + catalog_doc.update(catalog.model_dump()) + catalog_doc.save() + + def _get_catalog_from_platform(self) -> AwsRemoteCatalog: + try: + proxies = get_proxies() + response = requests.post( + self.api_endpoint_catalog, + verify=not config.is_env_true("SSL_NO_VERIFY"), + proxies=proxies, + ) + + if response.ok: + return self._parse_catalog(response.content) + self._raise_server_error(response) + except requests.exceptions.RequestException as e: + raise AwsCatalogLoaderException( + f"An unexpected network error occurred when trying to fetch remote catalog: {e}" + ) from e + + def _parse_catalog(self, document: bytes) -> AwsRemoteCatalog | None: + try: + catalog_json = json.loads(document) + except JSONDecodeError as e: + raise AwsCatalogLoaderException(f"Could not de-serialize json catalog: {e}") from e + remote_catalog = AwsRemoteCatalog.model_validate(catalog_json) + if remote_catalog.schema_version != self.supported_schema_version: + raise AwsCatalogLoaderException( + f"Unsupported schema version: '{remote_catalog.schema_version}'. Only '{self.supported_schema_version}' is supported" + ) + return remote_catalog + + def _raise_server_error(self, response: requests.Response): + try: + server_error = response.json() + if error_message := server_error.get("message"): + raise AwsCatalogLoaderException( + f"Unexpected AWS catalog server error: {response.text}" + ) + raise AwsCatalogLoaderException( + f"A server error occurred while calling remote catalog API (HTTP {response.status_code}): {error_message}" + ) + except Exception: + raise AwsCatalogLoaderException( + f"An unexpected server error occurred while calling remote catalog API (HTTP {response.status_code}): {response.text}" + ) diff --git a/localstack-core/localstack/utils/catalog/common.py b/localstack-core/localstack/utils/catalog/common.py new file mode 100644 index 0000000000000..809310cb0b8ca --- /dev/null +++ b/localstack-core/localstack/utils/catalog/common.py @@ -0,0 +1,58 @@ +from enum import StrEnum + +from pydantic import BaseModel + + +class CloudFormationResource(BaseModel): + methods: list[str] + + +class AwsServiceCatalog(BaseModel): + provider: str + operations: list[str] + plans: list[str] + + +class LocalStackMetadata(BaseModel): + version: str + + +class AwsRemoteCatalog(BaseModel): + schema_version: str + localstack: LocalStackMetadata + services: dict[str, dict[str, AwsServiceCatalog]] + cloudformation_resources: dict[str, dict[str, CloudFormationResource]] + + +class LocalstackEmulatorType(StrEnum): + COMMUNITY = "community" + PRO = "pro" + + +class AwsServiceSupportAtRuntime(StrEnum): + AVAILABLE = "AVAILABLE" + AVAILABLE_WITH_LICENSE_UPGRADE = "AVAILABLE_WITH_LICENSE_UPGRADE" + NOT_IMPLEMENTED = "NOT_IMPLEMENTED" + + +class AwsServicesSupportInLatest(StrEnum): + SUPPORTED = "SUPPORTED" + SUPPORTED_WITH_LICENSE_UPGRADE = "SUPPORTED_WITH_LICENSE_UPGRADE" + NOT_SUPPORTED = "NOT_SUPPORTED" + NON_DEFAULT_PROVIDER = "NON_DEFAULT_PROVIDER" + + +class AwsServiceOperationsSupportInLatest(StrEnum): + SUPPORTED = "SUPPORTED" + SUPPORTED_WITH_LICENSE_UPGRADE = "SUPPORTED_WITH_LICENSE_UPGRADE" + NOT_SUPPORTED = "NOT_SUPPORTED" + + +class CloudFormationResourcesSupportInLatest(StrEnum): + SUPPORTED = "SUPPORTED" + NOT_SUPPORTED = "NOT_SUPPORTED" + + +class CloudFormationResourcesSupportAtRuntime(StrEnum): + AVAILABLE = "AVAILABLE" + NOT_IMPLEMENTED = "NOT_IMPLEMENTED" diff --git a/localstack-core/localstack/utils/catalog/plugins.py b/localstack-core/localstack/utils/catalog/plugins.py new file mode 100644 index 0000000000000..8324268ba5340 --- /dev/null +++ b/localstack-core/localstack/utils/catalog/plugins.py @@ -0,0 +1,28 @@ +import logging + +from plux import PluginManager + +from localstack.utils.catalog.catalog import CatalogPlugin +from localstack.utils.objects import singleton_factory + +LOG = logging.getLogger(__name__) + + +@singleton_factory +def get_aws_catalog() -> CatalogPlugin: + plugin_manager = PluginManager(CatalogPlugin.namespace) + try: + plugin_name = "aws-catalog-remote-state-with-license" + if not plugin_manager.exists(plugin_name): + plugin_name = "aws-catalog-remote-state" + return plugin_manager.load(plugin_name) + except Exception as e: + LOG.debug( + "Failed to load catalog plugin with the latest LocalStack services support data, falling back to catalog without remote state: %s", + e, + ) + # Try to load runtime catalog from pro version first + fallback_plugin_name = "aws-catalog-runtime-only-with-license" + if not plugin_manager.exists(fallback_plugin_name): + fallback_plugin_name = "aws-catalog-runtime-only" + return plugin_manager.load(fallback_plugin_name) diff --git a/localstack-core/localstack/utils/cloudwatch/cloudwatch_util.py b/localstack-core/localstack/utils/cloudwatch/cloudwatch_util.py index 4df924d441ba6..58adfe8a9bf1e 100644 --- a/localstack-core/localstack/utils/cloudwatch/cloudwatch_util.py +++ b/localstack-core/localstack/utils/cloudwatch/cloudwatch_util.py @@ -2,7 +2,7 @@ import time from datetime import datetime, timezone from itertools import islice -from typing import Optional, TypedDict +from typing import TypedDict from werkzeug import Response as WerkzeugResponse @@ -20,8 +20,8 @@ class SqsMetricBatchData(TypedDict, total=False): MetricName: str QueueName: str - Value: Optional[int] - Unit: Optional[str] + Value: int | None + Unit: str | None def dimension_lambda(kwargs): @@ -30,7 +30,7 @@ def dimension_lambda(kwargs): def publish_lambda_metric( - metric, value, kwargs, account_id: Optional[str] = None, region_name: Optional[str] = None + metric, value, kwargs, account_id: str | None = None, region_name: str | None = None ): # publish metric only if CloudWatch service is available if not is_api_enabled("cloudwatch"): @@ -155,7 +155,7 @@ def store_cloudwatch_logs( log_stream_name, log_output, start_time=None, - auto_create_group: Optional[bool] = True, + auto_create_group: bool | None = True, ): if not is_api_enabled("logs"): return diff --git a/localstack-core/localstack/utils/collections.py b/localstack-core/localstack/utils/collections.py index 41860cd9a190c..52b202d3e2463 100644 --- a/localstack-core/localstack/utils/collections.py +++ b/localstack-core/localstack/utils/collections.py @@ -5,24 +5,17 @@ import logging import re -from collections.abc import Mapping +from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Sized from typing import ( Any, - Callable, - Dict, - Iterable, - Iterator, - List, Optional, - Sized, - Tuple, - Type, TypedDict, TypeVar, Union, cast, get_args, get_origin, + overload, ) import cachetools @@ -32,6 +25,9 @@ # default regex to match an item in a comma-separated list string DEFAULT_REGEX_LIST_ITEM = r"[\w-]+" +_E = TypeVar("_E") +"""TypeVar var used internally for container type parameters.""" + class AccessTrackingDict(dict): """ @@ -40,7 +36,7 @@ class AccessTrackingDict(dict): simply duplicates the entries of "wrapped" in the constructor, for simplicity. """ - def __init__(self, wrapped, callback: Callable[[Dict, str, List, Dict], Any] = None): + def __init__(self, wrapped, callback: Callable[[dict, str, list, dict], Any] = None): super().__init__(wrapped) self.callback = callback @@ -109,21 +105,18 @@ def __hash__(self): return hash(canonical_json(self._dict)) -_ListType = TypeVar("_ListType") - - -class PaginatedList(List[_ListType]): +class PaginatedList(list[_E]): """List which can be paginated and filtered. For usage in AWS APIs with paginated responses""" DEFAULT_PAGE_SIZE = 50 def get_page( self, - token_generator: Callable[[_ListType], str], + token_generator: Callable[[_E], str], next_token: str = None, page_size: int = None, - filter_function: Callable[[_ListType], bool] = None, - ) -> Tuple[List[_ListType], Optional[str]]: + filter_function: Callable[[_E], bool] = None, + ) -> tuple[list[_E], str | None]: if filter_function is not None: result_list = list(filter(filter_function, self)) else: @@ -155,7 +148,7 @@ def get_page( class CustomExpiryTTLCache(cachetools.TTLCache): """TTLCache that allows to set custom expiry times for individual keys.""" - def set_expiry(self, key: Any, ttl: Union[float, int]) -> float: + def set_expiry(self, key: Any, ttl: float | int) -> float: """Set the expiry of the given key in a TTLCache to ( + )""" with self.timer as time: # note: need to access the internal dunder API here @@ -280,13 +273,13 @@ def pick_attributes(dictionary, paths): return new_dictionary -def select_attributes(obj: Dict, attributes: List[str]) -> Dict: +def select_attributes(obj: dict, attributes: list[str]) -> dict: """Select a subset of attributes from the given dict (returns a copy)""" attributes = attributes if is_list_or_tuple(attributes) else [attributes] return {k: v for k, v in obj.items() if k in attributes} -def remove_attributes(obj: Dict, attributes: List[str], recursive: bool = False) -> Dict: +def remove_attributes(obj: dict, attributes: list[str], recursive: bool = False) -> dict: """Remove a set of attributes from the given dict (in-place)""" from localstack.utils.objects import recurse_object @@ -306,8 +299,8 @@ def _remove(o, **kwargs): def rename_attributes( - obj: Dict, old_to_new_attributes: Dict[str, str], in_place: bool = False -) -> Dict: + obj: dict, old_to_new_attributes: dict[str, str], in_place: bool = False +) -> dict: """Rename a set of attributes in the given dict object. Second parameter is a dict that maps old to new attribute names. Default is to return a copy, but can also pass in_place=True.""" if not in_place: @@ -322,7 +315,15 @@ def is_list_or_tuple(obj) -> bool: return isinstance(obj, (list, tuple)) -def ensure_list(obj: Any, wrap_none=False) -> Optional[List]: +@overload +def ensure_list(obj: None) -> None: ... + + +@overload +def ensure_list(obj: Any) -> list[Any]: ... + + +def ensure_list(obj: Any, wrap_none: bool = False) -> list[Any] | None: """Wrap the given object in a list, or return the object itself if it already is a list.""" if obj is None and not wrap_none: return obj @@ -385,7 +386,7 @@ def merge_dicts(*dicts, **kwargs): return result -def remove_none_values_from_dict(dict: Dict) -> Dict: +def remove_none_values_from_dict(dict: dict) -> dict: return {k: v for (k, v) in dict.items() if v is not None} @@ -399,7 +400,7 @@ def last_index_of(array, value): return result -def is_sub_dict(child_dict: Dict, parent_dict: Dict) -> bool: +def is_sub_dict(child_dict: dict, parent_dict: dict) -> bool: """Returns whether the first dict is a sub-dict (subset) of the second dict.""" return all(parent_dict.get(key) == val for key, val in child_dict.items()) @@ -421,7 +422,7 @@ def contained(item): return True -def is_none_or_empty(obj: Union[Optional[str], Optional[list]]) -> bool: +def is_none_or_empty(obj: str | None | list | None) -> bool: return ( obj is None or (isinstance(obj, str) and obj.strip() == "") @@ -429,7 +430,7 @@ def is_none_or_empty(obj: Union[Optional[str], Optional[list]]) -> bool: ) -def select_from_typed_dict(typed_dict: Type[TypedDict], obj: Dict, filter: bool = False) -> Dict: +def select_from_typed_dict(typed_dict: type[TypedDict], obj: dict, filter: bool = False) -> dict: """ Select a subset of attributes from a dictionary based on the keys of a given `TypedDict`. :param typed_dict: the `TypedDict` blueprint @@ -445,10 +446,10 @@ def select_from_typed_dict(typed_dict: Type[TypedDict], obj: Dict, filter: bool return selection -T = TypeVar("T", bound=Dict) +T = TypeVar("T", bound=dict) -def convert_to_typed_dict(typed_dict: Type[T], obj: Dict, strict: bool = False) -> T: +def convert_to_typed_dict(typed_dict: type[T], obj: dict, strict: bool = False) -> T: """ Converts the given object to the given typed dict (by calling the type constructors). Limitations: @@ -482,7 +483,7 @@ def convert_to_typed_dict(typed_dict: Type[T], obj: Dict, strict: bool = False) return result -def dict_multi_values(elements: Union[List, Dict]) -> Dict[str, List[Any]]: +def dict_multi_values(elements: list | dict) -> dict[str, list[Any]]: """ Return a dictionary with the original keys from the list of dictionary and the values are the list of values of the original dictionary. @@ -511,7 +512,7 @@ def dict_multi_values(elements: Union[List, Dict]) -> Dict[str, List[Any]]: def split_list_by( lst: Iterable[ItemType], predicate: Callable[[ItemType], bool] -) -> Tuple[List[ItemType], List[ItemType]]: +) -> tuple[list[ItemType], list[ItemType]]: truthy, falsy = [], [] for item in lst: @@ -523,7 +524,7 @@ def split_list_by( return truthy, falsy -def is_comma_delimited_list(string: str, item_regex: Optional[str] = None) -> bool: +def is_comma_delimited_list(string: str, item_regex: str | None = None) -> bool: """ Checks if the given string is a comma-delimited list of items. The optional `item_regex` parameter specifies the regex pattern for each item in the list. @@ -534,3 +535,29 @@ def is_comma_delimited_list(string: str, item_regex: Optional[str] = None) -> bo if pattern.match(string) is None: return False return True + + +def optional_list(condition: bool, items: Iterable[_E]) -> list[_E]: + """ + Given an iterable, either create a list out of the entire iterable (if `condition` is `True`), or return the empty list. + >>> print(optional_list(True, [1, 2, 3])) + [1, 2, 3] + >>> print(optional_list(False, [1, 2, 3])) + [] + """ + return list(filter(lambda _: condition, items)) + + +def iter_chunks(items: list[_E], chunk_size: int) -> Generator[list[_E], None, None]: + """ + Split a list into smaller chunks of a specified size and iterate over them. + + It is implemented as a generator and yields each chunk as needed, making it memory-efficient for large lists. + + :param items: A list of elements to be divided into chunks. + :param chunk_size: The maximum number of elements that a single chunk can contain. + :return: A generator that yields chunks (sublists) of the original list. Each chunk contains up to `chunk_size` + elements. + """ + for i in range(0, len(items), chunk_size): + yield items[i : i + chunk_size] diff --git a/localstack-core/localstack/utils/config_listener.py b/localstack-core/localstack/utils/config_listener.py index c8678baede5ae..f6b1f15cc047f 100644 --- a/localstack-core/localstack/utils/config_listener.py +++ b/localstack-core/localstack/utils/config_listener.py @@ -1,7 +1,7 @@ import json import logging import re -from typing import Callable, List +from collections.abc import Callable from requests.models import Response @@ -9,7 +9,7 @@ LOG = logging.getLogger(__name__) -CONFIG_LISTENERS: List[Callable[[str, str], None]] = [] +CONFIG_LISTENERS: list[Callable[[str, str], None]] = [] def trigger_config_listeners(variable, new_value): diff --git a/localstack-core/localstack/utils/container_networking.py b/localstack-core/localstack/utils/container_networking.py index 2e54dec0672ba..aaf8ba5286a71 100644 --- a/localstack-core/localstack/utils/container_networking.py +++ b/localstack-core/localstack/utils/container_networking.py @@ -2,7 +2,6 @@ import os import re from functools import lru_cache -from typing import Optional from localstack import config, constants from localstack.utils.container_utils.container_client import ContainerException @@ -12,8 +11,8 @@ LOG = logging.getLogger(__name__) -@lru_cache() -def get_main_container_network() -> Optional[str]: +@lru_cache +def get_main_container_network() -> str | None: """ Gets the main network of the LocalStack container (if we run in one, bridge otherwise) If there are multiple networks connected to the LocalStack container, we choose the first as "main" network @@ -49,8 +48,8 @@ def get_main_container_network() -> Optional[str]: return main_container_network -@lru_cache() -def get_endpoint_for_network(network: Optional[str] = None) -> str: +@lru_cache +def get_endpoint_for_network(network: str | None = None) -> str: """ Get the LocalStack endpoint (= IP address) on the given network. If a network is given, it will return the IP address/hostname of LocalStack on that network @@ -126,7 +125,7 @@ def get_main_container_id(): return None -@lru_cache() +@lru_cache def get_main_container_name(): """ Returns the container name of the LocalStack container diff --git a/localstack-core/localstack/utils/container_utils/container_client.py b/localstack-core/localstack/utils/container_utils/container_client.py index baac120b6ff3c..e15efa2e87044 100644 --- a/localstack-core/localstack/utils/container_utils/container_client.py +++ b/localstack-core/localstack/utils/container_utils/container_client.py @@ -5,24 +5,18 @@ import os import re import shlex -import sys import tarfile import tempfile from abc import ABCMeta, abstractmethod +from collections.abc import Callable from enum import Enum, unique from pathlib import Path from typing import ( - Callable, - Dict, - List, Literal, NamedTuple, - Optional, Protocol, - Tuple, + TypeAlias, TypedDict, - Union, - get_args, ) import dotenv @@ -40,6 +34,27 @@ WELL_KNOWN_IMAGE_REPO_PREFIXES = ("localhost/", "docker.io/library/") +def get_registry_from_image_name(image_name: str) -> str: + parts = image_name.split("/", maxsplit=1) + + if prefix := config.DOCKER_GLOBAL_IMAGE_PREFIX: + return prefix + + if len(parts) == 1: + # If no slash is present at all, it's an image name + return "docker.io" + + potential_registry = parts[0] + + registry_indicators = (".", ":", "localhost") + if any(indicator in potential_registry for indicator in registry_indicators): + # This indicates a registry domain or a local registry + return potential_registry + + # No explicit registry, assume Docker Hub + return "docker.io" + + @unique class DockerContainerStatus(Enum): DOWN = -1 @@ -60,7 +75,7 @@ class DockerContainerStats(TypedDict): MemUsage: tuple[int, int] NetIO: tuple[int, int] PIDs: int - SDKStats: Optional[dict] + SDKStats: dict | None class ContainerException(Exception): @@ -131,6 +146,7 @@ def close(self): raise NotImplementedError +# TODO: Migrate to StrEnum once the CLI does not need to support Python 3.10 (EOL Oct'26) anymore class DockerPlatform(str): """Platform in the format ``os[/arch[/variant]]``""" @@ -146,7 +162,7 @@ class Ulimit: name: str soft_limit: int - hard_limit: Optional[int] = None + hard_limit: int | None = None def __repr__(self): """Format: =[:]""" @@ -157,26 +173,18 @@ def __repr__(self): # defines the type for port mappings (source->target port range) -PortRange = Union[List, HashableList] +PortRange = list | HashableList # defines the protocol for a port range ("tcp" or "udp") PortProtocol = str -def isinstance_union(obj, class_or_tuple): - # that's some dirty hack - if sys.version_info < (3, 10): - return isinstance(obj, get_args(PortRange)) - else: - return isinstance(obj, class_or_tuple) - - class PortMappings: """Maps source to target port ranges for Docker port mappings.""" # bind host to be used for defining port mappings bind_host: str # maps `from` port range to `to` port range for port mappings - mappings: Dict[Tuple[PortRange, PortProtocol], List] + mappings: dict[tuple[PortRange, PortProtocol], list] def __init__(self, bind_host: str = None): self.bind_host = bind_host if bind_host else "" @@ -184,14 +192,14 @@ def __init__(self, bind_host: str = None): def add( self, - port: Union[int, PortRange], - mapped: Union[int, PortRange] = None, + port: int | PortRange, + mapped: int | PortRange = None, protocol: PortProtocol = "tcp", ): mapped = mapped or port - if isinstance_union(port, PortRange): + if isinstance(port, PortRange): for i in range(port[1] - port[0] + 1): - if isinstance_union(mapped, PortRange): + if isinstance(mapped, PortRange): self.add(port[0] + i, mapped[0] + i, protocol) else: self.add(port[0] + i, mapped, protocol) @@ -253,7 +261,7 @@ def entry(k, v): return " ".join([entry(k, v) for k, v in self.mappings.items()]) - def to_list(self) -> List[str]: # TODO test + def to_list(self) -> list[str]: # TODO test bind_address = f"{self.bind_host}:" if self.bind_host else "" def entry(k, v): @@ -269,7 +277,7 @@ def entry(k, v): return [item for k, v in self.mappings.items() for item in entry(k, v)] - def to_dict(self) -> Dict[str, Union[Tuple[str, Union[int, List[int]]], int]]: + def to_dict(self) -> dict[str, tuple[str, int | list[int]] | int]: bind_address = self.bind_host or "" def bind_port(bind_address, host_port): @@ -367,12 +375,18 @@ def __repr__(self): return f"" -SimpleVolumeBind = Tuple[str, str] +SimpleVolumeBind = tuple[str, str] """Type alias for a simple version of VolumeBind""" @dataclasses.dataclass -class BindMount: +class Mount: + def to_str(self) -> str: + return str(self) + + +@dataclasses.dataclass +class BindMount(Mount): """Represents a --volume argument run/create command. When using VolumeBind to bind-mount a file or directory that does not yet exist on the Docker host, -v creates the endpoint for you. It is always created as a directory. """ @@ -417,7 +431,7 @@ def parse(cls, param: str) -> "BindMount": @dataclasses.dataclass -class VolumeDirMount: +class VolumeDirMount(Mount): volume_path: str """ Absolute path inside /var/lib/localstack to mount into the container @@ -454,28 +468,25 @@ def to_docker_sdk_parameters(self) -> tuple[str, dict[str, str]]: } +VolumeMappingSpecification: TypeAlias = SimpleVolumeBind | Mount + + class VolumeMappings: - mappings: List[Union[SimpleVolumeBind, BindMount]] + mappings: list[VolumeMappingSpecification] - def __init__(self, mappings: List[Union[SimpleVolumeBind, BindMount, VolumeDirMount]] = None): + def __init__( + self, + mappings: list[VolumeMappingSpecification] = None, + ): self.mappings = mappings if mappings is not None else [] - def add(self, mapping: Union[SimpleVolumeBind, BindMount, VolumeDirMount]): + def add(self, mapping: VolumeMappingSpecification): self.append(mapping) - def append( - self, - mapping: Union[ - SimpleVolumeBind, - BindMount, - VolumeDirMount, - ], - ): + def append(self, mapping: VolumeMappingSpecification): self.mappings.append(mapping) - def find_target_mapping( - self, container_dir: str - ) -> Optional[Union[SimpleVolumeBind, BindMount, VolumeDirMount]]: + def find_target_mapping(self, container_dir: str) -> VolumeMappingSpecification | None: """ Looks through the volumes and returns the one where the container dir matches ``container_dir``. Returns None if there is no volume mapping to the given container directory. @@ -514,27 +525,27 @@ class VolumeInfo(NamedTuple): mode: str rw: bool propagation: str - name: Optional[str] = None - driver: Optional[str] = None + name: str | None = None + driver: str | None = None @dataclasses.dataclass class LogConfig: type: Literal["json-file", "syslog", "journald", "gelf", "fluentd", "none", "awslogs", "splunk"] - config: Dict[str, str] = dataclasses.field(default_factory=dict) + config: dict[str, str] = dataclasses.field(default_factory=dict) @dataclasses.dataclass class ContainerConfiguration: image_name: str - name: Optional[str] = None + name: str | None = None volumes: VolumeMappings = dataclasses.field(default_factory=VolumeMappings) ports: PortMappings = dataclasses.field(default_factory=PortMappings) - exposed_ports: List[str] = dataclasses.field(default_factory=list) - entrypoint: Optional[Union[List[str], str]] = None - additional_flags: Optional[str] = None - command: Optional[List[str]] = None - env_vars: Dict[str, str] = dataclasses.field(default_factory=dict) + exposed_ports: list[str] = dataclasses.field(default_factory=list) + entrypoint: list[str] | str | None = None + additional_flags: str | None = None + command: list[str] | None = None + env_vars: dict[str, str] = dataclasses.field(default_factory=dict) privileged: bool = False remove: bool = False @@ -542,19 +553,22 @@ class ContainerConfiguration: tty: bool = False detach: bool = False - stdin: Optional[str] = None - user: Optional[str] = None - cap_add: Optional[List[str]] = None - cap_drop: Optional[List[str]] = None - security_opt: Optional[List[str]] = None - network: Optional[str] = None - dns: Optional[str] = None - workdir: Optional[str] = None - platform: Optional[str] = None - ulimits: Optional[List[Ulimit]] = None - labels: Optional[Dict[str, str]] = None - init: Optional[bool] = None - log_config: Optional[LogConfig] = None + stdin: str | None = None + user: str | None = None + cap_add: list[str] | None = None + cap_drop: list[str] | None = None + security_opt: list[str] | None = None + network: str | None = None + dns: str | None = None + workdir: str | None = None + platform: str | None = None + ulimits: list[Ulimit] | None = None + labels: dict[str, str] | None = None + init: bool | None = None + log_config: LogConfig | None = None + cpu_shares: int | None = None + mem_limit: int | str | None = None + auth_config: dict[str, str] | None = None class ContainerConfigurator(Protocol): @@ -577,17 +591,17 @@ class DockerRunFlags: create: https://docs.docker.com/engine/reference/commandline/create/ """ - env_vars: Optional[Dict[str, str]] - extra_hosts: Optional[Dict[str, str]] - labels: Optional[Dict[str, str]] - volumes: Optional[List[SimpleVolumeBind]] - network: Optional[str] - platform: Optional[DockerPlatform] - privileged: Optional[bool] - ports: Optional[PortMappings] - ulimits: Optional[List[Ulimit]] - user: Optional[str] - dns: Optional[List[str]] + env_vars: dict[str, str] | None + extra_hosts: dict[str, str] | None + labels: dict[str, str] | None + volumes: list[SimpleVolumeBind] | None + network: str | None + platform: DockerPlatform | None + privileged: bool | None + ports: PortMappings | None + ulimits: list[Ulimit] | None + user: str | None + dns: list[str] | None class RegistryResolverStrategy(Protocol): @@ -621,7 +635,7 @@ def get_container_stats(self, container_name: str) -> DockerContainerStats: """Returns the usage statistics of the container with the given name""" pass - def get_networks(self, container_name: str) -> List[str]: + def get_networks(self, container_name: str) -> list[str]: LOG.debug("Getting networks for container: %s", container_name) container_attrs = self.inspect_container(container_name_or_id=container_name) return list(container_attrs["NetworkSettings"].get("Networks", {}).keys()) @@ -689,8 +703,16 @@ def unpause_container(self, container_name: str): """Unpauses a container with the given name.""" @abstractmethod - def remove_container(self, container_name: str, force=True, check_existence=False) -> None: - """Removes container with given name""" + def remove_container( + self, container_name: str, force=True, check_existence=False, volumes=False + ) -> None: + """Removes container + + :param container_name: Name of the container + :param force: Force the removal of a running container (uses SIGKILL) + :param check_existence: Return if container doesn't exist + :param volumes: Remove anonymous volumes associated with the container + """ @abstractmethod def remove_image(self, image: str, force: bool = True) -> None: @@ -701,17 +723,19 @@ def remove_image(self, image: str, force: bool = True) -> None: """ @abstractmethod - def list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: + def list_containers(self, filter: list[str] | str | None = None, all=True) -> list[dict]: """List all containers matching the given filters :return: A list of dicts with keys id, image, name, labels, status """ - def get_running_container_names(self) -> List[str]: + def get_running_container_names(self) -> list[str]: """Returns a list of the names of all running containers""" - result = self.list_containers(all=False) - result = [container["name"] for container in result] - return result + return self.__get_container_names(return_all=False) + + def get_all_container_names(self) -> list[str]: + """Returns a list of the names of all containers including stopped ones""" + return self.__get_container_names(return_all=True) def is_container_running(self, container_name: str) -> bool: """Checks whether a container with a given name is currently running""" @@ -722,7 +746,7 @@ def create_file_in_container( container_name, file_contents: bytes, container_path: str, - chmod_mode: Optional[int] = None, + chmod_mode: int | None = None, ) -> None: """ Create a file in container with the provided content. Provide the 'chmod_mode' argument if you want the file to have specific permissions. @@ -754,18 +778,25 @@ def copy_from_container( def pull_image( self, docker_image: str, - platform: Optional[DockerPlatform] = None, - log_handler: Optional[Callable[[str], None]] = None, + platform: DockerPlatform | None = None, + log_handler: Callable[[str], None] | None = None, + auth_config: dict[str, str] | None = None, ) -> None: """ Pulls an image with a given name from a Docker registry :log_handler: Optional parameter that can be used to process the logs. Logs will be streamed if possible, but this is not guaranteed. + :auth_config: Optional authentication configuration for private registries. Dict with keys: username, password, registry """ @abstractmethod - def push_image(self, docker_image: str) -> None: - """Pushes an image with a given name to a Docker registry""" + def push_image(self, docker_image: str, auth_config: dict[str, str] | None = None) -> None: + """ + Pushes an image with a given name to a Docker registry + + :param docker_image: Image name and tag to push + :param auth_config: Optional authentication configuration for private registries. Dict with keys: username, password, registry + """ @abstractmethod def build_image( @@ -773,7 +804,7 @@ def build_image( dockerfile_path: str, image_name: str, context_path: str = None, - platform: Optional[DockerPlatform] = None, + platform: DockerPlatform | None = None, ) -> str: """Builds an image from the given Dockerfile @@ -798,7 +829,7 @@ def get_docker_image_names( strip_latest: bool = True, include_tags: bool = True, strip_wellknown_repo_prefixes: bool = True, - ) -> List[str]: + ) -> list[str]: """ Get all names of docker images available to the container engine :param strip_latest: return images both with and without :latest tag @@ -817,13 +848,13 @@ def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: """Returns a blocking generator you can iterate over to retrieve log output as it happens.""" @abstractmethod - def inspect_container(self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: + def inspect_container(self, container_name_or_id: str) -> dict[str, dict | str]: """Get detailed attributes of a container. :return: Dict containing docker attributes as returned by the daemon """ - def inspect_container_volumes(self, container_name_or_id) -> List[VolumeInfo]: + def inspect_container_volumes(self, container_name_or_id) -> list[VolumeInfo]: """Return information about the volumes mounted into the given container. :param container_name_or_id: the container name or id @@ -838,7 +869,7 @@ def inspect_container_volumes(self, container_name_or_id) -> List[VolumeInfo]: @abstractmethod def inspect_image( self, image_name: str, pull: bool = True, strip_wellknown_repo_prefixes: bool = True - ) -> Dict[str, Union[dict, list, str]]: + ) -> dict[str, dict | list | str]: """Get detailed attributes of an image. :param image_name: Image name to inspect @@ -864,7 +895,7 @@ def delete_network(self, network_name: str) -> None: """ @abstractmethod - def inspect_network(self, network_name: str) -> Dict[str, Union[Dict, str]]: + def inspect_network(self, network_name: str) -> dict[str, dict | str]: """Get detailed attributes of an network. :return: Dict containing docker attributes as returned by the daemon @@ -875,8 +906,8 @@ def connect_container_to_network( self, network_name: str, container_name_or_id: str, - aliases: Optional[List] = None, - link_local_ips: List[str] = None, + aliases: list | None = None, + link_local_ips: list[str] = None, ) -> None: """ Connects a container to a given network @@ -911,7 +942,7 @@ def get_container_ip(self, container_name_or_id: str) -> str: If container has multiple networks, it will return the IP of the first """ - def get_image_cmd(self, docker_image: str, pull: bool = True) -> List[str]: + def get_image_cmd(self, docker_image: str, pull: bool = True) -> list[str]: """Get the command for the given image :param docker_image: Docker image to inspect :param pull: Whether to pull if image is not present @@ -980,6 +1011,9 @@ def create_container_from_config(self, container_config: ContainerConfiguration) ulimits=container_config.ulimits, init=container_config.init, log_config=container_config.log_config, + cpu_shares=container_config.cpu_shares, + mem_limit=container_config.mem_limit, + auth_config=container_config.auth_config, ) @abstractmethod @@ -987,31 +1021,34 @@ def create_container( self, image_name: str, *, - name: Optional[str] = None, - entrypoint: Optional[Union[List[str], str]] = None, + name: str | None = None, + entrypoint: list[str] | str | None = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[Union[VolumeMappings, List[SimpleVolumeBind]]] = None, - ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, - user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, - network: Optional[str] = None, - dns: Optional[Union[str, List[str]]] = None, - additional_flags: Optional[str] = None, - workdir: Optional[str] = None, - privileged: Optional[bool] = None, - labels: Optional[Dict[str, str]] = None, - platform: Optional[DockerPlatform] = None, - ulimits: Optional[List[Ulimit]] = None, - init: Optional[bool] = None, - log_config: Optional[LogConfig] = None, + command: list[str] | str | None = None, + volumes: VolumeMappings | list[SimpleVolumeBind] | None = None, + ports: PortMappings | None = None, + exposed_ports: list[str] | None = None, + env_vars: dict[str, str] | None = None, + user: str | None = None, + cap_add: list[str] | None = None, + cap_drop: list[str] | None = None, + security_opt: list[str] | None = None, + network: str | None = None, + dns: str | list[str] | None = None, + additional_flags: str | None = None, + workdir: str | None = None, + privileged: bool | None = None, + labels: dict[str, str] | None = None, + platform: DockerPlatform | None = None, + ulimits: list[Ulimit] | None = None, + init: bool | None = None, + log_config: LogConfig | None = None, + cpu_shares: int | None = None, + mem_limit: int | str | None = None, + auth_config: dict[str, str] | None = None, ) -> str: """Creates a container with the given image @@ -1024,32 +1061,35 @@ def run_container( image_name: str, stdin: bytes = None, *, - name: Optional[str] = None, - entrypoint: Optional[str] = None, + name: str | None = None, + entrypoint: str | None = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[Union[VolumeMappings, List[SimpleVolumeBind]]] = None, - ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, - user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, - network: Optional[str] = None, - dns: Optional[str] = None, - additional_flags: Optional[str] = None, - workdir: Optional[str] = None, - labels: Optional[Dict[str, str]] = None, - platform: Optional[DockerPlatform] = None, - privileged: Optional[bool] = None, - ulimits: Optional[List[Ulimit]] = None, - init: Optional[bool] = None, - log_config: Optional[LogConfig] = None, - ) -> Tuple[bytes, bytes]: + command: list[str] | str | None = None, + volumes: VolumeMappings | list[SimpleVolumeBind] | None = None, + ports: PortMappings | None = None, + exposed_ports: list[str] | None = None, + env_vars: dict[str, str] | None = None, + user: str | None = None, + cap_add: list[str] | None = None, + cap_drop: list[str] | None = None, + security_opt: list[str] | None = None, + network: str | None = None, + dns: str | None = None, + additional_flags: str | None = None, + workdir: str | None = None, + labels: dict[str, str] | None = None, + platform: DockerPlatform | None = None, + privileged: bool | None = None, + ulimits: list[Ulimit] | None = None, + init: bool | None = None, + log_config: LogConfig | None = None, + cpu_shares: int | None = None, + mem_limit: int | str | None = None, + auth_config: dict[str, str] | None = None, + ) -> tuple[bytes, bytes]: """Creates and runs a given docker container :return: A tuple (stdout, stderr) @@ -1057,7 +1097,7 @@ def run_container( def run_container_from_config( self, container_config: ContainerConfiguration - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: """Like ``run_container`` but uses the parameters from the configuration.""" return self.run_container( @@ -1087,20 +1127,23 @@ def run_container_from_config( ulimits=container_config.ulimits, init=container_config.init, log_config=container_config.log_config, + cpu_shares=container_config.cpu_shares, + mem_limit=container_config.mem_limit, + auth_config=container_config.auth_config, ) @abstractmethod def exec_in_container( self, container_name_or_id: str, - command: Union[List[str], str], + command: list[str] | str, interactive: bool = False, detach: bool = False, - env_vars: Optional[Dict[str, Optional[str]]] = None, - stdin: Optional[bytes] = None, - user: Optional[str] = None, - workdir: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + env_vars: dict[str, str | None] | None = None, + stdin: bytes | None = None, + user: str | None = None, + workdir: str | None = None, + ) -> tuple[bytes, bytes]: """Execute a given command in a container :return: A tuple (stdout, stderr) @@ -1113,8 +1156,8 @@ def start_container( stdin: bytes = None, interactive: bool = False, attach: bool = False, - flags: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + flags: str | None = None, + ) -> tuple[bytes, bytes]: """Start a given, already created container :return: A tuple (stdout, stderr) if attach or interactive is set, otherwise a tuple (b"container_name_or_id", b"") @@ -1127,7 +1170,7 @@ def attach_to_container(self, container_name_or_id: str): """ @abstractmethod - def login(self, username: str, password: str, registry: Optional[str] = None) -> None: + def login(self, username: str, password: str, registry: str | None = None) -> None: """ Login into an OCI registry @@ -1136,18 +1179,23 @@ def login(self, username: str, password: str, registry: Optional[str] = None) -> :param registry: Registry url """ + def __get_container_names(self, return_all: bool) -> list[str]: + result = self.list_containers(all=return_all) + result = [container["name"] for container in result] + return result + class Util: MAX_ENV_ARGS_LENGTH = 20000 @staticmethod - def format_env_vars(key: str, value: Optional[str]): + def format_env_vars(key: str, value: str | None): if value is None: return key return f"{key}={value}" @classmethod - def create_env_vars_file_flag(cls, env_vars: Dict) -> Tuple[List[str], Optional[str]]: + def create_env_vars_file_flag(cls, env_vars: dict) -> tuple[list[str], str | None]: if not env_vars: return [], None result = [] @@ -1185,14 +1233,14 @@ def mountable_tmp_file(): return f @staticmethod - def append_without_latest(image_names: List[str]): + def append_without_latest(image_names: list[str]): suffix = ":latest" for image in list(image_names): if image.endswith(suffix): image_names.append(image[: -len(suffix)]) @staticmethod - def strip_wellknown_repo_prefixes(image_names: List[str]) -> List[str]: + def strip_wellknown_repo_prefixes(image_names: list[str]) -> list[str]: """ Remove well-known repo prefixes like `localhost/` or `docker.io/library/` from the list of given image names. This is mostly to ensure compatibility of our Docker client with Podman API responses. @@ -1238,7 +1286,7 @@ def untar_to_path(tardata, target_path): LOG.debug("File to copy empty, ignoring...") @staticmethod - def _read_docker_cli_env_file(env_file: str) -> Dict[str, str]: + def _read_docker_cli_env_file(env_file: str) -> dict[str, str]: """ Read an environment file in docker CLI format, specified here: https://docs.docker.com/reference/cli/docker/container/run/#env @@ -1247,7 +1295,7 @@ def _read_docker_cli_env_file(env_file: str) -> Dict[str, str]: """ env_vars = {} try: - with open(env_file, mode="rt") as f: + with open(env_file) as f: env_file_lines = f.readlines() except FileNotFoundError as e: LOG.error( @@ -1280,16 +1328,16 @@ def _read_docker_cli_env_file(env_file: str) -> Dict[str, str]: @staticmethod def parse_additional_flags( additional_flags: str, - env_vars: Optional[Dict[str, str]] = None, - labels: Optional[Dict[str, str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, - network: Optional[str] = None, - platform: Optional[DockerPlatform] = None, - ports: Optional[PortMappings] = None, - privileged: Optional[bool] = None, - user: Optional[str] = None, - ulimits: Optional[List[Ulimit]] = None, - dns: Optional[Union[str, List[str]]] = None, + env_vars: dict[str, str] | None = None, + labels: dict[str, str] | None = None, + volumes: list[SimpleVolumeBind] | None = None, + network: str | None = None, + platform: DockerPlatform | None = None, + ports: PortMappings | None = None, + privileged: bool | None = None, + user: str | None = None, + ulimits: list[Ulimit] | None = None, + dns: str | list[str] | None = None, ) -> DockerRunFlags: """Parses additional CLI-formatted Docker flags, which could overwrite provided defaults. :param additional_flags: String which contains the flag definitions inspired by the Docker CLI reference: @@ -1510,11 +1558,12 @@ def parse_additional_flags( @staticmethod def convert_mount_list_to_dict( - volumes: Union[List[SimpleVolumeBind], VolumeMappings], - ) -> Dict[str, Dict[str, str]]: + volumes: list[SimpleVolumeBind] | VolumeMappings, + ) -> dict[str, dict[str, str]]: """Converts a List of (host_path, container_path) tuples to a Dict suitable as volume argument for docker sdk""" - def _map_to_dict(paths: SimpleVolumeBind | BindMount | VolumeDirMount): + def _map_to_dict(paths: VolumeMappingSpecification): + # TODO: move this logic to the `Mount` base class if isinstance(paths, (BindMount, VolumeDirMount)): return paths.to_docker_sdk_parameters() else: diff --git a/localstack-core/localstack/utils/container_utils/docker_cmd_client.py b/localstack-core/localstack/utils/container_utils/docker_cmd_client.py index ebb5dc7a10dd0..9066b2987a646 100644 --- a/localstack-core/localstack/utils/container_utils/docker_cmd_client.py +++ b/localstack-core/localstack/utils/container_utils/docker_cmd_client.py @@ -6,13 +6,12 @@ import re import shlex import subprocess -from typing import Callable, Dict, List, Optional, Tuple, Union +from collections.abc import Callable from localstack import config from localstack.utils.collections import ensure_list from localstack.utils.container_utils.container_client import ( AccessDenied, - BindMount, CancellableStream, ContainerClient, ContainerException, @@ -21,16 +20,17 @@ DockerNotAvailable, DockerPlatform, LogConfig, + Mount, NoSuchContainer, NoSuchImage, NoSuchNetwork, NoSuchObject, PortMappings, RegistryConnectionError, - SimpleVolumeBind, Ulimit, Util, - VolumeDirMount, + VolumeMappingSpecification, + get_registry_from_image_name, ) from localstack.utils.run import run from localstack.utils.strings import first_char_to_upper, to_str @@ -96,9 +96,9 @@ class CmdDockerClient(ContainerClient): different response payloads or error messages returned by the `docker` vs `podman` commands. """ - default_run_outfile: Optional[str] = None + default_run_outfile: str | None = None - def _docker_cmd(self) -> List[str]: + def _docker_cmd(self) -> list[str]: """ Get the configured, tested Docker CMD. :return: string to be used for running Docker commands @@ -202,7 +202,7 @@ def restart_container(self, container_name: str, timeout: int = 10) -> None: except subprocess.CalledProcessError as e: self._check_and_raise_no_such_container_error(container_name, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def pause_container(self, container_name: str) -> None: @@ -214,7 +214,7 @@ def pause_container(self, container_name: str) -> None: except subprocess.CalledProcessError as e: self._check_and_raise_no_such_container_error(container_name, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def unpause_container(self, container_name: str) -> None: @@ -226,7 +226,7 @@ def unpause_container(self, container_name: str) -> None: except subprocess.CalledProcessError as e: self._check_and_raise_no_such_container_error(container_name, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def remove_image(self, image: str, force: bool = True) -> None: @@ -243,7 +243,7 @@ def remove_image(self, image: str, force: bool = True) -> None: if any(msg in to_str(e.stdout) for msg in error_messages): raise NoSuchImage(image, stdout=e.stdout, stderr=e.stderr) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def commit( @@ -262,26 +262,34 @@ def commit( except subprocess.CalledProcessError as e: self._check_and_raise_no_such_container_error(container_name_or_id, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e - def remove_container(self, container_name: str, force=True, check_existence=False) -> None: - if check_existence and container_name not in self.get_running_container_names(): + def remove_container( + self, container_name: str, force=True, check_existence=False, volumes=False + ) -> None: + if check_existence and container_name not in self.get_all_container_names(): return cmd = self._docker_cmd() + ["rm"] if force: cmd.append("-f") + if volumes: + cmd.append("--volumes") cmd.append(container_name) LOG.debug("Removing container with cmd %s", cmd) try: - run(cmd) + output = run(cmd) + # When the container does not exist, the output could have the error message without any exception + if isinstance(output, str) and not force: + self._check_output_and_raise_no_such_container_error(container_name, output=output) except subprocess.CalledProcessError as e: - self._check_and_raise_no_such_container_error(container_name, error=e) + if not force: + self._check_and_raise_no_such_container_error(container_name, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e - def list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: + def list_containers(self, filter: list[str] | str | None = None, all=True) -> list[dict]: filter = [filter] if isinstance(filter, str) else filter cmd = self._docker_cmd() cmd.append("ps") @@ -297,7 +305,7 @@ def list_containers(self, filter: Union[List[str], str, None] = None, all=True) cmd_result = run(cmd).strip() except subprocess.CalledProcessError as e: raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e container_list = [] if cmd_result: @@ -351,15 +359,17 @@ def copy_from_container( if re.match(".*container .+ does not exist", to_str(e.stdout)): raise NoSuchContainer(container_name, stdout=e.stdout, stderr=e.stderr) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def pull_image( self, docker_image: str, - platform: Optional[DockerPlatform] = None, - log_handler: Optional[Callable[[str], None]] = None, + platform: DockerPlatform | None = None, + log_handler: Callable[[str], None] | None = None, + auth_config: dict[str, str] | None = None, ) -> None: + self._login_if_needed(auth_config, docker_image) cmd = self._docker_cmd() docker_image = self.registry_resolver_strategy.resolve(docker_image) cmd += ["pull", docker_image] @@ -380,10 +390,11 @@ def pull_image( if "Trying to pull" in stdout_str and "access to the resource is denied" in stdout_str: raise NoSuchImage(docker_image, stdout=e.stdout, stderr=e.stderr) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e - def push_image(self, docker_image: str) -> None: + def push_image(self, docker_image: str, auth_config: dict[str, str] | None = None) -> None: + self._login_if_needed(auth_config, docker_image) cmd = self._docker_cmd() cmd += ["push", docker_image] LOG.debug("Pushing image with cmd: %s", cmd) @@ -396,10 +407,18 @@ def push_image(self, docker_image: str) -> None: raise AccessDenied(docker_image) if "access token has insufficient scopes" in to_str(e.stdout): raise AccessDenied(docker_image) + if "authorization failed: no basic auth credentials" in to_str(e.stdout): + raise AccessDenied(docker_image) + if "failed to authorize: failed to fetch oauth token" in to_str(e.stdout): + raise AccessDenied(docker_image) + if "insufficient_scope: authorization failed" in to_str(e.stdout): + raise AccessDenied(docker_image) if "does not exist" in to_str(e.stdout): raise NoSuchImage(docker_image) if "connection refused" in to_str(e.stdout): raise RegistryConnectionError(e.stdout) + if "failed to do request:" in to_str(e.stdout): + raise RegistryConnectionError(e.stdout) # note: error message 'image not known' raised by Podman client if "image not known" in to_str(e.stdout): raise NoSuchImage(docker_image) @@ -412,7 +431,7 @@ def build_image( dockerfile_path: str, image_name: str, context_path: str = None, - platform: Optional[DockerPlatform] = None, + platform: DockerPlatform | None = None, ): cmd = self._docker_cmd() dockerfile_path = Util.resolve_dockerfile_path(dockerfile_path) @@ -474,7 +493,7 @@ def get_container_logs(self, container_name_or_id: str, safe=False) -> str: return "" self._check_and_raise_no_such_container_error(container_name_or_id, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: @@ -489,7 +508,7 @@ def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: return CancellableProcessStream(process) - def _inspect_object(self, object_name_or_id: str) -> Dict[str, Union[dict, list, str]]: + def _inspect_object(self, object_name_or_id: str) -> dict[str, dict | list | str]: cmd = self._docker_cmd() cmd += ["inspect", "--format", "{{json .}}", object_name_or_id] try: @@ -499,7 +518,7 @@ def _inspect_object(self, object_name_or_id: str) -> Dict[str, Union[dict, list, if "no such object" in to_str(e.stdout).lower(): raise NoSuchObject(object_name_or_id, stdout=e.stdout, stderr=e.stderr) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e object_data = json.loads(cmd_result.strip()) if isinstance(object_data, list): @@ -516,7 +535,7 @@ def _inspect_object(self, object_name_or_id: str) -> Dict[str, Union[dict, list, ) return object_data - def inspect_container(self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: + def inspect_container(self, container_name_or_id: str) -> dict[str, dict | str]: try: return self._inspect_object(container_name_or_id) except NoSuchObject as e: @@ -527,7 +546,7 @@ def inspect_image( image_name: str, pull: bool = True, strip_wellknown_repo_prefixes: bool = True, - ) -> Dict[str, Union[dict, list, str]]: + ) -> dict[str, dict | list | str]: image_name = self.registry_resolver_strategy.resolve(image_name) try: result = self._inspect_object(image_name) @@ -552,7 +571,7 @@ def create_network(self, network_name: str) -> str: return run(cmd).strip() except subprocess.CalledProcessError as e: raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def delete_network(self, network_name: str) -> None: @@ -566,10 +585,10 @@ def delete_network(self, network_name: str) -> None: raise NoSuchNetwork(network_name=network_name) else: raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e - def inspect_network(self, network_name: str) -> Dict[str, Union[Dict, str]]: + def inspect_network(self, network_name: str) -> dict[str, dict | str]: try: return self._inspect_object(network_name) except NoSuchObject as e: @@ -579,8 +598,8 @@ def connect_container_to_network( self, network_name: str, container_name_or_id: str, - aliases: Optional[List] = None, - link_local_ips: List[str] = None, + aliases: list | None = None, + link_local_ips: list[str] = None, ) -> None: LOG.debug( "Connecting container '%s' to network '%s' with aliases '%s'", @@ -603,7 +622,7 @@ def connect_container_to_network( raise NoSuchNetwork(network_name=network_name) self._check_and_raise_no_such_container_error(container_name_or_id, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def disconnect_container_from_network( @@ -621,7 +640,7 @@ def disconnect_container_from_network( raise NoSuchNetwork(network_name=network_name) self._check_and_raise_no_such_container_error(container_name_or_id, error=e) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def get_container_ip(self, container_name_or_id: str) -> str: @@ -641,10 +660,10 @@ def get_container_ip(self, container_name_or_id: str) -> str: if "no such object" in to_str(e.stdout).lower(): raise NoSuchContainer(container_name_or_id, stdout=e.stdout, stderr=e.stderr) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e - def login(self, username: str, password: str, registry: Optional[str] = None) -> None: + def login(self, username: str, password: str, registry: str | None = None) -> None: cmd = self._docker_cmd() # TODO specify password via stdin cmd += ["login", "-u", username, "-p", password] @@ -654,10 +673,10 @@ def login(self, username: str, password: str, registry: Optional[str] = None) -> run(cmd) except subprocess.CalledProcessError as e: raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e - @functools.lru_cache(maxsize=None) + @functools.cache def has_docker(self) -> bool: try: # do not use self._docker_cmd here (would result in a loop) @@ -667,6 +686,9 @@ def has_docker(self) -> bool: return False def create_container(self, image_name: str, **kwargs) -> str: + # Extract auth_config if provided + auth_config = kwargs.pop("auth_config", None) + self._login_if_needed(auth_config, image_name) image_name = self.registry_resolver_strategy.resolve(image_name) cmd, env_file = self._build_run_create_cmd("create", image_name, **kwargs) LOG.debug("Create container with cmd: %s", cmd) @@ -680,12 +702,14 @@ def create_container(self, image_name: str, **kwargs) -> str: if any(msg in to_str(e.stdout) for msg in error_messages): raise NoSuchImage(image_name, stdout=e.stdout, stderr=e.stderr) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e finally: Util.rm_env_vars_file(env_file) - def run_container(self, image_name: str, stdin=None, **kwargs) -> Tuple[bytes, bytes]: + def run_container(self, image_name: str, stdin=None, **kwargs) -> tuple[bytes, bytes]: + auth_config = kwargs.pop("auth_config", None) + self._login_if_needed(auth_config, image_name) image_name = self.registry_resolver_strategy.resolve(image_name) cmd, env_file = self._build_run_create_cmd("run", image_name, **kwargs) LOG.debug("Run container with cmd: %s", cmd) @@ -701,14 +725,14 @@ def run_container(self, image_name: str, stdin=None, **kwargs) -> Tuple[bytes, b def exec_in_container( self, container_name_or_id: str, - command: Union[List[str], str], + command: list[str] | str, interactive=False, detach=False, - env_vars: Optional[Dict[str, Optional[str]]] = None, - stdin: Optional[bytes] = None, - user: Optional[str] = None, - workdir: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + env_vars: dict[str, str | None] | None = None, + stdin: bytes | None = None, + user: str | None = None, + workdir: str | None = None, + ) -> tuple[bytes, bytes]: env_file = None cmd = self._docker_cmd() cmd.append("exec") @@ -724,7 +748,7 @@ def exec_in_container( env_flag, env_file = Util.create_env_vars_file_flag(env_vars) cmd += env_flag cmd.append(container_name_or_id) - cmd += command if isinstance(command, List) else [command] + cmd += command if isinstance(command, list) else [command] LOG.debug("Execute command in container: %s", cmd) try: return self._run_async_cmd(cmd, stdin, container_name_or_id) @@ -737,8 +761,8 @@ def start_container( stdin=None, interactive: bool = False, attach: bool = False, - flags: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + flags: str | None = None, + ) -> tuple[bytes, bytes]: cmd = self._docker_cmd() + ["start"] if flags: cmd.append(flags) @@ -756,8 +780,8 @@ def attach_to_container(self, container_name_or_id: str): return self._run_async_cmd(cmd, stdin=None, container_name=container_name_or_id) def _run_async_cmd( - self, cmd: List[str], stdin: bytes, container_name: str, image_name=None - ) -> Tuple[bytes, bytes]: + self, cmd: list[str], stdin: bytes, container_name: str, image_name=None + ) -> tuple[bytes, bytes]: kwargs = { "inherit_env": True, "asynchronous": True, @@ -787,7 +811,7 @@ def _run_async_cmd( if any(msg.lower() in to_str(e.stderr).lower() for msg in error_messages): raise NoSuchContainer(container_name, stdout=e.stdout, stderr=e.stderr) raise ContainerException( - "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr + f"Docker process returned with errorcode {e.returncode}", e.stdout, e.stderr ) from e def _build_run_create_cmd( @@ -795,32 +819,34 @@ def _build_run_create_cmd( action: str, image_name: str, *, - name: Optional[str] = None, - entrypoint: Optional[Union[List[str], str]] = None, + name: str | None = None, + entrypoint: list[str] | str | None = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, - ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, - user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, - network: Optional[str] = None, - dns: Optional[Union[str, List[str]]] = None, - additional_flags: Optional[str] = None, - workdir: Optional[str] = None, - privileged: Optional[bool] = None, - labels: Optional[Dict[str, str]] = None, - platform: Optional[DockerPlatform] = None, - ulimits: Optional[List[Ulimit]] = None, - init: Optional[bool] = None, - log_config: Optional[LogConfig] = None, - ) -> Tuple[List[str], str]: + command: list[str] | str | None = None, + volumes: list[VolumeMappingSpecification] | None = None, + ports: PortMappings | None = None, + exposed_ports: list[str] | None = None, + env_vars: dict[str, str] | None = None, + user: str | None = None, + cap_add: list[str] | None = None, + cap_drop: list[str] | None = None, + security_opt: list[str] | None = None, + network: str | None = None, + dns: str | list[str] | None = None, + additional_flags: str | None = None, + workdir: str | None = None, + privileged: bool | None = None, + labels: dict[str, str] | None = None, + platform: DockerPlatform | None = None, + ulimits: list[Ulimit] | None = None, + init: bool | None = None, + log_config: LogConfig | None = None, + cpu_shares: int | None = None, + mem_limit: int | str | None = None, + ) -> tuple[list[str], str]: env_file = None cmd = self._docker_cmd() + [action] if remove: @@ -883,16 +909,20 @@ def _build_run_create_cmd( cmd += ["--log-driver", log_config.type] for key, value in log_config.config.items(): cmd += ["--log-opt", f"{key}={value}"] + if cpu_shares: + cmd += ["--cpu-shares", str(cpu_shares)] + if mem_limit: + cmd += ["--memory", str(mem_limit)] if additional_flags: cmd += shlex.split(additional_flags) cmd.append(image_name) if command: - cmd += command if isinstance(command, List) else [command] + cmd += command if isinstance(command, list) else [command] return cmd, env_file @staticmethod - def _map_to_volume_param(volume: Union[SimpleVolumeBind, BindMount, VolumeDirMount]) -> str: + def _map_to_volume_param(volume: VolumeMappingSpecification) -> str: """ Maps the mount volume, to a parameter for the -v docker cli argument. @@ -903,7 +933,8 @@ def _map_to_volume_param(volume: Union[SimpleVolumeBind, BindMount, VolumeDirMou :param volume: Either a SimpleVolumeBind, in essence a tuple (host_dir, container_dir), or a VolumeBind object :return: String which is passable as parameter to the docker cli -v option """ - if isinstance(volume, (BindMount, VolumeDirMount)): + # TODO: move this logic to the VolumeMappingSpecification type + if isinstance(volume, Mount): return volume.to_str() else: return f"{volume[0]}:{volume[1]}" @@ -915,22 +946,44 @@ def _check_and_raise_no_such_container_error( Check the given client invocation error and raise a `NoSuchContainer` exception if it represents a `no such container` exception from Docker or Podman. """ + self._check_output_and_raise_no_such_container_error( + container_name_or_id, str(error.stdout), error=str(error.stderr) + ) - # consider different error messages for Docker/Podman - error_messages = ("No such container", "no container with name or ID") - process_stdout_lower = to_str(error.stdout).lower() - if any(msg.lower() in process_stdout_lower for msg in error_messages): - raise NoSuchContainer(container_name_or_id, stdout=error.stdout, stderr=error.stderr) + def _check_output_and_raise_no_such_container_error( + self, container_name_or_id: str, output: str, error: str | None = None + ): + """ + Check the given client invocation output and raise a `NoSuchContainer` exception if it + represents a `no such container` exception from Docker or Podman. + """ + possible_not_found_messages = ("No such container", "no container with name or ID") + if any(msg.lower() in output.lower() for msg in possible_not_found_messages): + raise NoSuchContainer(container_name_or_id, stdout=output, stderr=error) - def _transform_container_labels(self, labels: Union[str, Dict[str, str]]) -> Dict[str, str]: + def _transform_container_labels(self, labels: str | dict[str, str]) -> dict[str, str]: """ Transforms the container labels returned by the docker command from the key-value pair format to a dict :param labels: Input string, comma separated key value pairs. Example: key1=value1,key2=value2 :return: Dict representation of the passed values, example: {"key1": "value1", "key2": "value2"} """ - if isinstance(labels, Dict): + if isinstance(labels, dict): return labels labels = labels.split(",") labels = [label.partition("=") for label in labels] return {label[0]: label[2] for label in labels} + + def _login_if_needed(self, auth_config: dict[str, str] | None, image_name) -> None: + if auth_config: + LOG.warning( + "Using global docker login for authentication in docker_cmd_client. " + "This may lead to unexpected behaviors with concurrent requests to different registries. " + "Consider stop using LEGACY_DOCKER_CLIENT for thread-safe authentication." + ) + registry = get_registry_from_image_name(image_name) + self.login( + username=auth_config.get("username", ""), + password=auth_config.get("password", ""), + registry=registry, + ) diff --git a/localstack-core/localstack/utils/container_utils/docker_sdk_client.py b/localstack-core/localstack/utils/container_utils/docker_sdk_client.py index a01761d20d44f..469bb8d400746 100644 --- a/localstack-core/localstack/utils/container_utils/docker_sdk_client.py +++ b/localstack-core/localstack/utils/container_utils/docker_sdk_client.py @@ -6,9 +6,10 @@ import re import socket import threading -from functools import lru_cache +from collections.abc import Callable +from functools import cache from time import sleep -from typing import Callable, Dict, List, Optional, Tuple, Union, cast +from typing import cast from urllib.parse import quote import docker @@ -56,9 +57,9 @@ class SdkDockerClient(ContainerClient): is doing some of the heavy lifting for us to support both target platforms. """ - docker_client: Optional[DockerClient] + docker_client: DockerClient | None - def __init__(self): + def __init__(self) -> None: try: self.docker_client = self._create_client() logging.getLogger("urllib3").setLevel(logging.INFO) @@ -264,21 +265,23 @@ def unpause_container(self, container_name: str) -> None: except APIError as e: raise ContainerException() from e - def remove_container(self, container_name: str, force=True, check_existence=False) -> None: - LOG.debug("Removing container: %s", container_name) - if check_existence and container_name not in self.get_running_container_names(): + def remove_container( + self, container_name: str, force=True, check_existence=False, volumes=False + ) -> None: + LOG.debug("Removing container: %s, with volumes: %s", container_name, volumes) + if check_existence and container_name not in self.get_all_container_names(): LOG.debug("Aborting removing due to check_existence check") return try: container = self.client().containers.get(container_name) - container.remove(force=force) + container.remove(force=force, v=volumes) except NotFound: if not force: raise NoSuchContainer(container_name) except APIError as e: raise ContainerException() from e - def list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: + def list_containers(self, filter: list[str] | str | None = None, all=True) -> list[dict]: if filter: filter = [filter] if isinstance(filter, str) else filter filter = dict([f.split("=", 1) for f in filter]) @@ -337,14 +340,17 @@ def copy_from_container( def pull_image( self, docker_image: str, - platform: Optional[DockerPlatform] = None, - log_handler: Optional[Callable[[str], None]] = None, + platform: DockerPlatform | None = None, + log_handler: Callable[[str], None] | None = None, + auth_config: dict[str, str] | None = None, ) -> None: LOG.debug("Pulling Docker image: %s", docker_image) # some path in the docker image string indicates a custom repository docker_image = self.registry_resolver_strategy.resolve(docker_image) - kwargs: Dict[str, Union[str, bool]] = {"platform": platform} + kwargs: dict[str, str | bool | dict[str, str]] = {"platform": platform} + if auth_config: + kwargs["auth_config"] = auth_config try: if log_handler: # Use a lower-level API, as the 'stream' argument is not available in the higher-level `pull`-API @@ -359,10 +365,13 @@ def pull_image( except APIError as e: raise ContainerException() from e - def push_image(self, docker_image: str) -> None: + def push_image(self, docker_image: str, auth_config: dict[str, str] | None = None) -> None: LOG.debug("Pushing Docker image: %s", docker_image) + kwargs: dict[str, dict[str, str]] = {} + if auth_config: + kwargs["auth_config"] = auth_config try: - result = self.client().images.push(docker_image) + result = self.client().images.push(docker_image, **kwargs) # some SDK clients (e.g., 5.0.0) seem to return an error string, instead of raising if isinstance(result, (str, bytes)) and '"errorDetail"' in to_str(result): if "image does not exist locally" in to_str(result): @@ -373,8 +382,20 @@ def push_image(self, docker_image: str) -> None: raise AccessDenied(docker_image) if "access token has insufficient scopes" in to_str(result): raise AccessDenied(docker_image) + if "authorization failed: no basic auth credentials" in to_str(result): + raise AccessDenied(docker_image) + if "401 Unauthorized" in to_str(result): + raise AccessDenied(docker_image) + if "no basic auth credentials" in to_str(result): + raise AccessDenied(docker_image) + if "unauthorized: authentication required" in to_str(result): + raise AccessDenied(docker_image) + if "insufficient_scope: authorization failed" in to_str(result): + raise AccessDenied(docker_image) if "connection refused" in to_str(result): raise RegistryConnectionError(result) + if "failed to do request:" in to_str(result): + raise RegistryConnectionError(result) raise ContainerException(result) except ImageNotFound: raise NoSuchImage(docker_image) @@ -389,7 +410,7 @@ def build_image( dockerfile_path: str, image_name: str, context_path: str = None, - platform: Optional[DockerPlatform] = None, + platform: DockerPlatform | None = None, ): try: dockerfile_path = Util.resolve_dockerfile_path(dockerfile_path) @@ -466,7 +487,7 @@ def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: except APIError as e: raise ContainerException() from e - def inspect_container(self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: + def inspect_container(self, container_name_or_id: str) -> dict[str, dict | str]: try: return self.client().containers.get(container_name_or_id).attrs except NotFound: @@ -479,7 +500,7 @@ def inspect_image( image_name: str, pull: bool = True, strip_wellknown_repo_prefixes: bool = True, - ) -> Dict[str, Union[dict, list, str]]: + ) -> dict[str, dict | list | str]: image_name = self.registry_resolver_strategy.resolve(image_name) try: result = self.client().images.get(image_name).attrs @@ -513,7 +534,7 @@ def delete_network(self, network_name: str) -> None: except APIError as e: raise ContainerException() from e - def inspect_network(self, network_name: str) -> Dict[str, Union[Dict, str]]: + def inspect_network(self, network_name: str) -> dict[str, dict | str]: try: return self.client().networks.get(network_name).attrs except NotFound: @@ -525,8 +546,8 @@ def connect_container_to_network( self, network_name: str, container_name_or_id: str, - aliases: Optional[List] = None, - link_local_ips: List[str] = None, + aliases: list | None = None, + link_local_ips: list[str] = None, ) -> None: LOG.debug( "Connecting container '%s' to network '%s' with aliases '%s'", @@ -574,7 +595,7 @@ def get_container_ip(self, container_name_or_id: str) -> str: LOG.info("Container has more than one assigned network. Picking the first one...") return networks[network_names[0]]["IPAddress"] - @lru_cache(maxsize=None) + @cache def has_docker(self) -> bool: try: if not self.docker_client: @@ -619,8 +640,8 @@ def start_container( stdin=None, interactive: bool = False, attach: bool = False, - flags: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + flags: str | None = None, + ) -> tuple[bytes, bytes]: LOG.debug("Starting container %s", container_name_or_id) try: container = self.client().containers.get(container_name_or_id) @@ -668,7 +689,7 @@ def wait_for_result(*_): sock.sendall(to_bytes(stdin)) sock.shutdown(socket.SHUT_WR) stdout, stderr = self._read_from_sock(sock, False) - except socket.timeout: + except TimeoutError: LOG.debug( "Socket timeout when talking to the I/O streams of Docker container '%s'", container_name_or_id, @@ -701,31 +722,34 @@ def create_container( self, image_name: str, *, - name: Optional[str] = None, - entrypoint: Optional[Union[List[str], str]] = None, + name: str | None = None, + entrypoint: list[str] | str | None = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, - ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, - user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, - network: Optional[str] = None, - dns: Optional[Union[str, List[str]]] = None, - additional_flags: Optional[str] = None, - workdir: Optional[str] = None, - privileged: Optional[bool] = None, - labels: Optional[Dict[str, str]] = None, - platform: Optional[DockerPlatform] = None, - ulimits: Optional[List[Ulimit]] = None, - init: Optional[bool] = None, - log_config: Optional[LogConfig] = None, + command: list[str] | str | None = None, + volumes: list[SimpleVolumeBind] | None = None, + ports: PortMappings | None = None, + exposed_ports: list[str] | None = None, + env_vars: dict[str, str] | None = None, + user: str | None = None, + cap_add: list[str] | None = None, + cap_drop: list[str] | None = None, + security_opt: list[str] | None = None, + network: str | None = None, + dns: str | list[str] | None = None, + additional_flags: str | None = None, + workdir: str | None = None, + privileged: bool | None = None, + labels: dict[str, str] | None = None, + platform: DockerPlatform | None = None, + ulimits: list[Ulimit] | None = None, + init: bool | None = None, + log_config: LogConfig | None = None, + cpu_shares: int | None = None, + mem_limit: int | str | None = None, + auth_config: dict[str, str] | None = None, ) -> str: LOG.debug("Creating container with attributes: %s", locals()) extra_hosts = None @@ -790,6 +814,10 @@ def create_container( ) for ulimit in ulimits ] + if cpu_shares: + kwargs["cpu_shares"] = cpu_shares + if mem_limit: + kwargs["mem_limit"] = mem_limit mounts = None if volumes: mounts = Util.convert_mount_list_to_dict(volumes) @@ -819,7 +847,7 @@ def create_container(): container = create_container() except ImageNotFound: LOG.debug("Image not found. Pulling image %s", image_name) - self.pull_image(image_name, platform) + self.pull_image(image_name, platform, auth_config=auth_config) container = create_container() return container.id except ImageNotFound: @@ -832,32 +860,35 @@ def run_container( image_name: str, stdin=None, *, - name: Optional[str] = None, - entrypoint: Optional[str] = None, + name: str | None = None, + entrypoint: str | None = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, - ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, - user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, - network: Optional[str] = None, - dns: Optional[str] = None, - additional_flags: Optional[str] = None, - workdir: Optional[str] = None, - labels: Optional[Dict[str, str]] = None, - platform: Optional[DockerPlatform] = None, - privileged: Optional[bool] = None, - ulimits: Optional[List[Ulimit]] = None, - init: Optional[bool] = None, - log_config: Optional[LogConfig] = None, - ) -> Tuple[bytes, bytes]: + command: list[str] | str | None = None, + volumes: list[SimpleVolumeBind] | None = None, + ports: PortMappings | None = None, + exposed_ports: list[str] | None = None, + env_vars: dict[str, str] | None = None, + user: str | None = None, + cap_add: list[str] | None = None, + cap_drop: list[str] | None = None, + security_opt: list[str] | None = None, + network: str | None = None, + dns: str | None = None, + additional_flags: str | None = None, + workdir: str | None = None, + labels: dict[str, str] | None = None, + platform: DockerPlatform | None = None, + privileged: bool | None = None, + ulimits: list[Ulimit] | None = None, + init: bool | None = None, + log_config: LogConfig | None = None, + cpu_shares: int | None = None, + mem_limit: int | str | None = None, + auth_config: dict[str, str] | None = None, + ) -> tuple[bytes, bytes]: LOG.debug("Running container with image: %s", image_name) container = None try: @@ -888,6 +919,9 @@ def run_container( labels=labels, ulimits=ulimits, log_config=log_config, + cpu_shares=cpu_shares, + mem_limit=mem_limit, + auth_config=auth_config, ) result = self.start_container( container_name_or_id=container, @@ -903,14 +937,14 @@ def run_container( def exec_in_container( self, container_name_or_id: str, - command: Union[List[str], str], + command: list[str] | str, interactive=False, detach=False, - env_vars: Optional[Dict[str, Optional[str]]] = None, - stdin: Optional[bytes] = None, - user: Optional[str] = None, - workdir: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + env_vars: dict[str, str | None] | None = None, + stdin: bytes | None = None, + user: str | None = None, + workdir: str | None = None, + ) -> tuple[bytes, bytes]: LOG.debug("Executing command in container %s: %s", container_name_or_id, command) try: container: Container = self.client().containers.get(container_name_or_id) @@ -936,7 +970,7 @@ def exec_in_container( sock.shutdown(socket.SHUT_WR) stdout, stderr = self._read_from_sock(sock, tty) return stdout, stderr - except socket.timeout: + except TimeoutError: pass else: if detach: @@ -957,7 +991,7 @@ def exec_in_container( except APIError as e: raise ContainerException() from e - def login(self, username: str, password: str, registry: Optional[str] = None) -> None: + def login(self, username: str, password: str, registry: str | None = None) -> None: LOG.debug("Docker login for %s", username) try: self.client().login(username, password=password, registry=registry, reauth=True) diff --git a/localstack-core/localstack/utils/coverage_docs.py b/localstack-core/localstack/utils/coverage_docs.py deleted file mode 100644 index fde4628a32f67..0000000000000 --- a/localstack-core/localstack/utils/coverage_docs.py +++ /dev/null @@ -1,20 +0,0 @@ -_COVERAGE_LINK_BASE = "https://docs.localstack.cloud/references/coverage" - - -def get_coverage_link_for_service(service_name: str, action_name: str) -> str: - from localstack.services.plugins import SERVICE_PLUGINS - - available_services = SERVICE_PLUGINS.list_available() - - if service_name not in available_services: - return ( - f"The API for service '{service_name}' is either not included in your current license plan " - "or has not yet been emulated by LocalStack. " - f"Please refer to {_COVERAGE_LINK_BASE} for more details." - ) - else: - return ( - f"The API action '{action_name}' for service '{service_name}' is either not available in " - "your current license plan or has not yet been emulated by LocalStack. " - f"Please refer to {_COVERAGE_LINK_BASE}/coverage_{service_name} for more information." - ) diff --git a/localstack-core/localstack/utils/crypto.py b/localstack-core/localstack/utils/crypto.py index bd7150d96b871..70de3cec0da2b 100644 --- a/localstack-core/localstack/utils/crypto.py +++ b/localstack-core/localstack/utils/crypto.py @@ -3,9 +3,14 @@ import os import re import threading -from typing import Tuple +from asn1crypto import algos, cms, core +from asn1crypto import x509 as asn1_x509 from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import padding as sym_padding +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from .files import TMP_FILES, file_exists_not_empty, load_file, new_tmp_file, save_file @@ -27,6 +32,11 @@ PEM_KEY_START_REGEX = r"-----BEGIN(.*)PRIVATE KEY-----" PEM_KEY_END_REGEX = r"-----END(.*)PRIVATE KEY-----" +OID_AES256_CBC = "2.16.840.1.101.3.4.1.42" +OID_MGF1 = "1.2.840.113549.1.1.8" +OID_RSAES_OAEP = "1.2.840.113549.1.1.7" +OID_SHA256 = "2.16.840.1.101.3.4.2.1" + @synchronized(lock=SSL_CERT_LOCK) def generate_ssl_cert( @@ -44,8 +54,8 @@ def all_exist(*files): return all(os.path.exists(f) for f in files) def store_cert_key_files(base_filename): - key_file_name = "%s.key" % base_filename - cert_file_name = "%s.crt" % base_filename + key_file_name = f"{base_filename}.key" + cert_file_name = f"{base_filename}.crt" # TODO: Cleaner code to load the cert dynamically # extract key and cert from target_file and store into separate files content = load_file(target_file) @@ -75,9 +85,9 @@ def store_cert_key_files(base_filename): return target_file, cert_file_name, key_file_name if random and target_file: if "." in target_file: - target_file = target_file.replace(".", ".%s." % short_uid(), 1) + target_file = target_file.replace(".", f".{short_uid()}.", 1) else: - target_file = "%s.%s" % (target_file, short_uid()) + target_file = f"{target_file}.{short_uid()}" # create a key pair k = crypto.PKey() @@ -105,7 +115,7 @@ def store_cert_key_files(base_filename): cert.set_pubkey(k) alt_names = ( f"DNS:localhost,DNS:test.localhost.atlassian.io,DNS:localhost.localstack.cloud,DNS:{host_definition.host}IP:127.0.0.1" - ).encode("utf8") + ).encode() cert.add_extensions( [ crypto.X509Extension(b"subjectAltName", False, alt_names), @@ -124,10 +134,10 @@ def store_cert_key_files(base_filename): key_file.write(to_str(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))) cert_file_content = cert_file.getvalue().strip() key_file_content = key_file.getvalue().strip() - file_content = "%s\n%s" % (key_file_content, cert_file_content) + file_content = f"{key_file_content}\n{cert_file_content}" if target_file: - key_file_name = "%s.key" % target_file - cert_file_name = "%s.crt" % target_file + key_file_name = f"{target_file}.key" + cert_file_name = f"{target_file}.crt" # check existence to avoid permission denied issues: # https://github.com/localstack/localstack/issues/1607 if not all_exist(target_file, key_file_name, cert_file_name): @@ -146,9 +156,9 @@ def store_cert_key_files(base_filename): e, ) # Fix for https://github.com/localstack/localstack/issues/1743 - target_file = "%s.pem" % new_tmp_file() - key_file_name = "%s.key" % target_file - cert_file_name = "%s.crt" % target_file + target_file = f"{new_tmp_file()}.pem" + key_file_name = f"{target_file}.key" + cert_file_name = f"{target_file}.crt" TMP_FILES.append(target_file) TMP_FILES.append(key_file_name) TMP_FILES.append(cert_file_name) @@ -165,7 +175,7 @@ def unpad(s: bytes) -> bytes: return s[0 : -s[-1]] -def encrypt(key: bytes, message: bytes, iv: bytes = None, aad: bytes = None) -> Tuple[bytes, bytes]: +def encrypt(key: bytes, message: bytes, iv: bytes = None, aad: bytes = None) -> tuple[bytes, bytes]: iv = iv or b"0" * BLOCK_SIZE cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend()) encryptor = cipher.encryptor() @@ -184,3 +194,101 @@ def decrypt( decrypted = decryptor.update(encrypted) + decryptor.finalize() decrypted = unpad(decrypted) return decrypted + + +def pkcs7_envelope_encrypt(plaintext: bytes, recipient_pubkey: RSAPublicKey) -> bytes: + """ + Create a PKCS7 wrapper of some plaintext decryptable by recipient_pubkey. Uses RSA-OAEP with SHA-256 + to encrypt the AES-256-CBC content key. Hazmat's PKCS7EnvelopeBuilder doesn't support RSA-OAEP with SHA-256, + so we need to build the pieces manually and then put them together in an envelope with asn1crypto. + """ + + # Encrypt the plaintext with an AES session key, then encrypt the session key to the recipient_pubkey + session_key = os.urandom(32) + iv = os.urandom(16) + encrypted_session_key = recipient_pubkey.encrypt( + session_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None + ), + ) + cipher = Cipher(algorithms.AES(session_key), modes.CBC(iv), backend=default_backend()) + encryptor = cipher.encryptor() + padder = sym_padding.PKCS7(algorithms.AES.block_size).padder() + padded_plaintext = padder.update(plaintext) + padder.finalize() + encrypted_content = encryptor.update(padded_plaintext) + encryptor.finalize() + + # Now put together the envelope. + # Add the recipient with their copy of the session key + recipient_identifier = cms.RecipientIdentifier( + name="issuer_and_serial_number", + value=cms.IssuerAndSerialNumber( + { + "issuer": asn1_x509.Name.build({"common_name": "recipient"}), + "serial_number": 1, + } + ), + ) + key_enc_algorithm = cms.KeyEncryptionAlgorithm( + { + "algorithm": OID_RSAES_OAEP, + "parameters": algos.RSAESOAEPParams( + { + "hash_algorithm": algos.DigestAlgorithm( + { + "algorithm": OID_SHA256, + } + ), + "mask_gen_algorithm": algos.MaskGenAlgorithm( + { + "algorithm": OID_MGF1, + "parameters": algos.DigestAlgorithm( + { + "algorithm": OID_SHA256, + } + ), + } + ), + } + ), + } + ) + recipient_info = cms.KeyTransRecipientInfo( + { + "version": "v0", + "rid": recipient_identifier, + "key_encryption_algorithm": key_enc_algorithm, + "encrypted_key": encrypted_session_key, + } + ) + + # Add the encrypted content + content_enc_algorithm = cms.EncryptionAlgorithm( + { + "algorithm": OID_AES256_CBC, + "parameters": core.OctetString(iv), + } + ) + encrypted_content_info = cms.EncryptedContentInfo( + { + "content_type": "data", + "content_encryption_algorithm": content_enc_algorithm, + "encrypted_content": encrypted_content, + } + ) + enveloped_data = cms.EnvelopedData( + { + "version": "v0", + "recipient_infos": [recipient_info], + "encrypted_content_info": encrypted_content_info, + } + ) + + # Finally add a wrapper and return its bytes + content_info = cms.ContentInfo( + { + "content_type": "enveloped_data", + "content": enveloped_data, + } + ) + return content_info.dump() diff --git a/localstack-core/localstack/utils/diagnose.py b/localstack-core/localstack/utils/diagnose.py index 36b0b079631f9..c3c1bfc837ff4 100644 --- a/localstack-core/localstack/utils/diagnose.py +++ b/localstack-core/localstack/utils/diagnose.py @@ -3,7 +3,6 @@ import inspect import os import socket -from typing import Dict, List, Optional, Union from localstack import config from localstack.constants import DEFAULT_VOLUME_DIR @@ -41,7 +40,7 @@ INSPECT_DIRECTORIES = [DEFAULT_VOLUME_DIR, "/tmp"] -def get_localstack_logs() -> Dict: +def get_localstack_logs() -> dict: try: result = DOCKER_CLIENT.get_container_logs(get_main_container_name()) except Exception as e: @@ -50,7 +49,7 @@ def get_localstack_logs() -> Dict: return {"docker": result} -def get_localstack_config() -> Dict: +def get_localstack_config() -> dict: result = {} for k, v in inspect.getmembers(config): if k in EXCLUDE_CONFIG_KEYS: @@ -77,14 +76,14 @@ def get_localstack_config() -> Dict: return result -def inspect_main_container() -> Union[str, Dict]: +def inspect_main_container() -> str | dict: try: return DOCKER_CLIENT.inspect_container(get_main_container_name()) except Exception as e: return f"inspect failed: {e}" -def get_localstack_version() -> Dict[str, Optional[str]]: +def get_localstack_version() -> dict[str, str | None]: return { "build-date": os.environ.get("LOCALSTACK_BUILD_DATE"), "build-git-hash": os.environ.get("LOCALSTACK_BUILD_GIT_HASH"), @@ -92,7 +91,7 @@ def get_localstack_version() -> Dict[str, Optional[str]]: } -def resolve_endpoints() -> Dict[str, str]: +def resolve_endpoints() -> dict[str, str]: result = {} for endpoint in ENDPOINT_RESOLVE_LIST: try: @@ -103,7 +102,7 @@ def resolve_endpoints() -> Dict[str, str]: return result -def get_important_image_hashes() -> Dict[str, str]: +def get_important_image_hashes() -> dict[str, str]: result = {} for image in DIAGNOSE_IMAGES: try: @@ -116,17 +115,17 @@ def get_important_image_hashes() -> Dict[str, str]: return result -def get_service_stats() -> Dict[str, str]: +def get_service_stats() -> dict[str, str]: from localstack.services.plugins import SERVICE_PLUGINS return {service: state.value for service, state in SERVICE_PLUGINS.get_states().items()} -def get_file_tree() -> Dict[str, List[str]]: +def get_file_tree() -> dict[str, list[str]]: return {d: traverse_file_tree(d) for d in INSPECT_DIRECTORIES} -def traverse_file_tree(root: str) -> List[str]: +def traverse_file_tree(root: str) -> list[str]: try: result = [] if config.in_docker(): @@ -134,10 +133,10 @@ def traverse_file_tree(root: str) -> List[str]: result.append(dirpath) return result except Exception as e: - return ["traversing files failed %s" % e] + return [f"traversing files failed {e}"] -def get_docker_image_details() -> Dict[str, str]: +def get_docker_image_details() -> dict[str, str]: try: image = DOCKER_CLIENT.inspect_container(get_main_container_name())["Config"]["Image"] except ContainerException: diff --git a/localstack-core/localstack/utils/docker_utils.py b/localstack-core/localstack/utils/docker_utils.py index 9ff5f57134ca6..8d73c2839c031 100644 --- a/localstack-core/localstack/utils/docker_utils.py +++ b/localstack-core/localstack/utils/docker_utils.py @@ -2,13 +2,13 @@ import logging import platform import random -from typing import List, Optional, Union from localstack import config from localstack.constants import DEFAULT_VOLUME_DIR, DOCKER_IMAGE_NAME from localstack.utils.collections import ensure_list from localstack.utils.container_utils.container_client import ( ContainerClient, + DockerNotAvailable, PortMappings, VolumeInfo, ) @@ -74,12 +74,12 @@ def get_current_container_id() -> str: return container_id -def inspect_current_container_mounts() -> List[VolumeInfo]: +def inspect_current_container_mounts() -> list[VolumeInfo]: return DOCKER_CLIENT.inspect_container_volumes(get_current_container_id()) -@functools.lru_cache() -def get_default_volume_dir_mount() -> Optional[VolumeInfo]: +@functools.lru_cache +def get_default_volume_dir_mount() -> VolumeInfo | None: """ Returns the volume information of LocalStack's DEFAULT_VOLUME_DIR (/var/lib/localstack), if mounted, else it returns None. If we're not currently in docker a VauleError is raised. in a container, a ValueError is @@ -94,7 +94,7 @@ def get_default_volume_dir_mount() -> Optional[VolumeInfo]: return None -def get_host_path_for_path_in_docker(path): +def get_host_path_for_path_in_docker(path: str) -> str: """ Returns the calculated host location for a given subpath of DEFAULT_VOLUME_DIR inside the localstack container. The path **has** to be a subdirectory of DEFAULT_VOLUME_DIR (the dir itself *will not* work). @@ -132,8 +132,8 @@ def get_host_path_for_path_in_docker(path): def container_ports_can_be_bound( - ports: Union[IntOrPort, List[IntOrPort]], - address: Optional[str] = None, + ports: IntOrPort | list[IntOrPort], + address: str | None = None, ) -> bool: """Determine whether a given list of ports can be bound by Docker containers @@ -141,8 +141,7 @@ def container_ports_can_be_bound( :return: True iff all ports can be bound """ port_mappings = PortMappings(bind_host=address or "") - ports = ensure_list(ports) - for port in ports: + for port in ensure_list(ports): port = Port.wrap(port) port_mappings.add(port.port, port.port, protocol=port.protocol) try: @@ -153,10 +152,14 @@ def container_ports_can_be_bound( ports=port_mappings, remove=True, ) + except DockerNotAvailable as e: + LOG.warning("Cannot perform port check because Docker is not available.") + raise e except Exception as e: if "port is already allocated" not in str(e) and "address already in use" not in str(e): LOG.warning( - "Unexpected error when attempting to determine container port status", exc_info=e + "Unexpected error when attempting to determine container port status", + exc_info=LOG.isEnabledFor(logging.DEBUG), ) return False # TODO(srw): sometimes the command output from the docker container is "None", particularly when this function is @@ -185,7 +188,7 @@ def is_port_available_for_containers(port: IntOrPort) -> bool: return not is_container_port_reserved(port) and container_ports_can_be_bound(port) -def reserve_container_port(port: IntOrPort, duration: int = None): +def reserve_container_port(port: IntOrPort, duration: int | None = None) -> None: """Reserve the given container port for a short period of time""" reserved_docker_ports.reserve_port(port, duration=duration) @@ -197,10 +200,10 @@ def is_container_port_reserved(port: IntOrPort) -> bool: def reserve_available_container_port( - duration: int = None, - port_start: int = None, - port_end: int = None, - protocol: str = None, + duration: int | None = None, + port_start: int | None = None, + port_end: int | None = None, + protocol: str | None = None, ) -> int: """ Determine a free port within the given port range that can be bound by a Docker container, and reserve @@ -216,7 +219,7 @@ def reserve_available_container_port( protocol = protocol or "tcp" - def _random_port(): + def _random_port() -> Port: port = None while not port or reserved_docker_ports.is_port_reserved(port): port_number = random.randint( @@ -259,7 +262,7 @@ def _get_ports_check_docker_image() -> str: try: # inspect the running container to determine the image container = DOCKER_CLIENT.inspect_container(get_current_container_id()) - return container["Config"]["Image"] + return container["Config"]["Image"] # type: ignore[index] except Exception: # fall back to using the default Docker image return DOCKER_IMAGE_NAME diff --git a/localstack-core/localstack/utils/files.py b/localstack-core/localstack/utils/files.py index 7b71e26ca8664..d510de503886f 100644 --- a/localstack-core/localstack/utils/files.py +++ b/localstack-core/localstack/utils/files.py @@ -1,25 +1,36 @@ import configparser -import inspect import logging import os import shutil import stat import tempfile from pathlib import Path -from typing import Dict +from typing import Any, AnyStr, Literal, overload LOG = logging.getLogger(__name__) -TMP_FILES = [] +TMP_FILES: list[str] = [] -def parse_config_file(file_or_str: str, single_section: bool = True) -> Dict: +@overload +def parse_config_file(file_or_str: str, single_section: Literal[True]) -> dict[str, str]: ... + + +@overload +def parse_config_file( + file_or_str: str, single_section: Literal[False] +) -> dict[str, dict[str, str]]: ... + + +def parse_config_file( + file_or_str: str, single_section: bool = True +) -> dict[str, str] | dict[str, dict[str, str]]: """Parse the given properties config file/string and return a dict of section->key->value. If the config contains a single section, and 'single_section' is True, returns""" config = configparser.RawConfigParser() if os.path.exists(file_or_str): - file_or_str = load_file(file_or_str) + file_or_str = load_file(file_or_str) # type: ignore[assignment] try: config.read_string(file_or_str) @@ -31,7 +42,7 @@ def parse_config_file(file_or_str: str, single_section: bool = True) -> Dict: result = {sec: dict(config.items(sec)) for sec in sections} if len(sections) == 1 and single_section: - result = result[sections[0]] + return result[sections[0]] return result @@ -65,13 +76,18 @@ def cache_dir() -> Path: return get_user_cache_dir() / "localstack" -def save_file(file, content, append=False, permissions=None): +def save_file( + file: str | os.PathLike[AnyStr], + content: str | bytes, + append: bool = False, + permissions: int | None = None, +) -> None: mode = "a" if append else "w+" if not isinstance(content, str): mode = mode + "b" - def _opener(path, flags): - return os.open(path, flags, permissions) + def _opener(path: str, flags: int) -> int: + return os.open(path, flags, permissions) # type: ignore[arg-type] # make sure that the parent dir exists mkdir(os.path.dirname(file)) @@ -81,9 +97,53 @@ def _opener(path, flags): f.flush() -def load_file(file_path: str, default=None, mode=None): +@overload +def load_file( + file_path: str | os.PathLike[AnyStr], + default: None = None, + mode: None = None, + strict: bool = False, +) -> str | None: ... + + +@overload +def load_file( + file_path: str | os.PathLike[AnyStr], + default: str, + mode: None = None, + strict: bool = False, +) -> str | None: ... + + +@overload +def load_file( + file_path: str | os.PathLike[AnyStr], + default: bytes, + mode: Literal["rb"], + strict: bool = False, +) -> bytes | None: ... + + +def load_file( + file_path: str | os.PathLike[AnyStr], + default: str | bytes | None = None, + mode: str | None = None, + strict: bool = False, +) -> str | bytes | None: + """ + Return file contents + + :param file_path: path of the file + :param default: if strict=False then return this value if the file does not exist + :param mode: mode to open the file with (e.g. `r`, `rw`) + :param strict: raise an error if the file path is not a file + :return: the file contents + """ if not os.path.isfile(file_path): - return default + if strict: + raise FileNotFoundError(file_path) + else: + return default if not mode: mode = "r" with open(file_path, mode) as f: @@ -91,7 +151,11 @@ def load_file(file_path: str, default=None, mode=None): return result -def get_or_create_file(file_path, content=None, permissions=None): +def get_or_create_file( + file_path: os.PathLike[AnyStr], + content: str | bytes | None = None, + permissions: int | None = None, +) -> str | bytes | None: if os.path.exists(file_path): return load_file(file_path) content = "{}" if content is None else content @@ -100,9 +164,10 @@ def get_or_create_file(file_path, content=None, permissions=None): return content except Exception: pass + return None -def replace_in_file(search, replace, file_path): +def replace_in_file(search: str, replace: str, file_path: os.PathLike[AnyStr]) -> None: """Replace all occurrences of `search` with `replace` in the given file (overwrites in place!)""" content = load_file(file_path) or "" content_new = content.replace(search, replace) @@ -110,7 +175,7 @@ def replace_in_file(search, replace, file_path): save_file(file_path, content_new) -def mkdir(folder: str): +def mkdir(folder: str | bytes | os.PathLike[AnyStr]) -> None: if not os.path.exists(folder): os.makedirs(folder, exist_ok=True) @@ -126,7 +191,7 @@ def is_empty_dir(directory: str, ignore_hidden: bool = False) -> bool: return not bool(entries) -def ensure_readable(file_path: str, default_perms: int = None): +def ensure_readable(file_path: str, default_perms: int | None = None) -> None: if default_perms is None: default_perms = 0o644 try: @@ -137,7 +202,7 @@ def ensure_readable(file_path: str, default_perms: int = None): os.chmod(file_path, default_perms) -def chown_r(path: str, user: str): +def chown_r(path: str, user: str) -> None: """Recursive chown on the given file/directory path.""" # keep these imports here for Windows compatibility import grp @@ -153,7 +218,7 @@ def chown_r(path: str, user: str): os.chown(os.path.join(root, filename), uid, gid) -def chmod_r(path: str, mode: int): +def chmod_r(path: str, mode: int) -> None: """ Recursive chmod :param path: path to file or directory @@ -169,7 +234,7 @@ def chmod_r(path: str, mode: int): idempotent_chmod(os.path.join(root, filename), mode) -def idempotent_chmod(path: str, mode: int): +def idempotent_chmod(path: str, mode: int) -> None: """ Perform idempotent chmod on the given file path (non-recursively). The function attempts to call `os.chmod`, and will catch and only re-raise exceptions (e.g., PermissionError) if the file does not have the given mode already. @@ -179,14 +244,18 @@ def idempotent_chmod(path: str, mode: int): try: os.chmod(path, mode) except Exception: - existing_mode = os.stat(path) + try: + existing_mode = os.stat(path) + except FileNotFoundError: + # file deleted in the meantime, or otherwise not accessible (socket) + return if mode in (existing_mode.st_mode, stat.S_IMODE(existing_mode.st_mode)): # file already has the desired permissions -> return return raise -def rm_rf(path: str): +def rm_rf(path: str) -> None: """ Recursively removes a file or directory """ @@ -198,7 +267,7 @@ def rm_rf(path: str): # Running the native command can be an order of magnitude faster in Alpine on Travis-CI if is_debian(): try: - return run('rm -rf "%s"' % path) + return run(f'rm -rf "{path}"') # type: ignore[return-value] except Exception: pass # Make sure all files are writeable and dirs executable to remove @@ -214,13 +283,15 @@ def rm_rf(path: str): shutil.rmtree(path) -def cp_r(src: str, dst: str, rm_dest_on_conflict=False, ignore_copystat_errors=False, **kwargs): +def cp_r( + src: str, dst: str, rm_dest_on_conflict: bool = False, ignore_copystat_errors: bool = False +) -> None | str: """Recursively copies file/directory""" # attention: this patch is not threadsafe copystat_orig = shutil.copystat if ignore_copystat_errors: - def _copystat(*args, **kwargs): + def _copystat(*args: Any, **kwargs: Any) -> None: try: return copystat_orig(*args, **kwargs) except Exception: @@ -232,25 +303,19 @@ def _copystat(*args, **kwargs): if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) return shutil.copyfile(src, dst) - if "dirs_exist_ok" in inspect.getfullargspec(shutil.copytree).args: - kwargs["dirs_exist_ok"] = True try: - return shutil.copytree(src, dst, **kwargs) + return shutil.copytree(src, dst, dirs_exist_ok=True) except FileExistsError: if rm_dest_on_conflict: rm_rf(dst) - return shutil.copytree(src, dst, **kwargs) + return shutil.copytree(src, dst, dirs_exist_ok=True) raise except Exception as e: - def _info(_path): - return "%s (file=%s, symlink=%s)" % ( - _path, - os.path.isfile(_path), - os.path.islink(_path), - ) + def _info(_path: str) -> str: + return f"{_path} (file={os.path.isfile(_path)}, symlink={os.path.islink(_path)})" - LOG.debug("Error copying files from %s to %s: %s", _info(src), _info(dst), e) + LOG.debug("Error copying files from %s to %s", _info(src), _info(dst), e) raise finally: shutil.copystat = copystat_orig @@ -277,10 +342,10 @@ def disk_usage(path: str) -> int: def file_exists_not_empty(path: str) -> bool: """Return whether the given file or directory exists and is non-empty (i.e., >0 bytes content)""" - return path and disk_usage(path) > 0 + return bool(path) and disk_usage(path) > 0 -def cleanup_tmp_files(): +def cleanup_tmp_files() -> None: for tmp in TMP_FILES: try: rm_rf(tmp) @@ -289,7 +354,7 @@ def cleanup_tmp_files(): del TMP_FILES[:] -def new_tmp_file(suffix: str = None, dir: str = None) -> str: +def new_tmp_file(suffix: str | None = None, dir: str | None = None) -> str: """Return a path to a new temporary file.""" tmp_file, tmp_path = tempfile.mkstemp(suffix=suffix, dir=dir) os.close(tmp_file) @@ -297,8 +362,15 @@ def new_tmp_file(suffix: str = None, dir: str = None) -> str: return tmp_path -def new_tmp_dir(dir: str = None): - folder = new_tmp_file(dir=dir) - rm_rf(folder) - mkdir(folder) +def new_tmp_dir(dir: str | None = None, mode: int = 0o777) -> str: + """ + Create a new temporary directory with the specified permissions. The directory is added to the tracked temporary + files. + :param dir: parent directory for the temporary directory to be created. Systems's default otherwise. + :param mode: file permission for the directory (default: 0o777) + :return: the absolute path of the created directory + """ + folder = tempfile.mkdtemp(dir=dir) + TMP_FILES.append(folder) + idempotent_chmod(folder, mode=mode) return folder diff --git a/localstack-core/localstack/utils/functions.py b/localstack-core/localstack/utils/functions.py index 0640a84fea2a0..b3829306c1521 100644 --- a/localstack-core/localstack/utils/functions.py +++ b/localstack-core/localstack/utils/functions.py @@ -3,7 +3,8 @@ import functools import inspect import logging -from typing import Any, Callable, Dict, Optional, Tuple +from collections.abc import Callable +from typing import Any LOG = logging.getLogger(__name__) @@ -19,8 +20,8 @@ def run_safe(_python_lambda, *args, _default=None, **kwargs): def call_safe( - func: Callable, args: Tuple = None, kwargs: Dict = None, exception_message: str = None -) -> Optional[Any]: + func: Callable, args: tuple = None, kwargs: dict = None, exception_message: str = None +) -> Any | None: """ Call the given function with the given arguments, and if it fails, log the given exception_message. If logging.DEBUG is set for the logger, then we also log the traceback. @@ -32,7 +33,7 @@ def call_safe( :return: whatever the func returns """ if exception_message is None: - exception_message = "error calling function %s" % func.__name__ + exception_message = f"error calling function {func.__name__}" if args is None: args = () if kwargs is None: diff --git a/localstack-core/localstack/utils/http.py b/localstack-core/localstack/utils/http.py index 3e012fd71dad3..d52d2b53e2762 100644 --- a/localstack-core/localstack/utils/http.py +++ b/localstack-core/localstack/utils/http.py @@ -2,7 +2,6 @@ import math import os import re -from typing import Dict, Optional, Union from urllib.parse import parse_qs, parse_qsl, urlencode, urlparse, urlunparse import requests @@ -43,18 +42,18 @@ def create_chunked_data(data, chunk_size: int = 80): dl = len(data) ret = "" for i in range(dl // chunk_size): - ret += "%s\r\n" % (hex(chunk_size)[2:]) - ret += "%s\r\n\r\n" % (data[i * chunk_size : (i + 1) * chunk_size]) + ret += f"{hex(chunk_size)[2:]}\r\n" + ret += f"{data[i * chunk_size : (i + 1) * chunk_size]}\r\n\r\n" if len(data) % chunk_size != 0: - ret += "%s\r\n" % (hex(len(data) % chunk_size)[2:]) - ret += "%s\r\n" % (data[-(len(data) % chunk_size) :]) + ret += f"{hex(len(data) % chunk_size)[2:]}\r\n" + ret += f"{data[-(len(data) % chunk_size) :]}\r\n" ret += "0\r\n\r\n" return ret -def canonicalize_headers(headers: Union[Dict, CaseInsensitiveDict]) -> Dict: +def canonicalize_headers(headers: dict | CaseInsensitiveDict) -> dict: if not headers: return headers @@ -76,7 +75,7 @@ def add_path_parameters_to_url(uri: str, path_params: list): return urlunparse(url._replace(path=new_path)) -def add_query_params_to_url(uri: str, query_params: Dict) -> str: +def add_query_params_to_url(uri: str, query_params: dict) -> str: """ Add query parameters to the uri. :param uri: the base uri it can contains path arguments and query parameters @@ -103,7 +102,7 @@ def add_query_params_to_url(uri: str, query_params: Dict) -> str: def make_http_request( - url: str, data: Union[bytes, str] = None, headers: Dict[str, str] = None, method: str = "GET" + url: str, data: bytes | str = None, headers: dict[str, str] = None, method: str = "GET" ) -> Response: return requests.request( url=url, method=method, headers=headers, data=data, auth=NetrcBypassAuth(), verify=False @@ -141,7 +140,7 @@ def _wrapper(*args, **kwargs): safe_requests = _RequestsSafe() -def parse_request_data(method: str, path: str, data=None, headers=None) -> Dict: +def parse_request_data(method: str, path: str, data=None, headers=None) -> dict: """Extract request data either from query string as well as request body (e.g., for POST).""" result = {} headers = headers or {} @@ -165,7 +164,7 @@ def parse_request_data(method: str, path: str, data=None, headers=None) -> Dict: return result -def get_proxies() -> Dict[str, str]: +def get_proxies() -> dict[str, str]: proxy_map = {} if config.OUTBOUND_HTTP_PROXY: proxy_map["http"] = config.OUTBOUND_HTTP_PROXY @@ -179,7 +178,7 @@ def download( path: str, verify_ssl: bool = True, timeout: float = None, - request_headers: Optional[dict] = None, + request_headers: dict | None = None, quiet: bool = False, ) -> None: """Downloads file at url to the given path. Raises TimeoutError if the optional timeout (in secs) is reached. @@ -202,7 +201,7 @@ def download( r = s.get(url, stream=True, verify=_verify, timeout=timeout, headers=request_headers) # check status code before attempting to read body if not r.ok: - raise Exception("Failed to download %s, response code %s" % (url, r.status_code)) + raise Exception(f"Failed to download {url}, response code {r.status_code}") total_size = 0 if r.headers.get("Content-Length"): @@ -291,18 +290,19 @@ def download_github_artifact(url: str, target_file: str, timeout: int = None): Optionally allows to define a timeout in seconds.""" def do_download( - download_url: str, request_headers: Optional[dict] = None, print_error: bool = False + download_url: str, request_headers: dict | None = None, print_error: bool = False ): try: download(download_url, target_file, timeout=timeout, request_headers=request_headers) return True except Exception as e: if print_error: - LOG.exception( + LOG.error( "Unable to download Github artifact from %s to %s: %s %s", url, target_file, e, + exc_info=LOG.isEnabledFor(logging.DEBUG), ) # if a GitHub API token is set, use it to avoid rate limiting issues diff --git a/localstack-core/localstack/utils/iputils.py b/localstack-core/localstack/utils/iputils.py index b28ea2215c3c5..4e738c4367b2a 100644 --- a/localstack-core/localstack/utils/iputils.py +++ b/localstack-core/localstack/utils/iputils.py @@ -6,7 +6,8 @@ import ipaddress import json import subprocess as sp -from typing import Any, Generator, TypedDict +from collections.abc import Generator +from typing import Any, TypedDict from cachetools import TTLCache, cached diff --git a/localstack-core/localstack/utils/json.py b/localstack-core/localstack/utils/json.py index e2edcf3b4df35..53db4577d2519 100644 --- a/localstack-core/localstack/utils/json.py +++ b/localstack-core/localstack/utils/json.py @@ -5,7 +5,7 @@ import os from datetime import date, datetime from json import JSONDecodeError -from typing import Any, Union +from typing import Any from localstack.config import HostAndPort @@ -42,7 +42,7 @@ def default(self, o): try: if isinstance(o, bytes): return to_str(o) - return super(CustomEncoder, self).default(o) + return super().default(o) except Exception: return None @@ -63,9 +63,9 @@ class FileMappedDocument(dict): concurrent writes, run load(). To save and overwrite the current document on disk, run save(). """ - path: Union[str, os.PathLike] + path: str | os.PathLike - def __init__(self, path: Union[str, os.PathLike], mode=0o664): + def __init__(self, path: str | os.PathLike, mode=0o664): super().__init__() self.path = path self.mode = mode @@ -78,7 +78,7 @@ def load(self): if os.path.isdir(self.path): raise IsADirectoryError - with open(self.path, "r") as fd: + with open(self.path) as fd: self.update(json.load(fd)) def save(self): @@ -169,10 +169,24 @@ def extract_jsonpath(value, path): return result -def assign_to_path(target, path: str, value, delimiter: str = "."): +def assign_to_path(target: dict, path: str, value: any, delimiter: str = ".") -> dict: + """Assign the given value to a dict. If the path doesn't exist in the target dict, it will be created. + The delimiter can be used to provide a path with a different delimiter. + + Examples: + - assign_to_path({}, "a", "b") => {"a": "b"} + - assign_to_path({}, "a.b.c", "d") => {"a": {"b": {"c": "d"}}} + - assign_to_path({}, "a.b/c", "d", delimiter="/") => {"a.b": {"c": "d"}} + + """ parts = path.strip(delimiter).split(delimiter) + + if len(parts) == 1: + target[parts[0]] = value + return target + path_to_parent = delimiter.join(parts[:-1]) - parent = extract_from_jsonpointer_path(target, path_to_parent, auto_create=True) + parent = extract_from_jsonpointer_path(target, path_to_parent, delimiter, auto_create=True) if not isinstance(parent, dict): LOG.debug( 'Unable to find parent (type %s) for path "%s" in object: %s', diff --git a/localstack-core/localstack/utils/kinesis/kinesis_connector.py b/localstack-core/localstack/utils/kinesis/kinesis_connector.py index f68e79a379638..cc12bf75e1716 100644 --- a/localstack-core/localstack/utils/kinesis/kinesis_connector.py +++ b/localstack-core/localstack/utils/kinesis/kinesis_connector.py @@ -6,7 +6,8 @@ import socket import tempfile import threading -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from amazon_kclpy import kcl from amazon_kclpy.v2 import processor diff --git a/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode.py b/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode.py deleted file mode 100644 index a4b48e76f5b92..0000000000000 --- a/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -from localstack.aws.api.lambda_ import Arn -from localstack.utils.lambda_debug_mode.lambda_debug_mode_config import LambdaDebugModeConfig -from localstack.utils.lambda_debug_mode.lambda_debug_mode_session import LambdaDebugModeSession - -# Specifies the fault timeout value in seconds to be used by time restricted workflows when -# Debug Mode is enabled. The value is set to one hour to ensure eventual termination of -# long-running processes. -DEFAULT_LAMBDA_DEBUG_MODE_TIMEOUT_SECONDS: int = 3_600 - - -def is_lambda_debug_mode() -> bool: - return LambdaDebugModeSession.get().is_lambda_debug_mode() - - -def _lambda_debug_config_for(lambda_arn: Arn) -> Optional[LambdaDebugModeConfig]: - if not is_lambda_debug_mode(): - return None - debug_configuration = LambdaDebugModeSession.get().debug_config_for(lambda_arn=lambda_arn) - return debug_configuration - - -def is_lambda_debug_enabled_for(lambda_arn: Arn) -> bool: - """Returns True if the given lambda arn is subject of an active debugging configuration; False otherwise.""" - debug_configuration = _lambda_debug_config_for(lambda_arn=lambda_arn) - return debug_configuration is not None - - -def lambda_debug_port_for(lambda_arn: Arn) -> Optional[int]: - debug_configuration = _lambda_debug_config_for(lambda_arn=lambda_arn) - if debug_configuration is None: - return None - return debug_configuration.debug_port - - -def is_lambda_debug_timeout_enabled_for(lambda_arn: Arn) -> bool: - debug_configuration = _lambda_debug_config_for(lambda_arn=lambda_arn) - if debug_configuration is None: - return False - return not debug_configuration.enforce_timeouts diff --git a/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode_config.py b/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode_config.py deleted file mode 100644 index 6021261d88da4..0000000000000 --- a/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode_config.py +++ /dev/null @@ -1,230 +0,0 @@ -from __future__ import annotations - -import logging -from typing import Optional - -import yaml -from pydantic import BaseModel, Field, ValidationError -from yaml import Loader, MappingNode, MarkedYAMLError, SafeLoader - -from localstack.aws.api.lambda_ import Arn - -LOG = logging.getLogger(__name__) - - -class LambdaDebugConfig(BaseModel): - debug_port: Optional[int] = Field(None, alias="debug-port") - enforce_timeouts: bool = Field(False, alias="enforce-timeouts") - - -class LambdaDebugModeConfig(BaseModel): - # Bindings of Lambda function Arn and the respective debugging configuration. - functions: dict[Arn, LambdaDebugConfig] - - -class LambdaDebugModeConfigException(Exception): ... - - -class UnknownLambdaArnFormat(LambdaDebugModeConfigException): - unknown_lambda_arn: str - - def __init__(self, unknown_lambda_arn: str): - self.unknown_lambda_arn = unknown_lambda_arn - - def __str__(self): - return f"UnknownLambdaArnFormat: '{self.unknown_lambda_arn}'" - - -class PortAlreadyInUse(LambdaDebugModeConfigException): - port_number: int - - def __init__(self, port_number: int): - self.port_number = port_number - - def __str__(self): - return f"PortAlreadyInUse: '{self.port_number}'" - - -class DuplicateLambdaDebugConfig(LambdaDebugModeConfigException): - lambda_arn_debug_config_first: str - lambda_arn_debug_config_second: str - - def __init__(self, lambda_arn_debug_config_first: str, lambda_arn_debug_config_second: str): - self.lambda_arn_debug_config_first = lambda_arn_debug_config_first - self.lambda_arn_debug_config_second = lambda_arn_debug_config_second - - def __str__(self): - return ( - f"DuplicateLambdaDebugConfig: Lambda debug configuration in '{self.lambda_arn_debug_config_first}' " - f"is redefined in '{self.lambda_arn_debug_config_second}'" - ) - - -class _LambdaDebugModeConfigPostProcessingState: - ports_used: set[int] - - def __init__(self): - self.ports_used = set() - - -class _SafeLoaderWithDuplicateCheck(SafeLoader): - def __init__(self, stream): - super().__init__(stream) - self.add_constructor( - yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, - self._construct_mappings_with_duplicate_check, - ) - - @staticmethod - def _construct_mappings_with_duplicate_check(loader: Loader, node: MappingNode, deep=False): - # Constructs yaml bindings, whilst checking for duplicate mapping key definitions, raising a - # MarkedYAMLError when one is found. - mapping = dict() - for key_node, value_node in node.value: - key = loader.construct_object(key_node, deep=deep) - if key in mapping: - # Create a MarkedYAMLError to indicate the duplicate key issue - raise MarkedYAMLError( - context="while constructing a mapping", - context_mark=node.start_mark, - problem=f"found duplicate key: {key}", - problem_mark=key_node.start_mark, - ) - value = loader.construct_object(value_node, deep=deep) - mapping[key] = value - return mapping - - -def from_yaml_string(yaml_string: str) -> Optional[LambdaDebugModeConfig]: - try: - data = yaml.load(yaml_string, _SafeLoaderWithDuplicateCheck) - except yaml.YAMLError as yaml_error: - LOG.error( - "Could not parse yaml lambda debug mode configuration file due to: %s", - yaml_error, - ) - data = None - if not data: - return None - config = LambdaDebugModeConfig(**data) - return config - - -def post_process_lambda_debug_mode_config(config: LambdaDebugModeConfig) -> None: - _post_process_lambda_debug_mode_config( - post_processing_state=_LambdaDebugModeConfigPostProcessingState(), config=config - ) - - -def _post_process_lambda_debug_mode_config( - post_processing_state: _LambdaDebugModeConfigPostProcessingState, config: LambdaDebugModeConfig -): - config_functions = config.functions - lambda_arns = list(config_functions.keys()) - for lambda_arn in lambda_arns: - qualified_lambda_arn = _to_qualified_lambda_function_arn(lambda_arn) - if lambda_arn != qualified_lambda_arn: - if qualified_lambda_arn in config_functions: - raise DuplicateLambdaDebugConfig( - lambda_arn_debug_config_first=lambda_arn, - lambda_arn_debug_config_second=qualified_lambda_arn, - ) - config_functions[qualified_lambda_arn] = config_functions.pop(lambda_arn) - - for lambda_arn, lambda_debug_config in config_functions.items(): - _post_process_lambda_debug_config( - post_processing_state=post_processing_state, lambda_debug_config=lambda_debug_config - ) - - -def _to_qualified_lambda_function_arn(lambda_arn: Arn) -> Arn: - """ - Returns the $LATEST qualified version of a structurally unqualified version of a lambda Arn iff this - is detected to be structurally unqualified. Otherwise, it returns the given string. - Example: - - arn:aws:lambda:eu-central-1:000000000000:function:functionname:$LATEST -> - unchanged - - - arn:aws:lambda:eu-central-1:000000000000:function:functionname -> - arn:aws:lambda:eu-central-1:000000000000:function:functionname:$LATEST - - - arn:aws:lambda:eu-central-1:000000000000:function:functionname: -> - exception UnknownLambdaArnFormat - - - arn:aws:lambda:eu-central-1:000000000000:function -> - exception UnknownLambdaArnFormat - """ - - if not lambda_arn: - return lambda_arn - lambda_arn_parts = lambda_arn.split(":") - lambda_arn_parts_len = len(lambda_arn_parts) - - # The arn is qualified and with a non-empy qualifier. - is_qualified = lambda_arn_parts_len == 8 - if is_qualified and lambda_arn_parts[-1]: - return lambda_arn - - # Unknown lambda arn format. - is_unqualified = lambda_arn_parts_len == 7 - if not is_unqualified: - raise UnknownLambdaArnFormat(unknown_lambda_arn=lambda_arn) - - # Structure-wise, the arn is missing the qualifier. - qualifier = "$LATEST" - arn_tail = f":{qualifier}" if is_unqualified else qualifier - qualified_lambda_arn = lambda_arn + arn_tail - return qualified_lambda_arn - - -def _post_process_lambda_debug_config( - post_processing_state: _LambdaDebugModeConfigPostProcessingState, - lambda_debug_config: LambdaDebugConfig, -) -> None: - debug_port: Optional[int] = lambda_debug_config.debug_port - if debug_port is None: - return - if debug_port in post_processing_state.ports_used: - raise PortAlreadyInUse(port_number=debug_port) - post_processing_state.ports_used.add(debug_port) - - -def load_lambda_debug_mode_config(yaml_string: str) -> Optional[LambdaDebugModeConfig]: - # Attempt to parse the yaml string. - try: - yaml_data = yaml.load(yaml_string, _SafeLoaderWithDuplicateCheck) - except yaml.YAMLError as yaml_error: - LOG.error( - "Could not parse yaml lambda debug mode configuration file due to: %s", - yaml_error, - ) - yaml_data = None - if not yaml_data: - return None - - # Attempt to build the LambdaDebugModeConfig object from the yaml object. - try: - config = LambdaDebugModeConfig(**yaml_data) - except ValidationError as validation_error: - validation_errors = validation_error.errors() or list() - error_messages = [ - f"When parsing '{err.get('loc', '')}': {err.get('msg', str(err))}" - for err in validation_errors - ] - LOG.error( - "Unable to parse lambda debug mode configuration file due to errors: %s", - error_messages, - ) - return None - - # Attempt to post_process the configuration. - try: - post_process_lambda_debug_mode_config(config) - except LambdaDebugModeConfigException as lambda_debug_mode_error: - LOG.error( - "Invalid lambda debug mode configuration due to: %s", - lambda_debug_mode_error, - ) - config = None - - return config diff --git a/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode_session.py b/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode_session.py deleted file mode 100644 index f1155d531fa1e..0000000000000 --- a/localstack-core/localstack/utils/lambda_debug_mode/lambda_debug_mode_session.py +++ /dev/null @@ -1,175 +0,0 @@ -from __future__ import annotations - -import logging -import os -import time -from threading import Event, Thread -from typing import Optional - -from localstack.aws.api.lambda_ import Arn -from localstack.config import LAMBDA_DEBUG_MODE, LAMBDA_DEBUG_MODE_CONFIG_PATH -from localstack.utils.lambda_debug_mode.lambda_debug_mode_config import ( - LambdaDebugConfig, - LambdaDebugModeConfig, - load_lambda_debug_mode_config, -) -from localstack.utils.objects import singleton_factory - -LOG = logging.getLogger(__name__) - - -class LambdaDebugModeSession: - _is_lambda_debug_mode: bool - - _configuration_file_path: Optional[str] - _watch_thread: Optional[Thread] - _initialised_event: Optional[Event] - _stop_event: Optional[Event] - _config: Optional[LambdaDebugModeConfig] - - def __init__(self): - self._is_lambda_debug_mode = bool(LAMBDA_DEBUG_MODE) - - # Disabled Lambda Debug Mode state initialisation. - self._configuration_file_path = None - self._watch_thread = None - self._initialised_event = None - self._stop_event = None - self._config = None - - # Lambda Debug Mode is not enabled: leave as disabled state and return. - if not self._is_lambda_debug_mode: - return - - # Lambda Debug Mode is enabled. - # Instantiate the configuration requirements if a configuration file is given. - self._configuration_file_path = LAMBDA_DEBUG_MODE_CONFIG_PATH - if not self._configuration_file_path: - return - - # A configuration file path is given: initialised the resources to load and watch the file. - - # Signal and block on first loading to ensure this is enforced from the very first - # invocation, as this module is not loaded at startup. The LambdaDebugModeConfigWatch - # thread will then take care of updating the configuration periodically and asynchronously. - # This may somewhat slow down the first upstream thread loading this module, but not - # future calls. On the other hand, avoiding this mechanism means that first Lambda calls - # occur with no Debug configuration. - self._initialised_event = Event() - - # Signals when a shutdown signal from the application is registered. - self._stop_event = Event() - - self._watch_thread = Thread( - target=self._watch_logic, args=(), daemon=True, name="LambdaDebugModeConfigWatch" - ) - self._watch_thread.start() - - @staticmethod - @singleton_factory - def get() -> LambdaDebugModeSession: - """Returns a singleton instance of the Lambda Debug Mode session.""" - return LambdaDebugModeSession() - - def ensure_running(self) -> None: - # Nothing to start. - if self._watch_thread is None or self._watch_thread.is_alive(): - return - try: - self._watch_thread.start() - except Exception as exception: - exception_str = str(exception) - # The thread was already restarted by another process. - if ( - isinstance(exception, RuntimeError) - and exception_str - and "threads can only be started once" in exception_str - ): - return - LOG.error( - "Lambda Debug Mode could not restart the " - "hot reloading of the configuration file, '%s'", - exception_str, - ) - - def signal_stop(self) -> None: - stop_event = self._stop_event - if stop_event is not None: - stop_event.set() - - def _load_lambda_debug_mode_config(self): - yaml_configuration_string = None - try: - with open(self._configuration_file_path, "r") as df: - yaml_configuration_string = df.read() - except FileNotFoundError: - LOG.error( - "Error: The file lambda debug config file '%s' was not found.", - self._configuration_file_path, - ) - except IsADirectoryError: - LOG.error( - "Error: Expected a lambda debug config file but found a directory at '%s'.", - self._configuration_file_path, - ) - except PermissionError: - LOG.error( - "Error: Permission denied while trying to read the lambda debug config file '%s'.", - self._configuration_file_path, - ) - except Exception as ex: - LOG.error( - "Error: An unexpected error occurred while reading lambda debug config '%s': '%s'", - self._configuration_file_path, - ex, - ) - if not yaml_configuration_string: - return None - - self._config = load_lambda_debug_mode_config(yaml_configuration_string) - if self._config is not None: - LOG.info("Lambda Debug Mode is now enforcing the latest configuration.") - else: - LOG.warning( - "Lambda Debug Mode could not load the latest configuration due to an error, " - "check logs for more details." - ) - - def _config_file_epoch_last_modified_or_now(self) -> int: - try: - modified_time = os.path.getmtime(self._configuration_file_path) - return int(modified_time) - except Exception as e: - LOG.warning("Lambda Debug Mode could not access the configuration file: %s", e) - epoch_now = int(time.time()) - return epoch_now - - def _watch_logic(self) -> None: - # TODO: consider relying on system calls (watchdog lib for cross-platform support) - # instead of monitoring last modified dates. - # Run the first load and signal as initialised. - epoch_last_loaded: int = self._config_file_epoch_last_modified_or_now() - self._load_lambda_debug_mode_config() - self._initialised_event.set() - - # Monitor for file changes whilst the application is running. - while not self._stop_event.is_set(): - time.sleep(1) - epoch_last_modified = self._config_file_epoch_last_modified_or_now() - if epoch_last_modified > epoch_last_loaded: - epoch_last_loaded = epoch_last_modified - self._load_lambda_debug_mode_config() - - def _get_initialised_config(self) -> Optional[LambdaDebugModeConfig]: - # Check the session is not initialising, and if so then wait for initialisation to finish. - # Note: the initialisation event is otherwise left set since after first initialisation has terminated. - if self._initialised_event is not None: - self._initialised_event.wait() - return self._config - - def is_lambda_debug_mode(self) -> bool: - return self._is_lambda_debug_mode - - def debug_config_for(self, lambda_arn: Arn) -> Optional[LambdaDebugConfig]: - config = self._get_initialised_config() - return config.functions.get(lambda_arn) if config else None diff --git a/localstack-core/localstack/utils/net.py b/localstack-core/localstack/utils/net.py index c01c34f900f82..255b01c8b22bf 100644 --- a/localstack-core/localstack/utils/net.py +++ b/localstack-core/localstack/utils/net.py @@ -3,8 +3,9 @@ import re import socket import threading +from collections.abc import MutableMapping from contextlib import closing -from typing import Any, List, MutableMapping, NamedTuple, Optional, Union +from typing import Any, NamedTuple from urllib.parse import urlparse import dns.resolver @@ -49,14 +50,14 @@ def wrap(cls, port: "IntOrPort") -> "Port": # simple helper type to encapsulate int/Port argument types -IntOrPort = Union[int, Port] +IntOrPort = int | Port def is_port_open( - port_or_url: Union[int, str], + port_or_url: int | str, http_path: str = None, expect_success: bool = True, - protocols: Optional[Union[str, List[str]]] = None, + protocols: str | list[str] | None = None, quiet: bool = True, ): from localstack.utils.http import safe_requests @@ -92,11 +93,16 @@ def is_port_open( answers = resolver.query("google.com", "A") assert len(answers) > 0 else: - sock.sendto(bytes(), (host, port)) + sock.sendto(b"", (host, port)) sock.recvfrom(1024) except Exception: if not quiet: - LOG.exception("Error connecting to UDP port %s:%s", host, port) + LOG.error( + "Error connecting to UDP port %s:%s", + host, + port, + exc_info=LOG.isEnabledFor(logging.DEBUG), + ) return False elif nw_protocol == socket.SOCK_STREAM: result = sock.connect_ex((host, port)) @@ -158,8 +164,9 @@ def check(): status = is_port_open(port, http_path=http_path, expect_success=expect_success) if bool(status) != (not expect_closed): raise Exception( - "Port %s (path: %s) was not %s" - % (port, http_path, "closed" if expect_closed else "open") + "Port {} (path: {}) was not {}".format( + port, http_path, "closed" if expect_closed else "open" + ) ) return retry(check, sleep=sleep_time, retries=retries) @@ -190,7 +197,7 @@ def port_can_be_bound(port: IntOrPort, address: str = "") -> bool: return False -def get_free_udp_port(blocklist: List[int] = None) -> int: +def get_free_udp_port(blocklist: list[int] = None) -> int: blocklist = blocklist or [] for i in range(10): udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -202,7 +209,7 @@ def get_free_udp_port(blocklist: List[int] = None) -> int: raise Exception(f"Unable to determine free UDP port with blocklist {blocklist}") -def get_free_tcp_port(blocklist: List[int] = None) -> int: +def get_free_tcp_port(blocklist: list[int] = None) -> int: """ Tries to bind a socket to port 0 and returns the port that was assigned by the system. If the port is in the given ``blocklist``, or the port is marked as reserved in ``dynamic_port_range``, the procedure @@ -268,11 +275,11 @@ def _is_port_range_free(_range: PortRange): raise PortNotAvailableException("reached max_attempts when trying to find port range") -def resolve_hostname(hostname: str) -> Optional[str]: +def resolve_hostname(hostname: str) -> str | None: """Resolve the given hostname and return its IP address, or None if it cannot be resolved.""" try: return socket.gethostbyname(hostname) - except socket.error: + except OSError: return None @@ -280,7 +287,7 @@ def is_ip_address(addr: str) -> bool: try: socket.inet_aton(addr) return True - except socket.error: + except OSError: return False @@ -341,7 +348,8 @@ def subrange(self, start: int = None, end: int = None) -> "PortRange": if end > self.end: raise ValueError(f"end not in range ({end} < {self.end})") - port_range = PortRange(start, end) + # ensures that we return an instance of a subclass + port_range = type(self)(start, end) port_range._ports_cache = self._ports_cache port_range._ports_lock = self._ports_lock return port_range @@ -354,7 +362,7 @@ def as_range(self) -> range: """ return range(self.start, self.end + 1) - def reserve_port(self, port: Optional[IntOrPort] = None, duration: Optional[int] = None) -> int: + def reserve_port(self, port: IntOrPort | None = None, duration: int | None = None) -> int: """ Reserves the given port (if it is still free). If the given port is None, it reserves a free port from the configured port range for external services. If a port is given, it has to be within the configured @@ -468,9 +476,9 @@ def get_docker_host_from_container() -> str: if config.is_in_docker: try: result = socket.gethostbyname("host.docker.internal") - except socket.error: + except OSError: result = socket.gethostbyname("host.containers.internal") - except socket.error: + except OSError: # TODO if neither host resolves, we might be in linux. We could just use the default gateway then pass return result diff --git a/localstack-core/localstack/utils/no_exit_argument_parser.py b/localstack-core/localstack/utils/no_exit_argument_parser.py index 6455a59b12800..0b71c1185e037 100644 --- a/localstack-core/localstack/utils/no_exit_argument_parser.py +++ b/localstack-core/localstack/utils/no_exit_argument_parser.py @@ -1,6 +1,6 @@ import argparse import logging -from typing import NoReturn, Optional +from typing import NoReturn LOG = logging.getLogger(__name__) @@ -12,7 +12,7 @@ class NoExitArgumentParser(argparse.ArgumentParser): * ArgumentParser subclassing example: https://stackoverflow.com/a/59072378/6875981 """ - def exit(self, status: int = ..., message: Optional[str] = ...) -> NoReturn: + def exit(self, status: int = ..., message: str | None = ...) -> NoReturn: LOG.warning("Error in argument parser but preventing exit: %s", message) def error(self, message: str) -> NoReturn: diff --git a/localstack-core/localstack/utils/numbers.py b/localstack-core/localstack/utils/numbers.py index 19f5dbab42014..d5a9f0f6fe5d7 100644 --- a/localstack-core/localstack/utils/numbers.py +++ b/localstack-core/localstack/utils/numbers.py @@ -1,4 +1,4 @@ -from typing import Any, Union +from typing import Any def format_number(number: float, decimals: int = 2): @@ -11,6 +11,13 @@ def format_number(number: float, decimals: int = 2): def is_number(s: Any) -> bool: + # booleans inherit from int + # + # >>> a.__class__.__mro__ + # (, , ) + if s is False or s is True: + return False + try: float(s) # for int, long and float return True @@ -18,7 +25,7 @@ def is_number(s: Any) -> bool: return False -def to_number(s: Any) -> Union[int, float]: +def to_number(s: Any) -> int | float: """Cast the string representation of the given object to a number (int or float), or raise ValueError.""" try: return int(str(s)) diff --git a/localstack-core/localstack/utils/objects.py b/localstack-core/localstack/utils/objects.py index 9e5f5ba283e15..98d307139c082 100644 --- a/localstack-core/localstack/utils/objects.py +++ b/localstack-core/localstack/utils/objects.py @@ -1,12 +1,13 @@ import functools import re import threading -from typing import Any, Callable, Dict, Generic, List, Optional, Set, Type, TypeVar, Union +from collections.abc import Callable +from typing import Any, Generic, TypeVar from .collections import ensure_list from .strings import first_char_to_lower, first_char_to_upper -ComplexType = Union[List, Dict, object] +ComplexType = list | dict | object _T = TypeVar("_T") @@ -16,7 +17,7 @@ class Value(Generic[_T]): Simple value container. """ - value: Optional[_T] + value: _T | None def __init__(self, value: _T = None) -> None: self.value = value @@ -30,7 +31,7 @@ def set(self, value: _T): def is_set(self) -> bool: return self.value is not None - def get(self) -> Optional[_T]: + def get(self) -> _T | None: return self.value def __bool__(self): @@ -83,7 +84,7 @@ class SubtypesInstanceManager: """Simple instance manager base class that scans the subclasses of a base type for concrete named implementations, and lazily creates and returns (singleton) instances on demand.""" - _instances: Dict[str, "SubtypesInstanceManager"] + _instances: dict[str, "SubtypesInstanceManager"] @classmethod def get(cls, subtype_name: str, raise_if_missing: bool = True): @@ -104,7 +105,7 @@ def get(cls, subtype_name: str, raise_if_missing: bool = True): return instance @classmethod - def instances(cls) -> Dict[str, "SubtypesInstanceManager"]: + def instances(cls) -> dict[str, "SubtypesInstanceManager"]: base_type = cls.get_base_type() if not hasattr(base_type, "_instances"): base_type._instances = {} @@ -116,13 +117,13 @@ def impl_name() -> str: raise NotImplementedError @classmethod - def get_base_type(cls) -> Type: + def get_base_type(cls) -> type: """Get the base class for which instances are being managed - can be customized by subtypes.""" return cls # this requires that all subclasses have been imported before(!) -def get_all_subclasses(clazz: Type) -> Set[Type]: +def get_all_subclasses(clazz: type) -> set[type]: """Recursively get all subclasses of the given class.""" result = set() subs = clazz.__subclasses__() @@ -132,11 +133,11 @@ def get_all_subclasses(clazz: Type) -> Set[Type]: return result -def fully_qualified_class_name(klass: Type) -> str: +def fully_qualified_class_name(klass: type) -> str: return f"{klass.__module__}.{klass.__name__}" -def not_none_or(value: Optional[Any], alternative: Any) -> Any: +def not_none_or(value: Any | None, alternative: Any) -> Any: """Return 'value' if it is not None, or 'alternative' otherwise.""" return value if value is not None else alternative @@ -156,14 +157,14 @@ def recurse_object(obj: ComplexType, func: Callable, path: str = "") -> ComplexT def keys_to( - obj: ComplexType, op: Callable[[str], str], skip_children_of: List[str] = None + obj: ComplexType, op: Callable[[str], str], skip_children_of: list[str] = None ) -> ComplexType: """Recursively changes all dict keys to apply op. Skip children of any elements whose names are contained in skip_children_of (e.g., ['Tags'])""" skip_children_of = ensure_list(skip_children_of or []) def fix_keys(o, path="", **kwargs): - if any(re.match(r"(^|.*\.)%s($|[.\[].*)" % k, path) for k in skip_children_of): + if any(re.match(rf"(^|.*\.){k}($|[.\[].*)", path) for k in skip_children_of): return o if isinstance(o, dict): for k, v in dict(o).items(): @@ -175,11 +176,11 @@ def fix_keys(o, path="", **kwargs): return result -def keys_to_lower(obj: ComplexType, skip_children_of: List[str] = None) -> ComplexType: +def keys_to_lower(obj: ComplexType, skip_children_of: list[str] = None) -> ComplexType: return keys_to(obj, first_char_to_lower, skip_children_of) -def keys_to_upper(obj: ComplexType, skip_children_of: List[str] = None) -> ComplexType: +def keys_to_upper(obj: ComplexType, skip_children_of: list[str] = None) -> ComplexType: return keys_to(obj, first_char_to_upper, skip_children_of) diff --git a/localstack-core/localstack/utils/patch.py b/localstack-core/localstack/utils/patch.py index 2fa54e3cf2a39..c250860cfef9e 100644 --- a/localstack-core/localstack/utils/patch.py +++ b/localstack-core/localstack/utils/patch.py @@ -1,7 +1,8 @@ import functools import inspect import types -from typing import Any, Callable, List, Type +from collections.abc import Callable +from typing import Any def get_defining_object(method): @@ -71,7 +72,7 @@ def proxy(*args, **kwargs): class Patch: - applied_patches: List["Patch"] = [] + applied_patches: list["Patch"] = [] """Bookkeeping for patches that are applied. You can use this to debug patches. For instance, you could write something like:: @@ -97,19 +98,25 @@ def __init__(self, obj: Any, name: str, new: Any) -> None: self.is_applied = False def apply(self): + if self.is_applied: + return + if self.old and self.name == "__getattr__": raise Exception("You can't patch class types implementing __getattr__") if not self.old and self.name != "__getattr__": raise AttributeError(f"`{self.obj.__name__}` object has no attribute `{self.name}`") setattr(self.obj, self.name, self.new) - self.is_applied = True Patch.applied_patches.append(self) + self.is_applied = True def undo(self): + if not self.is_applied: + return + # If we added a method to a class type, we don't have a self.old. We just delete __getattr__ setattr(self.obj, self.name, self.old) if self.old else delattr(self.obj, self.name) - self.is_applied = False Patch.applied_patches.remove(self) + self.is_applied = False def __enter__(self): self.apply() @@ -120,7 +127,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): return self @staticmethod - def extend_class(target: Type, fn: Callable): + def extend_class(target: type, fn: Callable): def _getattr(obj, name): if name != fn.__name__: raise AttributeError(f"`{target.__name__}` object has no attribute `{name}`") @@ -164,9 +171,9 @@ def __str__(self): class Patches: - patches: List[Patch] + patches: list[Patch] - def __init__(self, patches: List[Patch] = None) -> None: + def __init__(self, patches: list[Patch] = None) -> None: super().__init__() self.patches = [] diff --git a/localstack-core/localstack/utils/platform.py b/localstack-core/localstack/utils/platform.py index e91e6f33a8483..ac06fd9d7865c 100644 --- a/localstack-core/localstack/utils/platform.py +++ b/localstack-core/localstack/utils/platform.py @@ -14,14 +14,14 @@ def is_windows() -> bool: return "windows" == platform.system().lower() -@lru_cache() +@lru_cache def is_debian() -> bool: from localstack.utils.files import load_file return "Debian" in load_file("/etc/issue", "") -@lru_cache() +@lru_cache def is_redhat() -> bool: from localstack.utils.files import load_file diff --git a/localstack-core/localstack/utils/run.py b/localstack-core/localstack/utils/run.py index 2c5aa0b07355e..60dafef182df7 100644 --- a/localstack-core/localstack/utils/run.py +++ b/localstack-core/localstack/utils/run.py @@ -7,9 +7,10 @@ import sys import threading import time +from collections.abc import Callable from functools import lru_cache from queue import Queue -from typing import Any, AnyStr, Callable, Dict, List, Optional, Union +from typing import Any, AnyStr from localstack import config @@ -23,19 +24,19 @@ def run( - cmd: Union[str, List[str]], + cmd: str | list[str], print_error=True, asynchronous=False, stdin=False, stderr=subprocess.STDOUT, outfile=None, - env_vars: Optional[Dict[AnyStr, AnyStr]] = None, + env_vars: dict[AnyStr, AnyStr] | None = None, inherit_cwd=False, inherit_env=True, tty=False, shell=True, cwd: str = None, -) -> Union[str, subprocess.Popen]: +) -> str | subprocess.Popen: LOG.debug("Executing command: %s", cmd) env_dict = os.environ.copy() if inherit_env else {} if env_vars: @@ -114,7 +115,7 @@ def pipe_streams(*args): return process except subprocess.CalledProcessError as e: if print_error: - print("ERROR: '%s': exit code %s; output: %s" % (cmd, e.returncode, e.output)) + print(f"ERROR: '{cmd}': exit code {e.returncode}; output: {e.output}") sys.stdout.flush() raise e @@ -151,7 +152,7 @@ def _worker(*_args): time.sleep(0.5) -def run_interactive(command: List[str]): +def run_interactive(command: list[str]): """ Run an interactive command in a subprocess. This blocks the current thread and attaches sys.stdin to the process. Copied from https://stackoverflow.com/a/43012138/804840 @@ -169,7 +170,7 @@ def is_command_available(cmd: str) -> bool: return False -def kill_process_tree(parent_pid): +def kill_process_tree(parent_pid: int) -> None: # Note: Do NOT import "psutil" at the root scope import psutil @@ -196,13 +197,13 @@ def is_root() -> bool: return get_os_user() == "root" -@lru_cache() +@lru_cache def get_os_user() -> str: # using getpass.getuser() seems to be reporting a different/invalid user in Docker/macOS return run("whoami").strip() -def to_str(obj: Union[str, bytes], errors="strict"): +def to_str(obj: str | bytes, errors="strict"): return obj.decode(config.DEFAULT_ENCODING, errors) if isinstance(obj, bytes) else obj @@ -211,10 +212,10 @@ class ShellCommandThread(FuncThread): def __init__( self, - cmd: Union[str, List[str]], + cmd: str | list[str], params: Any = None, - outfile: Union[str, int] = None, - env_vars: Dict[str, str] = None, + outfile: str | int = None, + env_vars: dict[str, str] = None, stdin: bool = False, auto_restart: bool = False, quiet: bool = True, @@ -223,8 +224,8 @@ def __init__( log_listener: Callable = None, stop_listener: Callable = None, strip_color: bool = False, - name: Optional[str] = None, - cwd: Optional[str] = None, + name: str | None = None, + cwd: str | None = None, ): params = params if params is not None else {} env_vars = env_vars if env_vars is not None else {} @@ -268,7 +269,7 @@ def convert_line(line): if self.strip_color: # strip color codes line = re.sub(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))", "", line) - return "%s\r\n" % line.strip() + return f"{line.strip()}\r\n" def filter_line(line): """Return True if this line should be filtered, i.e., not printed""" diff --git a/localstack-core/localstack/utils/scheduler.py b/localstack-core/localstack/utils/scheduler.py index c295ef70ced7a..640a834a332fa 100644 --- a/localstack-core/localstack/utils/scheduler.py +++ b/localstack-core/localstack/utils/scheduler.py @@ -1,8 +1,9 @@ import queue import threading import time +from collections.abc import Callable, Mapping from concurrent.futures import Executor -from typing import Any, Callable, List, Mapping, Optional, Tuple, Union +from typing import Any class ScheduledTask: @@ -13,12 +14,12 @@ class ScheduledTask: def __init__( self, task: Callable, - period: Optional[float] = None, + period: float | None = None, fixed_rate: bool = True, - start: Optional[float] = None, + start: float | None = None, on_error: Callable[[Exception], None] = None, - args: Optional[Union[tuple, list]] = None, - kwargs: Optional[Mapping[str, Any]] = None, + args: tuple | list | None = None, + kwargs: Mapping[str, Any] | None = None, ) -> None: super().__init__() self.task = task @@ -26,8 +27,8 @@ def __init__( self.period = period self.start = start self.on_error = on_error - self.args = args or tuple() - self.kwargs = kwargs or dict() + self.args = args or () + self.kwargs = kwargs or {} self.deadline = None self.error = None @@ -75,7 +76,7 @@ class Scheduler: POISON = (-1, "__POISON__") - def __init__(self, executor: Optional[Executor] = None) -> None: + def __init__(self, executor: Executor | None = None) -> None: """ Creates a new Scheduler. If an executor is passed, then that executor will be used to run the scheduled tasks asynchronously, otherwise they will be executed synchronously inside the event loop. Running tasks @@ -93,12 +94,12 @@ def __init__(self, executor: Optional[Executor] = None) -> None: def schedule( self, func: Callable, - period: Optional[float] = None, + period: float | None = None, fixed_rate: bool = True, - start: Optional[float] = None, + start: float | None = None, on_error: Callable[[Exception], None] = None, - args: Optional[Union[Tuple, List[Any]]] = None, - kwargs: Optional[Mapping[str, Any]] = None, + args: tuple | list[Any] | None = None, + kwargs: Mapping[str, Any] | None = None, ) -> ScheduledTask: """ Schedules a given task (function call). diff --git a/localstack-core/localstack/utils/server/tcp_proxy.py b/localstack-core/localstack/utils/server/tcp_proxy.py index 943120307a056..0327db2856d6e 100644 --- a/localstack-core/localstack/utils/server/tcp_proxy.py +++ b/localstack-core/localstack/utils/server/tcp_proxy.py @@ -1,8 +1,8 @@ import logging import select import socket +from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor -from typing import Callable from localstack.utils.serving import Server @@ -93,7 +93,7 @@ def do_run(self): try: src_socket, _ = self._server_socket.accept() self._thread_pool.submit(self._handle_request, src_socket) - except socket.timeout: + except TimeoutError: pass except OSError as e: # avoid creating an error message if OSError is thrown due to socket closing diff --git a/localstack-core/localstack/utils/serving.py b/localstack-core/localstack/utils/serving.py index 529e380518f88..81848ab2512a2 100644 --- a/localstack-core/localstack/utils/serving.py +++ b/localstack-core/localstack/utils/serving.py @@ -1,7 +1,6 @@ import abc import logging import threading -from typing import Optional from localstack.utils.net import is_port_open from localstack.utils.sync import poll_condition @@ -21,7 +20,7 @@ class Server(abc.ABC): def __init__(self, port: int, host: str = "localhost") -> None: super().__init__() - self._thread: Optional[FuncThread] = None + self._thread: FuncThread | None = None self._lifecycle_lock = threading.RLock() self._stopped = threading.Event() @@ -44,9 +43,9 @@ def protocol(self): @property def url(self): - return "%s://%s:%s" % (self.protocol, self.host, self.port) + return f"{self.protocol}://{self.host}:{self.port}" - def get_error(self) -> Optional[Exception]: + def get_error(self) -> Exception | None: """ If the thread running the server returned with an Exception, then this function will return that exception. """ diff --git a/localstack-core/localstack/utils/strings.py b/localstack-core/localstack/utils/strings.py index aead8aaade907..f3385cb08debd 100644 --- a/localstack-core/localstack/utils/strings.py +++ b/localstack-core/localstack/utils/strings.py @@ -7,10 +7,13 @@ import string import uuid import zlib -from typing import Dict, List, Union +from typing import TYPE_CHECKING, Any from localstack.config import DEFAULT_ENCODING +if TYPE_CHECKING: + from localstack.utils.objects import ComplexType + _unprintables = ( range(0x00, 0x09), range(0x0A, 0x0A), @@ -28,13 +31,13 @@ ) -def to_str(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="strict") -> str: +def to_str(obj: str | bytes, encoding: str = DEFAULT_ENCODING, errors: str = "strict") -> str: """If ``obj`` is an instance of ``binary_type``, return ``obj.decode(encoding, errors)``, otherwise return ``obj``""" return obj.decode(encoding, errors) if isinstance(obj, bytes) else obj -def to_bytes(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="strict") -> bytes: +def to_bytes(obj: str | bytes, encoding: str = DEFAULT_ENCODING, errors: str = "strict") -> bytes: """If ``obj`` is an instance of ``text_type``, return ``obj.encode(encoding, errors)``, otherwise return ``obj``""" return obj.encode(encoding, errors) if isinstance(obj, str) else obj @@ -42,10 +45,10 @@ def to_bytes(obj: Union[str, bytes], encoding: str = DEFAULT_ENCODING, errors="s def truncate(data: str, max_length: int = 100) -> str: data = str(data or "") - return ("%s..." % data[:max_length]) if len(data) > max_length else data + return (f"{data[:max_length]}...") if len(data) > max_length else data -def is_string(s, include_unicode=True, exclude_binary=False): +def is_string(s: Any, include_unicode: bool = True, exclude_binary: bool = False) -> bool: if isinstance(s, bytes) and exclude_binary: return False if isinstance(s, str): @@ -55,13 +58,13 @@ def is_string(s, include_unicode=True, exclude_binary=False): return False -def is_string_or_bytes(s): +def is_string_or_bytes(s: Any) -> bool: return is_string(s) or isinstance(s, str) or isinstance(s, bytes) -def is_base64(s): +def is_base64(s: Any) -> bool: regex = r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" - return is_string(s) and re.match(regex, s) + return is_string(s) and re.match(regex, s) is not None _re_camel_to_snake_case = re.compile("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))") @@ -86,32 +89,31 @@ def canonicalize_bool_to_str(val: bool) -> str: return "true" if str(val).lower() == "true" else "false" -def convert_to_printable_chars(value: Union[List, Dict, str]) -> str: +def convert_to_printable_chars(value: "str | ComplexType") -> "ComplexType": """Removes all unprintable characters from the given string.""" from localstack.utils.objects import recurse_object - if isinstance(value, (dict, list)): + if isinstance(value, str): + return REGEX_UNPRINTABLE_CHARS.sub("", value) + else: - def _convert(obj, **kwargs): + def _convert(obj: Any, **kwargs: Any) -> "ComplexType": if isinstance(obj, str): return convert_to_printable_chars(obj) return obj return recurse_object(value, _convert) - result = REGEX_UNPRINTABLE_CHARS.sub("", value) - return result - def first_char_to_lower(s: str) -> str: - return s and "%s%s" % (s[0].lower(), s[1:]) + return s and f"{s[0].lower()}{s[1:]}" def first_char_to_upper(s: str) -> str: - return s and "%s%s" % (s[0].upper(), s[1:]) + return s and f"{s[0].upper()}{s[1:]}" -def str_to_bool(value): +def str_to_bool(value: Any) -> Any: """Return the boolean value of the given string, or the verbatim value if it is not a string""" if isinstance(value, str): true_strings = ["true", "True"] @@ -119,15 +121,15 @@ def str_to_bool(value): return value -def str_insert(string, index, content): +def str_insert(string: str, index: int, content: str) -> str: """Insert a substring into an existing string at a certain index.""" - return "%s%s%s" % (string[:index], content, string[index:]) + return f"{string[:index]}{content}{string[index:]}" -def str_remove(string, index, end_index=None): +def str_remove(string: str, index: int, end_index: int | None = None) -> str: """Remove a substring from an existing string at a certain from-to index range.""" end_index = end_index or (index + 1) - return "%s%s" % (string[:index], string[end_index:]) + return f"{string[:index]}{string[end_index:]}" def str_startswith_ignore_case(value: str, prefix: str) -> bool: @@ -148,19 +150,19 @@ def long_uid() -> str: return str(uuid.uuid4()) -def md5(string: Union[str, bytes]) -> str: +def md5(string: str | bytes) -> str: m = hashlib.md5() m.update(to_bytes(string)) return m.hexdigest() -def checksum_crc32(string: Union[str, bytes]) -> str: +def checksum_crc32(string: str | bytes) -> str: bytes = to_bytes(string) checksum = zlib.crc32(bytes) return base64.b64encode(checksum.to_bytes(4, "big")).decode() -def checksum_crc32c(string: Union[str, bytes]): +def checksum_crc32c(string: str | bytes) -> str: # import botocore locally here to avoid a dependency of the CLI to botocore from botocore.httpchecksum import CrtCrc32cChecksum @@ -169,7 +171,7 @@ def checksum_crc32c(string: Union[str, bytes]): return base64.b64encode(checksum.digest()).decode() -def checksum_crc64nvme(string: Union[str, bytes]): +def checksum_crc64nvme(string: str | bytes) -> str: # import botocore locally here to avoid a dependency of the CLI to botocore from botocore.httpchecksum import CrtCrc64NvmeChecksum @@ -178,12 +180,12 @@ def checksum_crc64nvme(string: Union[str, bytes]): return base64.b64encode(checksum.digest()).decode() -def hash_sha1(string: Union[str, bytes]) -> str: +def hash_sha1(string: str | bytes) -> str: digest = hashlib.sha1(to_bytes(string)).digest() return base64.b64encode(digest).decode() -def hash_sha256(string: Union[str, bytes]) -> str: +def hash_sha256(string: str | bytes) -> str: digest = hashlib.sha256(to_bytes(string)).digest() return base64.b64encode(digest).decode() @@ -192,7 +194,7 @@ def base64_to_hex(b64_string: str) -> bytes: return binascii.hexlify(base64.b64decode(b64_string)) -def base64_decode(data: Union[str, bytes]) -> bytes: +def base64_decode(data: str | bytes) -> bytes: """Decode base64 data - with optional padding, and able to handle urlsafe encoding (containing -/_).""" data = to_str(data) missing_padding = len(data) % 4 @@ -224,7 +226,9 @@ def prepend_with_slash(input: str) -> str: return input -def key_value_pairs_to_dict(pairs: str, delimiter: str = ",", separator: str = "=") -> dict: +def key_value_pairs_to_dict( + pairs: str, delimiter: str = ",", separator: str = "=" +) -> dict[str, str]: """ Converts a string of key-value pairs to a dictionary. diff --git a/localstack-core/localstack/utils/sync.py b/localstack-core/localstack/utils/sync.py index e5beea032d150..de025f7a07aaf 100644 --- a/localstack-core/localstack/utils/sync.py +++ b/localstack-core/localstack/utils/sync.py @@ -4,7 +4,8 @@ import threading import time from collections import defaultdict -from typing import Callable, Literal, TypeVar +from collections.abc import Callable +from typing import Literal, TypeVar class ShortCircuitWaitException(Exception): @@ -140,3 +141,127 @@ def __len__(self): def __str__(self): with self._lock: return super().__str__() + + +class Once: + """ + An object that will perform an action exactly once. + Inspired by Golang's [sync.Once](https://pkg.go.dev/sync#Once) operation. + + + ### Example 1 + + Multiple threads using `Once::do` to ensure only 1 line is printed. + + ```python + import threading + import time + import random + + greet_once = Once() + def greet(): + print("This should happen only once.") + + greet_threads = [] + for _ in range(10): + t = threading.Thread(target=lambda: greet_once.do(greet)) + greet_threads.append(t) + t.start() + + for t in greet_threads: + t.join() + ``` + + + ### Example 2 + + Ensuring idemponent calling to prevent exceptions on multiple calls. + + ```python + import os + + class Service: + close_once: sync.Once + + def start(self): + with open("my-service.txt) as f: + myfile.write("Started service") + + def close(self): + # Ensure we only ever delete the file once on close + self.close_once.do(lambda: os.remove("my-service.txt")) + + ``` + + + """ + + _is_done: bool = False + _mu: threading.Lock = threading.Lock() + + def do(self, fn: Callable[[], None]): + """ + `do` calls the function `fn()` if-and-only-if `do` has never been called before. + + This ensures idempotent and thread-safe execution. + + If the function raises an exception, `do` considers `fn` as done, where subsequent calls are still no-ops. + """ + if self._is_done: + return + + with self._mu: + if not self._is_done: + try: + fn() + finally: + self._is_done = True + + +def once_func(fn: Callable[..., T]) -> Callable[..., T | None]: + """ + Wraps and returns a function that can only ever execute once. + + The first call to the returned function will permanently set the result. + If the wrapped function raises an exception, this will be re-raised on each subsequent call. + + This function can be used either as a decorator or called directly. + + Direct usage: + ```python + delete_file = once_func(os.remove) + + delete_file("myfile.txt") # deletes the file + delete_file("myfile.txt") # does nothing + ``` + + As a decorator: + ```python + @once_func + def delete_file(): + os.remove("myfile.txt") + + delete_file() # deletes the file + delete_file() # does nothing + ``` + """ + once = Once() + + result, exception = None, None + + def _do(*args, **kwargs): + nonlocal result, exception + try: + result = fn(*args, **kwargs) + except Exception as e: + exception = e + raise + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + once.do(lambda: _do(*args, **kwargs)) + if exception is not None: + raise exception + return result + + return wrapper diff --git a/localstack-core/localstack/utils/tagging.py b/localstack-core/localstack/utils/tagging.py index f2ab05160fd2b..2a7e9fd499b87 100644 --- a/localstack-core/localstack/utils/tagging.py +++ b/localstack-core/localstack/utils/tagging.py @@ -1,18 +1,25 @@ -from typing import Dict, List, Optional +from dataclasses import dataclass, field +from warnings import deprecated +@deprecated("`TaggingService` is deprecated. Please use the `RGTAPlugin`/`Tags` system.") class TaggingService: - def __init__(self, key_field: str = None, value_field: str = None): + key_field: str + value_field: str + + tags: dict[str, dict[str, str]] + + def __init__(self, key_field: str = "Key", value_field: str = "Value"): """ :param key_field: the field name representing the tag key as used by botocore specs :param value_field: the field name representing the tag value as used by botocore specs """ - self.key_field = key_field or "Key" - self.value_field = value_field or "Value" + self.key_field = key_field + self.value_field = value_field self.tags = {} - def list_tags_for_resource(self, arn: str, root_name: Optional[str] = None): + def list_tags_for_resource(self, arn: str, root_name: str | None = None): root_name = root_name or "Tags" result = [] @@ -21,7 +28,7 @@ def list_tags_for_resource(self, arn: str, root_name: Optional[str] = None): result.append({self.key_field: k, self.value_field: v}) return {root_name: result} - def tag_resource(self, arn: str, tags: List[Dict[str, str]]): + def tag_resource(self, arn: str, tags: list[dict[str, str]]): if not tags: return if arn not in self.tags: @@ -29,7 +36,7 @@ def tag_resource(self, arn: str, tags: List[Dict[str, str]]): for t in tags: self.tags[arn][t[self.key_field]] = t[self.value_field] - def untag_resource(self, arn: str, tag_names: List[str]): + def untag_resource(self, arn: str, tag_names: list[str]): tags = self.tags.get(arn, {}) for name in tag_names: tags.pop(name, None) @@ -40,3 +47,150 @@ def del_resource(self, arn: str): def __delitem__(self, arn: str): self.del_resource(arn) + + +ResourceARN = str +TagKey = str +TagValue = str +TagMap = dict[TagKey, TagValue] + + +@dataclass +class Tags: + """ + This dataclass provides utilities for performing resource tagging. Tags for resources are stored on + the service provider's store within this `Tags` dataclass with ResourceARN mapped against a dictionary + containing tags in the form TagKey:TagValue to remain agnostic to the service's tag format. + + The `Tags` dataclass supports updating / creating tags, deleting tags, and removing the + resource from the tag dictionary (_tags). It's important that when a resource is deleted to remove this + resource ARN from the store using the `delete_all_tags` method:: + + store = get_store(account_id, region) + store.TAGS.delete_all_tags(my_resource_arn) + + Do not use the `Tags` dataclass to determine the existence of a resource. For this, use ``connect_to`` or + direct Moto Backend introspection. It's important that resources do not exist within _tags unless they + currently have tags or have had tags in the past and the resource exists. The resource ARN should not exist within + _tags if the resource has been deleted. + + This distinction is important to maintain parity with the Resource Groups Tagging API (RGTA) which will tap into + supported service's `Tags` dataclass within its store. + """ + + _tags: dict[ResourceARN, TagMap] = field(default_factory=dict) + + def update_tags(self, arn: ResourceARN, tags: TagMap) -> None: + """ + Updates the tags of the specified resource. + + :param arn: The ARN of the resource to tag. + :param tags: A mapping of tag keys to tag values or an array of tag objects. + :return: None + """ + stored_tags = self._tags.setdefault(arn, {}) + + for k, v in tags.items(): + stored_tags[k] = v + + def get_tags(self, arn: ResourceARN) -> TagMap: + """ + Retrieves the tags for a specified resource. + + The tags are returned as a flat map of tag key/value pairs, e.g.:: + { + "Environment": "Production", + "Owner": "LocalStack", + } + + :param arn: The ARN of the resource you want to retrieve tags for. + :return: A dictionary copy of tag keys to tag values for the resource. + """ + if arn not in self._tags: + return {} + return self._tags[arn].copy() + + def delete_tags(self, arn: ResourceARN, keys: list[TagKey]) -> None: + """ + Deletes the tag on the resource specified by the provided tag keys. + + :param arn: The ARN of the resource to remove tags for. + :param keys: An array of tag keys to remove from the resource. + :return: None + """ + if tags := self._tags.get(arn): + for key in keys: + tags.pop(key, None) + + def delete_all_tags(self, arn: ResourceARN) -> None: + """ + Removes all the tags for a resource and removes it from the internal tagging store. + To be used once a resource is deleted or when you wish to remove all a resources tags. + + :param arn: The ARN of the resource to remove from the store. + :return: None + """ + self._tags.pop(arn, None) + + def get_resource_tag_map(self) -> dict[ResourceARN, TagMap]: + """ + Retrieves the entire mapping between Resource ARNs and their tags. + + This should not be used to retrieve tags for a single resource and should instead use the + `Tags.get_tags(resource_arn)`. It should only be used in scenarios where visibility into the + entire internal tag store is required such as with the Resource Groups Tagging API (RGTA). + + :return: A mapping between Resource ARN and tags. + """ + + return self._tags.copy() + + +# Tagging operations for various services return tags in one of two formats: +# +# - Tag list: A list of dicts, each dict containing the fields 'Key' and 'Value' and appropriate tag key value pairs. +# Some services, like CodePipeline, use the fields 'key' and 'value':: +# +# [ +# { +# "Key": "Environment", +# "Value": "Production", +# }, +# { +# "Key": "Owner", +# "Value": "LocalStack", +# } +# ] +# +# - Tag map: a direct mapping of tag keys to tag values.:: +# +# { +# "Environment": "Production", +# "Owner": "LocalStack", +# } + + +def tag_list_to_map( + tag_list: list[dict[str, str]], key_field: str = "Key", value_field: str = "Value" +) -> dict[str, str]: + """ + Convert a tag list to a tag map:: + + >> tag_list_to_map([{"Key": "temperature", "Value": "warm"}]) + {"temperature": "warm"} + + """ + return {tag[key_field]: tag[value_field] for tag in tag_list} + + +def tag_map_to_list( + tag_map: dict[str, str], key_field: str = "Key", value_field: str = "Value" +) -> list[dict[str, str]]: + """ + Convert a tag map to a tag list:: + + >> tag_map_to_list({"temperature": "warm"}) + [{"Key": "temperature", "Value": "warm"}] + + """ + return [{key_field: key, value_field: value} for key, value in tag_map.items()] diff --git a/localstack-core/localstack/utils/testutil.py b/localstack-core/localstack/utils/testutil.py index 2701cd7ce23a5..cc5f62c17eee0 100644 --- a/localstack-core/localstack/utils/testutil.py +++ b/localstack-core/localstack/utils/testutil.py @@ -7,7 +7,8 @@ import shutil import tempfile import time -from typing import Any, Callable, Dict, List, Optional +from collections.abc import Callable +from typing import Any from localstack.aws.api.lambda_ import Runtime from localstack.aws.connect import connect_externally_to, connect_to @@ -20,7 +21,7 @@ try: from typing import Literal except ImportError: - from typing_extensions import Literal + from typing import Literal import boto3 import requests @@ -71,7 +72,7 @@ def is_local_test_mode(): def create_lambda_archive( script: str, get_content: bool = False, - libs: List[str] = None, + libs: list[str] = None, runtime: str = None, file_name: str = None, exclude_func: Callable[[str], bool] = None, @@ -94,7 +95,7 @@ def create_lambda_archive( chmod_r(script_file, 0o777) # copy libs for lib in libs: - paths = [lib, "%s.py" % lib] + paths = [lib, f"{lib}.py"] try: module = importlib.import_module(lib) paths.append(module.__file__) @@ -255,7 +256,7 @@ def create_lambda_function( "Role": role or LAMBDA_TEST_ROLE.format(account_id=TEST_AWS_ACCOUNT_ID), "Code": lambda_code, "Timeout": timeout or LAMBDA_TIMEOUT_SEC, - "Environment": dict(Variables=envvars), + "Environment": {"Variables": envvars}, "Tags": tags, } kwargs.update(additional_kwargs) @@ -382,7 +383,7 @@ def assert_object(expected_object, all_objects): all_objects = [all_objects] found = find_object(expected_object, all_objects) if not found: - raise Exception("Expected object not found: %s in list %s" % (expected_object, all_objects)) + raise Exception(f"Expected object not found: {expected_object} in list {all_objects}") def find_object(expected_object, object_list): @@ -425,7 +426,7 @@ def list_all_s3_objects(s3_client): return map_all_s3_objects(s3_client=s3_client).values() -def delete_all_s3_objects(s3_client, buckets: str | List[str]): +def delete_all_s3_objects(s3_client, buckets: str | list[str]): buckets = ensure_list(buckets) for bucket in buckets: keys = all_s3_object_keys(s3_client, bucket) @@ -444,15 +445,15 @@ def download_s3_object(s3_client, bucket, path): return result -def all_s3_object_keys(s3_client, bucket: str) -> List[str]: +def all_s3_object_keys(s3_client, bucket: str) -> list[str]: response = s3_client.list_objects_v2(Bucket=bucket) keys = [obj["Key"] for obj in response.get("Contents", [])] return keys def map_all_s3_objects( - s3_client, to_json: bool = True, buckets: str | List[str] = None -) -> Dict[str, Any]: + s3_client, to_json: bool = True, buckets: str | list[str] = None +) -> dict[str, Any]: result = {} buckets = ensure_list(buckets) if not buckets: @@ -499,7 +500,7 @@ def send_update_dynamodb_ttl_request(table_name, ttl_status): def send_dynamodb_request(path, action, request_body): headers = { "Host": "dynamodb.amazonaws.com", - "x-amz-target": "DynamoDB_20120810.{}".format(action), + "x-amz-target": f"DynamoDB_20120810.{action}", "Authorization": mock_aws_request_headers( "dynamodb", aws_access_key_id=TEST_AWS_ACCESS_KEY_ID, region_name=TEST_AWS_REGION_NAME )["Authorization"], @@ -509,7 +510,7 @@ def send_dynamodb_request(path, action, request_body): def get_lambda_log_group_name(function_name): - return "/aws/lambda/{}".format(function_name) + return f"/aws/lambda/{function_name}" # TODO: make logs_client mandatory @@ -522,8 +523,7 @@ def check_expected_lambda_log_events_length( events = [line for line in events if line not in ["\x1b[0m", "\\x1b[0m"]] if len(events) != expected_length: print( - "Invalid # of Lambda %s log events: %s / %s: %s" - % ( + "Invalid # of Lambda {} log events: {} / {}: {}".format( function_name, len(events), expected_length, @@ -537,7 +537,7 @@ def check_expected_lambda_log_events_length( return events -def list_all_log_events(log_group_name: str, logs_client=None) -> List[Dict]: +def list_all_log_events(log_group_name: str, logs_client=None) -> list[dict]: logs = logs_client or connect_to().logs return list_all_resources( lambda kwargs: logs.filter_log_events(logGroupName=log_group_name, **kwargs), @@ -549,7 +549,7 @@ def list_all_log_events(log_group_name: str, logs_client=None) -> List[Dict]: def get_lambda_log_events( function_name, delay_time=DEFAULT_GET_LOG_EVENTS_DELAY, - regex_filter: Optional[str] = None, + regex_filter: str | None = None, log_group=None, logs_client=None, ): @@ -597,7 +597,7 @@ def list_all_resources( page_function: Callable[[dict], Any], last_token_attr_name: str, list_attr_name: str, - next_token_attr_name: Optional[str] = None, + next_token_attr_name: str | None = None, ) -> list: """ List all available resources by loading all available pages using `page_function`. diff --git a/localstack-core/localstack/utils/threads.py b/localstack-core/localstack/utils/threads.py index a981160177b34..4123302d69643 100644 --- a/localstack-core/localstack/utils/threads.py +++ b/localstack-core/localstack/utils/threads.py @@ -1,17 +1,21 @@ import concurrent.futures -import inspect import logging +import subprocess import threading import traceback +from collections.abc import Callable from concurrent.futures import Future from multiprocessing.dummy import Pool -from typing import Callable, List, Optional +from typing import Any, ParamSpec, TypeVar + +P = ParamSpec("P") +T = TypeVar("T") LOG = logging.getLogger(__name__) # arrays for temporary threads and resources -TMP_THREADS = [] -TMP_PROCESSES = [] +TMP_THREADS: list["FuncThread"] = [] +TMP_PROCESSES: list[subprocess.Popen[Any]] = [] counter_lock = threading.Lock() counter = 0 @@ -22,12 +26,12 @@ class FuncThread(threading.Thread): def __init__( self, - func, - params=None, - quiet=False, - on_stop: Callable[["FuncThread"], None] = None, - name: Optional[str] = None, - daemon=True, + func: "Callable[P, T]", + params: Any = None, + quiet: bool = False, + on_stop: Callable[["FuncThread"], None] | None = None, + name: str | None = None, + daemon: bool = True, ): global counter global counter_lock @@ -46,17 +50,14 @@ def __init__( self.params = params self.func = func self.quiet = quiet - self.result_future = Future() + self.result_future: Future[T | Exception | None] = Future() self._stop_event = threading.Event() self.on_stop = on_stop - def run(self): - result = None + def run(self) -> None: + result: Any = None try: - kwargs = {} - argspec = inspect.getfullargspec(self.func) - if argspec.varkw or "_thread" in (argspec.args or []) + (argspec.kwonlyargs or []): - kwargs["_thread"] = self + kwargs = {} # type: ignore[var-annotated] result = self.func(self.params, **kwargs) except Exception as e: self.result_future.set_exception(e) @@ -78,7 +79,7 @@ def run(self): LOG.debug(e) @property - def running(self): + def running(self) -> bool: return not self._stop_event.is_set() def stop(self, quiet: bool = False) -> None: @@ -91,27 +92,33 @@ def stop(self, quiet: bool = False) -> None: LOG.warning("error while calling on_stop callback: %s", e) -def start_thread(method, *args, **kwargs) -> FuncThread: # TODO: find all usages and add names... +def start_thread( + method: "Callable[P, T]", + params: Any = None, + quiet: bool = False, + on_stop: Callable[["FuncThread"], None] | None = None, + _shutdown_hook: bool = True, + name: str | None = None, +) -> FuncThread: """Start the given method in a background thread, and add the thread to the TMP_THREADS shutdown hook""" - _shutdown_hook = kwargs.pop("_shutdown_hook", True) - if not kwargs.get("name"): - LOG.debug( - "start_thread called without providing a custom name" - ) # technically we should add a new level here for *internal* warnings - kwargs.setdefault("name", method.__name__) - thread = FuncThread(method, *args, **kwargs) + if not name: + # technically we should add a new level here for *internal* warnings + LOG.debug("start_thread called without providing a custom name") + name = name or method.__name__ + thread = FuncThread(method, params=params, quiet=quiet, name=name, on_stop=on_stop) thread.start() if _shutdown_hook: TMP_THREADS.append(thread) return thread -def start_worker_thread(method, *args, **kwargs): - kwargs.setdefault("name", "start_worker_thread") - return start_thread(method, *args, _shutdown_hook=False, **kwargs) +def start_worker_thread( + method: "Callable[P, T]", params: Any = None, name: str | None = None +) -> FuncThread: + return start_thread(method, params, _shutdown_hook=False, name=name or "start_worker_thread") -def cleanup_threads_and_processes(quiet=True): +def cleanup_threads_and_processes(quiet: bool = True) -> None: from localstack.utils.run import kill_process_tree for thread in TMP_THREADS: @@ -153,7 +160,7 @@ def cleanup_threads_and_processes(quiet=True): TMP_PROCESSES.clear() -def parallelize(func: Callable, arr: List, size: int = None): +def parallelize(func: Callable, arr: list, size: int = None): # type: ignore if not size: size = len(arr) if size <= 0: diff --git a/localstack-core/localstack/utils/time.py b/localstack-core/localstack/utils/time.py index 6e59a04a18f98..66c93dc8e4b1d 100644 --- a/localstack-core/localstack/utils/time.py +++ b/localstack-core/localstack/utils/time.py @@ -1,6 +1,6 @@ import time from datetime import date, datetime, timezone, tzinfo -from typing import Optional +from zoneinfo import ZoneInfo TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S" TIMESTAMP_FORMAT_TZ = "%Y-%m-%dT%H:%M:%SZ" @@ -42,6 +42,11 @@ def epoch_timestamp() -> float: def parse_timestamp(ts_str: str) -> datetime: + """ + Parse the incoming date string into a timezone aware datetime object + :param ts_str: + :return: + """ for ts_format in [ TIMESTAMP_FORMAT, TIMESTAMP_FORMAT_TZ, @@ -49,13 +54,16 @@ def parse_timestamp(ts_str: str) -> datetime: TIMESTAMP_READABLE_FORMAT, ]: try: - return datetime.strptime(ts_str, ts_format) + value = datetime.strptime(ts_str, ts_format) + if value.tzinfo is None: + value = value.replace(tzinfo=ZoneInfo("UTC")) + return value except ValueError: pass - raise Exception("Unable to parse timestamp string with any known formats: %s" % ts_str) + raise Exception(f"Unable to parse timestamp string with any known formats: {ts_str}") -def now(millis: bool = False, tz: Optional[tzinfo] = None) -> int: +def now(millis: bool = False, tz: tzinfo | None = None) -> int: return mktime(datetime.now(tz=tz), millis=millis) diff --git a/localstack-core/localstack/utils/urls.py b/localstack-core/localstack/utils/urls.py index 97b92af754996..a6f4dd6e9f76a 100644 --- a/localstack-core/localstack/utils/urls.py +++ b/localstack-core/localstack/utils/urls.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack import config from localstack.config import HostAndPort @@ -12,7 +10,7 @@ def hostname_from_url(url: str) -> str: return url.split("://")[-1].split("/")[0].split(":")[0] -def localstack_host(custom_port: Optional[int] = None) -> HostAndPort: +def localstack_host(custom_port: int | None = None) -> HostAndPort: """ Determine the host and port to return to the user based on: - the user's configuration (e.g environment variable overrides) diff --git a/localstack-core/localstack/utils/xml.py b/localstack-core/localstack/utils/xml.py index 3e17cb57cc466..16e1c59af7ff1 100644 --- a/localstack-core/localstack/utils/xml.py +++ b/localstack-core/localstack/utils/xml.py @@ -9,7 +9,7 @@ def obj_to_xml(obj: Any) -> str: if isinstance(obj, list): return "".join([obj_to_xml(o) for o in obj]) if isinstance(obj, dict): - return "".join(["<{k}>{v}".format(k=k, v=obj_to_xml(v)) for (k, v) in obj.items()]) + return "".join([f"<{k}>{obj_to_xml(v)}" for (k, v) in obj.items()]) return str(obj) diff --git a/localstack-core/localstack/utils/xray/traceid.py b/localstack-core/localstack/utils/xray/traceid.py index dbc4b0fa7d644..7c8dc236fe254 100644 --- a/localstack-core/localstack/utils/xray/traceid.py +++ b/localstack-core/localstack/utils/xray/traceid.py @@ -29,7 +29,7 @@ def to_id(self): """ Convert TraceId object to a string. """ - return "%s%s%s%s%s" % ( + return "{}{}{}{}{}".format( TraceId.VERSION, TraceId.DELIMITER, format(self.start_time, "x"), diff --git a/localstack-core/mypy.ini b/localstack-core/mypy.ini index 5fdadc333f36c..30e0bd8e0b7b8 100644 --- a/localstack-core/mypy.ini +++ b/localstack-core/mypy.ini @@ -1,7 +1,7 @@ [mypy] explicit_package_bases = true mypy_path=localstack-core -files=localstack/aws/api/core.py,localstack/packages,localstack/services/transcribe,localstack/services/kinesis/packages.py +files=localstack/aws/api/core.py,localstack/utils/files.py,localstack/utils/docker_utils.py,localstack/utils/threads.py,localstack/utils/strings.py,localstack/packages,localstack/services/transcribe,localstack/services/kinesis/packages.py ignore_missing_imports = False follow_imports = silent ignore_errors = False diff --git a/plux.ini b/plux.ini new file mode 100644 index 0000000000000..6ef5ac38199b4 --- /dev/null +++ b/plux.ini @@ -0,0 +1,224 @@ +[localstack.aws.provider] +acm:default = localstack.services.providers:acm +apigateway:default = localstack.services.providers:apigateway +apigateway:legacy = localstack.services.providers:apigateway_legacy +apigateway:next_gen = localstack.services.providers:apigateway_next_gen +cloudformation:default = localstack.services.providers:cloudformation_v2 +cloudformation:engine-legacy = localstack.services.providers:cloudformation +cloudwatch:default = localstack.services.providers:cloudwatch +cloudwatch:v1 = localstack.services.providers:cloudwatch_v1 +cloudwatch:v2 = localstack.services.providers:cloudwatch_v2 +config:default = localstack.services.providers:awsconfig +dynamodb:default = localstack.services.providers:dynamodb +dynamodb:v2 = localstack.services.providers:dynamodb_v2 +dynamodbstreams:default = localstack.services.providers:dynamodbstreams +dynamodbstreams:v2 = localstack.services.providers:dynamodbstreams_v2 +ec2:default = localstack.services.providers:ec2 +es:default = localstack.services.providers:es +events:default = localstack.services.providers:events +events:legacy = localstack.services.providers:events_legacy +events:v1 = localstack.services.providers:events_v1 +events:v2 = localstack.services.providers:events_v2 +firehose:default = localstack.services.providers:firehose +iam:default = localstack.services.providers:iam +kinesis:default = localstack.services.providers:kinesis +kms:default = localstack.services.providers:kms +lambda:asf = localstack.services.providers:lambda_asf +lambda:default = localstack.services.providers:lambda_ +lambda:v2 = localstack.services.providers:lambda_v2 +logs:default = localstack.services.providers:logs +opensearch:default = localstack.services.providers:opensearch +redshift:default = localstack.services.providers:redshift +resource-groups:default = localstack.services.providers:resource_groups +resourcegroupstaggingapi:default = localstack.services.providers:resourcegroupstaggingapi +route53:default = localstack.services.providers:route53 +route53resolver:default = localstack.services.providers:route53resolver +s3:default = localstack.services.providers:s3 +s3control:default = localstack.services.providers:s3control +scheduler:default = localstack.services.providers:scheduler +secretsmanager:default = localstack.services.providers:secretsmanager +ses:default = localstack.services.providers:ses +sns:default = localstack.services.providers:sns +sqs:default = localstack.services.providers:sqs +ssm:default = localstack.services.providers:ssm +stepfunctions:default = localstack.services.providers:stepfunctions +stepfunctions:v2 = localstack.services.providers:stepfunctions_v2 +sts:default = localstack.services.providers:sts +support:default = localstack.services.providers:support +swf:default = localstack.services.providers:swf +transcribe:default = localstack.services.providers:transcribe + +[localstack.cloudformation.resource_providers] +aws::apigateway::account = localstack.services.apigateway.resource_providers.aws_apigateway_account_plugin:ApiGatewayAccountProviderPlugin +aws::apigateway::apikey = localstack.services.apigateway.resource_providers.aws_apigateway_apikey_plugin:ApiGatewayApiKeyProviderPlugin +aws::apigateway::basepathmapping = localstack.services.apigateway.resource_providers.aws_apigateway_basepathmapping_plugin:ApiGatewayBasePathMappingProviderPlugin +aws::apigateway::deployment = localstack.services.apigateway.resource_providers.aws_apigateway_deployment_plugin:ApiGatewayDeploymentProviderPlugin +aws::apigateway::domainname = localstack.services.apigateway.resource_providers.aws_apigateway_domainname_plugin:ApiGatewayDomainNameProviderPlugin +aws::apigateway::gatewayresponse = localstack.services.apigateway.resource_providers.aws_apigateway_gatewayresponse_plugin:ApiGatewayGatewayResponseProviderPlugin +aws::apigateway::method = localstack.services.apigateway.resource_providers.aws_apigateway_method_plugin:ApiGatewayMethodProviderPlugin +aws::apigateway::model = localstack.services.apigateway.resource_providers.aws_apigateway_model_plugin:ApiGatewayModelProviderPlugin +aws::apigateway::requestvalidator = localstack.services.apigateway.resource_providers.aws_apigateway_requestvalidator_plugin:ApiGatewayRequestValidatorProviderPlugin +aws::apigateway::resource = localstack.services.apigateway.resource_providers.aws_apigateway_resource_plugin:ApiGatewayResourceProviderPlugin +aws::apigateway::restapi = localstack.services.apigateway.resource_providers.aws_apigateway_restapi_plugin:ApiGatewayRestApiProviderPlugin +aws::apigateway::stage = localstack.services.apigateway.resource_providers.aws_apigateway_stage_plugin:ApiGatewayStageProviderPlugin +aws::apigateway::usageplan = localstack.services.apigateway.resource_providers.aws_apigateway_usageplan_plugin:ApiGatewayUsagePlanProviderPlugin +aws::apigateway::usageplankey = localstack.services.apigateway.resource_providers.aws_apigateway_usageplankey_plugin:ApiGatewayUsagePlanKeyProviderPlugin +aws::cdk::metadata = localstack.services.cdk.resource_providers.cdk_metadata_plugin:LambdaAliasProviderPlugin +aws::certificatemanager::certificate = localstack.services.certificatemanager.resource_providers.aws_certificatemanager_certificate_plugin:CertificateManagerCertificateProviderPlugin +aws::cloudformation::macro = localstack.services.cloudformation.resource_providers.aws_cloudformation_macro_plugin:CloudFormationMacroProviderPlugin +aws::cloudformation::stack = localstack.services.cloudformation.resource_providers.aws_cloudformation_stack_plugin:CloudFormationStackProviderPlugin +aws::cloudformation::waitcondition = localstack.services.cloudformation.resource_providers.aws_cloudformation_waitcondition_plugin:CloudFormationWaitConditionProviderPlugin +aws::cloudformation::waitconditionhandle = localstack.services.cloudformation.resource_providers.aws_cloudformation_waitconditionhandle_plugin:CloudFormationWaitConditionHandleProviderPlugin +aws::cloudwatch::alarm = localstack.services.cloudwatch.resource_providers.aws_cloudwatch_alarm_plugin:CloudWatchAlarmProviderPlugin +aws::cloudwatch::compositealarm = localstack.services.cloudwatch.resource_providers.aws_cloudwatch_compositealarm_plugin:CloudWatchCompositeAlarmProviderPlugin +aws::dynamodb::globaltable = localstack.services.dynamodb.resource_providers.aws_dynamodb_globaltable_plugin:DynamoDBGlobalTableProviderPlugin +aws::dynamodb::table = localstack.services.dynamodb.resource_providers.aws_dynamodb_table_plugin:DynamoDBTableProviderPlugin +aws::ec2::dhcpoptions = localstack.services.ec2.resource_providers.aws_ec2_dhcpoptions_plugin:EC2DHCPOptionsProviderPlugin +aws::ec2::instance = localstack.services.ec2.resource_providers.aws_ec2_instance_plugin:EC2InstanceProviderPlugin +aws::ec2::internetgateway = localstack.services.ec2.resource_providers.aws_ec2_internetgateway_plugin:EC2InternetGatewayProviderPlugin +aws::ec2::keypair = localstack.services.ec2.resource_providers.aws_ec2_keypair_plugin:EC2KeyPairProviderPlugin +aws::ec2::natgateway = localstack.services.ec2.resource_providers.aws_ec2_natgateway_plugin:EC2NatGatewayProviderPlugin +aws::ec2::networkacl = localstack.services.ec2.resource_providers.aws_ec2_networkacl_plugin:EC2NetworkAclProviderPlugin +aws::ec2::prefixlist = localstack.services.ec2.resource_providers.aws_ec2_prefixlist_plugin:EC2PrefixListProviderPlugin +aws::ec2::route = localstack.services.ec2.resource_providers.aws_ec2_route_plugin:EC2RouteProviderPlugin +aws::ec2::routetable = localstack.services.ec2.resource_providers.aws_ec2_routetable_plugin:EC2RouteTableProviderPlugin +aws::ec2::securitygroup = localstack.services.ec2.resource_providers.aws_ec2_securitygroup_plugin:EC2SecurityGroupProviderPlugin +aws::ec2::subnet = localstack.services.ec2.resource_providers.aws_ec2_subnet_plugin:EC2SubnetProviderPlugin +aws::ec2::subnetroutetableassociation = localstack.services.ec2.resource_providers.aws_ec2_subnetroutetableassociation_plugin:EC2SubnetRouteTableAssociationProviderPlugin +aws::ec2::transitgateway = localstack.services.ec2.resource_providers.aws_ec2_transitgateway_plugin:EC2TransitGatewayProviderPlugin +aws::ec2::transitgatewayattachment = localstack.services.ec2.resource_providers.aws_ec2_transitgatewayattachment_plugin:EC2TransitGatewayAttachmentProviderPlugin +aws::ec2::vpc = localstack.services.ec2.resource_providers.aws_ec2_vpc_plugin:EC2VPCProviderPlugin +aws::ec2::vpcendpoint = localstack.services.ec2.resource_providers.aws_ec2_vpcendpoint_plugin:EC2VPCEndpointProviderPlugin +aws::ec2::vpcgatewayattachment = localstack.services.ec2.resource_providers.aws_ec2_vpcgatewayattachment_plugin:EC2VPCGatewayAttachmentProviderPlugin +aws::ecr::repository = localstack.services.ecr.resource_providers.aws_ecr_repository_plugin:ECRRepositoryProviderPlugin +aws::elasticsearch::domain = localstack.services.opensearch.resource_providers.aws_elasticsearch_domain_plugin:ElasticsearchDomainProviderPlugin +aws::events::apidestination = localstack.services.events.resource_providers.aws_events_apidestination_plugin:EventsApiDestinationProviderPlugin +aws::events::connection = localstack.services.events.resource_providers.aws_events_connection_plugin:EventsConnectionProviderPlugin +aws::events::eventbus = localstack.services.events.resource_providers.aws_events_eventbus_plugin:EventsEventBusProviderPlugin +aws::events::eventbuspolicy = localstack.services.events.resource_providers.aws_events_eventbuspolicy_plugin:EventsEventBusPolicyProviderPlugin +aws::events::rule = localstack.services.events.resource_providers.aws_events_rule_plugin:EventsRuleProviderPlugin +aws::iam::accesskey = localstack.services.iam.resource_providers.aws_iam_accesskey_plugin:IAMAccessKeyProviderPlugin +aws::iam::group = localstack.services.iam.resource_providers.aws_iam_group_plugin:IAMGroupProviderPlugin +aws::iam::instanceprofile = localstack.services.iam.resource_providers.aws_iam_instanceprofile_plugin:IAMInstanceProfileProviderPlugin +aws::iam::managedpolicy = localstack.services.iam.resource_providers.aws_iam_managedpolicy_plugin:IAMManagedPolicyProviderPlugin +aws::iam::policy = localstack.services.iam.resource_providers.aws_iam_policy_plugin:IAMPolicyProviderPlugin +aws::iam::role = localstack.services.iam.resource_providers.aws_iam_role_plugin:IAMRoleProviderPlugin +aws::iam::servercertificate = localstack.services.iam.resource_providers.aws_iam_servercertificate_plugin:IAMServerCertificateProviderPlugin +aws::iam::servicelinkedrole = localstack.services.iam.resource_providers.aws_iam_servicelinkedrole_plugin:IAMServiceLinkedRoleProviderPlugin +aws::iam::user = localstack.services.iam.resource_providers.aws_iam_user_plugin:IAMUserProviderPlugin +aws::kms::alias = localstack.services.kms.resource_providers.aws_kms_alias_plugin:KMSAliasProviderPlugin +aws::kms::key = localstack.services.kms.resource_providers.aws_kms_key_plugin:KMSKeyProviderPlugin +aws::kinesis::stream = localstack.services.kinesis.resource_providers.aws_kinesis_stream_plugin:KinesisStreamProviderPlugin +aws::kinesis::streamconsumer = localstack.services.kinesis.resource_providers.aws_kinesis_streamconsumer_plugin:KinesisStreamConsumerProviderPlugin +aws::kinesisfirehose::deliverystream = localstack.services.kinesisfirehose.resource_providers.aws_kinesisfirehose_deliverystream_plugin:KinesisFirehoseDeliveryStreamProviderPlugin +aws::lambda::alias = localstack.services.lambda_.resource_providers.lambda_alias_plugin:LambdaAliasProviderPlugin +aws::lambda::codesigningconfig = localstack.services.lambda_.resource_providers.aws_lambda_codesigningconfig_plugin:LambdaCodeSigningConfigProviderPlugin +aws::lambda::eventinvokeconfig = localstack.services.lambda_.resource_providers.aws_lambda_eventinvokeconfig_plugin:LambdaEventInvokeConfigProviderPlugin +aws::lambda::eventsourcemapping = localstack.services.lambda_.resource_providers.aws_lambda_eventsourcemapping_plugin:LambdaEventSourceMappingProviderPlugin +aws::lambda::function = localstack.services.lambda_.resource_providers.generated.aws_lambda_function_plugin:LambdaFunctionProviderPlugin +aws::lambda::layerversion = localstack.services.lambda_.resource_providers.aws_lambda_layerversion_plugin:LambdaLayerVersionProviderPlugin +aws::lambda::layerversionpermission = localstack.services.lambda_.resource_providers.aws_lambda_layerversionpermission_plugin:LambdaLayerVersionPermissionProviderPlugin +aws::lambda::permission = localstack.services.lambda_.resource_providers.aws_lambda_permission_plugin:LambdaPermissionProviderPlugin +aws::lambda::url = localstack.services.lambda_.resource_providers.aws_lambda_url_plugin:LambdaUrlProviderPlugin +aws::lambda::version = localstack.services.lambda_.resource_providers.aws_lambda_version_plugin:LambdaVersionProviderPlugin +aws::logs::loggroup = localstack.services.logs.resource_providers.aws_logs_loggroup_plugin:LogsLogGroupProviderPlugin +aws::logs::logstream = localstack.services.logs.resource_providers.aws_logs_logstream_plugin:LogsLogStreamProviderPlugin +aws::logs::subscriptionfilter = localstack.services.logs.resource_providers.aws_logs_subscriptionfilter_plugin:LogsSubscriptionFilterProviderPlugin +aws::opensearchservice::domain = localstack.services.opensearch.resource_providers.aws_opensearchservice_domain_plugin:OpenSearchServiceDomainProviderPlugin +aws::redshift::cluster = localstack.services.redshift.resource_providers.aws_redshift_cluster_plugin:RedshiftClusterProviderPlugin +aws::resourcegroups::group = localstack.services.resource_groups.resource_providers.aws_resourcegroups_group_plugin:ResourceGroupsGroupProviderPlugin +aws::route53::healthcheck = localstack.services.route53.resource_providers.aws_route53_healthcheck_plugin:Route53HealthCheckProviderPlugin +aws::route53::recordset = localstack.services.route53.resource_providers.aws_route53_recordset_plugin:Route53RecordSetProviderPlugin +aws::s3::bucket = localstack.services.s3.resource_providers.aws_s3_bucket_plugin:S3BucketProviderPlugin +aws::s3::bucketpolicy = localstack.services.s3.resource_providers.aws_s3_bucketpolicy_plugin:S3BucketPolicyProviderPlugin +aws::ses::emailidentity = localstack.services.ses.resource_providers.aws_ses_emailidentity_plugin:SESEmailIdentityProviderPlugin +aws::sns::subscription = localstack.services.sns.resource_providers.aws_sns_subscription_plugin:SNSSubscriptionProviderPlugin +aws::sns::topic = localstack.services.sns.resource_providers.aws_sns_topic_plugin:SNSTopicProviderPlugin +aws::sns::topicpolicy = localstack.services.sns.resource_providers.aws_sns_topicpolicy_plugin:SNSTopicPolicyProviderPlugin +aws::sqs::queue = localstack.services.sqs.resource_providers.generated.aws_sqs_queue_plugin:SQSQueueProviderPlugin +aws::sqs::queueinlinepolicy = localstack.services.sqs.resource_providers.generated.aws_sqs_queueinlinepolicy_plugin:SQSQueueInlinePolicyProviderPlugin +aws::sqs::queuepolicy = localstack.services.sqs.resource_providers.generated.aws_sqs_queuepolicy_plugin:SQSQueuePolicyProviderPlugin +aws::ssm::maintenancewindow = localstack.services.ssm.resource_providers.aws_ssm_maintenancewindow_plugin:SSMMaintenanceWindowProviderPlugin +aws::ssm::maintenancewindowtarget = localstack.services.ssm.resource_providers.aws_ssm_maintenancewindowtarget_plugin:SSMMaintenanceWindowTargetProviderPlugin +aws::ssm::maintenancewindowtask = localstack.services.ssm.resource_providers.aws_ssm_maintenancewindowtask_plugin:SSMMaintenanceWindowTaskProviderPlugin +aws::ssm::parameter = localstack.services.ssm.resource_providers.aws_ssm_parameter_plugin:SSMParameterProviderPlugin +aws::ssm::patchbaseline = localstack.services.ssm.resource_providers.aws_ssm_patchbaseline_plugin:SSMPatchBaselineProviderPlugin +aws::scheduler::schedule = localstack.services.scheduler.resource_providers.aws_scheduler_schedule_plugin:SchedulerScheduleProviderPlugin +aws::scheduler::schedulegroup = localstack.services.scheduler.resource_providers.aws_scheduler_schedulegroup_plugin:SchedulerScheduleGroupProviderPlugin +aws::secretsmanager::resourcepolicy = localstack.services.secretsmanager.resource_providers.aws_secretsmanager_resourcepolicy_plugin:SecretsManagerResourcePolicyProviderPlugin +aws::secretsmanager::rotationschedule = localstack.services.secretsmanager.resource_providers.aws_secretsmanager_rotationschedule_plugin:SecretsManagerRotationScheduleProviderPlugin +aws::secretsmanager::secret = localstack.services.secretsmanager.resource_providers.aws_secretsmanager_secret_plugin:SecretsManagerSecretProviderPlugin +aws::secretsmanager::secrettargetattachment = localstack.services.secretsmanager.resource_providers.aws_secretsmanager_secrettargetattachment_plugin:SecretsManagerSecretTargetAttachmentProviderPlugin +aws::stepfunctions::activity = localstack.services.stepfunctions.resource_providers.aws_stepfunctions_activity_plugin:StepFunctionsActivityProviderPlugin +aws::stepfunctions::statemachine = localstack.services.stepfunctions.resource_providers.aws_stepfunctions_statemachine_plugin:StepFunctionsStateMachineProviderPlugin + +[localstack.hooks.configure_localstack_container] +_mount_machine_file = localstack.utils.analytics.metadata:_mount_machine_file + +[localstack.hooks.on_infra_ready] +_run_init_scripts_on_ready = localstack.runtime.init:_run_init_scripts_on_ready + +[localstack.hooks.on_infra_shutdown] +_run_init_scripts_on_shutdown = localstack.runtime.init:_run_init_scripts_on_shutdown +publish_metrics = localstack.utils.analytics.metrics.publisher:publish_metrics +remove_custom_endpoints = localstack.services.lambda_.plugins:remove_custom_endpoints +run_on_after_service_shutdown_handlers = localstack.runtime.shutdown:run_on_after_service_shutdown_handlers +run_shutdown_handlers = localstack.runtime.shutdown:run_shutdown_handlers +shutdown_services = localstack.runtime.shutdown:shutdown_services +stop_server = localstack.dns.plugins:stop_server + +[localstack.hooks.on_infra_start] +_patch_botocore_endpoint_in_memory = localstack.aws.client:_patch_botocore_endpoint_in_memory +_patch_botocore_json_parser = localstack.aws.client:_patch_botocore_json_parser +_patch_cbor2 = localstack.aws.client:_patch_cbor2 +_publish_config_as_analytics_event = localstack.runtime.analytics:_publish_config_as_analytics_event +_run_init_scripts_on_start = localstack.runtime.init:_run_init_scripts_on_start +apply_aws_runtime_patches = localstack.aws.patches:apply_aws_runtime_patches +apply_runtime_patches = localstack.runtime.patches:apply_runtime_patches +conditionally_enable_debugger = localstack.dev.debugger.plugins:conditionally_enable_debugger +delete_cached_certificate = localstack.plugins:delete_cached_certificate +deprecation_warnings = localstack.plugins:deprecation_warnings +eager_load_services = localstack.services.plugins:eager_load_services +init_response_mutation_handler = localstack.aws.handlers.response:init_response_mutation_handler +register_custom_endpoints = localstack.services.lambda_.plugins:register_custom_endpoints +register_swagger_endpoints = localstack.http.resources.swagger.plugins:register_swagger_endpoints +setup_dns_configuration_on_host = localstack.dns.plugins:setup_dns_configuration_on_host +start_dns_server = localstack.dns.plugins:start_dns_server +validate_configuration = localstack.services.lambda_.plugins:validate_configuration + +[localstack.hooks.prepare_host] +prepare_host_machine_id = localstack.utils.analytics.metadata:prepare_host_machine_id + +[localstack.init.runner] +py = localstack.runtime.init:PythonScriptRunner +sh = localstack.runtime.init:ShellScriptRunner + +[localstack.lambda.runtime_executor] +docker = localstack.services.lambda_.invocation.plugins:DockerRuntimeExecutorPlugin + +[localstack.openapi.spec] +localstack = localstack.plugins:CoreOASPlugin + +[localstack.packages] +dynamodb-local/community = localstack.services.dynamodb.plugins:dynamodb_local_package +elasticsearch/community = localstack.services.es.plugins:elasticsearch_package +ffmpeg/community = localstack.packages.plugins:ffmpeg_package +java/community = localstack.packages.plugins:java_package +jpype-jsonata/community = localstack.services.stepfunctions.plugins:jpype_jsonata_package +kinesis-mock/community = localstack.services.kinesis.plugins:kinesismock_package +lambda-java-libs/community = localstack.services.lambda_.plugins:lambda_java_libs +lambda-runtime/community = localstack.services.lambda_.plugins:lambda_runtime_package +opensearch/community = localstack.services.opensearch.plugins:opensearch_package +vosk/community = localstack.services.transcribe.plugins:vosk_package + +[localstack.runtime.components] +aws = localstack.aws.components:AwsComponents + +[localstack.runtime.server] +hypercorn = localstack.runtime.server.plugins:HypercornRuntimeServerPlugin +twisted = localstack.runtime.server.plugins:TwistedRuntimeServerPlugin + +[localstack.utils.catalog] +aws-catalog-remote-state = localstack.utils.catalog.catalog:AwsCatalogRemoteStatePlugin +aws-catalog-runtime-only = localstack.utils.catalog.catalog:AwsCatalogRuntimePlugin + diff --git a/pyproject.toml b/pyproject.toml index 88ffd3dc89913..878992c301468 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ # LocalStack project configuration [build-system] -requires = ['setuptools>=64', 'wheel', 'plux>=1.12', "setuptools_scm>=8.1"] +requires = ['setuptools>=64', 'wheel', 'setuptools_scm>=8.1'] build-backend = "setuptools.build_meta" [project] @@ -10,28 +10,26 @@ authors = [ ] description = "The core library and runtime of LocalStack" license = "Apache-2.0" -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ - "build", - "click>=7.1", + "asn1crypto>=1.5.1", + "click>=8.2.0", "cachetools>=5.0", "cryptography", - "dill==0.3.6", "dnslib>=0.9.10", "dnspython>=1.16.0", - "plux>=1.10", + "plux>=1.14.0", "psutil>=5.4.8", "python-dotenv>=0.19.1", "pyyaml>=5.1", "rich>=12.3.0", "requests>=2.20.0", "semver>=2.10", - "tailer>=0.4.1", ] -dynamic = ["version"] +dynamic = ["version", "entry-points"] classifiers = [ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.13", "Topic :: Internet", "Topic :: Software Development :: Testing", "Topic :: System :: Emulators", @@ -53,50 +51,58 @@ Issues = "https://github.com/localstack/localstack/issues" # minimal required to actually run localstack on the host for services natively implemented in python base-runtime = [ # pinned / updated by ASF update action - "boto3==1.39.4", + "boto3==1.42.59", # pinned / updated by ASF update action - "botocore==1.39.4", + "botocore==1.42.59", + # transitive dependency of botocore, added to avoid specific version "awscrt>=0.13.14,!=0.27.1", "cbor2>=5.5.0", + "dill==0.3.6", "dnspython>=1.16.0", "docker>=6.1.1", "jsonpatch>=1.24", + "jsonpointer>=3.0.0", + "jsonschema>=4.25.1", "hypercorn>=0.14.4", - "localstack-twisted>=23.0", + "localstack-twisted>=25.0", "openapi-core>=0.19.2", + "pydantic>=2.11.9", "pyopenssl>=23.0.0", + "python-dateutil>=2.9.0", "readerwriterlock>=1.0.7", "requests-aws4auth>=1.0", # explicitly set urllib3 to force its usage / ensure compatibility "urllib3>=2.0.7", "Werkzeug>=3.1.3", "xmltodict>=0.13.0", - "rolo>=0.7", + "rolo>=0.8.1", ] # required to actually run localstack on the host runtime = [ "localstack-core[base-runtime]", # pinned / updated by ASF update action - "awscli>=1.41.0", + "awscli==1.44.49", "airspeed-ext>=0.6.3", - # version that has a built wheel - "kclpy-ext>=3.0.0", # antlr4-python3-runtime: exact pin because antlr4 runtime is tightly coupled to the generated parser code "antlr4-python3-runtime==4.13.2", "apispec>=5.1.1", - "aws-sam-translator>=1.15.1", + # pinning it to a higher version than what moto-ext requires + "aws-sam-translator>=1.105.0", "crontab>=0.22.6", "cryptography>=41.0.5", - # allow Python programs full access to Java class libraries. Used for opt-in event ruler. - "jpype1-ext>=0.0.1", - "json5>=0.9.11", - "jsonpath-ng>=1.6.1", + "jinja2>=3.1.6", + # allow Python programs full access to Java class libraries. Used for stepfunctions jsonata support + "jpype1>=1.6.0", + "jsonpath-ng==1.7.0", "jsonpath-rw>=1.4.0", - "moto-ext[all]==5.1.6.post2", + # version that has a built wheel + "kclpy-ext>=3.0.0", + "moto-ext[all]>=5.1.22", "opensearch-py>=2.4.1", "pymongo>=4.2.0", "pyopenssl>=23.0.0", + "responses>=0.25.8", ] # for running tests and coverage analysis @@ -104,8 +110,8 @@ test = [ # runtime dependencies are required for running the tests "localstack-core[runtime]", "coverage[toml]>=5.5", - "deepdiff>=6.4.1", "httpx[http2]>=0.25", + "json5>=0.12.1", "pluggy>=1.3.0", "pytest>=7.4.2", "pytest-split>=0.8.0", @@ -122,6 +128,7 @@ dev = [ # test dependencies are required for developing localstack "localstack-core[test]", "coveralls>=3.3.1", + "deptry>=0.13.0", "Cython", "networkx>=2.8.4", "openapi-spec-validator>=0.7.1", @@ -131,6 +138,7 @@ dev = [ "ruff>=0.3.3", "rstr>=3.2.0", "mypy", + "watchdog>=6", ] # not strictly necessary for development, but provides type hint support for a better developer experience @@ -138,7 +146,7 @@ typehint = [ # typehint is an optional extension of the dev dependencies "localstack-core[dev]", # pinned / updated by ASF update action - "boto3-stubs[acm,acm-pca,amplify,apigateway,apigatewayv2,appconfig,appconfigdata,application-autoscaling,appsync,athena,autoscaling,backup,batch,ce,cloudcontrol,cloudformation,cloudfront,cloudtrail,cloudwatch,codebuild,codecommit,codeconnections,codedeploy,codepipeline,codestar-connections,cognito-identity,cognito-idp,dms,docdb,dynamodb,dynamodbstreams,ec2,ecr,ecs,efs,eks,elasticache,elasticbeanstalk,elbv2,emr,emr-serverless,es,events,firehose,fis,glacier,glue,iam,identitystore,iot,iot-data,iotanalytics,iotwireless,kafka,kinesis,kinesisanalytics,kinesisanalyticsv2,kms,lakeformation,lambda,logs,managedblockchain,mediaconvert,mediastore,mq,mwaa,neptune,opensearch,organizations,pi,pipes,pinpoint,qldb,qldb-session,rds,rds-data,redshift,redshift-data,resource-groups,resourcegroupstaggingapi,route53,route53resolver,s3,s3control,sagemaker,sagemaker-runtime,secretsmanager,serverlessrepo,servicediscovery,ses,sesv2,sns,sqs,ssm,sso-admin,stepfunctions,sts,timestream-query,timestream-write,transcribe,verifiedpermissions,wafv2,xray]", + "boto3-stubs[acm,acm-pca,amplify,apigateway,apigatewayv2,appconfig,appconfigdata,application-autoscaling,appsync,athena,autoscaling,backup,batch,ce,cloudcontrol,cloudformation,cloudfront,cloudtrail,cloudwatch,codebuild,codecommit,codeconnections,codedeploy,codepipeline,codestar-connections,cognito-identity,cognito-idp,dms,docdb,dynamodb,dynamodbstreams,ec2,ecr,ecs,efs,eks,elasticache,elasticbeanstalk,elbv2,emr,emr-serverless,es,events,firehose,fis,glacier,glue,iam,identitystore,iot,iot-data,iotwireless,kafka,kinesis,kinesisanalyticsv2,kms,lakeformation,lambda,logs,managedblockchain,mediaconvert,mq,mwaa,neptune,opensearch,organizations,pi,pipes,pinpoint,rds,rds-data,redshift,redshift-data,resource-groups,resourcegroupstaggingapi,route53,route53resolver,s3,s3control,sagemaker,sagemaker-runtime,secretsmanager,serverlessrepo,servicediscovery,ses,sesv2,sns,sqs,ssm,sso-admin,stepfunctions,sts,timestream-query,timestream-write,transcribe,verifiedpermissions,wafv2,xray]", ] [tool.setuptools] @@ -146,14 +154,13 @@ include-package-data = false # TODO using this is discouraged by setuptools, `project.scripts` should be used instead # However, `project.scripts` does not support non-python scripts. script-files = [ - "bin/localstack", - "bin/localstack.bat", "bin/localstack-supervisor", ] package-dir = { "" = "localstack-core" } [tool.setuptools.dynamic] readme = { file = ["README.md"], content-type = "text/markdown" } +entry-points = { file = ["plux.ini"] } [tool.setuptools.packages.find] where = ["localstack-core/"] @@ -168,7 +175,7 @@ exclude = ["tests*"] "localstack" = [ "aws/**/*.json", "services/**/*.html", - "services/**/resource_providers/*.schema.json", + "services/**/resource_providers/**/*.schema.json", "utils/kinesis/java/cloud/localstack/*.*", "openapi.yaml", "http/resources/swagger/templates/index.html" @@ -176,7 +183,7 @@ exclude = ["tests*"] [tool.ruff] # Generate code compatible with version defined in .python-version -target-version = "py311" +target-version = "py313" line-length = 100 src = ["localstack-core", "tests"] exclude = [ @@ -195,35 +202,73 @@ exclude = [ [tool.ruff.per-file-target-version] # Only allow minimum version for code used in the CLI -"localstack-core/localstack/cli/**" = "py39" -"localstack-core/localstack/packages/**" = "py39" -"localstack-core/localstack/config.py" = "py39" -"localstack-core/localstack/constants.py" = "py39" -"localstack-core/localstack/utils/analytics/**" = "py39" -"localstack-core/localstack/utils/bootstrap.py" = "py39" -"localstack-core/localstack/utils/json.py" = "py39" +"localstack-core/localstack/cli/**" = "py310" +"localstack-core/localstack/packages/**" = "py310" +"localstack-core/localstack/config.py" = "py310" +"localstack-core/localstack/constants.py" = "py310" +"localstack-core/localstack/utils/**" = "py310" # imported by CLI tests +"localstack-core/localstack/testing/pytest/**" = "py310" # imported by CLI tests +"localstack-core/localstack/aws/connect.py" = "py310" # imported by CLI tests +[tool.deptry] +known_first_party = [ + "vosk", # managed by localstack package manager + "debugpy", # managed by localstack package manager +] +extend_exclude = [ + "scripts/**", # dependencies not handled by pyproject.toml + "localstack-core/localstack/testing/**", # utilities for testing + "localstack-core/localstack/aws/mocking.py", # not used at runtime + "localstack-core/localstack/aws/scaffold.py", # not used at runtime + "localstack-core/localstack/dev/**", # internal dev tooling + "localstack-core/localstack/services/stepfunctions/asl/antlr/runtime/**" # generated code +] +pep621_dev_dependency_groups = ["dev", "typehint", "test"] + +[tool.deptry.package_module_name_map] +"localstack-core" = ["localstack"] + +[tool.deptry.per_rule_ignores] +DEP001 = [ + "com", # This appears in jpype code and imports actual java packages like com.fasterxml.jackson.databind + "stevedore", # used for CLI plugin debugging - TODO move this debugging CLI into plux +] +DEP002 = [ + "awscli", # necessary for python init scripts - TODO deprecate python init scripts and remove this + "awscrt", # defined to ignore a specific version - TODO evaluate and clean up +] [tool.ruff.lint] ignore = [ "B007", # TODO Loop control variable x not used within loop body - "B017", # TODO `pytest.raises(Exception)` should be considered evil "B019", # TODO Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks "B022", # TODO No arguments passed to `contextlib.suppress`. No exceptions will be suppressed and therefore this context manager is redundant "B023", # TODO Function definition does not bind loop variable `server` "B024", # TODO x is an abstract base class, but it has no abstract methods "B027", # TODO `Server.do_shutdown` is an empty method in an abstract base class, but has no abstract decorator "B904", # TODO Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling - "C408", # TODO Unnecessary `list` call (rewrite as a literal) - "C416", # TODO Unnecessary `set` comprehension "C901", # TODO function is too complex "E402", # TODO Module level import not at top of file "E501", # E501 Line too long - handled by black, see https://docs.astral.sh/ruff/faq/#is-ruff-compatible-with-black - "E721", # TODO Do not compare types, use `isinstance()` "T201", # TODO `print` found "T203", # TODO `pprint` found ] -select = ["B", "C", "E", "F", "I", "W", "T", "B9", "G"] +select = ["B", "C", "E", "F", "I", "W", "T", "B9", "G", "UP"] +extend-safe-fixes = [ + "UP006", # unsafe-fix for py39 + "UP035", # unsafe-fix for py39 +] + +# The rules below fill fix the code in a way that leaves multiple unused imports. +# Since F401 (removing unused imports) is currently an unsafe fix, these imports cannot be automatically removed. +# Therefore, we temporarily disable the rules below for __init__ files. +[tool.ruff.lint.per-file-ignores] +"tests/aws/services/lambda_/functions/**" = ["UP"] # lambda tests parametrize the runtime + +[tool.ruff.lint.pyupgrade] +# Avoid PEP 604 rewrites (e.g. Union[str, int] -> str | int), even with the `from __future__ import annotations` +# import. It only affects py39 and can be removed after we drop it. +keep-runtime-typing = true [tool.coverage.run] relative_files = true @@ -260,3 +305,6 @@ log_cli_date_format = "%Y-%m-%dT%H:%M:%S" # adding localstack-core itself here because it is referenced in the pyproject.toml for stacking the extras # pip, setuptools, and distribute are pip-tools defaults which need to be set again here unsafe-package = ["localstack-core", "pip", "setuptools", "distribute"] # packages that should not be pinned + +[tool.plux] +entrypoint_build_mode = "manual" diff --git a/requirements-base-runtime.txt b/requirements-base-runtime.txt index 1a63172ea035a..f89ff9902f151 100644 --- a/requirements-base-runtime.txt +++ b/requirements-base-runtime.txt @@ -1,40 +1,42 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile --extra=base-runtime --output-file=requirements-base-runtime.txt --strip-extras --unsafe-package=distribute --unsafe-package=localstack-core --unsafe-package=pip --unsafe-package=setuptools pyproject.toml # -attrs==25.3.0 +annotated-types==0.7.0 + # via pydantic +asn1crypto==1.5.1 + # via localstack-core (pyproject.toml) +attrs==25.4.0 # via # jsonschema # localstack-twisted # referencing -awscrt==0.27.4 +awscrt==0.31.2 # via localstack-core (pyproject.toml) -boto3==1.39.4 +boto3==1.42.59 # via localstack-core (pyproject.toml) -botocore==1.39.4 +botocore==1.42.59 # via # boto3 # localstack-core (pyproject.toml) # s3transfer -build==1.2.2.post1 - # via localstack-core (pyproject.toml) -cachetools==6.1.0 +cachetools==7.0.2 # via localstack-core (pyproject.toml) -cbor2==5.6.5 +cbor2==5.8.0 # via localstack-core (pyproject.toml) -certifi==2025.7.14 +certifi==2026.2.25 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography -charset-normalizer==3.4.2 +charset-normalizer==3.4.4 # via requests -click==8.2.1 +click==8.3.1 # via localstack-core (pyproject.toml) constantly==23.10.4 # via localstack-twisted -cryptography==45.0.5 +cryptography==46.0.5 # via # localstack-core (pyproject.toml) # pyopenssl @@ -42,7 +44,7 @@ dill==0.3.6 # via localstack-core (pyproject.toml) dnslib==0.9.26 # via localstack-core (pyproject.toml) -dnspython==2.7.0 +dnspython==2.8.0 # via localstack-core (pyproject.toml) docker==7.1.0 # via localstack-core (pyproject.toml) @@ -50,37 +52,40 @@ h11==0.16.0 # via # hypercorn # wsproto -h2==4.2.0 +h2==4.3.0 # via # hypercorn # localstack-twisted hpack==4.1.0 # via h2 -hypercorn==0.17.3 +hypercorn==0.18.0 # via localstack-core (pyproject.toml) hyperframe==6.1.0 # via h2 hyperlink==21.0.0 # via localstack-twisted -idna==3.10 +idna==3.11 # via # hyperlink # localstack-twisted # requests -incremental==24.7.2 +incremental==24.11.0 # via localstack-twisted isodate==0.7.2 # via openapi-core -jmespath==1.0.1 +jmespath==1.1.0 # via # boto3 # botocore jsonpatch==1.33 # via localstack-core (pyproject.toml) jsonpointer==3.0.0 - # via jsonpatch -jsonschema==4.24.0 # via + # jsonpatch + # localstack-core (pyproject.toml) +jsonschema==4.26.0 + # via + # localstack-core (pyproject.toml) # openapi-core # openapi-schema-validator # openapi-spec-validator @@ -88,23 +93,23 @@ jsonschema-path==0.3.4 # via # openapi-core # openapi-spec-validator -jsonschema-specifications==2025.4.1 +jsonschema-specifications==2025.9.1 # via # jsonschema # openapi-schema-validator -lazy-object-proxy==1.11.0 +lazy-object-proxy==1.12.0 # via openapi-spec-validator -localstack-twisted==24.3.0 +localstack-twisted==25.5.0 # via localstack-core (pyproject.toml) -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich -markupsafe==3.0.2 +markupsafe==3.0.3 # via werkzeug mdurl==0.1.2 # via markdown-it-py -more-itertools==10.7.0 +more-itertools==10.8.0 # via openapi-core -openapi-core==0.19.4 +openapi-core==0.22.0 # via localstack-core (pyproject.toml) openapi-schema-validator==0.6.3 # via @@ -112,35 +117,37 @@ openapi-schema-validator==0.6.3 # openapi-spec-validator openapi-spec-validator==0.7.2 # via openapi-core -packaging==25.0 - # via build -parse==1.20.2 - # via openapi-core +packaging==26.0 + # via incremental pathable==0.4.4 # via jsonschema-path -plux==1.12.1 +plux==1.14.0 # via localstack-core (pyproject.toml) priority==1.3.0 # via # hypercorn # localstack-twisted -psutil==7.0.0 +psutil==7.2.2 # via localstack-core (pyproject.toml) -pycparser==2.22 +pycparser==3.0 # via cffi +pydantic==2.12.5 + # via localstack-core (pyproject.toml) +pydantic-core==2.41.5 + # via pydantic pygments==2.19.2 # via rich -pyopenssl==25.1.0 +pyopenssl==25.3.0 # via # localstack-core (pyproject.toml) # localstack-twisted -pyproject-hooks==1.2.0 - # via build python-dateutil==2.9.0.post0 - # via botocore -python-dotenv==1.1.1 + # via + # botocore + # localstack-core (pyproject.toml) +python-dotenv==1.2.2 # via localstack-core (pyproject.toml) -pyyaml==6.0.2 +pyyaml==6.0.3 # via # jsonschema-path # localstack-core (pyproject.toml) @@ -151,7 +158,7 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -requests==2.32.4 +requests==2.32.5 # via # docker # jsonschema-path @@ -162,15 +169,15 @@ requests-aws4auth==1.3.1 # via localstack-core (pyproject.toml) rfc3339-validator==0.1.4 # via openapi-schema-validator -rich==14.0.0 +rich==14.3.3 # via localstack-core (pyproject.toml) -rolo==0.7.6 +rolo==0.8.1 # via localstack-core (pyproject.toml) -rpds-py==0.26.0 +rpds-py==0.30.0 # via # jsonschema # referencing -s3transfer==0.13.0 +s3transfer==0.16.0 # via boto3 semver==3.0.4 # via localstack-core (pyproject.toml) @@ -178,31 +185,30 @@ six==1.17.0 # via # python-dateutil # rfc3339-validator -tailer==0.4.1 - # via localstack-core (pyproject.toml) -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via # localstack-twisted - # pyopenssl + # openapi-core + # pydantic + # pydantic-core # readerwriterlock - # referencing -urllib3==2.5.0 + # typing-inspection +typing-inspection==0.4.2 + # via pydantic +urllib3==2.6.3 # via # botocore # docker # localstack-core (pyproject.toml) # requests -werkzeug==3.1.3 +werkzeug==3.1.6 # via # localstack-core (pyproject.toml) # openapi-core # rolo -wsproto==1.2.0 +wsproto==1.3.2 # via hypercorn -xmltodict==0.14.2 +xmltodict==1.0.4 # via localstack-core (pyproject.toml) -zope-interface==7.2 +zope-interface==8.2 # via localstack-twisted - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements-basic.txt b/requirements-basic.txt index e670f920bf911..06adc17bab3ca 100644 --- a/requirements-basic.txt +++ b/requirements-basic.txt @@ -1,58 +1,50 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile --output-file=requirements-basic.txt --strip-extras --unsafe-package=distribute --unsafe-package=localstack-core --unsafe-package=pip --unsafe-package=setuptools pyproject.toml # -build==1.2.2.post1 +asn1crypto==1.5.1 # via localstack-core (pyproject.toml) -cachetools==6.1.0 +cachetools==7.0.2 # via localstack-core (pyproject.toml) -certifi==2025.7.14 +certifi==2026.2.25 # via requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography -charset-normalizer==3.4.2 +charset-normalizer==3.4.4 # via requests -click==8.2.1 +click==8.3.1 # via localstack-core (pyproject.toml) -cryptography==45.0.5 - # via localstack-core (pyproject.toml) -dill==0.3.6 +cryptography==46.0.5 # via localstack-core (pyproject.toml) dnslib==0.9.26 # via localstack-core (pyproject.toml) -dnspython==2.7.0 +dnspython==2.8.0 # via localstack-core (pyproject.toml) -idna==3.10 +idna==3.11 # via requests -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -packaging==25.0 - # via build -plux==1.12.1 +plux==1.14.0 # via localstack-core (pyproject.toml) -psutil==7.0.0 +psutil==7.2.2 # via localstack-core (pyproject.toml) -pycparser==2.22 +pycparser==3.0 # via cffi pygments==2.19.2 # via rich -pyproject-hooks==1.2.0 - # via build -python-dotenv==1.1.1 +python-dotenv==1.2.2 # via localstack-core (pyproject.toml) -pyyaml==6.0.2 +pyyaml==6.0.3 # via localstack-core (pyproject.toml) -requests==2.32.4 +requests==2.32.5 # via localstack-core (pyproject.toml) -rich==14.0.0 +rich==14.3.3 # via localstack-core (pyproject.toml) semver==3.0.4 # via localstack-core (pyproject.toml) -tailer==0.4.1 - # via localstack-core (pyproject.toml) -urllib3==2.5.0 +urllib3==2.6.3 # via requests diff --git a/requirements-dev.txt b/requirements-dev.txt index 70e47448f694c..542e17131d269 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,55 +1,62 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile --extra=dev --output-file=requirements-dev.txt --strip-extras --unsafe-package=distribute --unsafe-package=localstack-core --unsafe-package=pip --unsafe-package=setuptools pyproject.toml # airspeed-ext==0.6.9 # via localstack-core +annotated-doc==0.0.4 + # via typer annotated-types==0.7.0 # via pydantic antlr4-python3-runtime==4.13.2 # via # localstack-core # moto-ext -anyio==4.9.0 +anyio==4.12.1 # via httpx -apispec==6.8.2 +apispec==6.9.0 # via localstack-core argparse==1.4.0 # via kclpy-ext -attrs==25.3.0 +asn1crypto==1.5.1 + # via + # localstack-core + # localstack-core (pyproject.toml) +attrs==25.4.0 # via # cattrs # jsii # jsonschema # localstack-twisted # referencing -aws-cdk-asset-awscli-v1==2.2.242 +aws-cdk-asset-awscli-v1==2.2.263 # via aws-cdk-lib -aws-cdk-asset-node-proxy-agent-v6==2.1.0 +aws-cdk-asset-node-proxy-agent-v6==2.1.1 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==45.2.0 +aws-cdk-cloud-assembly-schema==52.2.0 # via aws-cdk-lib -aws-cdk-lib==2.204.0 +aws-cdk-lib==2.241.0 # via localstack-core -aws-sam-translator==1.99.0 +aws-sam-translator==1.107.0 # via # cfn-lint # localstack-core -aws-xray-sdk==2.14.0 + # moto-ext +aws-xray-sdk==2.15.0 # via moto-ext -awscli==1.41.4 +awscli==1.44.49 # via localstack-core -awscrt==0.27.4 +awscrt==0.31.2 # via localstack-core -boto3==1.39.4 +boto3==1.42.59 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -botocore==1.39.4 +botocore==1.42.59 # via # aws-xray-sdk # awscli @@ -57,77 +64,73 @@ botocore==1.39.4 # localstack-core # moto-ext # s3transfer -build==1.2.2.post1 - # via - # localstack-core - # localstack-core (pyproject.toml) -cachetools==6.1.0 +cachetools==7.0.2 # via # airspeed-ext # localstack-core # localstack-core (pyproject.toml) -cattrs==24.1.3 +cattrs==25.3.0 # via jsii -cbor2==5.6.5 +cbor2==5.8.0 # via localstack-core -certifi==2025.7.14 +certifi==2026.2.25 # via # httpcore # httpx # opensearch-py # requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography -cfgv==3.4.0 +cfgv==3.5.0 # via pre-commit -cfn-lint==1.38.0 +cfn-lint==1.46.0 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.4 # via requests -click==8.2.1 +click==8.3.1 # via + # deptry # localstack-core # localstack-core (pyproject.toml) + # typer colorama==0.4.6 # via awscli constantly==23.10.4 # via localstack-twisted -constructs==10.4.2 +constructs==10.5.1 # via aws-cdk-lib -coverage==7.9.2 +coverage==7.13.4 # via # coveralls # localstack-core -coveralls==4.0.1 +coveralls==4.1.0 # via localstack-core (pyproject.toml) crontab==1.0.5 # via localstack-core -cryptography==45.0.5 +cryptography==46.0.5 # via # joserfc # localstack-core # localstack-core (pyproject.toml) # moto-ext # pyopenssl -cython==3.1.2 +cython==3.2.4 # via localstack-core (pyproject.toml) decorator==5.2.1 # via jsonpath-rw -deepdiff==8.5.0 - # via - # localstack-core - # localstack-snapshot +deepdiff==8.6.1 + # via localstack-snapshot +deptry==0.24.0 + # via localstack-core (pyproject.toml) dill==0.3.6 - # via - # localstack-core - # localstack-core (pyproject.toml) -distlib==0.3.9 + # via localstack-core +distlib==0.4.0 # via virtualenv dnslib==0.9.26 # via # localstack-core # localstack-core (pyproject.toml) -dnspython==2.7.0 +dnspython==2.8.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -136,22 +139,24 @@ docker==7.1.0 # via # localstack-core # moto-ext -docopt==0.6.2 - # via coveralls docutils==0.19 # via awscli events==0.5 # via opensearch-py -filelock==3.18.0 - # via virtualenv -graphql-core==3.2.6 +filelock==3.25.0 + # via + # python-discovery + # virtualenv +graphql-core==3.2.7 # via moto-ext +grpcio==1.78.0 + # via opensearch-protobufs h11==0.16.0 # via # httpcore # hypercorn # wsproto -h2==4.2.0 +h2==4.3.0 # via # httpx # hypercorn @@ -162,15 +167,15 @@ httpcore==1.0.9 # via httpx httpx==0.28.1 # via localstack-core -hypercorn==0.17.3 +hypercorn==0.18.0 # via localstack-core hyperframe==6.1.0 # via h2 hyperlink==21.0.0 # via localstack-twisted -identify==2.6.12 +identify==2.6.17 # via pre-commit -idna==3.10 +idna==3.11 # via # anyio # httpx @@ -179,30 +184,32 @@ idna==3.10 # requests importlib-resources==6.5.2 # via jsii -incremental==24.7.2 +incremental==24.11.0 # via localstack-twisted -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest isodate==0.7.2 # via openapi-core jinja2==3.1.6 - # via moto-ext -jmespath==1.0.1 + # via + # localstack-core + # moto-ext +jmespath==1.1.0 # via # boto3 # botocore -joserfc==1.2.2 +joserfc==1.6.3 # via moto-ext -jpype1-ext==0.0.2 +jpype1==1.6.0 # via localstack-core -jsii==1.112.0 +jsii==1.127.0 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 # aws-cdk-cloud-assembly-schema # aws-cdk-lib # constructs -json5==0.12.0 +json5==0.13.0 # via localstack-core jsonpatch==1.33 # via @@ -216,10 +223,13 @@ jsonpath-ng==1.7.0 jsonpath-rw==1.4.0 # via localstack-core jsonpointer==3.0.0 - # via jsonpatch -jsonschema==4.24.0 + # via + # jsonpatch + # localstack-core +jsonschema==4.26.0 # via # aws-sam-translator + # localstack-core # moto-ext # openapi-core # openapi-schema-validator @@ -228,45 +238,47 @@ jsonschema-path==0.3.4 # via # openapi-core # openapi-spec-validator -jsonschema-specifications==2025.4.1 +jsonschema-specifications==2025.9.1 # via # jsonschema # openapi-schema-validator kclpy-ext==3.0.5 # via localstack-core -lazy-object-proxy==1.11.0 +lazy-object-proxy==1.12.0 # via openapi-spec-validator -localstack-snapshot==0.3.0 +librt==0.8.1 + # via mypy +localstack-snapshot==0.3.3 # via localstack-core -localstack-twisted==24.3.0 +localstack-twisted==25.5.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug mdurl==0.1.2 # via markdown-it-py -more-itertools==10.7.0 +more-itertools==10.8.0 # via openapi-core -moto-ext==5.1.6.post2 +moto-ext==5.1.25 # via localstack-core mpmath==1.3.0 # via sympy -multipart==1.2.1 +multipart==1.3.1 # via moto-ext -mypy==1.17.0 +mypy==1.19.1 # via localstack-core (pyproject.toml) mypy-extensions==1.1.0 # via mypy -networkx==3.5 +networkx==3.6.1 # via # cfn-lint # localstack-core (pyproject.toml) -nodeenv==1.9.1 +nodeenv==1.10.0 # via pre-commit -openapi-core==0.19.4 +openapi-core==0.22.0 # via localstack-core openapi-schema-validator==0.6.3 # via @@ -277,34 +289,38 @@ openapi-spec-validator==0.7.2 # localstack-core (pyproject.toml) # moto-ext # openapi-core -opensearch-py==3.0.0 +opensearch-protobufs==0.19.0 + # via opensearch-py +opensearch-py==3.1.0 # via localstack-core orderly-set==5.5.0 # via deepdiff -packaging==25.0 +packaging==26.0 # via # apispec - # build - # jpype1-ext + # deptry + # incremental + # jpype1 # pytest # pytest-rerunfailures + # requirements-parser pandoc==2.4 # via localstack-core (pyproject.toml) -parse==1.20.2 - # via openapi-core pathable==0.4.4 # via jsonschema-path -pathspec==0.12.1 +pathspec==1.0.4 # via mypy -platformdirs==4.3.8 - # via virtualenv +platformdirs==4.9.2 + # via + # python-discovery + # virtualenv pluggy==1.6.0 # via # localstack-core # pytest -plumbum==1.9.0 +plumbum==1.10.0 # via pandoc -plux==1.12.1 +plux==1.14.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -313,13 +329,15 @@ ply==3.11 # jsonpath-ng # jsonpath-rw # pandoc -pre-commit==4.2.0 +pre-commit==4.5.1 # via localstack-core (pyproject.toml) priority==1.3.0 # via # hypercorn # localstack-twisted -psutil==7.0.0 +protobuf==7.34.0 + # via opensearch-protobufs +psutil==7.2.2 # via # localstack-core # localstack-core (pyproject.toml) @@ -331,43 +349,44 @@ publication==0.0.3 # aws-cdk-lib # constructs # jsii -py-partiql-parser==0.6.1 +py-partiql-parser==0.6.3 # via moto-ext -pyasn1==0.6.1 +pyasn1==0.6.2 # via rsa -pycparser==2.22 +pycparser==3.0 # via cffi -pydantic==2.11.7 - # via aws-sam-translator -pydantic-core==2.33.2 +pydantic==2.12.5 + # via + # aws-sam-translator + # localstack-core + # openapi-spec-validator +pydantic-core==2.41.5 # via pydantic pygments==2.19.2 # via # pytest # rich -pymongo==4.13.2 +pymongo==4.16.0 # via localstack-core -pyopenssl==25.1.0 +pyopenssl==25.3.0 # via # localstack-core # localstack-twisted -pypandoc==1.15 +pypandoc==1.16.2 # via localstack-core (pyproject.toml) -pyparsing==3.2.3 +pyparsing==3.3.2 # via moto-ext -pyproject-hooks==1.2.0 - # via build -pytest==8.4.1 +pytest==9.0.2 # via # localstack-core # pytest-rerunfailures # pytest-split # pytest-tinybird -pytest-httpserver==1.1.3 +pytest-httpserver==1.1.5 # via localstack-core -pytest-rerunfailures==15.1 +pytest-rerunfailures==16.1 # via localstack-core -pytest-split==0.10.0 +pytest-split==0.11.0 # via localstack-core pytest-tinybird==0.5.0 # via localstack-core @@ -375,13 +394,16 @@ python-dateutil==2.9.0.post0 # via # botocore # jsii + # localstack-core # moto-ext # opensearch-py -python-dotenv==1.1.1 +python-discovery==1.1.0 + # via virtualenv +python-dotenv==1.2.2 # via # localstack-core # localstack-core (pyproject.toml) -pyyaml==6.0.2 +pyyaml==6.0.3 # via # awscli # cfn-lint @@ -398,9 +420,9 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2026.2.28 # via cfn-lint -requests==2.32.4 +requests==2.32.5 # via # coveralls # docker @@ -415,17 +437,22 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 - # via moto-ext +requirements-parser==0.13.0 + # via deptry +responses==0.26.0 + # via + # localstack-core + # moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator -rich==14.0.0 +rich==14.3.3 # via # localstack-core # localstack-core (pyproject.toml) -rolo==0.7.6 + # typer +rolo==0.8.1 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.30.0 # via # jsonschema # referencing @@ -433,9 +460,9 @@ rsa==4.7.2 # via awscli rstr==3.2.2 # via localstack-core (pyproject.toml) -ruff==0.12.3 +ruff==0.15.4 # via localstack-core (pyproject.toml) -s3transfer==0.13.0 +s3transfer==0.16.0 # via # awscli # boto3 @@ -443,20 +470,16 @@ semver==3.0.4 # via # localstack-core # localstack-core (pyproject.toml) +shellingham==1.5.4 + # via typer six==1.17.0 # via # airspeed-ext # jsonpath-rw # python-dateutil # rfc3339-validator -sniffio==1.3.1 - # via anyio sympy==1.14.0 # via cfn-lint -tailer==0.4.1 - # via - # localstack-core - # localstack-core (pyproject.toml) typeguard==2.13.3 # via # aws-cdk-asset-awscli-v1 @@ -465,23 +488,25 @@ typeguard==2.13.3 # aws-cdk-lib # constructs # jsii -typing-extensions==4.14.1 +typer==0.24.1 + # via coveralls +typing-extensions==4.15.0 # via - # anyio # aws-sam-translator + # cattrs # cfn-lint + # grpcio # jsii # localstack-twisted # mypy + # openapi-core # pydantic # pydantic-core - # pyopenssl # readerwriterlock - # referencing # typing-inspection -typing-inspection==0.4.1 +typing-inspection==0.4.2 # via pydantic -urllib3==2.5.0 +urllib3==2.6.3 # via # botocore # docker @@ -489,26 +514,28 @@ urllib3==2.5.0 # opensearch-py # requests # responses -virtualenv==20.31.2 +virtualenv==21.1.0 # via pre-commit -websocket-client==1.8.0 +watchdog==6.0.0 + # via localstack-core (pyproject.toml) +websocket-client==1.9.0 # via localstack-core -werkzeug==3.1.3 +werkzeug==3.1.6 # via # localstack-core # moto-ext # openapi-core # pytest-httpserver # rolo -wrapt==1.17.2 +wrapt==2.1.1 # via aws-xray-sdk -wsproto==1.2.0 +wsproto==1.3.2 # via hypercorn -xmltodict==0.14.2 +xmltodict==1.0.4 # via # localstack-core # moto-ext -zope-interface==7.2 +zope-interface==8.2 # via localstack-twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements-runtime.txt b/requirements-runtime.txt index 8bf9f052e73a8..55d85a384d0cc 100644 --- a/requirements-runtime.txt +++ b/requirements-runtime.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile --extra=runtime --output-file=requirements-runtime.txt --strip-extras --unsafe-package=distribute --unsafe-package=localstack-core --unsafe-package=pip --unsafe-package=setuptools pyproject.toml @@ -12,32 +12,37 @@ antlr4-python3-runtime==4.13.2 # via # localstack-core (pyproject.toml) # moto-ext -apispec==6.8.2 +apispec==6.9.0 # via localstack-core (pyproject.toml) argparse==1.4.0 # via kclpy-ext -attrs==25.3.0 +asn1crypto==1.5.1 + # via + # localstack-core + # localstack-core (pyproject.toml) +attrs==25.4.0 # via # jsonschema # localstack-twisted # referencing -aws-sam-translator==1.99.0 +aws-sam-translator==1.107.0 # via # cfn-lint # localstack-core (pyproject.toml) -aws-xray-sdk==2.14.0 + # moto-ext +aws-xray-sdk==2.15.0 # via moto-ext -awscli==1.41.4 +awscli==1.44.49 # via localstack-core (pyproject.toml) -awscrt==0.27.4 +awscrt==0.31.2 # via localstack-core -boto3==1.39.4 +boto3==1.42.59 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -botocore==1.39.4 +botocore==1.42.59 # via # aws-xray-sdk # awscli @@ -45,28 +50,24 @@ botocore==1.39.4 # localstack-core # moto-ext # s3transfer -build==1.2.2.post1 - # via - # localstack-core - # localstack-core (pyproject.toml) -cachetools==6.1.0 +cachetools==7.0.2 # via # airspeed-ext # localstack-core # localstack-core (pyproject.toml) -cbor2==5.6.5 +cbor2==5.8.0 # via localstack-core -certifi==2025.7.14 +certifi==2026.2.25 # via # opensearch-py # requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography -cfn-lint==1.38.0 +cfn-lint==1.46.0 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.4 # via requests -click==8.2.1 +click==8.3.1 # via # localstack-core # localstack-core (pyproject.toml) @@ -76,7 +77,7 @@ constantly==23.10.4 # via localstack-twisted crontab==1.0.5 # via localstack-core (pyproject.toml) -cryptography==45.0.5 +cryptography==46.0.5 # via # joserfc # localstack-core @@ -86,14 +87,12 @@ cryptography==45.0.5 decorator==5.2.1 # via jsonpath-rw dill==0.3.6 - # via - # localstack-core - # localstack-core (pyproject.toml) + # via localstack-core dnslib==0.9.26 # via # localstack-core # localstack-core (pyproject.toml) -dnspython==2.7.0 +dnspython==2.8.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -106,44 +105,46 @@ docutils==0.19 # via awscli events==0.5 # via opensearch-py -graphql-core==3.2.6 +graphql-core==3.2.7 # via moto-ext +grpcio==1.78.0 + # via opensearch-protobufs h11==0.16.0 # via # hypercorn # wsproto -h2==4.2.0 +h2==4.3.0 # via # hypercorn # localstack-twisted hpack==4.1.0 # via h2 -hypercorn==0.17.3 +hypercorn==0.18.0 # via localstack-core hyperframe==6.1.0 # via h2 hyperlink==21.0.0 # via localstack-twisted -idna==3.10 +idna==3.11 # via # hyperlink # localstack-twisted # requests -incremental==24.7.2 +incremental==24.11.0 # via localstack-twisted isodate==0.7.2 # via openapi-core jinja2==3.1.6 - # via moto-ext -jmespath==1.0.1 + # via + # localstack-core (pyproject.toml) + # moto-ext +jmespath==1.1.0 # via # boto3 # botocore -joserfc==1.2.2 +joserfc==1.6.3 # via moto-ext -jpype1-ext==0.0.2 - # via localstack-core (pyproject.toml) -json5==0.12.0 +jpype1==1.6.0 # via localstack-core (pyproject.toml) jsonpatch==1.33 # via @@ -156,10 +157,13 @@ jsonpath-ng==1.7.0 jsonpath-rw==1.4.0 # via localstack-core (pyproject.toml) jsonpointer==3.0.0 - # via jsonpatch -jsonschema==4.24.0 + # via + # jsonpatch + # localstack-core +jsonschema==4.26.0 # via # aws-sam-translator + # localstack-core # moto-ext # openapi-core # openapi-schema-validator @@ -168,35 +172,35 @@ jsonschema-path==0.3.4 # via # openapi-core # openapi-spec-validator -jsonschema-specifications==2025.4.1 +jsonschema-specifications==2025.9.1 # via # jsonschema # openapi-schema-validator kclpy-ext==3.0.5 # via localstack-core (pyproject.toml) -lazy-object-proxy==1.11.0 +lazy-object-proxy==1.12.0 # via openapi-spec-validator -localstack-twisted==24.3.0 +localstack-twisted==25.5.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug mdurl==0.1.2 # via markdown-it-py -more-itertools==10.7.0 +more-itertools==10.8.0 # via openapi-core -moto-ext==5.1.6.post2 +moto-ext==5.1.25 # via localstack-core (pyproject.toml) mpmath==1.3.0 # via sympy -multipart==1.2.1 +multipart==1.3.1 # via moto-ext -networkx==3.5 +networkx==3.6.1 # via cfn-lint -openapi-core==0.19.4 +openapi-core==0.22.0 # via localstack-core openapi-schema-validator==0.6.3 # via @@ -206,18 +210,18 @@ openapi-spec-validator==0.7.2 # via # moto-ext # openapi-core -opensearch-py==3.0.0 +opensearch-protobufs==0.19.0 + # via opensearch-py +opensearch-py==3.1.0 # via localstack-core (pyproject.toml) -packaging==25.0 +packaging==26.0 # via # apispec - # build - # jpype1-ext -parse==1.20.2 - # via openapi-core + # incremental + # jpype1 pathable==0.4.4 # via jsonschema-path -plux==1.12.1 +plux==1.14.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -229,43 +233,46 @@ priority==1.3.0 # via # hypercorn # localstack-twisted -psutil==7.0.0 +protobuf==7.34.0 + # via opensearch-protobufs +psutil==7.2.2 # via # localstack-core # localstack-core (pyproject.toml) -py-partiql-parser==0.6.1 +py-partiql-parser==0.6.3 # via moto-ext -pyasn1==0.6.1 +pyasn1==0.6.2 # via rsa -pycparser==2.22 +pycparser==3.0 # via cffi -pydantic==2.11.7 - # via aws-sam-translator -pydantic-core==2.33.2 +pydantic==2.12.5 + # via + # aws-sam-translator + # localstack-core +pydantic-core==2.41.5 # via pydantic pygments==2.19.2 # via rich -pymongo==4.13.2 +pymongo==4.16.0 # via localstack-core (pyproject.toml) -pyopenssl==25.1.0 +pyopenssl==25.3.0 # via # localstack-core # localstack-core (pyproject.toml) # localstack-twisted -pyparsing==3.2.3 +pyparsing==3.3.2 # via moto-ext -pyproject-hooks==1.2.0 - # via build python-dateutil==2.9.0.post0 # via # botocore + # localstack-core # moto-ext # opensearch-py -python-dotenv==1.1.1 +python-dotenv==1.2.2 # via # localstack-core # localstack-core (pyproject.toml) -pyyaml==6.0.2 +pyyaml==6.0.3 # via # awscli # cfn-lint @@ -281,9 +288,9 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2026.2.28 # via cfn-lint -requests==2.32.4 +requests==2.32.5 # via # docker # jsonschema-path @@ -296,23 +303,25 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 - # via moto-ext +responses==0.26.0 + # via + # localstack-core (pyproject.toml) + # moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator -rich==14.0.0 +rich==14.3.3 # via # localstack-core # localstack-core (pyproject.toml) -rolo==0.7.6 +rolo==0.8.1 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.30.0 # via # jsonschema # referencing rsa==4.7.2 # via awscli -s3transfer==0.13.0 +s3transfer==0.16.0 # via # awscli # boto3 @@ -328,24 +337,20 @@ six==1.17.0 # rfc3339-validator sympy==1.14.0 # via cfn-lint -tailer==0.4.1 - # via - # localstack-core - # localstack-core (pyproject.toml) -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via # aws-sam-translator # cfn-lint + # grpcio # localstack-twisted + # openapi-core # pydantic # pydantic-core - # pyopenssl # readerwriterlock - # referencing # typing-inspection -typing-inspection==0.4.1 +typing-inspection==0.4.2 # via pydantic -urllib3==2.5.0 +urllib3==2.6.3 # via # botocore # docker @@ -353,21 +358,21 @@ urllib3==2.5.0 # opensearch-py # requests # responses -werkzeug==3.1.3 +werkzeug==3.1.6 # via # localstack-core # moto-ext # openapi-core # rolo -wrapt==1.17.2 +wrapt==2.1.1 # via aws-xray-sdk -wsproto==1.2.0 +wsproto==1.3.2 # via hypercorn -xmltodict==0.14.2 +xmltodict==1.0.4 # via # localstack-core # moto-ext -zope-interface==7.2 +zope-interface==8.2 # via localstack-twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements-test.txt b/requirements-test.txt index bcfaded15e52f..25d17b3e1118b 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile --extra=test --output-file=requirements-test.txt --strip-extras --unsafe-package=distribute --unsafe-package=localstack-core --unsafe-package=pip --unsafe-package=setuptools pyproject.toml @@ -12,44 +12,49 @@ antlr4-python3-runtime==4.13.2 # via # localstack-core # moto-ext -anyio==4.9.0 +anyio==4.12.1 # via httpx -apispec==6.8.2 +apispec==6.9.0 # via localstack-core argparse==1.4.0 # via kclpy-ext -attrs==25.3.0 +asn1crypto==1.5.1 + # via + # localstack-core + # localstack-core (pyproject.toml) +attrs==25.4.0 # via # cattrs # jsii # jsonschema # localstack-twisted # referencing -aws-cdk-asset-awscli-v1==2.2.242 +aws-cdk-asset-awscli-v1==2.2.263 # via aws-cdk-lib -aws-cdk-asset-node-proxy-agent-v6==2.1.0 +aws-cdk-asset-node-proxy-agent-v6==2.1.1 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==45.2.0 +aws-cdk-cloud-assembly-schema==52.2.0 # via aws-cdk-lib -aws-cdk-lib==2.204.0 +aws-cdk-lib==2.241.0 # via localstack-core (pyproject.toml) -aws-sam-translator==1.99.0 +aws-sam-translator==1.107.0 # via # cfn-lint # localstack-core -aws-xray-sdk==2.14.0 + # moto-ext +aws-xray-sdk==2.15.0 # via moto-ext -awscli==1.41.4 +awscli==1.44.49 # via localstack-core -awscrt==0.27.4 +awscrt==0.31.2 # via localstack-core -boto3==1.39.4 +boto3==1.42.59 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -botocore==1.39.4 +botocore==1.42.59 # via # aws-xray-sdk # awscli @@ -57,32 +62,28 @@ botocore==1.39.4 # localstack-core # moto-ext # s3transfer -build==1.2.2.post1 - # via - # localstack-core - # localstack-core (pyproject.toml) -cachetools==6.1.0 +cachetools==7.0.2 # via # airspeed-ext # localstack-core # localstack-core (pyproject.toml) -cattrs==24.1.3 +cattrs==25.3.0 # via jsii -cbor2==5.6.5 +cbor2==5.8.0 # via localstack-core -certifi==2025.7.14 +certifi==2026.2.25 # via # httpcore # httpx # opensearch-py # requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography -cfn-lint==1.38.0 +cfn-lint==1.46.0 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.4 # via requests -click==8.2.1 +click==8.3.1 # via # localstack-core # localstack-core (pyproject.toml) @@ -90,13 +91,13 @@ colorama==0.4.6 # via awscli constantly==23.10.4 # via localstack-twisted -constructs==10.4.2 +constructs==10.5.1 # via aws-cdk-lib -coverage==7.9.2 +coverage==7.13.4 # via localstack-core (pyproject.toml) crontab==1.0.5 # via localstack-core -cryptography==45.0.5 +cryptography==46.0.5 # via # joserfc # localstack-core @@ -105,19 +106,15 @@ cryptography==45.0.5 # pyopenssl decorator==5.2.1 # via jsonpath-rw -deepdiff==8.5.0 - # via - # localstack-core (pyproject.toml) - # localstack-snapshot +deepdiff==8.6.1 + # via localstack-snapshot dill==0.3.6 - # via - # localstack-core - # localstack-core (pyproject.toml) + # via localstack-core dnslib==0.9.26 # via # localstack-core # localstack-core (pyproject.toml) -dnspython==2.7.0 +dnspython==2.8.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -130,14 +127,16 @@ docutils==0.19 # via awscli events==0.5 # via opensearch-py -graphql-core==3.2.6 +graphql-core==3.2.7 # via moto-ext +grpcio==1.78.0 + # via opensearch-protobufs h11==0.16.0 # via # httpcore # hypercorn # wsproto -h2==4.2.0 +h2==4.3.0 # via # httpx # hypercorn @@ -148,13 +147,13 @@ httpcore==1.0.9 # via httpx httpx==0.28.1 # via localstack-core (pyproject.toml) -hypercorn==0.17.3 +hypercorn==0.18.0 # via localstack-core hyperframe==6.1.0 # via h2 hyperlink==21.0.0 # via localstack-twisted -idna==3.10 +idna==3.11 # via # anyio # httpx @@ -163,31 +162,33 @@ idna==3.10 # requests importlib-resources==6.5.2 # via jsii -incremental==24.7.2 +incremental==24.11.0 # via localstack-twisted -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest isodate==0.7.2 # via openapi-core jinja2==3.1.6 - # via moto-ext -jmespath==1.0.1 + # via + # localstack-core + # moto-ext +jmespath==1.1.0 # via # boto3 # botocore -joserfc==1.2.2 +joserfc==1.6.3 # via moto-ext -jpype1-ext==0.0.2 +jpype1==1.6.0 # via localstack-core -jsii==1.112.0 +jsii==1.127.0 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 # aws-cdk-cloud-assembly-schema # aws-cdk-lib # constructs -json5==0.12.0 - # via localstack-core +json5==0.13.0 + # via localstack-core (pyproject.toml) jsonpatch==1.33 # via # cfn-lint @@ -200,10 +201,13 @@ jsonpath-ng==1.7.0 jsonpath-rw==1.4.0 # via localstack-core jsonpointer==3.0.0 - # via jsonpatch -jsonschema==4.24.0 + # via + # jsonpatch + # localstack-core +jsonschema==4.26.0 # via # aws-sam-translator + # localstack-core # moto-ext # openapi-core # openapi-schema-validator @@ -212,37 +216,37 @@ jsonschema-path==0.3.4 # via # openapi-core # openapi-spec-validator -jsonschema-specifications==2025.4.1 +jsonschema-specifications==2025.9.1 # via # jsonschema # openapi-schema-validator kclpy-ext==3.0.5 # via localstack-core -lazy-object-proxy==1.11.0 +lazy-object-proxy==1.12.0 # via openapi-spec-validator -localstack-snapshot==0.3.0 +localstack-snapshot==0.3.3 # via localstack-core (pyproject.toml) -localstack-twisted==24.3.0 +localstack-twisted==25.5.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug mdurl==0.1.2 # via markdown-it-py -more-itertools==10.7.0 +more-itertools==10.8.0 # via openapi-core -moto-ext==5.1.6.post2 +moto-ext==5.1.25 # via localstack-core mpmath==1.3.0 # via sympy -multipart==1.2.1 +multipart==1.3.1 # via moto-ext -networkx==3.5 +networkx==3.6.1 # via cfn-lint -openapi-core==0.19.4 +openapi-core==0.22.0 # via localstack-core openapi-schema-validator==0.6.3 # via @@ -252,26 +256,26 @@ openapi-spec-validator==0.7.2 # via # moto-ext # openapi-core -opensearch-py==3.0.0 +opensearch-protobufs==0.19.0 + # via opensearch-py +opensearch-py==3.1.0 # via localstack-core orderly-set==5.5.0 # via deepdiff -packaging==25.0 +packaging==26.0 # via # apispec - # build - # jpype1-ext + # incremental + # jpype1 # pytest # pytest-rerunfailures -parse==1.20.2 - # via openapi-core pathable==0.4.4 # via jsonschema-path pluggy==1.6.0 # via # localstack-core (pyproject.toml) # pytest -plux==1.12.1 +plux==1.14.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -283,7 +287,9 @@ priority==1.3.0 # via # hypercorn # localstack-twisted -psutil==7.0.0 +protobuf==7.34.0 + # via opensearch-protobufs +psutil==7.2.2 # via # localstack-core # localstack-core (pyproject.toml) @@ -295,41 +301,41 @@ publication==0.0.3 # aws-cdk-lib # constructs # jsii -py-partiql-parser==0.6.1 +py-partiql-parser==0.6.3 # via moto-ext -pyasn1==0.6.1 +pyasn1==0.6.2 # via rsa -pycparser==2.22 +pycparser==3.0 # via cffi -pydantic==2.11.7 - # via aws-sam-translator -pydantic-core==2.33.2 +pydantic==2.12.5 + # via + # aws-sam-translator + # localstack-core +pydantic-core==2.41.5 # via pydantic pygments==2.19.2 # via # pytest # rich -pymongo==4.13.2 +pymongo==4.16.0 # via localstack-core -pyopenssl==25.1.0 +pyopenssl==25.3.0 # via # localstack-core # localstack-twisted -pyparsing==3.2.3 +pyparsing==3.3.2 # via moto-ext -pyproject-hooks==1.2.0 - # via build -pytest==8.4.1 +pytest==9.0.2 # via # localstack-core (pyproject.toml) # pytest-rerunfailures # pytest-split # pytest-tinybird -pytest-httpserver==1.1.3 +pytest-httpserver==1.1.5 # via localstack-core (pyproject.toml) -pytest-rerunfailures==15.1 +pytest-rerunfailures==16.1 # via localstack-core (pyproject.toml) -pytest-split==0.10.0 +pytest-split==0.11.0 # via localstack-core (pyproject.toml) pytest-tinybird==0.5.0 # via localstack-core (pyproject.toml) @@ -337,13 +343,14 @@ python-dateutil==2.9.0.post0 # via # botocore # jsii + # localstack-core # moto-ext # opensearch-py -python-dotenv==1.1.1 +python-dotenv==1.2.2 # via # localstack-core # localstack-core (pyproject.toml) -pyyaml==6.0.2 +pyyaml==6.0.3 # via # awscli # cfn-lint @@ -359,9 +366,9 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2026.2.28 # via cfn-lint -requests==2.32.4 +requests==2.32.5 # via # docker # jsonschema-path @@ -375,23 +382,25 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 - # via moto-ext +responses==0.26.0 + # via + # localstack-core + # moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator -rich==14.0.0 +rich==14.3.3 # via # localstack-core # localstack-core (pyproject.toml) -rolo==0.7.6 +rolo==0.8.1 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.30.0 # via # jsonschema # referencing rsa==4.7.2 # via awscli -s3transfer==0.13.0 +s3transfer==0.16.0 # via # awscli # boto3 @@ -405,14 +414,8 @@ six==1.17.0 # jsonpath-rw # python-dateutil # rfc3339-validator -sniffio==1.3.1 - # via anyio sympy==1.14.0 # via cfn-lint -tailer==0.4.1 - # via - # localstack-core - # localstack-core (pyproject.toml) typeguard==2.13.3 # via # aws-cdk-asset-awscli-v1 @@ -421,22 +424,22 @@ typeguard==2.13.3 # aws-cdk-lib # constructs # jsii -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via - # anyio # aws-sam-translator + # cattrs # cfn-lint + # grpcio # jsii # localstack-twisted + # openapi-core # pydantic # pydantic-core - # pyopenssl # readerwriterlock - # referencing # typing-inspection -typing-inspection==0.4.1 +typing-inspection==0.4.2 # via pydantic -urllib3==2.5.0 +urllib3==2.6.3 # via # botocore # docker @@ -444,24 +447,24 @@ urllib3==2.5.0 # opensearch-py # requests # responses -websocket-client==1.8.0 +websocket-client==1.9.0 # via localstack-core (pyproject.toml) -werkzeug==3.1.3 +werkzeug==3.1.6 # via # localstack-core # moto-ext # openapi-core # pytest-httpserver # rolo -wrapt==1.17.2 +wrapt==2.1.1 # via aws-xray-sdk -wsproto==1.2.0 +wsproto==1.3.2 # via hypercorn -xmltodict==0.14.2 +xmltodict==1.0.4 # via # localstack-core # moto-ext -zope-interface==7.2 +zope-interface==8.2 # via localstack-twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements-typehint.txt b/requirements-typehint.txt index 17d89ce33488d..59571e7a6ca8a 100644 --- a/requirements-typehint.txt +++ b/requirements-typehint.txt @@ -1,57 +1,64 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile --extra=typehint --output-file=requirements-typehint.txt --strip-extras --unsafe-package=distribute --unsafe-package=localstack-core --unsafe-package=pip --unsafe-package=setuptools pyproject.toml # airspeed-ext==0.6.9 # via localstack-core +annotated-doc==0.0.4 + # via typer annotated-types==0.7.0 # via pydantic antlr4-python3-runtime==4.13.2 # via # localstack-core # moto-ext -anyio==4.9.0 +anyio==4.12.1 # via httpx -apispec==6.8.2 +apispec==6.9.0 # via localstack-core argparse==1.4.0 # via kclpy-ext -attrs==25.3.0 +asn1crypto==1.5.1 + # via + # localstack-core + # localstack-core (pyproject.toml) +attrs==25.4.0 # via # cattrs # jsii # jsonschema # localstack-twisted # referencing -aws-cdk-asset-awscli-v1==2.2.242 +aws-cdk-asset-awscli-v1==2.2.263 # via aws-cdk-lib -aws-cdk-asset-node-proxy-agent-v6==2.1.0 +aws-cdk-asset-node-proxy-agent-v6==2.1.1 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==45.2.0 +aws-cdk-cloud-assembly-schema==52.2.0 # via aws-cdk-lib -aws-cdk-lib==2.204.0 +aws-cdk-lib==2.241.0 # via localstack-core -aws-sam-translator==1.99.0 +aws-sam-translator==1.107.0 # via # cfn-lint # localstack-core -aws-xray-sdk==2.14.0 + # moto-ext +aws-xray-sdk==2.15.0 # via moto-ext -awscli==1.41.4 +awscli==1.44.49 # via localstack-core -awscrt==0.27.4 +awscrt==0.31.2 # via localstack-core -boto3==1.39.4 +boto3==1.42.59 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -boto3-stubs==1.39.4 +boto3-stubs==1.42.59 # via localstack-core (pyproject.toml) -botocore==1.39.4 +botocore==1.42.59 # via # aws-xray-sdk # awscli @@ -59,79 +66,75 @@ botocore==1.39.4 # localstack-core # moto-ext # s3transfer -botocore-stubs==1.38.46 +botocore-stubs==1.42.41 # via boto3-stubs -build==1.2.2.post1 - # via - # localstack-core - # localstack-core (pyproject.toml) -cachetools==6.1.0 +cachetools==7.0.2 # via # airspeed-ext # localstack-core # localstack-core (pyproject.toml) -cattrs==24.1.3 +cattrs==25.3.0 # via jsii -cbor2==5.6.5 +cbor2==5.8.0 # via localstack-core -certifi==2025.7.14 +certifi==2026.2.25 # via # httpcore # httpx # opensearch-py # requests -cffi==1.17.1 +cffi==2.0.0 # via cryptography -cfgv==3.4.0 +cfgv==3.5.0 # via pre-commit -cfn-lint==1.38.0 +cfn-lint==1.46.0 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.4 # via requests -click==8.2.1 +click==8.3.1 # via + # deptry # localstack-core # localstack-core (pyproject.toml) + # typer colorama==0.4.6 # via awscli constantly==23.10.4 # via localstack-twisted -constructs==10.4.2 +constructs==10.5.1 # via aws-cdk-lib -coverage==7.9.2 +coverage==7.13.4 # via # coveralls # localstack-core -coveralls==4.0.1 +coveralls==4.1.0 # via localstack-core crontab==1.0.5 # via localstack-core -cryptography==45.0.5 +cryptography==46.0.5 # via # joserfc # localstack-core # localstack-core (pyproject.toml) # moto-ext # pyopenssl -cython==3.1.2 +cython==3.2.4 # via localstack-core decorator==5.2.1 # via jsonpath-rw -deepdiff==8.5.0 - # via - # localstack-core - # localstack-snapshot +deepdiff==8.6.1 + # via localstack-snapshot +deptry==0.24.0 + # via localstack-core dill==0.3.6 - # via - # localstack-core - # localstack-core (pyproject.toml) -distlib==0.3.9 + # via localstack-core +distlib==0.4.0 # via virtualenv dnslib==0.9.26 # via # localstack-core # localstack-core (pyproject.toml) -dnspython==2.7.0 +dnspython==2.8.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -140,22 +143,24 @@ docker==7.1.0 # via # localstack-core # moto-ext -docopt==0.6.2 - # via coveralls docutils==0.19 # via awscli events==0.5 # via opensearch-py -filelock==3.18.0 - # via virtualenv -graphql-core==3.2.6 +filelock==3.25.0 + # via + # python-discovery + # virtualenv +graphql-core==3.2.7 # via moto-ext +grpcio==1.78.0 + # via opensearch-protobufs h11==0.16.0 # via # httpcore # hypercorn # wsproto -h2==4.2.0 +h2==4.3.0 # via # httpx # hypercorn @@ -166,15 +171,15 @@ httpcore==1.0.9 # via httpx httpx==0.28.1 # via localstack-core -hypercorn==0.17.3 +hypercorn==0.18.0 # via localstack-core hyperframe==6.1.0 # via h2 hyperlink==21.0.0 # via localstack-twisted -identify==2.6.12 +identify==2.6.17 # via pre-commit -idna==3.10 +idna==3.11 # via # anyio # httpx @@ -183,30 +188,32 @@ idna==3.10 # requests importlib-resources==6.5.2 # via jsii -incremental==24.7.2 +incremental==24.11.0 # via localstack-twisted -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest isodate==0.7.2 # via openapi-core jinja2==3.1.6 - # via moto-ext -jmespath==1.0.1 + # via + # localstack-core + # moto-ext +jmespath==1.1.0 # via # boto3 # botocore -joserfc==1.2.2 +joserfc==1.6.3 # via moto-ext -jpype1-ext==0.0.2 +jpype1==1.6.0 # via localstack-core -jsii==1.112.0 +jsii==1.127.0 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 # aws-cdk-cloud-assembly-schema # aws-cdk-lib # constructs -json5==0.12.0 +json5==0.13.0 # via localstack-core jsonpatch==1.33 # via @@ -220,10 +227,13 @@ jsonpath-ng==1.7.0 jsonpath-rw==1.4.0 # via localstack-core jsonpointer==3.0.0 - # via jsonpatch -jsonschema==4.24.0 + # via + # jsonpatch + # localstack-core +jsonschema==4.26.0 # via # aws-sam-translator + # localstack-core # moto-ext # openapi-core # openapi-schema-validator @@ -232,251 +242,243 @@ jsonschema-path==0.3.4 # via # openapi-core # openapi-spec-validator -jsonschema-specifications==2025.4.1 +jsonschema-specifications==2025.9.1 # via # jsonschema # openapi-schema-validator kclpy-ext==3.0.5 # via localstack-core -lazy-object-proxy==1.11.0 +lazy-object-proxy==1.12.0 # via openapi-spec-validator -localstack-snapshot==0.3.0 +librt==0.8.1 + # via mypy +localstack-snapshot==0.3.3 # via localstack-core -localstack-twisted==24.3.0 +localstack-twisted==25.5.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich -markupsafe==3.0.2 +markupsafe==3.0.3 # via # jinja2 # werkzeug mdurl==0.1.2 # via markdown-it-py -more-itertools==10.7.0 +more-itertools==10.8.0 # via openapi-core -moto-ext==5.1.6.post2 +moto-ext==5.1.25 # via localstack-core mpmath==1.3.0 # via sympy -multipart==1.2.1 +multipart==1.3.1 # via moto-ext -mypy==1.17.0 +mypy==1.19.1 # via localstack-core -mypy-boto3-acm==1.39.0 - # via boto3-stubs -mypy-boto3-acm-pca==1.39.0 - # via boto3-stubs -mypy-boto3-amplify==1.39.0 +mypy-boto3-acm==1.42.3 # via boto3-stubs -mypy-boto3-apigateway==1.39.0 +mypy-boto3-acm-pca==1.42.3 # via boto3-stubs -mypy-boto3-apigatewayv2==1.39.0 +mypy-boto3-amplify==1.42.3 # via boto3-stubs -mypy-boto3-appconfig==1.39.0 +mypy-boto3-apigateway==1.42.3 # via boto3-stubs -mypy-boto3-appconfigdata==1.39.0 +mypy-boto3-apigatewayv2==1.42.3 # via boto3-stubs -mypy-boto3-application-autoscaling==1.39.0 +mypy-boto3-appconfig==1.42.3 # via boto3-stubs -mypy-boto3-appsync==1.39.0 +mypy-boto3-appconfigdata==1.42.3 # via boto3-stubs -mypy-boto3-athena==1.39.0 +mypy-boto3-application-autoscaling==1.42.3 # via boto3-stubs -mypy-boto3-autoscaling==1.39.0 +mypy-boto3-appsync==1.42.6 # via boto3-stubs -mypy-boto3-backup==1.39.0 +mypy-boto3-athena==1.42.43 # via boto3-stubs -mypy-boto3-batch==1.39.0 +mypy-boto3-autoscaling==1.42.33 # via boto3-stubs -mypy-boto3-ce==1.39.0 +mypy-boto3-backup==1.42.3 # via boto3-stubs -mypy-boto3-cloudcontrol==1.39.0 +mypy-boto3-batch==1.42.59 # via boto3-stubs -mypy-boto3-cloudformation==1.39.0 +mypy-boto3-ce==1.42.28 # via boto3-stubs -mypy-boto3-cloudfront==1.39.0 +mypy-boto3-cloudcontrol==1.42.3 # via boto3-stubs -mypy-boto3-cloudtrail==1.39.0 +mypy-boto3-cloudformation==1.42.3 # via boto3-stubs -mypy-boto3-cloudwatch==1.39.0 +mypy-boto3-cloudfront==1.42.40 # via boto3-stubs -mypy-boto3-codebuild==1.39.0 +mypy-boto3-cloudtrail==1.42.3 # via boto3-stubs -mypy-boto3-codecommit==1.39.0 +mypy-boto3-cloudwatch==1.42.56 # via boto3-stubs -mypy-boto3-codeconnections==1.39.0 +mypy-boto3-codebuild==1.42.3 # via boto3-stubs -mypy-boto3-codedeploy==1.39.0 +mypy-boto3-codecommit==1.42.3 # via boto3-stubs -mypy-boto3-codepipeline==1.39.0 +mypy-boto3-codeconnections==1.42.3 # via boto3-stubs -mypy-boto3-codestar-connections==1.39.0 +mypy-boto3-codedeploy==1.42.3 # via boto3-stubs -mypy-boto3-cognito-identity==1.39.0 +mypy-boto3-codepipeline==1.42.3 # via boto3-stubs -mypy-boto3-cognito-idp==1.39.0 +mypy-boto3-codestar-connections==1.42.3 # via boto3-stubs -mypy-boto3-dms==1.39.0 +mypy-boto3-cognito-identity==1.42.3 # via boto3-stubs -mypy-boto3-docdb==1.39.0 +mypy-boto3-cognito-idp==1.42.59 # via boto3-stubs -mypy-boto3-dynamodb==1.39.0 +mypy-boto3-dms==1.42.3 # via boto3-stubs -mypy-boto3-dynamodbstreams==1.39.0 +mypy-boto3-docdb==1.42.3 # via boto3-stubs -mypy-boto3-ec2==1.39.4 +mypy-boto3-dynamodb==1.42.55 # via boto3-stubs -mypy-boto3-ecr==1.39.0 +mypy-boto3-dynamodbstreams==1.42.3 # via boto3-stubs -mypy-boto3-ecs==1.39.0 +mypy-boto3-ec2==1.42.58 # via boto3-stubs -mypy-boto3-efs==1.39.0 +mypy-boto3-ecr==1.42.57 # via boto3-stubs -mypy-boto3-eks==1.39.0 +mypy-boto3-ecs==1.42.58 # via boto3-stubs -mypy-boto3-elasticache==1.39.0 +mypy-boto3-efs==1.42.3 # via boto3-stubs -mypy-boto3-elasticbeanstalk==1.39.0 +mypy-boto3-eks==1.42.47 # via boto3-stubs -mypy-boto3-elbv2==1.39.0 +mypy-boto3-elasticache==1.42.3 # via boto3-stubs -mypy-boto3-emr==1.39.0 +mypy-boto3-elasticbeanstalk==1.42.3 # via boto3-stubs -mypy-boto3-emr-serverless==1.39.0 +mypy-boto3-elbv2==1.42.3 # via boto3-stubs -mypy-boto3-es==1.39.0 +mypy-boto3-emr==1.42.3 # via boto3-stubs -mypy-boto3-events==1.39.0 +mypy-boto3-emr-serverless==1.42.23 # via boto3-stubs -mypy-boto3-firehose==1.39.0 +mypy-boto3-es==1.42.56 # via boto3-stubs -mypy-boto3-fis==1.39.0 +mypy-boto3-events==1.42.3 # via boto3-stubs -mypy-boto3-glacier==1.39.0 +mypy-boto3-firehose==1.42.3 # via boto3-stubs -mypy-boto3-glue==1.39.0 +mypy-boto3-fis==1.42.3 # via boto3-stubs -mypy-boto3-iam==1.39.0 +mypy-boto3-glacier==1.42.30 # via boto3-stubs -mypy-boto3-identitystore==1.39.0 +mypy-boto3-glue==1.42.43 # via boto3-stubs -mypy-boto3-iot==1.39.0 +mypy-boto3-iam==1.42.4 # via boto3-stubs -mypy-boto3-iot-data==1.39.0 +mypy-boto3-identitystore==1.42.20 # via boto3-stubs -mypy-boto3-iotanalytics==1.39.0 +mypy-boto3-iot==1.42.14 # via boto3-stubs -mypy-boto3-iotwireless==1.39.0 +mypy-boto3-iot-data==1.42.3 # via boto3-stubs -mypy-boto3-kafka==1.39.0 +mypy-boto3-iotwireless==1.42.3 # via boto3-stubs -mypy-boto3-kinesis==1.39.0 +mypy-boto3-kafka==1.42.50 # via boto3-stubs -mypy-boto3-kinesisanalytics==1.39.0 +mypy-boto3-kinesis==1.42.41 # via boto3-stubs -mypy-boto3-kinesisanalyticsv2==1.39.0 +mypy-boto3-kinesisanalyticsv2==1.42.3 # via boto3-stubs -mypy-boto3-kms==1.39.0 +mypy-boto3-kms==1.42.50 # via boto3-stubs -mypy-boto3-lakeformation==1.39.0 +mypy-boto3-lakeformation==1.42.45 # via boto3-stubs -mypy-boto3-lambda==1.39.0 +mypy-boto3-lambda==1.42.37 # via boto3-stubs -mypy-boto3-logs==1.39.0 +mypy-boto3-logs==1.42.10 # via boto3-stubs -mypy-boto3-managedblockchain==1.39.0 +mypy-boto3-managedblockchain==1.42.3 # via boto3-stubs -mypy-boto3-mediaconvert==1.39.0 +mypy-boto3-mediaconvert==1.42.37 # via boto3-stubs -mypy-boto3-mediastore==1.39.0 +mypy-boto3-mq==1.42.3 # via boto3-stubs -mypy-boto3-mq==1.39.0 +mypy-boto3-mwaa==1.42.3 # via boto3-stubs -mypy-boto3-mwaa==1.39.0 +mypy-boto3-neptune==1.42.57 # via boto3-stubs -mypy-boto3-neptune==1.39.0 +mypy-boto3-opensearch==1.42.56 # via boto3-stubs -mypy-boto3-opensearch==1.39.0 +mypy-boto3-organizations==1.42.41 # via boto3-stubs -mypy-boto3-organizations==1.39.0 +mypy-boto3-pi==1.42.3 # via boto3-stubs -mypy-boto3-pi==1.39.0 +mypy-boto3-pinpoint==1.42.3 # via boto3-stubs -mypy-boto3-pinpoint==1.39.0 +mypy-boto3-pipes==1.42.3 # via boto3-stubs -mypy-boto3-pipes==1.39.0 +mypy-boto3-rds==1.42.51 # via boto3-stubs -mypy-boto3-qldb==1.39.0 +mypy-boto3-rds-data==1.42.3 # via boto3-stubs -mypy-boto3-qldb-session==1.39.0 +mypy-boto3-redshift==1.42.42 # via boto3-stubs -mypy-boto3-rds==1.39.1 +mypy-boto3-redshift-data==1.42.3 # via boto3-stubs -mypy-boto3-rds-data==1.39.0 +mypy-boto3-resource-groups==1.42.3 # via boto3-stubs -mypy-boto3-redshift==1.39.0 +mypy-boto3-resourcegroupstaggingapi==1.42.3 # via boto3-stubs -mypy-boto3-redshift-data==1.39.0 +mypy-boto3-route53==1.42.6 # via boto3-stubs -mypy-boto3-resource-groups==1.39.0 +mypy-boto3-route53resolver==1.42.10 # via boto3-stubs -mypy-boto3-resourcegroupstaggingapi==1.39.0 +mypy-boto3-s3==1.42.37 # via boto3-stubs -mypy-boto3-route53==1.39.3 +mypy-boto3-s3control==1.42.37 # via boto3-stubs -mypy-boto3-route53resolver==1.39.0 +mypy-boto3-sagemaker==1.42.49 # via boto3-stubs -mypy-boto3-s3==1.39.2 +mypy-boto3-sagemaker-runtime==1.42.54 # via boto3-stubs -mypy-boto3-s3control==1.39.2 +mypy-boto3-secretsmanager==1.42.8 # via boto3-stubs -mypy-boto3-sagemaker==1.39.3 +mypy-boto3-serverlessrepo==1.42.3 # via boto3-stubs -mypy-boto3-sagemaker-runtime==1.39.0 +mypy-boto3-servicediscovery==1.42.3 # via boto3-stubs -mypy-boto3-secretsmanager==1.39.0 +mypy-boto3-ses==1.42.3 # via boto3-stubs -mypy-boto3-serverlessrepo==1.39.0 +mypy-boto3-sesv2==1.42.13 # via boto3-stubs -mypy-boto3-servicediscovery==1.39.0 +mypy-boto3-sns==1.42.3 # via boto3-stubs -mypy-boto3-ses==1.39.0 +mypy-boto3-sqs==1.42.3 # via boto3-stubs -mypy-boto3-sesv2==1.39.0 +mypy-boto3-ssm==1.42.54 # via boto3-stubs -mypy-boto3-sns==1.39.0 +mypy-boto3-sso-admin==1.42.41 # via boto3-stubs -mypy-boto3-sqs==1.39.0 +mypy-boto3-stepfunctions==1.42.3 # via boto3-stubs -mypy-boto3-ssm==1.39.0 +mypy-boto3-sts==1.42.3 # via boto3-stubs -mypy-boto3-sso-admin==1.39.0 +mypy-boto3-timestream-query==1.42.3 # via boto3-stubs -mypy-boto3-stepfunctions==1.39.0 +mypy-boto3-timestream-write==1.42.3 # via boto3-stubs -mypy-boto3-sts==1.39.0 +mypy-boto3-transcribe==1.42.25 # via boto3-stubs -mypy-boto3-timestream-query==1.39.0 +mypy-boto3-verifiedpermissions==1.42.33 # via boto3-stubs -mypy-boto3-timestream-write==1.39.0 +mypy-boto3-wafv2==1.42.57 # via boto3-stubs -mypy-boto3-transcribe==1.39.0 - # via boto3-stubs -mypy-boto3-verifiedpermissions==1.39.0 - # via boto3-stubs -mypy-boto3-wafv2==1.39.0 - # via boto3-stubs -mypy-boto3-xray==1.39.0 +mypy-boto3-xray==1.42.3 # via boto3-stubs mypy-extensions==1.1.0 # via mypy -networkx==3.5 +networkx==3.6.1 # via # cfn-lint # localstack-core -nodeenv==1.9.1 +nodeenv==1.10.0 # via pre-commit -openapi-core==0.19.4 +openapi-core==0.22.0 # via localstack-core openapi-schema-validator==0.6.3 # via @@ -487,34 +489,38 @@ openapi-spec-validator==0.7.2 # localstack-core # moto-ext # openapi-core -opensearch-py==3.0.0 +opensearch-protobufs==0.19.0 + # via opensearch-py +opensearch-py==3.1.0 # via localstack-core orderly-set==5.5.0 # via deepdiff -packaging==25.0 +packaging==26.0 # via # apispec - # build - # jpype1-ext + # deptry + # incremental + # jpype1 # pytest # pytest-rerunfailures + # requirements-parser pandoc==2.4 # via localstack-core -parse==1.20.2 - # via openapi-core pathable==0.4.4 # via jsonschema-path -pathspec==0.12.1 +pathspec==1.0.4 # via mypy -platformdirs==4.3.8 - # via virtualenv +platformdirs==4.9.2 + # via + # python-discovery + # virtualenv pluggy==1.6.0 # via # localstack-core # pytest -plumbum==1.9.0 +plumbum==1.10.0 # via pandoc -plux==1.12.1 +plux==1.14.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -523,13 +529,15 @@ ply==3.11 # jsonpath-ng # jsonpath-rw # pandoc -pre-commit==4.2.0 +pre-commit==4.5.1 # via localstack-core priority==1.3.0 # via # hypercorn # localstack-twisted -psutil==7.0.0 +protobuf==7.34.0 + # via opensearch-protobufs +psutil==7.2.2 # via # localstack-core # localstack-core (pyproject.toml) @@ -541,43 +549,44 @@ publication==0.0.3 # aws-cdk-lib # constructs # jsii -py-partiql-parser==0.6.1 +py-partiql-parser==0.6.3 # via moto-ext -pyasn1==0.6.1 +pyasn1==0.6.2 # via rsa -pycparser==2.22 +pycparser==3.0 # via cffi -pydantic==2.11.7 - # via aws-sam-translator -pydantic-core==2.33.2 +pydantic==2.12.5 + # via + # aws-sam-translator + # localstack-core + # openapi-spec-validator +pydantic-core==2.41.5 # via pydantic pygments==2.19.2 # via # pytest # rich -pymongo==4.13.2 +pymongo==4.16.0 # via localstack-core -pyopenssl==25.1.0 +pyopenssl==25.3.0 # via # localstack-core # localstack-twisted -pypandoc==1.15 +pypandoc==1.16.2 # via localstack-core -pyparsing==3.2.3 +pyparsing==3.3.2 # via moto-ext -pyproject-hooks==1.2.0 - # via build -pytest==8.4.1 +pytest==9.0.2 # via # localstack-core # pytest-rerunfailures # pytest-split # pytest-tinybird -pytest-httpserver==1.1.3 +pytest-httpserver==1.1.5 # via localstack-core -pytest-rerunfailures==15.1 +pytest-rerunfailures==16.1 # via localstack-core -pytest-split==0.10.0 +pytest-split==0.11.0 # via localstack-core pytest-tinybird==0.5.0 # via localstack-core @@ -585,13 +594,16 @@ python-dateutil==2.9.0.post0 # via # botocore # jsii + # localstack-core # moto-ext # opensearch-py -python-dotenv==1.1.1 +python-discovery==1.1.0 + # via virtualenv +python-dotenv==1.2.2 # via # localstack-core # localstack-core (pyproject.toml) -pyyaml==6.0.2 +pyyaml==6.0.3 # via # awscli # cfn-lint @@ -608,9 +620,9 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2026.2.28 # via cfn-lint -requests==2.32.4 +requests==2.32.5 # via # coveralls # docker @@ -625,17 +637,22 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 - # via moto-ext +requirements-parser==0.13.0 + # via deptry +responses==0.26.0 + # via + # localstack-core + # moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator -rich==14.0.0 +rich==14.3.3 # via # localstack-core # localstack-core (pyproject.toml) -rolo==0.7.6 + # typer +rolo==0.8.1 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.30.0 # via # jsonschema # referencing @@ -643,9 +660,9 @@ rsa==4.7.2 # via awscli rstr==3.2.2 # via localstack-core -ruff==0.12.3 +ruff==0.15.4 # via localstack-core -s3transfer==0.13.0 +s3transfer==0.16.0 # via # awscli # boto3 @@ -653,20 +670,16 @@ semver==3.0.4 # via # localstack-core # localstack-core (pyproject.toml) +shellingham==1.5.4 + # via typer six==1.17.0 # via # airspeed-ext # jsonpath-rw # python-dateutil # rfc3339-validator -sniffio==1.3.1 - # via anyio sympy==1.14.0 # via cfn-lint -tailer==0.4.1 - # via - # localstack-core - # localstack-core (pyproject.toml) typeguard==2.13.3 # via # aws-cdk-asset-awscli-v1 @@ -675,131 +688,29 @@ typeguard==2.13.3 # aws-cdk-lib # constructs # jsii -types-awscrt==0.27.4 +typer==0.24.1 + # via coveralls +types-awscrt==0.31.2 # via botocore-stubs -types-s3transfer==0.13.0 +types-s3transfer==0.16.0 # via boto3-stubs -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via - # anyio # aws-sam-translator - # boto3-stubs + # cattrs # cfn-lint + # grpcio # jsii # localstack-twisted # mypy - # mypy-boto3-acm - # mypy-boto3-acm-pca - # mypy-boto3-amplify - # mypy-boto3-apigateway - # mypy-boto3-apigatewayv2 - # mypy-boto3-appconfig - # mypy-boto3-appconfigdata - # mypy-boto3-application-autoscaling - # mypy-boto3-appsync - # mypy-boto3-athena - # mypy-boto3-autoscaling - # mypy-boto3-backup - # mypy-boto3-batch - # mypy-boto3-ce - # mypy-boto3-cloudcontrol - # mypy-boto3-cloudformation - # mypy-boto3-cloudfront - # mypy-boto3-cloudtrail - # mypy-boto3-cloudwatch - # mypy-boto3-codebuild - # mypy-boto3-codecommit - # mypy-boto3-codeconnections - # mypy-boto3-codedeploy - # mypy-boto3-codepipeline - # mypy-boto3-codestar-connections - # mypy-boto3-cognito-identity - # mypy-boto3-cognito-idp - # mypy-boto3-dms - # mypy-boto3-docdb - # mypy-boto3-dynamodb - # mypy-boto3-dynamodbstreams - # mypy-boto3-ec2 - # mypy-boto3-ecr - # mypy-boto3-ecs - # mypy-boto3-efs - # mypy-boto3-eks - # mypy-boto3-elasticache - # mypy-boto3-elasticbeanstalk - # mypy-boto3-elbv2 - # mypy-boto3-emr - # mypy-boto3-emr-serverless - # mypy-boto3-es - # mypy-boto3-events - # mypy-boto3-firehose - # mypy-boto3-fis - # mypy-boto3-glacier - # mypy-boto3-glue - # mypy-boto3-iam - # mypy-boto3-identitystore - # mypy-boto3-iot - # mypy-boto3-iot-data - # mypy-boto3-iotanalytics - # mypy-boto3-iotwireless - # mypy-boto3-kafka - # mypy-boto3-kinesis - # mypy-boto3-kinesisanalytics - # mypy-boto3-kinesisanalyticsv2 - # mypy-boto3-kms - # mypy-boto3-lakeformation - # mypy-boto3-lambda - # mypy-boto3-logs - # mypy-boto3-managedblockchain - # mypy-boto3-mediaconvert - # mypy-boto3-mediastore - # mypy-boto3-mq - # mypy-boto3-mwaa - # mypy-boto3-neptune - # mypy-boto3-opensearch - # mypy-boto3-organizations - # mypy-boto3-pi - # mypy-boto3-pinpoint - # mypy-boto3-pipes - # mypy-boto3-qldb - # mypy-boto3-qldb-session - # mypy-boto3-rds - # mypy-boto3-rds-data - # mypy-boto3-redshift - # mypy-boto3-redshift-data - # mypy-boto3-resource-groups - # mypy-boto3-resourcegroupstaggingapi - # mypy-boto3-route53 - # mypy-boto3-route53resolver - # mypy-boto3-s3 - # mypy-boto3-s3control - # mypy-boto3-sagemaker - # mypy-boto3-sagemaker-runtime - # mypy-boto3-secretsmanager - # mypy-boto3-serverlessrepo - # mypy-boto3-servicediscovery - # mypy-boto3-ses - # mypy-boto3-sesv2 - # mypy-boto3-sns - # mypy-boto3-sqs - # mypy-boto3-ssm - # mypy-boto3-sso-admin - # mypy-boto3-stepfunctions - # mypy-boto3-sts - # mypy-boto3-timestream-query - # mypy-boto3-timestream-write - # mypy-boto3-transcribe - # mypy-boto3-verifiedpermissions - # mypy-boto3-wafv2 - # mypy-boto3-xray + # openapi-core # pydantic # pydantic-core - # pyopenssl # readerwriterlock - # referencing # typing-inspection -typing-inspection==0.4.1 +typing-inspection==0.4.2 # via pydantic -urllib3==2.5.0 +urllib3==2.6.3 # via # botocore # docker @@ -807,26 +718,28 @@ urllib3==2.5.0 # opensearch-py # requests # responses -virtualenv==20.31.2 +virtualenv==21.1.0 # via pre-commit -websocket-client==1.8.0 +watchdog==6.0.0 + # via localstack-core +websocket-client==1.9.0 # via localstack-core -werkzeug==3.1.3 +werkzeug==3.1.6 # via # localstack-core # moto-ext # openapi-core # pytest-httpserver # rolo -wrapt==1.17.2 +wrapt==2.1.1 # via aws-xray-sdk -wsproto==1.2.0 +wsproto==1.3.2 # via hypercorn -xmltodict==0.14.2 +xmltodict==1.0.4 # via # localstack-core # moto-ext -zope-interface==7.2 +zope-interface==8.2 # via localstack-twisted # The following packages are considered to be unsafe in a requirements file: diff --git a/scripts/capture_notimplemented_responses.py b/scripts/capture_notimplemented_responses.py index c8747562a1da5..9f38a64cc2df8 100644 --- a/scripts/capture_notimplemented_responses.py +++ b/scripts/capture_notimplemented_responses.py @@ -7,7 +7,7 @@ import traceback from datetime import timedelta from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import botocore.config import requests @@ -37,7 +37,7 @@ # will only include available services response = requests.get("http://localhost:4566/_localstack/health").content.decode("utf-8") -latest_services_pro = [k for k in json.loads(response).get("services").keys()] +latest_services_pro = list(json.loads(response).get("services").keys()) exclude_services = {"azure"} latest_services_pro = [s for s in latest_services_pro if s not in exclude_services] @@ -150,7 +150,7 @@ def simulate_call(service: str, op: str) -> RowEntry: return result -def _make_api_call(client, service: str, op: str, parameters: Optional[Instance]): +def _make_api_call(client, service: str, op: str, parameters: Instance | None): result = RowEntry(service=service, operation=op, status_code=0) try: response = client._make_api_call(op, parameters) @@ -190,7 +190,7 @@ def map_to_notimplemented(row: RowEntry) -> bool: Some simple heuristics to check the API responses and classify them into implemented/notimplemented Ideally they all should behave the same way when receiving requests for not yet implemented endpoints - (501 with a "not yet implemented" message) + (501 error code and avoids relying on static "not yet implemented" error message strings) :param row: the RowEntry :return: True if we assume it is not implemented, False otherwise @@ -233,16 +233,6 @@ def map_to_notimplemented(row: RowEntry) -> bool: and "The requested URL was not found on the server" in row.get("error_message") ): return True - if ( - row["status_code"] == 501 - and row.get("error_message") is not None - and "not yet implemented" in row.get("error_message", "") - ): - return True - if row.get("error_message") is not None and "not yet implemented" in row.get( - "error_message", "" - ): - return True if row["status_code"] == 501: return True if ( @@ -337,7 +327,7 @@ def calculate_percentages(): implemented_aggregate = {} aggregate_list = [] - with open("./output-notimplemented.csv", "r") as fd: + with open("./output-notimplemented.csv") as fd: reader = csv.DictReader(fd, fieldnames=["service", "operation", "implemented"]) for line in reader: if line["implemented"] == "implemented": diff --git a/scripts/gather_outdated_snapshots.py b/scripts/gather_outdated_snapshots.py index 817ab45a4b661..99982972321da 100644 --- a/scripts/gather_outdated_snapshots.py +++ b/scripts/gather_outdated_snapshots.py @@ -39,7 +39,7 @@ def do_get_outdated_snapshots(path: str): recorded_date = recorded_snapshot_data.get("last_validated_date") date = datetime.datetime.fromisoformat(recorded_date) if date.timestamp() < date_limit: - outdated_snapshot_data = dict() + outdated_snapshot_data = {} if show_date: outdated_snapshot_data["last_validation_date"] = recorded_date if combine_parametrized: diff --git a/scripts/metrics_coverage/diff_metrics_coverage.py b/scripts/metrics_coverage/diff_metrics_coverage.py index 7409582d65471..0ab708b298958 100644 --- a/scripts/metrics_coverage/diff_metrics_coverage.py +++ b/scripts/metrics_coverage/diff_metrics_coverage.py @@ -42,7 +42,7 @@ def create_initial_coverage(path_to_initial_metrics: str) -> dict: pathlist = Path(path_to_initial_metrics).rglob("*.csv") coverage = {} for path in pathlist: - with open(path, "r") as csv_obj: + with open(path) as csv_obj: print(f"Processing integration test coverage metrics: {path}") csv_dict_reader = csv.DictReader(csv_obj) for metric in csv_dict_reader: @@ -85,7 +85,7 @@ def mark_coverage_acceptance_test( additional_tested = {} add_to_additional = False for path in pathlist: - with open(path, "r") as csv_obj: + with open(path) as csv_obj: print(f"Processing acceptance test coverage metrics: {path}") csv_dict_reader = csv.DictReader(csv_obj) for metric in csv_dict_reader: @@ -213,7 +213,7 @@ def create_readable_report( if additional_test_details: fd.write("

Additional Test Coverage

\n") fd.write( - "
Note: this is probalby wrong usage of the script. It includes operations that have been covered with the acceptance tests only" + "
Note: this is probably wrong usage of the script. It includes operations that have been covered with the acceptance tests only" ) fd.write(f"

{additional_test_details}

\n") fd.write("") diff --git a/scripts/render_marker_report.py b/scripts/render_marker_report.py index 2b1e0e1154825..80fac7d087224 100644 --- a/scripts/render_marker_report.py +++ b/scripts/render_marker_report.py @@ -53,7 +53,7 @@ class EnrichedReport: def load_file(filepath: str) -> str: - with open(filepath, "r") as fd: + with open(filepath) as fd: return fd.read() @@ -133,7 +133,7 @@ def main(): rendered_markdown = render_template( template=load_file(template_path), enriched_report=enriched_report ) - with open(output_path, "wt") as outfile: + with open(output_path, "w") as outfile: outfile.write(rendered_markdown) diff --git a/scripts/tinybird/retrieve_legacy_data_from_circleci.py b/scripts/tinybird/retrieve_legacy_data_from_circleci.py index acaefd0f1e166..45d63de807981 100644 --- a/scripts/tinybird/retrieve_legacy_data_from_circleci.py +++ b/scripts/tinybird/retrieve_legacy_data_from_circleci.py @@ -1,6 +1,6 @@ """Helper script to retrieve historical data and load into tinybird parity dashboard -The script is intended to be run locally. It was executed once, to retrieve the data from the past successful master builds +The script is intended to be run locally. It was executed once, to retrieve the data from the past successful main builds in order to get more data into the parity dashboard for a hackathon project. """ @@ -17,7 +17,7 @@ ) PROJECT_SLUG = "github/localstack/localstack" -MASTER_BRANCH = "master" +MASTER_BRANCH = "main" def send_request_to_connection(conn, url): @@ -48,7 +48,7 @@ def extract_artifacts_url_for_path(artifacts, path): def collect_workflows_past_30_days(): """ - Retrieves the workflows run from the past 30 days from circecli on 'master' branch, + Retrieves the workflows run from the past 30 days from circecli on 'main' branch, and retrieves the artifacts for each successful workflow run, that are collected in the 'report' job. The artifacts for coverage implementation, and raw-data collection are downloaded, and then processed and sent to tinybird backend. @@ -147,7 +147,7 @@ def collect_workflows_past_30_days(): "3c9c12e5-0fe7-4b1a-b224-7570808f8e19", ] # TODO check "next_page_token" - # -> wasn't required for the initial run, as on master everything was on one page for the past 30 days + # -> wasn't required for the initial run, as on main everything was on one page for the past 30 days workflows = json.loads(data.decode("utf-8")) count = 0 for item in workflows.get("items"): @@ -189,10 +189,12 @@ def collect_workflows_past_30_days(): # extract the required urls for metric-data-raw, and coverage data for community/pro metric_data_url = extract_artifacts_url_for_path( - artifacts=artifacts, path="parity_metrics/metric-report-raw-data-all" + artifacts=artifacts, + path="parity_metrics/metric-report-raw-data-all", ) community_cov_url = extract_artifacts_url_for_path( - artifacts=artifacts, path="community/implementation_coverage_full.csv" + artifacts=artifacts, + path="community/implementation_coverage_full.csv", ) pro_cov_url = extract_artifacts_url_for_path( artifacts=artifacts, path="pro/implementation_coverage_full.csv" @@ -224,7 +226,9 @@ def collect_workflows_past_30_days(): # trigger the tinybird_upload send_metric_report( - metric_report_file_path, source_type="community", timestamp=timestamp + metric_report_file_path, + source_type="community", + timestamp=timestamp, ) send_implemented_coverage( community_coverage_file_path, timestamp=timestamp, type="community" diff --git a/scripts/tinybird/upload_raw_test_metrics_and_coverage.py b/scripts/tinybird/upload_raw_test_metrics_and_coverage.py index 3fdaf3878206f..711dbd93f2d4d 100644 --- a/scripts/tinybird/upload_raw_test_metrics_and_coverage.py +++ b/scripts/tinybird/upload_raw_test_metrics_and_coverage.py @@ -140,7 +140,7 @@ def send_metadata_for_build(build_id: str, timestamp: str): CIRCLE_WORKFLOW_ID=b86a4bc4-bcd1-4170-94d6-4af66846c1c1 GitHub env examples: - GITHUB_REF=ref/heads/master or ref/pull//merge (will be used for 'pull_requests') + GITHUB_REF=ref/heads/main or ref/pull//merge (will be used for 'pull_requests') GITHUB_HEAD_REF=tinybird_data (used for 'branch', set only for pull_requests) GITHUB_REF_NAME=feature-branch-1 (will be used for 'branch' if GITHUB_HEAD_REF is not set) GITHUB_RUN_ID=1658821493 (will be used for 'workflow_id') @@ -210,7 +210,7 @@ def send_metric_report(metric_path: str, source_type: str, timestamp: str): pathlist = Path(metric_path).rglob("metric-report-raw-data-*.csv") for path in pathlist: print(f"checking {str(path)}") - with open(path, "r") as csv_obj: + with open(path) as csv_obj: reader_obj = csv.DictReader(csv_obj) data_to_remove = [field for field in reader_obj.fieldnames if field not in DATA_TO_KEEP] for row in reader_obj: @@ -259,7 +259,7 @@ def send_implemented_coverage(file: str, timestamp: str, type: str): count: int = 0 build_id = os.environ.get("CIRCLE_WORKFLOW_ID", "") or os.environ.get("GITHUB_RUN_ID", "") - with open(file, "r") as csv_obj: + with open(file) as csv_obj: reader_obj = csv.DictReader(csv_obj) for row in reader_obj: count = count + 1 diff --git a/scripts/update_cfn_resources.py b/scripts/update_cfn_resources.py new file mode 100644 index 0000000000000..55d5322a99239 --- /dev/null +++ b/scripts/update_cfn_resources.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +""" +Script to gather all CloudFormation resource types available across AWS regions. + +The collected data is written to `localstack/services/cloudformation/resources.py` as + +* `AWS_AVAILABLE_CFN_RESOURCES`: a dict, mapping resource names to the regions they are available in. +* `AWS_CFN_REGIONS_SCANNED`: a set of regions for which listing CloudFormation types succeeded. + +The script expects valid AWS credentials either via the environment or configured profiles (for local execution). + +Note: +We evaluated scraping the public documentation/zip schema endpoints, but they do not have resources like +``AWS::OpsWorksCM::Server`` that are still returned by the CloudFormation API. +To make sure we cover all resources, we rely on the list of available types from the API. +""" + +import argparse +import sys +from collections.abc import Iterable +from pathlib import Path + +import boto3 +from botocore.exceptions import ClientError + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEFAULT_RESOURCE_FILE = ( + REPO_ROOT / "localstack-core" / "localstack" / "services" / "cloudformation" / "resources.py" +) + + +def get_regions( + session: boto3.session.Session, explicit_regions: Iterable[str] | None = None +) -> list[str]: + if explicit_regions: + return sorted(set(explicit_regions)) + + return sorted(session.get_available_regions("cloudformation")) + + +def collect_region_resource_types( + session: boto3.session.Session, region: str +) -> tuple[set[str], bool]: + client = session.client("cloudformation", region_name=region) + resources: set[str] = set() + token: str | None = None + succeeded = True + + while True: + try: + params = { + "Visibility": "PUBLIC", + "Type": "RESOURCE", + "Filters": {"Category": "AWS_TYPES"}, + } + if token: + params["NextToken"] = token + response = client.list_types(**params) + except ClientError as exc: + print(f"Skipping region {region} due to error while listing types: {exc}") + succeeded = False + break + + for summary in response.get("TypeSummaries", []): + type_name = summary.get("TypeName") + if type_name: + resources.add(type_name) + + token = response.get("NextToken") + if not token: + break + + return resources, succeeded + + +def collect_all_resource_types( + session: boto3.session.Session, regions: Iterable[str] +) -> tuple[dict[str, set[str]], set[str]]: + aggregated: dict[str, set[str]] = {} + successful_regions: set[str] = set() + for region in regions: + print(f"Collecting CloudFormation resource types in region {region}") + region_resources, succeeded = collect_region_resource_types(session, region) + if not succeeded: + continue + + successful_regions.add(region) + for resource in region_resources: + aggregated.setdefault(resource, set()).add(region) + return aggregated, successful_regions + + +def render_resource_file(resources: dict[str, set[str]], successful_regions: set[str]) -> str: + lines: list[str] = [ + '"""Generated by scripts/update_cfn_resources.py – do not edit manually."""', + "", + ] + + if resources: + lines.append("AWS_AVAILABLE_CFN_RESOURCES = {") + for resource in sorted(resources): + regions = sorted(resources[resource]) + if regions: + lines.append(f' "{resource}": [') + for region_name in regions: + lines.append(f' "{region_name}",') + lines.append(" ],") + else: + lines.append(f' "{resource}": [],') + lines.append("}") + else: + lines.append("AWS_AVAILABLE_CFN_RESOURCES = {}") + + lines.append("") + + if successful_regions: + lines.append("AWS_CFN_REGIONS_SCANNED = {") + for region in sorted(successful_regions): + lines.append(f' "{region}",') + lines.append("}") + else: + lines.append("AWS_CFN_REGIONS_SCANNED = set()") + + lines.append("") + + return "\n".join(lines) + + +def write_resource_file(path: Path, content: str) -> None: + path.write_text(content, encoding="utf-8") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Update the AWS_AVAILABLE_CFN_RESOURCES constant.") + parser.add_argument("--profile", help="AWS profile name to use") + parser.add_argument( + "--regions", + nargs="*", + help="Optional list of AWS regions to scan. Defaults to all regions supporting CloudFormation.", + ) + parser.add_argument( + "--resource-file", + default=str(DEFAULT_RESOURCE_FILE), + help="Path to the resources.py file that should be updated.", + ) + parser.add_argument( + "--dry-run", action="store_true", help="Do not write the file, only print the resources." + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + + session_kwargs = {} + if args.profile: + session_kwargs["profile_name"] = args.profile + + try: + session = boto3.session.Session(**session_kwargs) + except Exception as exc: + print(f"Failed to create boto3 session: {exc}") + return 1 + + regions = get_regions(session, args.regions) + if not regions: + print("Could not determine any regions to scan.") + return 1 + + print(f"Scanning CloudFormation resource types in {len(regions)} regions") + resources, successful_regions = collect_all_resource_types(session, regions) + if not resources: + print("No CloudFormation resources were discovered.") + return 1 + print(f"Collected {len(resources)} resources across {len(successful_regions)} regions:") + + print("Updating resource file...") + content = render_resource_file(resources, successful_regions) + + if args.dry_run: + sys.stdout.write(content) + return 0 + + resource_file = Path(args.resource_file) + write_resource_file(resource_file, content) + print( + f"Updated {resource_file} with {len(resources)} CloudFormation resource types " + f"across {len(successful_regions)} regions." + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/aws/cdk_templates/Bookstore/BookstoreStack.json b/tests/aws/cdk_templates/Bookstore/BookstoreStack.json index e1486e5fcd76c..058c14eba1271 100644 --- a/tests/aws/cdk_templates/Bookstore/BookstoreStack.json +++ b/tests/aws/cdk_templates/Bookstore/BookstoreStack.json @@ -202,7 +202,7 @@ "EncryptionAtRestOptions": { "Enabled": false }, - "EngineVersion": "OpenSearch_2.11", + "EngineVersion": "OpenSearch_3.1", "LogPublishingOptions": {}, "NodeToNodeEncryptionOptions": { "Enabled": false diff --git a/tests/aws/conftest.py b/tests/aws/conftest.py index 9ee30b5b925a0..b6727d15b976a 100644 --- a/tests/aws/conftest.py +++ b/tests/aws/conftest.py @@ -1,5 +1,4 @@ import os -from typing import Optional import pytest from _pytest.config import Config @@ -8,13 +7,13 @@ from localstack import config as localstack_config from localstack import constants +from localstack.testing import config as test_config from localstack.testing.snapshots.transformer_utility import ( SNAPSHOT_BASIC_TRANSFORMER, SNAPSHOT_BASIC_TRANSFORMER_NEW, TransformerUtility, ) from localstack.utils.aws.arns import get_partition -from tests.aws.test_terraform import TestTerraform def pytest_configure(config: Config): @@ -28,6 +27,26 @@ def pytest_configure(config: Config): def pytest_runtestloop(session): + """ + This pytest plugin allows us to pre-install external dependencies that are usually lazy-loaded by the services. + This helps us surface download issues earlier. + This is not needed if we are running the test against an external instance, as it installs the dependencies on the + runner running the tests. + """ + if not session.items: + return + + if session.config.option.collectonly: + return + + if test_config.TEST_SKIP_LOCALSTACK_START: + return + + from localstack.testing.aws.util import is_aws_cloud + + if is_aws_cloud() and not test_config.TEST_FORCE_LOCALSTACK_START: + return + # second pytest lifecycle hook (before test runner starts) test_init_functions = set() @@ -58,19 +77,6 @@ def pytest_runtestloop(session): test_init_functions.add(transcribe_install_async) - # add init functions for certain tests that download/install things - for test_class in test_classes: - # set flag that terraform will be used - if TestTerraform is test_class: - test_init_functions.add(TestTerraform.init_async) - continue - - if not session.items: - return - - if session.config.option.collectonly: - return - for fn in test_init_functions: fn() @@ -87,9 +93,7 @@ def infrastructure_setup(cdk_template_path, aws_client): # Note: import needs to be local to avoid CDK import on every test run, which takes quite some time from localstack.testing.scenario.provisioning import InfraProvisioner - def _infrastructure_setup( - namespace: str, force_synth: Optional[bool] = False - ) -> InfraProvisioner: + def _infrastructure_setup(namespace: str, force_synth: bool | None = False) -> InfraProvisioner: """ :param namespace: repo-unique identifier for this CDK app. A directory with this name will be created at `tests/aws/cdk_templates//` @@ -154,3 +158,19 @@ def snapshot(request, _snapshot_session: SnapshotSession, account_id, region_nam _snapshot_session.add_transformer(SNAPSHOT_BASIC_TRANSFORMER_NEW, priority=2) return _snapshot_session + + +@pytest.hookimpl() +def pytest_addhooks(pluginmanager): + try: + # This is only relevant when running Community Tests against Pro pipeline + from localstack.pro.core.testing.pytest.store import StoreSerializationCheckerPlugin + + from localstack.testing.aws.util import is_aws_cloud + + if not test_config.TEST_SKIP_LOCALSTACK_START and not is_aws_cloud(): + # this directly accesses LocalStack state in memory, so it is not worth running in tests against external + # instances + pluginmanager.register(StoreSerializationCheckerPlugin(with_pickle=False)) + except ImportError: + pass diff --git a/tests/aws/scenario/bookstore/test_bookstore.py b/tests/aws/scenario/bookstore/test_bookstore.py index 149a7872a1fee..f22da4da2e745 100644 --- a/tests/aws/scenario/bookstore/test_bookstore.py +++ b/tests/aws/scenario/bookstore/test_bookstore.py @@ -220,10 +220,12 @@ def _sort_hits(snapshot_content: dict, *args) -> dict: snapshot.add_transformer(GenericTransformer(_sort_hits)) snapshot.add_transformer( KeyValueBasedTransformer( - lambda k, v: v - if k in ("took", "max_score", "_score") - and (isinstance(v, float) or isinstance(v, int)) - else None, + lambda k, v: ( + v + if k in ("took", "max_score", "_score") + and (isinstance(v, float) or isinstance(v, int)) + else None + ), replacement="", replace_reference=False, ) @@ -287,7 +289,9 @@ def _verify_search(category: str, expected_amount: int): "$..ClusterConfig.DedicatedMasterType", # added in LS "$..ClusterConfig.Options.DedicatedMasterCount", # added in LS "$..ClusterConfig.Options.DedicatedMasterType", # added in LS + "$..DomainStatusList..AIMLOptions", # missing "$..DomainStatusList..EBSOptions.Iops", # added in LS + "$..DomainStatusList..IdentityCenterOptions", # missing "$..DomainStatusList..IPAddressType", # missing "$..DomainStatusList..DomainProcessingStatus", # missing "$..DomainStatusList..ModifyingProperties", # missing @@ -299,7 +303,9 @@ def _verify_search(category: str, expected_amount: int): "$..ClusterConfig.MultiAZWithStandbyEnabled", # missing "$..AdvancedSecurityOptions.AnonymousAuthEnabled", # missing "$..AdvancedSecurityOptions.Options.AnonymousAuthEnabled", # missing + "$..DomainConfig.AIMLOptions", # missing "$..DomainConfig.ClusterConfig.Options.WarmEnabled", # missing + "$..DomainConfig.IdentityCenterOptions", # missing "$..DomainConfig.IPAddressType", # missing "$..DomainConfig.ModifyingProperties", # missing "$..ClusterConfig.Options.ColdStorageOptions", # missing diff --git a/tests/aws/scenario/bookstore/test_bookstore.snapshot.json b/tests/aws/scenario/bookstore/test_bookstore.snapshot.json index 4fdc081fcaf06..e042de8f6645c 100644 --- a/tests/aws/scenario/bookstore/test_bookstore.snapshot.json +++ b/tests/aws/scenario/bookstore/test_bookstore.snapshot.json @@ -549,11 +549,20 @@ } }, "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_opensearch_crud": { - "recorded-date": "15-07-2024, 12:55:29", + "recorded-date": "12-09-2025, 09:51:48", "recorded-content": { "describe_domains": { "DomainStatusList": [ { + "AIMLOptions": { + "NaturalLanguageQueryGenerationOptions": { + "CurrentState": "NOT_ENABLED", + "DesiredState": "DISABLED" + }, + "S3VectorsEngine": { + "Enabled": false + } + }, "ARN": "arn::es::111111111111:domain/", "AccessPolicies": "", "AdvancedOptions": { @@ -609,8 +618,9 @@ "Enabled": false }, "Endpoint": "", - "EngineVersion": "OpenSearch_2.11", + "EngineVersion": "OpenSearch_3.1", "IPAddressType": "ipv4", + "IdentityCenterOptions": {}, "ModifyingProperties": [], "NodeToNodeEncryptionOptions": { "Enabled": false @@ -628,7 +638,7 @@ "ServiceSoftwareOptions": { "AutomatedUpdateDate": "datetime", "Cancellable": false, - "CurrentVersion": "OpenSearch_2_11_R20240502-P2", + "CurrentVersion": "OpenSearch_3_1_R20250904-P1", "Description": "There is no software update available for this domain.", "NewVersion": "", "OptionalDeployment": true, @@ -663,6 +673,24 @@ }, "describe_domain_config": { "DomainConfig": { + "AIMLOptions": { + "Options": { + "NaturalLanguageQueryGenerationOptions": { + "CurrentState": "NOT_ENABLED", + "DesiredState": "DISABLED" + }, + "S3VectorsEngine": { + "Enabled": false + } + }, + "Status": { + "CreationDate": "datetime", + "PendingDeletion": false, + "State": "Active", + "UpdateDate": "datetime", + "UpdateVersion": "update-version" + } + }, "AccessPolicies": { "Options": "", "Status": { @@ -795,7 +823,7 @@ } }, "EngineVersion": { - "Options": "OpenSearch_2.11", + "Options": "OpenSearch_3.1", "Status": { "CreationDate": "datetime", "PendingDeletion": false, @@ -814,6 +842,16 @@ "UpdateVersion": "update-version" } }, + "IdentityCenterOptions": { + "Options": {}, + "Status": { + "CreationDate": "datetime", + "PendingDeletion": false, + "State": "Active", + "UpdateDate": "datetime", + "UpdateVersion": "update-version" + } + }, "LogPublishingOptions": { "Options": {}, "Status": { @@ -926,10 +964,8 @@ "get_compatible_versions": { "CompatibleVersions": [ { - "SourceVersion": "OpenSearch_2.11", - "TargetVersions": [ - "OpenSearch_2.13" - ] + "SourceVersion": "OpenSearch_3.1", + "TargetVersions": [] } ], "ResponseMetadata": { @@ -939,6 +975,10 @@ }, "list_versions": { "Versions": [ + "OpenSearch_3.1", + "OpenSearch_2.19", + "OpenSearch_2.17", + "OpenSearch_2.15", "OpenSearch_2.13", "OpenSearch_2.11", "OpenSearch_2.9", diff --git a/tests/aws/scenario/bookstore/test_bookstore.validation.json b/tests/aws/scenario/bookstore/test_bookstore.validation.json index 3b64f0fc25b2f..ddcc18749085b 100644 --- a/tests/aws/scenario/bookstore/test_bookstore.validation.json +++ b/tests/aws/scenario/bookstore/test_bookstore.validation.json @@ -3,7 +3,13 @@ "last_validated_date": "2024-07-15T12:54:17+00:00" }, "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_opensearch_crud": { - "last_validated_date": "2024-07-15T12:55:29+00:00" + "last_validated_date": "2025-09-12T10:07:07+00:00", + "durations_in_seconds": { + "setup": 1157.65, + "call": 3.36, + "teardown": 919.26, + "total": 2080.27 + } }, "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_search_books": { "last_validated_date": "2024-07-15T12:55:25+00:00" diff --git a/tests/aws/scenario/kinesis_firehose/conftest.py b/tests/aws/scenario/kinesis_firehose/conftest.py index 1facb91c31de9..e50dd1c0e7a41 100644 --- a/tests/aws/scenario/kinesis_firehose/conftest.py +++ b/tests/aws/scenario/kinesis_firehose/conftest.py @@ -13,7 +13,7 @@ def read_s3_data(aws_client, bucket_name: str) -> dict[str, str]: keys = [obj.get("Key") for obj in response.get("Contents")] - bucket_data = dict() + bucket_data = {} for key in keys: response = aws_client.s3.get_object(Bucket=bucket_name, Key=key) data = response["Body"].read().decode("utf-8") diff --git a/tests/aws/scenario/loan_broker/test_loan_broker.py b/tests/aws/scenario/loan_broker/test_loan_broker.py index 4eb105c440398..f356437d06e8a 100644 --- a/tests/aws/scenario/loan_broker/test_loan_broker.py +++ b/tests/aws/scenario/loan_broker/test_loan_broker.py @@ -82,10 +82,8 @@ def infrastructure(self, aws_client, infrastructure_setup): @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ - "$..Table.DeletionProtectionEnabled", "$..Table.ProvisionedThroughput.LastDecreaseDateTime", "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", ] ) def test_prefill_dynamodb_table(self, aws_client, infrastructure, snapshot): @@ -166,7 +164,13 @@ def test_prefill_dynamodb_table(self, aws_client, infrastructure, snapshot): ) @markers.aws.validated @markers.snapshot.skip_snapshot_verify( - paths=["$..traceHeader", "$..cause"] + paths=[ + "$..traceHeader", + "$..cause", + "$..redriveCount", + "$..redriveStatus", + "$..redriveStatusReason", + ] ) # TODO add missing properties def test_stepfunctions_input_recipient_list( self, aws_client, infrastructure, step_function_input, expected_result, snapshot diff --git a/tests/aws/scenario/loan_broker/test_loan_broker.snapshot.json b/tests/aws/scenario/loan_broker/test_loan_broker.snapshot.json index ad5837f072b94..9f244762e2228 100644 --- a/tests/aws/scenario/loan_broker/test_loan_broker.snapshot.json +++ b/tests/aws/scenario/loan_broker/test_loan_broker.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]": { - "recorded-date": "02-08-2023, 11:58:05", + "recorded-date": "13-10-2025, 18:55:01", "recorded-content": { "describe-execution-finished": { "executionArn": "", @@ -52,6 +52,9 @@ "outputDetails": { "included": true }, + "redriveCount": 0, + "redriveStatus": "NOT_REDRIVABLE", + "redriveStatusReason": "Execution is SUCCEEDED and cannot be redriven", "startDate": "datetime", "stateMachineArn": "", "status": "SUCCEEDED", @@ -65,7 +68,7 @@ } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]": { - "recorded-date": "02-08-2023, 11:58:07", + "recorded-date": "13-10-2025, 18:55:03", "recorded-content": { "describe-execution-finished": { "executionArn": "", @@ -108,6 +111,9 @@ "outputDetails": { "included": true }, + "redriveCount": 0, + "redriveStatus": "NOT_REDRIVABLE", + "redriveStatusReason": "Execution is SUCCEEDED and cannot be redriven", "startDate": "datetime", "stateMachineArn": "", "status": "SUCCEEDED", @@ -121,7 +127,7 @@ } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input2-FAILED]": { - "recorded-date": "02-08-2023, 11:58:10", + "recorded-date": "13-10-2025, 18:55:04", "recorded-content": { "describe-execution-finished": { "cause": "An error occurred while executing the state 'Get Credit Score from credit bureau' (entered at the event id #2). The JSONPath '$.Payload.body.score' specified for the field 'Score.$' could not be found in the input ''", @@ -136,6 +142,8 @@ "included": true }, "name": "", + "redriveCount": 0, + "redriveStatus": "REDRIVABLE", "startDate": "datetime", "stateMachineArn": "", "status": "FAILED", @@ -149,7 +157,7 @@ } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input3-FAILED]": { - "recorded-date": "02-08-2023, 11:58:10", + "recorded-date": "13-10-2025, 18:55:05", "recorded-content": { "describe-execution-finished": { "cause": "An error occurred while executing the state 'Get Credit Score from credit bureau' (entered at the event id #2). The JSONPath '$.SSN' specified for the field 'SSN.$' could not be found in the input ''", @@ -162,6 +170,8 @@ "included": true }, "name": "", + "redriveCount": 0, + "redriveStatus": "REDRIVABLE", "startDate": "datetime", "stateMachineArn": "", "status": "FAILED", @@ -175,7 +185,7 @@ } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input4-FAILED]": { - "recorded-date": "02-08-2023, 11:58:13", + "recorded-date": "13-10-2025, 18:55:07", "recorded-content": { "describe-execution-finished": { "cause": "An error occurred while executing the state 'Get all bank quotes' (entered at the event id #12). The JSONPath '$.Amount' specified for the field 'Amount.$' could not be found in the input ''", @@ -188,6 +198,8 @@ "included": true }, "name": "", + "redriveCount": 0, + "redriveStatus": "REDRIVABLE", "startDate": "datetime", "stateMachineArn": "", "status": "FAILED", @@ -201,7 +213,7 @@ } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_prefill_dynamodb_table": { - "recorded-date": "24-08-2023, 16:33:21", + "recorded-date": "13-10-2025, 18:54:57", "recorded-content": { "describe_table": { "Table": { @@ -233,7 +245,12 @@ "TableId": "", "TableName": "LoanBrokerBanksTable", "TableSizeBytes": 0, - "TableStatus": "ACTIVE" + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, diff --git a/tests/aws/scenario/loan_broker/test_loan_broker.validation.json b/tests/aws/scenario/loan_broker/test_loan_broker.validation.json index 21a07e5f891df..3b0ba70982865 100644 --- a/tests/aws/scenario/loan_broker/test_loan_broker.validation.json +++ b/tests/aws/scenario/loan_broker/test_loan_broker.validation.json @@ -1,20 +1,56 @@ { "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_prefill_dynamodb_table": { - "last_validated_date": "2023-08-24T14:33:21+00:00" + "last_validated_date": "2025-10-13T18:54:57+00:00", + "durations_in_seconds": { + "setup": 64.27, + "call": 1.44, + "teardown": 0.0, + "total": 65.71 + } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]": { - "last_validated_date": "2023-08-02T09:58:05+00:00" + "last_validated_date": "2025-10-13T18:55:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.37, + "teardown": 0.0, + "total": 3.37 + } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]": { - "last_validated_date": "2023-08-02T09:58:07+00:00" + "last_validated_date": "2025-10-13T18:55:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.85, + "teardown": 0.0, + "total": 1.85 + } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input2-FAILED]": { - "last_validated_date": "2023-08-02T09:58:10+00:00" + "last_validated_date": "2025-10-13T18:55:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.74, + "teardown": 0.0, + "total": 1.74 + } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input3-FAILED]": { - "last_validated_date": "2023-08-02T09:58:10+00:00" + "last_validated_date": "2025-10-13T18:55:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.61, + "teardown": 0.0, + "total": 0.61 + } }, "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input4-FAILED]": { - "last_validated_date": "2023-08-02T09:58:13+00:00" + "last_validated_date": "2025-10-13T18:56:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.85, + "teardown": 96.39, + "total": 98.24 + } } } diff --git a/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py b/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py index faebcdbaa8b5f..4b419e90cd4ac 100644 --- a/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py +++ b/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py @@ -1,5 +1,4 @@ # Source adapted from: https://github.com/aws-samples/aws-modern-application-workshop/blob/python-cdk -from __future__ import print_function import os diff --git a/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py b/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py index 2d58be787fc83..acf8ec729956e 100644 --- a/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py +++ b/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py @@ -2,7 +2,6 @@ # The code to be used as an AWS Lambda function for processing real-time # user click records from Kinesis Firehose and adding additional attributes # to them before they are stored in Amazon S3. -from __future__ import print_function import base64 import json diff --git a/tests/aws/scenario/note_taking/test_note_taking.py b/tests/aws/scenario/note_taking/test_note_taking.py index 492e3c1aa0946..96a2778a619bc 100644 --- a/tests/aws/scenario/note_taking/test_note_taking.py +++ b/tests/aws/scenario/note_taking/test_note_taking.py @@ -162,11 +162,8 @@ def _create_lambdas(): paths=[ "$..Tags", "$..get_resources.items", # TODO apigateway.get-resources - "$..Table.DeletionProtectionEnabled", "$..Table.ProvisionedThroughput.LastDecreaseDateTime", "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - "$..Table.WarmThroughput", ] ) def test_validate_infra_setup(self, aws_client, infrastructure, snapshot): diff --git a/tests/aws/services/acm/test_acm.py b/tests/aws/services/acm/test_acm.py index 616d6c46108ee..e3e25e096f65e 100644 --- a/tests/aws/services/acm/test_acm.py +++ b/tests/aws/services/acm/test_acm.py @@ -1,4 +1,5 @@ import pytest +from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import SortingTransformer from moto import settings as moto_settings @@ -6,7 +7,7 @@ from localstack.testing.pytest import markers from localstack.utils.crypto import generate_ssl_cert from localstack.utils.strings import short_uid -from localstack.utils.sync import retry +from localstack.utils.sync import retry, wait_until class TestACM: @@ -27,7 +28,7 @@ class TestACM: ] ) def test_import_certificate(self, tmp_path, aws_client, cleanups, snapshot): - with pytest.raises(Exception) as exc_info: + with pytest.raises(ClientError) as exc_info: aws_client.acm.import_certificate(Certificate=b"CERT123", PrivateKey=b"KEY123") assert exc_info.value.response["Error"]["Code"] == "ValidationException" @@ -72,9 +73,19 @@ def test_domain_validation(self, acm_request_certificate, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.key_value("SignatureAlgorithm")) certificate_arn = acm_request_certificate()["CertificateArn"] + + # we are manually waiting for some fields to be returned, as they are missing soon after creating the cert + def _cert_has_required_fields() -> bool: + _resp = aws_client.acm.describe_certificate(CertificateArn=certificate_arn) + return "DomainName" in _resp["Certificate"] + + if is_aws_cloud(): + wait_until(_cert_has_required_fields, wait=2, max_retries=20) + result = aws_client.acm.describe_certificate(CertificateArn=certificate_arn) snapshot.match("describe-certificate", result) + @markers.requires_in_process @markers.aws.needs_fixing def test_boto_wait_for_certificate_validation( self, acm_request_certificate, aws_client, monkeypatch @@ -84,8 +95,17 @@ def test_boto_wait_for_certificate_validation( waiter = aws_client.acm.get_waiter("certificate_validated") waiter.wait(CertificateArn=certificate_arn, WaiterConfig={"Delay": 0.5, "MaxAttempts": 3}) - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..Certificate.SignatureAlgorithm"]) + @markers.aws.manual_setup_required + # this test requires manual input to our DNS provider + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Certificate.SignatureAlgorithm", + # those should also be returned by AWS, but regenerating the snapshots needs manual input + # skipped for now, validated by other tests + "$..Certificate.Options.Export", + "$..Exported", + ] + ) def test_certificate_for_subdomain_wildcard( self, acm_request_certificate, aws_client, snapshot, monkeypatch ): @@ -123,7 +143,7 @@ def _get_cert_with_records(): if is_aws_cloud(): # Wait until DNS entry has been added (needs to be done manually!) # Note: When running parity tests against AWS, we need to add the CNAME record to our DNS - # server (currently with gandi.net), to enable validation of the certificate. + # server (currently with Route53), to enable validation of the certificate. prompt = ( f"Please add the following CNAME entry to the LocalStack DNS server, then hit [ENTER] once " f"the certificate has been validated in AWS: {dns_options['Name']} = {dns_options['Value']}" @@ -161,6 +181,7 @@ def _get_cert_issued(): "$..ResourceRecord", "$..SignatureAlgorithm", "$..Serial", + "$..ExportOption", ] ) def test_create_certificate_for_multiple_alternative_domains( @@ -206,3 +227,106 @@ def _certificate_ready(): cert_description["DomainValidationOptions"], key=lambda x: x["DomainName"] ) snapshot.match("describe-cert", cert_description) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..CreatedAt", + "$..Exported", + "$..ExportOption", + "$..ExtendedKeyUsages", + "$..KeyUsages", + ] + ) + def test_list_certificates_with_key_types_filter_imported_certificate( + self, tmp_path, aws_client, cleanups, snapshot + ): + _, cert_file, key_file = generate_ssl_cert(target_file=str(tmp_path / "cert")) + with open(key_file, "rb") as infile: + private_key_bytes = infile.read() + with open(cert_file, "rb") as infile: + certificate_bytes = infile.read() + + import_response = aws_client.acm.import_certificate( + Certificate=certificate_bytes, PrivateKey=private_key_bytes + ) + cert_arn = import_response["CertificateArn"] + cleanups.append(lambda: aws_client.acm.delete_certificate(CertificateArn=cert_arn)) + + cert_id = cert_arn.split("certificate/")[-1] + snapshot.add_transformer(snapshot.transform.regex(cert_id, "")) + + def _certificate_ready(): + response = aws_client.acm.describe_certificate(CertificateArn=cert_arn) + # expecting FAILED on aws due to not requesting a valid certificate + # expecting ISSUED as default response from moto + if response["Certificate"]["Status"] not in ["FAILED", "ISSUED"]: + raise Exception("Certificate not yet ready") + + retry(_certificate_ready, sleep=1, retries=30) + + def _list_response(): + response = aws_client.acm.list_certificates( + Includes={ + "keyTypes": [ + "RSA_1024", + "RSA_2048", + "RSA_3072", + "RSA_4096", + "EC_prime256v1", + "EC_secp384r1", + "EC_secp521r1", + ] + } + ) + + cert = next( + filter( + lambda c: c["CertificateArn"] == cert_arn, response["CertificateSummaryList"] + ) + ) + snapshot.match("list-certificates-with-key-types", cert) + + retry(_list_response, sleep=1, retries=30) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..CertificateSummaryList..DomainName", + "$..CertificateSummaryList..SubjectAlternativeNameSummaries", + ] + ) + def test_list_certificates_with_key_types_filter_requested_certificate( + self, acm_request_certificate, aws_client, snapshot + ): + create_response = acm_request_certificate() + cert_arn = create_response["CertificateArn"] + + cert_id = cert_arn.split("certificate/")[-1] + snapshot.add_transformer(snapshot.transform.regex(cert_id, "")) + + def _certificate_ready(): + response = aws_client.acm.describe_certificate(CertificateArn=cert_arn) + assert response["Certificate"]["Status"] in ["PENDING_VALIDATION", "ISSUED", "FAILED"] + return response + + retry(_certificate_ready, sleep=1, retries=30) + + list_response = aws_client.acm.list_certificates( + Includes={ + "keyTypes": [ + "RSA_1024", + "RSA_2048", + "RSA_3072", + "RSA_4096", + "EC_prime256v1", + "EC_secp384r1", + "EC_secp521r1", + ] + } + ) + + cert_arns = [cert["CertificateArn"] for cert in list_response["CertificateSummaryList"]] + assert cert_arn in cert_arns + + snapshot.match("list-certificates-with-key-types", list_response) diff --git a/tests/aws/services/acm/test_acm.snapshot.json b/tests/aws/services/acm/test_acm.snapshot.json index 7b68ad84f0ef6..64465322c4417 100644 --- a/tests/aws/services/acm/test_acm.snapshot.json +++ b/tests/aws/services/acm/test_acm.snapshot.json @@ -139,19 +139,21 @@ } }, "tests/aws/services/acm/test_acm.py::TestACM::test_create_certificate_for_multiple_alternative_domains": { - "recorded-date": "09-01-2024, 14:58:14", + "recorded-date": "14-10-2025, 09:18:38", "recorded-content": { "list-cert-summary-list": { "CertificateArn": "arn::acm::111111111111:certificate/", "CreatedAt": "datetime", "DomainName": "test.example.com", + "ExportOption": "DISABLED", + "Exported": false, "ExtendedKeyUsages": [], "HasAdditionalSubjectAlternativeNames": false, "InUse": false, "KeyAlgorithm": "RSA-2048", "KeyUsages": [], "RenewalEligibility": "INELIGIBLE", - "Status": "FAILED", + "Status": "PENDING_VALIDATION", "SubjectAlternativeNameSummaries": [ "*.test.example.com", "another.domain.com", @@ -201,7 +203,8 @@ "KeyAlgorithm": "RSA-2048", "KeyUsages": [], "Options": { - "CertificateTransparencyLoggingPreference": "ENABLED" + "CertificateTransparencyLoggingPreference": "ENABLED", + "Export": "DISABLED" }, "RenewalEligibility": "INELIGIBLE", "SignatureAlgorithm": "SHA256WITHRSA", @@ -218,7 +221,7 @@ } }, "tests/aws/services/acm/test_acm.py::TestACM::test_import_certificate": { - "recorded-date": "22-02-2024, 17:41:15", + "recorded-date": "14-10-2025, 09:17:56", "recorded-content": { "import-certificate-response": { "CertificateArn": "arn::acm::111111111111:certificate/", @@ -270,7 +273,8 @@ "NotAfter": "datetime", "NotBefore": "datetime", "Options": { - "CertificateTransparencyLoggingPreference": "DISABLED" + "CertificateTransparencyLoggingPreference": "DISABLED", + "Export": "DISABLED" }, "RenewalEligibility": "INELIGIBLE", "Serial": "03:e9", @@ -293,7 +297,7 @@ } }, "tests/aws/services/acm/test_acm.py::TestACM::test_domain_validation": { - "recorded-date": "12-04-2024, 15:36:37", + "recorded-date": "14-10-2025, 09:31:48", "recorded-content": { "describe-certificate": { "Certificate": { @@ -315,7 +319,8 @@ "KeyAlgorithm": "RSA-2048", "KeyUsages": [], "Options": { - "CertificateTransparencyLoggingPreference": "ENABLED" + "CertificateTransparencyLoggingPreference": "ENABLED", + "Export": "DISABLED" }, "RenewalEligibility": "INELIGIBLE", "SignatureAlgorithm": "", @@ -332,5 +337,69 @@ } } } + }, + "tests/aws/services/acm/test_acm.py::TestACM::test_list_certificates_with_key_types_filter_imported_certificate": { + "recorded-date": "09-12-2025, 12:36:59", + "recorded-content": { + "list-certificates-with-key-types": { + "CertificateArn": "arn::acm::111111111111:certificate/", + "CreatedAt": "datetime", + "DomainName": "localhost", + "ExportOption": "DISABLED", + "ExtendedKeyUsages": [ + "TLS_WEB_SERVER_AUTHENTICATION" + ], + "HasAdditionalSubjectAlternativeNames": false, + "ImportedAt": "datetime", + "InUse": false, + "KeyAlgorithm": "RSA-2048", + "KeyUsages": [ + "DIGITAL_SIGNATURE", + "NON_REPUDIATION", + "KEY_ENCIPHERMENT" + ], + "NotAfter": "datetime", + "NotBefore": "datetime", + "RenewalEligibility": "INELIGIBLE", + "Status": "ISSUED", + "SubjectAlternativeNameSummaries": [ + "localhost", + "test.localhost.atlassian.io", + "localhost.localstack.cloud", + "localhost.localstack.cloudIP:127.0.0.1" + ], + "Type": "IMPORTED" + } + } + }, + "tests/aws/services/acm/test_acm.py::TestACM::test_list_certificates_with_key_types_filter_requested_certificate": { + "recorded-date": "03-12-2025, 04:02:40", + "recorded-content": { + "list-certificates-with-key-types": { + "CertificateSummaryList": [ + { + "CertificateArn": "arn::acm::111111111111:certificate/", + "CreatedAt": "datetime", + "DomainName": "test-domain-2b60e4d1.localhost.localstack.cloud", + "Exported": false, + "ExtendedKeyUsages": [], + "HasAdditionalSubjectAlternativeNames": false, + "InUse": false, + "KeyAlgorithm": "RSA-2048", + "KeyUsages": [], + "RenewalEligibility": "INELIGIBLE", + "Status": "PENDING_VALIDATION", + "SubjectAlternativeNameSummaries": [ + "test-domain-2b60e4d1.localhost.localstack.cloud" + ], + "Type": "AMAZON_ISSUED" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/acm/test_acm.validation.json b/tests/aws/services/acm/test_acm.validation.json index 8201f405c3e71..032ea2a286ae4 100644 --- a/tests/aws/services/acm/test_acm.validation.json +++ b/tests/aws/services/acm/test_acm.validation.json @@ -3,12 +3,39 @@ "last_validated_date": "2023-04-18T17:01:27+00:00" }, "tests/aws/services/acm/test_acm.py::TestACM::test_create_certificate_for_multiple_alternative_domains": { - "last_validated_date": "2024-01-09T14:58:14+00:00" + "last_validated_date": "2025-10-14T09:18:39+00:00", + "durations_in_seconds": { + "setup": 0.45, + "call": 5.97, + "teardown": 0.19, + "total": 6.61 + } }, "tests/aws/services/acm/test_acm.py::TestACM::test_domain_validation": { - "last_validated_date": "2024-04-12T15:36:37+00:00" + "last_validated_date": "2025-10-14T09:31:48+00:00", + "durations_in_seconds": { + "setup": 0.46, + "call": 2.94, + "teardown": 0.21, + "total": 3.61 + } }, "tests/aws/services/acm/test_acm.py::TestACM::test_import_certificate": { - "last_validated_date": "2024-02-22T17:41:15+00:00" + "last_validated_date": "2025-10-14T09:17:56+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 1.25, + "teardown": 0.25, + "total": 1.98 + } + }, + "tests/aws/services/acm/test_acm.py::TestACM::test_list_certificates_with_key_types_filter_imported_certificate": { + "last_validated_date": "2025-12-09T12:42:45+00:00", + "durations_in_seconds": { + "setup": 0.57, + "call": 2.11, + "teardown": 0.23, + "total": 2.91 + } } } diff --git a/tests/aws/services/apigateway/apigateway_fixtures.py b/tests/aws/services/apigateway/apigateway_fixtures.py index e7d58b40c5ba2..61326ba581cb6 100644 --- a/tests/aws/services/apigateway/apigateway_fixtures.py +++ b/tests/aws/services/apigateway/apigateway_fixtures.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Dict from localstack.services.apigateway.helpers import ( host_based_url, @@ -12,16 +11,16 @@ # TODO convert the test util functions in this file to pytest fixtures -def assert_response_status(response: Dict, status: int): +def assert_response_status(response: dict, status: int): assert response.get("ResponseMetadata").get("HTTPStatusCode") == status -def assert_response_is_200(response: Dict) -> bool: +def assert_response_is_200(response: dict) -> bool: assert_response_status(response, 200) return True -def assert_response_is_201(response: Dict) -> bool: +def assert_response_is_201(response: dict) -> bool: assert_response_status(response, 201) return True diff --git a/tests/aws/services/apigateway/conftest.py b/tests/aws/services/apigateway/conftest.py index 88ac5575de221..c541e426e8389 100644 --- a/tests/aws/services/apigateway/conftest.py +++ b/tests/aws/services/apigateway/conftest.py @@ -1,3 +1,6 @@ +from itertools import count +from typing import TYPE_CHECKING + import pytest from botocore.config import Config @@ -17,6 +20,9 @@ ) from tests.aws.services.lambda_.test_lambda import TEST_LAMBDA_PYTHON_ECHO_STATUS_CODE +if TYPE_CHECKING: + from mypy_boto3_ec2.type_defs import VpcTypeDef + # default name used for created REST API stages DEFAULT_STAGE_NAME = "dev" @@ -238,3 +244,69 @@ def _import_apigateway_function(*args, **kwargs): def apigw_add_transformers(snapshot): snapshot.add_transformer(snapshot.transform.jsonpath("$..items..id", "id")) snapshot.add_transformer(snapshot.transform.key_value("deploymentId")) + + +@pytest.fixture +def apigw_test_invoke_response_formatter(snapshot): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value( + "latency", value_replacement="", reference_replacement=False + ), + snapshot.transform.jsonpath( + "$..headers.X-Amzn-Trace-Id", value_replacement="x-amz-trace-id" + ), + snapshot.transform.regex( + r"URI: https:\/\/.*?\/2015-03-31", "URI: https:///2015-03-31" + ), + snapshot.transform.regex( + r"Integration latency: \d*? ms", "Integration latency: ms" + ), + snapshot.transform.regex( + r"Date=[a-zA-Z]{3},\s\d{2}\s[a-zA-Z]{3}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT", + "Date=Day, dd MMM yyyy hh:mm:ss GMT", + ), + snapshot.transform.regex( + r"x-amzn-RequestId=[a-f0-9-]{36}", "x-amzn-RequestId=" + ), + snapshot.transform.regex( + r"[a-zA-Z]{3}\s[a-zA-Z]{3}\s\d{2}\s\d{2}:\d{2}:\d{2}\sUTC\s\d{4} :", + "DDD MMM dd hh:mm:ss UTC yyyy :", + ), + snapshot.transform.regex( + r"Authorization=.*?,", "Authorization=," + ), + snapshot.transform.regex( + r"X-Amz-Security-Token=.*?\s\[", "X-Amz-Security-Token= [" + ), + snapshot.transform.regex(r"\d{8}T\d{6}Z", ""), + ] + ) + + def _transform_log(_log: str) -> dict[str, str]: + return {f"line{index:02d}": line for index, line in enumerate(_log.split("\n"))} + + counter = count(start=1) + + # we want to do very precise matching on the log, and splitting on new lines will help in case the snapshot + # fails + # the snapshot library does not allow to ignore an array index as the last node, so we need to put it in a dict + + def transform_response(response: dict) -> dict: + response["log"] = _transform_log(response["log"]) + request_id = response["log"]["line00"].split(" ")[-1] + snapshot.add_transformer( + snapshot.transform.regex(request_id, f""), priority=-1 + ) + return response + + return transform_response + + +@pytest.fixture +def default_vpc(aws_client) -> "VpcTypeDef": + vpcs = aws_client.ec2.describe_vpcs() + for vpc in vpcs["Vpcs"]: + if vpc.get("IsDefault"): + return vpc + raise Exception("Default VPC not found") diff --git a/tests/aws/services/apigateway/test_apigateway_api.py b/tests/aws/services/apigateway/test_apigateway_api.py index 2e2a8c523dffd..19fb4a1d33b81 100644 --- a/tests/aws/services/apigateway/test_apigateway_api.py +++ b/tests/aws/services/apigateway/test_apigateway_api.py @@ -3,13 +3,15 @@ import os.path import time from operator import itemgetter +from typing import TYPE_CHECKING, Unpack import pytest from botocore.config import Config from botocore.exceptions import ClientError -from localstack_snapshot.snapshots.transformer import KeyValueBasedTransformer, SortingTransformer +from localstack_snapshot.snapshots.transformer import SortingTransformer from localstack.aws.api.apigateway import PutMode +from localstack.aws.connect import ServiceLevelClientFactory from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.files import load_file @@ -24,6 +26,10 @@ ) from tests.aws.services.apigateway.conftest import is_next_gen_api +if TYPE_CHECKING: + from mypy_boto3_apigateway.type_defs import CreateVpcLinkRequestTypeDef, VpcLinkResponseTypeDef + + LOG = logging.getLogger(__name__) THIS_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -98,6 +104,28 @@ def _factory(*args, **kwargs): delete_rest_api_retry(apigateway_client, rest_api_id) +@pytest.fixture +def apigw_create_vpc_link(aws_client): + vpc_links: list[tuple[ServiceLevelClientFactory, str]] = [] + + def _create_vpc_link( + client: ServiceLevelClientFactory | None = None, + **kwargs: Unpack["CreateVpcLinkRequestTypeDef"], + ) -> "VpcLinkResponseTypeDef": + client = client or aws_client + vpc_link = client.apigateway.create_vpc_link(**kwargs) + vpc_links.append((client, vpc_link["id"])) + return vpc_link + + yield _create_vpc_link + + for _client, vpc_link_id in vpc_links: + try: + _client.apigateway.delete_vpc_link(vpcLinkId=vpc_link_id) + except ClientError as e: + LOG.error("Error deleting VPC link: %s", e) + + class TestApiGatewayApiRestApi: @markers.aws.validated def test_list_and_delete_apis(self, apigw_create_rest_api, snapshot, aws_client): @@ -850,6 +878,33 @@ def test_update_resource_behaviour(self, apigw_create_rest_api, snapshot, aws_cl ) snapshot.match("add-unsupported", e.value.response) + @markers.aws.validated + def test_update_resource_on_root(self, apigw_create_rest_api, snapshot, aws_client): + snapshot.add_transformer(SortingTransformer("items", lambda x: x["path"])) + response = apigw_create_rest_api( + name=f"test-api-{short_uid()}", description="testing resource behaviour" + ) + api_id = response["id"] + root_id = response["rootResourceId"] + + patch_operations = [ + {"op": "replace", "path": "/pathPart", "value": "dogs"}, + ] + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_resource( + restApiId=api_id, resourceId=root_id, patchOperations=patch_operations + ) + snapshot.match("update-root-path-part", e.value.response) + + patch_operations = [ + {"op": "replace", "path": "/parentId", "value": root_id}, + ] + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_resource( + restApiId=api_id, resourceId=root_id, patchOperations=patch_operations + ) + snapshot.match("update-root-parent", e.value.response) + @markers.aws.validated def test_delete_resource(self, apigw_create_rest_api, snapshot, aws_client): response = apigw_create_rest_api( @@ -2221,6 +2276,13 @@ def test_invalid_delete_documentation_part(self, apigw_create_rest_api, snapshot snapshot.match("delete_already_deleted_documentation_part", e.value.response) @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # FIXME: AWS returns the `path` field for RESPONSE and METHOD + "$.get-documentations-parts-after-import.items[1].location.path", + "$.get-documentations-parts-after-import.items[3].location.path", + ] + ) def test_import_documentation_parts(self, aws_client, import_apigw, snapshot): # snapshot array "ids" snapshot.add_transformer(snapshot.transform.jsonpath("$..ids[*]", "id")) @@ -2231,7 +2293,9 @@ def test_import_documentation_parts(self, aws_client, import_apigw, snapshot): # get documentation parts to make sure import worked response = aws_client.apigateway.get_documentation_parts(restApiId=rest_api_id) - snapshot.match("create-import-documentations_parts", response["items"]) + snapshot.match("get-documentations-parts-after-api-imports", response) + # manually assert that the type is of string because snapshot auto-decodes JSON + assert isinstance(response["items"][0]["properties"], str) # delete documentation parts for doc_part_item in response["items"]: @@ -2253,6 +2317,39 @@ def test_import_documentation_parts(self, aws_client, import_apigw, snapshot): ) snapshot.match("import-documentation-parts", response) + # get documentation parts to make sure import worked + response = aws_client.apigateway.get_documentation_parts(restApiId=rest_api_id) + snapshot.match("get-documentations-parts-after-import", response) + assert isinstance(response["items"][0]["properties"], str) + + @markers.aws.validated + def test_import_documentation_parts_bad_file(self, aws_client, apigw_create_rest_api, snapshot): + rest_api_id = apigw_create_rest_api( + name=f"test-api-{short_uid()}", + description="APIGW test import documentation", + )["id"] + + bad_yaml_string = """test: + value: + - "value ... \"escaped\": $var, \"dt\": $(var +"%a")}\" + """ + + with pytest.raises(ClientError) as e: + aws_client.apigateway.import_documentation_parts( + restApiId=rest_api_id, + mode=PutMode.overwrite, + body=bad_yaml_string, + ) + snapshot.match("import-documentation-parts-bad-yaml-file", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.apigateway.import_documentation_parts( + restApiId=rest_api_id, + mode=PutMode.overwrite, + body="{'key:value}", + ) + snapshot.match("import-documentation-parts-bad-json-file", e.value.response) + class TestApiGatewayGatewayResponse: @markers.aws.validated @@ -2597,33 +2694,166 @@ def test_update_gateway_response( ) -class TestApigatewayTestInvoke: +class TestApiGatewayVpcLink: @markers.aws.validated - def test_invoke_test_method(self, create_rest_apigw, snapshot, aws_client): - snapshot.add_transformer( - KeyValueBasedTransformer( - lambda k, v: str(v) if k == "latency" else None, "latency", replace_reference=False + @markers.snapshot.skip_snapshot_verify(paths=["$.update_vpc_link.tags", "$.get_vpc_link.tags"]) + def test_vpc_link_lifecycle( + self, + aws_client, + snapshot, + cleanups, + default_vpc, + region_name, + apigw_create_vpc_link, + account_id, + ): + snapshot.add_transformer(snapshot.transform.key_value("nlb-arn")) + + retries = 240 if is_aws_cloud() else 3 + sleep = 3 if is_aws_cloud() else 1 + + if is_aws_cloud(): + vpc_id = default_vpc["VpcId"] + subnets = aws_client.ec2.describe_subnets( + Filters=[{"Name": "vpc-id", "Values": [vpc_id]}] + )["Subnets"] + # require at least 2 subnets for the NLB + assert len(subnets) >= 2 + nlb = aws_client.elbv2.create_load_balancer( + Name=f"nlb-{short_uid()}", + Type="network", + Subnets=[subnets[0]["SubnetId"], subnets[1]["SubnetId"]], + )["LoadBalancers"][0] + nlb_arn = nlb["LoadBalancerArn"] + cleanups.append(lambda: aws_client.elbv2.delete_load_balancer(LoadBalancerArn=nlb_arn)) + waiter = aws_client.elbv2.get_waiter("load_balancer_available") + waiter.wait( + LoadBalancerArns=[nlb_arn], WaiterConfig={"Delay": sleep, "MaxAttempts": retries} ) + else: + # ElbV2 is not available in community, so we just use a dummy arn + nlb_arn = f"arn:aws:elasticloadbalancing:{region_name}:{account_id}:loadbalancer/net/my-load-balancer/50dc6c495c0c9188" + + snapshot.match("nlb-arn", nlb_arn) + + # create vpc link + vpc_link_name = f"test-vpc-link-{short_uid()}" + create_vpc_link_response = apigw_create_vpc_link(name=vpc_link_name, targetArns=[nlb_arn]) + snapshot.match("create_vpc_link", create_vpc_link_response) + vpc_link_id = create_vpc_link_response["id"] + + # get vpc link + # AWS needs some time to make the VPC link available + def _wait_for_vpc_link_available(): + get_vpc_link_response = aws_client.apigateway.get_vpc_link(vpcLinkId=vpc_link_id) + assert get_vpc_link_response["status"] == "AVAILABLE" + return get_vpc_link_response + + vpc_link_response = retry(_wait_for_vpc_link_available, retries=retries, sleep=sleep) + snapshot.match("get_vpc_link", vpc_link_response) + + # get vpc links + get_vpc_links_response = aws_client.apigateway.get_vpc_links() + # for the snapshot to be stable, we need to filter for the VPC link we created + get_vpc_links_response["items"] = [ + item for item in get_vpc_links_response["items"] if item["id"] == vpc_link_id + ] + snapshot.match("get_vpc_links", get_vpc_links_response) + + # update vpc link + patch_operations = [ + {"op": "replace", "path": "/name", "value": f"{vpc_link_name}-updated"}, + ] + update_vpc_link_response = aws_client.apigateway.update_vpc_link( + vpcLinkId=vpc_link_id, patchOperations=patch_operations ) - # TODO: maybe transformer `log` better - snapshot.add_transformer( - snapshot.transform.key_value("log", "log", reference_replacement=False) - ) + snapshot.match("update_vpc_link", update_vpc_link_response) + + delete_response = aws_client.apigateway.delete_vpc_link(vpcLinkId=vpc_link_id) + snapshot.match("delete_vpc_link", delete_response) + + def _wait_for_deleted(): + try: + vpc_link = aws_client.apigateway.get_vpc_link(vpcLinkId=vpc_link_id) + # this assertion shouldn't happen, but this will ensure failure if the vpc link is still being deleted + assert vpc_link["status"] == "DELETED" + except aws_client.apigateway.exceptions.NotFoundException: + pass - api_id, _, root = create_rest_apigw(name="aws lambda api") + # waiting for delete, as it takes a long time and would prevent NLB deletion + retry(_wait_for_deleted, retries=retries, sleep=sleep) + + @markers.aws.validated + def test_create_vpc_link_invalid_parameters(self, aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.apigateway.create_vpc_link( + name=f"test-vpc-link-{short_uid()}", + targetArns=["invalid-arn"], + ) + snapshot.match("create_vpc_link_invalid_target_arn", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.apigateway.create_vpc_link( + name="", + targetArns=[], + ) + snapshot.match("create_vpc_link_empty_name", e.value.response) + + @markers.aws.validated + def test_get_vpc_link_invalid_id(self, aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.apigateway.get_vpc_link(vpcLinkId="invalid-id") + snapshot.match("get_vpc_link_invalid_id", e.value.response) + + @markers.aws.validated + def test_delete_vpc_link_invalid_id(self, aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.apigateway.delete_vpc_link(vpcLinkId="invalid-id") + snapshot.match("delete_vpc_link_invalid_id", e.value.response) + + @markers.aws.validated + def test_update_vpc_link_invalid_id(self, aws_client, snapshot): + patch_operations = [ + {"op": "replace", "path": "/name", "value": "new-name"}, + ] + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_vpc_link( + vpcLinkId="invalid-id", patchOperations=patch_operations + ) + snapshot.match("update_vpc_link_invalid_id", e.value.response) + + +class TestApigatewayTestInvoke: + @markers.aws.validated + def test_invoke_test_method( + self, create_rest_apigw, snapshot, aws_client, apigw_test_invoke_response_formatter + ): + rest_api_id, _, root = create_rest_apigw(name="aws lambda api") # Create the /pets resource root_resource_id, _ = create_rest_resource( - aws_client.apigateway, restApiId=api_id, parentId=root, pathPart="pets" + aws_client.apigateway, restApiId=rest_api_id, parentId=root, pathPart="pets" ) # Create the /pets/{petId} resource resource_id, _ = create_rest_resource( - aws_client.apigateway, restApiId=api_id, parentId=root_resource_id, pathPart="{petId}" + aws_client.apigateway, + restApiId=rest_api_id, + parentId=root_resource_id, + pathPart="{petId}", + ) + # Create the GET method for /pets + create_rest_resource_method( + aws_client.apigateway, + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + authorizationType="NONE", ) + # Create the GET method for /pets/{petId} create_rest_resource_method( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="GET", authorizationType="NONE", @@ -2634,7 +2864,7 @@ def test_invoke_test_method(self, create_rest_apigw, snapshot, aws_client): # Create the POST method for /pets/{petId} create_rest_resource_method( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="POST", authorizationType="NONE", @@ -2642,10 +2872,19 @@ def test_invoke_test_method(self, create_rest_apigw, snapshot, aws_client): "method.request.path.petId": True, }, ) + + # Create the response for method GET /pets + create_rest_api_method_response( + aws_client.apigateway, + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + statusCode="200", + ) # Create the response for method GET /pets/{petId} create_rest_api_method_response( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="GET", statusCode="200", @@ -2653,15 +2892,25 @@ def test_invoke_test_method(self, create_rest_apigw, snapshot, aws_client): # Create the response for method POST /pets/{petId} create_rest_api_method_response( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="POST", statusCode="200", ) + # Create the integration to connect GET /pets to a backend + create_rest_api_integration( + aws_client.apigateway, + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + type="MOCK", + integrationHttpMethod="GET", + requestTemplates={"application/json": json.dumps({"statusCode": 200})}, + ) # Create the integration to connect GET /pets/{petId} to a backend create_rest_api_integration( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="GET", type="MOCK", @@ -2674,7 +2923,7 @@ def test_invoke_test_method(self, create_rest_apigw, snapshot, aws_client): # Create the integration to connect POST /pets/{petId} to a backend create_rest_api_integration( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="POST", type="MOCK", @@ -2684,10 +2933,19 @@ def test_invoke_test_method(self, create_rest_apigw, snapshot, aws_client): }, requestTemplates={"application/json": json.dumps({"statusCode": 200})}, ) + # Create the 200 integration response for GET /pets + create_rest_api_integration_response( + aws_client.apigateway, + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + statusCode="200", + responseTemplates={"application/json": json.dumps({"pets": "all the pets"})}, + ) # Create the 200 integration response for GET /pets/{petId} create_rest_api_integration_response( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="GET", statusCode="200", @@ -2696,20 +2954,29 @@ def test_invoke_test_method(self, create_rest_apigw, snapshot, aws_client): # Create the 200 integration response for POST /pets/{petId} create_rest_api_integration_response( aws_client.apigateway, - restApiId=api_id, + restApiId=rest_api_id, resourceId=resource_id, httpMethod="POST", statusCode="200", responseTemplates={"application/json": json.dumps({"petId": "$input.params('petId')"})}, ) - def invoke_method(api_id, resource_id, path_with_query_string, method, body=""): + def invoke_method( + api_id: str, + target_resource_id: str, + method: str, + path_with_query_string: str | None = None, + body: str = "", + ): + kwargs = {} + if path_with_query_string is not None: + kwargs["pathWithQueryString"] = path_with_query_string res = aws_client.apigateway.test_invoke_method( restApiId=api_id, - resourceId=resource_id, + resourceId=target_resource_id, httpMethod=method, - pathWithQueryString=path_with_query_string, body=body, + **kwargs, ) assert 200 == res.get("status") return res @@ -2718,60 +2985,199 @@ def invoke_method(api_id, resource_id, path_with_query_string, method, body=""): invoke_method, retries=10, sleep=5, - api_id=api_id, - resource_id=resource_id, + api_id=rest_api_id, + target_resource_id=root_resource_id, + method="GET", + ) + assert "HTTP Method: GET, Resource Path: /pets" in response["log"] + snapshot.match( + "test-invoke-method-get-pets", + apigw_test_invoke_response_formatter(response), + ) + + response = retry( + invoke_method, + retries=10, + sleep=5, + api_id=rest_api_id, + target_resource_id=root_resource_id, + path_with_query_string="/random/test", + method="GET", + ) + snapshot.match( + "test-invoke-method-get-pets-bad-path", + apigw_test_invoke_response_formatter(response), + ) + + response = retry( + invoke_method, + retries=10, + sleep=5, + api_id=rest_api_id, + target_resource_id=resource_id, path_with_query_string="/pets/123", method="GET", ) assert "HTTP Method: GET, Resource Path: /pets/123" in response["log"] - snapshot.match("test-invoke-method-get", response) + snapshot.match( + "test-invoke-method-get-pets-id", + apigw_test_invoke_response_formatter(response), + ) + + response = retry( + invoke_method, + retries=10, + sleep=5, + api_id=rest_api_id, + target_resource_id=resource_id, + method="GET", + ) + assert "HTTP Method: GET, Resource Path: /pets/{petId}" in response["log"] + snapshot.match( + "test-invoke-method-get-pets-id-no-path", + apigw_test_invoke_response_formatter(response), + ) + + response = retry( + invoke_method, + retries=10, + sleep=5, + api_id=rest_api_id, + target_resource_id=resource_id, + path_with_query_string="/random/test", + method="GET", + ) + assert "HTTP Method: GET, Resource Path: /random/test" in response["log"] + snapshot.match( + "test-invoke-method-get-pets-id-bad-path", + apigw_test_invoke_response_formatter(response), + ) response = retry( invoke_method, retries=10, sleep=5, - api_id=api_id, - resource_id=resource_id, + api_id=rest_api_id, + target_resource_id=resource_id, path_with_query_string="/pets/123?foo=bar", method="GET", ) - snapshot.match("test-invoke-method-get-with-qs", response) + snapshot.match( + "test-invoke-method-get-with-qs", + apigw_test_invoke_response_formatter(response), + ) response = retry( invoke_method, retries=10, sleep=5, - api_id=api_id, - resource_id=resource_id, + api_id=rest_api_id, + target_resource_id=resource_id, path_with_query_string="/pets/123", method="POST", body=json.dumps({"foo": "bar"}), ) assert "HTTP Method: POST, Resource Path: /pets/123" in response["log"] - snapshot.match("test-invoke-method-post-with-body", response) + snapshot.match( + "test-invoke-method-post-with-body", + apigw_test_invoke_response_formatter(response), + ) - # assert resource and rest api doesn't exist + # assert method doesn't exist with pytest.raises(ClientError) as ex: aws_client.apigateway.test_invoke_method( - restApiId=api_id, + restApiId=rest_api_id, + resourceId=resource_id, + httpMethod="HEAD", + pathWithQueryString="/pets/123", + body=json.dumps({"foo": "bar"}), + ) + snapshot.match("resource-method-not-found", ex.value.response) + + # assert resource doesn't exist + with pytest.raises(ClientError) as ex: + aws_client.apigateway.test_invoke_method( + restApiId=rest_api_id, resourceId="invalid_res", httpMethod="POST", pathWithQueryString="/pets/123", body=json.dumps({"foo": "bar"}), ) snapshot.match("resource-id-not-found", ex.value.response) - assert ex.value.response["Error"]["Code"] == "NotFoundException" + # assert API doesn't exist with pytest.raises(ClientError) as ex: aws_client.apigateway.test_invoke_method( - restApiId=api_id, + restApiId=rest_api_id, resourceId="invalid_res", httpMethod="POST", pathWithQueryString="/pets/123", body=json.dumps({"foo": "bar"}), ) snapshot.match("rest-api-not-found", ex.value.response) - assert ex.value.response["Error"]["Code"] == "NotFoundException" + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + # TODO: our way of handling logs for TestInvokeMethod is too naive to properly handle all + # type of exceptions (we'll need to build logs as we progress through the invocation) + paths=[ + "$..log.line07", + ] + ) + def test_failed_invoke_test_method( + self, create_rest_apigw, snapshot, aws_client, apigw_test_invoke_response_formatter + ): + rest_api_id, _, root_resource_id = create_rest_apigw(name="test failed invoke") + + aws_client.apigateway.put_method( + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="ANY", + authorizationType="NONE", + ) + + aws_client.apigateway.put_integration( + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="ANY", + type="HTTP_PROXY", + uri="https://${stageVariables.testHost}", + integrationHttpMethod="ANY", + ) + # we are going to not declare this stage variable on purpose to make the call fail + + def invoke_method( + api_id: str, + target_resource_id: str, + method: str, + path_with_query_string: str | None = None, + body: str = "", + ): + kwargs = {} + if path_with_query_string is not None: + kwargs["pathWithQueryString"] = path_with_query_string + res = aws_client.apigateway.test_invoke_method( + restApiId=api_id, + resourceId=target_resource_id, + httpMethod=method, + body=body, + **kwargs, + ) + assert res.get("status") == 500 + return res + + response = retry( + invoke_method, + retries=10, + sleep=5, + api_id=rest_api_id, + target_resource_id=root_resource_id, + method="GET", + ) + snapshot.match( + "test-invoke-failure", + apigw_test_invoke_response_formatter(response), + ) class TestApigatewayIntegration: @@ -2868,36 +3274,93 @@ def test_put_integration_response_validation( snapshot.match("put-integration-response-wrong-resource", e.value.response) @markers.aws.validated - @pytest.mark.skipif( - condition=not is_aws_cloud(), reason="Validation behavior not yet implemented" - ) - def test_put_integration_request_parameter_bool_type( + def test_put_integration_response_templates( self, aws_client, apigw_create_rest_api, aws_client_factory, snapshot ): apigw_client = aws_client_factory(config=Config(parameter_validation=False)).apigateway response = apigw_create_rest_api( - name=f"test-api-{short_uid()}", - description="APIGW test PutIntegration RequestParam", + name=f"test-api-{short_uid()}", description="testing PutIntegrationResponse method exc" ) api_id = response["id"] - root_resource_id = response["rootResourceId"] + root_id = response["rootResourceId"] - bool_method = apigw_client.put_method( + aws_client.apigateway.put_method( restApiId=api_id, - resourceId=root_resource_id, - httpMethod="GET", + resourceId=root_id, + httpMethod="POST", authorizationType="NONE", - requestParameters={ - "method.request.path.testPath": True, - }, ) - snapshot.match("bool-method", bool_method) - with pytest.raises(ClientError) as e: - apigw_client.put_method( - restApiId=api_id, - resourceId=root_resource_id, - httpMethod="POST", + aws_client.apigateway.put_integration( + restApiId=api_id, + resourceId=root_id, + httpMethod="POST", + integrationHttpMethod="GET", + type="MOCK", + requestTemplates={"application/json": '{"statusCode": 200}'}, + ) + + response = apigw_client.put_integration_response( + restApiId=api_id, + resourceId=root_id, + httpMethod="POST", + statusCode="200", + selectionPattern="", + responseTemplates={"application/json": None}, + ) + + snapshot.match("put-integration-response-template-none", response) + + response = apigw_client.put_integration_response( + restApiId=api_id, + resourceId=root_id, + httpMethod="POST", + statusCode="200", + selectionPattern="", + responseTemplates={"application/json": ""}, + ) + snapshot.match("put-integration-response-template-empty", response) + + response = apigw_client.put_integration_response( + restApiId=api_id, + resourceId=root_id, + httpMethod="POST", + statusCode="200", + selectionPattern="", + ) + snapshot.match("put-integration-no-response-template", response) + + @markers.aws.validated + @pytest.mark.skipif( + condition=not is_aws_cloud(), reason="Validation behavior not yet implemented" + ) + def test_put_integration_request_parameter_bool_type( + self, aws_client, apigw_create_rest_api, aws_client_factory, snapshot + ): + apigw_client = aws_client_factory(config=Config(parameter_validation=False)).apigateway + response = apigw_create_rest_api( + name=f"test-api-{short_uid()}", + description="APIGW test PutIntegration RequestParam", + ) + api_id = response["id"] + root_resource_id = response["rootResourceId"] + + bool_method = apigw_client.put_method( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="GET", + authorizationType="NONE", + requestParameters={ + "method.request.path.testPath": True, + }, + ) + snapshot.match("bool-method", bool_method) + + with pytest.raises(ClientError) as e: + apigw_client.put_method( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="POST", authorizationType="NONE", requestParameters={ "method.request.path.testPath": True, @@ -2930,6 +3393,109 @@ def test_put_integration_request_parameter_bool_type( ) snapshot.match("put-integration-request-param-bool-value", e.value.response) + @markers.aws.validated + def test_create_integration_with_vpc_link( + self, create_rest_apigw, aws_client, snapshot, region_name + ): + """ + We cannot properly validate the CRUD logic of creating VPC Link, as they require an ELB Network Load Balancer + which are not implemented yet. But we can use stage variables to delay the evaluation of the real connection id + """ + snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace")) + rest_api_id, _, root_resource_id = create_rest_apigw(name="test vpc link") + + aws_client.apigateway.put_method( + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + authorizationType="NONE", + ) + + # see example here: + # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-api-with-vpclink-cli.html + put_integration = aws_client.apigateway.put_integration( + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + type="HTTP_PROXY", + uri=f"http://my-vpclink-test-nlb-1234567890abcdef.{region_name}.amazonaws.com", + integrationHttpMethod="GET", + connectionType="VPC_LINK", + connectionId="${stageVariables.vpcLinkId}", + ) + snapshot.match("put-integration-vpc-link", put_integration) + + get_integration = aws_client.apigateway.get_integration( + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + ) + snapshot.match("get-integration-vpc-link", get_integration) + + update_integration = aws_client.apigateway.update_integration( + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + patchOperations=[ + { + "op": "replace", + "path": "/connectionId", + "value": "${stageVariables.vpcLinkIdBeta}", + } + ], + ) + snapshot.match("update-integration-vpc-link", update_integration) + + get_integration_update = aws_client.apigateway.get_integration( + restApiId=rest_api_id, + resourceId=root_resource_id, + httpMethod="GET", + ) + snapshot.match("get-integration-update-vpc-link", get_integration_update) + + @markers.aws.validated + def test_get_integration_non_existent( + self, aws_client, apigw_create_rest_api, aws_client_factory, snapshot + ): + apigw_client = aws_client_factory(config=Config(parameter_validation=False)).apigateway + response = apigw_create_rest_api( + name=f"test-api-{short_uid()}", + description="APIGW test PutIntegration Types", + ) + api_id = response["id"] + root_resource_id = response["rootResourceId"] + + with pytest.raises(ClientError) as e: + apigw_client.get_integration( + restApiId=short_uid(), resourceId=root_resource_id, httpMethod="GET" + ) + snapshot.match("get-integration-no-api", e.value.response) + + with pytest.raises(ClientError) as e: + apigw_client.get_integration(restApiId=api_id, resourceId=short_uid(), httpMethod="GET") + snapshot.match("get-integration-no-resource", e.value.response) + + with pytest.raises(ClientError) as e: + apigw_client.get_integration( + restApiId=api_id, resourceId=root_resource_id, httpMethod="GET" + ) + snapshot.match("get-integration-no-method", e.value.response) + + aws_client.apigateway.put_method( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="GET", + authorizationType="NONE", + ) + + with pytest.raises(ClientError) as e: + apigw_client.get_integration( + restApiId=api_id, resourceId=root_resource_id, httpMethod="GET" + ) + snapshot.match("get-integration-no-integration", e.value.response) + + +class TestApigatewayIntegrationResponse: @markers.aws.validated def test_integration_response_wrong_api(self, aws_client, apigw_create_rest_api, snapshot): with pytest.raises(ClientError) as e: @@ -3217,6 +3783,87 @@ def test_lifecycle_integration_response(self, aws_client, apigw_create_rest_api, ) snapshot.match("delete-integration-response", delete_response) + @markers.aws.validated + def test_delete_integration_response_errors( + self, aws_client_factory, apigw_create_rest_api, snapshot + ): + snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace")) + apigw_client = aws_client_factory(config=Config(parameter_validation=False)).apigateway + response = apigw_create_rest_api(name=f"test-api-{short_uid()}") + api_id = response["id"] + root_resource_id = response["rootResourceId"] + + apigw_client.put_method( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="GET", + authorizationType="NONE", + ) + apigw_client.put_integration( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="GET", + type="MOCK", + requestTemplates={"application/json": '{"statusCode": 200}'}, + ) + + put_response = apigw_client.put_integration_response( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="GET", + statusCode="200", + responseTemplates={"application/json": '"created"'}, + selectionPattern="", + ) + snapshot.match("put-integration-response", put_response) + + with pytest.raises(ClientError) as e: + apigw_client.delete_integration_response( + restApiId=api_id, + resourceId="bad-resource", + httpMethod="GET", + statusCode="200", + ) + snapshot.match("non-existent-resource", e.value.response) + + with pytest.raises(ClientError) as e: + apigw_client.delete_integration_response( + restApiId="bad-api-id", + resourceId=root_resource_id, + httpMethod="GET", + statusCode="200", + ) + snapshot.match("non-existent-api-id", e.value.response) + + with pytest.raises(ClientError) as e: + apigw_client.delete_integration_response( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="POST", + statusCode="200", + ) + snapshot.match("non-existent-method", e.value.response) + + with pytest.raises(ClientError) as e: + apigw_client.delete_integration_response( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="GET", + statusCode="201", + ) + snapshot.match("non-existent-status-code", e.value.response) + + with pytest.raises(ClientError) as e: + apigw_client.delete_integration_response( + restApiId=api_id, + resourceId=root_resource_id, + httpMethod="WRONG", + statusCode="201", + ) + snapshot.match("bad-method", e.value.response) + + +class TestApigatewayMethodResponse: @markers.aws.validated def test_update_method_wrong_param_names(self, aws_client, apigw_create_rest_api, snapshot): snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace")) @@ -3812,3 +4459,278 @@ def test_lifecycle_method_response(self, aws_client, apigw_create_rest_api, snap statusCode="200", ) snapshot.match("delete-method-response", delete_response) + + +class TestApiGatewayStage: + @pytest.fixture + def create_api_for_deployment(self, apigw_create_rest_api, aws_client): + def _create() -> str: + # Create REST API with a MOCK method (required for deployment) + response = apigw_create_rest_api(name=f"test-stages-{short_uid()}") + api_id = response["id"] + root_id = response["rootResourceId"] + + aws_client.apigateway.put_method( + restApiId=api_id, + resourceId=root_id, + httpMethod="GET", + authorizationType="NONE", + ) + aws_client.apigateway.put_integration( + restApiId=api_id, + resourceId=root_id, + httpMethod="GET", + type="MOCK", + requestTemplates={"application/json": '{"statusCode": 200}'}, + ) + return api_id + + return _create + + @pytest.fixture + def logs_create_log_group(self, aws_client): + log_group_names = [] + + def _create_log_group(name: str = None) -> str: + if not name: + name = f"test-log-group-{short_uid()}" + + aws_client.logs.create_log_group(logGroupName=name) + log_group_names.append(name) + describe_result = aws_client.logs.describe_log_groups(logGroupNamePrefix=name) + return describe_result["logGroups"][0]["arn"] + + yield _create_log_group + + for _name in log_group_names: + try: + aws_client.logs.delete_log_group(logGroupName=_name) + except Exception as e: + LOG.debug("error cleaning up log group %s: %s", _name, e) + + @markers.aws.validated + def test_get_stages_filters_by_deployment_id( + self, create_api_for_deployment, aws_client, snapshot + ): + """ + Regression test for https://github.com/localstack/localstack/issues/13667 + Verifies that get_stages filters results by deploymentId when provided. + """ + snapshot.add_transformer(snapshot.transform.key_value("deploymentId")) + + # Create REST API with a MOCK method (required for deployment) + api_id = create_api_for_deployment() + + # Create two deployments with different stages + deploy1 = aws_client.apigateway.create_deployment(restApiId=api_id, stageName="stage1") + if is_aws_cloud(): + time.sleep(1) + deploy2 = aws_client.apigateway.create_deployment(restApiId=api_id, stageName="stage2") + + # get_stages without deploymentId returns all stages + all_stages = aws_client.apigateway.get_stages(restApiId=api_id) + snapshot.match("all-stages", all_stages) + assert len(all_stages["item"]) == 2 + + # get_stages with deploymentId should return only the matching stage + stages_deploy1 = aws_client.apigateway.get_stages( + restApiId=api_id, deploymentId=deploy1["id"] + ) + snapshot.match("stages-deploy1", stages_deploy1) + assert len(stages_deploy1["item"]) == 1 + assert stages_deploy1["item"][0]["stageName"] == "stage1" + + stages_deploy2 = aws_client.apigateway.get_stages( + restApiId=api_id, deploymentId=deploy2["id"] + ) + snapshot.match("stages-deploy2", stages_deploy2) + assert len(stages_deploy2["item"]) == 1 + assert stages_deploy2["item"][0]["stageName"] == "stage2" + + @markers.aws.validated + def test_stage_access_log_settings( + self, create_api_for_deployment, aws_client, logs_create_log_group, snapshot + ): + # see https://docs.aws.amazon.com/apigateway/latest/api/patch-operations.html#UpdateStage-Patch + # for list of possible operations + api_id = create_api_for_deployment() + log_group_arn = logs_create_log_group() + # AWS appends ":*" to the ARN which API Gateway doesn't allow + log_group_arn = log_group_arn.removesuffix(":*") + snapshot.add_transformer(snapshot.transform.regex(log_group_arn, "")) + + create_deployment = aws_client.apigateway.create_deployment(restApiId=api_id) + snapshot.match("create-deployment", create_deployment) + deployment_id = create_deployment["id"] + + stage_name = "dev" + create_stage = aws_client.apigateway.create_stage( + restApiId=api_id, + stageName=stage_name, + deploymentId=deployment_id, + description="dev stage", + ) + snapshot.match("create-stage", create_stage) + + update_stage = aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "add", + "path": "/accessLogSettings/destinationArn", + "value": log_group_arn, + }, + ], + ) + snapshot.match("update-stage-access-log-dest-add", update_stage) + + update_stage = aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "replace", + "path": "/accessLogSettings/destinationArn", + "value": log_group_arn + "a", + }, + ], + ) + snapshot.match("update-stage-access-log-dest-replace", update_stage) + + # $context.extendedRequestId $context.identity.sourceIp + update_stage = aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "add", + "path": "/accessLogSettings/format", + "value": "$context.requestId $context.identity.sourceIp", + }, + ], + ) + snapshot.match("update-stage-access-log-format-add", update_stage) + + update_stage = aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "remove", + "path": "/accessLogSettings", + }, + ], + ) + snapshot.match("update-stage-access-log-remove-all", update_stage) + + @markers.aws.validated + def test_stage_access_log_settings_validation( + self, create_api_for_deployment, aws_client, logs_create_log_group, snapshot + ): + # see https://docs.aws.amazon.com/apigateway/latest/api/patch-operations.html#UpdateStage-Patch + # for list of possible operations + api_id = create_api_for_deployment() + log_group_arn = logs_create_log_group() + # AWS appends ":*" to the ARN which API Gateway doesn't allow + log_group_arn = log_group_arn.removesuffix(":*") + snapshot.add_transformer(snapshot.transform.regex(log_group_arn, "")) + + create_deployment = aws_client.apigateway.create_deployment(restApiId=api_id) + snapshot.match("create-deployment", create_deployment) + deployment_id = create_deployment["id"] + + stage_name = "dev" + create_stage = aws_client.apigateway.create_stage( + restApiId=api_id, + stageName=stage_name, + deploymentId=deployment_id, + description="dev stage", + ) + snapshot.match("create-stage", create_stage) + + for op in ("add", "remove", "replace"): + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": op, + "path": "/accessLogSettings/randomValue", + "value": "updated", + }, + ], + ) + snapshot.match(f"invalid-path-{op}", e.value.response) + + for op in ("add", "remove", "replace"): + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": op, + "path": "/accessLogSettings/*", + "value": "updated", + }, + ], + ) + snapshot.match(f"star-and-{op}", e.value.response) + + for op in ("add", "replace"): + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": op, + "path": "/accessLogSettings", + "value": "test", + }, + ], + ) + snapshot.match(f"root-and-{op}", e.value.response) + + update_stage = aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "add", + "path": "/accessLogSettings/destinationArn", + "value": log_group_arn, + }, + ], + ) + snapshot.match("update-stage-access-log-dest-add", update_stage) + + for path in ("destinationArn", "format"): + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "replace", + "path": f"/accessLogSettings/{path}", + "value": "", + }, + ], + ) + snapshot.match(f"update-stage-access-log-{path}-empty", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "remove", + "path": "/accessLogSettings/destinationArn", + }, + ], + ) + snapshot.match("update-stage-access-log-dest-remove", e.value.response) diff --git a/tests/aws/services/apigateway/test_apigateway_api.snapshot.json b/tests/aws/services/apigateway/test_apigateway_api.snapshot.json index c47cd7a9b880b..85ccf98e4816d 100644 --- a/tests/aws/services/apigateway/test_apigateway_api.snapshot.json +++ b/tests/aws/services/apigateway/test_apigateway_api.snapshot.json @@ -2813,17 +2813,162 @@ } }, "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_invoke_test_method": { - "recorded-date": "15-04-2024, 20:48:35", + "recorded-date": "29-09-2025, 19:28:50", "recorded-content": { - "test-invoke-method-get": { + "test-invoke-method-get-pets": { + "body": { + "pets": "all the pets" + }, + "headers": { + "Content-Type": "application/json" + }, + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: GET, Resource Path: /pets", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: ", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Method response body after transformations: {\"pets\": \"all the pets\"}", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method response headers: {Content-Type=application/json}", + "line09": "DDD MMM dd hh:mm:ss UTC yyyy : Successfully completed execution", + "line10": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 200", + "line11": "" + }, + "multiValueHeaders": { + "Content-Type": [ + "application/json" + ] + }, + "status": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "test-invoke-method-get-pets-bad-path": { + "body": { + "pets": "all the pets" + }, + "headers": { + "Content-Type": "application/json" + }, + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: GET, Resource Path: /random/test", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: ", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Method response body after transformations: {\"pets\": \"all the pets\"}", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method response headers: {Content-Type=application/json}", + "line09": "DDD MMM dd hh:mm:ss UTC yyyy : Successfully completed execution", + "line10": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 200", + "line11": "" + }, + "multiValueHeaders": { + "Content-Type": [ + "application/json" + ] + }, + "status": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "test-invoke-method-get-pets-id": { "body": { "petId": "123" }, "headers": { "Content-Type": "application/json" }, - "latency": "latency", - "log": "log", + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: GET, Resource Path: /pets/123", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {petId=123}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: ", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Method response body after transformations: {\"petId\": \"123\"}", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method response headers: {Content-Type=application/json}", + "line09": "DDD MMM dd hh:mm:ss UTC yyyy : Successfully completed execution", + "line10": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 200", + "line11": "" + }, + "multiValueHeaders": { + "Content-Type": [ + "application/json" + ] + }, + "status": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "test-invoke-method-get-pets-id-no-path": { + "body": { + "petId": "{petId}" + }, + "headers": { + "Content-Type": "application/json" + }, + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: GET, Resource Path: /pets/{petId}", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {petId={petId}}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: ", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Method response body after transformations: {\"petId\": \"{petId}\"}", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method response headers: {Content-Type=application/json}", + "line09": "DDD MMM dd hh:mm:ss UTC yyyy : Successfully completed execution", + "line10": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 200", + "line11": "" + }, + "multiValueHeaders": { + "Content-Type": [ + "application/json" + ] + }, + "status": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "test-invoke-method-get-pets-id-bad-path": { + "body": { + "petId": "" + }, + "headers": { + "Content-Type": "application/json" + }, + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: GET, Resource Path: /random/test", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: ", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Method response body after transformations: {\"petId\": \"\"}", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method response headers: {Content-Type=application/json}", + "line09": "DDD MMM dd hh:mm:ss UTC yyyy : Successfully completed execution", + "line10": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 200", + "line11": "" + }, "multiValueHeaders": { "Content-Type": [ "application/json" @@ -2842,8 +2987,21 @@ "headers": { "Content-Type": "application/json" }, - "latency": "latency", - "log": "log", + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: GET, Resource Path: /pets/123", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {petId=123}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {foo=bar}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: ", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Method response body after transformations: {\"petId\": \"123\"}", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method response headers: {Content-Type=application/json}", + "line09": "DDD MMM dd hh:mm:ss UTC yyyy : Successfully completed execution", + "line10": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 200", + "line11": "" + }, "multiValueHeaders": { "Content-Type": [ "application/json" @@ -2862,8 +3020,21 @@ "headers": { "Content-Type": "application/json" }, - "latency": "latency", - "log": "log", + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: POST, Resource Path: /pets/123", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {petId=123}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: {\"foo\": \"bar\"}", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Method response body after transformations: {\"petId\": \"123\"}", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method response headers: {Content-Type=application/json}", + "line09": "DDD MMM dd hh:mm:ss UTC yyyy : Successfully completed execution", + "line10": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 200", + "line11": "" + }, "multiValueHeaders": { "Content-Type": [ "application/json" @@ -2875,6 +3046,17 @@ "HTTPStatusCode": 200 } }, + "resource-method-not-found": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Method identifier specified" + }, + "message": "Invalid Method identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, "resource-id-not-found": { "Error": { "Code": "NotFoundException", @@ -3162,56 +3344,62 @@ } }, "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts": { - "recorded-date": "15-04-2024, 20:57:15", + "recorded-date": "09-02-2026, 18:59:24", "recorded-content": { - "create-import-documentations_parts": [ - { - "id": "", - "location": { - "type": "API" - }, - "properties": { - "description": "API description", - "info": { - "description": "API info description 4", - "version": "API info version 3" + "get-documentations-parts-after-api-imports": { + "items": [ + { + "id": "", + "location": { + "type": "API" + }, + "properties": { + "description": "API description", + "info": { + "description": "API info description 4", + "version": "API info version 3" + } } - } - }, - { - "id": "", - "location": { - "type": "METHOD", - "path": "/", - "method": "GET" }, - "properties": { - "description": "Method description." - } - }, - { - "id": "", - "location": { - "type": "MODEL", - "name": "" + { + "id": "", + "location": { + "method": "GET", + "path": "/", + "type": "METHOD" + }, + "properties": { + "description": "Method description." + } }, - "properties": { - "title": " Schema" - } - }, - { - "id": "", - "location": { - "type": "RESPONSE", - "path": "/", - "method": "GET", - "statusCode": "200" + { + "id": "", + "location": { + "name": "", + "type": "MODEL" + }, + "properties": { + "title": " Schema" + } }, - "properties": { - "description": "200 response" + { + "id": "", + "location": { + "method": "GET", + "path": "/", + "statusCode": "200", + "type": "RESPONSE" + }, + "properties": { + "description": "200 response" + } } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 } - ], + }, "import-documentation-parts": { "ids": [ "", @@ -3223,6 +3411,60 @@ "HTTPHeaders": {}, "HTTPStatusCode": 200 } + }, + "get-documentations-parts-after-import": { + "items": [ + { + "id": "", + "location": { + "type": "API" + }, + "properties": { + "description": "API description", + "info": { + "description": "API info description 4", + "version": "API info version 3" + } + } + }, + { + "id": "", + "location": { + "method": "GET", + "path": "/", + "type": "METHOD" + }, + "properties": { + "description": "Method description." + } + }, + { + "id": "", + "location": { + "name": "", + "type": "MODEL" + }, + "properties": { + "title": " Schema" + } + }, + { + "id": "", + "location": { + "method": "GET", + "path": "/", + "statusCode": "200", + "type": "RESPONSE" + }, + "properties": { + "description": "200 response" + } + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } } } }, @@ -3651,7 +3893,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_integration_response": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_lifecycle_integration_response": { "recorded-date": "11-06-2025, 09:12:54", "recorded-content": { "put-integration-response": { @@ -3736,7 +3978,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_api": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_api": { "recorded-date": "18-06-2025, 12:28:55", "recorded-content": { "put-integration-response-wrong-api": { @@ -3763,7 +4005,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_resource": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_resource": { "recorded-date": "18-06-2025, 12:28:25", "recorded-content": { "put-integration-response-wrong-resource": { @@ -3790,7 +4032,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_method": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_method": { "recorded-date": "18-06-2025, 11:47:00", "recorded-content": { "put-integration-response-wrong-method": { @@ -3817,7 +4059,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_statuscode": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_invalid_statuscode": { "recorded-date": "18-06-2025, 11:51:51", "recorded-content": { "put-integration-response-invalid-statusCode": { @@ -3842,7 +4084,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_responsetemplates": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_invalid_responsetemplates": { "recorded-date": "18-06-2025, 13:03:29", "recorded-content": { "put-integration-response-invalid-responseTemplates-1": { @@ -3868,7 +4110,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_integration": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_invalid_integration": { "recorded-date": "26-06-2025, 11:21:05", "recorded-content": { "get-integration-response-without-integration": { @@ -3884,7 +4126,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_status_code": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_status_code": { "recorded-date": "26-06-2025, 11:21:43", "recorded-content": { "get-integration-response-wrong-status-code": { @@ -3900,7 +4142,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_method_response": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_lifecycle_method_response": { "recorded-date": "01-07-2025, 15:48:02", "recorded-content": { "put-method-response": { @@ -3987,7 +4229,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_response": { "recorded-date": "30-06-2025, 12:42:31", "recorded-content": { "remove-update-method-response": { @@ -4012,7 +4254,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_wrong_operations": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_response_wrong_operations": { "recorded-date": "30-06-2025, 13:54:57", "recorded-content": { "update-method-response-wrong-operation-1": { @@ -4067,7 +4309,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_negative_tests": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_response_negative_tests": { "recorded-date": "30-06-2025, 15:24:43", "recorded-content": { "update-method-response-wrong-statuscode": { @@ -4125,7 +4367,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_lack_response_parameters_and_models": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_lack_response_parameters_and_models": { "recorded-date": "01-07-2025, 15:48:38", "recorded-content": { "update-method-response-operation-without-response-parameters": { @@ -4163,7 +4405,7 @@ } } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_wrong_param_names": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_wrong_param_names": { "recorded-date": "01-07-2025, 15:49:16", "recorded-content": { "update-method-response-operation-with-wrong-param-name-1": { @@ -4592,5 +4834,793 @@ } } } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_delete_integration_response_errors": { + "recorded-date": "21-08-2025, 17:53:19", + "recorded-content": { + "put-integration-response": { + "responseTemplates": { + "application/json": "\"created\"" + }, + "selectionPattern": "", + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "non-existent-resource": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Resource identifier specified" + }, + "message": "Invalid Resource identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "non-existent-api-id": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Resource identifier specified" + }, + "message": "Invalid Resource identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "non-existent-method": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Integration identifier specified" + }, + "message": "Invalid Integration identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "non-existent-status-code": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Response status code specified" + }, + "message": "Invalid Response status code specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "bad-method": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Integration identifier specified" + }, + "message": "Invalid Integration identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_on_root": { + "recorded-date": "03-09-2025, 15:58:00", + "recorded-content": { + "update-root-path-part": { + "Error": { + "Code": "BadRequestException", + "Message": "Root resource cannot update its pathPart." + }, + "message": "Root resource cannot update its pathPart.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-root-parent": { + "Error": { + "Code": "BadRequestException", + "Message": "Root resource cannot update its parentId." + }, + "message": "Root resource cannot update its parentId.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_failed_invoke_test_method": { + "recorded-date": "29-09-2025, 19:44:14", + "recorded-content": { + "test-invoke-failure": { + "body": { + "message": "Internal server error" + }, + "headers": { + "x-amzn-ErrorType": "InternalServerErrorException" + }, + "latency": "", + "log": { + "line00": "Execution log for request ", + "line01": "DDD MMM dd hh:mm:ss UTC yyyy : Starting execution for request: ", + "line02": "DDD MMM dd hh:mm:ss UTC yyyy : HTTP Method: GET, Resource Path: /", + "line03": "DDD MMM dd hh:mm:ss UTC yyyy : Method request path: {}", + "line04": "DDD MMM dd hh:mm:ss UTC yyyy : Method request query string: {}", + "line05": "DDD MMM dd hh:mm:ss UTC yyyy : Method request headers: {}", + "line06": "DDD MMM dd hh:mm:ss UTC yyyy : Method request body before transformations: ", + "line07": "DDD MMM dd hh:mm:ss UTC yyyy : Execution failed due to configuration error: Invalid endpoint address", + "line08": "DDD MMM dd hh:mm:ss UTC yyyy : Method completed with status: 500", + "line09": "" + }, + "multiValueHeaders": { + "x-amzn-ErrorType": [ + "InternalServerErrorException" + ] + }, + "status": 500, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_create_integration_with_vpc_link": { + "recorded-date": "08-10-2025, 09:05:56", + "recorded-content": { + "put-integration-vpc-link": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionId": "${stageVariables.vpcLinkId}", + "connectionType": "VPC_LINK", + "httpMethod": "GET", + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP_PROXY", + "uri": "http://my-vpclink-test-nlb-1234567890abcdef..amazonaws.com", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "get-integration-vpc-link": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionId": "${stageVariables.vpcLinkId}", + "connectionType": "VPC_LINK", + "httpMethod": "GET", + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP_PROXY", + "uri": "http://my-vpclink-test-nlb-1234567890abcdef..amazonaws.com", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update-integration-vpc-link": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionId": "${stageVariables.vpcLinkIdBeta}", + "connectionType": "VPC_LINK", + "httpMethod": "GET", + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP_PROXY", + "uri": "http://my-vpclink-test-nlb-1234567890abcdef..amazonaws.com", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-integration-update-vpc-link": { + "cacheKeyParameters": [], + "cacheNamespace": "", + "connectionId": "${stageVariables.vpcLinkIdBeta}", + "connectionType": "VPC_LINK", + "httpMethod": "GET", + "passthroughBehavior": "WHEN_NO_MATCH", + "timeoutInMillis": 29000, + "type": "HTTP_PROXY", + "uri": "http://my-vpclink-test-nlb-1234567890abcdef..amazonaws.com", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_get_integration_non_existent": { + "recorded-date": "18-11-2025, 17:03:33", + "recorded-content": { + "get-integration-no-api": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Resource identifier specified" + }, + "message": "Invalid Resource identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "get-integration-no-resource": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Resource identifier specified" + }, + "message": "Invalid Resource identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "get-integration-no-method": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Method identifier specified" + }, + "message": "Invalid Method identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "get-integration-no-integration": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Integration identifier specified" + }, + "message": "Invalid Integration identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts_bad_file": { + "recorded-date": "18-11-2025, 17:21:35", + "recorded-content": { + "import-documentation-parts-bad-yaml-file": { + "Error": { + "Code": "BadRequestException", + "Message": "Unable to build importer with provided input." + }, + "message": "Unable to build importer with provided input.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "import-documentation-parts-bad-json-file": { + "Error": { + "Code": "BadRequestException", + "Message": "Unable to build importer with provided input." + }, + "message": "Unable to build importer with provided input.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_vpc_link_lifecycle": { + "recorded-date": "15-12-2025, 08:04:55", + "recorded-content": { + "nlb-arn": "", + "create_vpc_link": { + "id": "", + "name": "", + "status": "PENDING", + "targetArns": [ + "" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 202 + } + }, + "get_vpc_link": { + "id": "", + "name": "", + "status": "AVAILABLE", + "statusMessage": "Your vpc link is ready for use", + "tags": {}, + "targetArns": [ + "" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_vpc_links": { + "items": [ + { + "id": "", + "name": "", + "status": "AVAILABLE", + "statusMessage": "Your vpc link is ready for use", + "targetArns": [ + "" + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update_vpc_link": { + "id": "", + "name": "-updated", + "status": "AVAILABLE", + "statusMessage": "Your vpc link is ready for use", + "tags": {}, + "targetArns": [ + "" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_vpc_link": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 202 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_create_vpc_link_invalid_parameters": { + "recorded-date": "15-12-2025, 04:55:32", + "recorded-content": { + "create_vpc_link_invalid_target_arn": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid ARN. ARN is not well formed invalid-arn" + }, + "message": "Invalid ARN. ARN is not well formed invalid-arn", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create_vpc_link_empty_name": { + "Error": { + "Code": "BadRequestException", + "Message": "Name cannot be empty" + }, + "message": "Name cannot be empty", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_get_vpc_link_invalid_id": { + "recorded-date": "15-12-2025, 04:55:32", + "recorded-content": { + "get_vpc_link_invalid_id": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Vpc Link identifier specified" + }, + "message": "Invalid Vpc Link identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_delete_vpc_link_invalid_id": { + "recorded-date": "15-12-2025, 04:55:32", + "recorded-content": { + "delete_vpc_link_invalid_id": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Vpc Link identifier specified" + }, + "message": "Invalid Vpc Link identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_update_vpc_link_invalid_id": { + "recorded-date": "15-12-2025, 04:55:32", + "recorded-content": { + "update_vpc_link_invalid_id": { + "Error": { + "Code": "NotFoundException", + "Message": "Invalid Vpc Link identifier specified" + }, + "message": "Invalid Vpc Link identifier specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayStage::test_get_stages_filters_by_deployment_id": { + "recorded-date": "25-02-2026, 23:40:35", + "recorded-content": { + "all-stages": { + "item": [ + { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "stage1", + "tracingEnabled": false + }, + { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "stage2", + "tracingEnabled": false + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stages-deploy1": { + "item": [ + { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "stage1", + "tracingEnabled": false + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stages-deploy2": { + "item": [ + { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "stage2", + "tracingEnabled": false + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_templates": { + "recorded-date": "12-02-2026, 16:25:03", + "recorded-content": { + "put-integration-response-template-none": { + "responseTemplates": { + "application/json": null + }, + "selectionPattern": "", + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "put-integration-response-template-empty": { + "responseTemplates": { + "application/json": null + }, + "selectionPattern": "", + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "put-integration-no-response-template": { + "selectionPattern": "", + "statusCode": "200", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayStage::test_stage_access_log_settings": { + "recorded-date": "25-02-2026, 23:08:31", + "recorded-content": { + "create-deployment": { + "createdDate": "datetime", + "id": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "create-stage": { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "dev stage", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "dev", + "tracingEnabled": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "update-stage-access-log-dest-add": { + "accessLogSettings": { + "destinationArn": "" + }, + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "dev stage", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "dev", + "tracingEnabled": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update-stage-access-log-dest-replace": { + "accessLogSettings": { + "destinationArn": "a" + }, + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "dev stage", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "dev", + "tracingEnabled": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update-stage-access-log-format-add": { + "accessLogSettings": { + "destinationArn": "a", + "format": "$context.requestId $context.identity.sourceIp" + }, + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "dev stage", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "dev", + "tracingEnabled": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update-stage-access-log-remove-all": { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "dev stage", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "dev", + "tracingEnabled": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayStage::test_stage_access_log_settings_validation": { + "recorded-date": "25-02-2026, 23:16:02", + "recorded-content": { + "create-deployment": { + "createdDate": "datetime", + "id": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "create-stage": { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "dev stage", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "dev", + "tracingEnabled": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "invalid-path-add": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]" + }, + "message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-path-remove": { + "Error": { + "Code": "BadRequestException", + "Message": "Cannot remove method setting accessLogSettings/randomValue because there is no method setting for this method " + }, + "message": "Cannot remove method setting accessLogSettings/randomValue because there is no method setting for this method ", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-path-replace": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]" + }, + "message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "star-and-add": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]" + }, + "message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "star-and-remove": { + "Error": { + "Code": "BadRequestException", + "Message": "Cannot remove method setting accessLogSettings/* because there is no method setting for this method " + }, + "message": "Cannot remove method setting accessLogSettings/* because there is no method setting for this method ", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "star-and-replace": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]" + }, + "message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "root-and-add": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]" + }, + "message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "root-and-replace": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]" + }, + "message": "Invalid accessLogSettings path. Must be one of : [/accessLogSettings/destinationArn, /accessLogSettings/format]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-stage-access-log-dest-add": { + "accessLogSettings": { + "destinationArn": "" + }, + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "dev stage", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "dev", + "tracingEnabled": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update-stage-access-log-destinationArn-empty": { + "Error": { + "Code": "BadRequestException", + "Message": "Access Log value must not be empty" + }, + "message": "Access Log value must not be empty", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-stage-access-log-format-empty": { + "Error": { + "Code": "BadRequestException", + "Message": "Access Log value must not be empty" + }, + "message": "Access Log value must not be empty", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-stage-access-log-dest-remove": { + "Error": { + "Code": "BadRequestException", + "Message": "Cannot remove method setting accessLogSettings/destinationArn because there is no method setting for this method " + }, + "message": "Cannot remove method setting accessLogSettings/destinationArn because there is no method setting for this method ", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/apigateway/test_apigateway_api.validation.json b/tests/aws/services/apigateway/test_apigateway_api.validation.json index a2b0702a74ced..ad5d0abfc83e5 100644 --- a/tests/aws/services/apigateway/test_apigateway_api.validation.json +++ b/tests/aws/services/apigateway/test_apigateway_api.validation.json @@ -9,7 +9,22 @@ "last_validated_date": "2024-04-15T20:52:34+00:00" }, "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts": { - "last_validated_date": "2024-04-15T20:56:45+00:00" + "last_validated_date": "2026-02-09T18:59:24+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 2.22, + "teardown": 0.3, + "total": 3.44 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts_bad_file": { + "last_validated_date": "2025-11-18T17:21:35+00:00", + "durations_in_seconds": { + "setup": 0.83, + "call": 1.14, + "teardown": 0.34, + "total": 2.31 + } }, "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_create_documentation_part_operations": { "last_validated_date": "2024-04-15T20:53:48+00:00" @@ -104,6 +119,15 @@ "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_behaviour": { "last_validated_date": "2024-04-15T17:29:08+00:00" }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_on_root": { + "last_validated_date": "2025-09-03T15:58:00+00:00", + "durations_in_seconds": { + "setup": 0.82, + "call": 0.86, + "teardown": 0.3, + "total": 1.98 + } + }, "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_private_type": { "last_validated_date": "2025-07-04T16:09:35+00:00", "durations_in_seconds": { @@ -236,7 +260,121 @@ "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_update_gateway_response": { "last_validated_date": "2024-04-15T20:47:11+00:00" }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_integration": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayStage::test_get_stages_filters_by_deployment_id": { + "last_validated_date": "2026-02-25T23:40:35+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 13.16, + "teardown": 0.45, + "total": 14.52 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayStage::test_stage_access_log_settings": { + "last_validated_date": "2026-02-25T23:08:31+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 3.57, + "teardown": 0.62, + "total": 5.07 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayStage::test_stage_access_log_settings_validation": { + "last_validated_date": "2026-02-25T23:16:02+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 3.99, + "teardown": 0.49, + "total": 5.4 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_create_vpc_link_invalid_parameters": { + "last_validated_date": "2025-12-15T04:55:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_delete_vpc_link_invalid_id": { + "last_validated_date": "2025-12-15T04:55:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_get_vpc_link_invalid_id": { + "last_validated_date": "2025-12-15T04:55:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_update_vpc_link_invalid_id": { + "last_validated_date": "2025-12-15T04:55:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.02, + "total": 0.12 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayVpcLink::test_vpc_link_lifecycle": { + "last_validated_date": "2025-12-15T08:04:55+00:00", + "durations_in_seconds": { + "setup": 1.52, + "call": 548.36, + "teardown": 0.56, + "total": 550.44 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_create_integration_with_vpc_link": { + "last_validated_date": "2025-10-08T09:05:56+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 1.59, + "teardown": 0.63, + "total": 3.06 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_get_integration_non_existent": { + "last_validated_date": "2025-11-18T17:03:33+00:00", + "durations_in_seconds": { + "setup": 0.83, + "call": 1.6, + "teardown": 0.38, + "total": 2.81 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_request_parameter_bool_type": { + "last_validated_date": "2024-12-12T10:46:41+00:00" + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_templates": { + "last_validated_date": "2026-02-12T16:25:03+00:00", + "durations_in_seconds": { + "setup": 0.9, + "call": 1.65, + "teardown": 0.29, + "total": 2.84 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_wrong_type": { + "last_validated_date": "2024-04-15T20:48:47+00:00" + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_delete_integration_response_errors": { + "last_validated_date": "2025-08-21T17:53:19+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 2.14, + "teardown": 0.33, + "total": 3.31 + } + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_invalid_integration": { "last_validated_date": "2025-06-26T11:21:05+00:00", "durations_in_seconds": { "setup": 1.63, @@ -245,7 +383,7 @@ "total": 3.2 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_responsetemplates": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_invalid_responsetemplates": { "last_validated_date": "2025-06-18T13:03:29+00:00", "durations_in_seconds": { "setup": 1.43, @@ -254,7 +392,7 @@ "total": 3.8 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_statuscode": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_invalid_statuscode": { "last_validated_date": "2025-06-18T11:51:51+00:00", "durations_in_seconds": { "setup": 1.61, @@ -263,7 +401,7 @@ "total": 3.33 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_api": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_api": { "last_validated_date": "2025-06-18T12:28:55+00:00", "durations_in_seconds": { "setup": 1.08, @@ -272,7 +410,7 @@ "total": 1.4 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_method": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_method": { "last_validated_date": "2025-06-18T11:47:00+00:00", "durations_in_seconds": { "setup": 1.42, @@ -281,7 +419,7 @@ "total": 2.96 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_resource": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_resource": { "last_validated_date": "2025-06-18T12:28:25+00:00", "durations_in_seconds": { "setup": 1.35, @@ -290,7 +428,7 @@ "total": 2.98 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_status_code": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_integration_response_wrong_status_code": { "last_validated_date": "2025-06-26T11:21:43+00:00", "durations_in_seconds": { "setup": 1.58, @@ -299,7 +437,7 @@ "total": 3.4 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_integration_response": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_lifecycle_integration_response": { "last_validated_date": "2025-06-11T09:12:54+00:00", "durations_in_seconds": { "setup": 1.49, @@ -308,7 +446,10 @@ "total": 4.21 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_method_response": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegrationResponse::test_put_integration_response_validation": { + "last_validated_date": "2025-03-03T14:27:24+00:00" + }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_lifecycle_method_response": { "last_validated_date": "2025-07-01T15:48:02+00:00", "durations_in_seconds": { "setup": 1.38, @@ -317,16 +458,7 @@ "total": 4.55 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_request_parameter_bool_type": { - "last_validated_date": "2024-12-12T10:46:41+00:00" - }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_validation": { - "last_validated_date": "2025-03-03T14:27:24+00:00" - }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_wrong_type": { - "last_validated_date": "2024-04-15T20:48:47+00:00" - }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_lack_response_parameters_and_models": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_lack_response_parameters_and_models": { "last_validated_date": "2025-07-01T15:48:38+00:00", "durations_in_seconds": { "setup": 1.4, @@ -335,7 +467,7 @@ "total": 3.77 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_response": { "last_validated_date": "2025-06-30T12:42:31+00:00", "durations_in_seconds": { "setup": 1.26, @@ -344,7 +476,7 @@ "total": 3.24 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_negative_tests": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_response_negative_tests": { "last_validated_date": "2025-06-30T15:24:43+00:00", "durations_in_seconds": { "setup": 1.53, @@ -353,7 +485,7 @@ "total": 4.41 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_wrong_operations": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_response_wrong_operations": { "last_validated_date": "2025-06-30T13:54:57+00:00", "durations_in_seconds": { "setup": 1.36, @@ -362,7 +494,7 @@ "total": 4.13 } }, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_wrong_param_names": { + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayMethodResponse::test_update_method_wrong_param_names": { "last_validated_date": "2025-07-01T15:49:16+00:00", "durations_in_seconds": { "setup": 1.44, @@ -371,7 +503,22 @@ "total": 3.98 } }, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_failed_invoke_test_method": { + "last_validated_date": "2025-09-29T19:44:14+00:00", + "durations_in_seconds": { + "setup": 0.81, + "call": 1.14, + "teardown": 0.67, + "total": 2.62 + } + }, "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_invoke_test_method": { - "last_validated_date": "2024-04-15T20:48:35+00:00" + "last_validated_date": "2025-09-29T19:28:50+00:00", + "durations_in_seconds": { + "setup": 0.86, + "call": 4.31, + "teardown": 0.67, + "total": 5.84 + } } } diff --git a/tests/aws/services/apigateway/test_apigateway_basic.py b/tests/aws/services/apigateway/test_apigateway_basic.py index ec03c2b1612bb..2512c51b7f68b 100644 --- a/tests/aws/services/apigateway/test_apigateway_basic.py +++ b/tests/aws/services/apigateway/test_apigateway_basic.py @@ -4,7 +4,7 @@ import os import re from collections import namedtuple -from typing import Callable, Optional +from collections.abc import Callable import botocore import pytest @@ -142,7 +142,7 @@ def test_create_rest_api_with_custom_id(self, create_rest_apigw, url_function, a if not is_next_gen_api() and url_function == localstack_path_based_url: pytest.skip("This URL type is not supported in the legacy implementation") apigw_name = f"gw-{short_uid()}" - test_id = "testId123" + test_id = "test-id123" api_id, name, _ = create_rest_apigw(name=apigw_name, tags={TAG_KEY_CUSTOM_ID: test_id}) assert test_id == api_id assert apigw_name == name @@ -160,6 +160,20 @@ def test_create_rest_api_with_custom_id(self, create_rest_apigw, url_function, a assert response.ok assert response._content == b'{"echo": "foobar", "response": "mocked"}' + @markers.aws.only_localstack + # This is not a possible feature on aws. + def test_create_rest_api_with_invalid_custom_id(self, create_rest_apigw, aws_client): + apigw_name = f"gw-{short_uid()}" + test_id = "testId123" + with pytest.raises(ClientError) as exc: + create_rest_apigw(name=apigw_name, tags={TAG_KEY_CUSTOM_ID: test_id}) + + assert exc.value.response["Error"]["Code"] == "BadRequestException" + assert ( + exc.value.response["Error"]["Message"] + == f"The RestApiId '{test_id}' cannot contain uppercase characters" + ) + @markers.aws.validated def test_update_rest_api_deployment(self, create_rest_apigw, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.key_value("id")) @@ -301,6 +315,7 @@ def test_api_gateway_lambda_integration_aws_type( @pytest.mark.parametrize("disable_custom_cors", [True, False]) @pytest.mark.parametrize("origin", ["http://allowed", "http://denied"]) @markers.aws.only_localstack + @markers.requires_in_process def test_invoke_endpoint_cors_headers( self, url_type, disable_custom_cors, origin, monkeypatch, aws_client ): @@ -409,7 +424,7 @@ def _test_api_gateway_lambda_proxy_integration_no_asserts( path: str, role_arn: str, apigw_client, - data_mutator_fn: Optional[Callable] = None, + data_mutator_fn: Callable | None = None, ) -> ApiGatewayLambdaProxyIntegrationTestResult: """ Perform the setup needed to do a POST against a Lambda Proxy Integration; @@ -482,9 +497,7 @@ def _test_api_gateway_lambda_proxy_integration( try: parsed_body = json.loads(to_str(result.content)) except json.decoder.JSONDecodeError as e: - raise Exception( - "Couldn't json-decode content: {}".format(to_str(result.content)) - ) from e + raise Exception(f"Couldn't json-decode content: {to_str(result.content)}") from e assert parsed_body.get("return_status_code") == 203 assert parsed_body.get("return_headers") == {"foo": "bar123"} assert parsed_body.get("queryStringParameters") == {"foo": "foo", "bar": "baz"} @@ -645,7 +658,7 @@ def test_api_gateway_authorizer_crud(self, aws_client): restApiId=get_api_gateway_id, authorizerId=authorizer_id ) - with pytest.raises(Exception): + with pytest.raises(ClientError): aws_client.apigateway.get_authorizer( restApiId=get_api_gateway_id, authorizerId=authorizer_id ) @@ -717,8 +730,8 @@ def test_apigateway_with_custom_authorization_method( restApiId=api_id, name="lambda_authorizer", type="TOKEN", - authorizerUri="arn:aws:apigateway:us-east-1:lambda:path/ \ - 2015-03-31/functions/{}/invocations".format(lambda_uri), + authorizerUri=f"arn:aws:apigateway:us-east-1:lambda:path/ \ + 2015-03-31/functions/{lambda_uri}/invocations", identitySource="method.request.header.Auth", ) snapshot.match("authorizer", authorizer) @@ -786,9 +799,9 @@ def test_base_path_mapping(self, create_rest_apigw, aws_client): # DELETE aws_client.apigateway.delete_base_path_mapping(domainName=domain_name, basePath=base_path) - with pytest.raises(Exception): + with pytest.raises(ClientError): aws_client.apigateway.get_base_path_mapping(domainName=domain_name, basePath=base_path) - with pytest.raises(Exception): + with pytest.raises(ClientError): aws_client.apigateway.delete_base_path_mapping( domainName=domain_name, basePath=base_path ) @@ -842,9 +855,9 @@ def test_base_path_mapping_root(self, aws_client): # DELETE client.delete_base_path_mapping(domainName=domain_name, basePath=base_path) - with pytest.raises(Exception): + with pytest.raises(ClientError): client.get_base_path_mapping(domainName=domain_name, basePath=base_path) - with pytest.raises(Exception): + with pytest.raises(ClientError): client.delete_base_path_mapping(domainName=domain_name, basePath=base_path) @markers.aws.needs_fixing @@ -1236,42 +1249,8 @@ def test_apigw_test_invoke_method_api( create_role_with_policy, region_name, snapshot, + apigw_test_invoke_response_formatter, ): - snapshot.add_transformers_list( - [ - snapshot.transform.key_value( - "latency", value_replacement="", reference_replacement=False - ), - snapshot.transform.jsonpath( - "$..headers.X-Amzn-Trace-Id", value_replacement="x-amz-trace-id" - ), - snapshot.transform.regex( - r"URI: https:\/\/.*?\/2015-03-31", "URI: https:///2015-03-31" - ), - snapshot.transform.regex( - r"Integration latency: \d*? ms", "Integration latency: ms" - ), - snapshot.transform.regex( - r"Date=[a-zA-Z]{3},\s\d{2}\s[a-zA-Z]{3}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT", - "Date=Day, dd MMM yyyy hh:mm:ss GMT", - ), - snapshot.transform.regex( - r"x-amzn-RequestId=[a-f0-9-]{36}", "x-amzn-RequestId=" - ), - snapshot.transform.regex( - r"[a-zA-Z]{3}\s[a-zA-Z]{3}\s\d{2}\s\d{2}:\d{2}:\d{2}\sUTC\s\d{4} :", - "DDD MMM dd hh:mm:ss UTC yyyy :", - ), - snapshot.transform.regex( - r"Authorization=.*?,", "Authorization=," - ), - snapshot.transform.regex( - r"X-Amz-Security-Token=.*?\s\[", "X-Amz-Security-Token= [" - ), - snapshot.transform.regex(r"\d{8}T\d{6}Z", ""), - ] - ) - _, role_arn = create_role_with_policy( "Allow", "lambda:InvokeFunction", json.dumps(APIGATEWAY_ASSUME_ROLE_POLICY), "*" ) @@ -1348,18 +1327,10 @@ def _test_invoke_call( invoke_simple = retry(_test_invoke_call, retries=15, sleep=1, path_with_qs="/foo") - def _transform_log(_log: str) -> dict[str, str]: - return {f"line{index:02d}": line for index, line in enumerate(_log.split("\n"))} - - # we want to do very precise matching on the log, and splitting on new lines will help in case the snapshot - # fails - # the snapshot library does not allow to ignore an array index as the last node, so we need to put it in a dict - invoke_simple["log"] = _transform_log(invoke_simple["log"]) - request_id_1 = invoke_simple["log"]["line00"].split(" ")[-1] - snapshot.add_transformer( - snapshot.transform.regex(request_id_1, ""), priority=-1 + snapshot.match( + "test_invoke_method_response", + apigw_test_invoke_response_formatter(invoke_simple), ) - snapshot.match("test_invoke_method_response", invoke_simple) # run test_invoke_method API #2 invoke_with_parameters = retry( @@ -1373,12 +1344,10 @@ def _transform_log(_log: str) -> dict[str, str]: response_body = json.loads(invoke_with_parameters.get("body")).get("body") assert "response from" in response_body assert "val123" in response_body - invoke_with_parameters["log"] = _transform_log(invoke_with_parameters["log"]) - request_id_2 = invoke_with_parameters["log"]["line00"].split(" ")[-1] - snapshot.add_transformer( - snapshot.transform.regex(request_id_2, ""), priority=-1 + snapshot.match( + "test_invoke_method_response_with_body", + apigw_test_invoke_response_formatter(invoke_with_parameters), ) - snapshot.match("test_invoke_method_response_with_body", invoke_with_parameters) @markers.aws.validated @pytest.mark.parametrize("stage_name", ["local", "dev"]) @@ -1550,10 +1519,11 @@ class TestTagging: def test_tag_api(self, create_rest_apigw, aws_client, account_id, region_name): api_name = f"api-{short_uid()}" tags = {"foo": "bar"} + custom_id = "c0stiom1d" # add resource tags - api_id, _, _ = create_rest_apigw(name=api_name, tags={TAG_KEY_CUSTOM_ID: "c0stIOm1d"}) - assert api_id == "c0stIOm1d" + api_id, _, _ = create_rest_apigw(name=api_name, tags={TAG_KEY_CUSTOM_ID: custom_id}) + assert api_id == custom_id api_arn = arns.apigateway_restapi_arn(api_id, account_id, region_name) aws_client.apigateway.tag_resource(resourceArn=api_arn, tags=tags) diff --git a/tests/aws/services/apigateway/test_apigateway_basic.snapshot.json b/tests/aws/services/apigateway/test_apigateway_basic.snapshot.json index 4cdbcb8e1e311..8f40ae76a2fa4 100644 --- a/tests/aws/services/apigateway/test_apigateway_basic.snapshot.json +++ b/tests/aws/services/apigateway/test_apigateway_basic.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_test_invoke_method_api": { - "recorded-date": "11-04-2025, 18:02:16", + "recorded-date": "19-08-2025, 17:04:50", "recorded-content": { "test_invoke_method_response": { "body": { diff --git a/tests/aws/services/apigateway/test_apigateway_basic.validation.json b/tests/aws/services/apigateway/test_apigateway_basic.validation.json index 43de03144651a..e713561a4681c 100644 --- a/tests/aws/services/apigateway/test_apigateway_basic.validation.json +++ b/tests/aws/services/apigateway/test_apigateway_basic.validation.json @@ -15,7 +15,13 @@ "last_validated_date": "2024-07-12T20:04:15+00:00" }, "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_test_invoke_method_api": { - "last_validated_date": "2025-04-11T18:03:13+00:00" + "last_validated_date": "2025-08-19T17:04:53+00:00", + "durations_in_seconds": { + "setup": 11.42, + "call": 10.49, + "teardown": 2.81, + "total": 24.72 + } }, "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_update_rest_api_deployment": { "last_validated_date": "2024-04-12T21:24:49+00:00" diff --git a/tests/aws/services/apigateway/test_apigateway_common.py b/tests/aws/services/apigateway/test_apigateway_common.py index 50d032e0d2245..382c24d1e6261 100644 --- a/tests/aws/services/apigateway/test_apigateway_common.py +++ b/tests/aws/services/apigateway/test_apigateway_common.py @@ -580,6 +580,7 @@ def invoke_api(_data: dict) -> dict: snapshot.match("failed-validation-bad-data", response_post_bad_body.json()) @markers.aws.validated + @markers.requires_in_process # uses pytest httpserver def test_integration_request_parameters_mapping( self, create_rest_apigw, aws_client, echo_http_server_post ): @@ -791,6 +792,7 @@ def _invoke_api(path: str, headers: dict[str, str]) -> dict[str, str]: assert split_trace[1] != hardcoded_parent @markers.aws.validated + @markers.requires_in_process # uses pytest httpserver def test_input_path_template_formatting( self, aws_client, create_rest_apigw, echo_http_server_post, snapshot ): diff --git a/tests/aws/services/apigateway/test_apigateway_custom_ids.py b/tests/aws/services/apigateway/test_apigateway_custom_ids.py index 0accdcfdfd103..1df103348b91b 100644 --- a/tests/aws/services/apigateway/test_apigateway_custom_ids.py +++ b/tests/aws/services/apigateway/test_apigateway_custom_ids.py @@ -1,3 +1,5 @@ +import pytest +from botocore.exceptions import ClientError from moto.apigateway.utils import ( ApigwApiKeyIdentifier, ApigwResourceIdentifier, @@ -7,7 +9,7 @@ from localstack.testing.pytest import markers from localstack.utils.strings import long_uid, short_uid -API_ID = "ApiId" +API_ID = "api-id" ROOT_RESOURCE_ID = "RootId" PET_1_RESOURCE_ID = "Pet1Id" PET_2_RESOURCE_ID = "Pet2Id" @@ -16,6 +18,7 @@ # Custom ids can't be set on aws. @markers.aws.only_localstack +@markers.requires_in_process def test_apigateway_custom_ids( aws_client, set_resource_custom_id, create_rest_apigw, account_id, region_name, cleanups ): @@ -59,3 +62,23 @@ def test_apigateway_custom_ids( assert pet_resource_1["id"] == PET_1_RESOURCE_ID assert pet_resource_2["id"] == PET_2_RESOURCE_ID assert api_key["id"] == API_KEY_ID + + +@markers.aws.only_localstack +@markers.requires_in_process +def test_apigateway_invalid_rest_api_custom_id( + aws_client, set_resource_custom_id, create_rest_apigw, account_id, region_name, cleanups +): + rest_api_name = f"apigw-{short_uid()}" + bad_api_id = "UpperCaseApi" + + set_resource_custom_id( + ApigwRestApiIdentifier(account_id, region_name, rest_api_name), bad_api_id + ) + with pytest.raises(ClientError) as exc: + create_rest_apigw(name=rest_api_name) + assert exc.value.response["Error"]["Code"] == "BadRequestException" + assert ( + exc.value.response["Error"]["Message"] + == f"The RestApiId '{bad_api_id}' cannot contain uppercase characters" + ) diff --git a/tests/aws/services/apigateway/test_apigateway_extended.py b/tests/aws/services/apigateway/test_apigateway_extended.py index c95965db241c1..555bcb882f2da 100644 --- a/tests/aws/services/apigateway/test_apigateway_extended.py +++ b/tests/aws/services/apigateway/test_apigateway_extended.py @@ -46,8 +46,6 @@ def _create(**kwargs): @markers.snapshot.skip_snapshot_verify( paths=[ "$..body.host", - # TODO: not returned by LS - "$..endpointConfiguration.ipAddressType", ] ) def test_export_swagger_openapi(aws_client, snapshot, import_apigw, import_file, region_name): @@ -91,8 +89,6 @@ def test_export_swagger_openapi(aws_client, snapshot, import_apigw, import_file, @markers.snapshot.skip_snapshot_verify( paths=[ "$..body.servers..url", - # TODO: not returned by LS - "$..endpointConfiguration.ipAddressType", ] ) def test_export_oas30_openapi(aws_client, snapshot, import_apigw, region_name, import_file): @@ -272,7 +268,7 @@ def test_get_usage_plan_api_keys(self, aws_client, apigw_create_api_key, snapsho snapshot.match("create-api-key", create_api_key) create_api_key_2 = apigw_create_api_key(name=api_key_name_2) - snapshot.match("create-api-key-2", create_api_key) + snapshot.match("create-api-key-2", create_api_key_2) get_api_keys_after_create = aws_client.apigateway.get_api_keys() snapshot.match("get-api-keys-after-create-1", get_api_keys_after_create) @@ -332,3 +328,141 @@ def test_get_usage_plan_api_keys(self, aws_client, apigw_create_api_key, snapsho get_up_keys_bad_d = aws_client.apigateway.get_usage_plan_keys(usagePlanId="bad-id") snapshot.match("get-up-keys-bad-usage-plan", get_up_keys_bad_d) + + @markers.aws.validated + def test_negative_get_usage_plan_api_keys( + self, aws_client, apigw_create_api_key, snapshot, cleanup_api_keys, cleanups + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("id"), + snapshot.transform.key_value("value"), + snapshot.transform.key_value("name"), + ] + ) + + get_keys = aws_client.apigateway.get_api_keys(limit=-1) + snapshot.match("get-api-keys-invalid-limit", get_keys) + + create_api_key = apigw_create_api_key(name="key-enabled-false", enabled=False) + assert not create_api_key["enabled"] + snapshot.match("create-api-key", create_api_key) + + update_resp = aws_client.apigateway.update_api_key( + apiKey=create_api_key["id"], + patchOperations=[{"op": "replace", "path": "/name", "value": "new-name"}], + ) + snapshot.match("update-api-key-name", update_resp) + + @markers.aws.validated + def test_create_api_key_with_invalid_name( + self, aws_client, snapshot, cleanup_api_keys, cleanups + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("id"), + snapshot.transform.key_value("value"), + ] + ) + long_name = "a" * 1025 + with pytest.raises(ClientError) as e: + aws_client.apigateway.create_api_key(name=long_name) + snapshot.match("create-api-key-name-too-long", e.value.response) + + create_empty_name = aws_client.apigateway.create_api_key(name="") + snapshot.match("create-api-key-empty-name", create_empty_name) + + create_no_name = aws_client.apigateway.create_api_key() + snapshot.match("create-api-key-no-name", create_no_name) + + @markers.aws.validated + def test_create_api_key_with_invalid_value( + self, aws_client, snapshot, cleanup_api_keys, cleanups + ): + snapshot.add_transformers_list([snapshot.transform.key_value("id")]) + shot_value = "short" + with pytest.raises(ClientError) as e: + aws_client.apigateway.create_api_key(value=shot_value) + snapshot.match("create-api-key-short-value", e.value.response) + + long_value = "a" * 129 + with pytest.raises(ClientError) as e: + aws_client.apigateway.create_api_key(value=long_value) + snapshot.match("create-api-key-invalid-value-too-long", e.value.response) + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("id"), + snapshot.transform.key_value("value"), + ] + ) + empty_value = aws_client.apigateway.create_api_key(value="") + snapshot.add_transformers_list([snapshot.transform.key_value("id")]) + snapshot.match("create-api-key-empty-value", empty_value) + + @markers.aws.validated + def test_update_api_key_invalid_description_length( + self, aws_client, snapshot, apigw_create_api_key, cleanup_api_keys, cleanups + ): + long_description = "a" * 125001 + with pytest.raises(ClientError) as e: + apigw_create_api_key(description=long_description) + snapshot.match("create-api-key-description-too-long", e.value.response) + + api_key = apigw_create_api_key() + api_key_id = api_key["id"] + + long_description = "a" * 125001 + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_api_key( + apiKey=api_key_id, + patchOperations=[ + {"op": "replace", "path": "/description", "value": long_description} + ], + ) + snapshot.match("update-api-key-description-too-long", e.value.response) + + @markers.aws.validated + def test_update_api_key_invalid_enabled_type( + self, aws_client, snapshot, apigw_create_api_key, cleanup_api_keys, cleanups + ): + snapshot.add_transformers_list([snapshot.transform.key_value("id")]) + api_key = apigw_create_api_key() + api_key_id = api_key["id"] + + invalid_enabled_type = aws_client.apigateway.update_api_key( + apiKey=api_key_id, + patchOperations=[{"op": "replace", "path": "/enabled", "value": "not-a-boolean"}], + ) + snapshot.match("update-api-key-invalid-enabled-type", invalid_enabled_type) + + @markers.aws.validated + def test_update_api_key_immutable_field( + self, aws_client, snapshot, apigw_create_api_key, cleanup_api_keys, cleanups + ): + api_key = apigw_create_api_key() + api_key_id = api_key["id"] + + with pytest.raises(ClientError) as e: + aws_client.apigateway.update_api_key( + apiKey=api_key_id, + patchOperations=[{"op": "replace", "path": "/id", "value": "new-id"}], + ) + snapshot.match("update-api-key-immutable-field", e.value.response) + + @markers.aws.validated + def test_create_usage_plan_with_throttle( + self, aws_client, snapshot, cleanup_api_keys, cleanups + ): + snapshot.add_transformer( + [ + snapshot.transform.key_value("id"), + snapshot.transform.key_value("name"), + ] + ) + + invalid_burst_limit = aws_client.apigateway.create_usage_plan( + name=f"invalid-throttle-burst-gt-rate-{short_uid()}", + throttle={"rateLimit": 10, "burstLimit": 20}, + ) + snapshot.match("create-usage-plan-burst-gt-rate", invalid_burst_limit) diff --git a/tests/aws/services/apigateway/test_apigateway_extended.snapshot.json b/tests/aws/services/apigateway/test_apigateway_extended.snapshot.json index 76db5eff4a01b..f22d6adb87600 100644 --- a/tests/aws/services/apigateway/test_apigateway_extended.snapshot.json +++ b/tests/aws/services/apigateway/test_apigateway_extended.snapshot.json @@ -1847,7 +1847,7 @@ } }, "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_usage_plan_api_keys": { - "recorded-date": "10-10-2024, 18:54:42", + "recorded-date": "26-09-2025, 16:23:51", "recorded-content": { "get-api-keys": { "items": [], @@ -1872,11 +1872,11 @@ "create-api-key-2": { "createdDate": "datetime", "enabled": false, - "id": "", + "id": "", "lastUpdatedDate": "datetime", - "name": "", + "name": "", "stageKeys": [], - "value": "", + "value": "", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 201 @@ -2025,5 +2025,200 @@ } } } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_negative_get_usage_plan_api_keys": { + "recorded-date": "16-07-2025, 15:26:48", + "recorded-content": { + "get-api-keys-invalid-limit": { + "items": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "create-api-key": { + "createdDate": "datetime", + "enabled": false, + "id": "", + "lastUpdatedDate": "datetime", + "name": "", + "stageKeys": [], + "value": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "update-api-key-name": { + "createdDate": "datetime", + "enabled": false, + "id": "", + "lastUpdatedDate": "datetime", + "name": "", + "stageKeys": [], + "tags": {}, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_create_api_key_with_invalid_name": { + "recorded-date": "17-07-2025, 13:16:59", + "recorded-content": { + "create-api-key-name-too-long": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid API Key name, can be at most 1024 characters." + }, + "message": "Invalid API Key name, can be at most 1024 characters.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create-api-key-empty-name": { + "createdDate": "datetime", + "enabled": false, + "id": "", + "lastUpdatedDate": "datetime", + "stageKeys": [], + "value": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "create-api-key-no-name": { + "createdDate": "datetime", + "enabled": false, + "id": "", + "lastUpdatedDate": "datetime", + "stageKeys": [], + "value": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_create_api_key_with_invalid_value": { + "recorded-date": "17-07-2025, 13:59:45", + "recorded-content": { + "create-api-key-short-value": { + "Error": { + "Code": "BadRequestException", + "Message": "API Key value should be at least 20 characters" + }, + "message": "API Key value should be at least 20 characters", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create-api-key-invalid-value-too-long": { + "Error": { + "Code": "BadRequestException", + "Message": "API Key value exceeds maximum size of 128 characters" + }, + "message": "API Key value exceeds maximum size of 128 characters", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create-api-key-empty-value": { + "createdDate": "datetime", + "enabled": false, + "id": "", + "lastUpdatedDate": "datetime", + "stageKeys": [], + "value": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_update_api_key_invalid_description_length": { + "recorded-date": "17-07-2025, 15:02:27", + "recorded-content": { + "create-api-key-description-too-long": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid API Key description specified." + }, + "message": "Invalid API Key description specified.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-api-key-description-too-long": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid API Key description specified." + }, + "message": "Invalid API Key description specified.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_update_api_key_invalid_enabled_type": { + "recorded-date": "17-07-2025, 15:33:05", + "recorded-content": { + "update-api-key-invalid-enabled-type": { + "createdDate": "datetime", + "enabled": false, + "id": "", + "lastUpdatedDate": "datetime", + "stageKeys": [], + "tags": {}, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_update_api_key_immutable_field": { + "recorded-date": "18-07-2025, 14:31:57", + "recorded-content": { + "update-api-key-immutable-field": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid patch path '/id' specified for op 'replace'. Must be one of: [/description, /enabled, /name, /customerId]" + }, + "message": "Invalid patch path '/id' specified for op 'replace'. Must be one of: [/description, /enabled, /name, /customerId]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_create_usage_plan_with_throttle": { + "recorded-date": "26-09-2025, 15:15:33", + "recorded-content": { + "create-usage-plan-burst-gt-rate": { + "apiStages": [], + "id": "", + "name": "", + "throttle": { + "burstLimit": 20, + "rateLimit": 10.0 + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } } } diff --git a/tests/aws/services/apigateway/test_apigateway_extended.validation.json b/tests/aws/services/apigateway/test_apigateway_extended.validation.json index 1486731f72d07..8c1979af0efb9 100644 --- a/tests/aws/services/apigateway/test_apigateway_extended.validation.json +++ b/tests/aws/services/apigateway/test_apigateway_extended.validation.json @@ -1,9 +1,78 @@ { + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_create_api_key_with_invalid_name": { + "last_validated_date": "2025-09-26T15:36:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_create_api_key_with_invalid_value": { + "last_validated_date": "2025-09-26T15:36:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.39, + "teardown": 0.0, + "total": 0.39 + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_create_usage_plan_with_throttle": { + "last_validated_date": "2025-09-26T15:36:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.41, + "teardown": 0.0, + "total": 0.41 + } + }, "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_api_keys": { "last_validated_date": "2024-10-10T18:53:36+00:00" }, "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_usage_plan_api_keys": { - "last_validated_date": "2024-10-10T18:54:41+00:00" + "last_validated_date": "2025-09-26T16:25:01+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 4.05, + "teardown": 0.59, + "total": 5.52 + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_negative_get_usage_plan_api_keys": { + "last_validated_date": "2025-09-26T15:36:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.51, + "teardown": 0.24, + "total": 0.75 + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_update_api_key_immutable_field": { + "last_validated_date": "2025-09-26T15:36:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.29, + "teardown": 0.32, + "total": 0.61 + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_update_api_key_invalid_description_length": { + "last_validated_date": "2025-09-26T15:36:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.66, + "teardown": 0.29, + "total": 0.95 + } + }, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_update_api_key_invalid_enabled_type": { + "last_validated_date": "2025-09-26T15:36:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.55, + "teardown": 0.31, + "total": 0.86 + } }, "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": { "last_validated_date": "2025-05-06T18:34:11+00:00" diff --git a/tests/aws/services/apigateway/test_apigateway_http.py b/tests/aws/services/apigateway/test_apigateway_http.py index 5d81a181e82bd..6d946469db042 100644 --- a/tests/aws/services/apigateway/test_apigateway_http.py +++ b/tests/aws/services/apigateway/test_apigateway_http.py @@ -245,6 +245,8 @@ def invoke_api(url: str, method: str) -> dict: "$..headers.x-amzn-remapped-x-amzn-requestid", # TODO AWS doesn't seems to add Server to lambda invocation for lambda url "$..headers.x-amzn-remapped-server", + # The value of Accept-Encoding can change when the zstandard package is present + "$..content.headers.accept-encoding", ] ) @pytest.mark.skipif( diff --git a/tests/aws/services/apigateway/test_apigateway_import.py b/tests/aws/services/apigateway/test_apigateway_import.py index 6ed23dcc3c1ba..668989ee64d64 100644 --- a/tests/aws/services/apigateway/test_apigateway_import.py +++ b/tests/aws/services/apigateway/test_apigateway_import.py @@ -802,15 +802,14 @@ def call_api(): url_error = api_invoke_url(api_id=rest_api_id, stage="v2", path="/path1") - def call_api_error(): + def call_api_error() -> requests.Response: res = requests.get(url_error) assert res.status_code == 500 - return res.json() + return res resp = retry(call_api_error, retries=5, sleep=2) - # we remove the headers from the response, not really needed for this test - resp.pop("headers", None) - snapshot.match("get-error-resp-from-http", resp) + error = {"body": resp.json(), "errorType": resp.headers.get("x-amzn-ErrorType")} + snapshot.match("get-error-resp-from-http", error) @markers.aws.validated @markers.snapshot.skip_snapshot_verify( @@ -965,3 +964,28 @@ def test_put_rest_api_mode_binary_media_types( if is_aws_cloud(): # waiting before cleaning up to avoid TooManyRequests, as we create multiple REST APIs time.sleep(15) + + @markers.aws.validated + def test_import_api_bad_file(self, aws_client, import_apigw, apigw_create_rest_api, snapshot): + bad_yaml_string = """test: + value: + - "value ... \"escaped\": $var, \"dt\": $(var +"%a")}\" + """ + + bad_json_string = "{'key:value}" + + with pytest.raises(ClientError) as e: + import_apigw(body=bad_yaml_string) + snapshot.match("import-api-bad-yaml-file", e.value.response) + + with pytest.raises(ClientError) as e: + import_apigw(body=bad_json_string) + snapshot.match("import-api-bad-json-file", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.apigateway.put_rest_api(restApiId=short_uid(), body=bad_yaml_string) + snapshot.match("put-rest-api-bad-yaml-file", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.apigateway.put_rest_api(restApiId=short_uid(), body=bad_json_string) + snapshot.match("put-rest-api-bad-json-file", e.value.response) diff --git a/tests/aws/services/apigateway/test_apigateway_import.snapshot.json b/tests/aws/services/apigateway/test_apigateway_import.snapshot.json index b1b91697846da..fe6ded88a898f 100644 --- a/tests/aws/services/apigateway/test_apigateway_import.snapshot.json +++ b/tests/aws/services/apigateway/test_apigateway_import.snapshot.json @@ -4823,7 +4823,7 @@ } }, "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_stage_variables": { - "recorded-date": "02-07-2025, 16:31:50", + "recorded-date": "29-09-2025, 20:07:21", "recorded-content": { "get-resp-from-http": { "args": { @@ -4836,7 +4836,10 @@ "path": "/test-path" }, "get-error-resp-from-http": { - "message": "Internal server error" + "body": { + "message": "Internal server error" + }, + "errorType": "InternalServerErrorException" } } }, @@ -5471,5 +5474,54 @@ } } } + }, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_api_bad_file": { + "recorded-date": "18-11-2025, 17:29:30", + "recorded-content": { + "import-api-bad-yaml-file": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid OpenAPI input." + }, + "message": "Invalid OpenAPI input.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "import-api-bad-json-file": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid OpenAPI input." + }, + "message": "Invalid OpenAPI input.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "put-rest-api-bad-yaml-file": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid OpenAPI input." + }, + "message": "Invalid OpenAPI input.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "put-rest-api-bad-json-file": { + "Error": { + "Code": "BadRequestException", + "Message": "Invalid OpenAPI input." + }, + "message": "Invalid OpenAPI input.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/apigateway/test_apigateway_import.validation.json b/tests/aws/services/apigateway/test_apigateway_import.validation.json index 5f21b963b11b7..d61573549bde6 100644 --- a/tests/aws/services/apigateway/test_apigateway_import.validation.json +++ b/tests/aws/services/apigateway/test_apigateway_import.validation.json @@ -17,6 +17,15 @@ "total": 22.98 } }, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_api_bad_file": { + "last_validated_date": "2025-11-18T17:29:30+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.97, + "teardown": 0.01, + "total": 1.49 + } + }, "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api": { "last_validated_date": "2025-07-02T16:22:33+00:00", "durations_in_seconds": { @@ -144,12 +153,12 @@ } }, "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_stage_variables": { - "last_validated_date": "2025-07-02T16:31:50+00:00", + "last_validated_date": "2025-09-29T20:07:21+00:00", "durations_in_seconds": { - "setup": 0.11, - "call": 5.83, - "teardown": 69.73, - "total": 75.67 + "setup": 11.64, + "call": 11.14, + "teardown": 2.26, + "total": 25.04 } }, "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[merge]": { diff --git a/tests/aws/services/apigateway/test_apigateway_integrations.py b/tests/aws/services/apigateway/test_apigateway_integrations.py index 1b3c93c9367cc..34cd6d15fac8a 100644 --- a/tests/aws/services/apigateway/test_apigateway_integrations.py +++ b/tests/aws/services/apigateway/test_apigateway_integrations.py @@ -14,7 +14,9 @@ from localstack import config from localstack.aws.api.apigateway import IntegrationType +from localstack.aws.api.ec2 import VpcEndpoint from localstack.aws.api.lambda_ import Runtime +from localstack.config import in_docker from localstack.constants import APPLICATION_JSON from localstack.services.lambda_.networking import get_main_endpoint_from_container from localstack.testing.aws.util import is_aws_cloud @@ -184,6 +186,7 @@ def apigw_echo_http_server_post(apigw_echo_http_server): @markers.aws.validated +@markers.requires_in_process # uses pytest httpserver def test_http_integration_status_code_selection( create_rest_apigw, aws_client, status_code_http_server ): @@ -267,6 +270,7 @@ def invoke_api(url, requested_response_code: int, expected_response_code: int): @markers.aws.validated +@markers.requires_in_process # uses pytest httpserver def test_put_integration_responses(create_rest_apigw, aws_client, echo_http_server_post, snapshot): snapshot.add_transformers_list( [ @@ -883,15 +887,6 @@ def invoke_api(url) -> requests.Response: ) -@pytest.fixture -def default_vpc(aws_client): - vpcs = aws_client.ec2.describe_vpcs() - for vpc in vpcs["Vpcs"]: - if vpc.get("IsDefault"): - return vpc - raise Exception("Default VPC not found") - - @pytest.fixture def create_vpc_endpoint(default_vpc, aws_client): endpoints = [] @@ -914,9 +909,14 @@ def _create(**kwargs): "$..endpointConfiguration.types", "$..policy.Statement..Resource", "$..endpointConfiguration.ipAddressType", + "$.endpoint-details.ServiceRegion", ] ) @markers.aws.validated +@pytest.mark.skipif( + not is_aws_cloud() and not in_docker(), + reason="calling the vpce from a lambda requires LocalStack to run in docker", +) def test_create_execute_api_vpc_endpoint( create_rest_api_with_integration, dynamodb_create_table, @@ -926,6 +926,7 @@ def test_create_execute_api_vpc_endpoint( ec2_create_security_group, snapshot, aws_client, + region_name, ): poll_sleep = 5 if is_aws_cloud() else 1 # TODO: create a re-usable ec2_api() transformer @@ -955,7 +956,6 @@ def test_create_execute_api_vpc_endpoint( request_templates = {APPLICATION_JSON: json.dumps({"TableName": table_name})} # deploy REST API with integration - region_name = aws_client.apigateway.meta.region_name integration_uri = f"arn:aws:apigateway:{region_name}:dynamodb:action/Scan" api_id = create_rest_api_with_integration( integration_uri=integration_uri, @@ -971,7 +971,10 @@ def test_create_execute_api_vpc_endpoint( # create security group vpc_id = default_vpc["VpcId"] security_group = ec2_create_security_group( - VpcId=vpc_id, Description="Test SG for API GW", ports=[443] + VpcId=vpc_id, + Description="Test SG for API GW", + GroupName=f"test-sg-{short_uid()}", + ports=[443], ) security_group = security_group["GroupId"] subnets = aws_client.ec2.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]) @@ -994,15 +997,16 @@ def test_create_execute_api_vpc_endpoint( # wait until VPC endpoint is in state "available" def _check_available(): result = aws_client.ec2.describe_vpc_endpoints(VpcEndpointIds=[endpoint_id]) - endpoint_details = result["VpcEndpoints"][0] + _endpoint_details = result["VpcEndpoints"][0] # may have multiple entries in AWS - endpoint_details["DnsEntries"] = endpoint_details["DnsEntries"][:1] - endpoint_details.pop("SubnetIds", None) - endpoint_details.pop("NetworkInterfaceIds", None) - assert endpoint_details["State"] == "available" - snapshot.match("endpoint-details", endpoint_details) + _endpoint_details["DnsEntries"] = _endpoint_details["DnsEntries"][:1] + _endpoint_details.pop("SubnetIds", None) + _endpoint_details.pop("NetworkInterfaceIds", None) + assert _endpoint_details["State"] == "available" + snapshot.match("endpoint-details", _endpoint_details) + return _endpoint_details - retry(_check_available, retries=30, sleep=poll_sleep) + endpoint_details: VpcEndpoint = retry(_check_available, retries=30, sleep=poll_sleep) # update API with VPC endpoint patches = [ @@ -1012,21 +1016,15 @@ def _check_available(): aws_client.apigateway.update_rest_api(restApiId=api_id, patchOperations=patches) # create Lambda that invokes API via VPC endpoint (required as the endpoint is only accessible within the VPC) - subdomain = f"{api_id}-{endpoint_id}" - endpoint = api_invoke_url(subdomain, stage=DEFAULT_STAGE_NAME, path="/test") - host_header = urlparse(endpoint).netloc - - # create Lambda function that invokes the API GW (private VPC endpoint not accessible from outside of AWS) - if not is_aws_cloud(): - api_host = get_main_endpoint_from_container() - endpoint = endpoint.replace(host_header, f"{api_host}:{config.GATEWAY_LISTEN[0].port}") lambda_code = textwrap.dedent( - f""" + """ def handler(event, context): import requests - headers = {{"content-type": "application/json", "host": "{host_header}"}} - result = requests.post("{endpoint}", headers=headers) - return {{"content": result.content.decode("utf-8"), "code": result.status_code}} + url = event["url"] + headers = event["headers"] + + result = requests.post(url, headers=headers) + return {"content": result.content.decode("utf-8"), "code": result.status_code} """ ) func_name = f"test-{short_uid()}" @@ -1064,14 +1062,61 @@ def handler(event, context): aws_client.apigateway, restApiId=api_id, stageName=DEFAULT_STAGE_NAME ) - def _invoke_api(): - invoke_response = aws_client.lambda_.invoke(FunctionName=func_name, Payload="{}") + subdomain = f"{api_id}-{endpoint_id}" + endpoint = api_invoke_url(subdomain, stage=DEFAULT_STAGE_NAME, path="/test") + host_header = urlparse(endpoint).netloc + + # create Lambda function that invokes the API GW (private VPC endpoint not accessible from outside of AWS) + if not is_aws_cloud(): + api_host = get_main_endpoint_from_container() + endpoint = endpoint.replace(host_header, f"{api_host}:{config.GATEWAY_LISTEN[0].port}") + + def _invoke_api(url: str, headers: dict[str, str]): + invoke_response = aws_client.lambda_.invoke( + FunctionName=func_name, Payload=json.dumps({"url": url, "headers": headers}) + ) payload = json.load(invoke_response["Payload"]) items = json.loads(payload["content"])["Items"] assert len(items) == len(item_ids) # invoke Lambda and assert result - retry(_invoke_api, retries=15, sleep=poll_sleep) + # AWS + # url: https://{rest-api-id}-{vpce-id}.execute-api.{region}.amazonaws.com/{stage} + # host: {rest-api-id}.execute-api.{region}.amazonaws.com + # LocalStack + # url: http://localhost.localstack.cloud:4566/{stage} + # host: {rest-api-id}-{vpce-id}.execute-api.localhost.localstack.cloud + retry(lambda: _invoke_api(endpoint, {"host": host_header}), retries=15, sleep=poll_sleep) + + # invoke Lambda and assert result + # AWS + # url: https://{public-dns-hostname}.execute-api.{region}.vpce.amazonaws.com/{stage} + # x-apigw-api-id: {rest-api-id} + # LocalStack + # url: http://{public-dns-hostname}.execute-api.{region}.vpce.{localstack-host}/{stage} + # x-apigw-api-id: {rest-api-id} + protocol = "https" if is_aws_cloud() else "http" + vpc_endpoint_public_dns = endpoint_details["DnsEntries"][0]["DnsName"] + public_dns_url = f"{protocol}://{vpc_endpoint_public_dns}/{DEFAULT_STAGE_NAME}/test" + retry( + lambda: _invoke_api(public_dns_url, {"x-apigw-api-id": api_id}), + retries=15, + sleep=poll_sleep, + ) + + # invoke Lambda and assert result + # AWS + # url: https://{public-dns-hostname}.execute-api.{region}.vpce.amazonaws.com/{stage} + # host: {rest-api-id}.execute-api.{region}.amazonaws.com + # LocalStack + # url: http://{public-dns-hostname}.execute-api.{region}.vpce.{localstack_host}/{stage} + # host: {rest-api-id}.execute-api.{region}.{localstack-host} + host = api_invoke_url(api_id).partition("//")[-1].strip("/") + retry( + lambda: _invoke_api(public_dns_url, {"Host": host}), + retries=15, + sleep=poll_sleep, + ) @pytest.mark.skipif( @@ -1257,6 +1302,7 @@ def _invoke_api(): ) @pytest.mark.parametrize("integration", [IntegrationType.HTTP, IntegrationType.HTTP_PROXY]) @markers.aws.validated + @markers.requires_in_process # uses pytest httpserver def test_apigateway_header_remapping_http( self, snapshot, diff --git a/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json b/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json index 3b4a1be1aebdf..e92742b3d7768 100644 --- a/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json +++ b/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": { - "recorded-date": "15-04-2024, 23:07:07", + "recorded-date": "30-07-2025, 17:56:57", "recorded-content": { "endpoint-details": { "CreationTimestamp": "timestamp", @@ -35,6 +35,7 @@ "RequesterManaged": false, "RouteTableIds": [], "ServiceName": "com.amazonaws..execute-api", + "ServiceRegion": "", "State": "available", "Tags": [], "VpcEndpointId": "", @@ -46,6 +47,7 @@ "createdDate": "datetime", "disableExecuteApiEndpoint": false, "endpointConfiguration": { + "ipAddressType": "dualstack", "types": [ "PRIVATE" ], diff --git a/tests/aws/services/apigateway/test_apigateway_integrations.validation.json b/tests/aws/services/apigateway/test_apigateway_integrations.validation.json index 93c003bd54660..aa95dd450d738 100644 --- a/tests/aws/services/apigateway/test_apigateway_integrations.validation.json +++ b/tests/aws/services/apigateway/test_apigateway_integrations.validation.json @@ -12,7 +12,13 @@ "last_validated_date": "2024-12-11T15:28:54+00:00" }, "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": { - "last_validated_date": "2024-04-15T23:07:07+00:00" + "last_validated_date": "2025-07-30T17:57:02+00:00", + "durations_in_seconds": { + "setup": 12.89, + "call": 1064.8, + "teardown": 4.99, + "total": 1082.68 + } }, "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_path_param": { "last_validated_date": "2024-11-29T19:27:54+00:00" diff --git a/tests/aws/services/apigateway/test_apigateway_lambda.py b/tests/aws/services/apigateway/test_apigateway_lambda.py index 8aa53aaca9890..0cdd0defe1c6b 100644 --- a/tests/aws/services/apigateway/test_apigateway_lambda.py +++ b/tests/aws/services/apigateway/test_apigateway_lambda.py @@ -59,7 +59,14 @@ def handler(event, context, *args): @markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=CLOUDFRONT_SKIP_HEADERS) +@markers.snapshot.skip_snapshot_verify( + paths=CLOUDFRONT_SKIP_HEADERS + + [ + # The value of Accept-Encoding can change when the zstandard package is present + "$..headers.Accept-Encoding", + "$..multiValueHeaders.Accept-Encoding", + ] +) @markers.snapshot.skip_snapshot_verify( condition=lambda: not is_next_gen_api(), paths=[ @@ -1244,7 +1251,14 @@ def _invoke_url(url): @markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=CLOUDFRONT_SKIP_HEADERS) +@markers.snapshot.skip_snapshot_verify( + paths=CLOUDFRONT_SKIP_HEADERS + + [ + # The value of Accept-Encoding can change when the zstandard package is present + "$..content.headers.Accept-Encoding", + "$..content.multiValueHeaders.Accept-Encoding", + ] +) @markers.snapshot.skip_snapshot_verify( condition=lambda: not is_next_gen_api(), paths=[ diff --git a/tests/aws/services/apigateway/test_apigateway_stepfunctions.py b/tests/aws/services/apigateway/test_apigateway_stepfunctions.py index 985219697aaf8..cfdcf0a072ed5 100644 --- a/tests/aws/services/apigateway/test_apigateway_stepfunctions.py +++ b/tests/aws/services/apigateway/test_apigateway_stepfunctions.py @@ -132,13 +132,12 @@ def _prepare_integration(request_template=None, response_template=None): url = api_invoke_url(api_id=rest_api, stage="dev", path="/") req_template = { - "application/json": """ - { + "application/json": f""" + {{ "input": "$util.escapeJavaScript($input.json('$'))", - "stateMachineArn": "%s" - } + "stateMachineArn": "{sm_arn}" + }} """ - % sm_arn } match action: case "StartExecution": diff --git a/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py b/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py index 9bf6a05ff96ea..dd5bddcf5b814 100644 --- a/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py +++ b/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py @@ -1,6 +1,7 @@ import json import logging -from typing import Callable, ParamSpec, TypeVar +from collections.abc import Callable +from typing import ParamSpec, TypeVar import jsonpatch import pytest diff --git a/tests/aws/services/cloudformation/api/test_changesets.py b/tests/aws/services/cloudformation/api/test_changesets.py index 1f397310f5d21..086efdfbf8bff 100644 --- a/tests/aws/services/cloudformation/api/test_changesets.py +++ b/tests/aws/services/cloudformation/api/test_changesets.py @@ -1,12 +1,22 @@ import copy import json import os.path +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any import pytest -from botocore.exceptions import ClientError +from botocore.config import Config +from botocore.exceptions import ClientError, WaiterError +from tests.aws.services.cloudformation.api.test_stacks import ( + MINIMAL_TEMPLATE, +) +from tests.aws.services.cloudformation.conftest import ( + skip_if_legacy_engine, + skipped_v2_items, +) from localstack.aws.connect import ServiceLevelClientFactory -from localstack.services.cloudformation.v2.utils import is_v2_engine from localstack.testing.aws.cloudformation_utils import ( load_template_file, load_template_raw, @@ -14,11 +24,9 @@ ) from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers +from localstack.testing.pytest.fixtures import DeployResult from localstack.utils.strings import short_uid from localstack.utils.sync import ShortCircuitWaitException, poll_condition, wait_until -from tests.aws.services.cloudformation.api.test_stacks import ( - MINIMAL_TEMPLATE, -) class TestUpdates: @@ -62,9 +70,6 @@ def test_simple_update_single_resource( res.destroy() - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Not working in v2 yet" - ) @markers.aws.validated def test_simple_update_two_resources( self, aws_client: ServiceLevelClientFactory, deploy_cfn_template @@ -111,9 +116,6 @@ def test_simple_update_two_resources( # TODO: the error response is incorrect, however the test is otherwise validated and raises # an error because the SSM parameter has been deleted (removed from the stack). @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Test fails with the old engine" - ) def test_deleting_resource( self, aws_client: ServiceLevelClientFactory, deploy_cfn_template, snapshot ): @@ -285,11 +287,11 @@ def test_create_change_set_update_without_parameters( cleanup_stacks(stacks=[stack_id]) -# def test_create_change_set_with_template_url(): -# pass - - -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="change set type not implemented") +# TODO: Key error during deletion +# File "/Users/simon/work/localstack/localstack/localstack-core/localstack/services/cloudformation/v2/provider.py", line 162, in find_change_set_v2 +# return state.change_sets[change_set_name] +# ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ +# KeyError: 'arn:aws:cloudformation:us-east-1:000000000000:changeSet/change-set-926829fe/d065e78c' @markers.aws.validated def test_create_change_set_create_existing(cleanup_changesets, cleanup_stacks, aws_client): """tries to create an already existing stack""" @@ -339,7 +341,7 @@ def test_create_change_set_update_nonexisting(aws_client): os.path.dirname(__file__), "../../../templates/sns_topic_simple.yaml" ) - with pytest.raises(Exception) as ex: + with pytest.raises(ClientError) as ex: response = aws_client.cloudformation.create_change_set( StackName=stack_name, ChangeSetName=change_set_name, @@ -374,14 +376,17 @@ def test_create_change_set_invalid_params(aws_client): @markers.aws.validated -def test_create_change_set_missing_stackname(aws_client): +def test_create_change_set_missing_stackname(aws_client_factory): """in this case boto doesn't even let us send the request""" change_set_name = f"change-set-{short_uid()}" template_path = os.path.join( os.path.dirname(__file__), "../../../templates/sns_topic_simple.yaml" ) - with pytest.raises(Exception): - aws_client.cloudformation.create_change_set( + + # A client with parameter validation enabled would result in a client-side ParamValidationError. + cfn_client = aws_client_factory(config=Config(parameter_validation=False)).cloudformation + with pytest.raises(ClientError): + cfn_client.create_change_set( StackName="", ChangeSetName=change_set_name, TemplateBody=load_template_raw(template_path), @@ -481,6 +486,46 @@ def test_describe_change_set_nonexisting(snapshot, aws_client): snapshot.match("exception", ex.value) +@skip_if_legacy_engine() +@markers.aws.validated +def test_create_change_set_no_changes( + snapshot, + aws_client: ServiceLevelClientFactory, + deploy_cfn_template: Callable[..., DeployResult], +): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/simple_no_change.yaml" + ) + with open(template_path) as infile: + template_body = infile.read() + + stack = deploy_cfn_template( + template_path=template_path, + ) + + change_set_name = f"cs-{short_uid()}" + change_set_result = aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack.stack_id, + ChangeSetType="UPDATE", + TemplateBody=template_body, + ) + change_set_id = change_set_result["Id"] + with pytest.raises(WaiterError): + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_id, + ) + + snapshot.match( + "change-set-description", + aws_client.cloudformation.describe_change_set( + ChangeSetName=change_set_id, + ), + ) + + @pytest.mark.skipif( condition=not is_aws_cloud(), reason="fails because of the properties mutation in the result_handler", @@ -636,78 +681,49 @@ def test_create_and_then_remove_non_supported_resource_change_set(deploy_cfn_tem ) +@skip_if_legacy_engine() @markers.aws.validated def test_create_and_then_update_refreshes_template_metadata( aws_client, - cleanup_changesets, - cleanup_stacks, - is_change_set_finished, - is_change_set_created_and_available, + deploy_cfn_template, + snapshot, ): - stacks_to_cleanup = set() - changesets_to_cleanup = set() - - try: - stack_name = f"stack-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../templates/sns_topic_simple.yaml" - ) + stack_name = f"stack-{short_uid()}" + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/sns_topic_simple.yaml" + ) + with open(template_path) as infile: + template_body = infile.read() - template_body = load_template_raw(template_path) + topic_name = f"topic-{short_uid()}" - create_response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=f"change-set-{short_uid()}", - TemplateBody=template_body, - ChangeSetType="CREATE", - ) + deploy_cfn_template( + template=template_body, + stack_name=stack_name, + parameters={"TopicName": topic_name}, + ) - stacks_to_cleanup.add(create_response["StackId"]) - changesets_to_cleanup.add(create_response["Id"]) + # Note the metadata alone won't change if there are no changes to resources + # TODO: find a better way to make a replacement in yaml template + template_body = template_body.replace( + "TopicName: sns-topic-simple", + "TopicName: sns-topic-simple-updated", + ) + update_response = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=f"change-set-{short_uid()}", + TemplateBody=template_body, + ChangeSetType="UPDATE", + Parameters=[{"ParameterKey": "TopicName", "ParameterValue": topic_name}], + ) + with pytest.raises(WaiterError) as e: aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=create_response["Id"] - ) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=create_response["Id"] - ) - - wait_until(is_change_set_finished(create_response["Id"])) - - # Note the metadata alone won't change if there are no changes to resources - # TODO: find a better way to make a replacement in yaml template - template_body = template_body.replace( - "TopicName: sns-topic-simple", - "TopicName: sns-topic-simple-updated", - ) - - update_response = aws_client.cloudformation.create_change_set( StackName=stack_name, - ChangeSetName=f"change-set-{short_uid()}", - TemplateBody=template_body, - ChangeSetType="UPDATE", - ) - - stacks_to_cleanup.add(update_response["StackId"]) - changesets_to_cleanup.add(update_response["Id"]) - - wait_until(is_change_set_created_and_available(update_response["Id"])) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=update_response["Id"] + ChangeSetName=update_response["Id"], ) - wait_until(is_change_set_finished(update_response["Id"])) - - summary = aws_client.cloudformation.get_template_summary(StackName=stack_name) - - assert "TopicName" in summary["Metadata"] - assert "sns-topic-simple-updated" in summary["Metadata"] - finally: - cleanup_stacks(list(stacks_to_cleanup)) - cleanup_changesets(list(changesets_to_cleanup)) + snapshot.match("waiter-error", e.value) # TODO: the intention of this test is not particularly clear. The resource isn't removed, it'll just generate a new bucket with a new default name @@ -763,6 +779,10 @@ def assert_bucket_gone(): "$..IncludeNestedStacks", "$..Parameters", ] + + skipped_v2_items( + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + ) ) @markers.aws.validated def test_empty_changeset(snapshot, cleanups, aws_client): @@ -967,6 +987,10 @@ def test_create_while_in_review(aws_client, snapshot, cleanups): @markers.snapshot.skip_snapshot_verify( paths=["$..Capabilities", "$..IncludeNestedStacks", "$..NotificationARNs", "$..Parameters"] + + skipped_v2_items( + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + ) ) @markers.aws.validated def test_multiple_create_changeset(aws_client, snapshot, cleanups): @@ -1003,7 +1027,13 @@ def test_multiple_create_changeset(aws_client, snapshot, cleanups): ) -@markers.snapshot.skip_snapshot_verify(paths=["$..LastUpdatedTime", "$..StackStatusReason"]) +@markers.snapshot.skip_snapshot_verify( + paths=["$..LastUpdatedTime", "$..StackStatusReason"] + + skipped_v2_items( + # TODO + "$..Capabilities", + ) +) @markers.aws.validated def test_create_changeset_with_stack_id(aws_client, snapshot, cleanups): """ @@ -1085,6 +1115,10 @@ def test_create_changeset_with_stack_id(aws_client, snapshot, cleanups): "$..StatusReason", "$..StackStatusReason", ] + + skipped_v2_items( + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + ), ) @markers.aws.validated def test_name_conflicts(aws_client, snapshot, cleanups): @@ -1212,3 +1246,281 @@ def test_describe_change_set_with_similarly_named_stacks(deploy_cfn_template, aw )["ChangeSetId"] == response["Id"] ) + + +@dataclass +class NoValueScenario: + name: str + template: dict[str, Any] + should_fail: bool + + +cases = [ + NoValueScenario( + name="parameter", + template={ + "Parameters": { + "AWS::NoValue": { + "Type": "String", + "Default": "foo", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Ref": "AWS::NoValue", + }, + }, + }, + }, + }, + should_fail=True, + ), + NoValueScenario( + name="resource", + template={ + "Resources": { + "AWS::NoValue": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": short_uid(), + }, + }, + }, + }, + should_fail=True, + ), + NoValueScenario( + name="condition", + template={ + "Conditions": { + "AWS::NoValue": { + "Fn::Equals": [ + "a", + "a", + ], + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Condition": "AWS::NoValue", + "Properties": { + "Type": "String", + "Value": short_uid(), + }, + }, + }, + }, + should_fail=False, + ), +] + + +@skip_if_legacy_engine() +@markers.aws.validated +@pytest.mark.parametrize( + "case", + cases, + ids=[case.name for case in cases], +) +def test_using_pseudoparameters_in_places(aws_client, snapshot, cleanups, case): + """ + Test that AWS pseudoparameters (particularly AWS::NoValue) can + only be used in value positions of the template + """ + + stack_name = f"stack-{short_uid()}" + change_set_name = f"change-set-{short_uid()}" + + cleanups.append( + lambda: aws_client.cloudformation.delete_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + ) + ) + + if case.should_fail: + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + TemplateBody=json.dumps(case.template), + ChangeSetType="CREATE", + ) + + snapshot.match("error", exc_info.value.response) + + else: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + TemplateBody=json.dumps(case.template), + ChangeSetType="CREATE", + ) + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_name, + StackName=stack_name, + ) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.PolicyAction", + "$..Changes..ResourceChange.Scope", + ] +) +@skip_if_legacy_engine() +def test_describe_changeset_after_delete(aws_client, cleanups, snapshot): + """ + Test the behaviour of deleting a change set after it has been executed + """ + stack_name = f"stack-{short_uid()}" + cs_name_1 = f"cs-{short_uid()}" + cs_name_2 = f"cs-{short_uid()}" + topic_name_1 = f"topic-{short_uid()}" + topic_name_2 = f"topic-{short_uid()}" + + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + snapshot.add_transformers_list( + [ + snapshot.transform.regex(cs_name_1, ""), + snapshot.transform.regex(cs_name_2, ""), + snapshot.transform.regex(topic_name_1, ""), + snapshot.transform.regex(topic_name_2, ""), + ] + ) + + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) + + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/sns_topic_simple.yaml" + ) + with open(template_path) as infile: + template_body = infile.read() + + aws_client.cloudformation.create_change_set( + StackName=stack_name, + TemplateBody=template_body, + ChangeSetName=cs_name_1, + ChangeSetType="CREATE", + Parameters=[{"ParameterKey": "TopicName", "ParameterValue": topic_name_1}], + ) + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + StackName=stack_name, ChangeSetName=cs_name_1 + ) + + aws_client.cloudformation.execute_change_set(StackName=stack_name, ChangeSetName=cs_name_1) + aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) + + aws_client.cloudformation.delete_change_set(StackName=stack_name, ChangeSetName=cs_name_1) + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.describe_change_set(StackName=stack_name, ChangeSetName=cs_name_1) + + snapshot.match("describe-deleted-cs", exc_info.value.response) + + aws_client.cloudformation.create_change_set( + StackName=stack_name, + TemplateBody=template_body, + ChangeSetName=cs_name_2, + ChangeSetType="UPDATE", + Parameters=[{"ParameterKey": "TopicName", "ParameterValue": topic_name_2}], + ) + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + StackName=stack_name, ChangeSetName=cs_name_2 + ) + + describe_res = aws_client.cloudformation.describe_change_set( + StackName=stack_name, ChangeSetName=cs_name_2 + ) + + snapshot.match("describe-2", describe_res) + + +@markers.aws.validated +@skip_if_legacy_engine() +def test_update_change_set_with_aws_novalue_repro(aws_client, cleanups): + """ + Fix a bug with trying to access falsy conditions when updating + """ + stack_name = f"stack-{short_uid()}" + create_cs_name = f"cs-create-{short_uid()}" + update_cs_name = f"cs-update-{short_uid()}" + template_path = os.path.join(os.path.dirname(__file__), "../../../templates/aws_novalue.yml") + template_body = load_template_raw(template_path) + fallback_bucket = f"my-bucket-{short_uid()}" + + create_resp = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=create_cs_name, + TemplateBody=template_body, + ChangeSetType="CREATE", + Parameters=[ + {"ParameterKey": "SetBucketName", "ParameterValue": "no"}, + {"ParameterKey": "FallbackBucketName", "ParameterValue": fallback_bucket}, + ], + ) + create_cs_id = create_resp["Id"] + stack_id = create_resp["StackId"] + + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=create_cs_id + ) + aws_client.cloudformation.execute_change_set(ChangeSetName=create_cs_id) + aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) + + aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=update_cs_name, + TemplateBody=template_body, + ChangeSetType="UPDATE", + Parameters=[ + {"ParameterKey": "SetBucketName", "ParameterValue": "no"}, + {"ParameterKey": "FallbackBucketName", "ParameterValue": fallback_bucket}, + ], + ) + + +@markers.aws.validated +@skip_if_legacy_engine +def test_changeset_for_deleted_stack(aws_client, deploy_cfn_template, snapshot): + parameter_resource_body = { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": "Test"}, + } + template = json.dumps( + {"Resources": {f"Parameter{i}": parameter_resource_body for i in range(5)}} + ) + + stack = deploy_cfn_template(template=template) + aws_client.cloudformation.delete_stack(StackName=stack.stack_id) + + with pytest.raises(ClientError) as in_progress_ex: + aws_client.cloudformation.create_change_set( + StackName=stack.stack_id, + ChangeSetName="test", + TemplateBody=template, + ChangeSetType="UPDATE", + ) + + aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack.stack_id) + + with pytest.raises(ClientError) as complete_ex: + aws_client.cloudformation.create_change_set( + StackName=stack.stack_id, + ChangeSetName="test", + TemplateBody=template, + ChangeSetType="UPDATE", + ) + + snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) + snapshot.match("ErrorForInProgress", in_progress_ex.value.response) + snapshot.match("ErrorForComplete", complete_ex.value.response) diff --git a/tests/aws/services/cloudformation/api/test_changesets.snapshot.json b/tests/aws/services/cloudformation/api/test_changesets.snapshot.json index b3b80db8dd4fa..4affa37115e01 100644 --- a/tests/aws/services/cloudformation/api/test_changesets.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_changesets.snapshot.json @@ -172,7 +172,7 @@ } }, "tests/aws/services/cloudformation/api/test_changesets.py::test_delete_change_set_exception": { - "recorded-date": "12-03-2025, 10:14:25", + "recorded-date": "21-07-2025, 18:04:27", "recorded-content": { "e1": { "Error": { @@ -498,5 +498,216 @@ } } } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_deleting_resource": { + "recorded-date": "02-06-2025, 10:29:41", + "recorded-content": { + "get-parameter-error": { + "Error": { + "Code": "ParameterNotFound", + "Message": "" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_update_refreshes_template_metadata": { + "recorded-date": "20-08-2025, 22:16:11", + "recorded-content": { + "waiter-error": "Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression \"Status\" we matched expected path: \"FAILED\"" + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_no_changes": { + "recorded-date": "09-09-2025, 22:08:24", + "recorded-content": { + "change-set-description": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "UNAVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "FAILED", + "StatusReason": "The submitted information didn't contain changes. Submit different information to create a change set.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_changeset_after_delete": { + "recorded-date": "11-09-2025, 11:30:50", + "recorded-content": { + "describe-deleted-cs": { + "Error": { + "Code": "ChangeSetNotFound", + "Message": "ChangeSet [] does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "describe-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "CausingEntity": "TopicName", + "ChangeSource": "ParameterReference", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "TopicName", + "RequiresRecreation": "Always" + } + }, + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "Attribute": "Properties", + "Name": "TopicName", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "topic123", + "PhysicalResourceId": "arn::sns::111111111111:", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SNS::Topic", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "TopicName", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[resource]": { + "recorded-date": "11-09-2025, 22:15:52", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template format error: Resource name AWS::NoValue is non alphanumeric.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[condition]": { + "recorded-date": "11-09-2025, 22:15:55", + "recorded-content": {} + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[parameter]": { + "recorded-date": "11-09-2025, 22:15:51", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template format error: Parameter name AWS::NoValue is non alphanumeric.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_changeset_for_deleted_stack": { + "recorded-date": "10-10-2025, 18:02:05", + "recorded-content": { + "ErrorForCreate": { + "Error": { + "Code": "ValidationError", + "Message": "Stack [] already exists and cannot be created again with the changeSet [test].", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "ErrorForUpdate": { + "Error": { + "Code": "ValidationError", + "Message": "Stack: is in DELETE_COMPLETE state and can not be updated.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_changeset_for_deleted_stack": { + "recorded-date": "10-10-2025, 18:49:37", + "recorded-content": { + "ErrorForInProgress": { + "Error": { + "Code": "ValidationError", + "Message": "Stack: is in DELETE_IN_PROGRESS state and can not be updated.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "ErrorForComplete": { + "Error": { + "Code": "ValidationError", + "Message": "Stack: is in DELETE_COMPLETE state and can not be updated.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/cloudformation/api/test_changesets.validation.json b/tests/aws/services/cloudformation/api/test_changesets.validation.json index 3c3b7ffa3c6c3..a20a8cb87d8d9 100644 --- a/tests/aws/services/cloudformation/api/test_changesets.validation.json +++ b/tests/aws/services/cloudformation/api/test_changesets.validation.json @@ -47,9 +47,54 @@ "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": { "last_validated_date": "2025-04-02T10:05:26+00:00" }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_changeset_for_deleted_stack": { + "last_validated_date": "2025-10-10T19:59:52+00:00", + "durations_in_seconds": { + "setup": 0.25, + "call": 15.15, + "teardown": 0.12, + "total": 15.52 + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_update_refreshes_template_metadata": { + "last_validated_date": "2025-08-20T22:17:01+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 38.04, + "teardown": 50.06, + "total": 89.06 + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_missing_stackname": { + "last_validated_date": "2025-08-22T12:49:42+00:00", + "durations_in_seconds": { + "setup": 0.6, + "call": 0.51, + "teardown": 0.0, + "total": 1.11 + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_no_changes": { + "last_validated_date": "2025-09-09T22:08:24+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 8.73, + "teardown": 49.98, + "total": 59.67 + } + }, "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_without_parameters": { "last_validated_date": "2022-05-31T07:32:02+00:00" }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_changeset_for_deleted_stack": { + "last_validated_date": "2025-10-10T18:02:05+00:00", + "durations_in_seconds": { + "setup": 0.27, + "call": 16.63, + "teardown": 0.13, + "total": 17.03 + } + }, "tests/aws/services/cloudformation/api/test_changesets.py::test_create_changeset_with_stack_id": { "last_validated_date": "2023-11-28T06:48:23+00:00" }, @@ -60,7 +105,13 @@ "last_validated_date": "2023-11-22T07:49:15+00:00" }, "tests/aws/services/cloudformation/api/test_changesets.py::test_delete_change_set_exception": { - "last_validated_date": "2025-03-12T10:14:25+00:00" + "last_validated_date": "2025-07-21T18:04:27+00:00", + "durations_in_seconds": { + "setup": 0.31, + "call": 0.4, + "teardown": 0.0, + "total": 0.71 + } }, "tests/aws/services/cloudformation/api/test_changesets.py::test_deleted_changeset": { "last_validated_date": "2022-08-11T09:11:47+00:00" @@ -71,6 +122,15 @@ "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": { "last_validated_date": "2024-03-06T13:56:47+00:00" }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_changeset_after_delete": { + "last_validated_date": "2025-09-11T11:30:50+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 12.09, + "teardown": 0.17, + "total": 13.17 + } + }, "tests/aws/services/cloudformation/api/test_changesets.py::test_empty_changeset": { "last_validated_date": "2022-08-10T08:52:55+00:00" }, @@ -79,5 +139,41 @@ }, "tests/aws/services/cloudformation/api/test_changesets.py::test_name_conflicts": { "last_validated_date": "2023-11-22T09:58:04+00:00" + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_update_change_set_with_aws_novalue_repro": { + "last_validated_date": "2025-09-11T15:25:28+00:00", + "durations_in_seconds": { + "setup": 0.98, + "call": 21.48, + "teardown": 0.19, + "total": 22.65 + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[condition]": { + "last_validated_date": "2025-09-11T22:15:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.64, + "teardown": 0.09, + "total": 3.73 + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[parameter]": { + "last_validated_date": "2025-09-11T22:15:51+00:00", + "durations_in_seconds": { + "setup": 1.03, + "call": 0.32, + "teardown": 0.13, + "total": 1.48 + } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::test_using_pseudoparameters_in_places[resource]": { + "last_validated_date": "2025-09-11T22:15:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.16, + "teardown": 0.07, + "total": 0.23 + } } } diff --git a/tests/aws/services/cloudformation/api/test_nested_stacks.snapshot.json b/tests/aws/services/cloudformation/api/test_nested_stacks.snapshot.json index fbd1c318a283b..9abb0f348b6bc 100644 --- a/tests/aws/services/cloudformation/api/test_nested_stacks.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_nested_stacks.snapshot.json @@ -65,7 +65,7 @@ } }, "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": { - "recorded-date": "17-09-2024, 20:09:36", + "recorded-date": "20-08-2025, 14:06:53", "recorded-content": { "error": { "Error": { diff --git a/tests/aws/services/cloudformation/api/test_nested_stacks.validation.json b/tests/aws/services/cloudformation/api/test_nested_stacks.validation.json index f5936f2e379e7..cbf04b157d6dc 100644 --- a/tests/aws/services/cloudformation/api/test_nested_stacks.validation.json +++ b/tests/aws/services/cloudformation/api/test_nested_stacks.validation.json @@ -1,6 +1,12 @@ { "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": { - "last_validated_date": "2024-09-17T20:09:36+00:00" + "last_validated_date": "2025-08-20T14:06:54+00:00", + "durations_in_seconds": { + "setup": 1.2, + "call": 82.49, + "teardown": 1.32, + "total": 85.01 + } }, "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_output_in_params": { "last_validated_date": "2023-02-07T09:57:47+00:00" diff --git a/tests/aws/services/cloudformation/api/test_reference_resolving.py b/tests/aws/services/cloudformation/api/test_reference_resolving.py index a5a8fbf10b129..efcb8deb8f375 100644 --- a/tests/aws/services/cloudformation/api/test_reference_resolving.py +++ b/tests/aws/services/cloudformation/api/test_reference_resolving.py @@ -1,9 +1,11 @@ import os import pytest +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.services.cloudformation.engine.template_deployer import MOCK_REFERENCE from localstack.testing.pytest import markers +from localstack.utils import testutil from localstack.utils.strings import short_uid @@ -103,3 +105,110 @@ def test_reference_unsupported_resource(deploy_cfn_template, aws_client): value_of_unsupported = deployment.outputs["parameter"] assert ref_of_unsupported == MOCK_REFERENCE assert value_of_unsupported == f"The value of the attribute is: {MOCK_REFERENCE}" + + +@markers.aws.validated +@skip_if_legacy_engine() +def test_redeploy_cdk_with_reference( + aws_client, account_id, create_lambda_function, deploy_cfn_template, snapshot, cleanups +): + """ + Test a user scenario with a lambda function that fails to redeploy + + """ + # perform cdk bootstrap + template_file = os.path.join( + os.path.dirname(__file__), "../../../templates/cdk_bootstrap_v28.yaml" + ) + qualifier = short_uid() + bootstrap_stack = deploy_cfn_template( + template_path=template_file, + parameters={ + "CloudFormationExecutionPolicies": "", + "FileAssetsBucketKmsKeyId": "AWS_MANAGED_KEY", + "PublicAccessBlockConfiguration": "true", + "TrustedAccounts": "", + "TrustedAccountsForLookup": "", + "Qualifier": qualifier, + }, + ) + + lambda_bucket = bootstrap_stack.outputs["BucketName"] + + # upload the lambda function + lambda_src_1 = """ + def handler(event, context): + return {"status": "ok"} + """ + lambda_src_2 = """ + def handler(event, context): + return {"status": "foo"} + """ + + function_name = f"function-{short_uid()}" + cleanups.append(lambda: aws_client.lambda_.delete_function(FunctionName=function_name)) + + def deploy_or_update_lambda(content: str, lambda_key: str): + archive = testutil.create_lambda_archive(content) + with open(archive, "rb") as infile: + aws_client.s3.put_object(Bucket=lambda_bucket, Key=lambda_key, Body=infile) + + lambda_exists = False + try: + aws_client.lambda_.get_function(FunctionName=function_name) + lambda_exists = True + except Exception: + # TODO: work out the proper exception + pass + + if lambda_exists: + aws_client.lambda_.update_function_code( + FunctionName=function_name, + S3Bucket=lambda_bucket, + S3Key=lambda_key, + ) + else: + aws_client.lambda_.create_function( + FunctionName=function_name, + Runtime="python3.12", + Handler="handler", + Code={ + "S3Bucket": lambda_bucket, + "S3Key": lambda_key, + }, + # The role does not matter + Role=f"arn:aws:iam::{account_id}:role/LambdaExecutionRole", + ) + aws_client.lambda_.get_waiter("function_active_v2").wait(FunctionName=function_name) + + lambda_key_1 = f"{short_uid()}.zip" + deploy_or_update_lambda(lambda_src_1, lambda_key_1) + + # deploy the template the first time + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/cdk-lambda-redeploy.json" + ), + parameters={ + "DeployBucket": lambda_bucket, + "DeployKey": lambda_key_1, + "BootstrapVersion": f"/cdk-bootstrap/{qualifier}/version", + }, + ) + + lambda_key_2 = f"{short_uid()}.zip" + deploy_or_update_lambda(lambda_src_2, lambda_key_2) + + # deploy the template the second time + deploy_cfn_template( + stack_name=stack.stack_id, + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/cdk-lambda-redeploy.json" + ), + is_update=True, + parameters={ + "DeployBucket": lambda_bucket, + "DeployKey": lambda_key_2, + "BootstrapVersion": "28", + }, + ) diff --git a/tests/aws/services/cloudformation/api/test_reference_resolving.snapshot.json b/tests/aws/services/cloudformation/api/test_reference_resolving.snapshot.json index 2aebe631514be..488fba1a50d6c 100644 --- a/tests/aws/services/cloudformation/api/test_reference_resolving.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_reference_resolving.snapshot.json @@ -32,5 +32,9 @@ "MyTopicSubWithMap": "|arn::sns::111111111111:|something" } } + }, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_redeploy_cdk_with_reference": { + "recorded-date": "24-09-2025, 19:40:59", + "recorded-content": {} } } diff --git a/tests/aws/services/cloudformation/api/test_reference_resolving.validation.json b/tests/aws/services/cloudformation/api/test_reference_resolving.validation.json index 5422a01c739f0..ec17cf50bd2bd 100644 --- a/tests/aws/services/cloudformation/api/test_reference_resolving.validation.json +++ b/tests/aws/services/cloudformation/api/test_reference_resolving.validation.json @@ -5,6 +5,15 @@ "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": { "last_validated_date": "2023-05-11T11:43:51+00:00" }, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_redeploy_cdk_with_reference": { + "last_validated_date": "2025-09-24T19:41:46+00:00", + "durations_in_seconds": { + "setup": 12.48, + "call": 102.32, + "teardown": 47.28, + "total": 162.08 + } + }, "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_sub_resolving": { "last_validated_date": "2023-05-12T05:51:06+00:00" } diff --git a/tests/aws/services/cloudformation/api/test_resources.py b/tests/aws/services/cloudformation/api/test_resources.py new file mode 100644 index 0000000000000..2d26ae9aa42e0 --- /dev/null +++ b/tests/aws/services/cloudformation/api/test_resources.py @@ -0,0 +1,118 @@ +import json +import os + +import pytest +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.testing.pytest import markers +from localstack.testing.pytest.fixtures import StackDeployError +from localstack.utils.strings import short_uid + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_describe_non_existent_stack(aws_client, deploy_cfn_template, snapshot): + with pytest.raises(ClientError) as err: + aws_client.cloudformation.describe_stack_resource( + StackName="not-a-valid-stack", LogicalResourceId="not-a-valid-resource" + ) + + snapshot.match("error", err.value) + + +@markers.aws.validated +def test_describe_non_existent_resource(aws_client, deploy_cfn_template, snapshot): + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/ssm_parameter_defaultname.yaml" + ) + stack = deploy_cfn_template(template_path=template_path, parameters={"Input": "myvalue"}) + snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) + + with pytest.raises(ClientError) as err: + aws_client.cloudformation.describe_stack_resource( + StackName=stack.stack_id, LogicalResourceId="not-a-valid-resource" + ) + + snapshot.match("error", err.value) + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_invalid_logical_resource_id(deploy_cfn_template, snapshot): + template = { + "Resources": { + "my-bad-resource-id": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "Foo", + }, + } + } + } + with pytest.raises(ClientError) as err: + deploy_cfn_template(template=json.dumps(template)) + + snapshot.match("error", err.value) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify(paths=["$..StackResourceDetail.Metadata"]) +@skip_if_legacy_engine +def test_describe_deleted_resource_on_update(aws_client, snapshot, deploy_cfn_template): + template = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": "Test"}, + } + } + } + + stack = deploy_cfn_template(template=json.dumps(template)) + + # Update the template to remove the previous resource and create a new one + template["Resources"]["Parameter2"] = template["Resources"].pop("Parameter") + deploy_cfn_template(template=json.dumps(template), is_update=True, stack_name=stack.stack_name) + + with pytest.raises(ClientError) as err: + aws_client.cloudformation.describe_stack_resource( + StackName=stack.stack_name, LogicalResourceId="Parameter" + ) + + parameter2 = aws_client.cloudformation.describe_stack_resource( + StackName=stack.stack_name, LogicalResourceId="Parameter2" + ) + + snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) + snapshot.add_transformer(snapshot.transform.key_value("StackId")) + snapshot.add_transformer(snapshot.transform.key_value("StackName")) + + snapshot.match("error", err.value.response) + snapshot.match("parameter", parameter2) + + +@markers.aws.validated +@skip_if_legacy_engine +def test_describe_failed_resource(aws_client, snapshot, deploy_cfn_template): + template = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "Invalid", "Value": "Test"}, + } + } + } + + stack_name = f"test-stack-{short_uid()}" + with pytest.raises(StackDeployError): + deploy_cfn_template(template=json.dumps(template), stack_name=stack_name) + + with pytest.raises(ClientError) as err: + aws_client.cloudformation.describe_stack_resource( + StackName=stack_name, LogicalResourceId="Parameter" + ) + + snapshot.add_transformer(snapshot.transform.regex(stack_name, "")) + snapshot.match("error", err.value.response) diff --git a/tests/aws/services/cloudformation/api/test_resources.snapshot.json b/tests/aws/services/cloudformation/api/test_resources.snapshot.json new file mode 100644 index 0000000000000..353882f467bb9 --- /dev/null +++ b/tests/aws/services/cloudformation/api/test_resources.snapshot.json @@ -0,0 +1,71 @@ +{ + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_resource": { + "recorded-date": "25-07-2025, 22:01:35", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the DescribeStackResource operation: Resource not-a-valid-resource does not exist for stack " + } + }, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_stack": { + "recorded-date": "25-07-2025, 22:02:38", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the DescribeStackResource operation: Stack 'not-a-valid-stack' does not exist" + } + }, + "tests/aws/services/cloudformation/api/test_resources.py::test_invalid_logical_resource_id": { + "recorded-date": "25-07-2025, 22:21:31", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template format error: Resource name my-bad-resource-id is non alphanumeric." + } + }, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_deleted_resource_on_update": { + "recorded-date": "16-10-2025, 15:06:04", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Resource Parameter does not exist for stack ", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "parameter": { + "StackResourceDetail": { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTimestamp": "timestamp", + "LogicalResourceId": "Parameter2", + "Metadata": {}, + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "", + "StackName": "" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_failed_resource": { + "recorded-date": "16-10-2025, 15:02:53", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Resource Parameter does not exist for stack ", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/cloudformation/api/test_resources.validation.json b/tests/aws/services/cloudformation/api/test_resources.validation.json new file mode 100644 index 0000000000000..2f011bfb176ef --- /dev/null +++ b/tests/aws/services/cloudformation/api/test_resources.validation.json @@ -0,0 +1,47 @@ +{ + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_deleted_resource_on_update": { + "last_validated_date": "2025-10-16T15:08:41+00:00", + "durations_in_seconds": { + "setup": 0.28, + "call": 22.95, + "teardown": 4.6, + "total": 27.83 + } + }, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_failed_resource": { + "last_validated_date": "2025-10-16T15:02:53+00:00", + "durations_in_seconds": { + "setup": 0.29, + "call": 8.61, + "teardown": 0.0, + "total": 8.9 + } + }, + "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py::test_describe_non_existent_resource": { + "last_validated_date": "2025-07-25T22:01:40+00:00", + "durations_in_seconds": { + "setup": 1.11, + "call": 10.33, + "teardown": 4.37, + "total": 15.81 + } + }, + "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py::test_describe_non_existent_stack": { + "last_validated_date": "2025-07-25T22:02:38+00:00", + "durations_in_seconds": { + "setup": 1.04, + "call": 0.2, + "teardown": 0.0, + "total": 1.24 + } + }, + "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py::test_invalid_logical_resource_id": { + "last_validated_date": "2025-07-25T22:21:31+00:00", + "durations_in_seconds": { + "setup": 1.31, + "call": 0.35, + "teardown": 0.0, + "total": 1.66 + } + } +} diff --git a/tests/aws/services/cloudformation/api/test_stacks.py b/tests/aws/services/cloudformation/api/test_stacks.py index cfcf8adf8b881..bb39d16db8414 100644 --- a/tests/aws/services/cloudformation/api/test_stacks.py +++ b/tests/aws/services/cloudformation/api/test_stacks.py @@ -1,3 +1,4 @@ +import copy import json import os from collections import OrderedDict @@ -6,10 +7,15 @@ import botocore.exceptions import pytest import yaml -from botocore.exceptions import WaiterError +from botocore.exceptions import ClientError, WaiterError from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.cloudformation.conftest import ( + skip_if_legacy_engine, + skipped_v2_items, +) from localstack.aws.api.cloudformation import Capability +from localstack.aws.connect import ServiceLevelClientFactory from localstack.services.cloudformation.engine.entities import StackIdentifier from localstack.services.cloudformation.engine.yaml_parser import parse_yaml from localstack.testing.aws.util import is_aws_cloud @@ -22,6 +28,9 @@ class TestStacksApi: @markers.snapshot.skip_snapshot_verify( paths=["$..ChangeSetId", "$..EnableTerminationProtection"] + + skipped_v2_items( + "$..Parameters", + ), ) @markers.aws.validated def test_stack_lifecycle(self, deploy_cfn_template, snapshot, aws_client): @@ -89,6 +98,75 @@ def test_stack_description_special_chars(self, deploy_cfn_template, snapshot, aw ] snapshot.match("describe_stack", response) + @skip_if_legacy_engine() + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..ResourceChange.Details", + "$..ResourceChange.Scope", + "$..StackStatusReason", + ] + ) + @pytest.mark.parametrize( + "tags", [None, [{"Key": "foo", "Value": "bar"}]], ids=["no-tags", "with-tags"] + ) + def test_stack_description_lifecycle(self, snapshot, aws_client, cleanups, tags): + """ + Test when and how the stack metadata gets set: + * tags + * description + """ + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "test .test.net", + "Resources": { + "TestResource": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "DummyValue", + }, + } + }, + } + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + + kwargs = { + "StackName": stack_name, + "ChangeSetName": change_set_name, + "ChangeSetType": "CREATE", + "TemplateBody": json.dumps(template), + } + if tags is not None: + kwargs["Tags"] = tags + + change_set = aws_client.cloudformation.create_change_set(**kwargs) + change_set_id = change_set["Id"] + stack_id = change_set["StackId"] + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_id, StackName=stack_id + ) + change_set_description = aws_client.cloudformation.describe_change_set( + ChangeSetName=change_set_id + ) + snapshot.match("change-set-pre-execute", change_set_description) + stack_description = aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][ + 0 + ] + snapshot.match("stack-pre-execute", stack_description) + + aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) + + aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) + stack_description = aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][ + 0 + ] + snapshot.match("stack-post-execute", stack_description) + @markers.aws.validated def test_stack_name_creation(self, deploy_cfn_template, snapshot, aws_client): snapshot.add_transformer(snapshot.transform.cloudformation_api()) @@ -105,6 +183,18 @@ def test_stack_name_creation(self, deploy_cfn_template, snapshot, aws_client): snapshot.match("stack_response", e.value.response) + @markers.aws.validated + def test_create_stack_url_as_template(self, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + stack_name = f"stack-{short_uid()}" + + template_url = "https://raw.githubusercontent.com/aws-cloudformation/aws-cloudformation-templates/refs/heads/main/EC2/InstanceWithCfnInit.yaml" + + with pytest.raises(ClientError) as e: + aws_client.cloudformation.create_stack(StackName=stack_name, TemplateBody=template_url) + snapshot.match("stack_create_ec2", e.value) + @markers.aws.validated @pytest.mark.parametrize("fileformat", ["yaml", "json"]) def test_get_template_using_create_stack(self, snapshot, fileformat, aws_client): @@ -208,45 +298,6 @@ def test_stack_update_resources( resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name) snapshot.match("stack_resources", resources) - @markers.aws.needs_fixing - def test_list_stack_resources_for_removed_resource(self, deploy_cfn_template, aws_client): - template_path = os.path.join( - os.path.dirname(__file__), "../../../templates/eventbridge_policy.yaml" - ) - event_bus_name = f"bus-{short_uid()}" - stack = deploy_cfn_template( - template_path=template_path, - parameters={"EventBusName": event_bus_name}, - ) - - resources = aws_client.cloudformation.list_stack_resources(StackName=stack.stack_name)[ - "StackResourceSummaries" - ] - resources_before = len(resources) - assert resources_before == 3 - statuses = {res["ResourceStatus"] for res in resources} - assert statuses == {"CREATE_COMPLETE"} - - # remove one resource from the template, then update stack (via change set) - template_dict = parse_yaml(load_file(template_path)) - template_dict["Resources"].pop("eventPolicy2") - template2 = yaml.dump(template_dict) - - deploy_cfn_template( - stack_name=stack.stack_name, - is_update=True, - template=template2, - parameters={"EventBusName": event_bus_name}, - ) - - # get list of stack resources, again - make sure that deleted resource is not contained in result - resources = aws_client.cloudformation.list_stack_resources(StackName=stack.stack_name)[ - "StackResourceSummaries" - ] - assert len(resources) == resources_before - 1 - statuses = {res["ResourceStatus"] for res in resources} - assert statuses == {"UPDATE_COMPLETE"} - @markers.aws.validated def test_update_stack_with_same_template_withoutchange( self, deploy_cfn_template, aws_client, snapshot @@ -289,6 +340,7 @@ def test_update_stack_with_same_template_withoutchange_transformation( ) @markers.aws.validated + @skip_if_legacy_engine() def test_update_stack_actual_update(self, deploy_cfn_template, aws_client): template = load_file( os.path.join(os.path.dirname(__file__), "../../../templates/sqs_queue_update.yml") @@ -338,7 +390,7 @@ def test_failure_options_for_stack_creation( self, rollback_disabled, length_expected, aws_client ): template_with_error = open( - os.path.join(os.path.dirname(__file__), "../../../templates/multiple_bucket.yaml"), "r" + os.path.join(os.path.dirname(__file__), "../../../templates/multiple_bucket.yaml") ).read() stack_name = f"stack-{short_uid()}" @@ -382,7 +434,6 @@ def test_failure_options_for_stack_update( os.path.join( os.path.dirname(__file__), "../../../templates/multiple_bucket_update.yaml" ), - "r", ).read() aws_client.cloudformation.create_stack( @@ -424,6 +475,7 @@ def _assert_stack_process_finished(): ] assert len(updated_resources) == length_expected + @markers.requires_in_process @markers.aws.only_localstack def test_create_stack_with_custom_id( self, aws_client, cleanups, account_id, region_name, set_resource_custom_id @@ -436,7 +488,6 @@ def test_create_stack_with_custom_id( ) template = open( os.path.join(os.path.dirname(__file__), "../../../templates/sns_topic_simple.yaml"), - "r", ).read() stack = aws_client.cloudformation.create_stack( @@ -653,8 +704,6 @@ def test_events_resource_types(deploy_cfn_template, snapshot, aws_client): @markers.aws.validated def test_list_parameter_type(aws_client, deploy_cfn_template, cleanups): - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) stack = deploy_cfn_template( template_path=os.path.join( os.path.dirname(__file__), "../../../templates/cfn_parameter_list_type.yaml" @@ -667,6 +716,35 @@ def test_list_parameter_type(aws_client, deploy_cfn_template, cleanups): assert stack.outputs["ParamValue"] == "foo|bar" +@markers.aws.validated +def test_subnet_id_parameter_type(aws_client, deploy_cfn_template, cleanups, snapshot): + vpc_id = aws_client.ec2.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + cleanups.append(lambda: aws_client.ec2.delete_vpc(VpcId=vpc_id)) + aws_client.ec2.get_waiter("vpc_available").wait(VpcIds=[vpc_id]) + subnet_id_1 = aws_client.ec2.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/24")["Subnet"][ + "SubnetId" + ] + cleanups.append(lambda: aws_client.ec2.delete_subnet(SubnetId=subnet_id_1)) + subnet_id_2 = aws_client.ec2.create_subnet(VpcId=vpc_id, CidrBlock="10.0.1.0/24")["Subnet"][ + "SubnetId" + ] + cleanups.append(lambda: aws_client.ec2.delete_subnet(SubnetId=subnet_id_2)) + + subnets_list = ",".join([subnet_id_1, subnet_id_2]) + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/cfn_parameter_list_subnet_id_type.yaml" + ), + parameters={ + "ParamsList": subnets_list, + }, + ) + + snapshot.add_transformer(snapshot.transform.regex(subnet_id_1, "subnet-id-1")) + snapshot.add_transformer(snapshot.transform.regex(subnet_id_2, "subnet-id-2")) + snapshot.match("outputs", stack.outputs) + + @markers.aws.validated @pytest.mark.skipif(condition=not is_aws_cloud(), reason="rollback not implemented") def test_blocked_stack_deletion(aws_client, cleanups, snapshot): @@ -732,6 +810,7 @@ def test_blocked_stack_deletion(aws_client, cleanups, snapshot): @markers.snapshot.skip_snapshot_verify( paths=["$..EnableTerminationProtection", "$..LastUpdatedTime"] + + skipped_v2_items("$..Capabilities") ) @markers.aws.validated def test_name_conflicts(aws_client, snapshot, cleanups): @@ -827,9 +906,8 @@ def test_name_conflicts(aws_client, snapshot, cleanups): @markers.aws.validated def test_describe_stack_events_errors(aws_client, snapshot): - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: + with pytest.raises(botocore.exceptions.ParamValidationError) as e: aws_client.cloudformation.describe_stack_events() - snapshot.match("describe_stack_events_no_stack_name", e.value.response) with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: aws_client.cloudformation.describe_stack_events(StackName="does-not-exist") snapshot.match("describe_stack_events_stack_not_found", e.value.response) @@ -922,6 +1000,88 @@ def test_stack_deploy_order(deploy_cfn_template, aws_client, snapshot, deploy_or snapshot.match("events", filtered_events) +@skip_if_legacy_engine() +@markers.aws.validated +@pytest.mark.parametrize( + "deletions", + [ + pytest.param(["C"], id="C"), + pytest.param(["B", "C"], id="B-C"), + pytest.param(["A", "B", "C"], id="A-B-C"), + ], +) +@markers.snapshot.skip_snapshot_verify( + paths=[ + "delete-describe.ChangeSetId", + "$..EnableTerminationProtection", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..PolicyAction", + "$..PhysicalResourceId", + "$..Changes..ResourceChange.BeforeContext.Properties.Value", + "$..StackEvents..EventId", + "$..StackEvents..ResourceStatusReason", + "$..StackEvents..ResourceProperties.Value", + "all-events..EventId", + ] +) +def test_stack_deletion_order( + aws_client, capture_update_process, capture_resource_state_changes, snapshot, deletions +): + t1 = { + "Resources": { + "Dummy": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "dummy", + }, + }, + "A": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "root", + }, + "DependsOn": ["Dummy"], + }, + "B": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Ref": "A", + }, + }, + }, + "C": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Ref": "B", + }, + }, + }, + } + } + t2 = copy.deepcopy(t1) + for deletion in deletions: + del t2["Resources"][deletion] + + stack_id = capture_update_process(snapshot, t1, t2) + + # since resource deployments are serializable, we can capture events and check parity with them + events = list(capture_resource_state_changes(stack_id)) + to_snapshot = [(every["LogicalResourceId"], every["ResourceStatus"]) for every in events[::-1]] + snapshot.match("all-events", to_snapshot) + + @markers.snapshot.skip_snapshot_verify( paths=[ # TODO: this property is present in the response from LocalStack when @@ -936,6 +1096,10 @@ def test_stack_deploy_order(deploy_cfn_template, aws_client, snapshot, deploy_or "$..ResourceChange", "$..StackResourceDetail.Metadata", ] + + skipped_v2_items( + "$..Stacks..Outputs..Description", + "$..StackResourceDetail.DriftInformation", + ) ) @markers.aws.validated def test_no_echo_parameter(snapshot, aws_client, deploy_cfn_template): @@ -943,7 +1107,7 @@ def test_no_echo_parameter(snapshot, aws_client, deploy_cfn_template): snapshot.add_transformer(SortingTransformer("Parameters", lambda x: x.get("ParameterKey", ""))) template_path = os.path.join(os.path.dirname(__file__), "../../../templates/cfn_no_echo.yml") - template = open(template_path, "r").read() + template = open(template_path).read() deployment = deploy_cfn_template( template=template, @@ -971,6 +1135,28 @@ def test_no_echo_parameter(snapshot, aws_client, deploy_cfn_template): describe_stack_resource_details, ) + # create a change set + change_set_name = f"CreateChangeSetSecretParameterValue-{short_uid()}" + aws_client.cloudformation.create_change_set( + StackName=stack_name, + TemplateBody=template, + ChangeSetName=change_set_name, + Parameters=[ + {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue1"}, + ], + ) + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + StackName=stack_name, + ChangeSetName=change_set_name, + ) + change_sets = aws_client.cloudformation.describe_change_set( + StackName=stack_id, + ChangeSetName=change_set_name, + ) + snapshot.match("describe_change_set_on_create_complete", change_sets) + describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) + snapshot.match("describe_stack_on_create_complete", describe_stacks) + # Update stack via update_stack (and change the value of SecretParameter) aws_client.cloudformation.update_stack( StackName=stack_name, @@ -1069,3 +1255,55 @@ def test_stack_resource_not_found(deploy_cfn_template, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) snapshot.match("Error", ex.value.response) + + +@markers.aws.validated +def test_non_existing_stack_message(aws_client, snapshot): + with pytest.raises(botocore.exceptions.ClientError) as ex: + aws_client.cloudformation.describe_stacks(StackName="non-existing") + + snapshot.add_transformer(snapshot.transform.regex("non-existing", "")) + snapshot.match("Error", ex.value.response) + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_no_parameters_given(aws_client, deploy_cfn_template, snapshot): + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/ssm_parameter_defaultname.yaml" + ) + with pytest.raises(ClientError) as exc_info: + deploy_cfn_template(template_path=template_path) + snapshot.match("deploy-error", exc_info.value) + + +@markers.aws.validated +def test_blank_parameter_value(aws_client: ServiceLevelClientFactory, cleanups): + """ + While testing the new engine, I found that we don't handle the parameter value being blank well + """ + template = { + "Parameters": { + "MyFoo": { + "Type": "String", + }, + }, + "Resources": { + "MyTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "DisplayName": {"Ref": "MyFoo"}, + }, + }, + }, + } + + res = aws_client.cloudformation.create_stack( + TemplateBody=json.dumps(template), + StackName=f"stack-{short_uid()}", + Parameters=[{"ParameterKey": "MyFoo", "ParameterValue": ""}], + ) + stack_id = res["StackId"] + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) + + aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) diff --git a/tests/aws/services/cloudformation/api/test_stacks.snapshot.json b/tests/aws/services/cloudformation/api/test_stacks.snapshot.json index 9b4c3fe01f8b1..de702011a9e58 100644 --- a/tests/aws/services/cloudformation/api/test_stacks.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_stacks.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "recorded-date": "05-08-2022, 13:03:43", + "recorded-date": "08-08-2025, 15:32:46", "recorded-content": { "describe_stack": { "Capabilities": [ @@ -986,17 +986,6 @@ "tests/aws/services/cloudformation/api/test_stacks.py::test_describe_stack_events_errors": { "recorded-date": "26-03-2024, 17:54:41", "recorded-content": { - "describe_stack_events_no_stack_name": { - "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value null at 'stackName' failed to satisfy constraint: Member must not be null", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, "describe_stack_events_stack_not_found": { "Error": { "Code": "ValidationError", @@ -1573,7 +1562,7 @@ } }, "tests/aws/services/cloudformation/api/test_stacks.py::test_no_echo_parameter": { - "recorded-date": "19-12-2024, 11:35:19", + "recorded-date": "15-08-2025, 12:01:58", "recorded-content": { "describe_stacks": { "Stacks": [ @@ -1646,6 +1635,141 @@ "HTTPStatusCode": 200 } }, + "describe_change_set_on_create_complete": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "CausingEntity": "SecretParameter", + "ChangeSource": "ParameterReference", + "Evaluation": "Static", + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + } + }, + { + "CausingEntity": "SecretParameter", + "ChangeSource": "ParameterReference", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + } + }, + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + } + }, + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "LocalBucket", + "PhysicalResourceId": "cfn-noecho-bucket", + "Replacement": "False", + "ResourceType": "AWS::S3::Bucket", + "Scope": [ + "Metadata", + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "NormalParameter", + "ParameterValue": "Some default value here" + }, + { + "ParameterKey": "SecretParameter", + "ParameterValue": "****" + }, + { + "ParameterKey": "SecretParameterWithDefault", + "ParameterValue": "****" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_stack_on_create_complete": { + "Stacks": [ + { + "Capabilities": [ + "CAPABILITY_AUTO_EXPAND", + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "Description": "Secret value from parameter", + "OutputKey": "SecretValue", + "OutputValue": "SecretValue" + } + ], + "Parameters": [ + { + "ParameterKey": "NormalParameter", + "ParameterValue": "Some default value here" + }, + { + "ParameterKey": "SecretParameter", + "ParameterValue": "****" + }, + { + "ParameterKey": "SecretParameterWithDefault", + "ParameterValue": "****" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, "describe_updated_stacks": { "Stacks": [ { @@ -1697,8 +1821,8 @@ }, "describe_updated_change_set": { "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", "Changes": [ { "ResourceChange": { @@ -1831,8 +1955,8 @@ }, "describe_updated_change_set_no_echo_true": { "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", "Changes": [ { "ResourceChange": { @@ -1965,8 +2089,8 @@ }, "describe_updated_change_set_no_echo_false": { "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", "Changes": [ { "ResourceChange": { @@ -2286,5 +2410,1945 @@ } } } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_parameters_given": { + "recorded-date": "31-07-2025, 16:12:14", + "recorded-content": { + "deploy-error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Parameters: [Input] must have values" + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_non_existing_stack_message": { + "recorded-date": "21-07-2025, 18:00:27", + "recorded-content": { + "Error": { + "Error": { + "Code": "ValidationError", + "Message": "Stack with id does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[C]": { + "recorded-date": "01-08-2025, 16:11:25", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "dummy", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "A": [ + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "B": [ + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "C": [ + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Dummy": [ + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + }, + "all-events": [ + [ + "", + "REVIEW_IN_PROGRESS" + ], + [ + "", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_COMPLETE" + ], + [ + "A", + "CREATE_IN_PROGRESS" + ], + [ + "A", + "CREATE_COMPLETE" + ], + [ + "B", + "CREATE_IN_PROGRESS" + ], + [ + "B", + "CREATE_COMPLETE" + ], + [ + "C", + "CREATE_IN_PROGRESS" + ], + [ + "C", + "CREATE_COMPLETE" + ], + [ + "", + "CREATE_COMPLETE" + ], + [ + "", + "UPDATE_IN_PROGRESS" + ], + [ + "C", + "DELETE_IN_PROGRESS" + ], + [ + "C", + "DELETE_COMPLETE" + ], + [ + "", + "UPDATE_COMPLETE" + ], + [ + "", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_COMPLETE" + ], + [ + "A", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_COMPLETE" + ], + [ + "Dummy", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_COMPLETE" + ], + [ + "", + "DELETE_COMPLETE" + ] + ] + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[B-C]": { + "recorded-date": "01-08-2025, 16:11:54", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "dummy", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "A": [ + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "B": [ + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "C": [ + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Dummy": [ + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + }, + "all-events": [ + [ + "", + "REVIEW_IN_PROGRESS" + ], + [ + "", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_COMPLETE" + ], + [ + "A", + "CREATE_IN_PROGRESS" + ], + [ + "A", + "CREATE_COMPLETE" + ], + [ + "B", + "CREATE_IN_PROGRESS" + ], + [ + "B", + "CREATE_COMPLETE" + ], + [ + "C", + "CREATE_IN_PROGRESS" + ], + [ + "C", + "CREATE_COMPLETE" + ], + [ + "", + "CREATE_COMPLETE" + ], + [ + "", + "UPDATE_IN_PROGRESS" + ], + [ + "C", + "DELETE_IN_PROGRESS" + ], + [ + "C", + "DELETE_COMPLETE" + ], + [ + "B", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_COMPLETE" + ], + [ + "", + "UPDATE_COMPLETE" + ], + [ + "", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_COMPLETE" + ], + [ + "Dummy", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_COMPLETE" + ], + [ + "", + "DELETE_COMPLETE" + ] + ] + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[A-B-C]": { + "recorded-date": "01-08-2025, 16:12:23", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "dummy", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "A": [ + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "B": [ + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "C": [ + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Dummy": [ + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + }, + "all-events": [ + [ + "", + "REVIEW_IN_PROGRESS" + ], + [ + "", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_COMPLETE" + ], + [ + "A", + "CREATE_IN_PROGRESS" + ], + [ + "A", + "CREATE_COMPLETE" + ], + [ + "B", + "CREATE_IN_PROGRESS" + ], + [ + "B", + "CREATE_COMPLETE" + ], + [ + "C", + "CREATE_IN_PROGRESS" + ], + [ + "C", + "CREATE_COMPLETE" + ], + [ + "", + "CREATE_COMPLETE" + ], + [ + "", + "UPDATE_IN_PROGRESS" + ], + [ + "C", + "DELETE_IN_PROGRESS" + ], + [ + "C", + "DELETE_COMPLETE" + ], + [ + "B", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_COMPLETE" + ], + [ + "A", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_COMPLETE" + ], + [ + "", + "UPDATE_COMPLETE" + ], + [ + "", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_COMPLETE" + ], + [ + "", + "DELETE_COMPLETE" + ] + ] + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle[no-tags]": { + "recorded-date": "09-09-2025, 09:37:20", + "recorded-content": { + "change-set-pre-execute": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "TestResource", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stack-pre-execute": { + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "REVIEW_IN_PROGRESS", + "StackStatusReason": "User Initiated", + "Tags": [] + }, + "stack-post-execute": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "Description": "test .test.net", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle[with-tags]": { + "recorded-date": "09-09-2025, 09:37:28", + "recorded-content": { + "change-set-pre-execute": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "TestResource", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "Tags": [ + { + "Key": "foo", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stack-pre-execute": { + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "REVIEW_IN_PROGRESS", + "StackStatusReason": "User Initiated", + "Tags": [] + }, + "stack-post-execute": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "Description": "test .test.net", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [ + { + "Key": "foo", + "Value": "bar" + } + ] + } + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_create_stack_url_as_template": { + "recorded-date": "23-10-2025, 19:25:10", + "recorded-content": { + "stack_create_ec2": "An error occurred (ValidationError) when calling the CreateStack operation: Template format error: unsupported structure." + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_subnet_id_parameter_type": { + "recorded-date": "08-12-2025, 15:18:37", + "recorded-content": { + "outputs": { + "ParamValue": "subnet-id-1|subnet-id-2" + } + } } } diff --git a/tests/aws/services/cloudformation/api/test_stacks.validation.json b/tests/aws/services/cloudformation/api/test_stacks.validation.json index b1275f20421e5..fb734e04fbccd 100644 --- a/tests/aws/services/cloudformation/api/test_stacks.validation.json +++ b/tests/aws/services/cloudformation/api/test_stacks.validation.json @@ -1,4 +1,13 @@ { + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_create_stack_url_as_template": { + "last_validated_date": "2025-10-23T19:25:10+00:00", + "durations_in_seconds": { + "setup": 0.82, + "call": 0.82, + "teardown": 0.02, + "total": 1.66 + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": { "last_validated_date": "2024-06-25T17:21:51+00:00" }, @@ -26,8 +35,32 @@ "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": { "last_validated_date": "2022-10-05T11:33:55+00:00" }, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle[no-tags]": { + "last_validated_date": "2025-09-09T09:37:20+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 9.6, + "teardown": 0.19, + "total": 10.63 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle[with-tags]": { + "last_validated_date": "2025-09-09T09:37:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.26, + "teardown": 0.18, + "total": 8.44 + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "last_validated_date": "2022-08-05T11:03:43+00:00" + "last_validated_date": "2025-08-08T15:20:07+00:00", + "durations_in_seconds": { + "setup": 1.41, + "call": 61.6, + "teardown": 4.47, + "total": 67.48 + } }, "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": { "last_validated_date": "2023-11-28T12:24:40+00:00" @@ -44,6 +77,15 @@ "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": { "last_validated_date": "2024-05-07T09:26:39+00:00" }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_blank_parameter_value": { + "last_validated_date": "2025-09-10T21:37:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.32, + "teardown": 0.16, + "total": 6.48 + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::test_blocked_stack_deletion": { "last_validated_date": "2023-09-06T09:01:18+00:00" }, @@ -60,7 +102,58 @@ "last_validated_date": "2024-03-26T17:59:43+00:00" }, "tests/aws/services/cloudformation/api/test_stacks.py::test_no_echo_parameter": { - "last_validated_date": "2024-12-19T11:35:15+00:00" + "last_validated_date": "2025-08-15T12:01:58+00:00", + "durations_in_seconds": { + "setup": 1.28, + "call": 67.64, + "teardown": 4.43, + "total": 73.35 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_parameters_given": { + "last_validated_date": "2025-07-31T16:12:14+00:00", + "durations_in_seconds": { + "setup": 1.85, + "call": 0.32, + "teardown": 0.0, + "total": 2.17 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_non_existing_stack_message": { + "last_validated_date": "2025-07-21T18:00:27+00:00", + "durations_in_seconds": { + "setup": 0.3, + "call": 0.32, + "teardown": 0.0, + "total": 0.62 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[A-B-C]": { + "last_validated_date": "2025-08-01T16:12:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.8, + "teardown": 0.17, + "total": 28.97 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[B-C]": { + "last_validated_date": "2025-08-01T16:11:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.69, + "teardown": 0.13, + "total": 28.82 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[C]": { + "last_validated_date": "2025-08-01T16:11:25+00:00", + "durations_in_seconds": { + "setup": 1.17, + "call": 31.29, + "teardown": 0.13, + "total": 32.59 + } }, "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order2": { "last_validated_date": "2024-05-21T09:48:14+00:00" @@ -122,6 +215,15 @@ "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_resource_not_found": { "last_validated_date": "2025-01-29T09:08:15+00:00" }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_subnet_id_parameter_type": { + "last_validated_date": "2025-12-08T15:18:43+00:00", + "durations_in_seconds": { + "setup": 0.45, + "call": 13.02, + "teardown": 6.25, + "total": 19.72 + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::test_update_termination_protection": { "last_validated_date": "2023-01-04T15:23:22+00:00" }, diff --git a/tests/aws/services/cloudformation/api/test_templates.py b/tests/aws/services/cloudformation/api/test_templates.py index 07cd69d03276a..95a817fb3418f 100644 --- a/tests/aws/services/cloudformation/api/test_templates.py +++ b/tests/aws/services/cloudformation/api/test_templates.py @@ -1,11 +1,14 @@ import contextlib +import json import os import textwrap import pytest from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.testing.pytest import markers +from localstack.testing.pytest.fixtures import StackDeployError from localstack.utils.common import load_file from localstack.utils.strings import short_uid, to_bytes @@ -31,6 +34,77 @@ def test_get_template_summary(deploy_cfn_template, snapshot, aws_client): snapshot.match("template-summary", res) +@markers.aws.validated +@skip_if_legacy_engine() +def test_get_template_summary_non_executed_change_set(aws_client, snapshot, cleanups): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + template_body = { + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": short_uid(), + }, + }, + }, + } + stack_name = f"stack-{short_uid()}" + change_set_name = f"change-set-{short_uid()}" + response = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(template_body), + ChangeSetType="CREATE", + ) + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=response["Id"] + ) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=response["StackId"])) + + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.get_template_summary(StackName=stack_name) + + snapshot.match("error", exc_info.value.response) + + +@markers.aws.validated +@skip_if_legacy_engine() +def test_get_template_summary_no_resources(aws_client, snapshot): + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.get_template_summary(TemplateBody="{}") + snapshot.match("error", exc_info.value.response) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=["$..ResourceIdentifierSummaries..ResourceIdentifiers"] +) +@skip_if_legacy_engine() +def test_get_template_summary_failed_stack(deploy_cfn_template, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + template = { + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + # Note: missing Value parameter so the resource provider should fail + }, + }, + }, + } + + stack_name = f"stack-{short_uid()}" + with pytest.raises(StackDeployError): + deploy_cfn_template(template=json.dumps(template), stack_name=stack_name) + + summary = aws_client.cloudformation.get_template_summary(StackName=stack_name) + snapshot.match("template-summary", summary) + + @markers.aws.validated @pytest.mark.parametrize("url_style", ["s3_url", "http_path", "http_host", "http_invalid"]) def test_create_stack_from_s3_template_url( @@ -115,3 +189,64 @@ def test_validate_invalid_json_template_should_fail(aws_client, snapshot): aws_client.cloudformation.validate_template(TemplateBody=invalid_json) snapshot.match("validate-invalid-json", ctx.value.response) + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_get_template_no_arguments(aws_client, snapshot): + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.get_template() + snapshot.match("stack-error", exc_info.value.response) + + +@markers.aws.validated +def test_get_template_missing_resources_stack(aws_client, snapshot): + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.get_template(StackName="does-not-exist") + snapshot.match("stack-error", exc_info.value.response) + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_get_template_missing_resources_change_set(aws_client, snapshot): + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.get_template(ChangeSetName="does-not-exist") + snapshot.match("change-set-error", exc_info.value.response) + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_get_template_missing_resources_change_set_id(aws_client, snapshot): + change_set_id = ( + "arn:aws:cloudformation:us-east-1:000000000000:changeSet/change-set-926829fe/d065e78c" + ) + snapshot.add_transformer(snapshot.transform.regex(change_set_id, "")) + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.get_template(ChangeSetName=change_set_id) + snapshot.match("change-set-error", exc_info.value.response) + + +@markers.aws.validated +def test_create_stack_invalid_yaml_template_should_fail(aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + # add transformer to ignore the error location + # TODO: add this information back in to improve the UX + snapshot.add_transformer(snapshot.transform.regex(r"\s+\([^)]+\)", "")) + + stack_name = f"stack-{short_uid()}" + invalid_yaml = textwrap.dedent( + """\ + Resources: + MyBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: test + VersioningConfiguration: + Status: Enabled + """ + ) + + with pytest.raises(ClientError) as ctx: + aws_client.cloudformation.create_stack(StackName=stack_name, TemplateBody=invalid_yaml) + + snapshot.match("create-invalid-yaml", ctx.value.response) diff --git a/tests/aws/services/cloudformation/api/test_templates.snapshot.json b/tests/aws/services/cloudformation/api/test_templates.snapshot.json index be4f7159eae50..2934d32161ac0 100644 --- a/tests/aws/services/cloudformation/api/test_templates.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_templates.snapshot.json @@ -109,5 +109,171 @@ } } } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary_non_executed_change_set": { + "recorded-date": "10-09-2025, 23:25:47", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "GetTemplateSummary cannot be called on REVIEW_IN_PROGRESS stacks.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources": { + "recorded-date": "12-09-2025, 16:08:17", + "recorded-content": { + "stack-error": { + "Error": { + "Code": "ValidationError", + "Message": "Stack with id does-not-exist does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "change-set-error": { + "Error": { + "Code": "ValidationError", + "Message": "StackName is a required parameter.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_stack": { + "recorded-date": "12-09-2025, 16:08:42", + "recorded-content": { + "stack-error": { + "Error": { + "Code": "ValidationError", + "Message": "Stack with id does-not-exist does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_change_set": { + "recorded-date": "12-09-2025, 16:08:55", + "recorded-content": { + "change-set-error": { + "Error": { + "Code": "ValidationError", + "Message": "StackName is a required parameter.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_change_set_id": { + "recorded-date": "12-09-2025, 17:32:31", + "recorded-content": { + "change-set-error": { + "Error": { + "Code": "ChangeSetNotFound", + "Message": "ChangeSet [] does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary_no_resources": { + "recorded-date": "07-10-2025, 22:28:52", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template format error: At least one Resources member must be defined.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary_failed_stack": { + "recorded-date": "07-10-2025, 22:44:53", + "recorded-content": { + "template-summary": { + "Parameters": [], + "ResourceIdentifierSummaries": [ + { + "LogicalResourceIds": [ + "MyParameter" + ], + "ResourceIdentifiers": [ + "Name" + ], + "ResourceType": "AWS::SSM::Parameter" + } + ], + "ResourceTypes": [ + "AWS::SSM::Parameter" + ], + "Version": "2010-09-09", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_no_arguments": { + "recorded-date": "13-11-2025, 16:48:31", + "recorded-content": { + "stack-error": { + "Error": { + "Code": "ValidationError", + "Message": "StackName is required if ChangeSetName is not specified.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_invalid_yaml_template_should_fail": { + "recorded-date": "13-11-2025, 11:36:51", + "recorded-content": { + "create-invalid-yaml": { + "Error": { + "Code": "ValidationError", + "Message": "Template format error: YAML not well-formed.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/cloudformation/api/test_templates.validation.json b/tests/aws/services/cloudformation/api/test_templates.validation.json index 68f2a82074387..abfd75080a124 100644 --- a/tests/aws/services/cloudformation/api/test_templates.validation.json +++ b/tests/aws/services/cloudformation/api/test_templates.validation.json @@ -11,9 +11,90 @@ "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": { "last_validated_date": "2023-10-10T22:03:44+00:00" }, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_invalid_yaml_template_should_fail": { + "last_validated_date": "2025-11-13T11:36:51+00:00", + "durations_in_seconds": { + "setup": 1.06, + "call": 0.3, + "teardown": 0.0, + "total": 1.36 + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources": { + "last_validated_date": "2025-09-12T16:08:17+00:00", + "durations_in_seconds": { + "setup": 0.81, + "call": 0.23, + "teardown": 0.0, + "total": 1.04 + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_change_set": { + "last_validated_date": "2025-09-12T16:08:55+00:00", + "durations_in_seconds": { + "setup": 0.73, + "call": 0.18, + "teardown": 0.0, + "total": 0.91 + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_change_set_id": { + "last_validated_date": "2025-09-12T17:32:31+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 0.22, + "teardown": 0.0, + "total": 1.16 + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_missing_resources_stack": { + "last_validated_date": "2025-09-12T16:08:42+00:00", + "durations_in_seconds": { + "setup": 0.99, + "call": 0.2, + "teardown": 0.0, + "total": 1.19 + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_no_arguments": { + "last_validated_date": "2025-11-13T16:48:31+00:00", + "durations_in_seconds": { + "setup": 1.54, + "call": 0.19, + "teardown": 0.0, + "total": 1.73 + } + }, "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary": { "last_validated_date": "2023-05-24T13:05:00+00:00" }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary_failed_stack": { + "last_validated_date": "2025-10-07T22:44:53+00:00", + "durations_in_seconds": { + "setup": 1.0, + "call": 134.39, + "teardown": 0.0, + "total": 135.39 + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary_no_resources": { + "last_validated_date": "2025-10-07T22:28:52+00:00", + "durations_in_seconds": { + "setup": 1.58, + "call": 0.22, + "teardown": 0.0, + "total": 1.8 + } + }, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary_non_executed_change_set": { + "last_validated_date": "2025-09-10T23:25:47+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 3.77, + "teardown": 0.13, + "total": 4.82 + } + }, "tests/aws/services/cloudformation/api/test_templates.py::test_validate_invalid_json_template_should_fail": { "last_validated_date": "2024-06-18T17:25:49+00:00" }, diff --git a/tests/aws/services/cloudformation/api/test_transformers.py b/tests/aws/services/cloudformation/api/test_transformers.py index 568b260c407f5..ece2b55856013 100644 --- a/tests/aws/services/cloudformation/api/test_transformers.py +++ b/tests/aws/services/cloudformation/api/test_transformers.py @@ -6,6 +6,7 @@ import pytest from botocore.exceptions import WaiterError from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine, skipped_v2_items from localstack.aws.connect import ServiceLevelClientFactory from localstack.testing.pytest import markers @@ -35,30 +36,12 @@ def test_duplicate_resources(deploy_cfn_template, s3_bucket, snapshot, aws_clien aws_client.s3.put_object(Bucket=s3_bucket, Key="api.yaml", Body=to_bytes(api_spec)) # deploy template - template = """ - Parameters: - ApiName: - Type: String - BucketName: - Type: String - Resources: - RestApi: - Type: AWS::ApiGateway::RestApi - Properties: - Name: !Ref ApiName - Body: - 'Fn::Transform': - Name: 'AWS::Include' - Parameters: - Location: !Sub "s3://${BucketName}/api.yaml" - Outputs: - RestApiId: - Value: !Ref RestApi - """ - api_name = f"api-{short_uid()}" result = deploy_cfn_template( - template=template, parameters={"ApiName": api_name, "BucketName": s3_bucket} + parameters={"ApiName": api_name, "BucketName": s3_bucket}, + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/cfn_apigw_with_include_fn.yml" + ), ) # assert REST API is created properly @@ -71,6 +54,51 @@ def test_duplicate_resources(deploy_cfn_template, s3_bucket, snapshot, aws_clien snapshot.match("api-resources", resources) +@skip_if_legacy_engine() +@markers.aws.validated +def test_redeployment_with_fn_include(deploy_cfn_template, s3_bucket, snapshot, aws_client): + snapshot.add_transformers_list( + [ + *snapshot.transform.apigateway_api(), + snapshot.transform.key_value("aws:cloudformation:stack-id"), + snapshot.transform.key_value("aws:cloudformation:stack-name"), + ] + ) + + # put API spec to S3 + api_spec = """ + swagger: 2.0 + info: + version: "1.2.3" + title: "Test API" + basePath: /base + """ + aws_client.s3.put_object(Bucket=s3_bucket, Key="api.yaml", Body=to_bytes(api_spec)) + + # deploy template + api_name = f"api-{short_uid()}" + result = deploy_cfn_template( + parameters={"ApiName": api_name, "BucketName": s3_bucket}, + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/cfn_apigw_with_include_fn.yml" + ), + ) + + api_name = f"api-{short_uid()}" + updated_result = deploy_cfn_template( + stack_name=result.stack_name, + is_update=True, + parameters={"ApiName": api_name, "BucketName": s3_bucket}, + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/cfn_apigw_with_include_fn.yml" + ), + ) + + api_id = updated_result.outputs.get("RestApiId") + api = aws_client.apigateway.get_rest_api(restApiId=api_id) + snapshot.match("api-details", api) + + @markers.aws.validated def test_transformer_property_level(deploy_cfn_template, s3_bucket, aws_client, snapshot): api_spec = textwrap.dedent(""" @@ -291,6 +319,11 @@ def test_transform_foreach_multiple_resources(self, transform_template, snapshot "$..StackResources..PhysicalResourceId", "$..StackResources..StackId", ] + + skipped_v2_items( + # we now set this with the v2 provider for extra clarity but this field is not set on + # AWS + "$..StackResources..ResourceStatusReason", + ) ) def test_transform_foreach_use_case(self, aws_client, transform_template, snapshot): snapshot.add_transformer( diff --git a/tests/aws/services/cloudformation/api/test_transformers.snapshot.json b/tests/aws/services/cloudformation/api/test_transformers.snapshot.json index 05c710c033043..2532e696f64e0 100644 --- a/tests/aws/services/cloudformation/api/test_transformers.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_transformers.snapshot.json @@ -725,5 +725,34 @@ }, "array-value": "[\"a\",\"b\",\"c\"]" } + }, + "tests/aws/services/cloudformation/api/test_transformers.py::test_redeployment_with_fn_include": { + "recorded-date": "09-09-2025, 16:00:50", + "recorded-content": { + "api-details": { + "apiKeySource": "HEADER", + "createdDate": "datetime", + "disableExecuteApiEndpoint": false, + "endpointConfiguration": { + "ipAddressType": "ipv4", + "types": [ + "EDGE" + ] + }, + "id": "", + "name": "", + "rootResourceId": "", + "tags": { + "aws:cloudformation:logical-id": "RestApi", + "aws:cloudformation:stack-id": "", + "aws:cloudformation:stack-name": "" + }, + "version": "1.2.3", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/cloudformation/api/test_transformers.validation.json b/tests/aws/services/cloudformation/api/test_transformers.validation.json index 3c2aa5bed07df..10c4d5cbaa696 100644 --- a/tests/aws/services/cloudformation/api/test_transformers.validation.json +++ b/tests/aws/services/cloudformation/api/test_transformers.validation.json @@ -62,6 +62,15 @@ "total": 24.63 } }, + "tests/aws/services/cloudformation/api/test_transformers.py::test_redeployment_with_fn_include": { + "last_validated_date": "2025-09-09T16:00:55+00:00", + "durations_in_seconds": { + "setup": 0.7, + "call": 27.5, + "teardown": 5.29, + "total": 33.49 + } + }, "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_individual_resource_level": { "last_validated_date": "2024-06-13T06:43:21+00:00" }, diff --git a/tests/aws/services/cloudformation/api/test_update_stack.py b/tests/aws/services/cloudformation/api/test_update_stack.py index fedd7e30516c6..a1b9a918cf427 100644 --- a/tests/aws/services/cloudformation/api/test_update_stack.py +++ b/tests/aws/services/cloudformation/api/test_update_stack.py @@ -5,6 +5,7 @@ import botocore.errorfactory import botocore.exceptions import pytest +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.testing.pytest import markers from localstack.utils.files import load_file @@ -255,14 +256,18 @@ def test_no_parameters_update(deploy_cfn_template, aws_client): @markers.aws.validated -def test_update_with_previous_parameter_value(deploy_cfn_template, snapshot, aws_client): +def test_update_with_previous_parameter_value(deploy_cfn_template, aws_client): + topic_name = f"topic-{short_uid()}" stack = deploy_cfn_template( template_path=os.path.join( os.path.dirname(__file__), "../../../templates/sns_topic_parameter.yml" ), - parameters={"TopicName": f"topic-{short_uid()}"}, + parameters={"TopicName": topic_name}, ) + topic_arn = stack.outputs["TopicArn"] + assert topic_name in topic_arn + aws_client.cloudformation.update_stack( StackName=stack.stack_name, TemplateBody=load_file( @@ -275,6 +280,9 @@ def test_update_with_previous_parameter_value(deploy_cfn_template, snapshot, aws aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) + # this call makes sure the topic name has not changed + aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + @markers.aws.validated @pytest.mark.skip(reason="The correct error is not being raised") @@ -452,3 +460,32 @@ def test_diff_after_update(deploy_cfn_template, aws_client, snapshot): describe_stack_response = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) assert describe_stack_response["Stacks"][0]["StackStatus"] == "UPDATE_COMPLETE" + + +@markers.aws.validated +@skip_if_legacy_engine +def test_update_deleted_stack(aws_client, deploy_cfn_template, snapshot): + template = json.dumps( + { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": "Test"}, + } + } + } + ) + + stack = deploy_cfn_template(template=template) + stack.destroy() + + with pytest.raises(botocore.exceptions.ClientError) as ex: + aws_client.cloudformation.create_change_set( + StackName=stack.stack_id, + ChangeSetName="test", + TemplateBody=template, + ChangeSetType="UPDATE", + ) + + snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) + snapshot.match("Error", ex.value.response) diff --git a/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json b/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json index bf9201665b6d3..9462ff023e36f 100644 --- a/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json @@ -59,8 +59,63 @@ "recorded-content": {} }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "recorded-date": "21-11-2022, 10:38:33", - "recorded-content": {} + "recorded-date": "08-08-2025, 21:51:58", + "recorded-content": { + "topic-attributes": { + "DisplayName": "", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:topic-e848c848", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:topic-e848c848" + } + } }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_role_without_permissions": { "recorded-date": "21-11-2022, 14:14:52", @@ -131,5 +186,21 @@ } } } + }, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_deleted_stack": { + "recorded-date": "10-10-2025, 21:42:05", + "recorded-content": { + "Error": { + "Error": { + "Code": "ValidationError", + "Message": "Stack: is in DELETE_COMPLETE state and can not be updated.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/cloudformation/api/test_update_stack.validation.json b/tests/aws/services/cloudformation/api/test_update_stack.validation.json index 3821105abaa2a..831f96299a8d6 100644 --- a/tests/aws/services/cloudformation/api/test_update_stack.validation.json +++ b/tests/aws/services/cloudformation/api/test_update_stack.validation.json @@ -8,11 +8,26 @@ "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_template_error": { "last_validated_date": "2022-11-21T07:57:45+00:00" }, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_deleted_stack": { + "last_validated_date": "2025-10-10T21:42:05+00:00", + "durations_in_seconds": { + "setup": 0.25, + "call": 18.99, + "teardown": 0.12, + "total": 19.36 + } + }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": { "last_validated_date": "2022-11-21T14:36:32+00:00" }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "last_validated_date": "2022-11-21T09:38:33+00:00" + "last_validated_date": "2025-08-08T21:55:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.77, + "teardown": 49.74, + "total": 66.51 + } }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_resource_types": { "last_validated_date": "2022-11-19T13:34:18+00:00" diff --git a/tests/aws/services/cloudformation/conftest.py b/tests/aws/services/cloudformation/conftest.py new file mode 100644 index 0000000000000..289bf3508bcc8 --- /dev/null +++ b/tests/aws/services/cloudformation/conftest.py @@ -0,0 +1,21 @@ +import re +from collections.abc import Iterable + +import pytest + +from localstack.services.cloudformation.v2.utils import is_v2_engine +from localstack.testing.aws.util import is_aws_cloud +from localstack.utils.collections import optional_list + +SKIP_TYPE_RE = re.compile(r"^CFNV2\((?P[^\)]+)\)") + + +def skip_if_legacy_engine(reason: str | None = None): + return pytest.mark.skipif( + condition=not is_v2_engine() and not is_aws_cloud(), + reason=reason or "Not implemented in legacy engine", + ) + + +def skipped_v2_items[T](*items: Iterable[T]) -> list[T]: + return optional_list(is_v2_engine(), items) diff --git a/tests/aws/services/cloudformation/engine/test_conditions.py b/tests/aws/services/cloudformation/engine/test_conditions.py index 3bd8990172946..57235adf7ec0c 100644 --- a/tests/aws/services/cloudformation/engine/test_conditions.py +++ b/tests/aws/services/cloudformation/engine/test_conditions.py @@ -1,9 +1,15 @@ +import json import os.path import pytest +from botocore.exceptions import ClientError +from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine +from localstack.services.cloudformation.v2.utils import is_v2_engine from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers +from localstack.utils.collections import optional_list from localstack.utils.files import load_file from localstack.utils.strings import short_uid @@ -124,7 +130,33 @@ def test_dependent_ref(self, aws_client, snapshot): snapshot.match("dependent_ref_exc", e.value.response) @markers.aws.validated - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") + @skip_if_legacy_engine + def test_dependent_get_att(self, aws_client, snapshot): + """ + Tests behavior of a stack with 2 resources where one depends on the other. + The referenced resource won't be deployed due to its condition evaluating to false, so the GetAtt can't be resolved. + + This immediately leads to an error. + """ + + stack_name = f"test-condition-ref-stack-{short_uid()}" + changeset_name = "initial" + with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: + aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=changeset_name, + ChangeSetType="CREATE", + TemplateBody=load_file( + os.path.join(THIS_DIR, "../../../templates/conditions/get-att-condition.yml") + ), + Parameters=[ + {"ParameterKey": "OptionParameter", "ParameterValue": "option-b"}, + ], + ) + snapshot.match("dependent_ref_exc", e.value.response) + + @markers.aws.validated + @skip_if_legacy_engine() def test_dependent_ref_intrinsic_fn_condition(self, aws_client, deploy_cfn_template): """ Checks behavior of un-refable resources @@ -348,9 +380,14 @@ def test_conditional_att_to_conditional_resources(self, deploy_cfn_template, cre ["should_use_fallback", "match_value"], [ (None, "FallbackParamValue"), - ("true", "FallbackParamValue"), ("false", "DefaultParamValue"), - ], + ] + + optional_list( + not is_v2_engine(), + [ + ("true", "FallbackParamValue"), + ], + ), ) @markers.aws.validated def test_dependency_in_non_evaluated_if_branch( @@ -481,3 +518,89 @@ def test_update_conditions(self, deploy_cfn_template, aws_client): assert aws_client.s3.head_bucket(Bucket=bucket_1) with pytest.raises(aws_client.s3.exceptions.ClientError): aws_client.s3.head_bucket(Bucket=bucket_2) + + @markers.aws.validated + @pytest.mark.parametrize("should_deploy", ["yes", "no"]) + @skip_if_legacy_engine() + def test_references_to_disabled_resources( + self, deploy_cfn_template, aws_client, should_deploy, snapshot + ): + snapshot.add_transformer( + SortingTransformer("StackResources", lambda e: e["LogicalResourceId"]) + ) + snapshot.add_transformer(SortingTransformer("Parameters", lambda e: e["ParameterKey"])) + snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + parameter_value = short_uid() + snapshot.add_transformer(snapshot.transform.regex(parameter_value, "")) + + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/references_to_conditions.yml" + ), + parameters={ + "Toggle": should_deploy, + "ParameterValue": parameter_value, + }, + ) + + describe_stack_res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_id) + snapshot.match("stack-description", describe_stack_res) + + describe_resources = aws_client.cloudformation.describe_stack_resources( + StackName=stack.stack_id + ) + snapshot.match("resources-description", describe_resources) + + @markers.aws.validated + @skip_if_legacy_engine() + def test_conditional_resource_referencing_conditional( + self, deploy_cfn_template, aws_client, snapshot + ): + """ + Test that a template with a conditional resource referencing another conditional + resource via !GetAtt is accepted and deploys correctly when conditions are false. + """ + snapshot.add_transformer( + SortingTransformer("StackResources", lambda e: e["LogicalResourceId"]) + ) + snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/conditions/conditional-resource-getatt-delete.yaml", + ), + parameters={"EnableQueue": "false"}, + ) + + resources = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_id) + snapshot.match("resources-description", resources) + + +class TestValidateConditions: + @markers.aws.validated + @skip_if_legacy_engine + def test_validate_equals_args_len(self, aws_client, snapshot): + template = { + "Conditions": {"ShouldDeploy": {"Fn::Equals": ["a"]}}, + "Resources": { + "Topic1": { + "Type": "AWS::SNS::Topic", + }, + }, + } + + stack_name = f"stack-{short_uid()}" + change_set_name = f"ch-{short_uid()}" + with pytest.raises(ClientError) as ex: + aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + ChangeSetType="CREATE", + TemplateBody=json.dumps(template), + ) + + snapshot.match("error", ex.value.response) diff --git a/tests/aws/services/cloudformation/engine/test_conditions.snapshot.json b/tests/aws/services/cloudformation/engine/test_conditions.snapshot.json index 4d8bc281ae75c..a2558b6d066dd 100644 --- a/tests/aws/services/cloudformation/engine/test_conditions.snapshot.json +++ b/tests/aws/services/cloudformation/engine/test_conditions.snapshot.json @@ -759,5 +759,220 @@ } } } + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_get_att": { + "recorded-date": "30-09-2025, 19:25:10", + "recorded-content": { + "dependent_ref_exc": { + "Error": { + "Code": "ValidationError", + "Message": "Template format error: Unresolved resource dependencies [MyTopic] in the Resources block of the template", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[yes]": { + "recorded-date": "07-10-2025, 21:21:23", + "recorded-content": { + "stack-description": { + "Stacks": [ + { + "Capabilities": [ + "CAPABILITY_AUTO_EXPAND", + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ConditionalParameterValue", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "ParameterValue", + "ParameterValue": "" + }, + { + "ParameterKey": "Toggle", + "ParameterValue": "yes" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "resources-description": { + "StackResources": [ + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "AnotherResource", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "BaseParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "ConditionalParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[no]": { + "recorded-date": "07-10-2025, 21:21:37", + "recorded-content": { + "stack-description": { + "Stacks": [ + { + "Capabilities": [ + "CAPABILITY_AUTO_EXPAND", + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "ParameterValue", + "ParameterValue": "" + }, + { + "ParameterKey": "Toggle", + "ParameterValue": "no" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "resources-description": { + "StackResources": [ + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "BaseParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestValidateConditions::test_validate_equals_args_len": { + "recorded-date": "08-10-2025, 18:18:14", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: every Fn::Equals object requires a list of 2 string parameters.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_resource_referencing_conditional": { + "recorded-date": "04-02-2026, 01:53:15", + "recorded-content": { + "resources-description": { + "StackResources": [ + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "MyTable", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::DynamoDB::Table", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/cloudformation/engine/test_conditions.validation.json b/tests/aws/services/cloudformation/engine/test_conditions.validation.json index a2579fb8517c8..b628813cd9f79 100644 --- a/tests/aws/services/cloudformation/engine/test_conditions.validation.json +++ b/tests/aws/services/cloudformation/engine/test_conditions.validation.json @@ -1,6 +1,30 @@ { + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_resource_referencing_conditional": { + "last_validated_date": "2026-02-04T01:53:29+00:00", + "durations_in_seconds": { + "setup": 0.7, + "call": 23.45, + "teardown": 14.46, + "total": 38.61 + } + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_get_att": { + "last_validated_date": "2025-09-30T19:25:10+00:00", + "durations_in_seconds": { + "setup": 0.23, + "call": 0.36, + "teardown": 0.0, + "total": 0.59 + } + }, "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": { - "last_validated_date": "2023-06-26T12:18:26+00:00" + "last_validated_date": "2025-09-30T19:03:49+00:00", + "durations_in_seconds": { + "setup": 0.27, + "call": 0.34, + "teardown": 0.0, + "total": 0.61 + } }, "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": { "last_validated_date": "2023-06-26T12:24:03+00:00" @@ -17,7 +41,34 @@ "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": { "last_validated_date": "2023-06-26T22:43:18+00:00" }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[no]": { + "last_validated_date": "2025-10-07T21:21:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.01, + "teardown": 4.39, + "total": 11.4 + } + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[yes]": { + "last_validated_date": "2025-10-07T21:21:30+00:00", + "durations_in_seconds": { + "setup": 0.89, + "call": 10.41, + "teardown": 6.43, + "total": 17.73 + } + }, "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": { "last_validated_date": "2024-06-18T19:43:43+00:00" + }, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestValidateConditions::test_validate_equals_args_len": { + "last_validated_date": "2025-10-08T18:18:14+00:00", + "durations_in_seconds": { + "setup": 0.25, + "call": 0.36, + "teardown": 0.0, + "total": 0.61 + } } } diff --git a/tests/aws/services/cloudformation/engine/test_deletion_policy.py b/tests/aws/services/cloudformation/engine/test_deletion_policy.py new file mode 100644 index 0000000000000..e539fcb653097 --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_deletion_policy.py @@ -0,0 +1,85 @@ +import os + +import pytest +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +@markers.snapshot.skip_snapshot_verify( + paths=[ + # our message is different. The AWS message does not seem to include the parameter + # name but ours does + "$..message", + ] +) +@markers.aws.validated +def test_deletion_policy_with_deletion(aws_client, deploy_cfn_template, snapshot): + template_path = os.path.join( + os.path.dirname(__file__), + "../../../templates/deletion_policy.yaml", + ) + parameter_value = short_uid() + stack = deploy_cfn_template( + template_path=template_path, + parameters={ + "EnvType": "dev", + "ParameterValue": parameter_value, + }, + ) + + parameter_name = stack.outputs["ParameterName"] + value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] + assert value == parameter_value + + stack.destroy() + + with pytest.raises(ClientError) as exc_info: + aws_client.ssm.get_parameter(Name=parameter_name) + + snapshot.match("error", {"message": str(exc_info.value)}) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + "$..PhysicalResourceId", + ] +) +@skip_if_legacy_engine() +def test_deletion_policy_with_retain( + aws_client, deploy_cfn_template, capture_per_resource_events, snapshot, cleanups +): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + template_path = os.path.join( + os.path.dirname(__file__), + "../../../templates/deletion_policy.yaml", + ) + parameter_value = short_uid() + stack = deploy_cfn_template( + template_path=template_path, + parameters={ + "EnvType": "prod", + "ParameterValue": parameter_value, + }, + ) + + snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) + + parameter_name = stack.outputs["ParameterName"] + + # make sure we clean up the parameter + cleanups.append(lambda: aws_client.ssm.delete_parameter(Name=parameter_name)) + + value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] + assert value == parameter_value + + stack.destroy() + + value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] + assert value == parameter_value + + events = capture_per_resource_events(stack.stack_id) + snapshot.match("per-resource-events", events) diff --git a/tests/aws/services/cloudformation/engine/test_deletion_policy.snapshot.json b/tests/aws/services/cloudformation/engine/test_deletion_policy.snapshot.json new file mode 100644 index 0000000000000..0b62aa8baa838 --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_deletion_policy.snapshot.json @@ -0,0 +1,77 @@ +{ + "tests/aws/services/cloudformation/engine/test_deletion_policy.py::test_deletion_policy_with_deletion": { + "recorded-date": "23-01-2026, 12:03:31", + "recorded-content": { + "error": { + "message": "An error occurred (ParameterNotFound) when calling the GetParameter operation: " + } + } + }, + "tests/aws/services/cloudformation/engine/test_deletion_policy.py::test_deletion_policy_with_retain": { + "recorded-date": "23-01-2026, 12:03:43", + "recorded-content": { + "per-resource-events": { + "MyParameter": [ + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "CFN-MyParameter-EZmrlQrQjjTv", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "CFN-MyParameter-EZmrlQrQjjTv", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "CFN-MyParameter-EZmrlQrQjjTv", + "ResourceStatus": "DELETE_SKIPPED", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + } +} diff --git a/tests/aws/services/cloudformation/engine/test_deletion_policy.validation.json b/tests/aws/services/cloudformation/engine/test_deletion_policy.validation.json new file mode 100644 index 0000000000000..c33f7eb27d770 --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_deletion_policy.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/engine/test_deletion_policy.py::test_deletion_policy_with_deletion": { + "last_validated_date": "2026-01-23T12:03:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.38, + "teardown": 0.08, + "total": 12.46 + } + }, + "tests/aws/services/cloudformation/engine/test_deletion_policy.py::test_deletion_policy_with_retain": { + "last_validated_date": "2026-01-23T12:03:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.35, + "teardown": 0.18, + "total": 12.53 + } + } +} diff --git a/tests/aws/services/cloudformation/engine/test_mappings.py b/tests/aws/services/cloudformation/engine/test_mappings.py index cb854d39c38d9..f1b8c443a0509 100644 --- a/tests/aws/services/cloudformation/engine/test_mappings.py +++ b/tests/aws/services/cloudformation/engine/test_mappings.py @@ -1,16 +1,16 @@ import os import pytest +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.testing.pytest import markers -from localstack.testing.pytest.fixtures import StackDeployError from localstack.utils.files import load_file from localstack.utils.strings import short_uid THIS_DIR = os.path.dirname(__file__) -@markers.snapshot.skip_snapshot_verify class TestCloudFormationMappings: @markers.aws.validated def test_simple_mapping_working(self, aws_client, deploy_cfn_template): @@ -43,7 +43,7 @@ def test_simple_mapping_working(self, aws_client, deploy_cfn_template): ] @markers.aws.validated - @pytest.mark.skip(reason="not implemented") + @skip_if_legacy_engine() def test_mapping_with_nonexisting_key(self, aws_client, cleanups, snapshot): """ Tries to deploy a resource with a dependency on a mapping key @@ -69,51 +69,64 @@ def test_mapping_with_nonexisting_key(self, aws_client, cleanups, snapshot): ) snapshot.match("mapping_nonexisting_key_exc", e.value.response) - @markers.aws.only_localstack - def test_async_mapping_error_first_level(self, deploy_cfn_template): - """ - We don't (yet) support validating mappings synchronously in `create_changeset` like AWS does, however - we don't fail with a good error message at all. This test ensures that the deployment fails with a - nicer error message than a Python traceback about "`None` has no attribute `get`". - """ + @markers.aws.validated + @skip_if_legacy_engine() + def test_async_mapping_error_first_level_v2(self, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) topic_name = f"test-topic-{short_uid()}" - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../templates/mappings/simple-mapping.yaml", - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "C", - }, + template_path = os.path.join( + THIS_DIR, + "../../../templates/mappings/simple-mapping.yaml", + ) + parameters = [ + {"ParameterKey": "TopicName", "ParameterValue": topic_name}, + {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "C"}, + ] + + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + ChangeSetType="CREATE", + Parameters=parameters, + TemplateBody=open(template_path).read(), ) - assert "Cannot find map key 'C' in mapping 'TopicSuffixMap'" in str(exc_info.value) + snapshot.match("error", exc_info.value) - @markers.aws.only_localstack - def test_async_mapping_error_second_level(self, deploy_cfn_template): + @markers.aws.validated + @skip_if_legacy_engine() + def test_async_mapping_error_second_level_v2(self, aws_client, snapshot): """ Similar to the `test_async_mapping_error_first_level` test above, but checking the second level of mapping lookup """ + snapshot.add_transformer(snapshot.transform.cloudformation_api()) topic_name = f"test-topic-{short_uid()}" - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../templates/mappings/simple-mapping.yaml", - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "A", - "TopicAttributeSelector": "NotValid", - }, + template_path = os.path.join( + THIS_DIR, + "../../../templates/mappings/simple-mapping.yaml", + ) + parameters = [ + {"ParameterKey": "TopicName", "ParameterValue": topic_name}, + {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "A"}, + {"ParameterKey": "TopicAttributeSelector", "ParameterValue": "NotValid"}, + ] + + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + ChangeSetType="CREATE", + Parameters=parameters, + TemplateBody=open(template_path).read(), ) - assert "Cannot find map key 'NotValid' in mapping 'TopicSuffixMap' under key 'A'" in str( - exc_info.value - ) + snapshot.match("error", exc_info.value) @markers.aws.validated @pytest.mark.skip(reason="not implemented") diff --git a/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json b/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json index c0287ad3e85fe..b40e5054b585d 100644 --- a/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json +++ b/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json @@ -62,5 +62,17 @@ } } } + }, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level_v2": { + "recorded-date": "07-08-2025, 14:34:05", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template error: Unable to get mapping for TopicSuffixMap::C::Suffix" + } + }, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level_v2": { + "recorded-date": "07-08-2025, 15:05:47", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template error: Unable to get mapping for TopicSuffixMap::A::NotValid" + } } } diff --git a/tests/aws/services/cloudformation/engine/test_mappings.validation.json b/tests/aws/services/cloudformation/engine/test_mappings.validation.json index d59232a7b10f5..8a5c3011aa1e8 100644 --- a/tests/aws/services/cloudformation/engine/test_mappings.validation.json +++ b/tests/aws/services/cloudformation/engine/test_mappings.validation.json @@ -1,4 +1,22 @@ { + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level_v2": { + "last_validated_date": "2025-08-07T14:34:05+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 0.28, + "teardown": 0.0, + "total": 1.15 + } + }, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level_v2": { + "last_validated_date": "2025-08-07T15:05:47+00:00", + "durations_in_seconds": { + "setup": 1.01, + "call": 0.36, + "teardown": 0.0, + "total": 1.37 + } + }, "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": { "last_validated_date": "2024-10-15T17:22:43+00:00" }, diff --git a/tests/aws/services/cloudformation/engine/test_references.py b/tests/aws/services/cloudformation/engine/test_references.py index ced32e1e92a27..05643055fdddb 100644 --- a/tests/aws/services/cloudformation/engine/test_references.py +++ b/tests/aws/services/cloudformation/engine/test_references.py @@ -3,6 +3,7 @@ import pytest from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers @@ -122,3 +123,45 @@ def test_resolve_transitive_placeholders_in_strings(deploy_cfn_template, aws_cli snapshot.transform.regex(r"/cdk-bootstrap/(\w+)/", "/cdk-bootstrap/.../") ) snapshot.match("tags", tags) + + +@markers.aws.validated +@pytest.mark.parametrize("parameter_value", ["yes", "no"]) +def test_aws_novalue(deploy_cfn_template, parameter_value): + """ + Test that AWS::NoValue is correctly executed in the CFn engine + """ + fallback_bucket_name = f"my-bucket-{short_uid()}" + stack = deploy_cfn_template( + template_path=os.path.join(os.path.dirname(__file__), "../../../templates/aws_novalue.yml"), + parameters={ + "SetBucketName": parameter_value, + "FallbackBucketName": fallback_bucket_name, + }, + ) + outputs = stack.outputs + + match parameter_value: + case "yes": + assert outputs["BucketName"] == fallback_bucket_name + case "no": + assert outputs["BucketName"] != fallback_bucket_name + case other: + pytest.fail(f"Test setup error, unexpected parameter value: {other}") + + +@markers.aws.validated +@skip_if_legacy_engine() +class TestPseudoParameters: + def test_stack_id(self, deploy_cfn_template, snapshot): + template_path = os.path.join( + os.path.dirname(__file__), + "../../../templates/stack-id-validation.yaml", + ) + stack = deploy_cfn_template(template_path=template_path) + + random_component = stack.stack_id.split("-")[-1] + snapshot.add_transformer(snapshot.transform.regex(random_component, "")) + snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) + + snapshot.match("parameter-value", stack.outputs["ParameterValue"]) diff --git a/tests/aws/services/cloudformation/engine/test_references.snapshot.json b/tests/aws/services/cloudformation/engine/test_references.snapshot.json index 9e6600aaaa00f..d92850a19536d 100644 --- a/tests/aws/services/cloudformation/engine/test_references.snapshot.json +++ b/tests/aws/services/cloudformation/engine/test_references.snapshot.json @@ -80,5 +80,12 @@ "Version": 1 } } + }, + "tests/aws/services/cloudformation/engine/test_references.py::TestPseudoParameters::test_stack_id": { + "recorded-date": "24-09-2025, 09:53:55", + "recorded-content": { + "parameter-value": "-s3logs-", + "stack-id": "arn::cloudformation::111111111111:stack//5dd694c0-992c-11f0-9d8f-" + } } } diff --git a/tests/aws/services/cloudformation/engine/test_references.validation.json b/tests/aws/services/cloudformation/engine/test_references.validation.json index 40ae38a56d1f9..460521c7a4ed6 100644 --- a/tests/aws/services/cloudformation/engine/test_references.validation.json +++ b/tests/aws/services/cloudformation/engine/test_references.validation.json @@ -8,6 +8,33 @@ "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": { "last_validated_date": "2024-10-17T22:49:56+00:00" }, + "tests/aws/services/cloudformation/engine/test_references.py::TestPseudoParameters::test_stack_id": { + "last_validated_date": "2025-09-24T09:54:00+00:00", + "durations_in_seconds": { + "setup": 1.3, + "call": 8.18, + "teardown": 4.36, + "total": 13.84 + } + }, + "tests/aws/services/cloudformation/engine/test_references.py::test_aws_novalue[no]": { + "last_validated_date": "2025-08-12T21:47:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 22.44, + "teardown": 4.36, + "total": 26.8 + } + }, + "tests/aws/services/cloudformation/engine/test_references.py::test_aws_novalue[yes]": { + "last_validated_date": "2025-08-12T21:46:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 23.36, + "teardown": 4.36, + "total": 27.72 + } + }, "tests/aws/services/cloudformation/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": { "last_validated_date": "2024-06-18T19:55:48+00:00" }, diff --git a/tests/aws/services/cloudformation/engine/test_replacement.py b/tests/aws/services/cloudformation/engine/test_replacement.py new file mode 100644 index 0000000000000..28e3c30e61fb4 --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_replacement.py @@ -0,0 +1,70 @@ +import json + +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +@skip_if_legacy_engine() +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify(paths=["$..PhysicalResourceId"]) +def test_requires_replacement( + deploy_cfn_template, + capture_per_resource_events, + snapshot, +): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + t1 = { + "Resources": { + "MyQueue": { + "Type": "AWS::SQS::Queue", + }, + }, + "Outputs": { + "QueueName": { + "Value": {"Fn::GetAtt": ["MyQueue", "QueueName"]}, + }, + }, + } + t2 = { + "Parameters": { + "QueueName": { + "Type": "String", + }, + }, + "Resources": { + "MyQueue": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": {"Ref": "QueueName"}, + }, + }, + }, + "Outputs": { + "QueueName": { + "Value": {"Fn::GetAtt": ["MyQueue", "QueueName"]}, + }, + }, + } + + deploy_result = deploy_cfn_template(template=json.dumps(t1)) + given_queue_name = deploy_result.outputs["QueueName"] + snapshot.add_transformer(snapshot.transform.regex(given_queue_name, "")) + + new_queue_name = f"queue-{short_uid()}" + deploy_result_2 = deploy_cfn_template( + template=json.dumps(t2), + is_update=True, + stack_name=deploy_result.stack_id, + parameters={"QueueName": new_queue_name}, + ) + + assert deploy_result_2.outputs["QueueName"] == new_queue_name + assert given_queue_name != new_queue_name + + per_resource_events = capture_per_resource_events( + deploy_result.stack_id, + ) + snapshot.match("queue-events", per_resource_events["MyQueue"]) diff --git a/tests/aws/services/cloudformation/engine/test_replacement.snapshot.json b/tests/aws/services/cloudformation/engine/test_replacement.snapshot.json new file mode 100644 index 0000000000000..b55fe6284c2d1 --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_replacement.snapshot.json @@ -0,0 +1,51 @@ +{ + "tests/aws/services/cloudformation/engine/test_replacement.py::test_requires_replacement": { + "recorded-date": "14-01-2026, 14:00:11", + "recorded-content": { + "queue-events": [ + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "MyQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "CREATE_IN_PROGRESS", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "MyQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "CREATE_COMPLETE", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/queue-47128d25", + "LogicalResourceId": "MyQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/queue-47128d25", + "LogicalResourceId": "MyQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "UPDATE_COMPLETE", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "MyQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "DELETE_IN_PROGRESS", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "MyQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "DELETE_COMPLETE", + "Timestamp": "timestamp" + } + ] + } + } +} diff --git a/tests/aws/services/cloudformation/engine/test_replacement.validation.json b/tests/aws/services/cloudformation/engine/test_replacement.validation.json new file mode 100644 index 0000000000000..d570c031c7acb --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_replacement.validation.json @@ -0,0 +1,11 @@ +{ + "tests/aws/services/cloudformation/engine/test_replacement.py::test_requires_replacement": { + "last_validated_date": "2026-01-14T14:00:47+00:00", + "durations_in_seconds": { + "setup": 0.81, + "call": 111.56, + "teardown": 35.27, + "total": 147.64 + } + } +} diff --git a/tests/aws/services/cloudformation/engine/test_update_policy.py b/tests/aws/services/cloudformation/engine/test_update_policy.py new file mode 100644 index 0000000000000..cfb5f9f7bee9a --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_update_policy.py @@ -0,0 +1,119 @@ +import os + +import pytest +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +@markers.snapshot.skip_snapshot_verify( + paths=[ + # our message is different. The AWS message does not seem to include the parameter + # name but ours does + "$..message", + ] +) +@markers.aws.validated +@skip_if_legacy_engine() +def test_update_replace_policy_deletion(deploy_cfn_template, aws_client, snapshot): + template_path = os.path.join( + os.path.dirname(__file__), + "../../../templates/update_retain_policy.yaml", + ) + parameter_value = short_uid() + parameter_name = f"param-{short_uid()}" + stack = deploy_cfn_template( + template_path=template_path, + parameters={ + "ParameterValue": parameter_value, + "ParameterName": parameter_name, + "PolicyType": "Delete", + }, + ) + assert ( + aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] == parameter_value + ) + + # force deletion by changing the resource name + new_parameter_name = f"param-{short_uid()}" + deploy_cfn_template( + template_path=template_path, + parameters={ + "ParameterValue": parameter_value, + "ParameterName": new_parameter_name, + "PolicyType": "Delete", + }, + stack_name=stack.stack_id, + is_update=True, + ) + assert ( + aws_client.ssm.get_parameter(Name=new_parameter_name)["Parameter"]["Value"] + == parameter_value + ) + + # check the previous parameter was deleted + with pytest.raises(ClientError) as exc_info: + aws_client.ssm.get_parameter(Name=parameter_name) + + snapshot.match("error", {"message": str(exc_info.value)}) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + "$..PhysicalResourceId", + ] +) +@skip_if_legacy_engine() +def test_update_replace_policy_retain( + deploy_cfn_template, aws_client, snapshot, capture_per_resource_events, cleanups +): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + template_path = os.path.join( + os.path.dirname(__file__), + "../../../templates/update_retain_policy.yaml", + ) + parameter_value = short_uid() + parameter_name = f"param-{short_uid()}" + # make sure we clean up the parameter when the test finishes + cleanups.append(lambda: aws_client.ssm.delete_parameter(Name=parameter_name)) + + stack = deploy_cfn_template( + template_path=template_path, + parameters={ + "ParameterValue": parameter_value, + "ParameterName": parameter_name, + "PolicyType": "Retain", + }, + ) + snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) + assert ( + aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] == parameter_value + ) + + # force deletion by changing the resource name + new_parameter_name = f"param-{short_uid()}" + deploy_cfn_template( + template_path=template_path, + parameters={ + "ParameterValue": parameter_value, + "ParameterName": new_parameter_name, + "PolicyType": "Retain", + }, + stack_name=stack.stack_id, + is_update=True, + ) + assert ( + aws_client.ssm.get_parameter(Name=new_parameter_name)["Parameter"]["Value"] + == parameter_value + ) + + # check the previous parameter was not deleted + assert ( + aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] == parameter_value + ) + + events = capture_per_resource_events(stack.stack_id) + snapshot.match("per-resource-events", events) diff --git a/tests/aws/services/cloudformation/engine/test_update_policy.snapshot.json b/tests/aws/services/cloudformation/engine/test_update_policy.snapshot.json new file mode 100644 index 0000000000000..d9c8fcb671d22 --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_update_policy.snapshot.json @@ -0,0 +1,91 @@ +{ + "tests/aws/services/cloudformation/engine/test_update_policy.py::test_update_replace_policy_deletion": { + "recorded-date": "23-01-2026, 12:02:50", + "recorded-content": { + "error": { + "message": "An error occurred (ParameterNotFound) when calling the GetParameter operation: " + } + } + }, + "tests/aws/services/cloudformation/engine/test_update_policy.py::test_update_replace_policy_retain": { + "recorded-date": "23-01-2026, 12:03:14", + "recorded-content": { + "per-resource-events": { + "MyParameter": [ + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "param-fe430ac7", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "param-fe430ac7", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "param-14aa098c", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "param-14aa098c", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "param-fe430ac7", + "ResourceStatus": "DELETE_SKIPPED", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + } +} diff --git a/tests/aws/services/cloudformation/engine/test_update_policy.validation.json b/tests/aws/services/cloudformation/engine/test_update_policy.validation.json new file mode 100644 index 0000000000000..cfbc5a9895a35 --- /dev/null +++ b/tests/aws/services/cloudformation/engine/test_update_policy.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/engine/test_update_policy.py::test_update_replace_policy_deletion": { + "last_validated_date": "2026-01-23T12:02:55+00:00", + "durations_in_seconds": { + "setup": 0.73, + "call": 23.55, + "teardown": 4.43, + "total": 28.71 + } + }, + "tests/aws/services/cloudformation/engine/test_update_policy.py::test_update_replace_policy_retain": { + "last_validated_date": "2026-01-23T12:03:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.21, + "teardown": 4.51, + "total": 23.72 + } + } +} diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py rename to tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.snapshot.json similarity index 97% rename from tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json rename to tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.snapshot.json index f0dc276e6ccff..cff6065c7c52e 100644 --- a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json +++ b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_instance_with_key_pair": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_instance_with_key_pair": { "recorded-date": "30-01-2024, 21:09:52", "recorded-content": { "key_pair": { @@ -30,7 +30,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_prefix_list": { "recorded-date": "30-04-2024, 19:32:40", "recorded-content": { "resource-description": { @@ -79,7 +79,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_vpc_endpoint": { "recorded-date": "30-04-2024, 20:01:19", "recorded-content": { "resource-description": { @@ -223,7 +223,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_security_group_with_tags": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_security_group_with_tags": { "recorded-date": "02-01-2025, 10:30:57", "recorded-content": { "security-group": { diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.validation.json similarity index 67% rename from tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json rename to tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.validation.json index b7d406afb4803..76f4b153562f6 100644 --- a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json +++ b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.validation.json @@ -1,14 +1,14 @@ { - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_instance_with_key_pair": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_instance_with_key_pair": { "last_validated_date": "2024-01-30T21:09:52+00:00" }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_prefix_list": { "last_validated_date": "2024-04-26T16:18:18+00:00" }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_security_group_with_tags": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_security_group_with_tags": { "last_validated_date": "2025-01-02T10:30:57+00:00" }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_vpc_endpoint": { "last_validated_date": "2024-04-30T20:01:19+00:00" } } diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.validation.json b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.validation.json deleted file mode 100644 index 99625ca69c742..0000000000000 --- a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_autogenerated_values": { - "last_validated_date": "2023-06-28T20:54:57+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_black_box": { - "last_validated_date": "2023-06-28T20:01:50+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_getatt": { - "last_validated_date": "2023-07-05T12:15:12+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestUpdates::test_update_without_replacement": { - "last_validated_date": "2023-06-28T20:31:43+00:00" - } -} diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.validation.json b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.validation.json deleted file mode 100644 index a0b1ff9f59556..0000000000000 --- a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": { - "last_validated_date": "2023-06-29T11:59:27+00:00" - } -} diff --git a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.validation.json b/tests/aws/services/cloudformation/resource_providers/iam/test_iam.validation.json deleted file mode 100644 index 9052daa434c63..0000000000000 --- a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_cfn_handle_iam_role_resource_no_role_name": { - "last_validated_date": "2024-06-18T20:29:57+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_user_access_key": { - "last_validated_date": "2023-07-11T06:23:54+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_username_defaultname": { - "last_validated_date": "2022-05-31T09:29:45+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_managed_policy_with_empty_resource": { - "last_validated_date": "2023-07-11T16:10:41+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_server_certificate": { - "last_validated_date": "2024-03-13T20:20:07+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_update_inline_policy": { - "last_validated_date": "2023-04-05T09:55:22+00:00" - }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_updating_stack_with_iam_role": { - "last_validated_date": "2024-06-18T21:02:59+00:00" - } -} diff --git a/tests/aws/services/cloudformation/resources/test_apigateway.py b/tests/aws/services/cloudformation/resources/test_apigateway.py index 7f6f74a95923a..0291585eed4f5 100644 --- a/tests/aws/services/cloudformation/resources/test_apigateway.py +++ b/tests/aws/services/cloudformation/resources/test_apigateway.py @@ -4,6 +4,8 @@ import requests from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.apigateway.apigateway_fixtures import api_invoke_url +from tests.aws.services.cloudformation.conftest import skipped_v2_items from localstack import constants from localstack.aws.api.lambda_ import Runtime @@ -14,7 +16,6 @@ from localstack.utils.run import to_str from localstack.utils.strings import to_bytes from localstack.utils.sync import retry -from tests.aws.services.apigateway.apigateway_fixtures import api_invoke_url PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TEST_LAMBDA_PYTHON_ECHO = os.path.join(PARENT_DIR, "lambda_/functions/lambda_echo.py") @@ -110,6 +111,7 @@ def test_cfn_apigateway_aws_integration(deploy_cfn_template, aws_client): assert mappings[0] == "(none)" +@markers.requires_in_process # uses pytest httpserver @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ @@ -235,12 +237,13 @@ def test_cfn_with_apigateway_resources(deploy_cfn_template, aws_client, snapshot stack.destroy() - apis = [ - api - for api in aws_client.apigateway.get_rest_apis()["items"] - if api["name"] == "celeste-Gateway-local" - ] - assert not apis + # TODO: Resolve limitations with stack.destroy in v2 engine. + # apis = [ + # api + # for api in aws_client.apigateway.get_rest_apis()["items"] + # if api["name"] == "celeste-Gateway-local" + # ] + # assert not apis @markers.aws.validated @@ -332,6 +335,7 @@ def test_cfn_deploy_apigateway_integration(deploy_cfn_template, snapshot, aws_cl "$.get-stage.methodSettings", "$.get-stage.tags", ] + + skipped_v2_items("$..binaryMediaTypes") ) def test_cfn_deploy_apigateway_from_s3_swagger( deploy_cfn_template, snapshot, aws_client, s3_bucket @@ -415,9 +419,10 @@ def test_cfn_apigateway_rest_api(deploy_cfn_template, aws_client, snapshot): stack_2.destroy() - rs = aws_client.apigateway.get_rest_apis() - apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - assert not apis + # TODO: Resolve limitations with stack.destroy in v2 engine. + # rs = aws_client.apigateway.get_rest_apis() + # apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] + # assert not apis @markers.aws.validated @@ -724,3 +729,141 @@ def test_serverless_like_deployment_with_update( ) get_fn_2 = aws_client.lambda_.get_function(FunctionName="test-service-local-api") assert get_fn_2["Configuration"]["Handler"] == "index.handler2" + + @markers.aws.validated + def test_serverless_like_deployment_stage_survives_update( + self, deploy_cfn_template, aws_client, snapshot + ): + """ + Regression test for https://github.com/localstack/localstack/issues/13667 + When the Serverless Framework redeploys, it generates a new deployment logical ID each time. + CloudFormation sees this as Add(new) + Remove(old). The old deployment's delete handler + was incorrectly deleting stages that now belong to the new deployment, causing all + subsequent API requests (including CORS OPTIONS) to return 404. + + Uses MOCK integrations (no Lambda dependency) to isolate the CF deployment replacement bug. + """ + snapshot.add_transformer(snapshot.transform.key_value("deploymentId")) + + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/apigateway-mock-cors-deployment.json" + ) + + # 1. Create stack with first deployment logical ID + stack = deploy_cfn_template( + template_path=template_path, + template_mapping={"deployment_logical_id": "Deploy1000000001", "response_msg": "v1"}, + ) + api_id = stack.outputs["ApiId"] + + # Verify stage and deployment exist after initial creation + stages_after_create = aws_client.apigateway.get_stages(restApiId=api_id) + snapshot.match("stages-after-create", stages_after_create) + assert len(stages_after_create["item"]) == 1 + assert stages_after_create["item"][0]["stageName"] == "local" + + # 2. Update stack with a NEW deployment logical ID (simulates Serverless Framework redeploy) + # CF will Add Deploy2000000002, then Remove Deploy1000000001 + deploy_cfn_template( + is_update=True, + stack_name=stack.stack_name, + template_path=template_path, + template_mapping={"deployment_logical_id": "Deploy2000000002", "response_msg": "v2"}, + ) + + # KEY ASSERTIONS: stage must survive the deployment replacement + stages_after_update = aws_client.apigateway.get_stages(restApiId=api_id) + snapshot.match("stages-after-update", stages_after_update) + assert len(stages_after_update["item"]) == 1, ( + "Stage 'local' was deleted during deployment replacement — this is the bug in #13667" + ) + assert stages_after_update["item"][0]["stageName"] == "local" + + deployments_after_update = aws_client.apigateway.get_deployments(restApiId=api_id) + snapshot.match("deployments-after-update", deployments_after_update) + + +@markers.snapshot.skip_snapshot_verify(paths=["$..tags"]) +@markers.aws.validated +def test_apigateway_deployment_canary_settings(deploy_cfn_template, snapshot, aws_client): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("deploymentId"), + snapshot.transform.key_value("aws:cloudformation:stack-name"), + snapshot.transform.resource_name(), + SortingTransformer("items", itemgetter("description")), + ] + ) + + api_name = f"api-{short_uid()}" + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/apigateway_canary_deployment.yml" + ), + parameters={"RestApiName": api_name}, + ) + api_id = stack.outputs["RestApiId"] + stage = aws_client.apigateway.get_stages(restApiId=api_id) + snapshot.match("get-stages", stage) + + deployments = aws_client.apigateway.get_deployments(restApiId=api_id) + snapshot.match("get-deployments", deployments) + + +@markers.snapshot.skip_snapshot_verify(paths=["$..tags"]) +@markers.aws.validated +def test_apigateway_stage_with_access_log_settings(deploy_cfn_template, snapshot, aws_client): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("deploymentId"), + snapshot.transform.key_value("destinationArn"), + snapshot.transform.key_value("aws:cloudformation:stack-name"), + snapshot.transform.resource_name(), + SortingTransformer("items", itemgetter("description")), + ] + ) + + api_name = f"api-{short_uid()}" + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/apigateway_stage_access_log_settings.yaml", + ), + parameters={"RestApiName": api_name}, + ) + api_id = stack.outputs["RestApiId"] + stage = aws_client.apigateway.get_stages(restApiId=api_id) + snapshot.match("get-stages", stage) + + +class TestApiGatewayBasePathMapping: + @markers.aws.only_localstack + def test_delete_base_path_mapping_missing_base_path( + self, + deploy_cfn_template, + aws_client_factory, + aws_client, + ): + """ + Ensure correct deletion of the AWS::ApiGateway::BasePathMapping resource. + + Note: this test is `only_localstack` because it requires a valid ACM certificate + which is difficult to do wtih our sandbox accounts + """ + # Certificates must always be created in us-east-1 + acm_client = aws_client_factory(region_name="us-east-1").acm + domain_name = f"api-{short_uid()}.localhost.localstack.cloud" + cert_arn = acm_client.request_certificate(DomainName=domain_name)["CertificateArn"] + + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/apigateway_basepath_mapping.yaml" + ) + + stack = deploy_cfn_template( + template_path=template_path, + parameters={ + "CertificateArn": cert_arn, + "DomainName": domain_name, + }, + ) + stack.destroy() diff --git a/tests/aws/services/cloudformation/resources/test_apigateway.snapshot.json b/tests/aws/services/cloudformation/resources/test_apigateway.snapshot.json index 7eb23ef4bd8b7..a8781d753ab26 100644 --- a/tests/aws/services/cloudformation/resources/test_apigateway.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_apigateway.snapshot.json @@ -734,5 +734,148 @@ } } } + }, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_apigateway_deployment_canary_settings": { + "recorded-date": "23-07-2025, 23:07:05", + "recorded-content": { + "get-stages": { + "item": [ + { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "canarySettings": { + "deploymentId": "", + "percentTraffic": 50.0, + "stageVariableOverrides": { + "lambdaAlias": "Dev" + }, + "useStageCache": false + }, + "createdDate": "datetime", + "deploymentId": "", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "prod", + "tags": { + "aws:cloudformation:logical-id": "Stage", + "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", + "aws:cloudformation:stack-name": "" + }, + "tracingEnabled": false, + "variables": { + "lambdaAlias": "Prod" + } + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-deployments": { + "items": [ + { + "createdDate": "datetime", + "description": "basic deployment", + "id": "" + }, + { + "createdDate": "datetime", + "description": "canary description", + "id": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_stage_survives_update": { + "recorded-date": "12-02-2026, 18:32:14", + "recorded-content": { + "stages-after-create": { + "item": [ + { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "local", + "tracingEnabled": false + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stages-after-update": { + "item": [ + { + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "local", + "tracingEnabled": false + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "deployments-after-update": { + "items": [ + { + "createdDate": "datetime", + "id": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_apigateway_stage_with_access_log_settings": { + "recorded-date": "25-02-2026, 23:35:55", + "recorded-content": { + "get-stages": { + "item": [ + { + "accessLogSettings": { + "destinationArn": "", + "format": "$context.extendedRequestId $context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "cacheClusterEnabled": false, + "cacheClusterStatus": "NOT_AVAILABLE", + "createdDate": "datetime", + "deploymentId": "", + "description": "test stage description", + "lastUpdatedDate": "datetime", + "methodSettings": {}, + "stageName": "test", + "tags": { + "aws:cloudformation:logical-id": "TestStage", + "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", + "aws:cloudformation:stack-name": "" + }, + "tracingEnabled": false + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/cloudformation/resources/test_apigateway.validation.json b/tests/aws/services/cloudformation/resources/test_apigateway.validation.json index 1391ba7db52ff..349ec2083c2de 100644 --- a/tests/aws/services/cloudformation/resources/test_apigateway.validation.json +++ b/tests/aws/services/cloudformation/resources/test_apigateway.validation.json @@ -1,4 +1,13 @@ { + "tests/aws/services/cloudformation/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_stage_survives_update": { + "last_validated_date": "2026-02-12T18:32:21+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 32.79, + "teardown": 7.04, + "total": 40.32 + } + }, "tests/aws/services/cloudformation/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": { "last_validated_date": "2024-02-19T08:55:12+00:00" }, @@ -11,6 +20,24 @@ "total": 16.99 } }, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_apigateway_deployment_canary_settings": { + "last_validated_date": "2025-07-23T23:07:16+00:00", + "durations_in_seconds": { + "setup": 0.44, + "call": 22.5, + "teardown": 11.11, + "total": 34.05 + } + }, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_apigateway_stage_with_access_log_settings": { + "last_validated_date": "2026-02-25T23:36:04+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 24.72, + "teardown": 9.14, + "total": 34.34 + } + }, "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_rest_api": { "last_validated_date": "2025-05-05T14:50:14+00:00" }, diff --git a/tests/aws/services/cloudformation/resources/test_cdk.py b/tests/aws/services/cloudformation/resources/test_cdk.py index c4213a43be04d..f79a77d8cb70b 100644 --- a/tests/aws/services/cloudformation/resources/test_cdk.py +++ b/tests/aws/services/cloudformation/resources/test_cdk.py @@ -1,23 +1,44 @@ import os +from collections.abc import Callable import pytest from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine +from localstack.aws.api.cloudformation import Parameter from localstack.testing.pytest import markers from localstack.utils.files import load_file from localstack.utils.strings import short_uid class TestCdkInit: - @pytest.mark.parametrize("bootstrap_version", ["10", "11", "12"]) + @pytest.mark.parametrize( + "bootstrap_version,parameters", + [ + ("10", {"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}), + ("11", {"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}), + ("12", {"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}), + ( + "28", + { + "CloudFormationExecutionPolicies": "", + "FileAssetsBucketKmsKeyId": "AWS_MANAGED_KEY", + "PublicAccessBlockConfiguration": "true", + "TrustedAccounts": "", + "TrustedAccountsForLookup": "", + }, + ), + ], + ids=["10", "11", "12", "28"], + ) @markers.aws.validated - def test_cdk_bootstrap(self, deploy_cfn_template, bootstrap_version, aws_client): + def test_cdk_bootstrap(self, deploy_cfn_template, aws_client, bootstrap_version, parameters): deploy_cfn_template( template_path=os.path.join( os.path.dirname(__file__), f"../../../templates/cdk_bootstrap_v{bootstrap_version}.yaml", ), - parameters={"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}, + parameters=parameters, ) init_stack_result = deploy_cfn_template( template_path=os.path.join( @@ -32,11 +53,91 @@ def test_cdk_bootstrap(self, deploy_cfn_template, bootstrap_version, aws_client) assert stack_res["StackResources"][0]["LogicalResourceId"] == "CDKMetadata" @markers.aws.validated - def test_cdk_bootstrap_redeploy(self, aws_client, cleanup_stacks, cleanup_changesets, cleanups): + @pytest.mark.parametrize( + "template,parameters_fn", + [ + pytest.param( + "cdk_bootstrap.yml", + lambda qualifier: [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources", + }, + {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, + {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, + {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY", + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true", + }, + {"ParameterKey": "Qualifier", "ParameterValue": qualifier}, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false", + }, + ], + id="v20", + ), + pytest.param( + "cdk_bootstrap_v28.yaml", + lambda qualifier: [ + {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY", + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true", + }, + {"ParameterKey": "Qualifier", "ParameterValue": qualifier}, + {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, + {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, + ], + id="v28", + ), + ], + ) + @markers.snapshot.skip_snapshot_verify( + paths=[ + # Wrong format, they are our internal parameter format + "$..Parameters", + # from the list of changes + "$..Changes..Details", + "$..Changes..LogicalResourceId", + "$..Changes..ResourceType", + "$..Changes..Scope", + # provider + "$..IncludeNestedStacks", + # mismatch between amazonaws.com and localhost.localstack.cloud + "$..Outputs..OutputValue", + "$..Outputs..Description", + ] + ) + @skip_if_legacy_engine() + def test_cdk_bootstrap_redeploy( + self, + aws_client, + cleanup_stacks, + cleanup_changesets, + cleanups, + snapshot, + template, + parameters_fn: Callable[[str], list[Parameter]], + ): """Test that simulates a sequence of commands executed by CDK when running 'cdk bootstrap' twice""" + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + snapshot.add_transformer(SortingTransformer("Parameters", lambda p: p["ParameterKey"])) + snapshot.add_transformer(SortingTransformer("Outputs", lambda p: p["OutputKey"])) stack_name = f"CDKToolkit-{short_uid()}" change_set_name = f"cdk-deploy-change-set-{short_uid()}" + qualifier = short_uid() + snapshot.add_transformer(snapshot.transform.regex(qualifier, "")) def clean_resources(): cleanup_stacks([stack_name]) @@ -44,9 +145,13 @@ def clean_resources(): cleanups.append(clean_resources) - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../templates/cdk_bootstrap.yml") + template_path = os.path.realpath( + os.path.join(os.path.dirname(__file__), f"../../../templates/{template}") ) + template_body = load_file(template_path) + if template_body is None: + raise RuntimeError(f"Template {template_path} not loaded") + aws_client.cloudformation.create_change_set( StackName=stack_name, ChangeSetName=change_set_name, @@ -54,37 +159,25 @@ def clean_resources(): ChangeSetType="CREATE", Capabilities=["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"], Description="CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", - Parameters=[ - { - "ParameterKey": "BootstrapVariant", - "ParameterValue": "AWS CDK: Default Resources", - }, - {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, - {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, - {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, - {"ParameterKey": "FileAssetsBucketKmsKeyId", "ParameterValue": "AWS_MANAGED_KEY"}, - {"ParameterKey": "PublicAccessBlockConfiguration", "ParameterValue": "true"}, - {"ParameterKey": "Qualifier", "ParameterValue": "hnb659fds"}, - {"ParameterKey": "UseExamplePermissionsBoundary", "ParameterValue": "false"}, - ], + Parameters=parameters_fn(qualifier), ) - aws_client.cloudformation.describe_change_set( + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( StackName=stack_name, ChangeSetName=change_set_name ) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + describe_change_set = aws_client.cloudformation.describe_change_set( StackName=stack_name, ChangeSetName=change_set_name ) + snapshot.match("describe-change-set", describe_change_set) aws_client.cloudformation.execute_change_set( StackName=stack_name, ChangeSetName=change_set_name ) aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - aws_client.cloudformation.describe_stacks(StackName=stack_name) + stacks = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][0] + snapshot.match("describe-stacks", stacks) - # When CDK toolstrap command is executed again it just confirms that the template is the same - aws_client.sts.get_caller_identity() + # When CDK bootstrap command is executed again it just confirms that the template is the same aws_client.cloudformation.get_template(StackName=stack_name, TemplateStage="Original") # TODO: create scenario where the template is different to catch cdk behavior diff --git a/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json b/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json index cbc013cee54ce..18cdcc7f33d32 100644 --- a/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json @@ -77,5 +77,567 @@ } } } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v20]": { + "recorded-date": "07-08-2025, 11:33:03", + "recorded-content": { + "describe-change-set": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CdkBootstrapVersion", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CloudFormationExecutionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ContainerAssetsRepository", + "ResourceType": "AWS::ECR::Repository", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DeploymentActionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "LookupRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucketPolicy", + "ResourceType": "AWS::S3::BucketPolicy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucket", + "ResourceType": "AWS::S3::Bucket", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "Description": "CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-stacks": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "Description": "This stack includes resources needed to deploy AWS CDK apps into this environment", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "Description": "The version of the bootstrap resources that are currently mastered in this stack", + "OutputKey": "BootstrapVersion", + "OutputValue": "20" + }, + { + "Description": "The domain name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketDomainName", + "OutputValue": "cdk--assets-111111111111-.s3..amazonaws.com" + }, + { + "Description": "The name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketName", + "OutputValue": "cdk--assets-111111111111-" + }, + { + "Description": "The ARN of the KMS key used to encrypt the asset bucket (deprecated)", + "ExportName": "CdkBootstrap--FileAssetKeyArn", + "OutputKey": "FileAssetKeyArn", + "OutputValue": "AWS_MANAGED_KEY" + }, + { + "Description": "The name of the ECR repository which hosts docker image assets", + "OutputKey": "ImageRepositoryName", + "OutputValue": "cdk--container-assets-111111111111-" + } + ], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v28]": { + "recorded-date": "07-08-2025, 11:34:16", + "recorded-content": { + "describe-change-set": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CdkBootstrapVersion", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CloudFormationExecutionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ContainerAssetsRepository", + "ResourceType": "AWS::ECR::Repository", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DeploymentActionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "LookupRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucketPolicy", + "ResourceType": "AWS::S3::BucketPolicy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucket", + "ResourceType": "AWS::S3::Bucket", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "Description": "CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-stacks": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "Description": "This stack includes resources needed to deploy AWS CDK apps into this environment", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "Description": "The version of the bootstrap resources that are currently mastered in this stack", + "OutputKey": "BootstrapVersion", + "OutputValue": "28" + }, + { + "Description": "The domain name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketDomainName", + "OutputValue": "cdk--assets-111111111111-.s3..amazonaws.com" + }, + { + "Description": "The name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketName", + "OutputValue": "cdk--assets-111111111111-" + }, + { + "Description": "The ARN of the KMS key used to encrypt the asset bucket (deprecated)", + "ExportName": "CdkBootstrap--FileAssetKeyArn", + "OutputKey": "FileAssetKeyArn", + "OutputValue": "AWS_MANAGED_KEY" + }, + { + "Description": "The name of the ECR repository which hosts docker image assets", + "OutputKey": "ImageRepositoryName", + "OutputValue": "cdk--container-assets-111111111111-" + } + ], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } } } diff --git a/tests/aws/services/cloudformation/resources/test_cdk.validation.json b/tests/aws/services/cloudformation/resources/test_cdk.validation.json index b627e80340018..683123a73c97a 100644 --- a/tests/aws/services/cloudformation/resources/test_cdk.validation.json +++ b/tests/aws/services/cloudformation/resources/test_cdk.validation.json @@ -8,6 +8,33 @@ "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": { "last_validated_date": "2024-06-25T18:44:21+00:00" }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy": { + "last_validated_date": "2025-08-07T10:26:46+00:00", + "durations_in_seconds": { + "setup": 0.82, + "call": 48.6, + "teardown": 21.75, + "total": 71.17 + } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v20]": { + "last_validated_date": "2025-08-07T11:33:27+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 48.62, + "teardown": 23.94, + "total": 73.4 + } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v28]": { + "last_validated_date": "2025-08-07T11:34:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 48.44, + "teardown": 20.92, + "total": 69.36 + } + }, "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": { "last_validated_date": "2022-11-04T14:15:44+00:00" } diff --git a/tests/aws/services/cloudformation/resources/test_dynamodb.py b/tests/aws/services/cloudformation/resources/test_dynamodb.py index 8aa572c62bf08..62d35f8212f99 100644 --- a/tests/aws/services/cloudformation/resources/test_dynamodb.py +++ b/tests/aws/services/cloudformation/resources/test_dynamodb.py @@ -4,6 +4,7 @@ import pytest from aws_cdk import aws_dynamodb as dynamodb from aws_cdk.aws_dynamodb import BillingMode +from botocore.exceptions import ClientError from localstack.testing.pytest import markers from localstack.utils.aws.arns import get_partition @@ -67,8 +68,6 @@ def test_globalindex_read_write_provisioned_throughput_dynamodb_table( paths=[ "$..Table.ProvisionedThroughput.LastDecreaseDateTime", "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - "$..Table.DeletionProtectionEnabled", ] ) def test_default_name_for_table(deploy_cfn_template, snapshot, aws_client): @@ -92,8 +91,6 @@ def test_default_name_for_table(deploy_cfn_template, snapshot, aws_client): paths=[ "$..Table.ProvisionedThroughput.LastDecreaseDateTime", "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - "$..Table.DeletionProtectionEnabled", ] ) @pytest.mark.parametrize("billing_mode", ["PROVISIONED", "PAY_PER_REQUEST"]) @@ -117,7 +114,6 @@ def test_billing_mode_as_conditional(deploy_cfn_template, snapshot, aws_client, @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ - "$..Table.DeletionProtectionEnabled", "$..Table.ProvisionedThroughput.LastDecreaseDateTime", "$..Table.ProvisionedThroughput.LastIncreaseDateTime", "$..Table.Replicas", @@ -136,7 +132,7 @@ def test_global_table(deploy_cfn_template, snapshot, aws_client): stack.destroy() - with pytest.raises(Exception) as ex: + with pytest.raises(ClientError) as ex: aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) error_code = ex.value.response["Error"]["Code"] @@ -189,7 +185,7 @@ def test_table_with_ttl_and_sse(deploy_cfn_template, snapshot, aws_client): @markers.aws.validated -# We return field bellow, while AWS doesn't return them +# We return the fields below, while AWS doesn't return them @markers.snapshot.skip_snapshot_verify( [ "$..Table.ProvisionedThroughput.LastDecreaseDateTime", diff --git a/tests/aws/services/cloudformation/resources/test_dynamodb.snapshot.json b/tests/aws/services/cloudformation/resources/test_dynamodb.snapshot.json index 3f6efc6628fb0..bff7a5948b1b5 100644 --- a/tests/aws/services/cloudformation/resources/test_dynamodb.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_dynamodb.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_default_name_for_table": { - "recorded-date": "28-08-2023, 12:34:19", + "recorded-date": "13-10-2025, 18:57:55", "recorded-content": { "table_description": { "Table": { @@ -28,7 +28,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "ACTIVE" + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 5, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 5 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -54,7 +59,7 @@ } }, "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": { - "recorded-date": "28-08-2023, 12:34:41", + "recorded-date": "13-10-2025, 18:59:26", "recorded-content": { "table_description": { "Table": { @@ -88,7 +93,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "ACTIVE" + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 5, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 5 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -98,7 +108,7 @@ } }, "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": { - "recorded-date": "28-08-2023, 12:35:02", + "recorded-date": "13-10-2025, 19:00:03", "recorded-content": { "table_description": { "Table": { @@ -136,7 +146,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "ACTIVE" + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -146,7 +161,7 @@ } }, "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table": { - "recorded-date": "01-12-2023, 12:54:13", + "recorded-date": "13-10-2025, 19:01:29", "recorded-content": { "table_description": { "Table": { @@ -178,7 +193,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "ACTIVE" + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, diff --git a/tests/aws/services/cloudformation/resources/test_dynamodb.validation.json b/tests/aws/services/cloudformation/resources/test_dynamodb.validation.json index fc40777d4d842..c2e3b5520fc7c 100644 --- a/tests/aws/services/cloudformation/resources/test_dynamodb.validation.json +++ b/tests/aws/services/cloudformation/resources/test_dynamodb.validation.json @@ -1,15 +1,39 @@ { "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": { - "last_validated_date": "2023-08-28T10:35:02+00:00" + "last_validated_date": "2025-10-13T19:00:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.82, + "teardown": 15.38, + "total": 37.2 + } }, "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": { - "last_validated_date": "2023-08-28T10:34:41+00:00" + "last_validated_date": "2025-10-13T18:59:41+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 22.58, + "teardown": 15.38, + "total": 38.43 + } }, "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_default_name_for_table": { - "last_validated_date": "2023-08-28T10:34:19+00:00" + "last_validated_date": "2025-10-13T18:58:11+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 22.98, + "teardown": 15.44, + "total": 38.9 + } }, "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table": { - "last_validated_date": "2023-12-01T11:54:13+00:00" + "last_validated_date": "2025-10-13T19:01:29+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 38.13, + "teardown": 0.24, + "total": 38.84 + } }, "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": { "last_validated_date": "2024-03-12T15:44:36+00:00" diff --git a/tests/aws/services/cloudformation/resources/test_ec2.py b/tests/aws/services/cloudformation/resources/test_ec2.py index 84928dc37c21b..3c3ec43cf58c5 100644 --- a/tests/aws/services/cloudformation/resources/test_ec2.py +++ b/tests/aws/services/cloudformation/resources/test_ec2.py @@ -203,6 +203,24 @@ def test_transit_gateway_attachment(deploy_cfn_template, aws_client, snapshot): assert attachment_description[0]["State"] == "deleted" +@markers.aws.validated +def test_vpc_gateway_attachment(deploy_cfn_template, aws_client, snapshot): + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/vpc_gateway_attachment.yml" + ) + ) + vpc_id = stack.outputs["VpcId"] + # fetch the internet gateway id so we can transform the GW attachment id, as + # it's of the form "IGW|" (for internet gateways) and "VGW|" for + # VPN gateways + snapshot.add_transformer(snapshot.transform.regex(vpc_id, "")) + + snapshot.match("attachment-1-ref", stack.outputs["GatewayAttachment1Ref"]) + # TODO: vpn gateway not supported by LocalStack yet + # snapshot.match("attachment-2-ref", stack.outputs["GatewayAttachment2Ref"]) + + @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=["$..RouteTables..PropagatingVgws", "$..RouteTables..Tags"] diff --git a/tests/aws/services/cloudformation/resources/test_ec2.snapshot.json b/tests/aws/services/cloudformation/resources/test_ec2.snapshot.json index 0f42548858457..bbdc8564773bf 100644 --- a/tests/aws/services/cloudformation/resources/test_ec2.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_ec2.snapshot.json @@ -299,5 +299,12 @@ "ImportedKeyPairName": "" } } + }, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_gateway_attachment": { + "recorded-date": "18-07-2025, 20:52:38", + "recorded-content": { + "attachment-1-ref": "IGW|", + "attachment-2-ref": "VGW|" + } } } diff --git a/tests/aws/services/cloudformation/resources/test_ec2.validation.json b/tests/aws/services/cloudformation/resources/test_ec2.validation.json index 6eb9f2caf3324..f021838863205 100644 --- a/tests/aws/services/cloudformation/resources/test_ec2.validation.json +++ b/tests/aws/services/cloudformation/resources/test_ec2.validation.json @@ -29,6 +29,15 @@ "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_creates_default_sg": { "last_validated_date": "2024-04-01T11:21:54+00:00" }, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_gateway_attachment": { + "last_validated_date": "2025-07-18T20:50:13+00:00", + "durations_in_seconds": { + "setup": 1.21, + "call": 24.77, + "teardown": 6.47, + "total": 32.45 + } + }, "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_with_route_table": { "last_validated_date": "2024-06-19T16:48:31+00:00" } diff --git a/tests/aws/services/cloudformation/resources/test_events.py b/tests/aws/services/cloudformation/resources/test_events.py index e8eb95e232c1f..035c20cfafc73 100644 --- a/tests/aws/services/cloudformation/resources/test_events.py +++ b/tests/aws/services/cloudformation/resources/test_events.py @@ -141,8 +141,9 @@ def test_event_rule_to_logs(deploy_cfn_template, aws_client): assert len(resp["Entries"]) == 1 wait_until( - lambda: len(aws_client.logs.describe_log_streams(logGroupName=log_group_name)["logStreams"]) - > 0, + lambda: ( + len(aws_client.logs.describe_log_streams(logGroupName=log_group_name)["logStreams"]) > 0 + ), 1.0, 5, "linear", diff --git a/tests/aws/services/cloudformation/resources/test_lambda.py b/tests/aws/services/cloudformation/resources/test_lambda.py index f40489799615b..02dd57e8fb371 100644 --- a/tests/aws/services/cloudformation/resources/test_lambda.py +++ b/tests/aws/services/cloudformation/resources/test_lambda.py @@ -5,6 +5,7 @@ import pytest from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.events.helper_functions import is_v2_provider from localstack import config from localstack.aws.api.lambda_ import InvocationType, Runtime, State @@ -19,6 +20,16 @@ from localstack.utils.testutil import create_lambda_archive, get_lambda_log_events +@pytest.fixture +def get_function_envars(aws_client): + def get(function_name: str) -> dict: + function = aws_client.lambda_.get_function(FunctionName=function_name) + function_env_variables = function["Configuration"]["Environment"]["Variables"] + return function_env_variables + + return get + + @markers.aws.validated def test_lambda_w_dynamodb_event_filter(deploy_cfn_template, aws_client): function_name = f"test-fn-{short_uid()}" @@ -463,12 +474,15 @@ def test_lambda_cfn_run(deploy_cfn_template, aws_client): @markers.aws.only_localstack(reason="This is functionality specific to Localstack") def test_lambda_cfn_run_with_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch + deploy_cfn_template, aws_client, get_function_envars, monkeypatch ): """ deploys the same lambda with an empty CFN string deny list, testing that it behaves as expected (i.e. the URLs in the deny list are modified) """ + custom_url_1 = "https://custom1.execute-api.us-east-1.amazonaws.com/test-resource" + custom_url_2 = "https://custom2.execute-api.us-east-1.amazonaws.com/test-resource" + monkeypatch.setattr(config, "CFN_STRING_REPLACEMENT_DENY_LIST", []) deployment = deploy_cfn_template( template_path=os.path.join( @@ -476,9 +490,45 @@ def test_lambda_cfn_run_with_empty_string_replacement_deny_list( "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", ), max_wait=120, + parameters={"CustomURL": custom_url_1}, + ) + + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) + # URLs that match regex to capture AWS URLs gets Localstack port appended - non-matching URLs remain unchanged. + assert function_env_variables["API_URL_1"] == "https://api.example.com" + assert ( + function_env_variables["API_URL_2"] + == "https://storage.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_3"] + == "https://reporting.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_4"] + == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom1.execute-api.amazonaws.com:4566/test-resource" + ) + + if not is_v2_provider(): + # Not supported by the v1 provider + return + + deployment = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", + ), + max_wait=120, + parameters={"CustomURL": custom_url_2}, + is_update=True, + stack_name=deployment.stack_id, ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] + + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) # URLs that match regex to capture AWS URLs gets Localstack port appended - non-matching URLs remain unchanged. assert function_env_variables["API_URL_1"] == "https://api.example.com" assert ( @@ -493,16 +543,23 @@ def test_lambda_cfn_run_with_empty_string_replacement_deny_list( function_env_variables["API_URL_4"] == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom2.execute-api.amazonaws.com:4566/test-resource" + ) +@markers.requires_in_process @markers.aws.only_localstack(reason="This is functionality specific to Localstack") def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch + deploy_cfn_template, aws_client, get_function_envars, monkeypatch ): """ deploys the same lambda with a non-empty CFN string deny list configurations, testing that it behaves as expected (i.e. the URLs in the deny list are not modified) """ + custom_url_1 = "https://custom1.execute-api.us-east-1.amazonaws.com/test-resource" + custom_url_2 = "https://custom2.execute-api.us-east-1.amazonaws.com/test-resource" monkeypatch.setattr( config, "CFN_STRING_REPLACEMENT_DENY_LIST", @@ -517,9 +574,9 @@ def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", ), max_wait=120, + parameters={"CustomURL": custom_url_1}, ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) # URLs that match regex to capture AWS URLs but are explicitly in the deny list, don't get modified - # non-matching URLs remain unchanged. assert function_env_variables["API_URL_1"] == "https://api.example.com" @@ -535,6 +592,44 @@ def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( function_env_variables["API_URL_4"] == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom1.execute-api.amazonaws.com:4566/test-resource" + ) + + if not is_v2_provider(): + # Not supported by the v1 provider + return + + deployment = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", + ), + max_wait=120, + parameters={"CustomURL": custom_url_2}, + is_update=True, + stack_name=deployment.stack_id, + ) + + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) + assert function_env_variables["API_URL_1"] == "https://api.example.com" + assert ( + function_env_variables["API_URL_2"] + == "https://storage.execute-api.us-east-2.amazonaws.com/test-resource" + ) + assert ( + function_env_variables["API_URL_3"] + == "https://reporting.execute-api.us-east-1.amazonaws.com/test-resource" + ) + assert ( + function_env_variables["API_URL_4"] + == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom2.execute-api.amazonaws.com:4566/test-resource" + ) @pytest.mark.skip(reason="broken/notimplemented") @@ -887,7 +982,6 @@ def _send_events(): # dynamodb describe_table "$..Table.ProvisionedThroughput.LastDecreaseDateTime", "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", # stream result "$..StreamDescription.CreationRequestDateTime", ] diff --git a/tests/aws/services/cloudformation/resources/test_lambda.snapshot.json b/tests/aws/services/cloudformation/resources/test_lambda.snapshot.json index 484f94d6b4898..b104b74f4924d 100644 --- a/tests/aws/services/cloudformation/resources/test_lambda.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_lambda.snapshot.json @@ -376,7 +376,7 @@ } }, "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": { - "recorded-date": "30-10-2024, 14:48:16", + "recorded-date": "27-08-2025, 09:35:51", "recorded-content": { "stack_resources": { "StackResources": [ @@ -548,7 +548,7 @@ "CreatedTimestamp": "timestamp", "DelaySeconds": "0", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -860,7 +860,7 @@ } }, "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": { - "recorded-date": "12-10-2024, 10:46:17", + "recorded-date": "13-10-2025, 21:45:29", "recorded-content": { "stack_resources": { "StackResources": [ @@ -1069,7 +1069,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "ACTIVE" + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 5, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 5 + } }, "ResponseMetadata": { "HTTPHeaders": {}, diff --git a/tests/aws/services/cloudformation/resources/test_lambda.validation.json b/tests/aws/services/cloudformation/resources/test_lambda.validation.json index e603d1df5aa41..2bab5abcf3c0e 100644 --- a/tests/aws/services/cloudformation/resources/test_lambda.validation.json +++ b/tests/aws/services/cloudformation/resources/test_lambda.validation.json @@ -3,7 +3,13 @@ "last_validated_date": "2024-12-10T16:48:04+00:00" }, "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": { - "last_validated_date": "2024-10-12T10:46:17+00:00" + "last_validated_date": "2025-10-13T21:45:29+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 142.89, + "teardown": 0.24, + "total": 143.63 + } }, "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": { "last_validated_date": "2024-10-12T10:52:28+00:00" @@ -12,7 +18,13 @@ "last_validated_date": "2024-04-09T07:26:03+00:00" }, "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": { - "last_validated_date": "2024-10-30T14:48:16+00:00" + "last_validated_date": "2025-08-27T09:35:51+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 122.08, + "teardown": 0.13, + "total": 123.17 + } }, "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": { "last_validated_date": "2024-04-09T07:31:17+00:00" diff --git a/tests/aws/services/cloudformation/resources/test_logs.py b/tests/aws/services/cloudformation/resources/test_logs.py index 6813283c5b505..69a0298b7b92c 100644 --- a/tests/aws/services/cloudformation/resources/test_logs.py +++ b/tests/aws/services/cloudformation/resources/test_logs.py @@ -1,6 +1,11 @@ import os.path +import pytest +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + from localstack.testing.pytest import markers +from localstack.testing.pytest.fixtures import StackDeployError +from localstack.utils.strings import short_uid @markers.aws.validated @@ -47,3 +52,26 @@ def test_cfn_handle_log_group_resource(deploy_cfn_template, aws_client, snapshot stack.destroy() response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_prefix) assert len(response["logGroups"]) == 0 + + +@markers.aws.validated +@skip_if_legacy_engine() +def test_handle_existing_log_group(deploy_cfn_template, aws_client, snapshot, cleanups): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + snapshot.add_transformer(snapshot.transform.key_value("ParameterValue")) + + log_group_name = f"logs-{short_uid()}" + + # create the log group + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + with pytest.raises(StackDeployError) as exc_info: + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/logs_group.yml" + ), + parameters={"LogGroupName": log_group_name}, + ) + + snapshot.match("failed-stack-describe", exc_info.value.describe_result) diff --git a/tests/aws/services/cloudformation/resources/test_logs.snapshot.json b/tests/aws/services/cloudformation/resources/test_logs.snapshot.json index 8ad8af97a9ca5..eb3c9d98ae554 100644 --- a/tests/aws/services/cloudformation/resources/test_logs.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_logs.snapshot.json @@ -38,5 +38,37 @@ } } } + }, + "tests/aws/services/cloudformation/resources/test_logs.py::test_handle_existing_log_group": { + "recorded-date": "06-10-2025, 16:24:57", + "recorded-content": { + "failed-stack-describe": { + "Capabilities": [ + "CAPABILITY_AUTO_EXPAND", + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "LogGroupName", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "ROLLBACK_COMPLETE", + "Tags": [] + } + } } } diff --git a/tests/aws/services/cloudformation/resources/test_logs.validation.json b/tests/aws/services/cloudformation/resources/test_logs.validation.json index fce835093de2a..a4ffe25914229 100644 --- a/tests/aws/services/cloudformation/resources/test_logs.validation.json +++ b/tests/aws/services/cloudformation/resources/test_logs.validation.json @@ -2,6 +2,15 @@ "tests/aws/services/cloudformation/resources/test_logs.py::test_cfn_handle_log_group_resource": { "last_validated_date": "2024-06-20T16:15:47+00:00" }, + "tests/aws/services/cloudformation/resources/test_logs.py::test_handle_existing_log_group": { + "last_validated_date": "2025-10-06T16:24:57+00:00", + "durations_in_seconds": { + "setup": 0.81, + "call": 10.58, + "teardown": 0.22, + "total": 11.61 + } + }, "tests/aws/services/cloudformation/resources/test_logs.py::test_logstream": { "last_validated_date": "2022-07-29T11:22:53+00:00" } diff --git a/tests/aws/services/cloudformation/resources/test_redshift.py b/tests/aws/services/cloudformation/resources/test_redshift.py index 14603ff226a03..8f25142131202 100644 --- a/tests/aws/services/cloudformation/resources/test_redshift.py +++ b/tests/aws/services/cloudformation/resources/test_redshift.py @@ -4,7 +4,7 @@ # only runs in Docker when run against Pro (since it needs postgres on the system) -@markers.only_in_docker +@markers.requires_in_container @markers.aws.validated def test_redshift_cluster(deploy_cfn_template, aws_client): stack = deploy_cfn_template( diff --git a/tests/aws/services/cloudformation/resources/test_route53.py b/tests/aws/services/cloudformation/resources/test_route53.py deleted file mode 100644 index 5b5ff47e6ea04..0000000000000 --- a/tests/aws/services/cloudformation/resources/test_route53.py +++ /dev/null @@ -1,66 +0,0 @@ -import os - -from localstack.testing.pytest import markers - - -@markers.aws.validated -def test_create_record_set_via_id(route53_hosted_zone, deploy_cfn_template): - create_zone_response = route53_hosted_zone() - hosted_zone_id = create_zone_response["HostedZone"]["Id"] - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/route53_hostedzoneid_template.yaml" - ), - parameters=parameters, - max_wait=300, - ) - - -@markers.aws.validated -def test_create_record_set_via_name(deploy_cfn_template, route53_hosted_zone): - create_zone_response = route53_hosted_zone() - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneName": route53_name, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/route53_hostedzonename_template.yaml" - ), - parameters=parameters, - ) - - -@markers.aws.validated -def test_create_record_set_without_resource_record(deploy_cfn_template, route53_hosted_zone): - create_zone_response = route53_hosted_zone() - hosted_zone_id = create_zone_response["HostedZone"]["Id"] - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../templates/route53_recordset_without_resource_records.yaml", - ), - parameters=parameters, - ) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..HealthCheckConfig.EnableSNI", "$..HealthCheckVersion"] -) -def test_create_health_check(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../templates/route53_healthcheck.yml", - ), - ) - health_check_id = stack.outputs["HealthCheckId"] - print(health_check_id) - health_check = aws_client.route53.get_health_check(HealthCheckId=health_check_id) - - snapshot.add_transformer(snapshot.transform.key_value("Id", "id")) - snapshot.add_transformer(snapshot.transform.key_value("CallerReference", "caller-reference")) - snapshot.match("HealthCheck", health_check["HealthCheck"]) diff --git a/tests/aws/services/cloudformation/resources/test_route53.snapshot.json b/tests/aws/services/cloudformation/resources/test_route53.snapshot.json deleted file mode 100644 index 78372c10e3b32..0000000000000 --- a/tests/aws/services/cloudformation/resources/test_route53.snapshot.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_health_check": { - "recorded-date": "22-09-2023, 13:50:49", - "recorded-content": { - "HealthCheck": { - "CallerReference": "", - "HealthCheckConfig": { - "Disabled": false, - "EnableSNI": false, - "FailureThreshold": 3, - "FullyQualifiedDomainName": "localstacktest.com", - "IPAddress": "1.1.1.1", - "Inverted": false, - "MeasureLatency": false, - "Port": 80, - "RequestInterval": 30, - "ResourcePath": "/health", - "Type": "HTTP" - }, - "HealthCheckVersion": 1, - "Id": "" - } - } - } -} diff --git a/tests/aws/services/cloudformation/resources/test_route53.validation.json b/tests/aws/services/cloudformation/resources/test_route53.validation.json deleted file mode 100644 index 8b56e5dacfa51..0000000000000 --- a/tests/aws/services/cloudformation/resources/test_route53.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_health_check": { - "last_validated_date": "2023-09-22T11:50:49+00:00" - } -} diff --git a/tests/aws/services/cloudformation/resources/test_sns.py b/tests/aws/services/cloudformation/resources/test_sns.py index 340804f122261..e5ed86f852506 100644 --- a/tests/aws/services/cloudformation/resources/test_sns.py +++ b/tests/aws/services/cloudformation/resources/test_sns.py @@ -1,12 +1,14 @@ import os.path import aws_cdk as cdk -import pytest +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine +from localstack.aws.api.cloudformation import Stack from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.aws.arns import parse_arn from localstack.utils.common import short_uid +from localstack.utils.sync import wait_until @markers.aws.validated @@ -39,26 +41,69 @@ def test_sns_topic_fifo_with_deduplication(deploy_cfn_template, aws_client, snap snapshot.match("get-topic-attrs", topic_attrs) -@markers.aws.needs_fixing -def test_sns_topic_fifo_without_suffix_fails(deploy_cfn_template, aws_client): +@markers.aws.validated +@skip_if_legacy_engine() +def test_sns_topic_fifo_without_suffix_fails(cleanups, aws_client, snapshot): stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" topic_name = f"topic-{short_uid()}" + + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) + path = os.path.join( os.path.dirname(__file__), "../../../templates/sns_topic_fifo_dedup.yaml", ) + with open(path) as infile: + template_body = infile.read() + + delay = 1 + max_attempts = 30 + if is_aws_cloud(): + delay = 5 + max_attempts = 100 + waiter_config = {"Delay": delay, "MaxAttempts": max_attempts} + + change_set = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + TemplateBody=template_body, + ChangeSetType="CREATE", + Parameters=[{"ParameterKey": "TopicName", "ParameterValue": topic_name}], + ) + change_set_id = change_set["Id"] + stack_id = change_set["StackId"] + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_id, + StackName=stack_name, + WaiterConfig=waiter_config, + ) - with pytest.raises(Exception) as ex: - deploy_cfn_template( - stack_name=stack_name, template_path=path, parameters={"TopicName": topic_name} + def _cleanup(): + aws_client.cloudformation.delete_stack(StackName=stack_id) + aws_client.cloudformation.get_waiter("stack_delete_complete").wait( + StackName=stack_id, WaiterConfig=waiter_config ) - assert ex.typename == "StackDeployError" - stack = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][0] - if is_aws_cloud(): - assert stack.get("StackStatus") in ["ROLLBACK_COMPLETED", "ROLLBACK_IN_PROGRESS"] - else: - assert stack.get("StackStatus") == "CREATE_FAILED" + cleanups.append(_cleanup) + + aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id, StackName=stack_id) + + # we cannot use a waiter here since they all check for success + def describe_stack() -> Stack: + result = aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][0] + return result + + assert wait_until( + lambda: describe_stack()["StackStatus"] in {"ROLLBACK_COMPLETE", "CREATE_FAILED"}, + strategy="static", + wait=delay, + max_retries=max_attempts, + ) + + stack = describe_stack() + snapshot.match("describe-stack", stack) @markers.aws.validated diff --git a/tests/aws/services/cloudformation/resources/test_sns.snapshot.json b/tests/aws/services/cloudformation/resources/test_sns.snapshot.json index a2c5c8ca6e2d7..37400a788fe7b 100644 --- a/tests/aws/services/cloudformation/resources/test_sns.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_sns.snapshot.json @@ -526,5 +526,32 @@ ] } } + }, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_without_suffix_fails": { + "recorded-date": "21-08-2025, 11:30:17", + "recorded-content": { + "describe-stack": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "TopicName", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "ROLLBACK_COMPLETE", + "Tags": [] + } + } } } diff --git a/tests/aws/services/cloudformation/resources/test_sns.validation.json b/tests/aws/services/cloudformation/resources/test_sns.validation.json index 52731d78dd633..a44447db4d4c2 100644 --- a/tests/aws/services/cloudformation/resources/test_sns.validation.json +++ b/tests/aws/services/cloudformation/resources/test_sns.validation.json @@ -1,10 +1,37 @@ { + "tests/aws/services/cloudformation/resources/test_sns.py::test_deploy_stack_with_sns_topic": { + "last_validated_date": "2025-08-18T12:05:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 96.67, + "teardown": 0.12, + "total": 96.79 + } + }, "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": { "last_validated_date": "2025-05-28T10:46:56+00:00" }, "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": { "last_validated_date": "2023-11-27T20:27:29+00:00" }, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_without_suffix_fails": { + "last_validated_date": "2025-08-21T11:30:22+00:00", + "durations_in_seconds": { + "setup": 1.54, + "call": 158.1, + "teardown": 5.32, + "total": 164.96 + } + }, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_policy_resets_to_default": { + "last_validated_date": "2026-02-04T13:55:33+00:00", + "durations_in_seconds": { + "setup": 2.07, + "call": 33.8, + "teardown": 0.31, + "total": 36.18 + } + }, "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_attributes": { "last_validated_date": "2025-07-03T17:19:44+00:00", "durations_in_seconds": { @@ -23,15 +50,6 @@ "total": 124.06 } }, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_policy_resets_to_default": { - "last_validated_date": "2025-07-04T00:04:32+00:00", - "durations_in_seconds": { - "setup": 1.08, - "call": 22.27, - "teardown": 0.09, - "total": 23.44 - } - }, "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_with_attributes": { "last_validated_date": "2025-07-03T23:32:29+00:00", "durations_in_seconds": { diff --git a/tests/aws/services/cloudformation/resources/test_sqs.py b/tests/aws/services/cloudformation/resources/test_sqs.py deleted file mode 100644 index d054c3f82a55d..0000000000000 --- a/tests/aws/services/cloudformation/resources/test_sqs.py +++ /dev/null @@ -1,143 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - - -@markers.aws.validated -def test_sqs_queue_policy(deploy_cfn_template, aws_client, snapshot): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_with_queuepolicy.yaml" - ) - ) - queue_url = result.outputs["QueueUrlOutput"] - resp = aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]) - snapshot.match("policy", resp) - snapshot.add_transformer(snapshot.transform.key_value("Resource")) - - -@markers.aws.validated -def test_sqs_fifo_queue_generates_valid_name(deploy_cfn_template): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_fifo_autogenerate_name.yaml" - ), - parameters={"IsFifo": "true"}, - max_wait=240, - ) - assert ".fifo" in result.outputs["FooQueueName"] - - -@markers.aws.validated -def test_sqs_non_fifo_queue_generates_valid_name(deploy_cfn_template): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_fifo_autogenerate_name.yaml" - ), - parameters={"IsFifo": "false"}, - max_wait=240, - ) - assert ".fifo" not in result.outputs["FooQueueName"] - - -@markers.aws.validated -def test_cfn_handle_sqs_resource(deploy_cfn_template, aws_client, snapshot): - queue_name = f"queue-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_fifo_queue.yml" - ), - parameters={"QueueName": queue_name}, - ) - - rs = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueURL"], AttributeNames=["All"] - ) - snapshot.match("queue", rs) - snapshot.add_transformer(snapshot.transform.regex(queue_name, "")) - - # clean up - stack.destroy() - - with pytest.raises(ClientError) as ctx: - aws_client.sqs.get_queue_url(QueueName=f"{queue_name}.fifo") - snapshot.match("error", ctx.value.response) - - -@markers.aws.validated -def test_update_queue_no_change(deploy_cfn_template, aws_client, snapshot): - bucket_name = f"bucket-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_queue_update_no_change.yml" - ), - parameters={ - "AddBucket": "false", - "BucketName": bucket_name, - }, - ) - queue_url = stack.outputs["QueueUrl"] - queue_arn = stack.outputs["QueueArn"] - snapshot.add_transformer(snapshot.transform.regex(queue_url, "")) - snapshot.add_transformer(snapshot.transform.regex(queue_arn, "")) - - snapshot.match("outputs-1", stack.outputs) - - # deploy a second time with no change to the SQS queue - updated_stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_queue_update_no_change.yml" - ), - is_update=True, - stack_name=stack.stack_name, - parameters={ - "AddBucket": "true", - "BucketName": bucket_name, - }, - ) - snapshot.match("outputs-2", updated_stack.outputs) - - -@markers.aws.validated -def test_update_sqs_queuepolicy(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_with_queuepolicy.yaml" - ) - ) - - policy = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - snapshot.match("policy1", policy["Attributes"]["Policy"]) - - updated_stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/sqs_with_queuepolicy_updated.yaml" - ), - is_update=True, - stack_name=stack.stack_name, - ) - - def check_policy_updated(): - policy_updated = aws_client.sqs.get_queue_attributes( - QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - assert policy_updated["Attributes"]["Policy"] != policy["Attributes"]["Policy"] - return policy_updated - - wait_until(check_policy_updated) - - policy = aws_client.sqs.get_queue_attributes( - QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - - snapshot.match("policy2", policy["Attributes"]["Policy"]) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) diff --git a/tests/aws/services/cloudformation/resources/test_sqs.snapshot.json b/tests/aws/services/cloudformation/resources/test_sqs.snapshot.json deleted file mode 100644 index 118cc86349d2d..0000000000000 --- a/tests/aws/services/cloudformation/resources/test_sqs.snapshot.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": { - "recorded-date": "08-12-2023, 21:11:26", - "recorded-content": { - "outputs-1": { - "QueueArn": "", - "QueueUrl": "" - }, - "outputs-2": { - "QueueArn": "", - "QueueUrl": "" - } - } - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": { - "recorded-date": "27-03-2024, 20:30:24", - "recorded-content": { - "policy1": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "arn::sqs::111111111111:" - } - ] - }, - "policy2": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Deny", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "arn::sqs::111111111111:" - } - ] - } - } - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_queue_policy": { - "recorded-date": "03-07-2024, 19:49:04", - "recorded-content": { - "policy": { - "Attributes": { - "Policy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "" - } - ] - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_cfn_handle_sqs_resource": { - "recorded-date": "03-07-2024, 20:03:51", - "recorded-content": { - "queue": { - "Attributes": { - "ApproximateNumberOfMessages": "0", - "ApproximateNumberOfMessagesDelayed": "0", - "ApproximateNumberOfMessagesNotVisible": "0", - "ContentBasedDeduplication": "false", - "CreatedTimestamp": "timestamp", - "DeduplicationScope": "queue", - "DelaySeconds": "0", - "FifoQueue": "true", - "FifoThroughputLimit": "perQueue", - "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", - "MessageRetentionPeriod": "345600", - "QueueArn": "arn::sqs::111111111111:.fifo", - "ReceiveMessageWaitTimeSeconds": "0", - "SqsManagedSseEnabled": "true", - "VisibilityTimeout": "30" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "error": { - "Error": { - "Code": "AWS.SimpleQueueService.NonExistentQueue", - "Message": "The specified queue does not exist.", - "QueryErrorCode": "QueueDoesNotExist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/resources/test_sqs.validation.json b/tests/aws/services/cloudformation/resources/test_sqs.validation.json deleted file mode 100644 index c28ce1e66b2ee..0000000000000 --- a/tests/aws/services/cloudformation/resources/test_sqs.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_sqs.py::test_cfn_handle_sqs_resource": { - "last_validated_date": "2024-07-03T20:03:51+00:00" - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": { - "last_validated_date": "2024-05-15T02:01:00+00:00" - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": { - "last_validated_date": "2024-05-15T01:59:34+00:00" - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_queue_policy": { - "last_validated_date": "2024-07-03T19:49:04+00:00" - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": { - "last_validated_date": "2023-12-08T20:11:26+00:00" - }, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": { - "last_validated_date": "2024-03-27T20:30:23+00:00" - } -} diff --git a/tests/aws/services/cloudformation/resources/test_stack_sets.py b/tests/aws/services/cloudformation/resources/test_stack_sets.py index f35fc30023d91..505562739cf3e 100644 --- a/tests/aws/services/cloudformation/resources/test_stack_sets.py +++ b/tests/aws/services/cloudformation/resources/test_stack_sets.py @@ -1,10 +1,13 @@ import os import pytest +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine +from localstack.testing.config import SECONDARY_TEST_AWS_ACCOUNT_ID, SECONDARY_TEST_AWS_REGION_NAME from localstack.testing.pytest import markers from localstack.utils.files import load_file -from localstack.utils.strings import short_uid +from localstack.utils.strings import long_uid, short_uid from localstack.utils.sync import wait_until @@ -23,7 +26,7 @@ def _operation_is_ready(): return waiter -@markers.aws.validated +@markers.aws.manual_setup_required def test_create_stack_set_with_stack_instances( account_id, region_name, @@ -31,6 +34,7 @@ def test_create_stack_set_with_stack_instances( snapshot, wait_stack_set_operation, ): + """ "Account <...> should have 'AWSCloudFormationStackSetAdministrationRole' role with trust relationship to CloudFormation service.""" snapshot.add_transformer(snapshot.transform.key_value("StackSetId", "stack-set-id")) stack_set_name = f"StackSet-{short_uid()}" @@ -77,3 +81,44 @@ def test_create_stack_set_with_stack_instances( wait_stack_set_operation(stack_set_name, delete_instances_result["OperationId"]) aws_client.cloudformation.delete_stack_set(StackSetName=stack_set_name) + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_delete_nonexistent_stack_set(aws_client, snapshot): + # idempotent + aws_client.cloudformation.delete_stack_set( + StackSetName="non-existent-stack-set-id", + ) + + bad_stack_set_id = f"foo:{long_uid()}" + snapshot.add_transformer(snapshot.transform.regex(bad_stack_set_id, "")) + + aws_client.cloudformation.delete_stack_set( + StackSetName=bad_stack_set_id, + ) + + +@skip_if_legacy_engine() +@markers.aws.validated +def test_fetch_non_existent_stack_set_instances(aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.cloudformation.create_stack_instances( + StackSetName="non-existent-stack-set-id", + Accounts=[SECONDARY_TEST_AWS_ACCOUNT_ID], + Regions=[SECONDARY_TEST_AWS_REGION_NAME], + ) + + snapshot.match("non-existent-stack-set-name", e.value) + + bad_stack_set_id = f"foo:{long_uid()}" + snapshot.add_transformer(snapshot.transform.regex(bad_stack_set_id, "")) + + with pytest.raises(ClientError) as e: + aws_client.cloudformation.create_stack_instances( + StackSetName=bad_stack_set_id, + Accounts=[SECONDARY_TEST_AWS_ACCOUNT_ID], + Regions=[SECONDARY_TEST_AWS_REGION_NAME], + ) + + snapshot.match("non-existent-stack-set-id", e.value) diff --git a/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json b/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json index 3585f6e07d3c7..ea705ee574af8 100644 --- a/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json @@ -17,5 +17,12 @@ } } } + }, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": { + "recorded-date": "01-08-2025, 09:56:22", + "recorded-content": { + "non-existent-stack-set-name": "An error occurred (StackSetNotFoundException) when calling the CreateStackInstances operation: StackSet non-existent-stack-set-id not found", + "non-existent-stack-set-id": "An error occurred (StackSetNotFoundException) when calling the CreateStackInstances operation: StackSet not found" + } } } diff --git a/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json b/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json index f7406f8c55f29..648d349cdb9ad 100644 --- a/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json +++ b/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json @@ -1,5 +1,14 @@ { "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": { "last_validated_date": "2023-05-24T13:32:47+00:00" + }, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": { + "last_validated_date": "2025-08-01T09:56:22+00:00", + "durations_in_seconds": { + "setup": 1.15, + "call": 0.58, + "teardown": 0.0, + "total": 1.73 + } } } diff --git a/tests/aws/services/cloudformation/v2/test_change_set_conditions.py b/tests/aws/services/cloudformation/test_change_set_conditions.py similarity index 82% rename from tests/aws/services/cloudformation/v2/test_change_set_conditions.py rename to tests/aws/services/cloudformation/test_change_set_conditions.py index f6b5661736f37..b9ca722b71a9f 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_conditions.py +++ b/tests/aws/services/cloudformation/test_change_set_conditions.py @@ -1,15 +1,15 @@ +import json + import pytest +from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers -from localstack.utils.strings import long_uid +from localstack.utils.strings import long_uid, short_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +17,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -28,12 +27,6 @@ ) class TestChangeSetConditions: @markers.aws.validated - @pytest.mark.skip( - reason=( - "The inclusion of response parameters in executor is in progress, " - "currently it cannot delete due to missing topic arn in the request" - ) - ) def test_condition_update_removes_resource( self, snapshot, @@ -110,10 +103,6 @@ def test_condition_update_adds_resource( capture_update_process(snapshot, template_1, template_2) @markers.aws.validated - @pytest.mark.skip( - reason="The inclusion of response parameters in executor is in progress, " - "currently it cannot delete due to missing topic arn in the request" - ) def test_condition_add_new_negative_condition_to_existent_resource( self, snapshot, @@ -181,3 +170,33 @@ def test_condition_add_new_positive_condition_to_existent_resource( }, } capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_missing_condition(self, snapshot, aws_client): + template = { + "Resources": { + "MyVpc": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/24", + "Tags": [ + { + "Fn::If": ["MissingCondition", "AWS::NoValue", "AWS::NoValue"], + }, + ], + }, + }, + } + } + + cs_name = f"cs-{short_uid()}" + stack_name = f"stack-{short_uid()}" + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=cs_name, + ChangeSetType="CREATE", + TemplateBody=json.dumps(template), + ) + + snapshot.match("error", exc_info.value.response) diff --git a/tests/aws/services/cloudformation/v2/test_change_set_conditions.snapshot.json b/tests/aws/services/cloudformation/test_change_set_conditions.snapshot.json similarity index 98% rename from tests/aws/services/cloudformation/v2/test_change_set_conditions.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_conditions.snapshot.json index 147c4f2eae447..b637e191e5dcb 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_conditions.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_conditions.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { "recorded-date": "15-04-2025, 13:51:50", "recorded-content": { "create-change-set-1": { @@ -407,7 +407,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { "recorded-date": "15-04-2025, 14:31:36", "recorded-content": { "create-change-set-1": { @@ -766,7 +766,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { "recorded-date": "15-04-2025, 15:11:48", "recorded-content": { "create-change-set-1": { @@ -1174,7 +1174,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { "recorded-date": "15-04-2025, 16:00:40", "recorded-content": { "create-change-set-1": { @@ -1532,5 +1532,21 @@ "Tags": [] } } + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_missing_condition": { + "recorded-date": "08-10-2025, 15:58:18", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: unresolved condition dependency MissingCondition in Fn::If", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/cloudformation/test_change_set_conditions.validation.json b/tests/aws/services/cloudformation/test_change_set_conditions.validation.json new file mode 100644 index 0000000000000..546d39602228d --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_conditions.validation.json @@ -0,0 +1,23 @@ +{ + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { + "last_validated_date": "2025-04-15T15:11:48+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { + "last_validated_date": "2025-04-15T16:00:39+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { + "last_validated_date": "2025-04-15T14:31:36+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { + "last_validated_date": "2025-04-15T13:51:50+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_missing_condition": { + "last_validated_date": "2025-10-08T15:58:18+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 0.3, + "teardown": 0.0, + "total": 1.26 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.py b/tests/aws/services/cloudformation/test_change_set_depends_on.py similarity index 96% rename from tests/aws/services/cloudformation/v2/test_change_set_depends_on.py rename to tests/aws/services/cloudformation/test_change_set_depends_on.py index e4f7545a5667d..465ceea8c6362 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.py +++ b/tests/aws/services/cloudformation/test_change_set_depends_on.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.snapshot.json b/tests/aws/services/cloudformation/test_change_set_depends_on.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_depends_on.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_depends_on.snapshot.json index 1c31c72649fa4..ab9c65a4e310d 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_depends_on.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { "recorded-date": "19-05-2025, 12:55:10", "recorded-content": { "create-change-set-1": { @@ -456,7 +456,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { "recorded-date": "19-05-2025, 13:01:35", "recorded-content": { "create-change-set-1": { @@ -913,7 +913,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { "recorded-date": "19-05-2025, 18:10:11", "recorded-content": { "create-change-set-1": { @@ -1349,7 +1349,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { "recorded-date": "19-05-2025, 18:13:11", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_depends_on.validation.json b/tests/aws/services/cloudformation/test_change_set_depends_on.validation.json new file mode 100644 index 0000000000000..847dc940ebea5 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_depends_on.validation.json @@ -0,0 +1,14 @@ +{ + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { + "last_validated_date": "2025-05-19T18:10:11+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { + "last_validated_date": "2025-05-19T18:13:11+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { + "last_validated_date": "2025-05-19T12:55:09+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { + "last_validated_date": "2025-05-19T13:01:34+00:00" + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_exports_imports.py b/tests/aws/services/cloudformation/test_change_set_exports_imports.py new file mode 100644 index 0000000000000..78cae07924d2a --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_exports_imports.py @@ -0,0 +1,245 @@ +import json +import os + +import pytest +from botocore.exceptions import WaiterError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.aws.api.cloudformation import ChangeSetType +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +@pytest.fixture +def execute_change_set(aws_client): + def inner(change_set_name: str, stack_name: str | None = None): + aws_client.cloudformation.execute_change_set( + ChangeSetName=change_set_name, StackName=stack_name + ) + aws_client.cloudformation.describe_stacks(StackName=stack_name) + aws_client.cloudformation.get_waiter("stack_create_complete").wait( + StackName=stack_name, + WaiterConfig={ + "Delay": 5, + "MaxAttempts": 100, + }, + ) + + return inner + + +@skip_if_legacy_engine() +@markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + "$..Parameters", + ] +) +class TestChangeSetImportExport: + @markers.aws.validated + def test_describe_change_set_import( + self, + snapshot, + aws_client, + deploy_cfn_template, + execute_change_set, + cleanup_stacks, + cleanups, + ): + export_name = f"b-{short_uid()}" + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../templates/cfn_function_export.yml" + ), + parameters={"BucketExportName": export_name}, + ) + + empty_stack_name = f"stack-{short_uid()}" + change_set_body = { + "Resources": { + "MyFoo": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": {"Fn::ImportValue": export_name}}, + } + } + } + + change_set_name = f"change-set-{short_uid()}" + aws_client.cloudformation.create_change_set( + StackName=empty_stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(change_set_body), + ChangeSetType=ChangeSetType.CREATE, + ) + cleanups.append(lambda: cleanup_stacks([empty_stack_name])) + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_name, StackName=empty_stack_name, WaiterConfig={"Delay": 2} + ) + + describe = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=False + ) + + describe_with_values = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=True + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("Value"), + snapshot.transform.key_value("ChangeSetId"), + snapshot.transform.key_value("ChangeSetName"), + snapshot.transform.key_value("StackId"), + snapshot.transform.key_value("StackName"), + ] + ) + + snapshot.match("describe", describe) + snapshot.match("describe_with_values", describe_with_values) + + execute_change_set(change_set_name, empty_stack_name) + stack = aws_client.cloudformation.describe_stacks(StackName=empty_stack_name)["Stacks"][0] + snapshot.match("post-deploy-stack-describe", stack) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..ChangeSetId", + "$..DeletionTime", + # TODO: should be ROLLBACK_COMPLETE, instead is CREATE_FAILED + "$..StackStatus", + ] + ) + def test_describe_change_set_import_non_existent_export( + self, + snapshot, + aws_client, + deploy_cfn_template, + execute_change_set, + cleanup_stacks, + cleanups, + ): + export_name = f"b-{short_uid()}" + empty_stack_name = f"stack-{short_uid()}" + change_set_body = { + "Resources": { + "MyFoo": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": {"Fn::ImportValue": export_name}}, + } + } + } + + change_set_name = f"change-set-{short_uid()}" + aws_client.cloudformation.create_change_set( + StackName=empty_stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(change_set_body), + ChangeSetType=ChangeSetType.CREATE, + ) + cleanups.append(lambda: cleanup_stacks([empty_stack_name])) + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_name, StackName=empty_stack_name, WaiterConfig={"Delay": 2} + ) + + describe = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=False + ) + + describe_with_values = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=True + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(export_name, ""), + snapshot.transform.key_value("ChangeSetId"), + snapshot.transform.key_value("ChangeSetName"), + snapshot.transform.key_value("StackId"), + snapshot.transform.key_value("StackName"), + ] + ) + + snapshot.match("describe", describe) + snapshot.match("describe_with_values", describe_with_values) + + with pytest.raises(WaiterError): + execute_change_set(change_set_name, empty_stack_name) + stack = aws_client.cloudformation.describe_stacks(StackName=empty_stack_name)["Stacks"][0] + snapshot.match("post-deploy-stack-describe", stack) + + @markers.aws.validated + def test_describe_change_set_import_non_existent_export_then_create( + self, + snapshot, + aws_client, + deploy_cfn_template, + execute_change_set, + cleanup_stacks, + cleanups, + ): + """ + * First create a change set that refers to a non-existent export + * Then create the export + * Then execute the change set + """ + export_name = f"b-{short_uid()}" + empty_stack_name = f"stack-{short_uid()}" + change_set_body = { + "Resources": { + "MyFoo": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": {"Fn::ImportValue": export_name}}, + } + } + } + + change_set_name = f"change-set-{short_uid()}" + aws_client.cloudformation.create_change_set( + StackName=empty_stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(change_set_body), + ChangeSetType=ChangeSetType.CREATE, + ) + cleanups.append(lambda: cleanup_stacks([empty_stack_name])) + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_name, StackName=empty_stack_name, WaiterConfig={"Delay": 2} + ) + + describe = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=False + ) + + describe_with_values = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=True + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(export_name, ""), + snapshot.transform.key_value("ChangeSetId"), + snapshot.transform.key_value("ChangeSetName"), + snapshot.transform.key_value("StackId"), + snapshot.transform.key_value("StackName"), + ] + ) + + snapshot.match("describe", describe) + snapshot.match("describe_with_values", describe_with_values) + + # deploy the stack with the export + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../templates/cfn_function_export.yml" + ), + parameters={"BucketExportName": export_name}, + ) + + # now execute the change set + execute_change_set(change_set_name, empty_stack_name) + stack = aws_client.cloudformation.describe_stacks(StackName=empty_stack_name)["Stacks"][0] + snapshot.match("post-deploy-stack-describe", stack) diff --git a/tests/aws/services/cloudformation/test_change_set_exports_imports.snapshot.json b/tests/aws/services/cloudformation/test_change_set_exports_imports.snapshot.json new file mode 100644 index 0000000000000..73635a2e85a39 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_exports_imports.snapshot.json @@ -0,0 +1,259 @@ +{ + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import": { + "recorded-date": "05-08-2025, 09:48:09", + "recorded-content": { + "describe": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_with_values": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-deploy-stack-describe": { + "ChangeSetId": "", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export": { + "recorded-date": "05-08-2025, 09:55:27", + "recorded-content": { + "describe": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_with_values": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "StatusReason": "[WARN] --include-property-values option can return incomplete ChangeSet data because: ChangeSet creation failed for resource [MyFoo] because: No export named ", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-deploy-stack-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "ROLLBACK_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export_then_create": { + "recorded-date": "05-08-2025, 09:58:06", + "recorded-content": { + "describe": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_with_values": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "StatusReason": "[WARN] --include-property-values option can return incomplete ChangeSet data because: ChangeSet creation failed for resource [MyFoo] because: No export named ", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-deploy-stack-describe": { + "ChangeSetId": "", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_exports_imports.validation.json b/tests/aws/services/cloudformation/test_change_set_exports_imports.validation.json new file mode 100644 index 0000000000000..d3a8f3bb4a2ef --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_exports_imports.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import": { + "last_validated_date": "2025-08-05T09:48:09+00:00", + "durations_in_seconds": { + "setup": 1.32, + "call": 33.43, + "teardown": 8.03, + "total": 42.78 + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export": { + "last_validated_date": "2025-08-05T09:55:27+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 10.52, + "teardown": 2.54, + "total": 13.99 + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export_then_create": { + "last_validated_date": "2025-08-05T09:58:06+00:00", + "durations_in_seconds": { + "setup": 1.15, + "call": 33.86, + "teardown": 8.08, + "total": 43.09 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py b/tests/aws/services/cloudformation/test_change_set_fn_base64.py similarity index 90% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py rename to tests/aws/services/cloudformation/test_change_set_fn_base64.py index b8593dad92bd7..6b7a1c09ebbde 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_base64.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_base64.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_base64.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_base64.snapshot.json index bfec63bc4521b..46d93da24e484 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_base64.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { "recorded-date": "02-06-2025, 17:27:21", "recorded-content": { "create-change-set-1": { @@ -377,7 +377,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { "recorded-date": "02-06-2025, 17:28:46", "recorded-content": { "create-change-set-1": { @@ -755,7 +755,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { "recorded-date": "02-06-2025, 17:30:12", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_base64.validation.json similarity index 57% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_base64.validation.json rename to tests/aws/services/cloudformation/test_change_set_fn_base64.validation.json index b29b77f2c4405..818d0c8e783a9 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.validation.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_base64.validation.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { "last_validated_date": "2025-06-02T17:27:21+00:00", "durations_in_seconds": { "setup": 0.81, @@ -8,7 +8,7 @@ "total": 84.61 } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { "last_validated_date": "2025-06-02T17:30:12+00:00", "durations_in_seconds": { "setup": 0.0, @@ -17,7 +17,7 @@ "total": 85.61 } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { "last_validated_date": "2025-06-02T17:28:46+00:00", "durations_in_seconds": { "setup": 0.0, diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.py similarity index 90% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py rename to tests/aws/services/cloudformation/test_change_set_fn_get_attr.py index 5255ff0704736..381c26031011c 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.py @@ -1,15 +1,15 @@ +import os + import pytest +from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers -from localstack.utils.strings import long_uid +from localstack.utils.strings import long_uid, short_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +17,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -312,3 +311,30 @@ def test_immutable_property_update_causes_resource_replacement( } } capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @pytest.mark.parametrize("template_name", ["getatt_validation.yml", "getatt_validation2.yml"]) + def test_invalid_structure(self, snapshot, deploy_cfn_template, aws_client, template_name): + cs_name = f"cs-{short_uid()}" + stack_name = f"stack-{short_uid()}" + + with open( + os.path.join(os.path.dirname(__file__), f"../../templates/{template_name}") + ) as infile: + template_body = infile.read() + + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=cs_name, + ChangeSetType="CREATE", + TemplateBody=template_body, + Parameters=[ + { + "ParameterKey": "MyParameterValue", + "ParameterValue": "BadResourceName", + }, + ], + ) + + snapshot.match("error", exc_info.value.response) diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.snapshot.json similarity index 98% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_get_attr.snapshot.json index c9a382f83c5d3..628748f38c8b6 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { "recorded-date": "08-04-2025, 11:24:14", "recorded-content": { "create-change-set-1": { @@ -511,7 +511,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { "recorded-date": "08-04-2025, 12:17:00", "recorded-content": { "create-change-set-1": { @@ -1137,7 +1137,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { "recorded-date": "08-04-2025, 12:20:19", "recorded-content": { "create-change-set-1": { @@ -1596,7 +1596,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { "recorded-date": "08-04-2025, 12:33:53", "recorded-content": { "create-change-set-1": { @@ -1963,7 +1963,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { "recorded-date": "08-04-2025, 12:36:41", "recorded-content": { "create-change-set-1": { @@ -2380,7 +2380,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { "recorded-date": "08-04-2025, 14:46:11", "recorded-content": { "create-change-set-1": { @@ -3016,5 +3016,37 @@ "Tags": [] } } + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_invalid_structure[getatt_validation.yml]": { + "recorded-date": "08-10-2025, 12:13:08", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: every Fn::GetAtt object requires two non-empty parameters, the resource name and the resource attribute", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_invalid_structure[getatt_validation2.yml]": { + "recorded-date": "08-10-2025, 12:13:08", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: every Fn::GetAtt object requires two non-empty parameters, the resource name and the resource attribute", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/cloudformation/test_change_set_fn_get_attr.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.validation.json new file mode 100644 index 0000000000000..03b7ac8e94f84 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.validation.json @@ -0,0 +1,38 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { + "last_validated_date": "2025-04-08T11:24:14+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { + "last_validated_date": "2025-04-08T14:46:11+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { + "last_validated_date": "2025-04-08T12:20:18+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { + "last_validated_date": "2025-04-08T12:17:00+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_invalid_structure[getatt_validation.yml]": { + "last_validated_date": "2025-10-08T12:13:08+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 0.31, + "teardown": 0.0, + "total": 1.27 + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_invalid_structure[getatt_validation2.yml]": { + "last_validated_date": "2025-10-08T12:13:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.15, + "teardown": 0.0, + "total": 0.15 + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { + "last_validated_date": "2025-04-08T12:33:53+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { + "last_validated_date": "2025-04-08T12:36:40+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.py b/tests/aws/services/cloudformation/test_change_set_fn_join.py similarity index 96% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_join.py rename to tests/aws/services/cloudformation/test_change_set_fn_join.py index 718f1a1181043..e6f50c457bb6f 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_join.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_join.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_join.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_join.snapshot.json index ab448456fa342..e05a29dd37464 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_join.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { "recorded-date": "05-05-2025, 13:10:55", "recorded-content": { "create-change-set-1": { @@ -386,7 +386,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { "recorded-date": "05-05-2025, 13:15:58", "recorded-content": { "create-change-set-1": { @@ -773,7 +773,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { "recorded-date": "05-05-2025, 13:24:03", "recorded-content": { "create-change-set-1": { @@ -1285,7 +1285,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { "recorded-date": "05-05-2025, 13:31:26", "recorded-content": { "create-change-set-1": { @@ -1797,7 +1797,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { "recorded-date": "05-05-2025, 13:37:54", "recorded-content": { "create-change-set-1": { @@ -2184,7 +2184,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { "recorded-date": "05-05-2025, 13:42:26", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_fn_join.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_join.validation.json new file mode 100644 index 0000000000000..728504d82bfb2 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_join.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { + "last_validated_date": "2025-05-05T13:31:26+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { + "last_validated_date": "2025-05-05T13:24:03+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { + "last_validated_date": "2025-05-05T13:10:54+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { + "last_validated_date": "2025-05-05T13:42:26+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { + "last_validated_date": "2025-05-05T13:15:57+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { + "last_validated_date": "2025-05-05T13:37:54+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.py b/tests/aws/services/cloudformation/test_change_set_fn_select.py similarity index 58% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_select.py rename to tests/aws/services/cloudformation/test_change_set_fn_select.py index 16b5dee524632..8bb812cbe97d3 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_select.py @@ -1,15 +1,15 @@ +import json + import pytest +from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers -from localstack.utils.strings import long_uid +from localstack.utils.strings import long_uid, short_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +17,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -201,3 +200,109 @@ def test_fn_select_change_get_att_reference( } } capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @pytest.mark.parametrize( + "select_construct", + [ + {"Fn::Select": ["bad", ["test"]]}, + {"Fn::Select": [0, 2]}, + {"Fn::Select": [100, ["test"]]}, + ], + ids=["non-integer-index", "non-list-list", "index-out-of-range"], + ) + def test_invalid_select_index_type(self, snapshot, aws_client, cleanups, select_construct): + template = { + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": select_construct, + }, + }, + }, + } + + stack_name = f"stack-{short_uid()}" + change_set_name = f"stack-{short_uid()}" + + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + ChangeSetType="CREATE", + TemplateBody=json.dumps(template), + ) + + snapshot.match("error", exc_info.value.response) + + @markers.aws.validated + def test_nested_select_within_other_intrinsics(self, snapshot, deploy_cfn_template): + template = json.dumps( + { + "Resources": { + "Repo": { + "Type": "AWS::ECR::Repository", + }, + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 4, + { + "Fn::Split": [ + ":", + {"Fn::GetAtt": ["Repo", "Arn"]}, + ] + }, + ] + }, + ".dkr.ecr.", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + {"Fn::GetAtt": ["Repo", "Arn"]}, + ] + }, + ] + }, + ".", + {"Ref": "AWS::URLSuffix"}, + "/", + {"Ref": "Repo"}, + ], + ] + }, + }, + }, + }, + "Outputs": { + "RepoName": {"Value": {"Ref": "Repo"}}, + "ParameterValue": { + "Value": { + "Fn::GetAtt": ["Parameter", "Value"], + }, + }, + }, + } + ) + stack = deploy_cfn_template(template=template) + snapshot.add_transformer(snapshot.transform.regex(stack.outputs["RepoName"], "")) + # the domain name is different between AWS and LocalStack so transform this value out + snapshot.add_transformer( + snapshot.transform.regex( + r"(localhost\.localstack\.cloud(:\d+)?|amazonaws\.com)", "" + ) + ) + + snapshot.match("parameter-value", stack.outputs) diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_select.snapshot.json similarity index 96% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_select.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_select.snapshot.json index 3e286c96554e9..976defe99169b 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_select.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { "recorded-date": "28-05-2025, 13:14:01", "recorded-content": { "create-change-set-1": { @@ -377,7 +377,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { "recorded-date": "28-05-2025, 13:17:47", "recorded-content": { "create-change-set-1": { @@ -755,7 +755,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { "recorded-date": "28-05-2025, 13:21:34", "recorded-content": { "create-change-set-1": { @@ -1133,7 +1133,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { "recorded-date": "28-05-2025, 13:23:46", "recorded-content": { "create-change-set-1": { @@ -1511,7 +1511,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { "recorded-date": "28-05-2025, 13:32:24", "recorded-content": { "create-change-set-1": { @@ -1889,11 +1889,11 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_index_select_from_parameter_list": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_index_select_from_parameter_list": { "recorded-date": "28-05-2025, 13:56:52", "recorded-content": {} }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { "recorded-date": "28-05-2025, 14:44:47", "recorded-content": { "create-change-set-1": { @@ -2388,5 +2388,62 @@ "Tags": [] } } + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[non-integer-index]": { + "recorded-date": "12-09-2025, 11:09:16", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: Fn::Select requires a list argument with two elements: an integer index and a list", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[non-list-list]": { + "recorded-date": "12-09-2025, 11:09:16", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: Fn::Select requires a list argument with two elements: an integer index and a list", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[index-out-of-range]": { + "recorded-date": "12-09-2025, 11:09:16", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: Fn::Select requires a list argument with two elements: an integer index and a list", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_nested_select_within_other_intrinsics": { + "recorded-date": "16-09-2025, 10:30:37", + "recorded-content": { + "parameter-value": { + "ParameterValue": "111111111111.dkr.ecr../", + "RepoName": "" + } + } } } diff --git a/tests/aws/services/cloudformation/test_change_set_fn_select.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_select.validation.json new file mode 100644 index 0000000000000..d642aa33a59c3 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_select.validation.json @@ -0,0 +1,56 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { + "last_validated_date": "2025-05-28T13:14:01+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { + "last_validated_date": "2025-05-28T14:44:47+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { + "last_validated_date": "2025-05-28T13:32:24+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { + "last_validated_date": "2025-05-28T13:23:46+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { + "last_validated_date": "2025-05-28T13:21:34+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { + "last_validated_date": "2025-05-28T13:17:47+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[index-out-of-range]": { + "last_validated_date": "2025-09-12T11:09:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.0, + "total": 0.13 + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[non-integer-index]": { + "last_validated_date": "2025-09-12T11:09:16+00:00", + "durations_in_seconds": { + "setup": 0.76, + "call": 0.3, + "teardown": 0.0, + "total": 1.06 + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_invalid_select_index_type[non-list-list]": { + "last_validated_date": "2025-09-12T11:09:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.14, + "teardown": 0.0, + "total": 0.14 + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_nested_select_within_other_intrinsics": { + "last_validated_date": "2025-09-16T10:30:37+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 12.37, + "teardown": 6.51, + "total": 19.83 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.py b/tests/aws/services/cloudformation/test_change_set_fn_split.py similarity index 87% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_split.py rename to tests/aws/services/cloudformation/test_change_set_fn_split.py index fd85f7a61011c..33f25c4ecfcff 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_split.py @@ -1,15 +1,15 @@ +import json + import pytest +from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers -from localstack.utils.strings import long_uid +from localstack.utils.strings import long_uid, short_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +17,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -241,3 +240,30 @@ def test_fn_split_with_get_att( } capture_update_process(snapshot, template_1, template_2) + + +class TestSplitValidations: + @markers.aws.validated + @skip_if_legacy_engine + def test_validate_split_arguments_len(self, aws_client, snapshot): + template = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Fn::Split": {"Ref": "AWS::Region"}}, + }, + } + } + } + + with pytest.raises(ClientError) as ex: + aws_client.cloudformation.create_change_set( + StackName=f"st-{short_uid()}", + ChangeSetName=f"ch-{short_uid()}", + ChangeSetType="CREATE", + TemplateBody=json.dumps(template), + ) + + snapshot.match("validation", ex.value.response) diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_split.snapshot.json similarity index 98% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_split.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_split.snapshot.json index b31381319abae..555cb616241c7 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_split.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { "recorded-date": "02-06-2025, 11:19:05", "recorded-content": { "create-change-set-1": { @@ -377,7 +377,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { "recorded-date": "02-06-2025, 11:20:30", "recorded-content": { "create-change-set-1": { @@ -755,7 +755,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { "recorded-date": "02-06-2025, 11:22:03", "recorded-content": { "create-change-set-1": { @@ -1133,7 +1133,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { "recorded-date": "02-06-2025, 11:23:28", "recorded-content": { "create-change-set-1": { @@ -1577,7 +1577,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { "recorded-date": "02-06-2025, 11:26:00", "recorded-content": { "create-change-set-1": { @@ -2074,7 +2074,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { "recorded-date": "02-06-2025, 12:30:32", "recorded-content": { "create-change-set-1": { @@ -2451,5 +2451,21 @@ "Tags": [] } } + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestSplitValidations::test_validate_split_arguments_len": { + "recorded-date": "08-10-2025, 20:08:15", + "recorded-content": { + "validation": { + "Error": { + "Code": "ValidationError", + "Message": "Template error: every Fn::Split object requires two parameters, (1) a string delimiter and (2) a string to be split or a function that returns a string to be split.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/cloudformation/test_change_set_fn_split.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_split.validation.json new file mode 100644 index 0000000000000..a3e830ca7477b --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_split.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { + "last_validated_date": "2025-06-02T11:19:05+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { + "last_validated_date": "2025-06-02T12:30:32+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { + "last_validated_date": "2025-06-02T11:22:03+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { + "last_validated_date": "2025-06-02T11:20:29+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { + "last_validated_date": "2025-06-02T11:26:00+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { + "last_validated_date": "2025-06-02T11:23:28+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestSplitValidations::test_validate_split_arguments_len": { + "last_validated_date": "2025-10-08T20:08:15+00:00", + "durations_in_seconds": { + "setup": 0.23, + "call": 0.38, + "teardown": 0.0, + "total": 0.61 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py b/tests/aws/services/cloudformation/test_change_set_fn_sub.py similarity index 97% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py rename to tests/aws/services/cloudformation/test_change_set_fn_sub.py index 82984d02da21e..a42e353f86f40 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_sub.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_sub.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_sub.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_sub.snapshot.json index d11042ed00882..6ccd91b746d77 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_sub.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { "recorded-date": "20-05-2025, 09:54:49", "recorded-content": { "create-change-set-1": { @@ -385,7 +385,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { "recorded-date": "20-05-2025, 09:59:44", "recorded-content": { "create-change-set-1": { @@ -771,7 +771,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { "recorded-date": "20-05-2025, 11:29:16", "recorded-content": { "create-change-set-1": { @@ -1157,7 +1157,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { "recorded-date": "20-05-2025, 11:54:12", "recorded-content": { "create-change-set-1": { @@ -1543,7 +1543,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { "recorded-date": "20-05-2025, 12:01:36", "recorded-content": { "create-change-set-1": { @@ -1929,7 +1929,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { "recorded-date": "20-05-2025, 12:05:00", "recorded-content": { "create-change-set-1": { @@ -2315,7 +2315,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { "recorded-date": "20-05-2025, 15:08:40", "recorded-content": { "create-change-set-1": { @@ -2749,7 +2749,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { "recorded-date": "20-05-2025, 15:10:16", "recorded-content": { "create-change-set-1": { @@ -3183,7 +3183,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { "recorded-date": "20-05-2025, 15:26:13", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_fn_sub.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_sub.validation.json new file mode 100644 index 0000000000000..a8c8278cdb8a9 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_sub.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { + "last_validated_date": "2025-05-20T15:26:12+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { + "last_validated_date": "2025-05-20T11:54:12+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { + "last_validated_date": "2025-05-20T15:08:40+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { + "last_validated_date": "2025-05-20T09:54:49+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { + "last_validated_date": "2025-05-20T12:05:00+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { + "last_validated_date": "2025-05-20T11:29:16+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { + "last_validated_date": "2025-05-20T12:01:36+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { + "last_validated_date": "2025-05-20T15:10:15+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { + "last_validated_date": "2025-05-20T09:59:44+00:00" + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_fn_transform.py b/tests/aws/services/cloudformation/test_change_set_fn_transform.py new file mode 100644 index 0000000000000..8c3b4efff8a2b --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_transform.py @@ -0,0 +1,570 @@ +import os + +import pytest +from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.aws.api.lambda_ import Runtime +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +@skip_if_legacy_engine() +@markers.snapshot.skip_snapshot_verify( + paths=[ + "per-resource-events..*", + "delete-describe..*", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..Replacement", + "$..PolicyAction", + ] +) +class TestChangeSetFnTransform: + @pytest.fixture(scope="function") + def create_macro(self, aws_client, deploy_cfn_template, create_lambda_function): + def _inner(macro_name, code_path): + func_name = f"test_lambda_{short_uid()}" + create_lambda_function( + func_name=func_name, + handler_file=code_path, + runtime=Runtime.python3_12, + client=aws_client.lambda_, + ) + + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../templates/macro_resource.yml" + ), + parameters={"FunctionName": func_name, "MacroName": macro_name}, + ) + + yield _inner + + @markers.aws.validated + @pytest.mark.parametrize("include_format", ["yml", "json"]) + @markers.snapshot.skip_snapshot_verify( + paths=["$..Changes..ResourceChange.AfterContext.Properties.Name"] + ) + def test_embedded_fn_transform_include( + self, include_format, snapshot, capture_update_process, s3_bucket, aws_client, tmp_path + ): + name1 = f"name-1-{short_uid()}" + name2 = f"name-2-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + bucket = s3_bucket + file = tmp_path / "bucket_definition.yml" + + if include_format == "json": + template = f'{{"Parameter": {{ "Type": "AWS::SSM::Parameter","Properties": {{"Name": "{name2}", "Type": "String", "Value": "foo"}}}}}}' + else: + template = f""" + Parameter2: + Type: AWS::SSM::Parameter + Properties: + Name: {name2} + Type: String + Value: foo + """ + + file.write_text(data=template) + aws_client.s3.upload_file( + Bucket=bucket, + Key="template", + Filename=str(file.absolute()), + ) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + } + } + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + "Fn::Transform": { + "Name": "AWS::Include", + "Parameters": {"Location": f"s3://{bucket}/template"}, + }, + }, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @pytest.mark.parametrize("include_format", ["yml", "json"]) + def test_global_fn_transform_include( + self, include_format, snapshot, capture_update_process, s3_bucket, aws_client, tmp_path + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + bucket = s3_bucket + file = tmp_path / "bucket_definition.yml" + + if include_format == "json": + template = '{"Outputs":{"ParameterRef":{"Value":{"Ref":"Parameter"}}}} ' + else: + template = """ + Outputs: + ParameterRef: + Value: + Ref: Parameter + """ + + file.write_text(data=template) + aws_client.s3.upload_file( + Bucket=bucket, + Key="template", + Filename=str(file.absolute()), + ) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + } + } + template_2 = { + "Transform": { + "Name": "AWS::Include", + "Parameters": {"Location": f"s3://{bucket}/template"}, + }, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + }, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Changes..ResourceChange.AfterContext.Properties.Body.paths", + "$..Changes..ResourceChange.AfterContext.Properties.SourceArn", + ] + ) + def test_serverless_fn_transform( + self, snapshot, capture_update_process, s3_bucket, aws_client, tmp_path + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Value": "{Substitution}", "Type": "String", "Name": name1}, + } + } + } + template_2 = { + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "HelloWorldFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "index.handler", + "Runtime": "nodejs18.x", + "InlineCode": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};", + "Events": { + "ApiEvent": { + "Type": "Api", + "Properties": {"Path": "/hello", "Method": "get"}, + } + }, + }, + } + }, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_global_macro_fn_transform( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "name-1")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/replace_string.py" + ) + macro_name = "Substitution" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Value": "original", "Type": "String", "Name": name1}, + } + } + } + + template_2 = { + "Parameters": {"Substitution": {"Type": "String", "Default": "SubstitutionDefault"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Value": "{Substitution}", "Type": "String", "Name": name1}, + } + }, + "Transform": {"Name": macro_name}, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_embedded_macro_fn_transform( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "name-1")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/add_standard_tags.py" + ) + macro_name = "AddTags" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + } + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": "foo", + "Fn::Transform": macro_name, + }, + } + } + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_embedded_macro_for_attribute_fn_transform( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/return_random_string.py" + ) + macro_name = "GenerateRandom" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + "Parameters": {"Prefix": {"Ref": "Input"}}, + } + }, + }, + } + }, + } + + capture_update_process(snapshot, template_1, template_2, p2={"Input": "test"}) + + @markers.aws.validated + def test_multiple_fn_transform_order( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/replace_string.py" + ) + macro_name = "ReplaceString" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Value": "", + "Type": "String", + "Fn::Transform": [ + {"Name": "ReplaceString", "Parameters": {"Input": "snippet-transform"}}, + { + "Name": "ReplaceString", + "Parameters": {"Input": "second-snippet-transform"}, + }, + ], + }, + } + }, + "Transform": [ + {"Name": "ReplaceString", "Parameters": {"Input": "global-transform"}}, + {"Name": "ReplaceString", "Parameters": {"Input": "second-global-transform"}}, + ], + } + + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @pytest.mark.parametrize("transform", ["true", "false"]) + def test_conditional_transform( + self, + transform, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/replace_string.py" + ) + macro_name = "ReplaceString" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Parameters": {"Transform": {"Type": "String"}}, + "Conditions": {"Deploy": {"Fn::Equals": [{"Ref": "Transform"}, "true"]}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Condition": "Deploy", + "Properties": { + "Name": name1, + "Value": "", + "Type": "String", + "Fn::Transform": [ + {"Name": "ReplaceString", "Parameters": {"Input": "snippet-transform"}}, + ], + }, + } + }, + } + + capture_update_process(snapshot, template_1, template_2, p2={"Transform": transform}) + + @markers.aws.validated + def test_macro_with_intrinsic_function( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/replace_string.py" + ) + macro_name = "ReplaceString" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Value": "", + "Type": "String", + "Fn::Transform": [ + { + "Name": macro_name, + "Parameters": {"Input": {"Fn::Join": ["-", ["test", "string"]]}}, + }, + ], + }, + } + } + } + + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_remove_transform_in_update_change_set( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/return_random_string.py" + ) + macro_name = "GenerateRandom" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + } + }, + }, + } + }, + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + capture_update_process(snapshot, template_1, template_2, p1={"Input": "test"}) + + @markers.aws.validated + def test_update_parameter_transform_in_update_change_set( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../templates/macros/return_random_string.py" + ) + macro_name = "GenerateRandom" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + } + }, + }, + } + }, + } + + template_2 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + } + }, + }, + } + }, + } + + capture_update_process( + snapshot, template_1, template_2, p1={"Input": "test"}, p2={"Input": "test2"} + ) diff --git a/tests/aws/services/cloudformation/test_change_set_fn_transform.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_transform.snapshot.json new file mode 100644 index 0000000000000..6589a383c0fa6 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_transform.snapshot.json @@ -0,0 +1,6505 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[yml]": { + "recorded-date": "03-06-2025, 21:29:28", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-2-bd1e2d36" + } + }, + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "Parameter2": [ + { + "EventId": "Parameter2-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "name-2-bd1e2d36", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-bd1e2d36" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter2-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "name-2-bd1e2d36", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-bd1e2d36" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter2-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-bd1e2d36" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[json]": { + "recorded-date": "03-06-2025, 21:29:58", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-2-3e00e97a" + } + }, + "BeforeContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "name-2-3e00e97a", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "topic-name-1", + "Name": "Name", + "Path": "/Properties/Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-5cbf35a2-4a92-4554-a111-1f96269d3814", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-06c1c753-8966-4dc1-9a39-cfbe492abedb", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-2-3e00e97a", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-3e00e97a" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-2-3e00e97a", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-3e00e97a" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-3e00e97a" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "Requested update requires the creation of a new physical resource; hence creating one.", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[json]": { + "recorded-date": "03-06-2025, 21:30:48", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[yml]": { + "recorded-date": "03-06-2025, 21:30:23", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_serverless_fn_transform": { + "recorded-date": "03-06-2025, 21:32:10", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{Substitution}", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Role": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Handler": "index.handler", + "Runtime": "nodejs18.x", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "Details": [], + "LogicalResourceId": "HelloWorldFunction", + "ResourceType": "AWS::Lambda::Function", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "FunctionName": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Action": "lambda:InvokeFunction", + "SourceArn": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Principal": "apigateway.amazonaws.com" + } + }, + "Details": [], + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "ResourceType": "AWS::Lambda::Permission", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "Details": [], + "LogicalResourceId": "HelloWorldFunctionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{Substitution}", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/{{changeSet:KNOWN_AFTER_APPLY}}/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + } + }, + "Details": [], + "LogicalResourceId": "ServerlessRestApi", + "ResourceType": "AWS::ApiGateway::RestApi", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "RestApiId": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage" + } + }, + "Details": [], + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "ResourceType": "AWS::ApiGateway::Deployment", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "RestApiId": "{{changeSet:KNOWN_AFTER_APPLY}}", + "DeploymentId": "{{changeSet:KNOWN_AFTER_APPLY}}", + "StageName": "Prod" + } + }, + "Details": [], + "LogicalResourceId": "ServerlessRestApiProdStage", + "ResourceType": "AWS::ApiGateway::Stage", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "HelloWorldFunction", + "ResourceType": "AWS::Lambda::Function", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "ResourceType": "AWS::Lambda::Permission", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "HelloWorldFunctionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ServerlessRestApi", + "ResourceType": "AWS::ApiGateway::RestApi", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "ResourceType": "AWS::ApiGateway::Deployment", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ServerlessRestApiProdStage", + "ResourceType": "AWS::ApiGateway::Stage", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "HelloWorldFunction": [ + { + "EventId": "HelloWorldFunction-CREATE_COMPLETE-date", + "LogicalResourceId": "HelloWorldFunction", + "PhysicalResourceId": "-HelloWorldFunction-EdXB90Au1l86", + "ResourceProperties": { + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs18.x", + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::Lambda::Function", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunction-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunction", + "PhysicalResourceId": "-HelloWorldFunction-EdXB90Au1l86", + "ResourceProperties": { + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs18.x", + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::Lambda::Function", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunction-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunction", + "PhysicalResourceId": "", + "ResourceProperties": { + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs18.x", + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::Lambda::Function", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "HelloWorldFunctionApiEventPermissionProd": [ + { + "EventId": "HelloWorldFunctionApiEventPermissionProd-CREATE_COMPLETE-date", + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "PhysicalResourceId": "-HelloWorldFunctionApiEventPermissionProd-xfMFMMYChbqn", + "ResourceProperties": { + "FunctionName": "-HelloWorldFunction-EdXB90Au1l86", + "Action": "lambda:InvokeFunction", + "SourceArn": "arn::execute-api::111111111111:317w2e8pc3/*/GET/", + "Principal": "apigateway.amazonaws.com" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::Lambda::Permission", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionApiEventPermissionProd-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "PhysicalResourceId": "-HelloWorldFunctionApiEventPermissionProd-xfMFMMYChbqn", + "ResourceProperties": { + "FunctionName": "-HelloWorldFunction-EdXB90Au1l86", + "Action": "lambda:InvokeFunction", + "SourceArn": "arn::execute-api::111111111111:317w2e8pc3/*/GET/", + "Principal": "apigateway.amazonaws.com" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::Lambda::Permission", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionApiEventPermissionProd-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "PhysicalResourceId": "", + "ResourceProperties": { + "FunctionName": "-HelloWorldFunction-EdXB90Au1l86", + "Action": "lambda:InvokeFunction", + "SourceArn": "arn::execute-api::111111111111:317w2e8pc3/*/GET/", + "Principal": "apigateway.amazonaws.com" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::Lambda::Permission", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "HelloWorldFunctionRole": [ + { + "EventId": "HelloWorldFunctionRole-CREATE_COMPLETE-date", + "LogicalResourceId": "HelloWorldFunctionRole", + "PhysicalResourceId": "", + "ResourceProperties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::IAM::Role", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionRole-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionRole", + "PhysicalResourceId": "", + "ResourceProperties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::IAM::Role", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionRole-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionRole", + "PhysicalResourceId": "", + "ResourceProperties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::IAM::Role", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "Parameter": [ + { + "EventId": "Parameter-af425eba-d8e2-4ad9-9ebc-153095f8161d", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-adcb9c2b-0276-4487-9df6-5f12bf586323", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ServerlessRestApi": [ + { + "EventId": "ServerlessRestApi-CREATE_COMPLETE-date", + "LogicalResourceId": "ServerlessRestApi", + "PhysicalResourceId": "317w2e8pc3", + "ResourceProperties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/arn::lambda::111111111111:function:-HelloWorldFunction-EdXB90Au1l86/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::ApiGateway::RestApi", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApi-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApi", + "PhysicalResourceId": "317w2e8pc3", + "ResourceProperties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/arn::lambda::111111111111:function:-HelloWorldFunction-EdXB90Au1l86/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::ApiGateway::RestApi", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApi-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApi", + "PhysicalResourceId": "", + "ResourceProperties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/arn::lambda::111111111111:function:-HelloWorldFunction-EdXB90Au1l86/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::ApiGateway::RestApi", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ServerlessRestApiDeployment47fc2d5f9d": [ + { + "EventId": "ServerlessRestApiDeployment47fc2d5f9d-CREATE_COMPLETE-date", + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "PhysicalResourceId": "5oj1e3", + "ResourceProperties": { + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::ApiGateway::Deployment", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiDeployment47fc2d5f9d-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "PhysicalResourceId": "5oj1e3", + "ResourceProperties": { + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::ApiGateway::Deployment", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiDeployment47fc2d5f9d-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "PhysicalResourceId": "", + "ResourceProperties": { + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::ApiGateway::Deployment", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ServerlessRestApiProdStage": [ + { + "EventId": "ServerlessRestApiProdStage-CREATE_COMPLETE-date", + "LogicalResourceId": "ServerlessRestApiProdStage", + "PhysicalResourceId": "Prod", + "ResourceProperties": { + "DeploymentId": "5oj1e3", + "StageName": "Prod", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::ApiGateway::Stage", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiProdStage-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiProdStage", + "PhysicalResourceId": "Prod", + "ResourceProperties": { + "DeploymentId": "5oj1e3", + "StageName": "Prod", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::ApiGateway::Stage", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiProdStage-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiProdStage", + "PhysicalResourceId": "", + "ResourceProperties": { + "DeploymentId": "5oj1e3", + "StageName": "Prod", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::ApiGateway::Stage", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_macro_fn_transform": { + "recorded-date": "03-06-2025, 21:42:32", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "original", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "{Substitution}", + "Type": "String", + "Name": "name-1" + } + }, + "BeforeContext": { + "Properties": { + "Value": "original", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "{Substitution}", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "original", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "original", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "original", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "original", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_fn_transform": { + "recorded-date": "03-06-2025, 21:51:11", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Tags": { + "MacroAdded": "True" + }, + "Name": "name-1" + } + }, + "BeforeContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": { + "MacroAdded": "True" + }, + "Attribute": "Properties", + "AttributeChangeType": "Add", + "Name": "Tags", + "Path": "/Properties/Tags", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Tags": { + "MacroAdded": "True" + }, + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Tags": { + "MacroAdded": "True" + }, + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_for_attribute_fn_transform": { + "recorded-date": "03-06-2025, 21:34:07", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_multiple_fn_transform_order": { + "recorded-date": "03-06-2025, 21:34:52", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform": { + "recorded-date": "20-06-2025, 21:12:31", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[true]": { + "recorded-date": "20-06-2025, 21:15:45", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[false]": { + "recorded-date": "20-06-2025, 21:16:29", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_macro_with_intrinsic_function": { + "recorded-date": "20-06-2025, 22:19:25", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_remove_transform_in_update_change_set": { + "recorded-date": "31-07-2025, 21:36:49", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_update_parameter_transform_in_update_change_set": { + "recorded-date": "31-07-2025, 21:38:55", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_fn_transform.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_transform.validation.json new file mode 100644 index 0000000000000..16122b91de118 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_transform.validation.json @@ -0,0 +1,137 @@ +{ + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform": { + "last_validated_date": "2025-06-20T21:12:32+00:00", + "durations_in_seconds": { + "setup": 10.79, + "call": 35.71, + "teardown": 5.56, + "total": 52.06 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[false]": { + "last_validated_date": "2025-06-20T21:16:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 38.41, + "teardown": 5.71, + "total": 44.12 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[true]": { + "last_validated_date": "2025-06-20T21:15:45+00:00", + "durations_in_seconds": { + "setup": 10.81, + "call": 38.04, + "teardown": 5.12, + "total": 53.97 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[json]": { + "last_validated_date": "2025-06-03T21:29:58+00:00", + "durations_in_seconds": { + "setup": 0.39, + "call": 29.05, + "teardown": 0.82, + "total": 30.26 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[yml]": { + "last_validated_date": "2025-06-03T21:29:28+00:00", + "durations_in_seconds": { + "setup": 0.62, + "call": 26.34, + "teardown": 0.8, + "total": 27.76 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_fn_transform": { + "last_validated_date": "2025-06-03T21:51:12+00:00", + "durations_in_seconds": { + "setup": 10.68, + "call": 35.35, + "teardown": 5.52, + "total": 51.55 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_for_attribute_fn_transform": { + "last_validated_date": "2025-06-03T21:34:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 36.99, + "teardown": 5.18, + "total": 42.17 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[json]": { + "last_validated_date": "2025-06-03T21:30:48+00:00", + "durations_in_seconds": { + "setup": 0.39, + "call": 23.99, + "teardown": 0.8, + "total": 25.18 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[yml]": { + "last_validated_date": "2025-06-03T21:30:23+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 24.02, + "teardown": 0.89, + "total": 25.28 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_macro_fn_transform": { + "last_validated_date": "2025-06-03T21:42:32+00:00", + "durations_in_seconds": { + "setup": 10.81, + "call": 37.62, + "teardown": 5.34, + "total": 53.77 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_macro_with_function": { + "last_validated_date": "2025-06-20T22:19:25+00:00", + "durations_in_seconds": { + "setup": 10.9, + "call": 36.99, + "teardown": 5.46, + "total": 53.35 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_multiple_fn_transform_order": { + "last_validated_date": "2025-06-03T21:34:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 39.28, + "teardown": 5.61, + "total": 44.89 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_remove_transform_in_update_change_set": { + "last_validated_date": "2025-07-31T21:36:50+00:00", + "durations_in_seconds": { + "setup": 10.72, + "call": 36.7, + "teardown": 5.53, + "total": 52.95 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_serverless_fn_transform": { + "last_validated_date": "2025-06-03T21:32:10+00:00", + "durations_in_seconds": { + "setup": 0.35, + "call": 80.57, + "teardown": 0.71, + "total": 81.63 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_update_parameter_transform_in_update_change_set": { + "last_validated_date": "2025-07-31T21:38:55+00:00", + "durations_in_seconds": { + "setup": 10.84, + "call": 36.02, + "teardown": 5.42, + "total": 52.28 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.py b/tests/aws/services/cloudformation/test_change_set_global_macros.py similarity index 90% rename from tests/aws/services/cloudformation/v2/test_change_set_global_macros.py rename to tests/aws/services/cloudformation/test_change_set_global_macros.py index ef31281f2096e..85e2eeecee9bb 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.py +++ b/tests/aws/services/cloudformation/test_change_set_global_macros.py @@ -1,21 +1,17 @@ import json import os -import pytest from localstack_snapshot.snapshots.transformer import JsonpathTransformer, RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.testing.pytest.cloudformation.fixtures import _normalise_describe_change_set_output from localstack.utils.functions import call_safe from localstack.utils.strings import short_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -23,7 +19,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -51,7 +46,7 @@ def test_base_global_macro( ) ) macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../templates/macros/format_template.py" + os.path.dirname(__file__), "../../templates/macros/format_template.py" ) macro_name = "SubstitutionMacro" func_name = f"test_lambda_{short_uid()}" @@ -64,7 +59,7 @@ def test_base_global_macro( ) deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/macro_resource.yml" + os.path.dirname(__file__), "../../templates/macro_resource.yml" ), parameters={"FunctionName": func_name, "MacroName": macro_name}, ) @@ -108,8 +103,15 @@ def test_update_after_macro_for_before_version_is_deleted( snapshot, deploy_cfn_template, create_lambda_function, - capture_update_process, ): + """ + 1. create the macro + 2. deploy the first version of the template including the template + 3. delete the first macro + 4. create a second macro (same implementation) + 5. update the stack adding a second SSM parameter + 6. the deploy should work as the new macro is in place + """ snapshot.add_transformer( JsonpathTransformer( jsonpath="$..Outputs..OutputValue", @@ -119,12 +121,12 @@ def test_update_after_macro_for_before_version_is_deleted( ) snapshot.add_transformer(snapshot.transform.cloudformation_api()) macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../templates/macros/format_template.py" + os.path.dirname(__file__), "../../templates/macros/format_template.py" ) # Create the macro to be used in the first version of the template. macro_name_first = f"SubstitutionMacroFirst-{short_uid()}" - snapshot.add_transformer(RegexTransformer(macro_name_first, "macro-name-first")) + snapshot.add_transformer(RegexTransformer(macro_name_first, "")) func_name = f"test_lambda_{short_uid()}" create_lambda_function( func_name=func_name, @@ -135,7 +137,7 @@ def test_update_after_macro_for_before_version_is_deleted( ) macro_stack_first = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/macro_resource.yml" + os.path.dirname(__file__), "../../templates/macro_resource.yml" ), parameters={"FunctionName": func_name, "MacroName": macro_name_first}, ) @@ -160,7 +162,7 @@ def test_update_after_macro_for_before_version_is_deleted( ChangeSetName=change_set_name, TemplateBody=json.dumps(template_1), ChangeSetType="CREATE", - Parameters=list(), + Parameters=[], ) stack_id = change_set_details["StackId"] change_set_id = change_set_details["Id"] @@ -170,7 +172,7 @@ def test_update_after_macro_for_before_version_is_deleted( cleanups.append( lambda: call_safe( aws_client_no_retry.cloudformation.delete_change_set, - kwargs=dict(ChangeSetName=change_set_id), + kwargs={"ChangeSetName": change_set_id}, ) ) # Describe @@ -192,7 +194,7 @@ def test_update_after_macro_for_before_version_is_deleted( # ensure stack deletion cleanups.append( lambda: call_safe( - aws_client_no_retry.cloudformation.delete_stack, kwargs=dict(StackName=stack_id) + aws_client_no_retry.cloudformation.delete_stack, kwargs={"StackName": stack_id} ) ) describe = aws_client_no_retry.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][ @@ -205,7 +207,7 @@ def test_update_after_macro_for_before_version_is_deleted( # Create the macro to be used in the second version of the template. macro_name_second = f"SubstitutionMacroSecond-{short_uid()}" - snapshot.add_transformer(RegexTransformer(macro_name_second, "macro-name-second")) + snapshot.add_transformer(RegexTransformer(macro_name_second, "")) func_name = f"test_lambda_{short_uid()}" create_lambda_function( func_name=func_name, @@ -216,7 +218,7 @@ def test_update_after_macro_for_before_version_is_deleted( ) macro_stack_second = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/macro_resource.yml" + os.path.dirname(__file__), "../../templates/macro_resource.yml" ), parameters={"FunctionName": func_name, "MacroName": macro_name_second}, ) @@ -244,7 +246,7 @@ def test_update_after_macro_for_before_version_is_deleted( ChangeSetName=change_set_name, TemplateBody=json.dumps(template_2), ChangeSetType="UPDATE", - Parameters=list(), + Parameters=[], ) stack_id = change_set_details["StackId"] change_set_id = change_set_details["Id"] diff --git a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.snapshot.json b/tests/aws/services/cloudformation/test_change_set_global_macros.snapshot.json similarity index 97% rename from tests/aws/services/cloudformation/v2/test_change_set_global_macros.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_global_macros.snapshot.json index 686fa970b25f9..62197b35cf0bc 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_global_macros.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { "recorded-date": "24-06-2025, 15:16:22", "recorded-content": { "create-change-set-1": { @@ -338,8 +338,8 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { - "recorded-date": "26-06-2025, 11:59:06", + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { + "recorded-date": "14-08-2025, 14:14:38", "recorded-content": { "describe-change-set-1-prop-values": { "Capabilities": [], diff --git a/tests/aws/services/cloudformation/test_change_set_global_macros.validation.json b/tests/aws/services/cloudformation/test_change_set_global_macros.validation.json new file mode 100644 index 0000000000000..cede5d54fc10d --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_global_macros.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { + "last_validated_date": "2025-06-24T15:16:22+00:00", + "durations_in_seconds": { + "setup": 11.62, + "call": 34.97, + "teardown": 5.44, + "total": 52.03 + } + }, + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { + "last_validated_date": "2025-08-14T14:14:39+00:00", + "durations_in_seconds": { + "setup": 12.3, + "call": 53.17, + "teardown": 1.98, + "total": 67.45 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_mappings.py b/tests/aws/services/cloudformation/test_change_set_mappings.py similarity index 61% rename from tests/aws/services/cloudformation/v2/test_change_set_mappings.py rename to tests/aws/services/cloudformation/test_change_set_mappings.py index 05fa11a2cce80..28556771c83db 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_mappings.py +++ b/tests/aws/services/cloudformation/test_change_set_mappings.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -301,3 +296,179 @@ def test_mapping_key_deletion_with_resource_remap( }, } capture_update_process(snapshot, template_1, template_2) + + @markers.snapshot.skip_snapshot_verify(paths=["$..LastOperations"]) + @markers.aws.validated + def test_fn_find_in_map_with_nested_ref( + self, + snapshot, + capture_update_process, + ): + name1 = f"topic-name-1-{long_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + # Template mimicking the CloudFront/Route53 pattern from the bug report + # This is the common CDK pattern for alias target hosted zone IDs + template_1 = { + "Mappings": { + "AWSCloudFrontPartitionHostedZoneIdMap": { + "aws": {"zoneId": "Z2FDTNDATAQYW2"}, + "aws-cn": {"zoneId": "Z3RFFRIM2A3IF5"}, + } + }, + "Resources": { + "Topic1": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": name1, + # Using nested Ref - the problematic pattern + "DisplayName": { + "Fn::FindInMap": [ + "AWSCloudFrontPartitionHostedZoneIdMap", + {"Ref": "AWS::Partition"}, # Nested Ref + "zoneId", + ] + }, + }, + } + }, + } + + # Change the TopicName to create an actual update + # The key is that the FindInMap with nested Ref is processed without error + template_2 = { + "Mappings": { + "AWSCloudFrontPartitionHostedZoneIdMap": { + "aws": {"zoneId": "Z2FDTNDATAQYW2"}, + "aws-cn": {"zoneId": "Z3RFFRIM2A3IF5"}, + } + }, + "Resources": { + "Topic1": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": f"{name1}-updated", # Changed to trigger update + "DisplayName": { + "Fn::FindInMap": [ + "AWSCloudFrontPartitionHostedZoneIdMap", + {"Ref": "AWS::Partition"}, # Still has nested Ref + "zoneId", + ] + }, + }, + } + }, + } + + # Before the fix, this would raise NotImplementedError when processing the changeset + # After the fix, it successfully processes the changeset and detects the change + capture_update_process(snapshot, template_1, template_2) + + @markers.snapshot.skip_snapshot_verify(paths=["$..LastOperations"]) + @markers.aws.validated + def test_fn_find_in_map_with_nested_ref_change_mapping( + self, + snapshot, + capture_update_process, + region_name, + secondary_region_name, + ): + name1 = f"topic-name-1-{long_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + template_1 = { + "Mappings": { + "RegionMap": { + region_name: {"value": "value-1"}, + secondary_region_name: {"value": "value-2"}, + } + }, + "Resources": { + "Topic1": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": name1, + "DisplayName": { + "Fn::FindInMap": [ + "RegionMap", + {"Ref": "AWS::Region"}, # Nested Ref + "value", + ] + }, + }, + } + }, + } + + # Change the mapping values + template_2 = { + "Mappings": { + "RegionMap": { + region_name: {"value": "value-3"}, # changed + secondary_region_name: {"value": "value-4"}, # changed + } + }, + "Resources": { + "Topic1": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": name1, + "DisplayName": { + "Fn::FindInMap": [ + "RegionMap", + {"Ref": "AWS::Region"}, + "value", + ] + }, + }, + } + }, + } + + # Should detect the mapping change and mark resource as modified + capture_update_process(snapshot, template_1, template_2) + + @markers.snapshot.skip_snapshot_verify(paths=["$..LastOperations"]) + @markers.aws.validated + def test_fn_find_in_map_with_multiple_nested_functions( + self, + snapshot, + capture_update_process, + ): + name1 = f"topic-name-1-{long_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + template = { + "Parameters": { + "Environment": { + "Type": "String", + } + }, + "Mappings": { + "ComplexMap": { + "prod": {"aws": "prod-aws-value"}, + "dev": {"aws": "dev-aws-value"}, + } + }, + "Resources": { + "Topic1": { + "Type": "AWS::SNS::Topic", + "Properties": { + "TopicName": name1, + "DisplayName": { + "Fn::FindInMap": [ + "ComplexMap", + {"Ref": "Environment"}, # Nested Ref to parameter + {"Ref": "AWS::Partition"}, # Another nested Ref + ] + }, + }, + } + }, + } + + # Change the parameter value to trigger an update + # The nested Refs in FindInMap should still work without error + capture_update_process( + snapshot, template, template, p1={"Environment": "prod"}, p2={"Environment": "dev"} + ) diff --git a/tests/aws/services/cloudformation/v2/test_change_set_mappings.snapshot.json b/tests/aws/services/cloudformation/test_change_set_mappings.snapshot.json similarity index 67% rename from tests/aws/services/cloudformation/v2/test_change_set_mappings.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_mappings.snapshot.json index 58882da07da49..db8e9f5b38182 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_mappings.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_mappings.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { "recorded-date": "15-04-2025, 13:03:18", "recorded-content": { "create-change-set-1": { @@ -386,7 +386,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { "recorded-date": "15-04-2025, 13:04:44", "recorded-content": { "create-change-set-1": { @@ -773,7 +773,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { "recorded-date": "15-04-2025, 13:05:52", "recorded-content": { "create-change-set-1": { @@ -1140,7 +1140,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { "recorded-date": "15-04-2025, 13:07:01", "recorded-content": { "create-change-set-1": { @@ -1507,7 +1507,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { "recorded-date": "15-04-2025, 13:08:27", "recorded-content": { "create-change-set-1": { @@ -1966,7 +1966,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { "recorded-date": "15-04-2025, 13:15:54", "recorded-content": { "create-change-set-1": { @@ -2424,5 +2424,1263 @@ "Tags": [] } } + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_fn_find_in_map_with_nested_ref": { + "recorded-date": "02-02-2026, 11:35:11", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "DisplayName": "Z2FDTNDATAQYW2", + "TopicName": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Topic1", + "ResourceType": "AWS::SNS::Topic", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Topic1", + "ResourceType": "AWS::SNS::Topic", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "CREATE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "DisplayName": "Z2FDTNDATAQYW2", + "TopicName": "topic-name-1-updated" + } + }, + "BeforeContext": { + "Properties": { + "DisplayName": "Z2FDTNDATAQYW2", + "TopicName": "topic-name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "topic-name-1-updated", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "topic-name-1", + "Name": "TopicName", + "Path": "/Properties/TopicName", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SNS::Topic", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "TopicName", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SNS::Topic", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "UPDATE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "DELETE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ], + "Topic1": [ + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1-updated", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1-updated", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1-updated", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1-updated", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_fn_find_in_map_with_nested_ref_change_mapping": { + "recorded-date": "04-02-2026, 11:54:54", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "DisplayName": "value-1", + "TopicName": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Topic1", + "ResourceType": "AWS::SNS::Topic", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Topic1", + "ResourceType": "AWS::SNS::Topic", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "CREATE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "DisplayName": "value-3", + "TopicName": "topic-name-1" + } + }, + "BeforeContext": { + "Properties": { + "DisplayName": "value-1", + "TopicName": "topic-name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "value-3", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "value-1", + "Name": "DisplayName", + "Path": "/Properties/DisplayName", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "Replacement": "False", + "ResourceType": "AWS::SNS::Topic", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "DisplayName", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "Replacement": "False", + "ResourceType": "AWS::SNS::Topic", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "UPDATE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "DELETE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ], + "Topic1": [ + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_fn_find_in_map_with_multiple_nested_functions": { + "recorded-date": "03-02-2026, 09:11:57", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "DisplayName": "prod-aws-value", + "TopicName": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Topic1", + "ResourceType": "AWS::SNS::Topic", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Environment", + "ParameterValue": "prod" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Topic1", + "ResourceType": "AWS::SNS::Topic", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Environment", + "ParameterValue": "prod" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "CREATE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Environment", + "ParameterValue": "prod" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "DisplayName": "dev-aws-value", + "TopicName": "topic-name-1" + } + }, + "BeforeContext": { + "Properties": { + "DisplayName": "prod-aws-value", + "TopicName": "topic-name-1" + } + }, + "Details": [ + { + "CausingEntity": "Environment", + "ChangeSource": "ParameterReference", + "Evaluation": "Static", + "Target": { + "AfterValue": "dev-aws-value", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "prod-aws-value", + "Name": "DisplayName", + "Path": "/Properties/DisplayName", + "RequiresRecreation": "Never" + } + }, + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "AfterValue": "dev-aws-value", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "prod-aws-value", + "Name": "DisplayName", + "Path": "/Properties/DisplayName", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "Replacement": "False", + "ResourceType": "AWS::SNS::Topic", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Environment", + "ParameterValue": "dev" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "Attribute": "Properties", + "Name": "DisplayName", + "RequiresRecreation": "Never" + } + }, + { + "CausingEntity": "Environment", + "ChangeSource": "ParameterReference", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "DisplayName", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "Replacement": "False", + "ResourceType": "AWS::SNS::Topic", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Environment", + "ParameterValue": "dev" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "UPDATE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Environment", + "ParameterValue": "dev" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastOperations": [ + { + "OperationId": "", + "OperationType": "DELETE_STACK" + } + ], + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Environment", + "ParameterValue": "dev" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ], + "Topic1": [ + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Topic1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + } + ] + } + } } } diff --git a/tests/aws/services/cloudformation/test_change_set_mappings.validation.json b/tests/aws/services/cloudformation/test_change_set_mappings.validation.json new file mode 100644 index 0000000000000..6a8f667c41974 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_mappings.validation.json @@ -0,0 +1,47 @@ +{ + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_fn_find_in_map_with_multiple_nested_functions": { + "last_validated_date": "2026-02-03T09:11:57+00:00", + "durations_in_seconds": { + "setup": 0.65, + "call": 92.72, + "teardown": 0.33, + "total": 93.7 + } + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_fn_find_in_map_with_nested_ref": { + "last_validated_date": "2026-02-02T11:35:11+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 119.51, + "teardown": 0.31, + "total": 120.41 + } + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_fn_find_in_map_with_nested_ref_change_mapping": { + "last_validated_date": "2026-02-04T11:54:54+00:00", + "durations_in_seconds": { + "setup": 0.61, + "call": 91.81, + "teardown": 0.32, + "total": 92.74 + } + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { + "last_validated_date": "2025-04-15T13:05:52+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { + "last_validated_date": "2025-04-15T13:08:27+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { + "last_validated_date": "2025-04-15T13:07:01+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { + "last_validated_date": "2025-04-15T13:15:54+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { + "last_validated_date": "2025-04-15T13:04:43+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { + "last_validated_date": "2025-04-15T13:03:18+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_parameters.py b/tests/aws/services/cloudformation/test_change_set_parameters.py similarity index 66% rename from tests/aws/services/cloudformation/v2/test_change_set_parameters.py rename to tests/aws/services/cloudformation/test_change_set_parameters.py index ac04661b2ba8d..e2c0c43fe14f3 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_parameters.py +++ b/tests/aws/services/cloudformation/test_change_set_parameters.py @@ -1,15 +1,16 @@ +import copy +import json + import pytest +from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers -from localstack.utils.strings import long_uid +from localstack.utils.strings import long_uid, short_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +18,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -128,3 +128,64 @@ def test_update_parameter_default_value_with_dynamic_overrides( capture_update_process( snapshot, template_1, template_2, p1={"TopicName": name1}, p2={"TopicName": name2} ) + + @markers.aws.validated + def test_parameter_type_change(self, snapshot, capture_update_process): + snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) + + template1 = { + "Parameters": { + "Value": { + "Type": "Number", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "Value"}, + }, + }, + }, + } + template2 = copy.deepcopy(template1) + template2["Parameters"]["Value"]["Type"] = "String" + + capture_update_process( + snapshot, template1, template2, p1={"Value": "123"}, p2={"Value": "456"} + ) + + @markers.aws.validated + def test_invalid_parameter_type(self, snapshot, aws_client): + template = { + "Parameters": { + "Value": { + "Type": "Number", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": short_uid(), + }, + }, + }, + } + + stack_name = f"stack-{short_uid()}" + cs_name = f"cs-{short_uid()}" + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + ChangeSetName=cs_name, + StackName=stack_name, + ChangeSetType="CREATE", + TemplateBody=json.dumps(template), + Parameters=[ + {"ParameterKey": "Value", "ParameterValue": "not-a-number"}, + ], + ) + + snapshot.match("error", exc_info.value.response) diff --git a/tests/aws/services/cloudformation/v2/test_change_set_parameters.snapshot.json b/tests/aws/services/cloudformation/test_change_set_parameters.snapshot.json similarity index 82% rename from tests/aws/services/cloudformation/v2/test_change_set_parameters.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_parameters.snapshot.json index 4d0c44f81f248..55b2d7e63ffde 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_parameters.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_parameters.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { "recorded-date": "17-04-2025, 15:35:43", "recorded-content": { "create-change-set-1": { @@ -481,7 +481,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { "recorded-date": "17-04-2025, 15:39:55", "recorded-content": { "create-change-set-1": { @@ -963,7 +963,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { "recorded-date": "17-04-2025, 15:44:25", "recorded-content": { "create-change-set-1": { @@ -1445,7 +1445,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { "recorded-date": "17-04-2025, 15:46:46", "recorded-content": { "create-change-set-1": { @@ -1926,5 +1926,461 @@ "Tags": [] } } + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_invalid_parameter_type": { + "recorded-date": "06-10-2025, 20:45:59", + "recorded-content": { + "error": { + "Error": { + "Code": "ValidationError", + "Message": "Parameter 'Value' must be a number.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_parameter_type_change": { + "recorded-date": "06-10-2025, 20:55:52", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "123", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Value", + "ParameterValue": "123" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Value", + "ParameterValue": "123" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Value", + "ParameterValue": "123" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "456", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "123", + "Type": "String" + } + }, + "Details": [ + { + "CausingEntity": "Value", + "ChangeSource": "ParameterReference", + "Evaluation": "Static", + "Target": { + "AfterValue": "456", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "123", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + }, + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "AfterValue": "456", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "123", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Value", + "ParameterValue": "456" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "CausingEntity": "Value", + "ChangeSource": "ParameterReference", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + }, + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Value", + "ParameterValue": "456" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Value", + "ParameterValue": "456" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Value", + "ParameterValue": "456" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "MyParameter": [ + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } } } diff --git a/tests/aws/services/cloudformation/test_change_set_parameters.validation.json b/tests/aws/services/cloudformation/test_change_set_parameters.validation.json new file mode 100644 index 0000000000000..dc624db1e38cd --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_parameters.validation.json @@ -0,0 +1,32 @@ +{ + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_invalid_parameter_type": { + "last_validated_date": "2025-10-06T20:45:59+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 0.33, + "teardown": 0.01, + "total": 1.26 + } + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_parameter_type_change": { + "last_validated_date": "2025-10-06T20:55:52+00:00", + "durations_in_seconds": { + "setup": 0.99, + "call": 27.31, + "teardown": 0.17, + "total": 28.47 + } + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { + "last_validated_date": "2025-04-17T15:35:43+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { + "last_validated_date": "2025-04-17T15:46:46+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { + "last_validated_date": "2025-04-17T15:39:55+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { + "last_validated_date": "2025-04-17T15:44:24+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_ref.py b/tests/aws/services/cloudformation/test_change_set_ref.py similarity index 97% rename from tests/aws/services/cloudformation/v2/test_change_set_ref.py rename to tests/aws/services/cloudformation/test_change_set_ref.py index 3785e861094f2..1afbcdd9a7918 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_ref.py +++ b/tests/aws/services/cloudformation/test_change_set_ref.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_ref.snapshot.json b/tests/aws/services/cloudformation/test_change_set_ref.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_ref.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_ref.snapshot.json index d6aac38ddd772..00d1b512a7dc4 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_ref.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_ref.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { "recorded-date": "08-04-2025, 15:22:38", "recorded-content": { "create-change-set-1": { @@ -366,7 +366,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { "recorded-date": "08-04-2025, 15:36:44", "recorded-content": { "create-change-set-1": { @@ -825,7 +825,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { "recorded-date": "08-04-2025, 15:45:54", "recorded-content": { "create-change-set-1": { @@ -1356,7 +1356,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { "recorded-date": "08-04-2025, 15:51:05", "recorded-content": { "create-change-set-1": { @@ -1815,7 +1815,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { "recorded-date": "08-04-2025, 16:00:20", "recorded-content": { "create-change-set-1": { @@ -2441,7 +2441,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { "recorded-date": "19-05-2025, 10:22:18", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_ref.validation.json b/tests/aws/services/cloudformation/test_change_set_ref.validation.json new file mode 100644 index 0000000000000..f7d5b65ead975 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_ref.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { + "last_validated_date": "2025-04-08T15:36:44+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { + "last_validated_date": "2025-04-08T15:45:54+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { + "last_validated_date": "2025-04-08T15:51:05+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { + "last_validated_date": "2025-04-08T16:00:20+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { + "last_validated_date": "2025-04-08T15:22:37+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { + "last_validated_date": "2025-05-19T10:22:18+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_values.py b/tests/aws/services/cloudformation/test_change_set_values.py similarity index 89% rename from tests/aws/services/cloudformation/v2/test_change_set_values.py rename to tests/aws/services/cloudformation/test_change_set_values.py index 90084441dd4cb..6aae716cf1672 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_values.py +++ b/tests/aws/services/cloudformation/test_change_set_values.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_values.snapshot.json b/tests/aws/services/cloudformation/test_change_set_values.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_values.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_values.snapshot.json index c2b398a920fc4..10515aca705a3 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_values.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_values.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { + "tests/aws/services/cloudformation/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { "recorded-date": "23-05-2025, 17:56:06", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_values.validation.json b/tests/aws/services/cloudformation/test_change_set_values.validation.json new file mode 100644 index 0000000000000..4d140798dd566 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_values.validation.json @@ -0,0 +1,5 @@ +{ + "tests/aws/services/cloudformation/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { + "last_validated_date": "2025-05-23T17:56:06+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_sets.py b/tests/aws/services/cloudformation/test_change_sets.py similarity index 69% rename from tests/aws/services/cloudformation/v2/test_change_sets.py rename to tests/aws/services/cloudformation/test_change_sets.py index 20ef3e331d59e..9a2ed1d89f22a 100644 --- a/tests/aws/services/cloudformation/v2/test_change_sets.py +++ b/tests/aws/services/cloudformation/test_change_sets.py @@ -1,36 +1,28 @@ import copy import json +import os import pytest +from botocore.exceptions import WaiterError from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.aws.connect import ServiceLevelClientFactory -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import short_uid -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_legacy_engine() @markers.snapshot.skip_snapshot_verify( paths=[ "delete-describe..*", # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", "$..Parameters", - "$..Replacement", "$..PolicyAction", "$..PhysicalResourceId", ] @@ -724,78 +716,372 @@ def test_base_mapping_scenarios( ): capture_update_process(snapshot, template_1, template_2) + @markers.aws.validated + def test_single_resource_static_update( + self, aws_client: ServiceLevelClientFactory, snapshot, cleanups + ): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + parameter_name = f"parameter-{short_uid()}" + value1 = "foo" + value2 = "bar" + + t1 = { + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": parameter_name, + "Type": "String", + "Value": value1, + }, + }, + }, + } + + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + cs_result = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(t1), + ChangeSetType="CREATE", + ) + cs_id = cs_result["Id"] + stack_id = cs_result["StackId"] + aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) + + describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) + snapshot.match("describe-1", describe_result) + + aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) + aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) + + parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] + snapshot.match("parameter-1", parameter) + + t2 = copy.deepcopy(t1) + t2["Resources"]["MyParameter"]["Properties"]["Value"] = value2 + + change_set_name = f"cs-{short_uid()}" + cs_result = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(t2), + ) + cs_id = cs_result["Id"] + aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) + + describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) + snapshot.match("describe-2", describe_result) + + aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) + aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack_id) + + parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] + snapshot.match("parameter-2", parameter) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..PhysicalResourceId"]) + def test_queue_update( + self, aws_client, deploy_cfn_template, capture_per_resource_events, snapshot + ): + """ + A test where one of the templates creates a resource without any specified properties. We + make sure the second operation on that resource is an UPDATE not CREATE. + """ + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + template1 = os.path.join( + os.path.dirname(__file__), "../../templates/sqs_queue_update_1.yaml" + ) + template2 = os.path.join( + os.path.dirname(__file__), "../../templates/sqs_queue_update_2.yaml" + ) + + # Deploy order: + # - 1 + # - 2 + # - 1 + stack = deploy_cfn_template(template_path=template1) + outputs = stack.outputs + + queue_url = outputs["QueueUrl"] + + queue_attributes = aws_client.sqs.get_queue_attributes( + QueueUrl=queue_url, AttributeNames=["All"] + ) + snapshot.match("queue-attributes-1", queue_attributes) + + deploy_cfn_template(template_path=template2, is_update=True, stack_name=stack.stack_id) + queue_attributes = aws_client.sqs.get_queue_attributes( + QueueUrl=queue_url, AttributeNames=["All"] + ) + snapshot.match("queue-attributes-2", queue_attributes) + + deploy_cfn_template(template_path=template1, is_update=True, stack_name=stack.stack_id) + queue_attributes = aws_client.sqs.get_queue_attributes( + QueueUrl=queue_url, AttributeNames=["All"] + ) + snapshot.match("queue-attributes-3", queue_attributes) + + per_resource_events = capture_per_resource_events(stack.stack_id) + + snapshot.match("events", per_resource_events["StandardQueue"]) + + +@skip_if_legacy_engine() @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ + "delete-describe..*", + # + # Before/After Context "$..Capabilities", "$..IncludeNestedStacks", - "$..NotificationARNs", + "$..Scope", + "$..Details", "$..Parameters", - "$..Changes..ResourceChange.Details", - "$..Changes..ResourceChange.Scope", - "$..Changes..ResourceChange.PhysicalResourceId", - "$..Changes..ResourceChange.Replacement", + "$..Replacement", + "$..PolicyAction", + "$..PhysicalResourceId", ] ) -def test_single_resource_static_update(aws_client: ServiceLevelClientFactory, snapshot, cleanups): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - parameter_name = f"parameter-{short_uid()}" - value1 = "foo" - value2 = "bar" +def test_dynamic_ssm_parameter_lookup( + snapshot, + aws_client: ServiceLevelClientFactory, + aws_client_no_retry: ServiceLevelClientFactory, + cleanups, + create_parameter, + capture_update_process, +): + """ + Test reading parameter values from SSM works correctly""" + parameter_name = f"param-{short_uid()}" + value1 = f"value-1-{short_uid()}" + value2 = f"value-2-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("OutputValue"), + snapshot.transform.regex(parameter_name, ""), + snapshot.transform.regex(value1, ""), + snapshot.transform.regex(value2, ""), + ] + + snapshot.transform.cloudformation_api(), + ) + + create_parameter(Name=parameter_name, Value=value1, Type="String") + + template = { + "Parameters": { + "InputValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "DerivedParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "InputValue"}, + }, + }, + }, + "Outputs": { + "DerivedParameterName": { + "Value": {"Ref": "DerivedParameter"}, + }, + }, + } + + def update_parameter_value(): + aws_client.ssm.put_parameter(Name=parameter_name, Value=value2, Overwrite=True) + + capture_update_process( + snapshot, + template, + template, + p1={"InputValue": parameter_name}, + p2={"InputValue": parameter_name}, + custom_update_step=update_parameter_value, + ) + + +@skip_if_legacy_engine() +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + "delete-describe..*", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..Replacement", + "$..PolicyAction", + "$..PhysicalResourceId", + ] +) +def test_dynamic_ssm_parameter_lookup_no_change( + snapshot, + aws_client: ServiceLevelClientFactory, + aws_client_no_retry: ServiceLevelClientFactory, + cleanups, + create_parameter, + capture_update_process, +): + """ + Test reading parameter values from SSM works correctly""" + parameter_name = f"param-{short_uid()}" + parameter_value = f"value-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("OutputValue"), + snapshot.transform.regex(parameter_name, ""), + snapshot.transform.regex(parameter_value, ""), + ] + + snapshot.transform.cloudformation_api(), + ) + + create_parameter(Name=parameter_name, Value=parameter_value, Type="String") t1 = { + "Parameters": { + "InputValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, "Resources": { - "MyParameter": { + "DerivedParameter": { "Type": "AWS::SSM::Parameter", "Properties": { - "Name": parameter_name, "Type": "String", - "Value": value1, + "Value": {"Ref": "InputValue"}, }, }, }, + "Outputs": { + "DerivedParameterName": { + "Value": {"Ref": "DerivedParameter"}, + }, + }, + } + t2 = copy.deepcopy(t1) + t2["Resources"]["AnotherParameter"] = { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "new parameter", + }, } - stack_name = f"stack-{short_uid()}" + capture_update_process( + snapshot, + t1, + t2, + p1={"InputValue": parameter_name}, + p2={"InputValue": parameter_name}, + ) + + +@skip_if_legacy_engine() +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify(paths=["$..StatusReason"]) +def test_describe_failed_change_set(aws_client: ServiceLevelClientFactory, snapshot, cleanups): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + template = { + "Resources": { + "MyFoo": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Fn::Transform": { + "Name": "AWS::Include", + "Parameters": { + "Location": "s3://doesnotexist/key.yml", + }, + }, + }, + }, + }, + } + + stack_name = f"cs-{short_uid()}" change_set_name = f"cs-{short_uid()}" - cs_result = aws_client.cloudformation.create_change_set( + res = aws_client.cloudformation.create_change_set( StackName=stack_name, ChangeSetName=change_set_name, - TemplateBody=json.dumps(t1), ChangeSetType="CREATE", + TemplateBody=json.dumps(template), ) - cs_id = cs_result["Id"] - stack_id = cs_result["StackId"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) - describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) - snapshot.match("describe-1", describe_result) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=res["StackId"])) - aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) + with pytest.raises(WaiterError): + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=res["Id"] + ) - parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] - snapshot.match("parameter-1", parameter) + describe = aws_client.cloudformation.describe_change_set(ChangeSetName=res["Id"]) + snapshot.match("describe", describe) - t2 = copy.deepcopy(t1) - t2["Resources"]["MyParameter"]["Properties"]["Value"] = value2 - change_set_name = f"cs-{short_uid()}" - cs_result = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=json.dumps(t2), +@skip_if_legacy_engine() +@markers.aws.validated +def test_list_change_sets(deploy_cfn_template, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + template = { + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": short_uid(), + }, + }, + }, + } + + # first create an executed change set + stack = deploy_cfn_template(template=json.dumps(template)) + stack_id = stack.stack_id + + # now create a non-executed change set + template2 = copy.deepcopy(template) + template2["Resources"]["MyParameter"]["Properties"]["Value"] = short_uid() + + non_executed_change_set_name = f"cs-{short_uid()}" + non_executed_change_set_id = aws_client.cloudformation.create_change_set( + ChangeSetName=non_executed_change_set_name, + StackName=stack.stack_id, + ChangeSetType="UPDATE", + TemplateBody=json.dumps(template2), + )["Id"] + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=non_executed_change_set_id ) - cs_id = cs_result["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) - describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) - snapshot.match("describe-2", describe_result) + # now create and delete a change set + template3 = copy.deepcopy(template) + template3["Resources"]["MyParameter"]["Properties"]["Value"] = short_uid() + + deleted_change_set_name = f"cs-{short_uid()}" + deleted_change_set_id = aws_client.cloudformation.create_change_set( + ChangeSetName=deleted_change_set_name, + StackName=stack.stack_id, + ChangeSetType="UPDATE", + TemplateBody=json.dumps(template3), + )["Id"] + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=deleted_change_set_id + ) - aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack_id) + aws_client.cloudformation.delete_change_set(ChangeSetName=deleted_change_set_id) - parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] - snapshot.match("parameter-2", parameter) + change_sets = aws_client.cloudformation.list_change_sets(StackName=stack_id) + snapshot.match("change-sets", change_sets) diff --git a/tests/aws/services/cloudformation/v2/test_change_sets.snapshot.json b/tests/aws/services/cloudformation/test_change_sets.snapshot.json similarity index 57% rename from tests/aws/services/cloudformation/v2/test_change_sets.snapshot.json rename to tests/aws/services/cloudformation/test_change_sets.snapshot.json index 66b1117810662..0744e8ff4496b 100644 --- a/tests/aws/services/cloudformation/v2/test_change_sets.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_sets.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_sets.py::test_single_resource_static_update": { + "tests/aws/services/cloudformation/test_change_sets.py::test_single_resource_static_update": { "recorded-date": "18-03-2025, 16:52:36", "recorded-content": { "describe-1": { @@ -94,8 +94,8 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { - "recorded-date": "18-06-2025, 19:04:55", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { + "recorded-date": "21-08-2025, 11:39:10", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -106,7 +106,11 @@ } }, "describe-change-set-1-prop-values": { - "Capabilities": [], + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", "ChangeSetName": "", "Changes": [ @@ -140,7 +144,11 @@ } }, "describe-change-set-1": { - "Capabilities": [], + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", "ChangeSetName": "", "Changes": [ @@ -175,6 +183,11 @@ } }, "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", "CreationTime": "datetime", "DisableRollback": false, @@ -199,7 +212,11 @@ } }, "describe-change-set-2-prop-values": { - "Capabilities": [], + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", "ChangeSetName": "", "Changes": [ @@ -257,7 +274,11 @@ } }, "describe-change-set-2": { - "Capabilities": [], + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", "ChangeSetName": "", "Changes": [ @@ -307,6 +328,11 @@ } }, "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", "CreationTime": "datetime", "DisableRollback": false, @@ -323,6 +349,11 @@ "Tags": [] }, "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], "CreationTime": "datetime", "DeletionTime": "datetime", "DisableRollback": false, @@ -341,7 +372,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -355,7 +386,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -366,6 +397,34 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Stack": [ @@ -403,13 +462,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { - "recorded-date": "18-06-2025, 19:06:59", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { + "recorded-date": "05-08-2025, 14:17:58", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -599,9 +672,8 @@ }, "Details": [ { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -613,8 +685,9 @@ } }, { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -627,7 +700,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -696,7 +769,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -760,7 +833,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -774,7 +847,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -785,36 +858,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -852,13 +967,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { - "recorded-date": "18-06-2025, 19:09:04", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { + "recorded-date": "05-08-2025, 14:20:03", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -1080,9 +1209,8 @@ }, "Details": [ { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1094,8 +1222,9 @@ } }, { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1108,7 +1237,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1193,7 +1322,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1275,7 +1404,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1289,7 +1418,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1300,36 +1429,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -1367,13 +1538,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { - "recorded-date": "18-06-2025, 19:11:09", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { + "recorded-date": "05-08-2025, 14:22:08", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -1563,8 +1748,9 @@ }, "Details": [ { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1576,9 +1762,8 @@ } }, { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1591,7 +1776,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1660,7 +1845,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1724,7 +1909,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1738,7 +1923,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1749,36 +1934,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -1816,13 +2043,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { - "recorded-date": "18-06-2025, 19:13:17", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { + "recorded-date": "05-08-2025, 14:24:09", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -2044,9 +2285,8 @@ }, "Details": [ { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -2058,8 +2298,9 @@ } }, { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -2072,7 +2313,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2157,7 +2398,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2239,7 +2480,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -2253,7 +2494,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -2264,36 +2505,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" - } - ], - "Parameter": [ + }, { - "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::SSM::Parameter", + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + } + ], + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -2331,13 +2614,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { - "recorded-date": "18-06-2025, 19:13:55", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { + "recorded-date": "05-08-2025, 14:24:45", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -2585,33 +2882,61 @@ "Bucket": [ { "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", + "PhysicalResourceId": "-bucket-i4eammke7jqd", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::S3::Bucket", "Timestamp": "timestamp" }, { "LogicalResourceId": "Bucket", - "PhysicalResourceId": "-bucket-rvkyycxytnfz", + "PhysicalResourceId": "-bucket-i4eammke7jqd", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::S3::Bucket", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Bucket", + "PhysicalResourceId": "-bucket-i4eammke7jqd", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Bucket", + "PhysicalResourceId": "-bucket-i4eammke7jqd", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-ytEGT7JWBrkx", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -2649,13 +2974,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { - "recorded-date": "18-06-2025, 19:14:21", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { + "recorded-date": "05-08-2025, 14:25:08", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -2827,7 +3166,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2886,7 +3225,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2968,31 +3307,45 @@ "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -3030,21 +3383,35 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": { - "recorded-date": "18-06-2025, 19:14:21", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": { + "recorded-date": "05-08-2025, 14:25:08", "recorded-content": {} }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": { - "recorded-date": "18-06-2025, 19:14:21", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": { + "recorded-date": "05-08-2025, 14:25:08", "recorded-content": {} }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { - "recorded-date": "18-06-2025, 19:14:47", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { + "recorded-date": "05-08-2025, 14:25:30", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -3295,33 +3662,61 @@ "SSMParameter1": [ { "LogicalResourceId": "SSMParameter1", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "SSMParameter1", - "PhysicalResourceId": "CFN-SSMParameter1-YEPpTp1eTqmV", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter1", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter1", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "SSMParameter2": [ { "LogicalResourceId": "SSMParameter2", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "SSMParameter2", - "PhysicalResourceId": "CFN-SSMParameter2-Cy9JferYSQvx", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter2", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter2", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -3359,20 +3754,34 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { - "recorded-date": "18-06-2025, 19:15:20", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { + "recorded-date": "05-08-2025, 14:26:05", "recorded-content": { "before-value": "", "after-value": "" } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { - "recorded-date": "18-06-2025, 19:15:45", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { + "recorded-date": "05-08-2025, 14:26:31", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -3512,7 +3921,7 @@ } ], "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -3555,7 +3964,7 @@ } ], "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -3619,31 +4028,45 @@ "MySSMParameter": [ { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MySSMParameter", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MySSMParameter", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -3681,9 +4104,1960 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_single_resource_static_update": { + "recorded-date": "05-08-2025, 14:26:48", + "recorded-content": { + "describe-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "parameter-1": { + "ARN": "arn::ssm::111111111111:parameter/", + "DataType": "text", + "LastModifiedDate": "datetime", + "Name": "", + "Type": "String", + "Value": "foo", + "Version": 1 + }, + "describe-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "parameter-2": { + "ARN": "arn::ssm::111111111111:parameter/", + "DataType": "text", + "LastModifiedDate": "datetime", + "Name": "", + "Type": "String", + "Value": "bar", + "Version": 2 + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": { + "recorded-date": "31-07-2025, 14:01:52", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "topic-name", + "Type": "String", + "Description": "original" + } + }, + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "topic-name", + "Type": "String", + "Description": "changed" + } + }, + "BeforeContext": { + "Properties": { + "Value": "topic-name", + "Type": "String", + "Description": "original" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "changed", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "original", + "Name": "Description", + "Path": "/Properties/Description", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Description", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "CausingEntity": "Parameter1.Value", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Dynamic", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-IUxUU6IW7X39", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter1": [ + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Parameter2": [ + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-IUxUU6IW7X39", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": { + "recorded-date": "31-07-2025, 14:10:27", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "value", + "Type": "String", + "Name": "parameter-1-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "value", + "Type": "String", + "Name": "parameter-2-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "value", + "Type": "String", + "Name": "parameter-1-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "parameter-2-name", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "parameter-1-name", + "Name": "Name", + "Path": "/Properties/Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "value", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "value", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + }, + { + "CausingEntity": "Parameter1.Value", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", + "Target": { + "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "value", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-DAxvYPlPS8Me", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "CausingEntity": "Parameter1.Value", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-DAxvYPlPS8Me", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter1": [ + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-2-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Parameter2": [ + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-DAxvYPlPS8Me", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup": { + "recorded-date": "05-08-2025, 22:24:46", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "DerivedParameter": [ + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup_no_change": { + "recorded-date": "05-08-2025, 22:26:18", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "new parameter", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "AnotherParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "AnotherParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "AnotherParameter": [ + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "DerivedParameter": [ + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_describe_failed_change_set": { + "recorded-date": "09-09-2025, 16:22:00", + "recorded-content": { + "describe": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "UNAVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "FAILED", + "StatusReason": "Transform AWS::Include failed with: S3 bucket [doesnotexist] does not exist.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_list_change_sets": { + "recorded-date": "16-09-2025, 14:49:32", + "recorded-content": { + "change-sets": { + "Summaries": [ + { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_queue_update": { + "recorded-date": "05-12-2025, 10:45:20", + "recorded-content": { + "queue-attributes-1": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue-attributes-2": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "4321", + "MessageRetentionPeriod": "17539", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "17", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue-attributes-3": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "262144", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "events": [ + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "StandardQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "CREATE_IN_PROGRESS", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "StandardQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "CREATE_COMPLETE", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "StandardQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "StandardQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "UPDATE_COMPLETE", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "StandardQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "Timestamp": "timestamp" + }, + { + "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", + "LogicalResourceId": "StandardQueue", + "ResourceType": "AWS::SQS::Queue", + "ResourceStatus": "UPDATE_COMPLETE", + "Timestamp": "timestamp" + } + ] + } } } diff --git a/tests/aws/services/cloudformation/test_change_sets.validation.json b/tests/aws/services/cloudformation/test_change_sets.validation.json new file mode 100644 index 0000000000000..fe51926659666 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_sets.validation.json @@ -0,0 +1,167 @@ +{ + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { + "last_validated_date": "2025-08-05T14:25:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 22.53, + "teardown": 0.13, + "total": 22.66 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { + "last_validated_date": "2025-08-05T14:25:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.45, + "teardown": 0.15, + "total": 21.6 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { + "last_validated_date": "2025-08-05T14:26:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 25.72, + "teardown": 0.13, + "total": 25.85 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { + "last_validated_date": "2025-08-05T14:24:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 36.52, + "teardown": 0.13, + "total": 36.65 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { + "last_validated_date": "2025-08-21T11:39:10+00:00", + "durations_in_seconds": { + "setup": 1.16, + "call": 115.43, + "teardown": 0.33, + "total": 116.92 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { + "last_validated_date": "2025-08-05T14:17:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 120.52, + "teardown": 0.13, + "total": 120.65 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { + "last_validated_date": "2025-08-05T14:26:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.7, + "teardown": 6.54, + "total": 35.24 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { + "last_validated_date": "2025-08-05T14:24:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 120.64, + "teardown": 0.14, + "total": 120.78 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { + "last_validated_date": "2025-08-05T14:22:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 124.78, + "teardown": 0.14, + "total": 124.92 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { + "last_validated_date": "2025-08-05T14:20:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 124.89, + "teardown": 0.18, + "total": 125.07 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_queue_update": { + "last_validated_date": "2025-12-05T10:45:54+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 123.03, + "teardown": 33.91, + "total": 157.9 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_single_resource_static_update": { + "last_validated_date": "2025-08-05T14:26:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.65, + "teardown": 0.21, + "total": 16.86 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_describe_failed_change_set": { + "last_validated_date": "2025-09-09T16:22:00+00:00", + "durations_in_seconds": { + "setup": 0.89, + "call": 4.84, + "teardown": 0.15, + "total": 5.88 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup": { + "last_validated_date": "2025-08-05T22:24:46+00:00", + "durations_in_seconds": { + "setup": 1.11, + "call": 26.5, + "teardown": 0.36, + "total": 27.97 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup_no_change": { + "last_validated_date": "2025-08-05T22:26:18+00:00", + "durations_in_seconds": { + "setup": 1.02, + "call": 22.04, + "teardown": 0.39, + "total": 23.45 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_list_change_sets": { + "last_validated_date": "2025-09-16T14:49:36+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 15.36, + "teardown": 4.4, + "total": 20.71 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_single_resource_static_update": { + "last_validated_date": "2025-03-18T16:52:35+00:00" + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": { + "last_validated_date": "2025-07-31T14:10:27+00:00", + "durations_in_seconds": { + "setup": 1.23, + "call": 30.31, + "teardown": 0.12, + "total": 31.66 + } + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": { + "last_validated_date": "2025-07-31T14:01:52+00:00", + "durations_in_seconds": { + "setup": 1.71, + "call": 33.35, + "teardown": 0.13, + "total": 35.19 + } + } +} diff --git a/tests/aws/services/cloudformation/test_cloudformation_ui.py b/tests/aws/services/cloudformation/test_cloudformation_ui.py deleted file mode 100644 index 9974d55dbf9ab..0000000000000 --- a/tests/aws/services/cloudformation/test_cloudformation_ui.py +++ /dev/null @@ -1,22 +0,0 @@ -import requests - -from localstack import config -from localstack.testing.pytest import markers - -CLOUDFORMATION_UI_PATH = "/_localstack/cloudformation/deploy" - - -class TestCloudFormationUi: - @markers.aws.only_localstack - def test_get_cloudformation_ui(self): - # note: we get the external service url here because the UI is hosted on the external - # URL, however if `LOCALSTACK_HOST` is set to a hostname that does not resolve to - # `127.0.0.1` this test will fail. - cfn_ui_url = config.external_service_url() + CLOUDFORMATION_UI_PATH - response = requests.get(cfn_ui_url) - - # we simply test that the UI is available at the right path and that it returns HTML. - assert response.ok - assert "content-type" in response.headers - # this is a bit fragile but assert that the file returned contains at least something related to the UI - assert b"LocalStack" in response.content diff --git a/tests/aws/services/cloudformation/test_list_stacks.py b/tests/aws/services/cloudformation/test_list_stacks.py new file mode 100644 index 0000000000000..19c4414633e05 --- /dev/null +++ b/tests/aws/services/cloudformation/test_list_stacks.py @@ -0,0 +1,57 @@ +import json + +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + +pytestmark = skip_if_legacy_engine(reason="Requires the V2 engine") + + +@markers.aws.validated +def test_listing_stacks(aws_client, deploy_cfn_template, snapshot): + snapshot.add_transformer(snapshot.transform.key_value("StackId")) + snapshot.add_transformer(snapshot.transform.key_value("StackName")) + + template = { + "Parameters": { + "ParameterValue": { + "Type": "String", + } + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "ParameterValue"}, + }, + }, + }, + } + + s1 = f"stack-1-{short_uid()}" + s2 = f"stack-2-{short_uid()}" + s3 = f"stack-3-{short_uid()}" + + p1 = f"1-{short_uid()}" + p2 = f"2-{short_uid()}" + p3 = f"3-{short_uid()}" + + deploy_cfn_template( + stack_name=s1, template=json.dumps(template), parameters={"ParameterValue": p1} + ) + deploy_cfn_template( + stack_name=s2, template=json.dumps(template), parameters={"ParameterValue": p2} + ) + deploy_cfn_template( + stack_name=s3, template=json.dumps(template), parameters={"ParameterValue": p3} + ) + + stacks = aws_client.cloudformation.list_stacks()["StackSummaries"] + + # filter stacks to only include the ones we have captured + # TODO: use the stack ids instead to be clear in the unlikely event of a collision + stacks = [stack for stack in stacks if stack["StackName"] in (s1, s2, s3)] + stacks.sort(key=lambda stack: stack["StackName"]) + snapshot.match("stacks", stacks) diff --git a/tests/aws/services/cloudformation/test_list_stacks.snapshot.json b/tests/aws/services/cloudformation/test_list_stacks.snapshot.json new file mode 100644 index 0000000000000..e5d9e526b9d05 --- /dev/null +++ b/tests/aws/services/cloudformation/test_list_stacks.snapshot.json @@ -0,0 +1,39 @@ +{ + "tests/aws/services/cloudformation/test_list_stacks.py::test_listing_stacks": { + "recorded-date": "24-07-2025, 15:35:20", + "recorded-content": { + "stacks": [ + { + "StackId": "", + "StackName": "", + "CreationTime": "datetime", + "LastUpdatedTime": "datetime", + "StackStatus": "CREATE_COMPLETE", + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + }, + { + "StackId": "", + "StackName": "", + "CreationTime": "datetime", + "LastUpdatedTime": "datetime", + "StackStatus": "CREATE_COMPLETE", + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + }, + { + "StackId": "", + "StackName": "", + "CreationTime": "datetime", + "LastUpdatedTime": "datetime", + "StackStatus": "CREATE_COMPLETE", + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ] + } + } +} diff --git a/tests/aws/services/cloudformation/test_list_stacks.validation.json b/tests/aws/services/cloudformation/test_list_stacks.validation.json new file mode 100644 index 0000000000000..395ca189c5871 --- /dev/null +++ b/tests/aws/services/cloudformation/test_list_stacks.validation.json @@ -0,0 +1,11 @@ +{ + "tests/aws/services/cloudformation/test_list_stacks.py::test_listing_stacks": { + "last_validated_date": "2025-07-24T15:35:33+00:00", + "durations_in_seconds": { + "setup": 1.21, + "call": 24.49, + "teardown": 13.09, + "total": 38.79 + } + } +} diff --git a/tests/aws/services/cloudformation/test_template_engine.py b/tests/aws/services/cloudformation/test_template_engine.py index d039307ef5101..7b84e3608731d 100644 --- a/tests/aws/services/cloudformation/test_template_engine.py +++ b/tests/aws/services/cloudformation/test_template_engine.py @@ -7,6 +7,8 @@ import botocore.exceptions import pytest import yaml +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine from localstack.aws.api.lambda_ import Runtime from localstack.services.cloudformation.engine.yaml_parser import parse_yaml @@ -273,6 +275,16 @@ def test_join_no_value_construct(self, deploy_cfn_template, snapshot, aws_client snapshot.match("join-output", stack.outputs) + @markers.aws.validated + def test_fn_select_has_intrinsic_function(self, deploy_cfn_template, snapshot, aws_client): + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../templates/engine/fn_select_fn_mapp.yml" + ) + ) + + snapshot.match("fn-select-fn-map-output", stack.outputs) + class TestImports: @markers.aws.validated @@ -331,24 +343,27 @@ def test_create_stack_with_ssm_parameters( matching = [arn for arn in topic_arns if parameter_value in arn] assert len(matching) == 1 - tags = aws_client.sns.list_tags_for_resource(ResourceArn=matching[0]) - snapshot.match("topic-tags", tags) + tags = aws_client.sns.list_tags_for_resource(ResourceArn=matching[0])["Tags"] + snapshot.match( + "topic-tags", [tag for tag in tags if not tag["Key"].startswith("aws:cloudformation")] + ) @markers.aws.validated - def test_resolve_ssm(self, create_parameter, deploy_cfn_template): + @skip_if_legacy_engine() + def test_resolve_ssm(self, create_parameter, deploy_cfn_template, snapshot): parameter_key = f"param-key-{short_uid()}" parameter_value = f"param-value-{short_uid()}" + snapshot.add_transformer(snapshot.transform.regex(parameter_value, "")) create_parameter(Name=parameter_key, Value=parameter_value, Type="String") result = deploy_cfn_template( - parameters={"DynamicParameter": parameter_key}, + parameters={"DynamicParameter": parameter_key, "ParameterName": parameter_key}, template_path=os.path.join( os.path.dirname(__file__), "../../templates/resolve_ssm.yaml" ), ) - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value + snapshot.match("results", result.outputs) @markers.aws.validated def test_resolve_ssm_with_version(self, create_parameter, deploy_cfn_template, aws_client): @@ -366,8 +381,12 @@ def test_resolve_ssm_with_version(self, create_parameter, deploy_cfn_template, a Name=parameter_key, Overwrite=True, Type="String", Value=parameter_value_v2 ) + versioned_parameter_reference = f"{parameter_key}:{v1['Version']}" result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}:{v1['Version']}"}, + parameters={ + "DynamicParameter": versioned_parameter_reference, + "ParameterName": versioned_parameter_reference, + }, template_path=os.path.join( os.path.dirname(__file__), "../../templates/resolve_ssm.yaml" ), @@ -393,6 +412,31 @@ def test_resolve_ssm_secure(self, create_parameter, deploy_cfn_template): topic_name = result.outputs["TopicName"] assert topic_name == parameter_value + @skip_if_legacy_engine() + @markers.aws.validated + def test_resolve_ssm_missing_parameter(self, snapshot, deploy_cfn_template): + template = { + "Parameters": { + "InputValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "InputValue"}, + }, + }, + }, + } + with pytest.raises(ClientError) as exc_info: + deploy_cfn_template( + template=json.dumps(template), parameters={"InputValue": "parameter-does-not-exist"} + ) + snapshot.match("error-response", exc_info.value) + @markers.aws.validated def test_ssm_nested_with_nested_stack(self, s3_create_bucket, deploy_cfn_template, aws_client): """ @@ -476,7 +520,7 @@ def test_resolve_secretsmanager(self, create_secret, deploy_cfn_template, templa create_secret(Name=parameter_key, SecretString=parameter_value) result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}"}, + parameters={"DynamicParameter": parameter_key}, template_path=os.path.join( os.path.dirname(__file__), "../../templates", @@ -487,6 +531,23 @@ def test_resolve_secretsmanager(self, create_secret, deploy_cfn_template, templa topic_name = result.outputs["TopicName"] assert topic_name == parameter_value + @markers.aws.validated + def test_resolve_secretsmanager_with_backslashes(self, create_secret, deploy_cfn_template): + parameter_key = f"param-key-{short_uid()}" + secret_value = json.dumps({"password": r"p\\30\asw\\\ord"}) + + create_secret(Name=parameter_key, SecretString=secret_value) + + result = deploy_cfn_template( + parameters={"DynamicParameter": parameter_key}, + template_path=os.path.join( + os.path.dirname(__file__), + "../../templates/resolve_secretsmanager_with_backslashes.yaml", + ), + ) + + assert secret_value == result.outputs["ParameterValue"] + class TestPreviousValues: @pytest.mark.skip(reason="outputs don't behave well in combination with conditions") @@ -781,7 +842,7 @@ def test_attribute_uses_macro(self, deploy_cfn_template, create_lambda_function, assert "test-" in resulting_value @markers.aws.validated - @pytest.mark.skip(reason="Fn::Transform does not support array of transformations") + @skip_if_legacy_engine() def test_scope_order_and_parameters( self, deploy_cfn_template, create_lambda_function, snapshot, aws_client ): @@ -1030,7 +1091,7 @@ def test_functions_and_references_during_transformation( self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client ): """ - This tests shows the state of instrinsic functions during the execution of the macro + This tests shows the state of intrinsic functions during the execution of the macro """ macro_function_path = os.path.join( os.path.dirname(__file__), "../../templates/macros/print_references.py" @@ -1169,7 +1230,7 @@ def test_pyplate_param_type_list(self, deploy_cfn_template, aws_client, snapshot assert bucket_name_output tagging = aws_client.s3.get_bucket_tagging(Bucket=bucket_name_output) - tags_s3 = [tag for tag in tagging["TagSet"]] + tags_s3 = list(tagging["TagSet"]) resp = [] for tag in tags_s3: @@ -1256,3 +1317,29 @@ def test_stack_id(self, deploy_cfn_template, snapshot): snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) snapshot.match("StackId", stack.outputs["StackId"]) + + +@markers.aws.validated +@skip_if_legacy_engine() +def test_no_type(aws_client, snapshot): + template = { + "Resources": { + "Foo": { + "Properties": { + "Name": "foo", + }, + }, + }, + } + + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + with pytest.raises(ClientError) as e: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + TemplateBody=json.dumps(template), + ChangeSetType="CREATE", + ) + + snapshot.match("error", e.value) diff --git a/tests/aws/services/cloudformation/test_template_engine.snapshot.json b/tests/aws/services/cloudformation/test_template_engine.snapshot.json index da52914bdd544..1a5dbd704fc76 100644 --- a/tests/aws/services/cloudformation/test_template_engine.snapshot.json +++ b/tests/aws/services/cloudformation/test_template_engine.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/cloudformation/test_template_engine.py::TestTypes::test_implicit_type_conversion": { - "recorded-date": "29-08-2023, 15:21:22", + "recorded-date": "27-08-2025, 09:39:00", "recorded-content": { "queue": { "Attributes": { @@ -14,7 +14,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perQueue", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -509,7 +509,7 @@ } }, "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "recorded-date": "15-01-2023, 17:54:23", + "recorded-date": "06-08-2025, 10:16:50", "recorded-content": { "stack-details": { "Capabilities": [ @@ -539,18 +539,12 @@ "StackStatus": "CREATE_COMPLETE", "Tags": [] }, - "topic-tags": { - "Tags": [ - { - "Key": "param-value", - "Value": "param " - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 + "topic-tags": [ + { + "Key": "param-value", + "Value": "param " } - } + ] } }, "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_macro_deployment": { @@ -683,5 +677,34 @@ "JoinWithNoValue": "Sample" } } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_fn_select_has_intrinsic_function": { + "recorded-date": "05-08-2025, 18:44:01", + "recorded-content": { + "fn-select-fn-map-output": { + "ParameterValue": "true" + } + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_missing_parameter": { + "recorded-date": "06-08-2025, 09:34:07", + "recorded-content": { + "error-response": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Parameter InputValue should either have input value or default value" + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm": { + "recorded-date": "24-09-2025, 22:06:32", + "recorded-content": { + "results": { + "ParameterValue": "abc:", + "TopicName": "" + } + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::test_no_type": { + "recorded-date": "15-12-2025, 10:55:28", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template format error: [/Resources/Foo] Every Resources object must contain a Type member." + } } } diff --git a/tests/aws/services/cloudformation/test_template_engine.validation.json b/tests/aws/services/cloudformation/test_template_engine.validation.json index e0bbb0be7e342..4919222874297 100644 --- a/tests/aws/services/cloudformation/test_template_engine.validation.json +++ b/tests/aws/services/cloudformation/test_template_engine.validation.json @@ -8,6 +8,15 @@ "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": { "last_validated_date": "2024-06-20T20:41:15+00:00" }, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_fn_select_has_intrinsic_function": { + "last_validated_date": "2025-08-05T18:44:05+00:00", + "durations_in_seconds": { + "setup": 0.23, + "call": 10.41, + "teardown": 4.4, + "total": 15.04 + } + }, "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function": { "last_validated_date": "2024-04-03T07:12:29+00:00" }, @@ -89,11 +98,44 @@ "tests/aws/services/cloudformation/test_template_engine.py::TestPseudoParameters::test_stack_id": { "last_validated_date": "2024-07-18T08:56:47+00:00" }, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager_with_backslashes": { + "last_validated_date": "2025-09-30T15:36:21+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 11.22, + "teardown": 4.49, + "total": 15.72 + } + }, "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": { "last_validated_date": "2024-08-08T21:21:23+00:00" }, "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "last_validated_date": "2023-01-15T16:54:23+00:00" + "last_validated_date": "2025-08-06T10:17:40+00:00", + "durations_in_seconds": { + "setup": 0.86, + "call": 10.93, + "teardown": 49.95, + "total": 61.74 + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm": { + "last_validated_date": "2025-09-24T22:07:22+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 10.67, + "teardown": 50.0, + "total": 61.62 + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_missing_parameter": { + "last_validated_date": "2025-08-06T09:34:07+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 0.29, + "teardown": 0.0, + "total": 1.16 + } }, "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": { "last_validated_date": "2024-07-16T16:38:43+00:00" @@ -102,6 +144,21 @@ "last_validated_date": "2023-06-12T15:08:47+00:00" }, "tests/aws/services/cloudformation/test_template_engine.py::TestTypes::test_implicit_type_conversion": { - "last_validated_date": "2023-08-29T13:21:22+00:00" + "last_validated_date": "2025-08-27T09:39:50+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 39.47, + "teardown": 50.06, + "total": 90.44 + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::test_no_type": { + "last_validated_date": "2025-12-15T10:55:28+00:00", + "durations_in_seconds": { + "setup": 0.75, + "call": 0.27, + "teardown": 0.0, + "total": 1.02 + } } } diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py deleted file mode 100644 index 466a3165ab6cd..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py +++ /dev/null @@ -1,1235 +0,0 @@ -import copy -import json -import os.path - -import pytest -from botocore.exceptions import ClientError -from tests.aws.services.cloudformation.api.test_stacks import ( - MINIMAL_TEMPLATE, -) - -from localstack.aws.connect import ServiceLevelClientFactory -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.cloudformation_utils import ( - load_template_file, - load_template_raw, - render_template, -) -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import ShortCircuitWaitException, poll_condition, wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestUpdates: - @markers.aws.validated - def test_simple_update_single_resource( - self, aws_client: ServiceLevelClientFactory, deploy_cfn_template - ): - value1 = "foo" - value2 = "bar" - stack_name = f"stack-{short_uid()}" - - t1 = { - "Resources": { - "MyParameter": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": value1, - }, - }, - }, - "Outputs": { - "ParameterName": { - "Value": {"Ref": "MyParameter"}, - }, - }, - } - - res = deploy_cfn_template(stack_name=stack_name, template=json.dumps(t1), is_update=False) - parameter_name = res.outputs["ParameterName"] - - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value1 - - t2 = copy.deepcopy(t1) - t2["Resources"]["MyParameter"]["Properties"]["Value"] = value2 - - deploy_cfn_template(stack_name=stack_name, template=json.dumps(t2), is_update=True) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value2 - - res.destroy() - - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Not working in v2 yet" - ) - @markers.aws.validated - def test_simple_update_two_resources( - self, aws_client: ServiceLevelClientFactory, deploy_cfn_template - ): - parameter_name = "my-parameter" - value1 = "foo" - value2 = "bar" - stack_name = f"stack-{short_uid()}" - - t1 = { - "Resources": { - "MyParameter1": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": value1, - }, - }, - "MyParameter2": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Name": parameter_name, - "Type": "String", - "Value": {"Fn::GetAtt": ["MyParameter1", "Value"]}, - }, - }, - }, - } - - res = deploy_cfn_template(stack_name=stack_name, template=json.dumps(t1), is_update=False) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value1 - - t2 = copy.deepcopy(t1) - t2["Resources"]["MyParameter1"]["Properties"]["Value"] = value2 - - deploy_cfn_template(stack_name=stack_name, template=json.dumps(t2), is_update=True) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value2 - - res.destroy() - - @markers.aws.validated - # TODO: the error response is incorrect, however the test is otherwise validated and raises - # an error because the SSM parameter has been deleted (removed from the stack). - @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Test fails with the old engine" - ) - def test_deleting_resource( - self, aws_client: ServiceLevelClientFactory, deploy_cfn_template, snapshot - ): - parameter_name = "my-parameter" - value1 = "foo" - - t1 = { - "Resources": { - "MyParameter1": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": value1, - }, - }, - "MyParameter2": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Name": parameter_name, - "Type": "String", - "Value": {"Fn::GetAtt": ["MyParameter1", "Value"]}, - }, - }, - }, - } - - stack = deploy_cfn_template(template=json.dumps(t1)) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value1 - - t2 = copy.deepcopy(t1) - del t2["Resources"]["MyParameter2"] - - deploy_cfn_template(stack_name=stack.stack_name, template=json.dumps(t2), is_update=True) - with pytest.raises(ClientError) as exc_info: - aws_client.ssm.get_parameter(Name=parameter_name) - - snapshot.match("get-parameter-error", exc_info.value.response) - - -@markers.aws.validated -def test_create_change_set_without_parameters( - cleanup_stacks, cleanup_changesets, is_change_set_created_and_available, aws_client -): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - # make sure the change set wasn't executed (which would create a topic) - topics = aws_client.sns.list_topics() - topic_arns = [x["TopicArn"] for x in topics["Topics"]] - assert not any("sns-topic-simple" in arn for arn in topic_arns) - # stack is initially in REVIEW_IN_PROGRESS state. only after executing the change_set will it change its status - stack_response = aws_client.cloudformation.describe_stacks(StackName=stack_id) - assert stack_response["Stacks"][0]["StackStatus"] == "REVIEW_IN_PROGRESS" - - # Change set can now either be already created/available or it is pending/unavailable - wait_until( - is_change_set_created_and_available(change_set_id), 2, 10, strategy="exponential" - ) - describe_response = aws_client.cloudformation.describe_change_set( - ChangeSetName=change_set_id - ) - - assert describe_response["ChangeSetName"] == change_set_name - assert describe_response["ChangeSetId"] == change_set_id - assert describe_response["StackId"] == stack_id - assert describe_response["StackName"] == stack_name - assert describe_response["ExecutionStatus"] == "AVAILABLE" - assert describe_response["Status"] == "CREATE_COMPLETE" - changes = describe_response["Changes"] - assert len(changes) == 1 - assert changes[0]["Type"] == "Resource" - assert changes[0]["ResourceChange"]["Action"] == "Add" - assert changes[0]["ResourceChange"]["ResourceType"] == "AWS::SNS::Topic" - assert changes[0]["ResourceChange"]["LogicalResourceId"] == "topic123" - finally: - cleanup_stacks([stack_id]) - cleanup_changesets([change_set_id]) - - -# TODO: implement -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="Not properly implemented") -@markers.aws.validated -def test_create_change_set_update_without_parameters( - cleanup_stacks, - cleanup_changesets, - is_change_set_created_and_available, - is_change_set_finished, - snapshot, - aws_client, -): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - """after creating a stack via a CREATE change set we send an UPDATE change set changing the SNS topic name""" - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - change_set_name2 = f"change-set-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - snapshot.match("create_change_set", response) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - # Change set can now either be already created/available or it is pending/unavailable - wait_until(is_change_set_created_and_available(change_set_id)) - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - wait_until(is_change_set_finished(change_set_id)) - template = load_template_raw(template_path) - - update_response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name2, - TemplateBody=template.replace("sns-topic-simple", "sns-topic-simple-2"), - ChangeSetType="UPDATE", - ) - assert wait_until(is_change_set_created_and_available(update_response["Id"])) - snapshot.match( - "describe_change_set", - aws_client.cloudformation.describe_change_set(ChangeSetName=update_response["Id"]), - ) - snapshot.match( - "list_change_set", aws_client.cloudformation.list_change_sets(StackName=stack_name) - ) - - describe_response = aws_client.cloudformation.describe_change_set( - ChangeSetName=update_response["Id"] - ) - changes = describe_response["Changes"] - assert len(changes) == 1 - assert changes[0]["Type"] == "Resource" - change = changes[0]["ResourceChange"] - assert change["Action"] == "Modify" - assert change["ResourceType"] == "AWS::SNS::Topic" - assert change["LogicalResourceId"] == "topic123" - assert "sns-topic-simple" in change["PhysicalResourceId"] - assert change["Replacement"] == "True" - assert "Properties" in change["Scope"] - assert len(change["Details"]) == 1 - assert change["Details"][0]["Target"]["Name"] == "TopicName" - assert change["Details"][0]["Target"]["RequiresRecreation"] == "Always" - finally: - cleanup_changesets(changesets=[change_set_id]) - cleanup_stacks(stacks=[stack_id]) - - -# def test_create_change_set_with_template_url(): -# pass - - -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="change set type not implemented") -@markers.aws.validated -def test_create_change_set_create_existing(cleanup_changesets, cleanup_stacks, aws_client): - """tries to create an already existing stack""" - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=change_set_id - ) - stack_id = response["StackId"] - assert change_set_id - assert stack_id - try: - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) - - with pytest.raises(Exception) as ex: - change_set_name2 = f"change-set-{short_uid()}" - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name2, - TemplateBody=load_template_raw("sns_topic_simple.yaml"), - ChangeSetType="CREATE", - ) - assert ex is not None - finally: - cleanup_changesets([change_set_id]) - cleanup_stacks([stack_id]) - - -@markers.aws.validated -def test_create_change_set_update_nonexisting(aws_client): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - - with pytest.raises(Exception) as ex: - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="UPDATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - err = ex.value.response["Error"] - assert err["Code"] == "ValidationError" - assert "does not exist" in err["Message"] - - -@markers.aws.validated -def test_create_change_set_invalid_params(aws_client): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - with pytest.raises(ClientError) as ex: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="INVALID", - ) - err = ex.value.response["Error"] - assert err["Code"] == "ValidationError" - - -@markers.aws.validated -def test_create_change_set_missing_stackname(aws_client): - """in this case boto doesn't even let us send the request""" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - with pytest.raises(Exception): - aws_client.cloudformation.create_change_set( - StackName="", - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - - -@pytest.mark.skip("CFNV2:Other") -@markers.aws.validated -def test_create_change_set_with_ssm_parameter( - cleanup_changesets, - cleanup_stacks, - is_change_set_created_and_available, - is_stack_created, - aws_client, -): - """References a simple stack parameter""" - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - parameter_name = f"ls-param-{short_uid()}" - parameter_value = f"ls-param-value-{short_uid()}" - sns_topic_logical_id = "topic123" - parameter_logical_id = "parameter123" - - aws_client.ssm.put_parameter(Name=parameter_name, Value=parameter_value, Type="String") - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamicparameter_ssm_string.yaml" - ) - template_rendered = render_template( - load_template_raw(template_path), parameter_name=parameter_name - ) - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_rendered, - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - # make sure the change set wasn't executed (which would create a new topic) - list_topics_response = aws_client.sns.list_topics() - matching_topics = [ - t for t in list_topics_response["Topics"] if parameter_value in t["TopicArn"] - ] - assert matching_topics == [] - - # stack is initially in REVIEW_IN_PROGRESS state. only after executing the change_set will it change its status - stack_response = aws_client.cloudformation.describe_stacks(StackName=stack_id) - assert stack_response["Stacks"][0]["StackStatus"] == "REVIEW_IN_PROGRESS" - - # Change set can now either be already created/available or it is pending/unavailable - wait_until(is_change_set_created_and_available(change_set_id)) - describe_response = aws_client.cloudformation.describe_change_set( - ChangeSetName=change_set_id - ) - - assert describe_response["ChangeSetName"] == change_set_name - assert describe_response["ChangeSetId"] == change_set_id - assert describe_response["StackId"] == stack_id - assert describe_response["StackName"] == stack_name - assert describe_response["ExecutionStatus"] == "AVAILABLE" - assert describe_response["Status"] == "CREATE_COMPLETE" - changes = describe_response["Changes"] - assert len(changes) == 1 - assert changes[0]["Type"] == "Resource" - assert changes[0]["ResourceChange"]["Action"] == "Add" - assert changes[0]["ResourceChange"]["ResourceType"] == "AWS::SNS::Topic" - assert changes[0]["ResourceChange"]["LogicalResourceId"] == sns_topic_logical_id - - parameters = describe_response["Parameters"] - assert len(parameters) == 1 - assert parameters[0]["ParameterKey"] == parameter_logical_id - assert parameters[0]["ParameterValue"] == parameter_name - assert parameters[0]["ResolvedValue"] == parameter_value # the important part - - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - wait_until(is_stack_created(stack_id)) - - topics = aws_client.sns.list_topics() - topic_arns = [x["TopicArn"] for x in topics["Topics"]] - assert any((parameter_value in t) for t in topic_arns) - finally: - cleanup_changesets([change_set_id]) - cleanup_stacks([stack_id]) - - -@pytest.mark.skip("CFNV2:Validation") -@markers.aws.validated -def test_describe_change_set_nonexisting(snapshot, aws_client): - with pytest.raises(Exception) as ex: - aws_client.cloudformation.describe_change_set( - StackName="somestack", ChangeSetName="DoesNotExist" - ) - snapshot.match("exception", ex.value) - - -@pytest.mark.skipif( - condition=not is_aws_cloud(), - reason="fails because of the properties mutation in the result_handler", -) -@markers.aws.validated -def test_execute_change_set( - is_change_set_finished, - is_change_set_created_and_available, - is_change_set_failed_and_unavailable, - cleanup_changesets, - cleanup_stacks, - aws_client, -): - """check if executing a change set succeeds in creating/modifying the resources in changed""" - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - template_body = load_template_raw(template_path) - - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - assert wait_until(is_change_set_created_and_available(change_set_id=change_set_id)) - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - assert wait_until(is_change_set_finished(change_set_id)) - # check if stack resource was created - topics = aws_client.sns.list_topics() - topic_arns = [x["TopicArn"] for x in topics["Topics"]] - assert any(("sns-topic-simple" in t) for t in topic_arns) - - # new change set name - change_set_name = f"change-set-{short_uid()}" - # check if update with identical stack leads to correct behavior - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="UPDATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert wait_until(is_change_set_failed_and_unavailable(change_set_id=change_set_id)) - describe_failed_change_set_result = aws_client.cloudformation.describe_change_set( - ChangeSetName=change_set_id - ) - assert describe_failed_change_set_result["ChangeSetName"] == change_set_name - assert ( - describe_failed_change_set_result["StatusReason"] - == "The submitted information didn't contain changes. Submit different information to create a change set." - ) - with pytest.raises(ClientError) as e: - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - e.match("InvalidChangeSetStatus") - e.match( - rf"ChangeSet \[{change_set_id}\] cannot be executed in its current status of \[FAILED\]" - ) - finally: - cleanup_changesets([change_set_id]) - cleanup_stacks([stack_id]) - - -@markers.aws.validated -def test_delete_change_set_exception(snapshot, aws_client): - """test error cases when trying to delete a change set""" - with pytest.raises(ClientError) as e1: - aws_client.cloudformation.delete_change_set( - StackName="nostack", ChangeSetName="DoesNotExist" - ) - snapshot.match("e1", e1.value.response) - - with pytest.raises(ClientError) as e2: - aws_client.cloudformation.delete_change_set(ChangeSetName="DoesNotExist") - snapshot.match("e2", e2.value.response) - - -@pytest.mark.skip("CFNV2:Other") -@markers.aws.validated -def test_create_delete_create(aws_client, cleanups, deploy_cfn_template): - """test the re-use of a changeset name with a re-used stack name""" - stack_name = f"stack-{short_uid()}" - change_set_name = f"cs-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - with open(template_path) as infile: - template = infile.read() - - # custom cloudformation deploy process since our `deploy_cfn_template` is too smart and uses IDs, unlike the CDK - def deploy(): - client = aws_client.cloudformation - client.create_change_set( - StackName=stack_name, - TemplateBody=template, - ChangeSetName=change_set_name, - ChangeSetType="CREATE", - ) - client.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=change_set_name - ) - - client.execute_change_set(StackName=stack_name, ChangeSetName=change_set_name) - client.get_waiter("stack_create_complete").wait( - StackName=stack_name, - ) - - def delete(suppress_exception: bool = False): - try: - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - except Exception: - if not suppress_exception: - raise - - deploy() - cleanups.append(lambda: delete(suppress_exception=True)) - delete() - deploy() - - -@pytest.mark.skip(reason="CFNV2:Metadata, CFNV2:Other") -@markers.aws.validated -def test_create_and_then_remove_non_supported_resource_change_set(deploy_cfn_template): - # first deploy cfn with a CodeArtifact resource that is not actually supported - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/code_artifact_template.yaml" - ) - template_body = load_template_raw(template_path) - stack = deploy_cfn_template( - template=template_body, - parameters={"CADomainName": f"domainname-{short_uid()}"}, - ) - - # removal of CodeArtifact should not throw exception - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/code_artifact_remove_template.yaml" - ) - template_body = load_template_raw(template_path) - deploy_cfn_template( - is_update=True, - template=template_body, - stack_name=stack.stack_name, - ) - - -@markers.aws.validated -def test_create_and_then_update_refreshes_template_metadata( - aws_client, - cleanup_changesets, - cleanup_stacks, - is_change_set_finished, - is_change_set_created_and_available, -): - stacks_to_cleanup = set() - changesets_to_cleanup = set() - - try: - stack_name = f"stack-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - - template_body = load_template_raw(template_path) - - create_response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=f"change-set-{short_uid()}", - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - - stacks_to_cleanup.add(create_response["StackId"]) - changesets_to_cleanup.add(create_response["Id"]) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=create_response["Id"] - ) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=create_response["Id"] - ) - - wait_until(is_change_set_finished(create_response["Id"])) - - # Note the metadata alone won't change if there are no changes to resources - # TODO: find a better way to make a replacement in yaml template - template_body = template_body.replace( - "TopicName: sns-topic-simple", - "TopicName: sns-topic-simple-updated", - ) - - update_response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=f"change-set-{short_uid()}", - TemplateBody=template_body, - ChangeSetType="UPDATE", - ) - - stacks_to_cleanup.add(update_response["StackId"]) - changesets_to_cleanup.add(update_response["Id"]) - - wait_until(is_change_set_created_and_available(update_response["Id"])) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=update_response["Id"] - ) - - wait_until(is_change_set_finished(update_response["Id"])) - - summary = aws_client.cloudformation.get_template_summary(StackName=stack_name) - - assert "TopicName" in summary["Metadata"] - assert "sns-topic-simple-updated" in summary["Metadata"] - finally: - cleanup_stacks(list(stacks_to_cleanup)) - cleanup_changesets(list(changesets_to_cleanup)) - - -# TODO: the intention of this test is not particularly clear. The resource isn't removed, it'll just generate a new bucket with a new default name -# TODO: rework this to a conditional instead of two templates + parameter usage instead of templating -@markers.aws.validated -def test_create_and_then_remove_supported_resource_change_set(deploy_cfn_template, aws_client): - first_bucket_name = f"test-bucket-1-{short_uid()}" - second_bucket_name = f"test-bucket-2-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/for_removal_setup.yaml" - ) - template_body = load_template_raw(template_path) - - stack = deploy_cfn_template( - template=template_body, - template_mapping={ - "first_bucket_name": first_bucket_name, - "second_bucket_name": second_bucket_name, - }, - ) - assert first_bucket_name in stack.outputs["FirstBucket"] - assert second_bucket_name in stack.outputs["SecondBucket"] - - available_buckets = aws_client.s3.list_buckets() - bucket_names = [bucket["Name"] for bucket in available_buckets["Buckets"]] - assert first_bucket_name in bucket_names - assert second_bucket_name in bucket_names - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/for_removal_remove.yaml" - ) - template_body = load_template_raw(template_path) - stack_updated = deploy_cfn_template( - is_update=True, - template=template_body, - template_mapping={"first_bucket_name": first_bucket_name}, - stack_name=stack.stack_name, - ) - - assert first_bucket_name in stack_updated.outputs["FirstBucket"] - - def assert_bucket_gone(): - available_buckets = aws_client.s3.list_buckets() - bucket_names = [bucket["Name"] for bucket in available_buckets["Buckets"]] - return first_bucket_name in bucket_names and second_bucket_name not in bucket_names - - poll_condition(condition=assert_bucket_gone, timeout=20, interval=5) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..NotificationARNs", - "$..IncludeNestedStacks", - "$..Parameters", - ] -) -@markers.aws.validated -def test_empty_changeset(snapshot, cleanups, aws_client): - """ - Creates a change set that doesn't actually update any resources and then tries to execute it - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - change_set_name_nochange = f"change-set-nochange-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cdkmetadata.yaml" - ) - template = load_template_file(template_path) - - # 1. create change set and execute - - first_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - ChangeSetType="CREATE", - ) - snapshot.match("first_changeset", first_changeset) - - def _check_changeset_available(): - status = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - )["Status"] - if status == "FAILED": - raise ShortCircuitWaitException("Change set in unrecoverable status") - return status == "CREATE_COMPLETE" - - assert wait_until(_check_changeset_available) - - describe_first_cs = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - ) - snapshot.match("describe_first_cs", describe_first_cs) - assert describe_first_cs["ExecutionStatus"] == "AVAILABLE" - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - ) - - def _check_changeset_success(): - status = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - )["ExecutionStatus"] - if status in ["EXECUTE_FAILED", "UNAVAILABLE", "OBSOLETE"]: - raise ShortCircuitWaitException("Change set in unrecoverable status") - return status == "EXECUTE_COMPLETE" - - assert wait_until(_check_changeset_success) - - # 2. create a new change set without changes - nochange_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name_nochange, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - ChangeSetType="UPDATE", - ) - snapshot.match("nochange_changeset", nochange_changeset) - - describe_nochange = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=nochange_changeset["Id"] - ) - snapshot.match("describe_nochange", describe_nochange) - assert describe_nochange["ExecutionStatus"] == "UNAVAILABLE" - - # 3. try to execute the unavailable change set - with pytest.raises(aws_client.cloudformation.exceptions.InvalidChangeSetStatusException) as e: - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=nochange_changeset["Id"] - ) - snapshot.match("error_execute_failed", e.value) - - -@pytest.mark.skip(reason="CFNV2:Other delete change set not implemented yet") -@markers.aws.validated -def test_deleted_changeset(snapshot, cleanups, aws_client): - """simple case verifying that proper exception is thrown when trying to get a deleted changeset""" - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - changeset_name = f"changeset-{short_uid()}" - stack_name = f"stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - snapshot.add_transformer(snapshot.transform.regex(stack_name, "")) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cdkmetadata.yaml" - ) - template = load_template_file(template_path) - - # 1. create change set - create = aws_client.cloudformation.create_change_set( - ChangeSetName=changeset_name, - StackName=stack_name, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - ChangeSetType="CREATE", - ) - snapshot.match("create", create) - - changeset_id = create["Id"] - - def _check_changeset_available(): - status = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=changeset_id - )["Status"] - if status == "FAILED": - raise ShortCircuitWaitException("Change set in unrecoverable status") - return status == "CREATE_COMPLETE" - - assert wait_until(_check_changeset_available) - - # 2. delete change set - aws_client.cloudformation.delete_change_set(ChangeSetName=changeset_id, StackName=stack_name) - - with pytest.raises(aws_client.cloudformation.exceptions.ChangeSetNotFoundException) as e: - aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=changeset_id - ) - snapshot.match("postdelete_changeset_notfound", e.value) - - -@pytest.mark.skip("CFNV2:Capabilities") -@markers.aws.validated -def test_autoexpand_capability_requirement(cleanups, aws_client): - stack_name = f"test-stack-{short_uid()}" - changeset_name = f"test-changeset-{short_uid()}" - queue_name = f"test-queue-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - template_body = load_template_raw( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_macro_languageextensions.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.InsufficientCapabilitiesException): - # requires the capability - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template_body, - Parameters=[ - {"ParameterKey": "QueueList", "ParameterValue": "faa,fbb,fcc"}, - {"ParameterKey": "QueueNameParam", "ParameterValue": queue_name}, - ], - ) - - # does not require the capability - create_changeset_result = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "QueueList", "ParameterValue": "faa,fbb,fcc"}, - {"ParameterKey": "QueueNameParam", "ParameterValue": queue_name}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=create_changeset_result["Id"] - ) - - -# FIXME: a CreateStack operation should work with an existing stack if its in REVIEW_IN_PROGRESS -@pytest.mark.skip(reason="not implemented correctly yet") -@markers.aws.validated -def test_create_while_in_review(aws_client, snapshot, cleanups): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"stack-{short_uid()}" - changeset_name = f"changeset-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - stack_id = changeset["StackId"] - changeset_id = changeset["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=changeset_name - ) - - # I would have actually expected this to throw, but it doesn't - create_stack_while_in_review = aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE - ) - snapshot.match("create_stack_while_in_review", create_stack_while_in_review) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # describe change set and stack (change set is now obsolete) - describe_stack = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_stack", describe_stack) - describe_change_set = aws_client.cloudformation.describe_change_set(ChangeSetName=changeset_id) - snapshot.match("describe_change_set", describe_change_set) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.snapshot.skip_snapshot_verify( - paths=["$..Capabilities", "$..IncludeNestedStacks", "$..NotificationARNs", "$..Parameters"] -) -@markers.aws.validated -def test_multiple_create_changeset(aws_client, snapshot, cleanups): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"repeated-stack-{short_uid()}" - initial_changeset_name = f"initial-changeset-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - snapshot.match( - "initial_changeset", - aws_client.cloudformation.describe_change_set(ChangeSetName=initial_changeset["Id"]), - ) - - # multiple change sets can exist for a given stack - additional_changeset_name = f"additionalchangeset-{short_uid()}" - additional_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=additional_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - snapshot.match("additional_changeset", additional_changeset) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=additional_changeset_name - ) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.snapshot.skip_snapshot_verify(paths=["$..LastUpdatedTime", "$..StackStatusReason"]) -@markers.aws.validated -def test_create_changeset_with_stack_id(aws_client, snapshot, cleanups): - """ - The test answers the question if the `StackName` parameter in `CreateChangeSet` can also be a full Stack ID (ARN). - This can make sense in two cases: - 1. a `CREATE` change set type while the stack is in `REVIEW_IN_PROGRESS` (otherwise it would fail) => covered by this test - 2. an `UPDATE` change set type when the stack has been deployed before already - - On an initial `CREATE` we can't actually know the stack ID yet since the `CREATE` will first create the stack. - - Error case: using `CREATE` with a stack ID from a stack that is in `DELETE_COMPLETE` state. - => A single stack instance identified by a unique ID can never leave its `DELETE_COMPLETE` state - => `DELETE_COMPLETE` is the only *real* terminal state of a Stack - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"repeated-stack-{short_uid()}" - initial_changeset_name = "initial-changeset" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - # create initial change set - initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - initial_stack_id = initial_changeset["StackId"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - - # new CREATE change set on stack that is in REVIEW_IN_PROGRESS state - additional_create_changeset_name = "additional-create" - additional_create_changeset = aws_client.cloudformation.create_change_set( - StackName=initial_stack_id, - ChangeSetName=additional_create_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=additional_create_changeset["Id"] - ) - - describe_stack = aws_client.cloudformation.describe_stacks(StackName=initial_stack_id) - snapshot.match("describe_stack", describe_stack) - - # delete and try to revive the stack with the same ID (won't work) - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - assert ( - aws_client.cloudformation.describe_stacks(StackName=initial_stack_id)["Stacks"][0][ - "StackStatus" - ] - == "DELETE_COMPLETE" - ) - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=initial_stack_id, - ChangeSetName="revived-stack-changeset", - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - snapshot.match("recreate_deleted_with_id_exception", e.value.response) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.snapshot.skip_snapshot_verify( - paths=[ - # gotta skip quite a lot unfortunately - # FIXME: tackle this when fixing API parity of CloudFormation - "$..EnableTerminationProtection", - "$..LastUpdatedTime", - "$..Capabilities", - "$..ChangeSetId", - "$..IncludeNestedStacks", - "$..NotificationARNs", - "$..Parameters", - "$..StackId", - "$..StatusReason", - "$..StackStatusReason", - ] -) -@markers.aws.validated -def test_name_conflicts(aws_client, snapshot, cleanups): - """ - changeset-based equivalent to tests.aws.services.cloudformation.api.test_stacks.test_name_conflicts - - Tests behavior of creating a stack and changeset with the same names of ones that were previously deleted - - 1. Create ChangeSet - 2. Create another ChangeSet - 3. Execute ChangeSet / Create Stack - 4. Creating a new ChangeSet (CREATE) for this stack should fail since it already exists & is running/active - 5. Delete Stack - 6. Create ChangeSet / re-use ChangeSet and Stack names from 1. - - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"repeated-stack-{short_uid()}" - initial_changeset_name = f"initial-changeset-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - initial_stack_id = initial_changeset["StackId"] - initial_changeset_id = initial_changeset["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - - # actually create the stack - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # creating should now fail (stack is created & active) - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - snapshot.match("create_changeset_existingstack_exc", e.value.response) - - # delete stack - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - # creating for stack name with same name should work again - # re-using the changset name should also not matter :) - second_initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - second_initial_stack_id = second_initial_changeset["StackId"] - second_initial_changeset_id = second_initial_changeset["Id"] - assert second_initial_changeset_id != initial_changeset_id - assert initial_stack_id != second_initial_stack_id - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=second_initial_changeset_id - ) - - # only one should be active, and this one is in review state right now - new_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("new_stack_desc", new_stack_desc) - assert len(new_stack_desc["Stacks"]) == 1 - assert new_stack_desc["Stacks"][0]["StackId"] == second_initial_stack_id - - # can still access both by using the ARN (stack id) - # and they should be different from each other - stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=initial_stack_id) - new_stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=second_initial_stack_id) - snapshot.match("stack_id_desc", stack_id_desc) - snapshot.match("new_stack_id_desc", new_stack_id_desc) - - # can still access all change sets by their ID - initial_changeset_id_desc = aws_client.cloudformation.describe_change_set( - ChangeSetName=initial_changeset_id - ) - snapshot.match("initial_changeset_id_desc", initial_changeset_id_desc) - second_initial_changeset_id_desc = aws_client.cloudformation.describe_change_set( - ChangeSetName=second_initial_changeset_id - ) - snapshot.match("second_initial_changeset_id_desc", second_initial_changeset_id_desc) - - -@markers.aws.validated -def test_describe_change_set_with_similarly_named_stacks(deploy_cfn_template, aws_client): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - - # create a changeset - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_keypair.yml" - ) - template_body = load_template_raw(template_path) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - - # delete the stack - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - # create a new changeset with the same name - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - - # ensure that the correct changeset is returned when requested by stack name - assert ( - aws_client.cloudformation.describe_change_set( - ChangeSetName=response["Id"], StackName=stack_name - )["ChangeSetId"] - == response["Id"] - ) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.snapshot.json deleted file mode 100644 index 930b1ff1e8b93..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.snapshot.json +++ /dev/null @@ -1,517 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_update_without_parameters": { - "recorded-date": "31-05-2022, 09:32:02", - "recorded-content": { - "create_change_set": { - "Id": "arn::cloudformation::111111111111:changeSet//", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPStatusCode": 200, - "HTTPHeaders": {} - } - }, - "describe_change_set": { - "ChangeSetName": "", - "ChangeSetId": "arn::cloudformation::111111111111:changeSet//", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "Status": "CREATE_COMPLETE", - "NotificationARNs": [], - "RollbackConfiguration": {}, - "Capabilities": [], - "Changes": [ - { - "Type": "Resource", - "ResourceChange": { - "Action": "Modify", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceType": "AWS::SNS::Topic", - "Replacement": "True", - "Scope": [ - "Properties" - ], - "Details": [ - { - "Target": { - "Attribute": "Properties", - "Name": "TopicName", - "RequiresRecreation": "Always" - }, - "Evaluation": "Static", - "ChangeSource": "DirectModification" - } - ] - } - } - ], - "IncludeNestedStacks": false, - "ResponseMetadata": { - "HTTPStatusCode": 200, - "HTTPHeaders": {} - } - }, - "list_change_set": { - "Summaries": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "ChangeSetId": "arn::cloudformation::111111111111:changeSet//", - "ChangeSetName": "", - "ExecutionStatus": "AVAILABLE", - "Status": "CREATE_COMPLETE", - "CreationTime": "datetime", - "IncludeNestedStacks": false - } - ], - "ResponseMetadata": { - "HTTPStatusCode": 200, - "HTTPHeaders": {} - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_empty_changeset": { - "recorded-date": "10-08-2022, 10:52:55", - "recorded-content": { - "first_changeset": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackId": "arn::cloudformation::111111111111:stack//" - }, - "describe_first_cs": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "CDKMetadata", - "ResourceType": "AWS::CDK::Metadata", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE" - }, - "nochange_changeset": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackId": "arn::cloudformation::111111111111:stack//" - }, - "describe_nochange": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [], - "CreationTime": "datetime", - "ExecutionStatus": "UNAVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "FAILED", - "StatusReason": "The submitted information didn't contain changes. Submit different information to create a change set." - }, - "error_execute_failed": "An error occurred (InvalidChangeSetStatus) when calling the ExecuteChangeSet operation: ChangeSet [arn::cloudformation::111111111111:changeSet/] cannot be executed in its current status of [FAILED]" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_deleted_changeset": { - "recorded-date": "11-08-2022, 11:11:47", - "recorded-content": { - "create": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackId": "arn::cloudformation::111111111111:stack//" - }, - "postdelete_changeset_notfound": "An error occurred (ChangeSetNotFound) when calling the DescribeChangeSet operation: ChangeSet [arn::cloudformation::111111111111:changeSet/] does not exist" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_nonexisting": { - "recorded-date": "11-03-2025, 19:12:57", - "recorded-content": { - "exception": "An error occurred (ValidationError) when calling the DescribeChangeSet operation: Stack [somestack] does not exist" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_delete_change_set_exception": { - "recorded-date": "12-03-2025, 10:14:25", - "recorded-content": { - "e1": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [nostack] does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "e2": { - "Error": { - "Code": "ValidationError", - "Message": "StackName must be specified if ChangeSetName is not specified as an ARN.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_name_conflicts": { - "recorded-date": "22-11-2023, 10:58:04", - "recorded-content": { - "create_changeset_existingstack_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [] already exists and cannot be created again with the changeSet [].", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "new_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "REVIEW_IN_PROGRESS", - "StackStatusReason": "User Initiated", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "new_stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "REVIEW_IN_PROGRESS", - "StackStatusReason": "User Initiated", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "initial_changeset_id_desc": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "EXECUTE_COMPLETE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "second_initial_changeset_id_desc": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_while_in_review": { - "recorded-date": "22-11-2023, 08:49:15", - "recorded-content": { - "create_stack_while_in_review": { - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_change_set": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "OBSOLETE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_template_rendering_with_list": { - "recorded-date": "23-11-2023, 09:23:26", - "recorded-content": { - "resolved-template": { - "d": [ - { - "userid": 1 - }, - 1, - "string" - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_changeset_with_stack_id": { - "recorded-date": "28-11-2023, 07:48:23", - "recorded-content": { - "describe_stack": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "REVIEW_IN_PROGRESS", - "StackStatusReason": "User Initiated", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "recreate_deleted_with_id_exception": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [arn::cloudformation::111111111111:stack//] already exists and cannot be created again with the changeSet [revived-stack-changeset].", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_multiple_create_changeset": { - "recorded-date": "28-11-2023, 07:38:49", - "recorded-content": { - "initial_changeset": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "additional_changeset": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_deleting_resource": { - "recorded-date": "02-06-2025, 10:29:41", - "recorded-content": { - "get-parameter-error": { - "Error": { - "Code": "ParameterNotFound", - "Message": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.validation.json deleted file mode 100644 index fe83ba323389a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.validation.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { - "last_validated_date": "2025-04-03T07:11:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { - "last_validated_date": "2025-04-03T07:13:00+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": { - "last_validated_date": "2025-04-03T07:12:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": { - "last_validated_date": "2025-04-03T07:12:37+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { - "last_validated_date": "2025-04-03T07:23:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_conditions": { - "last_validated_date": "2025-04-01T14:34:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_direct_update": { - "last_validated_date": "2025-04-01T08:32:30+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_dynamic_update": { - "last_validated_date": "2025-04-01T12:30:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_execute_with_ref": { - "last_validated_date": "2025-04-11T14:34:09+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { - "last_validated_date": "2025-04-01T13:31:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { - "last_validated_date": "2025-04-01T13:20:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_parameter_changes": { - "last_validated_date": "2025-04-01T12:43:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": { - "last_validated_date": "2025-04-01T16:46:22+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": { - "last_validated_date": "2025-04-01T16:40:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_deleting_resource": { - "last_validated_date": "2025-06-02T10:29:46+00:00", - "durations_in_seconds": { - "setup": 1.06, - "call": 20.61, - "teardown": 4.46, - "total": 26.13 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": { - "last_validated_date": "2025-04-02T10:05:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_update_without_parameters": { - "last_validated_date": "2022-05-31T07:32:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_changeset_with_stack_id": { - "last_validated_date": "2023-11-28T06:48:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_delete_create": { - "last_validated_date": "2024-08-13T10:46:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_while_in_review": { - "last_validated_date": "2023-11-22T07:49:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_delete_change_set_exception": { - "last_validated_date": "2025-03-12T10:14:25+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_deleted_changeset": { - "last_validated_date": "2022-08-11T09:11:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_nonexisting": { - "last_validated_date": "2025-03-11T19:12:57+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": { - "last_validated_date": "2024-03-06T13:56:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_empty_changeset": { - "last_validated_date": "2022-08-10T08:52:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_multiple_create_changeset": { - "last_validated_date": "2023-11-28T06:38:49+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_name_conflicts": { - "last_validated_date": "2023-11-22T09:58:04+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py deleted file mode 100644 index 483b46808e6a7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py +++ /dev/null @@ -1,36 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.skip(reason="Not implemented") -@markers.aws.validated -def test_drift_detection_on_lambda(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_simple.yml" - ) - ) - - aws_client.lambda_.update_function_configuration( - FunctionName=stack.outputs["LambdaName"], - Runtime="python3.8", - Description="different description", - Environment={"Variables": {"ENDPOINT_URL": "localhost.localstack.cloud"}}, - ) - - drift_detection = aws_client.cloudformation.detect_stack_resource_drift( - StackName=stack.stack_name, LogicalResourceId="Function" - ) - - snapshot.match("drift_detection", drift_detection) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.snapshot.json deleted file mode 100644 index 8584f783fa4ff..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.snapshot.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py::test_drift_detection_on_lambda": { - "recorded-date": "11-11-2022, 08:44:20", - "recorded-content": { - "drift_detection": { - "StackResourceDrift": { - "ActualProperties": { - "Description": "different description", - "Environment": { - "Variables": { - "ENDPOINT_URL": "localhost.localstack.cloud" - } - }, - "Handler": "index.handler", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.8" - }, - "ExpectedProperties": { - "Description": "function to test lambda function url", - "Environment": { - "Variables": { - "ENDPOINT_URL": "aws.amazon.com" - } - }, - "Handler": "index.handler", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9" - }, - "LogicalResourceId": "Function", - "PhysicalResourceId": "stack-0d03b713-Function-ijoJmdBJP4re", - "PropertyDifferences": [ - { - "ActualValue": "different description", - "DifferenceType": "NOT_EQUAL", - "ExpectedValue": "function to test lambda function url", - "PropertyPath": "/Description" - }, - { - "ActualValue": "localhost.localstack.cloud", - "DifferenceType": "NOT_EQUAL", - "ExpectedValue": "aws.amazon.com", - "PropertyPath": "/Environment/Variables/ENDPOINT_URL" - }, - { - "ActualValue": "python3.8", - "DifferenceType": "NOT_EQUAL", - "ExpectedValue": "python3.9", - "PropertyPath": "/Runtime" - } - ], - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack/stack-0d03b713/", - "StackResourceDriftStatus": "MODIFIED", - "Timestamp": "timestamp" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.validation.json deleted file mode 100644 index 65b14bd8a839d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py::test_drift_detection_on_lambda": { - "last_validated_date": "2022-11-11T07:44:20+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py deleted file mode 100644 index 8e5e475341e9a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py +++ /dev/null @@ -1,251 +0,0 @@ -import json -import os -import re - -import botocore -import botocore.errorfactory -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsApi: - @pytest.mark.skip(reason="feature not implemented") - @pytest.mark.parametrize( - "extension_type, extension_name, artifact", - [ - ( - "RESOURCE", - "LocalStack::Testing::TestResource", - "resourcetypes/localstack-testing-testresource.zip", - ), - ( - "MODULE", - "LocalStack::Testing::TestModule::MODULE", - "modules/localstack-testing-testmodule-module.zip", - ), - ("HOOK", "LocalStack::Testing::TestHook", "hooks/localstack-testing-testhook.zip"), - ], - ) - @markers.aws.validated - def test_crud_extension( - self, - deploy_cfn_template, - s3_bucket, - snapshot, - extension_name, - extension_type, - artifact, - aws_client, - ): - bucket_name = s3_bucket - artifact_path = os.path.join( - os.path.dirname(__file__), "../artifacts/extensions/", artifact - ) - key_name = f"key-{short_uid()}" - aws_client.s3.upload_file(artifact_path, bucket_name, key_name) - - register_response = aws_client.cloudformation.register_type( - Type=extension_type, - TypeName=extension_name, - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - - snapshot.add_transformer( - snapshot.transform.key_value("RegistrationToken", "registration-token") - ) - snapshot.add_transformer( - snapshot.transform.key_value("DefaultVersionId", "default-version-id") - ) - snapshot.add_transformer(snapshot.transform.key_value("LogRoleArn", "log-role-arn")) - snapshot.add_transformer(snapshot.transform.key_value("LogGroupName", "log-group-name")) - snapshot.add_transformer( - snapshot.transform.key_value("ExecutionRoleArn", "execution-role-arn") - ) - snapshot.match("register_response", register_response) - - describe_type_response = aws_client.cloudformation.describe_type_registration( - RegistrationToken=register_response["RegistrationToken"] - ) - snapshot.match("describe_type_response", describe_type_response) - - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - - describe_response = aws_client.cloudformation.describe_type( - Arn=describe_type_response["TypeArn"], - ) - snapshot.match("describe_response", describe_response) - - list_response = aws_client.cloudformation.list_type_registrations( - TypeName=extension_name, - ) - snapshot.match("list_response", list_response) - - deregister_response = aws_client.cloudformation.deregister_type( - Arn=describe_type_response["TypeArn"] - ) - snapshot.match("deregister_response", deregister_response) - - @pytest.mark.skip(reason="test not completed") - @markers.aws.validated - def test_extension_versioning(self, s3_bucket, snapshot, aws_client): - """ - This tests validates some api behaviours and errors resulting of creating and deleting versions of extensions. - The process of this test: - - register twice the same extension to have multiple versions - - set the last one as a default one. - - try to delete the whole extension. - - try to delete a version of the extension that doesn't exist. - - delete the first version of the extension. - - try to delete the last available version using the version arn. - - delete the whole extension. - """ - bucket_name = s3_bucket - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/modules/localstack-testing-testmodule-module.zip", - ) - key_name = f"key-{short_uid()}" - aws_client.s3.upload_file(artifact_path, bucket_name, key_name) - - register_response = aws_client.cloudformation.register_type( - Type="MODULE", - TypeName="LocalStack::Testing::TestModule::MODULE", - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - - register_response = aws_client.cloudformation.register_type( - Type="MODULE", - TypeName="LocalStack::Testing::TestModule::MODULE", - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - - versions_response = aws_client.cloudformation.list_type_versions( - TypeName="LocalStack::Testing::TestModule::MODULE", Type="MODULE" - ) - snapshot.match("versions", versions_response) - - set_default_response = aws_client.cloudformation.set_type_default_version( - Arn=versions_response["TypeVersionSummaries"][1]["Arn"] - ) - snapshot.match("set_default_response", set_default_response) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.deregister_type( - Type="MODULE", TypeName="LocalStack::Testing::TestModule::MODULE" - ) - snapshot.match("multiple_versions_error", e.value.response) - - arn = versions_response["TypeVersionSummaries"][1]["Arn"] - with pytest.raises(botocore.errorfactory.ClientError) as e: - arn = re.sub(r"/\d{8}", "99999999", arn) - aws_client.cloudformation.deregister_type(Arn=arn) - snapshot.match("version_not_found_error", e.value.response) - - delete_first_version_response = aws_client.cloudformation.deregister_type( - Arn=versions_response["TypeVersionSummaries"][0]["Arn"] - ) - snapshot.match("delete_unused_version_response", delete_first_version_response) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.deregister_type( - Arn=versions_response["TypeVersionSummaries"][1]["Arn"] - ) - snapshot.match("error_for_deleting_default_with_arn", e.value.response) - - delete_default_response = aws_client.cloudformation.deregister_type( - Type="MODULE", TypeName="LocalStack::Testing::TestModule::MODULE" - ) - snapshot.match("deleting_default_response", delete_default_response) - - @pytest.mark.skip(reason="feature not implemented") - @markers.aws.validated - def test_extension_not_complete(self, s3_bucket, snapshot, aws_client): - """ - This tests validates the error of Extension not found using the describe_type operation when the registration - of the extension is still in progress. - """ - bucket_name = s3_bucket - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/hooks/localstack-testing-testhook.zip", - ) - key_name = f"key-{short_uid()}" - aws_client.s3.upload_file(artifact_path, bucket_name, key_name) - - register_response = aws_client.cloudformation.register_type( - Type="HOOK", - TypeName="LocalStack::Testing::TestHook", - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.describe_type( - Type="HOOK", TypeName="LocalStack::Testing::TestHook" - ) - snapshot.match("not_found_error", e.value) - - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - aws_client.cloudformation.deregister_type( - Type="HOOK", - TypeName="LocalStack::Testing::TestHook", - ) - - @pytest.mark.skip(reason="feature not implemented") - @markers.aws.validated - def test_extension_type_configuration(self, register_extension, snapshot, aws_client): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/hooks/localstack-testing-deployablehook.zip", - ) - extension = register_extension( - extension_type="HOOK", - extension_name="LocalStack::Testing::DeployableHook", - artifact_path=artifact_path, - ) - - extension_configuration = json.dumps( - { - "CloudFormationConfiguration": { - "HookConfiguration": {"TargetStacks": "ALL", "FailureMode": "FAIL"} - } - } - ) - response_set_configuration = aws_client.cloudformation.set_type_configuration( - TypeArn=extension["TypeArn"], Configuration=extension_configuration - ) - snapshot.match("set_type_configuration_response", response_set_configuration) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.batch_describe_type_configurations( - TypeConfigurationIdentifiers=[{}] - ) - snapshot.match("batch_describe_configurations_errors", e.value) - - describe = aws_client.cloudformation.batch_describe_type_configurations( - TypeConfigurationIdentifiers=[ - { - "TypeArn": extension["TypeArn"], - }, - ] - ) - snapshot.match("batch_describe_configurations", describe) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.snapshot.json deleted file mode 100644 index 9b165272441a9..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.snapshot.json +++ /dev/null @@ -1,687 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": { - "recorded-date": "02-03-2023, 16:11:19", - "recorded-content": { - "register_response": { - "RegistrationToken": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_type_response": { - "ProgressStatus": "IN_PROGRESS", - "TypeArn": "arn::cloudformation::111111111111:type/resource/LocalStack-Testing-TestResource", - "TypeVersionArn": "arn::cloudformation::111111111111:type/resource/LocalStack-Testing-TestResource/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_response": { - "Arn": "arn::cloudformation::111111111111:type/resource/LocalStack-Testing-TestResource/", - "DefaultVersionId": "", - "DeprecatedStatus": "LIVE", - "Description": "An example resource schema demonstrating some basic constructs and validation rules.", - "ExecutionRoleArn": "", - "IsDefaultVersion": true, - "LastUpdated": "datetime", - "ProvisioningType": "FULLY_MUTABLE", - "Schema": { - "typeName": "LocalStack::Testing::TestResource", - "description": "An example resource schema demonstrating some basic constructs and validation rules.", - "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", - "definitions": {}, - "properties": { - "Name": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "Name" - ], - "createOnlyProperties": [ - "/properties/Name" - ], - "primaryIdentifier": [ - "/properties/Name" - ], - "handlers": { - "create": { - "permissions": [] - }, - "read": { - "permissions": [] - }, - "update": { - "permissions": [] - }, - "delete": { - "permissions": [] - }, - "list": { - "permissions": [] - } - } - }, - "SourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", - "TimeCreated": "datetime", - "Type": "RESOURCE", - "TypeName": "LocalStack::Testing::TestResource", - "Visibility": "PRIVATE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_response": { - "RegistrationTokenList": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deregister_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": { - "recorded-date": "02-03-2023, 16:11:53", - "recorded-content": { - "register_response": { - "RegistrationToken": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_type_response": { - "ProgressStatus": "IN_PROGRESS", - "TypeArn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE", - "TypeVersionArn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_response": { - "Arn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/", - "DefaultVersionId": "", - "DeprecatedStatus": "LIVE", - "Description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "IsDefaultVersion": true, - "LastUpdated": "datetime", - "Schema": { - "typeName": "LocalStack::Testing::TestModule::MODULE", - "description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "properties": { - "Parameters": { - "type": "object", - "properties": { - "BucketName": { - "type": "object", - "properties": { - "Type": { - "type": "string" - }, - "Description": { - "type": "string" - } - }, - "required": [ - "Type", - "Description" - ], - "description": "Name for the bucket" - } - } - }, - "Resources": { - "properties": { - "S3Bucket": { - "type": "object", - "properties": { - "Type": { - "type": "string", - "const": "AWS::S3::Bucket" - }, - "Properties": { - "type": "object" - } - } - } - }, - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": true - }, - "TimeCreated": "datetime", - "Type": "MODULE", - "TypeName": "LocalStack::Testing::TestModule::MODULE", - "Visibility": "PRIVATE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_response": { - "RegistrationTokenList": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deregister_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": { - "recorded-date": "02-03-2023, 16:12:56", - "recorded-content": { - "register_response": { - "RegistrationToken": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_type_response": { - "ProgressStatus": "IN_PROGRESS", - "TypeArn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-TestHook", - "TypeVersionArn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-TestHook/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_response": { - "Arn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-TestHook/", - "ConfigurationSchema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "examples": [ - { - "CloudFormationConfiguration": { - "HookConfiguration": { - "TargetStacks": "ALL", - "Properties": {}, - "FailureMode": "FAIL" - } - } - } - ], - "description": "This schema validates the CFN hook type configuration that could be set by customers", - "additionalProperties": false, - "title": "CloudFormation Hook Type Configuration Schema", - "type": "object", - "definitions": { - "InvocationPoint": { - "description": "Invocation points are the point in provisioning workflow where hooks will be executed.", - "type": "string", - "enum": [ - "PRE_PROVISION" - ] - }, - "HookTarget": { - "description": "Hook targets are the destination where hooks will be invoked against.", - "additionalProperties": false, - "type": "object", - "properties": { - "InvocationPoint": { - "$ref": "#/definitions/InvocationPoint" - }, - "Action": { - "$ref": "#/definitions/Action" - }, - "TargetName": { - "$ref": "#/definitions/TargetName" - } - }, - "required": [ - "TargetName", - "Action", - "InvocationPoint" - ] - }, - "StackRole": { - "pattern": "arn:.+:iam::[0-9]{12}:role/.+", - "description": "The Amazon Resource Name (ARN) of the IAM execution role to use to perform stack operations", - "type": "string", - "maxLength": 256 - }, - "Action": { - "description": "Target actions are the type of operation hooks will be executed at.", - "type": "string", - "enum": [ - "CREATE", - "UPDATE", - "DELETE" - ] - }, - "TargetName": { - "minLength": 1, - "pattern": "^(?!.*\\*\\?).*$", - "description": "Type name of hook target. Hook targets are the destination where hooks will be invoked against.", - "type": "string", - "maxLength": 256 - }, - "StackName": { - "pattern": "^[a-zA-Z][-a-zA-Z0-9]*$", - "description": "CloudFormation Stack name", - "type": "string", - "maxLength": 128 - } - }, - "properties": { - "CloudFormationConfiguration": { - "additionalProperties": false, - "properties": { - "HookConfiguration": { - "additionalProperties": false, - "type": "object", - "properties": { - "TargetStacks": { - "default": "NONE", - "description": "Attribute to specify which stacks this hook applies to or should get invoked for", - "type": "string", - "enum": [ - "ALL", - "NONE" - ] - }, - "StackFilters": { - "description": "Filters to allow hooks to target specific stack attributes", - "additionalProperties": false, - "type": "object", - "properties": { - "FilteringCriteria": { - "default": "ALL", - "description": "Attribute to specify the filtering behavior. ANY will make the Hook pass if one filter matches. ALL will make the Hook pass if all filters match", - "type": "string", - "enum": [ - "ALL", - "ANY" - ] - }, - "StackNames": { - "description": "List of stack names as filters", - "additionalProperties": false, - "type": "object", - "properties": { - "Exclude": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack names that the hook is going to be excluded from", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackName" - } - }, - "Include": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack names that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackName" - } - } - }, - "minProperties": 1 - }, - "StackRoles": { - "description": "List of stack roles that are performing the stack operations.", - "additionalProperties": false, - "type": "object", - "properties": { - "Exclude": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack roles that the hook is going to be excluded from", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackRole" - } - }, - "Include": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack roles that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackRole" - } - } - }, - "minProperties": 1 - } - }, - "required": [ - "FilteringCriteria" - ] - }, - "TargetFilters": { - "oneOf": [ - { - "additionalProperties": false, - "type": "object", - "properties": { - "Actions": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of actions that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/Action" - } - }, - "TargetNames": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of type names that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/TargetName" - } - }, - "InvocationPoints": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of invocation points that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/InvocationPoint" - } - } - }, - "minProperties": 1 - }, - { - "additionalProperties": false, - "type": "object", - "properties": { - "Targets": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of hook targets", - "type": "array", - "items": { - "$ref": "#/definitions/HookTarget" - } - } - }, - "required": [ - "Targets" - ] - } - ], - "description": "Attribute to specify which targets should invoke the hook", - "type": "object" - }, - "Properties": { - "typeName": "LocalStack::Testing::TestHook", - "description": "Hook runtime properties", - "additionalProperties": false, - "type": "object", - "definitions": {}, - "properties": { - "EncryptionAlgorithm": { - "default": "AES256", - "description": "Encryption algorithm for SSE", - "type": "string" - } - } - }, - "FailureMode": { - "default": "WARN", - "description": "Attribute to specify CloudFormation behavior on hook failure.", - "type": "string", - "enum": [ - "FAIL", - "WARN" - ] - } - }, - "required": [ - "TargetStacks", - "FailureMode" - ] - } - }, - "required": [ - "HookConfiguration" - ] - } - }, - "required": [ - "CloudFormationConfiguration" - ], - "$id": "https://schema.cloudformation..amazonaws.com/cloudformation.hook.configuration.schema.v1.json" - }, - "DefaultVersionId": "", - "DeprecatedStatus": "LIVE", - "Description": "Example resource SSE (Server Side Encryption) verification hook", - "DocumentationUrl": "https://github.com/aws-cloudformation/example-sse-hook/blob/master/README.md", - "IsDefaultVersion": true, - "LastUpdated": "datetime", - "Schema": { - "typeName": "LocalStack::Testing::TestHook", - "description": "Example resource SSE (Server Side Encryption) verification hook", - "sourceUrl": "https://github.com/aws-cloudformation/example-sse-hook", - "documentationUrl": "https://github.com/aws-cloudformation/example-sse-hook/blob/master/README.md", - "typeConfiguration": { - "properties": { - "EncryptionAlgorithm": { - "description": "Encryption algorithm for SSE", - "default": "AES256", - "type": "string" - } - }, - "additionalProperties": false - }, - "required": [], - "handlers": { - "preCreate": { - "targetNames": [ - "AWS::S3::Bucket" - ], - "permissions": [] - }, - "preUpdate": { - "targetNames": [ - "AWS::S3::Bucket" - ], - "permissions": [] - }, - "preDelete": { - "targetNames": [ - "AWS::S3::Bucket" - ], - "permissions": [] - } - }, - "additionalProperties": false - }, - "SourceUrl": "https://github.com/aws-cloudformation/example-sse-hook", - "TimeCreated": "datetime", - "Type": "HOOK", - "TypeName": "LocalStack::Testing::TestHook", - "Visibility": "PRIVATE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_response": { - "RegistrationTokenList": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deregister_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": { - "recorded-date": "02-03-2023, 16:14:12", - "recorded-content": { - "versions": { - "TypeVersionSummaries": [ - { - "Arn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/00000050", - "Description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "IsDefaultVersion": true, - "TimeCreated": "datetime", - "Type": "MODULE", - "TypeName": "LocalStack::Testing::TestModule::MODULE", - "VersionId": "00000050" - }, - { - "Arn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/00000051", - "Description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "IsDefaultVersion": false, - "TimeCreated": "datetime", - "Type": "MODULE", - "TypeName": "LocalStack::Testing::TestModule::MODULE", - "VersionId": "00000051" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "set_default_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "multiple_versions_error": { - "Error": { - "Code": "CFNRegistryException", - "Message": "This type has more than one active version. Please deregister non-default active versions before attempting to deregister the type.", - "Type": "Sender" - }, - "Message": "This type has more than one active version. Please deregister non-default active versions before attempting to deregister the type.", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "version_not_found_error": { - "Error": { - "Code": "CFNRegistryException", - "Message": "TypeName is invalid", - "Type": "Sender" - }, - "Message": "TypeName is invalid", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "delete_unused_version_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "error_for_deleting_default_with_arn": { - "Error": { - "Code": "CFNRegistryException", - "Message": "Version '00000051' is the default version and cannot be deregistered. Deregister the resource type 'LocalStack::Testing::TestModule::MODULE' instead.", - "Type": "Sender" - }, - "Message": "Version '00000051' is the default version and cannot be deregistered. Deregister the resource type 'LocalStack::Testing::TestModule::MODULE' instead.", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleting_default_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": { - "recorded-date": "02-03-2023, 16:15:26", - "recorded-content": { - "not_found_error": "An error occurred (TypeNotFoundException) when calling the DescribeType operation: The type 'LocalStack::Testing::TestHook' cannot be found." - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": { - "recorded-date": "06-03-2023, 15:33:33", - "recorded-content": { - "set_type_configuration_response": { - "ConfigurationArn": "arn::cloudformation::111111111111:type-configuration/hook/LocalStack-Testing-DeployableHook/default", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "batch_describe_configurations_errors": "An error occurred (ValidationError) when calling the BatchDescribeTypeConfigurations operation: 1 validation error detected: Value null at 'typeConfigurationIdentifiers' failed to satisfy constraint: Member must not be null", - "batch_describe_configurations": { - "Errors": [], - "TypeConfigurations": [ - { - "Alias": "default", - "Arn": "arn::cloudformation::111111111111:type-configuration/hook/LocalStack-Testing-DeployableHook/default", - "Configuration": { - "CloudFormationConfiguration": { - "HookConfiguration": { - "TargetStacks": "ALL", - "FailureMode": "FAIL" - } - } - }, - "LastUpdated": "datetime", - "TypeArn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-DeployableHook" - } - ], - "UnprocessedTypeConfigurations": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.validation.json deleted file mode 100644 index 4687c7c2e5103..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": { - "last_validated_date": "2023-03-02T15:12:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": { - "last_validated_date": "2023-03-02T15:11:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": { - "last_validated_date": "2023-03-02T15:11:19+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": { - "last_validated_date": "2023-03-02T15:15:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": { - "last_validated_date": "2023-03-06T14:33:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": { - "last_validated_date": "2023-03-02T15:14:12+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py deleted file mode 100644 index 7f3375678845d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py +++ /dev/null @@ -1,81 +0,0 @@ -import json -import os - -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.cloudformation_utils import load_template_file -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsHooks: - @pytest.mark.skip(reason="feature not implemented") - @pytest.mark.parametrize("failure_mode", ["FAIL", "WARN"]) - @markers.aws.validated - def test_hook_deployment( - self, failure_mode, register_extension, snapshot, cleanups, aws_client - ): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/hooks/localstack-testing-deployablehook.zip", - ) - extension = register_extension( - extension_type="HOOK", - extension_name="LocalStack::Testing::DeployableHook", - artifact_path=artifact_path, - ) - - extension_configuration = json.dumps( - { - "CloudFormationConfiguration": { - "HookConfiguration": {"TargetStacks": "ALL", "FailureMode": failure_mode} - } - } - ) - aws_client.cloudformation.set_type_configuration( - TypeArn=extension["TypeArn"], Configuration=extension_configuration - ) - - template = load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/s3_bucket_name.yml", - ) - ) - - stack_name = f"stack-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template, - Parameters=[{"ParameterKey": "Name", "ParameterValue": f"bucket-{short_uid()}"}], - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - if failure_mode == "WARN": - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - else: - with pytest.raises(botocore.exceptions.WaiterError): - aws_client.cloudformation.get_waiter("stack_create_complete").wait( - StackName=stack_name - ) - - events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ - "StackEvents" - ] - - failed_events = [e for e in events if "HookStatusReason" in e] - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer( - snapshot.transform.key_value( - "EventId", value_replacement="", reference_replacement=False - ) - ) - snapshot.match("event_error", failed_events[0]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.snapshot.json deleted file mode 100644 index c75998e8991f9..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.snapshot.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": { - "recorded-date": "06-03-2023, 15:00:08", - "recorded-content": { - "event_error": { - "EventId": "", - "HookFailureMode": "FAIL", - "HookInvocationPoint": "PRE_PROVISION", - "HookStatus": "HOOK_COMPLETE_FAILED", - "HookStatusReason": "Hook failed with message: Intentional fail", - "HookType": "LocalStack::Testing::DeployableHook", - "LogicalResourceId": "myb3B4550BC", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": { - "recorded-date": "06-03-2023, 15:01:59", - "recorded-content": { - "event_error": { - "EventId": "", - "HookFailureMode": "WARN", - "HookInvocationPoint": "PRE_PROVISION", - "HookStatus": "HOOK_COMPLETE_FAILED", - "HookStatusReason": "Hook failed with message: Intentional fail. Failure was ignored under WARN mode.", - "HookType": "LocalStack::Testing::DeployableHook", - "LogicalResourceId": "myb3B4550BC", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.validation.json deleted file mode 100644 index f20a821925dd1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": { - "last_validated_date": "2023-03-06T14:00:08+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": { - "last_validated_date": "2023-03-06T14:01:59+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py deleted file mode 100644 index 73bc059d62288..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py +++ /dev/null @@ -1,47 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsModules: - @pytest.mark.skip(reason="feature not supported") - @markers.aws.validated - def test_module_usage(self, deploy_cfn_template, register_extension, snapshot, aws_client): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/modules/localstack-testing-testmodule-module.zip", - ) - register_extension( - extension_type="MODULE", - extension_name="LocalStack::Testing::TestModule::MODULE", - artifact_path=artifact_path, - ) - - template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/registry/module.yml", - ) - - module_bucket_name = f"bucket-module-{short_uid()}" - stack = deploy_cfn_template( - template_path=template_path, - parameters={"BucketName": module_bucket_name}, - max_wait=300, - ) - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name)[ - "StackResources" - ] - - snapshot.add_transformer(snapshot.transform.regex(module_bucket_name, "bucket-name-")) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("resource_description", resources[0]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.snapshot.json deleted file mode 100644 index 8696dae584507..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.snapshot.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": { - "recorded-date": "27-02-2023, 16:06:45", - "recorded-content": { - "resource_description": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "BucketModuleS3Bucket", - "ModuleInfo": { - "LogicalIdHierarchy": "BucketModule", - "TypeHierarchy": "LocalStack::Testing::TestModule::MODULE" - }, - "PhysicalResourceId": "bucket-name-hello", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.validation.json deleted file mode 100644 index 8c17cae314b38..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": { - "last_validated_date": "2023-02-27T15:06:45+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py deleted file mode 100644 index c311980ea441e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py +++ /dev/null @@ -1,51 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsResourceTypes: - @pytest.mark.skip(reason="feature not implemented") - @markers.aws.validated - def test_deploy_resource_type( - self, deploy_cfn_template, register_extension, snapshot, aws_client - ): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/resourcetypes/localstack-testing-deployableresource.zip", - ) - - register_extension( - extension_type="RESOURCE", - extension_name="LocalStack::Testing::DeployableResource", - artifact_path=artifact_path, - ) - - template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/registry/resource-provider.yml", - ) - - resource_name = f"name-{short_uid()}" - stack = deploy_cfn_template( - template_path=template_path, parameters={"Name": resource_name}, max_wait=900 - ) - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name)[ - "StackResources" - ] - - snapshot.add_transformer(snapshot.transform.regex(resource_name, "resource-name")) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("resource_description", resources[0]) - - # Make sure to destroy the stack before unregistration - stack.destroy() diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.snapshot.json deleted file mode 100644 index 57898783864f7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.snapshot.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": { - "recorded-date": "28-02-2023, 12:48:27", - "recorded-content": { - "resource_description": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "MyCustomResource", - "PhysicalResourceId": "Test", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "LocalStack::Testing::DeployableResource", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.validation.json deleted file mode 100644 index 51a7ddf2e5932..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": { - "last_validated_date": "2023-02-28T11:48:27+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py deleted file mode 100644 index ad163a709f4db..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py +++ /dev/null @@ -1,366 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError, WaiterError - -from localstack import config -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -# pytestmark = pytest.mark.skipif( -# condition=not is_v2_engine() and not is_aws_cloud(), -# reason="Only targeting the new engine", -# ) - -pytestmark = pytest.mark.skip(reason="CFNV2:NestedStack") - - -@markers.aws.needs_fixing -def test_nested_stack(deploy_cfn_template, s3_create_bucket, aws_client): - # upload template to S3 - artifacts_bucket = f"cf-artifacts-{short_uid()}" - artifacts_path = "stack.yaml" - s3_create_bucket(Bucket=artifacts_bucket, ACL="public-read") - aws_client.s3.put_object( - Bucket=artifacts_bucket, - Key=artifacts_path, - Body=load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/template5.yaml") - ), - ) - - # deploy template - param_value = short_uid() - stack_bucket_name = f"test-{param_value}" # this is the bucket name generated by template5 - - deploy_cfn_template( - template=load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/template6.yaml") - ) - % (artifacts_bucket, artifacts_path), - parameters={"GlobalParam": param_value}, - ) - - # assert that nested resources have been created - def assert_bucket_exists(): - response = aws_client.s3.head_bucket(Bucket=stack_bucket_name) - assert 200 == response["ResponseMetadata"]["HTTPStatusCode"] - - retry(assert_bucket_exists) - - -@markers.aws.validated -def test_nested_stack_output_refs(deploy_cfn_template, s3_create_bucket, aws_client): - """test output handling of nested stacks incl. referencing the nested output in the parent stack""" - bucket_name = s3_create_bucket() - nested_bucket_name = f"test-bucket-nested-{short_uid()}" - key = f"test-key-{short_uid()}" - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-output-refs.nested.yaml", - ), - Bucket=bucket_name, - Key=key, - ) - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-output-refs.yaml" - ), - template_mapping={ - "s3_bucket_url": f"/{bucket_name}/{key}", - "nested_bucket_name": nested_bucket_name, - }, - max_wait=120, # test is flaky, so we need to wait a bit longer - ) - - nested_stack_id = result.outputs["CustomNestedStackId"] - nested_stack_details = aws_client.cloudformation.describe_stacks(StackName=nested_stack_id) - nested_stack_outputs = nested_stack_details["Stacks"][0]["Outputs"] - assert "InnerCustomOutput" not in result.outputs - assert ( - nested_bucket_name - == [ - o["OutputValue"] for o in nested_stack_outputs if o["OutputKey"] == "InnerCustomOutput" - ][0] - ) - assert f"{nested_bucket_name}-suffix" == result.outputs["CustomOutput"] - - -@markers.aws.validated -def test_nested_with_nested_stack(deploy_cfn_template, s3_create_bucket, aws_client): - bucket_name = s3_create_bucket() - bucket_to_create_name = f"test-bucket-{short_uid()}" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - nested_stacks = ["nested_child.yml", "nested_parent.yml"] - urls = [] - - for nested_stack in nested_stacks: - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/", nested_stack), - Bucket=bucket_name, - Key=nested_stack, - ) - - urls.append(f"https://{bucket_name}.s3.{domain}/{nested_stack}") - - outputs = deploy_cfn_template( - max_wait=120 if is_aws_cloud() else None, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested_grand_parent.yml" - ), - parameters={ - "ChildStackURL": urls[0], - "ParentStackURL": urls[1], - "BucketToCreate": bucket_to_create_name, - }, - ).outputs - - assert f"arn:aws:s3:::{bucket_to_create_name}" == outputs["parameterValue"] - - -@markers.aws.validated -@pytest.mark.skip(reason="UPDATE isn't working on nested stacks") -def test_lifecycle_nested_stack(deploy_cfn_template, s3_create_bucket, aws_client): - bucket_name = s3_create_bucket() - nested_bucket_name = f"test-bucket-nested-{short_uid()}" - altered_nested_bucket_name = f"test-bucket-nested-{short_uid()}" - key = f"test-key-{short_uid()}" - - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-output-refs.nested.yaml", - ), - Bucket=bucket_name, - Key=key, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-output-refs.yaml" - ), - template_mapping={ - "s3_bucket_url": f"/{bucket_name}/{key}", - "nested_bucket_name": nested_bucket_name, - }, - ) - assert aws_client.s3.head_bucket(Bucket=nested_bucket_name) - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-output-refs.yaml" - ), - template_mapping={ - "s3_bucket_url": f"/{bucket_name}/{key}", - "nested_bucket_name": altered_nested_bucket_name, - }, - max_wait=120 if is_aws_cloud() else None, - ) - - assert aws_client.s3.head_bucket(Bucket=altered_nested_bucket_name) - - stack.destroy() - - def _assert_bucket_is_deleted(): - try: - aws_client.s3.head_bucket(Bucket=altered_nested_bucket_name) - return False - except ClientError: - return True - - retry(_assert_bucket_is_deleted, retries=5, sleep=2, sleep_before=2) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..Role.AssumeRolePolicyDocument..Action", - ] -) -@markers.aws.validated -def test_nested_output_in_params(deploy_cfn_template, s3_create_bucket, snapshot, aws_client): - """ - Deploys a Stack with two nested stacks (sub1 and sub2) with a dependency between each other sub2 depends on sub1. - The `sub2` stack uses an output parameter of `sub1` as an input parameter. - - Resources: - - Stack - - 2x Nested Stack - - SNS Topic - - IAM role with policy (sns:Publish) - - """ - # upload template to S3 for nested stacks - template_bucket = f"cfn-root-{short_uid()}" - sub1_path = "sub1.yaml" - sub2_path = "sub2.yaml" - s3_create_bucket(Bucket=template_bucket, ACL="public-read") - aws_client.s3.put_object( - Bucket=template_bucket, - Key=sub1_path, - Body=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-outputref/sub1.yaml", - ) - ), - ) - aws_client.s3.put_object( - Bucket=template_bucket, - Key=sub2_path, - Body=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-outputref/sub2.yaml", - ) - ), - ) - topic_name = f"test-topic-{short_uid()}" - role_name = f"test-role-{short_uid()}" - - if is_aws_cloud(): - base_path = "https://s3.amazonaws.com" - else: - base_path = "http://localhost:4566" - - deploy_cfn_template( - template=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-outputref/root.yaml", - ) - ), - parameters={ - "Sub1TemplateUrl": f"{base_path}/{template_bucket}/{sub1_path}", - "Sub2TemplateUrl": f"{base_path}/{template_bucket}/{sub2_path}", - "TopicName": topic_name, - "RoleName": role_name, - }, - ) - # validations - snapshot.add_transformer(snapshot.transform.key_value("RoleId", "role-id")) - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - snapshot.add_transformer(snapshot.transform.regex(role_name, "")) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - get_role_response = aws_client.iam.get_role(RoleName=role_name) - snapshot.match("get_role_response", get_role_response) - role_policies = aws_client.iam.list_role_policies(RoleName=role_name) - snapshot.match("role_policies", role_policies) - policy_name = role_policies["PolicyNames"][0] - actual_policy = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=policy_name) - snapshot.match("actual_policy", actual_policy) - - sns_pager = aws_client.sns.get_paginator("list_topics") - topics = sns_pager.paginate().build_full_result()["Topics"] - filtered_topics = [t["TopicArn"] for t in topics if topic_name in t["TopicArn"]] - assert len(filtered_topics) == 1 - - -@markers.aws.validated -def test_nested_stacks_conditions(deploy_cfn_template, s3_create_bucket, aws_client): - """ - see: TestCloudFormationConditions.test_condition_on_outputs - - equivalent to the condition test but for a nested stack - """ - bucket_name = s3_create_bucket() - nested_bucket_name = f"test-bucket-nested-{short_uid()}" - key = f"test-key-{short_uid()}" - - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-conditions.nested.yaml", - ), - Bucket=bucket_name, - Key=key, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-conditions.yaml" - ), - parameters={ - "S3BucketPath": f"/{bucket_name}/{key}", - "S3BucketName": nested_bucket_name, - }, - ) - - assert stack.outputs["ProdBucket"] == f"{nested_bucket_name}-prod" - assert aws_client.s3.head_bucket(Bucket=stack.outputs["ProdBucket"]) - - # Ensure that nested stack names are correctly generated - nested_stack = aws_client.cloudformation.describe_stacks( - StackName=stack.outputs["NestedStackArn"] - ) - assert ":" not in nested_stack["Stacks"][0]["StackName"] - - -@markers.aws.validated -def test_deletion_of_failed_nested_stack(s3_create_bucket, aws_client, region_name, snapshot): - """ - This test confirms that after deleting a stack parent with a failed nested stack. The nested stack is also deleted - """ - - bucket_name = s3_create_bucket() - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_failed_nested_stack_child.yml" - ), - Bucket=bucket_name, - Key="child.yml", - ) - - stack_name = f"stack-{short_uid()}" - child_template_url = ( - f"https://{bucket_name}.s3.{config.LOCALSTACK_HOST.host_and_port()}/child.yml" - ) - if is_aws_cloud(): - child_template_url = f"https://{bucket_name}.s3.{region_name}.amazonaws.com/child.yml" - - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_failed_nested_stack_parent.yml", - ), - ), - Parameters=[ - {"ParameterKey": "TemplateUri", "ParameterValue": child_template_url}, - ], - OnFailure="DO_NOTHING", - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - - with pytest.raises(WaiterError): - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - stack_status = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][0][ - "StackStatus" - ] - assert stack_status == "CREATE_FAILED" - - stacks = aws_client.cloudformation.describe_stacks()["Stacks"] - nested_stack_name = [ - stack for stack in stacks if f"{stack_name}-ChildStack-" in stack["StackName"] - ][0]["StackName"] - - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - with pytest.raises(ClientError) as ex: - aws_client.cloudformation.describe_stacks(StackName=nested_stack_name) - - snapshot.match("error", ex.value.response) - snapshot.add_transformer(snapshot.transform.regex(nested_stack_name, "")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.snapshot.json deleted file mode 100644 index d343aff512da3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.snapshot.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_output_in_params": { - "recorded-date": "07-02-2023, 10:57:47", - "recorded-content": { - "get_role_response": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "IsTruncated": false, - "PolicyNames": [ - "PolicyA" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "actual_policy": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sns:Publish" - ], - "Effect": "Allow", - "Resource": [ - "arn::sns::111111111111:" - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "PolicyA", - "RoleName": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": { - "recorded-date": "17-09-2024, 20:09:36", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Stack with id does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.validation.json deleted file mode 100644 index 26a6749598c8d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": { - "last_validated_date": "2024-09-17T20:09:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_output_in_params": { - "last_validated_date": "2023-02-07T09:57:47+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py deleted file mode 100644 index b6013fc8dbbcc..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py +++ /dev/null @@ -1,113 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.engine.template_deployer import MOCK_REFERENCE -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.parametrize("attribute_name", ["TopicName", "TopicArn"]) -@markers.aws.validated -def test_nested_getatt_ref(deploy_cfn_template, aws_client, attribute_name, snapshot): - topic_name = f"test-topic-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_getatt_ref.yaml" - ), - parameters={"MyParam": topic_name, "CustomOutputName": attribute_name}, - ) - snapshot.match("outputs", deployment.outputs) - topic_arn = deployment.outputs["MyTopicArn"] - - # Verify the nested GetAtt Ref resolved correctly - custom_ref = deployment.outputs["MyTopicCustom"] - if attribute_name == "TopicName": - assert custom_ref == topic_name - - if attribute_name == "TopicArn": - assert custom_ref == topic_arn - - # Verify resource was created - topic_arns = [t["TopicArn"] for t in aws_client.sns.list_topics()["Topics"]] - assert topic_arn in topic_arns - - -@markers.aws.validated -def test_sub_resolving(deploy_cfn_template, aws_client, snapshot): - """ - Tests different cases for Fn::Sub resolving - - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html - - - TODO: cover all supported functions for VarName / VarValue: - Fn::Base64 - Fn::FindInMap - Fn::GetAtt - Fn::GetAZs - Fn::If - Fn::ImportValue - Fn::Join - Fn::Select - Ref - - """ - topic_name = f"test-topic-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_sub_resovling.yaml" - ), - parameters={"MyParam": topic_name}, - ) - snapshot.match("outputs", deployment.outputs) - topic_arn = deployment.outputs["MyTopicArn"] - - # Verify the parts in the Fn::Sub string are resolved correctly. - sub_output = deployment.outputs["MyTopicSub"] - param, ref, getatt_topicname, getatt_topicarn = sub_output.split("|") - assert param == topic_name - assert ref == topic_arn - assert getatt_topicname == topic_name - assert getatt_topicarn == topic_arn - - map_sub_output = deployment.outputs["MyTopicSubWithMap"] - att_in_map, ref_in_map, static_in_map = map_sub_output.split("|") - assert att_in_map == topic_name - assert ref_in_map == topic_arn - assert static_in_map == "something" - - # Verify resource was created - topic_arns = [t["TopicArn"] for t in aws_client.sns.list_topics()["Topics"]] - assert topic_arn in topic_arns - - -@pytest.mark.skip(reason="CFNV2:Validation") -@markers.aws.only_localstack -def test_reference_unsupported_resource(deploy_cfn_template, aws_client): - """ - This test verifies that templates can be deployed even when unsupported resources are references - Make sure to update the template as coverage of resources increases. - """ - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_ref_unsupported.yml" - ), - ) - - ref_of_unsupported = deployment.outputs["reference"] - value_of_unsupported = deployment.outputs["parameter"] - assert ref_of_unsupported == MOCK_REFERENCE - assert value_of_unsupported == f"The value of the attribute is: {MOCK_REFERENCE}" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.snapshot.json deleted file mode 100644 index 0c364dca777b8..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.snapshot.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": { - "recorded-date": "11-05-2023, 13:43:51", - "recorded-content": { - "outputs": { - "MyTopicArn": "arn::sns::111111111111:", - "MyTopicCustom": "", - "MyTopicName": "", - "MyTopicRef": "arn::sns::111111111111:" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": { - "recorded-date": "11-05-2023, 13:44:18", - "recorded-content": { - "outputs": { - "MyTopicArn": "arn::sns::111111111111:", - "MyTopicCustom": "arn::sns::111111111111:", - "MyTopicName": "", - "MyTopicRef": "arn::sns::111111111111:" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_sub_resolving": { - "recorded-date": "12-05-2023, 07:51:06", - "recorded-content": { - "outputs": { - "MyTopicArn": "arn::sns::111111111111:", - "MyTopicName": "", - "MyTopicRef": "arn::sns::111111111111:", - "MyTopicSub": "|arn::sns::111111111111:||arn::sns::111111111111:", - "MyTopicSubWithMap": "|arn::sns::111111111111:|something" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.validation.json deleted file mode 100644 index eb277de08d538..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": { - "last_validated_date": "2023-05-11T11:44:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": { - "last_validated_date": "2023-05-11T11:43:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_sub_resolving": { - "last_validated_date": "2023-05-12T05:51:06+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py deleted file mode 100644 index e3cda139c5118..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py +++ /dev/null @@ -1,812 +0,0 @@ -import json -import os - -import botocore.exceptions -import pytest -import yaml - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -def get_events_canceled_by_policy(cfn_client, stack_name): - events = cfn_client.describe_stack_events(StackName=stack_name)["StackEvents"] - - failed_events_by_policy = [ - event - for event in events - if "ResourceStatusReason" in event - and ( - "Action denied by stack policy" in event["ResourceStatusReason"] - or "Action not allowed by stack policy" in event["ResourceStatusReason"] - or "Resource update cancelled" in event["ResourceStatusReason"] - ) - ] - - return failed_events_by_policy - - -def delete_stack_after_process(cfn_client, stack_name): - progress_is_finished = False - while not progress_is_finished: - status = cfn_client.describe_stacks(StackName=stack_name)["Stacks"][0]["StackStatus"] - progress_is_finished = "PROGRESS" not in status - cfn_client.delete_stack(StackName=stack_name) - - -class TestStackPolicy: - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_policy_lifecycle(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("initial_policy", obtained_policy) - - policy = { - "Statement": [ - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"} - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"} - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_updated", obtained_policy) - - policy = {} - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_deleted", obtained_policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_policy_with_url(self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client): - """Test to validate the setting of a Stack Policy through an URL""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - bucket_name = s3_create_bucket() - key = "policy.json" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_policy.json"), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - aws_client.cloudformation.set_stack_policy(StackName=stack.stack_name, StackPolicyURL=url) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_invalid_policy_with_url( - self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client - ): - """Test to validate the error response resulting of setting an invalid Stack Policy through an URL""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - bucket_name = s3_create_bucket() - key = "policy.json" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/invalid_stack_policy.json" - ), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyURL=url - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_empty_policy_with_url( - self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client - ): - """Test to validate the setting of an empty Stack Policy through an URL""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - bucket_name = s3_create_bucket() - key = "policy.json" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/empty_policy.json"), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - aws_client.cloudformation.set_stack_policy(StackName=stack.stack_name, StackPolicyURL=url) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_policy_both_policy_and_url( - self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client - ): - """Test to validate the API behavior when trying to set a Stack policy using both the body and the URL""" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - bucket_name = s3_create_bucket() - key = "policy.json" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_policy.json"), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - policy = { - "Statement": [ - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"} - ] - } - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy), StackPolicyURL=url - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_empty_policy(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ), - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = {} - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_not_json_policy(self, deploy_cfn_template, snapshot, aws_client): - """Test to validate the error response when setting and Invalid Policy""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ), - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=short_uid() - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_different_principal_attribute(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": short_uid(), - "Resource": "*", - } - ] - } - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - error_response = ex.value.response["Error"] - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_different_action_attribute(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Delete:*", - "Principal": short_uid(), - "Resource": "*", - } - ] - } - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - @pytest.mark.parametrize("resource_type", ["AWS::S3::Bucket", "AWS::SNS::Topic"]) - def test_prevent_update(self, resource_type, deploy_cfn_template, aws_client): - """ - Test to validate the correct behavior of the update operation on a Stack with a Policy that prevents an update - for a specific resource type - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*", - "Condition": {"StringEquals": {"ResourceType": [resource_type]}}, - }, - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - {"ParameterKey": "BucketName", "ParameterValue": f"new-bucket-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - # if the policy prevents one resource to update the whole update fails - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=5, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - @pytest.mark.parametrize( - "resource", - [ - {"id": "bucket123", "type": "AWS::S3::Bucket"}, - {"id": "topic123", "type": "AWS::SNS::Topic"}, - ], - ) - def test_prevent_deletion(self, resource, deploy_cfn_template, aws_client): - """ - Test to validate that CFn won't delete resources during an update operation that are protected by the Stack - Policy - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:Delete", - "Principal": "*", - "Resource": "*", - "Condition": {"StringEquals": {"ResourceType": [resource["type"]]}}, - } - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - template_dict = yaml.load(template) - del template_dict["Resources"][resource["id"]] - template = yaml.dump(template_dict) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - {"ParameterKey": "BucketName", "ParameterValue": f"new-bucket-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=6, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_prevent_modifying_with_policy_specifying_resource_id( - self, deploy_cfn_template, aws_client - ): - """ - Test to validate that CFn won't modify a resource protected by a stack policy that specifies the resource - using the logical Resource Id - """ - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/simple_api.yaml") - ) - stack = deploy_cfn_template( - template=template, - parameters={"ApiName": f"api-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:Modify", - "Principal": "*", - "Resource": "LogicalResourceId/Api", - } - ] - } - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - TemplateBody=template, - StackName=stack.stack_name, - Parameters=[ - {"ParameterKey": "ApiName", "ParameterValue": f"new-api-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=6, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_prevent_replacement(self, deploy_cfn_template, aws_client): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:Replace", - "Principal": "*", - "Resource": "*", - } - ] - } - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"bucket-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=6, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_update_with_policy(self, deploy_cfn_template, aws_client): - """ - Test to validate the completion of a stack update that is allowed by the Stack Policy - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*", - "Condition": {"StringEquals": {"ResourceType": ["AWS::EC2::Subnet"]}}, - }, - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_update_with_empty_policy(self, deploy_cfn_template, is_stack_updated, aws_client): - """ - Test to validate the behavior of a stack update that has an empty Stack Policy - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - aws_client.cloudformation.set_stack_policy(StackName=stack.stack_name, StackPolicyBody="{}") - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - {"ParameterKey": "BucketName", "ParameterValue": f"new-bucket-{short_uid()}"}, - ], - ) - - def _assert_stack_is_updated(): - assert is_stack_updated(stack.stack_name) - - retry(_assert_stack_is_updated, retries=5, sleep=2, sleep_before=1) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - @pytest.mark.parametrize("reverse_statements", [False, True]) - def test_update_with_overlapping_policies( - self, reverse_statements, deploy_cfn_template, is_stack_updated, aws_client - ): - """ - This test validates the behaviour when two statements in policy contradict each other. - According to the AWS triage, the last statement is the one that is followed. - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - statements = [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - - if reverse_statements: - statements.reverse() - - policy = {"Statement": statements} - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - ], - ) - - def _assert_stack_is_updated(): - assert is_stack_updated(stack.stack_name) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - retry( - _assert_stack_is_updated if not reverse_statements else _assert_failing_update_state, - retries=5, - sleep=2, - sleep_before=2, - ) - - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_create_stack_with_policy(self, snapshot, cleanup_stacks, aws_client): - stack_name = f"stack-{short_uid()}" - - policy = { - "Statement": [ - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - - aws_client.cloudformation.create_stack( - StackName=stack_name, - StackPolicyBody=json.dumps(policy), - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"} - ], - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack_name) - snapshot.match("policy", obtained_policy) - cleanup_stacks([stack_name]) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_policy_with_update_operation( - self, deploy_cfn_template, is_stack_updated, snapshot, cleanup_stacks, aws_client - ): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/simple_api.yaml") - ) - stack = deploy_cfn_template( - template=template, - parameters={"ApiName": f"api-{short_uid()}"}, - ) - - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "ApiName", "ParameterValue": f"api-{short_uid()}"}, - ], - StackPolicyBody=json.dumps(policy), - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - # This part makes sure that the policy being set during the last update doesn't affect the requested changes - def _assert_stack_is_updated(): - assert is_stack_updated(stack.stack_name) - - retry(_assert_stack_is_updated, retries=5, sleep=2, sleep_before=1) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_after_update", obtained_policy) - - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_policy_during_update( - self, deploy_cfn_template, is_stack_updated, snapshot, cleanup_stacks, aws_client - ): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/simple_api.yaml") - ) - stack = deploy_cfn_template( - template=template, - parameters={"ApiName": f"api-{short_uid()}"}, - ) - - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "ApiName", "ParameterValue": f"api-{short_uid()}"}, - ], - StackPolicyDuringUpdateBody=json.dumps(policy), - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_during_update", obtained_policy) - - def _assert_update_failed(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - retry(_assert_update_failed, retries=5, sleep=2, sleep_before=1) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_after_update", obtained_policy) - - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="feature not implemented") - def test_prevent_stack_update(self, deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - stack = deploy_cfn_template( - template=template, parameters={"TopicName": f"topic-{short_uid()}"} - ) - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"} - ], - ) - - def _assert_failing_update_state(): - events = aws_client.cloudformation.describe_stack_events(StackName=stack.stack_name)[ - "StackEvents" - ] - failed_event_update = [ - event for event in events if event["ResourceStatus"] == "UPDATE_FAILED" - ] - assert failed_event_update - assert "Action denied by stack policy" in failed_event_update[0]["ResourceStatusReason"] - - try: - retry(_assert_failing_update_state, retries=5, sleep=2, sleep_before=2) - finally: - progress_is_finished = False - while not progress_is_finished: - status = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)[ - "Stacks" - ][0]["StackStatus"] - progress_is_finished = "PROGRESS" not in status - aws_client.cloudformation.delete_stack(StackName=stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="feature not implemented") - def test_prevent_resource_deletion(self, deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - - template = template.replace("DeletionPolicy: Delete", "DeletionPolicy: Retain") - stack = deploy_cfn_template( - template=template, parameters={"TopicName": f"topic-{short_uid()}"} - ) - aws_client.cloudformation.delete_stack(StackName=stack.stack_name) - - aws_client.sns.get_topic_attributes(TopicArn=stack.outputs["TopicArn"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.snapshot.json deleted file mode 100644 index 46160d7841335..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.snapshot.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": { - "recorded-date": "10-11-2022, 12:40:34", - "recorded-content": { - "policy": { - "StackPolicyBody": {}, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_invalid_policy": { - "recorded-date": "14-11-2022, 15:13:18", - "recorded-content": { - "error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": { - "recorded-date": "15-11-2022, 16:02:20", - "recorded-content": { - "initial_policy": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Allow", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_updated": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_deleted": { - "StackPolicyBody": {}, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": { - "recorded-date": "11-11-2022, 13:58:17", - "recorded-content": { - "policy": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Allow", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": { - "recorded-date": "11-11-2022, 14:07:44", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": { - "recorded-date": "11-11-2022, 14:19:19", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "You cannot specify both StackPolicyURL and StackPolicyBody", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": { - "recorded-date": "11-11-2022, 14:25:18", - "recorded-content": { - "policy": { - "StackPolicyBody": {}, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": { - "recorded-date": "21-11-2022, 15:48:27", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": { - "recorded-date": "16-11-2022, 11:01:36", - "recorded-content": { - "error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": { - "recorded-date": "21-11-2022, 15:44:16", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": { - "recorded-date": "16-11-2022, 15:42:23", - "recorded-content": { - "policy": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Allow", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": { - "recorded-date": "17-11-2022, 11:04:31", - "recorded-content": { - "policy": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_after_update": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": { - "recorded-date": "17-11-2022, 11:09:28", - "recorded-content": { - "policy_during_update": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_after_update": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": { - "recorded-date": "28-10-2022, 12:10:42", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": { - "recorded-date": "28-10-2022, 12:29:11", - "recorded-content": {} - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.validation.json deleted file mode 100644 index 3b728f9fbb277..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.validation.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": { - "last_validated_date": "2022-11-16T14:42:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": { - "last_validated_date": "2022-11-21T14:44:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": { - "last_validated_date": "2022-11-16T10:01:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": { - "last_validated_date": "2022-11-10T11:40:34+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": { - "last_validated_date": "2022-11-21T14:48:27+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": { - "last_validated_date": "2022-11-17T10:09:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": { - "last_validated_date": "2022-11-15T15:02:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": { - "last_validated_date": "2022-10-28T10:29:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": { - "last_validated_date": "2022-10-28T10:10:42+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": { - "last_validated_date": "2022-11-11T13:25:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": { - "last_validated_date": "2022-11-11T13:07:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": { - "last_validated_date": "2022-11-11T13:19:19+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": { - "last_validated_date": "2022-11-17T10:04:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": { - "last_validated_date": "2022-11-11T12:58:17+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py deleted file mode 100644 index a2d262a76e8e0..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py +++ /dev/null @@ -1,1107 +0,0 @@ -import json -import os -from collections import OrderedDict -from itertools import permutations - -import botocore.exceptions -import pytest -import yaml -from botocore.exceptions import WaiterError -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.aws.api.cloudformation import Capability -from localstack.services.cloudformation.engine.entities import StackIdentifier -from localstack.services.cloudformation.engine.yaml_parser import parse_yaml -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry, wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestStacksApi: - @pytest.mark.skip(reason="CFNV2:Other") - @markers.snapshot.skip_snapshot_verify( - paths=["$..ChangeSetId", "$..EnableTerminationProtection"] - ) - @markers.aws.validated - def test_stack_lifecycle(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("ParameterValue", "parameter-value")) - api_name = f"test_{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.yaml" - ) - - deployed = deploy_cfn_template( - template_path=template_path, - parameters={"ApiName": api_name}, - ) - stack_name = deployed.stack_name - creation_description = aws_client.cloudformation.describe_stacks(StackName=stack_name)[ - "Stacks" - ][0] - snapshot.match("creation", creation_description) - - api_name = f"test_{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=deployed.stack_name, - template_path=template_path, - parameters={"ApiName": api_name}, - ) - update_description = aws_client.cloudformation.describe_stacks(StackName=stack_name)[ - "Stacks" - ][0] - snapshot.match("update", update_description) - - aws_client.cloudformation.delete_stack( - StackName=stack_name, - ) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("describe_deleted_by_name_exc", e.value.response) - - deleted = aws_client.cloudformation.describe_stacks(StackName=deployed.stack_id)["Stacks"][ - 0 - ] - assert "DeletionTime" in deleted - snapshot.match("deleted", deleted) - - @pytest.mark.skip(reason="CFNV2:DescribeStacks") - @markers.aws.validated - def test_stack_description_special_chars(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "test .test.net", - "Resources": { - "TestResource": { - "Type": "AWS::EC2::VPC", - "Properties": {"CidrBlock": "100.30.20.0/20"}, - } - }, - } - deployed = deploy_cfn_template(template=json.dumps(template)) - response = aws_client.cloudformation.describe_stacks(StackName=deployed.stack_id)["Stacks"][ - 0 - ] - snapshot.match("describe_stack", response) - - @markers.aws.validated - def test_stack_name_creation(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"*@{short_uid()}_$" - - with pytest.raises(Exception) as e: - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_template.yaml" - ), - stack_name=stack_name, - ) - - snapshot.match("stack_response", e.value.response) - - @pytest.mark.skip(reason="CFNV2:Other") - @markers.aws.validated - @pytest.mark.parametrize("fileformat", ["yaml", "json"]) - def test_get_template_using_create_stack(self, snapshot, fileformat, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"stack-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), - f"../../../../../templates/sns_topic_template.{fileformat}", - ) - ), - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - template_original = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Original" - ) - snapshot.match("template_original", template_original) - - template_processed = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.match("template_processed", template_processed) - - @pytest.mark.skip(reason="CFNV2:Other") - @markers.aws.validated - @pytest.mark.parametrize("fileformat", ["yaml", "json"]) - def test_get_template_using_changesets( - self, deploy_cfn_template, snapshot, fileformat, aws_client - ): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - f"../../../../../templates/sns_topic_template.{fileformat}", - ) - ) - - template_original = aws_client.cloudformation.get_template( - StackName=stack.stack_id, TemplateStage="Original" - ) - snapshot.match("template_original", template_original) - - template_processed = aws_client.cloudformation.get_template( - StackName=stack.stack_id, TemplateStage="Processed" - ) - snapshot.match("template_processed", template_processed) - - @pytest.mark.skip(reason="CFNV2:Other, CFNV2:DescribeStack") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=["$..ParameterValue", "$..PhysicalResourceId", "$..Capabilities"] - ) - def test_stack_update_resources( - self, - deploy_cfn_template, - is_change_set_finished, - is_change_set_created_and_available, - snapshot, - aws_client, - ): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) - - api_name = f"test_{short_uid()}" - - # create stack - deployed = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.yaml" - ), - parameters={"ApiName": api_name}, - ) - stack_name = deployed.stack_name - stack_id = deployed.stack_id - - # assert snapshot of created stack - snapshot.match( - "stack_created", - aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][0], - ) - - # update stack, with one additional resource - api_name = f"test_{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=deployed.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.update.yaml" - ), - parameters={"ApiName": api_name}, - ) - - # assert snapshot of updated stack - snapshot.match( - "stack_updated", - aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][0], - ) - - # describe stack resources - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name) - snapshot.match("stack_resources", resources) - - @pytest.mark.skip(reason="CFNV2:Other, CFNV2:DescribeStack") - @markers.aws.needs_fixing - def test_list_stack_resources_for_removed_resource(self, deploy_cfn_template, aws_client): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/eventbridge_policy.yaml" - ) - event_bus_name = f"bus-{short_uid()}" - stack = deploy_cfn_template( - template_path=template_path, - parameters={"EventBusName": event_bus_name}, - ) - - resources = aws_client.cloudformation.list_stack_resources(StackName=stack.stack_name)[ - "StackResourceSummaries" - ] - resources_before = len(resources) - assert resources_before == 3 - statuses = {res["ResourceStatus"] for res in resources} - assert statuses == {"CREATE_COMPLETE"} - - # remove one resource from the template, then update stack (via change set) - template_dict = parse_yaml(load_file(template_path)) - template_dict["Resources"].pop("eventPolicy2") - template2 = yaml.dump(template_dict) - - deploy_cfn_template( - stack_name=stack.stack_name, - is_update=True, - template=template2, - parameters={"EventBusName": event_bus_name}, - ) - - # get list of stack resources, again - make sure that deleted resource is not contained in result - resources = aws_client.cloudformation.list_stack_resources(StackName=stack.stack_name)[ - "StackResourceSummaries" - ] - assert len(resources) == resources_before - 1 - statuses = {res["ResourceStatus"] for res in resources} - assert statuses == {"UPDATE_COMPLETE"} - - @markers.aws.validated - def test_update_stack_with_same_template_withoutchange( - self, deploy_cfn_template, aws_client, snapshot - ): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_no_change.yaml" - ) - ) - stack = deploy_cfn_template(template=template) - - with pytest.raises(Exception) as ctx: # TODO: capture proper exception - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, TemplateBody=template - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack.stack_name - ) - - snapshot.match("no_change_exception", ctx.value.response) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.validated - def test_update_stack_with_same_template_withoutchange_transformation( - self, deploy_cfn_template, aws_client - ): - template = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/simple_no_change_with_transformation.yaml", - ) - ) - stack = deploy_cfn_template(template=template) - - # transformations will always work even if there's no change in the template! - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack.stack_name - ) - - @markers.aws.validated - def test_update_stack_actual_update(self, deploy_cfn_template, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sqs_queue_update.yml") - ) - queue_name = f"test-queue-{short_uid()}" - stack = deploy_cfn_template( - template=template, parameters={"QueueName": queue_name}, max_wait=360 - ) - - queue_arn_1 = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueUrl"], AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - assert queue_arn_1 - - stack2 = deploy_cfn_template( - template=template, - stack_name=stack.stack_name, - parameters={"QueueName": f"{queue_name}-new"}, - is_update=True, - max_wait=360, - ) - - queue_arn_2 = aws_client.sqs.get_queue_attributes( - QueueUrl=stack2.outputs["QueueUrl"], AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - assert queue_arn_2 - - assert queue_arn_1 != queue_arn_2 - - @markers.snapshot.skip_snapshot_verify(paths=["$..StackEvents"]) - @markers.aws.validated - def test_list_events_after_deployment(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(SortingTransformer("StackEvents", lambda x: x["Timestamp"])) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - ) - response = aws_client.cloudformation.describe_stack_events(StackName=stack.stack_name) - snapshot.match("events", response) - - @markers.aws.validated - @pytest.mark.skip(reason="disable rollback not supported") - @pytest.mark.parametrize("rollback_disabled, length_expected", [(False, 0), (True, 1)]) - def test_failure_options_for_stack_creation( - self, rollback_disabled, length_expected, aws_client - ): - template_with_error = open( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/multiple_bucket.yaml" - ), - "r", - ).read() - - stack_name = f"stack-{short_uid()}" - bucket_1_name = f"bucket-{short_uid()}" - bucket_2_name = f"bucket!#${short_uid()}" - - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template_with_error, - DisableRollback=rollback_disabled, - Parameters=[ - {"ParameterKey": "BucketName1", "ParameterValue": bucket_1_name}, - {"ParameterKey": "BucketName2", "ParameterValue": bucket_2_name}, - ], - ) - - assert wait_until( - lambda _: stack_process_is_finished(aws_client.cloudformation, stack_name), - wait=10, - strategy="exponential", - ) - - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name)[ - "StackResources" - ] - created_resources = [ - resource for resource in resources if "CREATE_COMPLETE" in resource["ResourceStatus"] - ] - assert len(created_resources) == length_expected - - aws_client.cloudformation.delete_stack(StackName=stack_name) - - @markers.aws.validated - @pytest.mark.skipif(reason="disable rollback not enabled", condition=not is_aws_cloud()) - @pytest.mark.parametrize("rollback_disabled, length_expected", [(False, 2), (True, 1)]) - def test_failure_options_for_stack_update( - self, rollback_disabled, length_expected, aws_client, cleanups - ): - stack_name = f"stack-{short_uid()}" - template = open( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/multiple_bucket_update.yaml" - ), - "r", - ).read() - - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template, - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - def _assert_stack_process_finished(): - return stack_process_is_finished(aws_client.cloudformation, stack_name) - - assert wait_until(_assert_stack_process_finished) - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name)[ - "StackResources" - ] - created_resources = [ - resource for resource in resources if "CREATE_COMPLETE" in resource["ResourceStatus"] - ] - assert len(created_resources) == 2 - - aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template, - DisableRollback=rollback_disabled, - Parameters=[ - {"ParameterKey": "Days", "ParameterValue": "-1"}, - ], - ) - - assert wait_until(_assert_stack_process_finished) - - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name)[ - "StackResources" - ] - updated_resources = [ - resource - for resource in resources - if resource["ResourceStatus"] in ["CREATE_COMPLETE", "UPDATE_COMPLETE"] - ] - assert len(updated_resources) == length_expected - - @pytest.mark.skip(reason="CFNV2:Other") - @markers.aws.only_localstack - def test_create_stack_with_custom_id( - self, aws_client, cleanups, account_id, region_name, set_resource_custom_id - ): - stack_name = f"stack-{short_uid()}" - custom_id = short_uid() - - set_resource_custom_id( - StackIdentifier(account_id, region_name, stack_name), custom_id=custom_id - ) - template = open( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - "r", - ).read() - - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template, - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - assert stack["StackId"].split("/")[-1] == custom_id - - # We need to wait until the stack is created otherwise we can end up in a scenario - # where we try to delete the stack before creating its resources, failing the test - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - -def stack_process_is_finished(cfn_client, stack_name): - return ( - "PROGRESS" - not in cfn_client.describe_stacks(StackName=stack_name)["Stacks"][0]["StackStatus"] - ) - - -@markers.aws.validated -@pytest.mark.skip(reason="Not Implemented") -def test_linting_error_during_creation(snapshot, aws_client): - stack_name = f"stack-{short_uid()}" - bad_template = {"Resources": "", "Outputs": ""} - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=json.dumps(bad_template) - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - -@markers.aws.validated -@pytest.mark.skip(reason="feature not implemented") -def test_notifications( - deploy_cfn_template, - sns_create_topic, - is_stack_created, - is_stack_updated, - sqs_create_queue, - sns_create_sqs_subscription, - cleanup_stacks, - aws_client, -): - stack_name = f"stack-{short_uid()}" - topic_arn = sns_create_topic()["TopicArn"] - sqs_url = sqs_create_queue() - sns_create_sqs_subscription(topic_arn, sqs_url) - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - aws_client.cloudformation.create_stack( - StackName=stack_name, - NotificationARNs=[topic_arn], - TemplateBody=template, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - cleanup_stacks([stack_name]) - - assert wait_until(is_stack_created(stack_name)) - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}, - ], - ) - assert wait_until(is_stack_updated(stack_name)) - - messages = {} - - def _assert_messages(): - sqs_messages = aws_client.sqs.receive_message(QueueUrl=sqs_url)["Messages"] - for sqs_message in sqs_messages: - sns_message = json.loads(sqs_message["Body"]) - messages.update({sns_message["MessageId"]: sns_message}) - - # Assert notifications of resources created - assert [message for message in messages.values() if "CREATE_" in message["Message"]] - - # Assert notifications of resources deleted - assert [message for message in messages.values() if "UPDATE_" in message["Message"]] - - # Assert notifications of resources deleted - assert [message for message in messages.values() if "DELETE_" in message["Message"]] - - retry(_assert_messages, retries=10, sleep=2) - - -@pytest.mark.skip(reason="CFNV2:Other, CFNV2:Describe") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - # parameters may be out of order - "$..Stacks..Parameters", - ] -) -def test_updating_an_updated_stack_sets_status(deploy_cfn_template, snapshot, aws_client): - """ - The status of a stack that has been updated twice should be "UPDATE_COMPLETE" - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - # need multiple templates to support updates to the stack - template_1 = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_update_1.yaml") - ) - template_2 = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_update_2.yaml") - ) - template_3 = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_update_3.yaml") - ) - - topic_1_name = f"topic-1-{short_uid()}" - topic_2_name = f"topic-2-{short_uid()}" - topic_3_name = f"topic-3-{short_uid()}" - snapshot.add_transformers_list( - [ - snapshot.transform.regex(topic_1_name, "topic-1"), - snapshot.transform.regex(topic_2_name, "topic-2"), - snapshot.transform.regex(topic_3_name, "topic-3"), - ] - ) - - parameters = { - "Topic1Name": topic_1_name, - "Topic2Name": topic_2_name, - "Topic3Name": topic_3_name, - } - - def wait_for(waiter_type: str) -> None: - aws_client.cloudformation.get_waiter(waiter_type).wait( - StackName=stack.stack_name, - WaiterConfig={ - "Delay": 5, - "MaxAttempts": 5, - }, - ) - - stack = deploy_cfn_template(template=template_1, parameters=parameters) - wait_for("stack_create_complete") - - # update the stack - deploy_cfn_template( - template=template_2, - is_update=True, - stack_name=stack.stack_name, - parameters=parameters, - ) - wait_for("stack_update_complete") - - # update the stack again - deploy_cfn_template( - template=template_3, - is_update=True, - stack_name=stack.stack_name, - parameters=parameters, - ) - wait_for("stack_update_complete") - - res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - snapshot.match("describe-result", res) - - -@pytest.mark.skip(reason="CFNV2:Other, CFNV2:Describe") -@markers.aws.validated -def test_update_termination_protection(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("ParameterValue", "parameter-value")) - - # create stack - api_name = f"test_{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.yaml" - ) - stack = deploy_cfn_template(template_path=template_path, parameters={"ApiName": api_name}) - - # update termination protection (true) - aws_client.cloudformation.update_termination_protection( - EnableTerminationProtection=True, StackName=stack.stack_name - ) - res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - snapshot.match("describe-stack-1", res) - - # update termination protection (false) - aws_client.cloudformation.update_termination_protection( - EnableTerminationProtection=False, StackName=stack.stack_name - ) - res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - snapshot.match("describe-stack-2", res) - - -@pytest.mark.skip(reason="CFNV2:Other, CFNV2:Describe") -@markers.aws.validated -def test_events_resource_types(deploy_cfn_template, snapshot, aws_client): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cdk_sample_app.yaml" - ) - stack = deploy_cfn_template(template_path=template_path, max_wait=500) - events = aws_client.cloudformation.describe_stack_events(StackName=stack.stack_name)[ - "StackEvents" - ] - - resource_types = list({event["ResourceType"] for event in events}) - resource_types.sort() - snapshot.match("resource_types", resource_types) - - -@pytest.mark.skip(reason="CFNV2:Deletion") -@markers.aws.validated -def test_list_parameter_type(aws_client, deploy_cfn_template, cleanups): - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_parameter_list_type.yaml" - ), - parameters={ - "ParamsList": "foo,bar", - }, - ) - - assert stack.outputs["ParamValue"] == "foo|bar" - - -@markers.aws.validated -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="rollback not implemented") -def test_blocked_stack_deletion(aws_client, cleanups, snapshot): - """ - uses AWS::IAM::Policy for demonstrating this behavior - - 1. create fails - 2. rollback fails even though create didn't even provision anything - 3. trying to delete the stack afterwards also doesn't work - 4. deleting the stack with retain resources works - """ - cfn = aws_client.cloudformation - stack_name = f"test-stacks-blocked-{short_uid()}" - policy_name = f"test-broken-policy-{short_uid()}" - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.regex(policy_name, "")) - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/iam_policy_invalid.yaml") - ) - waiter_config = {"Delay": 1, "MaxAttempts": 20} - - snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) - snapshot.add_transformer( - snapshot.transform.key_value("ResourceStatusReason", reference_replacement=False) - ) - - stack = cfn.create_stack( - StackName=stack_name, - TemplateBody=template_body, - Parameters=[{"ParameterKey": "Name", "ParameterValue": policy_name}], - Capabilities=[Capability.CAPABILITY_NAMED_IAM], - ) - stack_id = stack["StackId"] - cleanups.append(lambda: cfn.delete_stack(StackName=stack_id, RetainResources=["BrokenPolicy"])) - with pytest.raises(WaiterError): - cfn.get_waiter("stack_create_complete").wait(StackName=stack_id, WaiterConfig=waiter_config) - stack_post_create = cfn.describe_stacks(StackName=stack_id) - snapshot.match("stack_post_create", stack_post_create) - - cfn.delete_stack(StackName=stack_id) - with pytest.raises(WaiterError): - cfn.get_waiter("stack_delete_complete").wait(StackName=stack_id, WaiterConfig=waiter_config) - stack_post_fail_delete = cfn.describe_stacks(StackName=stack_id) - snapshot.match("stack_post_fail_delete", stack_post_fail_delete) - - cfn.delete_stack(StackName=stack_id, RetainResources=["BrokenPolicy"]) - cfn.get_waiter("stack_delete_complete").wait(StackName=stack_id, WaiterConfig=waiter_config) - stack_post_success_delete = cfn.describe_stacks(StackName=stack_id) - snapshot.match("stack_post_success_delete", stack_post_success_delete) - stack_events = cfn.describe_stack_events(StackName=stack_id) - snapshot.match("stack_events", stack_events) - - -MINIMAL_TEMPLATE = """ -Resources: - SimpleParam: - Type: AWS::SSM::Parameter - Properties: - Value: test - Type: String -""" - - -@pytest.mark.skip(reason="CFNV2:Validation") -@markers.snapshot.skip_snapshot_verify( - paths=["$..EnableTerminationProtection", "$..LastUpdatedTime"] -) -@markers.aws.validated -def test_name_conflicts(aws_client, snapshot, cleanups): - """ - Tests behavior of creating a stack with the same name of one that was previously deleted - - 1. Create Stack - 2. Delete Stack - 3. Create Stack with same name as in 1. - - Step 3 should be successful because you can re-use StackNames, - but only one stack for a given stack name can be `ACTIVE` at one time. - - We didn't exhaustively test yet what is considered as Active by CloudFormation - For now the assumption is that anything != "DELETE_COMPLETED" is considered "ACTIVE" - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"repeated-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE - ) - stack_id = stack["StackId"] - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # only one can be active at a time - with pytest.raises(aws_client.cloudformation.exceptions.AlreadyExistsException) as e: - aws_client.cloudformation.create_stack(StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE) - snapshot.match("create_stack_already_exists_exc", e.value.response) - - created_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][ - 0 - ]["StackStatus"] - snapshot.match("created_stack_desc", created_stack_desc) - - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - # describe with name fails - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("deleted_stack_not_found_exc", e.value.response) - - # describe events with name fails - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stack_events(StackName=stack_name) - snapshot.match("deleted_stack_events_not_found_by_name", e.value.response) - - # describe with stack id (ARN) succeeds - deleted_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("deleted_stack_desc", deleted_stack_desc) - - # creating a new stack with the same name as the previously deleted one should work - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE - ) - # should issue a new unique stack ID/ARN - new_stack_id = stack["StackId"] - assert stack_id != new_stack_id - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - new_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("new_stack_desc", new_stack_desc) - assert len(new_stack_desc["Stacks"]) == 1 - assert new_stack_desc["Stacks"][0]["StackId"] == new_stack_id - - # can still access both by using the ARN (stack id) - # and they should be different from each other - stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=stack_id) - new_stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=new_stack_id) - snapshot.match("stack_id_desc", stack_id_desc) - snapshot.match("new_stack_id_desc", new_stack_id_desc) - - # check if the describing the stack events return the right stack - stack_events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ - "StackEvents" - ] - assert all(stack_event["StackId"] == new_stack_id for stack_event in stack_events) - # describing events by the old stack id should still yield the old events - stack_events = aws_client.cloudformation.describe_stack_events(StackName=stack_id)[ - "StackEvents" - ] - assert all(stack_event["StackId"] == stack_id for stack_event in stack_events) - - # deleting the stack by name should delete the new, not already deleted stack - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - # describe with stack id returns stack deleted - deleted_stack_desc = aws_client.cloudformation.describe_stacks(StackName=new_stack_id) - snapshot.match("deleted_second_stack_desc", deleted_stack_desc) - - -@pytest.mark.skip(reason="CFNV2:Validation") -@markers.aws.validated -def test_describe_stack_events_errors(aws_client, snapshot): - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stack_events() - snapshot.match("describe_stack_events_no_stack_name", e.value.response) - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stack_events(StackName="does-not-exist") - snapshot.match("describe_stack_events_stack_not_found", e.value.response) - - -TEMPLATE_ORDER_CASES = list(permutations(["A", "B", "C"])) - - -@pytest.mark.skip(reason="CFNV2:Other stack events") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..StackId", - # TODO - "$..PhysicalResourceId", - # TODO - "$..ResourceProperties", - ] -) -@pytest.mark.parametrize( - "deploy_order", TEMPLATE_ORDER_CASES, ids=["-".join(vals) for vals in TEMPLATE_ORDER_CASES] -) -def test_stack_deploy_order(deploy_cfn_template, aws_client, snapshot, deploy_order: tuple[str]): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("EventId")) - resources = { - "A": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": "root", - }, - }, - "B": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": { - "Ref": "A", - }, - }, - }, - "C": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": { - "Ref": "B", - }, - }, - }, - } - - resources = OrderedDict( - [ - (logical_resource_id, resources[logical_resource_id]) - for logical_resource_id in deploy_order - ] - ) - assert len(resources) == 3 - - stack = deploy_cfn_template( - template=json.dumps( - { - "Resources": resources, - } - ) - ) - - stack.destroy() - - events = aws_client.cloudformation.describe_stack_events( - StackName=stack.stack_id, - )["StackEvents"] - - filtered_events = [] - for event in events: - # only the resources we care about - if event["LogicalResourceId"] not in deploy_order: - continue - - # only _COMPLETE events - if not event["ResourceStatus"].endswith("_COMPLETE"): - continue - - filtered_events.append(event) - - # sort by event time - filtered_events.sort(key=lambda e: e["Timestamp"]) - - snapshot.match("events", filtered_events) - - -@pytest.mark.skip(reason="CFNV2:DescribeStack") -@markers.snapshot.skip_snapshot_verify( - paths=[ - # TODO: this property is present in the response from LocalStack when - # there is an active changeset, however it is not present on AWS - # because the change set has not been executed. - "$..Stacks..ChangeSetId", - # FIXME: tackle this when fixing API parity of CloudFormation - "$..Capabilities", - "$..IncludeNestedStacks", - "$..LastUpdatedTime", - "$..NotificationARNs", - "$..ResourceChange", - "$..StackResourceDetail.Metadata", - ] -) -@markers.aws.validated -def test_no_echo_parameter(snapshot, aws_client, deploy_cfn_template): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(SortingTransformer("Parameters", lambda x: x.get("ParameterKey", ""))) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_no_echo.yml" - ) - template = open(template_path, "r").read() - - deployment = deploy_cfn_template( - template=template, - parameters={"SecretParameter": "SecretValue"}, - ) - stack_id = deployment.stack_id - stack_name = deployment.stack_name - - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_stacks", describe_stacks) - - # Check Resource Metadata. - describe_stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=stack_id - ) - for resource in describe_stack_resources["StackResources"]: - resource_logical_id = resource["LogicalResourceId"] - - # Get detailed information about the resource - describe_stack_resource_details = aws_client.cloudformation.describe_stack_resource( - StackName=stack_name, LogicalResourceId=resource_logical_id - ) - snapshot.match( - f"describe_stack_resource_details_{resource_logical_id}", - describe_stack_resource_details, - ) - - # Update stack via update_stack (and change the value of SecretParameter) - aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue1"}, - ], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack_name) - update_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks", update_stacks) - - # Update stack via create_change_set (and change the value of SecretParameter) - change_set_name = f"UpdateSecretParameterValue-{short_uid()}" - aws_client.cloudformation.create_change_set( - StackName=stack_name, - TemplateBody=template, - ChangeSetName=change_set_name, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue2"}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, - ChangeSetName=change_set_name, - ) - change_sets = aws_client.cloudformation.describe_change_set( - StackName=stack_id, - ChangeSetName=change_set_name, - ) - snapshot.match("describe_updated_change_set", change_sets) - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks_change_set", describe_stacks) - - # Change `NoEcho` of a parameter from true to false and update stack via create_change_set. - change_set_name = f"UpdateSecretParameterNoEchoToFalse-{short_uid()}" - template_dict = parse_yaml(load_file(template_path)) - template_dict["Parameters"]["SecretParameter"]["NoEcho"] = False - template_no_echo_false = yaml.dump(template_dict) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - TemplateBody=template_no_echo_false, - ChangeSetName=change_set_name, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue2"}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, - ChangeSetName=change_set_name, - ) - change_sets = aws_client.cloudformation.describe_change_set( - StackName=stack_id, - ChangeSetName=change_set_name, - ) - snapshot.match("describe_updated_change_set_no_echo_true", change_sets) - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks_no_echo_true", describe_stacks) - - # Change `NoEcho` of a parameter back from false to true and update stack via create_change_set. - change_set_name = f"UpdateSecretParameterNoEchoToTrue-{short_uid()}" - aws_client.cloudformation.create_change_set( - StackName=stack_name, - TemplateBody=template, - ChangeSetName=change_set_name, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue2"}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, - ChangeSetName=change_set_name, - ) - change_sets = aws_client.cloudformation.describe_change_set( - StackName=stack_id, - ChangeSetName=change_set_name, - ) - snapshot.match("describe_updated_change_set_no_echo_false", change_sets) - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks_no_echo_false", describe_stacks) - - -@pytest.mark.skip(reason="CFNV2:Validation") -@markers.aws.validated -def test_stack_resource_not_found(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - parameters={"TopicName": f"topic{short_uid()}"}, - ) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.describe_stack_resource( - StackName=stack.stack_name, LogicalResourceId="NonExistentResource" - ) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) - snapshot.match("Error", ex.value.response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.snapshot.json deleted file mode 100644 index 979af0c8a9573..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.snapshot.json +++ /dev/null @@ -1,2290 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "recorded-date": "05-08-2022, 13:03:43", - "recorded-content": { - "describe_stack": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "Description": "test .test.net", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_update_resources": { - "recorded-date": "30-08-2022, 00:13:26", - "recorded-content": { - "stack_created": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "test_12395eb4" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - }, - "stack_updated": { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "test_5a3df175" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - }, - "stack_resources": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Api", - "PhysicalResourceId": "", - "ResourceStatus": "UPDATE_COMPLETE", - "ResourceType": "AWS::ApiGateway::RestApi", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "-bucket-10xf2vf1pqap8", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": { - "recorded-date": "05-10-2022, 13:33:55", - "recorded-content": { - "events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "topic123-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "", - "ResourceProperties": { - "TopicName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "topic123-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceProperties": { - "TopicName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "topic123-CREATE_COMPLETE-date", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceProperties": { - "TopicName": "" - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": { - "recorded-date": "28-11-2023, 13:24:40", - "recorded-content": { - "creation": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - }, - "update": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - }, - "describe_deleted_by_name_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Stack with id does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleted": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_linting_error_during_creation": { - "recorded-date": "11-11-2022, 08:10:14", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Any Resources member must be an object.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_updating_an_updated_stack_sets_status": { - "recorded-date": "02-12-2022, 11:19:41", - "recorded-content": { - "describe-result": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Topic2Name", - "ParameterValue": "topic-2" - }, - { - "ParameterKey": "Topic1Name", - "ParameterValue": "topic-1" - }, - { - "ParameterKey": "Topic3Name", - "ParameterValue": "topic-3" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_update_termination_protection": { - "recorded-date": "04-01-2023, 16:23:22", - "recorded-content": { - "describe-stack-1": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": true, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe-stack-2": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_events_resource_types": { - "recorded-date": "15-02-2023, 10:46:53", - "recorded-content": { - "resource_types": [ - "AWS::CloudFormation::Stack", - "AWS::SNS::Subscription", - "AWS::SNS::Topic", - "AWS::SQS::Queue", - "AWS::SQS::QueuePolicy" - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_name_creation": { - "recorded-date": "19-04-2023, 12:44:47", - "recorded-content": { - "stack_response": { - "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value '*@da591fa3_$' at 'stackName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*|arn:[-a-zA-Z0-9:/._+]*", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_blocked_stack_deletion": { - "recorded-date": "06-09-2023, 11:01:18", - "recorded-content": { - "stack_post_create": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Name", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "ROLLBACK_FAILED", - "StackStatusReason": "The following resource(s) failed to delete: [BrokenPolicy]. ", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_post_fail_delete": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Name", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_FAILED", - "StackStatusReason": "The following resource(s) failed to delete: [BrokenPolicy]. ", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_post_success_delete": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Name", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_SKIPPED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_SKIPPED", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_FAILED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_FAILED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-CREATE_FAILED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "CREATE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_name_conflicts": { - "recorded-date": "26-03-2024, 17:59:43", - "recorded-content": { - "create_stack_already_exists_exc": { - "Error": { - "Code": "AlreadyExistsException", - "Message": "Stack [] already exists", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "created_stack_desc": "CREATE_COMPLETE", - "deleted_stack_not_found_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Stack with id does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleted_stack_events_not_found_by_name": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [] does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleted_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "new_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "new_stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deleted_second_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_describe_stack_events_errors": { - "recorded-date": "26-03-2024, 17:54:41", - "recorded-content": { - "describe_stack_events_no_stack_name": { - "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value null at 'stackName' failed to satisfy constraint: Member must not be null", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "describe_stack_events_stack_not_found": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [does-not-exist] does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": { - "recorded-date": "07-05-2024, 08:34:18", - "recorded-content": { - "no_change_exception": { - "Error": { - "Code": "ValidationError", - "Message": "No updates are to be performed.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-B-C]": { - "recorded-date": "29-05-2024, 11:44:14", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xvqPt7CmcHKX", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-FCaKHvMgdicm", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xvqPt7CmcHKX" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-Xr56esN3SasR", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-FCaKHvMgdicm" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-Xr56esN3SasR", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-FCaKHvMgdicm" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-FCaKHvMgdicm", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xvqPt7CmcHKX" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xvqPt7CmcHKX", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-C-B]": { - "recorded-date": "29-05-2024, 11:44:32", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-4tNP69dd8iSL", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-d81WSIsD2X3i", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-4tNP69dd8iSL" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-kStA2w3izJOh", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-d81WSIsD2X3i" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-kStA2w3izJOh", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-d81WSIsD2X3i" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-d81WSIsD2X3i", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-4tNP69dd8iSL" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-4tNP69dd8iSL", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-A-C]": { - "recorded-date": "29-05-2024, 11:44:51", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-a0yQkOAYKMk5", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-RvqPXWdIGzrt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-a0yQkOAYKMk5" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-iPNi3cV9jXAt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-RvqPXWdIGzrt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-iPNi3cV9jXAt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-RvqPXWdIGzrt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-RvqPXWdIGzrt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-a0yQkOAYKMk5" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-a0yQkOAYKMk5", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-C-A]": { - "recorded-date": "29-05-2024, 11:45:12", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xNtQNbQrdc1T", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-UY120OHcpDMZ", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xNtQNbQrdc1T" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-GOhk98pWaTFw", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-UY120OHcpDMZ" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-GOhk98pWaTFw", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-UY120OHcpDMZ" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-UY120OHcpDMZ", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xNtQNbQrdc1T" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xNtQNbQrdc1T", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-A-B]": { - "recorded-date": "29-05-2024, 11:45:31", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-BFvOY1qz1Osv", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-qCiX6NdW4hEt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-BFvOY1qz1Osv" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-ki0TLXKJfPgN", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-qCiX6NdW4hEt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-ki0TLXKJfPgN", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-qCiX6NdW4hEt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-qCiX6NdW4hEt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-BFvOY1qz1Osv" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-BFvOY1qz1Osv", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-B-A]": { - "recorded-date": "29-05-2024, 11:45:50", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-LQadBXOC2eGc", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-p6Hy6dxQCfjl", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-LQadBXOC2eGc" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-YYmzIb8agve7", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-p6Hy6dxQCfjl" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-YYmzIb8agve7", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-p6Hy6dxQCfjl" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-p6Hy6dxQCfjl", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-LQadBXOC2eGc" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-LQadBXOC2eGc", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_no_echo_parameter": { - "recorded-date": "19-12-2024, 11:35:19", - "recorded-content": { - "describe_stacks": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "SecretValue" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_resource_details_LocalBucket": { - "StackResourceDetail": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "LocalBucket", - "Metadata": { - "SensitiveData": "SecretValue" - }, - "PhysicalResourceId": "cfn-noecho-bucket", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_change_set": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Modify", - "Details": [ - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - } - ], - "LogicalResourceId": "LocalBucket", - "PhysicalResourceId": "cfn-noecho-bucket", - "Replacement": "False", - "ResourceType": "AWS::S3::Bucket", - "Scope": [ - "Metadata", - "Properties" - ] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks_change_set": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_change_set_no_echo_true": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Modify", - "Details": [ - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - } - ], - "LogicalResourceId": "LocalBucket", - "PhysicalResourceId": "cfn-noecho-bucket", - "Replacement": "False", - "ResourceType": "AWS::S3::Bucket", - "Scope": [ - "Metadata", - "Properties" - ] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "NewSecretValue2" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks_no_echo_true": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_change_set_no_echo_false": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Modify", - "Details": [ - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - } - ], - "LogicalResourceId": "LocalBucket", - "PhysicalResourceId": "cfn-noecho-bucket", - "Replacement": "False", - "ResourceType": "AWS::S3::Bucket", - "Scope": [ - "Metadata", - "Properties" - ] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks_no_echo_false": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": { - "recorded-date": "02-01-2025, 19:08:41", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": { - "recorded-date": "02-01-2025, 19:09:40", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": { - "recorded-date": "02-01-2025, 19:11:14", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": { - "recorded-date": "02-01-2025, 19:11:20", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_resource_not_found": { - "recorded-date": "29-01-2025, 09:08:15", - "recorded-content": { - "Error": { - "Error": { - "Code": "ValidationError", - "Message": "Resource NonExistentResource does not exist for stack ", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.validation.json deleted file mode 100644 index 005063a3a34ee..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.validation.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": { - "last_validated_date": "2024-06-25T17:21:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[True-1]": { - "last_validated_date": "2024-06-25T17:22:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template[json]": { - "last_validated_date": "2022-08-11T08:55:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template[yaml]": { - "last_validated_date": "2022-08-11T08:55:10+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": { - "last_validated_date": "2025-01-02T19:09:40+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": { - "last_validated_date": "2025-01-02T19:08:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": { - "last_validated_date": "2025-01-02T19:11:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": { - "last_validated_date": "2025-01-02T19:11:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": { - "last_validated_date": "2022-10-05T11:33:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "last_validated_date": "2022-08-05T11:03:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": { - "last_validated_date": "2023-11-28T12:24:40+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_name_creation": { - "last_validated_date": "2023-04-19T10:44:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_update_resources": { - "last_validated_date": "2022-08-29T22:13:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": { - "last_validated_date": "2024-05-07T08:35:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": { - "last_validated_date": "2024-05-07T09:26:39+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_blocked_stack_deletion": { - "last_validated_date": "2023-09-06T09:01:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_describe_stack_events_errors": { - "last_validated_date": "2024-03-26T17:54:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_events_resource_types": { - "last_validated_date": "2023-02-15T09:46:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_linting_error_during_creation": { - "last_validated_date": "2022-11-11T07:10:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_name_conflicts": { - "last_validated_date": "2024-03-26T17:59:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_no_echo_parameter": { - "last_validated_date": "2024-12-19T11:35:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2": { - "last_validated_date": "2024-05-21T09:48:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[A-B-C]": { - "last_validated_date": "2024-05-21T10:00:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[A-C-B]": { - "last_validated_date": "2024-05-21T10:01:07+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[B-A-C]": { - "last_validated_date": "2024-05-21T10:01:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[B-C-A]": { - "last_validated_date": "2024-05-21T10:01:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[C-A-B]": { - "last_validated_date": "2024-05-21T10:02:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[C-B-A]": { - "last_validated_date": "2024-05-21T10:02:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order0]": { - "last_validated_date": "2024-05-21T09:49:59+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order1]": { - "last_validated_date": "2024-05-21T09:50:22+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order2]": { - "last_validated_date": "2024-05-21T09:50:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order3]": { - "last_validated_date": "2024-05-21T09:51:07+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order4]": { - "last_validated_date": "2024-05-21T09:51:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order5]": { - "last_validated_date": "2024-05-21T09:51:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-B-C]": { - "last_validated_date": "2024-05-29T11:44:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-C-B]": { - "last_validated_date": "2024-05-29T11:44:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-A-C]": { - "last_validated_date": "2024-05-29T11:44:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-C-A]": { - "last_validated_date": "2024-05-29T11:45:12+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-A-B]": { - "last_validated_date": "2024-05-29T11:45:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-B-A]": { - "last_validated_date": "2024-05-29T11:45:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_resource_not_found": { - "last_validated_date": "2025-01-29T09:08:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_update_termination_protection": { - "last_validated_date": "2023-01-04T15:23:22+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_updating_an_updated_stack_sets_status": { - "last_validated_date": "2022-12-02T10:19:41+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py deleted file mode 100644 index fbfe9d191a009..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py +++ /dev/null @@ -1,124 +0,0 @@ -import contextlib -import os -import textwrap - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import load_file -from localstack.utils.strings import short_uid, to_bytes - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..ResourceIdentifierSummaries..ResourceIdentifiers", "$..Parameters"] -) -def test_get_template_summary(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - - deployment = deploy_cfn_template( - template_path=os.path.join( - # This template has no parameters, and so shows the issue - os.path.dirname(__file__), - "../../../../../templates/sns_topic_simple.yaml", - ) - ) - - res = aws_client.cloudformation.get_template_summary(StackName=deployment.stack_name) - - snapshot.match("template-summary", res) - - -@markers.aws.validated -@pytest.mark.parametrize("url_style", ["s3_url", "http_path", "http_host", "http_invalid"]) -def test_create_stack_from_s3_template_url( - url_style, snapshot, s3_create_bucket, aws_client, cleanups -): - topic_name = f"topic-{short_uid()}" - bucket_name = s3_create_bucket() - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - snapshot.add_transformer(snapshot.transform.regex(bucket_name, "")) - - stack_name = f"s-{short_uid()}" - template = textwrap.dedent( - """ - AWSTemplateFormatVersion: '2010-09-09' - Parameters: - TopicName: - Type: String - Resources: - topic123: - Type: AWS::SNS::Topic - Properties: - TopicName: !Ref TopicName - """ - ) - - aws_client.s3.put_object(Bucket=bucket_name, Key="test/template.yml", Body=to_bytes(template)) - - match url_style: - case "s3_url": - template_url = f"s3://{bucket_name}/test/template.yml" - case "http_path": - template_url = f"https://s3.amazonaws.com/{bucket_name}/test/template.yml" - case "http_host": - template_url = f"https://{bucket_name}.s3.amazonaws.com/test/template.yml" - case "http_invalid": - # note: using an invalid (non-existing) URL here, but in fact all non-S3 HTTP URLs are invalid in real AWS - template_url = "https://example.com/dummy.yml" - case _: - raise Exception(f"Unexpected `url_style` parameter: {url_style}") - - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - # deploy stack - error_expected = url_style in ["s3_url", "http_invalid"] - context_manager = pytest.raises(ClientError) if error_expected else contextlib.nullcontext() - with context_manager as ctx: - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateURL=template_url, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": topic_name}], - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # assert that either error was raised, or topic has been created - if error_expected: - snapshot.match("create-error", ctx.value.response) - else: - results = list(aws_client.sns.get_paginator("list_topics").paginate()) - matching = [ - t for res in results for t in res["Topics"] if t["TopicArn"].endswith(topic_name) - ] - snapshot.match("matching-topic", matching) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Parameters..DefaultValue"]) -def test_validate_template(aws_client, snapshot): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/valid_template.json") - ) - - resp = aws_client.cloudformation.validate_template(TemplateBody=template) - snapshot.match("validate-template", resp) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Error..Message"]) -def test_validate_invalid_json_template_should_fail(aws_client, snapshot): - invalid_json = '{"this is invalid JSON"="bobbins"}' - - with pytest.raises(ClientError) as ctx: - aws_client.cloudformation.validate_template(TemplateBody=invalid_json) - - snapshot.match("validate-invalid-json", ctx.value.response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.snapshot.json deleted file mode 100644 index 66cd35eaffec3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.snapshot.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_get_template_summary": { - "recorded-date": "24-05-2023, 15:05:00", - "recorded-content": { - "template-summary": { - "Metadata": "{'TopicName': 'sns-topic-simple'}", - "Parameters": [], - "ResourceIdentifierSummaries": [ - { - "LogicalResourceIds": [ - "topic123" - ], - "ResourceType": "AWS::SNS::Topic" - } - ], - "ResourceTypes": [ - "AWS::SNS::Topic" - ], - "Version": "2010-09-09", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": { - "recorded-date": "11-10-2023, 00:03:44", - "recorded-content": { - "create-error": { - "Error": { - "Code": "ValidationError", - "Message": "S3 error: Domain name specified in is not a valid S3 domain", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": { - "recorded-date": "11-10-2023, 00:03:53", - "recorded-content": { - "matching-topic": [ - { - "TopicArn": "arn::sns::111111111111:" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": { - "recorded-date": "11-10-2023, 00:04:02", - "recorded-content": { - "matching-topic": [ - { - "TopicArn": "arn::sns::111111111111:" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": { - "recorded-date": "11-10-2023, 00:04:04", - "recorded-content": { - "create-error": { - "Error": { - "Code": "ValidationError", - "Message": "TemplateURL must be a supported URL.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_template": { - "recorded-date": "18-06-2024, 17:23:30", - "recorded-content": { - "validate-template": { - "Parameters": [ - { - "Description": "The EC2 Key Pair to allow SSH access to the instance", - "NoEcho": false, - "ParameterKey": "KeyExample" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_invalid_json_template_should_fail": { - "recorded-date": "18-06-2024, 17:25:49", - "recorded-content": { - "validate-invalid-json": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: JSON not well-formed. (line 1, column 25)", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.validation.json deleted file mode 100644 index 77965368c70b2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": { - "last_validated_date": "2023-10-10T22:04:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": { - "last_validated_date": "2023-10-10T22:04:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": { - "last_validated_date": "2023-10-10T22:03:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": { - "last_validated_date": "2023-10-10T22:03:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_get_template_summary": { - "last_validated_date": "2023-05-24T13:05:00+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_invalid_json_template_should_fail": { - "last_validated_date": "2024-06-18T17:25:49+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_template": { - "last_validated_date": "2024-06-18T17:23:30+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py deleted file mode 100644 index ecb2d8a625d83..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py +++ /dev/null @@ -1,164 +0,0 @@ -import textwrap - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid, to_bytes - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..tags"]) -def test_duplicate_resources(deploy_cfn_template, s3_bucket, snapshot, aws_client): - snapshot.add_transformers_list( - [ - *snapshot.transform.apigateway_api(), - snapshot.transform.key_value("aws:cloudformation:stack-id"), - snapshot.transform.key_value("aws:cloudformation:stack-name"), - ] - ) - - # put API spec to S3 - api_spec = """ - swagger: 2.0 - info: - version: "1.2.3" - title: "Test API" - basePath: /base - """ - aws_client.s3.put_object(Bucket=s3_bucket, Key="api.yaml", Body=to_bytes(api_spec)) - - # deploy template - template = """ - Parameters: - ApiName: - Type: String - BucketName: - Type: String - Resources: - RestApi: - Type: AWS::ApiGateway::RestApi - Properties: - Name: !Ref ApiName - Body: - 'Fn::Transform': - Name: 'AWS::Include' - Parameters: - Location: !Sub "s3://${BucketName}/api.yaml" - Outputs: - RestApiId: - Value: !Ref RestApi - """ - - api_name = f"api-{short_uid()}" - result = deploy_cfn_template( - template=template, parameters={"ApiName": api_name, "BucketName": s3_bucket} - ) - - # assert REST API is created properly - api_id = result.outputs.get("RestApiId") - result = aws_client.apigateway.get_rest_api(restApiId=api_id) - assert result - snapshot.match("api-details", result) - - resources = aws_client.apigateway.get_resources(restApiId=api_id) - snapshot.match("api-resources", resources) - - -@pytest.mark.skip( - reason=( - "CFNV2:AWS::Include the transformation is run however the " - "physical resource id for the resource is not available" - ) -) -@markers.aws.validated -def test_transformer_property_level(deploy_cfn_template, s3_bucket, aws_client, snapshot): - api_spec = textwrap.dedent(""" - Value: from_transformation - """) - aws_client.s3.put_object(Bucket=s3_bucket, Key="data.yaml", Body=to_bytes(api_spec)) - - # deploy template - template = textwrap.dedent(""" - Parameters: - BucketName: - Type: String - Resources: - MyParameter: - Type: AWS::SSM::Parameter - Properties: - Description: hello - Type: String - "Fn::Transform": - Name: "AWS::Include" - Parameters: - Location: !Sub "s3://${BucketName}/data.yaml" - Outputs: - ParameterName: - Value: !Ref MyParameter - """) - - result = deploy_cfn_template(template=template, parameters={"BucketName": s3_bucket}) - param_name = result.outputs["ParameterName"] - param = aws_client.ssm.get_parameter(Name=param_name) - assert ( - param["Parameter"]["Value"] == "from_transformation" - ) # value coming from the transformation - describe_result = ( - aws_client.ssm.get_paginator("describe_parameters") - .paginate(Filters=[{"Key": "Name", "Values": [param_name]}]) - .build_full_result() - ) - assert ( - describe_result["Parameters"][0]["Description"] == "hello" - ) # value from a property on the same level as the transformation - - original_template = aws_client.cloudformation.get_template( - StackName=result.stack_id, TemplateStage="Original" - ) - snapshot.match("original_template", original_template) - processed_template = aws_client.cloudformation.get_template( - StackName=result.stack_id, TemplateStage="Processed" - ) - snapshot.match("processed_template", processed_template) - - -@pytest.mark.skip( - reason=( - "CFNV2:AWS::Include the transformation is run however the " - "physical resource id for the resource is not available" - ) -) -@markers.aws.validated -def test_transformer_individual_resource_level(deploy_cfn_template, s3_bucket, aws_client): - api_spec = textwrap.dedent(""" - Type: AWS::SNS::Topic - """) - aws_client.s3.put_object(Bucket=s3_bucket, Key="data.yaml", Body=to_bytes(api_spec)) - - # deploy template - template = textwrap.dedent(""" - Parameters: - BucketName: - Type: String - Resources: - MyResource: - "Fn::Transform": - Name: "AWS::Include" - Parameters: - Location: !Sub "s3://${BucketName}/data.yaml" - Outputs: - ResourceRef: - Value: !Ref MyResource - """) - - result = deploy_cfn_template(template=template, parameters={"BucketName": s3_bucket}) - resource_ref = result.outputs["ResourceRef"] - # just checking that this doens't fail, i.e. the topic exists - aws_client.sns.get_topic_attributes(TopicArn=resource_ref) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.snapshot.json deleted file mode 100644 index cd79d06b34d9e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.snapshot.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_duplicate_resources": { - "recorded-date": "15-07-2025, 19:28:05", - "recorded-content": { - "api-details": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "RestApi", - "aws:cloudformation:stack-id": "", - "aws:cloudformation:stack-name": "" - }, - "version": "1.2.3", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "api-resources": { - "items": [ - { - "id": "", - "path": "/" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_property_level": { - "recorded-date": "06-06-2024, 10:37:03", - "recorded-content": { - "original_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "\nParameters:\n BucketName:\n Type: String\nResources:\n MyParameter:\n Type: AWS::SSM::Parameter\n Properties:\n Description: hello\n Type: String\n \"Fn::Transform\":\n Name: \"AWS::Include\"\n Parameters:\n Location: !Sub \"s3://${BucketName}/data.yaml\"\nOutputs:\n ParameterName:\n Value: !Ref MyParameter\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "ParameterName": { - "Value": { - "Ref": "MyParameter" - } - } - }, - "Parameters": { - "BucketName": { - "Type": "String" - } - }, - "Resources": { - "MyParameter": { - "Properties": { - "Description": "hello", - "Type": "String", - "Value": "from_transformation" - }, - "Type": "AWS::SSM::Parameter" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.validation.json deleted file mode 100644 index ac2a6ccf07d7d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_duplicate_resources": { - "last_validated_date": "2025-07-15T19:28:15+00:00", - "durations_in_seconds": { - "setup": 1.05, - "call": 13.13, - "teardown": 10.12, - "total": 24.3 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_individual_resource_level": { - "last_validated_date": "2024-06-13T06:43:21+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_property_level": { - "last_validated_date": "2024-06-06T10:38:33+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py deleted file mode 100644 index c8d04ddeab95e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py +++ /dev/null @@ -1,468 +0,0 @@ -import json -import os -import textwrap - -import botocore.errorfactory -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.testutil import upload_file_to_bucket - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.skip(reason="CFNV2:UpdateStack") -@markers.aws.validated -def test_basic_update(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - response = aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ), - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - snapshot.add_transformer(snapshot.transform.key_value("StackId", "stack-id")) - snapshot.match("update_response", response) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@pytest.mark.skip(reason="CFNV2:UpdateStack") -@markers.aws.validated -def test_update_using_template_url(deploy_cfn_template, s3_create_bucket, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - file_url = upload_file_to_bucket( - aws_client.s3, - s3_create_bucket(), - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml"), - )["Url"] - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateURL=file_url, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="Not supported") -def test_update_with_previous_template(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.needs_fixing -@pytest.mark.skip(reason="templates are not partially not valid => re-evaluate") -@pytest.mark.parametrize( - "capability", - [ - {"value": "CAPABILITY_IAM", "template": "iam_policy.yml"}, - {"value": "CAPABILITY_NAMED_IAM", "template": "iam_role_policy.yaml"}, - ], -) -# The AUTO_EXPAND option is used for macros -def test_update_with_capabilities(capability, deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/", capability["template"]) - ) - - parameter_key = "RoleName" if capability["value"] == "CAPABILITY_NAMED_IAM" else "Name" - - with pytest.raises(botocore.errorfactory.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[{"ParameterKey": parameter_key, "ParameterValue": f"{short_uid()}"}], - ) - - snapshot.match("error", ex.value.response) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Capabilities=[capability["value"]], - Parameters=[{"ParameterKey": parameter_key, "ParameterValue": f"{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="Not raising the correct error") -def test_update_with_resource_types(deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - # Test with invalid type - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - ResourceTypes=["AWS::EC2:*"], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - snapshot.match("invalid_type_error", ex.value.response) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - ResourceTypes=["AWS::EC2::*"], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - snapshot.match("resource_not_allowed", ex.value.response) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - ResourceTypes=["AWS::SNS::Topic"], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="Update value not being applied") -def test_set_notification_arn_with_update(deploy_cfn_template, sns_create_topic, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - topic_arn = sns_create_topic()["TopicArn"] - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - NotificationARNs=[topic_arn], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - description = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)["Stacks"][0] - assert topic_arn in description["NotificationARNs"] - - -@markers.aws.validated -@pytest.mark.skip(reason="Update value not being applied") -def test_update_tags(deploy_cfn_template, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - key = f"key-{short_uid()}" - value = f"value-{short_uid()}" - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - Tags=[{"Key": key, "Value": value}], - TemplateBody=template, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - tags = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)["Stacks"][0][ - "Tags" - ] - assert tags[0]["Key"] == key - assert tags[0]["Value"] == value - - -@markers.aws.validated -@pytest.mark.skip(reason="The correct error is not being raised") -def test_no_template_error(deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack(StackName=stack.stack_name) - - snapshot.match("error", ex.value.response) - - -@pytest.mark.skip(reason="CFNV2:UpdateStack") -@markers.aws.validated -def test_no_parameters_update(deploy_cfn_template, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack(StackName=stack.stack_name, TemplateBody=template) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@pytest.mark.skip(reason="CFNV2:UpdateStack") -@markers.aws.validated -def test_update_with_previous_parameter_value(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.update.yml" - ) - ), - Parameters=[{"ParameterKey": "TopicName", "UsePreviousValue": True}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="The correct error is not being raised") -def test_update_with_role_without_permissions( - deploy_cfn_template, snapshot, create_role, aws_client -): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - account_arn = aws_client.sts.get_caller_identity()["Arn"] - assume_policy_doc = { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Principal": {"AWS": account_arn}, - "Effect": "Deny", - } - ], - } - - role_arn = create_role(AssumeRolePolicyDocument=json.dumps(assume_policy_doc))["Role"]["Arn"] - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - RoleARN=role_arn, - ) - - snapshot.match("error", ex.value.response) - - -@markers.aws.validated -@pytest.mark.skip(reason="The correct error is not being raised") -def test_update_with_invalid_rollback_configuration_errors( - deploy_cfn_template, snapshot, aws_client -): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - # Test invalid alarm type - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - RollbackConfiguration={"RollbackTriggers": [{"Arn": short_uid(), "Type": "Another"}]}, - ) - snapshot.match("type_error", ex.value.response) - - # Test invalid alarm arn - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - RollbackConfiguration={ - "RollbackTriggers": [ - { - "Arn": "arn:aws:cloudwatch:us-east-1:123456789012:example-name", - "Type": "AWS::CloudWatch::Alarm", - } - ] - }, - ) - - snapshot.match("arn_error", ex.value.response) - - -@markers.aws.validated -@pytest.mark.skip(reason="The update value is not being applied") -def test_update_with_rollback_configuration(deploy_cfn_template, aws_client): - aws_client.cloudwatch.put_metric_alarm( - AlarmName="HighResourceUsage", - ComparisonOperator="GreaterThanThreshold", - EvaluationPeriods=1, - MetricName="CPUUsage", - Namespace="CustomNamespace", - Period=60, - Statistic="Average", - Threshold=70, - TreatMissingData="notBreaching", - ) - - alarms = aws_client.cloudwatch.describe_alarms(AlarmNames=["HighResourceUsage"]) - alarm_arn = alarms["MetricAlarms"][0]["AlarmArn"] - - rollback_configuration = { - "RollbackTriggers": [ - {"Arn": alarm_arn, "Type": "AWS::CloudWatch::Alarm"}, - ], - "MonitoringTimeInMinutes": 123, - } - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[{"ParameterKey": "TopicName", "UsePreviousValue": True}], - RollbackConfiguration=rollback_configuration, - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - config = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)["Stacks"][0][ - "RollbackConfiguration" - ] - assert config == rollback_configuration - - # cleanup - aws_client.cloudwatch.delete_alarms(AlarmNames=["HighResourceUsage"]) - - -@pytest.mark.skip(reason="CFNV2:UpdateStack") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(["$..Stacks..ChangeSetId"]) -def test_diff_after_update(deploy_cfn_template, aws_client, snapshot): - template_1 = textwrap.dedent(""" - Resources: - SimpleParam: - Type: AWS::SSM::Parameter - Properties: - Value: before-stack-update - Type: String - """) - template_2 = textwrap.dedent(""" - Resources: - SimpleParam1: - Type: AWS::SSM::Parameter - Properties: - Value: after-stack-update - Type: String - """) - - stack = deploy_cfn_template( - template=template_1, - ) - - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack.stack_name) - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template_2, - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - get_template_response = aws_client.cloudformation.get_template(StackName=stack.stack_name) - snapshot.match("get-template-response", get_template_response) - - with pytest.raises(botocore.exceptions.ClientError) as exc_info: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template_2, - ) - snapshot.match("update-error", exc_info.value.response) - - describe_stack_response = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - assert describe_stack_response["Stacks"][0]["StackStatus"] == "UPDATE_COMPLETE" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.snapshot.json deleted file mode 100644 index 1b15733a652eb..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.snapshot.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_resource_types": { - "recorded-date": "19-11-2022, 14:34:18", - "recorded-content": { - "invalid_type_error": { - "Error": { - "Code": "ValidationError", - "Message": "Resource type AWS::SNS::Topic is not allowed by parameter ResourceTypes [AWS::EC2:*]", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "resource_not_allowed": { - "Error": { - "Code": "ValidationError", - "Message": "Resource type AWS::SNS::Topic is not allowed by parameter ResourceTypes [AWS::EC2::*]", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_basic_update": { - "recorded-date": "21-11-2022, 08:27:37", - "recorded-content": { - "update_response": { - "StackId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_template_error": { - "recorded-date": "21-11-2022, 08:57:45", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Either Template URL or Template Body must be specified.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_parameters_error_update": { - "recorded-date": "21-11-2022, 09:45:22", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "recorded-date": "21-11-2022, 10:38:33", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_role_without_permissions": { - "recorded-date": "21-11-2022, 14:14:52", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Role arn::iam::111111111111:role/role-fb405076 is invalid or cannot be assumed", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": { - "recorded-date": "21-11-2022, 15:36:32", - "recorded-content": { - "type_error": { - "Error": { - "Code": "ValidationError", - "Message": "Rollback Trigger Type not supported", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "arn_error": { - "Error": { - "Code": "ValidationError", - "Message": "RelativeId of a Rollback Trigger's ARN is incorrect", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_diff_after_update": { - "recorded-date": "09-04-2024, 06:19:23", - "recorded-content": { - "get-template-response": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "\nResources:\n SimpleParam1:\n Type: AWS::SSM::Parameter\n Properties:\n Value: after-stack-update\n Type: String\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "update-error": { - "Error": { - "Code": "ValidationError", - "Message": "No updates are to be performed.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.validation.json deleted file mode 100644 index 4723c7f6aae06..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_basic_update": { - "last_validated_date": "2022-11-21T07:27:37+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_diff_after_update": { - "last_validated_date": "2024-04-09T06:19:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_template_error": { - "last_validated_date": "2022-11-21T07:57:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": { - "last_validated_date": "2022-11-21T14:36:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "last_validated_date": "2022-11-21T09:38:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_resource_types": { - "last_validated_date": "2022-11-19T13:34:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_role_without_permissions": { - "last_validated_date": "2022-11-21T13:14:52+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py deleted file mode 100644 index 724cb12eb98f5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py +++ /dev/null @@ -1,83 +0,0 @@ -import json - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -pytestmark = pytest.mark.skip("CFNV2:Validation") - - -@markers.aws.validated -@pytest.mark.parametrize( - "outputs", - [ - { - "MyOutput": { - "Value": None, - }, - }, - { - "MyOutput": { - "Value": None, - "AnotherValue": None, - }, - }, - { - "MyOutput": {}, - }, - ], - ids=["none-value", "missing-def", "multiple-nones"], -) -def test_invalid_output_structure(deploy_cfn_template, snapshot, aws_client, outputs): - template = { - "Resources": { - "Foo": { - "Type": "AWS::SNS::Topic", - }, - }, - "Outputs": outputs, - } - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template(template=json.dumps(template)) - - snapshot.match("validation-error", e.value.response) - - -@markers.aws.validated -def test_missing_resources_block(deploy_cfn_template, snapshot, aws_client): - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template(template=json.dumps({})) - - snapshot.match("validation-error", e.value.response) - - -@markers.aws.validated -@pytest.mark.parametrize( - "properties", - [ - { - "Properties": {}, - }, - { - "Type": "AWS::SNS::Topic", - "Invalid": 10, - }, - ], - ids=[ - "missing-type", - "invalid-key", - ], -) -def test_resources_blocks(deploy_cfn_template, snapshot, aws_client, properties): - template = {"Resources": {"A": properties}} - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template(template=json.dumps(template)) - - snapshot.match("validation-error", e.value.response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.snapshot.json deleted file mode 100644 index 3a5eeb52ded32..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.snapshot.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[none-value]": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "[/Outputs/MyOutput/Value] 'null' values are not allowed in templates", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[missing-def]": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "[/Outputs/MyOutput/Value] 'null' values are not allowed in templates", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[multiple-nones]": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Outputs member must contain a Value object", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_missing_resources_block": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: At least one Resources member must be defined.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[missing-type]": { - "recorded-date": "31-05-2024, 14:53:32", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: [/Resources/A] Every Resources object must contain a Type member.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[invalid-key]": { - "recorded-date": "31-05-2024, 14:53:32", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Invalid template resource property 'Invalid'", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.validation.json deleted file mode 100644 index e2041c42e47d1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[missing-def]": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[multiple-nones]": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[none-value]": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_missing_resources_block": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[invalid-key]": { - "last_validated_date": "2024-05-31T14:53:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[missing-type]": { - "last_validated_date": "2024-05-31T14:53:32+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py deleted file mode 100644 index 403c7c0b08baf..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py +++ /dev/null @@ -1,51 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.pytest.fixtures import StackDeployError - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestResourceAttributes: - @pytest.mark.skip(reason="failing on unresolved attributes is not enabled yet") - @markers.snapshot.skip_snapshot_verify - @markers.aws.validated - def test_invalid_getatt_fails(self, aws_client, deploy_cfn_template, snapshot): - """ - Check how CloudFormation behaves on invalid attribute names for resources in a Fn::GetAtt - - Not yet completely correct yet since this should actually initiate a rollback and the stack resource status should be set accordingly - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_invalid_getatt.yaml", - ) - ) - stack_events = exc_info.value.events - snapshot.match("stack_events", {"events": stack_events}) - - @markers.aws.validated - def test_dependency_on_attribute_with_dot_notation( - self, deploy_cfn_template, aws_client, snapshot - ): - """ - Test that a resource can depend on another resource's attribute with dot notation - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_getatt_dot_dependency.yml", - ) - ) - snapshot.match("outputs", deployment.outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.snapshot.json deleted file mode 100644 index 8e699f7013c15..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.snapshot.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": { - "recorded-date": "01-08-2023, 11:54:31", - "recorded-content": { - "stack_events": { - "events": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "[Error] /Outputs/InvalidOutput/Value/Fn::GetAtt: Resource type AWS::SSM::Parameter does not support attribute {Invalid}. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": { - "recorded-date": "21-03-2024, 21:10:29", - "recorded-content": { - "outputs": { - "DeadArn": "arn::sqs::111111111111:" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.validation.json deleted file mode 100644 index 6a74c8a6ddc2d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": { - "last_validated_date": "2024-03-21T21:10:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": { - "last_validated_date": "2023-08-01T09:54:31+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py deleted file mode 100644 index 21d8af81371bc..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py +++ /dev/null @@ -1,494 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -THIS_DIR = os.path.dirname(__file__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestCloudFormationConditions: - @markers.aws.validated - def test_simple_condition_evaluation_deploys_resource( - self, aws_client, deploy_cfn_template, cleanups - ): - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/simple-condition.yaml" - ), - parameters={"OptionParameter": "option-a", "TopicName": topic_name}, - ) - # verify that CloudFormation includes the resource - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - assert stack_resources["StackResources"] - - # verify actual resource deployment - assert [ - t - for t in aws_client.sns.get_paginator("list_topics") - .paginate() - .build_full_result()["Topics"] - if topic_name in t["TopicArn"] - ] - - @markers.aws.validated - def test_simple_condition_evaluation_doesnt_deploy_resource( - self, aws_client, deploy_cfn_template, cleanups - ): - """Note: Conditions allow us to deploy stacks that won't actually contain any deployed resources""" - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/simple-condition.yaml" - ), - parameters={"OptionParameter": "option-b", "TopicName": topic_name}, - ) - # verify that CloudFormation ignores the resource - aws_client.cloudformation.describe_stack_resources(StackName=deployment.stack_id) - - # FIXME: currently broken in localstack - # assert stack_resources['StackResources'] == [] - - # verify actual resource deployment - assert [ - t for t in aws_client.sns.list_topics()["Topics"] if topic_name in t["TopicArn"] - ] == [] - - @pytest.mark.parametrize( - "should_set_custom_name", - ["yep", "nope"], - ) - @markers.aws.validated - def test_simple_intrinsic_fn_condition_evaluation( - self, aws_client, deploy_cfn_template, should_set_custom_name - ): - """ - Tests a simple Fn::If condition evaluation - - The conditional ShouldSetCustomName (yep | nope) switches between an autogenerated and a predefined name for the topic - - FIXME: this should also work with the simple-intrinsic-condition-name-conflict.yaml template where the ID of the condition and the ID of the parameter are the same(!). - It is currently broken in LocalStack though - """ - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/simple-intrinsic-condition.yaml" - ), - parameters={ - "TopicName": topic_name, - "ShouldSetCustomName": should_set_custom_name, - }, - ) - # verify that the topic has the correct name - topic_arn = deployment.outputs["TopicArn"] - if should_set_custom_name == "yep": - assert topic_name in topic_arn - else: - assert topic_name not in topic_arn - - @markers.aws.validated - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - def test_dependent_ref(self, aws_client, snapshot): - """ - Tests behavior of a stack with 2 resources where one depends on the other. - The referenced resource won't be deployed due to its condition evaluating to false, so the ref can't be resolved. - - This immediately leads to an error. - """ - topic_name = f"test-topic-{short_uid()}" - ssm_param_name = f"test-param-{short_uid()}" - - stack_name = f"test-condition-ref-stack-{short_uid()}" - changeset_name = "initial" - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - ChangeSetType="CREATE", - TemplateBody=load_file( - os.path.join(THIS_DIR, "../../../../../templates/conditions/ref-condition.yaml") - ), - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "SsmParamName", "ParameterValue": ssm_param_name}, - {"ParameterKey": "OptionParameter", "ParameterValue": "option-b"}, - ], - ) - snapshot.match("dependent_ref_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - def test_dependent_ref_intrinsic_fn_condition(self, aws_client, deploy_cfn_template): - """ - Checks behavior of un-refable resources - """ - topic_name = f"test-topic-{short_uid()}" - ssm_param_name = f"test-param-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../../../templates/conditions/ref-condition-intrinsic-condition.yaml", - ), - parameters={ - "TopicName": topic_name, - "SsmParamName": ssm_param_name, - "OptionParameter": "option-b", - }, - ) - - @markers.aws.validated - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - def test_dependent_ref_with_macro( - self, aws_client, deploy_cfn_template, lambda_su_role, cleanups - ): - """ - specifying option-b would normally lead to an error without the macro because of the unresolved ref. - Because the macro replaced the resources though, the test passes. - We've therefore shown that conditions aren't fully evaluated before the transformations - - Related findings: - * macros are not allowed to transform Parameters (macro invocation by CFn will fail in this case) - - """ - - log_group_name = f"test-log-group-{short_uid()}" - aws_client.logs.create_log_group(logGroupName=log_group_name) - - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/ref-condition-macro-def.yaml" - ), - parameters={ - "FnRole": lambda_su_role, - "LogGroupName": log_group_name, - "LogRoleARN": lambda_su_role, - }, - ) - - topic_name = f"test-topic-{short_uid()}" - ssm_param_name = f"test-param-{short_uid()}" - stack_name = f"test-condition-ref-macro-stack-{short_uid()}" - changeset_name = "initial" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - ChangeSetType="CREATE", - TemplateBody=load_file( - os.path.join( - THIS_DIR, "../../../../../templates/conditions/ref-condition-macro.yaml" - ) - ), - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "SsmParamName", "ParameterValue": ssm_param_name}, - {"ParameterKey": "OptionParameter", "ParameterValue": "option-b"}, - ], - ) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=changeset_name, StackName=stack_name - ) - - @pytest.mark.parametrize( - ["env_type", "should_create_bucket", "should_create_policy"], - [ - ("test", False, False), - ("test", True, False), - ("prod", False, False), - ("prod", True, True), - ], - ids=[ - "test-nobucket-nopolicy", - "test-bucket-nopolicy", - "prod-nobucket-nopolicy", - "prod-bucket-policy", - ], - ) - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - @markers.aws.validated - def test_nested_conditions( - self, - aws_client, - deploy_cfn_template, - cleanups, - env_type, - should_create_bucket, - should_create_policy, - snapshot, - ): - """ - Tests the case where a condition references another condition - - EnvType == "prod" && BucketName != "" ==> creates bucket + policy - EnvType == "test" && BucketName != "" ==> creates bucket only - EnvType == "test" && BucketName == "" ==> no resource created - EnvType == "prod" && BucketName == "" ==> no resource created - """ - bucket_name = f"ls-test-bucket-{short_uid()}" if should_create_bucket else "" - stack_name = f"condition-test-stack-{short_uid()}" - changeset_name = "initial" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - if bucket_name: - snapshot.add_transformer(snapshot.transform.regex(bucket_name, "")) - snapshot.add_transformer(snapshot.transform.regex(stack_name, "")) - - template = load_file( - os.path.join(THIS_DIR, "../../../../../templates/conditions/nested-conditions.yaml") - ) - create_cs_result = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - TemplateBody=template, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "EnvType", "ParameterValue": env_type}, - {"ParameterKey": "BucketName", "ParameterValue": bucket_name}, - ], - ) - snapshot.match("create_cs_result", create_cs_result) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=changeset_name, StackName=stack_name - ) - - describe_changeset_result = aws_client.cloudformation.describe_change_set( - ChangeSetName=changeset_name, StackName=stack_name - ) - snapshot.match("describe_changeset_result", describe_changeset_result) - aws_client.cloudformation.execute_change_set( - ChangeSetName=changeset_name, StackName=stack_name - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - stack_resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name) - if should_create_policy: - stack_policy = [ - sr - for sr in stack_resources["StackResources"] - if sr["ResourceType"] == "AWS::S3::BucketPolicy" - ][0] - snapshot.add_transformer( - snapshot.transform.regex(stack_policy["PhysicalResourceId"], ""), - priority=-1, - ) - - snapshot.match("stack_resources", stack_resources) - stack_events = aws_client.cloudformation.describe_stack_events(StackName=stack_name) - snapshot.match("stack_events", stack_events) - describe_stack_result = aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("describe_stack_result", describe_stack_result) - - # manual assertions - - # check that bucket exists - try: - aws_client.s3.head_bucket(Bucket=bucket_name) - bucket_exists = True - except Exception: - bucket_exists = False - - assert bucket_exists == should_create_bucket - - if bucket_exists: - # check if a policy exists on the bucket - try: - aws_client.s3.get_bucket_policy(Bucket=bucket_name) - bucket_policy_exists = True - except Exception: - bucket_policy_exists = False - - assert bucket_policy_exists == should_create_policy - - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - @markers.aws.validated - def test_output_reference_to_skipped_resource(self, deploy_cfn_template, aws_client, snapshot): - """test what happens to outputs that reference a resource that isn't deployed due to a falsy condition""" - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/ref-condition-output.yaml" - ), - parameters={ - "OptionParameter": "option-b", - }, - ) - snapshot.match("unresolved_resource_reference_exception", e.value.response) - - @pytest.mark.aws_validated - @pytest.mark.parametrize("create_parameter", ("true", "false"), ids=("create", "no-create")) - def test_conditional_att_to_conditional_resources(self, deploy_cfn_template, create_parameter): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_if_attribute_none.yml" - ) - - deployed = deploy_cfn_template( - template_path=template_path, - parameters={"CreateParameter": create_parameter}, - ) - - if create_parameter == "false": - assert deployed.outputs["Result"] == "Value1" - else: - assert deployed.outputs["Result"] == "Value2" - - # def test_updating_only_conditions_during_stack_update(self): - # ... - - # def test_condition_with_unsupported_intrinsic_functions(self): - # ... - - @pytest.mark.parametrize( - ["should_use_fallback", "match_value"], - [ - (None, "FallbackParamValue"), - ("false", "DefaultParamValue"), - # CFNV2:Other - # ("true", "FallbackParamValue"), - ], - ) - @markers.aws.validated - def test_dependency_in_non_evaluated_if_branch( - self, deploy_cfn_template, aws_client, should_use_fallback, match_value - ): - parameters = ( - {"ShouldUseFallbackParameter": should_use_fallback} if should_use_fallback else {} - ) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_if_conditional_reference.yaml", - ), - parameters=parameters, - ) - param = aws_client.ssm.get_parameter(Name=stack.outputs["ParameterName"]) - assert param["Parameter"]["Value"] == match_value - - @markers.aws.validated - def test_sub_in_conditions(self, deploy_cfn_template, aws_client): - region = aws_client.cloudformation.meta.region_name - topic_prefix = f"test-topic-{short_uid()}" - suffix = short_uid() - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/conditions/intrinsic-functions-in-conditions.yaml", - ), - parameters={ - "TopicName": f"{topic_prefix}-{region}", - "TopicPrefix": topic_prefix, - "TopicNameWithSuffix": f"{topic_prefix}-{region}-{suffix}", - "TopicNameSuffix": suffix, - }, - ) - - topic_arn = stack.outputs["TopicRef"] - aws_client.sns.get_topic_attributes(TopicArn=topic_arn) - assert topic_arn.split(":")[-1] == f"{topic_prefix}-{region}" - - topic_arn_with_suffix = stack.outputs["TopicWithSuffixRef"] - aws_client.sns.get_topic_attributes(TopicArn=topic_arn_with_suffix) - assert topic_arn_with_suffix.split(":")[-1] == f"{topic_prefix}-{region}-{suffix}" - - @markers.aws.validated - @pytest.mark.parametrize("env,region", [("dev", "us-west-2"), ("production", "us-east-1")]) - def test_conditional_in_conditional(self, env, region, deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/conditions/conditional-in-conditional.yml", - ), - parameters={ - "SelectedRegion": region, - "Environment": env, - }, - ) - - if env == "production" and region == "us-east-1": - assert stack.outputs["Result"] == "true" - else: - assert stack.outputs["Result"] == "false" - - @markers.aws.validated - def test_conditional_with_select(self, deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/conditions/conditional-with-select.yml", - ), - ) - - managed_policy_arn = stack.outputs["PolicyArn"] - assert aws_client.iam.get_policy(PolicyArn=managed_policy_arn) - - @markers.aws.validated - def test_condition_on_outputs(self, deploy_cfn_template, aws_client): - """ - The stack has 2 outputs. - Each is gated by a different condition value ("test" vs. "prod"). - Only one of them should be returned for the stack outputs - """ - nested_bucket_name = f"test-bucket-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-conditions.nested.yaml", - ), - parameters={ - "BucketBaseName": nested_bucket_name, - "Mode": "prod", - }, - ) - assert "TestBucket" not in stack.outputs - assert stack.outputs["ProdBucket"] == f"{nested_bucket_name}-prod" - assert aws_client.s3.head_bucket(Bucket=stack.outputs["ProdBucket"]) - - @markers.aws.validated - def test_update_conditions(self, deploy_cfn_template, aws_client): - original_bucket_name = f"test-bucket-{short_uid()}" - stack_name = f"test-update-conditions-{short_uid()}" - deploy_cfn_template( - stack_name=stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_condition_update_1.yml" - ), - parameters={"OriginalBucketName": original_bucket_name}, - ) - assert aws_client.s3.head_bucket(Bucket=original_bucket_name) - - bucket_1 = f"test-bucket-1-{short_uid()}" - bucket_2 = f"test-bucket-2-{short_uid()}" - - deploy_cfn_template( - stack_name=stack_name, - is_update=True, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_condition_update_2.yml" - ), - parameters={ - "OriginalBucketName": original_bucket_name, - "FirstBucket": bucket_1, - "SecondBucket": bucket_2, - }, - ) - - assert aws_client.s3.head_bucket(Bucket=original_bucket_name) - assert aws_client.s3.head_bucket(Bucket=bucket_1) - with pytest.raises(aws_client.s3.exceptions.ClientError): - aws_client.s3.head_bucket(Bucket=bucket_2) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.snapshot.json deleted file mode 100644 index 358e26e2e16a7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.snapshot.json +++ /dev/null @@ -1,763 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": { - "recorded-date": "26-06-2023, 14:20:49", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": { - "recorded-date": "26-06-2023, 14:21:54", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "Bucket", - "ResourceType": "AWS::S3::Bucket", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_COMPLETE-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": { - "recorded-date": "26-06-2023, 14:22:58", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": { - "recorded-date": "26-06-2023, 14:24:03", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "Bucket", - "ResourceType": "AWS::S3::Bucket", - "Scope": [] - }, - "Type": "Resource" - }, - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "Policy", - "ResourceType": "AWS::S3::BucketPolicy", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Policy-CREATE_COMPLETE-date", - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceProperties": { - "Bucket": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn::s3:::/*" - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - } - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Policy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceProperties": { - "Bucket": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn::s3:::/*" - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Policy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceProperties": { - "Bucket": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn::s3:::/*" - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_COMPLETE-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": { - "recorded-date": "26-06-2023, 14:18:26", - "recorded-content": { - "dependent_ref_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Unresolved resource dependencies [MyTopic] in the Resources block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": { - "recorded-date": "27-06-2023, 00:43:18", - "recorded-content": { - "unresolved_resource_reference_exception": { - "Error": { - "Code": "ValidationError", - "Message": "Unresolved resource dependencies [MyTopic] in the Outputs block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.validation.json deleted file mode 100644 index e285748924d8a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": { - "last_validated_date": "2023-06-26T12:18:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": { - "last_validated_date": "2023-06-26T12:24:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": { - "last_validated_date": "2023-06-26T12:22:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": { - "last_validated_date": "2023-06-26T12:21:54+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": { - "last_validated_date": "2023-06-26T12:20:49+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": { - "last_validated_date": "2023-06-26T22:43:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": { - "last_validated_date": "2024-06-18T19:43:43+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py deleted file mode 100644 index a088355fd966a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py +++ /dev/null @@ -1,266 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.pytest.fixtures import StackDeployError -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -THIS_DIR = os.path.dirname(__file__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.snapshot.skip_snapshot_verify -class TestCloudFormationMappings: - @markers.aws.validated - def test_simple_mapping_working(self, aws_client, deploy_cfn_template): - """ - A very simple test to deploy a resource with a name depending on a value that needs to be looked up from the mapping - """ - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping.yaml" - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "A", - }, - ) - # verify that CloudFormation includes the resource - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - assert stack_resources["StackResources"] - - expected_topic_name = f"{topic_name}-suffix-a" - - # verify actual resource deployment - assert [ - t - for t in aws_client.sns.get_paginator("list_topics") - .paginate() - .build_full_result()["Topics"] - if expected_topic_name in t["TopicArn"] - ] - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_with_nonexisting_key(self, aws_client, cleanups, snapshot): - """ - Tries to deploy a resource with a dependency on a mapping key - which is not included in the Mappings section and thus can't be resolved - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join(THIS_DIR, "../../../../../templates/mappings/simple-mapping.yaml") - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "C"}, - ], - ) - snapshot.match("mapping_nonexisting_key_exc", e.value.response) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.only_localstack - def test_async_mapping_error_first_level(self, deploy_cfn_template): - """ - We don't (yet) support validating mappings synchronously in `create_changeset` like AWS does, however - we don't fail with a good error message at all. This test ensures that the deployment fails with a - nicer error message than a Python traceback about "`None` has no attribute `get`". - """ - topic_name = f"test-topic-{short_uid()}" - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../../../templates/mappings/simple-mapping.yaml", - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "C", - }, - ) - - assert "Cannot find map key 'C' in mapping 'TopicSuffixMap'" in str(exc_info.value) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.only_localstack - def test_async_mapping_error_second_level(self, deploy_cfn_template): - """ - Similar to the `test_async_mapping_error_first_level` test above, but - checking the second level of mapping lookup - """ - topic_name = f"test-topic-{short_uid()}" - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../../../templates/mappings/simple-mapping.yaml", - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "A", - "TopicAttributeSelector": "NotValid", - }, - ) - - assert "Cannot find map key 'NotValid' in mapping 'TopicSuffixMap' under key 'A'" in str( - exc_info.value - ) - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_with_invalid_refs(self, aws_client, deploy_cfn_template, cleanups, snapshot): - """ - The Mappings section can only include static elements (strings and lists). - In this test one value is instead a `Ref` which should be rejected by the service - - Also note the overlap with the `test_mapping_with_nonexisting_key` case here. - Even though we specify a non-existing key here again (`C`), the returned error is for the invalid structure. - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping-invalid-ref.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "C"}, - {"ParameterKey": "TopicNameSuffix", "ParameterValue": "suffix-c"}, - ], - ) - snapshot.match("mapping_invalid_ref_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_maximum_nesting_depth(self, aws_client, cleanups, snapshot): - """ - Tries to deploy a template containing a mapping with a nesting depth of 3. - The maximum depth is 2 so it should fail - - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping-nesting-depth.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "A"}, - ], - ) - snapshot.match("mapping_maximum_level_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_minimum_nesting_depth(self, aws_client, cleanups, snapshot): - """ - Tries to deploy a template containing a mapping with a nesting depth of 1. - The required depth is 2, so it should fail for a single level - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping-single-level.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "A"}, - ], - ) - snapshot.match("mapping_minimum_level_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.parametrize( - "map_key,should_error", - [ - ("A", False), - ("B", True), - ], - ids=["should-deploy", "should-not-deploy"], - ) - def test_mapping_ref_map_key(self, deploy_cfn_template, aws_client, map_key, should_error): - topic_name = f"topic-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/mappings/mapping-ref-map-key.yaml" - ), - parameters={ - "MapName": "MyMap", - "MapKey": map_key, - "TopicName": topic_name, - }, - ) - - topic_arn = stack.outputs.get("TopicArn") - if should_error: - assert topic_arn is None - else: - assert topic_arn is not None - - aws_client.sns.get_topic_attributes(TopicArn=topic_arn) - - @markers.aws.validated - def test_aws_refs_in_mappings(self, deploy_cfn_template, account_id): - """ - This test asserts that Pseudo references aka "AWS::" are supported inside a mapping inside a Conditional. - It's worth remembering that even with references being supported, AWS rejects names that are not alphanumeric - in Mapping name or the second level key. - """ - stack_name = f"Stack{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/mappings/mapping-aws-ref-map-key.yaml" - ), - stack_name=stack_name, - template_mapping={"StackName": stack_name}, - ) - assert stack.outputs.get("TopicArn") diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.snapshot.json deleted file mode 100644 index b5ecf4d26a841..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.snapshot.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": { - "recorded-date": "12-06-2023, 16:47:23", - "recorded-content": { - "mapping_nonexisting_key_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template error: Unable to get mapping for TopicSuffixMap::C::Suffix", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": { - "recorded-date": "12-06-2023, 16:47:24", - "recorded-content": { - "mapping_invalid_ref_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Mappings attribute must be a String or a List.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": { - "recorded-date": "12-06-2023, 16:47:24", - "recorded-content": { - "mapping_maximum_level_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Mappings attribute must be a String or a List.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": { - "recorded-date": "12-06-2023, 16:47:25", - "recorded-content": { - "mapping_minimum_level_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Mappings member A must be a map", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.validation.json deleted file mode 100644 index b66abfb0050a0..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": { - "last_validated_date": "2024-10-15T17:22:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": { - "last_validated_date": "2023-06-12T14:47:24+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": { - "last_validated_date": "2023-06-12T14:47:25+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": { - "last_validated_date": "2024-10-17T22:40:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": { - "last_validated_date": "2024-10-17T22:41:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": { - "last_validated_date": "2023-06-12T14:47:24+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": { - "last_validated_date": "2023-06-12T14:47:23+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py deleted file mode 100644 index d89ae634ae003..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py +++ /dev/null @@ -1,131 +0,0 @@ -import json -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestDependsOn: - @pytest.mark.skip(reason="not supported yet") - @markers.aws.validated - def test_depends_on_with_missing_reference( - self, deploy_cfn_template, aws_client, cleanups, snapshot - ): - stack_name = f"test-stack-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_dependson_nonexisting_resource.yaml", - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="init", - ChangeSetType="CREATE", - TemplateBody=load_file(template_path), - ) - snapshot.match("depends_on_nonexisting_exception", e.value.response) - - -class TestFnSub: - # TODO: add test for list sub without a second argument (i.e. the list) - # => Template error: One or more Fn::Sub intrinsic functions don't specify expected arguments. Specify a string as first argument, and an optional second argument to specify a mapping of values to replace in the string - - @markers.aws.validated - def test_fn_sub_cases(self, deploy_cfn_template, aws_client, snapshot): - ssm_parameter_name = f"test-param-{short_uid()}" - snapshot.add_transformer( - snapshot.transform.regex(ssm_parameter_name, "") - ) - snapshot.add_transformer( - snapshot.transform.key_value( - "UrlSuffixPseudoParam", "", reference_replacement=False - ) - ) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/engine/cfn_fn_sub.yaml" - ), - parameters={"ParameterName": ssm_parameter_name}, - ) - - snapshot.match("outputs", deployment.outputs) - - @markers.aws.validated - def test_non_string_parameter_in_sub(self, deploy_cfn_template, aws_client, snapshot): - ssm_parameter_name = f"test-param-{short_uid()}" - snapshot.add_transformer( - snapshot.transform.regex(ssm_parameter_name, "") - ) - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_number_in_sub.yml" - ), - parameters={"ParameterName": ssm_parameter_name}, - ) - - get_param_res = aws_client.ssm.get_parameter(Name=ssm_parameter_name)["Parameter"] - snapshot.match("get-parameter-result", get_param_res) - - -@pytest.mark.skip(reason="CFNV2:Validation") -@markers.aws.validated -def test_useful_error_when_invalid_ref(deploy_cfn_template, snapshot): - """ - When trying to resolve a non-existent !Ref, make sure the error message includes the name of the !Ref - to clarify which !Ref cannot be resolved. - """ - logical_resource_id = "Topic" - ref_name = "InvalidRef" - - template = json.dumps( - { - "Resources": { - logical_resource_id: { - "Type": "AWS::SNS::Topic", - "Properties": { - "Name": { - "Ref": ref_name, - }, - }, - } - } - } - ) - - with pytest.raises(ClientError) as exc_info: - deploy_cfn_template(template=template) - - snapshot.match("validation_error", exc_info.value.response) - - -@markers.aws.validated -def test_resolve_transitive_placeholders_in_strings(deploy_cfn_template, aws_client, snapshot): - queue_name = f"q-{short_uid()}" - parameter_ver = f"v{short_uid()}" - stack_name = f"stack-{short_uid()}" - stack = deploy_cfn_template( - stack_name=stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/legacy_transitive_ref.yaml" - ), - max_wait=300 if is_aws_cloud() else 10, - parameters={"QueueName": queue_name, "Qualifier": parameter_ver}, - ) - tags = aws_client.sqs.list_queue_tags(QueueUrl=stack.outputs["QueueURL"]) - snapshot.add_transformer( - snapshot.transform.regex(r"/cdk-bootstrap/(\w+)/", "/cdk-bootstrap/.../") - ) - snapshot.match("tags", tags) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.snapshot.json deleted file mode 100644 index c17fb974377b0..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.snapshot.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": { - "recorded-date": "10-07-2023, 15:22:26", - "recorded-content": { - "depends_on_nonexisting_exception": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Unresolved resource dependencies [NonExistingResource] in the Resources block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_fn_sub_cases": { - "recorded-date": "23-08-2023, 20:41:02", - "recorded-content": { - "outputs": { - "ListRefGetAtt": "unimportant", - "ListRefGetAttMapping": "unimportant", - "ListRefMultipleMix": "Param1Value--Param1Value", - "ListRefParam": "Param1Value", - "ListRefPseudoParam": "", - "ListRefResourceDirect": "Param1Value", - "ListRefResourceMappingRef": "Param1Value", - "ListStatic": "this is a static string", - "StringRefGetAtt": "unimportant", - "StringRefMultiple": "Param1Value - Param1Value", - "StringRefParam": "Param1Value", - "StringRefPseudoParam": "", - "StringRefResource": "Param1Value", - "StringStatic": "this is a static string", - "UrlSuffixPseudoParam": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_useful_error_when_invalid_ref": { - "recorded-date": "28-05-2024, 11:42:58", - "recorded-content": { - "validation_error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Unresolved resource dependencies [InvalidRef] in the Resources block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": { - "recorded-date": "18-06-2024, 19:55:48", - "recorded-content": { - "tags": { - "Tags": { - "test": "arn::ssm::111111111111:parameter/cdk-bootstrap/.../version" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": { - "recorded-date": "17-10-2024, 22:49:56", - "recorded-content": { - "get-parameter-result": { - "ARN": "arn::ssm::111111111111:parameter/", - "DataType": "text", - "LastModifiedDate": "datetime", - "Name": "", - "Type": "String", - "Value": "my number is 3", - "Version": 1 - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.validation.json deleted file mode 100644 index b2edacb2b077b..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": { - "last_validated_date": "2023-07-10T13:22:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_fn_sub_cases": { - "last_validated_date": "2023-08-23T18:41:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": { - "last_validated_date": "2024-10-17T22:49:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": { - "last_validated_date": "2024-06-18T19:55:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_useful_error_when_invalid_ref": { - "last_validated_date": "2024-05-28T11:42:58+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler1/api.zip b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler1/api.zip deleted file mode 100644 index 8f8c0f78f6257..0000000000000 Binary files a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler1/api.zip and /dev/null differ diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler2/api.zip b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler2/api.zip deleted file mode 100644 index f45beec4a069f..0000000000000 Binary files a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler2/api.zip and /dev/null differ diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_acm.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_acm.py deleted file mode 100644 index 5e215533958e9..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_acm.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -TEST_TEMPLATE = """ -Resources: - cert1: - Type: "AWS::CertificateManager::Certificate" - Properties: - DomainName: "{{domain}}" - DomainValidationOptions: - - DomainName: "{{domain}}" - HostedZoneId: zone123 # using dummy ID for now - ValidationMethod: DNS -Outputs: - Cert: - Value: !Ref cert1 -""" - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.only_localstack -def test_cfn_acm_certificate(deploy_cfn_template, aws_client): - domain = f"domain-{short_uid()}.com" - deploy_cfn_template(template=TEST_TEMPLATE, template_mapping={"domain": domain}) - - result = aws_client.acm.list_certificates()["CertificateSummaryList"] - result = [cert for cert in result if cert["DomainName"] == domain] - assert result diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py deleted file mode 100644 index 9d1b99b86b976..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py +++ /dev/null @@ -1,716 +0,0 @@ -import json -import os.path -from operator import itemgetter - -import pytest -import requests -from tests.aws.services.apigateway.apigateway_fixtures import api_invoke_url - -from localstack import constants -from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid -from localstack.utils.files import load_file -from localstack.utils.run import to_str -from localstack.utils.strings import to_bytes - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -TEST_LAMBDA_PYTHON_ECHO = os.path.join(PARENT_DIR, "lambda_/functions/lambda_echo.py") - -TEST_TEMPLATE_1 = """ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Parameters: - ApiName: - Type: String - IntegrationUri: - Type: String -Resources: - Api: - Type: AWS::Serverless::Api - Properties: - StageName: dev - Name: !Ref ApiName - DefinitionBody: - swagger: 2.0 - info: - version: "1.0" - title: "Public API" - basePath: /base - schemes: - - "https" - x-amazon-apigateway-binary-media-types: - - "*/*" - paths: - /test: - post: - responses: {} - x-amazon-apigateway-integration: - uri: !Ref IntegrationUri - httpMethod: "POST" - type: "http_proxy" -""" - - -# this is an `only_localstack` test because it makes use of _custom_id_ tag -@markers.aws.only_localstack -def test_cfn_apigateway_aws_integration(deploy_cfn_template, aws_client): - api_name = f"rest-api-{short_uid()}" - custom_id = short_uid() - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/apigw-awsintegration-request-parameters.yaml", - ), - parameters={ - "ApiName": api_name, - "CustomTagKey": "_custom_id_", - "CustomTagValue": custom_id, - }, - ) - - # check resources creation - apis = [ - api for api in aws_client.apigateway.get_rest_apis()["items"] if api["name"] == api_name - ] - assert len(apis) == 1 - api_id = apis[0]["id"] - - # check resources creation - resources = aws_client.apigateway.get_resources(restApiId=api_id)["items"] - assert ( - resources[0]["resourceMethods"]["GET"]["requestParameters"]["method.request.path.id"] - is False - ) - assert ( - resources[0]["resourceMethods"]["GET"]["methodIntegration"]["requestParameters"][ - "integration.request.path.object" - ] - == "method.request.path.id" - ) - - # check domains creation - domain_names = [ - domain["domainName"] for domain in aws_client.apigateway.get_domain_names()["items"] - ] - expected_domain = "cfn5632.localstack.cloud" # hardcoded value from template yaml file - assert expected_domain in domain_names - - # check basepath mappings creation - mappings = [ - mapping["basePath"] - for mapping in aws_client.apigateway.get_base_path_mappings(domainName=expected_domain)[ - "items" - ] - ] - assert len(mappings) == 1 - assert mappings[0] == "(none)" - - -@markers.aws.validated -def test_cfn_apigateway_swagger_import(deploy_cfn_template, echo_http_server_post, aws_client): - api_name = f"rest-api-{short_uid()}" - deploy_cfn_template( - template=TEST_TEMPLATE_1, - parameters={"ApiName": api_name, "IntegrationUri": echo_http_server_post}, - ) - - # get API details - apis = [ - api for api in aws_client.apigateway.get_rest_apis()["items"] if api["name"] == api_name - ] - assert len(apis) == 1 - api_id = apis[0]["id"] - - # construct API endpoint URL - url = api_invoke_url(api_id, stage="dev", path="/test") - - # invoke API endpoint, assert results - result = requests.post(url, data="test 123") - assert result.ok - content = json.loads(to_str(result.content)) - assert content["data"] == "test 123" - assert content["url"].endswith("/post") - - -@pytest.mark.skip( - reason="The v2 provider appears to instead return the correct url: " - "https://e1i3grfiws.execute-api.us-east-1.localhost.localstack.cloud/prod/" -) -@markers.aws.only_localstack -def test_url_output(httpserver, deploy_cfn_template): - httpserver.expect_request("").respond_with_data(b"", 200) - api_name = f"rest-api-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway-url-output.yaml" - ), - template_mapping={ - "api_name": api_name, - "integration_uri": httpserver.url_for("/{proxy}"), - }, - ) - - assert len(stack.outputs) == 2 - api_id = stack.outputs["ApiV1IdOutput"] - api_url = stack.outputs["ApiV1UrlOutput"] - assert api_id - assert api_url - assert api_id in api_url - - assert f"https://{api_id}.execute-api.{constants.LOCALHOST_HOSTNAME}:4566" in api_url - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.get-method-post.methodIntegration.connectionType", # TODO: maybe because this is a MOCK integration - ] -) -def test_cfn_with_apigateway_resources(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template35.yaml" - ) - ) - apis = [ - api - for api in aws_client.apigateway.get_rest_apis()["items"] - if api["name"] == "celeste-Gateway-local" - ] - assert len(apis) == 1 - api_id = apis[0]["id"] - - resources = [ - res - for res in aws_client.apigateway.get_resources(restApiId=api_id)["items"] - if res.get("pathPart") == "account" - ] - - assert len(resources) == 1 - - resp = aws_client.apigateway.get_method( - restApiId=api_id, resourceId=resources[0]["id"], httpMethod="POST" - ) - snapshot.match("get-method-post", resp) - - models = aws_client.apigateway.get_models(restApiId=api_id) - models["items"].sort(key=itemgetter("name")) - snapshot.match("get-models", models) - - schemas = [model["schema"] for model in models["items"]] - for schema in schemas: - # assert that we can JSON load the schema, and that the schema is a valid JSON - assert isinstance(json.loads(schema), dict) - - stack.destroy() - - # TODO: Resolve limitations with stack.destroy in v2 engine. - # apis = [ - # api - # for api in aws_client.apigateway.get_rest_apis()["items"] - # if api["name"] == "celeste-Gateway-local" - # ] - # assert not apis - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.get-resources.items..resourceMethods.ANY", # TODO: empty in AWS - ] -) -def test_cfn_deploy_apigateway_models(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.apigateway_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_models.json" - ) - ) - - api_id = stack.outputs["RestApiId"] - - resources = aws_client.apigateway.get_resources(restApiId=api_id) - resources["items"].sort(key=itemgetter("path")) - snapshot.match("get-resources", resources) - - models = aws_client.apigateway.get_models(restApiId=api_id) - models["items"].sort(key=itemgetter("name")) - snapshot.match("get-models", models) - - request_validators = aws_client.apigateway.get_request_validators(restApiId=api_id) - snapshot.match("get-request-validators", request_validators) - - for resource in resources["items"]: - if resource["path"] == "/validated": - resp = aws_client.apigateway.get_method( - restApiId=api_id, resourceId=resource["id"], httpMethod="ANY" - ) - snapshot.match("get-method-any", resp) - - # construct API endpoint URL - url = api_invoke_url(api_id, stage="local", path="/validated") - - # invoke API endpoint, assert results - valid_data = {"string_field": "string", "integer_field": 123456789} - - result = requests.post(url, json=valid_data) - assert result.ok - - # invoke API endpoint, assert results - invalid_data = {"string_field": "string"} - - result = requests.post(url, json=invalid_data) - assert result.status_code == 400 - - result = requests.get(url) - assert result.status_code == 400 - - -@markers.aws.validated -def test_cfn_deploy_apigateway_integration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/apigateway_integration_no_authorizer.yml", - ), - max_wait=120, - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "stack-name")) - - rest_api_id = stack.outputs["RestApiId"] - rest_api = aws_client.apigateway.get_rest_api(restApiId=rest_api_id) - snapshot.match("rest_api", rest_api) - snapshot.add_transformer(snapshot.transform.key_value("rootResourceId")) - - resource_id = stack.outputs["ResourceId"] - method = aws_client.apigateway.get_method( - restApiId=rest_api_id, resourceId=resource_id, httpMethod="GET" - ) - snapshot.match("method", method) - # TODO: snapshot the authorizer too? it's not attached to the REST API - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.resources.items..resourceMethods.GET", # TODO: after importing, AWS returns them empty? - # TODO: missing from LS response - "$.get-stage.methodSettings", - "$.get-stage.tags", - "$..binaryMediaTypes", - ] -) -def test_cfn_deploy_apigateway_from_s3_swagger( - deploy_cfn_template, snapshot, aws_client, s3_bucket -): - snapshot.add_transformer(snapshot.transform.key_value("deploymentId")) - # put the swagger file in S3 - swagger_template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../files/pets.json") - ) - key_name = "swagger-template-pets.json" - response = aws_client.s3.put_object(Bucket=s3_bucket, Key=key_name, Body=swagger_template) - object_etag = response["ETag"] - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_integration_from_s3.yml" - ), - parameters={ - "S3BodyBucket": s3_bucket, - "S3BodyKey": key_name, - "S3BodyETag": object_etag, - }, - max_wait=120, - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "stack-name")) - - rest_api_id = stack.outputs["RestApiId"] - rest_api = aws_client.apigateway.get_rest_api(restApiId=rest_api_id) - snapshot.match("rest-api", rest_api) - - resources = aws_client.apigateway.get_resources(restApiId=rest_api_id) - resources["items"] = sorted(resources["items"], key=itemgetter("path")) - snapshot.match("resources", resources) - - get_stage = aws_client.apigateway.get_stage(restApiId=rest_api_id, stageName="local") - snapshot.match("get-stage", get_stage) - - -@markers.aws.validated -def test_cfn_apigateway_rest_api(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway.json" - ) - ) - - rs = aws_client.apigateway.get_rest_apis() - apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - assert not apis - - stack.destroy() - - stack_2 = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway.json" - ), - parameters={"Create": "True"}, - ) - rs = aws_client.apigateway.get_rest_apis() - apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - assert len(apis) == 1 - - rs = aws_client.apigateway.get_models(restApiId=apis[0]["id"]) - assert len(rs["items"]) == 3 - - stack_2.destroy() - - # TODO: Resolve limitations with stack.destroy in v2 engine. - # rs = aws_client.apigateway.get_rest_apis() - # apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - # assert not apis - - -@markers.aws.validated -def test_account(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_account.yml" - ) - ) - - account_info = aws_client.apigateway.get_account() - assert account_info["cloudwatchRoleArn"] == stack.outputs["RoleArn"] - - # Assert that after deletion of stack, the apigw account is not updated - stack.destroy() - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack.stack_name) - account_info = aws_client.apigateway.get_account() - assert account_info["cloudwatchRoleArn"] == stack.outputs["RoleArn"] - - -@markers.aws.validated -@pytest.mark.skip( - reason="CFNV2:Other ApiDeployment creation fails due to the REST API not having a method set" -) -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..tags.'aws:cloudformation:logical-id'", - "$..tags.'aws:cloudformation:stack-id'", - "$..tags.'aws:cloudformation:stack-name'", - ] -) -def test_update_usage_plan(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("apiId"), - snapshot.transform.key_value("stage"), - snapshot.transform.key_value("id"), - snapshot.transform.key_value("name"), - snapshot.transform.key_value("aws:cloudformation:stack-name"), - snapshot.transform.resource_name(), - ] - ) - rest_api_name = f"api-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_usage_plan.yml" - ), - parameters={"QuotaLimit": "5000", "RestApiName": rest_api_name, "TagValue": "value1"}, - ) - - usage_plan = aws_client.apigateway.get_usage_plan(usagePlanId=stack.outputs["UsagePlanId"]) - snapshot.match("usage-plan", usage_plan) - assert usage_plan["quota"]["limit"] == 5000 - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template=load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_usage_plan.yml" - ) - ), - parameters={ - "QuotaLimit": "7000", - "RestApiName": rest_api_name, - "TagValue": "value-updated", - }, - ) - - usage_plan = aws_client.apigateway.get_usage_plan(usagePlanId=stack.outputs["UsagePlanId"]) - snapshot.match("updated-usage-plan", usage_plan) - assert usage_plan["quota"]["limit"] == 7000 - - -@pytest.mark.skip( - reason="CFNV2:Other ApiDeployment creation fails due to the REST API not having a method set" -) -@markers.snapshot.skip_snapshot_verify( - paths=["$..createdDate", "$..description", "$..lastUpdatedDate", "$..tags"] -) -@markers.aws.validated -def test_update_apigateway_stage(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("deploymentId"), - snapshot.transform.key_value("aws:cloudformation:stack-name"), - snapshot.transform.resource_name(), - ] - ) - - api_name = f"api-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_update_stage.yml" - ), - parameters={"RestApiName": api_name}, - ) - api_id = stack.outputs["RestApiId"] - stage = aws_client.apigateway.get_stage(stageName="dev", restApiId=api_id) - snapshot.match("created-stage", stage) - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_update_stage.yml" - ), - parameters={ - "Description": "updated-description", - "Method": "POST", - "RestApiName": api_name, - }, - ) - # Changes to the stage or one of the methods it depends on does not trigger a redeployment - stage = aws_client.apigateway.get_stage(stageName="dev", restApiId=api_id) - snapshot.match("updated-stage", stage) - - -@markers.aws.validated -def test_api_gateway_with_policy_as_dict(deploy_cfn_template, snapshot, aws_client): - template = """ - Parameters: - RestApiName: - Type: String - Resources: - MyApi: - Type: AWS::ApiGateway::RestApi - Properties: - Name: !Ref RestApiName - Policy: - Version: "2012-10-17" - Statement: - - Sid: AllowInvokeAPI - Action: "*" - Effect: Allow - Principal: - AWS: "*" - Resource: "*" - Outputs: - MyApiId: - Value: !Ref MyApi - """ - - rest_api_name = f"api-{short_uid()}" - stack = deploy_cfn_template( - template=template, - parameters={"RestApiName": rest_api_name}, - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "stack-name")) - - rest_api = aws_client.apigateway.get_rest_api(restApiId=stack.outputs.get("MyApiId")) - - # note: API Gateway seems to perform double-escaping of the policy document for REST APIs, if specified as dict - policy = to_bytes(rest_api["policy"]).decode("unicode_escape") - rest_api["policy"] = json.loads(policy) - - snapshot.match("rest-api", rest_api) - - -@pytest.mark.skip( - reason="CFNV2:Other lambda function fails on creation due to invalid function name" -) -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.put-ssm-param.Tier", - "$.get-resources.items..resourceMethods.GET", - "$.get-resources.items..resourceMethods.OPTIONS", - "$..methodIntegration.cacheNamespace", - "$.get-authorizers.items..authorizerResultTtlInSeconds", - ] -) -def test_rest_api_serverless_ref_resolving( - deploy_cfn_template, snapshot, aws_client, create_parameter, create_lambda_function -): - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformers_list( - [ - snapshot.transform.resource_name(), - snapshot.transform.key_value("cacheNamespace"), - snapshot.transform.key_value("uri"), - snapshot.transform.key_value("authorizerUri"), - ] - ) - create_parameter(Name="/test-stack/testssm/random-value", Value="x-test-header", Type="String") - - fn_name = f"test-{short_uid()}" - lambda_authorizer = create_lambda_function( - func_name=fn_name, - handler_file=TEST_LAMBDA_PYTHON_ECHO, - runtime=Runtime.python3_12, - ) - - create_parameter( - Name="/test-stack/testssm/lambda-arn", - Value=lambda_authorizer["CreateFunctionResponse"]["FunctionArn"], - Type="String", - ) - - stack = deploy_cfn_template( - template=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/apigateway_serverless_api_resolving.yml", - ) - ), - parameters={"AllowedOrigin": "http://localhost:8000"}, - ) - rest_api_id = stack.outputs.get("ApiGatewayApiId") - - resources = aws_client.apigateway.get_resources(restApiId=rest_api_id) - snapshot.match("get-resources", resources) - - authorizers = aws_client.apigateway.get_authorizers(restApiId=rest_api_id) - snapshot.match("get-authorizers", authorizers) - - root_resource = resources["items"][0] - - for http_method in root_resource["resourceMethods"]: - method = aws_client.apigateway.get_method( - restApiId=rest_api_id, resourceId=root_resource["id"], httpMethod=http_method - ) - snapshot.match(f"get-method-{http_method}", method) - - -class TestServerlessApigwLambda: - @pytest.mark.skip( - reason="Requires investigation into the stack not being available in the v2 provider" - ) - @markers.aws.validated - def test_serverless_like_deployment_with_update( - self, deploy_cfn_template, aws_client, cleanups - ): - """ - Regression test for serverless. Since adding a delete handler for the "AWS::ApiGateway::Deployment" resource, - the update was failing due to the delete raising an Exception because of a still connected Stage. - - This test recreates a simple recreated deployment procedure as done by "serverless" where - `serverless deploy` actually both creates a stack and then immediately updates it. - The second UpdateStack is then caused by another `serverless deploy`, e.g. when changing the lambda configuration - """ - - # 1. deploy create - template_content = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/serverless-apigw-lambda.create.json", - ) - ) - stack_name = f"slsstack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template_content, - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait( - StackName=stack["StackId"] - ) - - # 2. update first - # get deployed bucket name - outputs = aws_client.cloudformation.describe_stacks(StackName=stack["StackId"])["Stacks"][ - 0 - ]["Outputs"] - outputs = {k["OutputKey"]: k["OutputValue"] for k in outputs} - bucket_name = outputs["ServerlessDeploymentBucketName"] - - # upload zip file to s3 bucket - # "serverless/test-service/local/1708076358388-2024-02-16T09:39:18.388Z/api.zip" - handler1_filename = os.path.join(os.path.dirname(__file__), "handlers/handler1/api.zip") - aws_client.s3.upload_file( - Filename=handler1_filename, - Bucket=bucket_name, - Key="serverless/test-service/local/1708076358388-2024-02-16T09:39:18.388Z/api.zip", - ) - - template_content = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/serverless-apigw-lambda.update.json", - ) - ) - stack = aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template_content, - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack["StackId"] - ) - - get_fn_1 = aws_client.lambda_.get_function(FunctionName="test-service-local-api") - assert get_fn_1["Configuration"]["Handler"] == "index.handler" - - # # 3. update second - # # upload zip file to s3 bucket - handler2_filename = os.path.join(os.path.dirname(__file__), "handlers/handler2/api.zip") - aws_client.s3.upload_file( - Filename=handler2_filename, - Bucket=bucket_name, - Key="serverless/test-service/local/1708076568092-2024-02-16T09:42:48.092Z/api.zip", - ) - - template_content = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/serverless-apigw-lambda.update2.json", - ) - ) - stack = aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template_content, - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack["StackId"] - ) - get_fn_2 = aws_client.lambda_.get_function(FunctionName="test-service-local-api") - assert get_fn_2["Configuration"]["Handler"] == "index.handler2" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.snapshot.json deleted file mode 100644 index bffa8bf5ed3af..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.snapshot.json +++ /dev/null @@ -1,682 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": { - "recorded-date": "15-07-2025, 19:29:28", - "recorded-content": { - "rest_api": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "method": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "GET", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "connectionType": "INTERNET", - "httpMethod": "GET", - "integrationResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Amzn-Trace-Id'", - "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,POST'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "statusCode": "200" - } - }, - "passthroughBehavior": "WHEN_NO_MATCH", - "timeoutInMillis": 29000, - "type": "HTTP_PROXY", - "uri": "http://www.example.com" - }, - "methodResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Headers": true, - "method.response.header.Access-Control-Allow-Methods": true, - "method.response.header.Access-Control-Allow-Origin": true - }, - "statusCode": "200" - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": { - "recorded-date": "15-07-2025, 19:29:58", - "recorded-content": { - "rest-api": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "policy": { - "Statement": [ - { - "Action": "*", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Sid": "AllowInvokeAPI" - } - ], - "Version": "2012-10-17" - }, - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "MyApi", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": { - "recorded-date": "15-07-2025, 20:32:03", - "recorded-content": { - "rest-api": { - "apiKeySource": "HEADER", - "binaryMediaTypes": [ - "image/png", - "image/jpg", - "image/gif", - "application/pdf" - ], - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "REGIONAL" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "ApiGatewayRestApi", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "version": "1.0.0", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "resources": { - "items": [ - { - "id": "", - "path": "/" - }, - { - "id": "", - "parentId": "", - "path": "/pets", - "pathPart": "pets", - "resourceMethods": { - "GET": {} - } - }, - { - "id": "", - "parentId": "", - "path": "/pets/{petId}", - "pathPart": "{petId}", - "resourceMethods": { - "GET": {} - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-stage": { - "cacheClusterEnabled": false, - "cacheClusterStatus": "NOT_AVAILABLE", - "createdDate": "datetime", - "deploymentId": "", - "description": "Test Stage 123", - "lastUpdatedDate": "datetime", - "methodSettings": { - "*/*": { - "cacheDataEncrypted": false, - "cacheTtlInSeconds": 300, - "cachingEnabled": false, - "dataTraceEnabled": true, - "loggingLevel": "ERROR", - "metricsEnabled": true, - "requireAuthorizationForCacheControl": true, - "throttlingBurstLimit": 5000, - "throttlingRateLimit": 10000.0, - "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER" - } - }, - "stageName": "local", - "tags": { - "aws:cloudformation:logical-id": "ApiGWStage", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "tracingEnabled": true, - "variables": { - "TestCasing": "myvar", - "testCasingTwo": "myvar2", - "testlowcasing": "myvar3" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": { - "recorded-date": "21-06-2024, 00:09:05", - "recorded-content": { - "get-resources": { - "items": [ - { - "id": "", - "path": "/" - }, - { - "id": "", - "parentId": "", - "path": "/validated", - "pathPart": "validated", - "resourceMethods": { - "ANY": {} - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-models": { - "items": [ - { - "contentType": "application/json", - "description": "This is a default empty schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object" - } - }, - { - "contentType": "application/json", - "description": "This is a default error schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - }, - { - "contentType": "application/json", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "", - "type": "object", - "properties": { - "integer_field": { - "type": "number" - }, - "string_field": { - "type": "string" - } - }, - "required": [ - "string_field", - "integer_field" - ] - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-request-validators": { - "items": [ - { - "id": "", - "name": "", - "validateRequestBody": true, - "validateRequestParameters": false - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-method-any": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "ANY", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "integrationResponses": { - "200": { - "statusCode": "200" - } - }, - "passthroughBehavior": "NEVER", - "requestTemplates": { - "application/json": { - "statusCode": 200 - } - }, - "timeoutInMillis": 29000, - "type": "MOCK" - }, - "methodResponses": { - "200": { - "statusCode": "200" - } - }, - "requestModels": { - "application/json": "" - }, - "requestValidatorId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_with_apigateway_resources": { - "recorded-date": "20-06-2024, 23:54:26", - "recorded-content": { - "get-method-post": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "POST", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "integrationResponses": { - "202": { - "responseTemplates": { - "application/json": { - "operation": "celeste_account_create", - "data": { - "key": "123e4567-e89b-12d3-a456-426614174000", - "secret": "123e4567-e89b-12d3-a456-426614174000" - } - } - }, - "selectionPattern": "2\\d{2}", - "statusCode": "202" - }, - "404": { - "responseTemplates": { - "application/json": { - "message": "Not Found" - } - }, - "selectionPattern": "404", - "statusCode": "404" - }, - "500": { - "responseTemplates": { - "application/json": { - "message": "Unknown " - } - }, - "selectionPattern": "5\\d{2}", - "statusCode": "500" - } - }, - "passthroughBehavior": "WHEN_NO_TEMPLATES", - "requestTemplates": { - "application/json": "" - }, - "timeoutInMillis": 29000, - "type": "MOCK" - }, - "methodResponses": { - "202": { - "responseModels": { - "application/json": "" - }, - "statusCode": "202" - }, - "500": { - "responseModels": { - "application/json": "" - }, - "statusCode": "500" - } - }, - "operationName": "create_account", - "requestParameters": { - "method.request.path.account": true - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-models": { - "items": [ - { - "contentType": "application/json", - "description": "This is a default empty schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object" - } - }, - { - "contentType": "application/json", - "description": "This is a default error schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - }, - { - "contentType": "application/json", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "AccountCreate", - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "email": { - "format": "email", - "type": "string" - } - } - } - }, - { - "contentType": "application/json", - "id": "", - "name": "", - "schema": {} - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": { - "recorded-date": "06-07-2023, 21:01:08", - "recorded-content": { - "get-resources": { - "items": [ - { - "id": "", - "path": "/", - "resourceMethods": { - "GET": {}, - "OPTIONS": {} - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-authorizers": { - "items": [ - { - "authType": "custom", - "authorizerUri": "", - "id": "", - "identitySource": "method.request.header.Authorization", - "name": "", - "type": "TOKEN" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-method-GET": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "GET", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "httpMethod": "POST", - "passthroughBehavior": "WHEN_NO_MATCH", - "timeoutInMillis": 29000, - "type": "AWS_PROXY", - "uri": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-method-OPTIONS": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "OPTIONS", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "integrationResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Credentials": "'true'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,x-test-header'", - "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,POST,GET,PUT'", - "method.response.header.Access-Control-Allow-Origin": "'http://localhost:8000'" - }, - "responseTemplates": { - "application/json": {} - }, - "statusCode": "200" - } - }, - "passthroughBehavior": "WHEN_NO_MATCH", - "requestTemplates": { - "application/json": { - "statusCode": 200 - } - }, - "timeoutInMillis": 29000, - "type": "MOCK" - }, - "methodResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Credentials": false, - "method.response.header.Access-Control-Allow-Headers": false, - "method.response.header.Access-Control-Allow-Methods": false, - "method.response.header.Access-Control-Allow-Origin": false - }, - "statusCode": "200" - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_usage_plan": { - "recorded-date": "13-09-2024, 09:57:21", - "recorded-content": { - "usage-plan": { - "apiStages": [ - { - "apiId": "", - "stage": "" - } - ], - "id": "", - "name": "", - "quota": { - "limit": 5000, - "offset": 0, - "period": "MONTH" - }, - "tags": { - "aws:cloudformation:logical-id": "UsagePlan", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "test": "value1", - "test2": "hardcoded" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "updated-usage-plan": { - "apiStages": [ - { - "apiId": "", - "stage": "" - } - ], - "id": "", - "name": "", - "quota": { - "limit": 7000, - "offset": 0, - "period": "MONTH" - }, - "tags": { - "aws:cloudformation:logical-id": "UsagePlan", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "test": "value-updated", - "test2": "hardcoded" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_apigateway_stage": { - "recorded-date": "07-11-2024, 05:35:20", - "recorded-content": { - "created-stage": { - "cacheClusterEnabled": false, - "cacheClusterStatus": "NOT_AVAILABLE", - "createdDate": "datetime", - "deploymentId": "", - "lastUpdatedDate": "datetime", - "methodSettings": {}, - "stageName": "dev", - "tags": { - "aws:cloudformation:logical-id": "Stage", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "tracingEnabled": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "updated-stage": { - "cacheClusterEnabled": false, - "cacheClusterStatus": "NOT_AVAILABLE", - "createdDate": "datetime", - "deploymentId": "", - "lastUpdatedDate": "datetime", - "methodSettings": {}, - "stageName": "dev", - "tags": { - "aws:cloudformation:logical-id": "Stage", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "tracingEnabled": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.validation.json deleted file mode 100644 index 43ad31fc92767..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.validation.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": { - "last_validated_date": "2024-02-19T08:55:12+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": { - "last_validated_date": "2025-07-15T19:30:16+00:00", - "durations_in_seconds": { - "setup": 0.5, - "call": 11.81, - "teardown": 17.53, - "total": 29.84 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_apigateway_rest_api": { - "last_validated_date": "2024-06-25T18:12:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": { - "last_validated_date": "2025-07-16T00:25:05+00:00", - "durations_in_seconds": { - "setup": 1.15, - "call": 18.86, - "teardown": 8.08, - "total": 28.09 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": { - "last_validated_date": "2025-07-15T19:29:44+00:00", - "durations_in_seconds": { - "setup": 0.57, - "call": 26.97, - "teardown": 15.37, - "total": 42.91 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": { - "last_validated_date": "2024-06-21T00:09:05+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_with_apigateway_resources": { - "last_validated_date": "2024-06-20T23:54:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": { - "last_validated_date": "2023-07-06T19:01:08+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_apigateway_stage": { - "last_validated_date": "2024-11-07T05:35:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_usage_plan": { - "last_validated_date": "2024-09-13T09:57:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py deleted file mode 100644 index 89e176d0f1cde..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py +++ /dev/null @@ -1,149 +0,0 @@ -import os - -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestCdkInit: - @pytest.mark.skip( - reason="CFNV2:Destroy each test passes individually but because we don't delete resources, running all parameterized options fails" - ) - @pytest.mark.parametrize("bootstrap_version", ["10", "11", "12"]) - @markers.aws.validated - def test_cdk_bootstrap(self, deploy_cfn_template, bootstrap_version, aws_client): - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - f"../../../../../templates/cdk_bootstrap_v{bootstrap_version}.yaml", - ), - parameters={"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}, - ) - init_stack_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cdk_init_template.yaml" - ) - ) - assert init_stack_result.outputs["BootstrapVersionOutput"] == bootstrap_version - stack_res = aws_client.cloudformation.describe_stack_resources( - StackName=init_stack_result.stack_id, LogicalResourceId="CDKMetadata" - ) - assert len(stack_res["StackResources"]) == 1 - assert stack_res["StackResources"][0]["LogicalResourceId"] == "CDKMetadata" - - @pytest.mark.skip(reason="CFNV2:Provider") - @markers.aws.validated - def test_cdk_bootstrap_redeploy(self, aws_client, cleanup_stacks, cleanup_changesets, cleanups): - """Test that simulates a sequence of commands executed by CDK when running 'cdk bootstrap' twice""" - - stack_name = f"CDKToolkit-{short_uid()}" - change_set_name = f"cdk-deploy-change-set-{short_uid()}" - - def clean_resources(): - cleanup_stacks([stack_name]) - cleanup_changesets([change_set_name]) - - cleanups.append(clean_resources) - - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/cdk_bootstrap.yml") - ) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - Capabilities=["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"], - Description="CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", - Parameters=[ - { - "ParameterKey": "BootstrapVariant", - "ParameterValue": "AWS CDK: Default Resources", - }, - {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, - {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, - {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, - {"ParameterKey": "FileAssetsBucketKmsKeyId", "ParameterValue": "AWS_MANAGED_KEY"}, - {"ParameterKey": "PublicAccessBlockConfiguration", "ParameterValue": "true"}, - {"ParameterKey": "Qualifier", "ParameterValue": "hnb659fds"}, - {"ParameterKey": "UseExamplePermissionsBoundary", "ParameterValue": "false"}, - ], - ) - aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=change_set_name - ) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=change_set_name - ) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=change_set_name - ) - - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - aws_client.cloudformation.describe_stacks(StackName=stack_name) - - # When CDK toolstrap command is executed again it just confirms that the template is the same - aws_client.sts.get_caller_identity() - aws_client.cloudformation.get_template(StackName=stack_name, TemplateStage="Original") - - # TODO: create scenario where the template is different to catch cdk behavior - - -class TestCdkSampleApp: - @pytest.mark.skip(reason="CFNV2:Provider") - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Attributes.Policy.Statement..Condition", - "$..Attributes.Policy.Statement..Resource", - "$..StackResourceSummaries..PhysicalResourceId", - ] - ) - @markers.aws.validated - def test_cdk_sample(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.sqs_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - snapshot.add_transformer( - SortingTransformer("StackResourceSummaries", lambda x: x["LogicalResourceId"]), - priority=-1, - ) - - deploy = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cdk_sample_app.yaml" - ), - max_wait=120, - ) - - queue_url = deploy.outputs["QueueUrl"] - - queue_attr_policy = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url, AttributeNames=["Policy"] - ) - snapshot.match("queue_attr_policy", queue_attr_policy) - stack_resources = aws_client.cloudformation.list_stack_resources(StackName=deploy.stack_id) - snapshot.match("stack_resources", stack_resources) - - # physical resource id of the queue policy AWS::SQS::QueuePolicy - queue_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deploy.stack_id, LogicalResourceId="CdksampleQueuePolicyFA91005A" - ) - snapshot.add_transformer( - snapshot.transform.regex( - queue_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - # TODO: make sure phys id of the resource conforms to this format: stack-d98dcad5-CdksampleQueuePolicyFA91005A-1WYVV4PMCWOYI diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.snapshot.json deleted file mode 100644 index 2068d98220c4a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.snapshot.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": { - "recorded-date": "04-11-2022, 15:15:44", - "recorded-content": { - "queue_attr_policy": { - "Attributes": { - "Policy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com" - }, - "Action": "sqs:SendMessage", - "Resource": "arn::sqs::111111111111:", - "Condition": { - "ArnEquals": { - "aws:SourceArn": "arn::sns::111111111111:" - } - } - } - ] - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResourceSummaries": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleQueue3139C8CD", - "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SQS::Queue" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleQueueCdksampleStackCdksampleTopicCB3FDFDDC0BCF47C", - "PhysicalResourceId": "arn::sns::111111111111::", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Subscription" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleQueuePolicyFA91005A", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SQS::QueuePolicy" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleTopic7AD235A4", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Topic" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.validation.json deleted file mode 100644 index b627e80340018..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[10]": { - "last_validated_date": "2024-06-25T18:37:34+00:00" - }, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[11]": { - "last_validated_date": "2024-06-25T18:40:57+00:00" - }, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": { - "last_validated_date": "2024-06-25T18:44:21+00:00" - }, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": { - "last_validated_date": "2022-11-04T14:15:44+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py deleted file mode 100644 index 65f79e38e23a2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py +++ /dev/null @@ -1,137 +0,0 @@ -import logging -import os -import textwrap -import time -import uuid -from threading import Thread -from typing import TYPE_CHECKING - -import pytest -import requests - -from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -if TYPE_CHECKING: - try: - from mypy_boto3_ssm import SSMClient - except ImportError: - pass - -LOG = logging.getLogger(__name__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -PARAMETER_NAME = "wait-handle-url" - - -class SignalSuccess(Thread): - def __init__(self, client: "SSMClient"): - Thread.__init__(self) - self.client = client - self.session = requests.Session() - self.should_break = False - - def run(self): - while not self.should_break: - try: - LOG.debug("fetching parameter") - res = self.client.get_parameter(Name=PARAMETER_NAME) - url = res["Parameter"]["Value"] - LOG.info("signalling url %s", url) - - payload = { - "Status": "SUCCESS", - "Reason": "Wait condition reached", - "UniqueId": str(uuid.uuid4()), - "Data": "Application has completed configuration.", - } - r = self.session.put(url, json=payload) - LOG.debug("status from signalling: %s", r.status_code) - r.raise_for_status() - LOG.debug("status signalled") - break - except self.client.exceptions.ParameterNotFound: - LOG.warning("parameter not available, trying again") - time.sleep(5) - except Exception: - LOG.exception("got python exception") - raise - - def stop(self): - self.should_break = True - - -@markers.snapshot.skip_snapshot_verify(paths=["$..WaitConditionName"]) -@markers.aws.validated -def test_waitcondition(deploy_cfn_template, snapshot, aws_client): - """ - Complicated test, since we have a wait condition that must signal - a successful value to before the stack finishes. We use the - fact that CFn will deploy the SSM parameter before moving on - to the wait condition itself, so in a background thread we - try to set the value to success so that the stack will - deploy correctly. - """ - signal_thread = SignalSuccess(aws_client.ssm) - signal_thread.daemon = True - signal_thread.start() - - try: - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_waitcondition.yaml" - ), - parameters={"ParameterName": PARAMETER_NAME}, - ) - finally: - signal_thread.stop() - - wait_handle_id = stack.outputs["WaitHandleId"] - wait_condition_name = stack.outputs["WaitConditionRef"] - - # TODO: more stringent tests - assert wait_handle_id is not None - # snapshot.match("waithandle_ref", wait_handle_id) - snapshot.match("waitcondition_ref", {"WaitConditionName": wait_condition_name}) - - -@markers.aws.validated -def test_create_macro(deploy_cfn_template, create_lambda_function, snapshot, aws_client): - macro_name = f"macro-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(macro_name, "")) - - function_name = f"macro_lambda_{short_uid()}" - - handler_code = textwrap.dedent( - """ - def handler(event, context): - pass - """ - ) - - create_lambda_function( - func_name=function_name, - handler_file=handler_code, - runtime=Runtime.python3_12, - ) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/macro_resource.yml" - ) - assert os.path.isfile(template_path) - stack = deploy_cfn_template( - template_path=template_path, - parameters={ - "FunctionName": function_name, - "MacroName": macro_name, - }, - ) - - snapshot.match("stack-outputs", stack.outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.snapshot.json deleted file mode 100644 index 3c607af7f69ec..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.snapshot.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_waitconditionhandle": { - "recorded-date": "17-05-2023, 15:55:08", - "recorded-content": { - "waithandle_ref": "https://cloudformation-waitcondition-.s3..amazonaws.com/arn%3Aaws%3Acloudformation%3A%3A111111111111%3Astack/stack-03ad7786/c7b3de40-f4c2-11ed-b84b-0a57ddc705d2/WaitHandle?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230517T145504Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86399&X-Amz-Credential=AKIAYYGVRKE7CKDBHLUS%2F20230517%2F%2Fs3%2Faws4_request&X-Amz-Signature=3c79384f6647bd2c655ac78e6811ea0fff9b3a52a9bd751005d35f2a04f6533c" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_waitcondition": { - "recorded-date": "18-05-2023, 11:09:21", - "recorded-content": { - "waitcondition_ref": { - "WaitConditionName": "arn::cloudformation::111111111111:stack/stack-6cc1b50e/f9764ac0-f563-11ed-82f7-061d4a7b8a1e/WaitHandle" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_create_macro": { - "recorded-date": "09-06-2023, 14:30:11", - "recorded-content": { - "stack-outputs": { - "MacroRef": "" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.validation.json deleted file mode 100644 index 0aeaeefb84d2e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_create_macro": { - "last_validated_date": "2023-06-09T12:30:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_waitcondition": { - "last_validated_date": "2023-05-18T09:09:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py deleted file mode 100644 index d1acf12c8a064..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py +++ /dev/null @@ -1,118 +0,0 @@ -import json -import os -import re - -import pytest -from localstack_snapshot.snapshots.transformer import KeyValueBasedTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.snapshots.transformer_utility import PATTERN_ARN -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_alarm_creation(deploy_cfn_template, snapshot): - snapshot.add_transformer(snapshot.transform.resource_name()) - alarm_name = f"alarm-{short_uid()}" - - template = json.dumps( - { - "Resources": { - "Alarm": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "AlarmName": alarm_name, - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "Errors", - "Namespace": "AWS/Lambda", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - }, - } - }, - "Outputs": { - "AlarmName": {"Value": {"Ref": "Alarm"}}, - "AlarmArnFromAtt": {"Value": {"Fn::GetAtt": "Alarm.Arn"}}, - }, - } - ) - - outputs = deploy_cfn_template(template=template).outputs - snapshot.match("alarm_outputs", outputs) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..StateReason", - "$..StateReasonData", - "$..StateValue", - ] -) -def test_composite_alarm_creation(aws_client, deploy_cfn_template, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("Region", "region-name-full")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cw_composite_alarm.yml" - ), - ) - composite_alarm_name = stack.outputs["CompositeAlarmName"] - - def alarm_action_name_transformer(key: str, val: str): - if key == "AlarmActions" and isinstance(val, list) and len(val) == 1: - # we expect only one item in the list - value = val[0] - match = re.match(PATTERN_ARN, value) - if match: - res = match.groups()[-1] - if ":" in res: - return res.split(":")[-1] - return res - return None - - snapshot.add_transformer( - KeyValueBasedTransformer(alarm_action_name_transformer, "alarm-action-name"), - ) - response = aws_client.cloudwatch.describe_alarms( - AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] - ) - snapshot.match("composite_alarm", response["CompositeAlarms"]) - - metric_alarm_name = stack.outputs["MetricAlarmName"] - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[metric_alarm_name]) - snapshot.match("metric_alarm", response["MetricAlarms"]) - - stack.destroy() - response = aws_client.cloudwatch.describe_alarms( - AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] - ) - assert not response["CompositeAlarms"] - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[metric_alarm_name]) - assert not response["MetricAlarms"] - - -@markers.aws.validated -def test_alarm_ext_statistic(aws_client, deploy_cfn_template, snapshot): - snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cw_simple_alarm.yml" - ), - ) - alarm_name = stack.outputs["MetricAlarmName"] - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) - snapshot.match("simple_alarm", response["MetricAlarms"]) - - stack.destroy() - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) - assert not response["MetricAlarms"] diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.snapshot.json deleted file mode 100644 index 171d60de6e8ac..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.snapshot.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_creation": { - "recorded-date": "25-09-2023, 10:28:42", - "recorded-content": { - "alarm_outputs": { - "AlarmArnFromAtt": "arn::cloudwatch::111111111111:alarm:", - "AlarmName": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_composite_alarm_creation": { - "recorded-date": "16-07-2024, 10:41:22", - "recorded-content": { - "composite_alarm": [ - { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:HighResourceUsage", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "Indicates that the system resource usage is high while no known deployment is in progress", - "AlarmName": "HighResourceUsage", - "AlarmRule": "(ALARM(HighCPUUsage) OR ALARM(HighMemoryUsage))", - "InsufficientDataActions": [], - "OKActions": [], - "StateReason": "arn::cloudwatch::111111111111:alarm:HighResourceUsage was created and its alarm rule evaluates to OK", - "StateReasonData": { - "triggeringAlarms": [ - { - "arn": "arn::cloudwatch::111111111111:alarm:HighCPUUsage", - "state": { - "value": "INSUFFICIENT_DATA", - "timestamp": "date" - } - }, - { - "arn": "arn::cloudwatch::111111111111:alarm:HighMemoryUsage", - "state": { - "value": "INSUFFICIENT_DATA", - "timestamp": "date" - } - } - ] - }, - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK", - "StateTransitionedTimestamp": "timestamp" - } - ], - "metric_alarm": [ - { - "AlarmName": "HighMemoryUsage", - "AlarmArn": "arn::cloudwatch::111111111111:alarm:HighMemoryUsage", - "AlarmDescription": "Memory usage is high", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "ActionsEnabled": true, - "OKActions": [], - "AlarmActions": [], - "InsufficientDataActions": [], - "StateValue": "INSUFFICIENT_DATA", - "StateReason": "Unchecked: Initial alarm creation", - "StateUpdatedTimestamp": "timestamp", - "MetricName": "MemoryUsage", - "Namespace": "CustomNamespace", - "Statistic": "Average", - "Dimensions": [], - "Period": 60, - "EvaluationPeriods": 1, - "Threshold": 65.0, - "ComparisonOperator": "GreaterThanThreshold", - "TreatMissingData": "breaching", - "StateTransitionedTimestamp": "timestamp" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_no_statistic": { - "recorded-date": "27-11-2023, 10:08:09", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_ext_statistic": { - "recorded-date": "27-11-2023, 10:09:46", - "recorded-content": { - "simple_alarm": [ - { - "AlarmName": "", - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmDescription": "uses extended statistic", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "ActionsEnabled": true, - "OKActions": [], - "AlarmActions": [], - "InsufficientDataActions": [], - "StateValue": "INSUFFICIENT_DATA", - "StateReason": "Unchecked: Initial alarm creation", - "StateUpdatedTimestamp": "timestamp", - "MetricName": "Duration", - "Namespace": "", - "ExtendedStatistic": "p99", - "Dimensions": [ - { - "Name": "FunctionName", - "Value": "my-function" - } - ], - "Period": 300, - "Unit": "Count", - "EvaluationPeriods": 3, - "DatapointsToAlarm": 3, - "Threshold": 10.0, - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "TreatMissingData": "ignore", - "StateTransitionedTimestamp": "timestamp" - } - ] - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.validation.json deleted file mode 100644 index 9888ffd954a05..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_creation": { - "last_validated_date": "2023-09-25T08:28:42+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_ext_statistic": { - "last_validated_date": "2023-11-27T09:09:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_composite_alarm_creation": { - "last_validated_date": "2024-07-16T10:43:30+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py deleted file mode 100644 index 4a0b900772ef6..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py +++ /dev/null @@ -1,217 +0,0 @@ -import os - -import aws_cdk as cdk -import pytest -from aws_cdk import aws_dynamodb as dynamodb -from aws_cdk.aws_dynamodb import BillingMode - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.aws.arns import get_partition -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_deploy_stack_with_dynamodb_table(deploy_cfn_template, aws_client, region_name): - env = "Staging" - ddb_table_name_prefix = f"ddb-table-{short_uid()}" - ddb_table_name = f"{ddb_table_name_prefix}-{env}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/deploy_template_3.yaml" - ), - parameters={"tableName": ddb_table_name_prefix, "env": env}, - ) - - assert stack.outputs["Arn"].startswith(f"arn:{get_partition(region_name)}:dynamodb") - assert f"table/{ddb_table_name}" in stack.outputs["Arn"] - assert stack.outputs["Name"] == ddb_table_name - - rs = aws_client.dynamodb.list_tables() - assert ddb_table_name in rs["TableNames"] - - stack.destroy() - rs = aws_client.dynamodb.list_tables() - assert ddb_table_name not in rs["TableNames"] - - -@markers.aws.validated -def test_globalindex_read_write_provisioned_throughput_dynamodb_table( - deploy_cfn_template, aws_client -): - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/deploy_template_3.yaml" - ), - parameters={"tableName": "dynamodb", "env": "test"}, - ) - - response = aws_client.dynamodb.describe_table(TableName="dynamodb-test") - - if response["Table"]["ProvisionedThroughput"]: - throughput = response["Table"]["ProvisionedThroughput"] - assert isinstance(throughput["ReadCapacityUnits"], int) - assert isinstance(throughput["WriteCapacityUnits"], int) - - for global_index in response["Table"]["GlobalSecondaryIndexes"]: - index_provisioned = global_index["ProvisionedThroughput"] - test_read_capacity = index_provisioned["ReadCapacityUnits"] - test_write_capacity = index_provisioned["WriteCapacityUnits"] - assert isinstance(test_read_capacity, int) - assert isinstance(test_write_capacity, int) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - "$..Table.DeletionProtectionEnabled", - ] -) -def test_default_name_for_table(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_table_defaults.yml" - ), - ) - - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - list_tags = aws_client.dynamodb.list_tags_of_resource(ResourceArn=stack.outputs["TableArn"]) - snapshot.match("list_tags_of_resource", list_tags) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - "$..Table.DeletionProtectionEnabled", - ] -) -@pytest.mark.parametrize("billing_mode", ["PROVISIONED", "PAY_PER_REQUEST"]) -def test_billing_mode_as_conditional(deploy_cfn_template, snapshot, aws_client, billing_mode): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - snapshot.add_transformer( - snapshot.transform.key_value("LatestStreamLabel", "latest-stream-label") - ) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_billing_conditional.yml" - ), - parameters={"BillingModeParameter": billing_mode}, - ) - - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Table.DeletionProtectionEnabled", - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - ] -) -def test_global_table(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_global_table.yml" - ), - ) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - stack.destroy() - - with pytest.raises(Exception) as ex: - aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - - error_code = ex.value.response["Error"]["Code"] - assert "ResourceNotFoundException" == error_code - - -@markers.aws.validated -def test_ttl_cdk(aws_client, snapshot, infrastructure_setup): - infra = infrastructure_setup(namespace="DDBTableTTL") - stack = cdk.Stack(infra.cdk_app, "DDBStackTTL") - - table = dynamodb.Table( - stack, - id="Table", - billing_mode=BillingMode.PAY_PER_REQUEST, - partition_key=dynamodb.Attribute(name="id", type=dynamodb.AttributeType.STRING), - removal_policy=cdk.RemovalPolicy.RETAIN, - time_to_live_attribute="expire_at", - ) - - cdk.CfnOutput(stack, "TableName", value=table.table_name) - - with infra.provisioner() as prov: - outputs = prov.get_stack_outputs(stack_name="DDBStackTTL") - table_name = outputs["TableName"] - table = aws_client.dynamodb.describe_time_to_live(TableName=table_name) - snapshot.match("table", table) - - -@markers.aws.validated -# We return field bellow, while AWS doesn't return them -@markers.snapshot.skip_snapshot_verify( - [ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - ] -) -def test_table_with_ttl_and_sse(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_table_sse_enabled.yml" - ), - ) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - snapshot.add_transformer(snapshot.transform.key_value("KMSMasterKeyArn", "kms-arn")) - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - -@markers.aws.validated -# We return the fields bellow, while AWS doesn't return them -@markers.snapshot.skip_snapshot_verify( - [ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - ] -) -def test_global_table_with_ttl_and_sse(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/dynamodb_global_table_sse_enabled.yml", - ), - ) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - snapshot.add_transformer(snapshot.transform.key_value("KMSMasterKeyArn", "kms-arn")) - - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.snapshot.json deleted file mode 100644 index 88af39a8953e1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.snapshot.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_default_name_for_table": { - "recorded-date": "28-08-2023, 12:34:19", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "keyName", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "keyName", - "KeyType": "HASH" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5 - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_of_resource": { - "Tags": [ - { - "Key": "TagKey1", - "Value": "TagValue1" - }, - { - "Key": "TagKey2", - "Value": "TagValue2" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": { - "recorded-date": "28-08-2023, 12:34:41", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "id", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "LatestStreamArn": "arn::dynamodb::111111111111:table//stream/", - "LatestStreamLabel": "", - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5 - }, - "StreamSpecification": { - "StreamEnabled": true, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": { - "recorded-date": "28-08-2023, 12:35:02", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "id", - "AttributeType": "S" - } - ], - "BillingModeSummary": { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "datetime" - }, - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "LatestStreamArn": "arn::dynamodb::111111111111:table//stream/", - "LatestStreamLabel": "", - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - }, - "StreamSpecification": { - "StreamEnabled": true, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table": { - "recorded-date": "01-12-2023, 12:54:13", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "keyName", - "AttributeType": "S" - } - ], - "BillingModeSummary": { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "datetime" - }, - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "keyName", - "KeyType": "HASH" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_ttl_cdk": { - "recorded-date": "14-02-2024, 13:29:07", - "recorded-content": { - "table": { - "TimeToLiveDescription": { - "AttributeName": "expire_at", - "TimeToLiveStatus": "ENABLED" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_table_with_ttl_and_sse": { - "recorded-date": "12-03-2024, 15:42:18", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "pk", - "AttributeType": "S" - }, - { - "AttributeName": "sk", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "pk", - "KeyType": "HASH" - }, - { - "AttributeName": "sk", - "KeyType": "RANGE" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 1, - "WriteCapacityUnits": 1 - }, - "SSEDescription": { - "KMSMasterKeyArn": "", - "SSEType": "KMS", - "Status": "ENABLED" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": { - "recorded-date": "12-03-2024, 15:44:36", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "gsi1pk", - "AttributeType": "S" - }, - { - "AttributeName": "gsi1sk", - "AttributeType": "S" - }, - { - "AttributeName": "pk", - "AttributeType": "S" - }, - { - "AttributeName": "sk", - "AttributeType": "S" - } - ], - "BillingModeSummary": { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "datetime" - }, - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "GlobalSecondaryIndexes": [ - { - "IndexArn": "arn::dynamodb::111111111111:table//index/GSI1", - "IndexName": "GSI1", - "IndexSizeBytes": 0, - "IndexStatus": "ACTIVE", - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "gsi1pk", - "KeyType": "HASH" - }, - { - "AttributeName": "gsi1sk", - "KeyType": "RANGE" - } - ], - "Projection": { - "ProjectionType": "ALL" - }, - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - } - } - ], - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "pk", - "KeyType": "HASH" - }, - { - "AttributeName": "sk", - "KeyType": "RANGE" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - }, - "SSEDescription": { - "KMSMasterKeyArn": "", - "SSEType": "KMS", - "Status": "ENABLED" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableClassSummary": { - "TableClass": "STANDARD" - }, - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.validation.json deleted file mode 100644 index a93ac64a42317..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": { - "last_validated_date": "2023-08-28T10:35:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": { - "last_validated_date": "2023-08-28T10:34:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_default_name_for_table": { - "last_validated_date": "2023-08-28T10:34:19+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table": { - "last_validated_date": "2023-12-01T11:54:13+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": { - "last_validated_date": "2024-03-12T15:44:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_table_with_ttl_and_sse": { - "last_validated_date": "2024-03-12T15:42:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_ttl_cdk": { - "last_validated_date": "2024-02-14T13:29:07+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py deleted file mode 100644 index a31bf40d39240..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py +++ /dev/null @@ -1,376 +0,0 @@ -import os - -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -THIS_FOLDER = os.path.dirname(__file__) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..PropagatingVgws"]) -def test_simple_route_table_creation_without_vpc(deploy_cfn_template, aws_client, snapshot): - ec2 = aws_client.ec2 - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_FOLDER, "../../../../../templates/ec2_route_table_isolated.yaml" - ), - ) - - route_table_id = stack.outputs["RouteTableId"] - route_table = ec2.describe_route_tables(RouteTableIds=[route_table_id])["RouteTables"][0] - - tags = route_table.pop("Tags") - tags_dict = {tag["Key"]: tag["Value"] for tag in tags if "aws:cloudformation" not in tag["Key"]} - snapshot.match("tags", tags_dict) - - snapshot.match("route_table", route_table) - snapshot.add_transformer(snapshot.transform.key_value("VpcId", "vpc-id")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId", "vpc-id")) - - stack.destroy() - with pytest.raises(ec2.exceptions.ClientError): - ec2.describe_route_tables(RouteTableIds=[route_table_id]) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..PropagatingVgws"]) -def test_simple_route_table_creation(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_FOLDER, "../../../../../templates/ec2_route_table_simple.yaml" - ) - ) - - route_table_id = stack.outputs["RouteTableId"] - ec2 = aws_client.ec2 - route_table = ec2.describe_route_tables(RouteTableIds=[route_table_id])["RouteTables"][0] - - tags = route_table.pop("Tags") - tags_dict = {tag["Key"]: tag["Value"] for tag in tags if "aws:cloudformation" not in tag["Key"]} - snapshot.match("tags", tags_dict) - - snapshot.match("route_table", route_table) - snapshot.add_transformer(snapshot.transform.key_value("VpcId", "vpc-id")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId", "vpc-id")) - - stack.destroy() - with pytest.raises(ec2.exceptions.ClientError): - ec2.describe_route_tables(RouteTableIds=[route_table_id]) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_vpc_creates_default_sg(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/ec2_vpc_default_sg.yaml") - ) - - vpc_id = result.outputs.get("VpcId") - default_sg = result.outputs.get("VpcDefaultSG") - default_acl = result.outputs.get("VpcDefaultAcl") - - assert vpc_id - assert default_sg - assert default_acl - - security_groups = aws_client.ec2.describe_security_groups(GroupIds=[default_sg])[ - "SecurityGroups" - ] - assert security_groups[0]["VpcId"] == vpc_id - - acls = aws_client.ec2.describe_network_acls(NetworkAclIds=[default_acl])["NetworkAcls"] - assert acls[0]["VpcId"] == vpc_id - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_cfn_with_multiple_route_tables(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/template36.yaml"), - max_wait=180, - ) - vpc_id = result.outputs["VPC"] - - resp = aws_client.ec2.describe_route_tables(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]) - - # 4 route tables being created (validated against AWS): 3 in template + 1 default = 4 - assert len(resp["RouteTables"]) == 4 - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..PropagatingVgws", "$..Tags", "$..Tags..Key", "$..Tags..Value"] -) -def test_cfn_with_multiple_route_table_associations(deploy_cfn_template, aws_client, snapshot): - # TODO: stack does not deploy to AWS - stack = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/template37.yaml") - ) - route_table_id = stack.outputs["RouteTable"] - route_table = aws_client.ec2.describe_route_tables( - Filters=[{"Name": "route-table-id", "Values": [route_table_id]}] - )["RouteTables"][0] - - snapshot.match("route_table", route_table) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableAssociationId")) - snapshot.add_transformer(snapshot.transform.key_value("SubnetId")) - snapshot.add_transformer(snapshot.transform.key_value("VpcId")) - - -@pytest.mark.skip(reason="CFNV2:Describe") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..DriftInformation", "$..Metadata"]) -def test_internet_gateway_ref_and_attr(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/internet_gateway.yml") - ) - - response = aws_client.cloudformation.describe_stack_resource( - StackName=stack.stack_name, LogicalResourceId="Gateway" - ) - - snapshot.add_transformer(snapshot.transform.key_value("RefAttachment", "internet-gateway-ref")) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - snapshot.match("outputs", stack.outputs) - snapshot.match("description", response["StackResourceDetail"]) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..OwnerId"]) -def test_dhcp_options(aws_client, deploy_cfn_template, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/dhcp_options.yml") - ) - - response = aws_client.ec2.describe_dhcp_options( - DhcpOptionsIds=[stack.outputs["RefDhcpOptions"]] - ) - snapshot.add_transformer(snapshot.transform.key_value("DhcpOptionsId", "dhcp-options-id")) - snapshot.add_transformer(SortingTransformer("DhcpConfigurations", lambda x: x["Key"])) - snapshot.match("description", response["DhcpOptions"][0]) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Tags", - "$..Options.AssociationDefaultRouteTableId", - "$..Options.PropagationDefaultRouteTableId", - "$..Options.TransitGatewayCidrBlocks", # an empty list returned by Moto but not by AWS - "$..Options.SecurityGroupReferencingSupport", # not supported by Moto - ] -) -def test_transit_gateway_attachment(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_FOLDER, "../../../../../templates/transit_gateway_attachment.yml" - ) - ) - - gateway_description = aws_client.ec2.describe_transit_gateways( - TransitGatewayIds=[stack.outputs["TransitGateway"]] - ) - attachment_description = aws_client.ec2.describe_transit_gateway_attachments( - TransitGatewayAttachmentIds=[stack.outputs["Attachment"]] - ) - - snapshot.add_transformer(snapshot.transform.key_value("TransitGatewayRouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("AssociationDefaultRouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("PropagatioDefaultRouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("ResourceId")) - snapshot.add_transformer(snapshot.transform.key_value("TransitGatewayAttachmentId")) - snapshot.add_transformer(snapshot.transform.key_value("TransitGatewayId")) - - snapshot.match("attachment", attachment_description["TransitGatewayAttachments"][0]) - snapshot.match("gateway", gateway_description["TransitGateways"][0]) - - stack.destroy() - - descriptions = aws_client.ec2.describe_transit_gateways( - TransitGatewayIds=[stack.outputs["TransitGateway"]] - ) - if is_aws_cloud(): - # aws changes the state to deleted - descriptions = descriptions["TransitGateways"][0] - assert descriptions["State"] == "deleted" - else: - # moto directly deletes the transit gateway - transit_gateways_ids = [ - tgateway["TransitGatewayId"] for tgateway in descriptions["TransitGateways"] - ] - assert stack.outputs["TransitGateway"] not in transit_gateways_ids - - attachment_description = aws_client.ec2.describe_transit_gateway_attachments( - TransitGatewayAttachmentIds=[stack.outputs["Attachment"]] - )["TransitGatewayAttachments"] - assert attachment_description[0]["State"] == "deleted" - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..RouteTables..PropagatingVgws", "$..RouteTables..Tags"] -) -def test_vpc_with_route_table(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template33.yaml" - ) - ) - - route_id = stack.outputs["RouteTableId"] - response = aws_client.ec2.describe_route_tables(RouteTableIds=[route_id]) - - # Convert tags to dictionary for easier comparison - response["RouteTables"][0]["Tags"] = { - tag["Key"]: tag["Value"] for tag in response["RouteTables"][0]["Tags"] - } - - snapshot.match("route_table", response) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("VpcId")) - - stack.destroy() - - with pytest.raises(aws_client.ec2.exceptions.ClientError): - aws_client.ec2.describe_route_tables(RouteTableIds=[route_id]) - - -@pytest.mark.skip(reason="update doesn't change value for instancetype") -@markers.aws.validated -def test_cfn_update_ec2_instance_type(deploy_cfn_template, aws_client, cleanups): - if aws_client.cloudformation.meta.region_name not in [ - "ap-northeast-1", - "eu-central-1", - "eu-south-1", - "eu-west-1", - "eu-west-2", - "us-east-1", - ]: - pytest.skip() - - key_name = f"testkey-{short_uid()}" - aws_client.ec2.create_key_pair(KeyName=key_name) - cleanups.append(lambda: aws_client.ec2.delete_key_pair(KeyName=key_name)) - - # get alpine image id - if is_aws_cloud(): - images = aws_client.ec2.describe_images( - Filters=[ - {"Name": "name", "Values": ["alpine-3.19.0-x86_64-bios-*"]}, - {"Name": "state", "Values": ["available"]}, - ] - )["Images"] - image_id = images[0]["ImageId"] - else: - image_id = "ami-0a63f96a6a8d4d2c5" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_instance.yml" - ), - parameters={"KeyName": key_name, "InstanceType": "t2.nano", "ImageId": image_id}, - ) - - instance_id = stack.outputs["InstanceId"] - instance = aws_client.ec2.describe_instances(InstanceIds=[instance_id])["Reservations"][0][ - "Instances" - ][0] - assert instance["InstanceType"] == "t2.nano" - - deploy_cfn_template( - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_instance.yml" - ), - parameters={"KeyName": key_name, "InstanceType": "t2.medium", "ImageId": image_id}, - is_update=True, - ) - - instance = aws_client.ec2.describe_instances(InstanceIds=[instance_id])["Reservations"][0][ - "Instances" - ][0] - assert instance["InstanceType"] == "t2.medium" - - -@markers.aws.validated -def test_ec2_security_group_id_with_vpc(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_vpc_securitygroup.yml" - ), - ) - - ec2_client = aws_client.ec2 - with_vpcid_sg_group_id = ec2_client.describe_security_groups( - Filters=[ - { - "Name": "group-id", - "Values": [stack.outputs["SGWithVpcIdGroupId"]], - }, - ] - )["SecurityGroups"][0] - without_vpcid_sg_group_id = ec2_client.describe_security_groups( - Filters=[ - { - "Name": "group-id", - "Values": [stack.outputs["SGWithoutVpcIdGroupId"]], - }, - ] - )["SecurityGroups"][0] - - snapshot.add_transformer( - snapshot.transform.regex(with_vpcid_sg_group_id["GroupId"], "") - ) - snapshot.add_transformer( - snapshot.transform.regex(without_vpcid_sg_group_id["GroupId"], "") - ) - snapshot.add_transformer( - snapshot.transform.regex( - without_vpcid_sg_group_id["GroupName"], "" - ) - ) - snapshot.match("references", stack.outputs) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - # fingerprint algorithm is different but presence is ensured by CFn output implementation - "$..ImportedKeyPairFingerprint", - ], -) -def test_keypair_create_import(deploy_cfn_template, snapshot, aws_client): - imported_key_name = f"imported-key-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(imported_key_name, "")) - generated_key_name = f"generated-key-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(generated_key_name, "")) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_import_keypair.yaml" - ), - parameters={"ImportedKeyName": imported_key_name, "GeneratedKeyName": generated_key_name}, - ) - - outputs = stack.outputs - # for the generated key pair, use the EC2 API to get the fingerprint and snapshot the value - key_res = aws_client.ec2.describe_key_pairs(KeyNames=[outputs["GeneratedKeyPairName"]])[ - "KeyPairs" - ][0] - snapshot.add_transformer(snapshot.transform.regex(key_res["KeyFingerprint"], "")) - - snapshot.match("outputs", outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.snapshot.json deleted file mode 100644 index 4b71ac67803dc..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.snapshot.json +++ /dev/null @@ -1,303 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_internet_gateway_ref_and_attr": { - "recorded-date": "13-02-2023, 17:13:41", - "recorded-content": { - "outputs": { - "IdAttachment": "", - "RefAttachment": "" - }, - "description": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "Gateway", - "Metadata": {}, - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::EC2::InternetGateway", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_dhcp_options": { - "recorded-date": "19-10-2023, 14:51:28", - "recorded-content": { - "description": { - "DhcpConfigurations": [ - { - "Key": "domain-name", - "Values": [ - { - "Value": "example.com" - } - ] - }, - { - "Key": "domain-name-servers", - "Values": [ - { - "Value": "AmazonProvidedDNS" - } - ] - }, - { - "Key": "netbios-name-servers", - "Values": [ - { - "Value": "10.2.5.1" - } - ] - }, - { - "Key": "netbios-node-type", - "Values": [ - { - "Value": "2" - } - ] - }, - { - "Key": "ntp-servers", - "Values": [ - { - "Value": "10.2.5.1" - } - ] - } - ], - "DhcpOptionsId": "", - "OwnerId": "111111111111", - "Tags": [ - { - "Key": "project", - "Value": "123" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "myDhcpOptions" - }, - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-698b113f" - }, - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-698b113f/d892a0f0-6eb8-11ee-ab19-0a5372e03565" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_transit_gateway_attachment": { - "recorded-date": "08-04-2025, 10:51:02", - "recorded-content": { - "attachment": { - "Association": { - "State": "associated", - "TransitGatewayRouteTableId": "" - }, - "CreationTime": "datetime", - "ResourceId": "", - "ResourceOwnerId": "111111111111", - "ResourceType": "vpc", - "State": "available", - "Tags": [ - { - "Key": "Name", - "Value": "example-tag" - } - ], - "TransitGatewayAttachmentId": "", - "TransitGatewayId": "", - "TransitGatewayOwnerId": "111111111111" - }, - "gateway": { - "CreationTime": "datetime", - "Description": "TGW Route Integration Test", - "Options": { - "AmazonSideAsn": 65000, - "AssociationDefaultRouteTableId": "", - "AutoAcceptSharedAttachments": "disable", - "DefaultRouteTableAssociation": "enable", - "DefaultRouteTablePropagation": "enable", - "DnsSupport": "enable", - "MulticastSupport": "disable", - "PropagationDefaultRouteTableId": "", - "SecurityGroupReferencingSupport": "disable", - "VpnEcmpSupport": "enable" - }, - "OwnerId": "111111111111", - "State": "available", - "Tags": [ - { - "Key": "Application", - "Value": "arn::cloudformation::111111111111:stack/stack-31597705/521e4e40-ecce-11ee-806c-0affc1ff51e7" - } - ], - "TransitGatewayArn": "arn::ec2::111111111111:transit-gateway/", - "TransitGatewayId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_with_route_table": { - "recorded-date": "19-06-2024, 16:48:31", - "recorded-content": { - "route_table": { - "RouteTables": [ - { - "Associations": [], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "100.0.0.0/20", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "Tags": { - "aws:cloudformation:logical-id": "RouteTable", - "aws:cloudformation:stack-id": "", - "aws:cloudformation:stack-name": "", - "env": "production" - }, - "VpcId": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": { - "recorded-date": "01-07-2024, 20:10:52", - "recorded-content": { - "tags": { - "Name": "Suspicious Route Table" - }, - "route_table": { - "Associations": [], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "10.0.0.0/16", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "VpcId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation": { - "recorded-date": "01-07-2024, 20:13:48", - "recorded-content": { - "tags": { - "Name": "Suspicious Route table" - }, - "route_table": { - "Associations": [], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "10.0.0.0/16", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "VpcId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": { - "recorded-date": "02-07-2024, 15:29:41", - "recorded-content": { - "route_table": { - "Associations": [ - { - "AssociationState": { - "State": "associated" - }, - "Main": false, - "RouteTableAssociationId": "", - "RouteTableId": "", - "SubnetId": "" - }, - { - "AssociationState": { - "State": "associated" - }, - "Main": false, - "RouteTableAssociationId": "", - "RouteTableId": "", - "SubnetId": "" - } - ], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "100.0.0.0/20", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "Tags": [ - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-2264231d/d12f4090-3887-11ef-ba9f-0e78e2279133" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "RouteTable" - }, - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-2264231d" - }, - { - "Key": "env", - "Value": "production" - } - ], - "VpcId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": { - "recorded-date": "19-07-2024, 15:53:16", - "recorded-content": { - "references": { - "SGWithVpcIdGroupId": "", - "SGWithVpcIdRef": "", - "SGWithoutVpcIdGroupId": "", - "SGWithoutVpcIdRef": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_keypair_create_import": { - "recorded-date": "12-08-2024, 21:51:36", - "recorded-content": { - "outputs": { - "GeneratedKeyPairFingerprint": "", - "GeneratedKeyPairName": "", - "ImportedKeyPairFingerprint": "4LmcYnyBOqlloHZ5TKAxfa8BgMK2wL6WeOOTvXVdhmw=", - "ImportedKeyPairName": "" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.validation.json deleted file mode 100644 index 9c06cf509f1a5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.validation.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_update_ec2_instance_type": { - "last_validated_date": "2024-06-19T19:56:42+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": { - "last_validated_date": "2024-07-02T15:29:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_dhcp_options": { - "last_validated_date": "2023-10-19T12:51:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": { - "last_validated_date": "2024-07-19T15:53:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_internet_gateway_ref_and_attr": { - "last_validated_date": "2023-02-13T16:13:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_keypair_create_import": { - "last_validated_date": "2024-08-12T21:51:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation": { - "last_validated_date": "2024-07-01T20:13:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": { - "last_validated_date": "2024-07-01T20:10:52+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_transit_gateway_attachment": { - "last_validated_date": "2025-04-08T10:51:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_creates_default_sg": { - "last_validated_date": "2024-04-01T11:21:54+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_with_route_table": { - "last_validated_date": "2024-06-19T16:48:31+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py deleted file mode 100644 index a3619407f9ea5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py +++ /dev/null @@ -1,54 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.skip_offline -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..DomainStatus.AdvancedSecurityOptions.AnonymousAuthEnabled", - "$..DomainStatus.AutoTuneOptions.State", - "$..DomainStatus.ChangeProgressDetails", - "$..DomainStatus.DomainProcessingStatus", - "$..DomainStatus.EBSOptions.VolumeSize", - "$..DomainStatus.ElasticsearchClusterConfig.DedicatedMasterCount", - "$..DomainStatus.ElasticsearchClusterConfig.InstanceCount", - "$..DomainStatus.ElasticsearchClusterConfig.ZoneAwarenessConfig", - "$..DomainStatus.ElasticsearchClusterConfig.ZoneAwarenessEnabled", - "$..DomainStatus.Endpoint", - "$..DomainStatus.ModifyingProperties", - "$..DomainStatus.Processing", - "$..DomainStatus.ServiceSoftwareOptions.CurrentVersion", - ] -) -def test_cfn_handle_elasticsearch_domain(deploy_cfn_template, aws_client, snapshot): - domain_name = f"es-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/elasticsearch_domain.yml" - ) - - deploy_cfn_template(template_path=template_path, parameters={"DomainName": domain_name}) - - rs = aws_client.es.describe_elasticsearch_domain(DomainName=domain_name) - status = rs["DomainStatus"] - snapshot.match("domain", rs) - - tags = aws_client.es.list_tags(ARN=status["ARN"])["TagList"] - snapshot.match("tags", tags) - - snapshot.add_transformer(snapshot.transform.key_value("DomainName")) - snapshot.add_transformer(snapshot.transform.key_value("Endpoint")) - snapshot.add_transformer(snapshot.transform.key_value("TLSSecurityPolicy")) - snapshot.add_transformer(snapshot.transform.key_value("CurrentVersion")) - snapshot.add_transformer(snapshot.transform.key_value("Description")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.snapshot.json deleted file mode 100644 index 427b5a9768e3c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.snapshot.json +++ /dev/null @@ -1,312 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": { - "recorded-date": "02-07-2024, 17:30:21", - "recorded-content": { - "domain": { - "DomainStatus": { - "ARN": "arn::es::111111111111:domain/", - "AccessPolicies": "", - "AdvancedOptions": { - "override_main_response_version": "false", - "rest.action.multi.allow_explicit_index": "true" - }, - "AdvancedSecurityOptions": { - "AnonymousAuthEnabled": false, - "Enabled": false, - "InternalUserDatabaseEnabled": false - }, - "AutoTuneOptions": { - "State": "ENABLED" - }, - "ChangeProgressDetails": { - "ChangeId": "", - "ConfigChangeStatus": "ApplyingChanges", - "InitiatedBy": "CUSTOMER", - "LastUpdatedTime": "datetime", - "StartTime": "datetime" - }, - "CognitoOptions": { - "Enabled": false - }, - "Created": true, - "Deleted": false, - "DomainEndpointOptions": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "" - }, - "DomainId": "111111111111/", - "DomainName": "", - "DomainProcessingStatus": "Creating", - "EBSOptions": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 20, - "VolumeType": "gp2" - }, - "ElasticsearchClusterConfig": { - "ColdStorageOptions": { - "Enabled": false - }, - "DedicatedMasterCount": 3, - "DedicatedMasterEnabled": true, - "DedicatedMasterType": "m3.medium.elasticsearch", - "InstanceCount": 2, - "InstanceType": "m3.medium.elasticsearch", - "WarmEnabled": false, - "ZoneAwarenessConfig": { - "AvailabilityZoneCount": 2 - }, - "ZoneAwarenessEnabled": true - }, - "ElasticsearchVersion": "7.10", - "EncryptionAtRestOptions": { - "Enabled": false - }, - "Endpoint": "search--4kyrgtn4a3gwrja6k4o7nvcrha..es.amazonaws.com", - "ModifyingProperties": [ - { - "ActiveValue": "", - "Name": "AdvancedOptions", - "PendingValue": { - "override_main_response_version": "false", - "rest.action.multi.allow_explicit_index": "true" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.AnonymousAuthDisableDate", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.AnonymousAuthEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.InternalUserDatabaseEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.JWTOptions", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.MasterUserOptions", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.SAMLOptions", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.ColdStorageOptions", - "PendingValue": { - "Enabled": false - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.DedicatedMasterCount", - "PendingValue": "3", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.DedicatedMasterEnabled", - "PendingValue": "true", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.DedicatedMasterType", - "PendingValue": "m3.medium.elasticsearch", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.InstanceCount", - "PendingValue": "2", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.InstanceType", - "PendingValue": "m3.medium.elasticsearch", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.MultiAZWithStandbyEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmCount", - "PendingValue": "", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmStorage", - "PendingValue": "", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmType", - "PendingValue": "", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.ZoneAwarenessEnabled", - "PendingValue": "true", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchVersion", - "PendingValue": "7.10", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "IPAddressType", - "PendingValue": "ipv4", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "TAGS", - "PendingValue": { - "k1": "v1", - "k2": "v2" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "DomainEndpointOptions", - "PendingValue": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "EBSOptions", - "PendingValue": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 20, - "VolumeType": "gp2" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "EncryptionAtRestOptions", - "PendingValue": { - "Enabled": false - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "NodeToNodeEncryptionOptions", - "PendingValue": { - "Enabled": false - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "OffPeakWindowOptions", - "PendingValue": { - "Enabled": true, - "OffPeakWindow": { - "WindowStartTime": { - "Hours": 2, - "Minutes": 0 - } - } - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "SnapshotOptions", - "PendingValue": { - "AutomatedSnapshotStartHour": 0 - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "SoftwareUpdateOptions", - "PendingValue": { - "AutoSoftwareUpdateEnabled": false - }, - "ValueType": "STRINGIFIED_JSON" - } - ], - "NodeToNodeEncryptionOptions": { - "Enabled": false - }, - "Processing": false, - "ServiceSoftwareOptions": { - "AutomatedUpdateDate": "datetime", - "Cancellable": false, - "CurrentVersion": "", - "Description": "", - "NewVersion": "", - "OptionalDeployment": true, - "UpdateAvailable": false, - "UpdateStatus": "COMPLETED" - }, - "SnapshotOptions": { - "AutomatedSnapshotStartHour": 0 - }, - "UpgradeProcessing": false - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "tags": [ - { - "Key": "k1", - "Value": "v1" - }, - { - "Key": "k2", - "Value": "v2" - } - ] - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.validation.json deleted file mode 100644 index 879e604d1082c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": { - "last_validated_date": "2024-07-02T17:30:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py deleted file mode 100644 index 59f63ff949f12..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py +++ /dev/null @@ -1,248 +0,0 @@ -import json -import logging -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - -LOG = logging.getLogger(__name__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.skip( - reason="CFNV2:Destroy resource name conflict with another test case resource in this suite" -) -@markers.aws.validated -def test_cfn_event_api_destination_resource(deploy_cfn_template, region_name, aws_client): - def _assert(expected_len): - rs = aws_client.events.list_event_buses() - event_buses = [eb for eb in rs["EventBuses"] if eb["Name"] == "my-test-bus"] - assert len(event_buses) == expected_len - rs = aws_client.events.list_connections() - connections = [con for con in rs["Connections"] if con["Name"] == "my-test-conn"] - assert len(connections) == expected_len - rs = aws_client.events.list_api_destinations() - api_destinations = [ - ad for ad in rs["ApiDestinations"] if ad["Name"] == "my-test-destination" - ] - assert len(api_destinations) == expected_len - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_apidestination.yml" - ), - parameters={ - "Region": region_name, - }, - ) - _assert(1) - - stack.destroy() - _assert(0) - - -@pytest.mark.skip(reason="CFNV2:Describe") -@markers.aws.validated -def test_eventbus_policies(deploy_cfn_template, aws_client): - event_bus_name = f"event-bus-{short_uid()}" - - stack_response = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/eventbridge_policy.yaml" - ), - parameters={"EventBusName": event_bus_name}, - ) - - describe_response = aws_client.events.describe_event_bus(Name=event_bus_name) - policy = json.loads(describe_response["Policy"]) - assert len(policy["Statement"]) == 2 - - # verify physical resource ID creation - pol1_description = aws_client.cloudformation.describe_stack_resource( - StackName=stack_response.stack_name, LogicalResourceId="eventPolicy" - ) - pol2_description = aws_client.cloudformation.describe_stack_resource( - StackName=stack_response.stack_name, LogicalResourceId="eventPolicy2" - ) - assert ( - pol1_description["StackResourceDetail"]["PhysicalResourceId"] - != pol2_description["StackResourceDetail"]["PhysicalResourceId"] - ) - - deploy_cfn_template( - is_update=True, - stack_name=stack_response.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/eventbridge_policy_singlepolicy.yaml", - ), - parameters={"EventBusName": event_bus_name}, - ) - - describe_response = aws_client.events.describe_event_bus(Name=event_bus_name) - policy = json.loads(describe_response["Policy"]) - assert len(policy["Statement"]) == 1 - - -@markers.aws.validated -def test_eventbus_policy_statement(deploy_cfn_template, aws_client): - event_bus_name = f"event-bus-{short_uid()}" - statement_id = f"statement-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/eventbridge_policy_statement.yaml" - ), - parameters={"EventBusName": event_bus_name, "StatementId": statement_id}, - ) - - describe_response = aws_client.events.describe_event_bus(Name=event_bus_name) - policy = json.loads(describe_response["Policy"]) - assert policy["Version"] == "2012-10-17" - assert len(policy["Statement"]) == 1 - statement = policy["Statement"][0] - assert statement["Sid"] == statement_id - assert statement["Action"] == "events:PutEvents" - assert statement["Principal"] == "*" - assert statement["Effect"] == "Allow" - assert event_bus_name in statement["Resource"] - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_event_rule_to_logs(deploy_cfn_template, aws_client): - event_rule_name = f"event-rule-{short_uid()}" - log_group_name = f"log-group-{short_uid()}" - event_bus_name = f"bus-{short_uid()}" - resource_policy_name = f"policy-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_loggroup.yaml" - ), - parameters={ - "EventRuleName": event_rule_name, - "LogGroupName": log_group_name, - "EventBusName": event_bus_name, - "PolicyName": resource_policy_name, - }, - ) - - log_groups = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name)["logGroups"] - log_group_names = [lg["logGroupName"] for lg in log_groups] - assert log_group_name in log_group_names - - message_token = f"test-message-{short_uid()}" - resp = aws_client.events.put_events( - Entries=[ - { - "Source": "unittest", - "Resources": [], - "DetailType": "ls-detail-type", - "Detail": json.dumps({"messagetoken": message_token}), - "EventBusName": event_bus_name, - } - ] - ) - assert len(resp["Entries"]) == 1 - - wait_until( - lambda: len(aws_client.logs.describe_log_streams(logGroupName=log_group_name)["logStreams"]) - > 0, - 1.0, - 5, - "linear", - ) - log_streams = aws_client.logs.describe_log_streams(logGroupName=log_group_name)["logStreams"] - log_events = aws_client.logs.get_log_events( - logGroupName=log_group_name, logStreamName=log_streams[0]["logStreamName"] - ) - assert message_token in log_events["events"][0]["message"] - - -@markers.aws.validated -def test_event_rule_creation_without_target(deploy_cfn_template, aws_client, snapshot): - event_rule_name = f"event-rule-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(event_rule_name, "event-rule-name")) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_rule_without_targets.yaml" - ), - parameters={"EventRuleName": event_rule_name}, - ) - - response = aws_client.events.describe_rule( - Name=event_rule_name, - ) - snapshot.match("describe_rule", response) - - -@markers.aws.validated -def test_cfn_event_bus_resource(deploy_cfn_template, aws_client): - def _assert(expected_len): - rs = aws_client.events.list_event_buses() - event_buses = [eb for eb in rs["EventBuses"] if eb["Name"] == "my-test-bus"] - assert len(event_buses) == expected_len - rs = aws_client.events.list_connections() - connections = [con for con in rs["Connections"] if con["Name"] == "my-test-conn"] - assert len(connections) == expected_len - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template31.yaml" - ) - ) - _assert(1) - - stack.destroy() - _assert(0) - - -@markers.aws.validated -def test_rule_properties(deploy_cfn_template, aws_client, snapshot): - event_bus_name = f"events-{short_uid()}" - rule_name = f"rule-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(event_bus_name, "")) - snapshot.add_transformer(snapshot.transform.regex(rule_name, "")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_rule_properties.yaml" - ), - parameters={"EventBusName": event_bus_name, "RuleName": rule_name}, - ) - - rule_id = stack.outputs["RuleWithoutNameArn"].rsplit("/")[-1] - snapshot.add_transformer(snapshot.transform.regex(rule_id, "")) - - without_bus_id = stack.outputs["RuleWithoutBusArn"].rsplit("/")[-1] - snapshot.add_transformer(snapshot.transform.regex(without_bus_id, "")) - - snapshot.match("outputs", stack.outputs) - - -@markers.aws.validated -def test_rule_pattern_transformation(aws_client, deploy_cfn_template, snapshot): - """ - The CFn provider for a rule applies a transformation to some properties. Extend this test as more properties or - situations arise. - """ - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_rule_pattern.yml" - ), - ) - - rule = aws_client.events.describe_rule(Name=stack.outputs["RuleName"]) - snapshot.match("rule", rule) - snapshot.add_transformer(snapshot.transform.key_value("Name")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.snapshot.json deleted file mode 100644 index 9d0f00f3548f7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.snapshot.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_properties": { - "recorded-date": "01-12-2023, 15:03:52", - "recorded-content": { - "outputs": { - "RuleWithNameArn": "arn::events::111111111111:rule//", - "RuleWithNameRef": "|", - "RuleWithoutBusArn": "arn::events::111111111111:rule/", - "RuleWithoutBusRef": "", - "RuleWithoutNameArn": "arn::events::111111111111:rule//", - "RuleWithoutNameRef": "|" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_pattern_transformation": { - "recorded-date": "08-11-2024, 15:49:06", - "recorded-content": { - "rule": { - "Arn": "arn::events::111111111111:rule/", - "CreatedBy": "111111111111", - "EventBusName": "default", - "EventPattern": { - "detail-type": [ - "Object Created" - ], - "source": [ - "aws.s3" - ], - "detail": { - "bucket": { - "name": [ - "test-s3-bucket" - ] - }, - "object": { - "key": [ - { - "suffix": "/test.json" - } - ] - } - } - }, - "Name": "", - "State": "ENABLED", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_event_rule_creation_without_target": { - "recorded-date": "22-01-2025, 14:15:04", - "recorded-content": { - "describe_rule": { - "Arn": "arn::events::111111111111:rule/event-rule-name", - "CreatedBy": "111111111111", - "EventBusName": "default", - "Name": "event-rule-name", - "ScheduleExpression": "cron(0 1 * * ? *)", - "State": "ENABLED", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.validation.json deleted file mode 100644 index f9456ffe87bad..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_cfn_event_api_destination_resource": { - "last_validated_date": "2024-04-16T06:36:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_event_rule_creation_without_target": { - "last_validated_date": "2025-01-22T14:15:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_eventbus_policy_statement": { - "last_validated_date": "2024-11-14T21:46:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_pattern_transformation": { - "last_validated_date": "2024-11-08T15:49:06+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_properties": { - "last_validated_date": "2023-12-01T14:03:52+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py deleted file mode 100644 index bf3d5a79f2931..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py +++ /dev/null @@ -1,49 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Destinations"]) -def test_firehose_stack_with_kinesis_as_source(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - bucket_name = f"bucket-{short_uid()}" - stream_name = f"stream-{short_uid()}" - delivery_stream_name = f"delivery-stream-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/firehose_kinesis_as_source.yaml" - ), - parameters={ - "BucketName": bucket_name, - "StreamName": stream_name, - "DeliveryStreamName": delivery_stream_name, - }, - max_wait=150, - ) - snapshot.match("outputs", stack.outputs) - - def _assert_stream_available(): - status = aws_client.firehose.describe_delivery_stream( - DeliveryStreamName=delivery_stream_name - ) - assert status["DeliveryStreamDescription"]["DeliveryStreamStatus"] == "ACTIVE" - - retry(_assert_stream_available, sleep=2, retries=15) - - response = aws_client.firehose.describe_delivery_stream(DeliveryStreamName=delivery_stream_name) - assert delivery_stream_name == response["DeliveryStreamDescription"]["DeliveryStreamName"] - snapshot.match("delivery_stream", response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.snapshot.json deleted file mode 100644 index 6bc7b63f87e77..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.snapshot.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": { - "recorded-date": "14-09-2022, 11:19:29", - "recorded-content": { - "outputs": { - "deliveryStreamRef": "" - }, - "delivery_stream": { - "DeliveryStreamDescription": { - "CreateTimestamp": "timestamp", - "DeliveryStreamARN": "arn::firehose::111111111111:deliverystream/", - "DeliveryStreamName": "", - "DeliveryStreamStatus": "ACTIVE", - "DeliveryStreamType": "KinesisStreamAsSource", - "Destinations": [ - { - "DestinationId": "destinationId-000000000001", - "ExtendedS3DestinationDescription": { - "BucketARN": "arn::s3:::", - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64 - }, - "CloudWatchLoggingOptions": { - "Enabled": false - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - "RetryOptions": { - "DurationInSeconds": 300 - } - }, - "EncryptionConfiguration": { - "NoEncryptionConfig": "NoEncryption" - }, - "ErrorOutputPrefix": "firehoseTest-errors/!{firehose:error-output-type}/", - "Prefix": "firehoseTest/!{partitionKeyFromQuery:s3Prefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "MetadataExtractionQuery", - "ParameterValue": "{s3Prefix: .tableName}" - }, - { - "ParameterName": "JsonParsingEngine", - "ParameterValue": "JQ-1.6" - } - ], - "Type": "MetadataExtraction" - } - ] - }, - "RoleARN": "arn::iam::111111111111:role/", - "S3BackupMode": "Disabled" - }, - "S3DestinationDescription": { - "BucketARN": "arn::s3:::", - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64 - }, - "CloudWatchLoggingOptions": { - "Enabled": false - }, - "CompressionFormat": "UNCOMPRESSED", - "EncryptionConfiguration": { - "NoEncryptionConfig": "NoEncryption" - }, - "ErrorOutputPrefix": "firehoseTest-errors/!{firehose:error-output-type}/", - "Prefix": "firehoseTest/!{partitionKeyFromQuery:s3Prefix}", - "RoleARN": "arn::iam::111111111111:role/" - } - } - ], - "HasMoreDestinations": false, - "Source": { - "KinesisStreamSourceDescription": { - "DeliveryStartTimestamp": "timestamp", - "KinesisStreamARN": "arn::kinesis::111111111111:stream/", - "RoleARN": "arn::iam::111111111111:role/" - } - }, - "VersionId": "1" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.validation.json deleted file mode 100644 index e12e5185d82f1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": { - "last_validated_date": "2022-09-14T09:19:29+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py deleted file mode 100644 index bb48345710803..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py +++ /dev/null @@ -1,94 +0,0 @@ -import json -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_events_sqs_sns_lambda(deploy_cfn_template, aws_client): - function_name = f"function-{short_uid()}" - queue_name = f"queue-{short_uid()}" - topic_name = f"topic-{short_uid()}" - bus_name = f"bus-{short_uid()}" - rule_name = f"function-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/integration_events_sns_sqs_lambda.yaml", - ), - parameters={ - "FunctionName": function_name, - "QueueName": queue_name, - "TopicName": topic_name, - "BusName": bus_name, - "RuleName": rule_name, - }, - ) - - assert len(stack.outputs) == 7 - lambda_name = stack.outputs["FnName"] - bus_name = stack.outputs["EventBusName"] - - topic_arn = stack.outputs["TopicArn"] - result = aws_client.sns.get_topic_attributes(TopicArn=topic_arn)["Attributes"] - assert json.loads(result.get("Policy")) == { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": {"Service": "events.amazonaws.com"}, - "Resource": topic_arn, - "Sid": "0", - } - ], - "Version": "2012-10-17", - } - - # put events - aws_client.events.put_events( - Entries=[ - { - "DetailType": "test-detail-type", - "Detail": '{"app": "localstack"}', - "Source": "test-source", - "EventBusName": bus_name, - }, - ] - ) - - def _check_lambda_invocations(): - groups = aws_client.logs.describe_log_groups( - logGroupNamePrefix=f"/aws/lambda/{lambda_name}" - ) - streams = aws_client.logs.describe_log_streams( - logGroupName=groups["logGroups"][0]["logGroupName"] - ) - assert ( - 0 < len(streams) <= 2 - ) # should be 1 or 2 because of the two potentially simultaneous calls - - all_events = [] - for s in streams["logStreams"]: - events = aws_client.logs.get_log_events( - logGroupName=groups["logGroups"][0]["logGroupName"], - logStreamName=s["logStreamName"], - )["events"] - all_events.extend(events) - - assert [e for e in all_events if topic_name in e["message"]] - assert [e for e in all_events if queue_name in e["message"]] - return True - - assert wait_until(_check_lambda_invocations) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.validation.json deleted file mode 100644 index 4213db8d36bbf..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py::test_events_sqs_sns_lambda": { - "last_validated_date": "2024-07-02T18:43:06+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py deleted file mode 100644 index 6cf7220a835c3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py +++ /dev/null @@ -1,184 +0,0 @@ -import json -import os - -import pytest - -from localstack import config -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..StreamDescription.StreamModeDetails"]) -def test_stream_creation(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.resource_name()) - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("StreamName", "stream-name"), - snapshot.transform.key_value("ShardId", "shard-id", reference_replacement=False), - snapshot.transform.key_value("EndingHashKey", "ending-hash-key"), - snapshot.transform.key_value("StartingSequenceNumber", "sequence-number"), - ] - ) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - template = json.dumps( - { - "Resources": { - "TestStream": { - "Type": "AWS::Kinesis::Stream", - "Properties": {"ShardCount": 1}, - }, - }, - "Outputs": { - "StreamNameFromRef": {"Value": {"Ref": "TestStream"}}, - "StreamArnFromAtt": {"Value": {"Fn::GetAtt": "TestStream.Arn"}}, - }, - } - ) - - stack = deploy_cfn_template(template=template) - snapshot.match("stack_output", stack.outputs) - - description = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name) - snapshot.match("resource_description", description) - - stream_name = stack.outputs.get("StreamNameFromRef") - description = aws_client.kinesis.describe_stream(StreamName=stream_name) - snapshot.match("stream_description", description) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..StreamDescription.StreamModeDetails"]) -def test_default_parameters_kinesis(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/kinesis_default.yaml" - ) - ) - - stream_name = stack.outputs["KinesisStreamName"] - rs = aws_client.kinesis.describe_stream(StreamName=stream_name) - snapshot.match("describe_stream", rs) - - snapshot.add_transformer(snapshot.transform.key_value("StreamName")) - snapshot.add_transformer(snapshot.transform.key_value("ShardId")) - snapshot.add_transformer(snapshot.transform.key_value("StartingSequenceNumber")) - - -@markers.aws.validated -def test_cfn_handle_kinesis_firehose_resources(deploy_cfn_template, aws_client): - kinesis_stream_name = f"kinesis-stream-{short_uid()}" - firehose_role_name = f"firehose-role-{short_uid()}" - firehose_stream_name = f"firehose-stream-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_kinesis_stream.yaml" - ), - parameters={ - "KinesisStreamName": kinesis_stream_name, - "DeliveryStreamName": firehose_stream_name, - "KinesisRoleName": firehose_role_name, - }, - ) - - assert len(stack.outputs) == 1 - - rs = aws_client.firehose.describe_delivery_stream(DeliveryStreamName=firehose_stream_name) - assert rs["DeliveryStreamDescription"]["DeliveryStreamARN"] == stack.outputs["MyStreamArn"] - assert rs["DeliveryStreamDescription"]["DeliveryStreamName"] == firehose_stream_name - - rs = aws_client.kinesis.describe_stream(StreamName=kinesis_stream_name) - assert rs["StreamDescription"]["StreamName"] == kinesis_stream_name - - # clean up - stack.destroy() - - rs = aws_client.kinesis.list_streams() - assert kinesis_stream_name not in rs["StreamNames"] - rs = aws_client.firehose.list_delivery_streams() - assert firehose_stream_name not in rs["DeliveryStreamNames"] - - -# TODO: use a different template and move this test to a more generic API level test suite -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify # nothing really works here right now -def test_describe_template(s3_create_bucket, aws_client, cleanups, snapshot): - bucket_name = f"b-{short_uid()}" - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/cfn_kinesis_stream.yaml") - ) - s3_create_bucket(Bucket=bucket_name) - aws_client.s3.put_object(Bucket=bucket_name, Key="template.yml", Body=template_body) - - if is_aws_cloud(): - template_url = ( - f"https://{bucket_name}.s3.{aws_client.s3.meta.region_name}.amazonaws.com/template.yml" - ) - else: - template_url = f"{config.internal_service_url()}/{bucket_name}/template.yml" - - # get summary by template URL - get_template_summary_by_url = aws_client.cloudformation.get_template_summary( - TemplateURL=template_url - ) - snapshot.match("get_template_summary_by_url", get_template_summary_by_url) - - param_keys = {p["ParameterKey"] for p in get_template_summary_by_url["Parameters"]} - assert param_keys == {"KinesisStreamName", "DeliveryStreamName", "KinesisRoleName"} - - # get summary by template body - get_template_summary_by_body = aws_client.cloudformation.get_template_summary( - TemplateBody=template_body - ) - snapshot.match("get_template_summary_by_body", get_template_summary_by_body) - param_keys = {p["ParameterKey"] for p in get_template_summary_by_url["Parameters"]} - assert param_keys == {"KinesisStreamName", "DeliveryStreamName", "KinesisRoleName"} - - -@pytest.mark.skipif( - condition=not is_aws_cloud() and config.DDB_STREAMS_PROVIDER_V2, - reason="Not yet implemented in DDB Streams V2", -) -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..KinesisDataStreamDestinations..DestinationStatusDescription"] -) -def test_dynamodb_stream_response_with_cf(deploy_cfn_template, aws_client, snapshot): - table_name = f"table-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_kinesis_dynamodb.yml" - ), - parameters={"TableName": table_name}, - ) - - response = aws_client.dynamodb.describe_kinesis_streaming_destination(TableName=table_name) - snapshot.match("describe_kinesis_streaming_destination", response) - snapshot.add_transformer(snapshot.transform.key_value("TableName")) - - -@pytest.mark.skip( - reason="CFNV2:Other resource provider returns NULL physical resource id for StreamConsumer thus later references to this resource fail to compute" -) -@markers.aws.validated -def test_kinesis_stream_consumer_creations(deploy_cfn_template, aws_client): - consumer_name = f"{short_uid()}" - stack = deploy_cfn_template( - parameters={"TestConsumerName": consumer_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/kinesis_stream_consumer.yaml" - ), - ) - consumer_arn = stack.outputs["KinesisSConsumerARN"] - response = aws_client.kinesis.describe_stream_consumer(ConsumerARN=consumer_arn) - assert response["ConsumerDescription"]["ConsumerStatus"] == "ACTIVE" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.snapshot.json deleted file mode 100644 index 84936b7b55f43..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.snapshot.json +++ /dev/null @@ -1,279 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_stream_creation": { - "recorded-date": "12-09-2022, 14:11:29", - "recorded-content": { - "stack_output": { - "StreamArnFromAtt": "arn::kinesis::111111111111:stream/", - "StreamNameFromRef": "" - }, - "resource_description": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "TestStream", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Kinesis::Stream", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stream_description": { - "StreamDescription": { - "EncryptionType": "NONE", - "EnhancedMonitoring": [ - { - "ShardLevelMetrics": [] - } - ], - "HasMoreShards": false, - "RetentionPeriodHours": 24, - "Shards": [ - { - "HashKeyRange": { - "EndingHashKey": "", - "StartingHashKey": "0" - }, - "SequenceNumberRange": { - "StartingSequenceNumber": "" - }, - "ShardId": "shard-id" - } - ], - "StreamARN": "arn::kinesis::111111111111:stream/", - "StreamCreationTimestamp": "timestamp", - "StreamModeDetails": { - "StreamMode": "PROVISIONED" - }, - "StreamName": "", - "StreamStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_describe_template": { - "recorded-date": "22-05-2023, 09:25:32", - "recorded-content": { - "get_template_summary_by_url": { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]", - "Parameters": [ - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisRoleName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "DeliveryStreamName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisStreamName", - "ParameterType": "String" - } - ], - "ResourceIdentifierSummaries": [ - { - "LogicalResourceIds": [ - "MyBucket" - ], - "ResourceIdentifiers": [ - "BucketName" - ], - "ResourceType": "AWS::S3::Bucket" - }, - { - "LogicalResourceIds": [ - "MyRole" - ], - "ResourceIdentifiers": [ - "RoleName" - ], - "ResourceType": "AWS::IAM::Role" - }, - { - "LogicalResourceIds": [ - "KinesisStream" - ], - "ResourceIdentifiers": [ - "Name" - ], - "ResourceType": "AWS::Kinesis::Stream" - }, - { - "LogicalResourceIds": [ - "DeliveryStream" - ], - "ResourceIdentifiers": [ - "DeliveryStreamName" - ], - "ResourceType": "AWS::KinesisFirehose::DeliveryStream" - } - ], - "ResourceTypes": [ - "AWS::Kinesis::Stream", - "AWS::IAM::Role", - "AWS::S3::Bucket", - "AWS::KinesisFirehose::DeliveryStream" - ], - "Version": "2010-09-09", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_template_summary_by_body": { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]", - "Parameters": [ - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisRoleName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "DeliveryStreamName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisStreamName", - "ParameterType": "String" - } - ], - "ResourceIdentifierSummaries": [ - { - "LogicalResourceIds": [ - "MyBucket" - ], - "ResourceIdentifiers": [ - "BucketName" - ], - "ResourceType": "AWS::S3::Bucket" - }, - { - "LogicalResourceIds": [ - "MyRole" - ], - "ResourceIdentifiers": [ - "RoleName" - ], - "ResourceType": "AWS::IAM::Role" - }, - { - "LogicalResourceIds": [ - "KinesisStream" - ], - "ResourceIdentifiers": [ - "Name" - ], - "ResourceType": "AWS::Kinesis::Stream" - }, - { - "LogicalResourceIds": [ - "DeliveryStream" - ], - "ResourceIdentifiers": [ - "DeliveryStreamName" - ], - "ResourceType": "AWS::KinesisFirehose::DeliveryStream" - } - ], - "ResourceTypes": [ - "AWS::Kinesis::Stream", - "AWS::IAM::Role", - "AWS::S3::Bucket", - "AWS::KinesisFirehose::DeliveryStream" - ], - "Version": "2010-09-09", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_default_parameters_kinesis": { - "recorded-date": "02-07-2024, 18:59:10", - "recorded-content": { - "describe_stream": { - "StreamDescription": { - "EncryptionType": "NONE", - "EnhancedMonitoring": [ - { - "ShardLevelMetrics": [] - } - ], - "HasMoreShards": false, - "RetentionPeriodHours": 24, - "Shards": [ - { - "HashKeyRange": { - "EndingHashKey": "340282366920938463463374607431768211455", - "StartingHashKey": "0" - }, - "SequenceNumberRange": { - "StartingSequenceNumber": "" - }, - "ShardId": "" - } - ], - "StreamARN": "arn::kinesis::111111111111:stream/", - "StreamCreationTimestamp": "timestamp", - "StreamModeDetails": { - "StreamMode": "PROVISIONED" - }, - "StreamName": "", - "StreamStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": { - "recorded-date": "02-07-2024, 19:48:27", - "recorded-content": { - "describe_kinesis_streaming_destination": { - "KinesisDataStreamDestinations": [ - { - "DestinationStatus": "ACTIVE", - "StreamArn": "arn::kinesis::111111111111:stream/EventStream" - } - ], - "TableName": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.validation.json deleted file mode 100644 index 70bbffa38d0ee..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_cfn_handle_kinesis_firehose_resources": { - "last_validated_date": "2024-07-02T19:10:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_default_parameters_kinesis": { - "last_validated_date": "2024-07-02T18:59:10+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_describe_template": { - "last_validated_date": "2023-05-22T07:25:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": { - "last_validated_date": "2024-07-02T19:48:27+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_stream_creation": { - "last_validated_date": "2022-09-12T12:11:29+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py deleted file mode 100644 index 6625e3086df75..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py +++ /dev/null @@ -1,77 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_kms_key_disabled(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/kms_key_disabled.yaml" - ) - ) - - key_id = stack.outputs["KeyIdOutput"] - assert key_id - my_key = aws_client.kms.describe_key(KeyId=key_id) - assert not my_key["KeyMetadata"]["Enabled"] - - -@markers.aws.validated -def test_cfn_with_kms_resources(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("KeyAlias")) - - alias_name = f"alias/sample-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template34.yaml" - ), - parameters={"AliasName": alias_name}, - max_wait=300, - ) - snapshot.match("stack-outputs", stack.outputs) - - assert stack.outputs.get("KeyAlias") == alias_name - - def _get_matching_aliases(): - aliases = aws_client.kms.list_aliases()["Aliases"] - return [alias for alias in aliases if alias["AliasName"] == alias_name] - - assert len(_get_matching_aliases()) == 1 - - stack.destroy() - assert not _get_matching_aliases() - - -@markers.aws.validated -def test_deploy_stack_with_kms(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_kms_key.yml" - ), - ) - - assert "KeyId" in stack.outputs - - key_id = stack.outputs["KeyId"] - - stack.destroy() - - def assert_key_deleted(): - resp = aws_client.kms.describe_key(KeyId=key_id)["KeyMetadata"] - assert resp["KeyState"] == "PendingDeletion" - - retry(assert_key_deleted, retries=5, sleep=5) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.snapshot.json deleted file mode 100644 index 6b059512e8448..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.snapshot.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_cfn_with_kms_resources": { - "recorded-date": "29-05-2023, 15:45:17", - "recorded-content": { - "stack-outputs": { - "KeyAlias": "", - "KeyArn": "arn::kms::111111111111:key/" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.validation.json deleted file mode 100644 index 38f9f4302bd86..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_cfn_with_kms_resources": { - "last_validated_date": "2023-05-29T13:45:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_deploy_stack_with_kms": { - "last_validated_date": "2024-07-02T20:23:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_kms_key_disabled": { - "last_validated_date": "2024-07-02T20:12:46+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py deleted file mode 100644 index 67f11739b6e46..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py +++ /dev/null @@ -1,1384 +0,0 @@ -import base64 -import json -import os -from io import BytesIO - -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack import config -from localstack.aws.api.lambda_ import InvocationType, Runtime, State -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import in_default_partition, is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.aws.arns import get_partition -from localstack.utils.common import short_uid -from localstack.utils.files import load_file -from localstack.utils.http import safe_requests -from localstack.utils.strings import to_bytes, to_str -from localstack.utils.sync import retry, wait_until -from localstack.utils.testutil import create_lambda_archive, get_lambda_log_events - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_lambda_w_dynamodb_event_filter(deploy_cfn_template, aws_client): - function_name = f"test-fn-{short_uid()}" - table_name = f"ddb-tbl-{short_uid()}" - item_to_put = {"id": {"S": "test123"}, "id2": {"S": "test42"}} - item_to_put2 = {"id": {"S": "test123"}, "id2": {"S": "test67"}} - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_dynamodb_filtering.yaml" - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"eventName": ["MODIFY"]}', - }, - ) - - aws_client.dynamodb.put_item(TableName=table_name, Item=item_to_put) - aws_client.dynamodb.put_item(TableName=table_name, Item=item_to_put2) - - def _assert_single_lambda_call(): - events = get_lambda_log_events(function_name, logs_client=aws_client.logs) - assert len(events) == 1 - msg = events[0] - if not isinstance(msg, str): - msg = json.dumps(msg) - assert "MODIFY" in msg and "INSERT" not in msg - - retry(_assert_single_lambda_call, retries=30) - - -@markers.snapshot.skip_snapshot_verify( - [ - # TODO: Fix flaky ESM state mismatch upon update in LocalStack (expected Enabled, actual Disabled) - # This might be a parity issue if AWS does rolling updates (i.e., never disables the ESM upon update). - "$..EventSourceMappings..State", - ] -) -@markers.aws.validated -def test_lambda_w_dynamodb_event_filter_update(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - function_name = f"test-fn-{short_uid()}" - table_name = f"ddb-tbl-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(table_name, "")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_dynamodb_filtering.yaml" - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"eventName": ["DELETE"]}', - }, - ) - source_mappings = aws_client.lambda_.list_event_source_mappings(FunctionName=function_name) - snapshot.match("source_mappings", source_mappings) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_dynamodb_filtering.yaml" - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"eventName": ["MODIFY"]}', - }, - stack_name=stack.stack_name, - is_update=True, - ) - - source_mappings = aws_client.lambda_.list_event_source_mappings(FunctionName=function_name) - snapshot.match("updated_source_mappings", source_mappings) - - -@markers.aws.validated -def test_update_lambda_function(s3_create_bucket, deploy_cfn_template, aws_client): - function_name = f"lambda-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"Environment": "ORIGINAL", "FunctionName": function_name}, - ) - - response = aws_client.lambda_.get_function(FunctionName=function_name) - assert response["Configuration"]["Environment"]["Variables"]["TEST"] == "ORIGINAL" - - deploy_cfn_template( - stack_name=stack.stack_name, - is_update=True, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"Environment": "UPDATED", "FunctionName": function_name}, - ) - - response = aws_client.lambda_.get_function(FunctionName=function_name) - assert response["Configuration"]["Environment"]["Variables"]["TEST"] == "UPDATED" - - -@markers.aws.validated -def test_update_lambda_function_name(s3_create_bucket, deploy_cfn_template, aws_client): - function_name_1 = f"lambda-{short_uid()}" - function_name_2 = f"lambda-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"FunctionName": function_name_1}, - ) - - function_name = stack.outputs["LambdaName"] - response = aws_client.lambda_.get_function(FunctionName=function_name_1) - assert response["Configuration"]["Environment"]["Variables"]["TEST"] == "ORIGINAL" - - deploy_cfn_template( - stack_name=stack.stack_name, - is_update=True, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"FunctionName": function_name_2}, - ) - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_function(FunctionName=function_name) - - aws_client.lambda_.get_function(FunctionName=function_name_2) - - -@pytest.mark.skip(reason="CFNV2:Describe") -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Metadata", - "$..DriftInformation", - "$..Type", - "$..Message", - "$..access-control-allow-headers", - "$..access-control-allow-methods", - "$..access-control-allow-origin", - "$..access-control-expose-headers", - "$..server", - "$..content-length", - "$..InvokeMode", - ] -) -@markers.aws.validated -def test_cfn_function_url(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - - deploy = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_url.yaml" - ) - ) - - url_logical_resource_id = "UrlD4FAABD0" - snapshot.add_transformer( - snapshot.transform.regex(url_logical_resource_id, "") - ) - snapshot.add_transformer( - snapshot.transform.key_value( - "FunctionUrl", - ) - ) - snapshot.add_transformer( - snapshot.transform.key_value("x-amzn-trace-id", reference_replacement=False) - ) - snapshot.add_transformer(snapshot.transform.key_value("date", reference_replacement=False)) - - url_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deploy.stack_name, LogicalResourceId=url_logical_resource_id - ) - snapshot.match("url_resource", url_resource) - - url_config = aws_client.lambda_.get_function_url_config( - FunctionName=deploy.outputs["LambdaName"] - ) - snapshot.match("url_config", url_config) - - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException) as e: - aws_client.lambda_.get_function_url_config( - FunctionName=deploy.outputs["LambdaName"], Qualifier="unknownalias" - ) - - snapshot.match("exception_url_config_nonexistent_version", e.value.response) - - url_config_arn = aws_client.lambda_.get_function_url_config( - FunctionName=deploy.outputs["LambdaArn"] - ) - snapshot.match("url_config_arn", url_config_arn) - - response = safe_requests.get(deploy.outputs["LambdaUrl"]) - assert response.ok - assert response.json() == {"hello": "world"} - - lowered_headers = {k.lower(): v for k, v in response.headers.items()} - snapshot.match("response_headers", lowered_headers) - - -@pytest.mark.skip(reason="CFNV2:Other Function already exists error") -@markers.aws.validated -def test_lambda_alias(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda x: x["LogicalResourceId"]), priority=-1 - ) - - function_name = f"function{short_uid()}" - alias_name = f"alias{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(alias_name, "")) - snapshot.add_transformer(snapshot.transform.regex(function_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_alias.yml" - ), - parameters={"FunctionName": function_name, "AliasName": alias_name}, - ) - - invoke_result = aws_client.lambda_.invoke( - FunctionName=function_name, Qualifier=alias_name, Payload=b"{}" - ) - assert "FunctionError" not in invoke_result - snapshot.match("invoke_result", invoke_result) - - role_arn = aws_client.lambda_.get_function(FunctionName=function_name)["Configuration"]["Role"] - snapshot.add_transformer( - snapshot.transform.regex(role_arn.partition("role/")[-1], ""), priority=-1 - ) - - description = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_name - ) - snapshot.match("stack_resource_descriptions", description) - - alias = aws_client.lambda_.get_alias(FunctionName=function_name, Name=alias_name) - snapshot.match("Alias", alias) - - provisioned_concurrency_config = aws_client.lambda_.get_provisioned_concurrency_config( - FunctionName=function_name, - Qualifier=alias_name, - ) - snapshot.match("provisioned_concurrency_config", provisioned_concurrency_config) - - -@markers.aws.validated -def test_lambda_logging_config(deploy_cfn_template, snapshot, aws_client): - function_name = f"function{short_uid()}" - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(SortingTransformer("StackResources", lambda x: x["LogicalResourceId"])) - snapshot.add_transformer( - snapshot.transform.key_value("LogicalResourceId", reference_replacement=False) - ) - snapshot.add_transformer( - snapshot.transform.key_value("PhysicalResourceId", reference_replacement=False) - ) - snapshot.add_transformer(snapshot.transform.regex(function_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_logging_config.yaml" - ), - parameters={"FunctionName": function_name}, - ) - - description = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_name - ) - snapshot.match("stack_resource_descriptions", description) - - logging_config = aws_client.lambda_.get_function(FunctionName=function_name)["Configuration"][ - "LoggingConfig" - ] - snapshot.match("logging_config", logging_config) - - -@pytest.mark.skipif( - not in_default_partition(), reason="Test not applicable in non-default partitions" -) -@markers.aws.validated -def test_lambda_code_signing_config(deploy_cfn_template, snapshot, account_id, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(SortingTransformer("StackResources", lambda x: x["LogicalResourceId"])) - - signer_arn = f"arn:{get_partition(aws_client.lambda_.meta.region_name)}:signer:{aws_client.lambda_.meta.region_name}:{account_id}:/signing-profiles/test" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_code_signing_config.yml" - ), - parameters={"SignerArn": signer_arn}, - ) - - description = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name) - snapshot.match("stack_resource_descriptions", description) - - snapshot.match( - "config", - aws_client.lambda_.get_code_signing_config(CodeSigningConfigArn=stack.outputs["Arn"]), - ) - - -@markers.aws.validated -def test_event_invoke_config(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_event_invoke_config.yml" - ), - max_wait=180, - ) - - event_invoke_config = aws_client.lambda_.get_function_event_invoke_config( - FunctionName=stack.outputs["FunctionName"], - Qualifier=stack.outputs["FunctionQualifier"], - ) - - snapshot.match("event_invoke_config", event_invoke_config) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda ZIP flaky in CI - "$..CodeSize", - ] -) -@markers.aws.validated -def test_lambda_version(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]) - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_version.yaml" - ), - max_wait=180, - ) - function_name = deployment.outputs["FunctionName"] - function_version = deployment.outputs["FunctionVersion"] - - invoke_result = aws_client.lambda_.invoke( - FunctionName=function_name, Qualifier=function_version, Payload=b"{}" - ) - assert "FunctionError" not in invoke_result - snapshot.match("invoke_result", invoke_result) - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - snapshot.match("stack_resources", stack_resources) - - versions_by_fn = aws_client.lambda_.list_versions_by_function(FunctionName=function_name) - get_function_version = aws_client.lambda_.get_function( - FunctionName=function_name, Qualifier=function_version - ) - - snapshot.match("versions_by_fn", versions_by_fn) - snapshot.match("get_function_version", get_function_version) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda ZIP flaky in CI - "$..CodeSize", - ] -) -@markers.aws.validated -def test_lambda_version_provisioned_concurrency(deploy_cfn_template, snapshot, aws_client): - """Provisioned concurrency slows down the test case considerably (~2min 40s on AWS) - because CloudFormation waits until the provisioned Lambda functions are ready. - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]) - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_version_provisioned_concurrency.yaml", - ), - max_wait=240, - ) - function_name = deployment.outputs["FunctionName"] - function_version = deployment.outputs["FunctionVersion"] - - invoke_result = aws_client.lambda_.invoke( - FunctionName=function_name, Qualifier=function_version, Payload=b"{}" - ) - assert "FunctionError" not in invoke_result - snapshot.match("invoke_result", invoke_result) - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - snapshot.match("stack_resources", stack_resources) - - versions_by_fn = aws_client.lambda_.list_versions_by_function(FunctionName=function_name) - get_function_version = aws_client.lambda_.get_function( - FunctionName=function_name, Qualifier=function_version - ) - - snapshot.match("versions_by_fn", versions_by_fn) - snapshot.match("get_function_version", get_function_version) - - provisioned_concurrency_config = aws_client.lambda_.get_provisioned_concurrency_config( - FunctionName=function_name, - Qualifier=function_version, - ) - snapshot.match("provisioned_concurrency_config", provisioned_concurrency_config) - - -@markers.aws.validated -def test_lambda_cfn_run(deploy_cfn_template, aws_client): - """ - simply deploys a lambda and immediately invokes it - """ - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_simple.yaml" - ), - max_wait=120, - ) - fn_name = deployment.outputs["FunctionName"] - assert ( - aws_client.lambda_.get_function(FunctionName=fn_name)["Configuration"]["State"] - == State.Active - ) - aws_client.lambda_.invoke(FunctionName=fn_name, LogType="Tail", Payload=b"{}") - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.only_localstack(reason="This is functionality specific to Localstack") -def test_lambda_cfn_run_with_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch -): - """ - deploys the same lambda with an empty CFN string deny list, testing that it behaves as expected - (i.e. the URLs in the deny list are modified) - """ - monkeypatch.setattr(config, "CFN_STRING_REPLACEMENT_DENY_LIST", []) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", - ), - max_wait=120, - ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] - # URLs that match regex to capture AWS URLs gets Localstack port appended - non-matching URLs remain unchanged. - assert function_env_variables["API_URL_1"] == "https://api.example.com" - assert ( - function_env_variables["API_URL_2"] - == "https://storage.execute-api.amazonaws.com:4566/test-resource" - ) - assert ( - function_env_variables["API_URL_3"] - == "https://reporting.execute-api.amazonaws.com:4566/test-resource" - ) - assert ( - function_env_variables["API_URL_4"] - == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" - ) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.only_localstack(reason="This is functionality specific to Localstack") -def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch -): - """ - deploys the same lambda with a non-empty CFN string deny list configurations, testing that it behaves as expected - (i.e. the URLs in the deny list are not modified) - """ - monkeypatch.setattr( - config, - "CFN_STRING_REPLACEMENT_DENY_LIST", - [ - "https://storage.execute-api.us-east-2.amazonaws.com/test-resource", - "https://reporting.execute-api.us-east-1.amazonaws.com/test-resource", - ], - ) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", - ), - max_wait=120, - ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] - # URLs that match regex to capture AWS URLs but are explicitly in the deny list, don't get modified - - # non-matching URLs remain unchanged. - assert function_env_variables["API_URL_1"] == "https://api.example.com" - assert ( - function_env_variables["API_URL_2"] - == "https://storage.execute-api.us-east-2.amazonaws.com/test-resource" - ) - assert ( - function_env_variables["API_URL_3"] - == "https://reporting.execute-api.us-east-1.amazonaws.com/test-resource" - ) - assert ( - function_env_variables["API_URL_4"] - == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" - ) - - -@pytest.mark.skip(reason="broken/notimplemented") -@markers.aws.validated -def test_lambda_vpc(deploy_cfn_template, aws_client): - """ - this test showcases a very long-running deployment of a fairly straight forward lambda function - cloudformation will poll get_function until the active state has been reached - """ - fn_name = f"vpc-lambda-fn-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_vpc.yaml" - ), - parameters={ - "FunctionNameParam": fn_name, - }, - max_wait=600, - ) - assert ( - aws_client.lambda_.get_function(FunctionName=fn_name)["Configuration"]["State"] - == State.Active - ) - aws_client.lambda_.invoke(FunctionName=fn_name, LogType="Tail", Payload=b"{}") - - -@markers.aws.validated -def test_update_lambda_permissions(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_permission.yml" - ) - ) - - new_principal = aws_client.sts.get_caller_identity()["Account"] - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - parameters={"PrincipalForPermission": new_principal}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_permission.yml" - ), - ) - - policy = aws_client.lambda_.get_policy(FunctionName=stack.outputs["FunctionName"]) - - # The behaviour of thi principal acocunt setting changes with aws or lambda providers - principal = json.loads(policy["Policy"])["Statement"][0]["Principal"] - if isinstance(principal, dict): - principal = principal.get("AWS") or principal.get("Service", "") - - assert new_principal in principal - - -@markers.aws.validated -def test_multiple_lambda_permissions_for_singlefn(deploy_cfn_template, snapshot, aws_client): - deploy = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_permission_multiple.yaml", - ), - max_wait=240, - ) - fn_name = deploy.outputs["LambdaName"] - p1_sid = deploy.outputs["PermissionLambda"] - p2_sid = deploy.outputs["PermissionStates"] - - snapshot.add_transformer(snapshot.transform.regex(p1_sid, "")) - snapshot.add_transformer(snapshot.transform.regex(p2_sid, "")) - snapshot.add_transformer(snapshot.transform.regex(fn_name, "")) - snapshot.add_transformer(SortingTransformer("Statement", lambda s: s["Sid"])) - - policy = aws_client.lambda_.get_policy(FunctionName=fn_name) - # load the policy json, so we can properly snapshot it - policy["Policy"] = json.loads(policy["Policy"]) - snapshot.match("policy", policy) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - # Added by CloudFormation - "$..Tags.'aws:cloudformation:logical-id'", - "$..Tags.'aws:cloudformation:stack-id'", - "$..Tags.'aws:cloudformation:stack-name'", - ] -) -def test_lambda_function_tags(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - - function_name = f"fn-{short_uid()}" - environment = f"dev-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(environment, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_with_tags.yml", - ), - parameters={ - "FunctionName": function_name, - "Environment": environment, - }, - ) - snapshot.add_transformer(snapshot.transform.regex(deployment.stack_name, "")) - - get_function_result = aws_client.lambda_.get_function(FunctionName=function_name) - snapshot.match("get_function_result", get_function_result) - - -class TestCfnLambdaIntegrations: - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Attributes.EffectiveDeliveryPolicy", # broken in sns right now. needs to be wrapped within an http key - "$..Attributes.DeliveryPolicy", # shouldn't be there - "$..Attributes.Policy", # missing SNS:Receive - "$..CodeSize", - "$..Configuration.Layers", - "$..Tags", # missing cloudformation automatic resource tags for the lambda function - ] - ) - @markers.aws.validated - def test_cfn_lambda_permissions(self, deploy_cfn_template, snapshot, aws_client): - """ - * Lambda Function - * Lambda Permission - * SNS Topic - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer( - snapshot.transform.key_value("Sid"), priority=-1 - ) # TODO: need a better snapshot construct here - # Sid format: e.g. `-6JTUCQQ17UXN` - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_sns_permissions.yaml", - ), - max_wait=240, - ) - - # verify by checking APIs - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - snapshot.match("stack_resources", stack_resources) - - fn_name = deployment.outputs["FunctionName"] - topic_arn = deployment.outputs["TopicArn"] - - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_topic_attributes_result = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) - get_policy_result = aws_client.lambda_.get_policy(FunctionName=fn_name) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_topic_attributes_result", get_topic_attributes_result) - snapshot.match("get_policy_result", get_policy_result) - - # check that lambda is invoked - - msg = f"msg-verification-{short_uid()}" - aws_client.sns.publish(Message=msg, TopicArn=topic_arn) - - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - @pytest.mark.skip(reason="CFNV2:Other") - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda - "$..Tags", - "$..Configuration.CodeSize", # Lambda ZIP flaky in CI - # SQS - "$..Attributes.SqsManagedSseEnabled", - # IAM - "$..PolicyNames", - "$..PolicyName", - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..StackResources..PhysicalResourceId", # TODO: compatibility between AWS URL and localstack URL - ] - ) - @markers.aws.validated - def test_cfn_lambda_sqs_source(self, deploy_cfn_template, snapshot, aws_client): - """ - Resources: - * Lambda Function - * SQS Queue - * EventSourceMapping - * IAM Roles/Policies (e.g. sqs:ReceiveMessage for lambda service to poll SQS) - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer(snapshot.transform.key_value("RoleId")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_sqs_source.yaml" - ), - max_wait=240, - ) - fn_name = deployment.outputs["FunctionName"] - queue_url = deployment.outputs["QueueUrl"] - esm_id = deployment.outputs["ESMId"] - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - - # IAM::Policy seems to have a pretty weird physical resource ID (e.g. stack-fnSe-3OZPF82JL41D) - iam_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deployment.stack_id, LogicalResourceId="fnServiceRoleDefaultPolicy0ED5D3E5" - ) - snapshot.add_transformer( - snapshot.transform.regex( - iam_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - - snapshot.match("stack_resources", stack_resources) - - # query service APIs for resource states - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_esm_result = aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - get_queue_atts_result = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url, AttributeNames=["All"] - ) - role_arn = get_function_result["Configuration"]["Role"] - role_name = role_arn.partition("role/")[-1] - get_role_result = aws_client.iam.get_role(RoleName=role_name) - list_attached_role_policies_result = aws_client.iam.list_attached_role_policies( - RoleName=role_name - ) - list_inline_role_policies_result = aws_client.iam.list_role_policies(RoleName=role_name) - policies = [] - for rp in list_inline_role_policies_result["PolicyNames"]: - get_rp_result = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=rp) - policies.append(get_rp_result) - - snapshot.add_transformer( - snapshot.transform.jsonpath( - "$..policies..ResponseMetadata", "", reference_replacement=False - ) - ) - - snapshot.match("role_policies", {"policies": policies}) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_esm_result", get_esm_result) - snapshot.match("get_queue_atts_result", get_queue_atts_result) - snapshot.match("get_role_result", get_role_result) - snapshot.match("list_attached_role_policies_result", list_attached_role_policies_result) - snapshot.match("list_inline_role_policies_result", list_inline_role_policies_result) - - # TODO: extract - # TODO: is this even necessary? should the cloudformation deployment guarantee that this is enabled already? - def wait_esm_active(): - try: - return ( - aws_client.lambda_.get_event_source_mapping(UUID=esm_id)["State"] == "Enabled" - ) - except Exception as e: - print(e) - - assert wait_until(wait_esm_active) - - msg = f"msg-verification-{short_uid()}" - aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody=msg) - - # TODO: extract - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - deployment.destroy() - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - # TODO: consider moving into the dedicated DynamoDB => Lambda tests because it tests the filtering functionality rather than CloudFormation (just using CF to deploy resources) - # tests.aws.services.lambda_.test_lambda_integration_dynamodbstreams.TestDynamoDBEventSourceMapping.test_dynamodb_event_filter - @markers.aws.validated - def test_lambda_dynamodb_event_filter( - self, dynamodb_wait_for_table_active, deploy_cfn_template, aws_client, monkeypatch - ): - function_name = f"test-fn-{short_uid()}" - table_name = f"ddb-tbl-{short_uid()}" - - item_to_put = { - "PK": {"S": "person1"}, - "SK": {"S": "details"}, - "name": {"S": "John Doe"}, - } - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/lambda_dynamodb_event_filter.yaml", - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"dynamodb": {"NewImage": {"homemade": {"S": [{"exists": false}]}}}}', - }, - ) - aws_client.dynamodb.put_item(TableName=table_name, Item=item_to_put) - - def _send_events(): - log_events = aws_client.logs.filter_log_events( - logGroupName=f"/aws/lambda/{function_name}" - )["events"] - return any("Hello world!" in e["message"] for e in log_events) - - sleep = 10 if os.getenv("TEST_TARGET") == "AWS_CLOUD" else 1 - assert wait_until(_send_events, wait=sleep, max_retries=50) - - @pytest.mark.skip(reason="CFNV2:Describe") - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda - "$..Tags", - "$..Configuration.CodeSize", # Lambda ZIP flaky in CI - # IAM - "$..PolicyNames", - "$..policies..PolicyName", - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..StackResources..LogicalResourceId", - "$..StackResources..PhysicalResourceId", - # dynamodb describe_table - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - # stream result - "$..StreamDescription.CreationRequestDateTime", - ] - ) - @markers.aws.validated - def test_cfn_lambda_dynamodb_source(self, deploy_cfn_template, snapshot, aws_client): - """ - Resources: - * Lambda Function - * DynamoDB Table + Stream - * EventSourceMapping - * IAM Roles/Policies (e.g. dynamodb:GetRecords for lambda service to poll dynamodb) - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer(snapshot.transform.key_value("RoleId")) - snapshot.add_transformer( - snapshot.transform.key_value("ShardId", reference_replacement=False) - ) - snapshot.add_transformer( - snapshot.transform.key_value("StartingSequenceNumber", reference_replacement=False) - ) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_dynamodb_source.yaml", - ), - max_wait=240, - ) - fn_name = deployment.outputs["FunctionName"] - table_name = deployment.outputs["TableName"] - stream_arn = deployment.outputs["StreamArn"] - esm_id = deployment.outputs["ESMId"] - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - - # IAM::Policy seems to have a pretty weird physical resource ID (e.g. stack-fnSe-3OZPF82JL41D) - iam_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deployment.stack_id, LogicalResourceId="fnServiceRoleDefaultPolicy0ED5D3E5" - ) - snapshot.add_transformer( - snapshot.transform.regex( - iam_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - - snapshot.match("stack_resources", stack_resources) - - # query service APIs for resource states - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_esm_result = aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - describe_table_result = aws_client.dynamodb.describe_table(TableName=table_name) - describe_stream_result = aws_client.dynamodbstreams.describe_stream(StreamArn=stream_arn) - role_arn = get_function_result["Configuration"]["Role"] - role_name = role_arn.partition("role/")[-1] - get_role_result = aws_client.iam.get_role(RoleName=role_name) - list_attached_role_policies_result = aws_client.iam.list_attached_role_policies( - RoleName=role_name - ) - list_inline_role_policies_result = aws_client.iam.list_role_policies(RoleName=role_name) - policies = [] - for rp in list_inline_role_policies_result["PolicyNames"]: - get_rp_result = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=rp) - policies.append(get_rp_result) - - snapshot.add_transformer( - snapshot.transform.jsonpath( - "$..policies..ResponseMetadata", "", reference_replacement=False - ) - ) - - snapshot.match("role_policies", {"policies": policies}) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_esm_result", get_esm_result) - snapshot.match("describe_table_result", describe_table_result) - snapshot.match("describe_stream_result", describe_stream_result) - snapshot.match("get_role_result", get_role_result) - snapshot.match("list_attached_role_policies_result", list_attached_role_policies_result) - snapshot.match("list_inline_role_policies_result", list_inline_role_policies_result) - - # TODO: extract - # TODO: is this even necessary? should the cloudformation deployment guarantee that this is enabled already? - def wait_esm_active(): - try: - return ( - aws_client.lambda_.get_event_source_mapping(UUID=esm_id)["State"] == "Enabled" - ) - except Exception as e: - print(e) - - assert wait_until(wait_esm_active) - - msg = f"msg-verification-{short_uid()}" - aws_client.dynamodb.put_item( - TableName=table_name, Item={"id": {"S": "test"}, "msg": {"S": msg}} - ) - - # TODO: extract - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - deployment.destroy() - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - @pytest.mark.skip(reason="CFNV2:Describe") - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..Configuration.CodeSize", - "$..Tags", - # TODO: wait for ESM to become active in CloudFormation to mitigate these flaky fields - "$..Configuration.LastUpdateStatus", - "$..Configuration.State", - "$..Configuration.StateReason", - "$..Configuration.StateReasonCode", - ], - ) - @markers.aws.validated - def test_cfn_lambda_kinesis_source(self, deploy_cfn_template, snapshot, aws_client): - """ - Resources: - * Lambda Function - * Kinesis Stream - * EventSourceMapping - * IAM Roles/Policies (e.g. kinesis:GetRecords for lambda service to poll kinesis) - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.kinesis_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer(snapshot.transform.key_value("RoleId")) - snapshot.add_transformer( - snapshot.transform.key_value("ShardId", reference_replacement=False) - ) - snapshot.add_transformer( - snapshot.transform.key_value("StartingSequenceNumber", reference_replacement=False) - ) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_kinesis_source.yaml" - ), - max_wait=240, - ) - fn_name = deployment.outputs["FunctionName"] - stream_name = deployment.outputs["StreamName"] - # stream_arn = deployment.outputs["StreamArn"] - esm_id = deployment.outputs["ESMId"] - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - - # IAM::Policy seems to have a pretty weird physical resource ID (e.g. stack-fnSe-3OZPF82JL41D) - iam_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deployment.stack_id, LogicalResourceId="fnServiceRoleDefaultPolicy0ED5D3E5" - ) - snapshot.add_transformer( - snapshot.transform.regex( - iam_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - - snapshot.match("stack_resources", stack_resources) - - # query service APIs for resource states - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_esm_result = aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - describe_stream_result = aws_client.kinesis.describe_stream(StreamName=stream_name) - role_arn = get_function_result["Configuration"]["Role"] - role_name = role_arn.partition("role/")[-1] - get_role_result = aws_client.iam.get_role(RoleName=role_name) - list_attached_role_policies_result = aws_client.iam.list_attached_role_policies( - RoleName=role_name - ) - list_inline_role_policies_result = aws_client.iam.list_role_policies(RoleName=role_name) - policies = [] - for rp in list_inline_role_policies_result["PolicyNames"]: - get_rp_result = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=rp) - policies.append(get_rp_result) - - snapshot.add_transformer( - snapshot.transform.jsonpath( - "$..policies..ResponseMetadata", "", reference_replacement=False - ) - ) - - snapshot.match("role_policies", {"policies": policies}) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_esm_result", get_esm_result) - snapshot.match("describe_stream_result", describe_stream_result) - snapshot.match("get_role_result", get_role_result) - snapshot.match("list_attached_role_policies_result", list_attached_role_policies_result) - snapshot.match("list_inline_role_policies_result", list_inline_role_policies_result) - - # TODO: extract - # TODO: is this even necessary? should the cloudformation deployment guarantee that this is enabled already? - def wait_esm_active(): - try: - return ( - aws_client.lambda_.get_event_source_mapping(UUID=esm_id)["State"] == "Enabled" - ) - except Exception as e: - print(e) - - assert wait_until(wait_esm_active) - - msg = f"msg-verification-{short_uid()}" - data_msg = to_str(base64.b64encode(to_bytes(msg))) - aws_client.kinesis.put_record( - StreamName=stream_name, Data=msg, PartitionKey="samplepartitionkey" - ) - - # TODO: extract - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(data_msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - deployment.destroy() - - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - -class TestCfnLambdaDestinations: - """ - generic cases - 1. verify payload - - - [ ] SNS destination success - - [ ] SNS destination failure - - [ ] SQS destination success - - [ ] SQS destination failure - - [ ] Lambda destination success - - [ ] Lambda destination failure - - [ ] EventBridge destination success - - [ ] EventBridge destination failure - - meta cases - * test max event age - * test retry count - * qualifier issues - * reserved concurrency set to 0 => should immediately go to failure destination / dlq - * combination with DLQ - * test with a very long queue (reserved concurrency 1, high function duration, low max event age) - - edge cases - - [ ] Chaining async lambdas - - doc: - "If the function doesn't have enough concurrency available to process all events, additional requests are throttled. - For throttling errors (429) and system errors (500-series), Lambda returns the event to the queue and attempts to run the function again for up to 6 hours. - The retry interval increases exponentially from 1 second after the first attempt to a maximum of 5 minutes. - If the queue contains many entries, Lambda increases the retry interval and reduces the rate at which it reads events from the queue." - - """ - - @pytest.mark.parametrize( - ["on_success", "on_failure"], - [ - ("sqs", "sqs"), - # TODO: test needs further work - # ("sns", "sns"), - # ("lambda", "lambda"), - # ("eventbridge", "eventbridge") - ], - ) - @markers.aws.validated - def test_generic_destination_routing( - self, deploy_cfn_template, on_success, on_failure, aws_client - ): - """ - This fairly simple template lets us choose between the 4 different destinations for both OnSuccess as well as OnFailure. - The template chooses between one of 4 ARNs via indexed access according to this mapping: - - 0: SQS - 1: SNS - 2: Lambda - 3: EventBridge - - All of them are connected downstream to another Lambda function. - This function can be used to verify that the payload has propagated through the hole scenario. - It also allows us to verify the specific payload format depending on the service integration. - - │ - ▼ - Lambda - │ - ┌──────┬───┴───┬───────┐ - │ │ │ │ - ▼ ▼ ▼ ▼ - (direct) SQS SNS EventBridge - │ │ │ │ - │ │ │ │ - └──────┴───┬───┴───────┘ - │ - ▼ - Lambda - - # TODO: fix eventbridge name (reuse?) - """ - - name_to_index_map = {"sqs": "0", "sns": "1", "lambda": "2", "eventbridge": "3"} - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_destinations.yaml" - ), - parameters={ - # "RetryParam": "", - # "MaxEventAgeSecondsParam": "", - # "QualifierParameter": "", - "OnSuccessSwitch": name_to_index_map[on_success], - "OnFailureSwitch": name_to_index_map[on_failure], - }, - max_wait=600, - ) - - invoke_fn_name = deployment.outputs["LambdaName"] - collect_fn_name = deployment.outputs["CollectLambdaName"] - - msg = f"message-{short_uid()}" - - # Success case - aws_client.lambda_.invoke( - FunctionName=invoke_fn_name, - Payload=to_bytes(json.dumps({"message": msg, "should_fail": "0"})), - InvocationType=InvocationType.Event, - ) - - # Failure case - aws_client.lambda_.invoke( - FunctionName=invoke_fn_name, - Payload=to_bytes(json.dumps({"message": msg, "should_fail": "1"})), - InvocationType=InvocationType.Event, - ) - - def wait_for_logs(): - events = aws_client.logs.filter_log_events( - logGroupName=f"/aws/lambda/{collect_fn_name}" - )["events"] - message_events = [e["message"] for e in events if msg in e["message"]] - return len(message_events) >= 2 - # return len(events) >= 6 # note: each invoke comes with at least 3 events even without printing - - wait_until(wait_for_logs) - - -@markers.aws.validated -def test_python_lambda_code_deployed_via_s3(deploy_cfn_template, aws_client, s3_bucket): - bucket_key = "handler.zip" - zip_file = create_lambda_archive( - load_file( - os.path.join(os.path.dirname(__file__), "../../../../lambda_/functions/lambda_echo.py") - ), - get_content=True, - runtime=Runtime.python3_12, - ) - aws_client.s3.upload_fileobj(BytesIO(zip_file), s3_bucket, bucket_key) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_s3_code.yaml" - ), - parameters={ - "LambdaCodeBucket": s3_bucket, - "LambdaRuntime": "python3.10", - "LambdaHandler": "handler.handler", - }, - ) - - function_name = deployment.outputs["LambdaName"] - invocation_result = aws_client.lambda_.invoke( - FunctionName=function_name, Payload=json.dumps({"hello": "world"}) - ) - payload = json.load(invocation_result["Payload"]) - assert payload == {"hello": "world"} - assert invocation_result["StatusCode"] == 200 - - -@markers.aws.validated -def test_lambda_cfn_dead_letter_config_async_invocation( - deploy_cfn_template, aws_client, s3_create_bucket, snapshot -): - # invoke intentionally failing lambda async, which then forwards to the DLQ as configured. - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.sqs_api()) - - # cfn template was generated via serverless, but modified to work with pure cloudformation - s3_bucket = s3_create_bucket() - bucket_key = "serverless/dlq/local/1701682216701-2023-12-04T09:30:16.701Z/dlq.zip" - - zip_file = create_lambda_archive( - load_file( - os.path.join( - os.path.dirname(__file__), "../../../../lambda_/functions/lambda_handler_error.py" - ) - ), - get_content=True, - runtime=Runtime.python3_12, - ) - aws_client.s3.upload_fileobj(BytesIO(zip_file), s3_bucket, bucket_key) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_serverless.yml" - ), - parameters={"LambdaCodeBucket": s3_bucket}, - ) - function_name = deployment.outputs["LambdaName"] - - # async invocation - aws_client.lambda_.invoke(FunctionName=function_name, InvocationType="Event") - dlq_queue = deployment.outputs["DLQName"] - response = {} - - def check_dlq_message(response: dict): - response.update(aws_client.sqs.receive_message(QueueUrl=dlq_queue, VisibilityTimeout=0)) - assert response.get("Messages") - - retry(check_dlq_message, response=response, retries=5, sleep=2.5) - snapshot.match("failed-async-lambda", response) - - -@markers.aws.validated -def test_lambda_layer_crud(deploy_cfn_template, aws_client, s3_bucket, snapshot): - snapshot.add_transformers_list( - [snapshot.transform.key_value("LambdaName"), snapshot.transform.key_value("layer-name")] - ) - - layer_name = f"layer-{short_uid()}" - snapshot.match("layer-name", layer_name) - - bucket_key = "layer.zip" - zip_file = create_lambda_archive( - "hello", - get_content=True, - runtime=Runtime.python3_12, - file_name="hello.txt", - ) - aws_client.s3.upload_fileobj(BytesIO(zip_file), s3_bucket, bucket_key) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_layer_version.yml" - ), - parameters={"LayerBucket": s3_bucket, "LayerName": layer_name}, - ) - snapshot.match("cfn-output", deployment.outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.snapshot.json deleted file mode 100644 index f5743e2e003e4..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.snapshot.json +++ /dev/null @@ -1,1892 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_cfn_function_url": { - "recorded-date": "16-04-2024, 08:16:02", - "recorded-content": { - "url_resource": { - "StackResourceDetail": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "", - "Metadata": {}, - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Url", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "url_config": { - "AuthType": "NONE", - "CreationTime": "date", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionUrl": "", - "InvokeMode": "BUFFERED", - "LastModifiedTime": "date", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "exception_url_config_nonexistent_version": { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "The resource you requested does not exist." - }, - "Message": "The resource you requested does not exist.", - "Type": "User", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 404 - } - }, - "url_config_arn": { - "AuthType": "NONE", - "CreationTime": "date", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionUrl": "", - "InvokeMode": "BUFFERED", - "LastModifiedTime": "date", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "response_headers": { - "connection": "keep-alive", - "content-length": "17", - "content-type": "application/json", - "date": "date", - "x-amzn-requestid": "", - "x-amzn-trace-id": "x-amzn-trace-id" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_alias": { - "recorded-date": "07-05-2025, 15:39:26", - "recorded-content": { - "invoke_result": { - "ExecutedVersion": "1", - "Payload": { - "function_version": "1", - "initialization_type": null - }, - "StatusCode": 200, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "FunctionAlias", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Alias", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "LambdaFunction", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "MyFnServiceRole", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Version", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "Alias": { - "AliasArn": "arn::lambda::111111111111:function:", - "Description": "", - "FunctionVersion": "1", - "Name": "", - "RevisionId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "provisioned_concurrency_config": { - "AllocatedProvisionedConcurrentExecutions": 1, - "AvailableProvisionedConcurrentExecutions": 1, - "LastModified": "date", - "RequestedProvisionedConcurrentExecutions": 1, - "Status": "READY", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": { - "recorded-date": "09-04-2024, 07:26:03", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnAllowInvokeLambdaPermissionsStacktopicF723B1A748672DB5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Permission", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fntopic09ED913A", - "PhysicalResourceId": "arn::sns::111111111111::", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Subscription", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "topic69831491", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_topic_attributes_result": { - "Attributes": { - "DisplayName": "", - "EffectiveDeliveryPolicy": { - "http": { - "defaultHealthyRetryPolicy": { - "minDelayTarget": 20, - "maxDelayTarget": 20, - "numRetries": 3, - "numMaxDelayRetries": 0, - "numNoDelayRetries": 0, - "numMinDelayRetries": 0, - "backoffFunction": "linear" - }, - "disableSubscriptionOverrides": false, - "defaultRequestPolicy": { - "headerContentType": "text/plain; charset=UTF-8" - } - } - }, - "Owner": "111111111111", - "Policy": { - "Version": "2008-10-17", - "Id": "__default_policy_ID", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "SNS:GetTopicAttributes", - "SNS:SetTopicAttributes", - "SNS:AddPermission", - "SNS:RemovePermission", - "SNS:DeleteTopic", - "SNS:Subscribe", - "SNS:ListSubscriptionsByTopic", - "SNS:Publish" - ], - "Resource": "arn::sns::111111111111:", - "Condition": { - "StringEquals": { - "AWS:SourceOwner": "111111111111" - } - } - } - ] - }, - "SubscriptionsConfirmed": "0", - "SubscriptionsDeleted": "0", - "SubscriptionsPending": "0", - "TopicArn": "arn::sns::111111111111:" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_policy_result": { - "Policy": { - "Version": "2012-10-17", - "Id": "default", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com" - }, - "Action": "lambda:InvokeFunction", - "Resource": "arn::lambda::111111111111:function:", - "Condition": { - "ArnLike": { - "AWS:SourceArn": "arn::sns::111111111111:" - } - } - } - ] - }, - "RevisionId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": { - "recorded-date": "30-10-2024, 14:48:16", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRoleDefaultPolicy0ED5D3E5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnSqsEventSourceLambdaSqsSourceStackq2097017B53C3FF8C", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::EventSourceMapping", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "q14836DC8", - "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SQS::Queue", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sqs:ReceiveMessage", - "sqs:ChangeMessageVisibility", - "sqs:GetQueueUrl", - "sqs:DeleteMessage", - "sqs:GetQueueAttributes" - ], - "Effect": "Allow", - "Resource": "arn::sqs::111111111111:" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "fnServiceRoleDefaultPolicy0ED5D3E5", - "RoleName": "", - "ResponseMetadata": "" - } - ] - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_esm_result": { - "BatchSize": 1, - "EventSourceArn": "arn::sqs::111111111111:", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "MaximumBatchingWindowInSeconds": 0, - "State": "Enabled", - "StateTransitionReason": "USER_INITIATED", - "UUID": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_queue_atts_result": { - "Attributes": { - "ApproximateNumberOfMessages": "0", - "ApproximateNumberOfMessagesDelayed": "0", - "ApproximateNumberOfMessagesNotVisible": "0", - "CreatedTimestamp": "timestamp", - "DelaySeconds": "0", - "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", - "MessageRetentionPeriod": "345600", - "QueueArn": "arn::sqs::111111111111:", - "ReceiveMessageWaitTimeSeconds": "0", - "SqsManagedSseEnabled": "true", - "VisibilityTimeout": "30" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_role_result": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_attached_role_policies_result": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "PolicyName": "AWSLambdaBasicExecutionRole" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_inline_role_policies_result": { - "IsTruncated": false, - "PolicyNames": [ - "fnServiceRoleDefaultPolicy0ED5D3E5" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_code_signing_config": { - "recorded-date": "09-04-2024, 07:19:51", - "recorded-content": { - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "CodeSigningConfig", - "PhysicalResourceId": "arn::lambda::111111111111:code-signing-config:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::CodeSigningConfig", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "config": { - "CodeSigningConfig": { - "AllowedPublishers": { - "SigningProfileVersionArns": [ - "arn::signer::111111111111:/signing-profiles/test" - ] - }, - "CodeSigningConfigArn": "arn::lambda::111111111111:code-signing-config:", - "CodeSigningConfigId": "", - "CodeSigningPolicies": { - "UntrustedArtifactOnDeployment": "Enforce" - }, - "Description": "Code Signing", - "LastModified": "date" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_event_invoke_config": { - "recorded-date": "09-04-2024, 07:20:36", - "recorded-content": { - "event_invoke_config": { - "DestinationConfig": { - "OnFailure": {}, - "OnSuccess": {} - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "LastModified": "datetime", - "MaximumEventAgeInSeconds": 300, - "MaximumRetryAttempts": 1, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version": { - "recorded-date": "07-05-2025, 13:19:10", - "recorded-content": { - "invoke_result": { - "ExecutedVersion": "1", - "Payload": { - "function_version": "1" - }, - "StatusCode": 200, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnVersion7BF8AE5A", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "versions_by_fn": { - "Versions": [ - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function::$LATEST", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function_version": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": { - "recorded-date": "12-10-2024, 10:46:17", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnDynamoDBEventSourceLambdaDynamodbSourceStacktable153BBA79064FDF1D", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::EventSourceMapping", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRoleDefaultPolicy0ED5D3E5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "table8235A42E", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::DynamoDB::Table", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "dynamodb:ListStreams", - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": [ - "dynamodb:DescribeStream", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator" - ], - "Effect": "Allow", - "Resource": "arn::dynamodb::111111111111:table//stream/" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "fnServiceRoleDefaultPolicy0ED5D3E5", - "RoleName": "", - "ResponseMetadata": "" - } - ] - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_esm_result": { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::dynamodb::111111111111:table//stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 0, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_table_result": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "id", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "LatestStreamArn": "arn::dynamodb::111111111111:table//stream/", - "LatestStreamLabel": "", - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5 - }, - "StreamSpecification": { - "StreamEnabled": true, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stream_result": { - "StreamDescription": { - "CreationRequestDateTime": "datetime", - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "Shards": [ - { - "SequenceNumberRange": { - "StartingSequenceNumber": "starting-sequence-number" - }, - "ShardId": "shard-id" - } - ], - "StreamArn": "arn::dynamodb::111111111111:table//stream/", - "StreamLabel": "", - "StreamStatus": "ENABLED", - "StreamViewType": "NEW_AND_OLD_IMAGES", - "TableName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_role_result": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_attached_role_policies_result": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "PolicyName": "AWSLambdaBasicExecutionRole" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_inline_role_policies_result": { - "IsTruncated": false, - "PolicyNames": [ - "fnServiceRoleDefaultPolicy0ED5D3E5" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": { - "recorded-date": "12-10-2024, 10:52:28", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnKinesisEventSourceLambdaKinesisSourceStackstream996A3395ED86A30E", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::EventSourceMapping", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRoleDefaultPolicy0ED5D3E5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "stream19075594", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Kinesis::Stream", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStreamSummary", - "kinesis:GetRecords", - "kinesis:GetShardIterator", - "kinesis:ListShards", - "kinesis:SubscribeToShard", - "kinesis:DescribeStream", - "kinesis:ListStreams" - ], - "Effect": "Allow", - "Resource": "arn::kinesis::111111111111:stream/" - }, - { - "Action": "kinesis:DescribeStream", - "Effect": "Allow", - "Resource": "arn::kinesis::111111111111:stream/" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "fnServiceRoleDefaultPolicy0ED5D3E5", - "RoleName": "", - "ResponseMetadata": "" - } - ] - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_esm_result": { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::kinesis::111111111111:stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 10, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stream_result": { - "StreamDescription": { - "EncryptionType": "NONE", - "EnhancedMonitoring": [ - { - "ShardLevelMetrics": [] - } - ], - "HasMoreShards": false, - "RetentionPeriodHours": 24, - "Shards": [ - { - "HashKeyRange": { - "EndingHashKey": "ending_hash", - "StartingHashKey": "starting_hash" - }, - "SequenceNumberRange": { - "StartingSequenceNumber": "starting-sequence-number" - }, - "ShardId": "shard-id" - } - ], - "StreamARN": "arn::kinesis::111111111111:stream/", - "StreamCreationTimestamp": "timestamp", - "StreamModeDetails": { - "StreamMode": "PROVISIONED" - }, - "StreamName": "", - "StreamStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_role_result": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_attached_role_policies_result": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "PolicyName": "AWSLambdaBasicExecutionRole" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_inline_role_policies_result": { - "IsTruncated": false, - "PolicyNames": [ - "fnServiceRoleDefaultPolicy0ED5D3E5" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": { - "recorded-date": "09-04-2024, 07:25:05", - "recorded-content": { - "policy": { - "Policy": { - "Id": "default", - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Resource": "arn::lambda::111111111111:function:", - "Sid": "" - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Principal": { - "Service": "states.amazonaws.com" - }, - "Resource": "arn::lambda::111111111111:function:", - "Sid": "" - } - ], - "Version": "2012-10-17" - }, - "RevisionId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": { - "recorded-date": "09-04-2024, 07:39:50", - "recorded-content": { - "failed-async-lambda": { - "Messages": [ - { - "Body": {}, - "MD5OfBody": "99914b932bd37a50b983c5e7c90ae93b", - "MessageId": "", - "ReceiptHandle": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": { - "recorded-date": "12-10-2024, 10:42:00", - "recorded-content": { - "source_mappings": { - "EventSourceMappings": [ - { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::dynamodb::111111111111:table//stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FilterCriteria": { - "Filters": [ - { - "Pattern": { - "eventName": [ - "DELETE" - ] - } - } - ] - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 0, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "updated_source_mappings": { - "EventSourceMappings": [ - { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::dynamodb::111111111111:table//stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FilterCriteria": { - "Filters": [ - { - "Pattern": { - "eventName": [ - "MODIFY" - ] - } - } - ] - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 0, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_function_tags": { - "recorded-date": "01-10-2024, 12:52:51", - "recorded-content": { - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.11", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "Environment": "", - "aws:cloudformation:logical-id": "TestFunction", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "lambda:createdBy": "SAM" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_layer_crud": { - "recorded-date": "20-12-2024, 18:23:31", - "recorded-content": { - "layer-name": "", - "cfn-output": { - "LambdaArn": "arn::lambda::111111111111:function:", - "LambdaName": "", - "LayerVersionArn": "arn::lambda::111111111111:layer::1", - "LayerVersionRef": "arn::lambda::111111111111:layer::1" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_logging_config": { - "recorded-date": "08-04-2025, 12:10:56", - "recorded-content": { - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "logical-resource-id", - "PhysicalResourceId": "physical-resource-id", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "logical-resource-id", - "PhysicalResourceId": "physical-resource-id", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "logical-resource-id", - "PhysicalResourceId": "physical-resource-id", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "logging_config": { - "ApplicationLogLevel": "INFO", - "LogFormat": "JSON", - "LogGroup": "/aws/lambda/", - "SystemLogLevel": "INFO" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": { - "recorded-date": "07-05-2025, 13:23:25", - "recorded-content": { - "invoke_result": { - "ExecutedVersion": "1", - "Payload": { - "initialization_type": "provisioned-concurrency" - }, - "StatusCode": 200, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnVersion7BF8AE5A", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "versions_by_fn": { - "Versions": [ - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function::$LATEST", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function_version": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "provisioned_concurrency_config": { - "AllocatedProvisionedConcurrentExecutions": 1, - "AvailableProvisionedConcurrentExecutions": 1, - "LastModified": "date", - "RequestedProvisionedConcurrentExecutions": 1, - "Status": "READY", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.validation.json deleted file mode 100644 index 759e47d6a6561..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.validation.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaDestinations::test_generic_destination_routing[sqs-sqs]": { - "last_validated_date": "2024-12-10T16:48:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": { - "last_validated_date": "2024-10-12T10:46:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": { - "last_validated_date": "2024-10-12T10:52:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": { - "last_validated_date": "2024-04-09T07:26:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": { - "last_validated_date": "2024-10-30T14:48:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": { - "last_validated_date": "2024-04-09T07:31:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_cfn_function_url": { - "last_validated_date": "2024-04-16T08:16:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_event_invoke_config": { - "last_validated_date": "2024-04-09T07:20:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_alias": { - "last_validated_date": "2025-05-07T15:39:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": { - "last_validated_date": "2024-04-09T07:39:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_run": { - "last_validated_date": "2024-04-09T07:22:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_code_signing_config": { - "last_validated_date": "2024-04-09T07:19:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_function_tags": { - "last_validated_date": "2024-10-01T12:52:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_layer_crud": { - "last_validated_date": "2024-12-20T18:23:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_logging_config": { - "last_validated_date": "2025-04-08T12:12:01+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version": { - "last_validated_date": "2025-05-07T13:19:10+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": { - "last_validated_date": "2025-05-07T13:23:25+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": { - "last_validated_date": "2024-12-11T09:03:52+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": { - "last_validated_date": "2024-04-09T07:25:05+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_python_lambda_code_deployed_via_s3": { - "last_validated_date": "2024-04-09T07:38:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_function": { - "last_validated_date": "2024-11-07T03:16:40+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_function_name": { - "last_validated_date": "2024-11-07T03:10:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_permissions": { - "last_validated_date": "2024-04-09T07:23:41+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py deleted file mode 100644 index 75afa2549b354..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py +++ /dev/null @@ -1,60 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_logstream(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/logs_group_and_stream.yaml" - ) - ) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("LogGroupNameOutput")) - - group_name = stack.outputs["LogGroupNameOutput"] - stream_name = stack.outputs["LogStreamNameOutput"] - - snapshot.match("outputs", stack.outputs) - - streams = aws_client.logs.describe_log_streams( - logGroupName=group_name, logStreamNamePrefix=stream_name - )["logStreams"] - assert aws_client.logs.meta.partition == streams[0]["arn"].split(":")[1] - snapshot.match("describe_log_streams", streams) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..logGroups..logGroupArn", - "$..logGroups..logGroupClass", - "$..logGroups..retentionInDays", - ] -) -def test_cfn_handle_log_group_resource(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/logs_group.yml" - ) - ) - - log_group_prefix = stack.outputs["LogGroupNameOutput"] - - response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_prefix) - snapshot.match("describe_log_groups", response) - snapshot.add_transformer(snapshot.transform.key_value("logGroupName")) - - stack.destroy() - response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_prefix) - assert len(response["logGroups"]) == 0 diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.snapshot.json deleted file mode 100644 index 29964de53c6a8..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.snapshot.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py::test_logstream": { - "recorded-date": "29-07-2022, 13:22:53", - "recorded-content": { - "outputs": { - "LogStreamNameOutput": "", - "LogGroupNameOutput": "" - }, - "describe_log_streams": [ - { - "logStreamName": "", - "creationTime": "timestamp", - "arn": "arn::logs::111111111111:log-group::log-stream:", - "storedBytes": 0 - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py::test_cfn_handle_log_group_resource": { - "recorded-date": "20-06-2024, 16:15:47", - "recorded-content": { - "describe_log_groups": { - "logGroups": [ - { - "arn": "arn::logs::111111111111:log-group::*", - "creationTime": "timestamp", - "logGroupArn": "arn::logs::111111111111:log-group:", - "logGroupClass": "STANDARD", - "logGroupName": "", - "metricFilterCount": 0, - "retentionInDays": 731, - "storedBytes": 0 - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.validation.json deleted file mode 100644 index fce835093de2a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_logs.py::test_cfn_handle_log_group_resource": { - "last_validated_date": "2024-06-20T16:15:47+00:00" - }, - "tests/aws/services/cloudformation/resources/test_logs.py::test_logstream": { - "last_validated_date": "2022-07-29T11:22:53+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py deleted file mode 100644 index 8cb3ad8dbe6d3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py +++ /dev/null @@ -1,97 +0,0 @@ -import os -from operator import itemgetter - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.skip(reason="flaky") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..ClusterConfig.DedicatedMasterCount", # added in LS - "$..ClusterConfig.DedicatedMasterEnabled", # added in LS - "$..ClusterConfig.DedicatedMasterType", # added in LS - "$..SoftwareUpdateOptions", # missing - "$..OffPeakWindowOptions", # missing - "$..ChangeProgressDetails", # missing - "$..AutoTuneOptions.UseOffPeakWindow", # missing - "$..ClusterConfig.MultiAZWithStandbyEnabled", # missing - "$..AdvancedSecurityOptions.AnonymousAuthEnabled", # missing - # TODO different values: - "$..Processing", - "$..ServiceSoftwareOptions.CurrentVersion", - "$..ClusterConfig.DedicatedMasterEnabled", - "$..ClusterConfig.InstanceType", # TODO the type was set in cfn - "$..AutoTuneOptions.State", - '$..AdvancedOptions."rest.action.multi.allow_explicit_index"', # TODO this was set to false in cfn - ] -) -def test_domain(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("DomainId")) - snapshot.add_transformer(snapshot.transform.key_value("DomainName")) - snapshot.add_transformer(snapshot.transform.key_value("ChangeId")) - snapshot.add_transformer(snapshot.transform.key_value("Endpoint"), priority=-1) - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/opensearch_domain.yml" - ) - result = deploy_cfn_template(template_path=template_path) - domain_endpoint = result.outputs["SearchDomainEndpoint"] - assert domain_endpoint - domain_arn = result.outputs["SearchDomainArn"] - assert domain_arn - domain_name = result.outputs["SearchDomain"] - - domain = aws_client.opensearch.describe_domain(DomainName=domain_name) - assert domain["DomainStatus"] - snapshot.match("describe_domain", domain) - - assert domain_arn == domain["DomainStatus"]["ARN"] - tags_result = aws_client.opensearch.list_tags(ARN=domain_arn) - tags_result["TagList"].sort(key=itemgetter("Key")) - snapshot.match("list_tags", tags_result) - - -@pytest.mark.skip(reason="CFNV2:AdvancedOptions unsupported") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..DomainStatus.AccessPolicies", - "$..DomainStatus.AdvancedOptions.override_main_response_version", - "$..DomainStatus.AdvancedSecurityOptions.AnonymousAuthEnabled", - "$..DomainStatus.AutoTuneOptions.State", - "$..DomainStatus.AutoTuneOptions.UseOffPeakWindow", - "$..DomainStatus.ChangeProgressDetails", - "$..DomainStatus.ClusterConfig.DedicatedMasterCount", - "$..DomainStatus.ClusterConfig.InstanceCount", - "$..DomainStatus.ClusterConfig.MultiAZWithStandbyEnabled", - "$..DomainStatus.ClusterConfig.ZoneAwarenessConfig", - "$..DomainStatus.ClusterConfig.ZoneAwarenessEnabled", - "$..DomainStatus.EBSOptions.VolumeSize", - "$..DomainStatus.Endpoint", - "$..DomainStatus.OffPeakWindowOptions", - "$..DomainStatus.ServiceSoftwareOptions.CurrentVersion", - "$..DomainStatus.SoftwareUpdateOptions", - ] -) -def test_domain_with_alternative_types(deploy_cfn_template, aws_client, snapshot): - """ - Test that the alternative types for the OpenSearch domain are accepted using the resource documentation example - """ - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/opensearch_domain_alternative_types.yml", - ) - ) - domain_name = stack.outputs["SearchDomain"] - domain = aws_client.opensearch.describe_domain(DomainName=domain_name) - snapshot.match("describe_domain", domain) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.snapshot.json deleted file mode 100644 index 8d0498795db31..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.snapshot.json +++ /dev/null @@ -1,225 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain": { - "recorded-date": "31-08-2023, 17:42:29", - "recorded-content": { - "describe_domain": { - "DomainStatus": { - "ARN": "arn::es::111111111111:domain/", - "AccessPolicies": "", - "AdvancedOptions": { - "override_main_response_version": "false", - "rest.action.multi.allow_explicit_index": "false" - }, - "AdvancedSecurityOptions": { - "AnonymousAuthEnabled": false, - "Enabled": false, - "InternalUserDatabaseEnabled": false - }, - "AutoTuneOptions": { - "State": "ENABLED", - "UseOffPeakWindow": false - }, - "ChangeProgressDetails": { - "ChangeId": "" - }, - "ClusterConfig": { - "ColdStorageOptions": { - "Enabled": false - }, - "DedicatedMasterEnabled": false, - "InstanceCount": 1, - "InstanceType": "r5.large.search", - "MultiAZWithStandbyEnabled": false, - "WarmEnabled": false, - "ZoneAwarenessEnabled": false - }, - "CognitoOptions": { - "Enabled": false - }, - "Created": true, - "Deleted": false, - "DomainEndpointOptions": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" - }, - "DomainId": "", - "DomainName": "", - "EBSOptions": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 10, - "VolumeType": "gp2" - }, - "EncryptionAtRestOptions": { - "Enabled": false - }, - "Endpoint": "", - "EngineVersion": "OpenSearch_2.5", - "NodeToNodeEncryptionOptions": { - "Enabled": false - }, - "OffPeakWindowOptions": { - "Enabled": true, - "OffPeakWindow": { - "WindowStartTime": { - "Hours": 2, - "Minutes": 0 - } - } - }, - "Processing": false, - "ServiceSoftwareOptions": { - "AutomatedUpdateDate": "datetime", - "Cancellable": false, - "CurrentVersion": "OpenSearch_2_5_R20230308-P4", - "Description": "There is no software update available for this domain.", - "NewVersion": "", - "OptionalDeployment": true, - "UpdateAvailable": false, - "UpdateStatus": "COMPLETED" - }, - "SnapshotOptions": { - "AutomatedSnapshotStartHour": 0 - }, - "SoftwareUpdateOptions": { - "AutoSoftwareUpdateEnabled": false - }, - "UpgradeProcessing": false - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags": { - "TagList": [ - { - "Key": "anotherkey", - "Value": "hello" - }, - { - "Key": "foo", - "Value": "bar" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain_with_alternative_types": { - "recorded-date": "05-10-2023, 11:07:39", - "recorded-content": { - "describe_domain": { - "DomainStatus": { - "ARN": "arn::es::111111111111:domain/test-opensearch-domain", - "AccessPolicies": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn::iam::111111111111:root" - }, - "Action": "es:*", - "Resource": "arn::es::111111111111:domain/test-opensearch-domain/*" - } - ] - }, - "AdvancedOptions": { - "override_main_response_version": "true", - "rest.action.multi.allow_explicit_index": "true" - }, - "AdvancedSecurityOptions": { - "AnonymousAuthEnabled": false, - "Enabled": false, - "InternalUserDatabaseEnabled": false - }, - "AutoTuneOptions": { - "State": "ENABLED", - "UseOffPeakWindow": false - }, - "ChangeProgressDetails": { - "ChangeId": "" - }, - "ClusterConfig": { - "ColdStorageOptions": { - "Enabled": false - }, - "DedicatedMasterCount": 3, - "DedicatedMasterEnabled": true, - "DedicatedMasterType": "m3.medium.search", - "InstanceCount": 2, - "InstanceType": "m3.medium.search", - "MultiAZWithStandbyEnabled": false, - "WarmEnabled": false, - "ZoneAwarenessConfig": { - "AvailabilityZoneCount": 2 - }, - "ZoneAwarenessEnabled": true - }, - "CognitoOptions": { - "Enabled": false - }, - "Created": true, - "Deleted": false, - "DomainEndpointOptions": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" - }, - "DomainId": "111111111111/test-opensearch-domain", - "DomainName": "test-opensearch-domain", - "EBSOptions": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 20, - "VolumeType": "gp2" - }, - "EncryptionAtRestOptions": { - "Enabled": false - }, - "Endpoint": "search-test-opensearch-domain-lwnlbu3h4beauepbhlq5emyh3m..es.amazonaws.com", - "EngineVersion": "OpenSearch_1.0", - "NodeToNodeEncryptionOptions": { - "Enabled": false - }, - "OffPeakWindowOptions": { - "Enabled": true, - "OffPeakWindow": { - "WindowStartTime": { - "Hours": 2, - "Minutes": 0 - } - } - }, - "Processing": false, - "ServiceSoftwareOptions": { - "AutomatedUpdateDate": "datetime", - "Cancellable": false, - "CurrentVersion": "OpenSearch_1_0_R20230928", - "Description": "There is no software update available for this domain.", - "NewVersion": "", - "OptionalDeployment": true, - "UpdateAvailable": false, - "UpdateStatus": "COMPLETED" - }, - "SnapshotOptions": { - "AutomatedSnapshotStartHour": 0 - }, - "SoftwareUpdateOptions": { - "AutoSoftwareUpdateEnabled": false - }, - "UpgradeProcessing": false - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.validation.json deleted file mode 100644 index 1769b2a88f224..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain": { - "last_validated_date": "2023-08-31T15:42:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain_with_alternative_types": { - "last_validated_date": "2023-10-05T09:07:39+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py deleted file mode 100644 index b0c4f0b91b6a3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py +++ /dev/null @@ -1,28 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -# only runs in Docker when run against Pro (since it needs postgres on the system) -@markers.only_in_docker -@markers.aws.validated -def test_redshift_cluster(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_redshift.yaml" - ) - ) - - # very basic test to check the cluster deploys - assert stack.outputs["ClusterRef"] - assert stack.outputs["ClusterAttEndpointPort"] - assert stack.outputs["ClusterAttEndpointAddress"] diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.validation.json deleted file mode 100644 index 69f04be2accfe..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py::test_redshift_cluster": { - "last_validated_date": "2024-02-28T12:42:35+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py deleted file mode 100644 index db32df5683969..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py +++ /dev/null @@ -1,25 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Group.Description", "$..Group.GroupArn"]) -def test_group_defaults(aws_client, deploy_cfn_template, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/resource_group_defaults.yml" - ), - ) - - resource_group = aws_client.resource_groups.get_group(GroupName=stack.outputs["ResourceGroup"]) - snapshot.match("resource-group", resource_group) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.snapshot.json deleted file mode 100644 index a3f11aeabdeed..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.snapshot.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py::test_group_defaults": { - "recorded-date": "16-07-2024, 15:15:11", - "recorded-content": { - "resource-group": { - "Group": { - "GroupArn": "arn::resource-groups::111111111111:group/testgroup", - "Name": "testgroup" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.validation.json deleted file mode 100644 index 33b1cf0308598..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py::test_group_defaults": { - "last_validated_date": "2024-07-16T15:15:11+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py deleted file mode 100644 index 06cc700e4b077..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py +++ /dev/null @@ -1,76 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_create_record_set_via_id(route53_hosted_zone, deploy_cfn_template): - create_zone_response = route53_hosted_zone() - hosted_zone_id = create_zone_response["HostedZone"]["Id"] - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/route53_hostedzoneid_template.yaml" - ), - parameters=parameters, - max_wait=300, - ) - - -@markers.aws.validated -def test_create_record_set_via_name(deploy_cfn_template, route53_hosted_zone): - create_zone_response = route53_hosted_zone() - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneName": route53_name, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/route53_hostedzonename_template.yaml", - ), - parameters=parameters, - ) - - -@markers.aws.validated -def test_create_record_set_without_resource_record(deploy_cfn_template, route53_hosted_zone): - create_zone_response = route53_hosted_zone() - hosted_zone_id = create_zone_response["HostedZone"]["Id"] - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/route53_recordset_without_resource_records.yaml", - ), - parameters=parameters, - ) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..HealthCheckConfig.EnableSNI", "$..HealthCheckVersion"] -) -def test_create_health_check(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/route53_healthcheck.yml", - ), - ) - health_check_id = stack.outputs["HealthCheckId"] - print(health_check_id) - health_check = aws_client.route53.get_health_check(HealthCheckId=health_check_id) - - snapshot.add_transformer(snapshot.transform.key_value("Id", "id")) - snapshot.add_transformer(snapshot.transform.key_value("CallerReference", "caller-reference")) - snapshot.match("HealthCheck", health_check["HealthCheck"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.validation.json deleted file mode 100644 index 856faff5c112c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_health_check": { - "last_validated_date": "2023-09-22T11:50:49+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py deleted file mode 100644 index da1be1a4a16d2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py +++ /dev/null @@ -1,155 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_bucketpolicy(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("BucketName")) - bucket_name = f"ls-bucket-{short_uid()}" - snapshot.match("bucket", {"BucketName": bucket_name}) - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucketpolicy.yaml" - ), - parameters={"BucketName": bucket_name}, - template_mapping={"include_policy": True}, - ) - response = aws_client.s3.get_bucket_policy(Bucket=bucket_name)["Policy"] - snapshot.match("get-policy-true", response) - - deploy_cfn_template( - is_update=True, - stack_name=deploy_result.stack_id, - parameters={"BucketName": bucket_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucketpolicy.yaml" - ), - template_mapping={"include_policy": False}, - ) - with pytest.raises(ClientError) as err: - aws_client.s3.get_bucket_policy(Bucket=bucket_name) - snapshot.match("no-policy", err.value.response) - - -@markers.aws.validated -def test_bucket_autoname(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucket_autoname.yaml" - ) - ) - descr_response = aws_client.cloudformation.describe_stacks(StackName=result.stack_id) - output = descr_response["Stacks"][0]["Outputs"][0] - assert output["OutputKey"] == "BucketNameOutput" - assert result.stack_name.lower() in output["OutputValue"] - - -@markers.aws.validated -def test_bucket_versioning(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_versioned_bucket.yaml" - ) - ) - assert "BucketName" in result.outputs - bucket_name = result.outputs["BucketName"] - bucket_version = aws_client.s3.get_bucket_versioning(Bucket=bucket_name) - assert bucket_version["Status"] == "Enabled" - - -@markers.aws.validated -def test_website_configuration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.s3_api()) - - bucket_name_generated = f"ls-bucket-{short_uid()}" - - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucket_website_config.yaml" - ), - parameters={"BucketName": bucket_name_generated}, - ) - - bucket_name = result.outputs["BucketNameOutput"] - assert bucket_name_generated == bucket_name - website_url = result.outputs["WebsiteURL"] - assert website_url.startswith(f"http://{bucket_name}.s3-website") - response = aws_client.s3.get_bucket_website(Bucket=bucket_name) - - snapshot.match("get_bucket_website", response) - - -@markers.aws.validated -def test_cors_configuration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.s3_api()) - - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_cors_bucket.yaml" - ), - ) - bucket_name_optional = result.outputs["BucketNameAllParameters"] - cors_info = aws_client.s3.get_bucket_cors(Bucket=bucket_name_optional) - snapshot.match("cors-info-optional", cors_info) - - bucket_name_required = result.outputs["BucketNameOnlyRequired"] - cors_info = aws_client.s3.get_bucket_cors(Bucket=bucket_name_required) - snapshot.match("cors-info-only-required", cors_info) - - -@markers.aws.validated -def test_object_lock_configuration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.s3_api()) - - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_object_lock_config.yaml" - ), - ) - bucket_name_optional = result.outputs["LockConfigAllParameters"] - cors_info = aws_client.s3.get_object_lock_configuration(Bucket=bucket_name_optional) - snapshot.match("object-lock-info-with-configuration", cors_info) - - bucket_name_required = result.outputs["LockConfigOnlyRequired"] - cors_info = aws_client.s3.get_object_lock_configuration(Bucket=bucket_name_required) - snapshot.match("object-lock-info-only-enabled", cors_info) - - -@markers.aws.validated -def test_cfn_handle_s3_notification_configuration( - aws_client, - deploy_cfn_template, - snapshot, -): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_notification_sqs.yml" - ), - ) - rs = aws_client.s3.get_bucket_notification_configuration(Bucket=stack.outputs["BucketName"]) - snapshot.match("get_bucket_notification_configuration", rs) - - stack.destroy() - - with pytest.raises(ClientError) as ctx: - aws_client.s3.get_bucket_notification_configuration(Bucket=stack.outputs["BucketName"]) - snapshot.match("get_bucket_notification_configuration_error", ctx.value.response) - - snapshot.add_transformer(snapshot.transform.key_value("Id")) - snapshot.add_transformer(snapshot.transform.key_value("QueueArn")) - snapshot.add_transformer(snapshot.transform.key_value("BucketName")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.snapshot.json deleted file mode 100644 index de27f0ba24420..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.snapshot.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cors_configuration": { - "recorded-date": "20-04-2023, 20:17:17", - "recorded-content": { - "cors-info-optional": { - "CORSRules": [ - { - "AllowedHeaders": [ - "*", - "x-amz-*" - ], - "AllowedMethods": [ - "GET" - ], - "AllowedOrigins": [ - "*" - ], - "ExposeHeaders": [ - "Date" - ], - "ID": "test-cors-id", - "MaxAgeSeconds": 3600 - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "cors-info-only-required": { - "CORSRules": [ - { - "AllowedMethods": [ - "GET" - ], - "AllowedOrigins": [ - "*" - ] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_website_configuration": { - "recorded-date": "02-06-2023, 18:24:39", - "recorded-content": { - "get_bucket_website": { - "ErrorDocument": { - "Key": "error.html" - }, - "IndexDocument": { - "Suffix": "index.html" - }, - "RoutingRules": [ - { - "Condition": { - "HttpErrorCodeReturnedEquals": "404", - "KeyPrefixEquals": "out1/" - }, - "Redirect": { - "ReplaceKeyWith": "redirected.html" - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_object_lock_configuration": { - "recorded-date": "15-01-2024, 02:31:58", - "recorded-content": { - "object-lock-info-with-configuration": { - "ObjectLockConfiguration": { - "ObjectLockEnabled": "Enabled", - "Rule": { - "DefaultRetention": { - "Days": 2, - "Mode": "GOVERNANCE" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "object-lock-info-only-enabled": { - "ObjectLockConfiguration": { - "ObjectLockEnabled": "Enabled" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucketpolicy": { - "recorded-date": "31-05-2024, 13:41:44", - "recorded-content": { - "bucket": { - "BucketName": "" - }, - "get-policy-true": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Resource": [ - "arn::s3:::", - "arn::s3:::/*" - ] - } - ] - }, - "no-policy": { - "Error": { - "BucketName": "", - "Code": "NoSuchBucketPolicy", - "Message": "The bucket policy does not exist" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 404 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": { - "recorded-date": "20-06-2024, 16:57:13", - "recorded-content": { - "get_bucket_notification_configuration": { - "QueueConfigurations": [ - { - "Events": [ - "s3:ObjectCreated:*" - ], - "Id": "", - "QueueArn": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_bucket_notification_configuration_error": { - "Error": { - "BucketName": "", - "Code": "NoSuchBucket", - "Message": "The specified bucket does not exist" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 404 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.validation.json deleted file mode 100644 index 2b756e7a7e871..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucket_versioning": { - "last_validated_date": "2024-05-31T13:44:37+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucketpolicy": { - "last_validated_date": "2024-05-31T13:41:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": { - "last_validated_date": "2024-06-20T16:57:13+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cors_configuration": { - "last_validated_date": "2023-04-20T18:17:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_object_lock_configuration": { - "last_validated_date": "2024-01-15T02:31:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_website_configuration": { - "last_validated_date": "2023-06-02T16:24:39+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py deleted file mode 100644 index 6c039975b679e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py +++ /dev/null @@ -1,96 +0,0 @@ -import json -import os -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_sam_policies(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.iam_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sam_function-policies.yaml" - ) - ) - role_name = stack.outputs["HelloWorldFunctionIamRoleName"] - - roles = aws_client.iam.list_attached_role_policies(RoleName=role_name) - assert "AmazonSNSFullAccess" in [p["PolicyName"] for p in roles["AttachedPolicies"]] - snapshot.match("list_attached_role_policies", roles) - - -@markers.aws.validated -def test_sam_template(deploy_cfn_template, aws_client): - # deploy template - func_name = f"test-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template4.yaml" - ), - parameters={"FunctionName": func_name}, - ) - - # run Lambda test invocation - result = aws_client.lambda_.invoke(FunctionName=func_name) - result = json.load(result["Payload"]) - assert result == {"hello": "world"} - - -@markers.aws.validated -def test_sam_sqs_event(deploy_cfn_template, aws_client): - result_key = f"event-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sam_sqs_template.yml" - ), - parameters={"ResultKey": result_key}, - ) - - queue_url = stack.outputs["QueueUrl"] - bucket_name = stack.outputs["BucketName"] - - message_body = "test" - aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody=message_body) - - def get_object(): - return json.loads( - aws_client.s3.get_object(Bucket=bucket_name, Key=result_key)["Body"].read().decode() - )["Records"][0]["body"] - - body = retry(get_object, retries=10, sleep=5.0) - - assert body == message_body - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..tags", "$..Configuration.CodeSha256"]) -def test_cfn_handle_serverless_api_resource(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sam_api.yml" - ), - ) - - response = aws_client.apigateway.get_rest_api(restApiId=stack.outputs["ApiId"]) - snapshot.match("get_rest_api", response) - - response = aws_client.lambda_.get_function(FunctionName=stack.outputs["LambdaFunction"]) - snapshot.match("get_function", response) - - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.snapshot.json deleted file mode 100644 index fe0c314aae224..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.snapshot.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_policies": { - "recorded-date": "11-07-2023, 18:08:53", - "recorded-content": { - "list_attached_role_policies": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/", - "PolicyName": "" - }, - { - "PolicyArn": "arn::iam::aws:policy/", - "PolicyName": "" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_cfn_handle_serverless_api_resource": { - "recorded-date": "15-07-2025, 19:33:25", - "recorded-content": { - "get_rest_api": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "Api", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "version": "1.0", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "W479VjWcFpBg+yx255glPq1ZLEq5WjlmjJi7CmxLFio=", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.11", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "Lambda", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "lambda:createdBy": "SAM" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.validation.json deleted file mode 100644 index 3b1eb14e1cce4..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_cfn_handle_serverless_api_resource": { - "last_validated_date": "2025-07-15T19:33:44+00:00", - "durations_in_seconds": { - "setup": 0.46, - "call": 40.88, - "teardown": 19.65, - "total": 60.99 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_policies": { - "last_validated_date": "2023-07-11T16:08:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_sqs_event": { - "last_validated_date": "2024-04-19T19:45:49+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py deleted file mode 100644 index 5388d26b94a29..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py +++ /dev/null @@ -1,115 +0,0 @@ -import json -import os - -import aws_cdk as cdk -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..VersionIdsToStages"]) -def test_cfn_secretsmanager_gen_secret(deploy_cfn_template, aws_client, snapshot): - secret_name = f"dev/db/pass-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/secretsmanager_secret.yml" - ), - parameters={"SecretName": secret_name}, - ) - - secret = aws_client.secretsmanager.describe_secret(SecretId=secret_name) - snapshot.match("secret", secret) - snapshot.add_transformer(snapshot.transform.regex(rf"{secret_name}-\w+", "")) - snapshot.add_transformer(snapshot.transform.key_value("Name")) - - # assert that secret has been generated and added to the result template JSON - secret_value = aws_client.secretsmanager.get_secret_value(SecretId=secret_name)["SecretString"] - secret_json = json.loads(secret_value) - assert "password" in secret_json - assert len(secret_json["password"]) == 30 - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..VersionIdsToStages"]) -def test_cfn_handle_secretsmanager_secret(deploy_cfn_template, aws_client, snapshot): - secret_name = f"secret-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/secretsmanager_secret.yml" - ), - parameters={"SecretName": secret_name}, - ) - - rs = aws_client.secretsmanager.describe_secret(SecretId=secret_name) - snapshot.match("secret", rs) - snapshot.add_transformer(snapshot.transform.regex(rf"{secret_name}-\w+", "")) - snapshot.add_transformer(snapshot.transform.key_value("Name")) - - stack.destroy() - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.secretsmanager.describe_secret(SecretId=secret_name) - - snapshot.match("exception", ex.value.response) - - -@markers.aws.validated -@pytest.mark.parametrize("block_public_policy", ["true", "default"]) -def test_cfn_secret_policy(deploy_cfn_template, block_public_policy, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/secretsmanager_secret_policy.yml" - ), - parameters={"BlockPublicPolicy": block_public_policy}, - ) - secret_id = stack.outputs["SecretId"] - - snapshot.match("outputs", stack.outputs) - secret_name = stack.outputs["SecretId"].split(":")[-1] - snapshot.add_transformer(snapshot.transform.regex(secret_name, "")) - - res = aws_client.secretsmanager.get_resource_policy(SecretId=secret_id) - snapshot.match("resource_policy", res) - snapshot.add_transformer(snapshot.transform.key_value("Name", "policy-name")) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_cdk_deployment_generates_secret_value_if_no_value_is_provided( - aws_client, snapshot, infrastructure_setup -): - infra = infrastructure_setup(namespace="SecretGeneration") - stack_name = f"SecretGeneration{short_uid()}" - stack = cdk.Stack(infra.cdk_app, stack_name=stack_name) - - secret_name = f"my_secret{short_uid()}" - secret = cdk.aws_secretsmanager.Secret(stack, id=secret_name, secret_name=secret_name) - - cdk.CfnOutput(stack, "SecretName", value=secret.secret_name) - cdk.CfnOutput(stack, "SecretARN", value=secret.secret_arn) - - with infra.provisioner() as prov: - outputs = prov.get_stack_outputs(stack_name=stack_name) - - secret_name = outputs["SecretName"] - secret_arn = outputs["SecretARN"] - - response = aws_client.secretsmanager.get_secret_value(SecretId=secret_name) - - snapshot.add_transformer( - snapshot.transform.key_value("SecretString", reference_replacement=False) - ) - snapshot.add_transformer(snapshot.transform.regex(secret_arn, "")) - snapshot.add_transformer(snapshot.transform.regex(secret_name, "")) - - snapshot.match("generated_key", response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.snapshot.json deleted file mode 100644 index fcf5840b4d1b7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.snapshot.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": { - "recorded-date": "03-07-2024, 18:51:39", - "recorded-content": { - "outputs": { - "SecretId": "arn::secretsmanager::111111111111:secret:", - "SecretPolicyArn": "arn::secretsmanager::111111111111:secret:" - }, - "resource_policy": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "Name": "", - "ResourcePolicy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn::iam::111111111111:root" - }, - "Action": "secretsmanager:ReplicateSecretToRegions", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": { - "recorded-date": "03-07-2024, 18:52:05", - "recorded-content": { - "outputs": { - "SecretId": "arn::secretsmanager::111111111111:secret:", - "SecretPolicyArn": "arn::secretsmanager::111111111111:secret:" - }, - "resource_policy": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "Name": "", - "ResourcePolicy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn::iam::111111111111:root" - }, - "Action": "secretsmanager:ReplicateSecretToRegions", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": { - "recorded-date": "23-05-2024, 17:15:31", - "recorded-content": { - "generated_key": { - "ARN": "", - "CreatedDate": "datetime", - "Name": "", - "SecretString": "secret-string", - "VersionId": "", - "VersionStages": [ - "AWSCURRENT" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": { - "recorded-date": "03-07-2024, 15:39:56", - "recorded-content": { - "secret": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "CreatedDate": "datetime", - "Description": "Aurora Password", - "LastChangedDate": "datetime", - "Name": "", - "Tags": [ - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-63e3fdc5" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "Secret" - }, - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-63e3fdc5/79663e60-3952-11ef-809b-0affeb5ce635" - } - ], - "VersionIdsToStages": { - "2b1f1af7-47ee-aee1-5609-991d4352ae14": [ - "AWSCURRENT" - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": { - "recorded-date": "11-10-2024, 17:00:31", - "recorded-content": { - "secret": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "CreatedDate": "datetime", - "Description": "Aurora Password", - "LastChangedDate": "datetime", - "Name": "", - "Tags": [ - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-ab33fda4" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "Secret" - }, - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-ab33fda4/47ecee80-87f2-11ef-8f16-0a113fcea55f" - } - ], - "VersionIdsToStages": { - "c80fca61-0302-7921-4b9b-c2c16bc6f457": [ - "AWSCURRENT" - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "exception": { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "Secrets Manager can't find the specified secret." - }, - "Message": "Secrets Manager can't find the specified secret.", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.validation.json deleted file mode 100644 index 62afa75a4bedc..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": { - "last_validated_date": "2024-05-23T17:15:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": { - "last_validated_date": "2024-10-11T17:00:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": { - "last_validated_date": "2024-08-01T12:22:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": { - "last_validated_date": "2024-08-01T12:22:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": { - "last_validated_date": "2024-07-03T15:39:56+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py deleted file mode 100644 index 865248c9b80dd..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py +++ /dev/null @@ -1,159 +0,0 @@ -import os.path - -import aws_cdk as cdk -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Attributes.DeliveryPolicy", - "$..Attributes.EffectiveDeliveryPolicy", - "$..Attributes.Policy.Statement..Action", # SNS:Receive is added by moto but not returned in AWS - ] -) -def test_sns_topic_fifo_with_deduplication(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("TopicArn")) - topic_name = f"topic-{short_uid()}.fifo" - - deploy_cfn_template( - parameters={"TopicName": topic_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_fifo_dedup.yaml" - ), - ) - - topics = aws_client.sns.list_topics()["Topics"] - topic_arns = [t["TopicArn"] for t in topics] - - filtered_topics = [t for t in topic_arns if topic_name in t] - assert len(filtered_topics) == 1 - - # assert that the topic is properly created as Fifo - topic_attrs = aws_client.sns.get_topic_attributes(TopicArn=filtered_topics[0]) - snapshot.match("get-topic-attrs", topic_attrs) - - -@markers.aws.needs_fixing -def test_sns_topic_fifo_without_suffix_fails(deploy_cfn_template, aws_client): - stack_name = f"stack-{short_uid()}" - topic_name = f"topic-{short_uid()}" - path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/sns_topic_fifo_dedup.yaml", - ) - - with pytest.raises(Exception) as ex: - deploy_cfn_template( - stack_name=stack_name, template_path=path, parameters={"TopicName": topic_name} - ) - assert ex.typename == "StackDeployError" - - stack = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][0] - if is_aws_cloud(): - assert stack.get("StackStatus") in ["ROLLBACK_COMPLETED", "ROLLBACK_IN_PROGRESS"] - else: - assert stack.get("StackStatus") == "CREATE_FAILED" - - -@markers.aws.validated -def test_sns_subscription(deploy_cfn_template, aws_client): - topic_name = f"topic-{short_uid()}" - queue_name = f"topic-{short_uid()}" - stack = deploy_cfn_template( - parameters={"TopicName": topic_name, "QueueName": queue_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_subscription.yaml" - ), - ) - - topic_arn = stack.outputs["TopicArnOutput"] - assert topic_arn is not None - - subscriptions = aws_client.sns.list_subscriptions_by_topic(TopicArn=topic_arn) - assert len(subscriptions["Subscriptions"]) > 0 - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_deploy_stack_with_sns_topic(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/deploy_template_2.yaml" - ), - parameters={"CompanyName": "MyCompany", "MyEmail1": "my@email.com"}, - ) - assert len(stack.outputs) == 3 - - topic_arn = stack.outputs["MyTopic"] - rs = aws_client.sns.list_topics() - - # Topic resource created - topics = [tp for tp in rs["Topics"] if tp["TopicArn"] == topic_arn] - assert len(topics) == 1 - - stack.destroy() - - # assert topic resource removed - rs = aws_client.sns.list_topics() - topics = [tp for tp in rs["Topics"] if tp["TopicArn"] == topic_arn] - assert not topics - - -@markers.aws.validated -def test_update_subscription(snapshot, deploy_cfn_template, aws_client, sqs_queue, sns_topic): - topic_arn = sns_topic["Attributes"]["TopicArn"] - queue_url = sqs_queue - queue_arn = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - - stack = deploy_cfn_template( - parameters={"TopicArn": topic_arn, "QueueArn": queue_arn}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_subscription.yml" - ), - ) - sub_arn = stack.outputs["SubscriptionArn"] - subscription = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn) - snapshot.match("subscription-1", subscription) - - deploy_cfn_template( - parameters={"TopicArn": topic_arn, "QueueArn": queue_arn}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_subscription_update.yml" - ), - stack_name=stack.stack_name, - is_update=True, - ) - subscription_updated = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn) - snapshot.match("subscription-2", subscription_updated) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - -@markers.aws.validated -def test_sns_topic_with_attributes(infrastructure_setup, aws_client, snapshot): - infra = infrastructure_setup(namespace="SnsTests") - stack_name = f"stack-{short_uid()}" - stack = cdk.Stack(infra.cdk_app, stack_name=stack_name) - - # Add more configurations here conform they are needed to be tested - topic = cdk.aws_sns.Topic(stack, id="Topic", fifo=True, message_retention_period_in_days=30) - - cdk.CfnOutput(stack, "TopicArn", value=topic.topic_arn) - with infra.provisioner() as prov: - outputs = prov.get_stack_outputs(stack_name=stack_name) - response = aws_client.sns.get_topic_attributes( - TopicArn=outputs["TopicArn"], - ) - snapshot.match("topic-archive-policy", response["Attributes"]["ArchivePolicy"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.snapshot.json deleted file mode 100644 index 274530a669eed..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.snapshot.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": { - "recorded-date": "27-11-2023, 21:27:29", - "recorded-content": { - "get-topic-attrs": { - "Attributes": { - "ContentBasedDeduplication": "true", - "DisplayName": "", - "EffectiveDeliveryPolicy": { - "http": { - "defaultHealthyRetryPolicy": { - "minDelayTarget": 20, - "maxDelayTarget": 20, - "numRetries": 3, - "numMaxDelayRetries": 0, - "numNoDelayRetries": 0, - "numMinDelayRetries": 0, - "backoffFunction": "linear" - }, - "disableSubscriptionOverrides": false, - "defaultRequestPolicy": { - "headerContentType": "text/plain; charset=UTF-8" - } - } - }, - "FifoTopic": "true", - "Owner": "111111111111", - "Policy": { - "Version": "2008-10-17", - "Id": "__default_policy_ID", - "Statement": [ - { - "Sid": "__default_statement_ID", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "SNS:GetTopicAttributes", - "SNS:SetTopicAttributes", - "SNS:AddPermission", - "SNS:RemovePermission", - "SNS:DeleteTopic", - "SNS:Subscribe", - "SNS:ListSubscriptionsByTopic", - "SNS:Publish" - ], - "Resource": "", - "Condition": { - "StringEquals": { - "AWS:SourceOwner": "111111111111" - } - } - } - ] - }, - "SubscriptionsConfirmed": "0", - "SubscriptionsDeleted": "0", - "SubscriptionsPending": "0", - "TopicArn": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_update_subscription": { - "recorded-date": "29-03-2024, 21:16:26", - "recorded-content": { - "subscription-1": { - "Attributes": { - "ConfirmationWasAuthenticated": "true", - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "PendingConfirmation": "false", - "Protocol": "sqs", - "RawMessageDelivery": "true", - "SubscriptionArn": "arn::sns::111111111111::", - "SubscriptionPrincipal": "arn::iam::111111111111:user/", - "TopicArn": "arn::sns::111111111111:" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "subscription-2": { - "Attributes": { - "ConfirmationWasAuthenticated": "true", - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "PendingConfirmation": "false", - "Protocol": "sqs", - "RawMessageDelivery": "false", - "SubscriptionArn": "arn::sns::111111111111::", - "SubscriptionPrincipal": "arn::iam::111111111111:user/", - "TopicArn": "arn::sns::111111111111:" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_with_attributes": { - "recorded-date": "16-08-2024, 15:44:50", - "recorded-content": { - "topic-archive-policy": { - "MessageRetentionPeriod": "30" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.validation.json deleted file mode 100644 index a25c4e80b86b8..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": { - "last_validated_date": "2023-11-27T20:27:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_with_attributes": { - "last_validated_date": "2024-08-16T15:44:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_update_subscription": { - "last_validated_date": "2024-03-29T21:16:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py deleted file mode 100644 index 2599e2bb1f520..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py +++ /dev/null @@ -1,150 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_sqs_queue_policy(deploy_cfn_template, aws_client, snapshot): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_with_queuepolicy.yaml" - ) - ) - queue_url = result.outputs["QueueUrlOutput"] - resp = aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]) - snapshot.match("policy", resp) - snapshot.add_transformer(snapshot.transform.key_value("Resource")) - - -@markers.aws.validated -def test_sqs_fifo_queue_generates_valid_name(deploy_cfn_template): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_fifo_autogenerate_name.yaml" - ), - parameters={"IsFifo": "true"}, - max_wait=240, - ) - assert ".fifo" in result.outputs["FooQueueName"] - - -@markers.aws.validated -def test_sqs_non_fifo_queue_generates_valid_name(deploy_cfn_template): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_fifo_autogenerate_name.yaml" - ), - parameters={"IsFifo": "false"}, - max_wait=240, - ) - assert ".fifo" not in result.outputs["FooQueueName"] - - -@markers.aws.validated -def test_cfn_handle_sqs_resource(deploy_cfn_template, aws_client, snapshot): - queue_name = f"queue-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_fifo_queue.yml" - ), - parameters={"QueueName": queue_name}, - ) - - rs = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueURL"], AttributeNames=["All"] - ) - snapshot.match("queue", rs) - snapshot.add_transformer(snapshot.transform.regex(queue_name, "")) - - # clean up - stack.destroy() - - with pytest.raises(ClientError) as ctx: - aws_client.sqs.get_queue_url(QueueName=f"{queue_name}.fifo") - snapshot.match("error", ctx.value.response) - - -@markers.aws.validated -def test_update_queue_no_change(deploy_cfn_template, aws_client, snapshot): - bucket_name = f"bucket-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_queue_update_no_change.yml" - ), - parameters={ - "AddBucket": "false", - "BucketName": bucket_name, - }, - ) - queue_url = stack.outputs["QueueUrl"] - queue_arn = stack.outputs["QueueArn"] - snapshot.add_transformer(snapshot.transform.regex(queue_url, "")) - snapshot.add_transformer(snapshot.transform.regex(queue_arn, "")) - - snapshot.match("outputs-1", stack.outputs) - - # deploy a second time with no change to the SQS queue - updated_stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_queue_update_no_change.yml" - ), - is_update=True, - stack_name=stack.stack_name, - parameters={ - "AddBucket": "true", - "BucketName": bucket_name, - }, - ) - snapshot.match("outputs-2", updated_stack.outputs) - - -@markers.aws.validated -def test_update_sqs_queuepolicy(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_with_queuepolicy.yaml" - ) - ) - - policy = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - snapshot.match("policy1", policy["Attributes"]["Policy"]) - - updated_stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_with_queuepolicy_updated.yaml" - ), - is_update=True, - stack_name=stack.stack_name, - ) - - def check_policy_updated(): - policy_updated = aws_client.sqs.get_queue_attributes( - QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - assert policy_updated["Attributes"]["Policy"] != policy["Attributes"]["Policy"] - return policy_updated - - wait_until(check_policy_updated) - - policy = aws_client.sqs.get_queue_attributes( - QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - - snapshot.match("policy2", policy["Attributes"]["Policy"]) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.snapshot.json deleted file mode 100644 index 860864e9c0b2e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.snapshot.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_queue_no_change": { - "recorded-date": "08-12-2023, 21:11:26", - "recorded-content": { - "outputs-1": { - "QueueArn": "", - "QueueUrl": "" - }, - "outputs-2": { - "QueueArn": "", - "QueueUrl": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_sqs_queuepolicy": { - "recorded-date": "27-03-2024, 20:30:24", - "recorded-content": { - "policy1": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "arn::sqs::111111111111:" - } - ] - }, - "policy2": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Deny", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "arn::sqs::111111111111:" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_queue_policy": { - "recorded-date": "03-07-2024, 19:49:04", - "recorded-content": { - "policy": { - "Attributes": { - "Policy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "" - } - ] - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_cfn_handle_sqs_resource": { - "recorded-date": "03-07-2024, 20:03:51", - "recorded-content": { - "queue": { - "Attributes": { - "ApproximateNumberOfMessages": "0", - "ApproximateNumberOfMessagesDelayed": "0", - "ApproximateNumberOfMessagesNotVisible": "0", - "ContentBasedDeduplication": "false", - "CreatedTimestamp": "timestamp", - "DeduplicationScope": "queue", - "DelaySeconds": "0", - "FifoQueue": "true", - "FifoThroughputLimit": "perQueue", - "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", - "MessageRetentionPeriod": "345600", - "QueueArn": "arn::sqs::111111111111:.fifo", - "ReceiveMessageWaitTimeSeconds": "0", - "SqsManagedSseEnabled": "true", - "VisibilityTimeout": "30" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "error": { - "Error": { - "Code": "AWS.SimpleQueueService.NonExistentQueue", - "Message": "The specified queue does not exist.", - "QueryErrorCode": "QueueDoesNotExist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.validation.json deleted file mode 100644 index 18d7ae6c4fd05..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_cfn_handle_sqs_resource": { - "last_validated_date": "2024-07-03T20:03:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": { - "last_validated_date": "2024-05-15T02:01:00+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": { - "last_validated_date": "2024-05-15T01:59:34+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_queue_policy": { - "last_validated_date": "2024-07-03T19:49:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_queue_no_change": { - "last_validated_date": "2023-12-08T20:11:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_sqs_queuepolicy": { - "last_validated_date": "2024-03-27T20:30:23+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py deleted file mode 100644 index 1d9922d481668..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py +++ /dev/null @@ -1,165 +0,0 @@ -import os.path - -import botocore.exceptions -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) -def test_parameter_defaults(deploy_cfn_template, aws_client, snapshot): - ssm_parameter_value = f"custom-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" - ), - parameters={"Input": ssm_parameter_value}, - ) - - parameter_name = stack.outputs["CustomParameterOutput"] - param = aws_client.ssm.get_parameter(Name=parameter_name) - snapshot.match("ssm_parameter", param) - snapshot.add_transformer(snapshot.transform.key_value("Name")) - snapshot.add_transformer(snapshot.transform.key_value("Value")) - - stack.destroy() - - with pytest.raises(botocore.exceptions.ClientError) as ctx: - aws_client.ssm.get_parameter(Name=parameter_name) - snapshot.match("ssm_parameter_not_found", ctx.value.response) - - -@markers.aws.validated -def test_update_ssm_parameters(deploy_cfn_template, aws_client): - ssm_parameter_value = f"custom-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" - ), - parameters={"Input": ssm_parameter_value}, - ) - - ssm_parameter_value = f"new-custom-{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" - ), - parameters={"Input": ssm_parameter_value}, - ) - - parameter_name = stack.outputs["CustomParameterOutput"] - param = aws_client.ssm.get_parameter(Name=parameter_name) - assert param["Parameter"]["Value"] == ssm_parameter_value - - -@markers.aws.validated -def test_update_ssm_parameter_tag(deploy_cfn_template, aws_client): - ssm_parameter_value = f"custom-{short_uid()}" - tag_value = f"tag-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/ssm_parameter_defaultname_withtags.yaml", - ), - parameters={ - "Input": ssm_parameter_value, - "TagValue": tag_value, - }, - ) - parameter_name = stack.outputs["CustomParameterOutput"] - ssm_tags = aws_client.ssm.list_tags_for_resource( - ResourceType="Parameter", ResourceId=parameter_name - )["TagList"] - tags_pre_update = {tag["Key"]: tag["Value"] for tag in ssm_tags} - assert tags_pre_update["A"] == tag_value - - tag_value_new = f"tag-{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/ssm_parameter_defaultname_withtags.yaml", - ), - parameters={ - "Input": ssm_parameter_value, - "TagValue": tag_value_new, - }, - ) - - ssm_tags = aws_client.ssm.list_tags_for_resource( - ResourceType="Parameter", ResourceId=parameter_name - )["TagList"] - tags_post_update = {tag["Key"]: tag["Value"] for tag in ssm_tags} - assert tags_post_update["A"] == tag_value_new - - # TODO: re-enable after fixing updates in general - # deploy_cfn_template( - # is_update=True, - # stack_name=stack.stack_name, - # template_path=os.path.join( - # os.path.dirname(__file__), "../../templates/ssm_parameter_defaultname.yaml" - # ), - # parameters={ - # "Input": ssm_parameter_value, - # }, - # ) - # - # ssm_tags = aws_client.ssm.list_tags_for_resource(ResourceType="Parameter", ResourceId=parameter_name)['TagList'] - # assert ssm_tags == [] - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.snapshot.skip_snapshot_verify(paths=["$..DriftInformation", "$..Metadata"]) -@markers.aws.validated -def test_deploy_patch_baseline(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_patch_baseline.yml" - ), - ) - - describe_resource = aws_client.cloudformation.describe_stack_resource( - StackName=stack.stack_name, LogicalResourceId="myPatchBaseline" - )["StackResourceDetail"] - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer( - snapshot.transform.key_value("PhysicalResourceId", "physical_resource_id") - ) - snapshot.match("patch_baseline", describe_resource) - - -@markers.aws.validated -def test_maintenance_window(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_maintenance_window.yml" - ), - ) - - describe_resource = aws_client.cloudformation.describe_stack_resources( - StackName=stack.stack_name - )["StackResources"] - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer( - snapshot.transform.key_value("PhysicalResourceId", "physical_resource_id") - ) - snapshot.add_transformer( - SortingTransformer("MaintenanceWindow", lambda x: x["LogicalResourceId"]), priority=-1 - ) - snapshot.match("MaintenanceWindow", describe_resource) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.snapshot.json deleted file mode 100644 index b20140c4c46e1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.snapshot.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_deploy_patch_baseline": { - "recorded-date": "05-07-2023, 10:13:24", - "recorded-content": { - "patch_baseline": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "myPatchBaseline", - "Metadata": {}, - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SSM::PatchBaseline", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_maintenance_window": { - "recorded-date": "14-07-2023, 14:06:23", - "recorded-content": { - "MaintenanceWindow": [ - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchBaselineAML", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::PatchBaseline", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchBaselineAML2", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::PatchBaseline", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchServerMaintenanceWindow", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::MaintenanceWindow", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchServerMaintenanceWindowTarget", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::MaintenanceWindowTarget", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchServerTask", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::MaintenanceWindowTask", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_parameter_defaults": { - "recorded-date": "03-07-2024, 20:30:04", - "recorded-content": { - "ssm_parameter": { - "Parameter": { - "ARN": "arn::ssm::111111111111:parameter/", - "DataType": "text", - "LastModifiedDate": "datetime", - "Name": "", - "Type": "String", - "Value": "", - "Version": 1 - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "ssm_parameter_not_found": { - "Error": { - "Code": "ParameterNotFound", - "Message": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.validation.json deleted file mode 100644 index 3406bb65e62ee..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_deploy_patch_baseline": { - "last_validated_date": "2023-07-05T08:13:24+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_maintenance_window": { - "last_validated_date": "2023-07-14T12:06:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_parameter_defaults": { - "last_validated_date": "2024-07-03T20:30:04+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py deleted file mode 100644 index bae95c05ec516..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py +++ /dev/null @@ -1,87 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.fixture -def wait_stack_set_operation(aws_client): - def waiter(stack_set_name: str, operation_id: str): - def _operation_is_ready(): - operation = aws_client.cloudformation.describe_stack_set_operation( - StackSetName=stack_set_name, - OperationId=operation_id, - ) - return operation["StackSetOperation"]["Status"] not in ["RUNNING", "STOPPING"] - - wait_until(_operation_is_ready) - - return waiter - - -@pytest.mark.skip("CFNV2:StackSets") -@markers.aws.validated -def test_create_stack_set_with_stack_instances( - account_id, - region_name, - aws_client, - snapshot, - wait_stack_set_operation, -): - snapshot.add_transformer(snapshot.transform.key_value("StackSetId", "stack-set-id")) - - stack_set_name = f"StackSet-{short_uid()}" - - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/s3_cors_bucket.yaml") - ) - - result = aws_client.cloudformation.create_stack_set( - StackSetName=stack_set_name, - TemplateBody=template_body, - ) - - snapshot.match("create_stack_set", result) - - create_instances_result = aws_client.cloudformation.create_stack_instances( - StackSetName=stack_set_name, - Accounts=[account_id], - Regions=[region_name], - ) - - snapshot.match("create_stack_instances", create_instances_result) - - wait_stack_set_operation(stack_set_name, create_instances_result["OperationId"]) - - # make sure additional calls do not result in errors - # even the stack already exists, but returns operation id instead - create_instances_result = aws_client.cloudformation.create_stack_instances( - StackSetName=stack_set_name, - Accounts=[account_id], - Regions=[region_name], - ) - - assert "OperationId" in create_instances_result - - wait_stack_set_operation(stack_set_name, create_instances_result["OperationId"]) - - delete_instances_result = aws_client.cloudformation.delete_stack_instances( - StackSetName=stack_set_name, - Accounts=[account_id], - Regions=[region_name], - RetainStacks=False, - ) - wait_stack_set_operation(stack_set_name, delete_instances_result["OperationId"]) - - aws_client.cloudformation.delete_stack_set(StackSetName=stack_set_name) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.snapshot.json deleted file mode 100644 index ef518e6eb430c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.snapshot.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": { - "recorded-date": "24-05-2023, 15:32:47", - "recorded-content": { - "create_stack_set": { - "StackSetId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "create_stack_instances": { - "OperationId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.validation.json deleted file mode 100644 index 157a4655b2589..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": { - "last_validated_date": "2023-05-24T13:32:47+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py deleted file mode 100644 index 8bb3c96039211..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py +++ /dev/null @@ -1,388 +0,0 @@ -import json -import os -import urllib.parse - -import pytest -from localstack_snapshot.snapshots.transformer import JsonpathTransformer - -from localstack import config -from localstack.testing.pytest import markers -from localstack.testing.pytest.stepfunctions.utils import await_execution_terminated -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - - -@markers.aws.validated -def test_statemachine_definitionsubstitution(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/stepfunctions_statemachine_substitutions.yaml", - ) - ) - - assert len(stack.outputs) == 1 - statemachine_arn = stack.outputs["StateMachineArnOutput"] - - # execute statemachine - ex_result = aws_client.stepfunctions.start_execution(stateMachineArn=statemachine_arn) - - def _is_executed(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=ex_result["executionArn"])[ - "status" - ] - != "RUNNING" - ) - - wait_until(_is_executed) - execution_desc = aws_client.stepfunctions.describe_execution( - executionArn=ex_result["executionArn"] - ) - assert execution_desc["status"] == "SUCCEEDED" - # sync execution is currently not supported since botocore adds a "sync-" prefix - # ex_result = stepfunctions_client.start_sync_execution(stateMachineArn=statemachine_arn) - - assert "hello from statemachine" in execution_desc["output"] - - -@pytest.mark.skip( - reason="CFNV2:Other During change set describe the a Ref to a not yet deployed resource returns null which is an invalid input for Fn::Split" -) -@markers.aws.validated -def test_nested_statemachine_with_sync2(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_nested_sync2.json" - ) - ) - - parent_arn = stack.outputs["ParentStateMachineArnOutput"] - assert parent_arn - - ex_result = aws_client.stepfunctions.start_execution( - stateMachineArn=parent_arn, input='{"Value": 1}' - ) - - def _is_executed(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=ex_result["executionArn"])[ - "status" - ] - != "RUNNING" - ) - - wait_until(_is_executed) - execution_desc = aws_client.stepfunctions.describe_execution( - executionArn=ex_result["executionArn"] - ) - assert execution_desc["status"] == "SUCCEEDED" - output = json.loads(execution_desc["output"]) - assert output["Value"] == 3 - - -@markers.aws.needs_fixing -def test_apigateway_invoke(deploy_cfn_template, aws_client): - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_apigateway.yaml" - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello from stepfunctions" in execution_result["output"] - - -@markers.aws.validated -def test_apigateway_invoke_with_path(deploy_cfn_template, aws_client): - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/sfn_apigateway_two_integrations.yaml", - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello_with_path from stepfunctions" in execution_result["output"] - - -@markers.aws.only_localstack -def test_apigateway_invoke_localhost(deploy_cfn_template, aws_client): - """tests the same as above but with the "generic" localhost version of invoking the apigateway""" - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_apigateway.yaml" - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - api_url = deploy_result.outputs["LsApiEndpointA06D37E8"] - - # instead of changing the template, we're just mapping the endpoint here to the more generic path-based version - state_def = aws_client.stepfunctions.describe_state_machine(stateMachineArn=state_machine_arn)[ - "definition" - ] - parsed = urllib.parse.urlparse(api_url) - api_id = parsed.hostname.split(".")[0] - state = json.loads(state_def) - stage = state["States"]["LsCallApi"]["Parameters"]["Stage"] - state["States"]["LsCallApi"]["Parameters"]["ApiEndpoint"] = ( - f"{config.internal_service_url()}/restapis/{api_id}" - ) - state["States"]["LsCallApi"]["Parameters"]["Stage"] = stage - - aws_client.stepfunctions.update_state_machine( - stateMachineArn=state_machine_arn, definition=json.dumps(state) - ) - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello from stepfunctions" in execution_result["output"] - - -@markers.aws.only_localstack -def test_apigateway_invoke_localhost_with_path(deploy_cfn_template, aws_client): - """tests the same as above but with the "generic" localhost version of invoking the apigateway""" - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/sfn_apigateway_two_integrations.yaml", - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - api_url = deploy_result.outputs["LsApiEndpointA06D37E8"] - - # instead of changing the template, we're just mapping the endpoint here to the more generic path-based version - state_def = aws_client.stepfunctions.describe_state_machine(stateMachineArn=state_machine_arn)[ - "definition" - ] - parsed = urllib.parse.urlparse(api_url) - api_id = parsed.hostname.split(".")[0] - state = json.loads(state_def) - stage = state["States"]["LsCallApi"]["Parameters"]["Stage"] - state["States"]["LsCallApi"]["Parameters"]["ApiEndpoint"] = ( - f"{config.internal_service_url()}/restapis/{api_id}" - ) - state["States"]["LsCallApi"]["Parameters"]["Stage"] = stage - - aws_client.stepfunctions.update_state_machine( - stateMachineArn=state_machine_arn, definition=json.dumps(state) - ) - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello_with_path from stepfunctions" in execution_result["output"] - - -@pytest.mark.skip("Terminates with FAILED on cloud; convert to SFN v2 snapshot lambda test.") -@markers.aws.needs_fixing -def test_retry_and_catch(deploy_cfn_template, aws_client): - """ - Scenario: - - Lambda invoke (incl. 3 retries) - => catch (Send SQS message with body "Fail") - => next (Send SQS message with body "Success") - - The Lambda function simply raises an Exception, so it will always fail. - It should fail all 4 attempts (1x invoke + 3x retries) which should then trigger the catch path - and send a "Fail" message to the queue. - """ - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_retry_catch.yaml" - ) - ) - queue_url = stack.outputs["queueUrlOutput"] - statemachine_arn = stack.outputs["smArnOutput"] - assert statemachine_arn - - execution = aws_client.stepfunctions.start_execution(stateMachineArn=statemachine_arn) - execution_arn = execution["executionArn"] - - await_execution_terminated(aws_client.stepfunctions, execution_arn) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - - receive_result = aws_client.sqs.receive_message(QueueUrl=queue_url, WaitTimeSeconds=5) - assert receive_result["Messages"][0]["Body"] == "Fail" - - -@markers.aws.validated -def test_cfn_statemachine_with_dependencies(deploy_cfn_template, aws_client): - sm_name = f"sm_{short_uid()}" - activity_name = f"act_{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/statemachine_machine_with_activity.yml", - ), - max_wait=150, - parameters={"StateMachineName": sm_name, "ActivityName": activity_name}, - ) - - rs = aws_client.stepfunctions.list_state_machines() - statemachines = [sm for sm in rs["stateMachines"] if sm_name in sm["name"]] - assert len(statemachines) == 1 - - rs = aws_client.stepfunctions.list_activities() - activities = [act for act in rs["activities"] if activity_name in act["name"]] - assert len(activities) == 1 - - stack.destroy() - - rs = aws_client.stepfunctions.list_state_machines() - statemachines = [sm for sm in rs["stateMachines"] if sm_name in sm["name"]] - - assert not statemachines - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..encryptionConfiguration", "$..tracingConfiguration"] -) -def test_cfn_statemachine_default_s3_location( - s3_create_bucket, deploy_cfn_template, aws_client, sfn_snapshot -): - sfn_snapshot.add_transformers_list( - [ - JsonpathTransformer("$..roleArn", "role-arn"), - JsonpathTransformer("$..stateMachineArn", "state-machine-arn"), - JsonpathTransformer("$..name", "state-machine-name"), - ] - ) - cfn_template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/statemachine_machine_default_s3_location.yml", - ) - - stack_name = f"test-cfn-statemachine-default-s3-location-{short_uid()}" - - file_key = f"file-key-{short_uid()}.json" - bucket_name = s3_create_bucket() - state_machine_template = { - "Comment": "step: on create", - "StartAt": "S0", - "States": {"S0": {"Type": "Succeed"}}, - } - - aws_client.s3.put_object( - Bucket=bucket_name, Key=file_key, Body=json.dumps(state_machine_template) - ) - - stack = deploy_cfn_template( - stack_name=stack_name, - template_path=cfn_template_path, - max_wait=150, - parameters={"BucketName": bucket_name, "ObjectKey": file_key}, - ) - - stack_outputs = stack.outputs - statemachine_arn = stack_outputs["StateMachineArnOutput"] - - describe_state_machine_output_on_create = aws_client.stepfunctions.describe_state_machine( - stateMachineArn=statemachine_arn - ) - sfn_snapshot.match( - "describe_state_machine_output_on_create", describe_state_machine_output_on_create - ) - - file_key = f"2-{file_key}" - state_machine_template["Comment"] = "step: on update" - aws_client.s3.put_object( - Bucket=bucket_name, Key=file_key, Body=json.dumps(state_machine_template) - ) - deploy_cfn_template( - stack_name=stack_name, - template_path=cfn_template_path, - is_update=True, - parameters={"BucketName": bucket_name, "ObjectKey": file_key}, - ) - - describe_state_machine_output_on_update = aws_client.stepfunctions.describe_state_machine( - stateMachineArn=statemachine_arn - ) - sfn_snapshot.match( - "describe_state_machine_output_on_update", describe_state_machine_output_on_update - ) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..encryptionConfiguration", "$..tracingConfiguration"] -) -def test_statemachine_create_with_logging_configuration( - deploy_cfn_template, aws_client, sfn_snapshot -): - sfn_snapshot.add_transformers_list( - [ - JsonpathTransformer("$..roleArn", "role-arn"), - JsonpathTransformer("$..stateMachineArn", "state-machine-arn"), - JsonpathTransformer("$..name", "state-machine-name"), - JsonpathTransformer("$..logGroupArn", "log-group-arn"), - ] - ) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/statemachine_machine_logging_configuration.yml", - ) - ) - statemachine_arn = stack.outputs["StateMachineArnOutput"] - describe_state_machine_result = aws_client.stepfunctions.describe_state_machine( - stateMachineArn=statemachine_arn - ) - sfn_snapshot.match("describe_state_machine_result", describe_state_machine_result) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.snapshot.json deleted file mode 100644 index d0fc2a3e304de..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.snapshot.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": { - "recorded-date": "17-12-2024, 16:06:46", - "recorded-content": { - "describe_state_machine_output_on_create": { - "creationDate": "datetime", - "definition": { - "Comment": "step: on create", - "StartAt": "S0", - "States": { - "S0": { - "Type": "Succeed" - } - } - }, - "encryptionConfiguration": { - "type": "AWS_OWNED_KEY" - }, - "loggingConfiguration": { - "includeExecutionData": false, - "level": "OFF" - }, - "name": "", - "roleArn": "", - "stateMachineArn": "", - "status": "ACTIVE", - "tracingConfiguration": { - "enabled": false - }, - "type": "STANDARD", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_state_machine_output_on_update": { - "creationDate": "datetime", - "definition": { - "Comment": "step: on update", - "StartAt": "S0", - "States": { - "S0": { - "Type": "Succeed" - } - } - }, - "encryptionConfiguration": { - "type": "AWS_OWNED_KEY" - }, - "loggingConfiguration": { - "includeExecutionData": false, - "level": "OFF" - }, - "name": "", - "revisionId": "", - "roleArn": "", - "stateMachineArn": "", - "status": "ACTIVE", - "tracingConfiguration": { - "enabled": false - }, - "type": "STANDARD", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": { - "recorded-date": "24-03-2025, 21:58:55", - "recorded-content": { - "describe_state_machine_result": { - "creationDate": "datetime", - "definition": { - "StartAt": "S0", - "States": { - "S0": { - "Type": "Pass", - "End": true - } - } - }, - "encryptionConfiguration": { - "type": "AWS_OWNED_KEY" - }, - "loggingConfiguration": { - "destinations": [ - { - "cloudWatchLogsLogGroup": { - "logGroupArn": "" - } - } - ], - "includeExecutionData": true, - "level": "ALL" - }, - "name": "", - "roleArn": "", - "stateMachineArn": "", - "status": "ACTIVE", - "tracingConfiguration": { - "enabled": false - }, - "type": "STANDARD", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.validation.json deleted file mode 100644 index 267fe6634138d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": { - "last_validated_date": "2024-12-17T16:06:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": { - "last_validated_date": "2025-03-24T21:58:55+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py deleted file mode 100644 index 542d28c39a52f..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py +++ /dev/null @@ -1,1288 +0,0 @@ -import base64 -import json -import os -import re -from copy import deepcopy - -import botocore.exceptions -import pytest -import yaml - -from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.engine.yaml_parser import parse_yaml -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.cloudformation_utils import load_template_file, load_template_raw -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.pytest.fixtures import StackDeployError -from localstack.utils.common import short_uid -from localstack.utils.files import load_file -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -def create_macro( - macro_name, function_path, deploy_cfn_template, create_lambda_function, lambda_client -): - macro_function_path = function_path - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=lambda_client, - timeout=1, - ) - - return deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - -class TestTypes: - @markers.aws.validated - def test_implicit_type_conversion(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.sqs_api()) - stack = deploy_cfn_template( - max_wait=180, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates/engine/implicit_type_conversion.yml", - ), - ) - queue = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueUrl"], AttributeNames=["All"] - ) - snapshot.match("queue", queue) - - -class TestIntrinsicFunctions: - @pytest.mark.parametrize( - ("intrinsic_fn", "parameter_1", "parameter_2", "expected_bucket_created"), - [ - ("Fn::And", "0", "0", False), - ("Fn::And", "0", "1", False), - ("Fn::And", "1", "0", False), - ("Fn::And", "1", "1", True), - ("Fn::Or", "0", "0", False), - ("Fn::Or", "0", "1", True), - ("Fn::Or", "1", "0", True), - ("Fn::Or", "1", "1", True), - ], - ) - @markers.aws.validated - def test_and_or_functions( - self, - intrinsic_fn, - parameter_1, - parameter_2, - expected_bucket_created, - deploy_cfn_template, - aws_client, - ): - bucket_name = f"ls-bucket-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_intrinsic_functions.yaml" - ), - parameters={ - "Param1": parameter_1, - "Param2": parameter_2, - "BucketName": bucket_name, - }, - template_mapping={ - "intrinsic_fn": intrinsic_fn, - }, - ) - - buckets = aws_client.s3.list_buckets() - bucket_names = [b["Name"] for b in buckets["Buckets"]] - assert (bucket_name in bucket_names) == expected_bucket_created - - @markers.aws.validated - def test_base64_sub_and_getatt_functions(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_getatt_sub_base64.yml" - ) - original_string = f"string-{short_uid()}" - deployed = deploy_cfn_template( - template_path=template_path, parameters={"OriginalString": original_string} - ) - - converted_string = base64.b64encode(bytes(original_string, "utf-8")).decode("utf-8") - assert converted_string == deployed.outputs["Encoded"] - - @pytest.mark.skip(reason="CFNV2:LanguageExtensions") - @markers.aws.validated - def test_split_length_and_join_functions(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_select_split_join.yml" - ) - - first_value = f"string-{short_uid()}" - second_value = f"string-{short_uid()}" - deployed = deploy_cfn_template( - template_path=template_path, - parameters={ - "MultipleValues": f"{first_value};{second_value}", - "Value1": first_value, - "Value2": second_value, - }, - ) - - assert first_value == deployed.outputs["SplitResult"] - assert f"{first_value}_{second_value}" == deployed.outputs["JoinResult"] - - # TODO support join+split and length operations - # assert f"{first_value}_{second_value}" == deployed.outputs["SplitJoin"] - # assert 2 == deployed.outputs["LengthResult"] - - @markers.aws.validated - @pytest.mark.skip(reason="functions not currently supported") - def test_to_json_functions(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/function_to_json_string.yml" - ) - - first_value = f"string-{short_uid()}" - second_value = f"string-{short_uid()}" - deployed = deploy_cfn_template( - template_path=template_path, - parameters={ - "Value1": first_value, - "Value2": second_value, - }, - ) - - json_result = json.loads(deployed.outputs["Result"]) - - assert json_result["key1"] == first_value - assert json_result["key2"] == second_value - assert "value1" == deployed.outputs["Result2"] - - @markers.aws.validated - def test_find_map_function(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/function_find_in_map.yml" - ) - - deployed = deploy_cfn_template( - template_path=template_path, - ) - - assert deployed.outputs["Result"] == "us-east-1" - - @markers.aws.validated - @pytest.mark.skip(reason="function not currently supported") - def test_cidr_function(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_cidr.yml" - ) - - # TODO parametrize parameters and result - deployed = deploy_cfn_template( - template_path=template_path, - parameters={"IpBlock": "10.0.0.0/16", "Count": "1", "CidrBits": "8", "Select": "0"}, - ) - - assert deployed.outputs["Address"] == "10.0.0.0/24" - - @pytest.mark.parametrize( - "region", - [ - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2", - "ap-southeast-2", - "ap-northeast-1", - "eu-central-1", - "eu-west-1", - ], - ) - @markers.aws.validated - def test_get_azs_function(self, deploy_cfn_template, region, aws_client_factory): - """ - TODO parametrize this test. - For that we need to be able to parametrize the client region. The docs show the we should be - able to put any region in the parameters but it doesn't work. It only accepts the same region from the client config - if you put anything else it just returns an empty list. - """ - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_get_azs.yml" - ) - - aws_client = aws_client_factory(region_name=region) - deployed = deploy_cfn_template( - template_path=template_path, - custom_aws_client=aws_client, - parameters={"DeployRegion": region}, - ) - - azs = deployed.outputs["Zones"].split(";") - assert len(azs) > 0 - assert all(re.match(f"{region}[a-f]", az) for az in azs) - - @markers.aws.validated - def test_sub_not_ready(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/sub_dependencies.yaml" - ) - deploy_cfn_template( - template_path=template_path, - max_wait=120, - ) - - @markers.aws.validated - def test_cfn_template_with_short_form_fn_sub(self, deploy_cfn_template): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/engine/cfn_short_sub.yml" - ), - ) - - result = stack.outputs["Result"] - assert result == "test" - - @markers.aws.validated - def test_sub_number_type(self, deploy_cfn_template): - alarm_name_prefix = "alarm-test-latency-preemptive" - threshold = "1000.0" - period = "60" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/sub_number_type.yml" - ), - parameters={ - "ResourceNamePrefix": alarm_name_prefix, - "RestLatencyPreemptiveAlarmThreshold": threshold, - "RestLatencyPreemptiveAlarmPeriod": period, - }, - ) - - assert stack.outputs["AlarmName"] == f"{alarm_name_prefix}-{period}" - assert stack.outputs["Threshold"] == threshold - assert stack.outputs["Period"] == period - - @markers.aws.validated - def test_join_no_value_construct(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/engine/join_no_value.yml" - ) - ) - - snapshot.match("join-output", stack.outputs) - - -@pytest.mark.skip(reason="CFNV2:Imports unsupported") -class TestImports: - @markers.aws.validated - def test_stack_imports(self, deploy_cfn_template, aws_client): - queue_name1 = f"q-{short_uid()}" - queue_name2 = f"q-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/sqs_export.yml" - ), - parameters={"QueueName": queue_name1}, - ) - stack2 = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/sqs_import.yml" - ), - parameters={"QueueName": queue_name2}, - ) - queue_url1 = aws_client.sqs.get_queue_url(QueueName=queue_name1)["QueueUrl"] - queue_url2 = aws_client.sqs.get_queue_url(QueueName=queue_name2)["QueueUrl"] - - queue_arn1 = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url1, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - queue_arn2 = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url2, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - - assert stack2.outputs["MessageQueueArn1"] == queue_arn1 - assert stack2.outputs["MessageQueueArn2"] == queue_arn2 - - -@pytest.mark.skip(reason="CFNV2:resolve") -class TestSsmParameters: - @markers.aws.validated - def test_create_stack_with_ssm_parameters( - self, create_parameter, deploy_cfn_template, snapshot, aws_client - ): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("ParameterValue")) - snapshot.add_transformer(snapshot.transform.key_value("ResolvedValue")) - - parameter_name = f"ls-param-{short_uid()}" - parameter_value = f"ls-param-value-{short_uid()}" - create_parameter(Name=parameter_name, Value=parameter_value, Type="String") - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/dynamicparameter_ssm_string.yaml" - ), - template_mapping={"parameter_name": parameter_name}, - ) - - stack_description = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)[ - "Stacks" - ][0] - snapshot.match("stack-details", stack_description) - - topics = aws_client.sns.list_topics() - topic_arns = [t["TopicArn"] for t in topics["Topics"]] - - matching = [arn for arn in topic_arns if parameter_value in arn] - assert len(matching) == 1 - - tags = aws_client.sns.list_tags_for_resource(ResourceArn=matching[0]) - snapshot.match("topic-tags", tags) - - @markers.aws.validated - def test_resolve_ssm(self, create_parameter, deploy_cfn_template): - parameter_key = f"param-key-{short_uid()}" - parameter_value = f"param-value-{short_uid()}" - create_parameter(Name=parameter_key, Value=parameter_value, Type="String") - - result = deploy_cfn_template( - parameters={"DynamicParameter": parameter_key}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/resolve_ssm.yaml" - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value - - @markers.aws.validated - def test_resolve_ssm_with_version(self, create_parameter, deploy_cfn_template, aws_client): - parameter_key = f"param-key-{short_uid()}" - parameter_value_v0 = f"param-value-{short_uid()}" - parameter_value_v1 = f"param-value-{short_uid()}" - parameter_value_v2 = f"param-value-{short_uid()}" - - create_parameter(Name=parameter_key, Type="String", Value=parameter_value_v0) - - v1 = aws_client.ssm.put_parameter( - Name=parameter_key, Overwrite=True, Type="String", Value=parameter_value_v1 - ) - aws_client.ssm.put_parameter( - Name=parameter_key, Overwrite=True, Type="String", Value=parameter_value_v2 - ) - - result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}:{v1['Version']}"}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/resolve_ssm.yaml" - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value_v1 - - @markers.aws.needs_fixing - def test_resolve_ssm_secure(self, create_parameter, deploy_cfn_template): - parameter_key = f"param-key-{short_uid()}" - parameter_value = f"param-value-{short_uid()}" - - create_parameter(Name=parameter_key, Value=parameter_value, Type="SecureString") - - result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}"}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/resolve_ssm_secure.yaml" - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value - - @markers.aws.validated - def test_ssm_nested_with_nested_stack(self, s3_create_bucket, deploy_cfn_template, aws_client): - """ - When resolving the references in the cloudformation template for 'Fn::GetAtt' we need to consider the attribute subname. - Eg: In "Fn::GetAtt": "ChildParam.Outputs.Value", where attribute reference is ChildParam.Outputs.Value the: - resource logical id is ChildParam and attribute name is Outputs we need to fetch the Value attribute from the resource properties - of the model instance. - """ - - bucket_name = s3_create_bucket() - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../templates/nested_child_ssm.yaml"), - Bucket=bucket_name, - Key="nested_child_ssm.yaml", - ) - - key_value = "child-2-param-name" - - deploy_cfn_template( - max_wait=120 if is_aws_cloud() else 20, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/nested_parent_ssm.yaml" - ), - parameters={ - "ChildStackURL": f"https://{bucket_name}.s3.{domain}/nested_child_ssm.yaml", - "KeyValue": key_value, - }, - ) - - ssm_parameter = aws_client.ssm.get_parameter(Name="test-param")["Parameter"]["Value"] - - assert ssm_parameter == key_value - - @markers.aws.validated - def test_create_change_set_with_ssm_parameter_list( - self, deploy_cfn_template, aws_client, region_name, account_id, snapshot - ): - snapshot.add_transformer(snapshot.transform.key_value(key="role-name")) - - parameter_logical_id = "parameter123" - parameter_name = f"ls-param-{short_uid()}" - role_name = f"ls-role-{short_uid()}" - parameter_value = ",".join( - [ - f"arn:aws:ssm:{region_name}:{account_id}:parameter/some/params", - f"arn:aws:ssm:{region_name}:{account_id}:parameter/some/other/params", - ] - ) - snapshot.match("role-name", role_name) - - aws_client.ssm.put_parameter(Name=parameter_name, Value=parameter_value, Type="StringList") - - deploy_cfn_template( - max_wait=120 if is_aws_cloud() else 20, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/dynamicparameter_ssm_list.yaml" - ), - template_mapping={"role_name": role_name}, - parameters={parameter_logical_id: parameter_name}, - ) - role_policy = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName="policy-123") - snapshot.match("iam_role_policy", role_policy) - - -class TestSecretsManagerParameters: - @pytest.mark.skip(reason="CFNV2:resolve") - @pytest.mark.parametrize( - "template_name", - [ - "resolve_secretsmanager_full.yaml", - "resolve_secretsmanager_partial.yaml", - "resolve_secretsmanager.yaml", - ], - ) - @markers.aws.validated - def test_resolve_secretsmanager(self, create_secret, deploy_cfn_template, template_name): - parameter_key = f"param-key-{short_uid()}" - parameter_value = f"param-value-{short_uid()}" - - create_secret(Name=parameter_key, SecretString=parameter_value) - - result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}"}, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates", - template_name, - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value - - -class TestPreviousValues: - @pytest.mark.skip(reason="outputs don't behave well in combination with conditions") - @markers.aws.validated - def test_parameter_usepreviousvalue_behavior( - self, deploy_cfn_template, is_stack_updated, aws_client - ): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_reuse_param.yaml" - ) - - # 1. create with overridden default value. Due to the condition this should neither create the optional topic, - # nor the corresponding output - stack = deploy_cfn_template(template_path=template_path, parameters={"DeployParam": "no"}) - - stack_describe_response = aws_client.cloudformation.describe_stacks( - StackName=stack.stack_name - )["Stacks"][0] - assert len(stack_describe_response["Outputs"]) == 1 - - # 2. update using UsePreviousValue. DeployParam should still be "no", still overriding the default and the only - # change should be the changed tag on the required topic - aws_client.cloudformation.update_stack( - StackName=stack.stack_namestack_name, - TemplateBody=load_template_raw(template_path), - Parameters=[ - {"ParameterKey": "CustomTag", "ParameterValue": "trigger-change"}, - {"ParameterKey": "DeployParam", "UsePreviousValue": True}, - ], - ) - wait_until(is_stack_updated(stack.stack_id)) - stack_describe_response = aws_client.cloudformation.describe_stacks( - StackName=stack.stack_name - )["Stacks"][0] - assert len(stack_describe_response["Outputs"]) == 1 - - # 3. update with setting the deployparam to "yes" not. The condition will evaluate to true and thus create the - # topic + output note: for an even trickier challenge for the cloudformation engine, remove the second parameter - # key. Behavior should stay the same. - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=load_template_raw(template_path), - Parameters=[ - {"ParameterKey": "CustomTag", "ParameterValue": "trigger-change-2"}, - {"ParameterKey": "DeployParam", "ParameterValue": "yes"}, - ], - ) - assert is_stack_updated(stack.stack_id) - stack_describe_response = aws_client.cloudformation.describe_stacks( - StackName=stack.stack_id - )["Stacks"][0] - assert len(stack_describe_response["Outputs"]) == 2 - - -@pytest.mark.skip(reason="CFNV2:Imports unsupported") -class TestImportValues: - @markers.aws.validated - def test_cfn_with_exports(self, deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/engine/cfn_exports.yml" - ) - ) - - exports = aws_client.cloudformation.list_exports()["Exports"] - filtered = [exp for exp in exports if exp["ExportingStackId"] == stack.stack_id] - filtered.sort(key=lambda x: x["Name"]) - - snapshot.match("exports", filtered) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) - - @markers.aws.validated - def test_import_values_across_stacks(self, deploy_cfn_template, aws_client): - export_name = f"b-{short_uid()}" - - # create stack #1 - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_function_export.yml" - ), - parameters={"BucketExportName": export_name}, - ) - bucket_name1 = result.outputs.get("BucketName1") - assert bucket_name1 - - # create stack #2 - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_function_import.yml" - ), - parameters={"BucketExportName": export_name}, - ) - bucket_name2 = result.outputs.get("BucketName2") - assert bucket_name2 - - # assert that correct bucket tags have been created - tagging = aws_client.s3.get_bucket_tagging(Bucket=bucket_name2) - test_tag = [tag for tag in tagging["TagSet"] if tag["Key"] == "test"] - assert test_tag - assert test_tag[0]["Value"] == bucket_name1 - - # TODO support this method - # assert cfn_client.list_imports(ExportName=export_name)["Imports"] - - -class TestMacros: - @markers.aws.validated - def test_macro_deployment( - self, deploy_cfn_template, create_lambda_function, snapshot, aws_client - ): - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/format_template.py" - ) - macro_name = "SubstitutionMacro" - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - ) - - stack_with_macro = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - description = aws_client.cloudformation.describe_stack_resources( - StackName=stack_with_macro.stack_name - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("stack_outputs", stack_with_macro.outputs) - snapshot.match("stack_resource_descriptions", description) - - @pytest.mark.skip("CFNV2:GetTemplate") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..TemplateBody.Resources.Parameter.LogicalResourceId", - "$..TemplateBody.Conditions", - "$..TemplateBody.Mappings", - "$..TemplateBody.StackId", - "$..TemplateBody.StackName", - "$..TemplateBody.Transform", - ] - ) - def test_global_scope( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - This test validates the behaviour of a template deployment that includes a global transformation - """ - - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/format_template.py" - ) - macro_name = "SubstitutionMacro" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - new_value = f"new-value-{short_uid()}" - stack_name = f"stake-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - TemplateBody=load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_global_parameter.yml", - ) - ), - Parameters=[{"ParameterKey": "Substitution", "ParameterValue": new_value}], - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.add_transformer(snapshot.transform.regex(new_value, "new-value")) - snapshot.match("processed_template", processed_template) - - @pytest.mark.skip( - reason="CFNV2:Fn::Transform as resource property with missing Name and Parameters fields." - ) - @markers.aws.validated - @pytest.mark.parametrize( - "template_to_transform", - ["transformation_snippet_topic.yml", "transformation_snippet_topic.json"], - ) - def test_snipped_scope( - self, - deploy_cfn_template, - create_lambda_function, - snapshot, - template_to_transform, - aws_client, - ): - """ - This test validates the behaviour of a template deployment that includes a snipped transformation also the - responses from the get_template with different template formats. - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/add_standard_attributes.py" - ) - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - macro_name = "ConvertTopicToFifo" - stack_name = f"stake-macro-{short_uid()}" - deploy_cfn_template( - stack_name=stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - topic_name = f"topic-{short_uid()}.fifo" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates", - template_to_transform, - ), - parameters={"TopicName": topic_name}, - ) - original_template = aws_client.cloudformation.get_template( - StackName=stack.stack_name, TemplateStage="Original" - ) - processed_template = aws_client.cloudformation.get_template( - StackName=stack.stack_name, TemplateStage="Processed" - ) - snapshot.add_transformer(snapshot.transform.regex(topic_name, "topic-name")) - - snapshot.match("original_template", original_template) - snapshot.match("processed_template", processed_template) - - @markers.aws.validated - def test_attribute_uses_macro(self, deploy_cfn_template, create_lambda_function, aws_client): - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/return_random_string.py" - ) - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - ) - - macro_name = "GenerateRandom" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates", - "transformation_resource_att.yml", - ), - parameters={"Input": "test"}, - ) - - resulting_value = stack.outputs["Parameter"] - assert "test-" in resulting_value - - @markers.aws.validated - @pytest.mark.skip(reason="Fn::Transform does not support array of transformations") - def test_scope_order_and_parameters( - self, deploy_cfn_template, create_lambda_function, snapshot, aws_client - ): - """ - The test validates the order of execution of transformations and also asserts that any type of - transformation can receive inputs. - """ - - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/replace_string.py" - ) - macro_name = "ReplaceString" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_multiple_scope_parameter.yml", - ), - ) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack.stack_name, TemplateStage="Processed" - ) - snapshot.match("processed_template", processed_template) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..TemplateBody.Resources.Parameter.LogicalResourceId", - "$..TemplateBody.Conditions", - "$..TemplateBody.Mappings", - "$..TemplateBody.Parameters", - "$..TemplateBody.StackId", - "$..TemplateBody.StackName", - "$..TemplateBody.Transform", - "$..TemplateBody.Resources.Role.LogicalResourceId", - ] - ) - def test_capabilities_requirements( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - The test validates that AWS will return an error about missing CAPABILITY_AUTOEXPAND when adding a - resource during the transformation, and it will ask for CAPABILITY_NAMED_IAM when the new resource is a - IAM role - """ - - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/add_role.py" - ) - macro_name = "AddRole" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack_name = f"stack-{short_uid()}" - args = { - "StackName": stack_name, - "TemplateBody": load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_add_role.yml", - ) - ), - } - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack(**args) - snapshot.match("error", ex.value.response) - - args["Capabilities"] = [ - "CAPABILITY_AUTO_EXPAND", # Required to allow macro to add a role to template - "CAPABILITY_NAMED_IAM", # Required to allow CFn create added role - ] - aws_client.cloudformation.create_stack(**args) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.add_transformer(snapshot.transform.key_value("RoleName", "role-name")) - snapshot.match("processed_template", processed_template) - - @pytest.mark.skip("CFNV2:GetTemplate") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Event.fragment.Conditions", - "$..Event.fragment.Mappings", - "$..Event.fragment.Outputs", - "$..Event.fragment.Resources.Parameter.LogicalResourceId", - "$..Event.fragment.StackId", - "$..Event.fragment.StackName", - "$..Event.fragment.Transform", - ] - ) - def test_validate_lambda_internals( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - The test validates the content of the event pass into the macro lambda - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/print_internals.py" - ) - - macro_name = "PrintInternals" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack_name = f"stake-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - TemplateBody=load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_print_internals.yml", - ) - ), - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.match( - "event", - processed_template["TemplateBody"]["Resources"]["Parameter"]["Properties"]["Value"], - ) - - @pytest.mark.skip("CFNV2:Validation") - @markers.aws.validated - def test_to_validate_template_limit_for_macro( - self, deploy_cfn_template, create_lambda_function, snapshot, aws_client - ): - """ - The test validates the max size of a template that can be passed into the macro function - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/format_template.py" - ) - macro_name = "FormatTemplate" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - template_dict = parse_yaml( - load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_global_parameter.yml", - ) - ) - ) - for n in range(0, 1000): - template_dict["Resources"][f"Parameter{n}"] = deepcopy( - template_dict["Resources"]["Parameter"] - ) - - template = yaml.dump(template_dict) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack( - StackName=f"stack-{short_uid()}", TemplateBody=template - ) - - response = ex.value.response - response["Error"]["Message"] = response["Error"]["Message"].replace( - template, "" - ) - snapshot.match("error_response", response) - - @pytest.mark.skip("CFNV2:Validation") - @markers.aws.validated - def test_error_pass_macro_as_reference(self, snapshot, aws_client): - """ - This test shows that the CFn will reject any transformation name that has been specified as reference, for - example, a parameter. - """ - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack( - StackName=f"stack-{short_uid()}", - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_macro_as_reference.yml", - ) - ), - Capabilities=["CAPABILITY_AUTO_EXPAND"], - Parameters=[{"ParameterKey": "MacroName", "ParameterValue": "NonExistent"}], - ) - snapshot.match("error", ex.value.response) - - @pytest.mark.skip("CFNV2:GetTemplate") - @markers.aws.validated - def test_functions_and_references_during_transformation( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - This tests shows the state of intrinsic functions during the execution of the macro - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/print_references.py" - ) - macro_name = "PrintReferences" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack_name = f"stake-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - TemplateBody=load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_macro_params_as_reference.yml", - ) - ), - Parameters=[{"ParameterKey": "MacroInput", "ParameterValue": "CreateStackInput"}], - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.match( - "event", - processed_template["TemplateBody"]["Resources"]["Parameter"]["Properties"]["Value"], - ) - - @pytest.mark.skip(reason="CFNV2:Validation") - @pytest.mark.parametrize( - "macro_function", - [ - "return_unsuccessful_with_message.py", - "return_unsuccessful_without_message.py", - "return_invalid_template.py", - "raise_error.py", - ], - ) - @markers.aws.validated - def test_failed_state( - self, - deploy_cfn_template, - create_lambda_function, - snapshot, - cleanups, - macro_function, - aws_client, - ): - """ - This test shows the error responses for different negative responses from the macro lambda - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/", macro_function - ) - - macro_name = "Unsuccessful" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - template = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_unsuccessful.yml", - ) - ) - - stack_name = f"stack-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, Capabilities=["CAPABILITY_AUTO_EXPAND"], TemplateBody=template - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - with pytest.raises(botocore.exceptions.WaiterError): - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ - "StackEvents" - ] - - failed_events_by_policy = [ - event - for event in events - if "ResourceStatusReason" in event and event["ResourceStatus"] == "ROLLBACK_IN_PROGRESS" - ] - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("failed_description", failed_events_by_policy[0]) - - @markers.aws.validated - def test_pyplate_param_type_list(self, deploy_cfn_template, aws_client, snapshot): - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/pyplate_deploy_template.yml" - ), - ) - - tags = "Env=Prod,Application=MyApp,BU=ModernisationTeam" - param_tags = {pair.split("=")[0]: pair.split("=")[1] for pair in tags.split(",")} - - stack_with_macro = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/pyplate_example.yml" - ), - parameters={"Tags": tags}, - ) - - bucket_name_output = stack_with_macro.outputs["BucketName"] - assert bucket_name_output - - tagging = aws_client.s3.get_bucket_tagging(Bucket=bucket_name_output) - tags_s3 = [tag for tag in tagging["TagSet"]] - - resp = [] - for tag in tags_s3: - if tag["Key"] in param_tags: - assert tag["Value"] == param_tags[tag["Key"]] - resp.append([tag["Key"], tag["Value"]]) - assert len(tags_s3) >= len(param_tags) - snapshot.match("tags", sorted(resp)) - - -class TestStackEvents: - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..EventId", - "$..PhysicalResourceId", - "$..ResourceProperties", - # TODO: we do not maintain parity here, just that the property exists - "$..ResourceStatusReason", - ] - ) - def test_invalid_stack_deploy(self, deploy_cfn_template, aws_client, snapshot): - logical_resource_id = "MyParameter" - template = { - "Resources": { - logical_resource_id: { - "Type": "AWS::SSM::Parameter", - "Properties": { - # invalid: missing required property _type_ - "Value": "abc123", - }, - }, - }, - } - - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template(template=json.dumps(template)) - - stack_events = exc_info.value.events - # filter out only the single create event that failed - failed_events = [ - every - for every in stack_events - if every["ResourceStatus"] == "CREATE_FAILED" - and every["LogicalResourceId"] == logical_resource_id - ] - assert len(failed_events) == 1 - failed_event = failed_events[0] - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("failed_event", failed_event) - assert "ResourceStatusReason" in failed_event - - -class TestPseudoParameters: - @markers.aws.validated - def test_stack_id(self, deploy_cfn_template, snapshot): - template = { - "Resources": { - "MyParameter": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - }, - }, - "Outputs": { - "StackId": { - "Value": { - "Fn::GetAtt": [ - "MyParameter", - "Value", - ], - }, - }, - }, - } - - stack = deploy_cfn_template(template=json.dumps(template)) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - - snapshot.match("StackId", stack.outputs["StackId"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.snapshot.json deleted file mode 100644 index bcc4ddf05b2c7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.snapshot.json +++ /dev/null @@ -1,687 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestTypes::test_implicit_type_conversion": { - "recorded-date": "29-08-2023, 15:21:22", - "recorded-content": { - "queue": { - "Attributes": { - "ApproximateNumberOfMessages": "0", - "ApproximateNumberOfMessagesDelayed": "0", - "ApproximateNumberOfMessagesNotVisible": "0", - "ContentBasedDeduplication": "false", - "CreatedTimestamp": "timestamp", - "DeduplicationScope": "queue", - "DelaySeconds": "2", - "FifoQueue": "true", - "FifoThroughputLimit": "perQueue", - "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", - "MessageRetentionPeriod": "345600", - "QueueArn": "arn::sqs::111111111111:", - "ReceiveMessageWaitTimeSeconds": "0", - "SqsManagedSseEnabled": "true", - "VisibilityTimeout": "30" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_global_scope": { - "recorded-date": "30-01-2023, 20:14:48", - "recorded-content": { - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "ParameterName": { - "Value": { - "Ref": "Parameter" - } - } - }, - "Parameters": { - "Substitution": { - "Default": "SubstitutionDefault", - "Type": "String" - } - }, - "Resources": { - "Parameter": { - "Properties": { - "Type": "String", - "Value": "new-value" - }, - "Type": "AWS::SSM::Parameter" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope": { - "recorded-date": "06-12-2022, 09:44:49", - "recorded-content": { - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "ContentBasedDeduplication": true, - "FifoTopic": true, - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_scope_order_and_parameters": { - "recorded-date": "07-12-2022, 09:08:26", - "recorded-content": { - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Resources": { - "Parameter": { - "Properties": { - "Type": "String", - "Value": "snippet-transform second-snippet-transform global-transform second-global-transform " - }, - "Type": "AWS::SSM::Parameter" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": { - "recorded-date": "08-12-2022, 16:24:58", - "recorded-content": { - "original_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Parameters:\n TopicName:\n Type: String\n\nResources:\n Topic:\n Type: AWS::SNS::Topic\n Properties:\n TopicName:\n Ref: TopicName\n Fn::Transform: ConvertTopicToFifo\n\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - Topic\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "ContentBasedDeduplication": true, - "FifoTopic": true, - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": { - "recorded-date": "08-12-2022, 16:25:43", - "recorded-content": { - "original_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "Fn::Transform": "ConvertTopicToFifo", - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "ContentBasedDeduplication": true, - "FifoTopic": true, - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_capabilities_requirements": { - "recorded-date": "30-01-2023, 20:15:46", - "recorded-content": { - "error": { - "Error": { - "Code": "InsufficientCapabilitiesException", - "Message": "Requires capabilities : [CAPABILITY_AUTO_EXPAND]", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "ParameterName": { - "Value": { - "Ref": "Parameter" - } - } - }, - "Resources": { - "Parameter": { - "Properties": { - "Type": "String", - "Value": "not-important" - }, - "Type": "AWS::SSM::Parameter" - }, - "Role": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": "*" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/AdministratorAccess" - ] - ] - } - ], - "RoleName": "" - }, - "Type": "AWS::IAM::Role" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_validate_lambda_internals": { - "recorded-date": "30-01-2023, 20:16:45", - "recorded-content": { - "event": { - "Event": { - "accountId": "111111111111", - "fragment": { - "Parameters": { - "ExampleParameter": { - "Type": "String", - "Default": "example-value" - } - }, - "Resources": { - "Parameter": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Value": "", - "Type": "String" - } - } - } - }, - "transformId": "111111111111::PrintInternals", - "requestId": "", - "region": "", - "params": { - "Input": "test-input" - }, - "templateParameterValues": { - "ExampleParameter": "example-value" - } - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": { - "recorded-date": "30-01-2023, 20:17:04", - "recorded-content": { - "error_response": { - "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value '' at 'templateBody' failed to satisfy constraint: Member must have length less than or equal to 51200", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": { - "recorded-date": "30-01-2023, 20:17:05", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Key Name of transform definition must be a string.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_error_macro_param_as_reference": { - "recorded-date": "08-12-2022, 11:50:49", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": { - "recorded-date": "30-01-2023, 20:17:55", - "recorded-content": { - "event": { - "Params": { - "Input": "CreateStackInput" - }, - "FunctionValue": { - "Fn::Join": [ - " ", - [ - "Hello", - "World" - ] - ] - }, - "ValueOfRef": { - "Ref": "Substitution" - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": { - "recorded-date": "30-01-2023, 20:18:45", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Transform 111111111111::Unsuccessful failed with: failed because it is a test. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": { - "recorded-date": "30-01-2023, 20:19:35", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Transform 111111111111::Unsuccessful failed without an error message.. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": { - "recorded-date": "30-01-2023, 20:20:30", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Template format error: unsupported structure.. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": { - "recorded-date": "30-01-2023, 20:21:20", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Received malformed response from transform 111111111111::Unsuccessful. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "recorded-date": "15-01-2023, 17:54:23", - "recorded-content": { - "stack-details": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "parameter123", - "ParameterValue": "", - "ResolvedValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - }, - "topic-tags": { - "Tags": [ - { - "Key": "param-value", - "Value": "param " - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_macro_deployment": { - "recorded-date": "30-01-2023, 20:13:58", - "recorded-content": { - "stack_outputs": { - "MacroRef": "SubstitutionMacro" - }, - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Macro", - "PhysicalResourceId": "SubstitutionMacro", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Macro", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": { - "recorded-date": "12-06-2023, 17:08:47", - "recorded-content": { - "failed_event": { - "EventId": "MyParameter-CREATE_FAILED-date", - "LogicalResourceId": "MyParameter", - "PhysicalResourceId": "", - "ResourceProperties": { - "Value": "abc123" - }, - "ResourceStatus": "CREATE_FAILED", - "ResourceStatusReason": "Property validation failure: [The property {/Type} is required]", - "ResourceType": "AWS::SSM::Parameter", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_pyplate_param_type_list": { - "recorded-date": "17-05-2024, 06:19:03", - "recorded-content": { - "tags": [ - [ - "Application", - "MyApp" - ], - [ - "BU", - "ModernisationTeam" - ], - [ - "Env", - "Prod" - ] - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImportValues::test_cfn_with_exports": { - "recorded-date": "21-06-2024, 18:37:15", - "recorded-content": { - "exports": [ - { - "ExportingStackId": "", - "Name": "-TestExport-0", - "Value": "test" - }, - { - "ExportingStackId": "", - "Name": "-TestExport-1", - "Value": "test" - }, - { - "ExportingStackId": "", - "Name": "-TestExport-2", - "Value": "test" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestPseudoParameters::test_stack_id": { - "recorded-date": "18-07-2024, 08:56:47", - "recorded-content": { - "StackId": "" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": { - "recorded-date": "08-08-2024, 21:21:23", - "recorded-content": { - "role-name": "", - "iam_role_policy": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Effect": "Allow", - "Resource": [ - "arn::ssm::111111111111:parameter/some/params", - "arn::ssm::111111111111:parameter/some/other/params" - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "policy-123", - "RoleName": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": { - "recorded-date": "22-01-2025, 14:01:46", - "recorded-content": { - "join-output": { - "JoinConditionalNoValue": "", - "JoinOnlyNoValue": "", - "JoinWithNoValue": "Sample" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.validation.json deleted file mode 100644 index 408d1213a84b5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.validation.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImportValues::test_cfn_with_exports": { - "last_validated_date": "2024-06-21T18:37:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImports::test_stack_imports": { - "last_validated_date": "2024-07-04T14:19:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": { - "last_validated_date": "2024-06-20T20:41:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function": { - "last_validated_date": "2024-04-03T07:12:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-northeast-1]": { - "last_validated_date": "2024-05-09T08:34:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-southeast-2]": { - "last_validated_date": "2024-05-09T08:34:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-central-1]": { - "last_validated_date": "2024-05-09T08:34:39+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-west-1]": { - "last_validated_date": "2024-05-09T08:34:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-1]": { - "last_validated_date": "2024-05-09T08:32:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-2]": { - "last_validated_date": "2024-05-09T08:33:12+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-1]": { - "last_validated_date": "2024-05-09T08:33:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-2]": { - "last_validated_date": "2024-05-09T08:33:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": { - "last_validated_date": "2025-01-22T14:01:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_sub_number_type": { - "last_validated_date": "2024-08-09T06:55:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_capabilities_requirements": { - "last_validated_date": "2023-01-30T19:15:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": { - "last_validated_date": "2023-01-30T19:17:05+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": { - "last_validated_date": "2023-01-30T19:21:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": { - "last_validated_date": "2023-01-30T19:20:30+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": { - "last_validated_date": "2023-01-30T19:18:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": { - "last_validated_date": "2023-01-30T19:19:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": { - "last_validated_date": "2023-01-30T19:17:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_global_scope": { - "last_validated_date": "2023-01-30T19:14:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_macro_deployment": { - "last_validated_date": "2023-01-30T19:13:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_pyplate_param_type_list": { - "last_validated_date": "2024-05-17T06:19:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_scope_order_and_parameters": { - "last_validated_date": "2022-12-07T08:08:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": { - "last_validated_date": "2022-12-08T15:25:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": { - "last_validated_date": "2022-12-08T15:24:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": { - "last_validated_date": "2023-01-30T19:17:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_validate_lambda_internals": { - "last_validated_date": "2023-01-30T19:16:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestPseudoParameters::test_stack_id": { - "last_validated_date": "2024-07-18T08:56:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": { - "last_validated_date": "2024-08-08T21:21:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "last_validated_date": "2023-01-15T16:54:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": { - "last_validated_date": "2024-07-16T16:38:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": { - "last_validated_date": "2023-06-12T15:08:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestTypes::test_implicit_type_conversion": { - "last_validated_date": "2023-08-29T13:21:22+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_conditions.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_conditions.validation.json deleted file mode 100644 index daba45fdabc59..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_conditions.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { - "last_validated_date": "2025-04-15T15:11:48+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { - "last_validated_date": "2025-04-15T16:00:39+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { - "last_validated_date": "2025-04-15T14:31:36+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { - "last_validated_date": "2025-04-15T13:51:50+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_depends_on.validation.json deleted file mode 100644 index 6d50b4297ea1d..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { - "last_validated_date": "2025-05-19T18:10:11+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { - "last_validated_date": "2025-05-19T18:13:11+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { - "last_validated_date": "2025-05-19T12:55:09+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { - "last_validated_date": "2025-05-19T13:01:34+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.validation.json deleted file mode 100644 index b134dc47b4ce5..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { - "last_validated_date": "2025-04-08T11:24:14+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { - "last_validated_date": "2025-04-08T14:46:11+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { - "last_validated_date": "2025-04-08T12:20:18+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { - "last_validated_date": "2025-04-08T12:17:00+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { - "last_validated_date": "2025-04-08T12:33:53+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { - "last_validated_date": "2025-04-08T12:36:40+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_join.validation.json deleted file mode 100644 index b8cd37a40d981..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { - "last_validated_date": "2025-05-05T13:31:26+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { - "last_validated_date": "2025-05-05T13:24:03+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { - "last_validated_date": "2025-05-05T13:10:54+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { - "last_validated_date": "2025-05-05T13:42:26+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { - "last_validated_date": "2025-05-05T13:15:57+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { - "last_validated_date": "2025-05-05T13:37:54+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_select.validation.json deleted file mode 100644 index 49ee9ee8fcdc4..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { - "last_validated_date": "2025-05-28T13:14:01+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { - "last_validated_date": "2025-05-28T14:44:47+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { - "last_validated_date": "2025-05-28T13:32:24+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { - "last_validated_date": "2025-05-28T13:23:46+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { - "last_validated_date": "2025-05-28T13:21:34+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { - "last_validated_date": "2025-05-28T13:17:47+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_split.validation.json deleted file mode 100644 index a85de241f5b9d..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { - "last_validated_date": "2025-06-02T11:19:05+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { - "last_validated_date": "2025-06-02T12:30:32+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { - "last_validated_date": "2025-06-02T11:22:03+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { - "last_validated_date": "2025-06-02T11:20:29+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { - "last_validated_date": "2025-06-02T11:26:00+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { - "last_validated_date": "2025-06-02T11:23:28+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.validation.json deleted file mode 100644 index cd0626345c30e..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.validation.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { - "last_validated_date": "2025-05-20T15:26:12+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { - "last_validated_date": "2025-05-20T11:54:12+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { - "last_validated_date": "2025-05-20T15:08:40+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { - "last_validated_date": "2025-05-20T09:54:49+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { - "last_validated_date": "2025-05-20T12:05:00+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { - "last_validated_date": "2025-05-20T11:29:16+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { - "last_validated_date": "2025-05-20T12:01:36+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { - "last_validated_date": "2025-05-20T15:10:15+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { - "last_validated_date": "2025-05-20T09:59:44+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_global_macros.validation.json deleted file mode 100644 index d88f126525af3..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { - "last_validated_date": "2025-06-24T15:16:22+00:00", - "durations_in_seconds": { - "setup": 11.62, - "call": 34.97, - "teardown": 5.44, - "total": 52.03 - } - }, - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { - "last_validated_date": "2025-06-26T11:59:07+00:00", - "durations_in_seconds": { - "setup": 11.46, - "call": 52.25, - "teardown": 1.78, - "total": 65.49 - } - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_mappings.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_mappings.validation.json deleted file mode 100644 index 32d3348a4a4d6..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_mappings.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { - "last_validated_date": "2025-04-15T13:05:52+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { - "last_validated_date": "2025-04-15T13:08:27+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { - "last_validated_date": "2025-04-15T13:07:01+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { - "last_validated_date": "2025-04-15T13:15:54+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { - "last_validated_date": "2025-04-15T13:04:43+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { - "last_validated_date": "2025-04-15T13:03:18+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_parameters.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_parameters.validation.json deleted file mode 100644 index 05e1a75cbd323..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_parameters.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { - "last_validated_date": "2025-04-17T15:35:43+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { - "last_validated_date": "2025-04-17T15:46:46+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { - "last_validated_date": "2025-04-17T15:39:55+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { - "last_validated_date": "2025-04-17T15:44:24+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_ref.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_ref.validation.json deleted file mode 100644 index 1667558f83add..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_ref.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { - "last_validated_date": "2025-04-08T15:36:44+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { - "last_validated_date": "2025-04-08T15:45:54+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { - "last_validated_date": "2025-04-08T15:51:05+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { - "last_validated_date": "2025-04-08T16:00:20+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { - "last_validated_date": "2025-04-08T15:22:37+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { - "last_validated_date": "2025-05-19T10:22:18+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_values.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_values.validation.json deleted file mode 100644 index 1e1fbea183682..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_values.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { - "last_validated_date": "2025-05-23T17:56:06+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_sets.validation.json b/tests/aws/services/cloudformation/v2/test_change_sets.validation.json deleted file mode 100644 index f31398e53fe2f..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_sets.validation.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { - "last_validated_date": "2025-06-18T19:14:21+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 25.11, - "teardown": 0.14, - "total": 25.25 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { - "last_validated_date": "2025-06-18T19:14:47+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 26.23, - "teardown": 0.14, - "total": 26.37 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { - "last_validated_date": "2025-06-18T19:15:45+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 25.01, - "teardown": 0.15, - "total": 25.16 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { - "last_validated_date": "2025-06-18T19:13:55+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 37.82, - "teardown": 0.16, - "total": 37.98 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { - "last_validated_date": "2025-06-18T19:04:55+00:00", - "durations_in_seconds": { - "setup": 0.26, - "call": 116.94, - "teardown": 0.15, - "total": 117.35 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { - "last_validated_date": "2025-06-18T19:06:59+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 124.05, - "teardown": 0.16, - "total": 124.21 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { - "last_validated_date": "2025-06-18T19:15:20+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 26.07, - "teardown": 6.64, - "total": 32.71 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { - "last_validated_date": "2025-06-18T19:13:18+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 128.68, - "teardown": 0.14, - "total": 128.82 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { - "last_validated_date": "2025-06-18T19:11:09+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 124.56, - "teardown": 0.14, - "total": 124.7 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { - "last_validated_date": "2025-06-18T19:09:04+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 124.46, - "teardown": 0.14, - "total": 124.6 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::test_single_resource_static_update": { - "last_validated_date": "2025-03-18T16:52:35+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_dynamic_resolving.py b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.py new file mode 100644 index 0000000000000..57a3c4daa13bf --- /dev/null +++ b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.py @@ -0,0 +1,142 @@ +from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine + +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + +pytestmark = skip_if_legacy_engine(reason="Only valid for the V2 provider") + + +@markers.snapshot.skip_snapshot_verify( + paths=[ + "delete-describe..*", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..Replacement", + "$..PolicyAction", + ] +) +class TestSSMParameterValues: + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: parity of the events + "$..MyParameter..PhysicalResourceId" + ] + ) + def test_update_parameter_between_deployments( + self, aws_client, snapshot, create_parameter, capture_update_process + ): + param_name = f"param-{short_uid()}" + param_value_1 = f"param-value-1-{short_uid()}" + param_value_2 = f"param-value-2-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(param_name, ""), + snapshot.transform.regex(param_value_1, ""), + snapshot.transform.regex(param_value_2, ""), + snapshot.transform.key_value("PhysicalResourceId"), + ] + ) + + create_parameter(Name=param_name, Value=param_value_1, Type="String") + + template1 = { + "Parameters": { + "MyValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "MyValue"}, + }, + }, + }, + } + + def update_parameter_value(): + aws_client.ssm.put_parameter( + Name=param_name, Value=param_value_2, Type="String", Overwrite=True + ) + + capture_update_process( + snapshot=snapshot, + t1=template1, + t2=template1, + p1={"MyValue": param_name}, + p2={"MyValue": param_name}, + custom_update_step=update_parameter_value, + ) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: parity of the events + "$..MyParameter..PhysicalResourceId" + ] + ) + def test_change_parameter_type( + self, aws_client, snapshot, create_parameter, capture_update_process + ): + param_name = f"param-{short_uid()}" + param_value = f"param-value-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(param_name, ""), + snapshot.transform.regex(param_value, ""), + snapshot.transform.key_value("PhysicalResourceId"), + ] + ) + + create_parameter(Name=param_name, Value=param_value, Type="String") + + template1 = { + "Parameters": { + "MyValue": { + "Type": "String", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "MyValue"}, + }, + }, + }, + } + template2 = { + "Parameters": { + "MyValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "MyValue"}, + }, + }, + }, + } + + capture_update_process( + snapshot=snapshot, + t1=template1, + t2=template2, + p1={"MyValue": param_name}, + p2={"MyValue": param_name}, + ) diff --git a/tests/aws/services/cloudformation/v2/test_dynamic_resolving.snapshot.json b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.snapshot.json new file mode 100644 index 0000000000000..3d7b70dcba4da --- /dev/null +++ b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.snapshot.json @@ -0,0 +1,845 @@ +{ + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_update_parameter_between_deployments": { + "recorded-date": "06-08-2025, 12:36:33", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "MyParameter": [ + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_change_parameter_type": { + "recorded-date": "06-08-2025, 12:36:55", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "MyParameter": [ + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_dynamic_resolving.validation.json b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.validation.json new file mode 100644 index 0000000000000..f4c0e42c22b65 --- /dev/null +++ b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_change_parameter_type": { + "last_validated_date": "2025-08-06T12:36:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.56, + "teardown": 0.39, + "total": 21.95 + } + }, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_update_parameter_between_deployments": { + "last_validated_date": "2025-08-06T12:36:33+00:00", + "durations_in_seconds": { + "setup": 1.05, + "call": 22.47, + "teardown": 0.35, + "total": 23.87 + } + } +} diff --git a/tests/aws/services/cloudwatch/conftest.py b/tests/aws/services/cloudwatch/conftest.py new file mode 100644 index 0000000000000..e685952dd22df --- /dev/null +++ b/tests/aws/services/cloudwatch/conftest.py @@ -0,0 +1,52 @@ +import os +from typing import TYPE_CHECKING + +import pytest +from botocore.parsers import create_parser +from botocore.serialize import create_serializer + +from localstack.aws.spec import load_service +from localstack.testing.aws.util import is_aws_cloud + +if TYPE_CHECKING: + from mypy_boto3_cloudwatch import CloudWatchClient + + +def is_old_provider(): + return os.environ.get("PROVIDER_OVERRIDE_CLOUDWATCH") == "v1" and not is_aws_cloud() + + +@pytest.fixture(params=["query", "json", "smithy-rpc-v2-cbor"]) +def aws_cloudwatch_client(aws_client, monkeypatch, request) -> "CloudWatchClient": + protocol = request.param + if is_old_provider() and protocol in ("json", "smithy-rpc-v2-cbor"): + pytest.skip(f"Protocol '{protocol}' not supported in Moto") + """ + Currently, there are no way to select which protocol to use when creating a Boto3 client for a service that supports + multiple protocols, like CloudWatch. + To avoid mutating clients by patching the client initialization logic, we can hardcode the parser and serializer + used by the client instead. + """ + # TODO: remove once Botocore countains the new CloudWatch spec + # for now, we need to also patch the botocore client to be sure it contains the updated service model via the + # json patch + service_model = load_service("cloudwatch") + + # instantiate a client via our ExternalAwsClientFactory exposed via `aws_client` fixture + cloudwatch_client_wrapper = aws_client.cloudwatch + # this instance above is the `MetadataRequestInjector`, which wraps the actual client + cloudwatch_client = cloudwatch_client_wrapper._client + + # the default client behavior is to include validation + protocol_serializer = create_serializer(protocol) + protocol_parser = create_parser(protocol) + + monkeypatch.setattr(cloudwatch_client.meta, "_service_model", service_model) + monkeypatch.setattr(cloudwatch_client, "_serializer", protocol_serializer) + monkeypatch.setattr(cloudwatch_client, "_response_parser", protocol_parser) + monkeypatch.setattr(cloudwatch_client.meta.service_model, "resolved_protocol", protocol) + + # this is useful to know from the test itself which protocol is currently used + monkeypatch.setattr(cloudwatch_client, "test_client_protocol", protocol, raising=False) + + yield cloudwatch_client diff --git a/tests/aws/services/cloudwatch/test_cloudwatch.py b/tests/aws/services/cloudwatch/test_cloudwatch.py index 3cb2fbb9b73a5..a89510d380072 100644 --- a/tests/aws/services/cloudwatch/test_cloudwatch.py +++ b/tests/aws/services/cloudwatch/test_cloudwatch.py @@ -1,17 +1,17 @@ -import copy import gzip import json import logging -import os +import re import threading import time -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta, timezone from typing import TYPE_CHECKING from urllib.request import Request, urlopen import pytest import requests from botocore.exceptions import ClientError +from localstack_snapshot.snapshots.transformer import SortingTransformer from localstack import config from localstack.services.cloudwatch.provider import PATH_GET_RAW_METRICS @@ -24,28 +24,28 @@ from localstack.utils.common import retry, short_uid, to_str from localstack.utils.sync import poll_condition, wait_until +from .conftest import is_old_provider +from .utils import get_cloudwatch_client + if TYPE_CHECKING: from mypy_boto3_logs import CloudWatchLogsClient + PUBLICATION_RETRIES = 5 LOG = logging.getLogger(__name__) -def is_old_provider(): - return os.environ.get("PROVIDER_OVERRIDE_CLOUDWATCH") == "v1" and not is_aws_cloud() - - class TestCloudwatch: @markers.aws.validated - def test_put_metric_data_values_list(self, snapshot, aws_client): + def test_put_metric_data_values_list(self, snapshot, aws_cloudwatch_client): metric_name = "test-metric" namespace = f"ns-{short_uid()}" - utc_now = datetime.utcnow().replace(tzinfo=timezone.utc) + utc_now = datetime.now(tz=UTC) snapshot.add_transformer( snapshot.transform.key_value("Timestamp", reference_replacement=False) ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -58,9 +58,11 @@ def test_put_metric_data_values_list(self, snapshot, aws_client): ], ) + stats = {} + def get_stats() -> int: - global stats - stats = aws_client.cloudwatch.get_metric_statistics( + nonlocal stats + stats = aws_cloudwatch_client.get_metric_statistics( Namespace=namespace, MetricName=metric_name, StartTime=utc_now - timedelta(seconds=60), @@ -75,13 +77,13 @@ def get_stats() -> int: snapshot.match("get_metric_statistics", stats) @markers.aws.only_localstack - def test_put_metric_data_gzip(self, aws_client, region_name): + def test_put_metric_data_gzip_with_query_protocol(self, aws_client, region_name): metric_name = "test-metric" namespace = "namespace" data = ( "Action=PutMetricData&MetricData.member.1." - "MetricName=%s&MetricData.member.1.Value=1&" - "Namespace=%s&Version=2010-08-01" % (metric_name, namespace) + f"MetricName={metric_name}&MetricData.member.1.Value=1&" + f"Namespace={namespace}&Version=2010-08-01" ) bytes_data = bytes(data, encoding="utf-8") encoded_data = gzip.compress(bytes_data) @@ -115,13 +117,13 @@ def test_put_metric_data_gzip(self, aws_client, region_name): @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") - def test_put_metric_data_validation(self, aws_client): + def test_put_metric_data_validation(self, aws_cloudwatch_client, snapshot): namespace = f"ns-{short_uid()}" - utc_now = datetime.utcnow().replace(tzinfo=timezone.utc) + utc_now = datetime.now(tz=UTC) # test invalid due to having both Values and Value - with pytest.raises(Exception) as ex: - aws_client.cloudwatch.put_metric_data( + with pytest.raises(ClientError) as ex: + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -133,16 +135,11 @@ def test_put_metric_data_validation(self, aws_client): } ], ) - err = ex.value.response["Error"] - assert err["Code"] == "InvalidParameterCombination" - assert ( - err["Message"] - == "The parameters MetricData.member.1.Value and MetricData.member.1.Values are mutually exclusive and you have specified both." - ) + snapshot.match("invalid-param-combination", ex.value.response) # test invalid due to data can not have and values mismatched_counts - with pytest.raises(Exception) as ex: - aws_client.cloudwatch.put_metric_data( + with pytest.raises(ClientError) as ex: + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -154,16 +151,11 @@ def test_put_metric_data_validation(self, aws_client): } ], ) - err = ex.value.response["Error"] - assert err["Code"] == "InvalidParameterValue" - assert ( - err["Message"] - == "The parameters MetricData.member.1.Values and MetricData.member.1.Counts must be of the same size." - ) + snapshot.match("invalid-param-value", ex.value.response) # test invalid due to inserting both value and statistic values - with pytest.raises(Exception) as ex: - aws_client.cloudwatch.put_metric_data( + with pytest.raises(ClientError) as ex: + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -180,15 +172,10 @@ def test_put_metric_data_validation(self, aws_client): } ], ) - err = ex.value.response["Error"] - assert err["Code"] == "InvalidParameterCombination" - assert ( - err["Message"] - == "The parameters MetricData.member.1.Value and MetricData.member.1.StatisticValues are mutually exclusive and you have specified both." - ) + snapshot.match("invalid-param-combination-2", ex.value.response) # For some strange reason the AWS implementation allows this - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -207,27 +194,27 @@ def test_put_metric_data_validation(self, aws_client): ) @markers.aws.validated - def test_get_metric_data(self, aws_client): + def test_get_metric_data(self, aws_cloudwatch_client): namespace1 = f"test/{short_uid()}" namespace2 = f"test/{short_uid()}" - aws_client.cloudwatch.put_metric_data( - Namespace=namespace1, MetricData=[dict(MetricName="someMetric", Value=23)] + aws_cloudwatch_client.put_metric_data( + Namespace=namespace1, MetricData=[{"MetricName": "someMetric", "Value": 23}] ) - aws_client.cloudwatch.put_metric_data( - Namespace=namespace1, MetricData=[dict(MetricName="someMetric", Value=18)] + aws_cloudwatch_client.put_metric_data( + Namespace=namespace1, MetricData=[{"MetricName": "someMetric", "Value": 18}] ) - aws_client.cloudwatch.put_metric_data( - Namespace=namespace2, MetricData=[dict(MetricName="ug", Value=23)] + aws_cloudwatch_client.put_metric_data( + Namespace=namespace2, MetricData=[{"MetricName": "ug", "Value": 23}] ) - now = datetime.utcnow().replace(microsecond=0) + now = datetime.now(tz=UTC).replace(microsecond=0) start_time = now - timedelta(minutes=10) end_time = now + timedelta(minutes=5) def _get_metric_data_sum(): # filtering metric data with current time interval - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "some", @@ -254,21 +241,21 @@ def _get_metric_data_sum(): ) assert 2 == len(response["MetricDataResults"]) - for data_metric in response["MetricDataResults"]: + for _data_metric in response["MetricDataResults"]: # TODO: there's an issue in the implementation of the service here. # The returned timestamps should have the seconds set to 0 - if data_metric["Id"] == "some": + if _data_metric["Id"] == "some": assert 41.0 == sum( - data_metric["Values"] + _data_metric["Values"] ) # might fall under different 60s "buckets" - if data_metric["Id"] == "part": - assert 23.0 == sum(data_metric["Values"]) + if _data_metric["Id"] == "part": + assert 23.0 == sum(_data_metric["Values"]) # need to retry because the might most likely not be ingested immediately (it's fairly quick though) retry(_get_metric_data_sum, retries=10, sleep_before=2) # filtering metric data with current time interval - response = aws_client.cloudwatch.get_metric_data( + get_metric_data = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "some", @@ -290,23 +277,23 @@ def _get_metric_data_sum(): }, }, ], - StartTime=datetime.utcnow() + timedelta(hours=1), - EndTime=datetime.utcnow() + timedelta(hours=2), + StartTime=datetime.now(tz=UTC) + timedelta(hours=1), + EndTime=datetime.now(tz=UTC) + timedelta(hours=2), ) - for data_metric in response["MetricDataResults"]: + for data_metric in get_metric_data["MetricDataResults"]: if data_metric["Id"] == "some": assert len(data_metric["Values"]) == 0 if data_metric["Id"] == "part": assert len(data_metric["Values"]) == 0 @markers.aws.validated - def test_get_metric_data_for_multiple_metrics(self, aws_client, snapshot): + def test_get_metric_data_for_multiple_metrics(self, aws_cloudwatch_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -317,7 +304,7 @@ def test_get_metric_data_for_multiple_metrics(self, aws_client, snapshot): } ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -329,7 +316,7 @@ def test_get_metric_data_for_multiple_metrics(self, aws_client, snapshot): ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -347,7 +334,7 @@ def test_get_metric_data_for_multiple_metrics(self, aws_client, snapshot): ) def assert_results(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "result1", @@ -378,7 +365,7 @@ def assert_results(): EndTime=utc_now + timedelta(seconds=60), ) - assert len(response["MetricDataResults"][0]["Values"]) > 0 + assert len(response["MetricDataResults"][2]["Values"]) > 0 snapshot.match("get_metric_data", response) retry(assert_results, retries=10, sleep_before=1) @@ -388,11 +375,11 @@ def assert_results(): "stat", ["Sum", "SampleCount", "Minimum", "Maximum", "Average"], ) - def test_get_metric_data_stats(self, aws_client, snapshot, stat): - utc_now = datetime.now(tz=timezone.utc) + def test_get_metric_data_stats(self, aws_cloudwatch_client, snapshot, stat): + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -404,7 +391,7 @@ def test_get_metric_data_stats(self, aws_client, snapshot, stat): ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -422,7 +409,7 @@ def test_get_metric_data_stats(self, aws_client, snapshot, stat): ) def assert_results(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "result1", @@ -444,11 +431,11 @@ def assert_results(): retry(assert_results, retries=10, sleep_before=sleep_before) @markers.aws.validated - def test_get_metric_data_with_dimensions(self, aws_client, snapshot): - utc_now = datetime.now(tz=timezone.utc) + def test_get_metric_data_with_dimensions(self, aws_cloudwatch_client, snapshot): + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -461,7 +448,7 @@ def test_get_metric_data_with_dimensions(self, aws_client, snapshot): ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -474,7 +461,7 @@ def test_get_metric_data_with_dimensions(self, aws_client, snapshot): ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -492,7 +479,7 @@ def test_get_metric_data_with_dimensions(self, aws_client, snapshot): ) def assert_results(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "result1", @@ -522,13 +509,13 @@ def assert_results(): @markers.aws.only_localstack # this feature was a customer request and added with https://github.com/localstack/localstack/pull/3535 - def test_raw_metric_data(self, aws_client, region_name): + def test_raw_metric_data_internal_endpoint(self, aws_client, region_name): """ tests internal endpoint at "/_aws/cloudwatch/metrics/raw" """ namespace1 = f"test/{short_uid()}" aws_client.cloudwatch.put_metric_data( - Namespace=namespace1, MetricData=[dict(MetricName="someMetric", Value=23)] + Namespace=namespace1, MetricData=[{"MetricName": "someMetric", "Value": 23}] ) # the new v2 provider doesn't need the headers, will return results for all accounts/regions headers = mock_aws_request_headers( @@ -543,7 +530,7 @@ def test_raw_metric_data(self, aws_client, region_name): assert len(metrics_with_ns) == 1 @markers.aws.validated - def test_multiple_dimensions(self, aws_client): + def test_multiple_dimensions(self, aws_cloudwatch_client): namespaces = [ f"ns1-{short_uid()}", f"ns2-{short_uid()}", @@ -552,7 +539,7 @@ def test_multiple_dimensions(self, aws_client): num_dimensions = 2 for ns in namespaces: for i in range(3): - rs = aws_client.cloudwatch.put_metric_data( + put_metric_data = aws_cloudwatch_client.put_metric_data( Namespace=ns, MetricData=[ { @@ -567,10 +554,10 @@ def test_multiple_dimensions(self, aws_client): } ], ) - assert 200 == rs["ResponseMetadata"]["HTTPStatusCode"] + assert put_metric_data["ResponseMetadata"]["HTTPStatusCode"] == 200 def _check_metrics(): - rs = aws_client.cloudwatch.get_paginator("list_metrics").paginate().build_full_result() + rs = aws_cloudwatch_client.get_paginator("list_metrics").paginate().build_full_result() metrics = [m for m in rs["Metrics"] if m.get("Namespace") in namespaces] assert metrics assert len(metrics) == len(namespaces) * num_dimensions @@ -578,11 +565,23 @@ def _check_metrics(): retry(_check_metrics, sleep=2, retries=10, sleep_before=2) @markers.aws.validated - def test_describe_alarms_converts_date_format_correctly(self, aws_client, cleanups): + @markers.snapshot.skip_snapshot_verify( + condition=is_old_provider, paths=["$..MetricAlarms..StateTransitionedTimestamp"] + ) + def test_describe_alarms_converts_date_format_correctly( + self, aws_cloudwatch_client, cleanups, snapshot + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("AlarmName"), + snapshot.transform.key_value("MetricName"), + snapshot.transform.key_value("Namespace"), + ] + ) alarm_name = f"a-{short_uid()}:test" metric_name = f"test-metric-{short_uid()}" namespace = f"test-ns-{short_uid()}" - aws_client.cloudwatch.put_metric_alarm( + aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, Namespace=namespace, MetricName=metric_name, @@ -592,20 +591,21 @@ def test_describe_alarms_converts_date_format_correctly(self, aws_client, cleanu Statistic="Sum", Threshold=30, ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) - result = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) + result = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) alarm = result["MetricAlarms"][0] assert isinstance(alarm["AlarmConfigurationUpdatedTimestamp"], datetime) assert isinstance(alarm["StateUpdatedTimestamp"], datetime) + snapshot.match("describe-alarms", result) @markers.aws.validated - def test_put_composite_alarm_describe_alarms(self, aws_client, cleanups): + def test_put_composite_alarm_describe_alarms(self, aws_cloudwatch_client, cleanups): composite_alarm_name = f"composite-a-{short_uid()}" alarm_name = f"a-{short_uid()}" metric_name = "something" namespace = f"test-ns-{short_uid()}" alarm_rule = f'ALARM("{alarm_name}")' - aws_client.cloudwatch.put_metric_alarm( + aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, Namespace=namespace, MetricName=metric_name, @@ -615,15 +615,15 @@ def test_put_composite_alarm_describe_alarms(self, aws_client, cleanups): Statistic="Sum", Threshold=30, ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) - aws_client.cloudwatch.put_composite_alarm( + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) + aws_cloudwatch_client.put_composite_alarm( AlarmName=composite_alarm_name, AlarmRule=alarm_rule, ) cleanups.append( - lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[composite_alarm_name]) + lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[composite_alarm_name]) ) - result = aws_client.cloudwatch.describe_alarms( + result = aws_cloudwatch_client.describe_alarms( AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] ) alarm = result["CompositeAlarms"][0] @@ -635,12 +635,12 @@ def test_put_composite_alarm_describe_alarms(self, aws_client, cleanups): condition=is_old_provider, paths=["$..MetricAlarms..AlarmDescription", "$..MetricAlarms..StateTransitionedTimestamp"], ) - def test_store_tags(self, aws_client, cleanups, snapshot): + def test_store_tags(self, aws_cloudwatch_client, cleanups, snapshot): alarm_name = f"a-{short_uid()}" metric_name = "store_tags" namespace = f"test-ns-{short_uid()}" snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - put_metric_alarm = aws_client.cloudwatch.put_metric_alarm( + put_metric_alarm = aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, Namespace=namespace, MetricName=metric_name, @@ -650,31 +650,147 @@ def test_store_tags(self, aws_client, cleanups, snapshot): Statistic="Sum", Threshold=30, ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) snapshot.match("put_metric_alarm", put_metric_alarm) - describe_alarms = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + describe_alarms = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("describe_alarms", describe_alarms) alarm = describe_alarms["MetricAlarms"][0] alarm_arn = alarm["AlarmArn"] - list_tags_for_resource = aws_client.cloudwatch.list_tags_for_resource(ResourceARN=alarm_arn) + list_tags_for_resource = aws_cloudwatch_client.list_tags_for_resource(ResourceARN=alarm_arn) snapshot.match("list_tags_for_resource_empty ", list_tags_for_resource) # add tags tags = [{"Key": "tag1", "Value": "foo"}, {"Key": "tag2", "Value": "bar"}] - response = aws_client.cloudwatch.tag_resource(ResourceARN=alarm_arn, Tags=tags) - assert 200 == response["ResponseMetadata"]["HTTPStatusCode"] - list_tags_for_resource = aws_client.cloudwatch.list_tags_for_resource(ResourceARN=alarm_arn) + response = aws_cloudwatch_client.tag_resource(ResourceARN=alarm_arn, Tags=tags) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + list_tags_for_resource = aws_cloudwatch_client.list_tags_for_resource(ResourceARN=alarm_arn) snapshot.match("list_tags_for_resource", list_tags_for_resource) - response = aws_client.cloudwatch.untag_resource(ResourceARN=alarm_arn, TagKeys=["tag1"]) - assert 200 == response["ResponseMetadata"]["HTTPStatusCode"] - list_tags_for_resource_post_untag = aws_client.cloudwatch.list_tags_for_resource( + response = aws_cloudwatch_client.untag_resource(ResourceARN=alarm_arn, TagKeys=["tag1"]) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + list_tags_for_resource_post_untag = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match("list_tags_for_resource_post_untag", list_tags_for_resource_post_untag) + + @markers.aws.validated + @pytest.mark.skipif(is_old_provider(), reason="New test for v2 provider") + def test_tagging_metric_alarm(self, aws_cloudwatch_client, snapshot): + # Sort tags by key value in snapshots + snapshot.add_transformer(SortingTransformer("Tags", lambda o: o["Key"])) + alarm_name = f"a-{short_uid()}" + metric_name = "store_tags" + namespace = f"test-ns-{short_uid()}" + put_metric_alarm = aws_cloudwatch_client.put_metric_alarm( + AlarmName=alarm_name, + Namespace=namespace, + MetricName=metric_name, + EvaluationPeriods=1, + ComparisonOperator="GreaterThanThreshold", + Period=60, + Statistic="Sum", + Threshold=30, + Tags=[{"Key": "Environment", "Value": "Production"}], + ) + snapshot.match("put_metric_alarm", put_metric_alarm) + + describe_alarms = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) + alarm = describe_alarms["MetricAlarms"][0] + alarm_arn = alarm["AlarmArn"] + + list_tags_for_resource_after_creation = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match( + "list_tags_for_resource_after_creation", list_tags_for_resource_after_creation + ) + + # add tags + tags = [{"Key": "tag1", "Value": "foo"}, {"Key": "tag2", "Value": "bar"}] + response = aws_cloudwatch_client.tag_resource(ResourceARN=alarm_arn, Tags=tags) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + list_tags_for_resource_updated = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match("list_tags_for_resource_updated", list_tags_for_resource_updated) + response = aws_cloudwatch_client.untag_resource(ResourceARN=alarm_arn, TagKeys=["tag1"]) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + list_tags_for_resource_post_untag = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match("list_tags_for_resource_post_untag", list_tags_for_resource_post_untag) + + # delete alarm + aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name]) + list_tags_for_deleted_resource = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match("list_tags_for_deleted_resource", list_tags_for_deleted_resource) + + @markers.aws.validated + @pytest.mark.skipif(is_old_provider(), reason="New test for v2 provider") + def test_tagging_composite_alarm(self, aws_cloudwatch_client, snapshot): + # Sort tags by key value in snapshots + snapshot.add_transformer(SortingTransformer("Tags", lambda o: o["Key"])) + snapshot.add_transformer(snapshot.transform.cloudwatch_api()) + composite_alarm_name = f"composite-a-{short_uid()}" + alarm_name = f"a-{short_uid()}" + metric_name = "something" + namespace = f"test-ns-{short_uid()}" + alarm_rule = f'ALARM("{alarm_name}")' + aws_cloudwatch_client.put_metric_alarm( + AlarmName=alarm_name, + Namespace=namespace, + MetricName=metric_name, + EvaluationPeriods=1, + ComparisonOperator="GreaterThanThreshold", + Period=60, + Statistic="Sum", + Threshold=30, + ) + aws_cloudwatch_client.put_composite_alarm( + AlarmName=composite_alarm_name, + AlarmRule=alarm_rule, + Tags=[{"Key": "Environment", "Value": "Production"}], + ) + describe_alarms = aws_cloudwatch_client.describe_alarms( + AlarmTypes=["CompositeAlarm"], AlarmNames=[composite_alarm_name] + ) + alarm = describe_alarms["CompositeAlarms"][0] + alarm_arn = alarm["AlarmArn"] + + list_tags_for_resource_after_creation = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match( + "list_tags_for_resource_after_creation ", list_tags_for_resource_after_creation + ) + + # add tags + tags = [{"Key": "tag1", "Value": "foo"}, {"Key": "tag2", "Value": "bar"}] + response = aws_cloudwatch_client.tag_resource(ResourceARN=alarm_arn, Tags=tags) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + list_tags_for_resource_updated = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match("list_tags_for_resource_updated", list_tags_for_resource_updated) + response = aws_cloudwatch_client.untag_resource(ResourceARN=alarm_arn, TagKeys=["tag1"]) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + list_tags_for_resource_post_untag = aws_cloudwatch_client.list_tags_for_resource( ResourceARN=alarm_arn ) snapshot.match("list_tags_for_resource_post_untag", list_tags_for_resource_post_untag) + # delete alarm + aws_cloudwatch_client.delete_alarms(AlarmNames=[composite_alarm_name]) + list_tags_for_deleted_resource = aws_cloudwatch_client.list_tags_for_resource( + ResourceARN=alarm_arn + ) + snapshot.match("list_tags_for_deleted_resource", list_tags_for_deleted_resource) + aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name]) + @markers.aws.validated - def test_list_metrics_uniqueness(self, aws_client): + def test_list_metrics_uniqueness(self, aws_cloudwatch_client): """ This can take quite a while on AWS unfortunately From the AWS docs: @@ -685,7 +801,7 @@ def test_list_metrics_uniqueness(self, aws_client): namespace = f"test/{short_uid()}" sleep_seconds = 10 if is_aws_cloud() else 1 retries = 100 if is_aws_cloud() else 10 - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -695,7 +811,7 @@ def test_list_metrics_uniqueness(self, aws_client): } ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -707,7 +823,7 @@ def test_list_metrics_uniqueness(self, aws_client): ) # duplicating existing metric - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -719,13 +835,13 @@ def test_list_metrics_uniqueness(self, aws_client): ) def _count_single_metrics(): - results = aws_client.cloudwatch.list_metrics(Namespace=namespace)["Metrics"] + results = aws_cloudwatch_client.list_metrics(Namespace=namespace)["Metrics"] assert len(results) == 2 # asserting only unique values are returned retry(_count_single_metrics, retries=retries, sleep_before=sleep_seconds) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -742,17 +858,17 @@ def _count_single_metrics(): ) def _count_aggregated_metrics(): - results = aws_client.cloudwatch.list_metrics(Namespace=namespace)["Metrics"] + results = aws_cloudwatch_client.list_metrics(Namespace=namespace)["Metrics"] assert len(results) == 3 retry(_count_aggregated_metrics, retries=retries, sleep_before=sleep_seconds) @markers.aws.validated - def test_list_metrics_with_filters(self, aws_client): + def test_list_metrics_with_filters(self, aws_cloudwatch_client): namespace = f"test/{short_uid()}" sleep_seconds = 10 if is_aws_cloud() else 1 retries = 100 if is_aws_cloud() else 10 - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -761,7 +877,7 @@ def test_list_metrics_with_filters(self, aws_client): } ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -772,7 +888,7 @@ def test_list_metrics_with_filters(self, aws_client): ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -783,7 +899,7 @@ def test_list_metrics_with_filters(self, aws_client): ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -800,13 +916,13 @@ def test_list_metrics_with_filters(self, aws_client): ) def _count_all_metrics_in_namespace(): - results = aws_client.cloudwatch.list_metrics(Namespace=namespace)["Metrics"] + results = aws_cloudwatch_client.list_metrics(Namespace=namespace)["Metrics"] assert len(results) == 4 retry(_count_all_metrics_in_namespace, retries=retries, sleep_before=sleep_seconds) def _count_specific_metric_in_namespace(): - results = aws_client.cloudwatch.list_metrics( + results = aws_cloudwatch_client.list_metrics( Namespace=namespace, MetricName="CPUUtilization" )["Metrics"] assert len(results) == 1 @@ -814,7 +930,7 @@ def _count_specific_metric_in_namespace(): retry(_count_specific_metric_in_namespace, retries=retries, sleep_before=sleep_seconds) def _count_metrics_in_namespace_with_dimension(): - results = aws_client.cloudwatch.list_metrics( + results = aws_cloudwatch_client.list_metrics( Namespace=namespace, Dimensions=[{"Name": "InstanceId"}] )["Metrics"] assert len(results) == 3 @@ -824,7 +940,7 @@ def _count_metrics_in_namespace_with_dimension(): ) def _count_metrics_in_namespace_with_dimension_value(): - results = aws_client.cloudwatch.list_metrics( + results = aws_cloudwatch_client.list_metrics( Namespace=namespace, Dimensions=[{"Name": "InstanceId", "Value": "two"}] )["Metrics"] assert len(results) == 2 @@ -836,8 +952,21 @@ def _count_metrics_in_namespace_with_dimension_value(): ) @markers.aws.validated - def test_put_metric_alarm_escape_character(self, cleanups, aws_client): - aws_client.cloudwatch.put_metric_alarm( + @markers.snapshot.skip_snapshot_verify( + condition=is_old_provider, paths=["$..MetricAlarms..StateTransitionedTimestamp"] + ) + def test_put_metric_alarm_escape_character( + self, cleanups, aws_cloudwatch_client, snapshot, region_name + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("AlarmName"), + snapshot.transform.key_value("MetricName"), + snapshot.transform.key_value("Namespace"), + ] + ) + + aws_cloudwatch_client.put_metric_alarm( AlarmName="cpu-mon", AlarmDescription="<", MetricName="CPUUtilization-2", @@ -847,18 +976,28 @@ def test_put_metric_alarm_escape_character(self, cleanups, aws_client): Threshold=1, ComparisonOperator="GreaterThanThreshold", EvaluationPeriods=1, - AlarmActions=["arn:aws:sns:us-east-1:111122223333:MyTopic"], + AlarmActions=[f"arn:aws:sns:{region_name}:111122223333:MyTopic"], ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=["cpu-mon"])) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=["cpu-mon"])) - result = aws_client.cloudwatch.describe_alarms(AlarmNames=["cpu-mon"]) + result = aws_cloudwatch_client.describe_alarms(AlarmNames=["cpu-mon"]) assert result.get("MetricAlarms")[0]["AlarmDescription"] == "<" + snapshot.match("describe-alarms-escaped-character", result) @markers.aws.validated @markers.snapshot.skip_snapshot_verify( condition=is_old_provider, paths=["$..MetricAlarms..StateTransitionedTimestamp"] ) - def test_set_alarm(self, sns_create_topic, sqs_create_queue, aws_client, cleanups, snapshot): + def test_set_alarm( + self, + sns_create_topic, + sqs_create_queue, + aws_client, + aws_cloudwatch_client, + cleanups, + snapshot, + sns_create_sqs_subscription, + ): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) # create topics for state 'ALARM' and 'OK' topic_name_alarm = f"topic-{short_uid()}" @@ -876,20 +1015,6 @@ def test_set_alarm(self, sns_create_topic, sqs_create_queue, aws_client, cleanup queue_url_alarm = sqs_create_queue(QueueName=f"AlarmQueue-{uid}") queue_url_ok = sqs_create_queue(QueueName=f"OKQueue-{uid}") - arn_queue_alarm = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url_alarm, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - arn_queue_ok = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url_ok, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - aws_client.sqs.set_queue_attributes( - QueueUrl=queue_url_alarm, - Attributes={"Policy": get_sqs_policy(arn_queue_alarm, topic_arn_alarm)}, - ) - aws_client.sqs.set_queue_attributes( - QueueUrl=queue_url_ok, Attributes={"Policy": get_sqs_policy(arn_queue_ok, topic_arn_ok)} - ) - alarm_name = "test-alarm" alarm_description = "Test Alarm when CPU exceeds 50 percent" @@ -908,23 +1033,17 @@ def test_set_alarm(self, sns_create_topic, sqs_create_queue, aws_client, cleanup "Statistic": "AVERAGE", } # subscribe to SQS - subscription_alarm = aws_client.sns.subscribe( - TopicArn=topic_arn_alarm, Protocol="sqs", Endpoint=arn_queue_alarm - ) - cleanups.append( - lambda: aws_client.sns.unsubscribe( - SubscriptionArn=subscription_alarm["SubscriptionArn"] - ) + sns_create_sqs_subscription( + topic_arn=topic_arn_alarm, + queue_url=queue_url_alarm, ) - subscription_ok = aws_client.sns.subscribe( - TopicArn=topic_arn_ok, Protocol="sqs", Endpoint=arn_queue_ok - ) - cleanups.append( - lambda: aws_client.sns.unsubscribe(SubscriptionArn=subscription_ok["SubscriptionArn"]) + sns_create_sqs_subscription( + topic_arn=topic_arn_ok, + queue_url=queue_url_ok, ) # create alarm with actions for "OK" and "ALARM" - aws_client.cloudwatch.put_metric_alarm( + aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, AlarmDescription=alarm_description, MetricName=expected_trigger["MetricName"], @@ -934,20 +1053,19 @@ def test_set_alarm(self, sns_create_topic, sqs_create_queue, aws_client, cleanup Threshold=expected_trigger["Threshold"], Dimensions=[{"Name": "InstanceId", "Value": "i-0317828c84edbe100"}], Unit=expected_trigger["Unit"], - Statistic=expected_trigger["Statistic"].capitalize(), + Statistic="Average", OKActions=[topic_arn_ok], AlarmActions=[topic_arn_alarm], EvaluationPeriods=expected_trigger["EvaluationPeriods"], ComparisonOperator=expected_trigger["ComparisonOperator"], TreatMissingData=expected_trigger["TreatMissingData"], ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) # trigger alarm - state_value = "ALARM" state_reason = "testing alarm" - aws_client.cloudwatch.set_alarm_state( - AlarmName=alarm_name, StateReason=state_reason, StateValue=state_value + aws_cloudwatch_client.set_alarm_state( + AlarmName=alarm_name, StateReason=state_reason, StateValue="ALARM" ) retry( @@ -957,19 +1075,18 @@ def test_set_alarm(self, sns_create_topic, sqs_create_queue, aws_client, cleanup sqs_client=aws_client.sqs, expected_queue_url=queue_url_alarm, expected_topic_arn=topic_arn_alarm, - expected_new=state_value, + expected_new="ALARM", expected_reason=state_reason, alarm_name=alarm_name, alarm_description=alarm_description, expected_trigger=expected_trigger, ) - describe_alarm = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + describe_alarm = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("triggered-alarm", describe_alarm) # trigger OK - state_value = "OK" state_reason = "resetting alarm" - aws_client.cloudwatch.set_alarm_state( - AlarmName=alarm_name, StateReason=state_reason, StateValue=state_value + aws_cloudwatch_client.set_alarm_state( + AlarmName=alarm_name, StateReason=state_reason, StateValue="OK" ) retry( @@ -979,19 +1096,26 @@ def test_set_alarm(self, sns_create_topic, sqs_create_queue, aws_client, cleanup sqs_client=aws_client.sqs, expected_queue_url=queue_url_ok, expected_topic_arn=topic_arn_ok, - expected_new=state_value, + expected_new="OK", expected_reason=state_reason, alarm_name=alarm_name, alarm_description=alarm_description, expected_trigger=expected_trigger, ) - describe_alarm = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + describe_alarm = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("reset-alarm", describe_alarm) @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="New test for v2 provider") def test_trigger_composite_alarm( - self, sns_create_topic, sqs_create_queue, aws_client, cleanups, snapshot + self, + sns_create_topic, + sqs_create_queue, + aws_client, + cleanups, + snapshot, + sns_create_sqs_subscription, + aws_cloudwatch_client, ): # create topics for state 'ALARM' and 'OK' of the composite alarm topic_name_alarm = f"topic-alarm-{short_uid()}" @@ -1002,45 +1126,23 @@ def test_trigger_composite_alarm( sns_topic_ok = sns_create_topic(Name=topic_name_ok) topic_arn_ok = sns_topic_ok["TopicArn"] - # TODO extract SNS-to-SQS into a fixture # create queues for 'ALARM' and 'OK' of the composite alarm (will receive sns messages) queue_url_alarm = sqs_create_queue(QueueName=f"AlarmQueue-{short_uid()}") queue_url_ok = sqs_create_queue(QueueName=f"OKQueue-{short_uid()}") - arn_queue_alarm = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url_alarm, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - arn_queue_ok = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url_ok, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - aws_client.sqs.set_queue_attributes( - QueueUrl=queue_url_alarm, - Attributes={"Policy": get_sqs_policy(arn_queue_alarm, topic_arn_alarm)}, - ) - aws_client.sqs.set_queue_attributes( - QueueUrl=queue_url_ok, Attributes={"Policy": get_sqs_policy(arn_queue_ok, topic_arn_ok)} - ) - - # subscribe to SQS - subscription_alarm = aws_client.sns.subscribe( - TopicArn=topic_arn_alarm, Protocol="sqs", Endpoint=arn_queue_alarm - ) - cleanups.append( - lambda: aws_client.sns.unsubscribe( - SubscriptionArn=subscription_alarm["SubscriptionArn"] - ) - ) - subscription_ok = aws_client.sns.subscribe( - TopicArn=topic_arn_ok, Protocol="sqs", Endpoint=arn_queue_ok + sns_create_sqs_subscription( + topic_arn=topic_arn_alarm, + queue_url=queue_url_alarm, ) - cleanups.append( - lambda: aws_client.sns.unsubscribe(SubscriptionArn=subscription_ok["SubscriptionArn"]) + sns_create_sqs_subscription( + topic_arn=topic_arn_ok, + queue_url=queue_url_ok, ) # put metric alarms that would be parts of a composite one # TODO extract put metric alarm and associated cleanups into a fixture def _put_metric_alarm(alarm_name: str): - aws_client.cloudwatch.put_metric_alarm( + aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, MetricName="CPUUtilization", Namespace="AWS/EC2", @@ -1050,7 +1152,7 @@ def _put_metric_alarm(alarm_name: str): ComparisonOperator="GreaterThanThreshold", Threshold=30, ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) alarm_1_name = f"simple-alarm-1-{short_uid()}" alarm_2_name = f"simple-alarm-2-{short_uid()}" @@ -1058,10 +1160,10 @@ def _put_metric_alarm(alarm_name: str): _put_metric_alarm(alarm_1_name) _put_metric_alarm(alarm_2_name) - alarm_1_arn = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_1_name])[ + alarm_1_arn = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_1_name])[ "MetricAlarms" ][0]["AlarmArn"] - alarm_2_arn = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_2_name])[ + alarm_2_arn = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_2_name])[ "MetricAlarms" ][0]["AlarmArn"] @@ -1071,7 +1173,7 @@ def _put_metric_alarm(alarm_name: str): composite_alarm_rule = f'ALARM("{alarm_1_arn}") OR ALARM("{alarm_2_arn}")' - put_composite_alarm_response = aws_client.cloudwatch.put_composite_alarm( + put_composite_alarm_response = aws_cloudwatch_client.put_composite_alarm( AlarmName=composite_alarm_name, AlarmDescription=composite_alarm_description, AlarmRule=composite_alarm_rule, @@ -1079,11 +1181,11 @@ def _put_metric_alarm(alarm_name: str): AlarmActions=[topic_arn_alarm], ) cleanups.append( - lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[composite_alarm_name]) + lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[composite_alarm_name]) ) snapshot.match("put-composite-alarm", put_composite_alarm_response) - composite_alarms_list = aws_client.cloudwatch.describe_alarms( + composite_alarms_list = aws_cloudwatch_client.describe_alarms( AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] ) composite_alarm = composite_alarms_list["CompositeAlarms"][0] @@ -1148,7 +1250,7 @@ def _check_composite_alarm_ok_message( ) # trigger alarm 1 - composite one should also go into ALARM state - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_1_name, StateValue="ALARM", StateReason="trigger alarm 1" ) @@ -1157,7 +1259,7 @@ def _check_composite_alarm_ok_message( expected_triggering_child_state="ALARM", ) - composite_alarms_list = aws_client.cloudwatch.describe_alarms( + composite_alarms_list = aws_cloudwatch_client.describe_alarms( AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] ) composite_alarm_in_alarm_when_alarm_1_in_alarm = composite_alarms_list["CompositeAlarms"][0] @@ -1167,7 +1269,7 @@ def _check_composite_alarm_ok_message( ) # trigger OK for alarm 1 - composite one should also go back to OK - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_1_name, StateValue="OK", StateReason="resetting alarm 1" ) @@ -1176,7 +1278,7 @@ def _check_composite_alarm_ok_message( expected_triggering_child_state="OK", ) - composite_alarms_list = aws_client.cloudwatch.describe_alarms( + composite_alarms_list = aws_cloudwatch_client.describe_alarms( AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] ) composite_alarm_in_ok_when_alarm_1_back_to_ok = composite_alarms_list["CompositeAlarms"][0] @@ -1186,7 +1288,7 @@ def _check_composite_alarm_ok_message( ) # trigger alarm 2 - composite one should go again into ALARM state - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_2_name, StateValue="ALARM", StateReason="trigger alarm 2" ) @@ -1195,7 +1297,7 @@ def _check_composite_alarm_ok_message( expected_triggering_child_state="ALARM", ) - composite_alarms_list = aws_client.cloudwatch.describe_alarms( + composite_alarms_list = aws_cloudwatch_client.describe_alarms( AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] ) composite_alarm_in_alarm_when_alarm_2_in_alarm = composite_alarms_list["CompositeAlarms"][0] @@ -1205,7 +1307,7 @@ def _check_composite_alarm_ok_message( ) # trigger OK for alarm 2 - composite one should also go back to OK - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_2_name, StateValue="OK", StateReason="resetting alarm 2" ) @@ -1214,7 +1316,7 @@ def _check_composite_alarm_ok_message( expected_triggering_child_state="OK", ) - composite_alarms_list = aws_client.cloudwatch.describe_alarms( + composite_alarms_list = aws_cloudwatch_client.describe_alarms( AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] ) composite_alarm_in_ok_when_alarm_2_back_to_ok = composite_alarms_list["CompositeAlarms"][0] @@ -1224,14 +1326,14 @@ def _check_composite_alarm_ok_message( ) # trigger alarm 2 while alarm 1 is triggered - composite one shouldn't change - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_1_name, StateValue="ALARM", StateReason="trigger alarm 1" ) - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_2_name, StateValue="ALARM", StateReason="trigger alarm 2" ) - composite_alarms_list = aws_client.cloudwatch.describe_alarms( + composite_alarms_list = aws_cloudwatch_client.describe_alarms( AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] ) composite_alarm_is_triggered_by_alarm_1_and_then_not_changed_by_alarm_2 = ( @@ -1250,16 +1352,22 @@ def _check_composite_alarm_ok_message( "$..NewStateReason", "$..describe-alarms-for-metric..StateReason", # reason contains datapoint + date "$..describe-alarms-for-metric..StateReasonData.evaluatedDatapoints", + "$.alarm-triggered-sqs-msg.ActionExecutedAfterMuteWindow", ] ) @pytest.mark.skipif( condition=is_old_provider(), reason="DescribeAlarmHistory is not implemented" ) def test_put_metric_alarm( - self, sns_create_topic, sqs_create_queue, snapshot, aws_client, cleanups + self, + sns_create_topic, + sqs_queue, + snapshot, + aws_client, + cleanups, + sns_create_sqs_subscription, ): - sns_topic_alarm = sns_create_topic() - topic_arn_alarm = sns_topic_alarm["TopicArn"] + topic_arn_alarm = sns_create_topic()["TopicArn"] snapshot.add_transformer(snapshot.transform.cloudwatch_api()) snapshot.add_transformer( @@ -1275,41 +1383,33 @@ def test_put_metric_alarm( namespace = f"test-nsp-{short_uid()}" snapshot.add_transformer(snapshot.transform.regex(namespace, "")) - sqs_queue = sqs_create_queue() - arn_queue = aws_client.sqs.get_queue_attributes( - QueueUrl=sqs_queue, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - # required for AWS: - aws_client.sqs.set_queue_attributes( - QueueUrl=sqs_queue, - Attributes={"Policy": get_sqs_policy(arn_queue, topic_arn_alarm)}, - ) metric_name = "my-metric1" dimension = [{"Name": "InstanceId", "Value": "abc"}] alarm_name = f"test-alarm-{short_uid()}" - subscription = aws_client.sns.subscribe( - TopicArn=topic_arn_alarm, Protocol="sqs", Endpoint=arn_queue - ) - cleanups.append( - lambda: aws_client.sns.unsubscribe(SubscriptionArn=subscription["SubscriptionArn"]) + sns_create_sqs_subscription( + topic_arn=topic_arn_alarm, + queue_url=sqs_queue, ) + data = [ { "MetricName": metric_name, "Dimensions": dimension, "Value": 21, - "Timestamp": datetime.utcnow().replace(tzinfo=timezone.utc), + "Timestamp": datetime.now(tz=UTC), "Unit": "Seconds", }, { "MetricName": metric_name, "Dimensions": dimension, "Value": 22, - "Timestamp": datetime.utcnow().replace(tzinfo=timezone.utc), + "Timestamp": datetime.now(tz=UTC), "Unit": "Seconds", }, ] + # by sending two alarms, sometimes the alarm scheduler will trigger right at the time the metrics are being + # added, meaning the alarm will not go off instantly # create alarm with action for "ALARM" aws_client.cloudwatch.put_metric_alarm( @@ -1376,38 +1476,32 @@ def test_put_metric_alarm( ] ) def test_breaching_alarm_actions( - self, sns_create_topic, sqs_create_queue, snapshot, aws_client, cleanups + self, + sns_create_topic, + sqs_queue, + snapshot, + aws_client, + cleanups, + sns_create_sqs_subscription, + aws_cloudwatch_client, ): - sns_topic_alarm = sns_create_topic() - topic_arn_alarm = sns_topic_alarm["TopicArn"] + topic_arn_alarm = sns_create_topic()["TopicArn"] snapshot.add_transformer(snapshot.transform.cloudwatch_api()) snapshot.add_transformer( snapshot.transform.regex(topic_arn_alarm.split(":")[-1], ""), priority=2 ) - sqs_queue = sqs_create_queue() - arn_queue = aws_client.sqs.get_queue_attributes( - QueueUrl=sqs_queue, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - # required for AWS: - aws_client.sqs.set_queue_attributes( - QueueUrl=sqs_queue, - Attributes={"Policy": get_sqs_policy(arn_queue, topic_arn_alarm)}, - ) metric_name = "my-metric101" dimension = [{"Name": "InstanceId", "Value": "abc"}] namespace = "test/breaching-alarm" alarm_name = f"test-alarm-{short_uid()}" - subscription = aws_client.sns.subscribe( - TopicArn=topic_arn_alarm, Protocol="sqs", Endpoint=arn_queue - ) - cleanups.append( - lambda: aws_client.sns.unsubscribe(SubscriptionArn=subscription["SubscriptionArn"]) + sns_create_sqs_subscription( + topic_arn=topic_arn_alarm, + queue_url=sqs_queue, ) - snapshot.match("cloudwatch_sns_subscription", subscription) - aws_client.cloudwatch.put_metric_alarm( + aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, AlarmDescription="testing cloudwatch alarms", MetricName=metric_name, @@ -1423,22 +1517,22 @@ def test_breaching_alarm_actions( ComparisonOperator="GreaterThanThreshold", TreatMissingData="breaching", ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) + response = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) assert response["MetricAlarms"][0]["ActionsEnabled"] retry( _sqs_messages_snapshot, retries=80, sleep=3.0, - sleep_before=5, + sleep_before=5 if is_aws_cloud() else 0.5, expected_state="ALARM", sqs_client=aws_client.sqs, sqs_queue=sqs_queue, snapshot=snapshot, identifier="alarm-1", ) - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + response = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("alarm-1-describe", response) @markers.aws.validated @@ -1446,7 +1540,14 @@ def test_breaching_alarm_actions( condition=is_old_provider, paths=["$..MetricAlarms..StateTransitionedTimestamp"] ) def test_enable_disable_alarm_actions( - self, sns_create_topic, sqs_create_queue, snapshot, aws_client, cleanups + self, + sns_create_topic, + sqs_create_queue, + snapshot, + aws_client, + cleanups, + sns_create_sqs_subscription, + aws_cloudwatch_client, ): sns_topic_alarm = sns_create_topic() topic_arn_alarm = sns_topic_alarm["TopicArn"] @@ -1455,28 +1556,18 @@ def test_enable_disable_alarm_actions( snapshot.transform.regex(topic_arn_alarm.split(":")[-1], ""), priority=2 ) - sqs_queue = sqs_create_queue() - arn_queue = aws_client.sqs.get_queue_attributes( - QueueUrl=sqs_queue, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - # required for AWS: - aws_client.sqs.set_queue_attributes( - QueueUrl=sqs_queue, - Attributes={"Policy": get_sqs_policy(arn_queue, topic_arn_alarm)}, - ) + queue_url = sqs_create_queue() metric_name = "my-metric101" dimension = [{"Name": "InstanceId", "Value": "abc"}] namespace = f"test/enable-{short_uid()}" alarm_name = f"test-alarm-{short_uid()}" - subscription = aws_client.sns.subscribe( - TopicArn=topic_arn_alarm, Protocol="sqs", Endpoint=arn_queue - ) - cleanups.append( - lambda: aws_client.sns.unsubscribe(SubscriptionArn=subscription["SubscriptionArn"]) + sns_create_sqs_subscription( + topic_arn=topic_arn_alarm, + queue_url=queue_url, ) - snapshot.match("cloudwatch_sns_subscription", subscription) - aws_client.cloudwatch.put_metric_alarm( + + aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, AlarmDescription="testing cloudwatch alarms", MetricName=metric_name, @@ -1492,35 +1583,36 @@ def test_enable_disable_alarm_actions( ComparisonOperator="GreaterThanThreshold", TreatMissingData="ignore", ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) + response = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) assert response["MetricAlarms"][0]["ActionsEnabled"] snapshot.match("describe_alarm", response) - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_name, StateValue="ALARM", StateReason="testing alarm" ) + sleep_before = 5 if is_aws_cloud() else 0.5 retry( _sqs_messages_snapshot, retries=80, sleep=3.0, - sleep_before=5, + sleep_before=sleep_before, expected_state="ALARM", sqs_client=aws_client.sqs, - sqs_queue=sqs_queue, + sqs_queue=queue_url, snapshot=snapshot, identifier="alarm-state", ) - describe_alarm = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + describe_alarm = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("alarm-state-describe", describe_alarm) # disable alarm action - aws_client.cloudwatch.disable_alarm_actions(AlarmNames=[alarm_name]) - aws_client.cloudwatch.set_alarm_state( + aws_cloudwatch_client.disable_alarm_actions(AlarmNames=[alarm_name]) + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_name, StateValue="OK", StateReason="testing OK state" ) - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + response = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("describe_alarm_disabled", response) assert response["MetricAlarms"][0]["StateValue"] == "OK" assert not response["MetricAlarms"][0]["ActionsEnabled"] @@ -1528,17 +1620,17 @@ def test_enable_disable_alarm_actions( _check_alarm_triggered, retries=80, sleep=3.0, - sleep_before=5, + sleep_before=sleep_before, expected_state="OK", alarm_name=alarm_name, - cloudwatch_client=aws_client.cloudwatch, + cloudwatch_client=aws_cloudwatch_client, snapshot=snapshot, identifier="ok-state-action-disabled", ) # enable alarm action - aws_client.cloudwatch.enable_alarm_actions(AlarmNames=[alarm_name]) - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + aws_cloudwatch_client.enable_alarm_actions(AlarmNames=[alarm_name]) + response = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("describe_alarm_enabled", response) assert response["MetricAlarms"][0]["ActionsEnabled"] @@ -1556,26 +1648,26 @@ def test_aws_sqs_metrics_created(self, sqs_create_queue, snapshot, aws_client): aws_client.sqs.send_message(QueueUrl=sqs_url, MessageBody="Hello") dimensions = [{"Name": "QueueName", "Value": queue_name}] - metric_default = { - "MetricStat": { - "Metric": { - "Namespace": "AWS/SQS", - "Dimensions": dimensions, + def _create_metric(metric_id: str) -> dict: + return { + "Id": metric_id, + "MetricStat": { + "Metric": { + "Namespace": "AWS/SQS", + "Dimensions": dimensions, + }, + "Period": 60, + "Stat": "Sum", }, - "Period": 60, - "Stat": "Sum", - }, - } - sent = {"Id": "sent"} - sent.update(copy.deepcopy(metric_default)) + } + + sent = _create_metric("sent") sent["MetricStat"]["Metric"]["MetricName"] = "NumberOfMessagesSent" - sent_size = {"Id": "sent_size"} - sent_size.update(copy.deepcopy(metric_default)) + sent_size = _create_metric("sent_size") sent_size["MetricStat"]["Metric"]["MetricName"] = "SentMessageSize" - empty = {"Id": "empty_receives"} - empty.update(copy.deepcopy(metric_default)) + empty = _create_metric("empty_receives") empty["MetricStat"]["Metric"]["MetricName"] = "NumberOfEmptyReceives" def contains_sent_messages_metrics() -> int: @@ -1587,8 +1679,8 @@ def contains_sent_messages_metrics() -> int: ): res = aws_client.cloudwatch.get_metric_data( MetricDataQueries=[sent, sent_size, empty], - StartTime=datetime.utcnow() - timedelta(hours=1), - EndTime=datetime.utcnow(), + StartTime=datetime.now(tz=UTC) - timedelta(hours=1), + EndTime=datetime.now(tz=UTC), ) # add check for values, because AWS is sometimes a bit slower to fill those values up... if ( @@ -1603,24 +1695,24 @@ def contains_sent_messages_metrics() -> int: response = aws_client.cloudwatch.get_metric_data( MetricDataQueries=[sent, sent_size, empty], - StartTime=datetime.utcnow() - timedelta(hours=1), - EndTime=datetime.utcnow(), + StartTime=datetime.now(tz=UTC) - timedelta(hours=1), + EndTime=datetime.now(tz=UTC), ) snapshot.match("get_metric_data", response) # receive + delete message - sqs_messages = aws_client.sqs.receive_message(QueueUrl=sqs_url)["Messages"] + sqs_messages = aws_client.sqs.receive_message(QueueUrl=sqs_url, WaitTimeSeconds=5)[ + "Messages" + ] assert len(sqs_messages) == 1 receipt_handle = sqs_messages[0]["ReceiptHandle"] aws_client.sqs.delete_message(QueueUrl=sqs_url, ReceiptHandle=receipt_handle) - msg_received = {"Id": "num_msg_received"} - msg_received.update(copy.deepcopy(metric_default)) + msg_received = _create_metric("num_msg_received") msg_received["MetricStat"]["Metric"]["MetricName"] = "NumberOfMessagesReceived" - msg_deleted = {"Id": "num_msg_deleted"} - msg_deleted.update(copy.deepcopy(metric_default)) + msg_deleted = _create_metric("num_msg_deleted") msg_deleted["MetricStat"]["Metric"]["MetricName"] = "NumberOfMessagesDeleted" def contains_receive_delete_metrics() -> int: @@ -1629,8 +1721,8 @@ def contains_receive_delete_metrics() -> int: if all(m in metrics for m in ["NumberOfMessagesReceived", "NumberOfMessagesDeleted"]): res = aws_client.cloudwatch.get_metric_data( MetricDataQueries=[msg_received, msg_deleted], - StartTime=datetime.utcnow() - timedelta(hours=1), - EndTime=datetime.utcnow(), + StartTime=datetime.now(tz=UTC) - timedelta(hours=1), + EndTime=datetime.now(tz=UTC), ) # add check for values, because AWS is sometimes a bit slower to fill those values up... if res["MetricDataResults"][0]["Values"] and res["MetricDataResults"][1]["Values"]: @@ -1641,15 +1733,15 @@ def contains_receive_delete_metrics() -> int: response = aws_client.cloudwatch.get_metric_data( MetricDataQueries=[msg_received, msg_deleted], - StartTime=datetime.utcnow() - timedelta(hours=1), - EndTime=datetime.utcnow(), + StartTime=datetime.now(tz=UTC) - timedelta(hours=1), + EndTime=datetime.now(tz=UTC), ) snapshot.match("get_metric_data_2", response) @markers.aws.validated @pytest.mark.skipif(condition=is_old_provider(), reason="Old provider is not raising exception") - def test_invalid_dashboard_name(self, aws_client, region_name, snapshot): + def test_invalid_dashboard_name(self, aws_cloudwatch_client, region_name, snapshot): dashboard_name = f"test-{short_uid()}:invalid" dashboard_body = { "widgets": [ @@ -1669,8 +1761,8 @@ def test_invalid_dashboard_name(self, aws_client, region_name, snapshot): ] } - with pytest.raises(Exception) as ex: - aws_client.cloudwatch.put_dashboard( + with pytest.raises(ClientError) as ex: + aws_cloudwatch_client.put_dashboard( DashboardName=dashboard_name, DashboardBody=json.dumps(dashboard_body) ) @@ -1688,7 +1780,7 @@ def test_invalid_dashboard_name(self, aws_client, region_name, snapshot): "$..DashboardEntries..Size", # need to be skipped because size changes if the region name length is longer ] ) - def test_dashboard_lifecycle(self, aws_client, region_name, snapshot): + def test_dashboard_lifecycle(self, aws_cloudwatch_client, region_name, snapshot): dashboard_name = f"test-{short_uid()}" dashboard_body = { "widgets": [ @@ -1707,31 +1799,31 @@ def test_dashboard_lifecycle(self, aws_client, region_name, snapshot): } ] } - aws_client.cloudwatch.put_dashboard( + aws_cloudwatch_client.put_dashboard( DashboardName=dashboard_name, DashboardBody=json.dumps(dashboard_body) ) - response = aws_client.cloudwatch.get_dashboard(DashboardName=dashboard_name) + response = aws_cloudwatch_client.get_dashboard(DashboardName=dashboard_name) snapshot.add_transformer(snapshot.transform.key_value("DashboardName")) snapshot.match("get_dashboard", response) - dashboards_list = aws_client.cloudwatch.list_dashboards() + dashboards_list = aws_cloudwatch_client.list_dashboards() snapshot.match("list_dashboards", dashboards_list) # assert prefix filtering working - dashboards_list = aws_client.cloudwatch.list_dashboards(DashboardNamePrefix="no-valid") + dashboards_list = aws_cloudwatch_client.list_dashboards(DashboardNamePrefix="no-valid") snapshot.match("list_dashboards_prefix_empty", dashboards_list) - dashboards_list = aws_client.cloudwatch.list_dashboards(DashboardNamePrefix="test") + dashboards_list = aws_cloudwatch_client.list_dashboards(DashboardNamePrefix="test") snapshot.match("list_dashboards_prefix", dashboards_list) - aws_client.cloudwatch.delete_dashboards(DashboardNames=[dashboard_name]) - dashboards_list = aws_client.cloudwatch.list_dashboards() + aws_cloudwatch_client.delete_dashboards(DashboardNames=[dashboard_name]) + dashboards_list = aws_cloudwatch_client.list_dashboards() snapshot.match("list_dashboards_empty", dashboards_list) @markers.aws.validated @pytest.mark.skipif(condition=not is_aws_cloud(), reason="Operations not supported") def test_create_metric_stream( self, - aws_client, + aws_cloudwatch_client, firehose_create_delivery_stream, s3_create_bucket, create_role_with_policy, @@ -1790,7 +1882,7 @@ def test_create_metric_stream( time.sleep(15) metric_stream_name = f"MyMetricStream-{short_uid()}" - response_create = aws_client.cloudwatch.put_metric_stream( + response_create = aws_cloudwatch_client.put_metric_stream( Name=metric_stream_name, FirehoseArn=stream_arn, RoleArn=role_arn, @@ -1802,32 +1894,32 @@ def test_create_metric_stream( snapshot.match("create_metric_stream", response_create) - get_response = aws_client.cloudwatch.get_metric_stream(Name=metric_stream_name) + get_response = aws_cloudwatch_client.get_metric_stream(Name=metric_stream_name) snapshot.match("get_metric_stream", get_response) - response_list = aws_client.cloudwatch.list_metric_streams() + response_list = aws_cloudwatch_client.list_metric_streams() metric_streams = response_list.get("Entries", []) metric_streams_names = [metric_stream["Name"] for metric_stream in metric_streams] assert metric_stream_name in metric_streams_names - start_response = aws_client.cloudwatch.start_metric_streams(Names=[metric_stream_name]) + start_response = aws_cloudwatch_client.start_metric_streams(Names=[metric_stream_name]) snapshot.match("start_metric_stream", start_response) - stop_response = aws_client.cloudwatch.stop_metric_streams(Names=[metric_stream_name]) + stop_response = aws_cloudwatch_client.stop_metric_streams(Names=[metric_stream_name]) snapshot.match("stop_metric_stream", stop_response) - response_delete = aws_client.cloudwatch.delete_metric_stream(Name=metric_stream_name) + response_delete = aws_cloudwatch_client.delete_metric_stream(Name=metric_stream_name) snapshot.match("delete_metric_stream", response_delete) - response_list = aws_client.cloudwatch.list_metric_streams() + response_list = aws_cloudwatch_client.list_metric_streams() metric_streams = response_list.get("Entries", []) metric_streams_names = [metric_stream["Name"] for metric_stream in metric_streams] assert metric_stream_name not in metric_streams_names @markers.aws.validated @pytest.mark.skipif(condition=not is_aws_cloud(), reason="Operations not supported") - def test_insight_rule(self, aws_client, snapshot): + def test_insight_rule(self, aws_cloudwatch_client, snapshot): insight_rule_name = f"MyInsightRule-{short_uid()}" - response_create = aws_client.cloudwatch.put_insight_rule( + response_create = aws_cloudwatch_client.put_insight_rule( RuleName=insight_rule_name, RuleState="ENABLED", RuleDefinition=json.dumps( @@ -1847,43 +1939,43 @@ def test_insight_rule(self, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.key_value("Name")) snapshot.match("create_insight_rule", response_create) - response_describe = aws_client.cloudwatch.describe_insight_rules() + response_describe = aws_cloudwatch_client.describe_insight_rules() snapshot.match("describe_insight_rule", response_describe) - response_disable = aws_client.cloudwatch.disable_insight_rules( + response_disable = aws_cloudwatch_client.disable_insight_rules( RuleNames=[insight_rule_name] ) snapshot.match("disable_insight_rule", response_disable) - response_enable = aws_client.cloudwatch.enable_insight_rules(RuleNames=[insight_rule_name]) + response_enable = aws_cloudwatch_client.enable_insight_rules(RuleNames=[insight_rule_name]) snapshot.match("enable_insight_rule", response_enable) - insight_rule_report = aws_client.cloudwatch.get_insight_rule_report( + insight_rule_report = aws_cloudwatch_client.get_insight_rule_report( RuleName=insight_rule_name, - StartTime=datetime.utcnow() - timedelta(hours=1), - EndTime=datetime.utcnow(), + StartTime=datetime.now(tz=UTC) - timedelta(hours=1), + EndTime=datetime.now(tz=UTC), Period=300, MaxContributorCount=10, Metrics=["UniqueContributors"], ) snapshot.match("get_insight_rule_report", insight_rule_report) - response_list = aws_client.cloudwatch.describe_insight_rules() + response_list = aws_cloudwatch_client.describe_insight_rules() insight_rules_names = [ insight_rule["Name"] for insight_rule in response_list["InsightRules"] ] assert insight_rule_name in insight_rules_names - response_delete = aws_client.cloudwatch.delete_insight_rules(RuleNames=[insight_rule_name]) + response_delete = aws_cloudwatch_client.delete_insight_rules(RuleNames=[insight_rule_name]) snapshot.match("delete_insight_rule", response_delete) @markers.aws.validated @pytest.mark.skipif(condition=not is_aws_cloud(), reason="Operations not supported") - def test_anomaly_detector_lifecycle(self, aws_client, snapshot): + def test_anomaly_detector_lifecycle(self, aws_cloudwatch_client, snapshot): namespace = "MyNamespace" metric_name = "MyMetric" - response_create = aws_client.cloudwatch.put_anomaly_detector( + response_create = aws_cloudwatch_client.put_anomaly_detector( MetricName=metric_name, Namespace=namespace, Stat="Sum", @@ -1892,10 +1984,10 @@ def test_anomaly_detector_lifecycle(self, aws_client, snapshot): ) snapshot.match("create_anomaly_detector", response_create) - response_list = aws_client.cloudwatch.describe_anomaly_detectors() + response_list = aws_cloudwatch_client.describe_anomaly_detectors() snapshot.match("describe_anomaly_detector", response_list) - response_delete = aws_client.cloudwatch.delete_anomaly_detector( + response_delete = aws_cloudwatch_client.delete_anomaly_detector( MetricName=metric_name, Namespace=namespace, Stat="Sum", @@ -1905,16 +1997,16 @@ def test_anomaly_detector_lifecycle(self, aws_client, snapshot): @markers.aws.validated @pytest.mark.skipif(condition=not is_aws_cloud(), reason="Operations not supported") - def test_metric_widget(self, aws_client): + def test_metric_widget(self, aws_cloudwatch_client): metric_name = f"test-metric-{short_uid()}" namespace = f"ns-{short_uid()}" - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": metric_name, - "Timestamp": datetime.utcnow().replace(tzinfo=timezone.utc), + "Timestamp": datetime.now(tz=UTC), "Values": [1.0, 10.0], "Counts": [2, 4], "Unit": "Count", @@ -1922,7 +2014,7 @@ def test_metric_widget(self, aws_client): ], ) - response = aws_client.cloudwatch.get_metric_widget_image( + response = aws_cloudwatch_client.get_metric_widget_image( MetricWidget=json.dumps( { "metrics": [ @@ -1948,14 +2040,14 @@ def test_metric_widget(self, aws_client): @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="New test for v2 provider") - def test_describe_minimal_metric_alarm(self, snapshot, aws_client, cleanups): + def test_describe_minimal_metric_alarm(self, snapshot, aws_cloudwatch_client, cleanups): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) alarm_name = f"a-{short_uid()}" metric_name = f"m-{short_uid()}" name_space = f"n-sp-{short_uid()}" snapshot.add_transformer(TransformerUtility.key_value("MetricName")) - aws_client.cloudwatch.put_metric_alarm( + aws_cloudwatch_client.put_metric_alarm( AlarmName=alarm_name, MetricName=metric_name, Namespace=name_space, @@ -1965,39 +2057,27 @@ def test_describe_minimal_metric_alarm(self, snapshot, aws_client, cleanups): ComparisonOperator="GreaterThanThreshold", Threshold=30, ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + cleanups.append(lambda: aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name])) + response = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("describe_minimal_metric_alarm", response) @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="New test for v2 provider") - def test_set_alarm_invalid_input(self, aws_client, snapshot, cleanups): + def test_set_alarm_invalid_input(self, aws_cloudwatch_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) alarm_name = f"a-{short_uid()}" - metric_name = f"m-{short_uid()}" - name_space = f"n-sp-{short_uid()}" snapshot.add_transformer(TransformerUtility.key_value("MetricName")) - aws_client.cloudwatch.put_metric_alarm( - AlarmName=alarm_name, - MetricName=metric_name, - Namespace=name_space, - EvaluationPeriods=1, - Period=10, - Statistic="Sum", - ComparisonOperator="GreaterThanThreshold", - Threshold=30, - ) - cleanups.append(lambda: aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name])) - with pytest.raises(Exception) as ex: - aws_client.cloudwatch.set_alarm_state( + + with pytest.raises(ClientError) as ex: + aws_cloudwatch_client.set_alarm_state( AlarmName=alarm_name, StateValue="INVALID", StateReason="test" ) snapshot.match("error-invalid-state", ex.value.response) - with pytest.raises(Exception) as ex: - aws_client.cloudwatch.set_alarm_state( + with pytest.raises(ClientError) as ex: + aws_cloudwatch_client.set_alarm_state( AlarmName=f"{alarm_name}-nonexistent", StateValue="OK", StateReason="test" ) @@ -2005,13 +2085,13 @@ def test_set_alarm_invalid_input(self, aws_client, snapshot, cleanups): @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") - def test_get_metric_data_with_zero_and_labels(self, aws_client, snapshot): - utc_now = datetime.now(tz=timezone.utc) + def test_get_metric_data_with_zero_and_labels(self, aws_cloudwatch_client, snapshot): + utc_now = datetime.now(tz=UTC) namespace1 = f"test/{short_uid()}" # put metric data values = [0, 2, 4, 3.5, 7, 100] - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace1, MetricData=[ {"MetricName": "metric1", "Value": val, "Unit": "Seconds"} for val in values @@ -2021,7 +2101,7 @@ def test_get_metric_data_with_zero_and_labels(self, aws_client, snapshot): stats = ["Average", "Sum", "Minimum", "Maximum"] def _get_metric_data(): - return aws_client.cloudwatch.get_metric_data( + return aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "result_" + stat, @@ -2048,21 +2128,25 @@ def _match_results(): @markers.aws.validated @markers.snapshot.skip_snapshot_verify(paths=["$..Datapoints..Unit"]) - def test_get_metric_statistics(self, aws_client, snapshot): + def test_get_metric_statistics(self, aws_cloudwatch_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" for i in range(10): - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ - dict(MetricName="metric", Value=i, Timestamp=utc_now + timedelta(seconds=1)) + { + "MetricName": "metric", + "Value": i, + "Timestamp": utc_now + timedelta(seconds=1), + } ], ) def assert_results(): - stats_responce = aws_client.cloudwatch.get_metric_statistics( + stats_responce = aws_cloudwatch_client.get_metric_statistics( Namespace=namespace, MetricName="metric", StartTime=utc_now - timedelta(seconds=60), @@ -2078,12 +2162,12 @@ def assert_results(): retry(assert_results, retries=10, sleep=1.0, sleep_before=sleep_before) @markers.aws.validated - def test_list_metrics_pagination(self, aws_client): + def test_list_metrics_pagination(self, aws_cloudwatch_client): namespace = f"n-sp-{short_uid()}" metric_name = f"m-{short_uid()}" max_metrics = 500 # max metrics per page according to AWS docs for i in range(0, max_metrics + 1): - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2095,20 +2179,20 @@ def test_list_metrics_pagination(self, aws_client): ) def assert_metrics_count(): - response = aws_client.cloudwatch.list_metrics(Namespace=namespace) + response = aws_cloudwatch_client.list_metrics(Namespace=namespace) assert len(response["Metrics"]) == max_metrics and response.get("NextToken") is not None retry(assert_metrics_count, retries=10, sleep=1.0, sleep_before=1.0) @markers.aws.validated @pytest.mark.skipif(condition=is_old_provider(), reason="not supported by the old provider") - def test_get_metric_data_pagination(self, aws_client): + def test_get_metric_data_pagination(self, aws_cloudwatch_client): namespace = f"n-sp-{short_uid()}" metric_name = f"m-{short_uid()}" max_data_points = 10 # default is 100,800 according to AWS docs - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.now(tz=UTC) for i in range(0, max_data_points * 2): - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2121,7 +2205,7 @@ def test_get_metric_data_pagination(self, aws_client): ) def assert_data_points_count(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "m1", @@ -2146,14 +2230,14 @@ def assert_data_points_count(): retry(assert_data_points_count, retries=10, sleep=1.0, sleep_before=2.0) @markers.aws.validated - def test_put_metric_uses_utc(self, aws_client): + def test_put_metric_uses_utc(self, aws_cloudwatch_client): namespace = f"n-sp-{short_uid()}" metric_name = f"m-{short_uid()}" now_local = datetime.now(timezone(timedelta(hours=-5), "America/Cancun")).replace( tzinfo=None ) # Remove the tz info to avoid boto converting it to UTC - now_utc = datetime.utcnow() - aws_client.cloudwatch.put_metric_data( + now_utc = datetime.now(tz=UTC) + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2165,7 +2249,7 @@ def test_put_metric_uses_utc(self, aws_client): ) def assert_found_in_utc(): - response = aws_client.cloudwatch.get_metric_statistics( + response = aws_cloudwatch_client.get_metric_statistics( Namespace=namespace, MetricName=metric_name, StartTime=now_local - timedelta(seconds=60), @@ -2175,7 +2259,7 @@ def assert_found_in_utc(): ) assert len(response["Datapoints"]) == 0 - response = aws_client.cloudwatch.get_metric_statistics( + response = aws_cloudwatch_client.get_metric_statistics( Namespace=namespace, MetricName=metric_name, StartTime=now_utc - timedelta(seconds=60), @@ -2188,12 +2272,12 @@ def assert_found_in_utc(): retry(assert_found_in_utc, retries=10, sleep=1.0) @markers.aws.validated - def test_default_ordering(self, aws_client): + def test_default_ordering(self, aws_cloudwatch_client): namespace = f"n-sp-{short_uid()}" metric_name = f"m-{short_uid()}" - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.now(tz=UTC) for i in range(0, 10): - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2206,7 +2290,7 @@ def test_default_ordering(self, aws_client): ) def assert_ordering(): - default_ordering = aws_client.cloudwatch.get_metric_data( + default_ordering = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "m1", @@ -2225,7 +2309,7 @@ def assert_ordering(): MaxDatapoints=10, ) - ascending_ordering = aws_client.cloudwatch.get_metric_data( + ascending_ordering = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "m1", @@ -2245,7 +2329,7 @@ def assert_ordering(): ScanBy="TimestampAscending", ) - descening_ordering = aws_client.cloudwatch.get_metric_data( + descening_ordering = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "m1", @@ -2277,11 +2361,11 @@ def assert_ordering(): @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") - def test_handle_different_units(self, aws_client, snapshot): + def test_handle_different_units(self, aws_cloudwatch_client, snapshot): namespace = f"n-sp-{short_uid()}" metric_name = "m-test" - now = datetime.utcnow().replace(tzinfo=timezone.utc) - aws_client.cloudwatch.put_metric_data( + now = datetime.now(tz=UTC) + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2305,7 +2389,7 @@ def test_handle_different_units(self, aws_client, snapshot): ) def assert_results(): - response = aws_client.cloudwatch.get_metric_statistics( + response = aws_cloudwatch_client.get_metric_statistics( Namespace=namespace, MetricName=metric_name, StartTime=now - timedelta(seconds=60), @@ -2322,11 +2406,11 @@ def assert_results(): retry(assert_results, retries=retries, sleep=1.0, sleep_before=sleep_before) @markers.aws.validated - def test_get_metric_data_with_different_units(self, aws_client, snapshot): + def test_get_metric_data_with_different_units(self, aws_cloudwatch_client, snapshot): namespace = f"n-sp-{short_uid()}" metric_name = "m-test" - now = datetime.utcnow().replace(tzinfo=timezone.utc) - aws_client.cloudwatch.put_metric_data( + now = datetime.now(tz=UTC) + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2345,7 +2429,7 @@ def test_get_metric_data_with_different_units(self, aws_client, snapshot): ) def assert_results(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "m1", @@ -2400,9 +2484,12 @@ def assert_results(): ], ) @markers.aws.needs_fixing - @pytest.mark.skip(reason="Not supported in either provider, needs to be fixed in new one") + @pytest.mark.skipif( + condition=not is_aws_cloud(), + reason="Not supported in either provider, needs to be fixed in new one", + ) def test_get_metric_data_different_units_no_unit_in_query( - self, aws_client, snapshot, metric_data + self, aws_cloudwatch_client, snapshot, metric_data ): # From the docs: """ @@ -2423,15 +2510,15 @@ def test_get_metric_data_different_units_no_unit_in_query( namespace = f"n-sp-{short_uid()}" metric_name = "m-test" - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.now(tz=UTC) for m in metric_data: m["MetricName"] = metric_name m["Timestamp"] = now - aws_client.cloudwatch.put_metric_data(Namespace=namespace, MetricData=metric_data) + aws_cloudwatch_client.put_metric_data(Namespace=namespace, MetricData=metric_data) def assert_results(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "m1", @@ -2469,14 +2556,14 @@ def assert_results(): ) @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") - def test_label_generation(self, aws_client, snapshot, input_pairs): + def test_label_generation(self, aws_cloudwatch_client, snapshot, input_pairs): # Whenever values differ for a statistic type or period, that value is added to the label - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace1 = f"test/{short_uid()}" # put metric data values = [0, 2, 7, 100] - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace1, MetricData=[ {"MetricName": "metric1", "Value": val, "Unit": "Seconds"} for val in values @@ -2486,7 +2573,7 @@ def test_label_generation(self, aws_client, snapshot, input_pairs): # get_metric_data def _get_metric_data(): - return aws_client.cloudwatch.get_metric_data( + return aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": f"result_{stat}_{str(period)}_{unit}", @@ -2506,16 +2593,16 @@ def _get_metric_data(): def _match_results(): response = _get_metric_data() # keep one assert to avoid storing incorrect values - sum = [ + _sum = [ res for res in response["MetricDataResults"] if res["Id"].startswith("result_Sum") ][0] - assert [int(val) for val in sum["Values"]] == [109] + assert [int(val) for val in _sum["Values"]] == [109] snapshot.match("label_generation", response) retry(_match_results, retries=10, sleep=1.0) @markers.aws.validated - def test_get_metric_with_null_dimensions(self, aws_client, snapshot): + def test_get_metric_with_null_dimensions(self, aws_cloudwatch_client, snapshot): """ This test validates the behaviour when there is metric data with dimensions and the get_metric_data call has no dimensions specified. The expected behaviour is that the call should return the metric data with @@ -2526,7 +2613,7 @@ def test_get_metric_with_null_dimensions(self, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.key_value("Label")) namespace = f"n-{short_uid()}" metric_name = "m-test" - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2544,7 +2631,7 @@ def test_get_metric_with_null_dimensions(self, aws_client, snapshot): ) def assert_results(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "m1", @@ -2559,8 +2646,8 @@ def assert_results(): }, } ], - StartTime=datetime.utcnow() - timedelta(hours=1), - EndTime=datetime.utcnow(), + StartTime=datetime.now(tz=UTC) - timedelta(hours=1), + EndTime=datetime.now(tz=UTC), ) assert len(response["MetricDataResults"][0]["Values"]) == 0 snapshot.match("get_metric_with_null_dimensions", response) @@ -2635,12 +2722,12 @@ def log_group_exists(): snapshot.match("lambda-alarm-invocations", invocation_res) @markers.aws.validated - def test_get_metric_with_no_results(self, snapshot, aws_client): - utc_now = datetime.now(tz=timezone.utc) + def test_get_metric_with_no_results(self, snapshot, aws_cloudwatch_client): + utc_now = datetime.now(tz=UTC) namespace = f"n-{short_uid()}" metric = f"m-{short_uid()}" - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2651,14 +2738,14 @@ def test_get_metric_with_no_results(self, snapshot, aws_client): ) def assert_metric_ready(): - list_of_metrics = aws_client.cloudwatch.list_metrics( + list_of_metrics = aws_cloudwatch_client.list_metrics( Namespace=namespace, MetricName=metric ) assert len(list_of_metrics["Metrics"]) == 1 retry(assert_metric_ready, sleep=1, retries=10) - data = aws_client.cloudwatch.get_metric_data( + data = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "result", @@ -2687,6 +2774,7 @@ def assert_metric_ready(): @markers.aws.only_localstack @pytest.mark.skipif(is_old_provider(), reason="old provider has known concurrency issues") # test some basic concurrency tasks + # this test is not enabled for multi-protocols as there is nothing to gain from it def test_parallel_put_metric_data_list_metrics(self, aws_client): num_threads = 20 create_barrier = threading.Barrier(num_threads) @@ -2716,7 +2804,7 @@ def _put_metric_get_metric_data(runner: int): ], ) else: - now = datetime.utcnow().replace(microsecond=0) + now = datetime.now(tz=UTC).replace(microsecond=0) start_time = now - timedelta(minutes=10) end_time = now + timedelta(minutes=5) aws_client.cloudwatch.get_metric_data( @@ -2772,11 +2860,11 @@ def _put_metric_get_metric_data(runner: int): "$..describe-alarm.MetricAlarms..StateTransitionedTimestamp", ], ) - def test_delete_alarm(self, aws_client, snapshot): + def test_delete_alarm(self, aws_cloudwatch_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) alarm_name = "test-alarm" - aws_client.cloudwatch.put_metric_alarm( + aws_cloudwatch_client.put_metric_alarm( AlarmName="test-alarm", Namespace=f"my-namespace-{short_uid()}", MetricName="metric1", @@ -2786,13 +2874,13 @@ def test_delete_alarm(self, aws_client, snapshot): Statistic="Sum", Threshold=30, ) - result = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + result = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("describe-alarm", result) - delete_result = aws_client.cloudwatch.delete_alarms(AlarmNames=[alarm_name]) + delete_result = aws_cloudwatch_client.delete_alarms(AlarmNames=[alarm_name]) snapshot.match("delete-alarm", delete_result) - describe_alarm = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) + describe_alarm = aws_cloudwatch_client.describe_alarms(AlarmNames=[alarm_name]) snapshot.match("describe-after-delete", describe_alarm) @markers.aws.validated @@ -2802,10 +2890,10 @@ def test_delete_alarm(self, aws_client, snapshot): "$..list-metrics..Metrics", ], ) - def test_multiple_dimensions_statistics(self, aws_client, snapshot): + def test_multiple_dimensions_statistics(self, aws_cloudwatch_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" metric_name = "http.server.requests.count" dimensions = [ @@ -2816,7 +2904,7 @@ def test_multiple_dimensions_statistics(self, aws_client, snapshot): {"Name": "uri", "Value": "/greetings"}, {"Name": "status", "Value": "200"}, ] - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2825,11 +2913,11 @@ def test_multiple_dimensions_statistics(self, aws_client, snapshot): "Unit": "Count", "StorageResolution": 1, "Dimensions": dimensions, - "Timestamp": datetime.now(tz=timezone.utc), + "Timestamp": datetime.now(tz=UTC), } ], ) - aws_client.cloudwatch.put_metric_data( + aws_cloudwatch_client.put_metric_data( Namespace=namespace, MetricData=[ { @@ -2838,13 +2926,13 @@ def test_multiple_dimensions_statistics(self, aws_client, snapshot): "Unit": "Count", "StorageResolution": 1, "Dimensions": dimensions, - "Timestamp": datetime.now(tz=timezone.utc), + "Timestamp": datetime.now(tz=UTC), } ], ) def assert_results(): - response = aws_client.cloudwatch.get_metric_data( + response = aws_cloudwatch_client.get_metric_data( MetricDataQueries=[ { "Id": "result1", @@ -2872,7 +2960,7 @@ def assert_results(): retry(assert_results, retries=retries, sleep_before=sleep_before) def list_metrics(): - res = aws_client.cloudwatch.list_metrics( + res = aws_cloudwatch_client.list_metrics( Namespace=namespace, MetricName=metric_name, Dimensions=dimensions ) assert len(res["Metrics"]) > 0 @@ -2892,11 +2980,11 @@ def sort_dimensions(data: dict): @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="New test for v2 provider") - def test_invalid_amount_of_datapoints(self, aws_client, snapshot): + def test_invalid_amount_of_datapoints(self, aws_cloudwatch_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) with pytest.raises(ClientError) as ex: - aws_client.cloudwatch.get_metric_statistics( + aws_cloudwatch_client.get_metric_statistics( Namespace="namespace", MetricName="metric_name", StartTime=utc_now, @@ -2907,7 +2995,7 @@ def test_invalid_amount_of_datapoints(self, aws_client, snapshot): snapshot.match("error-invalid-amount-datapoints", ex.value.response) with pytest.raises(ClientError) as ex: - aws_client.cloudwatch.get_metric_statistics( + aws_cloudwatch_client.get_metric_statistics( Namespace="namespace", MetricName="metric_name", StartTime=utc_now, @@ -2918,7 +3006,7 @@ def test_invalid_amount_of_datapoints(self, aws_client, snapshot): snapshot.match("error-invalid-time-frame", ex.value.response) - response = aws_client.cloudwatch.get_metric_statistics( + response = aws_cloudwatch_client.get_metric_statistics( Namespace=f"namespace_{short_uid()}", MetricName="metric_name", StartTime=utc_now, @@ -2930,6 +3018,211 @@ def test_invalid_amount_of_datapoints(self, aws_client, snapshot): snapshot.match("get-metric-statitics", response) +class TestCloudWatchMultiProtocol: + @pytest.fixture + def cloudwatch_http_client(self, region_name, aws_http_client_factory): + def _create_client(protocol: str): + return get_cloudwatch_client( + client_factory=aws_http_client_factory, region=region_name, protocol=protocol + ) + + return _create_client + + @markers.aws.validated + def test_multi_protocol_client_fixture(self, aws_cloudwatch_client): + """ + Smoke test to validate that the client is indeed using the right protocol + """ + response = aws_cloudwatch_client.describe_alarms() + response_headers = response["ResponseMetadata"]["HTTPHeaders"] + content_type = response_headers["content-type"] + if aws_cloudwatch_client.test_client_protocol == "query": + assert content_type in ("text/xml", "application/xml") + elif aws_cloudwatch_client.test_client_protocol == "json": + assert content_type == "application/x-amz-json-1.0" + elif aws_cloudwatch_client.test_client_protocol == "smithy-rpc-v2-cbor": + assert content_type == "application/cbor" + assert response_headers["smithy-protocol"] == "rpc-v2-cbor" + + @markers.aws.validated + @pytest.mark.parametrize("protocol", ["json", "smithy-rpc-v2-cbor", "query"]) + @markers.snapshot.skip_snapshot_verify( + paths=[ + # LogAlarms is not defined in the Boto specs anywhere, but is returned in raw responses (deprecated?) + "$.describe-alarms..LogAlarms", + ] + ) + def test_basic_operations_multiple_protocols( + self, cloudwatch_http_client, aws_client, snapshot, protocol + ): + if is_old_provider() and protocol != "query": + pytest.skip( + "Skipping as Moto does not support any other protocol than `query` for CloudWatch for now" + ) + snapshot.add_transformer(snapshot.transform.key_value("Label")) + http_client = cloudwatch_http_client(protocol) + response = http_client.post( + "DescribeAlarms", + payload={}, + ) + snapshot.match("describe-alarms", response) + + namespace1 = f"test/{short_uid()}" + namespace2 = f"test/{short_uid()}" + now = datetime.now(tz=UTC).replace(microsecond=0) + start_time = now - timedelta(minutes=1) + end_time = now + timedelta(minutes=5) + + parameters = [ + { + "Namespace": namespace1, + "MetricData": [{"MetricName": "someMetric", "Value": 23}], + }, + { + "Namespace": namespace1, + "MetricData": [{"MetricName": "someMetric", "Value": 18}], + }, + { + "Namespace": namespace2, + "MetricData": [{"MetricName": "ug", "Value": 23, "Timestamp": now}], + }, + ] + for index, input_values in enumerate(parameters): + response = http_client.post_raw( + operation="PutMetricData", + payload=input_values, + ) + assert response.status_code == 200 + # Check if x-amzn-RequestId is in the response headers - case-sensitive check + assert "x-amzn-RequestId" in dict(response.headers) + + get_metric_input = { + "MetricDataQueries": [ + { + "Id": "some", + "MetricStat": { + "Metric": { + "Namespace": namespace1, + "MetricName": "someMetric", + }, + "Period": 60, + "Stat": "Sum", + }, + }, + { + "Id": "part", + "MetricStat": { + "Metric": {"Namespace": namespace2, "MetricName": "ug"}, + "Period": 60, + "Stat": "Sum", + }, + }, + ], + "StartTime": start_time, + "EndTime": end_time, + } + + def _get_metric_data_sum(): + # we can use the default AWS Client here, it is for the retries + _response = aws_client.cloudwatch.get_metric_data(**get_metric_input) + assert len(_response["MetricDataResults"]) == 2 + + for _data_metric in _response["MetricDataResults"]: + # TODO: there's an issue in the implementation of the service here. + # The returned timestamps should have the seconds set to 0 + if _data_metric["Id"] == "some": + assert sum(_data_metric["Values"]) == 41.0 + if _data_metric["Id"] == "part": + assert 23.0 == sum(_data_metric["Values"]) == 23.0 + + # need to retry because the might most likely not be ingested immediately (it's fairly quick though) + retry(_get_metric_data_sum, retries=10, sleep_before=2) + + response = http_client.post( + operation="GetMetricData", + payload=get_metric_input, + ) + snapshot.match("get-metric-data", response) + + # we need special assertions for raw timestamp values, based on the protocol: + if protocol == "query": + timestamp = response["GetMetricDataResponse"]["GetMetricDataResult"][ + "MetricDataResults" + ]["member"][0]["Timestamps"]["member"] + assert re.match(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", timestamp) + + elif protocol == "json": + timestamp = response["MetricDataResults"][0]["Timestamps"][0] + assert isinstance(timestamp, float) + # assert this format: 1765977780.0 + assert re.match(r"^\d{10}\.0", str(timestamp)) + else: + timestamp = response["MetricDataResults"][0]["Timestamps"][0] + assert isinstance(timestamp, datetime) + assert timestamp.microsecond == 0 + assert timestamp.year == now.year + assert now.day - 1 <= timestamp.day <= now.day + 1 + + # we need to decode more for CBOR, to verify we encode it the same way as AWS (datetime format + proper + # underlying format (float) + # See https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#timestamp-type-serialization + # https://datatracker.ietf.org/doc/html/rfc8949.html#section-3.4 + response_raw = http_client.post_raw( + operation="GetMetricData", + payload=get_metric_input, + ) + # assert that the timestamp is encoded as a Tag (6 major type) with Double of length 8 + assert b"Timestamps\x9f\xc1\xfbA" in response_raw.content + + @markers.aws.validated + @pytest.mark.skipif(is_old_provider(), reason="Wrong behavior in v1 in SetAlarmState") + @pytest.mark.parametrize("protocol", ["json", "smithy-rpc-v2-cbor", "query"]) + def test_exception_serializing_with_no_shape_in_spec( + self, cloudwatch_http_client, snapshot, protocol + ): + alarm_name = f"a-{short_uid()}" + http_client = cloudwatch_http_client(protocol) + + # ValidationError is not defined in Botocore specs + invalid_value_response = http_client.post( + "SetAlarmState", + payload={ + "AlarmName": alarm_name, + "StateValue": "INVALID", + "StateReason": "test", + }, + status_code=400, + ) + snapshot.match("invalid-value-response", invalid_value_response) + + # RPC v2, by default, should always return 400. It only returns a different status code if Query Mode is enabled + status_code = 404 if protocol == "query" else 400 + + not_found_response = http_client.post( + "SetAlarmState", + payload={ + "AlarmName": alarm_name, + "StateValue": "OK", + "StateReason": "test", + }, + status_code=status_code, + ) + snapshot.match("not-found-response", not_found_response) + + # if the client sends `x-amzn-query-mode: true`, AWS responds with a different status code for RPC v2 + not_found_query_mode = http_client.post( + "SetAlarmState", + payload={ + "AlarmName": alarm_name, + "StateValue": "OK", + "StateReason": "test", + }, + status_code=404, + query_mode=True, + ) + snapshot.match("not-found-response-query-mode-true", not_found_query_mode) + + def _get_lambda_logs(logs_client: "CloudWatchLogsClient", fn_name: str): log_events = logs_client.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")["events"] filtered_logs = [event for event in log_events if event["message"].startswith("{")] @@ -2954,18 +3247,22 @@ def _check_alarm_triggered( def _sqs_messages_snapshot(expected_state, sqs_client, sqs_queue, snapshot, identifier): result = sqs_client.receive_message(QueueUrl=sqs_queue, WaitTimeSeconds=2, VisibilityTimeout=0) found_msg = None - receipt_handle = None + other_messages = [] for msg in result["Messages"]: body = json.loads(msg["Body"]) message = json.loads(body["Message"]) + receipt_handle = msg["ReceiptHandle"] + sqs_client.delete_message(QueueUrl=sqs_queue, ReceiptHandle=receipt_handle) + if message["NewStateValue"] == expected_state: found_msg = message - receipt_handle = msg["ReceiptHandle"] break + else: + other_messages.append(msg) + assert found_msg, ( - f"no message found for {expected_state}. Got {len(result['Messages'])} messages.\n{json.dumps(result)}" + f"no message found for {expected_state}. Got {len(other_messages)} messages.\n{other_messages}" ) - sqs_client.delete_message(QueueUrl=sqs_queue, ReceiptHandle=receipt_handle) snapshot.match(f"{identifier}-sqs-msg", found_msg) diff --git a/tests/aws/services/cloudwatch/test_cloudwatch.snapshot.json b/tests/aws/services/cloudwatch/test_cloudwatch.snapshot.json index 87abfc826b4a8..2b9decda1abe0 100644 --- a/tests/aws/services/cloudwatch/test_cloudwatch.snapshot.json +++ b/tests/aws/services/cloudwatch/test_cloudwatch.snapshot.json @@ -1,255 +1,5141 @@ { - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions": { - "recorded-date": "12-09-2023, 12:00:45", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_alarm_lambda_target": { + "recorded-date": "03-01-2024, 17:30:00", + "recorded-content": { + "lambda-alarm-invocations": { + "source": "aws.cloudwatch", + "alarmArn": "arn::cloudwatch::111111111111:alarm:", + "accountId": "111111111111", + "time": "date", + "region": "", + "alarmData": { + "alarmName": "", + "state": { + "value": "ALARM", + "reason": "testing alarm", + "timestamp": "date" + }, + "previousState": { + "value": "INSUFFICIENT_DATA", + "reason": "Unchecked: Initial alarm creation", + "timestamp": "date" + }, + "configuration": { + "description": "testing lambda alarm action", + "metrics": [ + { + "id": "", + "metricStat": { + "metric": { + "namespace": "namespace", + "name": "metric1", + "dimensions": {} + }, + "period": 10, + "stat": "Average" + }, + "returnData": true + } + ] + } + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[json]": { + "recorded-date": "06-10-2025, 14:45:54", + "recorded-content": { + "describe-alarms": { + "CompositeAlarms": [], + "LogAlarms": [], + "MetricAlarms": [] + }, + "get-metric-data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "some", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 41.0 + ] + }, + { + "Id": "part", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 23.0 + ] + } + ] + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[smithy-rpc-v2-cbor]": { + "recorded-date": "06-10-2025, 14:45:56", + "recorded-content": { + "describe-alarms": { + "CompositeAlarms": [], + "LogAlarms": [], + "MetricAlarms": [] + }, + "get-metric-data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "some", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 41.0 + ] + }, + { + "Id": "part", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 23.0 + ] + } + ] + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[query]": { + "recorded-date": "06-10-2025, 14:45:59", + "recorded-content": { + "describe-alarms": { + "DescribeAlarmsResponse": { + "@xmlns": "http://monitoring.amazonaws.com/doc/2010-08-01/", + "DescribeAlarmsResult": { + "CompositeAlarms": null, + "LogAlarms": null, + "MetricAlarms": null + } + } + }, + "get-metric-data": { + "GetMetricDataResponse": { + "@xmlns": "http://monitoring.amazonaws.com/doc/2010-08-01/", + "GetMetricDataResult": { + "Messages": null, + "MetricDataResults": { + "member": [ + { + "Id": "some", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": { + "member": "41.0" + } + }, + { + "Id": "part", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": { + "member": "23.0" + } + } + ] + } + } + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[json]": { + "recorded-date": "22-09-2025, 19:17:52", + "recorded-content": { + "invalid-value-response": { + "__type": "com.amazon.coral.validate#ValidationException", + "message": "1 validation error detected: Value 'INVALID' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]" + }, + "not-found-response": { + "__type": "com.amazonaws.cloudwatch.v2010_08_01#ResourceNotFound" + }, + "not-found-response-query-mode-true": { + "__type": "com.amazonaws.cloudwatch.v2010_08_01#ResourceNotFound" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[smithy-rpc-v2-cbor]": { + "recorded-date": "22-09-2025, 19:17:53", + "recorded-content": { + "invalid-value-response": { + "__type": "com.amazon.coral.validate#ValidationException", + "message": "1 validation error detected: Value 'INVALID' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]" + }, + "not-found-response": { + "__type": "com.amazonaws.cloudwatch.v2010_08_01#ResourceNotFound" + }, + "not-found-response-query-mode-true": { + "__type": "com.amazonaws.cloudwatch.v2010_08_01#ResourceNotFound" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[query]": { + "recorded-date": "22-09-2025, 19:17:54", + "recorded-content": { + "invalid-value-response": { + "ErrorResponse": { + "@xmlns": "http://monitoring.amazonaws.com/doc/2010-08-01/", + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value 'INVALID' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]", + "Type": "Sender" + }, + "RequestId": "" + } + }, + "not-found-response": { + "ErrorResponse": { + "@xmlns": "http://monitoring.amazonaws.com/doc/2010-08-01/", + "Error": { + "Code": "ResourceNotFound", + "Type": "Sender" + }, + "RequestId": "" + } + }, + "not-found-response-query-mode-true": { + "ErrorResponse": { + "@xmlns": "http://monitoring.amazonaws.com/doc/2010-08-01/", + "Error": { + "Code": "ResourceNotFound", + "Type": "Sender" + }, + "RequestId": "" + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[query]": { + "recorded-date": "19-09-2025, 17:40:30", + "recorded-content": { + "get_metric_statistics": { + "Datapoints": [ + { + "Maximum": 10.0, + "SampleCount": 6.0, + "Sum": 42.0, + "Timestamp": "timestamp", + "Unit": "Count" + } + ], + "Label": "test-metric", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[json]": { + "recorded-date": "19-09-2025, 17:40:31", + "recorded-content": { + "get_metric_statistics": { + "Datapoints": [ + { + "Maximum": 10.0, + "SampleCount": 6.0, + "Sum": 42.0, + "Timestamp": "timestamp", + "Unit": "Count" + } + ], + "Label": "test-metric", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 17:40:32", + "recorded-content": { + "get_metric_statistics": { + "Datapoints": [ + { + "Maximum": 10.0, + "SampleCount": 6.0, + "Sum": 42.0, + "Timestamp": "timestamp", + "Unit": "Count" + } + ], + "Label": "test-metric", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[query]": { + "recorded-date": "19-09-2025, 18:08:03", + "recorded-content": { + "invalid-param-combination": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "The parameters MetricData.member.1.Value and MetricData.member.1.Values are mutually exclusive and you have specified both.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-param-value": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The parameters MetricData.member.1.Values and MetricData.member.1.Counts must be of the same size.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-param-combination-2": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "The parameters MetricData.member.1.Value and MetricData.member.1.StatisticValues are mutually exclusive and you have specified both.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[json]": { + "recorded-date": "19-09-2025, 18:08:04", + "recorded-content": { + "invalid-param-combination": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "The parameters MetricData.member.1.Value and MetricData.member.1.Values are mutually exclusive and you have specified both.", + "QueryErrorCode": "InvalidParameterCombinationException", + "Type": "Sender" + }, + "message": "The parameters MetricData.member.1.Value and MetricData.member.1.Values are mutually exclusive and you have specified both.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-param-value": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The parameters MetricData.member.1.Values and MetricData.member.1.Counts must be of the same size.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "message": "The parameters MetricData.member.1.Values and MetricData.member.1.Counts must be of the same size.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-param-combination-2": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "The parameters MetricData.member.1.Value and MetricData.member.1.StatisticValues are mutually exclusive and you have specified both.", + "QueryErrorCode": "InvalidParameterCombinationException", + "Type": "Sender" + }, + "message": "The parameters MetricData.member.1.Value and MetricData.member.1.StatisticValues are mutually exclusive and you have specified both.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 18:08:05", + "recorded-content": { + "invalid-param-combination": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "The parameters MetricData.member.1.Value and MetricData.member.1.Values are mutually exclusive and you have specified both.", + "QueryErrorCode": "InvalidParameterCombinationException", + "Type": "Sender" + }, + "message": "The parameters MetricData.member.1.Value and MetricData.member.1.Values are mutually exclusive and you have specified both.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-param-value": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The parameters MetricData.member.1.Values and MetricData.member.1.Counts must be of the same size.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "message": "The parameters MetricData.member.1.Values and MetricData.member.1.Counts must be of the same size.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-param-combination-2": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "The parameters MetricData.member.1.Value and MetricData.member.1.StatisticValues are mutually exclusive and you have specified both.", + "QueryErrorCode": "InvalidParameterCombinationException", + "Type": "Sender" + }, + "message": "The parameters MetricData.member.1.Value and MetricData.member.1.StatisticValues are mutually exclusive and you have specified both.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[query]": { + "recorded-date": "19-09-2025, 20:29:17", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 50.0 + ] + }, + { + "Id": "result2", + "Label": "metric2", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 25.0 + ] + }, + { + "Id": "result3", + "Label": "metric3", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 55.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[json]": { + "recorded-date": "19-09-2025, 20:29:18", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 50.0 + ] + }, + { + "Id": "result2", + "Label": "metric2", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 25.0 + ] + }, + { + "Id": "result3", + "Label": "metric3", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 55.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 20:29:20", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 50.0 + ] + }, + { + "Id": "result2", + "Label": "metric2", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 25.0 + ] + }, + { + "Id": "result3", + "Label": "metric3", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 55.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Sum]": { + "recorded-date": "19-09-2025, 20:31:51", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 66.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-SampleCount]": { + "recorded-date": "19-09-2025, 20:31:54", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Minimum]": { + "recorded-date": "19-09-2025, 20:31:56", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 1.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Maximum]": { + "recorded-date": "19-09-2025, 20:31:59", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Average]": { + "recorded-date": "19-09-2025, 20:32:03", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 6.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Sum]": { + "recorded-date": "19-09-2025, 20:32:05", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 66.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-SampleCount]": { + "recorded-date": "19-09-2025, 20:32:08", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Minimum]": { + "recorded-date": "19-09-2025, 20:32:10", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 1.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Maximum]": { + "recorded-date": "19-09-2025, 20:32:13", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Average]": { + "recorded-date": "19-09-2025, 20:32:15", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 6.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Sum]": { + "recorded-date": "19-09-2025, 20:32:18", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 66.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-SampleCount]": { + "recorded-date": "19-09-2025, 20:32:20", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Minimum]": { + "recorded-date": "19-09-2025, 20:32:22", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 1.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Maximum]": { + "recorded-date": "19-09-2025, 20:32:25", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Average]": { + "recorded-date": "19-09-2025, 20:32:27", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 6.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[query]": { + "recorded-date": "19-09-2025, 20:33:39", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[json]": { + "recorded-date": "19-09-2025, 20:33:41", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 20:33:44", + "recorded-content": { + "get_metric_data": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 11.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[query]": { + "recorded-date": "19-09-2025, 20:38:27", + "recorded-content": { + "describe-alarms": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[json]": { + "recorded-date": "19-09-2025, 20:38:27", + "recorded-content": { + "describe-alarms": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 20:38:28", + "recorded-content": { + "describe-alarms": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[query]": { + "recorded-date": "19-09-2025, 20:42:24", + "recorded-content": { + "put_metric_alarm": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarms": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "store_tags", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_empty ": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource": { + "Tags": [ + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_post_untag": { + "Tags": [ + { + "Key": "tag2", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[json]": { + "recorded-date": "19-09-2025, 20:42:26", + "recorded-content": { + "put_metric_alarm": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarms": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "store_tags", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_empty ": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource": { + "Tags": [ + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_post_untag": { + "Tags": [ + { + "Key": "tag2", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 20:42:27", + "recorded-content": { + "put_metric_alarm": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarms": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "store_tags", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_empty ": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource": { + "Tags": [ + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_post_untag": { + "Tags": [ + { + "Key": "tag2", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[query]": { + "recorded-date": "24-09-2025, 15:16:44", + "recorded-content": { + "describe-alarms-escaped-character": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111122223333:MyTopic" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "<", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 600, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 1.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[json]": { + "recorded-date": "24-09-2025, 15:16:45", + "recorded-content": { + "describe-alarms-escaped-character": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111122223333:MyTopic" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "<", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 600, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 1.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[smithy-rpc-v2-cbor]": { + "recorded-date": "24-09-2025, 15:16:46", + "recorded-content": { + "describe-alarms-escaped-character": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111122223333:MyTopic" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "<", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 600, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 1.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[query]": { + "recorded-date": "19-09-2025, 21:03:22", + "recorded-content": { + "put-composite-alarm": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "composite-alarm-in-alarm-when-alarm-1-is-in-alarm": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + }, + "composite-alarm-in-ok-when-alarm-1-is-back-to-ok": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "OK", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK" + }, + "composite-alarm-in-alarm-when-alarm-2-is-in-alarm": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + }, + "composite-alarm-in-ok-when-alarm-2-is-back-to-ok": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "OK", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK" + }, + "composite-alarm-is-triggered-by-alarm-1-and-then-unchanged-by-alarm-2": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[json]": { + "recorded-date": "19-09-2025, 21:03:34", + "recorded-content": { + "put-composite-alarm": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "composite-alarm-in-alarm-when-alarm-1-is-in-alarm": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + }, + "composite-alarm-in-ok-when-alarm-1-is-back-to-ok": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "OK", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK" + }, + "composite-alarm-in-alarm-when-alarm-2-is-in-alarm": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + }, + "composite-alarm-in-ok-when-alarm-2-is-back-to-ok": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "OK", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK" + }, + "composite-alarm-is-triggered-by-alarm-1-and-then-unchanged-by-alarm-2": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:03:49", + "recorded-content": { + "put-composite-alarm": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "composite-alarm-in-alarm-when-alarm-1-is-in-alarm": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + }, + "composite-alarm-in-ok-when-alarm-1-is-back-to-ok": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "OK", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK" + }, + "composite-alarm-in-alarm-when-alarm-2-is-in-alarm": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + }, + "composite-alarm-in-ok-when-alarm-2-is-back-to-ok": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "OK", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK" + }, + "composite-alarm-is-triggered-by-alarm-1-and-then-unchanged-by-alarm-2": { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "composite alarm description", + "AlarmName": "", + "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", + "InsufficientDataActions": [], + "OKActions": [ + "arn::sns::111111111111:" + ], + "StateReason": "", + "StateReasonData": { + "triggeringAlarms": [ + { + "arn": "arn::cloudwatch::111111111111:alarm:", + "state": { + "value": "ALARM", + "timestamp": "date" + } + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[query]": { + "recorded-date": "19-09-2025, 21:11:41", + "recorded-content": { + "alarm-1-sqs-msg": { + "AWSAccountId": "111111111111", + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "date", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "InsufficientDataActions": [], + "NewStateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", + "NewStateValue": "ALARM", + "OKActions": [ + "arn::sns::111111111111:" + ], + "OldStateValue": "INSUFFICIENT_DATA", + "Region": "", + "StateChangeTime": "date", + "Trigger": { + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "name": "InstanceId", + "value": "abc" + } + ], + "EvaluateLowSampleCountPercentile": "", + "EvaluationPeriods": 2, + "MetricName": "my-metric101", + "Namespace": "", + "Period": 10, + "Statistic": "AVERAGE", + "StatisticType": "Statistic", + "Threshold": 2.0, + "TreatMissingData": "breaching", + "Unit": "Seconds" + } + }, + "alarm-1-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", + "StateReasonData": { + "version": "1.0", + "queryDate": "date", + "unit": "Seconds", + "statistic": "Average", + "period": 10, + "recentDatapoints": [], + "threshold": 2.0, + "evaluatedDatapoints": [ + { + "timestamp": "date" + }, + { + "timestamp": "date" + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "breaching", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[json]": { + "recorded-date": "19-09-2025, 21:12:42", + "recorded-content": { + "alarm-1-sqs-msg": { + "AWSAccountId": "111111111111", + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "date", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "InsufficientDataActions": [], + "NewStateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", + "NewStateValue": "ALARM", + "OKActions": [ + "arn::sns::111111111111:" + ], + "OldStateValue": "INSUFFICIENT_DATA", + "Region": "", + "StateChangeTime": "date", + "Trigger": { + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "name": "InstanceId", + "value": "abc" + } + ], + "EvaluateLowSampleCountPercentile": "", + "EvaluationPeriods": 2, + "MetricName": "my-metric101", + "Namespace": "", + "Period": 10, + "Statistic": "AVERAGE", + "StatisticType": "Statistic", + "Threshold": 2.0, + "TreatMissingData": "breaching", + "Unit": "Seconds" + } + }, + "alarm-1-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", + "StateReasonData": { + "version": "1.0", + "queryDate": "date", + "unit": "Seconds", + "statistic": "Average", + "period": 10, + "recentDatapoints": [], + "threshold": 2.0, + "evaluatedDatapoints": [ + { + "timestamp": "date" + }, + { + "timestamp": "date" + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "breaching", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:13:17", + "recorded-content": { + "alarm-1-sqs-msg": { + "AWSAccountId": "111111111111", + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "date", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "InsufficientDataActions": [], + "NewStateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", + "NewStateValue": "ALARM", + "OKActions": [ + "arn::sns::111111111111:" + ], + "OldStateValue": "INSUFFICIENT_DATA", + "Region": "", + "StateChangeTime": "date", + "Trigger": { + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "name": "InstanceId", + "value": "abc" + } + ], + "EvaluateLowSampleCountPercentile": "", + "EvaluationPeriods": 2, + "MetricName": "my-metric101", + "Namespace": "", + "Period": 10, + "Statistic": "AVERAGE", + "StatisticType": "Statistic", + "Threshold": 2.0, + "TreatMissingData": "breaching", + "Unit": "Seconds" + } + }, + "alarm-1-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", + "StateReasonData": { + "version": "1.0", + "queryDate": "date", + "unit": "Seconds", + "statistic": "Average", + "period": 10, + "recentDatapoints": [], + "threshold": 2.0, + "evaluatedDatapoints": [ + { + "timestamp": "date" + }, + { + "timestamp": "date" + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "breaching", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[query]": { + "recorded-date": "19-09-2025, 21:17:31", + "recorded-content": { + "describe_alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "alarm-state-sqs-msg": { + "AWSAccountId": "111111111111", + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "date", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "InsufficientDataActions": [], + "NewStateReason": "testing alarm", + "NewStateValue": "ALARM", + "OKActions": [ + "arn::sns::111111111111:" + ], + "OldStateValue": "INSUFFICIENT_DATA", + "Region": "", + "StateChangeTime": "date", + "Trigger": { + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "name": "InstanceId", + "value": "abc" + } + ], + "EvaluateLowSampleCountPercentile": "", + "EvaluationPeriods": 2, + "MetricName": "my-metric101", + "Namespace": "", + "Period": 10, + "Statistic": "AVERAGE", + "StatisticType": "Statistic", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + }, + "alarm-state-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarm_disabled": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": false, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "ok-state-action-disabled-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": false, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarm_enabled": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[json]": { + "recorded-date": "19-09-2025, 21:17:46", + "recorded-content": { + "describe_alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "alarm-state-sqs-msg": { + "AWSAccountId": "111111111111", + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "date", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "InsufficientDataActions": [], + "NewStateReason": "testing alarm", + "NewStateValue": "ALARM", + "OKActions": [ + "arn::sns::111111111111:" + ], + "OldStateValue": "INSUFFICIENT_DATA", + "Region": "", + "StateChangeTime": "date", + "Trigger": { + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "name": "InstanceId", + "value": "abc" + } + ], + "EvaluateLowSampleCountPercentile": "", + "EvaluationPeriods": 2, + "MetricName": "my-metric101", + "Namespace": "", + "Period": 10, + "Statistic": "AVERAGE", + "StatisticType": "Statistic", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + }, + "alarm-state-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarm_disabled": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": false, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "ok-state-action-disabled-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": false, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarm_enabled": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:18:00", + "recorded-content": { + "describe_alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "alarm-state-sqs-msg": { + "AWSAccountId": "111111111111", + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "date", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "InsufficientDataActions": [], + "NewStateReason": "testing alarm", + "NewStateValue": "ALARM", + "OKActions": [ + "arn::sns::111111111111:" + ], + "OldStateValue": "INSUFFICIENT_DATA", + "Region": "", + "StateChangeTime": "date", + "Trigger": { + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "name": "InstanceId", + "value": "abc" + } + ], + "EvaluateLowSampleCountPercentile": "", + "EvaluationPeriods": 2, + "MetricName": "my-metric101", + "Namespace": "", + "Period": 10, + "Statistic": "AVERAGE", + "StatisticType": "Statistic", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + }, + "alarm-state-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarm_disabled": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": false, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "ok-state-action-disabled-describe": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": false, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_alarm_enabled": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "my-metric101", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "testing OK state", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 2.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[query]": { + "recorded-date": "19-09-2025, 21:32:13", + "recorded-content": { + "error-invalid-dashboardname": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The value for field DashboardName contains invalid characters. It can only contain alphanumerics, dash (-) and underscore (_).\n", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[json]": { + "recorded-date": "19-09-2025, 21:32:13", + "recorded-content": { + "error-invalid-dashboardname": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The value for field DashboardName contains invalid characters. It can only contain alphanumerics, dash (-) and underscore (_).\n", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "message": "The value for field DashboardName contains invalid characters. It can only contain alphanumerics, dash (-) and underscore (_).\n", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:32:13", + "recorded-content": { + "error-invalid-dashboardname": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The value for field DashboardName contains invalid characters. It can only contain alphanumerics, dash (-) and underscore (_).\n", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "message": "The value for field DashboardName contains invalid characters. It can only contain alphanumerics, dash (-) and underscore (_).\n", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[query]": { + "recorded-date": "19-09-2025, 21:32:55", + "recorded-content": { + "get_dashboard": { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardBody": { + "widgets": [ + { + "type": "metric", + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "properties": { + "metrics": [ + [ + "AWS/EC2", + "CPUUtilization", + "InstanceId", + "i-12345678" + ] + ], + "region": "", + "view": "timeSeries", + "stacked": false + } + } + ] + }, + "DashboardName": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards": { + "DashboardEntries": [ + { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardName": "", + "LastModified": "datetime", + "Size": 240 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_prefix_empty": { + "DashboardEntries": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_prefix": { + "DashboardEntries": [ + { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardName": "", + "LastModified": "datetime", + "Size": 240 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_empty": { + "DashboardEntries": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[json]": { + "recorded-date": "19-09-2025, 21:32:56", + "recorded-content": { + "get_dashboard": { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardBody": { + "widgets": [ + { + "type": "metric", + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "properties": { + "metrics": [ + [ + "AWS/EC2", + "CPUUtilization", + "InstanceId", + "i-12345678" + ] + ], + "region": "", + "view": "timeSeries", + "stacked": false + } + } + ] + }, + "DashboardName": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards": { + "DashboardEntries": [ + { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardName": "", + "LastModified": "datetime", + "Size": 240 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_prefix_empty": { + "DashboardEntries": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_prefix": { + "DashboardEntries": [ + { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardName": "", + "LastModified": "datetime", + "Size": 240 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_empty": { + "DashboardEntries": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:32:58", + "recorded-content": { + "get_dashboard": { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardBody": { + "widgets": [ + { + "type": "metric", + "x": 0, + "y": 0, + "width": 6, + "height": 6, + "properties": { + "metrics": [ + [ + "AWS/EC2", + "CPUUtilization", + "InstanceId", + "i-12345678" + ] + ], + "region": "", + "view": "timeSeries", + "stacked": false + } + } + ] + }, + "DashboardName": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards": { + "DashboardEntries": [ + { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardName": "", + "LastModified": "datetime", + "Size": 240 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_prefix_empty": { + "DashboardEntries": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_prefix": { + "DashboardEntries": [ + { + "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", + "DashboardName": "", + "LastModified": "datetime", + "Size": 240 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_dashboards_empty": { + "DashboardEntries": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[query]": { + "recorded-date": "19-09-2025, 21:35:54", + "recorded-content": { + "create_metric_stream": { + "Arn": "arn::cloudwatch::111111111111:metric-stream/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_metric_stream": { + "Arn": "arn::cloudwatch::111111111111:metric-stream/", + "CreationDate": "datetime", + "FirehoseArn": "", + "IncludeLinkedAccountsMetrics": false, + "LastUpdateDate": "datetime", + "Name": "", + "OutputFormat": "json", + "RoleArn": "", + "State": "running", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "start_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stop_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[json]": { + "recorded-date": "19-09-2025, 21:39:06", + "recorded-content": { + "create_metric_stream": { + "Arn": "arn::cloudwatch::111111111111:metric-stream/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_metric_stream": { + "Arn": "arn::cloudwatch::111111111111:metric-stream/", + "CreationDate": "datetime", + "FirehoseArn": "", + "IncludeLinkedAccountsMetrics": false, + "LastUpdateDate": "datetime", + "Name": "", + "OutputFormat": "json", + "RoleArn": "", + "State": "running", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "start_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stop_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:41:37", + "recorded-content": { + "create_metric_stream": { + "Arn": "arn::cloudwatch::111111111111:metric-stream/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_metric_stream": { + "Arn": "arn::cloudwatch::111111111111:metric-stream/", + "CreationDate": "datetime", + "FirehoseArn": "", + "IncludeLinkedAccountsMetrics": false, + "LastUpdateDate": "datetime", + "Name": "", + "OutputFormat": "json", + "RoleArn": "", + "State": "running", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "start_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stop_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_metric_stream": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[query]": { + "recorded-date": "19-09-2025, 21:45:12", + "recorded-content": { + "create_insight_rule": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_insight_rule": { + "InsightRules": [ + { + "ApplyOnTransformedLogs": false, + "Definition": { + "Schema": { + "Name": "", + "Version": 1 + }, + "LogGroupNames": [ + "API-Gateway-Access-Logs*" + ], + "LogFormat": "CLF", + "Fields": { + "4": "IpAddress", + "7": "StatusCode" + }, + "Contribution": { + "Keys": [ + "IpAddress" + ], + "Filters": [ + { + "Match": "StatusCode", + "EqualTo": 200 + } + ] + }, + "AggregateOn": "Count" + }, + "ManagedRule": false, + "Name": "", + "Schema": "/1", + "State": "ENABLED" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "disable_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "enable_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_insight_rule_report": { + "AggregateValue": 0.0, + "AggregationStatistic": "SampleCount", + "ApproximateUniqueCount": 0, + "Contributors": [], + "KeyLabels": [ + "IpAddress" + ], + "MetricDatapoints": [ + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[json]": { + "recorded-date": "19-09-2025, 21:45:14", + "recorded-content": { + "create_insight_rule": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_insight_rule": { + "InsightRules": [ + { + "ApplyOnTransformedLogs": false, + "Definition": { + "Schema": { + "Name": "", + "Version": 1 + }, + "LogGroupNames": [ + "API-Gateway-Access-Logs*" + ], + "LogFormat": "CLF", + "Fields": { + "4": "IpAddress", + "7": "StatusCode" + }, + "Contribution": { + "Keys": [ + "IpAddress" + ], + "Filters": [ + { + "Match": "StatusCode", + "EqualTo": 200 + } + ] + }, + "AggregateOn": "Count" + }, + "ManagedRule": false, + "Name": "", + "Schema": "/1", + "State": "ENABLED" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "disable_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "enable_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_insight_rule_report": { + "AggregateValue": 0.0, + "AggregationStatistic": "SampleCount", + "ApproximateUniqueCount": 0, + "Contributors": [], + "KeyLabels": [ + "IpAddress" + ], + "MetricDatapoints": [ + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:45:15", + "recorded-content": { + "create_insight_rule": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_insight_rule": { + "InsightRules": [ + { + "ApplyOnTransformedLogs": false, + "Definition": { + "Schema": { + "Name": "", + "Version": 1 + }, + "LogGroupNames": [ + "API-Gateway-Access-Logs*" + ], + "LogFormat": "CLF", + "Fields": { + "4": "IpAddress", + "7": "StatusCode" + }, + "Contribution": { + "Keys": [ + "IpAddress" + ], + "Filters": [ + { + "Match": "StatusCode", + "EqualTo": 200 + } + ] + }, + "AggregateOn": "Count" + }, + "ManagedRule": false, + "Name": "", + "Schema": "/1", + "State": "ENABLED" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "disable_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "enable_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_insight_rule_report": { + "AggregateValue": 0.0, + "AggregationStatistic": "SampleCount", + "ApproximateUniqueCount": 0, + "Contributors": [], + "KeyLabels": [ + "IpAddress" + ], + "MetricDatapoints": [ + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + }, + { + "Timestamp": "timestamp", + "UniqueContributors": 0.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_insight_rule": { + "Failures": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[query]": { + "recorded-date": "19-09-2025, 21:45:48", + "recorded-content": { + "create_anomaly_detector": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_anomaly_detector": { + "AnomalyDetectors": [ + { + "Configuration": { + "ExcludedTimeRanges": [] + }, + "Dimensions": [ + { + "Name": "DimensionName", + "Value": "DimensionValue" + } + ], + "MetricName": "MyMetric", + "Namespace": "MyNamespace", + "SingleMetricAnomalyDetector": { + "AccountId": "111111111111", + "Dimensions": [ + { + "Name": "DimensionName", + "Value": "DimensionValue" + } + ], + "MetricName": "MyMetric", + "Namespace": "MyNamespace", + "Stat": "Sum" + }, + "Stat": "Sum", + "StateValue": "PENDING_TRAINING" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_anomaly_detector": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[json]": { + "recorded-date": "19-09-2025, 21:45:48", + "recorded-content": { + "create_anomaly_detector": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_anomaly_detector": { + "AnomalyDetectors": [ + { + "Configuration": { + "ExcludedTimeRanges": [] + }, + "Dimensions": [ + { + "Name": "DimensionName", + "Value": "DimensionValue" + } + ], + "MetricName": "MyMetric", + "Namespace": "MyNamespace", + "SingleMetricAnomalyDetector": { + "AccountId": "111111111111", + "Dimensions": [ + { + "Name": "DimensionName", + "Value": "DimensionValue" + } + ], + "MetricName": "MyMetric", + "Namespace": "MyNamespace", + "Stat": "Sum" + }, + "Stat": "Sum", + "StateValue": "PENDING_TRAINING" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_anomaly_detector": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:45:48", + "recorded-content": { + "create_anomaly_detector": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_anomaly_detector": { + "AnomalyDetectors": [ + { + "Configuration": { + "ExcludedTimeRanges": [] + }, + "Dimensions": [ + { + "Name": "DimensionName", + "Value": "DimensionValue" + } + ], + "MetricName": "MyMetric", + "Namespace": "MyNamespace", + "SingleMetricAnomalyDetector": { + "AccountId": "111111111111", + "Dimensions": [ + { + "Name": "DimensionName", + "Value": "DimensionValue" + } + ], + "MetricName": "MyMetric", + "Namespace": "MyNamespace", + "Stat": "Sum" + }, + "Stat": "Sum", + "StateValue": "PENDING_TRAINING" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_anomaly_detector": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[query]": { + "recorded-date": "19-09-2025, 21:47:20", + "recorded-content": { + "describe_minimal_metric_alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 10, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[json]": { + "recorded-date": "19-09-2025, 21:47:20", + "recorded-content": { + "describe_minimal_metric_alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 10, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 21:47:21", + "recorded-content": { + "describe_minimal_metric_alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "", + "Namespace": "", + "OKActions": [], + "Period": 10, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[query]": { + "recorded-date": "19-09-2025, 22:46:59", + "recorded-content": { + "get_metric_data_with_zero_and_labels": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Average", + "Label": "metric1 Average", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 19.416666666666668 + ] + }, + { + "Id": "result_Sum", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 116.5 + ] + }, + { + "Id": "result_Minimum", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] + }, + { + "Id": "result_Maximum", + "Label": "metric1 Maximum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 100.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[json]": { + "recorded-date": "19-09-2025, 22:47:00", + "recorded-content": { + "get_metric_data_with_zero_and_labels": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Average", + "Label": "metric1 Average", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 19.416666666666668 + ] + }, + { + "Id": "result_Sum", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 116.5 + ] + }, + { + "Id": "result_Minimum", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] + }, + { + "Id": "result_Maximum", + "Label": "metric1 Maximum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 100.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 22:47:02", + "recorded-content": { + "get_metric_data_with_zero_and_labels": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Average", + "Label": "metric1 Average", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 19.416666666666668 + ] + }, + { + "Id": "result_Sum", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 116.5 + ] + }, + { + "Id": "result_Minimum", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] + }, + { + "Id": "result_Maximum", + "Label": "metric1 Maximum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 100.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[query]": { + "recorded-date": "19-09-2025, 22:47:57", + "recorded-content": { + "get_metric_statistics": { + "Datapoints": [ + { + "Average": 4.5, + "Maximum": 9.0, + "Minimum": 0.0, + "SampleCount": 10.0, + "Sum": 45.0, + "Timestamp": "timestamp", + "Unit": "None" + } + ], + "Label": "metric", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[json]": { + "recorded-date": "19-09-2025, 22:48:01", + "recorded-content": { + "get_metric_statistics": { + "Datapoints": [ + { + "Average": 4.5, + "Maximum": 9.0, + "Minimum": 0.0, + "SampleCount": 10.0, + "Sum": 45.0, + "Timestamp": "timestamp", + "Unit": "None" + } + ], + "Label": "metric", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 22:48:04", + "recorded-content": { + "get_metric_statistics": { + "Datapoints": [ + { + "Average": 4.5, + "Maximum": 9.0, + "Minimum": 0.0, + "SampleCount": 10.0, + "Sum": 45.0, + "Timestamp": "timestamp", + "Unit": "None" + } + ], + "Label": "metric", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[query]": { + "recorded-date": "19-09-2025, 22:56:18", + "recorded-content": { + "get_metric_statistics_with_different_units": { + "Datapoints": [ + { + "Average": 10.0, + "Timestamp": "timestamp", + "Unit": "None" + }, + { + "Average": 5.0, + "Timestamp": "timestamp", + "Unit": "Count" + }, + { + "Average": 1.0, + "Timestamp": "timestamp", + "Unit": "Seconds" + } + ], + "Label": "m-test", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[json]": { + "recorded-date": "19-09-2025, 22:56:20", + "recorded-content": { + "get_metric_statistics_with_different_units": { + "Datapoints": [ + { + "Average": 10.0, + "Timestamp": "timestamp", + "Unit": "None" + }, + { + "Average": 5.0, + "Timestamp": "timestamp", + "Unit": "Count" + }, + { + "Average": 1.0, + "Timestamp": "timestamp", + "Unit": "Seconds" + } + ], + "Label": "m-test", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 22:56:22", + "recorded-content": { + "get_metric_statistics_with_different_units": { + "Datapoints": [ + { + "Average": 10.0, + "Timestamp": "timestamp", + "Unit": "None" + }, + { + "Average": 5.0, + "Timestamp": "timestamp", + "Unit": "Count" + }, + { + "Average": 1.0, + "Timestamp": "timestamp", + "Unit": "Seconds" + } + ], + "Label": "m-test", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[query]": { + "recorded-date": "19-09-2025, 22:57:24", + "recorded-content": { + "get_metric_data_with_different_units": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 1.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[json]": { + "recorded-date": "19-09-2025, 22:57:26", + "recorded-content": { + "get_metric_data_with_different_units": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 1.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 22:57:28", + "recorded-content": { + "get_metric_data_with_different_units": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 1.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data0]": { + "recorded-date": "19-09-2025, 22:59:13", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 60000.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data1]": { + "recorded-date": "19-09-2025, 22:59:15", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 120000.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data2]": { + "recorded-date": "19-09-2025, 22:59:17", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Count, Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 5.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data0]": { + "recorded-date": "19-09-2025, 22:59:20", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 60000.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data1]": { + "recorded-date": "19-09-2025, 22:59:22", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 120000.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data2]": { + "recorded-date": "19-09-2025, 22:59:24", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Count, Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 5.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data0]": { + "recorded-date": "19-09-2025, 22:59:27", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 60000.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data1]": { + "recorded-date": "19-09-2025, 22:59:29", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 120000.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data2]": { + "recorded-date": "19-09-2025, 22:59:31", + "recorded-content": { + "get_metric_data_with_no_unit_specified": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "m1", + "Label": "m-test", + "Messages": [ + { + "Code": "MultipleUnits", + "Value": "Multiple units returned: '[Count, Milliseconds, Seconds]'" + } + ], + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 5.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs0]": { + "recorded-date": "19-09-2025, 23:00:36", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_30_Seconds", + "Label": "metric1 Minimum 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs1]": { + "recorded-date": "19-09-2025, 23:00:37", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_60_Seconds", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs2]": { + "recorded-date": "19-09-2025, 23:00:39", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_30_Seconds", + "Label": "metric1 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs3]": { + "recorded-date": "19-09-2025, 23:00:40", "recorded-content": { - "cloudwatch_sns_subscription": { - "SubscriptionArn": "arn::sns::111111111111::", + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_30_Milliseconds", + "Label": "metric1 Minimum 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs4]": { + "recorded-date": "19-09-2025, 23:00:42", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_60_Milliseconds", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs5]": { + "recorded-date": "19-09-2025, 23:00:44", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_30_Milliseconds", + "Label": "metric1 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs6]": { + "recorded-date": "19-09-2025, 23:00:45", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_60_Milliseconds", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs0]": { + "recorded-date": "19-09-2025, 23:00:46", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_30_Seconds", + "Label": "metric1 Minimum 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "describe_alarm": { - "CompositeAlarms": [], - "MetricAlarms": [ + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs1]": { + "recorded-date": "19-09-2025, 23:00:47", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "my-metric101", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "Unchecked: Initial alarm creation", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "INSUFFICIENT_DATA", - "Statistic": "Average", - "Threshold": 2.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_60_Seconds", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "alarm-state-describe": { - "CompositeAlarms": [], - "MetricAlarms": [ + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs2]": { + "recorded-date": "19-09-2025, 23:00:49", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "my-metric101", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "testing alarm", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "ALARM", - "Statistic": "Average", - "Threshold": 2.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_30_Seconds", + "Label": "metric1 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "alarm-state-sqs-msg": { - "AWSAccountId": "111111111111", - "AlarmActions": [ - "arn::sns::111111111111:" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs3]": { + "recorded-date": "19-09-2025, 23:00:50", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_30_Milliseconds", + "Label": "metric1 Minimum 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "date", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "InsufficientDataActions": [], - "NewStateReason": "testing alarm", - "NewStateValue": "ALARM", - "OKActions": [ - "arn::sns::111111111111:" + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs4]": { + "recorded-date": "19-09-2025, 23:00:52", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_60_Milliseconds", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } ], - "OldStateValue": "INSUFFICIENT_DATA", - "Region": "", - "StateChangeTime": "date", - "Trigger": { - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "name": "InstanceId", - "value": "abc" - } - ], - "EvaluateLowSampleCountPercentile": "", - "EvaluationPeriods": 2, - "MetricName": "my-metric101", - "Namespace": "", - "Period": 10, - "Statistic": "AVERAGE", - "StatisticType": "Statistic", - "Threshold": 2.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 } - }, - "describe_alarm_disabled": { - "CompositeAlarms": [], - "MetricAlarms": [ + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs5]": { + "recorded-date": "19-09-2025, 23:00:53", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": false, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "my-metric101", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "testing OK state", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK", - "Statistic": "Average", - "Threshold": 2.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_30_Milliseconds", + "Label": "metric1 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "ok-state-action-disabled-describe": { - "CompositeAlarms": [], - "MetricAlarms": [ + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs6]": { + "recorded-date": "19-09-2025, 23:00:55", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": false, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "my-metric101", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "testing OK state", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK", - "Statistic": "Average", - "Threshold": 2.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_60_Milliseconds", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "describe_alarm_enabled": { - "CompositeAlarms": [], - "MetricAlarms": [ + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs0]": { + "recorded-date": "19-09-2025, 23:00:56", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "my-metric101", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "testing OK state", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK", - "Statistic": "Average", - "Threshold": 2.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_30_Seconds", + "Label": "metric1 Minimum 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] } ], "ResponseMetadata": { @@ -259,296 +5145,181 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions": { - "recorded-date": "12-09-2023, 11:56:52", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs1]": { + "recorded-date": "19-09-2025, 23:00:58", "recorded-content": { - "cloudwatch_sns_subscription": { - "SubscriptionArn": "arn::sns::111111111111::", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "alarm-1-describe": { - "CompositeAlarms": [], - "MetricAlarms": [ + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "my-metric101", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", - "StateReasonData": { - "version": "1.0", - "queryDate": "date", - "unit": "Seconds", - "statistic": "Average", - "period": 10, - "recentDatapoints": [], - "threshold": 2.0, - "evaluatedDatapoints": [ - { - "timestamp": "date" - }, - { - "timestamp": "date" - } - ] - }, - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "ALARM", - "Statistic": "Average", - "Threshold": 2.0, - "TreatMissingData": "breaching", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_60_Seconds", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 0.0 + ] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "alarm-1-sqs-msg": { - "AWSAccountId": "111111111111", - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "date", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "InsufficientDataActions": [], - "NewStateReason": "Threshold Crossed: no datapoints were received for 2 periods and 2 missing datapoints were treated as [Breaching].", - "NewStateValue": "ALARM", - "OKActions": [ - "arn::sns::111111111111:" - ], - "OldStateValue": "INSUFFICIENT_DATA", - "Region": "", - "StateChangeTime": "date", - "Trigger": { - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "name": "InstanceId", - "value": "abc" - } - ], - "EvaluateLowSampleCountPercentile": "", - "EvaluationPeriods": 2, - "MetricName": "my-metric101", - "Namespace": "", - "Period": 10, - "Statistic": "AVERAGE", - "StatisticType": "Statistic", - "Threshold": 2.0, - "TreatMissingData": "breaching", - "Unit": "Seconds" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs2]": { + "recorded-date": "19-09-2025, 23:00:59", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_30_Seconds", + "Label": "metric1 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm": { - "recorded-date": "12-05-2025, 16:20:57", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs3]": { + "recorded-date": "19-09-2025, 23:01:01", "recorded-content": { - "describe-alarm": { - "CompositeAlarms": [], - "MetricAlarms": [ + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 1, - "InsufficientDataActions": [], - "MetricName": "my-metric1", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "Unchecked: Initial alarm creation", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "INSUFFICIENT_DATA", - "Statistic": "Average", - "Threshold": 21.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_30_Milliseconds", + "Label": "metric1 Minimum 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "alarm-triggered-sqs-msg": { - "AWSAccountId": "111111111111", - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "date", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "InsufficientDataActions": [], - "NewStateReason": "Threshold Crossed: 1 datapoint [21.5 (MM/DD/YY HH:MM:SS)] was greater than the threshold (21.0).", - "NewStateValue": "ALARM", - "OKActions": [ - "arn::sns::111111111111:" + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs4]": { + "recorded-date": "19-09-2025, 23:01:02", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result_Sum_60_Seconds", + "Label": "metric1 Sum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Minimum_60_Milliseconds", + "Label": "metric1 Minimum", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } ], - "OldStateValue": "INSUFFICIENT_DATA", - "Region": "", - "StateChangeTime": "date", - "Trigger": { - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "name": "InstanceId", - "value": "abc" - } - ], - "EvaluateLowSampleCountPercentile": "", - "EvaluationPeriods": 1, - "MetricName": "my-metric1", - "Namespace": "", - "Period": 10, - "Statistic": "AVERAGE", - "StatisticType": "Statistic", - "Threshold": 21.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 } - }, - "describe-alarm-history": { - "AlarmHistoryItems": [ + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs5]": { + "recorded-date": "19-09-2025, 23:01:03", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "AlarmName": "", - "AlarmType": "MetricAlarm", - "HistoryData": { - "version": "1.0", - "oldState": { - "stateValue": "INSUFFICIENT_DATA", - "stateReason": "Unchecked: Initial alarm creation" - }, - "newState": { - "stateValue": "ALARM", - "stateReason": "Threshold Crossed: 1 datapoint [21.5 (MM/DD/YY HH:MM:SS)] was greater than the threshold (21.0).", - "stateReasonData": { - "version": "1.0", - "queryDate": "date", - "startDate": "date", - "unit": "Seconds", - "statistic": "Average", - "period": 10, - "recentDatapoints": [ - 21.5 - ], - "threshold": 21.0, - "evaluatedDatapoints": [ - { - "timestamp": "date", - "sampleCount": 2.0, - "value": 21.5 - } - ] - } - } - }, - "HistoryItemType": "StateUpdate", - "HistorySummary": "Alarm updated from INSUFFICIENT_DATA to ALARM", - "Timestamp": "timestamp" + "Id": "result_Sum_60_Seconds", + "Label": "metric1 60", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_30_Milliseconds", + "Label": "metric1 30", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "describe-alarms-for-metric": { - "MetricAlarms": [ + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs6]": { + "recorded-date": "19-09-2025, 23:01:05", + "recorded-content": { + "label_generation": { + "Messages": [], + "MetricDataResults": [ { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "testing cloudwatch alarms", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "abc" - } - ], - "EvaluationPeriods": 1, - "InsufficientDataActions": [], - "MetricName": "my-metric1", - "Namespace": "", - "OKActions": [ - "arn::sns::111111111111:" - ], - "Period": 10, - "StateReason": "Threshold Crossed: 1 datapoint [21.5 (MM/DD/YY HH:MM:SS)] was greater than the threshold (21.0).", - "StateReasonData": { - "version": "1.0", - "queryDate": "date", - "startDate": "date", - "unit": "Seconds", - "statistic": "Average", - "period": 10, - "recentDatapoints": [ - 21.5 - ], - "threshold": 21.0, - "evaluatedDatapoints": [ - { - "timestamp": "date", - "sampleCount": 2.0, - "value": 21.5 - } - ] - }, - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "ALARM", - "Statistic": "Average", - "Threshold": 21.0, - "TreatMissingData": "ignore", - "Unit": "Seconds" + "Id": "result_Sum_60_Seconds", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 109.0 + ] + }, + { + "Id": "result_Sum_60_Milliseconds", + "Label": "metric1", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] } ], "ResponseMetadata": { @@ -558,65 +5329,81 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created": { - "recorded-date": "25-09-2023, 10:25:29", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[query]": { + "recorded-date": "19-09-2025, 23:02:11", "recorded-content": { - "get_metric_data": { + "get_metric_with_null_dimensions": { "Messages": [], "MetricDataResults": [ { - "Id": "sent", - "Label": "NumberOfMessagesSent", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 1.0 - ] - }, - { - "Id": "sent_size", - "Label": "SentMessageSize", + "Id": "", + "Label": "", "StatusCode": "Complete", "Timestamps": "timestamp", - "Values": [ - 5.0 - ] - }, + "Values": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[json]": { + "recorded-date": "19-09-2025, 23:02:13", + "recorded-content": { + "get_metric_with_null_dimensions": { + "Messages": [], + "MetricDataResults": [ { - "Id": "empty_receives", - "Label": "NumberOfEmptyReceives", + "Id": "", + "Label": "", "StatusCode": "Complete", "Timestamps": "timestamp", - "Values": [ - 1.0 - ] + "Values": [] } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "get_metric_data_2": { + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 23:02:15", + "recorded-content": { + "get_metric_with_null_dimensions": { "Messages": [], "MetricDataResults": [ { - "Id": "num_msg_received", - "Label": "NumberOfMessagesReceived", + "Id": "", + "Label": "", "StatusCode": "Complete", "Timestamps": "timestamp", - "Values": [ - 1.0 - ] - }, + "Values": [] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[query]": { + "recorded-date": "19-09-2025, 23:04:56", + "recorded-content": { + "result": { + "Messages": [], + "MetricDataResults": [ { - "Id": "num_msg_deleted", - "Label": "NumberOfMessagesDeleted", + "Id": "result", + "Label": "", "StatusCode": "Complete", "Timestamps": "timestamp", - "Values": [ - 1.0 - ] + "Values": [] } ], "ResponseMetadata": { @@ -626,20 +5413,20 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list": { - "recorded-date": "25-09-2023, 10:26:17", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[json]": { + "recorded-date": "19-09-2025, 23:05:05", "recorded-content": { - "get_metric_statistics": { - "Datapoints": [ + "result": { + "Messages": [], + "MetricDataResults": [ { - "Maximum": 10.0, - "SampleCount": 6.0, - "Sum": 42.0, - "Timestamp": "timestamp", - "Unit": "Count" + "Id": "result", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] } ], - "Label": "test-metric", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -647,16 +5434,31 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags": { - "recorded-date": "02-09-2024, 14:03:31", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 23:05:13", "recorded-content": { - "put_metric_alarm": { + "result": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result", + "Label": "", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [] + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "describe_alarms": { + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[query]": { + "recorded-date": "19-09-2025, 23:06:53", + "recorded-content": { + "describe-alarm": { "CompositeAlarms": [], "MetricAlarms": [ { @@ -669,7 +5471,7 @@ "Dimensions": [], "EvaluationPeriods": 1, "InsufficientDataActions": [], - "MetricName": "store_tags", + "MetricName": "metric1", "Namespace": "", "OKActions": [], "Period": 60, @@ -686,36 +5488,15 @@ "HTTPStatusCode": 200 } }, - "list_tags_for_resource_empty ": { - "Tags": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_for_resource": { - "Tags": [ - { - "Key": "tag1", - "Value": "foo" - }, - { - "Key": "tag2", - "Value": "bar" - } - ], + "delete-alarm": { "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "list_tags_for_resource_post_untag": { - "Tags": [ - { - "Key": "tag2", - "Value": "bar" - } - ], + "describe-after-delete": { + "CompositeAlarms": [], + "MetricAlarms": [], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -723,48 +5504,32 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle": { - "recorded-date": "21-11-2023, 13:38:11", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[json]": { + "recorded-date": "19-09-2025, 23:06:54", "recorded-content": { - "get_dashboard": { - "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", - "DashboardBody": { - "widgets": [ - { - "type": "metric", - "x": 0, - "y": 0, - "width": 6, - "height": 6, - "properties": { - "metrics": [ - [ - "AWS/EC2", - "CPUUtilization", - "InstanceId", - "i-12345678" - ] - ], - "region": "", - "view": "timeSeries", - "stacked": false - } - } - ] - }, - "DashboardName": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_dashboards": { - "DashboardEntries": [ + "describe-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", - "DashboardName": "", - "LastModified": "datetime", - "Size": 225 + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "metric1", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 } ], "ResponseMetadata": { @@ -772,29 +5537,15 @@ "HTTPStatusCode": 200 } }, - "list_dashboards_prefix_empty": { - "DashboardEntries": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_dashboards_prefix": { - "DashboardEntries": [ - { - "DashboardArn": "arn::cloudwatch::111111111111:dashboard/", - "DashboardName": "", - "LastModified": "datetime", - "Size": 225 - } - ], + "delete-alarm": { "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "list_dashboards_empty": { - "DashboardEntries": [], + "describe-after-delete": { + "CompositeAlarms": [], + "MetricAlarms": [], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -802,44 +5553,48 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream": { - "recorded-date": "26-10-2023, 09:12:10", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 23:06:55", "recorded-content": { - "create_metric_stream": { - "Arn": "arn::cloudwatch::111111111111:metric-stream/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_metric_stream": { - "Arn": "arn::cloudwatch::111111111111:metric-stream/", - "CreationDate": "datetime", - "FirehoseArn": "", - "IncludeLinkedAccountsMetrics": false, - "LastUpdateDate": "datetime", - "Name": "", - "OutputFormat": "json", - "RoleArn": "", - "State": "running", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "start_metric_stream": { + "describe-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ + { + "ActionsEnabled": true, + "AlarmActions": [], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "metric1", + "Namespace": "", + "OKActions": [], + "Period": 60, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Sum", + "Threshold": 30.0 + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "stop_metric_stream": { + "delete-alarm": { "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "delete_metric_stream": { + "describe-after-delete": { + "CompositeAlarms": [], + "MetricAlarms": [], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -847,253 +5602,217 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule": { - "recorded-date": "26-10-2023, 10:07:59", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[query]": { + "recorded-date": "19-09-2025, 23:07:39", "recorded-content": { - "create_insight_rule": { + "get-metric-stats-max": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "http.server.requests.count", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 5.0 + ] + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "describe_insight_rule": { - "InsightRules": [ - { - "Definition": { - "Schema": { - "Name": "", - "Version": 1 - }, - "LogGroupNames": [ - "API-Gateway-Access-Logs*" - ], - "LogFormat": "CLF", - "Fields": { - "4": "IpAddress", - "7": "StatusCode" - }, - "Contribution": { - "Keys": [ - "IpAddress" - ], - "Filters": [ - { - "Match": "StatusCode", - "EqualTo": 200 - } - ] - }, - "AggregateOn": "Count" - }, - "ManagedRule": false, - "Name": "", - "Schema": "/1", - "State": "ENABLED" - }, + "list-metrics": { + "Metrics": [ { - "Definition": { - "Schema": { - "Name": "", - "Version": 1 - }, - "LogGroupNames": [ - "API-Gateway-Access-Logs*" - ], - "LogFormat": "CLF", - "Fields": { - "4": "IpAddress", - "7": "StatusCode" + "Dimensions": [ + { + "Name": "error", + "Value": "none" }, - "Contribution": { - "Keys": [ - "IpAddress" - ], - "Filters": [ - { - "Match": "StatusCode", - "EqualTo": 200 - } - ] + { + "Name": "exception", + "Value": "none" }, - "AggregateOn": "Count" - }, - "ManagedRule": false, - "Name": "", - "Schema": "/1", - "State": "ENABLED" - }, - { - "Definition": { - "Schema": { - "Name": "", - "Version": 1 + { + "Name": "method", + "Value": "GET" }, - "LogGroupNames": [ - "API-Gateway-Access-Logs*" - ], - "LogFormat": "CLF", - "Fields": { - "4": "IpAddress", - "7": "StatusCode" + { + "Name": "outcome", + "Value": "SUCCESS" }, - "Contribution": { - "Keys": [ - "IpAddress" - ], - "Filters": [ - { - "Match": "StatusCode", - "EqualTo": 200 - } - ] + { + "Name": "status", + "Value": "200" }, - "AggregateOn": "Count" - }, - "ManagedRule": false, - "Name": "", - "Schema": "/1", - "State": "ENABLED" + { + "Name": "uri", + "Value": "/greetings" + } + ], + "MetricName": "http.server.requests.count", + "Namespace": "" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "disable_insight_rule": { - "Failures": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "enable_insight_rule": { - "Failures": [], + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[json]": { + "recorded-date": "19-09-2025, 23:07:47", + "recorded-content": { + "get-metric-stats-max": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "http.server.requests.count", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 5.0 + ] + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "get_insight_rule_report": { - "AggregateValue": 0.0, - "AggregationStatistic": "SampleCount", - "ApproximateUniqueCount": 0, - "Contributors": [], - "KeyLabels": [ - "IpAddress" - ], - "MetricDatapoints": [ - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, - { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 - }, + "list-metrics": { + "Metrics": [ { - "Timestamp": "timestamp", - "UniqueContributors": 0.0 + "Dimensions": [ + { + "Name": "error", + "Value": "none" + }, + { + "Name": "exception", + "Value": "none" + }, + { + "Name": "method", + "Value": "GET" + }, + { + "Name": "outcome", + "Value": "SUCCESS" + }, + { + "Name": "status", + "Value": "200" + }, + { + "Name": "uri", + "Value": "/greetings" + } + ], + "MetricName": "http.server.requests.count", + "Namespace": "" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "delete_insight_rule": { - "Failures": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle": { - "recorded-date": "26-10-2023, 10:42:43", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 23:07:59", "recorded-content": { - "create_anomaly_detector": { + "get-metric-stats-max": { + "Messages": [], + "MetricDataResults": [ + { + "Id": "result1", + "Label": "http.server.requests.count", + "StatusCode": "Complete", + "Timestamps": "timestamp", + "Values": [ + 5.0 + ] + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "describe_anomaly_detector": { - "AnomalyDetectors": [ + "list-metrics": { + "Metrics": [ { - "Configuration": { - "ExcludedTimeRanges": [] - }, "Dimensions": [ { - "Name": "DimensionName", - "Value": "DimensionValue" + "Name": "error", + "Value": "none" + }, + { + "Name": "exception", + "Value": "none" + }, + { + "Name": "method", + "Value": "GET" + }, + { + "Name": "outcome", + "Value": "SUCCESS" + }, + { + "Name": "status", + "Value": "200" + }, + { + "Name": "uri", + "Value": "/greetings" } ], - "MetricName": "MyMetric", - "Namespace": "MyNamespace", - "SingleMetricAnomalyDetector": { - "Dimensions": [ - { - "Name": "DimensionName", - "Value": "DimensionValue" - } - ], - "MetricName": "MyMetric", - "Namespace": "MyNamespace", - "Stat": "Sum" - }, - "Stat": "Sum", - "StateValue": "PENDING_TRAINING" + "MetricName": "http.server.requests.count", + "Namespace": "" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } + } + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[query]": { + "recorded-date": "19-09-2025, 23:08:52", + "recorded-content": { + "error-invalid-amount-datapoints": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "You have requested up to 86400 datapoints, which exceeds the limit of 1440. You may reduce the datapoints requested by increasing Period, or decreasing the time range.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } }, - "delete_anomaly_detector": { + "error-invalid-time-frame": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The parameter StartTime must be less than the parameter EndTime.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "get-metric-statitics": { + "Datapoints": [], + "Label": "metric_name", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -1101,77 +5820,38 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_alarm_lambda_target": { - "recorded-date": "03-01-2024, 17:30:00", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[json]": { + "recorded-date": "19-09-2025, 23:08:53", "recorded-content": { - "lambda-alarm-invocations": { - "source": "aws.cloudwatch", - "alarmArn": "arn::cloudwatch::111111111111:alarm:", - "accountId": "111111111111", - "time": "date", - "region": "", - "alarmData": { - "alarmName": "", - "state": { - "value": "ALARM", - "reason": "testing alarm", - "timestamp": "date" - }, - "previousState": { - "value": "INSUFFICIENT_DATA", - "reason": "Unchecked: Initial alarm creation", - "timestamp": "date" - }, - "configuration": { - "description": "testing lambda alarm action", - "metrics": [ - { - "id": "", - "metricStat": { - "metric": { - "namespace": "namespace", - "name": "metric1", - "dimensions": {} - }, - "period": 10, - "stat": "Average" - }, - "returnData": true - } - ] - } + "error-invalid-amount-datapoints": { + "Error": { + "Code": "InvalidParameterCombination", + "Message": "You have requested up to 86400 datapoints, which exceeds the limit of 1440. You may reduce the datapoints requested by increasing Period, or decreasing the time range.", + "QueryErrorCode": "InvalidParameterCombinationException", + "Type": "Sender" + }, + "message": "You have requested up to 86400 datapoints, which exceeds the limit of 1440. You may reduce the datapoints requested by increasing Period, or decreasing the time range.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm": { - "recorded-date": "25-10-2023, 17:17:06", - "recorded-content": { - "describe_minimal_metric_alarm": { - "CompositeAlarms": [], - "MetricAlarms": [ - { - "ActionsEnabled": true, - "AlarmActions": [], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [], - "EvaluationPeriods": 1, - "InsufficientDataActions": [], - "MetricName": "", - "Namespace": "", - "OKActions": [], - "Period": 10, - "StateReason": "Unchecked: Initial alarm creation", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "INSUFFICIENT_DATA", - "Statistic": "Sum", - "Threshold": 30.0 - } - ], + }, + "error-invalid-time-frame": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The parameter StartTime must be less than the parameter EndTime.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "message": "The parameter StartTime must be less than the parameter EndTime.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "get-metric-statitics": { + "Datapoints": [], + "Label": "metric_name", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -1179,87 +5859,125 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input": { - "recorded-date": "24-11-2023, 12:23:16", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[smithy-rpc-v2-cbor]": { + "recorded-date": "19-09-2025, 23:08:54", "recorded-content": { - "error-invalid-state": { + "error-invalid-amount-datapoints": { "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value 'INVALID' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]", + "Code": "InvalidParameterCombination", + "Message": "You have requested up to 86400 datapoints, which exceeds the limit of 1440. You may reduce the datapoints requested by increasing Period, or decreasing the time range.", + "QueryErrorCode": "InvalidParameterCombinationException", "Type": "Sender" }, + "message": "You have requested up to 86400 datapoints, which exceeds the limit of 1440. You may reduce the datapoints requested by increasing Period, or decreasing the time range.", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 } }, - "error-resource-not-found": { + "error-invalid-time-frame": { "Error": { - "Code": "ResourceNotFound", + "Code": "InvalidParameterValue", + "Message": "The parameter StartTime must be less than the parameter EndTime.", + "QueryErrorCode": "InvalidParameterValueException", "Type": "Sender" }, + "message": "The parameter StartTime must be less than the parameter EndTime.", "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 404 + "HTTPStatusCode": 400 + } + }, + "get-metric-statitics": { + "Datapoints": [], + "Label": "metric_name", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics": { - "recorded-date": "23-11-2023, 14:39:07", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[query]": { + "recorded-date": "22-09-2025, 19:14:19", "recorded-content": { - "get_metric_data": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "result1", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 50.0 - ] - }, - { - "Id": "result2", - "Label": "metric2", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 25.0 - ] - }, + "triggered-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "Id": "result3", - "Label": "metric3", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 55.0 - ] + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "i-0317828c84edbe100" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "CPUUtilization-3", + "Namespace": "", + "OKActions": [ + "" + ], + "Period": 300, + "StateReason": "testing alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 50.0, + "TreatMissingData": "ignore", + "Unit": "Percent" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Sum]": { - "recorded-date": "04-12-2023, 12:22:53", - "recorded-content": { - "get_metric_data": { - "Messages": [], - "MetricDataResults": [ + }, + "reset-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "Id": "result1", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 66.0 - ] + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "i-0317828c84edbe100" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "CPUUtilization-3", + "Namespace": "", + "OKActions": [ + "" + ], + "Period": 300, + "StateReason": "resetting alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 50.0, + "TreatMissingData": "ignore", + "Unit": "Percent" } ], "ResponseMetadata": { @@ -1269,43 +5987,86 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[SampleCount]": { - "recorded-date": "04-12-2023, 12:22:55", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[json]": { + "recorded-date": "22-09-2025, 19:14:26", "recorded-content": { - "get_metric_data": { - "Messages": [], - "MetricDataResults": [ + "triggered-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "Id": "result1", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 11.0 - ] + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "i-0317828c84edbe100" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "CPUUtilization-3", + "Namespace": "", + "OKActions": [ + "" + ], + "Period": 300, + "StateReason": "testing alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 50.0, + "TreatMissingData": "ignore", + "Unit": "Percent" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Minimum]": { - "recorded-date": "04-12-2023, 12:22:58", - "recorded-content": { - "get_metric_data": { - "Messages": [], - "MetricDataResults": [ + }, + "reset-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "Id": "result1", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 1.0 - ] + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "i-0317828c84edbe100" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "CPUUtilization-3", + "Namespace": "", + "OKActions": [ + "" + ], + "Period": 300, + "StateReason": "resetting alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 50.0, + "TreatMissingData": "ignore", + "Unit": "Percent" } ], "ResponseMetadata": { @@ -1315,43 +6076,86 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Maximum]": { - "recorded-date": "04-12-2023, 12:23:00", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[smithy-rpc-v2-cbor]": { + "recorded-date": "22-09-2025, 19:14:33", "recorded-content": { - "get_metric_data": { - "Messages": [], - "MetricDataResults": [ + "triggered-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "Id": "result1", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 11.0 - ] + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "i-0317828c84edbe100" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "CPUUtilization-3", + "Namespace": "", + "OKActions": [ + "" + ], + "Period": 300, + "StateReason": "testing alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 50.0, + "TreatMissingData": "ignore", + "Unit": "Percent" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Average]": { - "recorded-date": "04-12-2023, 12:23:02", - "recorded-content": { - "get_metric_data": { - "Messages": [], - "MetricDataResults": [ + }, + "reset-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "Id": "result1", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 6.0 - ] + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "i-0317828c84edbe100" + } + ], + "EvaluationPeriods": 2, + "InsufficientDataActions": [], + "MetricName": "CPUUtilization-3", + "Namespace": "", + "OKActions": [ + "" + ], + "Period": 300, + "StateReason": "resetting alarm", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "OK", + "Statistic": "Average", + "Threshold": 50.0, + "TreatMissingData": "ignore", + "Unit": "Percent" } ], "ResponseMetadata": { @@ -1361,174 +6165,272 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions": { - "recorded-date": "23-11-2023, 15:10:11", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[query]": { + "recorded-date": "22-09-2025, 19:16:47", "recorded-content": { - "get_metric_data": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "result1", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 11.0 - ] - } - ], + "error-invalid-state": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value 'INVALID' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]", + "Type": "Sender" + }, "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 200 + "HTTPStatusCode": 400 + } + }, + "error-resource-not-found": { + "Error": { + "Code": "ResourceNotFound", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics": { - "recorded-date": "04-12-2023, 14:13:08", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[json]": { + "recorded-date": "22-09-2025, 19:16:48", "recorded-content": { - "get_metric_statistics": { - "Datapoints": [ - { - "Average": 4.5, - "Maximum": 9.0, - "Minimum": 0.0, - "SampleCount": 10.0, - "Sum": 45.0, - "Timestamp": "timestamp", - "Unit": "None" - } - ], - "Label": "metric", + "error-invalid-state": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value 'INVALID' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]", + "QueryErrorCode": "ValidationException", + "Type": "Sender" + }, "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 200 + "HTTPStatusCode": 400 + } + }, + "error-resource-not-found": { + "Error": { + "Code": "ResourceNotFound", + "Message": "", + "QueryErrorCode": "ResourceNotFound", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels": { - "recorded-date": "04-12-2023, 09:13:59", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[smithy-rpc-v2-cbor]": { + "recorded-date": "22-09-2025, 19:16:48", "recorded-content": { - "get_metric_data_with_zero_and_labels": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "result_Average", - "Label": "metric1 Average", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 19.416666666666668 - ] - }, - { - "Id": "result_Sum", - "Label": "metric1 Sum", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 116.5 - ] - }, - { - "Id": "result_Minimum", - "Label": "metric1 Minimum", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 0.0 - ] - }, - { - "Id": "result_Maximum", - "Label": "metric1 Maximum", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 100.0 - ] - } - ], + "error-invalid-state": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value 'INVALID' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]", + "QueryErrorCode": "ValidationException", + "Type": "Sender" + }, "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 200 + "HTTPStatusCode": 400 + } + }, + "error-resource-not-found": { + "Error": { + "Code": "ResourceNotFound", + "Message": "", + "QueryErrorCode": "ResourceNotFound", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units": { - "recorded-date": "05-01-2024, 16:15:39", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm": { + "recorded-date": "26-02-2026, 10:14:11", "recorded-content": { - "get_metric_statistics_with_different_units": { - "Datapoints": [ - { - "Average": 10.0, - "Timestamp": "timestamp", - "Unit": "None" - }, - { - "Average": 5.0, - "Timestamp": "timestamp", - "Unit": "Count" - }, + "describe-alarm": { + "CompositeAlarms": [], + "MetricAlarms": [ { - "Average": 1.0, - "Timestamp": "timestamp", + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "Name": "InstanceId", + "Value": "abc" + } + ], + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "my-metric1", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Unchecked: Initial alarm creation", + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "INSUFFICIENT_DATA", + "Statistic": "Average", + "Threshold": 21.0, + "TreatMissingData": "ignore", "Unit": "Seconds" } ], - "Label": "m-test", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units": { - "recorded-date": "07-12-2023, 14:04:49", - "recorded-content": { - "get_metric_data_with_different_units": { - "Messages": [], - "MetricDataResults": [ + }, + "alarm-triggered-sqs-msg": { + "AWSAccountId": "111111111111", + "ActionExecutedAfterMuteWindow": false, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "date", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "InsufficientDataActions": [], + "NewStateReason": "Threshold Crossed: 1 datapoint [21.5 (MM/DD/YY HH:MM:SS)] was greater than the threshold (21.0).", + "NewStateValue": "ALARM", + "OKActions": [ + "arn::sns::111111111111:" + ], + "OldStateValue": "INSUFFICIENT_DATA", + "Region": "", + "StateChangeTime": "date", + "Trigger": { + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ + { + "name": "InstanceId", + "value": "abc" + } + ], + "EvaluateLowSampleCountPercentile": "", + "EvaluationPeriods": 1, + "MetricName": "my-metric1", + "Namespace": "", + "Period": 10, + "Statistic": "AVERAGE", + "StatisticType": "Statistic", + "Threshold": 21.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" + } + }, + "describe-alarm-history": { + "AlarmHistoryItems": [ { - "Id": "m1", - "Label": "m-test", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 1.0 - ] + "AlarmName": "", + "AlarmType": "MetricAlarm", + "HistoryData": { + "version": "1.0", + "oldState": { + "stateValue": "INSUFFICIENT_DATA", + "stateReason": "Unchecked: Initial alarm creation" + }, + "newState": { + "stateValue": "ALARM", + "stateReason": "Threshold Crossed: 1 datapoint [21.5 (MM/DD/YY HH:MM:SS)] was greater than the threshold (21.0).", + "stateReasonData": { + "version": "1.0", + "queryDate": "date", + "startDate": "date", + "unit": "Seconds", + "statistic": "Average", + "period": 10, + "recentDatapoints": [ + 21.5 + ], + "threshold": 21.0, + "evaluatedDatapoints": [ + { + "timestamp": "date", + "sampleCount": 2.0, + "value": 21.5 + } + ] + } + } + }, + "HistoryItemType": "StateUpdate", + "HistorySummary": "Alarm updated from INSUFFICIENT_DATA to ALARM", + "Timestamp": "timestamp" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data0]": { - "recorded-date": "15-12-2023, 08:58:39", - "recorded-content": { - "get_metric_data_with_no_unit_specified": { - "Messages": [], - "MetricDataResults": [ + }, + "describe-alarms-for-metric": { + "MetricAlarms": [ { - "Id": "m1", - "Label": "m-test", - "Messages": [ + "ActionsEnabled": true, + "AlarmActions": [ + "arn::sns::111111111111:" + ], + "AlarmArn": "arn::cloudwatch::111111111111:alarm:", + "AlarmConfigurationUpdatedTimestamp": "timestamp", + "AlarmDescription": "testing cloudwatch alarms", + "AlarmName": "", + "ComparisonOperator": "GreaterThanThreshold", + "Dimensions": [ { - "Code": "MultipleUnits", - "Value": "Multiple units returned: '[Milliseconds, Seconds]'" + "Name": "InstanceId", + "Value": "abc" } ], - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 60000.0 - ] + "EvaluationPeriods": 1, + "InsufficientDataActions": [], + "MetricName": "my-metric1", + "Namespace": "", + "OKActions": [ + "arn::sns::111111111111:" + ], + "Period": 10, + "StateReason": "Threshold Crossed: 1 datapoint [21.5 (MM/DD/YY HH:MM:SS)] was greater than the threshold (21.0).", + "StateReasonData": { + "version": "1.0", + "queryDate": "date", + "startDate": "date", + "unit": "Seconds", + "statistic": "Average", + "period": 10, + "recentDatapoints": [ + 21.5 + ], + "threshold": 21.0, + "evaluatedDatapoints": [ + { + "timestamp": "date", + "sampleCount": 2.0, + "value": 21.5 + } + ] + }, + "StateTransitionedTimestamp": "timestamp", + "StateUpdatedTimestamp": "timestamp", + "StateValue": "ALARM", + "Statistic": "Average", + "Threshold": 21.0, + "TreatMissingData": "ignore", + "Unit": "Seconds" } ], "ResponseMetadata": { @@ -1538,89 +6440,37 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs0]": { - "recorded-date": "15-12-2023, 11:27:23", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created": { + "recorded-date": "26-02-2026, 12:06:25", "recorded-content": { - "label_generation": { + "get_metric_data": { "Messages": [], "MetricDataResults": [ { - "Id": "result_Sum_60_Seconds", - "Label": "metric1 Sum 60", + "Id": "sent", + "Label": "NumberOfMessagesSent", "StatusCode": "Complete", "Timestamps": "timestamp", "Values": [ - 109.0 + 1.0 ] }, { - "Id": "result_Minimum_30_Seconds", - "Label": "metric1 Minimum 30", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 0.0 - ] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs1]": { - "recorded-date": "15-12-2023, 11:27:25", - "recorded-content": { - "label_generation": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "result_Sum_60_Seconds", - "Label": "metric1 Sum", + "Id": "sent_size", + "Label": "SentMessageSize", "StatusCode": "Complete", "Timestamps": "timestamp", "Values": [ - 109.0 + 5.0 ] }, { - "Id": "result_Minimum_60_Seconds", - "Label": "metric1 Minimum", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 0.0 - ] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data1]": { - "recorded-date": "15-12-2023, 08:58:42", - "recorded-content": { - "get_metric_data_with_no_unit_specified": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "m1", - "Label": "m-test", - "Messages": [ - { - "Code": "MultipleUnits", - "Value": "Multiple units returned: '[Milliseconds, Seconds]'" - } - ], + "Id": "empty_receives", + "Label": "NumberOfEmptyReceives", "StatusCode": "Complete", "Timestamps": "timestamp", "Values": [ - 120000.0 + 1.0 ] } ], @@ -1628,31 +6478,26 @@ "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs2]": { - "recorded-date": "15-12-2023, 11:27:27", - "recorded-content": { - "label_generation": { + }, + "get_metric_data_2": { "Messages": [], "MetricDataResults": [ { - "Id": "result_Sum_60_Seconds", - "Label": "metric1 60", + "Id": "num_msg_received", + "Label": "NumberOfMessagesReceived", "StatusCode": "Complete", "Timestamps": "timestamp", "Values": [ - 109.0 + 1.0 ] }, { - "Id": "result_Sum_30_Seconds", - "Label": "metric1 30", + "Id": "num_msg_deleted", + "Label": "NumberOfMessagesDeleted", "StatusCode": "Complete", "Timestamps": "timestamp", "Values": [ - 109.0 + 1.0 ] } ], @@ -1663,312 +6508,172 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data2]": { - "recorded-date": "15-12-2023, 08:58:44", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_metric_alarm[query]": { + "recorded-date": "17-02-2026, 12:33:44", "recorded-content": { - "get_metric_data_with_no_unit_specified": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "m1", - "Label": "m-test", - "Messages": [ - { - "Code": "MultipleUnits", - "Value": "Multiple units returned: '[Count, Milliseconds, Seconds]'" - } - ], - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 5.0 - ] - } - ], + "put_metric_alarm": { "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs3]": { - "recorded-date": "15-12-2023, 11:27:29", - "recorded-content": { - "label_generation": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "result_Sum_60_Seconds", - "Label": "metric1 Sum 60", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 109.0 - ] - }, + }, + "list_tags_for_resource_after_creation": { + "Tags": [ { - "Id": "result_Minimum_30_Milliseconds", - "Label": "metric1 Minimum 30", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [] + "Key": "Environment", + "Value": "Production" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs4]": { - "recorded-date": "15-12-2023, 11:27:31", - "recorded-content": { - "label_generation": { - "Messages": [], - "MetricDataResults": [ + }, + "list_tags_for_resource_updated": { + "Tags": [ { - "Id": "result_Sum_60_Seconds", - "Label": "metric1 Sum", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 109.0 - ] + "Key": "Environment", + "Value": "Production" }, { - "Id": "result_Minimum_60_Milliseconds", - "Label": "metric1 Minimum", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [] + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs5]": { - "recorded-date": "15-12-2023, 11:27:33", - "recorded-content": { - "label_generation": { - "Messages": [], - "MetricDataResults": [ + }, + "list_tags_for_resource_post_untag": { + "Tags": [ { - "Id": "result_Sum_60_Seconds", - "Label": "metric1 60", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 109.0 - ] + "Key": "Environment", + "Value": "Production" }, { - "Id": "result_Sum_30_Milliseconds", - "Label": "metric1 30", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [] + "Key": "tag2", + "Value": "bar" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } + }, + "list_tags_for_deleted_resource": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs6]": { - "recorded-date": "15-12-2023, 11:27:35", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_metric_alarm[json]": { + "recorded-date": "17-02-2026, 12:33:45", "recorded-content": { - "label_generation": { - "Messages": [], - "MetricDataResults": [ - { - "Id": "result_Sum_60_Seconds", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 109.0 - ] - }, + "put_metric_alarm": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_after_creation": { + "Tags": [ { - "Id": "result_Sum_60_Milliseconds", - "Label": "metric1", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [] + "Key": "Environment", + "Value": "Production" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results": { - "recorded-date": "10-01-2024, 15:29:50", - "recorded-content": { - "result": { - "Messages": [], - "MetricDataResults": [ + }, + "list_tags_for_resource_updated": { + "Tags": [ { - "Id": "result", - "Label": "", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [] + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions": { - "recorded-date": "09-01-2024, 20:13:11", - "recorded-content": { - "get_metric_with_null_dimensions": { - "Messages": [], - "MetricDataResults": [ + }, + "list_tags_for_resource_post_untag": { + "Tags": [ { - "Id": "", - "Label": "", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [] + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag2", + "Value": "bar" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } + }, + "list_tags_for_deleted_resource": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm": { - "recorded-date": "19-01-2024, 15:29:42", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_metric_alarm[smithy-rpc-v2-cbor]": { + "recorded-date": "17-02-2026, 12:33:45", "recorded-content": { - "triggered-alarm": { - "CompositeAlarms": [], - "MetricAlarms": [ - { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "i-0317828c84edbe100" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "CPUUtilization-3", - "Namespace": "", - "OKActions": [ - "" - ], - "Period": 300, - "StateReason": "testing alarm", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "ALARM", - "Statistic": "Average", - "Threshold": 50.0, - "TreatMissingData": "ignore", - "Unit": "Percent" - } - ], + "put_metric_alarm": { "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "reset-alarm": { - "CompositeAlarms": [], - "MetricAlarms": [ + "list_tags_for_resource_after_creation": { + "Tags": [ { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "Test Alarm when CPU exceeds 50 percent", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [ - { - "Name": "InstanceId", - "Value": "i-0317828c84edbe100" - } - ], - "EvaluationPeriods": 2, - "InsufficientDataActions": [], - "MetricName": "CPUUtilization-3", - "Namespace": "", - "OKActions": [ - "" - ], - "Period": 300, - "StateReason": "resetting alarm", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK", - "Statistic": "Average", - "Threshold": 50.0, - "TreatMissingData": "ignore", - "Unit": "Percent" + "Key": "Environment", + "Value": "Production" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm": { - "recorded-date": "12-01-2024, 14:06:14", - "recorded-content": { - "describe-alarm": { - "CompositeAlarms": [], - "MetricAlarms": [ + }, + "list_tags_for_resource_updated": { + "Tags": [ { - "ActionsEnabled": true, - "AlarmActions": [], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmName": "", - "ComparisonOperator": "GreaterThanThreshold", - "Dimensions": [], - "EvaluationPeriods": 1, - "InsufficientDataActions": [], - "MetricName": "metric1", - "Namespace": "", - "OKActions": [], - "Period": 60, - "StateReason": "Unchecked: Initial alarm creation", - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "INSUFFICIENT_DATA", - "Statistic": "Sum", - "Threshold": 30.0 + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" } ], "ResponseMetadata": { @@ -1976,15 +6681,24 @@ "HTTPStatusCode": 200 } }, - "delete-alarm": { + "list_tags_for_resource_post_untag": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag2", + "Value": "bar" + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "describe-after-delete": { - "CompositeAlarms": [], - "MetricAlarms": [], + "list_tags_for_deleted_resource": { + "Tags": [], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -1992,20 +6706,14 @@ } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics": { - "recorded-date": "26-07-2024, 15:38:56", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_composite_alarm[query]": { + "recorded-date": "17-02-2026, 12:33:17", "recorded-content": { - "get-metric-stats-max": { - "Messages": [], - "MetricDataResults": [ + "list_tags_for_resource_after_creation ": { + "Tags": [ { - "Id": "result1", - "Label": "http.server.requests.count", - "StatusCode": "Complete", - "Timestamps": "timestamp", - "Values": [ - 5.0 - ] + "Key": "Environment", + "Value": "Production" } ], "ResponseMetadata": { @@ -2013,255 +6721,168 @@ "HTTPStatusCode": 200 } }, - "list-metrics": { - "Metrics": [ + "list_tags_for_resource_updated": { + "Tags": [ { - "Dimensions": [ - { - "Name": "error", - "Value": "none" - }, - { - "Name": "exception", - "Value": "none" - }, - { - "Name": "method", - "Value": "GET" - }, - { - "Name": "outcome", - "Value": "SUCCESS" - }, - { - "Name": "status", - "Value": "200" - }, - { - "Name": "uri", - "Value": "/greetings" - } - ], - "MetricName": "http.server.requests.count", - "Namespace": "" + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_tags_for_resource_post_untag": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag2", + "Value": "bar" } ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } + }, + "list_tags_for_deleted_resource": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints": { - "recorded-date": "23-08-2024, 14:14:54", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_composite_alarm[json]": { + "recorded-date": "17-02-2026, 12:33:18", "recorded-content": { - "error-invalid-amount-datapoints": { - "Error": { - "Code": "InvalidParameterCombination", - "Message": "You have requested up to 86400 datapoints, which exceeds the limit of 1440. You may reduce the datapoints requested by increasing Period, or decreasing the time range.", - "Type": "Sender" - }, + "list_tags_for_resource_after_creation ": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + } + ], "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 400 + "HTTPStatusCode": 200 } }, - "error-invalid-time-frame": { - "Error": { - "Code": "InvalidParameterValue", - "Message": "The parameter StartTime must be less than the parameter EndTime.", - "Type": "Sender" - }, + "list_tags_for_resource_updated": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" + } + ], "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 400 + "HTTPStatusCode": 200 } }, - "get-metric-statitics": { - "Datapoints": [], - "Label": "metric_name", + "list_tags_for_resource_post_untag": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag2", + "Value": "bar" + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } - } - } - }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name": { - "recorded-date": "04-09-2024, 16:28:05", - "recorded-content": { - "error-invalid-dashboardname": { - "Error": { - "Code": "InvalidParameterValue", - "Message": "The value for field DashboardName contains invalid characters. It can only contain alphanumerics, dash (-) and underscore (_).\n", - "Type": "Sender" - }, + }, + "list_tags_for_deleted_resource": { + "Tags": [], "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 400 + "HTTPStatusCode": 200 } } } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm": { - "recorded-date": "14-11-2024, 14:25:30", + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_composite_alarm[smithy-rpc-v2-cbor]": { + "recorded-date": "17-02-2026, 12:33:18", "recorded-content": { - "put-composite-alarm": { + "list_tags_for_resource_after_creation ": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + } + ], "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } }, - "composite-alarm-in-alarm-when-alarm-1-is-in-alarm": { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "composite alarm description", - "AlarmName": "", - "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", - "InsufficientDataActions": [], - "OKActions": [ - "arn::sns::111111111111:" - ], - "StateReason": "", - "StateReasonData": { - "triggeringAlarms": [ - { - "arn": "arn::cloudwatch::111111111111:alarm:", - "state": { - "value": "ALARM", - "timestamp": "date" - } - } - ] - }, - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "ALARM" - }, - "composite-alarm-in-ok-when-alarm-1-is-back-to-ok": { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "composite alarm description", - "AlarmName": "", - "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", - "InsufficientDataActions": [], - "OKActions": [ - "arn::sns::111111111111:" - ], - "StateReason": "", - "StateReasonData": { - "triggeringAlarms": [ - { - "arn": "arn::cloudwatch::111111111111:alarm:", - "state": { - "value": "OK", - "timestamp": "date" - } - } - ] - }, - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK" - }, - "composite-alarm-in-alarm-when-alarm-2-is-in-alarm": { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "composite alarm description", - "AlarmName": "", - "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", - "InsufficientDataActions": [], - "OKActions": [ - "arn::sns::111111111111:" + "list_tags_for_resource_updated": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag1", + "Value": "foo" + }, + { + "Key": "tag2", + "Value": "bar" + } ], - "StateReason": "", - "StateReasonData": { - "triggeringAlarms": [ - { - "arn": "arn::cloudwatch::111111111111:alarm:", - "state": { - "value": "ALARM", - "timestamp": "date" - } - } - ] - }, - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "ALARM" + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } }, - "composite-alarm-in-ok-when-alarm-2-is-back-to-ok": { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "composite alarm description", - "AlarmName": "", - "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", - "InsufficientDataActions": [], - "OKActions": [ - "arn::sns::111111111111:" + "list_tags_for_resource_post_untag": { + "Tags": [ + { + "Key": "Environment", + "Value": "Production" + }, + { + "Key": "tag2", + "Value": "bar" + } ], - "StateReason": "", - "StateReasonData": { - "triggeringAlarms": [ - { - "arn": "arn::cloudwatch::111111111111:alarm:", - "state": { - "value": "OK", - "timestamp": "date" - } - } - ] - }, - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK" + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } }, - "composite-alarm-is-triggered-by-alarm-1-and-then-unchanged-by-alarm-2": { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "composite alarm description", - "AlarmName": "", - "AlarmRule": "ALARM(\"arn::cloudwatch::111111111111:alarm:\") OR ALARM(\"arn::cloudwatch::111111111111:alarm:\")", - "InsufficientDataActions": [], - "OKActions": [ - "arn::sns::111111111111:" - ], - "StateReason": "", - "StateReasonData": { - "triggeringAlarms": [ - { - "arn": "arn::cloudwatch::111111111111:alarm:", - "state": { - "value": "ALARM", - "timestamp": "date" - } - } - ] - }, - "StateTransitionedTimestamp": "timestamp", - "StateUpdatedTimestamp": "timestamp", - "StateValue": "ALARM" + "list_tags_for_deleted_resource": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } } } } diff --git a/tests/aws/services/cloudwatch/test_cloudwatch.validation.json b/tests/aws/services/cloudwatch/test_cloudwatch.validation.json index 428f5d6c84b8f..47634cdfa5ec3 100644 --- a/tests/aws/services/cloudwatch/test_cloudwatch.validation.json +++ b/tests/aws/services/cloudwatch/test_cloudwatch.validation.json @@ -1,74 +1,1562 @@ { + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[json]": { + "last_validated_date": "2025-12-17T13:53:06+00:00", + "durations_in_seconds": { + "setup": 0.56, + "call": 3.57, + "teardown": 0.0, + "total": 4.13 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[query]": { + "last_validated_date": "2025-12-17T13:53:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.0, + "teardown": 0.0, + "total": 3.0 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_basic_operations_multiple_protocols[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-12-17T13:53:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.15, + "teardown": 0.0, + "total": 3.15 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[json]": { + "last_validated_date": "2025-09-22T19:17:52+00:00", + "durations_in_seconds": { + "setup": 0.43, + "call": 2.35, + "teardown": 0.01, + "total": 2.79 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[query]": { + "last_validated_date": "2025-09-22T19:17:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.02, + "teardown": 0.0, + "total": 1.02 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_exception_serializing_with_no_shape_in_spec[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-22T19:17:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.32, + "teardown": 0.0, + "total": 1.32 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_multi_protocol_client_fixture[json]": { + "last_validated_date": "2025-09-19T17:26:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.0, + "total": 0.13 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_multi_protocol_client_fixture[query]": { + "last_validated_date": "2025-09-19T17:26:49+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 0.59, + "teardown": 0.0, + "total": 0.64 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudWatchMultiProtocol::test_multi_protocol_client_fixture[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T17:26:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.14, + "teardown": 0.0, + "total": 0.14 + } + }, "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_alarm_lambda_target": { "last_validated_date": "2024-01-03T17:30:00+00:00" }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle": { - "last_validated_date": "2023-10-26T08:42:43+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[json]": { + "last_validated_date": "2025-09-19T21:45:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.33, + "teardown": 0.0, + "total": 0.33 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[query]": { + "last_validated_date": "2025-09-19T21:45:48+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.67, + "teardown": 0.0, + "total": 1.16 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:45:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.35, + "teardown": 0.0, + "total": 0.35 + } }, "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created": { - "last_validated_date": "2023-09-25T08:25:29+00:00" + "last_validated_date": "2026-02-26T12:06:25+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 90.98, + "teardown": 0.19, + "total": 91.67 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[json]": { + "last_validated_date": "2025-09-19T21:12:42+00:00", + "durations_in_seconds": { + "setup": 0.15, + "call": 59.84, + "teardown": 1.27, + "total": 61.26 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[query]": { + "last_validated_date": "2025-09-19T21:11:41+00:00", + "durations_in_seconds": { + "setup": 0.99, + "call": 18.72, + "teardown": 1.11, + "total": 20.82 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:13:17+00:00", + "durations_in_seconds": { + "setup": 0.15, + "call": 33.39, + "teardown": 1.29, + "total": 34.83 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[json]": { + "last_validated_date": "2025-09-19T21:39:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 188.54, + "teardown": 3.22, + "total": 191.76 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[query]": { + "last_validated_date": "2025-09-19T21:35:58+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 111.63, + "teardown": 3.45, + "total": 115.56 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:41:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 148.02, + "teardown": 3.33, + "total": 151.35 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[json]": { + "last_validated_date": "2025-09-19T21:32:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.37, + "teardown": 0.01, + "total": 1.38 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[query]": { + "last_validated_date": "2025-09-19T21:32:55+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 1.44, + "teardown": 0.0, + "total": 1.92 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:32:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.4, + "teardown": 0.0, + "total": 1.4 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering[json]": { + "last_validated_date": "2025-09-19T22:55:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.69, + "teardown": 0.0, + "total": 1.69 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering[query]": { + "last_validated_date": "2025-09-19T22:55:31+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 3.35, + "teardown": 0.0, + "total": 3.4 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:55:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.35, + "teardown": 0.0, + "total": 3.35 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[json]": { + "last_validated_date": "2025-09-19T23:06:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.7, + "teardown": 0.0, + "total": 0.7 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[query]": { + "last_validated_date": "2025-09-19T23:06:53+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.06, + "teardown": 0.01, + "total": 1.58 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T23:06:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.06, + "teardown": 0.0, + "total": 1.06 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[json]": { + "last_validated_date": "2025-09-19T20:38:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.33, + "teardown": 0.24, + "total": 0.57 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[query]": { + "last_validated_date": "2025-09-19T20:38:27+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.78, + "teardown": 0.22, + "total": 1.48 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:38:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.36, + "teardown": 0.22, + "total": 0.58 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[json]": { + "last_validated_date": "2025-09-19T21:47:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.38, + "teardown": 0.35, + "total": 0.73 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[query]": { + "last_validated_date": "2025-09-19T21:47:20+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.79, + "teardown": 0.33, + "total": 1.62 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:47:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.31, + "teardown": 0.21, + "total": 0.52 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[json]": { + "last_validated_date": "2025-09-19T21:17:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.26, + "teardown": 1.11, + "total": 14.37 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[query]": { + "last_validated_date": "2025-09-19T21:17:32+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 13.76, + "teardown": 1.41, + "total": 15.72 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:18:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.05, + "teardown": 1.11, + "total": 14.16 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data[json]": { + "last_validated_date": "2025-09-19T20:25:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.58, + "teardown": 0.0, + "total": 2.58 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data[query]": { + "last_validated_date": "2025-09-19T20:25:46+00:00", + "durations_in_seconds": { + "setup": 0.08, + "call": 2.92, + "teardown": 0.0, + "total": 3.0 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:25:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.86, + "teardown": 0.0, + "total": 2.86 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data0]": { + "last_validated_date": "2025-09-19T22:59:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.24, + "teardown": 0.01, + "total": 2.25 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data1]": { + "last_validated_date": "2025-09-19T22:59:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.23, + "teardown": 0.0, + "total": 2.23 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[json-metric_data2]": { + "last_validated_date": "2025-09-19T22:59:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.55, + "teardown": 0.0, + "total": 2.55 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data0]": { + "last_validated_date": "2025-09-19T22:59:13+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 2.55, + "teardown": 0.01, + "total": 3.06 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data1]": { + "last_validated_date": "2025-09-19T22:59:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.24, + "teardown": 0.01, + "total": 2.25 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[query-metric_data2]": { + "last_validated_date": "2025-09-19T22:59:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.24, + "teardown": 0.01, + "total": 2.25 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data0]": { + "last_validated_date": "2025-09-19T22:59:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.23, + "teardown": 0.01, + "total": 2.24 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data1]": { + "last_validated_date": "2025-09-19T22:59:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.23, + "teardown": 0.0, + "total": 2.23 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[smithy-rpc-v2-cbor-metric_data2]": { + "last_validated_date": "2025-09-19T22:59:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.23, + "teardown": 0.0, + "total": 2.23 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[json]": { + "last_validated_date": "2025-09-19T20:29:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.44, + "teardown": 0.0, + "total": 1.44 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[query]": { + "last_validated_date": "2025-09-19T20:29:17+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 2.94, + "teardown": 0.01, + "total": 3.44 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:29:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.76, + "teardown": 0.0, + "total": 1.76 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination[json]": { + "last_validated_date": "2025-09-19T22:54:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.75, + "teardown": 0.0, + "total": 4.75 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination[query]": { + "last_validated_date": "2025-09-19T22:54:08+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 5.12, + "teardown": 0.0, + "total": 5.17 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:54:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.77, + "teardown": 0.0, + "total": 4.77 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Average]": { + "last_validated_date": "2025-09-19T20:32:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.34, + "teardown": 0.01, + "total": 2.35 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Maximum]": { + "last_validated_date": "2025-09-19T20:32:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.33, + "teardown": 0.0, + "total": 2.33 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Minimum]": { + "last_validated_date": "2025-09-19T20:32:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.36, + "teardown": 0.0, + "total": 2.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-SampleCount]": { + "last_validated_date": "2025-09-19T20:32:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.68, + "teardown": 0.0, + "total": 2.68 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[json-Sum]": { + "last_validated_date": "2025-09-19T20:32:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.37, + "teardown": 0.0, + "total": 2.37 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Average]": { + "last_validated_date": "2025-09-19T20:32:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.83, + "teardown": 0.0, + "total": 3.83 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Maximum]": { + "last_validated_date": "2025-09-19T20:31:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.77, + "teardown": 0.0, + "total": 2.77 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Minimum]": { + "last_validated_date": "2025-09-19T20:31:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.35, + "teardown": 0.01, + "total": 2.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-SampleCount]": { + "last_validated_date": "2025-09-19T20:31:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.33, + "teardown": 0.01, + "total": 2.34 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[query-Sum]": { + "last_validated_date": "2025-09-19T20:31:51+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 2.65, + "teardown": 0.0, + "total": 3.15 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Average]": { + "last_validated_date": "2025-09-19T20:32:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.34, + "teardown": 0.01, + "total": 2.35 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Maximum]": { + "last_validated_date": "2025-09-19T20:32:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.66, + "teardown": 0.0, + "total": 2.66 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Minimum]": { + "last_validated_date": "2025-09-19T20:32:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.32, + "teardown": 0.0, + "total": 2.32 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-SampleCount]": { + "last_validated_date": "2025-09-19T20:32:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.34, + "teardown": 0.0, + "total": 2.34 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[smithy-rpc-v2-cbor-Sum]": { + "last_validated_date": "2025-09-19T20:32:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.65, + "teardown": 0.0, + "total": 2.65 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[json]": { + "last_validated_date": "2025-09-19T22:57:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.22, + "teardown": 0.0, + "total": 2.22 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[query]": { + "last_validated_date": "2025-09-19T22:57:24+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 2.55, + "teardown": 0.0, + "total": 3.02 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:57:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.24, + "teardown": 0.01, + "total": 2.25 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[json]": { + "last_validated_date": "2025-09-19T20:33:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.44, + "teardown": 0.01, + "total": 2.45 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[query]": { + "last_validated_date": "2025-09-19T20:33:39+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 2.77, + "teardown": 0.0, + "total": 3.26 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:33:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.74, + "teardown": 0.0, + "total": 2.74 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[json]": { + "last_validated_date": "2025-09-19T22:47:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.34, + "teardown": 0.01, + "total": 1.35 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions": { - "last_validated_date": "2024-01-19T15:05:50+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[query]": { + "last_validated_date": "2025-09-19T22:46:59+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.7, + "teardown": 0.01, + "total": 2.21 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream": { - "last_validated_date": "2023-10-26T07:12:10+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:47:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.35, + "teardown": 0.0, + "total": 1.35 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle": { - "last_validated_date": "2023-10-25T11:16:20+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[json]": { + "last_validated_date": "2025-09-19T22:48:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.54, + "teardown": 0.0, + "total": 3.54 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm": { - "last_validated_date": "2024-01-12T14:06:42+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[query]": { + "last_validated_date": "2025-09-19T22:47:57+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 3.81, + "teardown": 0.01, + "total": 4.29 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly": { - "last_validated_date": "2024-09-04T15:59:17+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:48:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.46, + "teardown": 0.01, + "total": 3.47 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions": { - "last_validated_date": "2023-09-12T10:00:45+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[json]": { + "last_validated_date": "2025-09-19T23:05:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.46, + "teardown": 0.01, + "total": 8.47 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data0]": { - "last_validated_date": "2024-01-11T11:07:38+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[query]": { + "last_validated_date": "2025-09-19T23:04:56+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 11.03, + "teardown": 0.01, + "total": 11.56 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data1]": { - "last_validated_date": "2024-01-11T11:07:41+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T23:05:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.43, + "teardown": 0.0, + "total": 8.43 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data2]": { - "last_validated_date": "2024-01-11T11:07:43+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[json]": { + "last_validated_date": "2025-09-19T23:02:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.22, + "teardown": 0.0, + "total": 2.22 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results": { - "last_validated_date": "2024-01-10T15:29:50+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[query]": { + "last_validated_date": "2025-09-19T23:02:11+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 2.56, + "teardown": 0.01, + "total": 3.08 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions": { - "last_validated_date": "2024-03-05T14:34:47+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T23:02:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.22, + "teardown": 0.0, + "total": 2.22 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units": { - "last_validated_date": "2024-01-05T16:15:39+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[json]": { + "last_validated_date": "2025-09-19T22:56:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.22, + "teardown": 0.01, + "total": 2.23 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule": { - "last_validated_date": "2023-10-26T08:07:59+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[query]": { + "last_validated_date": "2025-09-19T22:56:18+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 2.54, + "teardown": 0.01, + "total": 3.04 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints": { - "last_validated_date": "2024-08-23T14:16:44+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:56:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.22, + "teardown": 0.01, + "total": 2.23 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name": { - "last_validated_date": "2024-09-04T16:28:05+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[json]": { + "last_validated_date": "2025-09-19T21:45:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.04, + "teardown": 0.0, + "total": 2.04 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics": { - "last_validated_date": "2024-07-29T07:56:05+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[query]": { + "last_validated_date": "2025-09-19T21:45:12+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.13, + "teardown": 0.0, + "total": 1.64 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:45:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.53, + "teardown": 0.0, + "total": 1.53 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[json]": { + "last_validated_date": "2025-09-19T23:08:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.92, + "teardown": 0.01, + "total": 0.93 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[query]": { + "last_validated_date": "2025-09-19T23:08:52+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.27, + "teardown": 0.01, + "total": 1.79 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T23:08:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.94, + "teardown": 0.0, + "total": 0.94 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[json]": { + "last_validated_date": "2025-09-19T21:32:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.01, + "total": 0.44 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[query]": { + "last_validated_date": "2025-09-19T21:32:13+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.44, + "teardown": 0.01, + "total": 0.93 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:32:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.51, + "teardown": 0.01, + "total": 0.52 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs0]": { + "last_validated_date": "2025-09-19T23:00:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.36, + "teardown": 0.01, + "total": 1.37 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs1]": { + "last_validated_date": "2025-09-19T23:00:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.34, + "teardown": 0.0, + "total": 1.34 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs2]": { + "last_validated_date": "2025-09-19T23:00:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.35, + "teardown": 0.01, + "total": 1.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs3]": { + "last_validated_date": "2025-09-19T23:00:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.69, + "teardown": 0.01, + "total": 1.7 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs4]": { + "last_validated_date": "2025-09-19T23:00:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.34, + "teardown": 0.0, + "total": 1.34 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs5]": { + "last_validated_date": "2025-09-19T23:00:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.33, + "teardown": 0.0, + "total": 1.33 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[json-input_pairs6]": { + "last_validated_date": "2025-09-19T23:00:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.67, + "teardown": 0.01, + "total": 1.68 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs0]": { + "last_validated_date": "2025-09-19T23:00:36+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.68, + "teardown": 0.01, + "total": 2.18 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs1]": { + "last_validated_date": "2025-09-19T23:00:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.35, + "teardown": 0.01, + "total": 1.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs2]": { + "last_validated_date": "2025-09-19T23:00:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.35, + "teardown": 0.01, + "total": 1.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs3]": { + "last_validated_date": "2025-09-19T23:00:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.68, + "teardown": 0.01, + "total": 1.69 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs4]": { + "last_validated_date": "2025-09-19T23:00:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.35, + "teardown": 0.01, + "total": 1.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs5]": { + "last_validated_date": "2025-09-19T23:00:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.46, + "teardown": 0.01, + "total": 2.47 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[query-input_pairs6]": { + "last_validated_date": "2025-09-19T23:00:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.53, + "teardown": 0.01, + "total": 0.54 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs0]": { + "last_validated_date": "2025-09-19T23:00:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.48, + "teardown": 0.01, + "total": 1.49 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs1]": { + "last_validated_date": "2025-09-19T23:00:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.35, + "teardown": 0.01, + "total": 1.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs2]": { + "last_validated_date": "2025-09-19T23:00:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.67, + "teardown": 0.01, + "total": 1.68 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs3]": { + "last_validated_date": "2025-09-19T23:01:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.33, + "teardown": 0.0, + "total": 1.33 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs4]": { + "last_validated_date": "2025-09-19T23:01:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.33, + "teardown": 0.0, + "total": 1.33 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs5]": { + "last_validated_date": "2025-09-19T23:01:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.35, + "teardown": 0.0, + "total": 1.35 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[smithy-rpc-v2-cbor-input_pairs6]": { + "last_validated_date": "2025-09-19T23:01:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.66, + "teardown": 0.01, + "total": 1.67 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination[json]": { + "last_validated_date": "2025-09-19T22:51:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 79.99, + "teardown": 0.0, + "total": 79.99 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination[query]": { + "last_validated_date": "2025-09-19T22:49:59+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 82.22, + "teardown": 0.0, + "total": 82.27 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:52:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 79.3, + "teardown": 0.0, + "total": 79.3 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness[json]": { + "last_validated_date": "2025-09-19T20:44:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.42, + "teardown": 0.0, + "total": 21.42 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness[query]": { + "last_validated_date": "2025-09-19T20:44:38+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 21.66, + "teardown": 0.0, + "total": 21.71 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:45:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.34, + "teardown": 0.0, + "total": 21.34 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters[json]": { + "last_validated_date": "2025-09-19T20:48:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 42.35, + "teardown": 0.0, + "total": 42.35 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters[query]": { + "last_validated_date": "2025-09-19T20:47:28+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 42.68, + "teardown": 0.0, + "total": 42.73 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:48:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 42.3, + "teardown": 0.0, + "total": 42.3 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget[json]": { + "last_validated_date": "2025-09-19T21:46:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.64, + "teardown": 0.0, + "total": 0.64 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget[query]": { + "last_validated_date": "2025-09-19T21:46:32+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 0.86, + "teardown": 0.0, + "total": 0.91 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:46:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.65, + "teardown": 0.0, + "total": 0.65 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions[json]": { + "last_validated_date": "2025-09-19T20:36:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.96, + "teardown": 0.0, + "total": 8.96 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions[query]": { + "last_validated_date": "2025-09-19T20:35:53+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 7.56, + "teardown": 0.0, + "total": 7.61 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:36:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.21, + "teardown": 0.0, + "total": 9.21 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[json]": { + "last_validated_date": "2025-09-19T23:07:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.12, + "teardown": 0.01, + "total": 8.13 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[query]": { + "last_validated_date": "2025-09-19T23:07:39+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 4.83, + "teardown": 0.01, + "total": 5.35 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T23:07:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 11.47, + "teardown": 0.01, + "total": 11.48 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms[json]": { + "last_validated_date": "2025-09-19T20:41:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.65, + "teardown": 0.47, + "total": 1.12 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms[query]": { + "last_validated_date": "2025-09-19T20:41:14+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 0.95, + "teardown": 0.47, + "total": 1.47 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:41:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.97, + "teardown": 0.4, + "total": 1.37 + } }, "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm": { - "last_validated_date": "2025-05-12T16:20:56+00:00" + "last_validated_date": "2026-02-26T10:14:12+00:00", + "durations_in_seconds": { + "setup": 1.02, + "call": 54.49, + "teardown": 1.48, + "total": 56.99 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[json]": { + "last_validated_date": "2025-09-24T15:16:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.4, + "teardown": 0.34, + "total": 0.74 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[query]": { + "last_validated_date": "2025-09-24T15:16:45+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.71, + "teardown": 0.23, + "total": 1.43 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-24T15:16:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.31, + "teardown": 0.23, + "total": 0.54 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[json]": { + "last_validated_date": "2025-09-19T18:08:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.34, + "teardown": 0.0, + "total": 1.34 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[query]": { + "last_validated_date": "2025-09-19T18:08:03+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.67, + "teardown": 0.0, + "total": 2.17 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T18:08:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.36, + "teardown": 0.0, + "total": 1.36 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[json]": { + "last_validated_date": "2025-09-19T17:40:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.83, + "teardown": 0.0, + "total": 0.83 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[query]": { + "last_validated_date": "2025-09-19T17:40:30+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.14, + "teardown": 0.0, + "total": 1.65 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T17:40:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.43, + "teardown": 0.0, + "total": 1.43 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc[json]": { + "last_validated_date": "2025-09-19T22:54:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.67, + "teardown": 0.0, + "total": 1.67 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc[query]": { + "last_validated_date": "2025-09-19T22:54:28+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 2.02, + "teardown": 0.0, + "total": 2.07 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:54:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.03, + "teardown": 0.0, + "total": 2.03 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[json]": { + "last_validated_date": "2025-09-19T20:56:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.58, + "teardown": 1.29, + "total": 6.87 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[query]": { + "last_validated_date": "2025-09-19T20:56:48+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 6.51, + "teardown": 1.36, + "total": 8.37 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:57:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.37, + "teardown": 1.35, + "total": 6.72 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[json]": { + "last_validated_date": "2025-09-19T22:29:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.77, + "teardown": 0.28, + "total": 1.05 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[query]": { + "last_validated_date": "2025-09-19T22:29:26+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.1, + "teardown": 0.24, + "total": 1.83 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T22:29:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.74, + "teardown": 0.21, + "total": 0.95 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[json]": { + "last_validated_date": "2025-09-19T20:42:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.31, + "teardown": 0.23, + "total": 1.54 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[query]": { + "last_validated_date": "2025-09-19T20:42:24+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 1.39, + "teardown": 0.22, + "total": 2.09 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T20:42:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.41, + "teardown": 0.22, + "total": 1.63 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_composite_alarm[json]": { + "last_validated_date": "2026-02-17T12:33:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.02, + "teardown": 0.0, + "total": 1.02 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_composite_alarm[query]": { + "last_validated_date": "2026-02-17T12:33:17+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 0.78, + "teardown": 0.0, + "total": 0.96 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_composite_alarm[smithy-rpc-v2-cbor]": { + "last_validated_date": "2026-02-17T12:33:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.76, + "teardown": 0.0, + "total": 0.76 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_metric_alarm[json]": { + "last_validated_date": "2026-02-17T12:33:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.57, + "teardown": 0.0, + "total": 0.57 + } + }, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_metric_alarm[query]": { + "last_validated_date": "2026-02-17T12:33:44+00:00", + "durations_in_seconds": { + "setup": 0.2, + "call": 0.67, + "teardown": 0.0, + "total": 0.87 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list": { - "last_validated_date": "2023-09-25T08:26:17+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_tagging_metric_alarm[smithy-rpc-v2-cbor]": { + "last_validated_date": "2026-02-17T12:33:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.6, + "teardown": 0.0, + "total": 0.6 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm": { - "last_validated_date": "2024-01-19T15:30:05+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[json]": { + "last_validated_date": "2025-09-19T21:03:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 10.2, + "teardown": 2.24, + "total": 12.44 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags": { - "last_validated_date": "2024-09-02T14:03:31+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[query]": { + "last_validated_date": "2025-09-19T21:03:23+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 12.35, + "teardown": 2.05, + "total": 14.9 + } }, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm": { - "last_validated_date": "2024-11-14T14:25:30+00:00" + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm[smithy-rpc-v2-cbor]": { + "last_validated_date": "2025-09-19T21:03:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 11.87, + "teardown": 2.21, + "total": 14.08 + } } } diff --git a/tests/aws/services/cloudwatch/utils.py b/tests/aws/services/cloudwatch/utils.py new file mode 100644 index 0000000000000..85af14e89c517 --- /dev/null +++ b/tests/aws/services/cloudwatch/utils.py @@ -0,0 +1,187 @@ +import abc +import json +from typing import Any + +import xmltodict +from botocore.auth import SigV4Auth +from botocore.serialize import create_serializer + +# import the unpatched cbor2 on purpose to avoid being polluted by Kinesis-only patches +from cbor2 import loads as cbor2_loads +from requests import Response + +from localstack import constants +from localstack.aws.spec import get_service_catalog +from localstack.config import LOCALSTACK_HOST +from localstack.testing.aws.util import is_aws_cloud + + +class BaseCloudWatchHttpClient(abc.ABC): + """ + Simple HTTP client for making CloudWatch requests manually using different protocols. + + This serialization type is not yet available via boto3 client, and we have more control over raw responses. + """ + + protocol: str = "" + + def __init__( + self, + region_name: str, + client_factory, + ): + self.region_name = region_name + # CloudWatch uses the signing name `monitoring` + self._client = client_factory( + "monitoring", region=self.region_name, signer_factory=SigV4Auth + ) + self.service_model = get_service_catalog().get("cloudwatch") + self.target_prefix = self.service_model.metadata.get("targetPrefix") or "" + + @abc.abstractmethod + def _deserialize_response(self, response: Response) -> Any: ... + + @abc.abstractmethod + def _build_headers(self, operation: str, query_mode: bool = False) -> dict: ... + + def _serialize_body(self, body: dict, operation: str) -> str | bytes: + # here we use the Botocore serializer directly, since it has some complex behavior, + # and we know CloudWatch supports it by default + protocol_serializer = create_serializer(self.protocol) + operation_model = self.service_model.operation_model(operation) + request = protocol_serializer.serialize_to_request(body, operation_model) + return request["body"] + + @property + def host(self) -> str: + return ( + f"monitoring.{self.region_name}.amazonaws.com" + if is_aws_cloud() + else LOCALSTACK_HOST.host_and_port() + ) + + def _build_endpoint(self, operation: str) -> str: + return f"https://{self.host}" + + def post_raw( + self, operation: str, payload: dict, query_mode: bool = False, **kwargs + ) -> Response: + """ + Perform a CloudWatch operation, encoding the request payload based on the `_serialize_body` and returning + the raw response without any processing or checks. + """ + response = self._client.post( + self._build_endpoint(operation), + data=self._serialize_body(payload, operation), + headers=self._build_headers(operation, query_mode), + **kwargs, + ) + return response + + def post( + self, operation: str, payload: dict, status_code: int = 200, query_mode: bool = False + ) -> Any: + """ + Perform a CloudWatch operation and decode it based on the `_deserialize_response` implementation + """ + response = self.post_raw(operation, payload, query_mode=query_mode) + response_body = self._deserialize_response(response) + if response.status_code != status_code: + raise ValueError(f"Bad status: {response.status_code}, response body: {response_body}") + return response_body + + +class CloudWatchCBORHTTPClient(BaseCloudWatchHttpClient): + protocol = "smithy-rpc-v2-cbor" + + def _deserialize_response(self, response: Response) -> Any: + if response.content: + return cbor2_loads(response.content) + return {} + + def _build_headers(self, operation: str, query_mode: bool = False) -> dict: + headers = { + "content-type": constants.APPLICATION_CBOR, + "accept": constants.APPLICATION_CBOR, + "host": self.host, + "Smithy-Protocol": "rpc-v2-cbor", + } + if query_mode: + headers["x-amzn-query-mode"] = "true" + + return headers + + def _build_endpoint(self, operation: str) -> str: + return f"https://{self.host}/service/{self.target_prefix}/operation/{operation}" + + +class CloudWatchJSONHTTPClient(BaseCloudWatchHttpClient): + protocol = "json" + + def _deserialize_response(self, response: Response) -> Any: + if response.content: + return json.loads(response.content) + return {} + + def _build_headers(self, operation: str, query_mode: bool = False) -> dict: + headers = { + "Content-Type": constants.APPLICATION_AMZ_JSON_1_0, + "X-Amz-Target": f"{self.target_prefix}.{operation}", + "Host": self.host, + "x-amzn-query-mode": "true" if query_mode else "false", + } + if query_mode: + headers["x-amzn-query-mode"] = "true" + + return headers + + +class CloudWatchQueryHTTPClient(BaseCloudWatchHttpClient): + protocol = "query" + + def _deserialize_response(self, response: Response) -> Any: + if not response.content: + return {} + content_type = response.headers.get("Content-Type", "") + if content_type.startswith(constants.APPLICATION_XML) or content_type.startswith( + constants.TEXT_XML + ): + # FIXME: the snapshot library doesn't deal well with ResponseMetadata in raw responses, will be fixed when + # we release a new version + # https://github.com/localstack/localstack-snapshot/pull/13 + response = xmltodict.parse(response.content) + container = response[next(iter(response.keys()))] + if isinstance(container, dict): + container.pop("ResponseMetadata", None) + return response + elif content_type.startswith(constants.APPLICATION_JSON): + return json.loads(response.content) + else: + return response.content + + def _build_headers(self, operation: str, query_mode: bool = False) -> dict: + return { + "content-type": constants.APPLICATION_X_WWW_FORM_URLENCODED, + "host": self.host, + } + + +def get_cloudwatch_client(client_factory, region: str, protocol: str) -> BaseCloudWatchHttpClient: + match protocol: + case "smithy-rpc-v2-cbor": + return CloudWatchCBORHTTPClient( + region_name=region, + client_factory=client_factory, + ) + case "json": + return CloudWatchJSONHTTPClient( + region_name=region, + client_factory=client_factory, + ) + case "query": + return CloudWatchQueryHTTPClient( + region_name=region, + client_factory=client_factory, + ) + case _: + raise ValueError("protocol must be in ['smithy-rpc-v2-cbor', 'json', 'query']") diff --git a/tests/aws/services/dynamodb/test_dynamodb.py b/tests/aws/services/dynamodb/test_dynamodb.py index 2c0ab3e50b42f..8f3ebbb09bd54 100644 --- a/tests/aws/services/dynamodb/test_dynamodb.py +++ b/tests/aws/services/dynamodb/test_dynamodb.py @@ -3,7 +3,6 @@ import time from datetime import datetime from time import sleep -from typing import Dict import botocore.exceptions import pytest @@ -12,6 +11,7 @@ from botocore.config import Config from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import SortingTransformer +from localstack_snapshot.snapshots.transformer_utility import TransformerUtility from localstack import config from localstack.aws.api.dynamodb import ( @@ -26,6 +26,7 @@ from localstack.testing.pytest import markers from localstack.utils import testutil from localstack.utils.aws import arns, queries, resources +from localstack.utils.aws.arns import kinesis_stream_arn from localstack.utils.aws.resources import create_dynamodb_table from localstack.utils.common import json_safe, long_uid, retry, short_uid from localstack.utils.sync import poll_condition, wait_until @@ -94,7 +95,7 @@ def test_large_data_download(self, aws_client, ddb_test_table): # Create a large amount of items num_items = 20 for i in range(0, num_items): - item = {PARTITION_KEY: {"S": "id%s" % i}, "data1": {"S": "foobar123 " * 1000}} + item = {PARTITION_KEY: {"S": f"id{i}"}, "data1": {"S": "foobar123 " * 1000}} aws_client.dynamodb.put_item(TableName=ddb_test_table, Item=item) # Retrieve the items. The data will be transmitted to the client with chunked transfer encoding @@ -251,7 +252,7 @@ def test_time_to_live(self, aws_client, ddb_test_table): @markers.aws.only_localstack def test_list_tags_of_resource(self, aws_client): - table_name = "ddb-table-%s" % short_uid() + table_name = f"ddb-table-{short_uid()}" rs = aws_client.dynamodb.create_table( TableName=table_name, @@ -906,6 +907,144 @@ def _get_records_amount(record_amount: int): retry(lambda: _get_records_amount(6), sleep=1, retries=3) snapshot.match("get-records", {"Records": records}) + @markers.aws.validated + def test_kinesis_streaming_destination_crud( + self, + aws_client, + dynamodb_create_table, + kinesis_create_stream, + snapshot, + account_id, + region_name, + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("TableName"), + snapshot.transform.key_value("StreamArn"), + ] + ) + + table_name = f"table-{short_uid()}" + + dynamodb_create_table( + table_name=table_name, + partition_key=PARTITION_KEY, + ) + + # Transform table ID which appears in some error messages + table_id = aws_client.dynamodb.describe_table(TableName=table_name)["Table"]["TableId"] + snapshot.add_transformer(snapshot.transform.regex(table_id, "")) + + stream_name = kinesis_create_stream() + stream_arn = aws_client.kinesis.describe_stream(StreamName=stream_name)[ + "StreamDescription" + ]["StreamARN"] + + # EnableKinesisStreamingDestination: Invalid table must raise + with pytest.raises(ClientError) as exc: + aws_client.dynamodb.enable_kinesis_streaming_destination( + TableName="bad-table", + StreamArn=stream_arn, + ) + snapshot.match("enable-bad-table", exc.value.response) + + # EnableKinesisStreamingDestination: Happy path + response = aws_client.dynamodb.enable_kinesis_streaming_destination( + TableName=table_name, + StreamArn=stream_arn, + ) + snapshot.match("enable", response) + + def _is_stream_active(): + assert ( + aws_client.dynamodb.describe_kinesis_streaming_destination(TableName=table_name)[ + "KinesisDataStreamDestinations" + ][0]["DestinationStatus"] + == "ACTIVE" + ) + + retry(_is_stream_active, retries=90, sleep=3) + + # DescribeKinesisStreamingDestination: Happy path + response = aws_client.dynamodb.describe_kinesis_streaming_destination( + TableName=table_name, + ) + snapshot.match("describe", response) + + # UpdateKinesisStreamingDestination: Missing params + with pytest.raises(ClientError) as exc: + aws_client.dynamodb.update_kinesis_streaming_destination( + TableName=table_name, + StreamArn=stream_arn, + ) + snapshot.match("update-missing-params", exc.value.response) + + # UpdateKinesisStreamingDestination: Bad precision + with pytest.raises(ClientError) as exc: + aws_client.dynamodb.update_kinesis_streaming_destination( + TableName=table_name, + StreamArn=stream_arn, + UpdateKinesisStreamingConfiguration={ + "ApproximateCreationDateTimePrecision": "SECOND", + }, + ) + snapshot.match("update-bad-precision", exc.value.response) + + # UpdateKinesisStreamingDestination: Bad stream ARN + with pytest.raises(ClientError) as exc: + aws_client.dynamodb.update_kinesis_streaming_destination( + TableName=table_name, + StreamArn=kinesis_stream_arn("bad-stream", account_id, region_name), + UpdateKinesisStreamingConfiguration={ + "ApproximateCreationDateTimePrecision": "MICROSECOND", + }, + ) + snapshot.match("update-bad-stream-arn", exc.value.response) + + response = aws_client.dynamodb.update_kinesis_streaming_destination( + TableName=table_name, + StreamArn=stream_arn, + UpdateKinesisStreamingConfiguration={ + "ApproximateCreationDateTimePrecision": "MICROSECOND", + }, + ) + snapshot.match("update", response) + + retry(_is_stream_active, retries=90, sleep=3) + + # TODO: Describe... after Update... + + # UpdateKinesisStreamingDestination: Ensure not idempotent + with pytest.raises(ClientError) as exc: + aws_client.dynamodb.update_kinesis_streaming_destination( + TableName=table_name, + StreamArn=stream_arn, + UpdateKinesisStreamingConfiguration={ + "ApproximateCreationDateTimePrecision": "MICROSECOND", + }, + ) + snapshot.match("update-no-idempotency", exc.value.response) + + # DisableKinesisStreamingDestination: Invalid table must raise + with pytest.raises(ClientError) as exc: + aws_client.dynamodb.disable_kinesis_streaming_destination( + TableName="bad-table", + StreamArn=stream_arn, + ) + snapshot.match("disable-bad-table", exc.value.response) + + # TODO: DisableKinesisStreamingDestination: Invoking before stream is ACTIVE must raise + # TODO: DisableKinesisStreamingDestination: Bad stream must raise + + # DisableKinesisStreamingDestination: Happy path + response = aws_client.dynamodb.disable_kinesis_streaming_destination( + TableName=table_name, + StreamArn=stream_arn, + ) + snapshot.match("disable", response) + + # TODO: Describe... after Disable... + @markers.aws.only_localstack @pytest.mark.skipif( condition=not is_aws_cloud() and config.DDB_STREAMS_PROVIDER_V2, @@ -1352,32 +1491,101 @@ def test_create_duplicate_table(self, dynamodb_create_table_with_parameters, sna ) snapshot.match("Error", ctx.value) - @markers.aws.only_localstack( - reason="timing issues - needs a check to see if table is successfully deleted" + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..TableDescription.TableStatus", # DDBLocal directly goes to ACTIVE status + "$..Table.ProvisionedThroughput.LastDecreaseDateTime", # Not returned by DDBLocal + "$..Table.ProvisionedThroughput.LastIncreaseDateTime", # Not returned by DDBLocal + # The following attributes (prefixed TableDescription) are returned by DDBLocal DeleteTable but not in parity with AWS + "$..TableDescription.AttributeDefinitions", + "$..TableDescription.CreationDateTime", + "$..TableDescription.KeySchema", + "$..TableDescription.ProvisionedThroughput.LastDecreaseDateTime", + "$..TableDescription.ProvisionedThroughput.LastIncreaseDateTime", + "$..TableDescription.TableId", + ] ) - def test_delete_table(self, dynamodb_create_table, aws_client): + def test_table_crud(self, aws_client, cleanups, snapshot, dynamodb_wait_for_table_active): + snapshot.add_transformer( + [ + TransformerUtility.key_value("TableName"), + TransformerUtility.key_value("TableArn"), + ] + ) + table_name = f"test-ddb-table-{short_uid()}" - tables_before = len(aws_client.dynamodb.list_tables()["TableNames"]) + # CreateTable + response = aws_client.dynamodb.create_table( + TableName=table_name, + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + cleanups.append(lambda: aws_client.dynamodb.delete_table(TableName=table_name)) + snapshot.match("create-table", response) - dynamodb_create_table( - table_name=table_name, - partition_key=PARTITION_KEY, + dynamodb_wait_for_table_active(table_name=table_name) + + # ListTables + assert table_name in aws_client.dynamodb.list_tables()["TableNames"] + + # DescribeTable + response = aws_client.dynamodb.describe_table(TableName=table_name) + snapshot.match("describe-table", response) + + # DeleteTable + response = aws_client.dynamodb.delete_table(TableName=table_name) + snapshot.match("delete-table", response) + + # ListTable: after DeleteTable + retry( + lambda: table_name not in aws_client.dynamodb.list_tables()["TableNames"], + sleep=1, + retries=30, ) - table_list = aws_client.dynamodb.list_tables() - # TODO: fix assertion, to enable parallel test execution! - assert tables_before + 1 == len(table_list["TableNames"]) - assert table_name in table_list["TableNames"] + # DeleteTable: delete non-existent table + with pytest.raises(ClientError) as exc: + aws_client.dynamodb.delete_table(TableName="non-existent") + snapshot.match("delete-non-existent-table", exc.value.response) - aws_client.dynamodb.delete_table(TableName=table_name) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..TableDescription.TableStatus", # DDBLocal directly goes to ACTIVE status + "$..Table.ProvisionedThroughput.LastDecreaseDateTime", # Not returned by DDBLocal + "$..Table.ProvisionedThroughput.LastIncreaseDateTime", # Not returned by DDBLocal + ] + ) + def test_table_warm_throughput( + self, dynamodb_create_table_with_parameters, snapshot, aws_client + ): + """ + This test ensures that WarmThroughput params provided to CreateTable are reflected in DescribeTable + """ - table_list = aws_client.dynamodb.list_tables() - assert tables_before == len(table_list["TableNames"]) + snapshot.add_transformer( + [ + TransformerUtility.key_value("TableName"), + TransformerUtility.key_value("TableArn"), + ] + ) - with pytest.raises(Exception) as ctx: - aws_client.dynamodb.delete_table(TableName=table_name) - assert ctx.match("ResourceNotFoundException") + table_name = f"test-ddb-table-{short_uid()}" + + response = dynamodb_create_table_with_parameters( + TableName=table_name, + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + WarmThroughput={"ReadUnitsPerSecond": 1000, "WriteUnitsPerSecond": 1200}, + ) + snapshot.match("create-table", response) + + response = aws_client.dynamodb.describe_table(TableName=table_name) + snapshot.match("describe-table", response) @markers.aws.validated def test_transaction_write_items( @@ -1419,6 +1627,42 @@ def test_transaction_write_items( ) snapshot.match("Response", response) + table_name_arn = f"test-ddb-table-{short_uid()}" + table = dynamodb_create_table_with_parameters( + TableName=table_name_arn, + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + Tags=TEST_DDB_TAGS, + ) + + table_arn = table["TableDescription"]["TableArn"] + response_arn = aws_client.dynamodb.transact_write_items( + TransactItems=[ + { + "ConditionCheck": { + "TableName": table_arn, + "ConditionExpression": "attribute_not_exists(id)", + "Key": {"id": {"S": "test1"}}, + } + }, + {"Put": {"TableName": table_arn, "Item": {"id": {"S": "test2"}}}}, + { + "Update": { + "TableName": table_arn, + "Key": {"id": {"S": "test3"}}, + "UpdateExpression": "SET attr1 = :v1, attr2 = :v2", + "ExpressionAttributeValues": { + ":v1": {"S": "value1"}, + ":v2": {"S": "value2"}, + }, + } + }, + {"Delete": {"TableName": table_arn, "Key": {"id": {"S": "test4"}}}}, + ] + ) + snapshot.match("Response_using_table_arn", response_arn) + @markers.aws.validated def test_transaction_write_canceled( self, dynamodb_create_table_with_parameters, snapshot, aws_client @@ -1498,6 +1742,18 @@ def test_transact_get_items(self, dynamodb_create_table, snapshot, aws_client): ) snapshot.match("TransactGetItems", result) + table_name_arn = f"test-ddb-table-{short_uid()}" + table = dynamodb_create_table( + table_name=table_name_arn, + partition_key=PARTITION_KEY, + ) + table_arn = table["TableDescription"]["TableArn"] + aws_client.dynamodb.put_item(TableName=table_name_arn, Item={"id": {"S": "Sarah"}}) + result = aws_client.dynamodb.transact_get_items( + TransactItems=[{"Get": {"Key": {"id": {"S": "Sarah"}}, "TableName": table_arn}}] + ) + snapshot.match("TransactGetItems-with-TableArn", result) + @markers.aws.validated def test_batch_write_items(self, dynamodb_create_table_with_parameters, snapshot, aws_client): table_name = f"test-ddb-table-{short_uid()}" @@ -1754,7 +2010,7 @@ def test_query_on_deleted_resource(self, dynamodb_create_table, aws_client): rs = aws_client.dynamodb.query( TableName=table_name, - KeyConditionExpression="{} = :username".format(partition_key), + KeyConditionExpression=f"{partition_key} = :username", ExpressionAttributeValues={":username": {"S": "test"}}, ) assert rs["ResponseMetadata"]["HTTPStatusCode"] == 200 @@ -1764,7 +2020,7 @@ def test_query_on_deleted_resource(self, dynamodb_create_table, aws_client): with pytest.raises(Exception) as ctx: aws_client.dynamodb.query( TableName=table_name, - KeyConditionExpression="{} = :username".format(partition_key), + KeyConditionExpression=f"{partition_key} = :username", ExpressionAttributeValues={":username": {"S": "test"}}, ) assert ctx.match("ResourceNotFoundException") @@ -1809,9 +2065,14 @@ def test_dynamodb_create_table_with_sse_specification( assert result["TableDescription"]["SSEDescription"]["KMSMasterKeyArn"] == kms_master_key_arn @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..KeyMetadata.CurrentKeyMaterialId"] # Not supported by LS + ) def test_dynamodb_create_table_with_partial_sse_specification( self, dynamodb_create_table_with_parameters, snapshot, aws_client ): + snapshot.add_transformer(TransformerUtility.key_value("CurrentKeyMaterialId")) + table_name = f"test_table_{short_uid()}" sse_specification = {"Enabled": True} @@ -1841,9 +2102,14 @@ def test_dynamodb_create_table_with_partial_sse_specification( assert "SSESpecification" not in result["Table"] @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..KeyMetadata.CurrentKeyMaterialId"] # Not supported by LS + ) def test_dynamodb_update_table_without_sse_specification_change( self, dynamodb_create_table_with_parameters, snapshot, aws_client ): + snapshot.add_transformer(TransformerUtility.key_value("CurrentKeyMaterialId")) + table_name = f"test_table_{short_uid()}" sse_specification = {"Enabled": True} @@ -1991,7 +2257,7 @@ def test_dynamodb_idempotent_writing( ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, ) - def _transact_write(_d: Dict): + def _transact_write(_d: dict): return aws_client.dynamodb.transact_write_items( ClientRequestToken="dedupe_token", TransactItems=[ @@ -2146,6 +2412,7 @@ def test_data_encoding_consistency( paths=[ "$..PointInTimeRecoveryDescription..EarliestRestorableDateTime", "$..PointInTimeRecoveryDescription..LatestRestorableDateTime", + "$..ContinuousBackupsDescription.PointInTimeRecoveryDescription.RecoveryPeriodInDays", ] ) @markers.aws.validated @@ -2500,6 +2767,8 @@ def _get_records_amount(record_amount: int): @pytest.mark.parametrize("billing_mode", ["PAY_PER_REQUEST", "PROVISIONED"]) @markers.snapshot.skip_snapshot_verify( paths=[ + # Warm throughput for GSI is not implemented in LS. DDB Local doesn't support it either. + "$..Table.GlobalSecondaryIndexes..WarmThroughput", # LS returns those and not AWS, probably because no changes happened there yet "$..ProvisionedThroughput.LastDecreaseDateTime", "$..ProvisionedThroughput.LastIncreaseDateTime", @@ -2558,3 +2827,22 @@ def test_gsi_with_billing_mode( describe_table = aws_client.dynamodb.describe_table(TableName=table_name) snapshot.match("describe-table", describe_table) + + @markers.aws.validated + def test_dynamodb_describe_contributor_insights( + self, dynamodb_create_table, snapshot, aws_client + ): + table_name = f"test-ddb-table-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("TableName"), + ] + ) + + dynamodb_create_table( + table_name=table_name, + partition_key=PARTITION_KEY, + ) + result = aws_client.dynamodb.describe_contributor_insights(TableName=table_name) + snapshot.match("describe-contributor-insights-disabled", result) diff --git a/tests/aws/services/dynamodb/test_dynamodb.snapshot.json b/tests/aws/services/dynamodb/test_dynamodb.snapshot.json index 4842ef3f2406b..fd99f9432264c 100644 --- a/tests/aws/services/dynamodb/test_dynamodb.snapshot.json +++ b/tests/aws/services/dynamodb/test_dynamodb.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_local_secondary_index": { - "recorded-date": "23-08-2023, 16:32:11", + "recorded-date": "08-10-2025, 12:33:08", "recorded-content": { "Items": { "Count": 1, @@ -26,7 +26,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_in_put_item": { - "recorded-date": "23-08-2023, 16:32:21", + "recorded-date": "08-10-2025, 12:33:15", "recorded-content": { "PutFirstItem": { "ResponseMetadata": { @@ -77,7 +77,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_empty_and_binary_values": { - "recorded-date": "23-08-2023, 16:32:29", + "recorded-date": "08-10-2025, 12:33:22", "recorded-content": { "PutFirstItem": { "ResponseMetadata": { @@ -94,7 +94,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_binary": { - "recorded-date": "23-08-2023, 16:32:35", + "recorded-date": "08-10-2025, 12:33:28", "recorded-content": { "Response": { "UnprocessedItems": {}, @@ -106,7 +106,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_transaction": { - "recorded-date": "23-08-2023, 16:32:44", + "recorded-date": "08-10-2025, 12:33:37", "recorded-content": { "ExecutedTransaction": { "Responses": [], @@ -138,7 +138,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_batch_execute_statement": { - "recorded-date": "23-08-2023, 16:32:51", + "recorded-date": "08-10-2025, 12:33:43", "recorded-content": { "ExecutedStatement": { "Responses": [ @@ -170,7 +170,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_partiql_missing": { - "recorded-date": "23-08-2023, 16:33:00", + "recorded-date": "08-10-2025, 12:33:58", "recorded-content": { "FirstNameNotMissing": [ { @@ -185,30 +185,36 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_create_duplicate_table": { - "recorded-date": "23-08-2023, 16:33:08", + "recorded-date": "08-10-2025, 12:35:25", "recorded-content": { "Error": "An error occurred (ResourceInUseException) when calling the CreateTable operation: Table already exists: " } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_items": { - "recorded-date": "23-08-2023, 16:33:16", + "recorded-date": "07-01-2026, 10:30:09", "recorded-content": { "Response": { "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 } + }, + "Response_using_table_arn": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } } } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_canceled": { - "recorded-date": "23-08-2023, 16:33:24", + "recorded-date": "08-10-2025, 12:35:51", "recorded-content": { "Error": "An error occurred (TransactionCanceledException) when calling the TransactWriteItems operation: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None]" } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_binary_data": { - "recorded-date": "23-08-2023, 16:33:31", + "recorded-date": "08-10-2025, 12:35:59", "recorded-content": { "WriteResponse": { "ResponseMetadata": { @@ -227,7 +233,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_get_items": { - "recorded-date": "23-08-2023, 16:33:37", + "recorded-date": "08-01-2026, 19:22:29", "recorded-content": { "TransactGetItems": { "Responses": [ @@ -243,11 +249,26 @@ "HTTPHeaders": {}, "HTTPStatusCode": 200 } + }, + "TransactGetItems-with-TableArn": { + "Responses": [ + { + "Item": { + "id": { + "S": "Sarah" + } + } + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } } } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items": { - "recorded-date": "23-08-2023, 16:33:43", + "recorded-date": "08-10-2025, 12:36:12", "recorded-content": { "BatchWriteResponse": { "UnprocessedItems": {}, @@ -259,13 +280,13 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_pay_per_request": { - "recorded-date": "23-08-2023, 16:33:43", + "recorded-date": "08-10-2025, 12:36:20", "recorded-content": { "Error": "An error occurred (ValidationException) when calling the CreateTable operation: One or more parameter values were invalid: Neither ReadCapacityUnits nor WriteCapacityUnits can be specified when BillingMode is PAY_PER_REQUEST" } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_partial_sse_specification": { - "recorded-date": "10-01-2024, 12:59:51", + "recorded-date": "08-10-2025, 14:03:41", "recorded-content": { "SSEDescription": { "KMSMasterKeyArn": "arn::kms::111111111111:key/", @@ -277,6 +298,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "Default key that protects my DynamoDB data when no other key is defined", "Enabled": true, @@ -304,7 +326,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_get_batch_items": { - "recorded-date": "23-08-2023, 16:33:58", + "recorded-date": "08-10-2025, 12:37:20", "recorded-content": { "Response": { "Responses": { @@ -319,7 +341,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_idempotent_writing": { - "recorded-date": "23-08-2023, 16:39:54", + "recorded-date": "08-10-2025, 12:37:34", "recorded-content": { "Response1": { "ResponseMetadata": { @@ -336,13 +358,13 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_matching_schema": { - "recorded-date": "23-08-2023, 16:34:26", + "recorded-date": "08-10-2025, 12:37:41", "recorded-content": { "ValidationException": "An error occurred (ValidationException) when calling the BatchWriteItem operation: The provided key element does not match the schema" } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_data_encoding_consistency": { - "recorded-date": "24-08-2023, 10:56:37", + "recorded-date": "08-10-2025, 12:37:49", "recorded-content": { "GetItem": { "data": { @@ -391,7 +413,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_continuous_backup_update": { - "recorded-date": "23-08-2023, 16:34:39", + "recorded-date": "08-10-2025, 12:38:00", "recorded-content": { "update-continuous-backup": { "ContinuousBackupsDescription": { @@ -399,7 +421,8 @@ "PointInTimeRecoveryDescription": { "EarliestRestorableDateTime": "datetime", "LatestRestorableDateTime": "datetime", - "PointInTimeRecoveryStatus": "ENABLED" + "PointInTimeRecoveryStatus": "ENABLED", + "RecoveryPeriodInDays": 35 } }, "ResponseMetadata": { @@ -413,7 +436,8 @@ "PointInTimeRecoveryDescription": { "EarliestRestorableDateTime": "datetime", "LatestRestorableDateTime": "datetime", - "PointInTimeRecoveryStatus": "ENABLED" + "PointInTimeRecoveryStatus": "ENABLED", + "RecoveryPeriodInDays": 35 } }, "ResponseMetadata": { @@ -424,7 +448,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_existing_table": { - "recorded-date": "22-10-2023, 20:45:30", + "recorded-date": "08-10-2025, 12:37:41", "recorded-content": { "exc-not-found-transact-write-items": { "Error": { @@ -440,7 +464,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items_streaming": { - "recorded-date": "22-10-2023, 23:23:09", + "recorded-date": "08-10-2025, 12:36:19", "recorded-content": { "create-table": { "TableDescription": { @@ -636,11 +660,11 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_describe_with_exclusive_start_shard_id": { - "recorded-date": "22-10-2023, 22:27:28", + "recorded-date": "08-10-2025, 12:37:28", "recorded-content": {} }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_stream_view_type": { - "recorded-date": "31-05-2024, 14:49:57", + "recorded-date": "08-10-2025, 12:34:09", "recorded-content": { "create-table": { "TableDescription": { @@ -831,7 +855,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_on_conditions_check_failure": { - "recorded-date": "03-01-2024, 17:52:20", + "recorded-date": "08-10-2025, 12:45:55", "recorded-content": { "items": { "Error": { @@ -857,7 +881,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming": { - "recorded-date": "31-05-2024, 14:47:04", + "recorded-date": "08-10-2025, 12:46:04", "recorded-content": { "create-table": { "TableDescription": { @@ -1129,7 +1153,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming_for_different_tables": { - "recorded-date": "02-04-2024, 21:45:36", + "recorded-date": "08-10-2025, 12:46:18", "recorded-content": { "create-table-stream": { "TableDescription": { @@ -1419,7 +1443,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_update_table_without_sse_specification_change": { - "recorded-date": "17-12-2024, 10:40:03", + "recorded-date": "08-10-2025, 14:06:57", "recorded-content": { "create_table_sse_description": { "KMSMasterKeyArn": "arn::kms::111111111111:key/", @@ -1431,6 +1455,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "Default key that protects my DynamoDB data when no other key is defined", "Enabled": true, @@ -1458,7 +1483,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_statement_empy_parameter": { - "recorded-date": "03-01-2025, 09:24:27", + "recorded-date": "08-10-2025, 12:33:51", "recorded-content": { "invalid-param-error": { "Error": { @@ -1473,7 +1498,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PAY_PER_REQUEST]": { - "recorded-date": "08-01-2025, 18:17:06", + "recorded-date": "08-10-2025, 12:46:32", "recorded-content": { "create-table-with-gsi": { "TableDescription": { @@ -1576,6 +1601,11 @@ "NumberOfDecreasesToday": 0, "ReadCapacityUnits": 0, "WriteCapacityUnits": 0 + }, + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 } } ], @@ -1595,7 +1625,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "" + "TableStatus": "", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -1605,7 +1640,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PROVISIONED]": { - "recorded-date": "08-01-2025, 18:17:21", + "recorded-date": "08-10-2025, 12:46:48", "recorded-content": { "create-table-with-gsi": { "TableDescription": { @@ -1701,6 +1736,11 @@ "NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 1 + }, + "WarmThroughput": { + "ReadUnitsPerSecond": 1, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 1 } } ], @@ -1720,7 +1760,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "" + "TableStatus": "", + "WarmThroughput": { + "ReadUnitsPerSecond": 5, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 5 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -1730,7 +1775,7 @@ } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_streams_on_global_tables": { - "recorded-date": "22-05-2025, 12:44:58", + "recorded-date": "08-10-2025, 12:35:17", "recorded-content": { "region-streams": { "Streams": [ @@ -1759,5 +1804,321 @@ } } } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_kinesis_streaming_destination_crud": { + "recorded-date": "23-07-2025, 13:31:04", + "recorded-content": { + "enable-bad-table": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested resource not found: Table: bad-table not found" + }, + "message": "Requested resource not found: Table: bad-table not found", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "enable": { + "DestinationStatus": "ENABLING", + "EnableKinesisStreamingConfiguration": {}, + "StreamArn": "", + "TableName": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe": { + "KinesisDataStreamDestinations": [ + { + "DestinationStatus": "ACTIVE", + "StreamArn": "" + } + ], + "TableName": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update-missing-params": { + "Error": { + "Code": "ValidationException", + "Message": "Streaming destination cannot be updated with given parameters: UpdateKinesisStreamingConfiguration cannot be null or contain only null values" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-bad-precision": { + "Error": { + "Code": "ValidationException", + "Message": "1 validation error detected: Value 'SECOND' at 'updateKinesisStreamingConfiguration.approximateCreationDateTimePrecision' failed to satisfy constraint: Member must satisfy enum value set: [MILLISECOND, MICROSECOND]" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-bad-stream-arn": { + "Error": { + "Code": "ValidationException", + "Message": "Table is not in a valid state to enable Kinesis Streaming Destination: No streaming destination with streamArn: arn::kinesis::111111111111:stream/bad-stream found for table with tableName: " + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update": { + "DestinationStatus": "UPDATING", + "StreamArn": "", + "TableName": "", + "UpdateKinesisStreamingConfiguration": { + "ApproximateCreationDateTimePrecision": "MICROSECOND" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update-no-idempotency": { + "Error": { + "Code": "ValidationException", + "Message": "Invalid Request: Precision is already set to the desired value of MICROSECOND for tableId: , kdsArn: " + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "disable-bad-table": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested resource not found: Table: bad-table not found" + }, + "message": "Requested resource not found: Table: bad-table not found", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "disable": { + "DestinationStatus": "DISABLING", + "StreamArn": "", + "TableName": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_describe_contributor_insights": { + "recorded-date": "08-10-2025, 12:46:55", + "recorded-content": { + "describe-contributor-insights-disabled": { + "ContributorInsightsStatus": "DISABLED", + "TableName": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_table_crud": { + "recorded-date": "08-10-2025, 12:35:32", + "recorded-content": { + "create-table": { + "TableDescription": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "CreationDateTime": "datetime", + "DeletionProtectionEnabled": false, + "ItemCount": 0, + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "NumberOfDecreasesToday": 0, + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "TableArn": "arn::dynamodb::111111111111:table/", + "TableId": "", + "TableName": "", + "TableSizeBytes": 0, + "TableStatus": "CREATING" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-table": { + "Table": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "CreationDateTime": "datetime", + "DeletionProtectionEnabled": false, + "ItemCount": 0, + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "NumberOfDecreasesToday": 0, + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "TableArn": "arn::dynamodb::111111111111:table/", + "TableId": "", + "TableName": "", + "TableSizeBytes": 0, + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 5, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 5 + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-table": { + "TableDescription": { + "DeletionProtectionEnabled": false, + "ItemCount": 0, + "ProvisionedThroughput": { + "NumberOfDecreasesToday": 0, + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "TableArn": "arn::dynamodb::111111111111:table/", + "TableId": "", + "TableName": "", + "TableSizeBytes": 0, + "TableStatus": "DELETING" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-non-existent-table": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested resource not found: Table: non-existent not found" + }, + "message": "Requested resource not found: Table: non-existent not found", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_table_warm_throughput": { + "recorded-date": "08-10-2025, 12:35:39", + "recorded-content": { + "create-table": { + "TableDescription": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "CreationDateTime": "datetime", + "DeletionProtectionEnabled": false, + "ItemCount": 0, + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "NumberOfDecreasesToday": 0, + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "TableArn": "arn::dynamodb::111111111111:table/", + "TableId": "", + "TableName": "", + "TableSizeBytes": 0, + "TableStatus": "CREATING", + "WarmThroughput": { + "ReadUnitsPerSecond": 1000, + "Status": "UPDATING", + "WriteUnitsPerSecond": 1200 + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-table": { + "Table": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "CreationDateTime": "datetime", + "DeletionProtectionEnabled": false, + "ItemCount": 0, + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "NumberOfDecreasesToday": 0, + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "TableArn": "arn::dynamodb::111111111111:table/", + "TableId": "", + "TableName": "", + "TableSizeBytes": 0, + "TableStatus": "ACTIVE", + "WarmThroughput": { + "ReadUnitsPerSecond": 1000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 1200 + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_stream_destination_records": { + "recorded-date": "08-10-2025, 12:45:48", + "recorded-content": {} } } diff --git a/tests/aws/services/dynamodb/test_dynamodb.validation.json b/tests/aws/services/dynamodb/test_dynamodb.validation.json index 6a2220f1f2937..48d1c39790524 100644 --- a/tests/aws/services/dynamodb/test_dynamodb.validation.json +++ b/tests/aws/services/dynamodb/test_dynamodb.validation.json @@ -1,101 +1,338 @@ { "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_binary": { - "last_validated_date": "2023-08-23T14:32:35+00:00" + "last_validated_date": "2025-10-08T12:33:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.15, + "teardown": 0.41, + "total": 6.56 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items": { - "last_validated_date": "2023-08-23T14:33:43+00:00" + "last_validated_date": "2025-10-08T12:36:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.2, + "teardown": 0.34, + "total": 5.54 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items_streaming": { - "last_validated_date": "2023-10-22T21:23:09+00:00" + "last_validated_date": "2025-10-08T12:36:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.08, + "teardown": 0.41, + "total": 7.49 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_existing_table": { - "last_validated_date": "2023-10-22T18:45:30+00:00" + "last_validated_date": "2025-10-08T12:37:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_matching_schema": { - "last_validated_date": "2023-08-23T14:34:26+00:00" + "last_validated_date": "2025-10-08T12:37:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.37, + "teardown": 0.5, + "total": 6.87 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_continuous_backup_update": { - "last_validated_date": "2023-08-23T14:34:39+00:00" + "last_validated_date": "2025-10-08T12:38:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 10.06, + "teardown": 0.41, + "total": 10.47 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_create_duplicate_table": { - "last_validated_date": "2023-08-23T14:33:08+00:00" + "last_validated_date": "2025-10-08T12:35:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.98, + "teardown": 2.68, + "total": 8.66 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_data_encoding_consistency": { - "last_validated_date": "2023-08-24T08:56:37+00:00" + "last_validated_date": "2025-10-08T12:37:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.8, + "teardown": 0.34, + "total": 8.14 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_batch_execute_statement": { - "last_validated_date": "2023-08-23T14:32:51+00:00" + "last_validated_date": "2025-10-08T12:33:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.75, + "teardown": 2.14, + "total": 6.89 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_partial_sse_specification": { - "last_validated_date": "2024-01-10T12:59:50+00:00" + "last_validated_date": "2025-10-08T14:03:41+00:00", + "durations_in_seconds": { + "setup": 0.67, + "call": 7.68, + "teardown": 0.3, + "total": 8.65 + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_describe_contributor_insights": { + "last_validated_date": "2025-10-08T12:46:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.54, + "teardown": 0.49, + "total": 7.03 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_statement_empy_parameter": { - "last_validated_date": "2025-01-03T09:24:27+00:00" + "last_validated_date": "2025-10-08T12:33:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.77, + "teardown": 0.35, + "total": 7.12 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_transaction": { - "last_validated_date": "2023-08-23T14:32:44+00:00" + "last_validated_date": "2025-10-08T12:33:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.99, + "teardown": 0.41, + "total": 8.4 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_get_batch_items": { - "last_validated_date": "2023-08-23T14:33:58+00:00" + "last_validated_date": "2025-10-08T12:37:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.7, + "teardown": 0.4, + "total": 5.1 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_idempotent_writing": { - "last_validated_date": "2023-08-23T14:39:54+00:00" + "last_validated_date": "2025-10-08T12:37:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.81, + "teardown": 0.42, + "total": 6.23 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_partiql_missing": { - "last_validated_date": "2023-08-23T14:33:00+00:00" + "last_validated_date": "2025-10-08T12:33:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.34, + "teardown": 1.54, + "total": 7.88 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_pay_per_request": { - "last_validated_date": "2023-08-23T14:33:43+00:00" + "last_validated_date": "2025-10-08T12:36:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.18, + "teardown": 0.17, + "total": 0.35 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_records_with_update_item": { "last_validated_date": "2024-06-26T14:28:26+00:00" }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_stream_view_type": { - "last_validated_date": "2024-05-31T14:49:56+00:00" + "last_validated_date": "2025-10-08T12:34:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 10.26, + "teardown": 0.38, + "total": 10.64 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_describe_with_exclusive_start_shard_id": { - "last_validated_date": "2023-10-22T20:27:28+00:00" + "last_validated_date": "2025-10-08T12:37:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.84, + "teardown": 0.46, + "total": 7.3 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_update_table_without_sse_specification_change": { - "last_validated_date": "2024-12-17T10:39:19+00:00" + "last_validated_date": "2025-10-08T14:06:57+00:00", + "durations_in_seconds": { + "setup": 0.77, + "call": 7.22, + "teardown": 39.29, + "total": 47.28 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_empty_and_binary_values": { - "last_validated_date": "2023-08-23T14:32:29+00:00" + "last_validated_date": "2025-10-08T12:33:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.36, + "teardown": 0.0, + "total": 6.36 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PAY_PER_REQUEST]": { - "last_validated_date": "2025-01-08T18:17:06+00:00" + "last_validated_date": "2025-10-08T12:46:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.17, + "teardown": 0.51, + "total": 14.68 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PROVISIONED]": { - "last_validated_date": "2025-01-08T18:17:21+00:00" + "last_validated_date": "2025-10-08T12:46:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.33, + "teardown": 0.33, + "total": 15.66 + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_kinesis_streaming_destination_crud": { + "last_validated_date": "2025-07-23T13:31:04+00:00", + "durations_in_seconds": { + "setup": 1.29, + "call": 238.19, + "teardown": 1.53, + "total": 241.01 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_in_put_item": { - "last_validated_date": "2023-08-23T14:32:21+00:00" + "last_validated_date": "2025-10-08T12:33:15+00:00", + "durations_in_seconds": { + "setup": 6.35, + "call": 0.92, + "teardown": 0.19, + "total": 7.46 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_on_conditions_check_failure": { - "last_validated_date": "2024-01-03T17:52:19+00:00" + "last_validated_date": "2025-10-08T12:45:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.6, + "teardown": 0.34, + "total": 6.94 + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_stream_destination_records": { + "last_validated_date": "2025-10-08T12:45:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 468.26, + "teardown": 0.62, + "total": 468.88 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_streams_on_global_tables": { - "last_validated_date": "2025-05-22T12:44:55+00:00" + "last_validated_date": "2025-10-08T12:35:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 64.24, + "teardown": 3.24, + "total": 67.48 + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_table_crud": { + "last_validated_date": "2025-10-08T12:35:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.7, + "teardown": 0.21, + "total": 6.91 + } + }, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_table_warm_throughput": { + "last_validated_date": "2025-10-08T12:35:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.84, + "teardown": 0.33, + "total": 7.17 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_get_items": { - "last_validated_date": "2023-08-23T14:33:37+00:00" + "last_validated_date": "2026-01-08T19:22:29+00:00", + "durations_in_seconds": { + "setup": 0.66, + "call": 17.33, + "teardown": 0.67, + "total": 18.66 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming": { - "last_validated_date": "2024-05-31T14:47:04+00:00" + "last_validated_date": "2025-10-08T12:46:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.54, + "teardown": 0.32, + "total": 8.86 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming_for_different_tables": { - "last_validated_date": "2024-04-02T21:45:36+00:00" + "last_validated_date": "2025-10-08T12:46:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.7, + "teardown": 0.77, + "total": 13.47 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_binary_data": { - "last_validated_date": "2023-08-23T14:33:31+00:00" + "last_validated_date": "2025-10-08T12:35:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.62, + "teardown": 0.33, + "total": 7.95 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_canceled": { - "last_validated_date": "2023-08-23T14:33:24+00:00" + "last_validated_date": "2025-10-08T12:35:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.79, + "teardown": 0.4, + "total": 6.19 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_items": { - "last_validated_date": "2023-08-23T14:33:16+00:00" + "last_validated_date": "2026-01-07T10:30:09+00:00", + "durations_in_seconds": { + "setup": 0.62, + "call": 13.46, + "teardown": 0.55, + "total": 14.63 + } }, "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_local_secondary_index": { - "last_validated_date": "2023-08-23T14:32:11+00:00" + "last_validated_date": "2025-10-08T12:33:08+00:00", + "durations_in_seconds": { + "setup": 0.89, + "call": 8.17, + "teardown": 0.49, + "total": 9.55 + } } } diff --git a/tests/aws/services/dynamodbstreams/test_dynamodb_streams.py b/tests/aws/services/dynamodbstreams/test_dynamodb_streams.py index cb1eb03fe0154..65d132c406d6c 100644 --- a/tests/aws/services/dynamodbstreams/test_dynamodb_streams.py +++ b/tests/aws/services/dynamodbstreams/test_dynamodb_streams.py @@ -24,7 +24,6 @@ class TestDynamoDBStreams: paths=[ "$..Table.ProvisionedThroughput.LastDecreaseDateTime", "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", ] ) def test_table_v2_stream(self, aws_client, infrastructure_setup, snapshot): diff --git a/tests/aws/services/dynamodbstreams/test_dynamodb_streams.snapshot.json b/tests/aws/services/dynamodbstreams/test_dynamodb_streams.snapshot.json index 129c09529015e..3d1602474b5d0 100644 --- a/tests/aws/services/dynamodbstreams/test_dynamodb_streams.snapshot.json +++ b/tests/aws/services/dynamodbstreams/test_dynamodb_streams.snapshot.json @@ -35,7 +35,7 @@ } }, "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_table_v2_stream": { - "recorded-date": "12-06-2024, 21:57:48", + "recorded-date": "13-10-2025, 19:03:15", "recorded-content": { "global-table-v2": { "Table": { @@ -73,7 +73,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "" + "TableStatus": "", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, diff --git a/tests/aws/services/dynamodbstreams/test_dynamodb_streams.validation.json b/tests/aws/services/dynamodbstreams/test_dynamodb_streams.validation.json index da11f399f14e0..22c9c9d7fa3c0 100644 --- a/tests/aws/services/dynamodbstreams/test_dynamodb_streams.validation.json +++ b/tests/aws/services/dynamodbstreams/test_dynamodb_streams.validation.json @@ -6,6 +6,12 @@ "last_validated_date": "2024-11-20T11:02:24+00:00" }, "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_table_v2_stream": { - "last_validated_date": "2024-06-12T21:57:48+00:00" + "last_validated_date": "2025-10-13T19:03:15+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 52.37, + "teardown": 0.01, + "total": 52.85 + } } } diff --git a/tests/aws/services/ec2/test_ec2.py b/tests/aws/services/ec2/test_ec2.py index bc809e1edd022..589576154c875 100644 --- a/tests/aws/services/ec2/test_ec2.py +++ b/tests/aws/services/ec2/test_ec2.py @@ -4,7 +4,6 @@ import pytest from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import SortingTransformer -from moto.ec2 import ec2_backends from moto.ec2.utils import ( random_security_group_id, random_subnet_id, @@ -13,10 +12,12 @@ from localstack.constants import AWS_REGION_US_EAST_1, TAG_KEY_CUSTOM_ID from localstack.services.ec2.patches import SecurityGroupIdentifier, VpcIdentifier +from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.id_generator import localstack_id_manager from localstack.utils.strings import short_uid from localstack.utils.sync import retry +from localstack.utils.urls import localstack_host LOG = logging.getLogger(__name__) @@ -411,6 +412,53 @@ def test_describe_vpc_endpoints_with_filter(self, aws_client, region_name): # clean up aws_client.ec2.delete_vpc(VpcId=vpc_id) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Groups", "$..ServiceRegion"]) + def test_vpc_endpoint_dns_names( + self, aws_client, create_vpc, region_name, snapshot, cleanups, ec2_create_vpc_endpoint + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("GroupId"), + snapshot.transform.key_value("VpcEndpointId"), + snapshot.transform.key_value("VpcId"), + snapshot.transform.key_value("HostedZoneId"), + snapshot.transform.key_value("subnet-id"), + snapshot.transform.jsonpath( + "$.available-endpoint.NetworkInterfaceIds[*]", + value_replacement="network-interface-id", + ), + snapshot.transform.key_value("dns-suffix"), + snapshot.transform.key_value("host"), + ] + ) + host = "amazonaws.com" if is_aws_cloud() else localstack_host().host_and_port() + snapshot.match("host", host) + + vpc = create_vpc(cidr_block="10.0.0.0/24") + vpc_id = vpc["Vpc"]["VpcId"] + aws_client.ec2.modify_vpc_attribute(VpcId=vpc_id, EnableDnsSupport={"Value": True}) + aws_client.ec2.modify_vpc_attribute(VpcId=vpc_id, EnableDnsHostnames={"Value": True}) + subnet = aws_client.ec2.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/24") + subnet_id = subnet["Subnet"]["SubnetId"] + snapshot.match("subnet-id", subnet_id) + + service_name = f"com.amazonaws.{region_name}.execute-api" + vpc_endpoint = ec2_create_vpc_endpoint( + VpcId=vpc["Vpc"]["VpcId"], + ServiceName=service_name, + VpcEndpointType="Interface", + PrivateDnsEnabled=True, + SubnetIds=[subnet_id], + ) + + # LS only returns one dns entry + vpc_endpoint["DnsEntries"] = vpc_endpoint["DnsEntries"][:1] + snapshot.match( + "dns-suffix", vpc_endpoint["DnsEntries"][0]["DnsName"].split(".")[0].split("-")[-1] + ) + snapshot.match("available-endpoint", vpc_endpoint) + @markers.aws.validated @pytest.mark.parametrize("id_type", ["id", "name"]) def test_modify_launch_template(self, create_launch_template, id_type, aws_client): @@ -619,6 +667,7 @@ def test_create_subnet_with_custom_id_and_vpc_id(self, cleanups, aws_client, cre assert subnet["Tags"][0]["Key"] == TAG_KEY_CUSTOM_ID assert subnet["Tags"][0]["Value"] == custom_subnet_id + @markers.requires_in_process @markers.aws.only_localstack @pytest.mark.parametrize("strategy", ["tag", "id_manager"]) @pytest.mark.parametrize("default_vpc", [True, False]) @@ -700,6 +749,7 @@ def _create_security_group() -> dict: "$..Tags", # Tags can differ between environments "$..Vpc.IsDefault", # TODO: CreateVPC should return an IsDefault param "$..Vpc.DhcpOptionsId", # FIXME: DhcpOptionsId uses different reference formats in AWS vs LocalStack + "$..Vpc.State", # Moto VPC immediately reaches the 'available' state, vs. AWS VPC which has a 'pending' state ] ) @markers.aws.validated @@ -934,28 +984,7 @@ def test_raise_duplicate_launch_template_name(create_launch_template): assert e.value.response["Error"]["Code"] == "InvalidLaunchTemplateName.AlreadyExistsException" -@pytest.fixture -def pickle_backends(): - def _can_pickle(*args) -> bool: - import dill - - try: - for i in args: - dill.dumps(i) - except TypeError: - return False - return True - - return _can_pickle - - -@markers.aws.only_localstack -def test_pickle_ec2_backend(pickle_backends, aws_client): - _ = aws_client.ec2.describe_account_attributes() - pickle_backends(ec2_backends) - assert pickle_backends(ec2_backends) - - +@markers.requires_in_process @markers.aws.only_localstack def test_create_specific_vpc_id(account_id, region_name, create_vpc, set_resource_custom_id): cidr_block = "10.0.0.0/16" diff --git a/tests/aws/services/ec2/test_ec2.snapshot.json b/tests/aws/services/ec2/test_ec2.snapshot.json index 026c53fa57960..54f7fa64be9c4 100644 --- a/tests/aws/services/ec2/test_ec2.snapshot.json +++ b/tests/aws/services/ec2/test_ec2.snapshot.json @@ -494,5 +494,60 @@ } } } + }, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vpc_endpoint_dns_names": { + "recorded-date": "29-07-2025, 23:29:35", + "recorded-content": { + "host": "", + "subnet-id": "", + "dns-suffix": "", + "network-interface-id": "", + "available-endpoint": { + "CreationTimestamp": "", + "DnsEntries": [ + { + "DnsName": "-.execute-api..vpce.", + "HostedZoneId": "" + } + ], + "DnsOptions": { + "DnsRecordIpType": "ipv4" + }, + "Groups": [ + { + "GroupId": "", + "GroupName": "default" + } + ], + "IpAddressType": "ipv4", + "NetworkInterfaceIds": [ + "" + ], + "OwnerId": "111111111111", + "PolicyDocument": { + "Statement": [ + { + "Action": "*", + "Effect": "Allow", + "Principal": "*", + "Resource": "*" + } + ] + }, + "PrivateDnsEnabled": true, + "RequesterManaged": false, + "RouteTableIds": [], + "ServiceName": "com.amazonaws..execute-api", + "ServiceRegion": "", + "State": "available", + "SubnetIds": [ + "" + ], + "Tags": [], + "VpcEndpointId": "", + "VpcEndpointType": "Interface", + "VpcId": "" + } + } } } diff --git a/tests/aws/services/ec2/test_ec2.validation.json b/tests/aws/services/ec2/test_ec2.validation.json index c26b3e4033cc4..42aa02a1f0dc0 100644 --- a/tests/aws/services/ec2/test_ec2.validation.json +++ b/tests/aws/services/ec2/test_ec2.validation.json @@ -17,6 +17,15 @@ "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vcp_peering_difference_regions": { "last_validated_date": "2024-06-07T21:28:25+00:00" }, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vpc_endpoint_dns_names": { + "last_validated_date": "2025-07-29T23:29:36+00:00", + "durations_in_seconds": { + "setup": 0.75, + "call": 59.53, + "teardown": 122.78, + "total": 183.06 + } + }, "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_ids": { "last_validated_date": "2025-05-28T09:17:24+00:00" }, diff --git a/tests/aws/services/es/test_es.py b/tests/aws/services/es/test_es.py index a736aed91cc06..4cb8c3c3246d1 100644 --- a/tests/aws/services/es/test_es.py +++ b/tests/aws/services/es/test_es.py @@ -5,8 +5,14 @@ import pytest from localstack import config -from localstack.constants import ELASTICSEARCH_DEFAULT_VERSION, OPENSEARCH_DEFAULT_VERSION -from localstack.services.opensearch.packages import elasticsearch_package, opensearch_package +from localstack.services.opensearch.packages import ( + ELASTICSEARCH_DEFAULT_VERSION, + OPENSEARCH_DEFAULT_VERSION, + elasticsearch_package, + opensearch_package, +) +from localstack.testing import config as test_config +from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.common import safe_requests as requests from localstack.utils.common import short_uid, start_worker_thread @@ -46,11 +52,14 @@ def run_install(*args): @pytest.fixture(autouse=True) def elasticsearch(): + if is_aws_cloud() or test_config.TEST_SKIP_LOCALSTACK_START: + # we don't install the dependencies if LocalStack is not running in process + return + if not installed.is_set(): install_async() assert installed.wait(timeout=5 * 60), "gave up waiting for elasticsearch to install" - yield def try_cluster_health(cluster_url: str): diff --git a/tests/aws/services/events/conftest.py b/tests/aws/services/events/conftest.py index 77b9d925e033c..3b9d9068af98f 100644 --- a/tests/aws/services/events/conftest.py +++ b/tests/aws/services/events/conftest.py @@ -1,6 +1,5 @@ import json import logging -from typing import Tuple import pytest @@ -30,42 +29,6 @@ def _create_default_or_custom_event_bus(event_bus_type: str = "default"): return _create_default_or_custom_event_bus -@pytest.fixture -def create_role_event_bus_source_to_bus_target(create_iam_role_with_policy): - def _create_role_event_bus_to_bus(): - assume_role_policy_document_bus_source_to_bus_target = { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": {"Service": "events.amazonaws.com"}, - "Action": "sts:AssumeRole", - } - ], - } - - policy_document_bus_source_to_bus_target = { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Action": "events:PutEvents", - "Resource": "arn:aws:events:*:*:event-bus/*", - } - ], - } - - role_arn_bus_source_to_bus_target = create_iam_role_with_policy( - RoleDefinition=assume_role_policy_document_bus_source_to_bus_target, - PolicyDefinition=policy_document_bus_source_to_bus_target, - ) - - return role_arn_bus_source_to_bus_target - - yield _create_role_event_bus_to_bus - - @pytest.fixture def events_create_archive(aws_client, region_name, account_id): archives = [] @@ -192,7 +155,7 @@ def put_events_with_filter_to_sqs( ): def _put_events_with_filter_to_sqs( pattern: dict, - entries_asserts: list[Tuple[list[dict], bool]], + entries_asserts: list[tuple[list[dict], bool]], event_bus_name: str = None, input_path: str = None, input_transformer: dict[dict, str] = None, @@ -203,7 +166,7 @@ def _put_events_with_filter_to_sqs( event_bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=event_bus_name) - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() events_put_rule( Name=rule_name, @@ -350,47 +313,6 @@ def _add_resource_policy_logs_events_access(log_group_arn: str): aws_client.logs.delete_resource_policy(policyName=policy_name) -@pytest.fixture -def get_primary_secondary_client( - aws_client_factory, - secondary_aws_client_factory, - region_name, - secondary_region_name, - account_id, - secondary_account_id, -): - def _get_primary_secondary_clients(cross_scenario: str): - secondary_region = secondary_region_name - secondary_account = secondary_account_id - if cross_scenario not in ["region", "account", "region_account"]: - raise ValueError(f"cross_scenario {cross_scenario} not supported") - - primary_client = aws_client_factory(region_name=region_name) - - if cross_scenario == "region": - secondary_account = account_id - secondary_client = aws_client_factory(region_name=secondary_region_name) - - elif cross_scenario == "account": - secondary_region = region_name - secondary_client = secondary_aws_client_factory(region_name=region_name) - - elif cross_scenario == "region_account": - secondary_client = secondary_aws_client_factory(region_name=secondary_region) - - else: - raise ValueError(f"cross_scenario {cross_scenario} not supported") - - return { - "primary_aws_client": primary_client, - "secondary_aws_client": secondary_client, - "secondary_region_name": secondary_region, - "secondary_account_id": secondary_account, - } - - return _get_primary_secondary_clients - - @pytest.fixture def connection_name(): return f"test-connection-{short_uid()}" diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_list_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_list_missing_NEG.json5 new file mode 100644 index 0000000000000..bfcefe970b6e8 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_list_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "state" : [{ "anything-but": { "equals-ignore-case": ["initializing", "stopped"] }}] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_list_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_list_null.json5 new file mode 100644 index 0000000000000..1ad070e089648 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_list_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": null + } + }, + "EventPattern": { + "detail": { + "state" : [{ "anything-but": { "equals-ignore-case": ["initializing", "stopped"] }}] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_missing_NEG.json5 new file mode 100644 index 0000000000000..de4dd8683bbb4 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "state" : [{ "anything-but": { "equals-ignore-case": "initializing" }}] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_null.json5 new file mode 100644 index 0000000000000..1767b0a6fff44 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_ignorecase_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": null + } + }, + "EventPattern": { + "detail": { + "state" : [{ "anything-but": { "equals-ignore-case": "initializing" }}] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_missing_NEG.json5 new file mode 100644 index 0000000000000..f0801cb59d4ab --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": [ "stopped", "overloaded" ] } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_null.json5 new file mode 100644 index 0000000000000..a0267aa546757 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": null + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": [ "stopped", "running" ] } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_null_type_EXC.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_null_type_EXC.json5 new file mode 100644 index 0000000000000..3bbef69085000 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_list_null_type_EXC.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": "pending" + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": [ "stopped", null ] } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_string_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_missing_NEG.json5 new file mode 100644 index 0000000000000..f9c051678cfe0 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": "initializing" } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_but_string_null_type_EXC.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_null_type_EXC.json5 new file mode 100644 index 0000000000000..5445373362fae --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_but_string_null_type_EXC.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": null + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": null } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_prefix_int_value.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_int_value.json5 new file mode 100644 index 0000000000000..98c949e5d6efb --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_int_value.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": 12 + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "prefix": "init" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_int.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_int.json5 new file mode 100644 index 0000000000000..dfc91d45784d0 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_int.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": 12 + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "prefix": ["init", "test"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_missing_NEG.json5 new file mode 100644 index 0000000000000..89d953c3cf0d1 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "prefix": ["init", "post"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_null.json5 new file mode 100644 index 0000000000000..38269ae490e32 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_list_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": null + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "prefix": ["init", "test"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_prefix_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_missing_NEG.json5 new file mode 100644 index 0000000000000..5495b5b2d9642 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "prefix": "init" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_prefix_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_null.json5 new file mode 100644 index 0000000000000..5e6eac0e8522c --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_prefix_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": null + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "prefix": "init" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_missing_NEG.json5 new file mode 100644 index 0000000000000..a1d44c76c52cd --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "FileName": [ { "anything-but": { "suffix": [".txt", ".jpg"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_null.json5 new file mode 100644 index 0000000000000..ebd6b6c1f16f3 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "FileName": null + } + }, + "EventPattern": { + "detail": { + "FileName": [ { "anything-but": { "suffix": [".txt", ".jpg"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_null_type_EXC.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_null_type_EXC.json5 new file mode 100644 index 0000000000000..70c01f5e1c6ae --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_list_null_type_EXC.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "FileName": "file.txt.bak" + } + }, + "EventPattern": { + "detail": { + "FileName": [ { "anything-but": { "suffix": [null, ".txt"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_suffix_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_missing_NEG.json5 new file mode 100644 index 0000000000000..0b250afca0b84 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "FileName": [ { "anything-but": { "suffix": ".txt" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_suffix_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_null.json5 new file mode 100644 index 0000000000000..69cf795fbed84 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_suffix_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "FileName": null + } + }, + "EventPattern": { + "detail": { + "FileName": [ { "anything-but": { "suffix": ".txt" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_int.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_int.json5 new file mode 100644 index 0000000000000..efc2c6bf92bd8 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_int.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "FilePath": 123 + } + }, + "EventPattern": { + "detail": { + "FilePath": [ { "anything-but": { "wildcard": "*/dir/*" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_list_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_list_missing_NEG.json5 new file mode 100644 index 0000000000000..4455f194e5e15 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_list_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "wildcard": ["*/init/*", "*/dir/*"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_list_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_list_null.json5 new file mode 100644 index 0000000000000..7c9e91cd5096b --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_list_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "state": null + } + }, + "EventPattern": { + "detail": { + "state": [ { "anything-but": { "wildcard": ["*/init/*", "*/dir/*"] } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_missing_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_missing_NEG.json5 new file mode 100644 index 0000000000000..b03e6dbbb15bd --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_missing_NEG.json5 @@ -0,0 +1,17 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": {} + }, + "EventPattern": { + "detail": { + "FilePath": [ { "anything-but": { "wildcard": "*/init/*" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_null.json5 b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_null.json5 new file mode 100644 index 0000000000000..606da0a9675b2 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_anything_wildcard_null.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-anything-but +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "FilePath": null + } + }, + "EventPattern": { + "detail": { + "FilePath": [ { "anything-but": { "wildcard": "*/dir/*" } } ] + } + } +} diff --git a/tests/aws/services/events/event_pattern_templates/content_ip_address_bad_type_NEG.json5 b/tests/aws/services/events/event_pattern_templates/content_ip_address_bad_type_NEG.json5 new file mode 100644 index 0000000000000..0f1dbb8e7fb96 --- /dev/null +++ b/tests/aws/services/events/event_pattern_templates/content_ip_address_bad_type_NEG.json5 @@ -0,0 +1,19 @@ +// Based on https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns-content-based-filtering.html#eb-filtering-ip-matching +{ + "Event": { + "id": "1", + "source": "test-source", + "detail-type": "test-detail-type", + "account": "123456789012", + "region": "us-east-2", + "time": "2022-07-13T13:48:01Z", + "detail": { + "sourceIPAddress": 123 + } + }, + "EventPattern": { + "detail": { + "sourceIPAddress": [ { "cidr": "10.0.0.0/24" } ] + } + } +} diff --git a/tests/aws/services/events/helper_functions.py b/tests/aws/services/events/helper_functions.py index 0c39f6b7b813a..7ddd238f7c1a4 100644 --- a/tests/aws/services/events/helper_functions.py +++ b/tests/aws/services/events/helper_functions.py @@ -1,6 +1,6 @@ import json import os -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from localstack.testing.aws.util import is_aws_cloud from localstack.utils.sync import retry @@ -24,7 +24,7 @@ def events_time_string_to_timestamp(time_string: str) -> datetime: def get_cron_expression(delta_minutes: int) -> tuple[str, datetime]: """Get a exact cron expression for a future time in UTC from now rounded to the next full minute + delta_minutes.""" - now = datetime.now(timezone.utc) + now = datetime.now(UTC) future_time = now + timedelta(minutes=delta_minutes) # Round to the next full minute diff --git a/tests/aws/services/events/test_archive_and_replay.py b/tests/aws/services/events/test_archive_and_replay.py index 18c18804c24f4..8f70514949a80 100644 --- a/tests/aws/services/events/test_archive_and_replay.py +++ b/tests/aws/services/events/test_archive_and_replay.py @@ -1,5 +1,5 @@ import json -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta import pytest from botocore.exceptions import ClientError @@ -377,7 +377,7 @@ def test_start_list_describe_canceled_replay( aws_client, snapshot, ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) # setup event bus @@ -393,7 +393,7 @@ def test_start_list_describe_canceled_replay( rule_arn = response["RuleArn"] # setup sqs target - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() target_id = f"target-{short_uid()}" aws_client.events.put_targets( Rule=rule_name, @@ -503,7 +503,7 @@ def test_start_list_describe_canceled_replay( def test_list_replays_with_prefix( self, events_create_archive, events_create_event_bus, aws_client, snapshot ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) event_bus_name = f"test-bus-{short_uid()}" @@ -558,7 +558,7 @@ def test_list_replays_with_prefix( def test_list_replays_with_event_source_arn( self, events_create_event_bus, events_create_archive, aws_client, snapshot ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) event_bus_name = f"test-bus-{short_uid()}" @@ -596,7 +596,7 @@ def test_list_replays_with_event_source_arn( def test_list_replay_with_limit( self, events_create_event_bus, events_create_archive, aws_client, snapshot ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) event_bus_name = f"test-bus-{short_uid()}" @@ -666,8 +666,8 @@ def test_start_replay_error_unknown_event_bus( f"arn:aws:events:{region_name}:{account_id}:event-bus/{not_existing_event_bus_name}" ) - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) replay_name = f"test-replay-{short_uid()}" with pytest.raises(ClientError) as error: @@ -708,8 +708,8 @@ def test_start_replay_error_unknown_archive( self, aws_client, region_name, account_id, snapshot ): not_existing_archive_name = f"doesnotexist-{short_uid()}" - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) with pytest.raises(ClientError) as error: aws_client.events.start_replay( ReplayName="test-replay", @@ -739,8 +739,8 @@ def test_start_replay_error_duplicate_name_same_archive( ] replay_name = f"test-replay-{short_uid()}" - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) aws_client.events.start_replay( ReplayName=replay_name, Description="description of the replay", @@ -786,8 +786,8 @@ def test_start_replay_error_duplicate_different_archive( "ArchiveArn" ] - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) replay_name = f"test-replay-{short_uid()}" aws_client.events.start_replay( @@ -828,7 +828,7 @@ def test_start_replay_error_invalid_end_time( response = events_create_archive() archive_arn = response["ArchiveArn"] - start_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) end_time = start_time.replace(microsecond=0) - timedelta( seconds=negative_time_delta_seconds ) @@ -864,8 +864,8 @@ def tests_concurrency_error_too_many_active_replays( )["ArchiveArn"] replay_name_prefix = short_uid() - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) num_replays = 10 for i in range(num_replays): diff --git a/tests/aws/services/events/test_events.py b/tests/aws/services/events/test_events.py index cb748eb832c1c..26e920f870132 100644 --- a/tests/aws/services/events/test_events.py +++ b/tests/aws/services/events/test_events.py @@ -40,6 +40,7 @@ "payload": {"acc_id": "0a787ecb-4015", "sf_id": "baz"}, "listsingle": ["HIGH"], "listmulti": ["ACTIVE", "INACTIVE"], + "valid": True, } TEST_EVENT_DETAIL = { @@ -293,8 +294,8 @@ def test_put_events_response_entries_order( ): """Test that put_events response contains each EventId only once, even with multiple targets.""" - queue_url_1, queue_arn_1 = sqs_as_events_target() - queue_url_2, queue_arn_2 = sqs_as_events_target() + queue_url_1, queue_arn_1, _ = sqs_as_events_target() + queue_url_2, queue_arn_2, _ = sqs_as_events_target() rule_name = f"test-rule-{short_uid()}" @@ -454,7 +455,7 @@ def test_put_events_with_time_field( ): """Test that EventBridge correctly handles datetime serialization in events.""" rule_name = f"test-rule-{short_uid()}" - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() snapshot.add_transformers_list( [ @@ -886,8 +887,8 @@ def test_put_events_bus_to_bus( ): monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) - bus_name_one = "bus1-{}".format(short_uid()) - bus_name_two = "bus2-{}".format(short_uid()) + bus_name_one = f"bus1-{short_uid()}" + bus_name_two = f"bus2-{short_uid()}" events_create_event_bus(Name=bus_name_one) event_bus_2_arn = events_create_event_bus(Name=bus_name_two)["EventBusArn"] @@ -954,7 +955,7 @@ def test_put_events_bus_to_bus( ) # Create sqs target - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() # Rule and target bus 2 to sqs rule_name_bus_two = f"rule-{short_uid()}" diff --git a/tests/aws/services/events/test_events_inputs.py b/tests/aws/services/events/test_events_inputs.py index 65e225a460c87..4f1ef2ffc9cc7 100644 --- a/tests/aws/services/events/test_events_inputs.py +++ b/tests/aws/services/events/test_events_inputs.py @@ -36,7 +36,7 @@ def test_put_event_input_path_and_input_transformer( sqs_as_events_target, events_create_event_bus, events_put_rule, aws_client, snapshot ): - _, queue_arn = sqs_as_events_target() + _, queue_arn, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) @@ -164,8 +164,8 @@ def test_put_events_with_input_path_multiple_targets( snapshot, ): # prepare target queues - queue_url_1, queue_arn_1 = sqs_as_events_target() - queue_url_2, queue_arn_2 = sqs_as_events_target() + queue_url_1, queue_arn_1, _ = sqs_as_events_target() + queue_url_2, queue_arn_2, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) @@ -355,7 +355,7 @@ def test_put_events_with_input_transformer_missing_keys( aws_client_factory, snapshot, ): - _, queue_arn = sqs_as_events_target() + _, queue_arn, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) @@ -424,7 +424,7 @@ def test_input_transformer_predefined_variables( # https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html#eb-transform-input-predefined # prepare target queues - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) @@ -500,6 +500,7 @@ def test_input_transformer_predefined_variables( '{"multi_replacement": "users//second/"}', # TODO known limitation due to sqs message handling sting with new line # '" single list item\n multiple list items"', + '"Command is !"', ], ) def test_input_transformer_nested_keys_replacement( @@ -529,6 +530,7 @@ def test_input_transformer_nested_keys_replacement( "systemstring": "$.detail.awsAccountId", # with resolve to empty value "listsingle": "$.detail.listsingle", "listmulti": "$.detail.listmulti", + "valid": "$.detail.valid", } input_transformer = { "InputPathsMap": input_path_map, diff --git a/tests/aws/services/events/test_events_inputs.snapshot.json b/tests/aws/services/events/test_events_inputs.snapshot.json index cf6ee9653ff58..5f401e3d1fcfe 100644 --- a/tests/aws/services/events/test_events_inputs.snapshot.json +++ b/tests/aws/services/events/test_events_inputs.snapshot.json @@ -511,5 +511,18 @@ } ] } + }, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Command is !\"]": { + "recorded-date": "08-10-2025, 09:20:09", + "recorded-content": { + "input-transformed-messages": [ + { + "MessageId": "", + "ReceiptHandle": "", + "MD5OfBody": "", + "Body": "\"Command is true!\"" + } + ] + } } } diff --git a/tests/aws/services/events/test_events_inputs.validation.json b/tests/aws/services/events/test_events_inputs.validation.json index 7a2e137c1e527..67442bda914da 100644 --- a/tests/aws/services/events/test_events_inputs.validation.json +++ b/tests/aws/services/events/test_events_inputs.validation.json @@ -1,104 +1,275 @@ { "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path": { - "last_validated_date": "2024-05-13T12:27:07+00:00" + "last_validated_date": "2025-10-08T11:09:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.27, + "teardown": 1.23, + "total": 2.5 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_max_level_depth": { - "last_validated_date": "2024-05-13T12:27:13+00:00" + "last_validated_date": "2025-10-08T11:10:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.25, + "teardown": 1.03, + "total": 2.28 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_multiple_targets": { - "last_validated_date": "2024-05-13T12:27:16+00:00" + "last_validated_date": "2025-10-08T11:10:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.95, + "teardown": 1.43, + "total": 3.38 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail0]": { - "last_validated_date": "2024-05-13T12:27:09+00:00" + "last_validated_date": "2025-10-08T11:09:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.27, + "teardown": 1.01, + "total": 2.28 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail1]": { - "last_validated_date": "2024-05-13T12:27:11+00:00" + "last_validated_date": "2025-10-08T11:09:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.24, + "teardown": 1.01, + "total": 2.25 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement": { "last_validated_date": "2024-12-06T11:07:17+00:00" }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" multiple list items\"]": { - "last_validated_date": "2024-12-13T18:03:28+00:00" + "last_validated_date": "2025-10-08T11:10:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.3, + "teardown": 1.06, + "total": 2.36 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item multiple list items system account id payload user id\"]": { - "last_validated_date": "2024-12-13T18:03:32+00:00" + "last_validated_date": "2025-10-08T11:10:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.28, + "teardown": 1.05, + "total": 2.33 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item\"\\n\" multiple list items\"\\n\" system account id\"\\n\" payload\"\\n\" user id\"]": { "last_validated_date": "2024-12-13T17:27:50+00:00" }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item\"]": { - "last_validated_date": "2024-12-13T18:03:25+00:00" + "last_validated_date": "2025-10-08T11:10:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.29, + "teardown": 1.05, + "total": 2.34 + } + }, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Command is !\"]": { + "last_validated_date": "2025-10-08T11:11:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.28, + "teardown": 1.14, + "total": 2.42 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Payload of with path users-service/users/ and \"]": { - "last_validated_date": "2024-12-13T18:03:14+00:00" + "last_validated_date": "2025-10-08T11:10:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.26, + "teardown": 1.16, + "total": 2.42 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Payload of with path users-service/users/\"]": { "last_validated_date": "2024-12-13T13:20:30+00:00" }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : \"\"}]": { - "last_validated_date": "2024-12-16T12:26:02+00:00" + "last_validated_date": "2025-10-08T11:10:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.24, + "teardown": 1.08, + "total": 2.32 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : }]": { - "last_validated_date": "2024-12-13T18:03:16+00:00" + "last_validated_date": "2025-10-08T11:10:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.28, + "teardown": 1.03, + "total": 2.31 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\": }]": { "last_validated_date": "2024-12-13T14:56:24+00:00" }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"nested\": {\"level1\": {\"level2\": {\"level3\": \"users-service/users/\"} } }, \"bod\": \"\"}]": { - "last_validated_date": "2024-12-13T18:03:23+00:00" + "last_validated_date": "2025-10-08T11:10:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.38, + "teardown": 1.04, + "total": 2.42 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": \"\"}]": { "last_validated_date": "2024-12-13T13:20:32+00:00" }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": }]": { - "last_validated_date": "2024-12-13T18:03:12+00:00" + "last_validated_date": "2025-10-08T11:10:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.46, + "teardown": 1.06, + "total": 2.52 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": [, \"hardcoded\"]}]": { - "last_validated_date": "2024-12-13T18:03:21+00:00" + "last_validated_date": "2025-10-08T11:10:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.47, + "teardown": 1.53, + "total": 3.0 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"id\": \"\", \"body\": }]": { "last_validated_date": "2024-12-13T14:54:39+00:00" }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"id\": , \"body\": }]": { - "last_validated_date": "2024-12-13T18:03:18+00:00" + "last_validated_date": "2025-10-08T11:10:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.28, + "teardown": 1.02, + "total": 2.3 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"multi_replacement\": \"users//second/\"}]": { - "last_validated_date": "2024-12-13T18:03:35+00:00" + "last_validated_date": "2025-10-08T11:10:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.92, + "teardown": 1.05, + "total": 2.97 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"singlelistitem\": }]": { - "last_validated_date": "2024-12-13T18:03:30+00:00" + "last_validated_date": "2025-10-08T11:10:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.28, + "teardown": 1.17, + "total": 2.45 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"not_valid\": \"users-service/users/\", \"bod\": }]": { - "last_validated_date": "2024-12-13T14:55:05+00:00" + "last_validated_date": "2025-10-08T11:11:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.22, + "teardown": 1.08, + "total": 7.3 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"payload\": \"\"}]": { - "last_validated_date": "2024-12-13T14:55:13+00:00" + "last_validated_date": "2025-10-08T11:11:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.17, + "teardown": 1.06, + "total": 7.23 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"singlelistitem\": \"\"}]": { - "last_validated_date": "2024-12-13T17:19:20+00:00" + "last_validated_date": "2025-10-08T11:11:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.17, + "teardown": 1.52, + "total": 7.69 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[\"Message containing all pre defined variables \"]": { - "last_validated_date": "2024-06-11T08:33:10+00:00" + "last_validated_date": "2025-10-08T11:10:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.71, + "teardown": 1.15, + "total": 2.86 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[{\"originalEvent\": , \"originalEventJson\": }]": { - "last_validated_date": "2024-06-11T08:33:13+00:00" + "last_validated_date": "2025-10-08T11:10:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.34, + "teardown": 1.02, + "total": 2.36 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_json": { - "last_validated_date": "2024-06-11T08:33:04+00:00" + "last_validated_date": "2025-10-08T11:10:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.1, + "teardown": 2.07, + "total": 5.17 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string": { "last_validated_date": "2024-05-13T12:27:20+00:00" }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"Event of type, at time , info extracted from detail \"]": { - "last_validated_date": "2024-06-11T08:32:56+00:00" + "last_validated_date": "2025-10-08T11:10:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.6, + "teardown": 2.31, + "total": 4.91 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"{[/Check with special starting characters for event of type\"]": { - "last_validated_date": "2024-06-11T08:33:00+00:00" + "last_validated_date": "2025-10-08T11:10:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.76, + "teardown": 2.05, + "total": 4.81 + } }, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_missing_keys": { - "last_validated_date": "2025-03-12T10:19:13+00:00" + "last_validated_date": "2025-10-08T11:10:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.3, + "teardown": 0.91, + "total": 2.21 + } }, "tests/aws/services/events/test_events_inputs.py::test_put_event_input_path_and_input_transformer": { - "last_validated_date": "2025-03-12T10:19:01+00:00" + "last_validated_date": "2025-10-08T11:09:52+00:00", + "durations_in_seconds": { + "setup": 0.58, + "call": 1.76, + "teardown": 1.39, + "total": 3.73 + } } } diff --git a/tests/aws/services/events/test_events_patterns.py b/tests/aws/services/events/test_events_patterns.py index a8af3d5cc1a8b..8c5bc3a73789c 100644 --- a/tests/aws/services/events/test_events_patterns.py +++ b/tests/aws/services/events/test_events_patterns.py @@ -3,7 +3,6 @@ import os from datetime import datetime from pathlib import Path -from typing import List, Tuple import json5 import pytest @@ -26,18 +25,18 @@ TEST_PAYLOAD_DIR = os.path.join(THIS_FOLDER, "test_payloads") -def load_request_templates(directory_path: str) -> List[Tuple[dict, str]]: +def load_request_templates(directory_path: str) -> list[tuple[dict, str]]: json5_files = list_files_with_suffix(directory_path, ".json5") return [load_request_template(file_path) for file_path in json5_files] -def load_request_template(file_path: str) -> Tuple[dict, str]: - with open(file_path, "r") as df: +def load_request_template(file_path: str) -> tuple[dict, str]: + with open(file_path) as df: template = json5.load(df) return template, Path(file_path).stem -def list_files_with_suffix(directory_path: str, suffix: str) -> List[str]: +def list_files_with_suffix(directory_path: str, suffix: str) -> list[str]: files = [] for root, _, filenames in os.walk(directory_path): for filename in filenames: @@ -117,8 +116,8 @@ def test_event_pattern_with_multi_key(self, aws_client): """ with ( - open(COMPLEX_MULTI_KEY_EVENT, "r") as event_file, - open(COMPLEX_MULTI_KEY_EVENT_PATTERN, "r") as event_pattern_file, + open(COMPLEX_MULTI_KEY_EVENT) as event_file, + open(COMPLEX_MULTI_KEY_EVENT_PATTERN) as event_pattern_file, ): event = event_file.read() event_pattern = event_pattern_file.read() @@ -446,7 +445,7 @@ def test_put_event_with_content_base_rule_in_pattern( snapshot, aws_client, ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() # Create event bus event_bus_name = f"event-bus-{short_uid()}" diff --git a/tests/aws/services/events/test_events_patterns.snapshot.json b/tests/aws/services/events/test_events_patterns.snapshot.json index 3dbc5cd4f1301..dd25607688d8e 100644 --- a/tests/aws/services/events/test_events_patterns.snapshot.json +++ b/tests/aws/services/events/test_events_patterns.snapshot.json @@ -1,22 +1,22 @@ { "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating]": { - "recorded-date": "22-01-2025, 10:56:14", + "recorded-date": "15-10-2025, 13:11:55", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[list_within_dict]": { - "recorded-date": "22-01-2025, 10:56:14", + "recorded-date": "15-10-2025, 13:11:56", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_NEG]": { - "recorded-date": "22-01-2025, 10:56:14", + "recorded-date": "15-10-2025, 13:11:56", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match]": { - "recorded-date": "22-01-2025, 10:56:15", + "recorded-date": "15-10-2025, 13:11:56", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[int_nolist_EXC]": { - "recorded-date": "22-01-2025, 10:56:15", + "recorded-date": "15-10-2025, 13:11:56", "recorded-content": { "int_nolist_EXC": { "exception_message": { @@ -35,39 +35,39 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:58", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_NEG]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_NEG]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_simplified]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match_NEG]": { - "recorded-date": "22-01-2025, 10:56:18", + "recorded-date": "15-10-2025, 13:12:00", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string]": { - "recorded-date": "22-01-2025, 10:56:18", + "recorded-date": "15-10-2025, 13:12:00", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_operatorcasing_EXC]": { - "recorded-date": "22-01-2025, 10:56:18", + "recorded-date": "15-10-2025, 13:12:01", "recorded-content": { "content_numeric_operatorcasing_EXC": { "exception_message": { @@ -86,19 +86,19 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list_NEG]": { - "recorded-date": "22-01-2025, 10:56:19", + "recorded-date": "15-10-2025, 13:12:01", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address]": { - "recorded-date": "22-01-2025, 10:56:19", + "recorded-date": "15-10-2025, 13:12:02", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_NEG]": { - "recorded-date": "22-01-2025, 10:56:19", + "recorded-date": "15-10-2025, 13:12:02", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_nolist_EXC]": { - "recorded-date": "22-01-2025, 10:56:20", + "recorded-date": "15-10-2025, 13:12:02", "recorded-content": { "string_nolist_EXC": { "exception_message": { @@ -117,27 +117,27 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb_NEG]": { - "recorded-date": "22-01-2025, 10:56:20", + "recorded-date": "15-10-2025, 13:12:03", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_NEG]": { - "recorded-date": "22-01-2025, 10:56:20", + "recorded-date": "15-10-2025, 13:12:03", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean_NEG]": { - "recorded-date": "22-01-2025, 10:56:20", + "recorded-date": "15-10-2025, 13:12:03", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb]": { - "recorded-date": "22-01-2025, 10:56:20", + "recorded-date": "15-10-2025, 13:12:03", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists-parent]": { - "recorded-date": "22-01-2025, 10:56:21", + "recorded-date": "15-10-2025, 13:12:03", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_EXC]": { - "recorded-date": "22-01-2025, 10:56:21", + "recorded-date": "15-10-2025, 13:12:03", "recorded-content": { "arrays_empty_EXC": { "exception_message": { @@ -156,55 +156,55 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[nested_json_NEG]": { - "recorded-date": "22-01-2025, 10:56:21", + "recorded-date": "15-10-2025, 13:12:04", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern]": { - "recorded-date": "22-01-2025, 10:56:21", + "recorded-date": "15-10-2025, 13:12:04", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_multiple_list]": { - "recorded-date": "22-01-2025, 10:56:21", + "recorded-date": "15-10-2025, 13:12:04", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list]": { - "recorded-date": "22-01-2025, 10:56:21", + "recorded-date": "15-10-2025, 13:12:04", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dynamodb]": { - "recorded-date": "22-01-2025, 10:56:22", + "recorded-date": "15-10-2025, 13:12:05", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase]": { - "recorded-date": "22-01-2025, 10:56:22", + "recorded-date": "15-10-2025, 13:12:05", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_empty]": { - "recorded-date": "22-01-2025, 10:56:22", + "recorded-date": "15-10-2025, 13:12:05", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_many_rules]": { - "recorded-date": "22-01-2025, 10:56:22", + "recorded-date": "15-10-2025, 13:12:05", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value]": { - "recorded-date": "22-01-2025, 10:56:23", + "recorded-date": "15-10-2025, 13:12:06", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list]": { - "recorded-date": "22-01-2025, 10:56:23", + "recorded-date": "15-10-2025, 13:12:06", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating_NEG]": { - "recorded-date": "22-01-2025, 10:56:23", + "recorded-date": "15-10-2025, 13:12:07", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_NEG]": { - "recorded-date": "22-01-2025, 10:56:23", + "recorded-date": "15-10-2025, 13:12:07", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_case_sensitive_EXC]": { - "recorded-date": "22-01-2025, 10:56:24", + "recorded-date": "15-10-2025, 13:12:07", "recorded-content": { "operator_case_sensitive_EXC": { "exception_message": { @@ -223,15 +223,15 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists]": { - "recorded-date": "22-01-2025, 10:56:24", + "recorded-date": "15-10-2025, 13:12:07", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_NEG]": { - "recorded-date": "22-01-2025, 10:56:24", + "recorded-date": "15-10-2025, 13:12:07", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_EXC]": { - "recorded-date": "22-01-2025, 10:56:25", + "recorded-date": "15-10-2025, 13:12:09", "recorded-content": { "content_numeric_EXC": { "exception_message": { @@ -250,87 +250,87 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or_NEG]": { - "recorded-date": "22-01-2025, 10:56:25", + "recorded-date": "15-10-2025, 13:12:09", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or]": { - "recorded-date": "22-01-2025, 10:56:25", + "recorded-date": "15-10-2025, 13:12:09", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and_NEG]": { - "recorded-date": "22-01-2025, 10:56:26", + "recorded-date": "15-10-2025, 13:12:10", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event]": { - "recorded-date": "22-01-2025, 10:56:26", + "recorded-date": "15-10-2025, 13:12:10", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_NEG]": { - "recorded-date": "22-01-2025, 10:56:26", + "recorded-date": "15-10-2025, 13:12:10", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_ignorecase]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:11", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:11", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value_NEG]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:11", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern_NEG]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:11", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:11", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[sample1]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:12", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[key_case_sensitive_NEG]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:12", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event_NEG]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:12", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[prefix]": { - "recorded-date": "22-01-2025, 10:56:28", + "recorded-date": "15-10-2025, 13:12:12", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix]": { - "recorded-date": "22-01-2025, 10:56:28", + "recorded-date": "15-10-2025, 13:12:13", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list]": { - "recorded-date": "22-01-2025, 10:56:29", + "recorded-date": "15-10-2025, 13:12:14", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_NEG]": { - "recorded-date": "22-01-2025, 10:56:29", + "recorded-date": "15-10-2025, 13:12:14", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string]": { - "recorded-date": "22-01-2025, 10:56:29", + "recorded-date": "15-10-2025, 13:12:15", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_null_NEG]": { - "recorded-date": "22-01-2025, 10:56:30", + "recorded-date": "15-10-2025, 13:12:15", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase_NEG]": { - "recorded-date": "22-01-2025, 10:56:30", + "recorded-date": "15-10-2025, 13:12:16", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_complex_EXC]": { - "recorded-date": "22-01-2025, 10:56:31", + "recorded-date": "15-10-2025, 13:12:16", "recorded-content": { "content_wildcard_complex_EXC": { "exception_message": { @@ -349,55 +349,55 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_NEG]": { - "recorded-date": "22-01-2025, 10:56:32", + "recorded-date": "15-10-2025, 13:12:18", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix]": { - "recorded-date": "22-01-2025, 10:56:33", + "recorded-date": "15-10-2025, 13:12:19", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_NEG]": { - "recorded-date": "22-01-2025, 10:56:33", + "recorded-date": "15-10-2025, 13:12:19", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[minimal]": { - "recorded-date": "22-01-2025, 10:56:33", + "recorded-date": "15-10-2025, 13:12:19", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false]": { - "recorded-date": "22-01-2025, 10:56:34", + "recorded-date": "15-10-2025, 13:12:21", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[number_comparison_float]": { - "recorded-date": "22-01-2025, 10:56:35", + "recorded-date": "15-10-2025, 13:12:21", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_NEG]": { - "recorded-date": "22-01-2025, 10:56:35", + "recorded-date": "15-10-2025, 13:12:22", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix]": { - "recorded-date": "22-01-2025, 10:56:35", + "recorded-date": "15-10-2025, 13:12:22", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_NEG]": { - "recorded-date": "22-01-2025, 10:56:36", + "recorded-date": "15-10-2025, 13:12:23", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase]": { - "recorded-date": "22-01-2025, 10:56:37", + "recorded-date": "15-10-2025, 13:12:24", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false_NEG]": { - "recorded-date": "22-01-2025, 10:56:37", + "recorded-date": "15-10-2025, 13:12:24", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_NEG]": { - "recorded-date": "22-01-2025, 10:56:37", + "recorded-date": "15-10-2025, 13:12:24", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_syntax_EXC]": { - "recorded-date": "22-01-2025, 10:56:38", + "recorded-date": "15-10-2025, 13:12:25", "recorded-content": { "content_numeric_syntax_EXC": { "exception_message": { @@ -416,19 +416,19 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and]": { - "recorded-date": "22-01-2025, 10:56:38", + "recorded-date": "15-10-2025, 13:12:25", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean]": { - "recorded-date": "22-01-2025, 10:56:38", + "recorded-date": "15-10-2025, 13:12:25", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number]": { - "recorded-date": "22-01-2025, 10:56:38", + "recorded-date": "15-10-2025, 13:12:26", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-anything-but]": { - "recorded-date": "22-01-2025, 10:56:38", + "recorded-date": "15-10-2025, 13:12:26", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_source": { @@ -580,7 +580,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_star_EXC]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:12:00", "recorded-content": { "content_wildcard_repeating_star_EXC": { "exception_message": { @@ -599,7 +599,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_EXC]": { - "recorded-date": "22-01-2025, 10:56:23", + "recorded-date": "15-10-2025, 13:12:06", "recorded-content": { "content_ignorecase_EXC": { "exception_message": { @@ -618,7 +618,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_EXC]": { - "recorded-date": "22-01-2025, 10:56:34", + "recorded-date": "15-10-2025, 13:12:20", "recorded-content": { "content_ip_address_EXC": { "exception_message": { @@ -637,7 +637,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_EXC]": { - "recorded-date": "22-01-2025, 10:56:24", + "recorded-date": "15-10-2025, 13:12:07", "recorded-content": { "content_anything_but_ignorecase_EXC": { "exception_message": { @@ -656,7 +656,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_EXC]": { - "recorded-date": "22-01-2025, 10:56:29", + "recorded-date": "15-10-2025, 13:12:13", "recorded-content": { "content_anything_but_ignorecase_list_EXC": { "exception_message": { @@ -675,7 +675,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_list_EXC]": { - "recorded-date": "22-01-2025, 10:56:31", + "recorded-date": "15-10-2025, 13:12:17", "recorded-content": { "content_ignorecase_list_EXC": { "exception_message": { @@ -754,27 +754,27 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null]": { - "recorded-date": "22-01-2025, 10:56:25", + "recorded-date": "15-10-2025, 13:12:09", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_NEG]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list]": { - "recorded-date": "22-01-2025, 10:56:18", + "recorded-date": "15-10-2025, 13:12:01", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_NEG]": { - "recorded-date": "22-01-2025, 10:56:23", + "recorded-date": "15-10-2025, 13:12:06", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard]": { - "recorded-date": "22-01-2025, 10:56:34", + "recorded-date": "15-10-2025, 13:12:20", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_int_EXC]": { - "recorded-date": "22-01-2025, 10:56:33", + "recorded-date": "15-10-2025, 13:12:20", "recorded-content": { "content_wildcard_int_EXC": { "exception_message": { @@ -793,7 +793,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_list_EXC]": { - "recorded-date": "22-01-2025, 10:56:36", + "recorded-date": "15-10-2025, 13:12:23", "recorded-content": { "content_wildcard_list_EXC": { "exception_message": { @@ -812,7 +812,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_type_EXC]": { - "recorded-date": "22-01-2025, 10:56:16", + "recorded-date": "15-10-2025, 13:11:58", "recorded-content": { "content_anything_wildcard_list_type_EXC": { "exception_message": { @@ -831,7 +831,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_type_EXC]": { - "recorded-date": "22-01-2025, 10:56:32", + "recorded-date": "15-10-2025, 13:12:18", "recorded-content": { "content_anything_wildcard_type_EXC": { "exception_message": { @@ -850,7 +850,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_type_EXC]": { - "recorded-date": "22-01-2025, 10:56:14", + "recorded-date": "15-10-2025, 13:11:56", "recorded-content": { "content_anything_suffix_list_type_EXC": { "exception_message": { @@ -869,15 +869,15 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_NEG]": { - "recorded-date": "22-01-2025, 10:56:17", + "recorded-date": "15-10-2025, 13:11:59", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_int_EXC]": { - "recorded-date": "22-01-2025, 10:56:22", + "recorded-date": "15-10-2025, 13:12:05", "recorded-content": { "content_anything_suffix_int_EXC": { "exception_message": { @@ -896,15 +896,15 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:11", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_NEG]": { - "recorded-date": "22-01-2025, 10:56:31", + "recorded-date": "15-10-2025, 13:12:17", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_type_EXC]": { - "recorded-date": "22-01-2025, 10:56:33", + "recorded-date": "15-10-2025, 13:12:19", "recorded-content": { "content_anything_prefix_list_type_EXC": { "exception_message": { @@ -923,7 +923,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_EXC]": { - "recorded-date": "22-01-2025, 10:56:36", + "recorded-date": "15-10-2025, 13:12:22", "recorded-content": { "content_anything_prefix_int_EXC": { "exception_message": { @@ -942,7 +942,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_list_EXC]": { - "recorded-date": "22-01-2025, 10:56:19", + "recorded-date": "15-10-2025, 13:12:02", "recorded-content": { "content_prefix_list_EXC": { "exception_message": { @@ -961,7 +961,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_int_EXC]": { - "recorded-date": "22-01-2025, 10:56:26", + "recorded-date": "15-10-2025, 13:12:09", "recorded-content": { "content_prefix_int_EXC": { "exception_message": { @@ -980,7 +980,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_int_EXC]": { - "recorded-date": "22-01-2025, 10:56:30", + "recorded-date": "15-10-2025, 13:12:16", "recorded-content": { "content_suffix_int_EXC": { "exception_message": { @@ -999,7 +999,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_list_EXC]": { - "recorded-date": "22-01-2025, 10:56:34", + "recorded-date": "15-10-2025, 13:12:21", "recorded-content": { "content_suffix_list_EXC": { "exception_message": { @@ -1018,7 +1018,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_ignorecase_EXC]": { - "recorded-date": "22-01-2025, 10:56:30", + "recorded-date": "15-10-2025, 13:12:15", "recorded-content": { "content_anything_prefix_ignorecase_EXC": { "exception_message": { @@ -1037,7 +1037,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_ignorecase_EXC]": { - "recorded-date": "22-01-2025, 10:56:32", + "recorded-date": "15-10-2025, 13:12:18", "recorded-content": { "content_anything_suffix_ignorecase_EXC": { "exception_message": { @@ -1056,7 +1056,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_mask_EXC]": { - "recorded-date": "22-01-2025, 10:56:15", + "recorded-date": "15-10-2025, 13:11:57", "recorded-content": { "content_ip_address_bad_mask_EXC": { "exception_message": { @@ -1075,15 +1075,15 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_NEG]": { - "recorded-date": "22-01-2025, 10:56:20", + "recorded-date": "15-10-2025, 13:12:03", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6]": { - "recorded-date": "22-01-2025, 10:56:21", + "recorded-date": "15-10-2025, 13:12:04", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_ip_EXC]": { - "recorded-date": "22-01-2025, 10:56:28", + "recorded-date": "15-10-2025, 13:12:12", "recorded-content": { "content_ip_address_bad_ip_EXC": { "exception_message": { @@ -1102,7 +1102,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_type_EXC]": { - "recorded-date": "22-01-2025, 10:56:35", + "recorded-date": "15-10-2025, 13:12:22", "recorded-content": { "content_ip_address_type_EXC": { "exception_message": { @@ -1121,7 +1121,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_bad_ip_EXC]": { - "recorded-date": "22-01-2025, 10:56:37", + "recorded-date": "15-10-2025, 13:12:23", "recorded-content": { "content_ip_address_v6_bad_ip_EXC": { "exception_message": { @@ -1144,7 +1144,7 @@ "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_list_empty_NEG]": { - "recorded-date": "22-01-2025, 10:56:16", + "recorded-date": "15-10-2025, 13:11:58", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_plain_string_payload": { @@ -1163,15 +1163,15 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but_NEG]": { - "recorded-date": "22-01-2025, 10:56:19", + "recorded-date": "15-10-2025, 13:12:02", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but]": { - "recorded-date": "22-01-2025, 10:56:33", + "recorded-date": "15-10-2025, 13:12:19", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-string_NEG]": { - "recorded-date": "22-01-2025, 10:56:25", + "recorded-date": "15-10-2025, 13:12:08", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_event_payload": { @@ -1190,11 +1190,11 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-null_NEG]": { - "recorded-date": "22-01-2025, 10:56:32", + "recorded-date": "15-10-2025, 13:12:18", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-int-float]": { - "recorded-date": "22-01-2025, 10:56:25", + "recorded-date": "15-10-2025, 13:12:09", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_array_event_payload": { @@ -1213,7 +1213,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_empty_EXC]": { - "recorded-date": "22-01-2025, 10:56:14", + "recorded-date": "15-10-2025, 13:11:55", "recorded-content": { "content_anything_prefix_empty_EXC": { "exception_message": { @@ -1232,35 +1232,35 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty_NEG]": { - "recorded-date": "22-01-2025, 10:56:14", + "recorded-date": "15-10-2025, 13:11:55", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_empty]": { - "recorded-date": "22-01-2025, 10:56:15", + "recorded-date": "15-10-2025, 13:11:56", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_empty_NEG]": { - "recorded-date": "22-01-2025, 10:56:16", + "recorded-date": "15-10-2025, 13:11:58", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_empty]": { - "recorded-date": "22-01-2025, 10:56:22", + "recorded-date": "15-10-2025, 13:12:05", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty]": { - "recorded-date": "22-01-2025, 10:56:22", + "recorded-date": "15-10-2025, 13:12:05", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_zero]": { - "recorded-date": "22-01-2025, 10:56:27", + "recorded-date": "15-10-2025, 13:12:11", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_empty]": { - "recorded-date": "22-01-2025, 10:56:29", + "recorded-date": "15-10-2025, 13:12:14", "recorded-content": {} }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_empty_EXC]": { - "recorded-date": "22-01-2025, 10:56:37", + "recorded-date": "15-10-2025, 13:12:24", "recorded-content": { "content_anything_suffix_empty_EXC": { "exception_message": { @@ -1279,7 +1279,7 @@ } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_number_EXC]": { - "recorded-date": "22-01-2025, 10:56:28", + "recorded-date": "15-10-2025, 13:12:13", "recorded-content": { "content_numeric_number_EXC": { "exception_message": { @@ -1315,5 +1315,154 @@ } } } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_null]": { + "recorded-date": "15-10-2025, 13:11:57", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_null]": { + "recorded-date": "15-10-2025, 13:11:58", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_missing_NEG]": { + "recorded-date": "15-10-2025, 13:11:58", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_null]": { + "recorded-date": "15-10-2025, 13:11:59", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_null]": { + "recorded-date": "15-10-2025, 13:12:01", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:06", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null_type_EXC]": { + "recorded-date": "15-10-2025, 13:12:08", + "recorded-content": { + "content_anything_but_string_null_type_EXC": { + "exception_message": { + "Error": { + "Code": "InvalidEventPatternException", + "Message": "Event pattern is not valid. Reason: Value of anything-but must be an array or single string/number value.", + "MessageRaw": "Event pattern is not valid. Reason: Value of anything-but must be an array or single string/number value.\n at [Source: (String)\"{\"detail\": {\"state\": [{\"anything-but\": null}]}}\"; line: 1, column: 44]" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "exception_type": "" + } + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:10", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:11", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_null]": { + "recorded-date": "15-10-2025, 13:12:11", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:13", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_null_type_EXC]": { + "recorded-date": "15-10-2025, 13:12:14", + "recorded-content": { + "content_anything_but_string_list_null_type_EXC": { + "exception_message": { + "Error": { + "Code": "InvalidEventPatternException", + "Message": "Event pattern is not valid. Reason: Inside anything but list, start|null|boolean is not supported.", + "MessageRaw": "Event pattern is not valid. Reason: Inside anything but list, start|null|boolean is not supported.\n at [Source: (String)\"{\"detail\": {\"state\": [{\"anything-but\": [\"stopped\", null]}]}}\"; line: 1, column: 56]\n at [Source: (String)\"{\"detail\": {\"state\": [{\"anything-but\": [\"stopped\", null]}]}}\"; line: 1, column: 56]" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "exception_type": "" + } + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_null]": { + "recorded-date": "15-10-2025, 13:12:14", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_null]": { + "recorded-date": "15-10-2025, 13:12:15", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_null_type_EXC]": { + "recorded-date": "15-10-2025, 13:12:17", + "recorded-content": { + "content_anything_suffix_list_null_type_EXC": { + "exception_message": { + "Error": { + "Code": "InvalidEventPatternException", + "Message": "Event pattern is not valid. Reason: prefix/suffix match pattern must be a string", + "MessageRaw": "Event pattern is not valid. Reason: prefix/suffix match pattern must be a string\n at [Source: (String)\"{\"detail\": {\"FileName\": [{\"anything-but\": {\"suffix\": [null, \".txt\"]}}]}}\"; line: 1, column: 59]\n at [Source: (String)\"{\"detail\": {\"FileName\": [{\"anything-but\": {\"suffix\": [null, \".txt\"]}}]}}\"; line: 1, column: 59]" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "exception_type": "" + } + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:18", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:21", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_null]": { + "recorded-date": "15-10-2025, 13:12:22", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:25", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:25", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_missing_NEG]": { + "recorded-date": "15-10-2025, 13:12:26", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_null]": { + "recorded-date": "15-10-2025, 13:12:26", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_value]": { + "recorded-date": "15-10-2025, 13:12:01", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_int]": { + "recorded-date": "15-10-2025, 13:12:15", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_type_NEG]": { + "recorded-date": "15-10-2025, 13:12:04", + "recorded-content": {} + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_int]": { + "recorded-date": "15-10-2025, 13:12:10", + "recorded-content": {} } } diff --git a/tests/aws/services/events/test_events_patterns.validation.json b/tests/aws/services/events/test_events_patterns.validation.json index e4d69240f1b7a..93c607ce4bfd3 100644 --- a/tests/aws/services/events/test_events_patterns.validation.json +++ b/tests/aws/services/events/test_events_patterns.validation.json @@ -3,394 +3,1402 @@ "last_validated_date": "2024-12-06T09:49:56+00:00" }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_NEG]": { - "last_validated_date": "2025-01-22T10:56:36+00:00" + "last_validated_date": "2025-10-15T13:12:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_EXC]": { - "last_validated_date": "2025-01-22T10:56:21+00:00" + "last_validated_date": "2025-10-15T13:12:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_null_NEG]": { - "last_validated_date": "2025-01-22T10:56:30+00:00" + "last_validated_date": "2025-10-15T13:12:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean]": { - "last_validated_date": "2025-01-22T10:56:38+00:00" + "last_validated_date": "2025-10-15T13:12:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean_NEG]": { - "last_validated_date": "2025-01-22T10:56:20+00:00" + "last_validated_date": "2025-10-15T13:12:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_many_rules]": { - "last_validated_date": "2025-01-22T10:56:22+00:00" + "last_validated_date": "2025-10-15T13:12:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match]": { - "last_validated_date": "2025-01-22T10:56:15+00:00" + "last_validated_date": "2025-10-15T13:11:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match_NEG]": { - "last_validated_date": "2025-01-22T10:56:18+00:00" + "last_validated_date": "2025-10-15T13:12:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.48, + "teardown": 0.0, + "total": 0.48 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or]": { - "last_validated_date": "2025-01-22T10:56:25+00:00" + "last_validated_date": "2025-10-15T13:12:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or_NEG]": { - "last_validated_date": "2025-01-22T10:56:25+00:00" + "last_validated_date": "2025-10-15T13:12:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase]": { - "last_validated_date": "2025-01-22T10:56:22+00:00" + "last_validated_date": "2025-10-15T13:12:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_EXC]": { - "last_validated_date": "2025-01-22T10:56:24+00:00" + "last_validated_date": "2025-10-15T13:12:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_NEG]": { - "last_validated_date": "2025-01-22T10:56:35+00:00" + "last_validated_date": "2025-10-15T13:12:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.53, + "teardown": 0.0, + "total": 0.53 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list]": { - "last_validated_date": "2025-01-22T10:56:29+00:00" + "last_validated_date": "2025-10-15T13:12:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_EXC]": { - "last_validated_date": "2025-01-22T10:56:29+00:00" + "last_validated_date": "2025-10-15T13:12:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.55, + "teardown": 0.0, + "total": 0.55 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_NEG]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_missing_NEG]": { + "last_validated_date": "2025-10-15T13:11:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_null]": { + "last_validated_date": "2025-10-15T13:12:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_null]": { + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number]": { - "last_validated_date": "2025-01-22T10:56:38+00:00" + "last_validated_date": "2025-10-15T13:12:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_NEG]": { - "last_validated_date": "2025-01-22T10:56:23+00:00" + "last_validated_date": "2025-10-15T13:12:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list]": { - "last_validated_date": "2025-01-22T10:56:23+00:00" + "last_validated_date": "2025-10-15T13:12:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list_NEG]": { - "last_validated_date": "2025-01-22T10:56:19+00:00" + "last_validated_date": "2025-10-15T13:12:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.44, + "teardown": 0.0, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_zero]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string]": { - "last_validated_date": "2025-01-22T10:56:18+00:00" + "last_validated_date": "2025-10-15T13:12:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_NEG]": { - "last_validated_date": "2025-01-22T10:56:20+00:00" + "last_validated_date": "2025-10-15T13:12:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list]": { - "last_validated_date": "2025-01-22T10:56:21+00:00" + "last_validated_date": "2025-10-15T13:12:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_NEG]": { - "last_validated_date": "2025-01-22T10:56:32+00:00" + "last_validated_date": "2025-10-15T13:12:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_null]": { + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_null_type_EXC]": { + "last_validated_date": "2025-10-15T13:12:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null]": { - "last_validated_date": "2025-01-22T10:56:25+00:00" + "last_validated_date": "2025-10-15T13:12:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null_type_EXC]": { + "last_validated_date": "2025-10-15T13:12:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_NEG]": { - "last_validated_date": "2025-01-22T10:56:19+00:00" + "last_validated_date": "2025-10-15T13:12:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_empty_EXC]": { - "last_validated_date": "2025-01-22T10:56:14+00:00" + "last_validated_date": "2025-10-15T13:11:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_ignorecase_EXC]": { - "last_validated_date": "2025-01-22T10:56:30+00:00" + "last_validated_date": "2025-10-15T13:12:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_EXC]": { - "last_validated_date": "2025-01-22T10:56:36+00:00" + "last_validated_date": "2025-10-15T13:12:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_value]": { + "last_validated_date": "2025-10-15T13:12:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_NEG]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_int]": { + "last_validated_date": "2025-10-15T13:12:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_null]": { + "last_validated_date": "2025-10-15T13:12:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_type_EXC]": { - "last_validated_date": "2025-01-22T10:56:33+00:00" + "last_validated_date": "2025-10-15T13:12:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_null]": { + "last_validated_date": "2025-10-15T13:12:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix]": { - "last_validated_date": "2025-01-22T10:56:28+00:00" + "last_validated_date": "2025-10-15T13:12:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_NEG]": { - "last_validated_date": "2025-01-22T10:56:26+00:00" + "last_validated_date": "2025-10-15T13:12:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_empty_EXC]": { - "last_validated_date": "2025-01-22T10:56:37+00:00" + "last_validated_date": "2025-10-15T13:12:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_ignorecase_EXC]": { - "last_validated_date": "2025-01-22T10:56:32+00:00" + "last_validated_date": "2025-10-15T13:12:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_int_EXC]": { - "last_validated_date": "2025-01-22T10:56:22+00:00" + "last_validated_date": "2025-10-15T13:12:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_NEG]": { - "last_validated_date": "2025-01-22T10:56:31+00:00" + "last_validated_date": "2025-10-15T13:12:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.46, + "teardown": 0.0, + "total": 0.46 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_null]": { + "last_validated_date": "2025-10-15T13:11:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.55, + "teardown": 0.0, + "total": 0.55 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_null_type_EXC]": { + "last_validated_date": "2025-10-15T13:12:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_type_EXC]": { - "last_validated_date": "2025-01-22T10:56:14+00:00" + "last_validated_date": "2025-10-15T13:11:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_null]": { + "last_validated_date": "2025-10-15T13:12:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard]": { - "last_validated_date": "2025-01-22T10:56:34+00:00" + "last_validated_date": "2025-10-15T13:12:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_NEG]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_empty]": { - "last_validated_date": "2025-01-22T10:56:22+00:00" + "last_validated_date": "2025-10-15T13:12:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_int]": { + "last_validated_date": "2025-10-15T13:12:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list]": { - "last_validated_date": "2025-01-22T10:56:18+00:00" + "last_validated_date": "2025-10-15T13:12:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_NEG]": { - "last_validated_date": "2025-01-22T10:56:23+00:00" + "last_validated_date": "2025-10-15T13:12:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_null]": { + "last_validated_date": "2025-10-15T13:12:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_type_EXC]": { - "last_validated_date": "2025-01-22T10:56:16+00:00" + "last_validated_date": "2025-10-15T13:11:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_missing_NEG]": { + "last_validated_date": "2025-10-15T13:12:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_null]": { + "last_validated_date": "2025-10-15T13:11:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.44, + "teardown": 0.0, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_type_EXC]": { - "last_validated_date": "2025-01-22T10:56:32+00:00" + "last_validated_date": "2025-10-15T13:12:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists]": { - "last_validated_date": "2025-01-22T10:56:24+00:00" + "last_validated_date": "2025-10-15T13:12:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_NEG]": { - "last_validated_date": "2025-01-22T10:56:37+00:00" + "last_validated_date": "2025-10-15T13:12:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false]": { - "last_validated_date": "2025-01-22T10:56:34+00:00" + "last_validated_date": "2025-10-15T13:12:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.01, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false_NEG]": { - "last_validated_date": "2025-01-22T10:56:37+00:00" + "last_validated_date": "2025-10-15T13:12:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase]": { - "last_validated_date": "2025-01-22T10:56:37+00:00" + "last_validated_date": "2025-10-15T13:12:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.01, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_EXC]": { - "last_validated_date": "2025-01-22T10:56:23+00:00" + "last_validated_date": "2025-10-15T13:12:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_NEG]": { - "last_validated_date": "2025-01-22T10:56:33+00:00" + "last_validated_date": "2025-10-15T13:12:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty]": { - "last_validated_date": "2025-01-22T10:56:22+00:00" + "last_validated_date": "2025-10-15T13:12:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty_NEG]": { - "last_validated_date": "2025-01-22T10:56:14+00:00" + "last_validated_date": "2025-10-15T13:11:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.44, + "teardown": 0.0, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_list_EXC]": { - "last_validated_date": "2025-01-22T10:56:31+00:00" + "last_validated_date": "2025-10-15T13:12:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address]": { - "last_validated_date": "2025-01-22T10:56:19+00:00" + "last_validated_date": "2025-10-15T13:12:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_EXC]": { - "last_validated_date": "2025-01-22T10:56:34+00:00" + "last_validated_date": "2025-10-15T13:12:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_NEG]": { - "last_validated_date": "2025-01-22T10:56:24+00:00" + "last_validated_date": "2025-10-15T13:12:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_ip_EXC]": { - "last_validated_date": "2025-01-22T10:56:28+00:00" + "last_validated_date": "2025-10-15T13:12:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_mask_EXC]": { - "last_validated_date": "2025-01-22T10:56:15+00:00" + "last_validated_date": "2025-10-15T13:11:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_type_NEG]": { + "last_validated_date": "2025-10-15T13:12:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_type_EXC]": { - "last_validated_date": "2025-01-22T10:56:35+00:00" + "last_validated_date": "2025-10-15T13:12:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6]": { - "last_validated_date": "2025-01-22T10:56:21+00:00" + "last_validated_date": "2025-10-15T13:12:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_NEG]": { - "last_validated_date": "2025-01-22T10:56:20+00:00" + "last_validated_date": "2025-10-15T13:12:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.44, + "teardown": 0.0, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_bad_ip_EXC]": { - "last_validated_date": "2025-01-22T10:56:37+00:00" + "last_validated_date": "2025-10-15T13:12:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_EXC]": { - "last_validated_date": "2025-01-22T10:56:25+00:00" + "last_validated_date": "2025-10-15T13:12:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and]": { - "last_validated_date": "2025-01-22T10:56:38+00:00" + "last_validated_date": "2025-10-15T13:12:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and_NEG]": { - "last_validated_date": "2025-01-22T10:56:26+00:00" + "last_validated_date": "2025-10-15T13:12:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_number_EXC]": { - "last_validated_date": "2025-01-22T10:56:28+00:00" + "last_validated_date": "2025-10-15T13:12:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_operatorcasing_EXC]": { - "last_validated_date": "2025-01-22T10:56:18+00:00" + "last_validated_date": "2025-10-15T13:12:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_syntax_EXC]": { - "last_validated_date": "2025-01-22T10:56:38+00:00" + "last_validated_date": "2025-10-15T13:12:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix]": { - "last_validated_date": "2025-01-22T10:56:33+00:00" + "last_validated_date": "2025-10-15T13:12:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.48, + "teardown": 0.0, + "total": 0.48 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_NEG]": { - "last_validated_date": "2025-01-22T10:56:29+00:00" + "last_validated_date": "2025-10-15T13:12:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_empty]": { - "last_validated_date": "2025-01-22T10:56:15+00:00" + "last_validated_date": "2025-10-15T13:11:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_ignorecase]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_int_EXC]": { - "last_validated_date": "2025-01-22T10:56:26+00:00" + "last_validated_date": "2025-10-15T13:12:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_list_EXC]": { - "last_validated_date": "2025-01-22T10:56:19+00:00" + "last_validated_date": "2025-10-15T13:12:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix]": { - "last_validated_date": "2025-01-22T10:56:35+00:00" + "last_validated_date": "2025-10-15T13:12:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_NEG]": { - "last_validated_date": "2025-01-22T10:56:14+00:00" + "last_validated_date": "2025-10-15T13:11:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_empty]": { - "last_validated_date": "2025-01-22T10:56:29+00:00" + "last_validated_date": "2025-10-15T13:12:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase_NEG]": { - "last_validated_date": "2025-01-22T10:56:30+00:00" + "last_validated_date": "2025-10-15T13:12:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_int_EXC]": { - "last_validated_date": "2025-01-22T10:56:30+00:00" + "last_validated_date": "2025-10-15T13:12:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_list_EXC]": { - "last_validated_date": "2025-01-22T10:56:34+00:00" + "last_validated_date": "2025-10-15T13:12:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_complex_EXC]": { - "last_validated_date": "2025-01-22T10:56:31+00:00" + "last_validated_date": "2025-10-15T13:12:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_empty_NEG]": { - "last_validated_date": "2025-01-22T10:56:16+00:00" + "last_validated_date": "2025-10-15T13:11:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_int_EXC]": { - "last_validated_date": "2025-01-22T10:56:33+00:00" + "last_validated_date": "2025-10-15T13:12:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.51, + "teardown": 0.0, + "total": 0.51 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_list_EXC]": { - "last_validated_date": "2025-01-22T10:56:36+00:00" + "last_validated_date": "2025-10-15T13:12:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.42, + "teardown": 0.0, + "total": 0.42 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating_NEG]": { - "last_validated_date": "2025-01-22T10:56:23+00:00" + "last_validated_date": "2025-10-15T13:12:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.44, + "teardown": 0.0, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating]": { - "last_validated_date": "2025-01-22T10:56:14+00:00" + "last_validated_date": "2025-10-15T13:11:55+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.46, + "teardown": 0.0, + "total": 0.94 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_NEG]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_star_EXC]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:12:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.41, + "teardown": 0.0, + "total": 0.41 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_simplified]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event]": { - "last_validated_date": "2025-01-22T10:56:26+00:00" + "last_validated_date": "2025-10-15T13:12:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event_NEG]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern]": { - "last_validated_date": "2025-01-22T10:56:21+00:00" + "last_validated_date": "2025-10-15T13:12:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern_NEG]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dynamodb]": { - "last_validated_date": "2025-01-22T10:56:22+00:00" + "last_validated_date": "2025-10-15T13:12:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[empty_prefix]": { "last_validated_date": "2025-01-21T13:16:50+00:00" }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb]": { - "last_validated_date": "2025-01-22T10:56:20+00:00" + "last_validated_date": "2025-10-15T13:12:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb_NEG]": { - "last_validated_date": "2025-01-22T10:56:20+00:00" + "last_validated_date": "2025-10-15T13:12:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_list_empty_NEG]": { - "last_validated_date": "2025-01-22T10:56:16+00:00" + "last_validated_date": "2025-10-15T13:11:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[int_nolist_EXC]": { - "last_validated_date": "2025-01-22T10:56:15+00:00" + "last_validated_date": "2025-10-15T13:11:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[key_case_sensitive_NEG]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[list_within_dict]": { - "last_validated_date": "2025-01-22T10:56:14+00:00" + "last_validated_date": "2025-10-15T13:11:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[minimal]": { - "last_validated_date": "2025-01-22T10:56:33+00:00" + "last_validated_date": "2025-10-15T13:12:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[nested_json_NEG]": { - "last_validated_date": "2025-01-22T10:56:21+00:00" + "last_validated_date": "2025-10-15T13:12:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value]": { - "last_validated_date": "2025-01-22T10:56:23+00:00" + "last_validated_date": "2025-10-15T13:12:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.0, + "total": 0.43 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value_NEG]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[number_comparison_float]": { - "last_validated_date": "2025-01-22T10:56:35+00:00" + "last_validated_date": "2025-10-15T13:12:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.44, + "teardown": 0.0, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-int-float]": { - "last_validated_date": "2025-01-22T10:56:25+00:00" + "last_validated_date": "2025-10-15T13:12:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-null_NEG]": { - "last_validated_date": "2025-01-22T10:56:32+00:00" + "last_validated_date": "2025-10-15T13:12:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-string_NEG]": { - "last_validated_date": "2025-01-22T10:56:25+00:00" + "last_validated_date": "2025-10-15T13:12:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.54, + "teardown": 0.0, + "total": 0.54 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_case_sensitive_EXC]": { - "last_validated_date": "2025-01-22T10:56:24+00:00" + "last_validated_date": "2025-10-15T13:12:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_multiple_list]": { - "last_validated_date": "2025-01-22T10:56:21+00:00" + "last_validated_date": "2025-10-15T13:12:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-anything-but]": { - "last_validated_date": "2025-01-22T10:56:38+00:00" + "last_validated_date": "2025-10-15T13:12:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists-parent]": { - "last_validated_date": "2025-01-22T10:56:21+00:00" + "last_validated_date": "2025-10-15T13:12:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists]": { - "last_validated_date": "2025-01-22T10:56:17+00:00" + "last_validated_date": "2025-10-15T13:11:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but]": { - "last_validated_date": "2025-01-22T10:56:33+00:00" + "last_validated_date": "2025-10-15T13:12:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but_NEG]": { - "last_validated_date": "2025-01-22T10:56:19+00:00" + "last_validated_date": "2025-10-15T13:12:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[prefix]": { - "last_validated_date": "2025-01-22T10:56:28+00:00" + "last_validated_date": "2025-10-15T13:12:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.44, + "teardown": 0.0, + "total": 0.44 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[sample1]": { - "last_validated_date": "2025-01-22T10:56:27+00:00" + "last_validated_date": "2025-10-15T13:12:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.24, + "teardown": 0.0, + "total": 0.24 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string]": { - "last_validated_date": "2025-01-22T10:56:29+00:00" + "last_validated_date": "2025-10-15T13:12:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_empty]": { - "last_validated_date": "2025-01-22T10:56:22+00:00" + "last_validated_date": "2025-10-15T13:12:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_nolist_EXC]": { - "last_validated_date": "2025-01-22T10:56:20+00:00" + "last_validated_date": "2025-10-15T13:12:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_source": { "last_validated_date": "2024-07-11T13:55:39+00:00" diff --git a/tests/aws/services/events/test_events_schedule.py b/tests/aws/services/events/test_events_schedule.py index aef36fadb04f2..e0aa3fdd78bd4 100644 --- a/tests/aws/services/events/test_events_schedule.py +++ b/tests/aws/services/events/test_events_schedule.py @@ -1,6 +1,6 @@ import json import time -from datetime import timedelta, timezone +from datetime import UTC, timedelta import pytest from botocore.exceptions import ClientError @@ -148,7 +148,7 @@ def tests_schedule_rate_target_sqs( def tests_schedule_rate_custom_input_target_sqs( self, sqs_as_events_target, events_put_rule, aws_client, snapshot ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() bus_name = "default" rule_name = f"test-rule-{short_uid()}" @@ -343,7 +343,7 @@ def test_schedule_cron_target_sqs( aws_client, snapshot, ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() schedule_cron, target_datetime = get_cron_expression( 1 @@ -377,7 +377,7 @@ def test_schedule_cron_target_sqs( # check if message was delivered at the correct time time_message = events_time_string_to_timestamp( json.loads(messages[0]["Body"])["time"] - ).replace(tzinfo=timezone.utc) + ).replace(tzinfo=UTC) # TODO fix JobScheduler to execute on exact time # round datetime to nearest minute @@ -391,7 +391,7 @@ def test_schedule_cron_target_sqs( def tests_scheduled_rule_does_not_trigger_on_put_events( self, sqs_as_events_target, events_put_rule, aws_client ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() bus_name = "default" rule_name = f"test-rule-{short_uid()}" @@ -416,9 +416,11 @@ def tests_scheduled_rule_does_not_trigger_on_put_events( "DetailType": "core.update-account-command", "Detail": json.dumps({"command": ["update-account"]}), } - aws_client.events.put_events(Entries=[test_event]) + for _ in range(3): + aws_client.events.put_events(Entries=[test_event]) messages = aws_client.sqs.receive_message( QueueUrl=queue_url, WaitTimeSeconds=10 if is_aws_cloud() else 3 ) - assert not messages.get("Messages") + # we switch the assertion to take into account that the rule might trigger instantly + assert len(messages.get("Messages") or []) < 2 diff --git a/tests/aws/services/events/test_events_schedule.validation.json b/tests/aws/services/events/test_events_schedule.validation.json index 2dce0326ca018..22391bc81e5ea 100644 --- a/tests/aws/services/events/test_events_schedule.validation.json +++ b/tests/aws/services/events/test_events_schedule.validation.json @@ -69,12 +69,12 @@ "last_validated_date": "2025-01-22T13:22:43+00:00" }, "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_scheduled_rule_does_not_trigger_on_put_events": { - "last_validated_date": "2025-06-04T19:23:59+00:00", + "last_validated_date": "2026-02-27T22:37:04+00:00", "durations_in_seconds": { - "setup": 0.56, - "call": 11.78, - "teardown": 1.18, - "total": 13.52 + "setup": 0.01, + "call": 11.95, + "teardown": 0.9, + "total": 12.86 } }, "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[ rate(10 minutes)]": { diff --git a/tests/aws/services/events/test_events_targets.py b/tests/aws/services/events/test_events_targets.py index a4c641466f4d2..61329317d3e9a 100644 --- a/tests/aws/services/events/test_events_targets.py +++ b/tests/aws/services/events/test_events_targets.py @@ -4,6 +4,7 @@ import base64 import json +import threading import time import aws_cdk as cdk @@ -41,6 +42,7 @@ class TestEventsTargetApiDestination: # TODO validate against AWS & use common fixtures + @markers.requires_in_process # uses pytest httpserver @markers.aws.only_localstack @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") @pytest.mark.parametrize("auth", API_DESTINATION_AUTHS) @@ -157,11 +159,7 @@ def _handler(_request: Request): ] aws_client.events.put_events(Entries=entries) - # clean up - aws_client.events.delete_connection(Name=connection_name) - aws_client.events.delete_api_destination(Name=dest_name) - clean_up(rule_name=rule_name, target_ids=target_id) - + # Wait for event delivery before cleanup to_recv = 2 if auth["type"] == "OAUTH_CLIENT_CREDENTIALS" else 1 assert poll_condition(lambda: len(httpserver.log) >= to_recv, timeout=5) @@ -205,6 +203,113 @@ def _handler(_request: Request): assert oauth_request.headers["oauthheader"] == "value2" assert oauth_request.args["oauthquery"] == "value3" + # Clean up after verification + aws_client.events.delete_connection(Name=connection_name) + aws_client.events.delete_api_destination(Name=dest_name) + clean_up(rule_name=rule_name, target_ids=target_id) + + @markers.requires_in_process # uses pytest httpserver + @markers.aws.only_localstack + @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") + def test_put_events_returns_immediately_with_slow_api_destination( + self, httpserver: HTTPServer, aws_client, clean_up + ): + """ + Test that put_events returns immediately even when the target API destination + is blocked and cannot respond. This validates the fix for GitHub issue #12107. + + The test verifies that: + 1. put_events returns successfully while the API destination handler is blocked + 2. The event is eventually delivered to the destination asynchronously + """ + # Create a blocking event to control when the handler can respond + blocking_event = threading.Event() + + # Create an endpoint that blocks until the event is set + def _blocking_handler(_request: Request): + # Block until event is set (with timeout for test cleanup) + if not blocking_event.wait(timeout=20.0): + raise TimeoutError("Test timed out waiting for blocking event") + return Response(json.dumps({"status": "received"}), mimetype="application/json") + + httpserver.expect_request("").respond_with_handler(_blocking_handler) + http_endpoint = httpserver.url_for("/") + + # Create connection with BASIC auth (simplest for this test) + connection_name = f"test-conn-{short_uid()}" + connection_arn = aws_client.events.create_connection( + Name=connection_name, + AuthorizationType="BASIC", + AuthParameters={ + "BasicAuthParameters": { + "Username": "user", + "Password": "pass", + }, + }, + )["ConnectionArn"] + + # Create API destination + dest_name = f"test-dest-{short_uid()}" + dest_arn = aws_client.events.create_api_destination( + Name=dest_name, + ConnectionArn=connection_arn, + InvocationEndpoint=http_endpoint, + HttpMethod="POST", + )["ApiDestinationArn"] + + # Create rule and target + rule_name = f"test-rule-{short_uid()}" + target_id = f"target-{short_uid()}" + pattern = json.dumps({"source": ["test.async"], "detail-type": ["async.test"]}) + + aws_client.events.put_rule(Name=rule_name, EventPattern=pattern) + aws_client.events.put_targets( + Rule=rule_name, + Targets=[ + { + "Id": target_id, + "Arn": dest_arn, + "Input": '{"test": "async_behavior"}', + } + ], + ) + + # Send event - this should return immediately even though handler is blocked + response = aws_client.events.put_events( + Entries=[ + { + "Source": "test.async", + "DetailType": "async.test", + "Detail": '{"message": "testing async behavior"}', + } + ] + ) + + # Verify the event was accepted (EventId returned) even while handler is blocked + # This proves put_events is non-blocking + assert response["FailedEntryCount"] == 0 + assert len(response["Entries"]) == 1 + assert "EventId" in response["Entries"][0] + + # Now unblock the handler so the event can be delivered + blocking_event.set() + + # Verify the event is eventually delivered to the destination + # (this proves background processing is working) + assert poll_condition(lambda: len(httpserver.log) >= 1, timeout=10), ( + "Event was not delivered to the API destination within 10 seconds" + ) + + # Verify the correct data was sent to the endpoint + event_request, _ = httpserver.log[0] + event_data = event_request.get_json(force=True) + assert event_data["test"] == "async_behavior" + + # Clean up + aws_client.events.delete_connection(Name=connection_name) + aws_client.events.delete_api_destination(Name=dest_name) + clean_up(rule_name=rule_name, target_ids=target_id) + class TestEventsTargetApiGateway: @markers.aws.validated @@ -639,7 +744,7 @@ def test_put_events_with_target_events( EventPattern=json.dumps(TEST_EVENT_PATTERN), ) - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() target_id = f"target-{short_uid()}" aws_client.events.put_targets( Rule=rule_name_target_to_sqs, diff --git a/tests/aws/services/firehose/conftest.py b/tests/aws/services/firehose/conftest.py index a0b903ccdbf4b..de0933521f838 100644 --- a/tests/aws/services/firehose/conftest.py +++ b/tests/aws/services/firehose/conftest.py @@ -22,7 +22,7 @@ def _get_data(): keys = [obj.get("Key") for obj in response.get("Contents")] - bucket_data = dict() + bucket_data = {} for key in keys: response = s3.get_object(Bucket=bucket_name, Key=key) data = response["Body"].read().decode("utf-8") diff --git a/tests/aws/services/firehose/helper_functions.py b/tests/aws/services/firehose/helper_functions.py index 6993e264734d4..84ffa79958db4 100644 --- a/tests/aws/services/firehose/helper_functions.py +++ b/tests/aws/services/firehose/helper_functions.py @@ -1,8 +1,5 @@ -from typing import Union - - def get_firehose_iam_documents( - bucket_arns: Union[list[str], str], stream_arns: Union[list[str], str] + bucket_arns: list[str] | str, stream_arns: list[str] | str ) -> tuple[dict, dict]: """ Generate the required IAM role and policy documents for Firehose. diff --git a/tests/aws/services/firehose/test_firehose.py b/tests/aws/services/firehose/test_firehose.py index 33497aa875fb5..5ed44c9c8a719 100644 --- a/tests/aws/services/firehose/test_firehose.py +++ b/tests/aws/services/firehose/test_firehose.py @@ -10,6 +10,7 @@ from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.aws import arns +from localstack.utils.aws.arns import iam_role_arn, s3_bucket_arn from localstack.utils.strings import short_uid, to_bytes, to_str from localstack.utils.sync import poll_condition, retry from tests.aws.services.firehose.helper_functions import get_firehose_iam_documents @@ -38,6 +39,7 @@ def handler(event, context): @pytest.mark.parametrize("lambda_processor_enabled", [True, False]) @markers.aws.unknown +@markers.requires_in_process # uses pytest httpserver def test_kinesis_firehose_http( aws_client, lambda_processor_enabled: bool, @@ -518,7 +520,7 @@ def test_kinesis_firehose_kinesis_as_source_multiple_delivery_streams( ) # poll file from s3 buckets - s3_data = dict() + s3_data = {} for bucket_name in [bucket_a_name, bucket_b_name]: s3_data_bucket = read_s3_data(bucket_name, timeout=300) assert len(s3_data_bucket.keys()) == 1 @@ -588,3 +590,78 @@ def assert_s3_contents(): retry_options["sleep_before"] = 10 retry(assert_s3_contents, **retry_options) + + +class TestFirehoseResourceTagging: + @pytest.fixture + def delivery_stream( + self, + aws_client, + s3_bucket, + create_iam_role_and_attach_policy, + account_id, + region_name, + firehose_create_delivery_stream, + ): + """Factory fixture to create a sample delivery stream with required pre-reqs.""" + + def _delivery_stream(tags: list[dict[str, str]] = None): + document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "firehose.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + role_name = f"role-{short_uid()}" + create_iam_role_and_attach_policy( + RoleName=role_name, + RoleDefinition=document, + PolicyArn="arn:aws:iam::aws:policy/AmazonS3FullAccess", + ) + + stream_name = f"stream-{short_uid()}" + + kwargs = { + "DeliveryStreamName": stream_name, + "DeliveryStreamType": "DirectPut", + "S3DestinationConfiguration": { + "RoleARN": iam_role_arn(role_name, account_id, region_name), + "BucketARN": s3_bucket_arn(s3_bucket, region_name), + }, + } + + if tags is not None: + kwargs["Tags"] = tags + + firehose_create_delivery_stream(**kwargs) + + return stream_name + + yield _delivery_stream + + @markers.aws.validated + def test_resource_tagging(self, aws_client, snapshot, delivery_stream): + stream_name = delivery_stream(tags=[{"Key": "subject", "Value": "taboo"}]) + + response = aws_client.firehose.list_tags_for_delivery_stream(DeliveryStreamName=stream_name) + snapshot.match("list-tags-1", response) + + response = aws_client.firehose.untag_delivery_stream( + DeliveryStreamName=stream_name, TagKeys=["subject", "moment"] + ) + snapshot.match("remove-tags", response) + + response = aws_client.firehose.list_tags_for_delivery_stream(DeliveryStreamName=stream_name) + snapshot.match("list-tags-2", response) + + response = aws_client.firehose.tag_delivery_stream( + DeliveryStreamName=stream_name, Tags=[{"Key": "feeling", "Value": "uncomfortable"}] + ) + snapshot.match("add-tags", response) + + response = aws_client.firehose.list_tags_for_delivery_stream(DeliveryStreamName=stream_name) + snapshot.match("list-tags-3", response) diff --git a/tests/aws/services/firehose/test_firehose.snapshot.json b/tests/aws/services/firehose/test_firehose.snapshot.json index 3ae5658d02344..a906dc1ad5449 100644 --- a/tests/aws/services/firehose/test_firehose.snapshot.json +++ b/tests/aws/services/firehose/test_firehose.snapshot.json @@ -11,5 +11,56 @@ } } } + }, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseResourceTagging::test_resource_tagging": { + "recorded-date": "05-03-2026, 08:34:26", + "recorded-content": { + "list-tags-1": { + "HasMoreTags": false, + "Tags": [ + { + "Key": "subject", + "Value": "taboo" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "remove-tags": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-2": { + "HasMoreTags": false, + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "add-tags": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-3": { + "HasMoreTags": false, + "Tags": [ + { + "Key": "feeling", + "Value": "uncomfortable" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/firehose/test_firehose.validation.json b/tests/aws/services/firehose/test_firehose.validation.json index d864164ae54ad..eb4b52e9fe708 100644 --- a/tests/aws/services/firehose/test_firehose.validation.json +++ b/tests/aws/services/firehose/test_firehose.validation.json @@ -4,5 +4,14 @@ }, "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_multiple_delivery_streams_with_kinesis_as_source": { "last_validated_date": "2024-01-25T10:54:39+00:00" + }, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseResourceTagging::test_resource_tagging": { + "last_validated_date": "2026-03-02T13:20:05+00:00", + "durations_in_seconds": { + "setup": 2.44, + "call": 23.89, + "teardown": 23.4, + "total": 49.73 + } } } diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_basic.yaml b/tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_basic.yaml similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_basic.yaml rename to tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_basic.yaml diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_basic_autogenerated.yaml b/tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_basic_autogenerated.yaml similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_basic_autogenerated.yaml rename to tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_basic_autogenerated.yaml diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_full.yaml b/tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_full.yaml similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_full.yaml rename to tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_full.yaml diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_getatt.yaml b/tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_getatt.yaml similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_getatt.yaml rename to tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_getatt.yaml diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_getatt_exploration.yaml b/tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_getatt_exploration.yaml similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_getatt_exploration.yaml rename to tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_getatt_exploration.yaml diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_update.yaml b/tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_update.yaml similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/templates/user_update.yaml rename to tests/aws/services/iam/resource_providers/aws_iam_user/templates/user_update.yaml diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py b/tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py rename to tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.snapshot.json b/tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.snapshot.json similarity index 81% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.snapshot.json rename to tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.snapshot.json index 5c5363b50efb3..11d09d2ae4fad 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.snapshot.json +++ b/tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_black_box": { + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestBasicCRD::test_black_box": { "recorded-date": "28-06-2023, 22:01:50", "recorded-content": { "stack-outputs": { @@ -8,7 +8,7 @@ "describe-resource": { "User": { "Arn": "arn::iam::111111111111:user/", - "CreateDate": "datetime", + "CreateDate": "", "Path": "/", "UserId": "", "UserName": "" @@ -20,7 +20,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestUpdates::test_update_without_replacement": { + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestUpdates::test_update_without_replacement": { "recorded-date": "28-06-2023, 22:31:43", "recorded-content": { "stack-outputs-before-update": { @@ -29,7 +29,7 @@ "describe-resource-before-update": { "User": { "Arn": "arn::iam::111111111111:user/", - "CreateDate": "datetime", + "CreateDate": "", "Path": "/", "UserId": "", "UserName": "" @@ -42,7 +42,7 @@ "describe-resource-after-update": { "User": { "Arn": "arn::iam::111111111111:user/-updated", - "CreateDate": "datetime", + "CreateDate": "", "Path": "/", "UserId": "", "UserName": "-updated" @@ -57,7 +57,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_autogenerated_values": { + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestBasicCRD::test_autogenerated_values": { "recorded-date": "28-06-2023, 22:54:57", "recorded-content": { "stack_outputs": { @@ -66,7 +66,7 @@ "autogenerated-get-user": { "User": { "Arn": "arn::iam::111111111111:user/", - "CreateDate": "datetime", + "CreateDate": "", "Path": "/", "UserId": "", "UserName": "" @@ -78,7 +78,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_getatt": { + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestBasicCRD::test_getatt": { "recorded-date": "05-07-2023, 14:15:12", "recorded-content": { "stack-outputs": { @@ -88,7 +88,7 @@ "describe-resource": { "User": { "Arn": "arn::iam::111111111111:user/", - "CreateDate": "datetime", + "CreateDate": "", "Path": "/", "UserId": "", "UserName": "" @@ -103,7 +103,7 @@ "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, - "LastUpdatedTimestamp": "timestamp", + "LastUpdatedTimestamp": "", "LogicalResourceId": "MyResource", "Metadata": {}, "PhysicalResourceId": "", diff --git a/tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.validation.json b/tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.validation.json new file mode 100644 index 0000000000000..102dee48e5e6a --- /dev/null +++ b/tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.validation.json @@ -0,0 +1,14 @@ +{ + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestBasicCRD::test_autogenerated_values": { + "last_validated_date": "2023-06-28T20:54:57+00:00" + }, + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestBasicCRD::test_black_box": { + "last_validated_date": "2023-06-28T20:01:50+00:00" + }, + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestBasicCRD::test_getatt": { + "last_validated_date": "2023-07-05T12:15:12+00:00" + }, + "tests/aws/services/iam/resource_providers/aws_iam_user/test_basic_user.py::TestUpdates::test_update_without_replacement": { + "last_validated_date": "2023-06-28T20:31:43+00:00" + } +} diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py b/tests/aws/services/iam/resource_providers/aws_iam_user/test_exploration.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py rename to tests/aws/services/iam/resource_providers/aws_iam_user/test_exploration.py diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py b/tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py rename to tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.py diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.snapshot.json b/tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.snapshot.json similarity index 81% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.snapshot.json rename to tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.snapshot.json index cc28d998b40b0..c0838bbf3ac2a 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.snapshot.json +++ b/tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": { + "tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": { "recorded-date": "29-06-2023, 13:59:27", "recorded-content": { "stack-outputs": { @@ -8,7 +8,7 @@ "describe-user-resource": { "User": { "Arn": "arn::iam::111111111111:user/", - "CreateDate": "datetime", + "CreateDate": "", "Path": "/", "UserId": "", "UserName": "" @@ -22,14 +22,14 @@ "Groups": [ { "Arn": "arn::iam::111111111111:group/", - "CreateDate": "datetime", + "CreateDate": "", "GroupId": "", "GroupName": "", "Path": "/" }, { "Arn": "arn::iam::111111111111:group/", - "CreateDate": "datetime", + "CreateDate": "", "GroupId": "", "GroupName": "", "Path": "/" diff --git a/tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.validation.json b/tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.validation.json new file mode 100644 index 0000000000000..844553ba7c906 --- /dev/null +++ b/tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.validation.json @@ -0,0 +1,5 @@ +{ + "tests/aws/services/iam/resource_providers/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": { + "last_validated_date": "2023-06-29T11:59:27+00:00" + } +} diff --git a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.py b/tests/aws/services/iam/resource_providers/test_iam.py similarity index 56% rename from tests/aws/services/cloudformation/resource_providers/iam/test_iam.py rename to tests/aws/services/iam/resource_providers/test_iam.py index 9edac28396e11..65778e06d776e 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.py +++ b/tests/aws/services/iam/resource_providers/test_iam.py @@ -13,7 +13,7 @@ def test_delete_role_detaches_role_policy(deploy_cfn_template, aws_client): role_name = f"LsRole{short_uid()}" stack = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_role_policy.yaml" + os.path.dirname(__file__), "../../../templates/iam_role_policy.yaml" ), parameters={"RoleName": role_name}, ) @@ -26,7 +26,7 @@ def test_delete_role_detaches_role_policy(deploy_cfn_template, aws_client): is_update=True, stack_name=stack.stack_name, template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_role_policy.yaml" + os.path.dirname(__file__), "../../../templates/iam_role_policy.yaml" ), parameters={"RoleName": f"role-{short_uid()}"}, ) @@ -46,7 +46,7 @@ def test_policy_attachments(deploy_cfn_template, aws_client): linked_role_id = short_uid() deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_policy_attachments.yaml" + os.path.dirname(__file__), "../../../templates/iam_policy_attachments.yaml" ), template_mapping={ "role_name": role_name, @@ -119,7 +119,7 @@ def test_iam_user_access_key(deploy_cfn_template, snapshot, aws_client): user_name = f"user-{short_uid()}" stack = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_access_key.yaml" + os.path.dirname(__file__), "../../../templates/iam_access_key.yaml" ), parameters={"UserName": user_name}, ) @@ -133,7 +133,7 @@ def test_iam_user_access_key(deploy_cfn_template, snapshot, aws_client): stack_name=stack.stack_name, is_update=True, template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_access_key.yaml" + os.path.dirname(__file__), "../../../templates/iam_access_key.yaml" ), parameters={"UserName": user_name, "Status": "Inactive", "Serial": "2"}, ) @@ -158,7 +158,7 @@ def test_update_inline_policy(deploy_cfn_template, snapshot, aws_client): stack = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_policy_role.yaml" + os.path.dirname(__file__), "../../../templates/iam_policy_role.yaml" ), parameters={ "PolicyName": policy_name, @@ -179,7 +179,7 @@ def test_update_inline_policy(deploy_cfn_template, snapshot, aws_client): deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_policy_role_updated.yaml" + os.path.dirname(__file__), "../../../templates/iam_policy_role_updated.yaml" ), parameters={ "PolicyName": policy_name, @@ -223,9 +223,7 @@ def test_managed_policy_with_empty_resource(deploy_cfn_template, snapshot, aws_c "policyName": f"managed-policy-{short_uid()}", } - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/dynamodb_iam.yaml" - ) + template_path = os.path.join(os.path.dirname(__file__), "../../../templates/dynamodb_iam.yaml") stack = deploy_cfn_template(template_path=template_path, parameters=parameters) @@ -245,7 +243,7 @@ def test_managed_policy_with_empty_resource(deploy_cfn_template, snapshot, aws_c def test_server_certificate(deploy_cfn_template, snapshot, aws_client): stack = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_server_certificate.yaml" + os.path.dirname(__file__), "../../../templates/iam_server_certificate.yaml" ), parameters={"certificateName": f"server-certificate-{short_uid()}"}, ) @@ -275,7 +273,7 @@ def test_server_certificate(deploy_cfn_template, snapshot, aws_client): def test_cfn_handle_iam_role_resource_no_role_name(deploy_cfn_template, aws_client): stack = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/iam_role_defaults.yml" + os.path.dirname(__file__), "../../../templates/iam_role_defaults.yml" ) ) role_path_prefix = "/test-role-prefix/" @@ -296,9 +294,7 @@ def test_updating_stack_with_iam_role(deploy_cfn_template, aws_client): # Create stack and wait for 'CREATE_COMPLETE' status of the stack stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/template7.json" - ), + template_path=os.path.join(os.path.dirname(__file__), "../../../templates/template7.json"), parameters={ "LambdaRoleName": lambda_role_name, "LambdaFunctionName": lambda_function_name, @@ -318,9 +314,7 @@ def test_updating_stack_with_iam_role(deploy_cfn_template, aws_client): # Update stack and wait for 'UPDATE_COMPLETE' status of the stack stack = deploy_cfn_template( is_update=True, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/template7.json" - ), + template_path=os.path.join(os.path.dirname(__file__), "../../../templates/template7.json"), stack_name=stack.stack_name, parameters={ "LambdaRoleName": lambda_role_name_new, @@ -333,3 +327,213 @@ def test_updating_stack_with_iam_role(deploy_cfn_template, aws_client): "Role" ) assert stack.outputs["TestStackRoleName"] == lambda_role_name_new + + +@markers.aws.validated +def test_managedpolicy_with_fn_sub_json_string(deploy_cfn_template, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.iam_api()) + + policy_name = f"test-policy-{short_uid()}" + + template_json = { + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": {"ClusterName": {"Type": "String", "Default": "test-cluster"}}, + "Resources": { + "TestPolicy": { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "ManagedPolicyName": policy_name, + "PolicyDocument": { + "Fn::Sub": json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:GetObject", + "Resource": "arn:${AWS::Partition}:s3:::bucket-${ClusterName}-${AWS::Region}/*", + } + ], + } + ) + }, + }, + } + }, + "Outputs": {"PolicyArn": {"Value": {"Ref": "TestPolicy"}}}, + } + + stack = deploy_cfn_template( + template=json.dumps(template_json), parameters={"ClusterName": "my-cluster"} + ) + + policy_arn = stack.outputs["PolicyArn"] + policy_version = aws_client.iam.get_policy_version( + PolicyArn=policy_arn, + VersionId=aws_client.iam.get_policy(PolicyArn=policy_arn)["Policy"]["DefaultVersionId"], + )["PolicyVersion"] + + snapshot.match("policy_document", policy_version["Document"]) + + +@markers.aws.validated +def test_managedpolicy_with_fn_sub_multiline_yaml(deploy_cfn_template, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.iam_api()) + + policy_name = f"test-policy-{short_uid()}" + + template_yaml = f""" +AWSTemplateFormatVersion: "2010-09-09" +Parameters: + ClusterName: + Type: String + Default: "test-cluster" +Resources: + TestPolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + ManagedPolicyName: {policy_name} + PolicyDocument: !Sub | + {{ + "Version": "2012-10-17", + "Statement": [ + {{ + "Effect": "Allow", + "Action": "eks:DescribeCluster", + "Resource": "arn:${{AWS::Partition}}:eks:${{AWS::Region}}:${{AWS::AccountId}}:cluster/${{ClusterName}}" + }} + ] + }} +Outputs: + PolicyArn: + Value: !Ref TestPolicy +""" + + stack = deploy_cfn_template(template=template_yaml, parameters={"ClusterName": "my-cluster"}) + + policy_arn = stack.outputs["PolicyArn"] + policy_version = aws_client.iam.get_policy_version( + PolicyArn=policy_arn, + VersionId=aws_client.iam.get_policy(PolicyArn=policy_arn)["Policy"]["DefaultVersionId"], + )["PolicyVersion"] + + snapshot.match("policy_document_multiline", policy_version["Document"]) + + +@markers.aws.validated +def test_managedpolicy_with_fn_sub_and_arrays(deploy_cfn_template, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.iam_api()) + + policy_name = f"test-policy-{short_uid()}" + + template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": {"BucketName": {"Type": "String", "Default": "my-bucket"}}, + "Resources": { + "TestPolicy": { + "Type": "AWS::IAM::ManagedPolicy", + "Properties": { + "ManagedPolicyName": policy_name, + "PolicyDocument": { + "Fn::Sub": json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:GetObject", "s3:PutObject"], + "Resource": [ + "arn:${AWS::Partition}:s3:::${BucketName}/*", + "arn:${AWS::Partition}:s3:::${BucketName}-${AWS::Region}/*", + ], + } + ], + } + ) + }, + }, + } + }, + "Outputs": {"PolicyArn": {"Value": {"Ref": "TestPolicy"}}}, + } + + stack = deploy_cfn_template( + template=json.dumps(template), parameters={"BucketName": "test-bucket"} + ) + + policy_arn = stack.outputs["PolicyArn"] + policy_version = aws_client.iam.get_policy_version( + PolicyArn=policy_arn, + VersionId=aws_client.iam.get_policy(PolicyArn=policy_arn)["Policy"]["DefaultVersionId"], + )["PolicyVersion"] + + snapshot.match("policy_with_arrays", policy_version["Document"]) + + +@markers.aws.validated +def test_inline_policy_with_fn_sub_json_string(deploy_cfn_template, snapshot, aws_client): + """ + Test inline IAM Policy (AWS::IAM::Policy) with Fn::Sub returning a JSON string. + + Verifies that inline policies also handle JSON strings from Fn::Sub correctly. + """ + snapshot.add_transformer(snapshot.transform.iam_api()) + + role_name = f"test-role-{short_uid()}" + policy_name = f"test-policy-{short_uid()}" + + template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "TestRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": role_name, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + }, + }, + }, + "TestPolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName": policy_name, + "Roles": [{"Ref": "TestRole"}], + "PolicyDocument": { + "Fn::Sub": json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:GetObject", + "Resource": "arn:${AWS::Partition}:s3:::my-bucket-${AWS::Region}/*", + } + ], + } + ) + }, + }, + }, + }, + } + + deploy_cfn_template(template=json.dumps(template)) + + # Verify the inline policy was attached to the role + policies = aws_client.iam.list_role_policies(RoleName=role_name) + assert policy_name in policies["PolicyNames"] + + # Get the policy document + policy_doc = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=policy_name)[ + "PolicyDocument" + ] + + snapshot.match("inline_policy_document", policy_doc) diff --git a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.snapshot.json b/tests/aws/services/iam/resource_providers/test_iam.snapshot.json similarity index 67% rename from tests/aws/services/cloudformation/resource_providers/iam/test_iam.snapshot.json rename to tests/aws/services/iam/resource_providers/test_iam.snapshot.json index 044291d4e7cf1..2027856d4de0d 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.snapshot.json +++ b/tests/aws/services/iam/resource_providers/test_iam.snapshot.json @@ -1,24 +1,24 @@ { - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_username_defaultname": { - "recorded-date": "31-05-2022, 11:29:45", + "tests/aws/services/iam/resource_providers/test_iam.py::test_iam_username_defaultname": { + "recorded-date": "10-02-2026, 15:51:18", "recorded-content": { "get_iam_user": { "User": { + "Arn": "arn::iam::111111111111:user/", + "CreateDate": "", "Path": "/", - "UserName": "", "UserId": "", - "Arn": "arn::iam::111111111111:user/", - "CreateDate": "datetime" + "UserName": "" }, "ResponseMetadata": { - "HTTPStatusCode": 200, - "HTTPHeaders": {} + "HTTPHeaders": {}, + "HTTPStatusCode": 200 } } } }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_managed_policy_with_empty_resource": { - "recorded-date": "11-07-2023, 18:10:41", + "tests/aws/services/iam/resource_providers/test_iam.py::test_managed_policy_with_empty_resource": { + "recorded-date": "10-02-2026, 15:56:19", "recorded-content": { "outputs": { "PolicyArn": "arn::iam::111111111111:policy/", @@ -30,7 +30,7 @@ "Policy": { "Arn": "arn::iam::111111111111:policy/", "AttachmentCount": 0, - "CreateDate": "datetime", + "CreateDate": "", "DefaultVersionId": "v1", "IsAttachable": true, "Path": "/", @@ -38,7 +38,7 @@ "PolicyId": "", "PolicyName": "", "Tags": [], - "UpdateDate": "datetime" + "UpdateDate": "" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -47,8 +47,8 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_user_access_key": { - "recorded-date": "11-07-2023, 08:23:54", + "tests/aws/services/iam/resource_providers/test_iam.py::test_iam_user_access_key": { + "recorded-date": "10-02-2026, 15:52:59", "recorded-content": { "key_outputs": { "AccessKeyId": "", @@ -56,20 +56,20 @@ }, "access_key": { "AccessKeyId": "", - "CreateDate": "datetime", + "CreateDate": "", "Status": "Active", "UserName": "" }, "access_key_updated": { "AccessKeyId": "", - "CreateDate": "datetime", + "CreateDate": "", "Status": "Inactive", "UserName": "" } } }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_update_inline_policy": { - "recorded-date": "05-04-2023, 11:55:22", + "tests/aws/services/iam/resource_providers/test_iam.py::test_update_inline_policy": { + "recorded-date": "10-02-2026, 15:54:55", "recorded-content": { "user_inline_policy": { "PolicyDocument": { @@ -155,8 +155,8 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_server_certificate": { - "recorded-date": "13-03-2024, 20:20:07", + "tests/aws/services/iam/resource_providers/test_iam.py::test_server_certificate": { + "recorded-date": "10-02-2026, 15:58:53", "recorded-content": { "outputs": { "Arn": "arn::iam::111111111111:server-certificate/", @@ -167,11 +167,11 @@ "CertificateBody": "-----BEGIN CERTIFICATE-----\nMIIEHTCCAwWgAwIBAgIDAJojMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYDVQQGEwJV\nUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEX\nMBUGA1UECgwOTXlPcmdhbml6YXRpb24xHTAbBgNVBAsMFE15T3JnYW5pemF0aW9u\nYWxVbml0MRcwFQYDVQQDDA5NeSBvd24gUm9vdCBDQTAeFw0yMTAzMTExNTAwNDla\nFw0zMDAzMDkxNTAwNDlaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZv\ncm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEXMBUGA1UECgwOTXlPcmdhbml6\nYXRpb24xHTAbBgNVBAsMFE15T3JnYW5pemF0aW9uYWxVbml0MRQwEgYDVQQDDAtl\neGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMnKQhQG\npRuxcO5RF8VMyAmWe4rs4XWeodVQflYtJVY+mCg/JidmgYe1EYXvE2Qqf1Xzi2O2\noEJJSAs/s+Wb91yzunnoHVR/5uTHdjN2e6HRhEmUFlJuconjlmBxVKe1LG4Ra8yr\nJA+E0tS2kzrGCLNcFpghQ982GJjuvRWm9nAAsCJPm7N8a/Gm1opMdUkiH1b/3d47\n0wugisz6fYRHQ61UIYfjNUWlg/tV1thGOScAB2RyusQJdTB422BQAlpD4TTX8uj8\nWd0GhYjpM8DWWpSUOFsoYOHBc3bPr7ctpOoIG8gZcs56zDwZi9CVda4viS/8HPnC\nr8jXaQW1pqwP8ekCAwEAAaOBijCBhzAJBgNVHRMEAjAAMB0GA1UdDgQWBBTaOaPu\nXmtLDTJVv++VYBiQr9gHCTAfBgNVHSMEGDAWgBTaOaPuXmtLDTJVv++VYBiQr9gH\nCTATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCB4AwGAYDVR0RBBEwD4IN\nKi5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAWIZu4sma7MmWTXSMwKSP\nstQDWdIvcwthD8ozHkLsNdl5eKqOEndAc0wb7mSk1z8rRkSsd0D0T2zaKyduCYrs\neBAMhS2+NnHWcXxhn0VOkmXhw5kO8Un14KIptRH0y8FIqHMJ8LrSiK9g9fWCRlI9\ng7eBipu43hzGyMiBP3K0EQ4m49QXlIEwG3OIWak5hdR29h3cD6xXMXaUtlOswsAN\n3PDG/gcjZWZpkwPlaVzwjV8MRsYLmQIYdHPr/qF1FWddYPvK89T0nzpgiuFdBOTY\nW6I1TeTAXFXG2Qf4trXsh5vsFNAisxlRF3mkpixYP5OmVXTOyN7cCOSPOUh6Uctv\neg==\n-----END CERTIFICATE-----", "ServerCertificateMetadata": { "Arn": "arn::iam::111111111111:server-certificate/", - "Expiration": "datetime", + "Expiration": "", "Path": "/", "ServerCertificateId": "", "ServerCertificateName": "", - "UploadDate": "datetime" + "UploadDate": "" }, "Tags": [] }, @@ -192,5 +192,71 @@ } } } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_managedpolicy_with_fn_sub_json_string": { + "recorded-date": "10-02-2026, 16:02:06", + "recorded-content": { + "policy_document": { + "Statement": [ + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": "arn::s3:::bucket-my-cluster-/*" + } + ], + "Version": "2012-10-17" + } + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_managedpolicy_with_fn_sub_multiline_yaml": { + "recorded-date": "10-02-2026, 16:02:38", + "recorded-content": { + "policy_document_multiline": { + "Statement": [ + { + "Action": "eks:DescribeCluster", + "Effect": "Allow", + "Resource": "arn::eks::111111111111:cluster/my-cluster" + } + ], + "Version": "2012-10-17" + } + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_managedpolicy_with_fn_sub_and_arrays": { + "recorded-date": "10-02-2026, 16:03:09", + "recorded-content": { + "policy_with_arrays": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn::s3:::test-bucket/*", + "arn::s3:::test-bucket-/*" + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_inline_policy_with_fn_sub_json_string": { + "recorded-date": "10-02-2026, 16:03:58", + "recorded-content": { + "inline_policy_document": { + "Statement": [ + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": "arn::s3:::my-bucket-/*" + } + ], + "Version": "2012-10-17" + } + } } } diff --git a/tests/aws/services/iam/resource_providers/test_iam.validation.json b/tests/aws/services/iam/resource_providers/test_iam.validation.json new file mode 100644 index 0000000000000..1664ad8e4023c --- /dev/null +++ b/tests/aws/services/iam/resource_providers/test_iam.validation.json @@ -0,0 +1,119 @@ +{ + "tests/aws/services/iam/resource_providers/test_iam.py::test_cfn_handle_iam_role_resource_no_role_name": { + "last_validated_date": "2026-02-10T15:59:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 42.14, + "teardown": 0.29, + "total": 42.43 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_delete_role_detaches_role_policy": { + "last_validated_date": "2026-02-10T15:48:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 81.93, + "teardown": 13.76, + "total": 95.69 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_iam_user_access_key": { + "last_validated_date": "2026-02-10T15:53:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 61.62, + "teardown": 41.76, + "total": 103.38 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_iam_username_defaultname": { + "last_validated_date": "2026-02-10T15:51:57+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 46.37, + "teardown": 39.4, + "total": 86.32 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_inline_policy_with_fn_sub_json_string": { + "last_validated_date": "2026-02-10T16:04:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 43.9, + "teardown": 16.02, + "total": 59.92 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_managed_policy_with_empty_resource": { + "last_validated_date": "2026-02-10T15:56:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 42.49, + "teardown": 17.74, + "total": 60.23 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_managedpolicy_with_fn_sub_and_arrays": { + "last_validated_date": "2026-02-10T16:03:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 26.86, + "teardown": 4.79, + "total": 31.65 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_managedpolicy_with_fn_sub_json_string": { + "last_validated_date": "2026-02-10T16:02:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 27.12, + "teardown": 4.76, + "total": 31.88 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_managedpolicy_with_fn_sub_multiline_yaml": { + "last_validated_date": "2026-02-10T16:02:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 26.71, + "teardown": 4.78, + "total": 31.49 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_policy_attachments": { + "last_validated_date": "2026-02-10T15:50:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 66.78, + "teardown": 41.88, + "total": 108.66 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_server_certificate": { + "last_validated_date": "2026-02-10T15:58:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 136.18, + "teardown": 0.72, + "total": 136.9 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_update_inline_policy": { + "last_validated_date": "2026-02-10T15:55:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 74.43, + "teardown": 41.78, + "total": 116.21 + } + }, + "tests/aws/services/iam/resource_providers/test_iam.py::test_updating_stack_with_iam_role": { + "last_validated_date": "2026-02-10T16:01:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 95.95, + "teardown": 27.18, + "total": 123.13 + } + } +} diff --git a/tests/aws/services/kinesis/test_kinesis.py b/tests/aws/services/kinesis/test_kinesis.py index 041b25bc28bcf..d226df5523543 100644 --- a/tests/aws/services/kinesis/test_kinesis.py +++ b/tests/aws/services/kinesis/test_kinesis.py @@ -24,6 +24,7 @@ from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.aws import resources +from localstack.utils.aws.arns import kinesis_stream_arn from localstack.utils.common import retry, select_attributes, short_uid from localstack.utils.files import load_file from localstack.utils.kinesis import kinesis_connector @@ -133,6 +134,95 @@ def test_create_stream_without_shard_count( snapshot.match("Shards", shards) + @markers.aws.validated + def test_resource_policy_crud( + self, + account_id, + kinesis_create_stream, + wait_for_stream_ready, + aws_client, + snapshot, + ): + """Test complete CRUD cycle for Kinesis resource policies""" + + stream_name = kinesis_create_stream() + wait_for_stream_ready(stream_name) + describe_stream = aws_client.kinesis.describe_stream(StreamName=stream_name) + resource_arn = describe_stream["StreamDescription"]["StreamARN"] + principal_arn = f"arn:aws:iam::{account_id}:root" + + # retrieve, no policy yet + resp = aws_client.kinesis.get_resource_policy(ResourceARN=resource_arn) + snapshot.match("default_policy_if_not_set", resp) + + # put + policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowCrossAccountWrite", + "Effect": "Allow", + "Principal": {"AWS": principal_arn}, + "Action": "kinesis:PutRecord", + "Resource": resource_arn, + } + ], + } + resp = aws_client.kinesis.put_resource_policy( + ResourceARN=resource_arn, Policy=json.dumps(policy) + ) + snapshot.match("put_resource_policy_if_not_set", resp) + + # retrieve + resp = aws_client.kinesis.get_resource_policy(ResourceARN=resource_arn) + snapshot.match("get_resource_policy_after_set", resp) + + # update + updated_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowCrossAccountReadWrite", + "Effect": "Allow", + "Principal": {"AWS": principal_arn}, + "Action": ["kinesis:PutRecord", "kinesis:GetRecords"], + "Resource": resource_arn, + } + ], + } + resp = aws_client.kinesis.put_resource_policy( + ResourceARN=resource_arn, Policy=json.dumps(updated_policy) + ) + snapshot.match("update_resource_policy", resp) + + # get the right policy after updating + resp = aws_client.kinesis.get_resource_policy(ResourceARN=resource_arn) + snapshot.match("get_resource_policy_after_update", resp) + + # delete it + resp = aws_client.kinesis.delete_resource_policy(ResourceARN=resource_arn) + snapshot.match("delete_resource_policy", resp) + + # get, policy should no longer exist + resp = aws_client.kinesis.get_resource_policy(ResourceARN=resource_arn) + snapshot.match("get_resource_policy_after_delete", resp) + + # deleting non existent policy for a valid arn + with pytest.raises(ClientError): + aws_client.kinesis.delete_resource_policy(ResourceARN=resource_arn) + + # put a policy for a non-existent stream + not_existent_arn = "arn:aws:kinesis:us-east-1:000000000000:stream/non-existent-xxxxxx" + policy["Statement"][0]["Resource"] = not_existent_arn + with pytest.raises(ClientError): + aws_client.kinesis.put_resource_policy( + ResourceARN=not_existent_arn, Policy=json.dumps(policy) + ) + + # TODO put a policy for an invalid stream arn, but it behaves differently + # on localstack and AWS, as the later triggers end-point-resolution in botocore + # and fails client side + @markers.aws.validated def test_stream_consumers( self, @@ -175,7 +265,15 @@ def test_stream_consumers( snapshot.match("One_consumer_by_describe_stream", consumer_description_by_arn) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..Records..EncryptionType"]) + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Events..EncryptionType", + # TODO: We set this to the final sequence_number of returned records which is technically not correct, + # but will suffice as a checkpoint for now. See AWS docs: + # https://docs.aws.amazon.com/kinesis/latest/APIReference/API_SubscribeToShardEvent.html#API_SubscribeToShardEvent_Contents + "$..Events..ContinuationSequenceNumber", + ] + ) def test_subscribe_to_shard( self, kinesis_create_stream, @@ -210,21 +308,26 @@ def test_subscribe_to_shard( # put records num_records = 5 msg = "Hello world" - for i in range(num_records): - aws_client.kinesis.put_records( - StreamName=stream_name, Records=[{"Data": f"{msg}_{i}", "PartitionKey": "1"}] - ) + aws_client.kinesis.put_records( + StreamName=stream_name, + Records=[{"Data": f"{msg}_{i}", "PartitionKey": "1"} for i in range(num_records)], + ) # read out results results = [] for entry in stream: - records = entry["SubscribeToShardEvent"]["Records"] - results.extend(records) - if len(results) >= num_records: + event = entry["SubscribeToShardEvent"] + records = event.get("Records", []) + + if not records: + continue + + results.append(event) + num_records -= len(records) + if num_records <= 0: break - results.sort(key=lambda k: k.get("Data")) - snapshot.match("Records", results) + snapshot.match("Events", results) @markers.aws.validated @markers.snapshot.skip_snapshot_verify(paths=["$..Records..EncryptionType"]) @@ -499,6 +602,7 @@ def test_record_lifecycle_data_integrity( @markers.aws.validated @patch.object(kinesis_provider, "MAX_SUBSCRIPTION_SECONDS", 3) + @markers.requires_in_process def test_subscribe_to_shard_timeout( self, kinesis_create_stream, @@ -709,6 +813,36 @@ def _get_record(): record = retry(_get_record, sleep=1, retries=5) assert record["Data"].decode("utf-8") == test_data + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + # error message is wrong in Kinesis (returns the full ARN) + paths=["$..message"], + ) + def test_cbor_exceptions( + self, + kinesis_create_stream, + wait_for_stream_ready, + aws_client, + kinesis_http_client, + region_name, + account_id, + snapshot, + ): + fake_name = "wrong-stream-name" + fake_stream_arn = kinesis_stream_arn( + account_id=account_id, region_name=region_name, stream_name=fake_name + ) + describe_response_raw = kinesis_http_client.post_raw( + operation="DescribeStream", + payload={"StreamARN": fake_stream_arn}, + ) + assert describe_response_raw.status_code == 400 + cbor_content = describe_response_raw.content + describe_response_data = cbor2_loads(cbor_content) + snapshot.match("cbor-error", describe_response_data) + assert describe_response_data["__type"] == "ResourceNotFoundException" + # TODO: add manual assertion on CBOR body? + class TestKinesisJavaSDK: # the lambda function is stored in the lambda common functions folder to re-use existing caching in CI @@ -762,6 +896,7 @@ def test_subscribe_to_shard_with_java_sdk_v2_lambda( condition=is_aws_cloud(), reason="Duplicate of all tests in TestKinesis. Since we cannot unmark test cases, only run against LocalStack.", ) +@markers.requires_in_process # monkeypatching, it's quite nonsensical to execute these tests, although they work class TestKinesisMockScala(TestKinesis): @pytest.fixture(autouse=True) def set_kinesis_mock_scala_engine(self, monkeypatch): @@ -805,7 +940,7 @@ def process_records(records): num_events_kinesis = 10 kinesis.put_records( Records=[ - {"Data": "{}", "PartitionKey": "test_%s" % i} + {"Data": "{}", "PartitionKey": f"test_{i}"} for i in range(0, num_events_kinesis) ], StreamName=stream_name, diff --git a/tests/aws/services/kinesis/test_kinesis.snapshot.json b/tests/aws/services/kinesis/test_kinesis.snapshot.json index e71ca426b61e6..73f6d14d62b50 100644 --- a/tests/aws/services/kinesis/test_kinesis.snapshot.json +++ b/tests/aws/services/kinesis/test_kinesis.snapshot.json @@ -144,38 +144,44 @@ } }, "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard": { - "recorded-date": "26-08-2022, 09:33:29", + "recorded-date": "19-08-2025, 13:10:10", "recorded-content": { - "Records": [ - { - "SequenceNumber": "", - "ApproximateArrivalTimestamp": "timestamp", - "Data": "b'Hello world_0'", - "PartitionKey": "1" - }, - { - "SequenceNumber": "", - "ApproximateArrivalTimestamp": "timestamp", - "Data": "b'Hello world_1'", - "PartitionKey": "1" - }, - { - "SequenceNumber": "", - "ApproximateArrivalTimestamp": "timestamp", - "Data": "b'Hello world_2'", - "PartitionKey": "1" - }, - { - "SequenceNumber": "", - "ApproximateArrivalTimestamp": "timestamp", - "Data": "b'Hello world_3'", - "PartitionKey": "1" - }, + "Events": [ { - "SequenceNumber": "", - "ApproximateArrivalTimestamp": "timestamp", - "Data": "b'Hello world_4'", - "PartitionKey": "1" + "Records": [ + { + "SequenceNumber": "", + "ApproximateArrivalTimestamp": "timestamp", + "Data": "b'Hello world_0'", + "PartitionKey": "1" + }, + { + "SequenceNumber": "", + "ApproximateArrivalTimestamp": "timestamp", + "Data": "b'Hello world_1'", + "PartitionKey": "1" + }, + { + "SequenceNumber": "", + "ApproximateArrivalTimestamp": "timestamp", + "Data": "b'Hello world_2'", + "PartitionKey": "1" + }, + { + "SequenceNumber": "", + "ApproximateArrivalTimestamp": "timestamp", + "Data": "b'Hello world_3'", + "PartitionKey": "1" + }, + { + "SequenceNumber": "", + "ApproximateArrivalTimestamp": "timestamp", + "Data": "b'Hello world_4'", + "PartitionKey": "1" + } + ], + "ContinuationSequenceNumber": "", + "MillisBehindLatest": 0 } ] } @@ -220,5 +226,94 @@ } ] } + }, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_exceptions": { + "recorded-date": "04-09-2025, 16:59:26", + "recorded-content": { + "cbor-error": { + "__type": "ResourceNotFoundException", + "message": "Stream wrong-stream-name under account 111111111111 not found." + } + } + }, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_resource_policy_crud": { + "recorded-date": "01-08-2025, 13:00:05", + "recorded-content": { + "default_policy_if_not_set": { + "Policy": {}, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "put_resource_policy_if_not_set": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_resource_policy_after_set": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowCrossAccountWrite", + "Effect": "Allow", + "Principal": { + "AWS": "arn::iam::111111111111:root" + }, + "Action": "kinesis:PutRecord", + "Resource": "arn::kinesis::111111111111:" + } + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update_resource_policy": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_resource_policy_after_update": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowCrossAccountReadWrite", + "Effect": "Allow", + "Principal": { + "AWS": "arn::iam::111111111111:root" + }, + "Action": [ + "kinesis:PutRecord", + "kinesis:GetRecords" + ], + "Resource": "arn::kinesis::111111111111:" + } + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_resource_policy": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_resource_policy_after_delete": { + "Policy": {}, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/kinesis/test_kinesis.validation.json b/tests/aws/services/kinesis/test_kinesis.validation.json index 93c7322a4e4cf..bf6e1dd09245c 100644 --- a/tests/aws/services/kinesis/test_kinesis.validation.json +++ b/tests/aws/services/kinesis/test_kinesis.validation.json @@ -5,6 +5,15 @@ "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_blob_handling": { "last_validated_date": "2024-07-31T11:17:28+00:00" }, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_exceptions": { + "last_validated_date": "2025-09-16T15:31:43+00:00", + "durations_in_seconds": { + "setup": 0.57, + "call": 0.48, + "teardown": 0.0, + "total": 1.05 + } + }, "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_shard_count": { "last_validated_date": "2022-08-26T07:30:59+00:00" }, @@ -20,11 +29,26 @@ "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_record_lifecycle_data_integrity": { "last_validated_date": "2022-08-25T10:39:44+00:00" }, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_resource_policy_crud": { + "last_validated_date": "2025-08-05T15:23:27+00:00", + "durations_in_seconds": { + "setup": 0.63, + "call": 6.43, + "teardown": 0.08, + "total": 7.14 + } + }, "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_stream_consumers": { "last_validated_date": "2022-08-26T08:23:46+00:00" }, "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard": { - "last_validated_date": "2022-08-26T07:33:29+00:00" + "last_validated_date": "2025-08-19T13:10:10+00:00", + "durations_in_seconds": { + "setup": 0.73, + "call": 10.61, + "teardown": 0.43, + "total": 11.77 + } }, "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp": { "last_validated_date": "2024-06-21T15:20:46+00:00" diff --git a/tests/aws/services/kms/test_kms.py b/tests/aws/services/kms/test_kms.py index 92fcf1f085139..3a528d5103288 100644 --- a/tests/aws/services/kms/test_kms.py +++ b/tests/aws/services/kms/test_kms.py @@ -6,21 +6,28 @@ from datetime import datetime from random import getrandbits +import cbor2 import pytest +from asn1crypto import cms from botocore.config import Config from botocore.exceptions import ClientError +from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, hmac, serialization -from cryptography.hazmat.primitives.asymmetric import ec, padding, utils +from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa, utils +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.keywrap import aes_key_wrap_with_padding from cryptography.hazmat.primitives.serialization import load_der_public_key +from localstack_snapshot.snapshots.transformer import RegexTransformer from localstack.services.kms.models import ( + HEADER_LEN, IV_LEN, ON_DEMAND_ROTATION_LIMIT, Ciphertext, _serialize_ciphertext_blob, ) from localstack.services.kms.utils import get_hash_algorithm -from localstack.testing.aws.util import in_default_partition +from localstack.testing.aws.util import in_default_partition, is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.crypto import encrypt from localstack.utils.strings import short_uid, to_str @@ -52,6 +59,18 @@ def get_signature_kwargs(signing_algorithm, message_type): return kwargs +def generate_encrypted_symmetric_key_material(public_key: bytes) -> bytes: + symmetric_key_material = bytes(getrandbits(8) for _ in range(32)) + public_key = load_der_public_key(public_key) + encrypted_key = public_key.encrypt( + symmetric_key_material, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None + ), + ) + return encrypted_key + + @pytest.fixture(scope="class") def kms_client_for_region(aws_client_factory): def _kms_client( @@ -69,32 +88,31 @@ def user_arn(aws_client): def _get_all_key_ids(kms_client): ids = set() - next_token = None + next_marker = None while True: - kwargs = {"nextToken": next_token} if next_token else {} + kwargs = {"Marker": next_marker} if next_marker else {} response = kms_client.list_keys(**kwargs) for key in response["Keys"]: ids.add(key["KeyId"]) - if "nextToken" not in response: + if "NextMarker" not in response: break - next_token = response["nextToken"] + next_marker = response["NextMarker"] return ids def _get_alias(kms_client, alias_name, key_id=None): - next_token = None - # TODO potential bug on pagination on "nextToken" attribute key + next_marker = None while True: - kwargs = {"nextToken": next_token} if next_token else {} + kwargs = {"Marker": next_marker} if next_marker else {} if key_id: kwargs["KeyId"] = key_id response = kms_client.list_aliases(**kwargs) for alias in response["Aliases"]: if alias["AliasName"] == alias_name: return alias - if "nextToken" not in response: + if "NextMarker" not in response: break - next_token = response["nextToken"] + next_marker = response["NextMarker"] return None @@ -107,7 +125,7 @@ def kms_api_snapshot_transformer(self, snapshot): def test_create_alias(self, kms_create_alias, kms_create_key, snapshot): alias_name = f"{short_uid()}" alias_key_id = kms_create_key()["KeyId"] - with pytest.raises(Exception) as e: + with pytest.raises(ClientError) as e: kms_create_alias(AliasName=alias_name, TargetKeyId=alias_key_id) snapshot.match("create_alias", e.value.response) @@ -505,6 +523,34 @@ def test_get_key_invalid_uuid(self, snapshot, aws_client): aws_client.kms.describe_key(KeyId="mrk-fake-key-id") snapshot.match("describe-key-with-invalid-uuid-mrk", e.value.response) + @markers.aws.only_localstack + def test_create_key_custom_key_material_rsa_2048(self, kms_create_key, aws_client): + rsa_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + raw_private_key = rsa_key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + ) + raw_public_key = rsa_key.public_key().public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + custom_key_tag_value = base64.b64encode(raw_private_key).decode("utf-8") + + key_spec = "RSA_2048" + key_usage = "SIGN_VERIFY" + + key_id = kms_create_key( + KeySpec=key_spec, + KeyUsage=key_usage, + Tags=[ + {"TagKey": "_custom_key_material_", "TagValue": custom_key_tag_value}, + ], + )["KeyId"] + public_key = aws_client.kms.get_public_key(KeyId=key_id)["PublicKey"] + assert public_key == raw_public_key + @markers.aws.validated def test_list_keys(self, kms_create_key, aws_client): created_key = kms_create_key() @@ -717,12 +763,7 @@ def test_generate_random_invalid_number_of_bytes( snapshot.match("generate-random-exc", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Error.Message", - "$..message", - ] - ) + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) @pytest.mark.parametrize( "key_spec,sign_algo", [ @@ -1260,6 +1301,19 @@ def test_replicate_key( response = us_east_1_kms_client.describe_key(KeyId=key_id) snapshot.match("describe-key-from-region", response) + # ensure the key has completed replicating. + def _replicated_key_creation_is_complete(): + return ( + us_west_1_kms_client.describe_key(KeyId=key_id)["KeyMetadata"]["KeyState"] + == "Enabled" + ) + + assert poll_condition( + condition=_replicated_key_creation_is_complete, + timeout=120, + interval=5 if is_aws_cloud() else 0.5, + ) + # describe replicated key response = us_west_1_kms_client.describe_key(KeyId=key_id) snapshot.match("describe-replicated-key", response) @@ -1291,6 +1345,39 @@ def test_key_rotation_status(self, kms_key, aws_client): aws_client.kms.disable_key_rotation(KeyId=key_id) assert aws_client.kms.get_key_rotation_status(KeyId=key_id)["KeyRotationEnabled"] is False + @markers.aws.validated + def test_key_rotation_updates_current_key_material_id_for_aws_symmetric_keys( + self, kms_create_key, aws_client, snapshot + ): + key_id = kms_create_key( + KeyUsage="ENCRYPT_DECRYPT", KeySpec="SYMMETRIC_DEFAULT", Description="test-key" + )["KeyId"] + describe_key_before = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-before-rotation", describe_key_before) + + aws_client.kms.rotate_key_on_demand(KeyId=key_id) + + def _assert_on_demand_rotation_updates_material(): + response = aws_client.kms.describe_key(KeyId=key_id) + return ( + response["KeyMetadata"]["CurrentKeyMaterialId"] + != describe_key_before["KeyMetadata"]["CurrentKeyMaterialId"] + ) + + assert poll_condition( + condition=_assert_on_demand_rotation_updates_material, + timeout=90, + interval=5 if is_aws_cloud() else 0.5, + ) + + describe_key_after = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-rotation", describe_key_after) + + assert ( + describe_key_before["KeyMetadata"]["CurrentKeyMaterialId"] + != describe_key_after["KeyMetadata"]["CurrentKeyMaterialId"] + ) + @markers.aws.validated def test_key_rotations_encryption_decryption(self, kms_create_key, aws_client, snapshot): key_id = kms_create_key(KeyUsage="ENCRYPT_DECRYPT", KeySpec="SYMMETRIC_DEFAULT")["KeyId"] @@ -1328,7 +1415,7 @@ def test_key_rotations_encryption_decryption(self, kms_create_key, aws_client, s EncryptionAlgorithm="SYMMETRIC_DEFAULT", ) - snapshot.match("bad-ciphertext", e.value) + snapshot.match("bad-ciphertext", e.value.response) @markers.aws.validated def test_key_rotations_limits(self, kms_create_key, aws_client, snapshot): @@ -1348,6 +1435,15 @@ def _assert_on_demand_rotation_completed(): aws_client.kms.rotate_key_on_demand(KeyId=key_id) snapshot.match("error-response", e.value.response) + @markers.aws.validated + def test_rotate_on_demand_returns_arn_for_key_id(self, kms_create_key, aws_client, snapshot): + aws_kms_key = kms_create_key(KeyUsage="ENCRYPT_DECRYPT", KeySpec="SYMMETRIC_DEFAULT") + aws_key_rotate_on_demand_response = aws_client.kms.rotate_key_on_demand( + KeyId=aws_kms_key["KeyId"] + ) + snapshot.match("rotate-on-demand-aws-key", aws_key_rotate_on_demand_response) + assert aws_key_rotate_on_demand_response["KeyId"] == aws_kms_key["Arn"] + @markers.aws.validated def test_rotate_key_on_demand_modifies_key_material(self, kms_create_key, aws_client, snapshot): key_id = kms_create_key(KeyUsage="ENCRYPT_DECRYPT", KeySpec="SYMMETRIC_DEFAULT")["KeyId"] @@ -1441,11 +1537,6 @@ def test_rotate_key_on_demand_raises_error_given_key_that_does_not_exist( snapshot.match("error-response", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..message", - ], - ) def test_rotate_key_on_demand_raises_error_given_non_symmetric_key( self, kms_create_key, aws_client, snapshot ): @@ -1456,14 +1547,315 @@ def test_rotate_key_on_demand_raises_error_given_non_symmetric_key( snapshot.match("error-response", e.value.response) @markers.aws.validated - def test_rotate_key_on_demand_raises_error_given_key_with_imported_key_material( + def test_rotate_key_on_demand_updates_current_key_material_for_external_keys( self, kms_create_key, aws_client, snapshot ): - key_id = kms_create_key(Origin="EXTERNAL")["KeyId"] + snapshot.add_transformer( + snapshot.transform.key_value("ImportToken", reference_replacement=False) + ) + snapshot.add_transformer( + snapshot.transform.key_value("PublicKey", reference_replacement=False) + ) + + create_key_response = kms_create_key( + Origin="EXTERNAL", KeyUsage="ENCRYPT_DECRYPT", Description="test-key" + ) + key_id = create_key_response["KeyId"] + snapshot.match("create-kms-external-key", create_key_response) + + # Get the parameters required to import key material into our KMS key. + get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + assert get_parameters_response["KeyId"] == create_key_response["Arn"] + assert get_parameters_response["ImportToken"] + assert get_parameters_response["PublicKey"] + assert isinstance(get_parameters_response["ParametersValidTo"], datetime) + snapshot.match("get-import-parameters-response", get_parameters_response) + + # Create initial key material to use in external KMS key. + initial_key_material = generate_encrypted_symmetric_key_material( + get_parameters_response["PublicKey"] + ) + describe_empty_key_response = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-empty-key-response", describe_empty_key_response) + + # Import the initial key material. + import_key_material_response = aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=get_parameters_response["ImportToken"], + EncryptedKeyMaterial=initial_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + snapshot.match("import-key-material-response", import_key_material_response) + + # On initial import of material, it should automatically update the CurrentKeyMaterialId + describe_key_with_material_response = aws_client.kms.describe_key(KeyId=key_id) + assert "CurrentKeyMaterialId" in describe_key_with_material_response["KeyMetadata"] + current_key_material_id = describe_key_with_material_response["KeyMetadata"][ + "CurrentKeyMaterialId" + ] + snapshot.match("describe-key-with-material-response", describe_key_with_material_response) + + # Encrypt/Decrypt using key material. + plaintext = b"Hello World!1/?" + encrypted_response = aws_client.kms.encrypt( + Plaintext=plaintext, KeyId=key_id, EncryptionAlgorithm="SYMMETRIC_DEFAULT" + ) + snapshot.match("kms-encrypt-initial-response", encrypted_response) + initial_ciphertext = encrypted_response["CiphertextBlob"] + + decrypted_response = aws_client.kms.decrypt( + CiphertextBlob=initial_ciphertext, + KeyId=key_id, + ) + assert decrypted_response["Plaintext"] == plaintext + + # Import new key material and ensure that the key uses the new material after on-demand rotation. + new_get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + new_key_material = generate_encrypted_symmetric_key_material( + new_get_parameters_response["PublicKey"] + ) + import_new_key_material_response = aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=new_get_parameters_response["ImportToken"], + EncryptedKeyMaterial=new_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ImportType="NEW_KEY_MATERIAL", # Defaults to this if the key is empty, but will default to EXISTING_KEY_MATERIAL afterwards. + ) + + snapshot.match("import-new-key-material-response", import_new_key_material_response) + describe_key_with_material_response = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match( + "describe-key-with-new-material-response", describe_key_with_material_response + ) + + # Rotate to the new key material. + rotate_on_demand_response = aws_client.kms.rotate_key_on_demand(KeyId=key_id) + snapshot.match("rotate-key-on-demand-response", rotate_on_demand_response) + + def _assert_on_demand_rotation_updates_material(): + response = aws_client.kms.describe_key(KeyId=key_id) + return response["KeyMetadata"]["CurrentKeyMaterialId"] != current_key_material_id + + assert poll_condition( + condition=_assert_on_demand_rotation_updates_material, + timeout=90, + interval=5 if is_aws_cloud() else 0.5, + ) + + describe_key_after_rotate_response = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-rotation", describe_key_after_rotate_response) + + # Encrypt/Decrypt using the new key material. + new_encrypted_response = aws_client.kms.encrypt( + Plaintext=plaintext, KeyId=key_id, EncryptionAlgorithm="SYMMETRIC_DEFAULT" + ) + snapshot.match("kms-encrypt-rotated-key-response", new_encrypted_response) + new_ciphertext = new_encrypted_response["CiphertextBlob"] + + assert new_ciphertext != initial_ciphertext + new_decrypted_response = aws_client.kms.decrypt( + CiphertextBlob=new_ciphertext, + KeyId=key_id, + EncryptionAlgorithm="SYMMETRIC_DEFAULT", + ) + assert new_decrypted_response["Plaintext"] == plaintext + + @markers.aws.unknown + def test_rotate_key_on_demand_for_external_keys_decrypts_with_previous_material( + self, kms_create_key, aws_client, snapshot + ): + snapshot.add_transformer( + snapshot.transform.key_value("ImportToken", reference_replacement=False) + ) + snapshot.add_transformer( + snapshot.transform.key_value("PublicKey", reference_replacement=False) + ) + + create_key_response = kms_create_key( + Origin="EXTERNAL", KeyUsage="ENCRYPT_DECRYPT", Description="test-key" + ) + key_id = create_key_response["KeyId"] + snapshot.match("create-kms-external-key", create_key_response) + + get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + assert get_parameters_response["ImportToken"] + assert get_parameters_response["PublicKey"] + assert isinstance(get_parameters_response["ParametersValidTo"], datetime) + + initial_key_material = generate_encrypted_symmetric_key_material( + get_parameters_response["PublicKey"] + ) + + # Import the initial key material. + import_key_material_response = aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=get_parameters_response["ImportToken"], + EncryptedKeyMaterial=initial_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + snapshot.match("import-key-material-response", import_key_material_response) + + initial_key_material_id = import_key_material_response["KeyMaterialId"] + + # Encrypt with initial material, rotate to a new key, then ensure it decrypts with the correct key. + plaintext = b"Hello World!1/?" + encrypted_response = aws_client.kms.encrypt( + Plaintext=plaintext, KeyId=key_id, EncryptionAlgorithm="SYMMETRIC_DEFAULT" + ) + snapshot.match("kms-encrypt-initial-response", encrypted_response) + initial_ciphertext = encrypted_response["CiphertextBlob"] + + # Import new key material + new_get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + new_key_material = generate_encrypted_symmetric_key_material( + new_get_parameters_response["PublicKey"] + ) + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=new_get_parameters_response["ImportToken"], + EncryptedKeyMaterial=new_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ImportType="NEW_KEY_MATERIAL", + ) + + # Rotate to the new key material. + rotate_on_demand_response = aws_client.kms.rotate_key_on_demand(KeyId=key_id) + snapshot.match("rotate-key-on-demand-response", rotate_on_demand_response) + + def _assert_on_demand_rotation_updates_material(): + response = aws_client.kms.describe_key(KeyId=key_id) + return response["KeyMetadata"]["CurrentKeyMaterialId"] != initial_key_material_id + + assert poll_condition( + condition=_assert_on_demand_rotation_updates_material, + timeout=90, + interval=5 if is_aws_cloud() else 0.5, + ) + + decrypted_response = aws_client.kms.decrypt( + CiphertextBlob=initial_ciphertext, + KeyId=key_id, + ) + assert decrypted_response["Plaintext"] == plaintext + + @markers.aws.validated + def test_import_key_material_raises_if_there_is_already_key_material_pending( + self, kms_create_key, aws_client, snapshot + ): + snapshot.add_transformer(RegexTransformer(r"[a-f0-9]{64}", "")) + create_key_response = kms_create_key( + Origin="EXTERNAL", KeyUsage="ENCRYPT_DECRYPT", Description="test-key" + ) + key_id = create_key_response["KeyId"] + + get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + assert get_parameters_response["ImportToken"] + assert get_parameters_response["PublicKey"] + + initial_key_material = generate_encrypted_symmetric_key_material( + get_parameters_response["PublicKey"] + ) + import_initial = aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=get_parameters_response["ImportToken"], + EncryptedKeyMaterial=initial_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + snapshot.match("import-initial-material-response", import_initial) + + get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + assert get_parameters_response["ImportToken"] + assert get_parameters_response["PublicKey"] + + pending_key_material = generate_encrypted_symmetric_key_material( + get_parameters_response["PublicKey"] + ) + import_pending = aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=get_parameters_response["ImportToken"], + EncryptedKeyMaterial=pending_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ImportType="NEW_KEY_MATERIAL", + ) + snapshot.match("import-pending-material-response", import_pending) + + get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + assert get_parameters_response["ImportToken"] + assert get_parameters_response["PublicKey"] + + final_key_material = generate_encrypted_symmetric_key_material( + get_parameters_response["PublicKey"] + ) + with pytest.raises(ClientError) as e: + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=get_parameters_response["ImportToken"], + EncryptedKeyMaterial=final_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ImportType="NEW_KEY_MATERIAL", + ) + snapshot.match("existing-pending-material-error", e.value.response) + + @markers.aws.validated + def test_rotate_key_on_demand_raises_for_no_pending_key_material( + self, kms_create_key, aws_client, snapshot + ): + create_key_response = kms_create_key( + Origin="EXTERNAL", KeyUsage="ENCRYPT_DECRYPT", Description="test-key" + ) + key_id = create_key_response["KeyId"] + + get_parameters_response = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_2048" + ) + assert get_parameters_response["ImportToken"] + assert get_parameters_response["PublicKey"] + + initial_key_material = generate_encrypted_symmetric_key_material( + get_parameters_response["PublicKey"] + ) + + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=get_parameters_response["ImportToken"], + EncryptedKeyMaterial=initial_key_material, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) with pytest.raises(ClientError) as e: aws_client.kms.rotate_key_on_demand(KeyId=key_id) - snapshot.match("error-response", e.value.response) + snapshot.match("rotate-key-on-demand-invalid-state-error", e.value.response) + + @markers.aws.validated + @pytest.mark.parametrize( + "key_spec", ["SYMMETRIC_DEFAULT", "RSA_4096"] + ) # Check with a non-default key spec to test ordering of raised errors. + def test_rotate_key_on_demand_on_external_key_with_no_key_material( + self, kms_create_key, aws_client, snapshot, key_spec + ): + create_key_response = kms_create_key( + Origin="EXTERNAL", KeyUsage="ENCRYPT_DECRYPT", Description="test-key" + ) + key_id = create_key_response["KeyId"] + snapshot.match("create-kms-external-key", create_key_response) + + with pytest.raises(ClientError) as e: + aws_client.kms.rotate_key_on_demand(KeyId=key_id) + snapshot.match(f"rotate-key-on-demand-error-response-{key_spec}", e.value.response) @markers.aws.validated @pytest.mark.parametrize("rotation_period_in_days", [90, 180]) @@ -1573,6 +1965,74 @@ def test_cant_delete_deleted_key(self, kms_create_key, aws_client): aws_client.kms.schedule_key_deletion(KeyId=key_id) e.match("KMSInvalidStateException") + @markers.aws.validated + def test_import_key_rsa_aes_wrap_sha256(self, kms_create_key, aws_client, snapshot): + key_id = kms_create_key( + Origin="EXTERNAL", + KeySpec="RSA_4096", + KeyUsage="ENCRYPT_DECRYPT", + Description="test-key", + )["KeyId"] + + rsa_key = rsa.generate_private_key(public_exponent=65537, key_size=4096) + private_key_bytes = rsa_key.private_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + public_key_bytes = rsa_key.public_key().public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + # Wrap the Private Key with AES and RSA + aes_key = os.urandom(32) + encrypted_key_material = aes_key_wrap_with_padding( + aes_key, private_key_bytes, backend=default_backend() + ) + + params = aws_client.kms.get_parameters_for_import( + KeyId=key_id, + WrappingAlgorithm="RSA_AES_KEY_WRAP_SHA_256", + WrappingKeySpec="RSA_4096", + ) + public_key = load_der_public_key(params["PublicKey"]) + + # Wrap AES key using RSA public key + wrapped_aes_key = public_key.encrypt( + aes_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + + final_wrapped_blob = wrapped_aes_key + encrypted_key_material + + # Describe key before import + describe_before_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-before-import", describe_before_import) + + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=params["ImportToken"], + EncryptedKeyMaterial=final_wrapped_blob, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + + # Describe key after import + describe_key_after_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("import-key-material-response", describe_key_after_import) + + get_public_key_after_import = aws_client.kms.get_public_key(KeyId=key_id) + + assert get_public_key_after_import["PublicKey"] == public_key_bytes + + aws_client.kms.delete_imported_key_material(KeyId=key_id) + describe_key_after_deleted_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-deleted-import", describe_key_after_deleted_import) + @markers.aws.validated def test_hmac_create_key(self, kms_client_for_region, kms_create_key, snapshot, region_name): kms_client = kms_client_for_region(region_name) @@ -1613,6 +2073,195 @@ def test_hmac_create_key_invalid_operations(self, kms_create_key, snapshot, regi ) snapshot.match("create-hmac-key-invalid-key-usage", e.value.response) + @markers.aws.validated + @pytest.mark.parametrize( + "key_spec, curve, oaep_hash, signing_algorithm, wrapping_key_spec, wrapping_algorithm", + [ + ( + "ECC_NIST_P256", + ec.SECP256R1(), + hashes.SHA1(), + "ECDSA_SHA_256", + "RSA_2048", + "RSAES_OAEP_SHA_1", + ), + ( + "ECC_NIST_P384", + ec.SECP384R1(), + hashes.SHA1(), + "ECDSA_SHA_384", + "RSA_2048", + "RSAES_OAEP_SHA_1", + ), + ( + "ECC_NIST_P521", + ec.SECP521R1(), + hashes.SHA256(), + "ECDSA_SHA_512", + "RSA_4096", + "RSAES_OAEP_SHA_256", + ), + ( + "ECC_SECG_P256K1", + ec.SECP256K1(), + hashes.SHA1(), + "ECDSA_SHA_256", + "RSA_2048", + "RSAES_OAEP_SHA_1", + ), + ], + ids=["ECC_NIST_P256", "ECC_NIST_P384", "ECC_NIST_P521", "ECC_SECG_P256K1"], + ) + def test_import_key_ecc_keys( + self, + key_spec, + curve, + oaep_hash, + signing_algorithm, + wrapping_key_spec, + wrapping_algorithm, + kms_create_key, + aws_client, + snapshot, + ): + key = kms_create_key( + Origin="EXTERNAL", + KeySpec=key_spec, + KeyUsage="SIGN_VERIFY", + Description="test ecc key import", + ) + key_id = key["KeyId"] + + ecc_key = ec.generate_private_key(curve) + raw_private_key = ecc_key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + ) + raw_public_key = ecc_key.public_key().public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + import_params = aws_client.kms.get_parameters_for_import( + KeyId=key_id, + WrappingAlgorithm=wrapping_algorithm, + WrappingKeySpec=wrapping_key_spec, + ) + + public_key = load_der_public_key(import_params["PublicKey"]) + encrypted_key = public_key.encrypt( + raw_private_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=oaep_hash), + algorithm=oaep_hash, + label=None, + ), + ) + + describe_before = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-before-import", describe_before) + + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=import_params["ImportToken"], + EncryptedKeyMaterial=encrypted_key, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + + describe_after = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-import", describe_after) + + get_public_key_after_import = aws_client.kms.get_public_key(KeyId=key_id) + assert get_public_key_after_import["PublicKey"] == raw_public_key + + message = b"test sign verify 123 !%$@ 1234567890" + sign_result = aws_client.kms.sign( + KeyId=key_id, + Message=message, + MessageType="RAW", + SigningAlgorithm=signing_algorithm, + ) + snapshot.match("sign-result", sign_result) + + verify_result = aws_client.kms.verify( + KeyId=key_id, + Message=message, + MessageType="RAW", + SigningAlgorithm=signing_algorithm, + Signature=sign_result["Signature"], + ) + snapshot.match("verify-result", verify_result) + assert verify_result["SignatureValid"] + + aws_client.kms.delete_imported_key_material(KeyId=key_id) + describe_deleted = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-deleted-import", describe_deleted) + + @markers.aws.validated + @pytest.mark.parametrize( + "key_spec, key_length, mac_algo", + [ + ("HMAC_224", 28, "HMAC_SHA_224"), + ("HMAC_256", 32, "HMAC_SHA_256"), + ("HMAC_384", 48, "HMAC_SHA_384"), + ("HMAC_512", 64, "HMAC_SHA_512"), + ], + ids=["HMAC_224", "HMAC_256", "HMAC_384", "HMAC_512"], + ) + def test_import_key_hmac_keys( + self, key_spec, key_length, mac_algo, kms_create_key, aws_client, snapshot + ): + key = kms_create_key( + Origin="EXTERNAL", + KeySpec=key_spec, + KeyUsage="GENERATE_VERIFY_MAC", + Description="test import hmac key", + ) + key_id = key["KeyId"] + + plaintext = os.urandom(key_length) + + params = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_4096" + ) + + public_key = load_der_public_key(params["PublicKey"]) + encrypted_key = public_key.encrypt( + plaintext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None + ), + ) + describe_key_before_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-before-import", describe_key_before_import) + + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=params["ImportToken"], + EncryptedKeyMaterial=encrypted_key, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + describe_key_after_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-import", describe_key_after_import) + + # Generate and verify MAC + message = b"test-mac-message" + generate_mac = aws_client.kms.generate_mac( + KeyId=key_id, Message=message, MacAlgorithm=mac_algo + ) + snapshot.match("generate-mac-response", generate_mac) + + verify_mac = aws_client.kms.verify_mac( + KeyId=key_id, Message=message, Mac=generate_mac["Mac"], MacAlgorithm=mac_algo + ) + snapshot.match("verify-mac-response", verify_mac) + assert verify_mac["MacValid"] + + aws_client.kms.delete_imported_key_material(KeyId=key_id) + describe_key_after_deleted_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-deleted-import", describe_key_after_deleted_import) + @markers.aws.validated @pytest.mark.parametrize( "key_spec,mac_algo", @@ -1690,7 +2339,6 @@ def test_invalid_generate_mac(self, kms_create_key, key_spec, mac_algo, snapshot snapshot.match("generate-mac", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..message"]) @pytest.mark.parametrize( "key_spec,mac_algo,verify_msg", [ @@ -1787,8 +2435,9 @@ def test_plaintext_size_for_encrypt(self, kms_create_key, snapshot, aws_client): snapshot.match("invalid-plaintext-size-encrypt", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..message"]) + @markers.snapshot.skip_snapshot_verify(paths=["$..KeyMaterialId"]) def test_encrypt_decrypt_encryption_context(self, kms_create_key, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.key_value("KeyMaterialId")) key_id = kms_create_key()["KeyId"] message = b"test message 123 !%$@ 1234567890" encryption_context = {"context-key": "context-value"} @@ -1819,6 +2468,116 @@ def test_encrypt_decrypt_encryption_context(self, kms_create_key, snapshot, aws_ ) snapshot.match("decrypt_response_without_encryption_context", e.value.response) + with pytest.raises(ClientError) as e: + aws_client.kms.decrypt( + KeyId=key_id, + CiphertextBlob=ciphertext[HEADER_LEN:], + EncryptionAlgorithm=algo, + EncryptionContext=encryption_context, + ) + snapshot.match("decrypt_response_with_invalid_ciphertext", e.value.response) + + @markers.aws.only_localstack + def test_decrypt_recipient(self, kms_create_key, aws_client): + """ + Test that decryption to a Nitro recipient creates a correct PKCS7 envelope to that recipient. This is done + as an only-Localstack test because of the difficulty of spinning up a true Nitro enclave to generate a real + attestation for real AWS. + """ + + # Setup + key_id = kms_create_key(KeyUsage="ENCRYPT_DECRYPT", KeySpec="SYMMETRIC_DEFAULT")["KeyId"] + message = b"test message 123 !%$@ 1234567890" + ciphertext = aws_client.kms.encrypt( + KeyId=key_id, + Plaintext=base64.b64encode(message), + EncryptionAlgorithm="SYMMETRIC_DEFAULT", + )["CiphertextBlob"] + + # Set up our fake attestation. For now we just build a mini-attestation with only the public key, and + # skipping the other fields in it and the wrapping signature; in the future we could provide a more complete one. + rsa_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend(), + ) + der_public_key = rsa_key.public_key().public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + inner_attestation = cbor2.dumps({"public_key": der_public_key}) + wrapped_attestation = cbor2.dumps(["", "", inner_attestation, ""]) + recipient = {"AttestationDocument": wrapped_attestation} + + # Do the KMS call + ciphertext_for_recipient = aws_client.kms.decrypt( + KeyId=key_id, + CiphertextBlob=ciphertext, + EncryptionAlgorithm="SYMMETRIC_DEFAULT", + Recipient=recipient, + )["CiphertextForRecipient"] + + # Decrypt the PKCS7 envelope to recover the plaintext. Hazmat's pkcs7_decrypt_pem() doesn't support the RSA-OAEP + # with SHA-256 that AWS uses, so we use as1ncrypto to unpack the envelope and then Hazmat to decrypt + # the session key and then the plaintext. + + # Parse the PKCS7 envelope + content_info = cms.ContentInfo.load(ciphertext_for_recipient) + enveloped_data = content_info["content"] + + # Extract the encrypted session key and encrypted content + recipient_info = enveloped_data["recipient_infos"][0].chosen + encrypted_session_key = recipient_info["encrypted_key"].native + encrypted_content_info = enveloped_data["encrypted_content_info"] + encrypted_content = encrypted_content_info["encrypted_content"].native + + # Get the IV from the content encryption algorithm parameters + iv = encrypted_content_info["content_encryption_algorithm"]["parameters"].native + + # Decrypt the session key + session_key = rsa_key.decrypt( + encrypted_session_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None + ), + ) + + # Decrypt the content using the session key and iv + cipher = Cipher(algorithms.AES(session_key), modes.CBC(iv), backend=default_backend()) + decryptor = cipher.decryptor() + plaintext = decryptor.update(encrypted_content) + decryptor.finalize() + + # Make sure we did the decryption correctly + assert base64.b64decode(plaintext) == message + + @markers.aws.only_localstack + def test_decrypt_recipient_invalid_attestation(self, kms_create_key, aws_client): + """ + Test that if we are unable to extract the public key from the attestation, we ignore the recipient + and provide the plaintext in the response. + """ + + # Setup + key_id = kms_create_key(KeyUsage="ENCRYPT_DECRYPT", KeySpec="SYMMETRIC_DEFAULT")["KeyId"] + message = b"test message 123 !%$@ 1234567890" + ciphertext = aws_client.kms.encrypt( + KeyId=key_id, + Plaintext=base64.b64encode(message), + EncryptionAlgorithm="SYMMETRIC_DEFAULT", + )["CiphertextBlob"] + + # Invalid attestation document + recipient = {"AttestationDocument": base64.b64encode(b"invalidattestation")} + plaintext = aws_client.kms.decrypt( + KeyId=key_id, + CiphertextBlob=ciphertext, + EncryptionAlgorithm="SYMMETRIC_DEFAULT", + Recipient=recipient, + )["Plaintext"] + + # Still get the plaintext back + assert base64.b64decode(plaintext) == message + @markers.aws.validated def test_get_parameters_for_import(self, kms_create_key, snapshot, aws_client): sign_verify_key = kms_create_key( @@ -1843,6 +2602,19 @@ def test_get_parameters_for_import(self, kms_create_key, snapshot, aws_client): ) snapshot.match("response-error", e.value.response) + @markers.aws.validated + def test_get_parameters_for_import_raises_for_non_external_kms_keys( + self, kms_create_key, aws_client, snapshot + ): + aws_kms_key = kms_create_key() + with pytest.raises(ClientError) as e: + aws_client.kms.get_parameters_for_import( + KeyId=aws_kms_key["KeyId"], + WrappingAlgorithm="RSAES_OAEP_SHA_256", + WrappingKeySpec="RSA_2048", + ) + snapshot.match("get-import-parameters-error", e.value.response) + @markers.aws.validated def test_derive_shared_secret(self, kms_create_key, aws_client, snapshot): snapshot.add_transformer( @@ -1908,6 +2680,89 @@ def test_derive_shared_secret(self, kms_create_key, aws_client, snapshot): ) snapshot.match("response-invalid-public-key", e.value.response) + @markers.aws.validated + def test_derive_shared_secret_matches_openssl(self, kms_create_key, aws_client, snapshot): + snapshot.add_transformer( + snapshot.transform.key_value("SharedSecret", reference_replacement=False) + ) + + create_key = kms_create_key(KeySpec="ECC_NIST_P256", KeyUsage="KEY_AGREEMENT") + key_id = create_key["KeyId"] + + public_key = aws_client.kms.get_public_key(KeyId=key_id) + public_key_der = public_key["PublicKey"] + + local_private_key = ec.generate_private_key(ec.SECP256R1()) + local_public_key = local_private_key.public_key() + + local_public_key_der = local_public_key.public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + peer_public_key = load_der_public_key(public_key_der) + local_shared_secret = local_private_key.exchange(ec.ECDH(), peer_public_key) + + derived_shared_secret_resp = aws_client.kms.derive_shared_secret( + KeyId=key_id, + KeyAgreementAlgorithm="ECDH", + PublicKey=local_public_key_der, + ) + shared_secret = derived_shared_secret_resp["SharedSecret"] + + assert shared_secret == local_shared_secret, ( + f"KMS secret length: {len(shared_secret)}, OpenSSL secret length: {len(local_shared_secret)}" + ) + + snapshot.match("derived-shared-key-resp", derived_shared_secret_resp) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..CurrentKeyMaterialId"]) + def test_describe_with_alias_arn(self, kms_create_key, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.key_value("CurrentKeyMaterialId")) + + alias_name = f"alias/{short_uid()}" + created_key = kms_create_key(Description="test - description") + snapshot.match("created-key", created_key) + + aws_client.kms.create_alias(TargetKeyId=created_key["KeyId"], AliasName=alias_name) + alias = _get_alias(aws_client.kms, alias_name) + alias_arn = alias["AliasArn"] + + describe_response = aws_client.kms.describe_key(KeyId=alias_arn) + snapshot.match("describe-key", describe_response) + + @markers.aws.validated + def test_replicate_replica_key_should_fail( + self, + kms_client_for_region, + kms_create_key, + kms_replicate_key, + region_name, + secondary_region_name, + snapshot, + ): + """Tes that attempting to replicate a replica key should raise ValidationException""" + primary_key_id = kms_create_key( + region_name=region_name, MultiRegion=True, Description="test primary key" + )["KeyId"] + + replica_response = kms_replicate_key( + region_from=region_name, KeyId=primary_key_id, ReplicaRegion=secondary_region_name + ) + replica_key_id = replica_response["ReplicaKeyMetadata"]["KeyId"] + + # attempt to replicate the replica key from the secondary region + # This should fail because we're trying to replicate a replica + secondary_client = kms_client_for_region(secondary_region_name) + + with pytest.raises(ClientError) as exc_info: + secondary_client.replicate_key( + KeyId=replica_key_id, ReplicaRegion=secondary_region_name + ) + + error = exc_info.value.response + snapshot.match("fail-replicate-non-primary-key", error) + class TestKMSMultiAccounts: @markers.aws.needs_fixing @@ -2082,7 +2937,6 @@ def test_generate_data_key_without_plaintext(self, kms_key, aws_client, snapshot snapshot.match("generate-data-key-without-plaintext", result) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) def test_encryption_context_generate_data_key(self, kms_key, aws_client, snapshot): encryption_context = {"context-key": "context-value"} key_id = kms_key["KeyId"] @@ -2095,7 +2949,6 @@ def test_encryption_context_generate_data_key(self, kms_key, aws_client, snapsho snapshot.match("decrypt-without-encryption-context", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) def test_encryption_context_generate_data_key_without_plaintext( self, kms_key, aws_client, snapshot ): @@ -2110,7 +2963,6 @@ def test_encryption_context_generate_data_key_without_plaintext( snapshot.match("decrypt-without-encryption-context", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..message"]) def test_encryption_context_generate_data_key_pair(self, kms_key, aws_client, snapshot): encryption_context = {"context-key": "context-value"} key_id = kms_key["KeyId"] @@ -2123,7 +2975,6 @@ def test_encryption_context_generate_data_key_pair(self, kms_key, aws_client, sn snapshot.match("decrypt-without-encryption-context", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..message"]) def test_encryption_context_generate_data_key_pair_without_plaintext( self, kms_key, aws_client, snapshot ): diff --git a/tests/aws/services/kms/test_kms.snapshot.json b/tests/aws/services/kms/test_kms.snapshot.json index 0d4f5ff03be92..50b243450ab2a 100644 --- a/tests/aws/services/kms/test_kms.snapshot.json +++ b/tests/aws/services/kms/test_kms.snapshot.json @@ -165,12 +165,13 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key": { - "recorded-date": "13-04-2023, 11:29:30", + "recorded-date": "10-11-2025, 14:51:11", "recorded-content": { "describe-key": { "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "test key 123", "Enabled": true, @@ -188,7 +189,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_in_different_region": { - "recorded-date": "14-06-2024, 13:34:43", + "recorded-date": "10-11-2025, 14:57:15", "recorded-content": { "describe-key-diff-region-with-id": { "Error": { @@ -217,6 +218,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "test key 123", "Enabled": true, @@ -241,6 +243,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "test key 123", "Enabled": true, @@ -263,7 +266,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_does_not_exist": { - "recorded-date": "13-04-2023, 11:29:34", + "recorded-date": "08-09-2025, 19:02:52", "recorded-content": { "describe-nonexistent-key-with-id": { "Error": { @@ -290,9 +293,9 @@ "describe-key-with-valid-id-mrk": { "Error": { "Code": "NotFoundException", - "Message": "Key 'arn::kms::111111111111:key/mrk-d3b95762d3b95762d3b95762d3b95762' does not exist" + "Message": "Key '' does not exist" }, - "message": "Key 'arn::kms::111111111111:key/mrk-d3b95762d3b95762d3b95762d3b95762' does not exist", + "message": "Key '' does not exist", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 @@ -339,7 +342,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_key": { - "recorded-date": "11-04-2024, 15:51:52", + "recorded-date": "10-11-2025, 16:44:08", "recorded-content": { "describe-key-from-different-region": { "Error": { @@ -409,6 +412,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "test replicated key", "Enabled": true, @@ -446,16 +450,17 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms:ap-southeast-1:111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "", - "Enabled": false, + "Enabled": true, "EncryptionAlgorithms": [ "SYMMETRIC_DEFAULT" ], "KeyId": "", "KeyManager": "CUSTOMER", "KeySpec": "SYMMETRIC_DEFAULT", - "KeyState": "Creating", + "KeyState": "Enabled", "KeyUsage": "ENCRYPT_DECRYPT", "MultiRegion": true, "MultiRegionConfiguration": { @@ -848,7 +853,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key": { - "recorded-date": "13-04-2023, 11:34:18", + "recorded-date": "10-11-2025, 17:24:11", "recorded-content": { "create-hmac-key": { "AWSAccountId": "111111111111", @@ -1208,7 +1213,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_symmetric": { - "recorded-date": "24-01-2024, 10:44:12", + "recorded-date": "10-11-2025, 14:57:58", "recorded-content": { "created-key": { "AWSAccountId": "111111111111", @@ -1278,6 +1283,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "", "Enabled": true, @@ -1303,6 +1309,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "", "Enabled": false, @@ -1336,7 +1343,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_asymmetric": { - "recorded-date": "24-01-2024, 10:44:14", + "recorded-date": "10-11-2025, 16:00:07", "recorded-content": { "created-key": { "AWSAccountId": "111111111111", @@ -1565,7 +1572,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt_encryption_context": { - "recorded-date": "11-05-2023, 22:46:49", + "recorded-date": "22-09-2025, 21:45:01", "recorded-content": { "encrypt_response": { "CiphertextBlob": "ciphertext-blob", @@ -1579,6 +1586,7 @@ "decrypt_response_with_encryption_context": { "EncryptionAlgorithm": "SYMMETRIC_DEFAULT", "KeyId": "", + "KeyMaterialId": "", "Plaintext": "plaintext", "ResponseMetadata": { "HTTPHeaders": {}, @@ -1594,6 +1602,16 @@ "HTTPHeaders": {}, "HTTPStatusCode": 400 } + }, + "decrypt_response_with_invalid_ciphertext": { + "Error": { + "Code": "InvalidCiphertextException", + "Message": "" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } } } }, @@ -1674,12 +1692,13 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_create_multi_region_key": { - "recorded-date": "11-04-2024, 14:46:26", + "recorded-date": "10-11-2025, 16:02:52", "recorded-content": { "create_multi_region_key": { "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "test multi region key", "Enabled": true, @@ -1705,12 +1724,13 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_non_multi_region_keys_should_not_have_multi_region_properties": { - "recorded-date": "11-04-2024, 15:47:59", + "recorded-date": "10-11-2025, 16:04:38", "recorded-content": { "non_multi_region_keys_should_not_have_multi_region_properties": { "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "test non multi region key", "Enabled": true, @@ -2049,7 +2069,7 @@ } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_disabled": { - "recorded-date": "12-03-2025, 19:05:50", + "recorded-date": "10-11-2025, 17:37:46", "recorded-content": { "rotate-on-demand-response": { "KeyId": "", @@ -2186,9 +2206,18 @@ "recorded-content": {} }, "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_encryption_decryption": { - "recorded-date": "03-04-2025, 09:34:48", + "recorded-date": "09-02-2026, 19:06:42", "recorded-content": { - "bad-ciphertext": "An error occurred (InvalidCiphertextException) when calling the Decrypt operation: " + "bad-ciphertext": { + "Error": { + "Code": "InvalidCiphertextException", + "Message": "" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_limits": { @@ -2286,5 +2315,1356 @@ } } } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_rsa_aes_wrap_sha256": { + "recorded-date": "22-07-2025, 06:11:13", + "recorded-content": { + "describe-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "RSA_4096", + "Description": "test-key", + "Enabled": false, + "EncryptionAlgorithms": [ + "RSAES_OAEP_SHA_1", + "RSAES_OAEP_SHA_256" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "RSA_4096", + "KeyState": "PendingImport", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "import-key-material-response": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "RSA_4096", + "Description": "test-key", + "Enabled": true, + "EncryptionAlgorithms": [ + "RSAES_OAEP_SHA_1", + "RSAES_OAEP_SHA_256" + ], + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "RSA_4096", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "RSA_4096", + "Description": "test-key", + "Enabled": false, + "EncryptionAlgorithms": [ + "RSAES_OAEP_SHA_1", + "RSAES_OAEP_SHA_256" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "RSA_4096", + "KeyState": "PendingImport", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_with_alias_arn": { + "recorded-date": "05-08-2025, 17:00:25", + "recorded-content": { + "created-key": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test - description", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "AWS_KMS" + }, + "describe-key": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test - description", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "AWS_KMS" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P256]": { + "recorded-date": "04-08-2025, 08:10:22", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P256", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P256", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P256", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P256", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P256", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P256", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P384]": { + "recorded-date": "04-08-2025, 08:10:26", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P384", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P384", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_384" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P384", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P384", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_384" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_384", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_384", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P384", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P384", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_384" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_SECG_P256K1]": { + "recorded-date": "04-08-2025, 08:10:33", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_SECG_P256K1", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_SECG_P256K1", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_SECG_P256K1", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_SECG_P256K1", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_SECG_P256K1", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_SECG_P256K1", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P521]": { + "recorded-date": "04-08-2025, 08:10:29", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P521", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P521", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_512" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P521", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P521", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_512" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_512", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_512", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P521", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P521", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_512" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_224]": { + "recorded-date": "04-08-2025, 08:11:28", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_224", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_224", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_224" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_224", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_224", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_224" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_224", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_224", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_224", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_224", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_224" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_256]": { + "recorded-date": "04-08-2025, 08:11:32", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_256", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_256", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_256" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_256", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_256", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_256" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_256", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_256", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_256", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_256" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_384]": { + "recorded-date": "04-08-2025, 08:11:35", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_384", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_384", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_384" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_384", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_384", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_384" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_384", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_384", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_384", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_384", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_384" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_512]": { + "recorded-date": "04-08-2025, 08:11:38", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_512", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_512", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_512" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_512", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_512", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_512" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_512", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_512", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_512", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_512", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_512" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_replica_key_should_fail": { + "recorded-date": "08-09-2025, 16:34:57", + "recorded-content": { + "fail-replicate-non-primary-key": { + "Error": { + "Code": "UnsupportedOperationException", + "Message": " is not a multi-region primary key." + }, + "message": " is not a multi-region primary key.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_on_external_key_with_no_key_material[SYMMETRIC_DEFAULT]": { + "recorded-date": "05-11-2025, 16:12:01", + "recorded-content": { + "create-kms-external-key": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": false, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "PendingImport", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "rotate-key-on-demand-error-response-SYMMETRIC_DEFAULT": { + "Error": { + "Code": "KMSInvalidStateException", + "Message": "arn::kms::111111111111:key/ is pending import." + }, + "message": "arn::kms::111111111111:key/ is pending import.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_on_external_key_with_no_key_material[RSA_4096]": { + "recorded-date": "05-11-2025, 16:12:01", + "recorded-content": { + "create-kms-external-key": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": false, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "PendingImport", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "rotate-key-on-demand-error-response-RSA_4096": { + "Error": { + "Code": "KMSInvalidStateException", + "Message": "arn::kms::111111111111:key/ is pending import." + }, + "message": "arn::kms::111111111111:key/ is pending import.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_on_demand_returns_arn_for_key_id": { + "recorded-date": "06-11-2025, 11:07:58", + "recorded-content": { + "rotate-on-demand-aws-key": { + "KeyId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_updates_current_key_material_for_external_keys": { + "recorded-date": "09-11-2025, 16:21:58", + "recorded-content": { + "create-kms-external-key": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": false, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "PendingImport", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "get-import-parameters-response": { + "ImportToken": "import-token", + "KeyId": "arn::kms::111111111111:key/", + "ParametersValidTo": "datetime", + "PublicKey": "public-key", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-empty-key-response": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": false, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "PendingImport", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "import-key-material-response": { + "KeyId": "arn::kms::111111111111:key/", + "KeyMaterialId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-with-material-response": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "kms-encrypt-initial-response": { + "CiphertextBlob": "ciphertext-blob", + "EncryptionAlgorithm": "SYMMETRIC_DEFAULT", + "KeyId": "arn::kms::111111111111:key/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "import-new-key-material-response": { + "KeyId": "arn::kms::111111111111:key/", + "KeyMaterialId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-with-new-material-response": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "rotate-key-on-demand-response": { + "KeyId": "arn::kms::111111111111:key/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-rotation": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "kms-encrypt-rotated-key-response": { + "CiphertextBlob": "ciphertext-blob", + "EncryptionAlgorithm": "SYMMETRIC_DEFAULT", + "KeyId": "arn::kms::111111111111:key/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_parameters_for_import_raises_for_non_external_kms_keys": { + "recorded-date": "10-11-2025, 23:21:24", + "recorded-content": { + "get-import-parameters-error": { + "Error": { + "Code": "UnsupportedOperationException", + "Message": " origin is AWS_KMS which is not valid for this operation." + }, + "message": " origin is AWS_KMS which is not valid for this operation.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_material_raises_if_there_is_already_key_material_pending": { + "recorded-date": "10-11-2025, 23:15:45", + "recorded-content": { + "import-initial-material-response": { + "KeyId": "", + "KeyMaterialId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "import-pending-material-response": { + "KeyId": "", + "KeyMaterialId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "existing-pending-material-error": { + "Error": { + "Code": "KMSInvalidStateException", + "Message": "New key material (id: ) cannot be imported into KMS key , because another key material (id: ) is pending rotation." + }, + "message": "New key material (id: ) cannot be imported into KMS key , because another key material (id: ) is pending rotation.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_for_external_keys_decrypts_with_previous_material": { + "recorded-date": "12-11-2025, 21:00:07", + "recorded-content": { + "create-kms-external-key": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": false, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "PendingImport", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "import-key-material-response": { + "KeyId": "arn::kms::111111111111:key/", + "KeyMaterialId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "kms-encrypt-initial-response": { + "CiphertextBlob": "ciphertext-blob", + "EncryptionAlgorithm": "SYMMETRIC_DEFAULT", + "KeyId": "arn::kms::111111111111:key/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "rotate-key-on-demand-response": { + "KeyId": "arn::kms::111111111111:key/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_for_no_pending_key_material": { + "recorded-date": "19-11-2025, 10:21:48", + "recorded-content": { + "rotate-key-on-demand-invalid-state-error": { + "Error": { + "Code": "KMSInvalidStateException", + "Message": "No available key material pending rotation for the key: ." + }, + "message": "No available key material pending rotation for the key: .", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_updates_current_key_material_id_for_aws_symmetric_keys": { + "recorded-date": "19-11-2025, 10:55:13", + "recorded-content": { + "describe-key-before-rotation": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "AWS_KMS" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-rotation": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test-key", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "AWS_KMS" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_derive_shared_secret_matches_openssl": { + "recorded-date": "14-11-2025, 06:17:43", + "recorded-content": { + "derived-shared-key-resp": { + "KeyAgreementAlgorithm": "ECDH", + "KeyId": "", + "KeyOrigin": "AWS_KMS", + "SharedSecret": "shared-secret", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/kms/test_kms.validation.json b/tests/aws/services/kms/test_kms.validation.json index df19dfe77dbba..d06d7323d49c6 100644 --- a/tests/aws/services/kms/test_kms.validation.json +++ b/tests/aws/services/kms/test_kms.validation.json @@ -21,7 +21,13 @@ "last_validated_date": "2024-04-11T15:52:40+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key": { - "last_validated_date": "2024-04-11T15:26:14+00:00" + "last_validated_date": "2025-11-10T14:51:11+00:00", + "durations_in_seconds": { + "setup": 1.51, + "call": 0.36, + "teardown": 0.05, + "total": 1.92 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[lowercase_prefix]": { "last_validated_date": "2025-01-22T13:37:31+00:00" @@ -42,14 +48,38 @@ "last_validated_date": "2024-04-11T15:53:50+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_create_multi_region_key": { - "last_validated_date": "2024-04-11T15:53:40+00:00" + "last_validated_date": "2025-11-10T16:02:52+00:00", + "durations_in_seconds": { + "setup": 1.19, + "call": 0.35, + "teardown": 0.05, + "total": 1.59 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_derive_shared_secret": { "last_validated_date": "2024-12-25T14:45:00+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_derive_shared_secret_matches_openssl": { + "last_validated_date": "2025-11-14T06:17:43+00:00", + "durations_in_seconds": { + "setup": 1.43, + "call": 1.75, + "teardown": 0.38, + "total": 3.56 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_and_list_sign_key": { "last_validated_date": "2024-04-11T15:53:27+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_with_alias_arn": { + "last_validated_date": "2025-08-05T17:00:25+00:00", + "durations_in_seconds": { + "setup": 0.83, + "call": 0.99, + "teardown": 0.16, + "total": 1.98 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_disable_and_enable_key": { "last_validated_date": "2024-04-11T15:52:38+00:00" }, @@ -60,7 +90,13 @@ "last_validated_date": "2024-04-11T15:53:18+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt_encryption_context": { - "last_validated_date": "2024-04-11T15:54:22+00:00" + "last_validated_date": "2025-09-22T21:45:01+00:00", + "durations_in_seconds": { + "setup": 0.45, + "call": 0.99, + "teardown": 0.12, + "total": 1.56 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_1]": { "last_validated_date": "2024-04-11T15:53:20+00:00" @@ -120,10 +156,22 @@ "last_validated_date": "2024-04-11T15:52:50+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_does_not_exist": { - "last_validated_date": "2024-04-11T15:52:32+00:00" + "last_validated_date": "2025-09-08T19:02:52+00:00", + "durations_in_seconds": { + "setup": 0.83, + "call": 1.5, + "teardown": 0.22, + "total": 2.55 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_in_different_region": { - "last_validated_date": "2024-06-14T13:35:20+00:00" + "last_validated_date": "2025-11-10T14:57:15+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 1.45, + "teardown": 0.2, + "total": 2.58 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_invalid_uuid": { "last_validated_date": "2024-04-11T15:52:33+00:00" @@ -131,6 +179,15 @@ "tests/aws/services/kms/test_kms.py::TestKMS::test_get_parameters_for_import": { "last_validated_date": "2024-04-11T15:54:23+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_parameters_for_import_raises_for_non_external_kms_keys": { + "last_validated_date": "2025-11-10T23:21:24+00:00", + "durations_in_seconds": { + "setup": 1.01, + "call": 0.31, + "teardown": 0.05, + "total": 1.37 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_get_public_key": { "last_validated_date": "2024-04-11T15:53:25+00:00" }, @@ -138,16 +195,124 @@ "last_validated_date": "2024-04-11T15:53:55+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key": { - "last_validated_date": "2024-04-10T20:40:13+00:00" + "last_validated_date": "2025-11-10T17:24:11+00:00", + "durations_in_seconds": { + "setup": 1.02, + "call": 0.36, + "teardown": 0.05, + "total": 1.43 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key_invalid_operations": { "last_validated_date": "2023-04-13T09:31:06+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_asymmetric": { - "last_validated_date": "2024-04-11T15:53:35+00:00" + "last_validated_date": "2025-11-10T16:00:07+00:00", + "durations_in_seconds": { + "setup": 1.59, + "call": 0.64, + "teardown": 0.05, + "total": 2.28 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P256]": { + "last_validated_date": "2025-08-04T08:10:22+00:00", + "durations_in_seconds": { + "setup": 1.37, + "call": 4.01, + "teardown": 0.32, + "total": 5.7 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P384]": { + "last_validated_date": "2025-08-04T08:10:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.15, + "teardown": 0.31, + "total": 3.46 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P521]": { + "last_validated_date": "2025-08-04T08:10:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.18, + "teardown": 0.31, + "total": 3.49 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_SECG_P256K1]": { + "last_validated_date": "2025-08-04T08:10:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.28, + "teardown": 0.32, + "total": 3.6 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_224]": { + "last_validated_date": "2025-08-04T08:11:28+00:00", + "durations_in_seconds": { + "setup": 1.33, + "call": 3.61, + "teardown": 0.32, + "total": 5.26 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_256]": { + "last_validated_date": "2025-08-04T08:11:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.85, + "teardown": 0.32, + "total": 3.17 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_384]": { + "last_validated_date": "2025-08-04T08:11:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.89, + "teardown": 0.32, + "total": 3.21 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_512]": { + "last_validated_date": "2025-08-04T08:11:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.92, + "teardown": 0.32, + "total": 3.24 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_material_raises_if_there_is_already_key_material_pending": { + "last_validated_date": "2025-11-10T23:15:45+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 0.62, + "teardown": 0.05, + "total": 1.6 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_rsa_aes_wrap_sha256": { + "last_validated_date": "2025-07-22T06:11:13+00:00", + "durations_in_seconds": { + "setup": 1.91, + "call": 4.8, + "teardown": 0.37, + "total": 7.08 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_symmetric": { - "last_validated_date": "2024-04-11T15:53:31+00:00" + "last_validated_date": "2025-11-10T14:57:58+00:00", + "durations_in_seconds": { + "setup": 0.86, + "call": 0.68, + "teardown": 0.05, + "total": 1.59 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_224-HMAC_SHA_256]": { "last_validated_date": "2024-04-11T15:54:11+00:00" @@ -176,8 +341,23 @@ "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_status": { "last_validated_date": "2024-04-11T15:53:48+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_updates_current_key_material_id_for_aws_symmetric_keys": { + "last_validated_date": "2025-11-19T10:55:13+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 65.88, + "teardown": 0.05, + "total": 66.85 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_encryption_decryption": { - "last_validated_date": "2025-04-03T09:34:47+00:00" + "last_validated_date": "2026-02-09T19:06:42+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.13, + "teardown": 0.12, + "total": 1.74 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_limits": { "last_validated_date": "2025-04-03T11:10:33+00:00" @@ -195,7 +375,13 @@ "last_validated_date": "2024-04-11T15:52:34+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_non_multi_region_keys_should_not_have_multi_region_properties": { - "last_validated_date": "2024-04-11T15:53:41+00:00" + "last_validated_date": "2025-11-10T16:04:38+00:00", + "durations_in_seconds": { + "setup": 1.22, + "call": 0.3, + "teardown": 0.05, + "total": 1.57 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_plaintext_size_for_encrypt": { "last_validated_date": "2024-04-11T15:54:20+00:00" @@ -210,7 +396,22 @@ "last_validated_date": "2025-06-09T12:53:24+00:00" }, "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_key": { - "last_validated_date": "2024-04-11T15:53:44+00:00" + "last_validated_date": "2025-11-10T16:44:08+00:00", + "durations_in_seconds": { + "setup": 1.01, + "call": 64.64, + "teardown": 0.34, + "total": 65.99 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_replica_key_should_fail": { + "last_validated_date": "2025-09-08T16:34:57+00:00", + "durations_in_seconds": { + "setup": 0.85, + "call": 2.29, + "teardown": 0.34, + "total": 3.48 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_token": { "last_validated_date": "2024-04-11T15:52:46+00:00" @@ -218,9 +419,36 @@ "tests/aws/services/kms/test_kms.py::TestKMS::test_revoke_grant": { "last_validated_date": "2024-04-11T15:52:44+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_for_external_keys_decrypts_with_previous_material": { + "last_validated_date": "2025-11-12T21:00:07+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 66.22, + "teardown": 0.05, + "total": 67.18 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_modifies_key_material": { "last_validated_date": "2025-03-08T09:24:15+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_on_external_key_with_no_key_material[RSA_4096]": { + "last_validated_date": "2025-11-05T16:12:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.05, + "total": 0.15 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_on_external_key_with_no_key_material[SYMMETRIC_DEFAULT]": { + "last_validated_date": "2025-11-05T16:12:01+00:00", + "durations_in_seconds": { + "setup": 0.83, + "call": 0.21, + "teardown": 0.05, + "total": 1.09 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_is_disabled": { "last_validated_date": "2025-03-08T09:26:50+00:00" }, @@ -233,12 +461,45 @@ "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_non_symmetric_key": { "last_validated_date": "2025-03-08T09:27:44+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_for_no_pending_key_material": { + "last_validated_date": "2025-11-19T10:21:48+00:00", + "durations_in_seconds": { + "setup": 0.78, + "call": 0.39, + "teardown": 0.05, + "total": 1.22 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_updates_current_key_material_for_external_keys": { + "last_validated_date": "2025-11-09T16:21:58+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 61.39, + "teardown": 0.05, + "total": 62.4 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_disabled": { - "last_validated_date": "2025-03-12T19:05:50+00:00" + "last_validated_date": "2025-11-10T17:37:46+00:00", + "durations_in_seconds": { + "setup": 1.18, + "call": 6.38, + "teardown": 0.05, + "total": 7.61 + } }, "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_enabled": { "last_validated_date": "2025-03-12T19:07:01+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_on_demand_returns_arn_for_key_id": { + "last_validated_date": "2025-11-06T11:07:58+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 0.31, + "teardown": 0.05, + "total": 1.27 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_schedule_and_cancel_key_deletion": { "last_validated_date": "2024-04-11T15:52:36+00:00" }, diff --git a/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.snapshot.json b/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.snapshot.json index 618589334f8f8..44a2cb355001c 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.snapshot.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.py::test_adding_tags": { - "recorded-date": "19-05-2025, 09:32:18", + "recorded-date": "25-11-2025, 15:44:31", "recorded-content": { "event-source-mapping-tags": { "Tags": { diff --git a/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.validation.json b/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.validation.json index 2a6ef1af1c4db..b923c943582f4 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.validation.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.validation.json @@ -1,11 +1,11 @@ { "tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.py::test_adding_tags": { - "last_validated_date": "2025-05-19T09:33:12+00:00", + "last_validated_date": "2025-11-25T15:45:22+00:00", "durations_in_seconds": { - "setup": 0.54, - "call": 69.88, - "teardown": 54.76, - "total": 125.18 + "setup": 0.68, + "call": 103.05, + "teardown": 51.44, + "total": 155.17 } } } diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py index fed8e9c4a8723..102b626993f1e 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py @@ -219,11 +219,11 @@ def test_duplicate_event_source_mappings( )["TableDescription"]["LatestStreamArn"] # extra arguments for create_event_source_mapping calls - kwargs = dict( - StartingPosition="TRIM_HORIZON", - MaximumBatchingWindowInSeconds=1, - MaximumRetryAttempts=1, - ) + kwargs = { + "StartingPosition": "TRIM_HORIZON", + "MaximumBatchingWindowInSeconds": 1, + "MaximumRetryAttempts": 1, + } create_lambda_function( handler_file=TEST_LAMBDA_PYTHON_ECHO, @@ -309,7 +309,7 @@ def test_disabled_dynamodb_event_source_mapping( retry( check_expected_lambda_log_events_length, retries=10, - sleep=3, + sleep=5 if is_aws_cloud() else 1, function_name=function_name, expected_length=1, logs_client=aws_client.logs, @@ -371,12 +371,6 @@ def test_deletion_event_source_mapping_with_dynamodb( list_esm = aws_client.lambda_.list_event_source_mappings(EventSourceArn=latest_stream_arn) snapshot.match("list_event_source_mapping_result", list_esm) - # TODO re-record snapshot, now TableId is returned but new WarmThroughput property is not - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..TableDescription.TableId", - ], - ) @markers.aws.validated def test_dynamodb_event_source_mapping_with_sns_on_failure_destination_config( self, @@ -486,12 +480,6 @@ def verify_failure_received(): snapshot.match("failure_sns_message", failure_sns_message) - # TODO re-record snapshot, now TableId is returned but new WarmThroughput property is not - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..TableDescription.TableId", - ], - ) @markers.aws.validated def test_dynamodb_event_source_mapping_with_on_failure_destination_config( self, @@ -572,10 +560,8 @@ def verify_failure_received(): messages = retry(verify_failure_received, retries=15, sleep=sleep, sleep_before=5) snapshot.match("destination_queue_messages", messages) - # FIXME UpdateTable is not returning a WarmThroughput property @markers.snapshot.skip_snapshot_verify( paths=[ - "$..TableDescription.WarmThroughput", "$..requestContext.requestId", # TODO there is an extra uuid in the snapshot when run in CI on itest-ddb-v2-provider step, need to look why ], ) @@ -948,7 +934,6 @@ def test_dynamodb_invalid_event_filter( @markers.snapshot.skip_snapshot_verify( paths=[ - "$..TableDescription.TableId", "$..Records", # TODO Figure out why there is an extra log record ], ) @@ -1095,6 +1080,8 @@ def test_dynamodb_report_batch_item_failure_scenarios( ): snapshot.add_transformer(snapshot.transform.key_value("MD5OfBody")) snapshot.add_transformer(snapshot.transform.key_value("ReceiptHandle")) + snapshot.add_transformer(snapshot.transform.key_value("startSequenceNumber")) + snapshot.add_transformer(snapshot.transform.key_value("endSequenceNumber")) function_name = f"lambda_func-{short_uid()}" table_name = f"test-table-{short_uid()}" @@ -1149,11 +1136,14 @@ def verify_failure_received(): messages = retry(verify_failure_received, retries=15, sleep=sleep, sleep_before=5) snapshot.match("destination_queue_messages", messages) - events = get_lambda_log_events(function_name, logs_client=aws_client.logs) - # This will filter out exception messages being added to the log stream - invocation_events = [event for event in events if "Records" in event] - snapshot.match("dynamodb_events", invocation_events) + def _get_events(): + events = get_lambda_log_events(function_name, logs_client=aws_client.logs) + invocation_events = [event for event in events if "Records" in event] + assert len(invocation_events) == 4 + return invocation_events + + snapshot.match("dynamodb_events", retry(_get_events, retries=10)) @markers.aws.validated @pytest.mark.parametrize( diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.snapshot.json b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.snapshot.json index 709bfc346d2f0..a7c4133bd05f3 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.snapshot.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping": { - "recorded-date": "22-02-2025, 03:03:03", + "recorded-date": "25-11-2025, 15:47:40", "recorded-content": { "create-table-result": { "TableDescription": { @@ -100,7 +100,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_disabled_dynamodb_event_source_mapping": { - "recorded-date": "12-10-2024, 10:57:16", + "recorded-date": "25-11-2025, 17:53:07", "recorded-content": { "dynamodb_create_table_result": { "TableDescription": { @@ -199,7 +199,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_deletion_event_source_mapping_with_dynamodb": { - "recorded-date": "12-10-2024, 10:57:49", + "recorded-date": "25-11-2025, 15:49:36", "recorded-content": { "create_dynamodb_table_response": { "TableDescription": { @@ -302,7 +302,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_on_failure_destination_config": { - "recorded-date": "12-10-2024, 11:01:18", + "recorded-date": "25-11-2025, 15:53:08", "recorded-content": { "create_table_response": { "TableDescription": { @@ -376,7 +376,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "UPDATING" + "TableStatus": "UPDATING", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -451,7 +456,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[single-string]": { - "recorded-date": "12-10-2024, 11:21:25", + "recorded-date": "25-11-2025, 16:06:35", "recorded-content": { "exception_event_source_creation": { "Error": { @@ -468,7 +473,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[[{\"eventName\": [\"INSERT\"=123}]]": { - "recorded-date": "12-10-2024, 11:21:41", + "recorded-date": "25-11-2025, 16:06:50", "recorded-content": { "exception_event_source_creation": { "Error": { @@ -485,7 +490,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_duplicate_event_source_mappings": { - "recorded-date": "12-10-2024, 10:55:48", + "recorded-date": "25-11-2025, 15:47:54", "recorded-content": { "create-table-result": { "TableDescription": { @@ -564,7 +569,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[insert_same_entry_twice]": { - "recorded-date": "12-10-2024, 11:03:08", + "recorded-date": "25-11-2025, 15:56:13", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -708,7 +713,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_or_filter]": { - "recorded-date": "12-10-2024, 11:05:33", + "recorded-date": "25-11-2025, 15:57:25", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -1024,7 +1029,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_filter_type]": { - "recorded-date": "12-10-2024, 11:08:12", + "recorded-date": "25-11-2025, 15:59:22", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -1174,7 +1179,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_filter_type]": { - "recorded-date": "12-10-2024, 11:10:06", + "recorded-date": "25-11-2025, 16:01:13", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -1326,7 +1331,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_false_filter]": { - "recorded-date": "05-12-2024, 15:58:42", + "recorded-date": "25-11-2025, 16:02:27", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -1501,7 +1506,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[numeric_filter]": { - "recorded-date": "05-12-2024, 16:01:14", + "recorded-date": "25-11-2025, 16:04:16", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -1659,7 +1664,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[prefix_filter]": { - "recorded-date": "12-10-2024, 11:11:06", + "recorded-date": "25-11-2025, 16:05:14", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -1811,7 +1816,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[date_time_conversion]": { - "recorded-date": "12-10-2024, 11:20:10", + "recorded-date": "25-11-2025, 16:06:22", "recorded-content": { "table_creation_response": { "TableDescription": { @@ -1977,7 +1982,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_sns_on_failure_destination_config": { - "recorded-date": "12-10-2024, 10:59:12", + "recorded-date": "25-11-2025, 15:51:38", "recorded-content": { "create_table_response": { "TableDescription": { @@ -2051,7 +2056,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "UPDATING" + "TableStatus": "UPDATING", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -2123,7 +2133,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failures": { - "recorded-date": "12-10-2024, 11:25:25", + "recorded-date": "25-11-2025, 16:08:24", "recorded-content": { "create_table_response": { "TableDescription": { @@ -2197,7 +2207,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "UPDATING" + "TableStatus": "UPDATING", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -2698,7 +2713,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": { - "recorded-date": "12-10-2024, 11:30:34", + "recorded-date": "25-11-2025, 20:01:25", "recorded-content": { "create-table-result": { "TableDescription": { @@ -2902,7 +2917,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[null_item_identifier_failure]": { - "recorded-date": "12-10-2024, 11:33:20", + "recorded-date": "25-11-2025, 20:03:19", "recorded-content": { "create-table-result": { "TableDescription": { @@ -3106,7 +3121,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_failure]": { - "recorded-date": "12-10-2024, 11:35:08", + "recorded-date": "25-11-2025, 20:04:44", "recorded-content": { "create-table-result": { "TableDescription": { @@ -3310,7 +3325,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": { - "recorded-date": "14-10-2024, 21:33:18", + "recorded-date": "25-11-2025, 20:06:23", "recorded-content": { "create-table-result": { "TableDescription": { @@ -3514,7 +3529,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[unhandled_exception_in_function]": { - "recorded-date": "12-10-2024, 11:39:13", + "recorded-date": "25-11-2025, 20:08:17", "recorded-content": { "create-table-result": { "TableDescription": { @@ -3718,7 +3733,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_list_success]": { - "recorded-date": "12-10-2024, 11:40:57", + "recorded-date": "25-11-2025, 16:19:58", "recorded-content": { "create-table-result": { "TableDescription": { @@ -3792,7 +3807,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_success]": { - "recorded-date": "12-10-2024, 11:42:06", + "recorded-date": "25-11-2025, 16:21:35", "recorded-content": { "create-table-result": { "TableDescription": { @@ -3866,7 +3881,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_dict_success]": { - "recorded-date": "12-10-2024, 11:43:06", + "recorded-date": "25-11-2025, 16:23:20", "recorded-content": { "create-table-result": { "TableDescription": { @@ -3940,7 +3955,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_batch_item_failure_success]": { - "recorded-date": "12-10-2024, 11:44:06", + "recorded-date": "25-11-2025, 16:25:03", "recorded-content": { "create-table-result": { "TableDescription": { @@ -4014,7 +4029,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_batch_item_failure_success]": { - "recorded-date": "12-10-2024, 11:45:51", + "recorded-date": "25-11-2025, 16:26:14", "recorded-content": { "create-table-result": { "TableDescription": { @@ -4088,7 +4103,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": { - "recorded-date": "12-10-2024, 11:27:29", + "recorded-date": "25-11-2025, 20:00:14", "recorded-content": { "create-table-result": { "TableDescription": { @@ -4292,7 +4307,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_s3_on_failure_destination": { - "recorded-date": "03-01-2025, 16:42:26", + "recorded-date": "25-11-2025, 15:54:54", "recorded-content": { "create_table_response": { "TableDescription": { @@ -4461,7 +4476,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_esm_with_not_existing_dynamodb_stream": { - "recorded-date": "26-02-2025, 03:08:09", + "recorded-date": "25-11-2025, 15:45:36", "recorded-content": { "error": { "Error": { diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.validation.json b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.validation.json index 0bfbdbc8c52c6..ca93743bfff31 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.validation.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.validation.json @@ -1,95 +1,275 @@ { "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_deletion_event_source_mapping_with_dynamodb": { - "last_validated_date": "2024-10-12T10:57:32+00:00" + "last_validated_date": "2025-11-25T15:49:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.26, + "teardown": 4.32, + "total": 17.58 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_disabled_dynamodb_event_source_mapping": { - "last_validated_date": "2024-10-12T10:57:15+00:00" + "last_validated_date": "2025-11-25T17:53:08+00:00", + "durations_in_seconds": { + "setup": 11.58, + "call": 173.47, + "teardown": 1.62, + "total": 186.67 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_duplicate_event_source_mappings": { - "last_validated_date": "2024-10-12T10:55:43+00:00" + "last_validated_date": "2025-11-25T15:47:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 11.93, + "teardown": 2.17, + "total": 14.1 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_filter_type]": { - "last_validated_date": "2024-10-12T11:08:10+00:00" + "last_validated_date": "2025-11-25T15:59:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 115.45, + "teardown": 0.92, + "total": 116.37 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_multiple_filters]": { "last_validated_date": "2024-10-12T11:07:06+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_or_filter]": { - "last_validated_date": "2024-10-12T11:05:31+00:00" + "last_validated_date": "2025-11-25T15:57:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 71.88, + "teardown": 0.93, + "total": 72.81 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[date_time_conversion]": { - "last_validated_date": "2024-10-12T11:19:06+00:00" + "last_validated_date": "2025-11-25T16:06:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 67.38, + "teardown": 0.94, + "total": 68.32 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_false_filter]": { - "last_validated_date": "2024-12-05T15:58:41+00:00" + "last_validated_date": "2025-11-25T16:02:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 73.12, + "teardown": 0.94, + "total": 74.06 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_filter_type]": { - "last_validated_date": "2024-10-12T11:10:04+00:00" + "last_validated_date": "2025-11-25T16:01:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 110.57, + "teardown": 0.9, + "total": 111.47 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[insert_same_entry_twice]": { - "last_validated_date": "2024-10-12T11:03:06+00:00" + "last_validated_date": "2025-11-25T15:56:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 77.37, + "teardown": 0.91, + "total": 78.28 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[numeric_filter]": { - "last_validated_date": "2024-12-05T16:01:13+00:00" + "last_validated_date": "2025-11-25T16:04:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 107.3, + "teardown": 0.9, + "total": 108.2 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[prefix_filter]": { - "last_validated_date": "2024-10-12T11:11:04+00:00" + "last_validated_date": "2025-11-25T16:05:14+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 57.15, + "teardown": 0.96, + "total": 58.12 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping": { - "last_validated_date": "2025-02-22T03:03:01+00:00" + "last_validated_date": "2025-11-25T15:47:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 121.88, + "teardown": 1.83, + "total": 123.71 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_on_failure_destination_config": { - "last_validated_date": "2024-10-12T11:01:14+00:00" + "last_validated_date": "2025-11-25T15:53:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 87.98, + "teardown": 2.06, + "total": 90.04 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_s3_on_failure_destination": { - "last_validated_date": "2025-01-03T16:42:22+00:00" + "last_validated_date": "2025-11-25T15:54:54+00:00", + "durations_in_seconds": { + "setup": 0.71, + "call": 102.3, + "teardown": 2.95, + "total": 105.96 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_sns_on_failure_destination_config": { - "last_validated_date": "2024-10-12T10:59:07+00:00" + "last_validated_date": "2025-11-25T15:51:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 119.5, + "teardown": 3.1, + "total": 122.6 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[[{\"eventName\": [\"INSERT\"=123}]]": { - "last_validated_date": "2024-10-12T11:21:39+00:00" + "last_validated_date": "2025-11-25T16:06:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.98, + "teardown": 1.05, + "total": 15.03 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[single-string]": { - "last_validated_date": "2024-10-12T11:21:23+00:00" + "last_validated_date": "2025-11-25T16:06:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 11.83, + "teardown": 1.11, + "total": 12.94 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": { - "last_validated_date": "2024-10-12T11:30:32+00:00" + "last_validated_date": "2025-11-25T20:01:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 69.81, + "teardown": 0.84, + "total": 70.65 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_failure]": { - "last_validated_date": "2024-10-12T11:35:06+00:00" + "last_validated_date": "2025-11-25T20:04:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 83.59, + "teardown": 0.82, + "total": 84.41 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": { - "last_validated_date": "2024-10-14T21:33:15+00:00" + "last_validated_date": "2025-11-25T20:06:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 98.17, + "teardown": 0.89, + "total": 99.06 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": { - "last_validated_date": "2024-10-12T11:27:26+00:00" + "last_validated_date": "2025-11-25T20:00:14+00:00", + "durations_in_seconds": { + "setup": 10.94, + "call": 69.24, + "teardown": 0.82, + "total": 81.0 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[null_item_identifier_failure]": { - "last_validated_date": "2024-10-12T11:33:17+00:00" + "last_validated_date": "2025-11-25T20:03:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 113.33, + "teardown": 1.21, + "total": 114.54 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[unhandled_exception_in_function]": { - "last_validated_date": "2024-10-12T11:39:10+00:00" + "last_validated_date": "2025-11-25T20:08:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 112.91, + "teardown": 1.4, + "total": 114.31 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failures": { - "last_validated_date": "2024-10-12T11:25:21+00:00" + "last_validated_date": "2025-11-25T16:08:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 92.56, + "teardown": 1.75, + "total": 94.31 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_batch_item_failure_success]": { - "last_validated_date": "2024-10-12T11:44:04+00:00" + "last_validated_date": "2025-11-25T16:25:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 101.66, + "teardown": 0.95, + "total": 102.61 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_dict_success]": { - "last_validated_date": "2024-10-12T11:43:04+00:00" + "last_validated_date": "2025-11-25T16:23:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 104.0, + "teardown": 0.91, + "total": 104.91 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_list_success]": { - "last_validated_date": "2024-10-12T11:40:54+00:00" + "last_validated_date": "2025-11-25T16:19:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 128.73, + "teardown": 1.21, + "total": 129.94 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_batch_item_failure_success]": { - "last_validated_date": "2024-10-12T11:45:49+00:00" + "last_validated_date": "2025-11-25T16:26:14+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 70.12, + "teardown": 0.91, + "total": 71.04 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_success]": { - "last_validated_date": "2024-10-12T11:42:04+00:00" + "last_validated_date": "2025-11-25T16:21:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 96.77, + "teardown": 0.92, + "total": 97.69 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_esm_with_not_existing_dynamodb_stream": { - "last_validated_date": "2025-02-26T03:08:08+00:00" + "last_validated_date": "2025-11-25T15:45:36+00:00", + "durations_in_seconds": { + "setup": 10.88, + "call": 2.16, + "teardown": 0.8, + "total": 13.84 + } } } diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py index 27906cb93f71d..014fff29328d4 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py @@ -378,6 +378,10 @@ def test_kinesis_event_source_trim_horizon( stream_summary = aws_client.kinesis.describe_stream_summary(StreamName=stream_name) assert stream_summary["StreamDescriptionSummary"]["OpenShardCount"] == 1 + if is_aws_cloud(): + # 😿 + time.sleep(5) + # insert some records before event source mapping created for i in range(num_batches - 1): aws_client.kinesis.put_records( @@ -652,13 +656,22 @@ def verify_failure_received(): sqs_payload = retry(verify_failure_received, retries=15, sleep=sleep, sleep_before=5) snapshot.match("sqs_payload", sqs_payload) - batched_records = get_lambda_log_events(function_name, logs_client=aws_client.logs) - flattened_records = [ - record for batch in batched_records for record in batch.get("Records", []) - ] - sorted_records = sorted(flattened_records, key=lambda item: item["kinesis"]["partitionKey"]) + def _verify_messages_received(): + events = get_lambda_log_events(function_name, logs_client=aws_client.logs) + + # This will filter out exception messages being added to the log stream + batched_records = [event for event in events if "Records" in event] + assert len(batched_records) >= 5 + flattened_records = [ + record for batch in batched_records for record in batch.get("Records", []) + ] + sorted_records = sorted( + flattened_records, key=lambda item: item["kinesis"]["partitionKey"] + ) - snapshot.match("kinesis_records", {"Records": sorted_records}) + snapshot.match("kinesis_records", {"Records": sorted_records}) + + retry(_verify_messages_received) @markers.aws.validated @pytest.mark.parametrize( @@ -753,11 +766,15 @@ def verify_failure_received(): sqs_payload = retry(verify_failure_received, retries=15, sleep=sleep, sleep_before=5) snapshot.match("sqs_payload", sqs_payload) - events = get_lambda_log_events(function_name, logs_client=aws_client.logs) + def _get_events(): + events = get_lambda_log_events(function_name, logs_client=aws_client.logs) - # This will filter out exception messages being added to the log stream - invocation_events = [event for event in events if "Records" in event] - snapshot.match("kinesis_events", invocation_events) + # This will filter out exception messages being added to the log stream + invocation_events = [event for event in events if "Records" in event] + assert len(invocation_events) == 3 + return invocation_events + + snapshot.match("kinesis_events", retry(_get_events)) @markers.aws.validated def test_kinesis_event_source_mapping_with_sns_on_failure_destination_config( @@ -1078,6 +1095,7 @@ def _verify_messages_received(): pytest.param(60 if is_aws_cloud() else 5, 0, id="expire-before-ingestion"), ], ) + @markers.requires_in_process def test_kinesis_maximum_record_age_exceeded( self, create_lambda_function, @@ -1178,6 +1196,7 @@ def _verify_failure_received(): "$..Messages..MessageId", # Skip while no requestContext generated in StreamPoller due to transformation issues ] ) + @markers.requires_in_process def test_kinesis_maximum_record_age_exceeded_discard_records( self, create_lambda_function, @@ -1229,21 +1248,17 @@ def _patched_stream_parameters(self): "StreamDescription" ]["StreamARN"] - aws_client.kinesis.put_record( - Data="stream-data", - PartitionKey="test", - StreamName=stream_name, + retry( + lambda: aws_client.kinesis.put_record( + Data="stream-data", + PartitionKey="test", + StreamName=stream_name, + ) ) # Ensure that the first record has expired time.sleep(wait_before_processing) - # The first record in the batch will have expired with the remaining batch not exceeding any age-limits. - aws_client.kinesis.put_records( - Records=[{"Data": f"stream-data-{i + 1}", "PartitionKey": "test"} for i in range(5)], - StreamName=stream_name, - ) - destination_queue_url = sqs_create_queue() create_lambda_function( func_name=function_name, @@ -1258,6 +1273,12 @@ def _patched_stream_parameters(self): dead_letter_queue_arn = sqs_get_queue_arn(dead_letter_queue) destination_config = {"OnFailure": {"Destination": dead_letter_queue_arn}} + # The first record in the batch will have expired with the remaining batch not exceeding any age-limits. + aws_client.kinesis.put_records( + Records=[{"Data": f"stream-data-{i + 1}", "PartitionKey": "test"} for i in range(5)], + StreamName=stream_name, + ) + create_event_source_mapping_response = create_event_source_mapping( FunctionName=function_name, BatchSize=10, diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.snapshot.json b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.snapshot.json index 809b9f0d539cd..67bc897ea781d 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.snapshot.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping": { - "recorded-date": "12-10-2024, 11:47:16", + "recorded-date": "25-11-2025, 16:27:42", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 100, @@ -195,7 +195,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_async_invocation": { - "recorded-date": "11-12-2024, 09:54:54", + "recorded-date": "25-11-2025, 16:30:35", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -562,7 +562,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_trim_horizon": { - "recorded-date": "12-10-2024, 11:50:52", + "recorded-date": "25-11-2025, 17:33:31", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -1096,7 +1096,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_disable_kinesis_event_source_mapping": { - "recorded-date": "12-10-2024, 11:54:22", + "recorded-date": "25-11-2025, 16:34:05", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -1293,7 +1293,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_on_failure_destination_config": { - "recorded-date": "12-10-2024, 12:29:14", + "recorded-date": "25-11-2025, 16:35:32", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -1363,7 +1363,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisEventFiltering::test_kinesis_event_filtering_json_pattern": { - "recorded-date": "12-10-2024, 13:31:54", + "recorded-date": "25-11-2025, 17:06:08", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -1447,7 +1447,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_duplicate_event_source_mappings": { - "recorded-date": "12-10-2024, 11:48:49", + "recorded-date": "25-11-2025, 16:29:02", "recorded-content": { "create": { "BatchSize": 100, @@ -1490,7 +1490,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping_multiple_lambdas_single_kinesis_event_stream": { - "recorded-date": "12-10-2024, 13:58:19", + "recorded-date": "25-11-2025, 17:33:15", "recorded-content": { "create_event_source_mapping_response-a": { "BatchSize": 100, @@ -1587,7 +1587,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_sns_on_failure_destination_config": { - "recorded-date": "12-10-2024, 13:17:57", + "recorded-date": "25-11-2025, 16:48:33", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -1654,7 +1654,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failures": { - "recorded-date": "12-10-2024, 14:17:06", + "recorded-date": "25-11-2025, 19:03:18", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 3, @@ -1890,7 +1890,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_list_success]": { - "recorded-date": "12-10-2024, 13:21:25", + "recorded-date": "25-11-2025, 16:52:37", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -1945,7 +1945,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_success]": { - "recorded-date": "12-10-2024, 13:23:15", + "recorded-date": "25-11-2025, 16:53:51", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2000,7 +2000,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_dict_success]": { - "recorded-date": "12-10-2024, 13:25:13", + "recorded-date": "25-11-2025, 16:55:22", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2055,7 +2055,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_batch_item_failure_success]": { - "recorded-date": "12-10-2024, 13:27:12", + "recorded-date": "25-11-2025, 16:56:44", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2110,7 +2110,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_batch_item_failure_success]": { - "recorded-date": "12-10-2024, 13:28:05", + "recorded-date": "25-11-2025, 16:58:22", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2165,7 +2165,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": { - "recorded-date": "12-10-2024, 13:08:09", + "recorded-date": "25-11-2025, 21:22:47", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2299,7 +2299,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[null_item_identifier_failure]": { - "recorded-date": "12-10-2024, 13:10:20", + "recorded-date": "25-11-2025, 21:24:21", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2433,7 +2433,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_failure]": { - "recorded-date": "12-10-2024, 13:13:18", + "recorded-date": "25-11-2025, 21:25:25", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2567,7 +2567,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": { - "recorded-date": "12-10-2024, 13:14:13", + "recorded-date": "25-11-2025, 21:26:14", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2701,7 +2701,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[unhandled_exception_in_function]": { - "recorded-date": "14-10-2024, 18:10:16", + "recorded-date": "25-11-2025, 21:27:32", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2835,7 +2835,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": { - "recorded-date": "12-10-2024, 13:06:13", + "recorded-date": "25-11-2025, 21:21:12", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -2969,7 +2969,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_string_success]": { - "recorded-date": "12-10-2024, 13:19:37", + "recorded-date": "25-11-2025, 16:51:42", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -3024,7 +3024,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_empty_provided": { - "recorded-date": "11-10-2024, 11:04:55", + "recorded-date": "25-11-2025, 17:03:32", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -3081,7 +3081,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_s3_on_failure_destination": { - "recorded-date": "03-01-2025, 14:50:27", + "recorded-date": "25-11-2025, 16:50:20", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -3158,7 +3158,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_esm_with_not_existing_kinesis_stream": { - "recorded-date": "26-02-2025, 03:05:30", + "recorded-date": "25-11-2025, 16:26:16", "recorded-content": { "error": { "Error": { @@ -3175,7 +3175,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-while-retrying]": { - "recorded-date": "13-04-2025, 15:00:55", + "recorded-date": "25-11-2025, 16:59:40", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -3213,7 +3213,7 @@ "requestId": "", "functionArn": "arn::lambda::111111111111:function:", "condition": "RecordAgeExceeded", - "approximateInvokeCount": 1 + "approximateInvokeCount": 8 }, "version": "1.0", "timestamp": "", @@ -3240,7 +3240,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-before-ingestion]": { - "recorded-date": "13-04-2025, 16:29:29", + "recorded-date": "25-11-2025, 17:01:42", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 1, @@ -3305,7 +3305,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded_discard_records": { - "recorded-date": "13-04-2025, 17:05:16", + "recorded-date": "25-11-2025, 18:15:46", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.validation.json b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.validation.json index 4f3d4284e0547..76f6d770b45fa 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.validation.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.validation.json @@ -1,86 +1,260 @@ { "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisEventFiltering::test_kinesis_event_filtering_json_pattern": { - "last_validated_date": "2024-12-13T14:48:09+00:00" + "last_validated_date": "2025-11-25T17:06:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 153.83, + "teardown": 2.61, + "total": 156.44 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping": { - "last_validated_date": "2024-12-13T14:01:07+00:00" + "last_validated_date": "2025-11-25T16:27:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 84.38, + "teardown": 1.2, + "total": 85.58 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping_multiple_lambdas_single_kinesis_event_stream": { - "last_validated_date": "2024-12-13T14:02:48+00:00" + "last_validated_date": "2025-11-25T17:33:16+00:00", + "durations_in_seconds": { + "setup": 11.22, + "call": 92.09, + "teardown": 2.12, + "total": 105.43 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_disable_kinesis_event_source_mapping": { - "last_validated_date": "2024-12-13T14:10:20+00:00" + "last_validated_date": "2025-11-25T16:34:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 197.42, + "teardown": 1.38, + "total": 198.8 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_duplicate_event_source_mappings": { - "last_validated_date": "2024-12-13T14:03:01+00:00" + "last_validated_date": "2025-11-25T16:29:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.37, + "teardown": 2.02, + "total": 11.39 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_esm_with_not_existing_kinesis_stream": { - "last_validated_date": "2025-02-26T03:05:29+00:00" + "last_validated_date": "2025-11-25T16:26:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.18, + "teardown": 0.39, + "total": 2.57 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_empty_provided": { - "last_validated_date": "2024-12-13T14:45:29+00:00" + "last_validated_date": "2025-11-25T17:03:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 103.36, + "teardown": 1.17, + "total": 104.53 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_async_invocation": { - "last_validated_date": "2024-12-13T14:04:46+00:00" + "last_validated_date": "2025-11-25T16:30:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 92.33, + "teardown": 1.15, + "total": 93.48 + } + }, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_on_failure_destination_config": { + "last_validated_date": "2025-11-25T16:35:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 84.61, + "teardown": 2.35, + "total": 86.96 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_s3_on_failure_destination": { - "last_validated_date": "2025-01-03T14:50:23+00:00" + "last_validated_date": "2025-11-25T16:50:20+00:00", + "durations_in_seconds": { + "setup": 0.78, + "call": 103.02, + "teardown": 3.28, + "total": 107.08 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_sns_on_failure_destination_config": { - "last_validated_date": "2024-12-13T14:35:43+00:00" + "last_validated_date": "2025-11-25T16:48:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 107.83, + "teardown": 3.07, + "total": 110.9 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_trim_horizon": { - "last_validated_date": "2024-12-13T14:06:49+00:00" + "last_validated_date": "2025-11-25T17:33:32+00:00", + "durations_in_seconds": { + "setup": 11.15, + "call": 95.7, + "teardown": 1.63, + "total": 108.48 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded": { "last_validated_date": "2025-04-13T15:57:25+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-before-ingestion]": { - "last_validated_date": "2025-04-13T16:29:25+00:00" + "last_validated_date": "2025-11-25T17:01:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 119.95, + "teardown": 1.62, + "total": 121.57 + } + }, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-while-retrying]": { + "last_validated_date": "2025-11-25T16:59:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 76.3, + "teardown": 1.69, + "total": 77.99 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-with-mixed-arrival-batch]": { "last_validated_date": "2025-04-13T16:39:43+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded_discard_records": { - "last_validated_date": "2025-04-23T21:42:09+00:00" + "last_validated_date": "2025-11-25T18:15:47+00:00", + "durations_in_seconds": { + "setup": 11.3, + "call": 161.14, + "teardown": 2.18, + "total": 174.62 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": { - "last_validated_date": "2024-12-13T14:23:18+00:00" + "last_validated_date": "2025-11-25T21:22:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 93.51, + "teardown": 1.0, + "total": 94.51 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_failure]": { - "last_validated_date": "2024-12-13T14:27:36+00:00" + "last_validated_date": "2025-11-25T21:25:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 62.92, + "teardown": 1.0, + "total": 63.92 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": { - "last_validated_date": "2024-12-13T14:31:32+00:00" + "last_validated_date": "2025-11-25T21:26:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 47.63, + "teardown": 1.07, + "total": 48.7 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": { - "last_validated_date": "2024-12-13T14:20:08+00:00" + "last_validated_date": "2025-11-25T21:21:12+00:00", + "durations_in_seconds": { + "setup": 11.0, + "call": 78.58, + "teardown": 1.0, + "total": 90.58 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[null_item_identifier_failure]": { - "last_validated_date": "2024-12-13T14:25:26+00:00" + "last_validated_date": "2025-11-25T21:24:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 93.26, + "teardown": 1.03, + "total": 94.29 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[unhandled_exception_in_function]": { - "last_validated_date": "2024-12-13T14:34:41+00:00" + "last_validated_date": "2025-11-25T21:27:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 77.2, + "teardown": 1.77, + "total": 78.97 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failures": { - "last_validated_date": "2024-12-13T14:18:13+00:00" + "last_validated_date": "2025-11-25T19:03:19+00:00", + "durations_in_seconds": { + "setup": 11.23, + "call": 118.12, + "teardown": 2.25, + "total": 131.6 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_batch_item_failure_success]": { - "last_validated_date": "2024-12-13T14:42:49+00:00" + "last_validated_date": "2025-11-25T16:56:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 80.81, + "teardown": 1.14, + "total": 81.95 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_dict_success]": { - "last_validated_date": "2024-12-13T14:41:30+00:00" + "last_validated_date": "2025-11-25T16:55:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 89.38, + "teardown": 1.2, + "total": 90.58 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_list_success]": { - "last_validated_date": "2024-12-13T14:38:21+00:00" + "last_validated_date": "2025-11-25T16:52:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 54.19, + "teardown": 1.17, + "total": 55.36 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_string_success]": { - "last_validated_date": "2024-12-13T14:37:20+00:00" + "last_validated_date": "2025-11-25T16:51:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 80.53, + "teardown": 1.21, + "total": 81.74 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_batch_item_failure_success]": { - "last_validated_date": "2024-12-13T14:44:14+00:00" + "last_validated_date": "2025-11-25T16:58:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 97.16, + "teardown": 1.13, + "total": 98.29 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_success]": { - "last_validated_date": "2024-12-13T14:39:47+00:00" + "last_validated_date": "2025-11-25T16:53:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 73.11, + "teardown": 1.21, + "total": 74.32 + } } } diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.snapshot.json b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.snapshot.json index cae128ced9d5c..905e1db02b77b 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.snapshot.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_failing_lambda_retries_after_visibility_timeout": { - "recorded-date": "12-10-2024, 13:32:32", + "recorded-date": "25-11-2025, 17:06:47", "recorded-content": { "get_destination_queue_url": { "QueueUrl": "", @@ -100,7 +100,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_redrive_policy_with_failing_lambda": { - "recorded-date": "12-10-2024, 13:33:30", + "recorded-date": "25-11-2025, 17:07:40", "recorded-content": { "get_destination_queue_url": { "QueueUrl": "", @@ -201,7 +201,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_sqs_queue_as_lambda_dead_letter_queue": { - "recorded-date": "12-10-2024, 13:33:41", + "recorded-date": "25-11-2025, 17:07:49", "recorded-content": { "lambda-response-dlq-config": { "TargetArn": "arn::sqs::111111111111:" @@ -531,7 +531,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_on_lambda_error": { - "recorded-date": "12-10-2024, 13:34:40", + "recorded-date": "25-11-2025, 17:08:10", "recorded-content": { "dlq_messages": [ { @@ -553,7 +553,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_invalid_result_json_batch_fails": { - "recorded-date": "12-10-2024, 13:35:12", + "recorded-date": "25-11-2025, 17:08:43", "recorded-content": { "get_destination_queue_url": { "QueueUrl": "", @@ -666,7 +666,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_empty_json_batch_succeeds": { - "recorded-date": "12-10-2024, 13:35:43", + "recorded-date": "25-11-2025, 17:09:13", "recorded-content": { "get_destination_queue_url": { "QueueUrl": "", @@ -714,7 +714,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_event_source_mapping_default_batch_size": { - "recorded-date": "12-10-2024, 13:37:20", + "recorded-date": "25-11-2025, 17:10:49", "recorded-content": { "create-event-source-mapping": { "BatchSize": 10, @@ -759,7 +759,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping": { - "recorded-date": "12-10-2024, 13:38:02", + "recorded-date": "25-11-2025, 17:11:30", "recorded-content": { "create-event-source-mapping-response": { "BatchSize": 10, @@ -1304,7 +1304,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[None]": { - "recorded-date": "12-10-2024, 13:43:37", + "recorded-date": "25-11-2025, 17:22:25", "recorded-content": { "create_event_source_mapping_exception": { "Error": { @@ -1321,7 +1321,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[simple string]": { - "recorded-date": "12-10-2024, 13:43:42", + "recorded-date": "25-11-2025, 17:22:28", "recorded-content": { "create_event_source_mapping_exception": { "Error": { @@ -1338,7 +1338,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter2]": { - "recorded-date": "12-10-2024, 13:43:47", + "recorded-date": "25-11-2025, 17:22:31", "recorded-content": { "create_event_source_mapping_exception": { "Error": { @@ -1355,7 +1355,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter3]": { - "recorded-date": "12-10-2024, 13:43:52", + "recorded-date": "25-11-2025, 17:22:34", "recorded-content": { "create_event_source_mapping_exception": { "Error": { @@ -1372,7 +1372,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_message_body_and_attributes_passed_correctly": { - "recorded-date": "12-10-2024, 13:32:53", + "recorded-date": "25-11-2025, 17:07:07", "recorded-content": { "get_destination_queue_url": { "QueueUrl": "", @@ -1418,8 +1418,8 @@ "dataType": "String" } }, - "md5OfBody": "", "md5OfMessageAttributes": "d25a6aea97eb8f585bfa92d314504a92", + "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -1440,7 +1440,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_update": { - "recorded-date": "12-10-2024, 13:45:45", + "recorded-date": "25-11-2025, 23:41:34", "recorded-content": { "create-event-source-mapping-response": { "BatchSize": 10, @@ -1578,7 +1578,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_duplicate_event_source_mappings": { - "recorded-date": "12-10-2024, 13:45:56", + "recorded-date": "25-11-2025, 17:24:03", "recorded-content": { "create": { "BatchSize": 10, @@ -1611,7 +1611,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_fifo_message_group_parallelism": { - "recorded-date": "12-10-2024, 13:37:01", + "recorded-date": "25-11-2025, 17:10:34", "recorded-content": { "create_esm_disabled": { "BatchSize": 1, @@ -1664,7 +1664,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[15]": { - "recorded-date": "11-12-2024, 13:42:57", + "recorded-date": "25-11-2025, 17:12:32", "recorded-content": { "create-event-source-mapping-response": { "BatchSize": 15, @@ -1785,8 +1785,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfBody": "", "md5OfMessageAttributes": null, + "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -1819,8 +1819,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfBody": "", "md5OfMessageAttributes": null, + "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -1836,8 +1836,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfBody": "", "md5OfMessageAttributes": null, + "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -1853,8 +1853,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfBody": "", "md5OfMessageAttributes": null, + "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -1921,8 +1921,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -2023,8 +2023,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfBody": "", "md5OfMessageAttributes": null, + "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -2033,7 +2033,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_reserved_concurrency": { - "recorded-date": "25-02-2025, 16:35:01", + "recorded-date": "25-11-2025, 17:15:56", "recorded-content": { "put_concurrency_resp": { "ReservedConcurrentExecutions": 2, @@ -2579,7 +2579,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[100]": { - "recorded-date": "11-12-2024, 13:43:49", + "recorded-date": "25-11-2025, 17:13:27", "recorded-content": { "create-event-source-mapping-response": { "BatchSize": 100, @@ -2700,8 +2700,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfBody": "", "md5OfMessageAttributes": null, + "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -2717,8 +2717,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -2751,8 +2751,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -2768,8 +2768,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -2948,7 +2948,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[1000]": { - "recorded-date": "11-12-2024, 13:44:40", + "recorded-date": "25-11-2025, 17:14:22", "recorded-content": { "create-event-source-mapping-response": { "BatchSize": 1000, @@ -3120,8 +3120,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3154,8 +3154,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3205,8 +3205,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3273,8 +3273,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3307,8 +3307,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3317,7 +3317,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[10000]": { - "recorded-date": "11-12-2024, 13:45:32", + "recorded-date": "25-11-2025, 17:15:08", "recorded-content": { "create-event-source-mapping-response": { "BatchSize": 10000, @@ -3438,8 +3438,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3489,8 +3489,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3608,8 +3608,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3625,8 +3625,8 @@ "ApproximateFirstReceiveTimestamp": "" }, "messageAttributes": {}, - "md5OfMessageAttributes": null, "md5OfBody": "", + "md5OfMessageAttributes": null, "eventSource": "aws:sqs", "eventSourceARN": "arn::sqs::111111111111:", "awsRegion": "" @@ -3934,7 +3934,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-bigger]": { - "recorded-date": "10-12-2024, 19:37:10", + "recorded-date": "25-11-2025, 17:19:29", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -3998,7 +3998,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-smaller]": { - "recorded-date": "10-12-2024, 19:37:55", + "recorded-date": "25-11-2025, 17:20:06", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4062,7 +4062,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-range]": { - "recorded-date": "10-12-2024, 19:38:28", + "recorded-date": "25-11-2025, 17:20:56", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4189,7 +4189,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[single]": { - "recorded-date": "10-12-2024, 19:34:32", + "recorded-date": "25-11-2025, 17:16:37", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4248,7 +4248,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[or]": { - "recorded-date": "10-12-2024, 19:35:19", + "recorded-date": "25-11-2025, 17:17:10", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4308,7 +4308,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[and]": { - "recorded-date": "10-12-2024, 19:35:54", + "recorded-date": "25-11-2025, 17:17:56", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4372,7 +4372,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[exists]": { - "recorded-date": "10-12-2024, 19:36:24", + "recorded-date": "25-11-2025, 17:18:46", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4441,7 +4441,7 @@ "recorded-content": {} }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[valid-json-filter]": { - "recorded-date": "10-12-2024, 19:40:28", + "recorded-date": "25-11-2025, 17:22:22", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4500,7 +4500,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[prefix]": { - "recorded-date": "10-12-2024, 19:47:22", + "recorded-date": "25-11-2025, 17:21:46", "recorded-content": { "create_event_source_mapping_response": { "BatchSize": 10, @@ -4561,7 +4561,7 @@ } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_esm_with_not_existing_sqs_queue": { - "recorded-date": "26-02-2025, 03:01:33", + "recorded-date": "25-11-2025, 17:06:11", "recorded-content": { "error": { "Error": { diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.validation.json b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.validation.json index 6c608cee264c9..437e272f9130d 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.validation.json +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.validation.json @@ -1,21 +1,45 @@ { "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_duplicate_event_source_mappings": { - "last_validated_date": "2024-10-12T13:45:52+00:00" + "last_validated_date": "2025-11-25T17:24:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.62, + "teardown": 2.5, + "total": 7.12 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_event_source_mapping_default_batch_size": { - "last_validated_date": "2024-10-12T13:37:18+00:00" + "last_validated_date": "2025-11-25T17:10:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.06, + "teardown": 1.25, + "total": 15.31 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[and-filter]": { "last_validated_date": "2024-12-10T17:37:02+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[and]": { - "last_validated_date": "2024-12-10T19:35:53+00:00" + "last_validated_date": "2025-11-25T17:17:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 44.5, + "teardown": 0.78, + "total": 45.28 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[exists-filter]": { "last_validated_date": "2024-12-10T17:37:40+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[exists]": { - "last_validated_date": "2024-12-10T19:36:23+00:00" + "last_validated_date": "2025-11-25T17:18:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 48.86, + "teardown": 1.12, + "total": 49.98 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[filter0-item_matching0-item_not_matching0]": { "last_validated_date": "2024-10-12T13:38:37+00:00" @@ -42,96 +66,258 @@ "last_validated_date": "2024-10-12T13:43:31+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-bigger]": { - "last_validated_date": "2024-12-10T19:37:09+00:00" + "last_validated_date": "2025-11-25T17:19:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 43.09, + "teardown": 0.73, + "total": 43.82 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-prefix]": { "last_validated_date": "2024-12-10T17:40:32+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-range]": { - "last_validated_date": "2024-12-10T19:38:27+00:00" + "last_validated_date": "2025-11-25T17:20:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 48.79, + "teardown": 0.78, + "total": 49.57 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-smaller]": { - "last_validated_date": "2024-12-10T19:37:54+00:00" + "last_validated_date": "2025-11-25T17:20:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 36.08, + "teardown": 0.76, + "total": 36.84 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[or-filter]": { "last_validated_date": "2024-12-10T17:36:19+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[or]": { - "last_validated_date": "2024-12-10T19:35:18+00:00" + "last_validated_date": "2025-11-25T17:17:10+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 32.75, + "teardown": 0.78, + "total": 33.54 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[prefix]": { - "last_validated_date": "2024-12-10T19:47:21+00:00" + "last_validated_date": "2025-11-25T17:21:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 48.97, + "teardown": 0.79, + "total": 49.76 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[single-filter]": { "last_validated_date": "2024-12-10T17:35:39+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[single]": { - "last_validated_date": "2024-12-10T19:34:31+00:00" + "last_validated_date": "2025-11-25T17:16:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 40.12, + "teardown": 0.78, + "total": 40.9 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[valid-json-filter]": { - "last_validated_date": "2024-12-10T19:40:27+00:00" + "last_validated_date": "2025-11-25T17:22:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 35.36, + "teardown": 0.84, + "total": 36.2 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping": { - "last_validated_date": "2024-11-25T15:46:54+00:00" + "last_validated_date": "2025-11-25T17:11:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 40.34, + "teardown": 0.77, + "total": 41.11 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[10000]": { - "last_validated_date": "2024-12-11T13:45:31+00:00" + "last_validated_date": "2025-11-25T17:15:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 44.35, + "teardown": 1.29, + "total": 45.64 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[1000]": { - "last_validated_date": "2024-12-11T13:44:38+00:00" + "last_validated_date": "2025-11-25T17:14:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 53.64, + "teardown": 1.26, + "total": 54.9 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[100]": { - "last_validated_date": "2024-12-11T13:43:48+00:00" + "last_validated_date": "2025-11-25T17:13:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 53.8, + "teardown": 1.28, + "total": 55.08 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[15]": { - "last_validated_date": "2024-12-11T13:42:55+00:00" + "last_validated_date": "2025-11-25T17:12:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 60.49, + "teardown": 1.49, + "total": 61.98 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_reserved_concurrency": { - "last_validated_date": "2025-02-25T16:34:59+00:00" + "last_validated_date": "2025-11-25T17:15:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 46.6, + "teardown": 1.32, + "total": 47.92 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_update": { - "last_validated_date": "2024-10-12T13:45:43+00:00" + "last_validated_date": "2025-11-25T23:41:35+00:00", + "durations_in_seconds": { + "setup": 11.14, + "call": 93.47, + "teardown": 1.32, + "total": 105.93 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[None]": { - "last_validated_date": "2024-10-12T13:43:35+00:00" + "last_validated_date": "2025-11-25T17:22:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.29, + "teardown": 0.58, + "total": 2.87 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter2]": { - "last_validated_date": "2024-10-12T13:43:45+00:00" + "last_validated_date": "2025-11-25T17:22:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.29, + "teardown": 0.89, + "total": 3.18 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter3]": { - "last_validated_date": "2024-10-12T13:43:50+00:00" + "last_validated_date": "2025-11-25T17:22:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.26, + "teardown": 0.88, + "total": 3.14 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[simple string]": { - "last_validated_date": "2024-10-12T13:43:40+00:00" + "last_validated_date": "2025-11-25T17:22:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.28, + "teardown": 0.94, + "total": 3.22 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_esm_with_not_existing_sqs_queue": { - "last_validated_date": "2025-02-26T03:01:32+00:00" + "last_validated_date": "2025-11-25T17:06:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.14, + "teardown": 0.39, + "total": 2.53 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_failing_lambda_retries_after_visibility_timeout": { - "last_validated_date": "2024-11-25T12:12:47+00:00" + "last_validated_date": "2025-11-25T17:06:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 34.6, + "teardown": 1.4, + "total": 36.0 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_fifo_message_group_parallelism": { - "last_validated_date": "2024-10-12T13:37:00+00:00" + "last_validated_date": "2025-11-25T17:10:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 80.67, + "teardown": 0.62, + "total": 81.29 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_message_body_and_attributes_passed_correctly": { - "last_validated_date": "2024-10-12T13:32:50+00:00" + "last_validated_date": "2025-11-25T17:07:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.79, + "teardown": 1.4, + "total": 20.19 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_redrive_policy_with_failing_lambda": { - "last_validated_date": "2024-10-12T13:33:27+00:00" + "last_validated_date": "2025-11-25T17:07:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 31.89, + "teardown": 1.4, + "total": 33.29 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures": { "last_validated_date": "2025-03-03T11:31:14+00:00" }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_empty_json_batch_succeeds": { - "last_validated_date": "2024-10-12T13:35:40+00:00" + "last_validated_date": "2025-11-25T17:09:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.44, + "teardown": 1.45, + "total": 29.89 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_invalid_result_json_batch_fails": { - "last_validated_date": "2024-10-12T13:35:09+00:00" + "last_validated_date": "2025-11-25T17:08:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 31.06, + "teardown": 1.43, + "total": 32.49 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_on_lambda_error": { - "last_validated_date": "2024-10-12T13:34:37+00:00" + "last_validated_date": "2025-11-25T17:08:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.89, + "teardown": 1.27, + "total": 21.16 + } }, "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_sqs_queue_as_lambda_dead_letter_queue": { - "last_validated_date": "2024-10-12T13:33:39+00:00" + "last_validated_date": "2025-11-25T17:07:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.62, + "teardown": 1.19, + "total": 8.81 + } } } diff --git a/tests/aws/services/lambda_/functions/common/echo/dotnet/Makefile b/tests/aws/services/lambda_/functions/common/echo/dotnet/Makefile index 31704c534390c..58cab0cd7beb5 100644 --- a/tests/aws/services/lambda_/functions/common/echo/dotnet/Makefile +++ b/tests/aws/services/lambda_/functions/common/echo/dotnet/Makefile @@ -9,13 +9,13 @@ endif FUNCTION_ARCHITECTURE ?= $(ARCHITECTURE) # Target Dotnet framework version -FRAMEWORK ?= net8.0 +FRAMEWORK ?= net10.0 # Workaround for a Docker race condition causing an I/O error upon zipping to /out/handler.zip if # two builds are executed in short succession. Example: `make -C dotnet build && make -C dotnet6 build` BUILD_DIR ?= build-$(FRAMEWORK) -# https://gallery.ecr.aws/sam/build-dotnet8 -IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 +# https://gallery.ecr.aws/sam/build-dotnet10 +IMAGE ?= public.ecr.aws/sam/build-dotnet10:1.151.0 # Emulated builds with Dotnet8 are currently (2024-03-19) broken as discussed in many issues: # https://github.com/NuGet/Home/issues/12227 @@ -27,10 +27,10 @@ IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 build: mkdir -p $(BUILD_DIR) && \ - docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ + docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ cp $(BUILD_DIR)/handler.zip handler.zip clean: - $(RM) -r $(BUILD_DIR) handler.zip + $(RM) -rf $(BUILD_DIR) handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/echo/dotnet/src/dotnet.csproj b/tests/aws/services/lambda_/functions/common/echo/dotnet/src/dotnet.csproj index 1bd07d2e45a5a..980d160b3d02a 100644 --- a/tests/aws/services/lambda_/functions/common/echo/dotnet/src/dotnet.csproj +++ b/tests/aws/services/lambda_/functions/common/echo/dotnet/src/dotnet.csproj @@ -1,13 +1,13 @@  - net6.0;net8.0 + net6.0;net8.0;net10.0 true Lambda - + - + diff --git a/tests/aws/services/lambda_/functions/common/echo/dotnet10/Makefile b/tests/aws/services/lambda_/functions/common/echo/dotnet10/Makefile new file mode 100644 index 0000000000000..adf6948db981c --- /dev/null +++ b/tests/aws/services/lambda_/functions/common/echo/dotnet10/Makefile @@ -0,0 +1,20 @@ +UNAME := $(shell uname -m) +ifeq ($(UNAME),x86_64) + ARCHITECTURE ?= x86_64 +else + ARCHITECTURE ?= arm64 +endif + +# Target Dotnet framework version +FRAMEWORK ?= net10.0 + +# Forward build for different Dotnet framework version to avoid code duplication +build: + cd ../dotnet && $(MAKE) clean build ARCHITECTURE=$(ARCHITECTURE) FRAMEWORK=$(FRAMEWORK) + mv ../dotnet/handler.zip . + +clean: + $(RM) -rf build handler.zip + cd ../dotnet && $(MAKE) clean + +.PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/echo/dotnet6/Makefile b/tests/aws/services/lambda_/functions/common/echo/dotnet6/Makefile index 43573df40d616..95f945b450e31 100644 --- a/tests/aws/services/lambda_/functions/common/echo/dotnet6/Makefile +++ b/tests/aws/services/lambda_/functions/common/echo/dotnet6/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/echo/dotnet8/Makefile b/tests/aws/services/lambda_/functions/common/echo/dotnet8/Makefile index f7c7f2ec8908c..6d9e57e759ee0 100644 --- a/tests/aws/services/lambda_/functions/common/echo/dotnet8/Makefile +++ b/tests/aws/services/lambda_/functions/common/echo/dotnet8/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/echo/java/Makefile b/tests/aws/services/lambda_/functions/common/echo/java/Makefile index 6916941cbe138..a827eefbe5bbe 100644 --- a/tests/aws/services/lambda_/functions/common/echo/java/Makefile +++ b/tests/aws/services/lambda_/functions/common/echo/java/Makefile @@ -7,6 +7,6 @@ build: cp build/handler.zip handler.zip clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/echo/nodejs/Makefile b/tests/aws/services/lambda_/functions/common/echo/nodejs/Makefile index c3b2190a84a3a..705b003b3eaf8 100644 --- a/tests/aws/services/lambda_/functions/common/echo/nodejs/Makefile +++ b/tests/aws/services/lambda_/functions/common/echo/nodejs/Makefile @@ -3,6 +3,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/echo/provided/Makefile b/tests/aws/services/lambda_/functions/common/echo/provided/Makefile index 09d0587c47e0e..3406319b9eab3 100644 --- a/tests/aws/services/lambda_/functions/common/echo/provided/Makefile +++ b/tests/aws/services/lambda_/functions/common/echo/provided/Makefile @@ -3,6 +3,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/echo/provided/src/bootstrap b/tests/aws/services/lambda_/functions/common/echo/provided/src/bootstrap index b0be430f83a5b..d29aee6058074 100755 --- a/tests/aws/services/lambda_/functions/common/echo/provided/src/bootstrap +++ b/tests/aws/services/lambda_/functions/common/echo/provided/src/bootstrap @@ -19,5 +19,5 @@ do RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA") # Send the response (using stdin to circumvent max input length) - echo ${RESPONSE} | curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" --data @'-' + echo "$RESPONSE" | curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/${REQUEST_ID}/response" --data @'-' done diff --git a/tests/aws/services/lambda_/functions/common/echo/provided/src/function.sh b/tests/aws/services/lambda_/functions/common/echo/provided/src/function.sh index 641726e064c0d..8315ed7bc33bc 100755 --- a/tests/aws/services/lambda_/functions/common/echo/provided/src/function.sh +++ b/tests/aws/services/lambda_/functions/common/echo/provided/src/function.sh @@ -1,7 +1,7 @@ function handler () { - EVENT_DATA=$1 + EVENT_DATA="$1" echo "$EVENT_DATA" 1>&2; - RESPONSE=$EVENT_DATA + RESPONSE="$EVENT_DATA" - echo $RESPONSE + echo "$RESPONSE" } diff --git a/tests/aws/services/lambda_/functions/common/echo/python/Makefile b/tests/aws/services/lambda_/functions/common/echo/python/Makefile index c3b2190a84a3a..705b003b3eaf8 100644 --- a/tests/aws/services/lambda_/functions/common/echo/python/Makefile +++ b/tests/aws/services/lambda_/functions/common/echo/python/Makefile @@ -3,6 +3,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/Makefile index 31704c534390c..58cab0cd7beb5 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/Makefile @@ -9,13 +9,13 @@ endif FUNCTION_ARCHITECTURE ?= $(ARCHITECTURE) # Target Dotnet framework version -FRAMEWORK ?= net8.0 +FRAMEWORK ?= net10.0 # Workaround for a Docker race condition causing an I/O error upon zipping to /out/handler.zip if # two builds are executed in short succession. Example: `make -C dotnet build && make -C dotnet6 build` BUILD_DIR ?= build-$(FRAMEWORK) -# https://gallery.ecr.aws/sam/build-dotnet8 -IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 +# https://gallery.ecr.aws/sam/build-dotnet10 +IMAGE ?= public.ecr.aws/sam/build-dotnet10:1.151.0 # Emulated builds with Dotnet8 are currently (2024-03-19) broken as discussed in many issues: # https://github.com/NuGet/Home/issues/12227 @@ -27,10 +27,10 @@ IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 build: mkdir -p $(BUILD_DIR) && \ - docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ + docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ cp $(BUILD_DIR)/handler.zip handler.zip clean: - $(RM) -r $(BUILD_DIR) handler.zip + $(RM) -rf $(BUILD_DIR) handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/src/dotnet.csproj b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/src/dotnet.csproj index 3db7fab8de15c..8cb7e5da3e315 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/src/dotnet.csproj +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet/src/dotnet.csproj @@ -1,14 +1,14 @@  - net6.0;net8.0 + net6.0;net8.0;net10.0 true Lambda - + - + diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet10/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet10/Makefile new file mode 100644 index 0000000000000..89991b37bd4a7 --- /dev/null +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet10/Makefile @@ -0,0 +1,20 @@ +UNAME := $(shell uname -m) +ifeq ($(UNAME),x86_64) + ARCHITECTURE ?= x86_64 +else + ARCHITECTURE ?= arm64 +endif + +# Target Dotnet framework version +FRAMEWORK ?= net10.0 + +# Forward build for different Dotnet framework version to avoid code duplication +build: + cd ../dotnet && $(MAKE) clean build ARCHITECTURE=$(ARCHITECTURE) FRAMEWORK=$(FRAMEWORK) + mv ../dotnet/handler.zip . + +clean: + $(RM) -rf build handler.zip + cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) + +.PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet6/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet6/Makefile index 43573df40d616..95f945b450e31 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet6/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet6/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet8/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet8/Makefile index 7ec1ea467ff87..df0b572919fce 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet8/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/dotnet8/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/java/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/java/Makefile index 6916941cbe138..a827eefbe5bbe 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/java/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/java/Makefile @@ -7,6 +7,6 @@ build: cp build/handler.zip handler.zip clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/java8.al2/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/java8.al2/Makefile index 6916941cbe138..a827eefbe5bbe 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/java8.al2/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/java8.al2/Makefile @@ -7,6 +7,6 @@ build: cp build/handler.zip handler.zip clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs/Makefile index c3b2190a84a3a..705b003b3eaf8 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs/Makefile @@ -3,6 +3,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs16.x/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs16.x/Makefile index c3b2190a84a3a..705b003b3eaf8 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs16.x/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/nodejs16.x/Makefile @@ -3,6 +3,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection/python/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection/python/Makefile index 4426ee52bf0ef..493fcad61fbea 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection/python/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection/python/Makefile @@ -4,6 +4,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/endpointinjection_extra/provided/Makefile b/tests/aws/services/lambda_/functions/common/endpointinjection_extra/provided/Makefile index bccba26238a6c..90fc86e61283c 100644 --- a/tests/aws/services/lambda_/functions/common/endpointinjection_extra/provided/Makefile +++ b/tests/aws/services/lambda_/functions/common/endpointinjection_extra/provided/Makefile @@ -21,6 +21,6 @@ build: docker run --rm --platform $(DOCKER_PLATFORM) -v $$(pwd)/src:/app -v $$(pwd)/build:/out $(DOCKER_GOLANG_IMAGE) /bin/bash -c "cd /app && GOOS=linux GOARCH=$(GOARCH) CGO_ENABLED=0 go build -trimpath -ldflags=-buildid= -o /out/bootstrap main.go && chown $$(id -u):$$(id -g) /out/bootstrap" clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/dotnet/Makefile b/tests/aws/services/lambda_/functions/common/introspection/dotnet/Makefile index 31704c534390c..58cab0cd7beb5 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/dotnet/Makefile +++ b/tests/aws/services/lambda_/functions/common/introspection/dotnet/Makefile @@ -9,13 +9,13 @@ endif FUNCTION_ARCHITECTURE ?= $(ARCHITECTURE) # Target Dotnet framework version -FRAMEWORK ?= net8.0 +FRAMEWORK ?= net10.0 # Workaround for a Docker race condition causing an I/O error upon zipping to /out/handler.zip if # two builds are executed in short succession. Example: `make -C dotnet build && make -C dotnet6 build` BUILD_DIR ?= build-$(FRAMEWORK) -# https://gallery.ecr.aws/sam/build-dotnet8 -IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 +# https://gallery.ecr.aws/sam/build-dotnet10 +IMAGE ?= public.ecr.aws/sam/build-dotnet10:1.151.0 # Emulated builds with Dotnet8 are currently (2024-03-19) broken as discussed in many issues: # https://github.com/NuGet/Home/issues/12227 @@ -27,10 +27,10 @@ IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 build: mkdir -p $(BUILD_DIR) && \ - docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ + docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ cp $(BUILD_DIR)/handler.zip handler.zip clean: - $(RM) -r $(BUILD_DIR) handler.zip + $(RM) -rf $(BUILD_DIR) handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/dotnet/src/dotnet.csproj b/tests/aws/services/lambda_/functions/common/introspection/dotnet/src/dotnet.csproj index 1bd07d2e45a5a..980d160b3d02a 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/dotnet/src/dotnet.csproj +++ b/tests/aws/services/lambda_/functions/common/introspection/dotnet/src/dotnet.csproj @@ -1,13 +1,13 @@  - net6.0;net8.0 + net6.0;net8.0;net10.0 true Lambda - + - + diff --git a/tests/aws/services/lambda_/functions/common/introspection/dotnet10/Makefile b/tests/aws/services/lambda_/functions/common/introspection/dotnet10/Makefile new file mode 100644 index 0000000000000..89991b37bd4a7 --- /dev/null +++ b/tests/aws/services/lambda_/functions/common/introspection/dotnet10/Makefile @@ -0,0 +1,20 @@ +UNAME := $(shell uname -m) +ifeq ($(UNAME),x86_64) + ARCHITECTURE ?= x86_64 +else + ARCHITECTURE ?= arm64 +endif + +# Target Dotnet framework version +FRAMEWORK ?= net10.0 + +# Forward build for different Dotnet framework version to avoid code duplication +build: + cd ../dotnet && $(MAKE) clean build ARCHITECTURE=$(ARCHITECTURE) FRAMEWORK=$(FRAMEWORK) + mv ../dotnet/handler.zip . + +clean: + $(RM) -rf build handler.zip + cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) + +.PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/dotnet6/Makefile b/tests/aws/services/lambda_/functions/common/introspection/dotnet6/Makefile index 43573df40d616..95f945b450e31 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/dotnet6/Makefile +++ b/tests/aws/services/lambda_/functions/common/introspection/dotnet6/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/dotnet8/Makefile b/tests/aws/services/lambda_/functions/common/introspection/dotnet8/Makefile index 7ec1ea467ff87..df0b572919fce 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/dotnet8/Makefile +++ b/tests/aws/services/lambda_/functions/common/introspection/dotnet8/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/java/Makefile b/tests/aws/services/lambda_/functions/common/introspection/java/Makefile index 6916941cbe138..a827eefbe5bbe 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/java/Makefile +++ b/tests/aws/services/lambda_/functions/common/introspection/java/Makefile @@ -7,6 +7,6 @@ build: cp build/handler.zip handler.zip clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/nodejs/Makefile b/tests/aws/services/lambda_/functions/common/introspection/nodejs/Makefile index c3b2190a84a3a..705b003b3eaf8 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/nodejs/Makefile +++ b/tests/aws/services/lambda_/functions/common/introspection/nodejs/Makefile @@ -3,6 +3,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/provided/Makefile b/tests/aws/services/lambda_/functions/common/introspection/provided/Makefile index 61f2768c515b2..b0ff7673251d6 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/provided/Makefile +++ b/tests/aws/services/lambda_/functions/common/introspection/provided/Makefile @@ -10,6 +10,9 @@ DOCKER_PLATFORM ?= linux/$(ARCHITECTURE) # https://github.com/cargo-lambda/cargo-lambda/blob/7b0977e6fd9a6b03d8f6ddf71eff5a5b9999e0c0/crates/cargo-lambda-build/src/target_arch.rs#L10 ifeq ($(ARCHITECTURE),arm64) # ARM builds are finally fixed since 1.76.0: https://github.com/rust-lang/rust/issues/77071 + # TODO: Migrate to -gnu once we can avoid the following issue for the runtime provided.al2. + # It might require using the official build image (https://gallery.ecr.aws/sam/emulation-provided.al2), but + # that seems not worth fixing before provided.al2 deprecation 2026-06. # The suffix -musl instead of -gnu is required for the runtime `provided.al2` to fix a GLIBC version not found error: # /var/task/bootstrap: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /var/task/bootstrap) # https://github.com/awslabs/aws-lambda-rust-runtime/issues/17#issuecomment-645064821 @@ -18,15 +21,17 @@ else RUST_TARGET ?= x86_64-unknown-linux-musl endif -# https://hub.docker.com/_/rust/tags -DOCKER_RUST_IMAGE ?= rust:1.76.0 +# https://github.com/cargo-lambda/cargo-lambda/pkgs/container/cargo-lambda +DOCKER_RUST_IMAGE ?= ghcr.io/cargo-lambda/cargo-lambda:1.8.6 +# TODO: consider migrating to AWS SAM-based build (easier and better parity): +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/building-rust.html build: mkdir -p build && \ - docker run --rm --platform=$(DOCKER_PLATFORM) -v $$(pwd)/src:/app -v $$(pwd)/build:/out:cached $(DOCKER_RUST_IMAGE) \ - bash -c "rustup target add $(RUST_TARGET) && mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && cargo build --release --target $(RUST_TARGET) && cp ./target/$(RUST_TARGET)/release/bootstrap /out && chown $$(id -u):$$(id -g) /out/bootstrap" + docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/build:/out:cached $(DOCKER_RUST_IMAGE) \ + bash -c "rustup target add $(RUST_TARGET) && mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && cargo lambda build --target $(RUST_TARGET) && cp ./target/lambda/bootstrap/bootstrap /out && chown $$(id -u):$$(id -g) /out/bootstrap" clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.lock b/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.lock index 66d5cd706f319..a6614af3621d0 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.lock +++ b/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.lock @@ -1,22 +1,16 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "addr2line" -version = "0.21.0" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "gimli", + "memchr", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "async-stream" version = "0.3.3" @@ -39,31 +33,22 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "backtrace" -version = "0.3.69" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" -version = "0.20.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -82,27 +67,15 @@ dependencies = [ [[package]] name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cc" -version = "1.0.89" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "fnv" -version = "1.0.7" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "futures" @@ -121,9 +94,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -131,9 +104,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" @@ -148,38 +121,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.114", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -194,47 +167,43 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "hermit-abi" -version = "0.1.19" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "libc", + "bytes", + "itoa", ] [[package]] -name = "http" -version = "0.2.12" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "fnv", - "itoa", + "http", ] [[package]] -name = "http-body" -version = "0.4.4" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", + "futures-core", "http", + "http-body", "pin-project-lite", ] [[package]] name = "http-serde" -version = "1.1.3" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f560b665ad9f1572cfcaf034f7fb84338a7ce945216d64a90fd81f046a3caee" +checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" dependencies = [ "http", "serde", @@ -242,21 +211,36 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "httpdate" -version = "1.0.2" +name = "hyper" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] [[package]] -name = "hyper" -version = "0.14.28" +name = "hyper-util" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "bytes", "futures-channel", @@ -264,38 +248,37 @@ dependencies = [ "futures-util", "http", "http-body", - "httparse", - "httpdate", - "itoa", + "hyper", + "libc", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", - "want", ] [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "lambda_runtime" -version = "0.8.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deca8f65d7ce9a8bfddebb49d7d91b22e788a59ca0c5190f26794ab80ed7a702" +checksum = "dc0b4409eea054e4c06f0101fed547b2cf208e8eca9dc6d41dead4114577852b" dependencies = [ "async-stream", "base64", "bytes", "futures", "http", - "http-body", + "http-body-util", "http-serde", "hyper", "lambda_runtime_api_client", + "pin-project", "serde", "serde_json", "serde_path_to_error", @@ -307,21 +290,34 @@ dependencies = [ [[package]] name = "lambda_runtime_api_client" -version = "0.8.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "690c5ae01f3acac8c9c3348b556fc443054e9b7f1deaf53e9ebab716282bf0ed" +checksum = "7b4873061514cb57ffb6a599b77c46c65d6d783efe9bad8fd56b7cba7f0459ef" dependencies = [ + "bytes", + "futures-channel", + "futures-util", "http", + "http-body", + "http-body-util", "hyper", - "tokio", - "tower-service", + "hyper-util", + "tower", + "tracing", + "tracing-subscriber", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.153" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "lock_api" @@ -335,63 +331,41 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "miniz_oxide" -version = "0.7.2" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "adler", + "regex-automata", ] [[package]] -name = "mio" -version = "0.8.11" +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] -name = "num_cpus" -version = "1.13.1" +name = "mio" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ - "hermit-abi", "libc", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" @@ -438,9 +412,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -450,18 +424,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -476,16 +450,21 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "regex-automata" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] -name = "ryu" -version = "1.0.9" +name = "regex-syntax" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "scopeguard" @@ -495,33 +474,45 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] @@ -534,6 +525,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -545,24 +545,24 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.6" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -578,43 +578,56 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tokio" -version = "1.36.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.114", ] [[package]] @@ -630,36 +643,35 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper", "tower-layer", "tower-service", - "tracing", ] [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -669,49 +681,83 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", + "tracing-serde", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -721,6 +767,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.36.1" @@ -736,63 +788,44 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.52.4", + "windows-link", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-targets" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" -dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -802,15 +835,9 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -820,15 +847,15 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] -name = "windows_i686_gnu" -version = "0.52.4" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -838,15 +865,9 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -856,27 +877,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -886,12 +895,12 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.4" +name = "zmij" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" diff --git a/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.toml b/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.toml index 40e75bde46b15..f76843110508d 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.toml +++ b/tests/aws/services/lambda_/functions/common/introspection/provided/src/Cargo.toml @@ -5,10 +5,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +# Upgrade dependencies: +# 1. cargo install cargo-edit +# 2. cargo upgrade -i [dependencies] # https://crates.io/crates/lambda_runtime -lambda_runtime = "0.8.3" +lambda_runtime = "1.0.2" # https://crates.io/crates/serde_json -serde_json = "1.0.108" +serde_json = "1.0.149" # https://crates.io/crates/tokio -tokio = { version = "1.34.0", features = ["full"] } +tokio = { version = "1.49.0", features = ["full"] } diff --git a/tests/aws/services/lambda_/functions/common/introspection/python/Makefile b/tests/aws/services/lambda_/functions/common/introspection/python/Makefile index 4426ee52bf0ef..493fcad61fbea 100644 --- a/tests/aws/services/lambda_/functions/common/introspection/python/Makefile +++ b/tests/aws/services/lambda_/functions/common/introspection/python/Makefile @@ -4,6 +4,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/kinesis_sdkv2/Makefile b/tests/aws/services/lambda_/functions/common/kinesis_sdkv2/Makefile new file mode 100644 index 0000000000000..1fd2a9d5903cd --- /dev/null +++ b/tests/aws/services/lambda_/functions/common/kinesis_sdkv2/Makefile @@ -0,0 +1,10 @@ +# Top-level Makefile to invoke all make targets in sub-directories + +# Based on https://stackoverflow.com/a/72209214/6875981 +SUBDIRS := $(patsubst %/,%,$(wildcard */)) + +.PHONY: all $(MAKECMDGOALS) $(SUBDIRS) +$(MAKECMDGOALS) all: $(SUBDIRS) + +$(SUBDIRS): + $(MAKE) -C $@ $(MAKECMDGOALS) diff --git a/tests/aws/services/lambda_/functions/common/kinesis_sdkv2/java17/Makefile b/tests/aws/services/lambda_/functions/common/kinesis_sdkv2/java17/Makefile index 6916941cbe138..a827eefbe5bbe 100644 --- a/tests/aws/services/lambda_/functions/common/kinesis_sdkv2/java17/Makefile +++ b/tests/aws/services/lambda_/functions/common/kinesis_sdkv2/java17/Makefile @@ -7,6 +7,6 @@ build: cp build/handler.zip handler.zip clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/Makefile index 31704c534390c..58cab0cd7beb5 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/Makefile @@ -9,13 +9,13 @@ endif FUNCTION_ARCHITECTURE ?= $(ARCHITECTURE) # Target Dotnet framework version -FRAMEWORK ?= net8.0 +FRAMEWORK ?= net10.0 # Workaround for a Docker race condition causing an I/O error upon zipping to /out/handler.zip if # two builds are executed in short succession. Example: `make -C dotnet build && make -C dotnet6 build` BUILD_DIR ?= build-$(FRAMEWORK) -# https://gallery.ecr.aws/sam/build-dotnet8 -IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 +# https://gallery.ecr.aws/sam/build-dotnet10 +IMAGE ?= public.ecr.aws/sam/build-dotnet10:1.151.0 # Emulated builds with Dotnet8 are currently (2024-03-19) broken as discussed in many issues: # https://github.com/NuGet/Home/issues/12227 @@ -27,10 +27,10 @@ IMAGE ?= public.ecr.aws/sam/build-dotnet8:1.112.0 build: mkdir -p $(BUILD_DIR) && \ - docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ + docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/$(BUILD_DIR):/out $(IMAGE) bash -c "mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && dotnet lambda package --framework $(FRAMEWORK) --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \ cp $(BUILD_DIR)/handler.zip handler.zip clean: - $(RM) -r $(BUILD_DIR) handler.zip + $(RM) -rf $(BUILD_DIR) handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/src/dotnet.csproj b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/src/dotnet.csproj index 1bd07d2e45a5a..980d160b3d02a 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/src/dotnet.csproj +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet/src/dotnet.csproj @@ -1,13 +1,13 @@  - net6.0;net8.0 + net6.0;net8.0;net10.0 true Lambda - + - + diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet10/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet10/Makefile new file mode 100644 index 0000000000000..89991b37bd4a7 --- /dev/null +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet10/Makefile @@ -0,0 +1,20 @@ +UNAME := $(shell uname -m) +ifeq ($(UNAME),x86_64) + ARCHITECTURE ?= x86_64 +else + ARCHITECTURE ?= arm64 +endif + +# Target Dotnet framework version +FRAMEWORK ?= net10.0 + +# Forward build for different Dotnet framework version to avoid code duplication +build: + cd ../dotnet && $(MAKE) clean build ARCHITECTURE=$(ARCHITECTURE) FRAMEWORK=$(FRAMEWORK) + mv ../dotnet/handler.zip . + +clean: + $(RM) -rf build handler.zip + cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) + +.PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet6/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet6/Makefile index 43573df40d616..95f945b450e31 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet6/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet6/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet8/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet8/Makefile index 7ec1ea467ff87..df0b572919fce 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet8/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/dotnet8/Makefile @@ -14,7 +14,7 @@ build: mv ../dotnet/handler.zip . clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip cd ../dotnet && $(MAKE) clean FRAMEWORK=$(FRAMEWORK) .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/java/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/java/Makefile index 6916941cbe138..a827eefbe5bbe 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/java/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/java/Makefile @@ -7,6 +7,6 @@ build: cp build/handler.zip handler.zip clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/nodejs/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/nodejs/Makefile index c3b2190a84a3a..705b003b3eaf8 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/nodejs/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/nodejs/Makefile @@ -3,6 +3,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/Makefile index 61f2768c515b2..b0ff7673251d6 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/Makefile @@ -10,6 +10,9 @@ DOCKER_PLATFORM ?= linux/$(ARCHITECTURE) # https://github.com/cargo-lambda/cargo-lambda/blob/7b0977e6fd9a6b03d8f6ddf71eff5a5b9999e0c0/crates/cargo-lambda-build/src/target_arch.rs#L10 ifeq ($(ARCHITECTURE),arm64) # ARM builds are finally fixed since 1.76.0: https://github.com/rust-lang/rust/issues/77071 + # TODO: Migrate to -gnu once we can avoid the following issue for the runtime provided.al2. + # It might require using the official build image (https://gallery.ecr.aws/sam/emulation-provided.al2), but + # that seems not worth fixing before provided.al2 deprecation 2026-06. # The suffix -musl instead of -gnu is required for the runtime `provided.al2` to fix a GLIBC version not found error: # /var/task/bootstrap: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /var/task/bootstrap) # https://github.com/awslabs/aws-lambda-rust-runtime/issues/17#issuecomment-645064821 @@ -18,15 +21,17 @@ else RUST_TARGET ?= x86_64-unknown-linux-musl endif -# https://hub.docker.com/_/rust/tags -DOCKER_RUST_IMAGE ?= rust:1.76.0 +# https://github.com/cargo-lambda/cargo-lambda/pkgs/container/cargo-lambda +DOCKER_RUST_IMAGE ?= ghcr.io/cargo-lambda/cargo-lambda:1.8.6 +# TODO: consider migrating to AWS SAM-based build (easier and better parity): +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/building-rust.html build: mkdir -p build && \ - docker run --rm --platform=$(DOCKER_PLATFORM) -v $$(pwd)/src:/app -v $$(pwd)/build:/out:cached $(DOCKER_RUST_IMAGE) \ - bash -c "rustup target add $(RUST_TARGET) && mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && cargo build --release --target $(RUST_TARGET) && cp ./target/$(RUST_TARGET)/release/bootstrap /out && chown $$(id -u):$$(id -g) /out/bootstrap" + docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/build:/out:cached $(DOCKER_RUST_IMAGE) \ + bash -c "rustup target add $(RUST_TARGET) && mkdir -p /app2 && cp -r /app/* /app2 && cd /app2 && cargo lambda build --target $(RUST_TARGET) && cp ./target/lambda/bootstrap/bootstrap /out && chown $$(id -u):$$(id -g) /out/bootstrap" clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.lock b/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.lock index 9726e78122d90..3e2e61a1e250c 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.lock +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.lock @@ -1,12 +1,15 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "adler" -version = "1.0.2" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] [[package]] name = "async-stream" @@ -26,20 +29,26 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" -version = "0.13.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -56,236 +65,223 @@ dependencies = [ "tokio", ] -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "bytes" -version = "1.1.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "crc32fast" -version = "1.3.2" +name = "futures" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ - "cfg-if", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "crossbeam-channel" -version = "0.5.4" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "cfg-if", - "crossbeam-utils", + "futures-core", + "futures-sink", ] [[package]] -name = "crossbeam-utils" -version = "0.8.8" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" -dependencies = [ - "cfg-if", - "lazy_static", -] +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "flate2" -version = "1.0.23" +name = "futures-executor" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ - "cfg-if", - "crc32fast", - "libc", - "miniz_oxide", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] -name = "futures-channel" -version = "0.3.21" +name = "futures-macro" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "futures-core", + "proc-macro2", + "quote", + "syn 2.0.114", ] -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] -name = "getrandom" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hdrhistogram" -version = "7.5.0" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "base64", - "byteorder", - "crossbeam-channel", - "flate2", - "nom", - "num-traits", + "bytes", + "itoa", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "libc", + "bytes", + "http", ] [[package]] -name = "http" -version = "0.2.7" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "fnv", - "itoa", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "http-body" -version = "0.4.4" +name = "http-serde" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" dependencies = [ - "bytes", "http", - "pin-project-lite", + "serde", ] [[package]] name = "httparse" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "0.14.18" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "pin-utils", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] -name = "indexmap" -version = "1.8.1" +name = "hyper-util" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "autocfg", - "hashbrown", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "lambda_runtime" -version = "0.5.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c73464e59463e91bf179195a308738477df90240173649d5dd2a1f3a2e4a2d2" +checksum = "dc0b4409eea054e4c06f0101fed547b2cf208e8eca9dc6d41dead4114577852b" dependencies = [ "async-stream", + "base64", "bytes", + "futures", "http", + "http-body-util", + "http-serde", "hyper", "lambda_runtime_api_client", + "pin-project", "serde", "serde_json", + "serde_path_to_error", "tokio", "tokio-stream", "tower", @@ -294,27 +290,34 @@ dependencies = [ [[package]] name = "lambda_runtime_api_client" -version = "0.5.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e921024b5eb4e2f0800a5d6e25c7ed554562aa62f02cf5f60a48c26c8a678974" +checksum = "7b4873061514cb57ffb6a599b77c46c65d6d783efe9bad8fd56b7cba7f0459ef" dependencies = [ + "bytes", + "futures-channel", + "futures-util", "http", + "http-body", + "http-body-util", "hyper", - "tokio", - "tower-service", + "hyper-util", + "tower", + "tracing", + "tracing-subscriber", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "lock_api" @@ -328,74 +331,41 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "memchr" -version = "2.5.0" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "adler", + "regex-automata", ] [[package]] -name = "mio" -version = "0.8.11" +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] -name = "nom" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-traits" -version = "0.2.15" +name = "mio" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ - "autocfg", + "libc", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "once_cell" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" @@ -437,14 +407,14 @@ checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -452,74 +422,49 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] [[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" +name = "redox_syscall" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ - "getrandom", + "bitflags", ] [[package]] -name = "redox_syscall" -version = "0.2.13" +name = "regex-automata" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ - "bitflags", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "ryu" -version = "1.0.9" +name = "regex-syntax" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "scopeguard" @@ -529,33 +474,65 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", - "ryu", "serde", + "serde_core", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -569,24 +546,24 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.4.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "winapi", + "windows-sys 0.60.2", ] [[package]] @@ -600,34 +577,58 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tokio" -version = "1.18.5" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050c618355082ae5a89ec63bbf897225d5ffe84c7c4e036874e4d185a5044e" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", - "memchr", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -641,59 +642,38 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "tower" -version = "0.4.12" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "hdrhistogram", - "indexmap", - "pin-project", "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util", + "sync_wrapper", "tower-layer", "tower-service", - "tracing", ] [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -702,79 +682,97 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.21" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ - "lazy_static", + "once_cell", + "valuable", ] [[package]] -name = "try-lock" -version = "0.2.3" +name = "tracing-serde" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] [[package]] -name = "unicode-xid" -version = "0.2.3" +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "want" -version = "0.3.0" +name = "unicode-ident" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] -name = "winapi" -version = "0.3.9" +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "try-lock", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -791,33 +789,44 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link", "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.48.5", + "windows_x86_64_msvc 0.53.1", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -827,9 +836,9 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -839,9 +848,15 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -851,9 +866,9 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -863,15 +878,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -881,6 +896,12 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "zmij" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.toml b/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.toml index f6df3fcf493cd..40e624f079fd1 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.toml +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/provided/src/Cargo.toml @@ -6,6 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lambda_runtime = "0.5" +lambda_runtime = "1.0" serde_json = "1.0" -tokio = { version = "1.18", features = ["full"] } +tokio = { version = "1.49", features = ["full"] } diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception/python/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception/python/Makefile index 4426ee52bf0ef..493fcad61fbea 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception/python/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception/python/Makefile @@ -4,6 +4,6 @@ build: cp -r ./src/* build/ clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/common/uncaughtexception_extra/provided/Makefile b/tests/aws/services/lambda_/functions/common/uncaughtexception_extra/provided/Makefile index bccba26238a6c..90fc86e61283c 100644 --- a/tests/aws/services/lambda_/functions/common/uncaughtexception_extra/provided/Makefile +++ b/tests/aws/services/lambda_/functions/common/uncaughtexception_extra/provided/Makefile @@ -21,6 +21,6 @@ build: docker run --rm --platform $(DOCKER_PLATFORM) -v $$(pwd)/src:/app -v $$(pwd)/build:/out $(DOCKER_GOLANG_IMAGE) /bin/bash -c "cd /app && GOOS=linux GOARCH=$(GOARCH) CGO_ENABLED=0 go build -trimpath -ldflags=-buildid= -o /out/bootstrap main.go && chown $$(id -u):$$(id -g) /out/bootstrap" clean: - $(RM) -r build handler.zip + $(RM) -rf build handler.zip .PHONY: build clean diff --git a/tests/aws/services/lambda_/functions/lambda_invocation_type_failure.py b/tests/aws/services/lambda_/functions/lambda_invocation_type_failure.py new file mode 100644 index 0000000000000..6240255127695 --- /dev/null +++ b/tests/aws/services/lambda_/functions/lambda_invocation_type_failure.py @@ -0,0 +1,14 @@ +import os +import time + +init_type = os.environ["AWS_LAMBDA_INITIALIZATION_TYPE"] + +if init_type == "provisioned-concurrency": + raise Exception("Intentional failure upon provisioned concurrency initialization") + + +def handler(event, context): + if event.get("wait"): + time.sleep(event["wait"]) + print(f"{init_type=}") + return init_type diff --git a/tests/aws/services/lambda_/functions/lambda_process_inspection.py b/tests/aws/services/lambda_/functions/lambda_process_inspection.py index 8aa03169bcc80..8302879daca7d 100644 --- a/tests/aws/services/lambda_/functions/lambda_process_inspection.py +++ b/tests/aws/services/lambda_/functions/lambda_process_inspection.py @@ -1,8 +1,13 @@ +import os + + def handler(event, context): - pid = event.get("pid") - with open(f"/proc/{pid}/environ", mode="rt") as f: - environment = f.read() - environment = environment.split("\x00") - env_partition = [env.partition("=") for env in environment if env] - env_dict = {env[0]: env[2] for env in env_partition} - return {"environment": env_dict} + return {"environment": dict(os.environ)} + # TODO: rework init env snapshotting test case because /proc/1 introspection does not work anymore at AWS + # pid = event.get("pid") + # with open(f"/proc/{pid}/environ", mode="rt") as f: + # environment = f.read() + # environment = environment.split("\x00") + # env_partition = [env.partition("=") for env in environment if env] + # env_dict = {env[0]: env[2] for env in env_partition} + # return {"environment": env_dict} diff --git a/tests/aws/services/lambda_/functions/lambda_response_streaming.js b/tests/aws/services/lambda_/functions/lambda_response_streaming.js new file mode 100644 index 0000000000000..b60c70310d47d --- /dev/null +++ b/tests/aws/services/lambda_/functions/lambda_response_streaming.js @@ -0,0 +1,17 @@ +exports.handler = awslambda.streamifyResponse( + async (event, responseStream, context) => { + const metadata = { + status: 200, + headers: { + 'Content-Type': 'text/html', + 'Cache-Control': 'no-cache', + }, + } + + responseStream = awslambda.HttpResponseStream.from(responseStream, metadata) + + responseStream.write('Hello,'); + responseStream.write(' world!'); + responseStream.end(); + } +); diff --git a/tests/aws/services/lambda_/test_lambda.py b/tests/aws/services/lambda_/test_lambda.py index 08d8d77f0e4f2..9d2a17fcd5784 100644 --- a/tests/aws/services/lambda_/test_lambda.py +++ b/tests/aws/services/lambda_/test_lambda.py @@ -12,7 +12,6 @@ import time from concurrent.futures import ThreadPoolExecutor from io import BytesIO -from typing import Dict, TypeVar import pytest import requests @@ -27,6 +26,7 @@ from localstack.services.lambda_.runtimes import RUNTIMES_AGGREGATED from localstack.testing.aws.lambda_utils import ( concurrency_update_done, + concurrency_update_failed, get_invoke_init_type, update_done, ) @@ -119,6 +119,9 @@ TEST_LAMBDA_INTROSPECT_PYTHON = os.path.join(THIS_FOLDER, "functions/lambda_introspect.py") TEST_LAMBDA_ULIMITS = os.path.join(THIS_FOLDER, "functions/lambda_ulimits.py") TEST_LAMBDA_INVOCATION_TYPE = os.path.join(THIS_FOLDER, "functions/lambda_invocation_type.py") +TEST_LAMBDA_INVOCATION_TYPE_FAILURE = os.path.join( + THIS_FOLDER, "functions/lambda_invocation_type_failure.py" +) TEST_LAMBDA_VERSION = os.path.join(THIS_FOLDER, "functions/lambda_version.py") TEST_LAMBDA_CONTEXT_REQID = os.path.join(THIS_FOLDER, "functions/lambda_context.py") TEST_LAMBDA_PROCESS_INSPECTION = os.path.join(THIS_FOLDER, "functions/lambda_process_inspection.py") @@ -130,6 +133,7 @@ TEST_LAMBDA_CLOUDWATCH_LOGS = os.path.join(THIS_FOLDER, "functions/lambda_cloudwatch_logs.py") TEST_LAMBDA_XRAY_TRACEID = os.path.join(THIS_FOLDER, "functions/xray_tracing_traceid.py") TEST_LAMBDA_HOST_PREFIX_OPERATION = os.path.join(THIS_FOLDER, "functions/host_prefix_operation.py") +TEST_LAMBDA_RESPONSE_STREAMING = os.path.join(THIS_FOLDER, "functions/lambda_response_streaming.js") PYTHON_TEST_RUNTIMES = RUNTIMES_AGGREGATED["python"] NODE_TEST_RUNTIMES = RUNTIMES_AGGREGATED["nodejs"] @@ -146,13 +150,11 @@ "dns", ] -T = TypeVar("T") - -def read_streams(payload: T) -> T: +def read_streams[T](payload: T) -> T: new_payload = {} for k, v in payload.items(): - if isinstance(v, Dict): + if isinstance(v, dict): new_payload[k] = read_streams(v) elif isinstance(v, StreamingBody): new_payload[k] = to_str(v.read()) @@ -263,6 +265,7 @@ def _check_print_in_logs(): retry(_check_print_in_logs, retries=10) + @markers.requires_in_process @markers.aws.only_localstack def test_lambda_too_large_response_but_with_custom_limit( self, caplog, create_lambda_function, aws_client, monkeypatch @@ -396,7 +399,7 @@ def _invoke_lambda(*args): ) return json.load(result["Payload"])["environment"] - def _transform_to_key_dict(env: Dict[str, str]): + def _transform_to_key_dict(env: dict[str, str]): return { "AccessKeyId": env["AWS_ACCESS_KEY_ID"], "SecretAccessKey": env["AWS_SECRET_ACCESS_KEY"], @@ -451,10 +454,16 @@ class TestLambdaBehavior: "$..Payload.paths._var_task_gid", "$..Payload.paths._var_task_owner", "$..Payload.paths._var_task_uid", + # TODO fix runtime environment + "$..Payload.paths._tmp_gid", + "$..Payload.paths._tmp_mode", + "$..Payload.paths._tmp_owner", + "$..Payload.paths._tmp_uid", ], ) @markers.aws.validated @markers.only_on_amd64 + @markers.requires_docker # required due to environment mismatch def test_runtime_introspection_x86(self, create_lambda_function, snapshot, aws_client): func_name = f"test_lambda_x86_{short_uid()}" create_lambda_function( @@ -474,10 +483,15 @@ def test_runtime_introspection_x86(self, create_lambda_function, snapshot, aws_c "$..Payload.paths._var_task_gid", "$..Payload.paths._var_task_owner", "$..Payload.paths._var_task_uid", + "$..Payload.paths._tmp_gid", + "$..Payload.paths._tmp_mode", + "$..Payload.paths._tmp_owner", + "$..Payload.paths._tmp_uid", ], ) @markers.aws.validated @markers.only_on_arm64 + @markers.requires_docker # required due to environment mismatch def test_runtime_introspection_arm(self, create_lambda_function, snapshot, aws_client): func_name = f"test_lambda_arm_{short_uid()}" create_lambda_function( @@ -492,13 +506,14 @@ def test_runtime_introspection_arm(self, create_lambda_function, snapshot, aws_c snapshot.match("invoke_runtime_arm_introspection", invoke_result) @markers.aws.validated + @markers.requires_docker # also requires in process, but the docker part is more important here by design def test_runtime_ulimits(self, create_lambda_function, snapshot, monkeypatch, aws_client): """We consider ulimits parity as opt-in because development environments could hit these limits unlike in optimized production deployments.""" monkeypatch.setattr( config, "LAMBDA_DOCKER_FLAGS", - "--ulimit nofile=1024:1024 --ulimit nproc=742:742 --ulimit core=-1:-1 --ulimit stack=8388608:-1 --ulimit memlock=65536:65536", + "--ulimit nofile=1024:1024 --ulimit nproc=742:742 --ulimit core=10737418240 --ulimit stack=8388608:-1 --ulimit memlock=65536:65536", ) func_name = f"test_lambda_ulimits_{short_uid()}" @@ -512,6 +527,7 @@ def test_runtime_ulimits(self, create_lambda_function, snapshot, monkeypatch, aw snapshot.match("invoke_runtime_ulimits", invoke_result) @markers.aws.only_localstack + @markers.requires_in_process def test_ignore_architecture(self, create_lambda_function, monkeypatch, aws_client): """Test configuration to ignore lambda architecture by creating a lambda with non-native architecture.""" monkeypatch.setattr(config, "LAMBDA_IGNORE_ARCHITECTURE", True) @@ -544,6 +560,7 @@ def test_ignore_architecture(self, create_lambda_function, monkeypatch, aws_clie # Special case requiring both architectures @markers.only_on_amd64 @markers.only_on_arm64 + @markers.requires_docker def test_mixed_architecture(self, create_lambda_function, aws_client, snapshot): """Test emulation of a lambda function changing architectures. Limitation: only works on hosts that support both ARM and AMD64 architectures. @@ -605,9 +622,18 @@ def test_lambda_cache_local( snapshot.match("second_invoke_result", second_invoke_result) @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO the current error is Sandbox.Timedout. This is likely to change when the feature goes live? + "$..Payload.errorType", + "$..Payload.errorMessage", + ] + ) def test_lambda_invoke_with_timeout(self, create_lambda_function, snapshot, aws_client): # Snapshot generation could be flaky against AWS with a small timeout margin (e.g., 1.02 instead of 1.00) - regex = re.compile(r".*\s(?P[-a-z0-9]+) Task timed out after \d.\d+ seconds") + regex = re.compile( + r".*\s(?P[-a-z0-9]+)( Error:)? Task timed out after \d.\d+ seconds" + ) snapshot.add_transformer( KeyValueBasedTransformer( lambda k, v: regex.search(v).group("uuid") if k == "errorMessage" else None, @@ -768,40 +794,17 @@ def assert_events(): @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ - # not set directly on init in lambda, but only on runtime processes - "$..Payload.environment.AWS_ACCESS_KEY_ID", - "$..Payload.environment.AWS_SECRET_ACCESS_KEY", - "$..Payload.environment.AWS_SESSION_TOKEN", - "$..Payload.environment.AWS_XRAY_DAEMON_ADDRESS", - # variables set by default in the image/docker - "$..Payload.environment.HOME", - "$..Payload.environment.HOSTNAME", - # LocalStack specific variables + "$..Payload.environment.AWS_CONTAINER_AUTHORIZATION_TOKEN", + "$..Payload.environment.AWS_CONTAINER_CREDENTIALS_FULL_URI", "$..Payload.environment.AWS_ENDPOINT_URL", "$..Payload.environment.AWS_LAMBDA_FUNCTION_TIMEOUT", "$..Payload.environment.EDGE_PORT", - "$..Payload.environment.LOCALSTACK_FUNCTION_ACCOUNT_ID", "$..Payload.environment.LOCALSTACK_HOSTNAME", - "$..Payload.environment.LOCALSTACK_INIT_LOG_LEVEL", - "$..Payload.environment.LOCALSTACK_RUNTIME_ENDPOINT", - "$..Payload.environment.LOCALSTACK_RUNTIME_ID", - "$..Payload.environment.LOCALSTACK_USER", - "$..Payload.environment.LOCALSTACK_POST_INVOKE_WAIT_MS", - "$..Payload.environment.LOCALSTACK_MAX_PAYLOAD_SIZE", - "$..Payload.environment.LOCALSTACK_CHMOD_PATHS", - # internal AWS lambda functionality - "$..Payload.environment._AWS_XRAY_DAEMON_ADDRESS", - "$..Payload.environment._LAMBDA_CONSOLE_SOCKET", - "$..Payload.environment._LAMBDA_CONTROL_SOCKET", - "$..Payload.environment._LAMBDA_DIRECT_INVOKE_SOCKET", - "$..Payload.environment._LAMBDA_LOG_FD", - "$..Payload.environment._LAMBDA_RUNTIME_LOAD_TIME", - "$..Payload.environment._LAMBDA_SB_ID", - "$..Payload.environment._LAMBDA_SHARED_MEM_FD", - "$..Payload.environment._LAMBDA_TELEMETRY_API_PASSPHRASE", - "$..Payload.environment._X_AMZN_TRACE_ID", + # Missing in Python Lambda images (3.12, 3.13, 3.14) since around 2026-02-18 + "$..Payload.environment.LC_CTYPE", ] ) + @markers.requires_in_process def test_lambda_init_environment( self, aws_client, create_lambda_function, snapshot, monkeypatch ): @@ -818,12 +821,17 @@ def test_lambda_init_environment( snapshot.transform.key_value("AWS_LAMBDA_LOG_STREAM_NAME", "log-stream-name"), snapshot.transform.key_value("_X_AMZN_TRACE_ID", "xray-trace-id"), snapshot.transform.key_value("_LAMBDA_RUNTIME_LOAD_TIME", "runtime-load-time"), + snapshot.transform.key_value("AWS_SECRET_ACCESS_KEY", "aws-secret-access-key"), + snapshot.transform.key_value("AWS_ACCESS_KEY_ID", "aws-access-key-id"), + snapshot.transform.key_value("AWS_SESSION_TOKEN", "aws-session-token"), + snapshot.transform.key_value("_AWS_XRAY_DAEMON_ADDRESS", "xray-daemon-address"), ] ) create_result = create_lambda_function( func_name=func_name, + # TODO: rework init env snapshotting test case because /proc/1 introspection does not work anymore at AWS handler_file=TEST_LAMBDA_PROCESS_INSPECTION, - runtime=Runtime.python3_12, + runtime=Runtime.python3_14, client=aws_client.lambda_, ) snapshot.match("create-result", create_result) @@ -951,6 +959,32 @@ def test_lambda_url_invocation(self, create_lambda_function, snapshot, returnval }, ) + @markers.aws.validated + def test_function_url_with_response_streaming( + self, aws_client, create_lambda_function, snapshot + ): + function_name = f"test_lambda_{short_uid()}" + create_lambda_function( + func_name=function_name, + handler_file=TEST_LAMBDA_RESPONSE_STREAMING, + handler="lambda_response_streaming.handler", + runtime=Runtime.nodejs18_x, + ) + url_config = aws_client.lambda_.create_function_url_config( + FunctionName=function_name, AuthType="NONE", InvokeMode="RESPONSE_STREAM" + ) + aws_client.lambda_.add_permission( + FunctionName=function_name, + StatementId=str(short_uid()), + Action="lambda:InvokeFunctionUrl", + Principal="*", + FunctionUrlAuthType="NONE", + ) + + result = safe_requests.post(url_config["FunctionUrl"], data=b"{'key':'value'}") + snapshot.match("response_status", result.status_code) + snapshot.match("response_data", result.text) + @markers.aws.validated def test_lambda_url_invocation_custom_id(self, create_lambda_function, aws_client): function_name = f"test-function-{short_uid()}" @@ -1243,6 +1277,8 @@ def test_lambda_url_invocation_exception(self, create_lambda_function, snapshot, assert result.status_code == 502 @markers.aws.validated + # TODO Add error message + @markers.snapshot.skip_snapshot_verify(paths=["$..content.Message"]) def test_lambda_url_persists_after_alias_delete( self, create_lambda_function, snapshot, aws_client ): @@ -1373,6 +1409,8 @@ def test_lambda_url_non_existing_url(self): "$..headers.x-forwarded-for", "$..headers.x-amzn-trace-id", "$..origin", # TODO: LS Lambda should populate this value for AWS parity + # The value of Accept-Encoding can change when the zstandard package is present + "$..headers.accept-encoding", ] ) @markers.aws.validated @@ -1406,6 +1444,8 @@ def test_lambda_url_echo_http_fixture_default( @markers.snapshot.skip_snapshot_verify( paths=[ "$..content.headers.domain", # TODO: LS Lambda should populate this value for AWS parity + # The value of Accept-Encoding can change when the zstandard package is present + "$..headers.accept-encoding", "$..origin", # TODO: LS Lambda should populate this value for AWS parity ] ) @@ -1431,6 +1471,8 @@ def test_lambda_url_echo_http_fixture_trim_x_headers( @markers.snapshot.skip_snapshot_verify( paths=[ "$..origin", # FIXME: LS does not populate the value + # The value of Accept-Encoding can change when the zstandard package is present + "$..headers.accept-encoding", ] ) def test_lambda_url_form_payload(self, create_echo_http_server, snapshot, aws_client): @@ -1937,6 +1979,7 @@ def test_lambda_runtime_wrapper_not_found(self, aws_client, create_lambda_functi ) snapshot.match("invocation_error", result) + @markers.requires_in_process @markers.aws.only_localstack( reason="Can only induce Lambda-internal Docker error in LocalStack" ) @@ -1966,6 +2009,7 @@ def test_lambda_runtime_startup_timeout( r"retries: \d\): \[[^]]*\] Timeout while starting up lambda environment .*" ) + @markers.requires_in_process @markers.aws.only_localstack( reason="Can only induce Lambda-internal Docker error in LocalStack" ) @@ -2603,6 +2647,74 @@ def test_provisioned_concurrency(self, create_lambda_function, snapshot, aws_cli result2 = json.load(invoke_result2["Payload"]) assert result2 == "on-demand" + @markers.aws.validated + def test_provisioned_concurrency_init_failure( + self, create_lambda_function, snapshot, aws_client + ): + """Put provisioned concurrency on a Lambda function that fails upon initialization. + The intended failure only triggers for initialization of provisioned concurrency. + """ + min_concurrent_executions = 10 + 1 + check_concurrency_quota(aws_client, min_concurrent_executions) + + func_name = f"test_lambda_{short_uid()}" + create_function_response = create_lambda_function( + func_name=func_name, + runtime=Runtime.python3_12, + handler_file=TEST_LAMBDA_INVOCATION_TYPE_FAILURE, + # Provisioned concurrency requires publishing + Publish=True, + ) + version = create_function_response["CreateFunctionResponse"]["Version"] + + put_provisioned = aws_client.lambda_.put_provisioned_concurrency_config( + FunctionName=func_name, Qualifier=version, ProvisionedConcurrentExecutions=1 + ) + snapshot.match("put_provisioned", put_provisioned) + + get_provisioned_prewait = aws_client.lambda_.get_provisioned_concurrency_config( + FunctionName=func_name, Qualifier=version + ) + snapshot.match("get_provisioned_prewait", get_provisioned_prewait) + + # Invokes should be served on-demand during provisioned concurrency initialization + assert get_invoke_init_type(aws_client.lambda_, func_name, version) == "on-demand" + + # AWS attempts initialization a total of 6 times according to CloudWatch logs + wait_time = 10 if is_aws_cloud() else 1 + assert wait_until( + concurrency_update_failed(aws_client.lambda_, func_name, version), + wait=wait_time, + max_retries=80, + ) + + get_provisioned_postwait = aws_client.lambda_.get_provisioned_concurrency_config( + FunctionName=func_name, Qualifier=version + ) + snapshot.match("get_provisioned_postwait", get_provisioned_postwait) + + log_group_name = f"/aws/lambda/{func_name}" + + def _log_stream_available(): + result = aws_client.logs.describe_log_streams(logGroupName=log_group_name)["logStreams"] + # We are expecting 7 log streams: total 6 provisioned init attempts + 1 on-demand + # TODO: assert and implement 6 total initialization attempts in LS (LS only attempts once) + num_expected_log_streams = 7 if is_aws_cloud() else 2 + return len(result) >= num_expected_log_streams + + wait_until(_log_stream_available, strategy="linear") + + logs = aws_client.logs.filter_log_events(logGroupName=log_group_name) + expected_error_msg = ( + "[ERROR] Exception: Intentional failure upon provisioned concurrency initialization" + ) + # TODO: implement and enable this validation in LS + if is_aws_cloud(): + assert any(expected_error_msg in e["message"] for e in logs["events"]) + + # Invokes should not be scheduled to failed provisioned concurrency + assert get_invoke_init_type(aws_client.lambda_, func_name, version) == "on-demand" + @markers.aws.validated def test_provisioned_concurrency_on_alias(self, create_lambda_function, snapshot, aws_client): """ @@ -2861,6 +2973,7 @@ def test_reserved_concurrency( snapshot.add_transformer( snapshot.transform.key_value("SenderId", "", reference_replacement=False) ) + snapshot.add_transformer(snapshot.transform.key_value("AWSTraceHeader")) func_name = f"test_lambda_{short_uid()}" create_lambda_function( @@ -3330,10 +3443,16 @@ def test_lambda_alias_moving( FunctionVersion=second_publish_response["Version"], ) snapshot.match("update_alias_response", update_alias_response) + # check if alias moved to 2 - invocation_result_qualifier_v2 = aws_client.lambda_.invoke( - FunctionName=function_name, Qualifier=create_alias_response["Name"], Payload=b"{}" - ) + def _invoke_qualifier_v2(): + result = aws_client.lambda_.invoke( + FunctionName=function_name, Qualifier=create_alias_response["Name"], Payload=b"{}" + ) + assert result["ExecutedVersion"] == "2" + return result + + invocation_result_qualifier_v2 = retry(_invoke_qualifier_v2) snapshot.match("invocation_result_qualifier_v2", invocation_result_qualifier_v2) with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException) as e: aws_client.lambda_.invoke( @@ -3498,6 +3617,7 @@ def fetch_logs(): snapshot.match("end_log_entries", end_log_entries) @markers.aws.validated + @markers.requires_in_process def test_request_id_async_invoke_with_retry( self, aws_client, create_lambda_function, monkeypatch, snapshot ): diff --git a/tests/aws/services/lambda_/test_lambda.snapshot.json b/tests/aws/services/lambda_/test_lambda.snapshot.json index 121d9b01ef397..643df3f381722 100644 --- a/tests/aws/services/lambda_/test_lambda.snapshot.json +++ b/tests/aws/services/lambda_/test_lambda.snapshot.json @@ -152,7 +152,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[nodejs]": { - "recorded-date": "08-04-2024, 16:55:57", + "recorded-date": "24-11-2025, 23:08:41", "recorded-content": { "first_invoke_result": { "ExecutedVersion": "$LATEST", @@ -179,7 +179,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[python]": { - "recorded-date": "08-04-2024, 16:56:00", + "recorded-date": "24-11-2025, 23:08:44", "recorded-content": { "first_invoke_result": { "ExecutedVersion": "$LATEST", @@ -206,7 +206,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_with_timeout": { - "recorded-date": "08-04-2024, 16:56:05", + "recorded-date": "25-11-2025, 00:39:45", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -261,7 +261,8 @@ "ExecutedVersion": "$LATEST", "FunctionError": "Unhandled", "Payload": { - "errorMessage": "date Task timed out after 1.00 seconds" + "errorType": "Sandbox.Timedout", + "errorMessage": "RequestId: Error: Task timed out after 1.00 seconds" }, "StatusCode": 200, "ResponseMetadata": { @@ -272,7 +273,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_no_timeout": { - "recorded-date": "08-04-2024, 16:56:14", + "recorded-date": "24-11-2025, 23:08:55", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -335,7 +336,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_function_state": { - "recorded-date": "08-04-2024, 16:55:21", + "recorded-date": "24-11-2025, 23:08:11", "recorded-content": { "create-fn-response": { "Architectures": [ @@ -431,7 +432,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[python3.10]": { - "recorded-date": "08-04-2024, 16:57:44", + "recorded-date": "24-11-2025, 23:11:20", "recorded-content": { "invoke": { "ExecutedVersion": "$LATEST", @@ -454,7 +455,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[nodejs16.x]": { - "recorded-date": "08-04-2024, 16:57:41", + "recorded-date": "24-11-2025, 23:11:18", "recorded-content": { "invoke": { "ExecutedVersion": "$LATEST", @@ -491,7 +492,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[python3.10]": { - "recorded-date": "08-04-2024, 16:57:51", + "recorded-date": "24-11-2025, 23:11:26", "recorded-content": { "invoke-result": { "ExecutedVersion": "$LATEST", @@ -505,7 +506,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[nodejs16.x]": { - "recorded-date": "08-04-2024, 16:57:48", + "recorded-date": "24-11-2025, 23:11:23", "recorded-content": { "invoke-result": { "ExecutedVersion": "$LATEST", @@ -532,7 +533,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[python3.10]": { - "recorded-date": "08-04-2024, 16:58:09", + "recorded-date": "24-11-2025, 23:11:39", "recorded-content": { "invoke-result": { "Payload": "", @@ -545,7 +546,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[nodejs16.x]": { - "recorded-date": "08-04-2024, 16:57:59", + "recorded-date": "24-11-2025, 23:11:31", "recorded-content": { "invoke-result": { "Payload": "", @@ -689,7 +690,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_qualifier": { - "recorded-date": "08-04-2024, 16:58:22", + "recorded-date": "24-11-2025, 23:12:27", "recorded-content": { "creation-response": { "Architectures": [ @@ -769,7 +770,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_upload_lambda_from_s3": { - "recorded-date": "08-04-2024, 16:58:27", + "recorded-date": "24-11-2025, 23:12:31", "recorded-content": { "creation-response": { "Architectures": [ @@ -917,7 +918,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_block": { - "recorded-date": "08-04-2024, 17:02:07", + "recorded-date": "24-11-2025, 23:16:40", "recorded-content": { "v1_result": { "Architectures": [ @@ -1033,7 +1034,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_versions_with_code_changes": { - "recorded-date": "08-04-2024, 17:10:52", + "recorded-date": "24-11-2025, 23:26:21", "recorded-content": { "create_response": { "Architectures": [ @@ -1343,7 +1344,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_lambda_alias_moving": { - "recorded-date": "08-04-2024, 17:11:05", + "recorded-date": "25-11-2025, 23:37:28", "recorded-content": { "create_response": { "Architectures": [ @@ -1576,7 +1577,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_alias_routingconfig": { - "recorded-date": "08-04-2024, 17:11:17", + "recorded-date": "24-11-2025, 23:27:57", "recorded-content": { "create_alias_response": { "AliasArn": "arn::lambda::111111111111:function:", @@ -1597,7 +1598,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_different_iam_keys_environment": { - "recorded-date": "08-04-2024, 16:55:38", + "recorded-date": "24-11-2025, 23:08:26", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -2278,7 +2279,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_exception": { - "recorded-date": "08-04-2024, 16:57:23", + "recorded-date": "24-11-2025, 23:09:56", "recorded-content": { "get_fn_result": { "Code": { @@ -2362,7 +2363,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_crud": { - "recorded-date": "22-05-2025, 08:04:13", + "recorded-date": "24-11-2025, 23:14:18", "recorded-content": { "get_function_concurrency_default": { "ResponseMetadata": { @@ -2393,15 +2394,15 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaPermissions::test_lambda_permission_url_invocation": { - "recorded-date": "08-04-2024, 16:57:38", + "recorded-date": "24-11-2025, 23:11:15", "recorded-content": { "lambda_url_invocation_missing_permission": { - "Message": "Forbidden" + "Message": "Forbidden. For troubleshooting Function URL authorization issues, see: https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html" } } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[dict]": { - "recorded-date": "08-04-2024, 16:56:29", + "recorded-date": "24-11-2025, 23:09:04", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2444,7 +2445,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response]": { - "recorded-date": "08-04-2024, 16:56:33", + "recorded-date": "24-11-2025, 23:09:07", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2485,7 +2486,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response-json]": { - "recorded-date": "08-04-2024, 16:56:37", + "recorded-date": "24-11-2025, 23:09:11", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2528,7 +2529,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[list-mixed]": { - "recorded-date": "08-04-2024, 16:56:41", + "recorded-date": "24-11-2025, 23:09:13", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2569,7 +2570,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[string]": { - "recorded-date": "08-04-2024, 16:56:45", + "recorded-date": "24-11-2025, 23:09:17", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2610,7 +2611,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[integer]": { - "recorded-date": "08-04-2024, 16:56:49", + "recorded-date": "24-11-2025, 23:09:20", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2651,7 +2652,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[float]": { - "recorded-date": "08-04-2024, 16:56:53", + "recorded-date": "24-11-2025, 23:09:23", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2692,7 +2693,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[boolean]": { - "recorded-date": "08-04-2024, 16:56:57", + "recorded-date": "24-11-2025, 23:09:26", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -2733,7 +2734,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_arm": { - "recorded-date": "08-04-2024, 16:55:44", + "recorded-date": "24-11-2025, 23:08:31", "recorded-content": { "invoke_runtime_arm_introspection": { "ExecutedVersion": "$LATEST", @@ -2746,17 +2747,17 @@ "pwd": "/var/task", "paths": { "_var_task_mode": "drwxr-xr-x", - "_var_task_uid": 998, - "_var_task_owner": "slicer", - "_var_task_gid": 995, + "_var_task_uid": 0, + "_var_task_owner": "root", + "_var_task_gid": 0, "_opt_mode": "drwxr-xr-x", "_opt_uid": 0, "_opt_owner": "root", "_opt_gid": 0, - "_tmp_mode": "drwx------", - "_tmp_uid": 993, - "_tmp_owner": "sbx_user1051", - "_tmp_gid": 990, + "_tmp_mode": "drwxrwxrwt", + "_tmp_uid": 0, + "_tmp_owner": "root", + "_tmp_gid": 0, "_lambda-entrypoint.sh_mode": "-rwxr-xr-x", "_lambda-entrypoint.sh_uid": 0, "_lambda-entrypoint.sh_owner": "root", @@ -2772,7 +2773,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_x86": { - "recorded-date": "08-04-2024, 16:55:41", + "recorded-date": "24-11-2025, 23:08:29", "recorded-content": { "invoke_runtime_x86_introspection": { "ExecutedVersion": "$LATEST", @@ -2785,17 +2786,17 @@ "pwd": "/var/task", "paths": { "_var_task_mode": "drwxr-xr-x", - "_var_task_uid": 998, - "_var_task_owner": "slicer", - "_var_task_gid": 995, + "_var_task_uid": 0, + "_var_task_owner": "root", + "_var_task_gid": 0, "_opt_mode": "drwxr-xr-x", "_opt_uid": 0, "_opt_owner": "root", "_opt_gid": 0, - "_tmp_mode": "drwx------", - "_tmp_uid": 993, - "_tmp_owner": "sbx_user1051", - "_tmp_gid": 990, + "_tmp_mode": "drwxrwxrwt", + "_tmp_uid": 0, + "_tmp_owner": "root", + "_tmp_gid": 0, "_lambda-entrypoint.sh_mode": "-rwxr-xr-x", "_lambda-entrypoint.sh_uid": 0, "_lambda-entrypoint.sh_owner": "root", @@ -2811,7 +2812,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_ulimits": { - "recorded-date": "16-04-2024, 08:12:12", + "recorded-date": "24-11-2025, 23:08:34", "recorded-content": { "invoke_runtime_ulimits": { "ExecutedVersion": "$LATEST", @@ -2821,8 +2822,8 @@ -1 ], "RLIMIT_CORE": [ - -1, - -1 + 10737418240, + 10737418240 ], "RLIMIT_CPU": [ -1, @@ -2866,7 +2867,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency": { - "recorded-date": "08-04-2024, 17:08:11", + "recorded-date": "25-11-2025, 11:20:35", "recorded-content": { "fn": { "Architectures": [ @@ -2958,7 +2959,7 @@ }, "msg": { "Attributes": { - "AWSTraceHeader": "Root=1-6614247a-4513533344453f9a2d077845;Parent=282ed520d6ca75c8;Sampled=0", + "AWSTraceHeader": "", "ApproximateFirstReceiveTimestamp": "timestamp", "ApproximateReceiveCount": "1", "SenderId": "", @@ -2982,7 +2983,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency_async_queue": { - "recorded-date": "26-03-2025, 10:53:54", + "recorded-date": "24-11-2025, 23:23:27", "recorded-content": { "fn": { "Architectures": [ @@ -3052,7 +3053,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency": { - "recorded-date": "08-04-2024, 17:04:21", + "recorded-date": "24-11-2025, 23:18:51", "recorded-content": { "put_provisioned_5": { "AllocatedProvisionedConcurrentExecutions": 0, @@ -3090,7 +3091,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_provisioned_overlap": { - "recorded-date": "08-04-2024, 17:10:37", + "recorded-date": "24-11-2025, 23:26:07", "recorded-content": { "put_provisioned_5": { "AllocatedProvisionedConcurrentExecutions": 0, @@ -3230,11 +3231,11 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_format": { - "recorded-date": "08-04-2024, 17:11:17", + "recorded-date": "24-11-2025, 23:27:57", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke": { - "recorded-date": "08-04-2024, 17:11:26", + "recorded-date": "24-11-2025, 23:28:05", "recorded-content": { "invoke_result": { "ExecutedVersion": "$LATEST", @@ -3256,7 +3257,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_async_invoke_with_retry": { - "recorded-date": "08-04-2024, 17:13:39", + "recorded-date": "24-11-2025, 23:30:18", "recorded-content": { "invoke_result": { "Payload": "", @@ -3273,7 +3274,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke_url": { - "recorded-date": "08-04-2024, 17:11:35", + "recorded-date": "24-11-2025, 23:28:15", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -3333,7 +3334,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_error": { - "recorded-date": "24-02-2025, 16:26:37", + "recorded-date": "24-11-2025, 23:12:34", "recorded-content": { "invocation_error": { "ExecutedVersion": "$LATEST", @@ -3348,7 +3349,7 @@ " File \"\", line 1360, in _find_and_load\n", " File \"\", line 1331, in _find_and_load_unlocked\n", " File \"\", line 935, in _load_unlocked\n", - " File \"\", line 1022, in exec_module\n", + " File \"\", line 1027, in exec_module\n", " File \"\", line 488, in _call_with_frames_removed\n", " File \"/var/task/lambda_runtime_error.py\", line 1, in \n raise Exception(\"Runtime startup fails\")\n" ] @@ -3362,14 +3363,14 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invoke_exceptions": { - "recorded-date": "08-04-2024, 16:57:45", + "recorded-date": "24-11-2025, 23:11:21", "recorded-content": { "invoke_function_doesnotexist": { "Error": { "Code": "ResourceNotFoundException", - "Message": "Function not found: arn::lambda::111111111111:function:doesnotexist" + "Message": "Function not found: arn::lambda::111111111111:function:doesnotexist:$LATEST" }, - "Message": "Function not found: arn::lambda::111111111111:function:doesnotexist", + "Message": "Function not found: arn::lambda::111111111111:function:doesnotexist:$LATEST", "Type": "User", "ResponseMetadata": { "HTTPHeaders": {}, @@ -3379,7 +3380,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_wrapper_not_found": { - "recorded-date": "08-04-2024, 16:59:29", + "recorded-date": "24-11-2025, 23:13:27", "recorded-content": { "invocation_error": { "ExecutedVersion": "$LATEST", @@ -3397,7 +3398,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit": { - "recorded-date": "08-04-2024, 16:58:35", + "recorded-date": "24-11-2025, 23:12:36", "recorded-content": { "invocation_error": { "ExecutedVersion": "$LATEST", @@ -3415,7 +3416,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_exit": { - "recorded-date": "08-04-2024, 16:59:26", + "recorded-date": "24-11-2025, 23:13:24", "recorded-content": { "invocation_error": { "ExecutedVersion": "$LATEST", @@ -3433,7 +3434,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_error": { - "recorded-date": "08-04-2024, 16:59:23", + "recorded-date": "24-11-2025, 23:13:22", "recorded-content": { "invocation_error": { "ExecutedVersion": "$LATEST", @@ -3455,13 +3456,14 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit_segfault": { - "recorded-date": "08-04-2024, 16:59:20", + "recorded-date": "24-11-2025, 23:13:19", "recorded-content": { "invocation_error": { "ExecutedVersion": "$LATEST", "FunctionError": "Unhandled", "Payload": { - "errorMessage": "date Task timed out after 30.13 seconds" + "errorType": "Sandbox.Timedout", + "errorMessage": "RequestId: Error: Task timed out after 30.00 seconds" }, "StatusCode": 200, "ResponseMetadata": { @@ -3472,15 +3474,15 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[body-n\\x87r\\x9e\\xe9\\xb5\\xd7I\\xee\\x9bmt]": { - "recorded-date": "08-04-2024, 16:59:32", + "recorded-date": "24-11-2025, 23:13:29", "recorded-content": { "invoke_function_invalid_payload_body": { "Error": { "Code": "InvalidRequestContentException", - "Message": "Could not parse request body into json: Could not parse payload into json: Invalid UTF-8 start byte 0x87\n at [Source: (byte[])\"n\ufffdr\ufffd\ufffd\ufffdI\ufffdmt\"; line: 1, column: 3]" + "Message": "Could not parse request body into json: Could not parse payload into json: Invalid UTF-8 start byte 0x87\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 3]" }, "Type": "User", - "message": "Could not parse request body into json: Could not parse payload into json: Invalid UTF-8 start byte 0x87\n at [Source: (byte[])\"n\ufffdr\ufffd\ufffd\ufffdI\ufffdmt\"; line: 1, column: 3]", + "message": "Could not parse request body into json: Could not parse payload into json: Invalid UTF-8 start byte 0x87\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 3]", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 @@ -3489,15 +3491,15 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[message-\\x99\\xeb,j\\x07\\xa1zYh]": { - "recorded-date": "08-04-2024, 16:59:35", + "recorded-date": "24-11-2025, 23:13:31", "recorded-content": { "invoke_function_invalid_payload_message": { "Error": { "Code": "InvalidRequestContentException", - "Message": "Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 153)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: (byte[])\"\ufffd\ufffd,j\u0007\ufffdzYh\"; line: 1, column: 2]" + "Message": "Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 153)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]" }, "Type": "User", - "message": "Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 153)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: (byte[])\"\ufffd\ufffd,j\u0007\ufffdzYh\"; line: 1, column: 2]", + "message": "Could not parse request body into json: Could not parse payload into json: Unexpected character ((CTRL-CHAR, code 153)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 @@ -3506,23 +3508,23 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[1]": { - "recorded-date": "08-04-2024, 16:55:26", + "recorded-date": "24-11-2025, 23:08:16", "recorded-content": { "invoke-result-assumed-role-arn": "arn::sts::111111111111:assumed-role/lambda-autogenerated-/@lambda_function" } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[2]": { - "recorded-date": "08-04-2024, 16:55:32", + "recorded-date": "24-11-2025, 23:08:21", "recorded-content": { "invoke-result-assumed-role-arn": "arn::sts::111111111111:assumed-role/lambda-autogenerated-/" } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_large_payloads": { - "recorded-date": "08-04-2024, 16:55:07", + "recorded-date": "24-11-2025, 23:08:00", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_mixed_architecture": { - "recorded-date": "15-05-2024, 12:55:53", + "recorded-date": "24-11-2025, 23:08:39", "recorded-content": { "create_function_response": { "CreateEventSourceMappingResponse": null, @@ -3623,7 +3625,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_scheduling": { - "recorded-date": "16-04-2024, 08:03:42", + "recorded-date": "24-11-2025, 23:23:20", "recorded-content": { "get_provisioned_postwait": { "AllocatedProvisionedConcurrentExecutions": 1, @@ -3639,7 +3641,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_init_environment": { - "recorded-date": "08-04-2024, 16:56:25", + "recorded-date": "18-02-2026, 11:19:29", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -3668,7 +3670,7 @@ "PackageType": "Zip", "RevisionId": "", "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", + "Runtime": "python3.14", "RuntimeVersionConfig": { "RuntimeVersionArn": "arn::lambda:::runtime:" }, @@ -3694,35 +3696,35 @@ "ExecutedVersion": "$LATEST", "Payload": { "environment": { - "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", - "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", - "_LAMBDA_TELEMETRY_API_PASSPHRASE": "", - "LANG": "en_US.UTF-8", - "TZ": ":UTC", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_EXECUTION_ENV": "AWS_Lambda_python3.14", + "AWS_DEFAULT_REGION": "", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_REGION": "", + "PWD": "/var/task", "_HANDLER": "handler.handler", - "_LAMBDA_DIRECT_INVOKE_SOCKET": "9", - "_LAMBDA_CONTROL_SOCKET": "11", - "_LAMBDA_CONSOLE_SOCKET": "12", + "TZ": ":UTC", "LAMBDA_TASK_ROOT": "/var/task", - "LAMBDA_RUNTIME_DIR": "/var/runtime", - "_LAMBDA_LOG_FD": "18", - "_LAMBDA_SB_ID": "0", - "_LAMBDA_SHARED_MEM_FD": "8", - "AWS_REGION": "", - "AWS_DEFAULT_REGION": "", + "LANG": "en_US.UTF-8", + "AWS_SECRET_ACCESS_KEY": "", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", - "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_RUNTIME_API": ":9001", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "128", - "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", - "_AWS_XRAY_DAEMON_PORT": "2000", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", - "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "_X_AMZN_TRACE_ID": "", - "AWS_EXECUTION_ENV": "AWS_Lambda_rapid", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "_AWS_XRAY_DAEMON_ADDRESS": "", + "AWS_XRAY_DAEMON_ADDRESS": ":2000", + "SHLVL": "0", + "AWS_ACCESS_KEY_ID": "", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "AWS_LAMBDA_FUNCTION_NAME": "", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", - "_LAMBDA_RUNTIME_LOAD_TIME": "" + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "_AWS_XRAY_DAEMON_PORT": "2000", + "LC_CTYPE": "C.UTF-8", + "PYTHONPATH": "/var/runtime", + "_X_AMZN_TRACE_ID": "" } }, "StatusCode": 200, @@ -3734,7 +3736,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response": { - "recorded-date": "08-04-2024, 16:55:18", + "recorded-date": "24-11-2025, 23:08:09", "recorded-content": { "invoke_result": { "ExecutedVersion": "$LATEST", @@ -3765,7 +3767,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_handler_update": { - "recorded-date": "08-04-2024, 17:10:59", + "recorded-date": "24-11-2025, 23:26:26", "recorded-content": { "invoke_result_handler_one": { "ExecutedVersion": "$LATEST", @@ -3901,7 +3903,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invalid_invoke_mode": { - "recorded-date": "08-04-2024, 16:57:26", + "recorded-date": "24-11-2025, 23:11:02", "recorded-content": { "invoke_function_invalid_invoke_type": { "Error": { @@ -3916,7 +3918,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_update_function_url_config": { - "recorded-date": "08-04-2024, 16:57:18", + "recorded-date": "24-11-2025, 23:09:53", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -3992,7 +3994,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[RESPONSE_STREAM]": { - "recorded-date": "08-04-2024, 16:57:10", + "recorded-date": "24-11-2025, 23:09:46", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -4026,7 +4028,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[BUFFERED]": { - "recorded-date": "08-04-2024, 16:57:05", + "recorded-date": "24-11-2025, 23:09:43", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -4060,7 +4062,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[None]": { - "recorded-date": "08-04-2024, 16:57:02", + "recorded-date": "24-11-2025, 23:09:39", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -4093,7 +4095,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_default": { - "recorded-date": "18-07-2024, 14:18:08", + "recorded-date": "24-11-2025, 23:11:05", "recorded-content": { "url_response": { "args": { @@ -4126,7 +4128,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_trim_x_headers": { - "recorded-date": "08-04-2024, 16:57:34", + "recorded-date": "24-11-2025, 23:11:08", "recorded-content": { "url_response": { "args": { @@ -4152,15 +4154,15 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_large_response": { - "recorded-date": "08-04-2024, 16:55:12", + "recorded-date": "24-11-2025, 23:08:04", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_headers_and_status": { - "recorded-date": "08-04-2024, 16:57:14", + "recorded-date": "24-11-2025, 23:09:49", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_recreate_function": { - "recorded-date": "15-05-2024, 10:16:45", + "recorded-date": "24-11-2025, 23:13:45", "recorded-content": { "create_function_response_one": { "CreateEventSourceMappingResponse": null, @@ -4263,7 +4265,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_async_invoke_queue_upon_function_update": { - "recorded-date": "15-05-2024, 17:38:05", + "recorded-date": "24-11-2025, 23:27:40", "recorded-content": { "async_invoke_history_sorted": [ "01-sleep--variant-1", @@ -4304,7 +4306,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_form_payload": { - "recorded-date": "13-06-2024, 21:01:16", + "recorded-date": "24-11-2025, 23:11:12", "recorded-content": { "url_response": { "args": {}, @@ -4329,55 +4331,55 @@ "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_lambda_layer": { - "recorded-date": "14-06-2024, 15:16:46", + "recorded-date": "24-11-2025, 23:13:51", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function": { - "recorded-date": "14-06-2024, 15:16:50", + "recorded-date": "24-11-2025, 23:13:53", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function_configuration": { - "recorded-date": "14-06-2024, 15:16:53", + "recorded-date": "24-11-2025, 23:13:56", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_list_versions_by_function": { - "recorded-date": "14-06-2024, 15:16:56", + "recorded-date": "24-11-2025, 23:13:58", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_concurrency": { - "recorded-date": "14-06-2024, 15:16:59", + "recorded-date": "24-11-2025, 23:14:01", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_alias": { - "recorded-date": "14-06-2024, 15:17:03", + "recorded-date": "24-11-2025, 23:14:04", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_tags": { - "recorded-date": "14-06-2024, 15:17:07", + "recorded-date": "24-11-2025, 23:14:07", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_invocation": { - "recorded-date": "14-06-2024, 15:17:10", + "recorded-date": "24-11-2025, 23:14:10", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_publish_version": { - "recorded-date": "14-06-2024, 15:17:14", + "recorded-date": "24-11-2025, 23:14:13", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_delete_function": { - "recorded-date": "14-06-2024, 15:17:17", + "recorded-date": "24-11-2025, 23:14:15", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id": { - "recorded-date": "05-08-2024, 12:24:48", + "recorded-date": "24-11-2025, 23:09:33", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id_aliased": { - "recorded-date": "05-08-2024, 12:48:07", + "recorded-date": "24-11-2025, 23:09:36", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_persists_after_alias_delete": { - "recorded-date": "05-08-2024, 13:57:04", + "recorded-date": "24-11-2025, 23:11:00", "recorded-content": { "create_lambda_url_config": { "AuthType": "NONE", @@ -4401,7 +4403,7 @@ }, "invoke_deleted_alias_url_response_delayed": { "content": { - "Message": null + "Message": "Forbidden. For troubleshooting Function URL authorization issues, see: https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html" }, "headers": { "x-amzn-ErrorType": "AccessDeniedException" @@ -4411,7 +4413,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": { - "recorded-date": "09-10-2024, 16:15:57", + "recorded-date": "24-11-2025, 23:11:49", "recorded-content": { "invoke-result": { "ExecutedVersion": "$LATEST", @@ -4425,7 +4427,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": { - "recorded-date": "09-10-2024, 16:16:05", + "recorded-date": "24-11-2025, 23:11:57", "recorded-content": { "invoke-result": { "Payload": "", @@ -4438,7 +4440,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": { - "recorded-date": "09-10-2024, 16:16:14", + "recorded-date": "24-11-2025, 23:12:06", "recorded-content": { "invoke-result": { "ExecutedVersion": "$LATEST", @@ -4452,7 +4454,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": { - "recorded-date": "09-10-2024, 16:16:28", + "recorded-date": "24-11-2025, 23:12:16", "recorded-content": { "invoke-result": { "Payload": "", @@ -4465,7 +4467,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_on_alias": { - "recorded-date": "07-05-2025, 09:26:54", + "recorded-date": "24-11-2025, 23:21:03", "recorded-content": { "put_provisioned_5": { "AllocatedProvisionedConcurrentExecutions": 0, @@ -4503,7 +4505,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_update": { - "recorded-date": "22-05-2025, 14:11:12", + "recorded-date": "24-11-2025, 23:14:20", "recorded-content": { "put_function_concurrency": { "ReservedConcurrentExecutions": 3, @@ -4582,7 +4584,7 @@ } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_host_prefix_api_operation": { - "recorded-date": "26-05-2025, 16:38:54", + "recorded-date": "24-11-2025, 23:09:01", "recorded-content": { "invoke-result": { "ExecutedVersion": "$LATEST", @@ -4604,5 +4606,55 @@ } } } + }, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_function_url_with_response_streaming": { + "recorded-date": "24-11-2025, 23:09:29", + "recorded-content": { + "response_status": 200, + "response_data": "Hello, world!" + } + }, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_non_existing_url": { + "recorded-date": "24-11-2025, 23:11:02", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_init_failure": { + "recorded-date": "17-02-2026, 09:40:04", + "recorded-content": { + "put_provisioned": { + "AllocatedProvisionedConcurrentExecutions": 0, + "AvailableProvisionedConcurrentExecutions": 0, + "LastModified": "date", + "RequestedProvisionedConcurrentExecutions": 1, + "Status": "IN_PROGRESS", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 202 + } + }, + "get_provisioned_prewait": { + "AllocatedProvisionedConcurrentExecutions": 0, + "AvailableProvisionedConcurrentExecutions": 0, + "LastModified": "date", + "RequestedProvisionedConcurrentExecutions": 1, + "Status": "IN_PROGRESS", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_provisioned_postwait": { + "AllocatedProvisionedConcurrentExecutions": 0, + "AvailableProvisionedConcurrentExecutions": 0, + "LastModified": "date", + "RequestedProvisionedConcurrentExecutions": 1, + "Status": "FAILED", + "StatusReason": "FUNCTION_ERROR_INIT_FAILURE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/lambda_/test_lambda.validation.json b/tests/aws/services/lambda_/test_lambda.validation.json index 9b5d816f5ac1e..4c2c4448f5261 100644 --- a/tests/aws/services/lambda_/test_lambda.validation.json +++ b/tests/aws/services/lambda_/test_lambda.validation.json @@ -1,120 +1,351 @@ { "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_alias_routingconfig": { - "last_validated_date": "2024-04-08T17:11:17+00:00" + "last_validated_date": "2025-11-24T23:27:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 10.8, + "teardown": 0.56, + "total": 11.36 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_lambda_alias_moving": { - "last_validated_date": "2024-04-08T17:11:05+00:00" + "last_validated_date": "2025-11-25T23:37:28+00:00", + "durations_in_seconds": { + "setup": 11.16, + "call": 7.47, + "teardown": 1.06, + "total": 19.69 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[1]": { - "last_validated_date": "2024-04-08T16:55:25+00:00" + "last_validated_date": "2025-11-24T23:08:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.38, + "teardown": 0.52, + "total": 4.9 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[2]": { - "last_validated_date": "2024-04-08T16:55:31+00:00" + "last_validated_date": "2025-11-24T23:08:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.49, + "teardown": 0.48, + "total": 4.97 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_function_state": { - "last_validated_date": "2024-04-08T16:55:20+00:00" + "last_validated_date": "2025-11-24T23:08:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.78, + "teardown": 0.21, + "total": 1.99 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_different_iam_keys_environment": { - "last_validated_date": "2024-04-08T16:55:37+00:00" + "last_validated_date": "2025-11-24T23:08:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.58, + "teardown": 0.54, + "total": 5.12 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_large_response": { - "last_validated_date": "2024-04-08T16:55:11+00:00" + "last_validated_date": "2025-11-24T23:08:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.51, + "teardown": 0.31, + "total": 3.82 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response": { - "last_validated_date": "2024-04-08T16:55:18+00:00" + "last_validated_date": "2025-11-24T23:08:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.3, + "teardown": 0.35, + "total": 5.65 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_large_payloads": { - "last_validated_date": "2024-04-08T16:55:06+00:00" + "last_validated_date": "2025-11-24T23:08:00+00:00", + "durations_in_seconds": { + "setup": 11.1, + "call": 5.22, + "teardown": 0.57, + "total": 16.89 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[nodejs]": { - "last_validated_date": "2024-04-08T16:55:56+00:00" + "last_validated_date": "2025-11-24T23:08:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.13, + "teardown": 0.34, + "total": 2.47 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[python]": { - "last_validated_date": "2024-04-08T16:55:59+00:00" + "last_validated_date": "2025-11-24T23:08:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.17, + "teardown": 0.49, + "total": 2.66 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_host_prefix_api_operation": { - "last_validated_date": "2025-05-26T16:38:53+00:00" + "last_validated_date": "2025-11-24T23:09:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.38, + "teardown": 0.56, + "total": 3.94 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_init_environment": { - "last_validated_date": "2024-04-08T16:56:25+00:00" + "last_validated_date": "2026-02-18T11:19:29+00:00", + "durations_in_seconds": { + "setup": 12.32, + "call": 3.18, + "teardown": 1.93, + "total": 17.43 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_no_timeout": { - "last_validated_date": "2024-04-08T16:56:14+00:00" + "last_validated_date": "2025-11-24T23:08:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.59, + "teardown": 0.32, + "total": 6.91 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_timed_out_environment_reuse": { "last_validated_date": "2024-04-08T16:56:22+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_with_timeout": { - "last_validated_date": "2024-04-08T16:56:04+00:00" + "last_validated_date": "2025-11-25T00:39:46+00:00", + "durations_in_seconds": { + "setup": 11.18, + "call": 3.78, + "teardown": 1.05, + "total": 16.01 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_mixed_architecture": { - "last_validated_date": "2024-05-15T12:55:52+00:00" + "last_validated_date": "2025-11-24T23:08:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.18, + "teardown": 0.52, + "total": 4.7 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_arm": { - "last_validated_date": "2024-04-08T16:55:44+00:00" + "last_validated_date": "2025-11-24T23:08:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.13, + "teardown": 0.5, + "total": 2.63 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_x86": { - "last_validated_date": "2024-04-08T16:55:41+00:00" + "last_validated_date": "2025-11-24T23:08:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.08, + "teardown": 0.49, + "total": 2.57 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_ulimits": { - "last_validated_date": "2024-04-16T08:12:11+00:00" + "last_validated_date": "2025-11-24T23:08:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.06, + "teardown": 0.62, + "total": 2.68 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_recreate_function": { - "last_validated_date": "2024-05-15T10:16:44+00:00" + "last_validated_date": "2025-11-24T23:13:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.09, + "teardown": 0.44, + "total": 13.53 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_block": { - "last_validated_date": "2024-04-08T17:02:06+00:00" + "last_validated_date": "2025-11-24T23:16:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 138.97, + "teardown": 0.72, + "total": 139.69 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_crud": { - "last_validated_date": "2025-05-22T08:04:13+00:00" + "last_validated_date": "2025-11-24T23:14:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.14, + "teardown": 0.49, + "total": 2.63 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_update": { - "last_validated_date": "2025-05-22T14:11:12+00:00" + "last_validated_date": "2025-11-24T23:14:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.14, + "teardown": 0.49, + "total": 2.63 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_moves_with_alias": { "last_validated_date": "2023-03-21T07:47:38+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_scheduling": { - "last_validated_date": "2024-04-16T08:03:41+00:00" + "last_validated_date": "2025-11-24T23:23:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 136.09, + "teardown": 0.73, + "total": 136.82 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency": { - "last_validated_date": "2024-04-08T17:04:20+00:00" + "last_validated_date": "2025-11-24T23:18:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 130.34, + "teardown": 0.81, + "total": 131.15 + } + }, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_init_failure": { + "last_validated_date": "2026-02-17T09:40:06+00:00", + "durations_in_seconds": { + "setup": 12.05, + "call": 323.08, + "teardown": 2.23, + "total": 337.36 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_on_alias": { - "last_validated_date": "2025-05-07T09:26:54+00:00" + "last_validated_date": "2025-11-24T23:21:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 130.7, + "teardown": 0.67, + "total": 131.37 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency": { - "last_validated_date": "2024-04-08T17:08:10+00:00" + "last_validated_date": "2025-11-25T11:20:35+00:00", + "durations_in_seconds": { + "setup": 11.64, + "call": 6.88, + "teardown": 1.57, + "total": 20.09 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency_async_queue": { - "last_validated_date": "2025-03-26T10:54:29+00:00" + "last_validated_date": "2025-11-24T23:23:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.88, + "teardown": 0.86, + "total": 7.74 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_provisioned_overlap": { - "last_validated_date": "2024-04-08T17:10:36+00:00" + "last_validated_date": "2025-11-24T23:26:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 141.12, + "teardown": 0.75, + "total": 141.87 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_error": { - "last_validated_date": "2024-04-08T16:59:22+00:00" + "last_validated_date": "2025-11-24T23:13:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.18, + "teardown": 0.32, + "total": 2.5 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_exit": { - "last_validated_date": "2024-04-08T16:59:25+00:00" + "last_validated_date": "2025-11-24T23:13:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.11, + "teardown": 0.56, + "total": 2.67 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[body-n\\x87r\\x9e\\xe9\\xb5\\xd7I\\xee\\x9bmt]": { - "last_validated_date": "2024-04-08T16:59:31+00:00" + "last_validated_date": "2025-11-24T23:13:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.01, + "teardown": 0.3, + "total": 2.31 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[message-\\x99\\xeb,j\\x07\\xa1zYh]": { - "last_validated_date": "2024-04-08T16:59:34+00:00" + "last_validated_date": "2025-11-24T23:13:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.93, + "teardown": 0.53, + "total": 2.46 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_error": { - "last_validated_date": "2025-02-24T16:26:36+00:00" + "last_validated_date": "2025-11-24T23:12:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.33, + "teardown": 0.61, + "total": 2.94 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit": { - "last_validated_date": "2024-04-08T16:58:35+00:00" + "last_validated_date": "2025-11-24T23:12:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.23, + "teardown": 0.37, + "total": 2.6 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit_segfault": { - "last_validated_date": "2024-04-08T16:59:19+00:00" + "last_validated_date": "2025-11-24T23:13:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 42.13, + "teardown": 0.61, + "total": 42.74 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_wrapper_not_found": { - "last_validated_date": "2024-04-08T16:59:28+00:00" + "last_validated_date": "2025-11-24T23:13:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.93, + "teardown": 0.41, + "total": 2.34 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[nodejs16.x]": { "last_validated_date": "2022-09-09T17:15:38+00:00" @@ -123,177 +354,504 @@ "last_validated_date": "2023-04-26T17:37:23+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[nodejs16.x]": { - "last_validated_date": "2024-04-08T16:57:59+00:00" + "last_validated_date": "2025-11-24T23:11:31+00:00", + "durations_in_seconds": { + "setup": 1.71, + "call": 3.09, + "teardown": 0.44, + "total": 5.24 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[python3.10]": { - "last_validated_date": "2024-04-08T16:58:09+00:00" + "last_validated_date": "2025-11-24T23:11:39+00:00", + "durations_in_seconds": { + "setup": 1.96, + "call": 5.36, + "teardown": 0.38, + "total": 7.7 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event_error": { "last_validated_date": "2023-09-04T20:49:02+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": { - "last_validated_date": "2024-10-09T16:16:27+00:00" + "last_validated_date": "2025-11-24T23:12:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 10.05, + "teardown": 0.42, + "total": 10.47 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": { - "last_validated_date": "2024-10-09T16:16:13+00:00" + "last_validated_date": "2025-11-24T23:12:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.84, + "teardown": 0.35, + "total": 9.19 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": { - "last_validated_date": "2024-10-09T16:16:05+00:00" + "last_validated_date": "2025-11-24T23:11:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.08, + "teardown": 0.3, + "total": 7.38 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": { - "last_validated_date": "2024-10-09T16:15:57+00:00" + "last_validated_date": "2025-11-24T23:11:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.87, + "teardown": 0.37, + "total": 10.24 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[nodejs16.x]": { - "last_validated_date": "2024-04-08T16:57:47+00:00" + "last_validated_date": "2025-11-24T23:11:23+00:00", + "durations_in_seconds": { + "setup": 1.82, + "call": 0.39, + "teardown": 0.5, + "total": 2.71 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[python3.10]": { - "last_validated_date": "2024-04-08T16:57:50+00:00" + "last_validated_date": "2025-11-24T23:11:26+00:00", + "durations_in_seconds": { + "setup": 1.8, + "call": 0.41, + "teardown": 0.53, + "total": 2.74 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[nodejs16.x]": { - "last_validated_date": "2024-04-08T16:57:40+00:00" + "last_validated_date": "2025-11-24T23:11:18+00:00", + "durations_in_seconds": { + "setup": 1.78, + "call": 0.47, + "teardown": 0.57, + "total": 2.82 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[python3.10]": { - "last_validated_date": "2024-04-08T16:57:44+00:00" + "last_validated_date": "2025-11-24T23:11:20+00:00", + "durations_in_seconds": { + "setup": 1.87, + "call": 0.34, + "teardown": 0.49, + "total": 2.7 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_qualifier": { - "last_validated_date": "2024-04-08T16:58:20+00:00" + "last_validated_date": "2025-11-24T23:12:27+00:00", + "durations_in_seconds": { + "setup": 0.67, + "call": 8.66, + "teardown": 1.07, + "total": 10.4 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invoke_exceptions": { - "last_validated_date": "2024-04-08T16:57:45+00:00" + "last_validated_date": "2025-11-24T23:11:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.12, + "teardown": 0.0, + "total": 0.12 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_lambda_with_context": { "last_validated_date": "2022-09-09T18:19:33+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_upload_lambda_from_s3": { - "last_validated_date": "2024-04-08T16:58:25+00:00" + "last_validated_date": "2025-11-24T23:12:31+00:00", + "durations_in_seconds": { + "setup": 0.61, + "call": 2.5, + "teardown": 1.06, + "total": 4.17 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_cross_account_access": { "last_validated_date": "2024-06-14T12:09:31+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_delete_function": { - "last_validated_date": "2024-06-14T15:17:16+00:00" + "last_validated_date": "2025-11-24T23:14:15+00:00", + "durations_in_seconds": { + "setup": 1.9, + "call": 0.31, + "teardown": 0.36, + "total": 2.57 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_alias": { - "last_validated_date": "2024-06-14T15:17:02+00:00" + "last_validated_date": "2025-11-24T23:14:04+00:00", + "durations_in_seconds": { + "setup": 1.89, + "call": 0.45, + "teardown": 0.51, + "total": 2.85 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_concurrency": { - "last_validated_date": "2024-06-14T15:16:58+00:00" + "last_validated_date": "2025-11-24T23:14:01+00:00", + "durations_in_seconds": { + "setup": 1.81, + "call": 0.21, + "teardown": 0.51, + "total": 2.53 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_invocation": { - "last_validated_date": "2024-06-14T15:17:09+00:00" + "last_validated_date": "2025-11-24T23:14:10+00:00", + "durations_in_seconds": { + "setup": 1.86, + "call": 0.47, + "teardown": 0.6, + "total": 2.93 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_tags": { - "last_validated_date": "2024-06-14T15:17:06+00:00" + "last_validated_date": "2025-11-24T23:14:07+00:00", + "durations_in_seconds": { + "setup": 1.8, + "call": 0.37, + "teardown": 0.78, + "total": 2.95 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function": { - "last_validated_date": "2024-06-14T15:16:49+00:00" + "last_validated_date": "2025-11-24T23:13:53+00:00", + "durations_in_seconds": { + "setup": 1.82, + "call": 0.19, + "teardown": 0.51, + "total": 2.52 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function_configuration": { - "last_validated_date": "2024-06-14T15:16:52+00:00" + "last_validated_date": "2025-11-24T23:13:56+00:00", + "durations_in_seconds": { + "setup": 1.81, + "call": 0.18, + "teardown": 0.57, + "total": 2.56 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_lambda_layer": { - "last_validated_date": "2024-06-14T15:16:46+00:00" + "last_validated_date": "2025-11-24T23:13:51+00:00", + "durations_in_seconds": { + "setup": 5.4, + "call": 0.33, + "teardown": 0.0, + "total": 5.73 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_list_versions_by_function": { - "last_validated_date": "2024-06-14T15:16:55+00:00" + "last_validated_date": "2025-11-24T23:13:58+00:00", + "durations_in_seconds": { + "setup": 1.79, + "call": 0.16, + "teardown": 0.51, + "total": 2.46 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_publish_version": { - "last_validated_date": "2024-06-14T15:17:13+00:00" + "last_validated_date": "2025-11-24T23:14:13+00:00", + "durations_in_seconds": { + "setup": 1.87, + "call": 0.49, + "teardown": 0.65, + "total": 3.01 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaPermissions::test_lambda_permission_url_invocation": { - "last_validated_date": "2024-04-08T16:57:37+00:00" + "last_validated_date": "2025-11-24T23:11:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.26, + "teardown": 0.53, + "total": 2.79 + } + }, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_function_url_with_response_streaming": { + "last_validated_date": "2025-11-24T23:09:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.87, + "teardown": 0.52, + "total": 3.39 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_update_function_url_config": { - "last_validated_date": "2024-04-08T16:57:17+00:00" + "last_validated_date": "2025-11-24T23:09:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.1, + "teardown": 0.53, + "total": 3.63 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture": { "last_validated_date": "2024-03-28T22:20:14+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_default": { - "last_validated_date": "2024-07-18T14:18:07+00:00" + "last_validated_date": "2025-11-24T23:11:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.52, + "teardown": 0.58, + "total": 3.1 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_trim_x_headers": { - "last_validated_date": "2024-04-08T16:57:34+00:00" + "last_validated_date": "2025-11-24T23:11:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.54, + "teardown": 0.51, + "total": 3.05 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[BUFFERED]": { - "last_validated_date": "2024-04-08T16:57:05+00:00" + "last_validated_date": "2025-11-24T23:09:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.72, + "teardown": 0.54, + "total": 3.26 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[None]": { - "last_validated_date": "2024-04-08T16:57:01+00:00" + "last_validated_date": "2025-11-24T23:09:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.83, + "teardown": 0.52, + "total": 3.35 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[RESPONSE_STREAM]": { - "last_validated_date": "2024-04-08T16:57:09+00:00" + "last_validated_date": "2025-11-24T23:09:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.7, + "teardown": 0.56, + "total": 3.26 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_form_payload": { - "last_validated_date": "2024-06-13T21:01:15+00:00" + "last_validated_date": "2025-11-24T23:11:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.25, + "teardown": 0.55, + "total": 3.8 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_headers_and_status": { - "last_validated_date": "2024-04-08T16:57:13+00:00" + "last_validated_date": "2025-11-24T23:09:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.82, + "teardown": 0.56, + "total": 3.38 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invalid_invoke_mode": { - "last_validated_date": "2024-04-08T16:57:25+00:00" + "last_validated_date": "2025-11-24T23:11:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.73, + "teardown": 0.35, + "total": 2.08 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[boolean]": { - "last_validated_date": "2024-04-08T16:56:56+00:00" + "last_validated_date": "2025-11-24T23:09:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.42, + "teardown": 0.51, + "total": 2.93 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[dict]": { - "last_validated_date": "2024-04-08T16:56:29+00:00" + "last_validated_date": "2025-11-24T23:09:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.61, + "teardown": 0.53, + "total": 3.14 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[float]": { - "last_validated_date": "2024-04-08T16:56:52+00:00" + "last_validated_date": "2025-11-24T23:09:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.57, + "teardown": 0.48, + "total": 3.05 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response-json]": { - "last_validated_date": "2024-04-08T16:56:36+00:00" + "last_validated_date": "2025-11-24T23:09:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.56, + "teardown": 0.49, + "total": 3.05 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response]": { - "last_validated_date": "2024-04-08T16:56:32+00:00" + "last_validated_date": "2025-11-24T23:09:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.48, + "teardown": 0.49, + "total": 2.97 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[integer]": { - "last_validated_date": "2024-04-08T16:56:48+00:00" + "last_validated_date": "2025-11-24T23:09:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.8, + "teardown": 0.52, + "total": 3.32 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[list-mixed]": { - "last_validated_date": "2024-04-08T16:56:40+00:00" + "last_validated_date": "2025-11-24T23:09:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.5, + "teardown": 0.48, + "total": 2.98 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[string]": { - "last_validated_date": "2024-04-08T16:56:44+00:00" + "last_validated_date": "2025-11-24T23:09:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.59, + "teardown": 0.56, + "total": 3.15 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id": { - "last_validated_date": "2024-08-05T12:24:46+00:00" + "last_validated_date": "2025-11-24T23:09:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.67, + "teardown": 0.59, + "total": 3.26 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id_aliased": { - "last_validated_date": "2024-08-05T12:48:05+00:00" + "last_validated_date": "2025-11-24T23:09:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.85, + "teardown": 0.57, + "total": 3.42 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_exception": { - "last_validated_date": "2024-04-08T16:57:22+00:00" + "last_validated_date": "2025-11-24T23:09:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.88, + "teardown": 0.5, + "total": 3.38 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_non_existing_url": { - "last_validated_date": "2024-04-11T17:16:39+00:00" + "last_validated_date": "2025-11-24T23:11:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.29, + "teardown": 0.0, + "total": 0.29 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_persists_after_alias_delete": { - "last_validated_date": "2024-08-05T13:57:02+00:00" + "last_validated_date": "2025-11-24T23:11:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 62.73, + "teardown": 0.65, + "total": 63.38 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_async_invoke_queue_upon_function_update": { - "last_validated_date": "2024-05-15T18:29:38+00:00" + "last_validated_date": "2025-11-24T23:27:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 71.82, + "teardown": 1.85, + "total": 73.67 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_function_update_during_invoke": { "last_validated_date": "2024-05-15T19:05:04+00:00" }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_handler_update": { - "last_validated_date": "2024-04-08T17:10:58+00:00" + "last_validated_date": "2025-11-24T23:26:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.64, + "teardown": 0.65, + "total": 5.29 + } }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_versions_with_code_changes": { - "last_validated_date": "2024-04-08T17:10:52+00:00" + "last_validated_date": "2025-11-24T23:26:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.8, + "teardown": 0.46, + "total": 13.26 + } }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_async_invoke_with_retry": { - "last_validated_date": "2024-04-08T17:13:38+00:00" + "last_validated_date": "2025-11-24T23:30:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 122.59, + "teardown": 1.51, + "total": 124.1 + } }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_format": { - "last_validated_date": "2024-04-08T17:11:17+00:00" + "last_validated_date": "2025-11-24T23:27:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.16, + "teardown": 0.0, + "total": 0.16 + } }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke": { - "last_validated_date": "2024-04-08T17:11:26+00:00" + "last_validated_date": "2025-11-24T23:28:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.99, + "teardown": 0.42, + "total": 7.41 + } }, "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke_url": { - "last_validated_date": "2024-04-08T17:11:34+00:00" + "last_validated_date": "2025-11-24T23:28:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.54, + "teardown": 0.38, + "total": 9.92 + } } } diff --git a/tests/aws/services/lambda_/test_lambda_api.py b/tests/aws/services/lambda_/test_lambda_api.py index 9b897a1326192..4f6b0a89ab080 100644 --- a/tests/aws/services/lambda_/test_lambda_api.py +++ b/tests/aws/services/lambda_/test_lambda_api.py @@ -11,14 +11,15 @@ import base64 import io +import itertools import json import logging import re import threading +from collections.abc import Callable from hashlib import sha256 from io import BytesIO from random import randint -from typing import Callable import pytest import requests @@ -32,6 +33,7 @@ LogFormat, Runtime, ) +from localstack.config import HostAndPort from localstack.services.lambda_.api_utils import ARCHITECTURES from localstack.services.lambda_.provider import TAG_KEY_CUSTOM_URL from localstack.services.lambda_.provider_utils import LambdaLayerVersionIdentifier @@ -50,6 +52,7 @@ from localstack.utils import testutil from localstack.utils.aws import arns from localstack.utils.aws.arns import ( + capacity_provider_arn, get_partition, lambda_event_source_mapping_arn, lambda_function_arn, @@ -101,16 +104,12 @@ def test_create_deprecated_function_runtime_with_validation_disabled( func_name=function_name, runtime=Runtime.python3_7, role=lambda_su_role, - MemorySize=256, - Timeout=5, - LoggingConfig={ - "LogFormat": LogFormat.JSON, - }, ) @markers.aws.validated @markers.lambda_runtime_update @pytest.mark.parametrize("runtime", DEPRECATED_RUNTIMES) + @markers.requires_in_process def test_create_deprecated_function_runtime_with_validation_enabled( self, runtime, lambda_su_role, aws_client, monkeypatch, snapshot ): @@ -124,11 +123,6 @@ def test_create_deprecated_function_runtime_with_validation_enabled( func_name=function_name, runtime=runtime, role=lambda_su_role, - MemorySize=256, - Timeout=5, - LoggingConfig={ - "LogFormat": LogFormat.JSON, - }, ) snapshot.match("deprecation_error", e.value.response) @@ -321,9 +315,11 @@ def test_function_partial_advanced_logging_configuration_update( class TestLambdaFunction: @markers.snapshot.skip_snapshot_verify( - # The RuntimeVersionArn is currently a hardcoded id and therefore does not reflect the ARN resource update - # for different runtime versions" - paths=["$..RuntimeVersionConfig.RuntimeVersionArn"] + paths=[ + # The RuntimeVersionArn is currently a hardcoded id and therefore does not reflect the ARN resource update + # for different runtime versions" + "$..RuntimeVersionConfig.RuntimeVersionArn", + ] ) @markers.aws.validated def test_function_lifecycle(self, snapshot, create_lambda_function, lambda_su_role, aws_client): @@ -556,6 +552,7 @@ def test_get_function_wrong_region( snapshot.match("wrong_region_exception", e.value.response) @markers.aws.validated + @markers.requires_docker # Kubernetes executor is ready too fast, no in progress snapshot possible def test_lambda_code_location_zipfile( self, snapshot, create_lambda_function_aws, lambda_su_role, aws_client ): @@ -601,6 +598,7 @@ def test_lambda_code_location_zipfile( ) @markers.aws.validated + @markers.requires_docker # Kubernetes executor is ready too fast, no in progress snapshot possible def test_lambda_code_location_s3( self, s3_bucket, snapshot, create_lambda_function_aws, lambda_su_role, aws_client ): @@ -653,6 +651,66 @@ def test_lambda_code_location_s3( == get_function_response_updated["Configuration"]["CodeSize"] ) + @markers.aws.validated + def test_lambda_code_location_s3_errors( + self, s3_bucket, snapshot, lambda_su_role, aws_client, create_lambda_function_aws + ): + function_name = f"code-function-{short_uid()}" + bucket_key = "code/code-function.zip" + zip_file_bytes = create_lambda_archive(load_file(TEST_LAMBDA_PYTHON_ECHO), get_content=True) + aws_client.s3.upload_fileobj( + Fileobj=io.BytesIO(zip_file_bytes), Bucket=s3_bucket, Key=bucket_key + ) + + # try to create the function with invalid bucket path + with pytest.raises(ClientError) as e: + aws_client.lambda_.create_function( + FunctionName=function_name, + Handler="index.handler", + Code={ + "S3Bucket": f"some-random-non-existent-bucket-{short_uid()}", + "S3Key": bucket_key, + }, + PackageType="Zip", + Role=lambda_su_role, + Runtime=Runtime.python3_12, + ) + snapshot.match("create-error-wrong-bucket", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.lambda_.create_function( + FunctionName=function_name, + Handler="index.handler", + Code={"S3Bucket": s3_bucket, "S3Key": "non/existent.zip"}, + PackageType="Zip", + Role=lambda_su_role, + Runtime=Runtime.python3_12, + ) + snapshot.match("create-error-wrong-key", e.value.response) + + create_lambda_function_aws( + FunctionName=function_name, + Handler="index.handler", + Code={"S3Bucket": s3_bucket, "S3Key": bucket_key}, + PackageType="Zip", + Role=lambda_su_role, + Runtime=Runtime.python3_12, + ) + + with pytest.raises(ClientError) as e: + aws_client.lambda_.update_function_code( + FunctionName=function_name, + S3Bucket=f"some-random-non-existent-bucket-{short_uid()}", + S3Key=bucket_key, + ) + snapshot.match("update-error-wrong-bucket", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.lambda_.update_function_code( + FunctionName=function_name, S3Bucket=s3_bucket, S3Key="non/existent.zip" + ) + snapshot.match("update-error-wrong-key", e.value.response) + # TODO: fix type of AccessDeniedException yielding null @markers.snapshot.skip_snapshot_verify( paths=[ @@ -907,8 +965,12 @@ def test_function_name_and_qualifier_validation( "function_name_too_long-invoke", "incomplete_arn-invoke", ) + and not is_aws_cloud() ): - pytest.skip("skipping test case") + pytest.skip("Not implemented") + + if request.node.callspec.id == "incomplete_arn-create_function": + pytest.skip("Works against AWS") function_name = test_case["FunctionName"].format( region_name=region_name, account_id=account_id @@ -1386,6 +1448,7 @@ def test_invalid_invoke(self, aws_client, snapshot): reason="Test will fail against other executors as they are not patched to take longer for the update", ) @markers.aws.validated + @markers.requires_in_process def test_lambda_concurrent_code_updates( self, aws_client, create_lambda_function_aws, lambda_su_role, snapshot, monkeypatch ): @@ -1438,6 +1501,7 @@ def _runtime_client_path(*args, **kwargs): reason="Test will fail against other executors as they are not patched to take longer for the update", ) @markers.aws.validated + @markers.requires_docker def test_lambda_concurrent_config_updates( self, aws_client, create_lambda_function, lambda_su_role, snapshot, monkeypatch ): @@ -1631,6 +1695,7 @@ def _create_test_image(base_image: str): LOG.debug("Error cleaning up repository %s: %s", repository_name, e) @markers.aws.validated + @markers.requires_docker # Requires docker for image hash def test_lambda_image_crud( self, create_lambda_function_aws, lambda_su_role, ecr_image, snapshot, aws_client ): @@ -2033,6 +2098,7 @@ def test_publish_with_wrong_sha256( snapshot.match("publish_result", publish_result) @markers.aws.validated + @markers.requires_docker # Kubernetes executor is ready too fast, no in progress snapshot possible def test_publish_with_update( self, create_lambda_function_aws, lambda_su_role, snapshot, aws_client ): @@ -2693,6 +2759,9 @@ def test_function_revisions_version_and_alias( assert rev_a1_create_alias != rev_a2_update_alias @markers.aws.validated + @pytest.mark.skip( + reason="The API on AWS has changed significantly and this test needs re-writing" + ) def test_function_revisions_permissions(self, create_lambda_function, snapshot, aws_client): """Tests revision id lifecycle for adding and removing permissions""" # rev1: create function @@ -2924,8 +2993,8 @@ def test_tag_lifecycle(self, snapshot, aws_client, resource_arn_fixture, request @pytest.mark.parametrize( "create_resource_arn", - [lambda_function_arn, lambda_event_source_mapping_arn], - ids=["lambda_function", "event_source_mapping"], + [lambda_function_arn, lambda_event_source_mapping_arn, capacity_provider_arn], + ids=["lambda_function", "event_source_mapping", "capacity_provider"], ) @markers.aws.validated def test_tag_exceptions( @@ -3885,6 +3954,13 @@ def _wait_provisioned(): class TestLambdaPermissions: + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: adjust validation to new AWS behavior, raising function not found under a certain condition + "get_policy_fn_doesnotexist..Error.Message", + "get_policy_fn_doesnotexist..Message", + ] + ) @markers.aws.validated def test_permission_exceptions( self, create_lambda_function, account_id, snapshot, aws_client, region_name @@ -4078,6 +4154,7 @@ def test_add_lambda_permission_aws( snapshot.match("get_policy", get_policy_result) @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(["$..RevisionId"]) # TODO fix in follow up def test_lambda_permission_fn_versioning( self, create_lambda_function, account_id, snapshot, aws_client, region_name ): @@ -4731,6 +4808,7 @@ def test_create_url_config_custom_id_tag(self, create_lambda_function, aws_clien # region changes, https vs http, etc assert f"://{custom_id_value}.lambda-url." in url_config_created["FunctionUrl"] + @markers.requires_in_process @markers.aws.only_localstack def test_create_url_config_custom_id_tag_invalid_id( self, create_lambda_function, aws_client, caplog @@ -4848,7 +4926,7 @@ def _assert_create_aliased_function_url(fn_version: str, fn_alias: str): class TestLambdaSizeLimits: def _generate_sized_python_str(self, filepath: str, size: int) -> str: """Generate a text of the specified size by appending #s at the end of the file""" - with open(filepath, "r") as f: + with open(filepath) as f: py_str = f.read() py_str += "#" * (size - len(py_str)) return py_str @@ -5414,7 +5492,9 @@ def test_account_settings_total_code_size_config_update( class TestLambdaEventSourceMappings: @markers.aws.validated - def test_event_source_mapping_exceptions(self, snapshot, aws_client): + def test_event_source_mapping_exceptions( + self, snapshot, aws_client, region_name, secondary_region_name + ): with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException) as e: aws_client.lambda_.get_event_source_mapping(UUID=long_uid()) snapshot.match("get_unknown_uuid", e.value.response) @@ -5431,7 +5511,7 @@ def test_event_source_mapping_exceptions(self, snapshot, aws_client): aws_client.lambda_.list_event_source_mappings() aws_client.lambda_.list_event_source_mappings(FunctionName="doesnotexist") aws_client.lambda_.list_event_source_mappings( - EventSourceArn="arn:aws:sqs:us-east-1:111111111111:somequeue" + EventSourceArn=f"arn:aws:sqs:{region_name}:111111111111:somequeue" ) with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e: @@ -5441,17 +5521,17 @@ def test_event_source_mapping_exceptions(self, snapshot, aws_client): with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e: aws_client.lambda_.create_event_source_mapping( FunctionName="doesnotexist", - EventSourceArn="arn:aws:sqs:us-east-1:111111111111:somequeue", + EventSourceArn=f"arn:aws:sqs:{region_name}:111111111111:somequeue", ) snapshot.match("create_unknown_params", e.value.response) with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e: aws_client.lambda_.create_event_source_mapping( FunctionName="doesnotexist", - EventSourceArn="arn:aws:sqs:us-east-1:111111111111:somequeue", + EventSourceArn=f"arn:aws:sqs:{region_name}:111111111111:somequeue", DestinationConfig={ "OnSuccess": { - "Destination": "arn:aws:sqs:us-east-1:111111111111:someotherqueue" + "Destination": f"arn:aws:sqs:{region_name}:111111111111:someotherqueue" } }, ) @@ -5464,18 +5544,16 @@ def test_event_source_mapping_exceptions(self, snapshot, aws_client): @markers.snapshot.skip_snapshot_verify( paths=[ # all dynamodb service issues not related to lambda - "$..TableDescription.DeletionProtectionEnabled", "$..TableDescription.ProvisionedThroughput.LastDecreaseDateTime", "$..TableDescription.ProvisionedThroughput.LastIncreaseDateTime", "$..TableDescription.TableStatus", - "$..TableDescription.TableId", - "$..UUID", ] ) @markers.aws.validated - def test_event_source_mapping_lifecycle( + def test_event_source_mapping_lifecycle_delete_function( self, create_lambda_function, + create_event_source_mapping, snapshot, sqs_create_queue, cleanups, @@ -5507,7 +5585,7 @@ def test_event_source_mapping_lifecycle( role=lambda_su_role, ) # "minimal" - create_response = aws_client.lambda_.create_event_source_mapping( + create_response = create_event_source_mapping( FunctionName=function_name, EventSourceArn=stream_arn, DestinationConfig={"OnFailure": {"Destination": destination_queue_arn}}, @@ -5516,48 +5594,42 @@ def test_event_source_mapping_lifecycle( MaximumBatchingWindowInSeconds=1, MaximumRetryAttempts=1, ) + uuid = create_response["UUID"] - cleanups.append(lambda: aws_client.lambda_.delete_event_source_mapping(UUID=uuid)) snapshot.match("create_response", create_response) # the stream might not be active immediately(!) - def check_esm_active(): - return aws_client.lambda_.get_event_source_mapping(UUID=uuid)["State"] != "Creating" - - assert wait_until(check_esm_active) + _await_event_source_mapping_enabled(aws_client.lambda_, uuid) get_response = aws_client.lambda_.get_event_source_mapping(UUID=uuid) snapshot.match("get_response", get_response) - # + + delete_function_response = aws_client.lambda_.delete_function(FunctionName=function_name) + snapshot.match("delete_function_response", delete_function_response) + + def _assert_function_deleted(): + with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): + aws_client.lambda_.get_function(FunctionName=function_name) + return True + + wait_until(_assert_function_deleted) + + get_response_post_delete = aws_client.lambda_.get_event_source_mapping(UUID=uuid) + snapshot.match("get_response_post_delete", get_response_post_delete) + delete_response = aws_client.lambda_.delete_event_source_mapping(UUID=uuid) snapshot.match("delete_response", delete_response) - # TODO: continue here after initial CRUD PR - # check what happens when we delete the function - # check behavior in relation to version/alias - # wait until the stream is actually active - # - # lambda_client.update_event_source_mapping() - # - # lambda_client.list_event_source_mappings(FunctionName=function_name) - # lambda_client.list_event_source_mappings(FunctionName=function_name, EventSourceArn=queue_arn) - # lambda_client.list_event_source_mappings(EventSourceArn=queue_arn) - # - # lambda_client.delete_event_source_mapping(UUID=uuid) - @markers.snapshot.skip_snapshot_verify( paths=[ # all dynamodb service issues not related to lambda - "$..TableDescription.DeletionProtectionEnabled", "$..TableDescription.ProvisionedThroughput.LastDecreaseDateTime", "$..TableDescription.ProvisionedThroughput.LastIncreaseDateTime", "$..TableDescription.TableStatus", - "$..TableDescription.TableId", - "$..UUID", ] ) @markers.aws.validated - def test_event_source_mapping_lifecycle_delete_function( + def test_event_source_mapping_lifecycle( self, create_lambda_function, snapshot, @@ -5600,33 +5672,35 @@ def test_event_source_mapping_lifecycle_delete_function( MaximumBatchingWindowInSeconds=1, MaximumRetryAttempts=1, ) - uuid = create_response["UUID"] cleanups.append(lambda: aws_client.lambda_.delete_event_source_mapping(UUID=uuid)) snapshot.match("create_response", create_response) # the stream might not be active immediately(!) - _await_event_source_mapping_enabled(aws_client.lambda_, uuid) + def check_esm_active(): + return aws_client.lambda_.get_event_source_mapping(UUID=uuid)["State"] != "Creating" + + assert wait_until(check_esm_active) get_response = aws_client.lambda_.get_event_source_mapping(UUID=uuid) snapshot.match("get_response", get_response) - - delete_function_response = aws_client.lambda_.delete_function(FunctionName=function_name) - snapshot.match("delete_function_response", delete_function_response) - - def _assert_function_deleted(): - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_function(FunctionName=function_name) - return True - - wait_until(_assert_function_deleted) - - get_response_post_delete = aws_client.lambda_.get_event_source_mapping(UUID=uuid) - snapshot.match("get_response_post_delete", get_response_post_delete) # delete_response = aws_client.lambda_.delete_event_source_mapping(UUID=uuid) snapshot.match("delete_response", delete_response) + # TODO: continue here after initial CRUD PR + # check what happens when we delete the function + # check behavior in relation to version/alias + # wait until the stream is actually active + # + # lambda_client.update_event_source_mapping() + # + # lambda_client.list_event_source_mappings(FunctionName=function_name) + # lambda_client.list_event_source_mappings(FunctionName=function_name, EventSourceArn=queue_arn) + # lambda_client.list_event_source_mappings(EventSourceArn=queue_arn) + # + # lambda_client.delete_event_source_mapping(UUID=uuid) + @markers.aws.validated def test_function_name_variations( self, @@ -5671,10 +5745,10 @@ def _create_esm(snapshot_scope: str, tested_name: str): aws_client.lambda_.delete_event_source_mapping(UUID=result["UUID"]) def _assert_esm_deleted(): - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): + try: aws_client.lambda_.get_event_source_mapping(UUID=result["UUID"]) - - return True + except aws_client.lambda_.exceptions.ResourceNotFoundException: + return True wait_until(_assert_esm_deleted) @@ -5785,6 +5859,7 @@ def test_create_event_filter_criteria_validation( create_lambda_function, lambda_su_role, dynamodb_create_table, + create_event_source_mapping, snapshot, aws_client, ): @@ -5807,14 +5882,17 @@ def test_create_event_filter_criteria_validation( StreamSpecification={"StreamEnabled": True, "StreamViewType": "NEW_AND_OLD_IMAGES"}, ) stream_arn = update_table_response["TableDescription"]["LatestStreamArn"] + _await_dynamodb_table_active(aws_client.dynamodb, table_name) - response = aws_client.lambda_.create_event_source_mapping( + response = create_event_source_mapping( FunctionName=function_name, EventSourceArn=stream_arn, StartingPosition="LATEST", FilterCriteria={"Filters": []}, ) snapshot.match("response-with-empty-filters", response) + # Wait until ESM is enabled to mitigate cleanup failure + _await_event_source_mapping_enabled(aws_client.lambda_, response["UUID"]) with pytest.raises(ParamValidationError): aws_client.lambda_.create_event_source_mapping( @@ -6135,7 +6213,7 @@ class TestLambdaLayer: @markers.lambda_runtime_update @markers.aws.validated # AWS only allows a max of 15 compatible runtimes, split runtimes and run two tests - @pytest.mark.parametrize("runtimes", [ALL_RUNTIMES[:14], ALL_RUNTIMES[14:]]) + @pytest.mark.parametrize("runtimes", list(itertools.batched(ALL_RUNTIMES, 15, strict=False))) def test_layer_compatibilities(self, snapshot, dummylayer, cleanups, aws_client, runtimes): """Creates a single layer which is compatible with all""" layer_name = f"testlayer-{short_uid()}" @@ -6793,6 +6871,7 @@ def test_layer_policy_lifecycle( "get_layer_version_policy_postdeletes2", get_layer_version_policy_postdeletes2 ) + @markers.requires_in_process @markers.aws.only_localstack(reason="Deterministic id generation is LS only") def test_layer_deterministic_version( self, dummylayer, cleanups, aws_client, account_id, region_name, set_resource_custom_id @@ -6887,3 +6966,36 @@ def test_snapstart_exceptions(self, lambda_su_role, snapshot, aws_client): SnapStart={"ApplyOn": "invalidOption"}, ) snapshot.match("create_function_invalid_snapstart_apply", e.value.response) + + +class TestLambdaEndpoints: + @markers.aws.only_localstack + @markers.requires_in_process + @pytest.mark.parametrize( + "localstack_host", + [ + HostAndPort("localhost.localstack.cloud", 4566), + HostAndPort("127.0.0.1", 4566), + HostAndPort("localhost", 4566), + ], + ) + def test_s3_code_url( + self, aws_client, create_lambda_function_aws, lambda_su_role, monkeypatch, localstack_host + ): + monkeypatch.setattr(config, "LOCALSTACK_HOST", localstack_host) + function_name = f"function-{short_uid()}" + zip_file_bytes = create_lambda_archive(load_file(TEST_LAMBDA_PYTHON_ECHO), get_content=True) + create_lambda_function_aws( + FunctionName=function_name, + Handler="index.handler", + Code={"ZipFile": zip_file_bytes}, + PackageType="Zip", + Role=lambda_su_role, + Runtime=Runtime.python3_12, + ) + get_function_response = aws_client.lambda_.get_function(FunctionName=function_name) + s3_code_url = get_function_response["Code"]["Location"] + assert s3_code_url.startswith(f"http://{localstack_host}/") + + content = requests.get(s3_code_url).content + assert content == zip_file_bytes diff --git a/tests/aws/services/lambda_/test_lambda_api.snapshot.json b/tests/aws/services/lambda_/test_lambda_api.snapshot.json index 1e63ff2f5b8b0..35b46fa355c5e 100644 --- a/tests/aws/services/lambda_/test_lambda_api.snapshot.json +++ b/tests/aws/services/lambda_/test_lambda_api.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_lifecycle": { - "recorded-date": "12-09-2024, 11:29:18", + "recorded-date": "25-11-2025, 02:26:54", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -307,6 +307,7 @@ } }, "delete_response": { + "StatusCode": 204, "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 204 @@ -327,7 +328,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_redundant_updates": { - "recorded-date": "12-09-2024, 11:29:23", + "recorded-date": "25-11-2025, 02:27:00", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -622,7 +623,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[delete_function]": { - "recorded-date": "12-09-2024, 11:29:23", + "recorded-date": "25-11-2025, 02:27:00", "recorded-content": { "not_match_exception": { "Error": { @@ -651,7 +652,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function]": { - "recorded-date": "12-09-2024, 11:29:23", + "recorded-date": "25-11-2025, 02:27:01", "recorded-content": { "not_match_exception": { "Error": { @@ -680,7 +681,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function_configuration]": { - "recorded-date": "12-09-2024, 11:29:24", + "recorded-date": "25-11-2025, 02:27:01", "recorded-content": { "not_match_exception": { "Error": { @@ -709,7 +710,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function]": { - "recorded-date": "12-09-2024, 11:29:26", + "recorded-date": "25-11-2025, 02:27:03", "recorded-content": { "version_not_found_exception": { "Error": { @@ -726,7 +727,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_configuration]": { - "recorded-date": "12-09-2024, 11:29:28", + "recorded-date": "25-11-2025, 02:27:06", "recorded-content": { "version_not_found_exception": { "Error": { @@ -743,7 +744,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_event_invoke_config]": { - "recorded-date": "12-09-2024, 11:29:30", + "recorded-date": "25-11-2025, 02:27:08", "recorded-content": { "version_not_found_exception": { "Error": { @@ -760,7 +761,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_delete_on_nonexisting_version": { - "recorded-date": "12-09-2024, 11:29:32", + "recorded-date": "25-11-2025, 02:27:11", "recorded-content": { "delete_function_response_non_existent": { "Error": { @@ -789,7 +790,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[delete_function]": { - "recorded-date": "12-09-2024, 11:29:32", + "recorded-date": "25-11-2025, 02:27:12", "recorded-content": { "not_found_exception": { "Error": { @@ -806,7 +807,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function]": { - "recorded-date": "12-09-2024, 11:29:32", + "recorded-date": "25-11-2025, 02:27:12", "recorded-content": { "not_found_exception": { "Error": { @@ -823,7 +824,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_configuration]": { - "recorded-date": "12-09-2024, 11:29:33", + "recorded-date": "25-11-2025, 02:27:12", "recorded-content": { "not_found_exception": { "Error": { @@ -840,7 +841,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_url_config]": { - "recorded-date": "12-09-2024, 11:29:33", + "recorded-date": "25-11-2025, 02:27:12", "recorded-content": { "not_found_exception": { "Error": { @@ -857,7 +858,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_code_signing_config]": { - "recorded-date": "12-09-2024, 11:29:33", + "recorded-date": "25-11-2025, 02:27:12", "recorded-content": { "not_found_exception": { "Error": { @@ -874,7 +875,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_event_invoke_config]": { - "recorded-date": "12-09-2024, 11:29:33", + "recorded-date": "25-11-2025, 02:27:13", "recorded-content": { "not_found_exception": { "Error": { @@ -891,7 +892,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_concurrency]": { - "recorded-date": "12-09-2024, 11:29:33", + "recorded-date": "25-11-2025, 02:27:13", "recorded-content": { "not_found_exception": { "Error": { @@ -908,7 +909,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function]": { - "recorded-date": "12-09-2024, 11:29:35", + "recorded-date": "25-11-2025, 02:27:15", "recorded-content": { "wrong_region_exception": { "Error": { @@ -925,7 +926,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_configuration]": { - "recorded-date": "12-09-2024, 11:29:37", + "recorded-date": "25-11-2025, 02:27:17", "recorded-content": { "wrong_region_exception": { "Error": { @@ -942,7 +943,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_url_config]": { - "recorded-date": "12-09-2024, 11:29:39", + "recorded-date": "25-11-2025, 02:27:20", "recorded-content": { "wrong_region_exception": { "Error": { @@ -959,7 +960,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_code_signing_config]": { - "recorded-date": "12-09-2024, 11:29:41", + "recorded-date": "25-11-2025, 02:27:22", "recorded-content": { "wrong_region_exception": { "Error": { @@ -976,7 +977,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_event_invoke_config]": { - "recorded-date": "12-09-2024, 11:29:43", + "recorded-date": "25-11-2025, 02:27:24", "recorded-content": { "wrong_region_exception": { "Error": { @@ -993,7 +994,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_concurrency]": { - "recorded-date": "12-09-2024, 11:29:46", + "recorded-date": "25-11-2025, 02:27:27", "recorded-content": { "wrong_region_exception": { "Error": { @@ -1010,7 +1011,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[delete_function]": { - "recorded-date": "12-09-2024, 11:29:48", + "recorded-date": "25-11-2025, 02:27:29", "recorded-content": { "wrong_region_exception": { "Error": { @@ -1027,7 +1028,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[invoke]": { - "recorded-date": "12-09-2024, 11:29:50", + "recorded-date": "25-11-2025, 02:27:32", "recorded-content": { "wrong_region_exception": { "Error": { @@ -1044,7 +1045,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_zipfile": { - "recorded-date": "12-09-2024, 11:29:52", + "recorded-date": "25-11-2025, 02:27:35", "recorded-content": { "create-response-zip-file": { "Architectures": [ @@ -1234,7 +1235,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3": { - "recorded-date": "12-09-2024, 11:29:57", + "recorded-date": "25-11-2025, 02:27:41", "recorded-content": { "create_response_s3": { "Architectures": [ @@ -1424,7 +1425,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_version_on_create": { - "recorded-date": "10-04-2024, 09:12:04", + "recorded-date": "25-11-2025, 02:38:24", "recorded-content": { "create_response": { "Architectures": [ @@ -1806,7 +1807,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_version_lifecycle": { - "recorded-date": "12-07-2024, 11:43:40", + "recorded-date": "25-11-2025, 02:38:32", "recorded-content": { "create_response": { "Architectures": [ @@ -2641,7 +2642,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_wrong_sha256": { - "recorded-date": "10-04-2024, 09:12:15", + "recorded-date": "25-11-2025, 02:38:35", "recorded-content": { "create_response": { "Architectures": [ @@ -2791,7 +2792,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_update": { - "recorded-date": "10-04-2024, 09:12:18", + "recorded-date": "25-11-2025, 02:38:38", "recorded-content": { "create_response": { "Architectures": [ @@ -3029,7 +3030,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_lifecycle": { - "recorded-date": "21-11-2024, 13:44:49", + "recorded-date": "25-11-2025, 02:38:45", "recorded-content": { "create_response": { "Architectures": [ @@ -3621,7 +3622,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_notfound_and_invalid_routingconfigs": { - "recorded-date": "21-11-2024, 13:45:06", + "recorded-date": "25-11-2025, 21:33:38", "recorded-content": { "create_response": { "Architectures": [ @@ -3847,7 +3848,7 @@ "routing_config_exc_version_latest": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value '{$LATEST=0.5}' at 'routingConfig.additionalVersionWeights' failed to satisfy constraint: Map keys must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: [0-9]+, Member must not be null]" + "Message": "1 validation error detected: Value '{$LATEST=0.5}' at 'routingConfig.additionalVersionWeights' failed to satisfy constraint: Map keys must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: [0-9]+]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -3931,7 +3932,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_fn_create": { - "recorded-date": "10-04-2024, 09:13:05", + "recorded-date": "25-11-2025, 02:39:21", "recorded-content": { "get_function_result": { "Code": { @@ -4114,7 +4115,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_nonexisting_resource": { - "recorded-date": "10-04-2024, 09:13:14", + "recorded-date": "25-11-2025, 02:39:54", "recorded-content": { "pre_delete_get_function": { "Code": { @@ -4206,7 +4207,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_exceptions": { - "recorded-date": "10-04-2024, 09:13:39", + "recorded-date": "25-11-2025, 21:39:31", "recorded-content": { "fn_version_result": { "Architectures": [ @@ -4377,7 +4378,7 @@ "put_destination_invalid_arn_pattern": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'arn::_-/!lambda::111111111111:function:' at 'destinationConfig.onFailure.destination' failed to satisfy constraint: Member must satisfy regular expression pattern: ^$|arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\\-])+:([a-z]{2}((-gov)|(-iso(b?)))?-[a-z]+-\\d{1})?:(\\d{12})?:(.*)" + "Message": "1 validation error detected: Value 'arn::_-/!lambda::111111111111:function:' at 'destinationConfig.onFailure.destination' failed to satisfy constraint: Member must satisfy regular expression pattern: $|kafka://([^.]([a-zA-Z0-9\\-_.]{0,248}))|arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\\-])+:((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1})?:(\\d{12})?:(.*)" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -4867,7 +4868,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_exceptions": { - "recorded-date": "10-04-2024, 09:13:44", + "recorded-date": "25-11-2025, 02:40:18", "recorded-content": { "put_function_concurrency_with_function_name_doesnotexist": { "Error": { @@ -4908,7 +4909,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency": { - "recorded-date": "10-04-2024, 09:13:51", + "recorded-date": "25-11-2025, 02:40:29", "recorded-content": { "put_function_concurrency_with_reserved_0": { "ReservedConcurrentExecutions": 0, @@ -4946,7 +4947,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_aws": { - "recorded-date": "10-04-2024, 09:16:23", + "recorded-date": "25-11-2025, 02:42:56", "recorded-content": { "create_lambda": { "CreateEventSourceMappingResponse": null, @@ -5047,7 +5048,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_remove_multi_permissions": { - "recorded-date": "10-04-2024, 09:16:38", + "recorded-date": "25-11-2025, 02:43:09", "recorded-content": { "add_permission_1": { "Statement": { @@ -5191,7 +5192,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_create_multiple_lambda_permissions": { - "recorded-date": "10-04-2024, 09:16:41", + "recorded-date": "25-11-2025, 02:43:12", "recorded-content": { "add_permission_response_1": { "Statement": { @@ -5257,7 +5258,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_lifecycle": { - "recorded-date": "21-11-2024, 13:44:13", + "recorded-date": "25-11-2025, 02:43:27", "recorded-content": { "url_creation": { "AuthType": "NONE", @@ -5336,7 +5337,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_lambda": { - "recorded-date": "10-04-2024, 09:18:29", + "recorded-date": "25-11-2025, 02:46:01", "recorded-content": { "create_function_large_zip": { "Architectures": [ @@ -5384,7 +5385,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_variables_fails": { - "recorded-date": "10-04-2024, 09:18:46", + "recorded-date": "25-11-2025, 02:46:19", "recorded-content": { "failed_create_fn_result": { "Error": { @@ -5401,7 +5402,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_fails_multiple_keys": { - "recorded-date": "10-04-2024, 09:19:04", + "recorded-date": "25-11-2025, 02:46:36", "recorded-content": { "failured_create_fn_result_multi_key": { "Error": { @@ -5418,7 +5419,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_lambda_envvars_near_limit_succeeds": { - "recorded-date": "10-04-2024, 09:19:07", + "recorded-date": "25-11-2025, 02:46:39", "recorded-content": { "successful_create_fn_result": { "CreateEventSourceMappingResponse": null, @@ -5474,7 +5475,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_function_code_signing_config": { - "recorded-date": "10-04-2024, 09:19:11", + "recorded-date": "25-11-2025, 02:46:42", "recorded-content": { "create_code_signing_config": { "CodeSigningConfig": { @@ -5590,7 +5591,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_code_signing_not_found_excs": { - "recorded-date": "10-04-2024, 09:19:16", + "recorded-date": "25-11-2025, 02:46:47", "recorded-content": { "create_code_signing_config": { "CodeSigningConfig": { @@ -5769,7 +5770,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings": { - "recorded-date": "10-04-2024, 09:19:17", + "recorded-date": "25-11-2025, 02:46:47", "recorded-content": { "acc_settings_modded": { "AccountLimit": [ @@ -5791,7 +5792,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_exceptions": { - "recorded-date": "05-12-2024, 10:52:30", + "recorded-date": "27-11-2025, 01:04:32", "recorded-content": { "get_unknown_uuid": { "Error": { @@ -5868,7 +5869,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle": { - "recorded-date": "14-10-2024, 12:36:57", + "recorded-date": "25-11-2025, 02:47:33", "recorded-content": { "update_table_response": { "TableDescription": { @@ -5906,7 +5907,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "UPDATING" + "TableStatus": "UPDATING", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -6000,12 +6006,12 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_exceptions": { - "recorded-date": "24-10-2024, 15:22:29", + "recorded-date": "25-11-2025, 02:48:27", "recorded-content": { "tag_lambda_invalidarn": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'arn::something' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" + "Message": "1 validation error detected: Value 'arn::something' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -6074,7 +6080,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_limits": { - "recorded-date": "28-10-2024, 14:16:38", + "recorded-date": "25-11-2025, 02:48:31", "recorded-content": { "tag_lambda_too_many_tags": { "Error": { @@ -6282,7 +6288,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_lifecycle": { - "recorded-date": "24-10-2024, 15:22:49", + "recorded-date": "25-11-2025, 02:48:38", "recorded-content": { "list_tags_response_postfncreate": { "Tags": { @@ -6641,7 +6647,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_exceptions": { - "recorded-date": "10-04-2024, 09:13:57", + "recorded-date": "25-11-2025, 02:40:34", "recorded-content": { "publish_version_result": { "Architectures": [ @@ -6894,7 +6900,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_lambda_provisioned_lifecycle": { - "recorded-date": "10-04-2024, 09:16:15", + "recorded-date": "25-11-2025, 02:42:49", "recorded-content": { "publish_version_result": { "Architectures": [ @@ -7044,7 +7050,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_request_create_lambda": { - "recorded-date": "10-04-2024, 09:17:14", + "recorded-date": "25-11-2025, 02:44:03", "recorded-content": { "invalid_param_exc": { "Error": { @@ -7059,7 +7065,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_versions": { - "recorded-date": "24-10-2024, 15:22:40", + "recorded-date": "25-11-2025, 02:48:33", "recorded-content": { "tag_resource_exception": { "Error": { @@ -7088,7 +7094,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_list_paging": { - "recorded-date": "21-11-2024, 13:44:10", + "recorded-date": "25-11-2025, 02:43:23", "recorded-content": { "fn_version_result": { "Architectures": [ @@ -7212,7 +7218,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_exceptions": { - "recorded-date": "21-11-2024, 13:44:05", + "recorded-date": "25-11-2025, 02:43:19", "recorded-content": { "fn_version_result": { "Architectures": [ @@ -7650,7 +7656,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_permission_exceptions": { - "recorded-date": "10-04-2024, 09:16:20", + "recorded-date": "25-11-2025, 02:42:53", "recorded-content": { "add_permission_invalid_statement_id": { "Error": { @@ -7701,9 +7707,9 @@ "get_policy_fn_doesnotexist": { "Error": { "Code": "ResourceNotFoundException", - "Message": "Function not found: arn::lambda::111111111111:function:doesnotexist" + "Message": "The resource you requested does not exist." }, - "Message": "Function not found: arn::lambda::111111111111:function:doesnotexist", + "Message": "The resource you requested does not exist.", "Type": "User", "ResponseMetadata": { "HTTPHeaders": {}, @@ -7773,7 +7779,7 @@ "add_permission_fn_qualifier_invalid": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'invalid-qualifier-with-?-char' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)" + "Message": "1 validation error detected: Value 'invalid-qualifier-with-?-char' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: \\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -7831,7 +7837,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_unzipped_lambda": { - "recorded-date": "10-04-2024, 09:17:47", + "recorded-date": "25-11-2025, 02:45:12", "recorded-content": { "invalid_param_exc": { "Error": { @@ -7848,7 +7854,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_create_lambda_exceptions": { - "recorded-date": "01-04-2025, 13:08:21", + "recorded-date": "12-01-2026, 15:31:24", "recorded-content": { "invalid_role_arn_exc": { "Error": { @@ -7863,10 +7869,10 @@ "invalid_runtime_exc": { "Error": { "Code": "InvalidParameterValueException", - "Message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN" + "Message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN" }, "Type": "User", - "message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN", + "message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 @@ -7875,10 +7881,10 @@ "uppercase_runtime_exc": { "Error": { "Code": "InvalidParameterValueException", - "Message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN" + "Message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN" }, "Type": "User", - "message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN", + "message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 @@ -7920,7 +7926,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_update_lambda_exceptions": { - "recorded-date": "01-04-2025, 13:09:51", + "recorded-date": "12-01-2026, 15:31:27", "recorded-content": { "invalid_role_arn_exc": { "Error": { @@ -7935,10 +7941,10 @@ "invalid_runtime_exc": { "Error": { "Code": "InvalidParameterValueException", - "Message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN" + "Message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN" }, "Type": "User", - "message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN", + "message": "Value non-existent-runtime at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 @@ -7947,10 +7953,10 @@ "uppercase_runtime_exc": { "Error": { "Code": "InvalidParameterValueException", - "Message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN" + "Message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN" }, "Type": "User", - "message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9] or be a valid ARN", + "message": "Value PYTHON3.9 at 'runtime' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2] or be a valid ARN", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 400 @@ -7959,7 +7965,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_list_functions": { - "recorded-date": "12-09-2024, 11:30:20", + "recorded-date": "25-11-2025, 02:28:15", "recorded-content": { "create_response_1": { "CreateEventSourceMappingResponse": null, @@ -8260,7 +8266,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_exceptions": { - "recorded-date": "01-04-2025, 13:19:20", + "recorded-date": "12-01-2026, 15:31:58", "recorded-content": { "publish_result": { "CompatibleArchitectures": [ @@ -8287,7 +8293,7 @@ "list_layers_exc_compatibleruntime_invalid": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'runtimedoesnotexist' at 'compatibleRuntime' failed to satisfy constraint: Member must satisfy enum value set: [ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java25, java17, nodejs, nodejs4.3, java8.al2, go1.x, dotnet10, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs24.x, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, python3.13, python3.14, nodejs16.x, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby3.4, ruby2.5, python3.6, python2.7]" + "Message": "1 validation error detected: Value 'runtimedoesnotexist' at 'compatibleRuntime' failed to satisfy constraint: Member must satisfy enum value set: [ruby3.5, ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java25, java17, nodejs, nodejs4.3, java8.al2, go1.x, dotnet10, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs24.x, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, nodejs26.x, python3.13, python3.14, nodejs16.x, python3.15, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby3.4, ruby2.5, python3.6, python2.7]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -8362,7 +8368,7 @@ "get_layer_version_by_arn_exc_invalidarn": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'arn::lambda::111111111111:layer:' at 'arn' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+)" + "Message": "1 validation error detected: Value 'arn::lambda::111111111111:layer:' at 'arn' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+)" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -8438,7 +8444,7 @@ "publish_layer_version_exc_invalid_runtime_arch": { "Error": { "Code": "ValidationException", - "Message": "2 validation errors detected: Value '[invalidruntime]' at 'compatibleRuntimes' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9]; Value '[invalidarch]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: [Member must satisfy enum value set: [x86_64, arm64]]" + "Message": "2 validation errors detected: Value '[invalidruntime]' at 'compatibleRuntimes' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2]; Value '[invalidarch]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: [Member must satisfy enum value set: [x86_64, arm64], Member must not be null]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -8448,7 +8454,7 @@ "publish_layer_version_exc_partially_invalid_values": { "Error": { "Code": "ValidationException", - "Message": "2 validation errors detected: Value '[invalidruntime, invalidruntime2, nodejs20.x]' at 'compatibleRuntimes' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9]; Value '[invalidarch, x86_64]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: [Member must satisfy enum value set: [x86_64, arm64]]" + "Message": "2 validation errors detected: Value '[invalidruntime, invalidruntime2, nodejs20.x]' at 'compatibleRuntimes' failed to satisfy constraint: Member must satisfy enum value set: [nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs24.x, nodejs22.x, java25, python3.10, python3.11, java21, ruby3.3, ruby3.4, ruby3.2, python3.8, python3.9, java17, nodejs16.x, dotnet10, dotnet8, java11, dotnet6, nodejs18.x, provided.al2, java8.al2]; Value '[invalidarch, x86_64]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: [Member must satisfy enum value set: [x86_64, arm64], Member must not be null]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -8458,7 +8464,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_lifecycle": { - "recorded-date": "10-04-2024, 09:24:17", + "recorded-date": "25-11-2025, 02:50:43", "recorded-content": { "get_fn_result": { "Code": { @@ -8882,7 +8888,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_exceptions": { - "recorded-date": "10-04-2024, 09:24:29", + "recorded-date": "25-11-2025, 02:50:51", "recorded-content": { "publish_result": { "CompatibleArchitectures": [ @@ -9065,7 +9071,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_lifecycle": { - "recorded-date": "10-04-2024, 09:24:35", + "recorded-date": "25-11-2025, 02:50:55", "recorded-content": { "publish_result": { "CompatibleArchitectures": [ @@ -9193,7 +9199,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_s3_content": { - "recorded-date": "10-04-2024, 09:24:23", + "recorded-date": "25-11-2025, 02:50:45", "recorded-content": { "publish_layer_result": { "Content": { @@ -9214,7 +9220,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_exceptions": { - "recorded-date": "10-04-2024, 09:23:19", + "recorded-date": "25-11-2025, 21:58:40", "recorded-content": { "publish_result": { "CompatibleArchitectures": [ @@ -9390,7 +9396,7 @@ "add_layer_arn_without_version_exc": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value '[arn::lambda::111111111111:layer:]' at 'layers' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 140, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: (arn:[a-zA-Z0-9-]+:lambda:[a-z]{2}((-gov)|(-iso(b?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+), Member must not be null]" + "Message": "1 validation error detected: Value '[arn::lambda::111111111111:layer:]' at 'layers' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 2048, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+), Member must not be null]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -9412,7 +9418,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_crud": { - "recorded-date": "10-04-2024, 09:10:21", + "recorded-date": "25-11-2025, 02:37:05", "recorded-content": { "create-image-response": { "Architectures": [ @@ -9698,7 +9704,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_and_image_config_crud": { - "recorded-date": "10-04-2024, 09:11:14", + "recorded-date": "25-11-2025, 02:37:44", "recorded-content": { "create-image-with-config-response": { "Architectures": [ @@ -10170,7 +10176,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_zip_file_to_image": { - "recorded-date": "10-04-2024, 09:10:38", + "recorded-date": "25-11-2025, 02:37:15", "recorded-content": { "create-image-response": { "Architectures": [ @@ -10410,7 +10416,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size": { - "recorded-date": "10-04-2024, 09:19:29", + "recorded-date": "25-11-2025, 02:46:59", "recorded-content": { "total_code_size_diff_create_function": 276, "total_code_size_diff_update_function": 276, @@ -10419,7 +10425,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size_config_update": { - "recorded-date": "10-04-2024, 09:19:35", + "recorded-date": "25-11-2025, 02:47:04", "recorded-content": { "is_total_code_size_diff_create_function_more_than_200": true, "total_code_size_diff_update_function_configuration": 0, @@ -10427,7 +10433,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_lifecycle": { - "recorded-date": "10-04-2024, 09:13:21", + "recorded-date": "25-11-2025, 02:40:00", "recorded-content": { "put_invokeconfig_retries_0": { "DestinationConfig": { @@ -10794,7 +10800,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_fields": { - "recorded-date": "10-04-2024, 09:16:33", + "recorded-date": "25-11-2025, 02:43:05", "recorded-content": { "add_permission_principal_wildcard": { "Statement": { @@ -10913,7 +10919,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_lambda_permission_fn_versioning": { - "recorded-date": "10-04-2024, 09:16:29", + "recorded-date": "25-11-2025, 21:46:15", "recorded-content": { "add_permission": { "Statement": { @@ -11079,7 +11085,7 @@ } ] }, - "RevisionId": "", + "RevisionId": "", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -11149,10 +11155,10 @@ "add_permission_alias_revision_exception": { "Error": { "Code": "PreconditionFailedException", - "Message": "The Revision Id provided does not match the latest Revision Id. Call the GetFunction/GetAlias API to retrieve the latest Revision Id" + "Message": "The Revision Id provided does not match the latest Revision Id. Call the GetPolicy API to retrieve the latest Revision Id" }, "Type": "User", - "message": "The Revision Id provided does not match the latest Revision Id. Call the GetFunction/GetAlias API to retrieve the latest Revision Id", + "message": "The Revision Id provided does not match the latest Revision Id. Call the GetPolicy API to retrieve the latest Revision Id", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 412 @@ -11206,7 +11212,7 @@ } ] }, - "RevisionId": "", + "RevisionId": "", "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -11554,7 +11560,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_basic": { - "recorded-date": "10-04-2024, 09:12:53", + "recorded-date": "25-11-2025, 02:39:11", "recorded-content": { "create_function_response_rev1": { "CreateEventSourceMappingResponse": null, @@ -11879,7 +11885,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_version_and_alias": { - "recorded-date": "10-04-2024, 09:12:58", + "recorded-date": "25-11-2025, 02:39:15", "recorded-content": { "create_function_response_rev1": { "CreateEventSourceMappingResponse": null, @@ -12279,79 +12285,33 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_permissions": { - "recorded-date": "10-04-2024, 09:13:02", + "recorded-date": "25-11-2025, 03:29:57", "recorded-content": { "add_permission_revision_exception": { "Error": { "Code": "PreconditionFailedException", - "Message": "The Revision Id provided does not match the latest Revision Id. Call the GetFunction/GetAlias API to retrieve the latest Revision Id" + "Message": "The Revision Id provided does not match the latest Revision Id. Call the GetPolicy API to retrieve the latest Revision Id" }, "Type": "User", - "message": "The Revision Id provided does not match the latest Revision Id. Call the GetFunction/GetAlias API to retrieve the latest Revision Id", + "message": "The Revision Id provided does not match the latest Revision Id. Call the GetPolicy API to retrieve the latest Revision Id", "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 412 - } - }, - "add_permission_response": { - "Statement": { - "Sid": "s3", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com" + "HTTPHeaders": { + "connection": "keep-alive", + "content-length": "149", + "content-type": "application/json", + "date": "Tue, 25 Nov 2025 03:22:17 GMT", + "x-amzn-errortype": "PreconditionFailedException", + "x-amzn-requestid": "9f89d509-063e-4b8c-a3ae-1848cc7c0701" }, - "Action": "lambda:InvokeFunction", - "Resource": "arn::lambda::111111111111:function:" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 201 - } - }, - "get_policy_response_rev3": { - "Policy": { - "Version": "2012-10-17", - "Id": "default", - "Statement": [ - { - "Sid": "s3", - "Effect": "Allow", - "Principal": { - "Service": "s3.amazonaws.com" - }, - "Action": "lambda:InvokeFunction", - "Resource": "arn::lambda::111111111111:function:" - } - ] - }, - "RevisionId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "remove_permission_revision_exception": { - "Error": { - "Code": "PreconditionFailedException", - "Message": "The Revision Id provided does not match the latest Revision Id. Call the GetFunction/GetAlias API to retrieve the latest Revision Id" - }, - "Type": "User", - "message": "The Revision Id provided does not match the latest Revision Id. Call the GetFunction/GetAlias API to retrieve the latest Revision Id", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 412 - } - }, - "remove_permission_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 204 + "HTTPStatusCode": 412, + "RequestId": "9f89d509-063e-4b8c-a3ae-1848cc7c0701", + "RetryAttempts": 0 } } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_quota_exception": { - "recorded-date": "10-04-2024, 09:23:59", + "recorded-date": "25-11-2025, 02:50:21", "recorded-content": { "create_function_with_six_layers": { "Error": { @@ -12368,7 +12328,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": { - "recorded-date": "03-03-2025, 17:07:45", + "recorded-date": "25-11-2025, 02:48:02", "recorded-content": { "no_starting_position": { "Error": { @@ -12407,7 +12367,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_exceptions": { - "recorded-date": "31-03-2025, 16:15:53", + "recorded-date": "25-11-2025, 02:51:34", "recorded-content": { "create_function_invalid_snapstart_apply": { "Error": { @@ -12422,7 +12382,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_versions": { - "recorded-date": "10-04-2024, 09:11:51", + "recorded-date": "25-11-2025, 02:38:13", "recorded-content": { "create_image_response": { "Architectures": [ @@ -12881,7 +12841,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java11]": { - "recorded-date": "01-04-2025, 13:30:54", + "recorded-date": "12-01-2026, 15:32:08", "recorded-content": { "create_function_response": { "Architectures": [ @@ -13025,7 +12985,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java17]": { - "recorded-date": "01-04-2025, 13:30:58", + "recorded-date": "12-01-2026, 15:32:14", "recorded-content": { "create_function_response": { "Architectures": [ @@ -13169,7 +13129,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_vpc_config": { - "recorded-date": "12-09-2024, 11:34:43", + "recorded-date": "25-11-2025, 02:35:52", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -13522,7 +13482,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes0]": { - "recorded-date": "01-04-2025, 13:12:59", + "recorded-date": "12-01-2026, 15:31:31", "recorded-content": { "publish_result": { "CompatibleArchitectures": [ @@ -13530,20 +13490,21 @@ "x86_64" ], "CompatibleRuntimes": [ + "nodejs24.x", "nodejs22.x", "nodejs20.x", "nodejs18.x", "nodejs16.x", "nodejs14.x", "nodejs12.x", + "python3.14", "python3.13", "python3.12", "python3.11", "python3.10", "python3.9", "python3.8", - "python3.7", - "java21" + "python3.7" ], "Content": { "CodeSha256": "", @@ -13563,7 +13524,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes1]": { - "recorded-date": "01-04-2025, 13:13:03", + "recorded-date": "12-01-2026, 15:31:37", "recorded-content": { "publish_result": { "CompatibleArchitectures": [ @@ -13571,10 +13532,13 @@ "x86_64" ], "CompatibleRuntimes": [ + "java25", + "java21", "java17", "java11", "java8.al2", "java8", + "dotnet10", "dotnet8", "dotnet6", "dotnetcore3.1", @@ -13582,10 +13546,7 @@ "ruby3.4", "ruby3.3", "ruby3.2", - "ruby2.7", - "provided.al2023", - "provided.al2", - "provided" + "ruby2.7" ], "Content": { "CodeSha256": "", @@ -13605,7 +13566,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_limits": { - "recorded-date": "10-04-2024, 09:14:00", + "recorded-date": "25-11-2025, 02:40:37", "recorded-content": { "put_provisioned_concurrency_account_limit_exceeded": { "Error": { @@ -13632,7 +13593,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_limits": { - "recorded-date": "10-04-2024, 09:13:47", + "recorded-date": "25-11-2025, 02:40:20", "recorded-content": { "put_function_concurrency_account_limit_exceeded": { "Error": { @@ -13659,7 +13620,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java21]": { - "recorded-date": "01-04-2025, 13:31:02", + "recorded-date": "12-01-2026, 15:32:21", "recorded-content": { "create_function_response": { "Architectures": [ @@ -13803,7 +13764,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_zipped_create_lambda": { - "recorded-date": "10-04-2024, 09:17:26", + "recorded-date": "25-11-2025, 02:44:37", "recorded-content": { "invalid_param_exc": { "Error": { @@ -13818,7 +13779,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_arns": { - "recorded-date": "12-09-2024, 11:30:01", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "create-function-arn-response": { "CreateEventSourceMappingResponse": null, @@ -13921,7 +13882,7 @@ "invalid_function_name_exc": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'invalid:function:name' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Message": "1 validation error detected: Value 'invalid:function:name' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -13975,7 +13936,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_function_name_variations": { - "recorded-date": "14-10-2024, 12:46:37", + "recorded-date": "25-11-2025, 03:20:35", "recorded-content": { "name_only_create_esm": { "BatchSize": 10, @@ -14092,7 +14053,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_naming": { - "recorded-date": "21-11-2024, 13:45:11", + "recorded-date": "25-11-2025, 02:39:05", "recorded-content": { "create_response": { "Architectures": [ @@ -14224,12 +14185,12 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_invoke": { - "recorded-date": "12-09-2024, 11:34:43", + "recorded-date": "25-11-2025, 02:35:53", "recorded-content": { "invoke_function_name_pattern_exc": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'arn::lambda::123400000000@function:myfn' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Message": "1 validation error detected: Value 'arn::lambda::123400000000@function:myfn' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -14239,7 +14200,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_code_updates": { - "recorded-date": "12-09-2024, 11:34:47", + "recorded-date": "25-11-2025, 02:35:57", "recorded-content": { "create-function-response": { "Architectures": [ @@ -14299,7 +14260,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_config_updates": { - "recorded-date": "12-09-2024, 11:34:50", + "recorded-date": "25-11-2025, 02:36:01", "recorded-content": { "create-function-response": { "CreateEventSourceMappingResponse": null, @@ -14365,7 +14326,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_advanced_logging_configuration": { - "recorded-date": "19-04-2024, 08:20:18", + "recorded-date": "25-11-2025, 02:26:24", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -14617,7 +14578,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_advanced_logging_configuration_format_switch": { - "recorded-date": "17-04-2024, 14:16:55", + "recorded-date": "25-11-2025, 02:26:30", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -14955,7 +14916,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config0]": { - "recorded-date": "17-04-2024, 14:17:01", + "recorded-date": "25-11-2025, 02:26:35", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -15201,7 +15162,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config1]": { - "recorded-date": "17-04-2024, 14:17:06", + "recorded-date": "25-11-2025, 02:26:39", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -15447,7 +15408,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config2]": { - "recorded-date": "17-04-2024, 14:17:11", + "recorded-date": "25-11-2025, 02:26:44", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -15693,7 +15654,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config3]": { - "recorded-date": "17-04-2024, 14:17:17", + "recorded-date": "25-11-2025, 02:26:48", "recorded-content": { "create_response": { "CreateEventSourceMappingResponse": null, @@ -15935,11 +15896,11 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_update_function_configuration_full_arn": { - "recorded-date": "05-06-2024, 11:49:05", + "recorded-date": "25-11-2025, 02:26:17", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[java8]": { - "recorded-date": "01-04-2025, 13:02:29", + "recorded-date": "12-01-2026, 15:31:21", "recorded-content": { "deprecation_error": { "Error": { @@ -15956,7 +15917,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[go1.x]": { - "recorded-date": "01-04-2025, 13:02:29", + "recorded-date": "12-01-2026, 15:31:21", "recorded-content": { "deprecation_error": { "Error": { @@ -15973,7 +15934,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[provided]": { - "recorded-date": "01-04-2025, 13:02:30", + "recorded-date": "12-01-2026, 15:31:21", "recorded-content": { "deprecation_error": { "Error": { @@ -15990,7 +15951,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[ruby2.7]": { - "recorded-date": "01-04-2025, 13:02:30", + "recorded-date": "12-01-2026, 15:31:21", "recorded-content": { "deprecation_error": { "Error": { @@ -16007,7 +15968,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs14.x]": { - "recorded-date": "01-04-2025, 13:02:30", + "recorded-date": "12-01-2026, 15:31:22", "recorded-content": { "deprecation_error": { "Error": { @@ -16024,7 +15985,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[python3.7]": { - "recorded-date": "01-04-2025, 13:02:30", + "recorded-date": "12-01-2026, 15:31:22", "recorded-content": { "deprecation_error": { "Error": { @@ -16041,7 +16002,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[dotnetcore3.1]": { - "recorded-date": "01-04-2025, 13:02:31", + "recorded-date": "12-01-2026, 15:31:22", "recorded-content": { "deprecation_error": { "Error": { @@ -16058,7 +16019,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs12.x]": { - "recorded-date": "01-04-2025, 13:02:31", + "recorded-date": "12-01-2026, 15:31:22", "recorded-content": { "deprecation_error": { "Error": { @@ -16075,149 +16036,149 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_cross_region_arn_function_access": { - "recorded-date": "11-06-2024, 13:06:45", + "recorded-date": "25-11-2025, 02:26:19", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-get_function]": { - "recorded-date": "12-09-2024, 11:30:01", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-delete_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-invoke]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-create_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:52", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value '*' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-get_function]": { - "recorded-date": "12-09-2024, 11:30:01", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-delete_function]": { - "recorded-date": "12-09-2024, 11:30:01", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-invoke]": { - "recorded-date": "12-09-2024, 11:30:01", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-create_function]": { - "recorded-date": "12-09-2024, 11:30:01", + "recorded-date": "25-11-2025, 02:27:51", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'my-function!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-get_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:52", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'invalid!' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)" + "Value 'invalid!' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: \\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-delete_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:52", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'invalid!' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)" + "Value 'invalid!' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: \\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-invoke]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:52", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'invalid!' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)" + "Value 'invalid!' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: \\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-create_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:52", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'my-function:invalid!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'my-function:invalid!' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } @@ -16271,151 +16232,151 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-get_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-delete_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-invoke]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-create_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'invalid-account:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-get_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-delete_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-invoke]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:54", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-create_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:54", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-get_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:54", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-delete_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:54", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-invoke]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:54", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-create_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:54", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::ec2::111111111111:instance:i-1234567890abcdef0' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-get_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:54", "recorded-content": { "get_function_exception": { "Code": "ResourceNotFoundException", @@ -16424,7 +16385,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-delete_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:55", "recorded-content": { "delete_function_exception": { "Code": "ResourceNotFoundException", @@ -16433,7 +16394,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-invoke]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:55", "recorded-content": { "invoke_exception": { "Code": "ResourceNotFoundException", @@ -16442,7 +16403,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-create_function]": { - "recorded-date": "12-09-2024, 11:30:03", + "recorded-date": "25-11-2025, 02:27:55", "recorded-content": { "create_function_exception": { "Code": "InvalidParameterValueException", @@ -16454,108 +16415,102 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-get_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:55", "recorded-content": { "get_function_exception": { "Code": "ValidationException", - "Count": 2, + "Count": 1, "Errors": [ - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 170", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-delete_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:55", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", - "Count": 2, + "Count": 1, "Errors": [ - "Value 'arn::lambda:invalid-region:111111111111:function:my-functionaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 140", - "Value 'arn::lambda:invalid-region:111111111111:function:my-functionaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-functionaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-invoke]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:55", "recorded-content": { "invoke_exception": { "Code": "ValidationException", - "Count": 2, + "Count": 1, "Errors": [ - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 170", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-create_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:55", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 2, "Errors": [ "Value 'arn::lambda:invalid-region:111111111111:function:my-functionaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 140", - "Value 'arn::lambda:invalid-region:111111111111:function:my-functionaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-functionaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-get_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "get_function_exception": { "Code": "ValidationException", - "Count": 3, + "Count": 2, "Errors": [ "Value '' at 'qualifier' failed to satisfy constraint: Member must have length less than or equal to 128", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function-' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 170", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function-' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function-' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-delete_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", - "Count": 3, + "Count": 2, "Errors": [ "Value '' at 'qualifier' failed to satisfy constraint: Member must have length less than or equal to 128", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 140", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-invoke]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "invoke_exception": { "Code": "ValidationException", - "Count": 3, + "Count": 2, "Errors": [ "Value '' at 'qualifier' failed to satisfy constraint: Member must have length less than or equal to 128", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function-' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 170", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function-' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function-' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-create_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 2, "Errors": [ "Value 'arn::lambda:invalid-region:111111111111:function:my-function-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:' at 'functionName' failed to satisfy constraint: Member must have length less than or equal to 140", - "Value 'arn::lambda:invalid-region:111111111111:function:my-function-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:invalid-region:111111111111:function:my-function-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } @@ -16592,55 +16547,55 @@ "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-get_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-delete_function]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-invoke]": { - "recorded-date": "12-09-2024, 11:30:04", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-create_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:56", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:1:2' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-get_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:57", "recorded-content": { "get_function_exception": { "Code": "ResourceNotFoundException", @@ -16649,7 +16604,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-delete_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:57", "recorded-content": { "delete_function_exception": { "Code": "ResourceNotFoundException", @@ -16658,7 +16613,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-invoke]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:57", "recorded-content": { "invoke_exception": { "Code": "ResourceNotFoundException", @@ -16667,59 +16622,59 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-create_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:57", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-get_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:57", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-delete_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:57", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-invoke]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:57", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-create_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:58", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'function:my-function:$LATEST:extra' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-get_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:58", "recorded-content": { "get_function_exception": { "Code": "InvalidParameterValueException", @@ -16728,7 +16683,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-delete_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:58", "recorded-content": { "delete_function_exception": { "Code": "InvalidParameterValueException", @@ -16737,7 +16692,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-invoke]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:58", "recorded-content": { "invoke_exception": { "Code": "InvalidParameterValueException", @@ -16746,19 +16701,19 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-create_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:58", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:$LATEST:1' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:$LATEST:1' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-get_function]": { - "recorded-date": "12-09-2024, 11:30:05", + "recorded-date": "25-11-2025, 02:27:58", "recorded-content": { "get_function_exception": { "Code": "ResourceNotFoundException", @@ -16767,11 +16722,16 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-delete_function]": { - "recorded-date": "12-09-2024, 11:30:05", - "recorded-content": {} + "recorded-date": "25-11-2025, 02:27:58", + "recorded-content": { + "delete_function_exception": { + "Code": "ResourceNotFoundException", + "Message": "Function not found: arn::lambda::111111111111:function:my-function" + } + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-invoke]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "invoke_exception": { "Code": "ResourceNotFoundException", @@ -16780,163 +16740,163 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-create_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:$latest' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:$latest' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-get_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-delete_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-invoke]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-create_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-get_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-delete_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:27:59", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-invoke]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:28:00", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-create_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:28:00", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda:::function:my-function' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-get_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:28:00", "recorded-content": { "get_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-delete_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:28:00", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-invoke]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:28:00", "recorded-content": { "invoke_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_\\.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-create_function]": { - "recorded-date": "12-09-2024, 11:30:06", + "recorded-date": "25-11-2025, 02:28:00", "recorded-content": { "create_function_exception": { "Code": "ValidationException", "Count": 1, "Errors": [ - "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" + "Value 'arn::lambda::111111111111:function:my-function:$LATES' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?" ] } } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-get_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:52", "recorded-content": { "get_function_exception": { "Code": "ValidationException", @@ -16948,7 +16908,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-delete_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:52", "recorded-content": { "delete_function_exception": { "Code": "ValidationException", @@ -16960,7 +16920,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-invoke]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "invoke_exception": { "Code": "ValidationException", @@ -16972,7 +16932,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-create_function]": { - "recorded-date": "12-09-2024, 11:30:02", + "recorded-date": "25-11-2025, 02:27:53", "recorded-content": { "create_function_exception": { "Code": "ValidationException", @@ -17122,7 +17082,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_allow": { - "recorded-date": "12-09-2024, 11:35:20", + "recorded-date": "25-11-2025, 02:36:04", "recorded-content": { "put_recursion_config_response": { "RecursiveLoop": "Allow", @@ -17141,7 +17101,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_default_terminate": { - "recorded-date": "12-09-2024, 11:35:22", + "recorded-date": "25-11-2025, 02:36:11", "recorded-content": { "get_recursion_default_terminate_response": { "RecursiveLoop": "Terminate", @@ -17153,7 +17113,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_invalid_value": { - "recorded-date": "12-09-2024, 11:35:24", + "recorded-date": "25-11-2025, 02:36:14", "recorded-content": { "put_recursion_invalid_value_error": { "Error": { @@ -17168,7 +17128,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[lambda_function]": { - "recorded-date": "23-10-2024, 10:51:15", + "recorded-date": "25-11-2025, 02:39:44", "recorded-content": { "tag_single_response": { "ResponseMetadata": { @@ -17283,7 +17243,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[event_source_mapping]": { - "recorded-date": "23-10-2024, 10:51:27", + "recorded-date": "25-11-2025, 02:39:50", "recorded-content": { "tag_single_response": { "ResponseMetadata": { @@ -17398,7 +17358,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_esm_create": { - "recorded-date": "24-10-2024, 14:16:07", + "recorded-date": "25-11-2025, 02:39:41", "recorded-content": { "get_event_source_mapping_with_tag": true, "list_tags_result": { @@ -17413,7 +17373,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[lambda_function]": { - "recorded-date": "24-10-2024, 12:42:56", + "recorded-date": "25-11-2025, 21:37:01", "recorded-content": { "not_found_exception_tag": { "Error": { @@ -17466,7 +17426,7 @@ "invalid_arn_exception": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'arn::lambda::111111111111:foobar:' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" + "Message": "1 validation error detected: Value 'arn::lambda::111111111111:foobar:' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -17476,7 +17436,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[event_source_mapping]": { - "recorded-date": "24-10-2024, 12:42:57", + "recorded-date": "25-11-2025, 21:37:01", "recorded-content": { "not_found_exception_tag": { "Error": { @@ -17517,7 +17477,7 @@ "aliased_arn_exception": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'arn::lambda::111111111111:event-source-mapping::alias' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" + "Message": "1 validation error detected: Value 'arn::lambda::111111111111:event-source-mapping::alias' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -17527,7 +17487,7 @@ "invalid_arn_exception": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value 'arn::lambda::111111111111:foobar:' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" + "Message": "1 validation error detected: Value 'arn::lambda::111111111111:foobar:' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -17537,7 +17497,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle_delete_function": { - "recorded-date": "12-10-2024, 10:00:01", + "recorded-date": "25-11-2025, 02:47:20", "recorded-content": { "update_table_response": { "TableDescription": { @@ -17575,7 +17535,12 @@ "TableId": "", "TableName": "", "TableSizeBytes": 0, - "TableStatus": "UPDATING" + "TableStatus": "UPDATING", + "WarmThroughput": { + "ReadUnitsPerSecond": 12000, + "Status": "ACTIVE", + "WriteUnitsPerSecond": 4000 + } }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -17639,6 +17604,7 @@ } }, "delete_function_response": { + "StatusCode": 204, "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 204 @@ -17703,7 +17669,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_deletion_without_qualifier": { - "recorded-date": "21-11-2024, 13:44:17", + "recorded-date": "25-11-2025, 02:43:31", "recorded-content": { "url_creation": { "AuthType": "NONE", @@ -17782,7 +17748,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_deletion": { - "recorded-date": "21-11-2024, 13:44:51", + "recorded-date": "25-11-2025, 02:38:47", "recorded-content": { "create_response": { "Architectures": [ @@ -17841,7 +17807,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_update": { - "recorded-date": "21-11-2024, 13:44:53", + "recorded-date": "25-11-2025, 02:38:49", "recorded-content": { "create_response": { "Architectures": [ @@ -17906,7 +17872,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_filter_criteria_validation": { - "recorded-date": "11-12-2024, 11:29:54", + "recorded-date": "18-12-2025, 15:04:51", "recorded-content": { "response-with-empty-filters": { "BatchSize": 100, @@ -17937,7 +17903,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.13]": { - "recorded-date": "01-04-2025, 13:31:11", + "recorded-date": "12-01-2026, 15:32:37", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18081,7 +18047,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.12]": { - "recorded-date": "01-04-2025, 13:31:07", + "recorded-date": "12-01-2026, 15:32:31", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18225,7 +18191,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": { - "recorded-date": "20-02-2025, 17:53:33", + "recorded-date": "25-11-2025, 02:35:53", "recorded-content": { "create-response-non-existent-subnet-id": { "Error": { @@ -18242,7 +18208,7 @@ "create-response-invalid-format-subnet-id": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value '[]' at 'vpcConfig.subnetIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: ^subnet-[0-9a-z]*$]" + "Message": "1 validation error detected: Value '[]' at 'vpcConfig.subnetIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: subnet-[0-9a-z]*]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -18252,7 +18218,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": { - "recorded-date": "20-02-2025, 17:57:29", + "recorded-date": "25-11-2025, 02:35:53", "recorded-content": { "create-response-non-existent-security-group": { "Error": { @@ -18269,7 +18235,7 @@ "create-response-invalid-format-security-group": { "Error": { "Code": "ValidationException", - "Message": "1 validation error detected: Value '[]' at 'vpcConfig.securityGroupIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: ^sg-[0-9a-zA-Z]*$]" + "Message": "1 validation error detected: Value '[]' at 'vpcConfig.securityGroupIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 1024, Member must have length greater than or equal to 0, Member must satisfy regular expression pattern: sg-[0-9a-zA-Z]*]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -18279,7 +18245,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": { - "recorded-date": "03-03-2025, 16:49:40", + "recorded-date": "25-11-2025, 02:48:10", "recorded-content": { "no_starting_position": { "Error": { @@ -18306,7 +18272,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet8]": { - "recorded-date": "01-04-2025, 13:31:15", + "recorded-date": "12-01-2026, 15:32:49", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18450,7 +18416,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java11]": { - "recorded-date": "01-04-2025, 13:40:26", + "recorded-date": "12-01-2026, 15:33:06", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18542,7 +18508,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java17]": { - "recorded-date": "01-04-2025, 13:40:32", + "recorded-date": "12-01-2026, 15:33:11", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18634,7 +18600,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java21]": { - "recorded-date": "01-04-2025, 13:40:35", + "recorded-date": "12-01-2026, 15:33:15", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18726,7 +18692,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.12]": { - "recorded-date": "01-04-2025, 13:40:40", + "recorded-date": "12-01-2026, 15:33:21", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18818,7 +18784,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.13]": { - "recorded-date": "01-04-2025, 13:40:44", + "recorded-date": "12-01-2026, 15:33:24", "recorded-content": { "create_function_response": { "Architectures": [ @@ -18910,7 +18876,7 @@ } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet8]": { - "recorded-date": "01-04-2025, 13:40:47", + "recorded-date": "12-01-2026, 15:33:28", "recorded-content": { "create_function_response": { "Architectures": [ @@ -19000,5 +18966,621 @@ } } } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3_errors": { + "recorded-date": "25-11-2025, 02:27:45", + "recorded-content": { + "create-error-wrong-bucket": { + "Error": { + "Code": "InvalidParameterValueException", + "Message": "Error occurred while GetObject. S3 Error Code: NoSuchBucket. S3 Error Message: The specified bucket does not exist" + }, + "Type": "User", + "message": "Error occurred while GetObject. S3 Error Code: NoSuchBucket. S3 Error Message: The specified bucket does not exist", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create-error-wrong-key": { + "Error": { + "Code": "InvalidParameterValueException", + "Message": "Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist." + }, + "Type": "User", + "message": "Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-error-wrong-bucket": { + "Error": { + "Code": "InvalidParameterValueException", + "Message": "Error occurred while GetObject. S3 Error Code: NoSuchBucket. S3 Error Message: The specified bucket does not exist" + }, + "Type": "User", + "message": "Error occurred while GetObject. S3 Error Code: NoSuchBucket. S3 Error Message: The specified bucket does not exist", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "update-error-wrong-key": { + "Error": { + "Code": "InvalidParameterValueException", + "Message": "Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist." + }, + "Type": "User", + "message": "Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java25]": { + "recorded-date": "12-01-2026, 15:32:25", + "recorded-content": { + "create_function_response": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "get_function_response_latest": { + "Code": { + "Location": "", + "RepositoryType": "S3" + }, + "Configuration": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LastUpdateStatus": "Successful", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Active", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_function_response_version_1": { + "Code": { + "Location": "", + "RepositoryType": "S3" + }, + "Configuration": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "version1", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function::1", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LastUpdateStatus": "Successful", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Active", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java25]": { + "recorded-date": "12-01-2026, 15:33:18", + "recorded-content": { + "create_function_response": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "update_function_response": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LastUpdateStatus": "InProgress", + "LastUpdateStatusReason": "The function is being created.", + "LastUpdateStatusReasonCode": "Creating", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "PublishedVersions", + "OptimizationStatus": "Off" + }, + "State": "Active", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes2]": { + "recorded-date": "12-01-2026, 15:31:43", + "recorded-content": { + "publish_result": { + "CompatibleArchitectures": [ + "arm64", + "x86_64" + ], + "CompatibleRuntimes": [ + "provided.al2023", + "provided.al2", + "provided" + ], + "Content": { + "CodeSha256": "", + "CodeSize": "", + "Location": "" + }, + "CreatedDate": "date", + "Description": "", + "LayerArn": "arn::lambda::111111111111:layer:", + "LayerVersionArn": "arn::lambda::111111111111:layer::1", + "Version": 1, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[capacity_provider]": { + "recorded-date": "25-11-2025, 21:37:02", + "recorded-content": { + "not_found_exception_tag": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Capacity provider not found: arn::lambda::111111111111:capacity-provider:" + }, + "Message": "Capacity provider not found: arn::lambda::111111111111:capacity-provider:", + "Type": "User", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "not_found_exception_untag": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Capacity provider not found: arn::lambda::111111111111:capacity-provider:" + }, + "Message": "Capacity provider not found: arn::lambda::111111111111:capacity-provider:", + "Type": "User", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "not_found_exception_list": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Capacity provider not found: arn::lambda::111111111111:capacity-provider:" + }, + "Message": "Capacity provider not found: arn::lambda::111111111111:capacity-provider:", + "Type": "User", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "aliased_arn_exception": { + "Error": { + "Code": "ValidationException", + "Message": "1 validation error detected: Value 'arn::lambda::111111111111:capacity-provider::alias' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid_arn_exception": { + "Error": { + "Code": "ValidationException", + "Message": "1 validation error detected: Value 'arn::lambda::111111111111:foobar:' at 'resource' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet10]": { + "recorded-date": "12-01-2026, 15:33:00", + "recorded-content": { + "create_function_response": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "get_function_response_latest": { + "Code": { + "Location": "", + "RepositoryType": "S3" + }, + "Configuration": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LastUpdateStatus": "Successful", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Active", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get_function_response_version_1": { + "Code": { + "Location": "", + "RepositoryType": "S3" + }, + "Configuration": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "version1", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function::1", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LastUpdateStatus": "Successful", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Active", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet10]": { + "recorded-date": "12-01-2026, 15:33:33", + "recorded-content": { + "create_function_response": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "update_function_response": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LastUpdateStatus": "InProgress", + "LastUpdateStatusReason": "The function is being created.", + "LastUpdateStatusReasonCode": "Creating", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "PublishedVersions", + "OptimizationStatus": "Off" + }, + "State": "Active", + "Timeout": 5, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/lambda_/test_lambda_api.validation.json b/tests/aws/services/lambda_/test_lambda_api.validation.json index 757169d7ade65..1827100b42221 100644 --- a/tests/aws/services/lambda_/test_lambda_api.validation.json +++ b/tests/aws/services/lambda_/test_lambda_api.validation.json @@ -1,72 +1,198 @@ { "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_code_signing_not_found_excs": { - "last_validated_date": "2024-04-10T09:19:15+00:00" + "last_validated_date": "2025-11-25T02:46:47+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.41, + "teardown": 0.79, + "total": 4.21 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_function_code_signing_config": { - "last_validated_date": "2024-04-10T09:19:10+00:00" + "last_validated_date": "2025-11-25T02:46:42+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.77, + "teardown": 0.65, + "total": 3.43 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings": { - "last_validated_date": "2024-04-10T09:19:17+00:00" + "last_validated_date": "2025-11-25T02:46:47+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size": { - "last_validated_date": "2024-04-10T09:19:28+00:00" + "last_validated_date": "2025-11-25T02:46:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 10.87, + "teardown": 0.97, + "total": 11.85 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size_config_update": { - "last_validated_date": "2024-04-10T09:19:34+00:00" + "last_validated_date": "2025-11-25T02:47:04+00:00", + "durations_in_seconds": { + "setup": 0.02, + "call": 4.47, + "teardown": 0.82, + "total": 5.31 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_lifecycle": { - "last_validated_date": "2024-11-21T13:44:48+00:00" + "last_validated_date": "2025-11-25T02:38:45+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 6.7, + "teardown": 0.46, + "total": 7.17 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_naming": { - "last_validated_date": "2024-11-21T13:45:11+00:00" + "last_validated_date": "2025-11-25T02:39:05+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.82, + "teardown": 0.28, + "total": 3.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_deletion": { - "last_validated_date": "2024-11-21T13:44:51+00:00" + "last_validated_date": "2025-11-25T02:38:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.96, + "teardown": 0.3, + "total": 2.26 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_update": { - "last_validated_date": "2024-11-21T13:44:53+00:00" + "last_validated_date": "2025-11-25T02:38:49+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.07, + "teardown": 0.33, + "total": 2.41 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_notfound_and_invalid_routingconfigs": { - "last_validated_date": "2024-11-21T13:45:05+00:00" + "last_validated_date": "2025-11-25T21:33:39+00:00", + "durations_in_seconds": { + "setup": 10.94, + "call": 11.71, + "teardown": 0.73, + "total": 23.38 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_exceptions": { - "last_validated_date": "2024-04-10T09:13:37+00:00" + "last_validated_date": "2025-11-25T21:39:32+00:00", + "durations_in_seconds": { + "setup": 11.1, + "call": 12.72, + "teardown": 1.59, + "total": 25.41 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_lifecycle": { - "last_validated_date": "2024-04-10T09:13:20+00:00" + "last_validated_date": "2025-11-25T02:40:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 4.94, + "teardown": 0.61, + "total": 5.56 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_filter_criteria_validation": { - "last_validated_date": "2024-12-11T11:29:51+00:00" + "last_validated_date": "2025-12-18T15:07:44+00:00", + "durations_in_seconds": { + "setup": 12.13, + "call": 22.59, + "teardown": 2.65, + "total": 37.37 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_self_managed": { "last_validated_date": "2024-09-03T20:58:27+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": { - "last_validated_date": "2025-03-03T17:07:41+00:00" + "last_validated_date": "2025-11-25T02:48:02+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 10.38, + "teardown": 2.09, + "total": 12.48 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": { - "last_validated_date": "2025-03-03T16:49:39+00:00" + "last_validated_date": "2025-11-25T02:48:10+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 7.63, + "teardown": 0.78, + "total": 8.42 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_exceptions": { - "last_validated_date": "2024-12-05T10:52:30+00:00" + "last_validated_date": "2025-11-27T01:04:32+00:00", + "durations_in_seconds": { + "setup": 0.45, + "call": 1.36, + "teardown": 0.01, + "total": 1.82 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle": { - "last_validated_date": "2024-10-14T12:36:54+00:00" + "last_validated_date": "2025-11-25T02:47:33+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 11.24, + "teardown": 1.17, + "total": 12.42 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle_delete_function": { - "last_validated_date": "2024-10-12T09:59:58+00:00" + "last_validated_date": "2025-11-25T02:47:20+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 14.42, + "teardown": 0.96, + "total": 15.39 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_function_name_variations": { - "last_validated_date": "2024-10-14T12:46:32+00:00" + "last_validated_date": "2025-11-25T03:20:36+00:00", + "durations_in_seconds": { + "setup": 11.3, + "call": 206.47, + "teardown": 2.75, + "total": 220.52 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_advance_logging_configuration_format_switch": { "last_validated_date": "2024-04-10T08:58:47+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_create_lambda_exceptions": { - "last_validated_date": "2025-04-01T13:08:49+00:00" + "last_validated_date": "2026-01-12T15:31:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.3, + "teardown": 0.01, + "total": 1.31 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_delete_on_nonexisting_version": { - "last_validated_date": "2024-09-12T11:29:32+00:00" + "last_validated_date": "2025-11-25T02:27:11+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.45, + "teardown": 0.64, + "total": 3.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_advanced_configuration": { "last_validated_date": "2024-03-28T09:54:41+00:00" @@ -75,34 +201,94 @@ "last_validated_date": "2024-04-10T08:59:14+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_arns": { - "last_validated_date": "2024-09-12T11:30:00+00:00" + "last_validated_date": "2025-11-25T02:27:51+00:00", + "durations_in_seconds": { + "setup": 0.02, + "call": 4.21, + "teardown": 1.2, + "total": 5.43 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_lifecycle": { - "last_validated_date": "2024-09-12T11:29:18+00:00" + "last_validated_date": "2025-11-25T02:26:54+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.8, + "teardown": 0.39, + "total": 6.2 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-create_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.09, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-delete_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-get_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.08, + "teardown": 0.01, + "total": 0.09 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-invoke]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.13, + "teardown": 0.01, + "total": 0.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-create_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.15, + "teardown": 0.01, + "total": 0.17 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-delete_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-get_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.14, + "teardown": 0.01, + "total": 0.16 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-invoke]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_and_qualifier_too_long-delete_function]": { "last_validated_date": "2024-08-22T15:10:43+00:00" @@ -114,190 +300,571 @@ "last_validated_date": "2024-08-22T15:10:44+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-create_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.01, + "total": 0.14 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-delete_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.09, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-get_function]": { - "last_validated_date": "2024-09-12T11:30:01+00:00" + "last_validated_date": "2025-11-25T02:27:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-invoke]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.13, + "teardown": 0.01, + "total": 0.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-create_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:55+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.24, + "teardown": 0.01, + "total": 0.26 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-delete_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:55+00:00", + "durations_in_seconds": { + "setup": 0.12, + "call": 0.17, + "teardown": 0.01, + "total": 0.3 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-get_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:54+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-invoke]": { - "last_validated_date": "2024-08-22T15:20:33+00:00" + "last_validated_date": "2025-11-25T02:27:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.15, + "teardown": 0.01, + "total": 0.16 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-create_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.08, + "teardown": 0.01, + "total": 0.09 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-delete_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.01, + "total": 0.12 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-get_function]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:55+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-invoke]": { - "last_validated_date": "2024-09-12T11:30:04+00:00" + "last_validated_date": "2025-11-25T02:27:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.01, + "total": 0.12 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-delete_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.18, + "teardown": 0.01, + "total": 0.2 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-get_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.13, + "teardown": 0.01, + "total": 0.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-invoke]": { - "last_validated_date": "2024-08-22T15:20:38+00:00" + "last_validated_date": "2025-11-25T02:27:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.09, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-create_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-delete_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-get_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-invoke]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.1, + "teardown": 0.01, + "total": 0.12 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-create_function]": { - "last_validated_date": "2024-09-12T11:30:01+00:00" + "last_validated_date": "2025-11-25T02:27:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.13, + "teardown": 0.01, + "total": 0.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-delete_function]": { - "last_validated_date": "2024-09-12T11:30:01+00:00" + "last_validated_date": "2025-11-25T02:27:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.09, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-get_function]": { - "last_validated_date": "2024-09-12T11:30:01+00:00" + "last_validated_date": "2025-11-25T02:27:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.12, + "teardown": 0.01, + "total": 0.14 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-invoke]": { - "last_validated_date": "2024-09-12T11:30:01+00:00" + "last_validated_date": "2025-11-25T02:27:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.15, + "teardown": 0.01, + "total": 0.17 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-create_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-delete_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-get_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.13, + "teardown": 0.01, + "total": 0.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-invoke]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-create_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:54+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-delete_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.14, + "teardown": 0.01, + "total": 0.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-get_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-invoke]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:54+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-create_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.24, + "teardown": 0.01, + "total": 0.26 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-delete_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-get_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-invoke]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-create_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.16, + "teardown": 0.01, + "total": 0.18 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-delete_function]": { + "last_validated_date": "2025-11-25T02:27:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.18, + "teardown": 0.01, + "total": 0.2 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-get_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.14, + "teardown": 0.01, + "total": 0.16 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-invoke]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.09, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-create_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:28:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.1, + "teardown": 0.01, + "total": 0.12 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-delete_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-get_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-invoke]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:28:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.09, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-create_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.12, + "teardown": 0.01, + "total": 0.14 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-delete_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-get_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.14, + "teardown": 0.01, + "total": 0.16 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-invoke]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:27:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-create_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:28:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-delete_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:28:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-get_function]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:28:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-invoke]": { - "last_validated_date": "2024-09-12T11:30:06+00:00" + "last_validated_date": "2025-11-25T02:28:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-create_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:54+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-delete_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:54+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-get_function]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.01, + "total": 0.2 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-invoke]": { - "last_validated_date": "2024-09-12T11:30:03+00:00" + "last_validated_date": "2025-11-25T02:27:54+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-create_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.18, + "teardown": 0.02, + "total": 0.21 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-delete_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.09, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-get_function]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-invoke]": { - "last_validated_date": "2024-09-12T11:30:05+00:00" + "last_validated_date": "2025-11-25T02:27:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.15, + "teardown": 0.01, + "total": 0.17 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-create_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-delete_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.01, + "total": 0.14 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-get_function]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:52+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-invoke]": { - "last_validated_date": "2024-09-12T11:30:02+00:00" + "last_validated_date": "2025-11-25T02:27:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long0-create_function]": { "last_validated_date": "2024-08-22T15:07:10+00:00" @@ -333,363 +900,1128 @@ "last_validated_date": "2024-04-10T08:59:09+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[delete_function]": { - "last_validated_date": "2024-09-12T11:29:47+00:00" + "last_validated_date": "2025-11-25T02:27:29+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.82, + "teardown": 0.62, + "total": 2.45 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function]": { - "last_validated_date": "2024-09-12T11:29:35+00:00" + "last_validated_date": "2025-11-25T02:27:15+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.71, + "teardown": 0.54, + "total": 2.26 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_code_signing_config]": { - "last_validated_date": "2024-09-12T11:29:41+00:00" + "last_validated_date": "2025-11-25T02:27:22+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.73, + "teardown": 0.61, + "total": 2.35 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_concurrency]": { - "last_validated_date": "2024-09-12T11:29:45+00:00" + "last_validated_date": "2025-11-25T02:27:27+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.79, + "teardown": 0.64, + "total": 2.44 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_configuration]": { - "last_validated_date": "2024-09-12T11:29:37+00:00" + "last_validated_date": "2025-11-25T02:27:17+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.81, + "teardown": 0.55, + "total": 2.37 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_event_invoke_config]": { - "last_validated_date": "2024-09-12T11:29:43+00:00" + "last_validated_date": "2025-11-25T02:27:24+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.75, + "teardown": 0.59, + "total": 2.35 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_url_config]": { - "last_validated_date": "2024-09-12T11:29:39+00:00" + "last_validated_date": "2025-11-25T02:27:20+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.86, + "teardown": 0.6, + "total": 2.47 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[invoke]": { - "last_validated_date": "2024-09-12T11:29:49+00:00" + "last_validated_date": "2025-11-25T02:27:32+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.85, + "teardown": 0.53, + "total": 2.39 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_invoke": { - "last_validated_date": "2024-09-12T11:34:43+00:00" + "last_validated_date": "2025-11-25T02:35:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.08, + "teardown": 0.01, + "total": 0.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config": { "last_validated_date": "2025-02-20T17:44:18+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": { - "last_validated_date": "2025-02-20T17:57:29+00:00" + "last_validated_date": "2025-11-25T02:35:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.26, + "teardown": 0.01, + "total": 0.28 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": { - "last_validated_date": "2025-02-20T17:53:33+00:00" + "last_validated_date": "2025-11-25T02:35:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.7, + "teardown": 0.01, + "total": 0.72 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3": { - "last_validated_date": "2024-09-12T11:29:56+00:00" + "last_validated_date": "2025-11-25T02:27:41+00:00", + "durations_in_seconds": { + "setup": 0.81, + "call": 3.37, + "teardown": 1.14, + "total": 5.32 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3_errors": { + "last_validated_date": "2025-11-25T02:27:45+00:00", + "durations_in_seconds": { + "setup": 0.77, + "call": 2.72, + "teardown": 1.06, + "total": 4.55 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_zipfile": { - "last_validated_date": "2024-09-12T11:29:52+00:00" + "last_validated_date": "2025-11-25T02:27:35+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.18, + "teardown": 0.32, + "total": 3.51 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_code_updates": { - "last_validated_date": "2024-09-12T11:34:46+00:00" + "last_validated_date": "2025-11-25T02:35:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.46, + "teardown": 0.25, + "total": 3.72 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_config_updates": { - "last_validated_date": "2024-09-12T11:34:50+00:00" + "last_validated_date": "2025-11-25T02:36:01+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.58, + "teardown": 0.54, + "total": 4.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_list_functions": { - "last_validated_date": "2024-09-12T11:30:19+00:00" + "last_validated_date": "2025-11-25T02:28:15+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 10.93, + "teardown": 1.18, + "total": 12.12 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[delete_function]": { - "last_validated_date": "2024-09-12T11:29:32+00:00" + "last_validated_date": "2025-11-25T02:27:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.24, + "teardown": 0.01, + "total": 0.25 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function]": { - "last_validated_date": "2024-09-12T11:29:32+00:00" + "last_validated_date": "2025-11-25T02:27:12+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.13, + "teardown": 0.01, + "total": 0.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_code_signing_config]": { - "last_validated_date": "2024-09-12T11:29:33+00:00" + "last_validated_date": "2025-11-25T02:27:12+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.1, + "teardown": 0.01, + "total": 0.12 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_concurrency]": { - "last_validated_date": "2024-09-12T11:29:33+00:00" + "last_validated_date": "2025-11-25T02:27:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.01, + "total": 0.11 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_configuration]": { - "last_validated_date": "2024-09-12T11:29:33+00:00" + "last_validated_date": "2025-11-25T02:27:12+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.22, + "teardown": 0.01, + "total": 0.24 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_event_invoke_config]": { - "last_validated_date": "2024-09-12T11:29:33+00:00" + "last_validated_date": "2025-11-25T02:27:13+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.12, + "teardown": 0.01, + "total": 0.14 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_url_config]": { - "last_validated_date": "2024-09-12T11:29:33+00:00" + "last_validated_date": "2025-11-25T02:27:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.01, + "total": 0.21 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function]": { - "last_validated_date": "2024-09-12T11:29:25+00:00" + "last_validated_date": "2025-11-25T02:27:03+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.88, + "teardown": 0.54, + "total": 2.43 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_configuration]": { - "last_validated_date": "2024-09-12T11:29:27+00:00" + "last_validated_date": "2025-11-25T02:27:06+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.8, + "teardown": 0.64, + "total": 2.45 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_event_invoke_config]": { - "last_validated_date": "2024-09-12T11:29:29+00:00" + "last_validated_date": "2025-11-25T02:27:08+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.91, + "teardown": 0.56, + "total": 2.48 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[delete_function]": { - "last_validated_date": "2024-09-12T11:29:23+00:00" + "last_validated_date": "2025-11-25T02:27:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.19, + "teardown": 0.01, + "total": 0.21 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function]": { - "last_validated_date": "2024-09-12T11:29:23+00:00" + "last_validated_date": "2025-11-25T02:27:01+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.19, + "teardown": 0.01, + "total": 0.21 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function_configuration]": { - "last_validated_date": "2024-09-12T11:29:24+00:00" + "last_validated_date": "2025-11-25T02:27:01+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.19, + "teardown": 0.01, + "total": 0.21 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_redundant_updates": { - "last_validated_date": "2024-09-12T11:29:23+00:00" + "last_validated_date": "2025-11-25T02:27:00+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.22, + "teardown": 0.56, + "total": 5.79 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_update_lambda_exceptions": { - "last_validated_date": "2025-04-01T13:10:29+00:00" + "last_validated_date": "2026-01-12T15:31:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.59, + "teardown": 0.39, + "total": 2.98 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_vpc_config": { - "last_validated_date": "2024-09-12T11:34:40+00:00" + "last_validated_date": "2025-11-25T02:35:52+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 453.58, + "teardown": 3.34, + "total": 456.93 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_and_image_config_crud": { - "last_validated_date": "2024-04-10T09:11:13+00:00" + "last_validated_date": "2025-11-25T02:37:44+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 29.02, + "teardown": 0.36, + "total": 29.39 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_crud": { - "last_validated_date": "2024-04-10T09:10:21+00:00" + "last_validated_date": "2025-11-25T02:37:05+00:00", + "durations_in_seconds": { + "setup": 1.08, + "call": 50.01, + "teardown": 0.24, + "total": 51.33 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_versions": { - "last_validated_date": "2024-04-10T09:11:50+00:00" + "last_validated_date": "2025-11-25T02:38:15+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 28.73, + "teardown": 2.36, + "total": 31.1 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_zip_file_to_image": { - "last_validated_date": "2024-04-10T09:10:37+00:00" + "last_validated_date": "2025-11-25T02:37:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.45, + "teardown": 0.28, + "total": 9.73 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes0]": { - "last_validated_date": "2025-04-01T13:14:56+00:00" + "last_validated_date": "2026-01-12T15:31:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.08, + "teardown": 0.27, + "total": 4.35 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes1]": { - "last_validated_date": "2025-04-01T13:15:00+00:00" + "last_validated_date": "2026-01-12T15:31:37+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.66, + "teardown": 0.27, + "total": 5.94 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes2]": { + "last_validated_date": "2026-01-12T15:31:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.66, + "teardown": 0.28, + "total": 5.94 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_exceptions": { - "last_validated_date": "2025-04-01T13:19:40+00:00" + "last_validated_date": "2026-01-12T15:31:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 14.31, + "teardown": 0.42, + "total": 14.74 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_exceptions": { - "last_validated_date": "2024-04-10T09:23:18+00:00" + "last_validated_date": "2025-11-25T21:58:40+00:00", + "durations_in_seconds": { + "setup": 11.09, + "call": 36.8, + "teardown": 1.54, + "total": 49.43 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_quota_exception": { - "last_validated_date": "2024-04-10T09:23:58+00:00" + "last_validated_date": "2025-11-25T02:50:21+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 43.13, + "teardown": 0.46, + "total": 43.6 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_lifecycle": { - "last_validated_date": "2024-04-10T09:24:16+00:00" + "last_validated_date": "2025-11-25T02:50:43+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 20.73, + "teardown": 0.81, + "total": 21.55 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_exceptions": { - "last_validated_date": "2024-04-10T09:24:29+00:00" + "last_validated_date": "2025-11-25T02:50:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.76, + "teardown": 0.12, + "total": 5.89 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_lifecycle": { - "last_validated_date": "2024-04-10T09:24:34+00:00" + "last_validated_date": "2025-11-25T02:50:55+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 4.03, + "teardown": 0.1, + "total": 4.14 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_s3_content": { - "last_validated_date": "2024-04-10T09:24:22+00:00" + "last_validated_date": "2025-11-25T02:50:45+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.99, + "teardown": 0.84, + "total": 2.84 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_aws": { - "last_validated_date": "2024-04-10T09:16:22+00:00" + "last_validated_date": "2025-11-25T02:42:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.3, + "teardown": 0.61, + "total": 2.92 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_fields": { - "last_validated_date": "2024-04-10T09:16:33+00:00" + "last_validated_date": "2025-11-25T02:43:05+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.47, + "teardown": 0.72, + "total": 4.2 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_create_multiple_lambda_permissions": { - "last_validated_date": "2024-04-10T09:16:40+00:00" + "last_validated_date": "2025-11-25T02:43:12+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.21, + "teardown": 0.84, + "total": 3.06 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_lambda_permission_fn_versioning": { - "last_validated_date": "2024-04-10T09:16:28+00:00" + "last_validated_date": "2025-11-25T21:46:15+00:00", + "durations_in_seconds": { + "setup": 10.92, + "call": 4.0, + "teardown": 1.05, + "total": 15.97 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_permission_exceptions": { - "last_validated_date": "2024-04-10T09:16:19+00:00" + "last_validated_date": "2025-11-25T02:42:53+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.62, + "teardown": 0.37, + "total": 4.0 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_remove_multi_permissions": { - "last_validated_date": "2024-04-10T09:16:37+00:00" + "last_validated_date": "2025-11-25T02:43:09+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.84, + "teardown": 0.87, + "total": 3.72 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_lambda_provisioned_lifecycle": { - "last_validated_date": "2024-04-10T09:16:14+00:00" + "last_validated_date": "2025-11-25T02:42:49+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 131.69, + "teardown": 0.99, + "total": 132.69 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_exceptions": { - "last_validated_date": "2024-04-10T09:13:56+00:00" + "last_validated_date": "2025-11-25T02:40:34+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.8, + "teardown": 0.78, + "total": 4.59 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_limits": { - "last_validated_date": "2024-04-10T09:13:59+00:00" + "last_validated_date": "2025-11-25T02:40:37+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.19, + "teardown": 0.77, + "total": 2.97 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_allow": { - "last_validated_date": "2024-09-12T11:35:19+00:00" + "last_validated_date": "2025-11-25T02:36:04+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.01, + "teardown": 0.62, + "total": 2.64 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_default_terminate": { - "last_validated_date": "2024-09-12T11:35:21+00:00" + "last_validated_date": "2025-11-25T02:36:11+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.74, + "teardown": 5.69, + "total": 7.44 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_invalid_value": { - "last_validated_date": "2024-09-12T11:35:23+00:00" + "last_validated_date": "2025-11-25T02:36:14+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.8, + "teardown": 0.5, + "total": 2.31 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency": { - "last_validated_date": "2024-04-10T09:13:50+00:00" + "last_validated_date": "2025-11-25T02:40:29+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.85, + "teardown": 5.8, + "total": 8.66 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_exceptions": { - "last_validated_date": "2024-04-10T09:13:43+00:00" + "last_validated_date": "2025-11-25T02:40:18+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.1, + "teardown": 0.53, + "total": 2.64 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_limits": { - "last_validated_date": "2024-04-10T09:13:46+00:00" + "last_validated_date": "2025-11-25T02:40:20+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.02, + "teardown": 0.73, + "total": 2.76 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_basic": { - "last_validated_date": "2024-04-10T09:12:52+00:00" + "last_validated_date": "2025-11-25T02:39:11+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.96, + "teardown": 0.6, + "total": 6.57 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_permissions": { "last_validated_date": "2024-04-10T09:13:01+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_version_and_alias": { - "last_validated_date": "2024-04-10T09:12:57+00:00" + "last_validated_date": "2025-11-25T02:39:15+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.21, + "teardown": 0.68, + "total": 3.9 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_lambda_envvars_near_limit_succeeds": { - "last_validated_date": "2024-04-10T09:19:06+00:00" + "last_validated_date": "2025-11-25T02:46:39+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.08, + "teardown": 0.62, + "total": 2.71 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_fails_multiple_keys": { - "last_validated_date": "2024-04-10T09:19:04+00:00" + "last_validated_date": "2025-11-25T02:46:36+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 17.47, + "teardown": 0.01, + "total": 17.49 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_variables_fails": { - "last_validated_date": "2024-04-10T09:18:46+00:00" + "last_validated_date": "2025-11-25T02:46:19+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 17.69, + "teardown": 0.01, + "total": 17.71 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_lambda": { - "last_validated_date": "2024-04-10T09:18:27+00:00" + "last_validated_date": "2025-11-25T02:46:01+00:00", + "durations_in_seconds": { + "setup": 0.79, + "call": 46.62, + "teardown": 1.36, + "total": 48.77 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_request_create_lambda": { - "last_validated_date": "2024-04-10T09:17:14+00:00" + "last_validated_date": "2025-11-25T02:44:03+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 31.16, + "teardown": 0.01, + "total": 31.18 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_unzipped_lambda": { - "last_validated_date": "2024-04-10T09:17:46+00:00" + "last_validated_date": "2025-11-25T02:45:12+00:00", + "durations_in_seconds": { + "setup": 0.83, + "call": 33.16, + "teardown": 0.86, + "total": 34.85 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_zipped_create_lambda": { - "last_validated_date": "2024-04-10T09:17:26+00:00" + "last_validated_date": "2025-11-25T02:44:37+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 34.79, + "teardown": 0.01, + "total": 34.81 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_exceptions": { - "last_validated_date": "2025-03-31T16:15:53+00:00" + "last_validated_date": "2025-11-25T02:51:34+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.11, + "teardown": 0.01, + "total": 0.13 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet10]": { + "last_validated_date": "2026-01-12T15:33:00+00:00", + "durations_in_seconds": { + "setup": 4.89, + "call": 5.82, + "teardown": 0.44, + "total": 11.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet8]": { - "last_validated_date": "2025-04-01T13:31:14+00:00" + "last_validated_date": "2026-01-12T15:32:49+00:00", + "durations_in_seconds": { + "setup": 5.66, + "call": 5.86, + "teardown": 0.46, + "total": 11.98 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java11]": { - "last_validated_date": "2025-04-01T13:30:54+00:00" + "last_validated_date": "2026-01-12T15:32:08+00:00", + "durations_in_seconds": { + "setup": 6.05, + "call": 3.45, + "teardown": 0.54, + "total": 10.04 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java17]": { - "last_validated_date": "2025-04-01T13:30:57+00:00" + "last_validated_date": "2026-01-12T15:32:14+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.74, + "teardown": 0.49, + "total": 6.24 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java21]": { - "last_validated_date": "2025-04-01T13:31:02+00:00" + "last_validated_date": "2026-01-12T15:32:21+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 6.29, + "teardown": 0.45, + "total": 6.75 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java25]": { + "last_validated_date": "2026-01-12T15:32:25+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.39, + "teardown": 0.45, + "total": 3.85 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.12]": { - "last_validated_date": "2025-04-01T13:31:06+00:00" + "last_validated_date": "2026-01-12T15:32:31+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 5.65, + "teardown": 0.6, + "total": 6.29 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.13]": { - "last_validated_date": "2025-04-01T13:31:10+00:00" + "last_validated_date": "2026-01-12T15:32:37+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.63, + "teardown": 0.5, + "total": 6.14 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet10]": { + "last_validated_date": "2026-01-12T15:33:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.45, + "teardown": 0.41, + "total": 5.86 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet8]": { - "last_validated_date": "2025-04-01T13:42:13+00:00" + "last_validated_date": "2026-01-12T15:33:28+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.92, + "teardown": 0.37, + "total": 3.3 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java11]": { - "last_validated_date": "2025-04-01T13:41:52+00:00" + "last_validated_date": "2026-01-12T15:33:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.33, + "teardown": 0.38, + "total": 5.71 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java17]": { - "last_validated_date": "2025-04-01T13:41:56+00:00" + "last_validated_date": "2026-01-12T15:33:11+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.17, + "teardown": 0.4, + "total": 5.58 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java21]": { - "last_validated_date": "2025-04-01T13:42:01+00:00" + "last_validated_date": "2026-01-12T15:33:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.78, + "teardown": 0.45, + "total": 3.23 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java25]": { + "last_validated_date": "2026-01-12T15:33:18+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.76, + "teardown": 0.51, + "total": 3.28 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.12]": { - "last_validated_date": "2025-04-01T13:42:04+00:00" + "last_validated_date": "2026-01-12T15:33:21+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.78, + "teardown": 0.36, + "total": 3.15 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.13]": { - "last_validated_date": "2025-04-01T13:42:08+00:00" + "last_validated_date": "2026-01-12T15:33:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.76, + "teardown": 0.37, + "total": 3.13 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_esm_create": { - "last_validated_date": "2024-10-24T14:16:05+00:00" + "last_validated_date": "2025-11-25T02:39:41+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 18.83, + "teardown": 0.87, + "total": 19.71 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_fn_create": { - "last_validated_date": "2024-04-10T09:13:04+00:00" + "last_validated_date": "2025-11-25T02:39:21+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.88, + "teardown": 0.63, + "total": 2.52 + } + }, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[capacity_provider]": { + "last_validated_date": "2025-11-25T21:37:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.48, + "teardown": 0.01, + "total": 0.49 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[event_source_mapping]": { - "last_validated_date": "2024-10-24T12:42:57+00:00" + "last_validated_date": "2025-11-25T21:37:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.52, + "teardown": 0.01, + "total": 0.53 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[lambda_function]": { - "last_validated_date": "2024-10-24T12:42:56+00:00" + "last_validated_date": "2025-11-25T21:37:01+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 0.6, + "teardown": 0.01, + "total": 0.98 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle": { "last_validated_date": "2024-04-10T09:13:09+00:00" }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[event_source_mapping]": { - "last_validated_date": "2024-10-23T10:51:25+00:00" + "last_validated_date": "2025-11-25T02:39:50+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 4.29, + "teardown": 1.09, + "total": 5.39 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[lambda_function]": { - "last_validated_date": "2024-10-23T10:51:14+00:00" + "last_validated_date": "2025-11-25T02:39:44+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.16, + "teardown": 0.66, + "total": 3.83 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_nonexisting_resource": { - "last_validated_date": "2024-04-10T09:13:13+00:00" + "last_validated_date": "2025-11-25T02:39:54+00:00", + "durations_in_seconds": { + "setup": 1.95, + "call": 0.59, + "teardown": 0.44, + "total": 2.98 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_exceptions": { - "last_validated_date": "2024-10-24T15:22:27+00:00" + "last_validated_date": "2025-11-25T02:48:27+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.86, + "teardown": 0.76, + "total": 3.63 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_lifecycle": { - "last_validated_date": "2024-10-24T15:22:47+00:00" + "last_validated_date": "2025-11-25T02:48:38+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.94, + "teardown": 0.59, + "total": 4.54 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_limits": { - "last_validated_date": "2024-10-28T14:16:36+00:00" + "last_validated_date": "2025-11-25T02:48:31+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.95, + "teardown": 0.64, + "total": 3.6 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_versions": { - "last_validated_date": "2024-10-24T15:22:38+00:00" + "last_validated_date": "2025-11-25T02:48:33+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.06, + "teardown": 0.78, + "total": 2.85 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_deletion_without_qualifier": { - "last_validated_date": "2024-11-21T13:44:17+00:00" + "last_validated_date": "2025-11-25T02:43:31+00:00", + "durations_in_seconds": { + "setup": 0.02, + "call": 3.98, + "teardown": 0.34, + "total": 4.34 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_exceptions": { - "last_validated_date": "2024-11-21T13:44:04+00:00" + "last_validated_date": "2025-11-25T02:43:19+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 6.43, + "teardown": 0.58, + "total": 7.02 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_lifecycle": { - "last_validated_date": "2024-11-21T13:44:13+00:00" + "last_validated_date": "2025-11-25T02:43:27+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.03, + "teardown": 0.64, + "total": 3.68 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_list_paging": { - "last_validated_date": "2024-11-21T13:44:09+00:00" + "last_validated_date": "2025-11-25T02:43:23+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.68, + "teardown": 0.74, + "total": 4.43 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_version_on_create": { - "last_validated_date": "2024-04-10T09:12:04+00:00" + "last_validated_date": "2025-11-25T02:38:24+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 8.73, + "teardown": 0.32, + "total": 9.06 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_update": { - "last_validated_date": "2024-04-10T09:12:17+00:00" + "last_validated_date": "2025-11-25T02:38:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.48, + "teardown": 0.37, + "total": 2.85 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_wrong_sha256": { - "last_validated_date": "2024-04-10T09:12:14+00:00" + "last_validated_date": "2025-11-25T02:38:35+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.31, + "teardown": 0.42, + "total": 2.74 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_version_lifecycle": { - "last_validated_date": "2024-07-12T11:43:40+00:00" + "last_validated_date": "2025-11-25T02:38:32+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 7.33, + "teardown": 0.42, + "total": 7.76 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_advanced_logging_configuration_format_switch": { - "last_validated_date": "2024-04-17T14:16:55+00:00" + "last_validated_date": "2025-11-25T02:26:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.69, + "teardown": 0.54, + "total": 6.23 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_advanced_logging_configuration": { - "last_validated_date": "2024-04-19T08:20:18+00:00" + "last_validated_date": "2025-11-25T02:26:24+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.91, + "teardown": 0.58, + "total": 4.5 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config0]": { - "last_validated_date": "2024-04-17T14:17:00+00:00" + "last_validated_date": "2025-11-25T02:26:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.44, + "teardown": 0.55, + "total": 4.99 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config1]": { - "last_validated_date": "2024-04-17T14:17:05+00:00" + "last_validated_date": "2025-11-25T02:26:39+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.77, + "teardown": 0.52, + "total": 4.3 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config2]": { - "last_validated_date": "2024-04-17T14:17:11+00:00" + "last_validated_date": "2025-11-25T02:26:44+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.89, + "teardown": 0.54, + "total": 4.44 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config3]": { - "last_validated_date": "2024-04-17T14:17:16+00:00" + "last_validated_date": "2025-11-25T02:26:48+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.82, + "teardown": 0.53, + "total": 4.36 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_cross_region_arn_function_access": { - "last_validated_date": "2024-06-11T13:06:44+00:00" + "last_validated_date": "2025-11-25T02:26:19+00:00", + "durations_in_seconds": { + "setup": 0.02, + "call": 2.18, + "teardown": 0.61, + "total": 2.81 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_update_function_configuration_full_arn": { - "last_validated_date": "2024-06-05T11:49:05+00:00" + "last_validated_date": "2025-11-25T02:26:17+00:00", + "durations_in_seconds": { + "setup": 0.05, + "call": 6.97, + "teardown": 0.6, + "total": 7.62 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[dotnetcore3.1]": { - "last_validated_date": "2025-04-01T13:06:04+00:00" + "last_validated_date": "2026-01-12T15:31:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.25, + "teardown": 0.01, + "total": 0.26 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[go1.x]": { - "last_validated_date": "2025-04-01T13:06:03+00:00" + "last_validated_date": "2026-01-12T15:31:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.26, + "teardown": 0.01, + "total": 0.27 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[java8]": { - "last_validated_date": "2025-04-01T13:06:03+00:00" + "last_validated_date": "2026-01-12T15:31:21+00:00", + "durations_in_seconds": { + "setup": 12.06, + "call": 0.7, + "teardown": 0.01, + "total": 12.77 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs12.x]": { - "last_validated_date": "2025-04-01T13:06:05+00:00" + "last_validated_date": "2026-01-12T15:31:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.27, + "teardown": 0.01, + "total": 0.28 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs14.x]": { - "last_validated_date": "2025-04-01T13:06:04+00:00" + "last_validated_date": "2026-01-12T15:31:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.27, + "teardown": 0.01, + "total": 0.28 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[provided]": { - "last_validated_date": "2025-04-01T13:06:04+00:00" + "last_validated_date": "2026-01-12T15:31:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.25, + "teardown": 0.01, + "total": 0.26 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[python3.7]": { - "last_validated_date": "2025-04-01T13:06:04+00:00" + "last_validated_date": "2026-01-12T15:31:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.26, + "teardown": 0.01, + "total": 0.27 + } }, "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[ruby2.7]": { - "last_validated_date": "2025-04-01T13:06:04+00:00" + "last_validated_date": "2026-01-12T15:31:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.25, + "teardown": 0.01, + "total": 0.26 + } } } diff --git a/tests/aws/services/lambda_/test_lambda_common.py b/tests/aws/services/lambda_/test_lambda_common.py index 632f899b2040b..685b34785b35f 100644 --- a/tests/aws/services/lambda_/test_lambda_common.py +++ b/tests/aws/services/lambda_/test_lambda_common.py @@ -132,7 +132,7 @@ def _invoke_with_payload(payload): "$..environment.AWS_EXECUTION_ENV", # Only rust runtime "$..environment.LD_LIBRARY_PATH", # Only rust runtime (additional /var/lang/bin) "$..environment.PATH", # Only rust runtime (additional /var/lang/bin) - "$..environment.LC_CTYPE", # Only python3.11 (part of a broken image rollout, likely rolled back) + "$..environment.LC_CTYPE", # Only missing in Python 3.12, 3.13, and 3.14 "$..environment.RUBYLIB", # Changed around 2025-06-17 # Newer Nodejs images explicitly disable a temporary performance workaround for Nodejs 20 on certain hosts: # https://nodejs.org/api/cli.html#uv_use_io_uringvalue @@ -145,10 +145,13 @@ def _invoke_with_payload(payload): "$..environment.DOTNET_VERSION", # Changed from 127.0.0.1:9001 to 169.254.100.1:9001 around 2024-11, which would require network changes "$..environment.AWS_LAMBDA_RUNTIME_API", + # Only Ruby runtimes, changed + "$..environment.GEM_PATH", ] ) @markers.aws.validated @markers.multiruntime(scenario="introspection") + @markers.requires_docker def test_introspection_invoke(self, multiruntime_lambda, snapshot, aws_client): create_function_result = multiruntime_lambda.create_function( MemorySize=1024, Environment={"Variables": {"TEST_KEY": "TEST_VAL"}} @@ -267,6 +270,7 @@ class TestLambdaCallingLocalstack: runtimes=list(set(TESTED_RUNTIMES) - set(RUNTIMES_AGGREGATED.get("provided"))), ) @markers.aws.validated + @markers.requires_docker def test_manual_endpoint_injection(self, multiruntime_lambda, tmp_path, aws_client): """Test calling SQS from Lambda using manual AWS SDK client configuration via AWS_ENDPOINT_URL. This must work for all runtimes. diff --git a/tests/aws/services/lambda_/test_lambda_common.snapshot.json b/tests/aws/services/lambda_/test_lambda_common.snapshot.json index fa2db765c511b..7e06639ce3cab 100644 --- a/tests/aws/services/lambda_/test_lambda_common.snapshot.json +++ b/tests/aws/services/lambda_/test_lambda_common.snapshot.json @@ -1,70 +1,70 @@ { "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.8]": { - "recorded-date": "31-03-2025, 12:14:56", + "recorded-date": "12-01-2026, 15:34:58", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java11]": { - "recorded-date": "31-03-2025, 12:15:23", + "recorded-date": "12-01-2026, 15:35:26", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2]": { - "recorded-date": "31-03-2025, 12:17:30", + "recorded-date": "12-01-2026, 16:40:09", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java8.al2]": { - "recorded-date": "31-03-2025, 12:15:42", + "recorded-date": "12-01-2026, 15:35:32", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.2]": { - "recorded-date": "31-03-2025, 12:15:54", + "recorded-date": "12-01-2026, 15:35:40", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.11]": { - "recorded-date": "31-03-2025, 12:14:05", + "recorded-date": "12-01-2026, 15:34:39", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java17]": { - "recorded-date": "31-03-2025, 12:15:14", + "recorded-date": "12-01-2026, 15:35:18", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs18.x]": { - "recorded-date": "31-03-2025, 12:13:11", + "recorded-date": "12-01-2026, 15:34:02", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java21]": { - "recorded-date": "31-03-2025, 12:15:05", + "recorded-date": "12-01-2026, 15:35:12", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2023]": { - "recorded-date": "31-03-2025, 12:17:17", + "recorded-date": "12-01-2026, 16:39:59", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.9]": { - "recorded-date": "31-03-2025, 12:14:39", + "recorded-date": "12-01-2026, 15:34:52", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.10]": { - "recorded-date": "31-03-2025, 12:14:20", + "recorded-date": "12-01-2026, 15:34:47", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.12]": { - "recorded-date": "31-03-2025, 12:13:57", + "recorded-date": "12-01-2026, 15:34:31", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs20.x]": { - "recorded-date": "31-03-2025, 12:12:59", + "recorded-date": "12-01-2026, 15:33:56", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet6]": { - "recorded-date": "31-03-2025, 12:16:46", + "recorded-date": "12-01-2026, 15:36:08", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs16.x]": { - "recorded-date": "31-03-2025, 12:13:31", + "recorded-date": "12-01-2026, 15:34:10", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.8]": { - "recorded-date": "17-06-2025, 09:51:26", + "recorded-date": "12-01-2026, 15:37:41", "recorded-content": { "create_function_result": { "Architectures": [ @@ -135,12 +135,12 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_TASK_ROOT": "/var/task", "LANG": "en_US.UTF-8", @@ -151,7 +151,7 @@ "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "handler.handler", "_X_AMZN_TRACE_ID": "" @@ -179,12 +179,12 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_TASK_ROOT": "/var/task", "LANG": "en_US.UTF-8", @@ -195,7 +195,7 @@ "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "handler.handler", "_X_AMZN_TRACE_ID": "" @@ -205,7 +205,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java11]": { - "recorded-date": "17-06-2025, 09:51:40", + "recorded-date": "12-01-2026, 15:38:12", "recorded-content": { "create_function_result": { "Architectures": [ @@ -344,7 +344,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2]": { - "recorded-date": "17-06-2025, 09:52:11", + "recorded-date": "13-01-2026, 14:03:50", "recorded-content": { "create_function_result": { "Architectures": [ @@ -477,7 +477,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java8.al2]": { - "recorded-date": "17-06-2025, 09:51:44", + "recorded-date": "12-01-2026, 15:38:19", "recorded-content": { "create_function_result": { "Architectures": [ @@ -616,7 +616,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.2]": { - "recorded-date": "17-06-2025, 09:51:47", + "recorded-date": "12-01-2026, 15:38:25", "recorded-content": { "create_function_result": { "Architectures": [ @@ -701,7 +701,7 @@ "LD_LIBRARY_PATH": "/var/lang/lib:/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", "PATH": "/var/lang/bin:/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "PWD": "/var/task", - "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.0.0/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", + "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.1.3/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", @@ -747,7 +747,7 @@ "LD_LIBRARY_PATH": "/var/lang/lib:/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", "PATH": "/var/lang/bin:/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "PWD": "/var/task", - "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.0.0/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", + "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.1.3/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", @@ -761,7 +761,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.11]": { - "recorded-date": "17-06-2025, 09:51:17", + "recorded-date": "12-01-2026, 15:37:29", "recorded-content": { "create_function_result": { "Architectures": [ @@ -902,7 +902,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java17]": { - "recorded-date": "17-06-2025, 09:51:36", + "recorded-date": "12-01-2026, 15:38:06", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1037,7 +1037,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs18.x]": { - "recorded-date": "17-06-2025, 09:51:05", + "recorded-date": "12-01-2026, 15:37:02", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1178,7 +1178,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java21]": { - "recorded-date": "17-06-2025, 09:51:33", + "recorded-date": "12-01-2026, 15:37:59", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1313,7 +1313,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2023]": { - "recorded-date": "17-06-2025, 09:52:07", + "recorded-date": "13-01-2026, 14:03:37", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1383,20 +1383,20 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_TASK_ROOT": "/var/task", "LANG": "en_US.UTF-8", - "LD_LIBRARY_PATH": "/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", - "PATH": "/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "function.handler", "_X_AMZN_TRACE_ID": "" @@ -1423,20 +1423,20 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_TASK_ROOT": "/var/task", "LANG": "en_US.UTF-8", - "LD_LIBRARY_PATH": "/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", - "PATH": "/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "function.handler", "_X_AMZN_TRACE_ID": "" @@ -1446,7 +1446,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.9]": { - "recorded-date": "17-06-2025, 09:51:23", + "recorded-date": "12-01-2026, 15:37:38", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1587,7 +1587,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.10]": { - "recorded-date": "17-06-2025, 09:51:20", + "recorded-date": "12-01-2026, 15:37:35", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1728,7 +1728,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.12]": { - "recorded-date": "17-06-2025, 09:51:14", + "recorded-date": "12-01-2026, 15:37:23", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1871,7 +1871,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs20.x]": { - "recorded-date": "17-06-2025, 09:51:01", + "recorded-date": "12-01-2026, 15:36:56", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2010,7 +2010,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet6]": { - "recorded-date": "17-06-2025, 09:51:57", + "recorded-date": "12-01-2026, 15:38:44", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2081,12 +2081,12 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "DOTNET_ROOT": "/var/lang/bin", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_RUNTIME_NAME": "dotnet6", @@ -2098,10 +2098,10 @@ "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "dotnet::Dotnet.Function::FunctionHandler", - "_LAMBDA_TELEMETRY_LOG_FD": "3", + "_LAMBDA_TELEMETRY_LOG_FD": "62", "_X_AMZN_TRACE_ID": "" }, "packages": [] @@ -2127,12 +2127,12 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "DOTNET_ROOT": "/var/lang/bin", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_RUNTIME_NAME": "dotnet6", @@ -2144,10 +2144,10 @@ "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "dotnet::Dotnet.Function::FunctionHandler", - "_LAMBDA_TELEMETRY_LOG_FD": "3", + "_LAMBDA_TELEMETRY_LOG_FD": "62", "_X_AMZN_TRACE_ID": "" }, "packages": [] @@ -2155,7 +2155,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs16.x]": { - "recorded-date": "17-06-2025, 09:51:08", + "recorded-date": "12-01-2026, 15:37:08", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2225,12 +2225,12 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_TASK_ROOT": "/var/task", "LANG": "en_US.UTF-8", @@ -2242,7 +2242,7 @@ "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "index.handler", "_X_AMZN_TRACE_ID": "" @@ -2269,12 +2269,12 @@ "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", "AWS_LAMBDA_LOG_STREAM_NAME": "", - "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", "AWS_REGION": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "", "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", - "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", "LAMBDA_RUNTIME_DIR": "/var/runtime", "LAMBDA_TASK_ROOT": "/var/task", "LANG": "en_US.UTF-8", @@ -2286,7 +2286,7 @@ "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", - "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", "_AWS_XRAY_DAEMON_PORT": "2000", "_HANDLER": "index.handler", "_X_AMZN_TRACE_ID": "" @@ -2296,7 +2296,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.8]": { - "recorded-date": "31-03-2025, 12:22:12", + "recorded-date": "12-01-2026, 15:40:49", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2358,7 +2358,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java11]": { - "recorded-date": "31-03-2025, 12:22:27", + "recorded-date": "12-01-2026, 15:41:17", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2420,7 +2420,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2]": { - "recorded-date": "31-03-2025, 12:26:16", + "recorded-date": "13-01-2026, 14:03:02", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2481,7 +2481,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java8.al2]": { - "recorded-date": "31-03-2025, 12:22:31", + "recorded-date": "12-01-2026, 15:41:24", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2543,7 +2543,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.2]": { - "recorded-date": "31-03-2025, 12:22:34", + "recorded-date": "12-01-2026, 15:41:30", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2605,7 +2605,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.11]": { - "recorded-date": "31-03-2025, 12:22:04", + "recorded-date": "12-01-2026, 15:40:35", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2668,7 +2668,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java17]": { - "recorded-date": "31-03-2025, 12:22:24", + "recorded-date": "12-01-2026, 15:41:13", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2730,7 +2730,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs18.x]": { - "recorded-date": "31-03-2025, 12:21:54", + "recorded-date": "12-01-2026, 15:40:11", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2792,7 +2792,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java21]": { - "recorded-date": "31-03-2025, 12:22:21", + "recorded-date": "12-01-2026, 15:41:08", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2854,7 +2854,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2023]": { - "recorded-date": "31-03-2025, 12:26:03", + "recorded-date": "13-01-2026, 14:02:49", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2915,7 +2915,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.9]": { - "recorded-date": "31-03-2025, 12:22:09", + "recorded-date": "12-01-2026, 15:40:44", "recorded-content": { "create_function_result": { "Architectures": [ @@ -2978,7 +2978,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.10]": { - "recorded-date": "31-03-2025, 12:22:07", + "recorded-date": "12-01-2026, 15:40:38", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3041,7 +3041,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.12]": { - "recorded-date": "31-03-2025, 12:22:02", + "recorded-date": "12-01-2026, 15:40:30", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3104,7 +3104,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs20.x]": { - "recorded-date": "31-03-2025, 12:21:51", + "recorded-date": "12-01-2026, 15:40:05", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3166,7 +3166,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet6]": { - "recorded-date": "31-03-2025, 12:22:49", + "recorded-date": "12-01-2026, 15:41:49", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3228,7 +3228,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs16.x]": { - "recorded-date": "31-03-2025, 12:21:57", + "recorded-date": "12-01-2026, 15:40:16", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3290,7 +3290,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.8]": { - "recorded-date": "31-03-2025, 12:26:39", + "recorded-date": "12-01-2026, 15:43:02", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3343,7 +3343,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java11]": { - "recorded-date": "31-03-2025, 12:26:57", + "recorded-date": "12-01-2026, 15:42:47", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3396,7 +3396,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java8.al2]": { - "recorded-date": "31-03-2025, 12:27:11", + "recorded-date": "12-01-2026, 15:44:02", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3449,7 +3449,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.2]": { - "recorded-date": "31-03-2025, 12:26:45", + "recorded-date": "12-01-2026, 15:43:45", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3502,7 +3502,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.11]": { - "recorded-date": "31-03-2025, 12:26:19", + "recorded-date": "12-01-2026, 15:44:14", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3555,7 +3555,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java17]": { - "recorded-date": "31-03-2025, 12:26:29", + "recorded-date": "12-01-2026, 15:44:26", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3608,7 +3608,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs18.x]": { - "recorded-date": "31-03-2025, 12:26:41", + "recorded-date": "12-01-2026, 15:43:56", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3661,7 +3661,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java21]": { - "recorded-date": "31-03-2025, 12:27:07", + "recorded-date": "12-01-2026, 15:43:08", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3714,7 +3714,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.9]": { - "recorded-date": "31-03-2025, 12:27:16", + "recorded-date": "12-01-2026, 15:43:31", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3767,7 +3767,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.10]": { - "recorded-date": "31-03-2025, 12:27:14", + "recorded-date": "12-01-2026, 15:44:20", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3820,7 +3820,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.12]": { - "recorded-date": "31-03-2025, 12:27:00", + "recorded-date": "12-01-2026, 15:44:09", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3873,7 +3873,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs20.x]": { - "recorded-date": "31-03-2025, 12:26:50", + "recorded-date": "12-01-2026, 15:42:41", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3926,7 +3926,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet6]": { - "recorded-date": "31-03-2025, 12:26:33", + "recorded-date": "12-01-2026, 15:44:35", "recorded-content": { "create_function_result": { "Architectures": [ @@ -3979,7 +3979,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs16.x]": { - "recorded-date": "31-03-2025, 12:26:25", + "recorded-date": "12-01-2026, 15:43:51", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4032,47 +4032,47 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs20.x]": { - "recorded-date": "31-03-2025, 17:43:04", + "recorded-date": "12-01-2026, 15:44:41", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs18.x]": { - "recorded-date": "31-03-2025, 17:42:22", + "recorded-date": "12-01-2026, 15:47:01", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs16.x]": { - "recorded-date": "31-03-2025, 17:44:41", + "recorded-date": "12-01-2026, 15:46:54", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.8]": { - "recorded-date": "31-03-2025, 17:43:58", + "recorded-date": "12-01-2026, 15:45:37", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.9]": { - "recorded-date": "31-03-2025, 17:44:04", + "recorded-date": "12-01-2026, 15:46:31", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.10]": { - "recorded-date": "31-03-2025, 17:42:25", + "recorded-date": "12-01-2026, 15:48:10", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.11]": { - "recorded-date": "31-03-2025, 17:43:22", + "recorded-date": "12-01-2026, 15:48:04", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.12]": { - "recorded-date": "31-03-2025, 17:44:01", + "recorded-date": "12-01-2026, 15:47:58", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.2]": { - "recorded-date": "31-03-2025, 17:43:19", + "recorded-date": "12-01-2026, 15:46:48", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet8]": { - "recorded-date": "31-03-2025, 12:17:03", + "recorded-date": "12-01-2026, 15:36:17", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet8]": { - "recorded-date": "17-06-2025, 09:52:01", + "recorded-date": "12-01-2026, 15:38:53", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4219,7 +4219,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet8]": { - "recorded-date": "31-03-2025, 12:22:56", + "recorded-date": "12-01-2026, 15:42:01", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4281,7 +4281,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet8]": { - "recorded-date": "31-03-2025, 12:27:03", + "recorded-date": "12-01-2026, 15:44:06", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4334,35 +4334,35 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java17]": { - "recorded-date": "31-03-2025, 17:42:41", + "recorded-date": "12-01-2026, 15:48:25", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java21]": { - "recorded-date": "31-03-2025, 17:43:01", + "recorded-date": "12-01-2026, 15:45:52", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java11]": { - "recorded-date": "31-03-2025, 17:44:31", + "recorded-date": "12-01-2026, 15:45:23", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java8.al2]": { - "recorded-date": "31-03-2025, 17:43:50", + "recorded-date": "12-01-2026, 15:47:38", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet6]": { - "recorded-date": "31-03-2025, 17:43:15", + "recorded-date": "12-01-2026, 15:59:26", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet8]": { - "recorded-date": "31-03-2025, 17:43:54", + "recorded-date": "12-01-2026, 15:59:12", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.3]": { - "recorded-date": "31-03-2025, 12:16:02", + "recorded-date": "12-01-2026, 15:35:49", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.3]": { - "recorded-date": "17-06-2025, 09:51:50", + "recorded-date": "12-01-2026, 15:38:31", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4447,7 +4447,7 @@ "LD_LIBRARY_PATH": "/var/lang/lib:/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", "PATH": "/var/lang/bin:/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "PWD": "/var/task", - "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.0.0/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", + "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.1.3/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", @@ -4493,7 +4493,7 @@ "LD_LIBRARY_PATH": "/var/lang/lib:/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", "PATH": "/var/lang/bin:/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "PWD": "/var/task", - "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.0.0/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", + "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.1.3/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", @@ -4507,7 +4507,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.3]": { - "recorded-date": "31-03-2025, 12:22:37", + "recorded-date": "12-01-2026, 15:41:35", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4569,7 +4569,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.3]": { - "recorded-date": "31-03-2025, 12:26:22", + "recorded-date": "12-01-2026, 15:44:29", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4622,15 +4622,15 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.3]": { - "recorded-date": "31-03-2025, 17:44:35", + "recorded-date": "12-01-2026, 15:48:33", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.13]": { - "recorded-date": "31-03-2025, 12:13:46", + "recorded-date": "12-01-2026, 15:34:23", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.13]": { - "recorded-date": "17-06-2025, 09:51:11", + "recorded-date": "12-01-2026, 15:37:17", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4773,7 +4773,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.13]": { - "recorded-date": "31-03-2025, 12:21:59", + "recorded-date": "12-01-2026, 15:40:25", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4836,7 +4836,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.13]": { - "recorded-date": "31-03-2025, 12:26:53", + "recorded-date": "12-01-2026, 15:43:37", "recorded-content": { "create_function_result": { "Architectures": [ @@ -4889,15 +4889,15 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.13]": { - "recorded-date": "31-03-2025, 17:44:38", + "recorded-date": "12-01-2026, 15:46:37", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs22.x]": { - "recorded-date": "31-03-2025, 12:12:50", + "recorded-date": "12-01-2026, 15:33:51", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs22.x]": { - "recorded-date": "17-06-2025, 09:50:58", + "recorded-date": "12-01-2026, 15:36:50", "recorded-content": { "create_function_result": { "Architectures": [ @@ -5036,7 +5036,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs22.x]": { - "recorded-date": "31-03-2025, 12:21:49", + "recorded-date": "12-01-2026, 15:40:00", "recorded-content": { "create_function_result": { "Architectures": [ @@ -5098,7 +5098,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs22.x]": { - "recorded-date": "31-03-2025, 12:26:36", + "recorded-date": "12-01-2026, 15:42:56", "recorded-content": { "create_function_result": { "Architectures": [ @@ -5151,15 +5151,15 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs22.x]": { - "recorded-date": "31-03-2025, 17:42:44", + "recorded-date": "12-01-2026, 15:45:30", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.4]": { - "recorded-date": "31-03-2025, 12:16:24", + "recorded-date": "12-01-2026, 15:35:55", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.4]": { - "recorded-date": "17-06-2025, 09:51:54", + "recorded-date": "12-01-2026, 15:38:34", "recorded-content": { "create_function_result": { "Architectures": [ @@ -5244,7 +5244,7 @@ "LD_LIBRARY_PATH": "/var/lang/lib:/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", "PATH": "/var/lang/bin:/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "PWD": "/var/task", - "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.0.0/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", + "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.1.3/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", @@ -5290,7 +5290,7 @@ "LD_LIBRARY_PATH": "/var/lang/lib:/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", "PATH": "/var/lang/bin:/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", "PWD": "/var/task", - "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.0.0/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", + "RUBYLIB": "/var/runtime/gems/aws_lambda_ric-3.1.3/lib:/var/task:/var/runtime/lib:/opt/ruby/lib", "SHLVL": "0", "TEST_KEY": "TEST_VAL", "TZ": ":UTC", @@ -5304,7 +5304,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.4]": { - "recorded-date": "31-03-2025, 12:22:40", + "recorded-date": "12-01-2026, 15:41:39", "recorded-content": { "create_function_result": { "Architectures": [ @@ -5366,7 +5366,7 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.4]": { - "recorded-date": "31-03-2025, 12:26:47", + "recorded-date": "12-01-2026, 15:42:53", "recorded-content": { "create_function_result": { "Architectures": [ @@ -5419,7 +5419,1062 @@ } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.4]": { - "recorded-date": "31-03-2025, 17:43:08", + "recorded-date": "12-01-2026, 15:45:27", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs24.x]": { + "recorded-date": "12-01-2026, 15:33:43", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java25]": { + "recorded-date": "12-01-2026, 15:35:04", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.14]": { + "recorded-date": "12-01-2026, 15:34:15", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.14]": { + "recorded-date": "12-01-2026, 15:37:12", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "TEST_KEY": "TEST_VAL" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "handler.handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "python3.14", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "invocation_result_payload": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function:", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "memory_limit_in_mb": "1024", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_python3.14", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LC_CTYPE": "C.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "PWD": "/var/task", + "PYTHONPATH": "/var/runtime", + "SHLVL": "0", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "handler.handler", + "_X_AMZN_TRACE_ID": "" + }, + "packages": [] + }, + "invocation_result_payload_qualified": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function::$LATEST", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "memory_limit_in_mb": "1024", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_python3.14", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LC_CTYPE": "C.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "PWD": "/var/task", + "PYTHONPATH": "/var/runtime", + "SHLVL": "0", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "handler.handler", + "_X_AMZN_TRACE_ID": "" + }, + "packages": [] + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java25]": { + "recorded-date": "12-01-2026, 15:37:53", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "TEST_KEY": "TEST_VAL" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "invocation_result_payload": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function:", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "memory_limit_in_mb": "1024", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_java25", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "echo.Handler", + "_LAMBDA_TELEMETRY_LOG_FD": "62" + }, + "packages": [] + }, + "invocation_result_payload_qualified": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function::$LATEST", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "memory_limit_in_mb": "1024", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_java25", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "echo.Handler", + "_LAMBDA_TELEMETRY_LOG_FD": "62" + }, + "packages": [] + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.14]": { + "recorded-date": "12-01-2026, 15:40:19", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "handler.handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "python3.14", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "error_result": { + "ExecutedVersion": "$LATEST", + "FunctionError": "Unhandled", + "Payload": { + "errorMessage": "Failed: some_error_msg", + "errorType": "Exception", + "requestId": "", + "stackTrace": "" + }, + "StatusCode": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java25]": { + "recorded-date": "12-01-2026, 15:41:02", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "error_result": { + "ExecutedVersion": "$LATEST", + "FunctionError": "Unhandled", + "Payload": { + "errorMessage": "Error: some_error_msg", + "errorType": "java.lang.RuntimeException", + "stackTrace": "" + }, + "StatusCode": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.14]": { + "recorded-date": "12-01-2026, 15:43:25", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "AWS_LAMBDA_EXEC_WRAPPER": "/var/task/environment_wrapper" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "handler.handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "python3.14", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java25]": { + "recorded-date": "12-01-2026, 15:43:19", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "AWS_LAMBDA_EXEC_WRAPPER": "/var/task/environment_wrapper" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "echo.Handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.14]": { + "recorded-date": "12-01-2026, 15:46:25", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java25]": { + "recorded-date": "12-01-2026, 15:46:21", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs24.x]": { + "recorded-date": "12-01-2026, 15:36:47", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "TEST_KEY": "TEST_VAL" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "index.handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs24.x", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "invocation_result_payload": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function:", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_nodejs24.x", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "NODE_PATH": "/opt/nodejs/node24/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules:/var/runtime:/var/task", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "PWD": "/var/task", + "SHLVL": "0", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "index.handler", + "_X_AMZN_TRACE_ID": "" + }, + "packages": [] + }, + "invocation_result_payload_qualified": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function::$LATEST", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_nodejs24.x", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "NODE_PATH": "/opt/nodejs/node24/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules:/var/runtime:/var/task", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "PWD": "/var/task", + "SHLVL": "0", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "index.handler", + "_X_AMZN_TRACE_ID": "" + }, + "packages": [] + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs24.x]": { + "recorded-date": "12-01-2026, 15:39:54", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "index.handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs24.x", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "error_result": { + "ExecutedVersion": "$LATEST", + "FunctionError": "Unhandled", + "Payload": { + "errorType": "Error", + "errorMessage": "Error: some_error_msg", + "trace": "" + }, + "StatusCode": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs24.x]": { + "recorded-date": "12-01-2026, 15:43:42", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "AWS_LAMBDA_EXEC_WRAPPER": "/var/task/environment_wrapper" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "index.handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs24.x", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs24.x]": { + "recorded-date": "12-01-2026, 15:46:43", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet10]": { + "recorded-date": "12-01-2026, 15:36:25", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet10]": { + "recorded-date": "12-01-2026, 15:39:07", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "TEST_KEY": "TEST_VAL" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "invocation_result_payload": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function:", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "memory_limit_in_mb": "1024", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_dotnet10", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "DOTNET_ROOT": "/var/lang/bin", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_RUNTIME_NAME": "dotnet10", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "PWD": "/var/task", + "SHLVL": "0", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "dotnet::Dotnet.Function::FunctionHandler", + "_LAMBDA_TELEMETRY_LOG_FD": "62", + "_X_AMZN_TRACE_ID": "" + }, + "packages": [] + }, + "invocation_result_payload_qualified": { + "ctx": { + "aws_request_id": "", + "function_name": "", + "function_version": "$LATEST", + "invoked_function_arn": "arn::lambda::111111111111:function::$LATEST", + "log_group_name": "/aws/lambda/", + "log_stream_name": "", + "memory_limit_in_mb": "1024", + "remaining_time_in_millis": "" + }, + "environment": { + "AWS_ACCESS_KEY_ID": "", + "AWS_DEFAULT_REGION": "", + "AWS_EXECUTION_ENV": "AWS_Lambda_dotnet10", + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024", + "AWS_LAMBDA_FUNCTION_NAME": "", + "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST", + "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand", + "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/", + "AWS_LAMBDA_LOG_STREAM_NAME": "", + "AWS_LAMBDA_RUNTIME_API": "169.254.100.1:9001", + "AWS_REGION": "", + "AWS_SECRET_ACCESS_KEY": "", + "AWS_SESSION_TOKEN": "", + "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR", + "AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1:2000", + "DOTNET_ROOT": "/var/lang/bin", + "LAMBDA_RUNTIME_DIR": "/var/runtime", + "LAMBDA_RUNTIME_NAME": "dotnet10", + "LAMBDA_TASK_ROOT": "/var/task", + "LANG": "en_US.UTF-8", + "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib", + "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin", + "PWD": "/var/task", + "SHLVL": "0", + "TEST_KEY": "TEST_VAL", + "TZ": ":UTC", + "_AWS_XRAY_DAEMON_ADDRESS": "169.254.100.1", + "_AWS_XRAY_DAEMON_PORT": "2000", + "_HANDLER": "dotnet::Dotnet.Function::FunctionHandler", + "_LAMBDA_TELEMETRY_LOG_FD": "62", + "_X_AMZN_TRACE_ID": "" + }, + "packages": [] + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet10]": { + "recorded-date": "12-01-2026, 15:42:13", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "error_result": { + "ExecutedVersion": "$LATEST", + "FunctionError": "Unhandled", + "Payload": { + "errorType": "Exception", + "errorMessage": "Error: some_error_msg", + "stackTrace": "" + }, + "StatusCode": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet10]": { + "recorded-date": "12-01-2026, 15:43:14", + "recorded-content": { + "create_function_result": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": { + "AWS_LAMBDA_EXEC_WRAPPER": "/var/task/environment_wrapper" + } + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "dotnet::Dotnet.Function::FunctionHandler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 1024, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "dotnet10", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 3, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet10]": { + "recorded-date": "12-01-2026, 15:58:58", "recorded-content": {} } } diff --git a/tests/aws/services/lambda_/test_lambda_common.validation.json b/tests/aws/services/lambda_/test_lambda_common.validation.json index f17afd9193b9c..f5dd662ec0f68 100644 --- a/tests/aws/services/lambda_/test_lambda_common.validation.json +++ b/tests/aws/services/lambda_/test_lambda_common.validation.json @@ -1,431 +1,1091 @@ { + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet10]": { + "last_validated_date": "2026-01-12T15:58:58+00:00", + "durations_in_seconds": { + "setup": 7.03, + "call": 7.98, + "teardown": 0.71, + "total": 15.72 + } + }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet6]": { - "last_validated_date": "2025-03-31T17:43:14+00:00" + "last_validated_date": "2026-01-12T15:59:27+00:00", + "durations_in_seconds": { + "setup": 5.57, + "call": 7.45, + "teardown": 1.99, + "total": 15.01 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet8]": { - "last_validated_date": "2025-03-31T17:43:54+00:00" + "last_validated_date": "2026-01-12T15:59:12+00:00", + "durations_in_seconds": { + "setup": 6.03, + "call": 7.24, + "teardown": 0.7, + "total": 13.97 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java11]": { - "last_validated_date": "2025-03-31T17:44:30+00:00" + "last_validated_date": "2026-01-12T15:45:23+00:00", + "durations_in_seconds": { + "setup": 18.52, + "call": 22.14, + "teardown": 0.77, + "total": 41.43 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java17]": { - "last_validated_date": "2025-03-31T17:42:41+00:00" + "last_validated_date": "2026-01-12T15:48:25+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 14.25, + "teardown": 0.78, + "total": 15.04 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java21]": { - "last_validated_date": "2025-03-31T17:43:00+00:00" + "last_validated_date": "2026-01-12T15:45:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.37, + "teardown": 0.8, + "total": 15.17 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java25]": { + "last_validated_date": "2026-01-12T15:46:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.15, + "teardown": 0.7, + "total": 14.85 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java8.al2]": { - "last_validated_date": "2025-03-31T17:43:49+00:00" + "last_validated_date": "2026-01-12T15:47:38+00:00", + "durations_in_seconds": { + "setup": 13.48, + "call": 22.69, + "teardown": 0.72, + "total": 36.89 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs16.x]": { - "last_validated_date": "2025-03-31T17:44:41+00:00" + "last_validated_date": "2026-01-12T15:46:54+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 5.72, + "teardown": 0.8, + "total": 6.56 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs18.x]": { - "last_validated_date": "2025-03-31T17:42:21+00:00" + "last_validated_date": "2026-01-12T15:47:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.64, + "teardown": 0.71, + "total": 6.35 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs20.x]": { - "last_validated_date": "2025-03-31T17:43:04+00:00" + "last_validated_date": "2026-01-12T15:44:41+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 5.34, + "teardown": 0.8, + "total": 6.18 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs22.x]": { - "last_validated_date": "2025-03-31T17:42:44+00:00" + "last_validated_date": "2026-01-12T15:45:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.83, + "teardown": 0.68, + "total": 3.51 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs24.x]": { + "last_validated_date": "2026-01-12T15:46:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.29, + "teardown": 0.82, + "total": 6.11 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.10]": { - "last_validated_date": "2025-03-31T17:42:24+00:00" + "last_validated_date": "2026-01-12T15:48:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.44, + "teardown": 0.71, + "total": 6.15 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.11]": { - "last_validated_date": "2025-03-31T17:43:21+00:00" + "last_validated_date": "2026-01-12T15:48:04+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.43, + "teardown": 0.7, + "total": 6.14 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.12]": { - "last_validated_date": "2025-03-31T17:44:00+00:00" + "last_validated_date": "2026-01-12T15:47:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.55, + "teardown": 0.68, + "total": 6.23 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.13]": { - "last_validated_date": "2025-03-31T17:44:37+00:00" + "last_validated_date": "2026-01-12T15:46:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.45, + "teardown": 0.7, + "total": 6.15 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.14]": { + "last_validated_date": "2026-01-12T15:46:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.06, + "teardown": 0.79, + "total": 3.85 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.8]": { - "last_validated_date": "2025-03-31T17:43:57+00:00" + "last_validated_date": "2026-01-12T15:45:37+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 5.51, + "teardown": 0.81, + "total": 6.36 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.9]": { - "last_validated_date": "2025-03-31T17:44:04+00:00" + "last_validated_date": "2026-01-12T15:46:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.45, + "teardown": 0.8, + "total": 6.25 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.2]": { - "last_validated_date": "2025-03-31T17:43:18+00:00" + "last_validated_date": "2026-01-12T15:46:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.97, + "teardown": 0.74, + "total": 4.71 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.3]": { - "last_validated_date": "2025-03-31T17:44:34+00:00" + "last_validated_date": "2026-01-12T15:48:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.08, + "teardown": 0.71, + "total": 7.79 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.4]": { - "last_validated_date": "2025-03-31T17:43:07+00:00" + "last_validated_date": "2026-01-12T15:45:27+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 3.63, + "teardown": 0.72, + "total": 4.39 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet10]": { + "last_validated_date": "2026-01-12T15:36:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.92, + "teardown": 0.36, + "total": 8.28 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet6]": { - "last_validated_date": "2025-03-31T12:16:46+00:00" + "last_validated_date": "2026-01-12T15:36:08+00:00", + "durations_in_seconds": { + "setup": 5.52, + "call": 7.92, + "teardown": 0.43, + "total": 13.87 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet8]": { - "last_validated_date": "2025-03-31T12:17:03+00:00" + "last_validated_date": "2026-01-12T15:36:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.8, + "teardown": 0.38, + "total": 8.18 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java11]": { - "last_validated_date": "2025-03-31T12:15:22+00:00" + "last_validated_date": "2026-01-12T15:35:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.65, + "teardown": 0.34, + "total": 7.99 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java17]": { - "last_validated_date": "2025-03-31T12:15:13+00:00" + "last_validated_date": "2026-01-12T15:35:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.28, + "teardown": 0.39, + "total": 5.67 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java21]": { - "last_validated_date": "2025-03-31T12:15:05+00:00" + "last_validated_date": "2026-01-12T15:35:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.83, + "teardown": 0.36, + "total": 8.19 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java25]": { + "last_validated_date": "2026-01-12T15:35:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.26, + "teardown": 0.36, + "total": 5.62 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java8.al2]": { - "last_validated_date": "2025-03-31T12:15:42+00:00" + "last_validated_date": "2026-01-12T15:35:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.36, + "teardown": 0.38, + "total": 5.74 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs16.x]": { - "last_validated_date": "2025-03-31T12:13:31+00:00" + "last_validated_date": "2026-01-12T15:34:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.58, + "teardown": 0.38, + "total": 7.96 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs18.x]": { - "last_validated_date": "2025-03-31T12:13:11+00:00" + "last_validated_date": "2026-01-12T15:34:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.05, + "teardown": 0.36, + "total": 5.41 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs20.x]": { - "last_validated_date": "2025-03-31T12:12:59+00:00" + "last_validated_date": "2026-01-12T15:33:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.03, + "teardown": 0.36, + "total": 5.39 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs22.x]": { - "last_validated_date": "2025-03-31T12:12:50+00:00" + "last_validated_date": "2026-01-12T15:33:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.6, + "teardown": 0.36, + "total": 7.96 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs24.x]": { + "last_validated_date": "2026-01-12T15:33:43+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 8.99, + "teardown": 0.37, + "total": 9.4 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2023]": { - "last_validated_date": "2025-03-31T12:17:17+00:00" + "last_validated_date": "2026-01-12T16:39:59+00:00", + "durations_in_seconds": { + "setup": 11.96, + "call": 8.92, + "teardown": 0.39, + "total": 21.27 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2]": { - "last_validated_date": "2025-03-31T12:17:30+00:00" + "last_validated_date": "2026-01-12T16:40:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.86, + "teardown": 1.08, + "total": 10.94 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.10]": { - "last_validated_date": "2025-03-31T12:14:20+00:00" + "last_validated_date": "2026-01-12T15:34:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.48, + "teardown": 0.36, + "total": 7.84 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.11]": { - "last_validated_date": "2025-03-31T12:14:05+00:00" + "last_validated_date": "2026-01-12T15:34:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.39, + "teardown": 0.39, + "total": 7.78 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.12]": { - "last_validated_date": "2025-03-31T12:13:57+00:00" + "last_validated_date": "2026-01-12T15:34:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.64, + "teardown": 0.4, + "total": 8.04 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.13]": { - "last_validated_date": "2025-03-31T12:13:45+00:00" + "last_validated_date": "2026-01-12T15:34:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.53, + "teardown": 0.35, + "total": 7.88 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.14]": { + "last_validated_date": "2026-01-12T15:34:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.3, + "teardown": 0.37, + "total": 5.67 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.8]": { - "last_validated_date": "2025-03-31T12:14:56+00:00" + "last_validated_date": "2026-01-12T15:34:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.55, + "teardown": 0.46, + "total": 6.01 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.9]": { - "last_validated_date": "2025-03-31T12:14:39+00:00" + "last_validated_date": "2026-01-12T15:34:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.06, + "teardown": 0.35, + "total": 5.41 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.2]": { - "last_validated_date": "2025-03-31T12:15:53+00:00" + "last_validated_date": "2026-01-12T15:35:40+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 7.75, + "teardown": 0.37, + "total": 8.16 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.3]": { - "last_validated_date": "2025-03-31T12:16:02+00:00" + "last_validated_date": "2026-01-12T15:35:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.06, + "teardown": 0.36, + "total": 9.42 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.4]": { - "last_validated_date": "2025-03-31T12:16:24+00:00" + "last_validated_date": "2026-01-12T15:35:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.03, + "teardown": 0.41, + "total": 5.44 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet10]": { + "last_validated_date": "2026-01-12T15:39:07+00:00", + "durations_in_seconds": { + "setup": 7.8, + "call": 5.39, + "teardown": 0.35, + "total": 13.54 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet6]": { - "last_validated_date": "2025-06-17T09:54:42+00:00", + "last_validated_date": "2026-01-12T15:38:44+00:00", "durations_in_seconds": { - "setup": 0.0, - "call": 2.97, - "teardown": 0.37, - "total": 3.34 + "setup": 5.98, + "call": 3.2, + "teardown": 0.36, + "total": 9.54 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet8]": { - "last_validated_date": "2025-06-17T09:54:45+00:00", + "last_validated_date": "2026-01-12T15:38:53+00:00", "durations_in_seconds": { - "setup": 0.0, - "call": 3.14, - "teardown": 0.35, - "total": 3.49 + "setup": 5.89, + "call": 3.19, + "teardown": 0.39, + "total": 9.47 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java11]": { - "last_validated_date": "2025-06-17T09:54:24+00:00", + "last_validated_date": "2026-01-12T15:38:12+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 3.48, - "teardown": 0.38, - "total": 3.86 + "call": 5.99, + "teardown": 0.37, + "total": 6.36 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java17]": { - "last_validated_date": "2025-06-17T09:54:20+00:00", + "last_validated_date": "2026-01-12T15:38:06+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 3.21, - "teardown": 0.4, - "total": 3.61 + "call": 5.92, + "teardown": 0.38, + "total": 6.3 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java21]": { - "last_validated_date": "2025-06-17T09:54:17+00:00", + "last_validated_date": "2026-01-12T15:37:59+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 3.45, - "teardown": 0.36, - "total": 3.81 + "call": 5.86, + "teardown": 0.4, + "total": 6.26 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java25]": { + "last_validated_date": "2026-01-12T15:37:53+00:00", + "durations_in_seconds": { + "setup": 8.32, + "call": 3.24, + "teardown": 0.39, + "total": 11.95 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java8.al2]": { - "last_validated_date": "2025-06-17T09:54:28+00:00", + "last_validated_date": "2026-01-12T15:38:19+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 3.87, - "teardown": 0.37, - "total": 4.24 + "call": 6.3, + "teardown": 0.35, + "total": 6.65 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs16.x]": { - "last_validated_date": "2025-06-17T09:53:53+00:00", + "last_validated_date": "2026-01-12T15:37:08+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.92, - "teardown": 0.34, - "total": 3.26 + "call": 5.56, + "teardown": 0.39, + "total": 5.95 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs18.x]": { - "last_validated_date": "2025-06-17T09:53:50+00:00", + "last_validated_date": "2026-01-12T15:37:02+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.9, - "teardown": 0.34, - "total": 3.24 + "call": 5.56, + "teardown": 0.4, + "total": 5.96 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs20.x]": { - "last_validated_date": "2025-06-17T09:53:47+00:00", + "last_validated_date": "2026-01-12T15:36:56+00:00", "durations_in_seconds": { - "setup": 0.0, - "call": 2.91, - "teardown": 0.37, - "total": 3.28 + "setup": 0.01, + "call": 5.45, + "teardown": 0.34, + "total": 5.8 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs22.x]": { - "last_validated_date": "2025-06-17T09:53:44+00:00", + "last_validated_date": "2026-01-12T15:36:50+00:00", "durations_in_seconds": { - "setup": 11.98, - "call": 3.16, - "teardown": 0.35, - "total": 15.49 + "setup": 0.0, + "call": 2.86, + "teardown": 0.4, + "total": 3.26 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs24.x]": { + "last_validated_date": "2026-01-12T15:36:47+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 2.92, + "teardown": 0.41, + "total": 3.37 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2023]": { - "last_validated_date": "2025-06-17T09:54:51+00:00", + "last_validated_date": "2026-01-13T14:03:37+00:00", "durations_in_seconds": { - "setup": 0.0, - "call": 5.57, - "teardown": 0.4, - "total": 5.97 + "setup": 12.11, + "call": 15.08, + "teardown": 0.33, + "total": 27.52 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2]": { - "last_validated_date": "2025-06-17T09:54:58+00:00", + "last_validated_date": "2026-01-13T14:03:51+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 5.13, - "teardown": 1.61, - "total": 6.74 + "call": 12.86, + "teardown": 1.22, + "total": 14.08 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.10]": { - "last_validated_date": "2025-06-17T09:54:06+00:00", + "last_validated_date": "2026-01-12T15:37:35+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.9, - "teardown": 0.35, - "total": 3.25 + "call": 5.45, + "teardown": 0.4, + "total": 5.85 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.11]": { - "last_validated_date": "2025-06-17T09:54:03+00:00", + "last_validated_date": "2026-01-12T15:37:29+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.67, - "teardown": 0.42, - "total": 3.09 + "call": 5.26, + "teardown": 0.37, + "total": 5.63 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.12]": { - "last_validated_date": "2025-06-17T09:54:00+00:00", + "last_validated_date": "2026-01-12T15:37:23+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.83, - "teardown": 0.38, - "total": 3.21 + "call": 5.38, + "teardown": 0.37, + "total": 5.75 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.13]": { - "last_validated_date": "2025-06-17T09:53:57+00:00", + "last_validated_date": "2026-01-12T15:37:18+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.83, - "teardown": 0.35, - "total": 3.18 + "call": 5.47, + "teardown": 0.37, + "total": 5.84 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.14]": { + "last_validated_date": "2026-01-12T15:37:12+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 3.05, + "teardown": 0.36, + "total": 3.45 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.8]": { - "last_validated_date": "2025-06-17T09:54:13+00:00", + "last_validated_date": "2026-01-12T15:37:41+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.97, - "teardown": 0.34, - "total": 3.31 + "call": 2.83, + "teardown": 0.37, + "total": 3.2 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.9]": { - "last_validated_date": "2025-06-17T09:54:09+00:00", + "last_validated_date": "2026-01-12T15:37:38+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.82, - "teardown": 0.36, - "total": 3.18 + "call": 2.74, + "teardown": 0.37, + "total": 3.11 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.2]": { - "last_validated_date": "2025-06-17T09:54:31+00:00", + "last_validated_date": "2026-01-12T15:38:25+00:00", "durations_in_seconds": { - "setup": 0.0, - "call": 2.85, - "teardown": 0.36, - "total": 3.21 + "setup": 0.03, + "call": 5.62, + "teardown": 0.37, + "total": 6.02 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.3]": { - "last_validated_date": "2025-06-17T09:54:35+00:00", + "last_validated_date": "2026-01-12T15:38:31+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 3.02, - "teardown": 0.36, - "total": 3.38 + "call": 5.57, + "teardown": 0.43, + "total": 6.0 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.4]": { - "last_validated_date": "2025-06-17T09:54:38+00:00", + "last_validated_date": "2026-01-12T15:38:34+00:00", "durations_in_seconds": { "setup": 0.0, - "call": 2.97, - "teardown": 0.35, - "total": 3.32 + "call": 2.99, + "teardown": 0.39, + "total": 3.38 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet10]": { + "last_validated_date": "2026-01-12T15:43:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.05, + "teardown": 0.4, + "total": 6.45 } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet6]": { - "last_validated_date": "2025-03-31T12:26:32+00:00" + "last_validated_date": "2026-01-12T15:44:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.52, + "teardown": 0.35, + "total": 5.87 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet8]": { - "last_validated_date": "2025-03-31T12:27:03+00:00" + "last_validated_date": "2026-01-12T15:44:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.97, + "teardown": 0.38, + "total": 3.35 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java11]": { - "last_validated_date": "2025-03-31T12:26:57+00:00" + "last_validated_date": "2026-01-12T15:42:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.79, + "teardown": 0.36, + "total": 6.15 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java17]": { - "last_validated_date": "2025-03-31T12:26:29+00:00" + "last_validated_date": "2026-01-12T15:44:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.62, + "teardown": 0.48, + "total": 6.1 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java21]": { - "last_validated_date": "2025-03-31T12:27:06+00:00" + "last_validated_date": "2026-01-12T15:43:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.94, + "teardown": 0.49, + "total": 6.43 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java25]": { + "last_validated_date": "2026-01-12T15:43:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.53, + "teardown": 0.38, + "total": 4.91 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java8.al2]": { - "last_validated_date": "2025-03-31T12:27:10+00:00" + "last_validated_date": "2026-01-12T15:44:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.22, + "teardown": 0.36, + "total": 6.58 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs16.x]": { - "last_validated_date": "2025-03-31T12:26:24+00:00" + "last_validated_date": "2026-01-12T15:43:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.39, + "teardown": 0.37, + "total": 5.76 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs18.x]": { - "last_validated_date": "2025-03-31T12:26:41+00:00" + "last_validated_date": "2026-01-12T15:43:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.22, + "teardown": 0.35, + "total": 4.57 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs20.x]": { - "last_validated_date": "2025-03-31T12:26:50+00:00" + "last_validated_date": "2026-01-12T15:42:41+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 5.25, + "teardown": 0.36, + "total": 5.62 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs22.x]": { - "last_validated_date": "2025-03-31T12:26:35+00:00" + "last_validated_date": "2026-01-12T15:42:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.62, + "teardown": 0.35, + "total": 2.97 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs24.x]": { + "last_validated_date": "2026-01-12T15:43:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.24, + "teardown": 0.35, + "total": 5.59 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.10]": { - "last_validated_date": "2025-03-31T12:27:13+00:00" + "last_validated_date": "2026-01-12T15:44:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.11, + "teardown": 0.37, + "total": 5.48 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.11]": { - "last_validated_date": "2025-03-31T12:26:18+00:00" + "last_validated_date": "2026-01-12T15:44:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.12, + "teardown": 0.36, + "total": 5.48 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.12]": { - "last_validated_date": "2025-03-31T12:27:00+00:00" + "last_validated_date": "2026-01-12T15:44:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.62, + "teardown": 0.36, + "total": 2.98 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.13]": { - "last_validated_date": "2025-03-31T12:26:53+00:00" + "last_validated_date": "2026-01-12T15:43:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.4, + "teardown": 0.38, + "total": 5.78 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.14]": { + "last_validated_date": "2026-01-12T15:43:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.35, + "teardown": 0.4, + "total": 5.75 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.8]": { - "last_validated_date": "2025-03-31T12:26:38+00:00" + "last_validated_date": "2026-01-12T15:43:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.44, + "teardown": 0.38, + "total": 5.82 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.9]": { - "last_validated_date": "2025-03-31T12:27:16+00:00" + "last_validated_date": "2026-01-12T15:43:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.29, + "teardown": 0.36, + "total": 5.65 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.2]": { - "last_validated_date": "2025-03-31T12:26:44+00:00" + "last_validated_date": "2026-01-12T15:43:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.82, + "teardown": 0.35, + "total": 3.17 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.3]": { - "last_validated_date": "2025-03-31T12:26:21+00:00" + "last_validated_date": "2026-01-12T15:44:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.91, + "teardown": 0.36, + "total": 3.27 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.4]": { - "last_validated_date": "2025-03-31T12:26:47+00:00" + "last_validated_date": "2026-01-12T15:42:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.49, + "teardown": 0.36, + "total": 5.85 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet10]": { + "last_validated_date": "2026-01-12T15:42:13+00:00", + "durations_in_seconds": { + "setup": 5.87, + "call": 5.9, + "teardown": 0.35, + "total": 12.12 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet6]": { - "last_validated_date": "2025-03-31T12:22:48+00:00" + "last_validated_date": "2026-01-12T15:41:49+00:00", + "durations_in_seconds": { + "setup": 4.82, + "call": 5.45, + "teardown": 0.35, + "total": 10.62 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet8]": { - "last_validated_date": "2025-03-31T12:22:56+00:00" + "last_validated_date": "2026-01-12T15:42:01+00:00", + "durations_in_seconds": { + "setup": 5.69, + "call": 5.21, + "teardown": 0.44, + "total": 11.34 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java11]": { - "last_validated_date": "2025-03-31T12:22:27+00:00" + "last_validated_date": "2026-01-12T15:41:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.42, + "teardown": 0.36, + "total": 3.78 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java17]": { - "last_validated_date": "2025-03-31T12:22:23+00:00" + "last_validated_date": "2026-01-12T15:41:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.44, + "teardown": 0.35, + "total": 5.79 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java21]": { - "last_validated_date": "2025-03-31T12:22:21+00:00" + "last_validated_date": "2026-01-12T15:41:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.43, + "teardown": 0.41, + "total": 5.84 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java25]": { + "last_validated_date": "2026-01-12T15:41:02+00:00", + "durations_in_seconds": { + "setup": 6.59, + "call": 5.86, + "teardown": 0.34, + "total": 12.79 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java8.al2]": { - "last_validated_date": "2025-03-31T12:22:31+00:00" + "last_validated_date": "2026-01-12T15:41:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.09, + "teardown": 0.49, + "total": 6.58 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs16.x]": { - "last_validated_date": "2025-03-31T12:21:56+00:00" + "last_validated_date": "2026-01-12T15:40:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.05, + "teardown": 0.38, + "total": 5.43 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs18.x]": { - "last_validated_date": "2025-03-31T12:21:54+00:00" + "last_validated_date": "2026-01-12T15:40:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.15, + "teardown": 0.35, + "total": 5.5 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs20.x]": { - "last_validated_date": "2025-03-31T12:21:51+00:00" + "last_validated_date": "2026-01-12T15:40:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.06, + "teardown": 0.35, + "total": 5.41 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs22.x]": { - "last_validated_date": "2025-03-31T12:21:48+00:00" + "last_validated_date": "2026-01-12T15:40:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.08, + "teardown": 0.37, + "total": 5.45 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs24.x]": { + "last_validated_date": "2026-01-12T15:39:54+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 5.16, + "teardown": 0.37, + "total": 5.57 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2023]": { - "last_validated_date": "2025-03-31T12:26:02+00:00" + "last_validated_date": "2026-01-13T14:02:49+00:00", + "durations_in_seconds": { + "setup": 12.22, + "call": 13.39, + "teardown": 0.37, + "total": 25.98 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2]": { - "last_validated_date": "2025-03-31T12:26:16+00:00" + "last_validated_date": "2026-01-13T14:03:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.72, + "teardown": 1.19, + "total": 13.91 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.10]": { - "last_validated_date": "2025-03-31T12:22:07+00:00" + "last_validated_date": "2026-01-12T15:40:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.48, + "teardown": 0.49, + "total": 2.97 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.11]": { - "last_validated_date": "2025-03-31T12:22:04+00:00" + "last_validated_date": "2026-01-12T15:40:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.97, + "teardown": 0.37, + "total": 5.34 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.12]": { - "last_validated_date": "2025-03-31T12:22:02+00:00" + "last_validated_date": "2026-01-12T15:40:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.03, + "teardown": 0.36, + "total": 5.39 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.13]": { - "last_validated_date": "2025-03-31T12:21:59+00:00" + "last_validated_date": "2026-01-12T15:40:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.96, + "teardown": 0.37, + "total": 5.33 + } + }, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.14]": { + "last_validated_date": "2026-01-12T15:40:19+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 2.51, + "teardown": 0.36, + "total": 2.91 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.8]": { - "last_validated_date": "2025-03-31T12:22:12+00:00" + "last_validated_date": "2026-01-12T15:40:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.05, + "teardown": 0.34, + "total": 5.39 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.9]": { - "last_validated_date": "2025-03-31T12:22:09+00:00" + "last_validated_date": "2026-01-12T15:40:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.98, + "teardown": 0.36, + "total": 5.34 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.2]": { - "last_validated_date": "2025-03-31T12:22:34+00:00" + "last_validated_date": "2026-01-12T15:41:30+00:00", + "durations_in_seconds": { + "setup": 0.03, + "call": 5.32, + "teardown": 0.35, + "total": 5.7 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.3]": { - "last_validated_date": "2025-03-31T12:22:37+00:00" + "last_validated_date": "2026-01-12T15:41:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.48, + "teardown": 0.38, + "total": 5.86 + } }, "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.4]": { - "last_validated_date": "2025-03-31T12:22:40+00:00" + "last_validated_date": "2026-01-12T15:41:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.87, + "teardown": 0.39, + "total": 3.26 + } } } diff --git a/tests/aws/services/lambda_/test_lambda_destinations.py b/tests/aws/services/lambda_/test_lambda_destinations.py index 0e6b809b6e866..cff5c908cc8be 100644 --- a/tests/aws/services/lambda_/test_lambda_destinations.py +++ b/tests/aws/services/lambda_/test_lambda_destinations.py @@ -238,8 +238,11 @@ def receive_message(): receive_message_result = retry(receive_message, retries=120, sleep=3) snapshot.match("receive_message_result", receive_message_result) - @markers.snapshot.skip_snapshot_verify(paths=["$..Body.requestContext.functionArn"]) + @markers.snapshot.skip_snapshot_verify( + paths=["$..Body.requestContext.functionArn", "$..Messages..Attributes.AWSTraceHeader"] + ) @markers.aws.validated + @markers.requires_in_process def test_retries( self, snapshot, @@ -263,6 +266,7 @@ def test_retries( "MD5OfBody", value_replacement="", reference_replacement=False ) ) + snapshot.add_transformer(snapshot.transform.key_value("AWSTraceHeader")) test_delay_base = 60 if not is_aws_cloud(): @@ -356,7 +360,13 @@ def msg_in_queue(): assert len(set(request_ids)) == 1 # all 3 are equal @markers.snapshot.skip_snapshot_verify( - paths=["$..SenderId", "$..Body.requestContext.functionArn"] + paths=[ + # TODO since LS SQS uses the account id as SenderId, it is the same in both events, while it differs on AWS + "$..SenderId", + # TODO LS SQS uses the account id as SenderId, which in turn get mixed up with the account id in arn + "$..Body.requestContext.functionArn", + "$..Attributes.AWSTraceHeader", + ] ) @markers.aws.validated def test_maxeventage( @@ -383,6 +393,7 @@ def test_maxeventage( "MD5OfBody", value_replacement="", reference_replacement=False ) ) + snapshot.add_transformer(snapshot.transform.key_value("AWSTraceHeader")) queue_name = f"destination-queue-{short_uid()}" fn_name = f"retry-fn-{short_uid()}" diff --git a/tests/aws/services/lambda_/test_lambda_destinations.snapshot.json b/tests/aws/services/lambda_/test_lambda_destinations.snapshot.json index 0775ff1fc5f4b..c7f94c277a0b4 100644 --- a/tests/aws/services/lambda_/test_lambda_destinations.snapshot.json +++ b/tests/aws/services/lambda_/test_lambda_destinations.snapshot.json @@ -62,7 +62,7 @@ } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload0]": { - "recorded-date": "21-03-2024, 12:26:43", + "recorded-date": "24-11-2025, 23:11:08", "recorded-content": { "put_function_event_invoke_config": { "DestinationConfig": { @@ -125,7 +125,7 @@ } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload1]": { - "recorded-date": "21-03-2024, 12:26:48", + "recorded-date": "24-11-2025, 23:11:12", "recorded-content": { "put_function_event_invoke_config": { "DestinationConfig": { @@ -186,7 +186,7 @@ } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDLQ::test_dead_letter_queue": { - "recorded-date": "17-06-2024, 11:49:58", + "recorded-date": "24-11-2025, 23:11:05", "recorded-content": { "create_lambda_with_dlq": { "CreateEventSourceMappingResponse": null, @@ -344,22 +344,23 @@ "START RequestId: Version: $LATEST", "Lambda log message - print function", "[INFO]\tdate\t\tLambda log message - logging module", - "LAMBDA_WARNING: Unhandled exception. The most likely cause is an issue in the function code. However, in rare cases, a Lambda runtime update can cause unexpected function behavior. For functions using managed runtimes, runtime updates can be triggered by a function change, or can be applied automatically. To determine if the runtime has been updated, check the runtime version in the INIT_START log entry. If this error correlates with a change in the runtime version, you may be able to mitigate this error by temporarily rolling back to the previous runtime version. For more information, see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html", "[ERROR] Exception: Test exception (this is intentional)", "Traceback (most recent call last):", "\u00a0\u00a0File \"/var/task/handler.py\", line 55, in handler", - "\u00a0\u00a0\u00a0\u00a0raise Exception(\"Test exception (this is intentional)\")END RequestId: " + "\u00a0\u00a0\u00a0\u00a0raise Exception(\"Test exception (this is intentional)\")", + "END RequestId: " ] } } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_retries": { - "recorded-date": "22-03-2023, 18:50:18", + "recorded-date": "25-11-2025, 22:18:25", "recorded-content": { "queue_destination_payload": { "Messages": [ { "Attributes": { + "AWSTraceHeader": "", "ApproximateFirstReceiveTimestamp": "timestamp", "ApproximateReceiveCount": "2", "SenderId": "", @@ -404,10 +405,11 @@ } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_maxeventage": { - "recorded-date": "22-03-2023, 18:52:05", + "recorded-date": "27-11-2025, 02:28:57", "recorded-content": { "no_retry_failure_message": { "Attributes": { + "AWSTraceHeader": "", "ApproximateFirstReceiveTimestamp": "timestamp", "ApproximateReceiveCount": "1", "SenderId": "", @@ -445,6 +447,7 @@ }, "single_retry_failure_message": { "Attributes": { + "AWSTraceHeader": "", "ApproximateFirstReceiveTimestamp": "timestamp", "ApproximateReceiveCount": "1", "SenderId": "", @@ -483,7 +486,7 @@ } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_lambda_destination_default_retries": { - "recorded-date": "21-03-2024, 12:24:09", + "recorded-date": "24-11-2025, 23:14:10", "recorded-content": { "put_function_event_invoke_config": { "DestinationConfig": { @@ -543,7 +546,7 @@ } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationEventbridge::test_invoke_lambda_eventbridge": { - "recorded-date": "19-11-2024, 08:49:47", + "recorded-date": "24-11-2025, 23:21:20", "recorded-content": { "lambda_destination_event_bus_success": { "account": "111111111111", diff --git a/tests/aws/services/lambda_/test_lambda_destinations.validation.json b/tests/aws/services/lambda_/test_lambda_destinations.validation.json index 01ad2cef6650d..d4e405d4aff96 100644 --- a/tests/aws/services/lambda_/test_lambda_destinations.validation.json +++ b/tests/aws/services/lambda_/test_lambda_destinations.validation.json @@ -1,23 +1,65 @@ { "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDLQ::test_dead_letter_queue": { - "last_validated_date": "2024-06-17T11:49:58+00:00" + "last_validated_date": "2025-11-24T23:11:05+00:00", + "durations_in_seconds": { + "setup": 11.02, + "call": 176.18, + "teardown": 0.76, + "total": 187.96 + } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationEventbridge::test_invoke_lambda_eventbridge": { - "last_validated_date": "2024-11-19T08:54:21+00:00" + "last_validated_date": "2025-11-24T23:22:15+00:00", + "durations_in_seconds": { + "setup": 100.97, + "call": 21.64, + "teardown": 55.12, + "total": 177.73 + } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload0]": { - "last_validated_date": "2024-03-21T12:26:43+00:00" + "last_validated_date": "2025-11-24T23:11:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.93, + "teardown": 0.6, + "total": 3.53 + } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload1]": { - "last_validated_date": "2024-03-21T12:26:48+00:00" + "last_validated_date": "2025-11-24T23:11:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.09, + "teardown": 0.74, + "total": 3.83 + } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_lambda_destination_default_retries": { - "last_validated_date": "2024-03-21T12:24:09+00:00" + "last_validated_date": "2025-11-24T23:14:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 177.39, + "teardown": 0.87, + "total": 178.26 + } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_maxeventage": { - "last_validated_date": "2023-03-22T17:52:05+00:00" + "last_validated_date": "2025-11-27T02:28:58+00:00", + "durations_in_seconds": { + "setup": 11.21, + "call": 72.08, + "teardown": 1.45, + "total": 84.74 + } }, "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_retries": { - "last_validated_date": "2023-03-22T17:50:18+00:00" + "last_validated_date": "2025-11-25T22:18:25+00:00", + "durations_in_seconds": { + "setup": 10.92, + "call": 218.7, + "teardown": 1.26, + "total": 230.88 + } } } diff --git a/tests/aws/services/lambda_/test_lambda_developer_tools.py b/tests/aws/services/lambda_/test_lambda_developer_tools.py index cd637ef2c26e6..102daf9d23a2e 100644 --- a/tests/aws/services/lambda_/test_lambda_developer_tools.py +++ b/tests/aws/services/lambda_/test_lambda_developer_tools.py @@ -1,8 +1,10 @@ import json import os import time +from pathlib import Path import pytest +from botocore.exceptions import ClientError from localstack import config from localstack.aws.api.lambda_ import Runtime @@ -34,6 +36,7 @@ class TestHotReloading: ids=["nodejs20.x", "python3.12"], ) @markers.aws.only_localstack + @markers.requires_in_process def test_hot_reloading( self, create_lambda_function_aws, @@ -55,17 +58,25 @@ def test_hot_reloading( mkdir(hot_reloading_dir_path) cleanups.append(lambda: rm_rf(hot_reloading_dir_path)) function_content = load_file(handler_file) - with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="w") as f: f.write(function_content) mount_path = get_host_path_for_path_in_docker(hot_reloading_dir_path) - create_lambda_function_aws( + create_function_response = create_lambda_function_aws( FunctionName=function_name, Handler="handler.handler", Code={"S3Bucket": hot_reloading_bucket, "S3Key": mount_path}, Role=lambda_su_role, Runtime=runtime, ) + # The AWS Toolkit for VS Code depends on this naming convention: + # https://github.com/aws/aws-toolkit-vscode/blob/1f6250148ba4f2c22e89613b8e7801bd8c4be062/packages/core/src/lambda/utils.ts#L212 + assert create_function_response["CodeSha256"].startswith("hot-reloading") + + get_function_response = aws_client.lambda_.get_function(FunctionName=function_name) + code_location_path = Path.from_uri(get_function_response["Code"]["Location"]) + assert str(code_location_path) == mount_path + response = aws_client.lambda_.invoke(FunctionName=function_name, Payload=b"{}") response_dict = json.load(response["Payload"]) assert response_dict["counter"] == 1 @@ -74,7 +85,7 @@ def test_hot_reloading( response_dict = json.load(response["Payload"]) assert response_dict["counter"] == 2 assert response_dict["constant"] == "value1" - with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="w") as f: f.write(function_content.replace("value1", "value2")) # we have to sleep here, since the hot reloading is debounced with 500ms time.sleep(sleep_time) @@ -97,7 +108,7 @@ def test_hot_reloading( assert response_dict["counter"] == 1 assert response_dict["constant"] == "value2" # now writing something in the new folder to check if it will reload - with open(os.path.join(test_folder, "test-file"), mode="wt") as f: + with open(os.path.join(test_folder, "test-file"), mode="w") as f: f.write("test-content") time.sleep(sleep_time) response = aws_client.lambda_.invoke(FunctionName=function_name, Payload=b"{}") @@ -122,7 +133,7 @@ def test_hot_reloading_publish_version( mkdir(hot_reloading_dir_path) cleanups.append(lambda: rm_rf(hot_reloading_dir_path)) function_content = load_file(HOT_RELOADING_NODEJS_HANDLER) - with open(os.path.join(hot_reloading_dir_path, "handler.mjs"), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, "handler.mjs"), mode="w") as f: f.write(function_content) mount_path = get_host_path_for_path_in_docker(hot_reloading_dir_path) @@ -146,7 +157,7 @@ def test_hot_reloading_error_path_not_absolute( """Tests validation of hot reloading paths""" function_name = f"test-hot-reloading-{short_uid()}" hot_reloading_bucket = config.BUCKET_MARKER_LOCAL - with pytest.raises(Exception): + with pytest.raises(ClientError): aws_client.lambda_.create_function( FunctionName=function_name, Handler="handler.handler", @@ -155,6 +166,7 @@ def test_hot_reloading_error_path_not_absolute( Runtime=Runtime.python3_12, ) + @markers.requires_in_process @markers.aws.only_localstack def test_hot_reloading_environment_placeholder( self, create_lambda_function_aws, lambda_su_role, cleanups, aws_client, monkeypatch @@ -167,7 +179,7 @@ def test_hot_reloading_environment_placeholder( mkdir(hot_reloading_dir_path) cleanups.append(lambda: rm_rf(hot_reloading_dir_path)) function_content = load_file(HOT_RELOADING_PYTHON_HANDLER) - with open(os.path.join(hot_reloading_dir_path, "handler.py"), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, "handler.py"), mode="w") as f: f.write(function_content) mount_path = get_host_path_for_path_in_docker(hot_reloading_dir_path) @@ -188,6 +200,7 @@ def test_hot_reloading_environment_placeholder( class TestDockerFlags: + @markers.requires_in_process @markers.aws.only_localstack def test_additional_docker_flags(self, create_lambda_function, monkeypatch, aws_client): env_value = short_uid() @@ -204,6 +217,7 @@ def test_additional_docker_flags(self, create_lambda_function, monkeypatch, aws_ result_data = json.load(result["Payload"]) assert {"Hello": env_value} == result_data + @markers.requires_in_process @markers.aws.only_localstack def test_lambda_docker_networks(self, lambda_su_role, monkeypatch, aws_client, cleanups): function_name = f"test-network-{short_uid()}" diff --git a/tests/aws/services/lambda_/test_lambda_integration_xray.snapshot.json b/tests/aws/services/lambda_/test_lambda_integration_xray.snapshot.json new file mode 100644 index 0000000000000..c8fb4f96a01fc --- /dev/null +++ b/tests/aws/services/lambda_/test_lambda_integration_xray.snapshot.json @@ -0,0 +1,6 @@ +{ + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_xray_trace_propagation": { + "recorded-date": "24-11-2025, 23:08:06", + "recorded-content": {} + } +} diff --git a/tests/aws/services/lambda_/test_lambda_integration_xray.validation.json b/tests/aws/services/lambda_/test_lambda_integration_xray.validation.json new file mode 100644 index 0000000000000..a84c2234ed5e1 --- /dev/null +++ b/tests/aws/services/lambda_/test_lambda_integration_xray.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[Active]": { + "last_validated_date": "2025-11-24T23:08:01+00:00", + "durations_in_seconds": { + "setup": 10.9, + "call": 3.31, + "teardown": 0.57, + "total": 14.78 + } + }, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[PassThrough]": { + "last_validated_date": "2025-11-24T23:08:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.02, + "teardown": 0.28, + "total": 3.3 + } + }, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_xray_trace_propagation": { + "last_validated_date": "2025-11-24T23:08:07+00:00", + "durations_in_seconds": { + "setup": 0.3, + "call": 1.91, + "teardown": 1.1, + "total": 3.31 + } + } +} diff --git a/tests/aws/services/lambda_/test_lambda_performance.validation.json b/tests/aws/services/lambda_/test_lambda_performance.validation.json index 98ef9fb3d805a..75d4e556934a4 100644 --- a/tests/aws/services/lambda_/test_lambda_performance.validation.json +++ b/tests/aws/services/lambda_/test_lambda_performance.validation.json @@ -1,14 +1,56 @@ { + "tests/aws/services/lambda_/test_lambda_performance.py::test_invoke_warm_start": { + "last_validated_date": "2025-11-24T23:08:24+00:00", + "durations_in_seconds": { + "setup": 10.76, + "call": 23.36, + "teardown": 0.74, + "total": 34.86 + } + }, + "tests/aws/services/lambda_/test_lambda_performance.py::test_lambda_event_invoke": { + "last_validated_date": "2025-11-24T23:42:48+00:00", + "durations_in_seconds": { + "setup": 0.57, + "call": 2017.8, + "teardown": 2.58, + "total": 2020.95 + } + }, "tests/aws/services/lambda_/test_lambda_performance.py::test_number_of_function_versions_async": { - "last_validated_date": "2024-02-08T17:18:44+00:00" + "last_validated_date": "2025-11-24T23:08:48+00:00", + "durations_in_seconds": { + "setup": 0.64, + "call": 10.83, + "teardown": 1.51, + "total": 12.98 + } }, "tests/aws/services/lambda_/test_lambda_performance.py::test_number_of_function_versions_sync": { - "last_validated_date": "2024-02-08T17:01:53+00:00" + "last_validated_date": "2025-11-24T23:08:35+00:00", + "durations_in_seconds": { + "setup": 0.65, + "call": 8.5, + "teardown": 1.5, + "total": 10.65 + } }, "tests/aws/services/lambda_/test_lambda_performance.py::test_number_of_functions_async": { - "last_validated_date": "2024-02-08T17:55:20+00:00" + "last_validated_date": "2025-11-24T23:09:07+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 8.85, + "teardown": 2.01, + "total": 11.45 + } }, "tests/aws/services/lambda_/test_lambda_performance.py::test_number_of_functions_sync": { - "last_validated_date": "2024-02-08T17:50:10+00:00" + "last_validated_date": "2025-11-24T23:08:56+00:00", + "durations_in_seconds": { + "setup": 0.57, + "call": 5.6, + "teardown": 1.9, + "total": 8.07 + } } } diff --git a/tests/aws/services/lambda_/test_lambda_runtimes.py b/tests/aws/services/lambda_/test_lambda_runtimes.py index 6c0e82bbec038..6b3f0fc504f44 100644 --- a/tests/aws/services/lambda_/test_lambda_runtimes.py +++ b/tests/aws/services/lambda_/test_lambda_runtimes.py @@ -7,7 +7,6 @@ import os import shutil import textwrap -from typing import List import pytest @@ -45,7 +44,7 @@ class LambdaJavaTestlibsPackage(Package): def __init__(self): super().__init__("JavaLambdaTestlibs", LOCALSTACK_MAVEN_VERSION) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [LOCALSTACK_MAVEN_VERSION] def _get_installer(self, version: str) -> PackageInstaller: diff --git a/tests/aws/services/lambda_/test_lambda_runtimes.snapshot.json b/tests/aws/services/lambda_/test_lambda_runtimes.snapshot.json index 314aec2afb7e4..6d02d54a1ba6d 100644 --- a/tests/aws/services/lambda_/test_lambda_runtimes.snapshot.json +++ b/tests/aws/services/lambda_/test_lambda_runtimes.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs20.x]": { - "recorded-date": "26-11-2024, 09:42:35", + "recorded-date": "25-11-2025, 02:26:40", "recorded-content": { "creation-result": { "CreateEventSourceMappingResponse": null, @@ -66,7 +66,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs18.x]": { - "recorded-date": "26-11-2024, 09:42:45", + "recorded-date": "25-11-2025, 02:26:46", "recorded-content": { "creation-result": { "CreateEventSourceMappingResponse": null, @@ -132,7 +132,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs16.x]": { - "recorded-date": "26-11-2024, 09:42:54", + "recorded-date": "25-11-2025, 02:27:00", "recorded-content": { "creation-result": { "CreateEventSourceMappingResponse": null, @@ -198,7 +198,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_runtime_with_lib": { - "recorded-date": "26-11-2024, 09:43:08", + "recorded-date": "25-11-2025, 02:27:16", "recorded-content": { "create-result-jar-with-lib": { "CreateEventSourceMappingResponse": null, @@ -377,7 +377,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java21]": { - "recorded-date": "26-11-2024, 09:43:11", + "recorded-date": "25-11-2025, 02:27:23", "recorded-content": { "invoke_result": { "ExecutedVersion": "$LATEST", @@ -391,7 +391,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java17]": { - "recorded-date": "26-11-2024, 09:43:14", + "recorded-date": "25-11-2025, 02:27:26", "recorded-content": { "invoke_result": { "ExecutedVersion": "$LATEST", @@ -405,7 +405,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java11]": { - "recorded-date": "26-11-2024, 09:43:17", + "recorded-date": "25-11-2025, 02:27:29", "recorded-content": { "invoke_result": { "ExecutedVersion": "$LATEST", @@ -419,7 +419,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java8.al2]": { - "recorded-date": "26-11-2024, 09:43:20", + "recorded-date": "25-11-2025, 02:27:33", "recorded-content": { "invoke_result": { "ExecutedVersion": "$LATEST", @@ -433,7 +433,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java21]": { - "recorded-date": "26-11-2024, 09:43:37", + "recorded-date": "25-11-2025, 02:28:00", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -500,7 +500,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java17]": { - "recorded-date": "26-11-2024, 09:43:52", + "recorded-date": "25-11-2025, 02:28:10", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -567,7 +567,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java11]": { - "recorded-date": "26-11-2024, 09:44:07", + "recorded-date": "25-11-2025, 02:28:21", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -634,7 +634,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java8.al2]": { - "recorded-date": "26-11-2024, 09:44:22", + "recorded-date": "25-11-2025, 02:28:32", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -701,7 +701,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequestCustom-CUSTOM]": { - "recorded-date": "26-11-2024, 09:44:29", + "recorded-date": "25-11-2025, 02:28:38", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -764,7 +764,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom-INTERFACE]": { - "recorded-date": "26-11-2024, 09:44:39", + "recorded-date": "25-11-2025, 02:28:52", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -827,7 +827,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequest-INTERFACE]": { - "recorded-date": "26-11-2024, 09:44:50", + "recorded-date": "25-11-2025, 02:28:58", "recorded-content": { "create-result": { "CreateEventSourceMappingResponse": null, @@ -890,7 +890,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_lambda_subscribe_sns_topic": { - "recorded-date": "26-11-2024, 09:45:22", + "recorded-date": "25-11-2025, 02:29:22", "recorded-content": { "get-function": { "Code": { @@ -966,47 +966,47 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.12]": { - "recorded-date": "26-11-2024, 09:45:27", + "recorded-date": "25-11-2025, 02:29:31", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.11]": { - "recorded-date": "26-11-2024, 09:45:30", + "recorded-date": "25-11-2025, 02:29:34", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.10]": { - "recorded-date": "26-11-2024, 09:45:32", + "recorded-date": "25-11-2025, 02:29:37", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.9]": { - "recorded-date": "26-11-2024, 09:45:35", + "recorded-date": "25-11-2025, 02:29:39", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.8]": { - "recorded-date": "26-11-2024, 09:45:38", + "recorded-date": "25-11-2025, 02:29:42", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.12]": { - "recorded-date": "26-11-2024, 09:45:42", + "recorded-date": "25-11-2025, 02:29:50", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.11]": { - "recorded-date": "26-11-2024, 09:45:45", + "recorded-date": "25-11-2025, 02:29:52", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.10]": { - "recorded-date": "26-11-2024, 09:45:47", + "recorded-date": "25-11-2025, 02:29:55", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.9]": { - "recorded-date": "26-11-2024, 09:45:49", + "recorded-date": "25-11-2025, 02:29:58", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.8]": { - "recorded-date": "26-11-2024, 09:45:51", + "recorded-date": "25-11-2025, 02:30:00", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2023]": { - "recorded-date": "26-11-2024, 09:46:26", + "recorded-date": "25-11-2025, 02:30:08", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1067,7 +1067,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2]": { - "recorded-date": "26-11-2024, 09:46:32", + "recorded-date": "25-11-2025, 02:30:15", "recorded-content": { "create_function_result": { "Architectures": [ @@ -1128,23 +1128,23 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2023]": { - "recorded-date": "26-11-2024, 09:46:59", + "recorded-date": "25-11-2025, 02:30:24", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2]": { - "recorded-date": "26-11-2024, 09:47:11", + "recorded-date": "25-11-2025, 02:30:33", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.13]": { - "recorded-date": "26-11-2024, 09:45:25", + "recorded-date": "25-11-2025, 02:29:28", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.13]": { - "recorded-date": "26-11-2024, 09:45:40", + "recorded-date": "25-11-2025, 02:29:48", "recorded-content": {} }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs22.x]": { - "recorded-date": "26-11-2024, 09:42:29", + "recorded-date": "25-11-2025, 02:26:34", "recorded-content": { "creation-result": { "CreateEventSourceMappingResponse": null, @@ -1210,7 +1210,7 @@ } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestCloudwatchLogs::test_multi_line_prints": { - "recorded-date": "02-04-2025, 12:35:33", + "recorded-date": "25-11-2025, 02:30:38", "recorded-content": { "log-events": [ { @@ -1271,5 +1271,160 @@ } ] } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.14]": { + "recorded-date": "25-11-2025, 02:29:25", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.14]": { + "recorded-date": "25-11-2025, 02:29:45", + "recorded-content": {} + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java25]": { + "recorded-date": "25-11-2025, 02:27:19", + "recorded-content": { + "invoke_result": { + "ExecutedVersion": "$LATEST", + "Payload": {}, + "StatusCode": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java25]": { + "recorded-date": "25-11-2025, 02:27:50", + "recorded-content": { + "create-result": { + "CreateEventSourceMappingResponse": null, + "CreateFunctionResponse": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "<:1>", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": {} + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "cloud.localstack.awssdkv1.sample.SerializedInputLambdaHandler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 128, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "java25", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 30, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + }, + "invoke-result": { + "ExecutedVersion": "$LATEST", + "Payload": { + "bucket": "test_bucket", + "key": "test_key", + "validated": true + }, + "StatusCode": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs24.x]": { + "recorded-date": "25-11-2025, 02:26:24", + "recorded-content": { + "creation-result": { + "CreateEventSourceMappingResponse": null, + "CreateFunctionResponse": { + "Architectures": [ + "x86_64" + ], + "CodeSha256": "<:1>", + "CodeSize": "", + "Description": "", + "Environment": { + "Variables": {} + }, + "EphemeralStorage": { + "Size": 512 + }, + "FunctionArn": "arn::lambda::111111111111:function:", + "FunctionName": "", + "Handler": "lambda_handler_es6.handler", + "LastModified": "date", + "LoggingConfig": { + "LogFormat": "Text", + "LogGroup": "/aws/lambda/" + }, + "MemorySize": 128, + "PackageType": "Zip", + "RevisionId": "", + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs24.x", + "RuntimeVersionConfig": { + "RuntimeVersionArn": "arn::lambda:::runtime:" + }, + "SnapStart": { + "ApplyOn": "None", + "OptimizationStatus": "Off" + }, + "State": "Pending", + "StateReason": "The function is being created.", + "StateReasonCode": "Creating", + "Timeout": 30, + "TracingConfig": { + "Mode": "PassThrough" + }, + "Version": "$LATEST", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + } + }, + "invocation-result": { + "ExecutedVersion": "$LATEST", + "Payload": { + "statusCode": 200, + "body": "\"response from localstack lambda: {\\\"event_type\\\":\\\"test_lambda\\\"}\"" + }, + "StatusCode": 200, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/lambda_/test_lambda_runtimes.validation.json b/tests/aws/services/lambda_/test_lambda_runtimes.validation.json index 4d29b8b622534..6ce946784dd34 100644 --- a/tests/aws/services/lambda_/test_lambda_runtimes.validation.json +++ b/tests/aws/services/lambda_/test_lambda_runtimes.validation.json @@ -1,104 +1,353 @@ { "tests/aws/services/lambda_/test_lambda_runtimes.py::TestCloudwatchLogs::test_multi_line_prints": { - "last_validated_date": "2025-04-02T12:35:33+00:00" + "last_validated_date": "2025-11-25T02:30:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.69, + "teardown": 1.55, + "total": 6.24 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2023]": { - "last_validated_date": "2024-11-26T09:46:59+00:00" + "last_validated_date": "2025-11-25T02:30:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.99, + "teardown": 0.35, + "total": 9.34 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2]": { - "last_validated_date": "2024-11-26T09:47:11+00:00" + "last_validated_date": "2025-11-25T02:30:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.16, + "teardown": 0.42, + "total": 8.58 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2023]": { - "last_validated_date": "2024-11-26T09:46:26+00:00" + "last_validated_date": "2025-11-25T02:30:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.01, + "teardown": 0.21, + "total": 7.22 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2]": { - "last_validated_date": "2024-11-26T09:46:31+00:00" + "last_validated_date": "2025-11-25T02:30:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.33, + "teardown": 0.21, + "total": 7.54 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom-INTERFACE]": { - "last_validated_date": "2024-11-26T09:44:39+00:00" + "last_validated_date": "2025-11-25T02:28:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.14, + "teardown": 0.35, + "total": 13.49 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequest-INTERFACE]": { - "last_validated_date": "2024-11-26T09:44:50+00:00" + "last_validated_date": "2025-11-25T02:28:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 6.08, + "teardown": 0.32, + "total": 6.4 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequestCustom-CUSTOM]": { - "last_validated_date": "2024-11-26T09:44:29+00:00" + "last_validated_date": "2025-11-25T02:28:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.9, + "teardown": 0.36, + "total": 6.26 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_lambda_subscribe_sns_topic": { - "last_validated_date": "2024-11-26T09:45:21+00:00" + "last_validated_date": "2025-11-25T02:29:22+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 22.15, + "teardown": 1.56, + "total": 24.3 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_runtime_with_lib": { - "last_validated_date": "2024-11-26T09:43:07+00:00" + "last_validated_date": "2025-11-25T02:27:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.09, + "teardown": 1.37, + "total": 15.46 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java11]": { - "last_validated_date": "2024-11-26T09:44:07+00:00" + "last_validated_date": "2025-11-25T02:28:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 10.04, + "teardown": 0.68, + "total": 10.72 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java17]": { - "last_validated_date": "2024-11-26T09:43:52+00:00" + "last_validated_date": "2025-11-25T02:28:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.91, + "teardown": 0.63, + "total": 10.54 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java21]": { - "last_validated_date": "2024-11-26T09:43:37+00:00" + "last_validated_date": "2025-11-25T02:28:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.41, + "teardown": 0.61, + "total": 10.02 + } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java25]": { + "last_validated_date": "2025-11-25T02:27:50+00:00", + "durations_in_seconds": { + "setup": 0.04, + "call": 16.2, + "teardown": 0.66, + "total": 16.9 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java8.al2]": { - "last_validated_date": "2024-11-26T09:44:22+00:00" + "last_validated_date": "2025-11-25T02:28:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 10.43, + "teardown": 0.56, + "total": 10.99 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java11]": { - "last_validated_date": "2024-11-26T09:43:16+00:00" + "last_validated_date": "2025-11-25T02:27:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.62, + "teardown": 0.59, + "total": 3.21 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java17]": { - "last_validated_date": "2024-11-26T09:43:13+00:00" + "last_validated_date": "2025-11-25T02:27:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.57, + "teardown": 0.65, + "total": 3.22 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java21]": { - "last_validated_date": "2024-11-26T09:43:11+00:00" + "last_validated_date": "2025-11-25T02:27:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.8, + "teardown": 0.81, + "total": 3.61 + } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java25]": { + "last_validated_date": "2025-11-25T02:27:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.07, + "teardown": 0.62, + "total": 3.69 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java8.al2]": { - "last_validated_date": "2024-11-26T09:43:19+00:00" + "last_validated_date": "2025-11-25T02:27:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.93, + "teardown": 0.55, + "total": 3.48 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs16.x]": { - "last_validated_date": "2024-11-26T09:42:54+00:00" + "last_validated_date": "2025-11-25T02:27:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.06, + "teardown": 0.37, + "total": 14.43 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs18.x]": { - "last_validated_date": "2024-11-26T09:42:44+00:00" + "last_validated_date": "2025-11-25T02:26:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.48, + "teardown": 0.36, + "total": 5.84 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs20.x]": { - "last_validated_date": "2024-11-26T09:42:35+00:00" + "last_validated_date": "2025-11-25T02:26:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.52, + "teardown": 0.32, + "total": 5.84 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs22.x]": { - "last_validated_date": "2024-11-26T09:42:29+00:00" + "last_validated_date": "2025-11-25T02:26:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.62, + "teardown": 0.51, + "total": 10.13 + } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs24.x]": { + "last_validated_date": "2025-11-25T02:26:24+00:00", + "durations_in_seconds": { + "setup": 11.2, + "call": 10.48, + "teardown": 0.4, + "total": 22.08 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.10]": { - "last_validated_date": "2024-11-26T09:45:32+00:00" + "last_validated_date": "2025-11-25T02:29:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.42, + "teardown": 0.49, + "total": 2.91 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.11]": { - "last_validated_date": "2024-11-26T09:45:29+00:00" + "last_validated_date": "2025-11-25T02:29:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.15, + "teardown": 0.7, + "total": 2.85 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.12]": { - "last_validated_date": "2024-11-26T09:45:27+00:00" + "last_validated_date": "2025-11-25T02:29:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.22, + "teardown": 0.34, + "total": 2.56 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.13]": { - "last_validated_date": "2024-11-26T09:45:24+00:00" + "last_validated_date": "2025-11-25T02:29:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.39, + "teardown": 0.83, + "total": 3.22 + } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.14]": { + "last_validated_date": "2025-11-25T02:29:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.33, + "teardown": 0.29, + "total": 2.62 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.8]": { - "last_validated_date": "2024-11-26T09:45:37+00:00" + "last_validated_date": "2025-11-25T02:29:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.29, + "teardown": 0.6, + "total": 2.89 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.9]": { - "last_validated_date": "2024-11-26T09:45:35+00:00" + "last_validated_date": "2025-11-25T02:29:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.26, + "teardown": 0.61, + "total": 2.87 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.10]": { - "last_validated_date": "2024-11-26T09:45:47+00:00" + "last_validated_date": "2025-11-25T02:29:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.95, + "teardown": 0.55, + "total": 2.5 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.11]": { - "last_validated_date": "2024-11-26T09:45:44+00:00" + "last_validated_date": "2025-11-25T02:29:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.01, + "teardown": 0.28, + "total": 2.29 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.12]": { - "last_validated_date": "2024-11-26T09:45:42+00:00" + "last_validated_date": "2025-11-25T02:29:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.93, + "teardown": 0.52, + "total": 2.45 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.13]": { - "last_validated_date": "2024-11-26T09:45:40+00:00" + "last_validated_date": "2025-11-25T02:29:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.04, + "teardown": 0.61, + "total": 2.65 + } + }, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.14]": { + "last_validated_date": "2025-11-25T02:29:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.11, + "teardown": 0.57, + "total": 2.68 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.8]": { - "last_validated_date": "2024-11-26T09:45:51+00:00" + "last_validated_date": "2025-11-25T02:30:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.21, + "teardown": 0.57, + "total": 2.78 + } }, "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.9]": { - "last_validated_date": "2024-11-26T09:45:49+00:00" + "last_validated_date": "2025-11-25T02:29:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.99, + "teardown": 0.55, + "total": 2.54 + } } } diff --git a/tests/aws/services/lambda_/test_lambda_unzip_crash.py b/tests/aws/services/lambda_/test_lambda_unzip_crash.py new file mode 100644 index 0000000000000..cab2074f7a8a6 --- /dev/null +++ b/tests/aws/services/lambda_/test_lambda_unzip_crash.py @@ -0,0 +1,76 @@ +import tempfile +from pathlib import Path +from unittest.mock import patch + +import pytest + +from localstack.services.lambda_.invocation.lambda_models import S3Code +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +class TestLambdaUnzipCrash: + @markers.aws.only_localstack + def test_unzip_crash_leaves_no_corrupted_files( + self, aws_client, tmp_path, monkeypatch, account_id + ): + """ + Integration test to test fix for issue https://github.com/localstack/localstack/issues/13555 + Simulates what happens when we crash during Lambda code unzipping. + Makes sure no corrupted file is left. + """ + + unzip_call_count = 0 + function_id = f"test-unzip-crash-{short_uid()}" + + def crashing_unzip(zip_file, target_dir): + """Unzip that crashes after writing partial content""" + nonlocal unzip_call_count + unzip_call_count += 1 + + target_path = Path(target_dir) + target_path.mkdir(parents=True, exist_ok=True) + (target_path / "handler.py").write_text("# CORRUPTED - partial unpack\n") + + raise RuntimeError("Simulated crash during unzip") + + s3_code = S3Code( + id=function_id, + account_id=account_id, + s3_bucket="test-bucket", + s3_key="test.zip", + s3_object_version=None, + code_sha256="abc123", + code_size=1024, + ) + + def mock_download(self, target_file): + target_file.write(b"fake-zip") + target_file.flush() + + # Patch unzip and download + with patch( + "localstack.services.lambda_.invocation.lambda_models.unzip", side_effect=crashing_unzip + ): + with patch.object(S3Code, "_download_archive_to_file", mock_download): + # This will trigger the unzip operation (which will crash) + try: + s3_code.prepare_for_execution() + except RuntimeError: + pass + + # Check what's on disk + target_code_dir = ( + Path(tempfile.gettempdir()) / "lambda" / "test-bucket" / function_id / "code" + ) + + if target_code_dir.exists(): + handler_file = target_code_dir / "handler.py" + if handler_file.exists(): + content = handler_file.read_text() + if "CORRUPTED" in content: + pytest.fail( + f"Crash during unzip left corrupted files on disk at {target_code_dir}!" + ) + + assert unzip_call_count >= 1, "Unzip should have been called" diff --git a/tests/aws/services/logs/test_logs.py b/tests/aws/services/logs/test_logs.py deleted file mode 100644 index bf3abb3a5294f..0000000000000 --- a/tests/aws/services/logs/test_logs.py +++ /dev/null @@ -1,658 +0,0 @@ -import base64 -import gzip -import json -import re - -import pytest -from localstack_snapshot.pytest.snapshot import is_aws -from localstack_snapshot.snapshots.transformer import KeyValueBasedTransformer - -from localstack.aws.api.lambda_ import Runtime -from localstack.constants import APPLICATION_AMZ_JSON_1_1 -from localstack.testing.config import TEST_AWS_REGION_NAME -from localstack.testing.pytest import markers -from localstack.utils import testutil -from localstack.utils.aws import arns -from localstack.utils.aws.arns import get_partition -from localstack.utils.common import now_utc, poll_condition, retry, short_uid -from tests.aws.services.lambda_.test_lambda import TEST_LAMBDA_PYTHON_ECHO - -logs_role = { - "Statement": { - "Effect": "Allow", - "Principal": {"Service": f"logs.{TEST_AWS_REGION_NAME}.amazonaws.com"}, - "Action": "sts:AssumeRole", - } -} -kinesis_permission = { - "Version": "2012-10-17", - "Statement": [{"Effect": "Allow", "Action": "kinesis:PutRecord", "Resource": "*"}], -} -s3_firehose_role = { - "Statement": { - "Sid": "", - "Effect": "Allow", - "Principal": {"Service": "firehose.amazonaws.com"}, - "Action": "sts:AssumeRole", - } -} -s3_firehose_permission = { - "Version": "2012-10-17", - "Statement": [{"Effect": "Allow", "Action": ["s3:*", "s3-object-lambda:*"], "Resource": "*"}], -} - -firehose_permission = { - "Version": "2012-10-17", - "Statement": [{"Effect": "Allow", "Action": ["firehose:*"], "Resource": "*"}], -} - - -@pytest.fixture -def logs_log_group(aws_client): - name = f"test-log-group-{short_uid()}" - aws_client.logs.create_log_group(logGroupName=name) - yield name - aws_client.logs.delete_log_group(logGroupName=name) - - -@pytest.fixture -def logs_log_stream(logs_log_group, aws_client): - name = f"test-log-stream-{short_uid()}" - aws_client.logs.create_log_stream(logGroupName=logs_log_group, logStreamName=name) - yield name - aws_client.logs.delete_log_stream(logStreamName=name, logGroupName=logs_log_group) - - -class TestCloudWatchLogs: - # TODO make creation and description atomic to avoid possible flake? - @markers.aws.validated - def test_create_and_delete_log_group(self, aws_client): - test_name = f"test-log-group-{short_uid()}" - log_groups_before = aws_client.logs.describe_log_groups( - logGroupNamePrefix="test-log-group-" - ).get("logGroups", []) - - aws_client.logs.create_log_group(logGroupName=test_name) - - log_groups_between = aws_client.logs.describe_log_groups( - logGroupNamePrefix="test-log-group-" - ).get("logGroups", []) - assert poll_condition( - lambda: len(log_groups_between) == len(log_groups_before) + 1, timeout=5.0, interval=0.5 - ) - - aws_client.logs.delete_log_group(logGroupName=test_name) - - log_groups_after = aws_client.logs.describe_log_groups( - logGroupNamePrefix="test-log-group-" - ).get("logGroups", []) - assert poll_condition( - lambda: len(log_groups_after) == len(log_groups_between) - 1, timeout=5.0, interval=0.5 - ) - assert len(log_groups_after) == len(log_groups_before) - - @markers.aws.validated - def test_resource_does_not_exist(self, aws_client, snapshot, cleanups): - log_group_name = f"log-group-{short_uid()}" - log_stream_name = f"log-stream-{short_uid()}" - with pytest.raises(Exception) as ctx: - aws_client.logs.get_log_events( - logGroupName=log_group_name, logStreamName=log_stream_name - ) - snapshot.match("error-log-group-does-not-exist", ctx.value.response) - - aws_client.logs.create_log_group(logGroupName=log_group_name) - cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) - - with pytest.raises(Exception) as ctx: - aws_client.logs.get_log_events( - logGroupName=log_group_name, logStreamName=log_stream_name - ) - snapshot.match("error-log-stream-does-not-exist", ctx.value.response) - - @markers.aws.validated - def test_list_tags_log_group(self, snapshot, aws_client): - test_name = f"test-log-group-{short_uid()}" - try: - aws_client.logs.create_log_group(logGroupName=test_name, tags={"env": "testing1"}) - response = aws_client.logs.list_tags_log_group(logGroupName=test_name) - snapshot.match("list_tags_after_create_log_group", response) - - # get group arn, to use the tag-resource api - log_group_arn = aws_client.logs.describe_log_groups(logGroupNamePrefix=test_name)[ - "logGroups" - ][0]["arn"].rstrip(":*") - - # add a tag - new api - aws_client.logs.tag_resource( - resourceArn=log_group_arn, tags={"test1": "val1", "test2": "val2"} - ) - - response = aws_client.logs.list_tags_log_group(logGroupName=test_name) - response_2 = aws_client.logs.list_tags_for_resource(resourceArn=log_group_arn) - - snapshot.match("list_tags_log_group_after_tag_resource", response) - snapshot.match("list_tags_for_resource_after_tag_resource", response_2) - # values should be the same - assert response["tags"] == response_2["tags"] - - # add a tag - old api - aws_client.logs.tag_log_group(logGroupName=test_name, tags={"test3": "val3"}) - - response = aws_client.logs.list_tags_log_group(logGroupName=test_name) - response_2 = aws_client.logs.list_tags_for_resource(resourceArn=log_group_arn) - - snapshot.match("list_tags_log_group_after_tag_log_group", response) - snapshot.match("list_tags_for_resource_after_tag_log_group", response_2) - assert response["tags"] == response_2["tags"] - - # untag - use both apis - aws_client.logs.untag_log_group(logGroupName=test_name, tags=["test3"]) - aws_client.logs.untag_resource(resourceArn=log_group_arn, tagKeys=["env", "test1"]) - - response = aws_client.logs.list_tags_log_group(logGroupName=test_name) - response_2 = aws_client.logs.list_tags_for_resource(resourceArn=log_group_arn) - snapshot.match("list_tags_log_group_after_untag", response) - snapshot.match("list_tags_for_resource_after_untag", response_2) - - assert response["tags"] == response_2["tags"] - - finally: - # clean up - aws_client.logs.delete_log_group(logGroupName=test_name) - - @markers.snapshot.skip_snapshot_verify( - paths=[ - # TODO 'describe-log-groups' returns different attributes on AWS when using - # 'logGroupNamePattern' compared to 'logGroupNamePrefix' (for the same log group) - # seems like a weird issue on AWS side, we just exclude the paths here for this particular call - "$..describe-log-groups-pattern.logGroups..metricFilterCount", - "$..describe-log-groups-pattern.logGroups..storedBytes", - "$..describe-log-groups-pattern.nextToken", - ] - ) - @markers.aws.validated - def test_create_and_delete_log_stream(self, logs_log_group, aws_client, region_name, snapshot): - snapshot.add_transformer(snapshot.transform.logs_api()) - test_name = f"test-log-stream-{short_uid()}" - - # filter for prefix/entire name here - response = aws_client.logs.describe_log_groups(logGroupNamePrefix=logs_log_group) - snapshot.match("describe-log-groups-prefix", response) - - # pattern for the short-uid - # for some reason, this does not work immediately on AWS - assert poll_condition( - lambda: len( - aws_client.logs.describe_log_groups( - logGroupNamePattern=logs_log_group.split("-")[-1] - ).get("logGroups") - ) - == 1, - timeout=5.0, - interval=0.5, - ) - response = aws_client.logs.describe_log_groups( - logGroupNamePattern=logs_log_group.split("-")[-1] - ) - snapshot.match("describe-log-groups-pattern", response) - - # using prefix + pattern should raise error - with pytest.raises(Exception) as ctx: - aws_client.logs.describe_log_groups( - logGroupNamePattern=logs_log_group, logGroupNamePrefix=logs_log_group - ) - snapshot.match("error-describe-logs-group", ctx.value.response) - - aws_client.logs.create_log_stream(logGroupName=logs_log_group, logStreamName=test_name) - log_streams_between = aws_client.logs.describe_log_streams(logGroupName=logs_log_group).get( - "logStreams", [] - ) - - snapshot.match("logs_log_group", log_streams_between) - - # using log-group-name and log-group-identifier should raise exception - with pytest.raises(Exception) as ctx: - aws_client.logs.describe_log_streams( - logGroupName=logs_log_group, logGroupIdentifier=logs_log_group - ) - snapshot.match("error-describe-logs-streams", ctx.value.response) - - # log group identifier using the name of the log-group - response = aws_client.logs.describe_log_streams(logGroupIdentifier=logs_log_group).get( - "logStreams" - ) - snapshot.match("log_group_identifier", response) - # log group identifier using arn - response = aws_client.logs.describe_log_streams( - logGroupIdentifier=arns.log_group_arn( - logs_log_group, - account_id=aws_client.sts.get_caller_identity()["Account"], - region_name=region_name, - ) - ).get("logStreams") - snapshot.match("log_group_identifier-arn", response) - - aws_client.logs.delete_log_stream(logGroupName=logs_log_group, logStreamName=test_name) - - log_streams_after = aws_client.logs.describe_log_streams(logGroupName=logs_log_group).get( - "logStreams", [] - ) - assert len(log_streams_after) == 0 - - @markers.aws.validated - def test_put_events_multi_bytes_msg(self, logs_log_group, logs_log_stream, aws_client): - body_msg = "🙀 - 参よ - 日本語" - events = [{"timestamp": now_utc(millis=True), "message": body_msg}] - response = aws_client.logs.put_log_events( - logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events - ) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - - def get_log_events(): - events = aws_client.logs.get_log_events( - logGroupName=logs_log_group, logStreamName=logs_log_stream - )["events"] - assert events[0]["message"] == body_msg - - retry( - get_log_events, - retries=20 if is_aws() else 3, - sleep=5 if is_aws() else 1, - sleep_before=3 if is_aws() else 0, - ) - - @markers.aws.validated - def test_filter_log_events_response_header(self, logs_log_group, logs_log_stream, aws_client): - events = [ - {"timestamp": now_utc(millis=True), "message": "log message 1"}, - {"timestamp": now_utc(millis=True), "message": "log message 2"}, - ] - response = aws_client.logs.put_log_events( - logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events - ) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - - response = aws_client.logs.filter_log_events(logGroupName=logs_log_group) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - assert ( - response["ResponseMetadata"]["HTTPHeaders"]["content-type"] == APPLICATION_AMZ_JSON_1_1 - ) - - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Statement.Condition.StringEquals", - "$..add_permission.ResponseMetadata.HTTPStatusCode", - ] - ) - def test_put_subscription_filter_lambda( - self, - logs_log_group, - logs_log_stream, - create_lambda_function, - snapshot, - aws_client, - region_name, - ): - snapshot.add_transformer(snapshot.transform.lambda_api()) - # special replacements for this test case: - snapshot.add_transformer(snapshot.transform.key_value("logGroupName")) - snapshot.add_transformer(snapshot.transform.key_value("logStreamName")) - snapshot.add_transformer( - KeyValueBasedTransformer( - lambda k, v: ( - v - if k == "id" and (isinstance(v, str) and re.match(re.compile(r"^[0-9]+$"), v)) - else None - ), - replacement="id", - replace_reference=False, - ), - ) - - test_lambda_name = f"test-lambda-function-{short_uid()}" - func_arn = create_lambda_function( - handler_file=TEST_LAMBDA_PYTHON_ECHO, - func_name=test_lambda_name, - runtime=Runtime.python3_12, - )["CreateFunctionResponse"]["FunctionArn"] - aws_client.lambda_.invoke(FunctionName=test_lambda_name, Payload=b"{}") - # get account-id to set the correct policy - account_id = aws_client.sts.get_caller_identity()["Account"] - result = aws_client.lambda_.add_permission( - FunctionName=test_lambda_name, - StatementId=test_lambda_name, - Principal=f"logs.{region_name}.amazonaws.com", - Action="lambda:InvokeFunction", - SourceArn=f"arn:{get_partition(region_name)}:logs:{region_name}:{account_id}:log-group:{logs_log_group}:*", - SourceAccount=account_id, - ) - - snapshot.match("add_permission", result) - - result = aws_client.logs.put_subscription_filter( - logGroupName=logs_log_group, - filterName="test", - filterPattern="", - destinationArn=func_arn, - ) - snapshot.match("put_subscription_filter", result) - - aws_client.logs.put_log_events( - logGroupName=logs_log_group, - logStreamName=logs_log_stream, - logEvents=[ - {"timestamp": now_utc(millis=True), "message": "test"}, - {"timestamp": now_utc(millis=True), "message": "test 2"}, - ], - ) - - response = aws_client.logs.describe_subscription_filters(logGroupName=logs_log_group) - assert len(response["subscriptionFilters"]) == 1 - snapshot.match("describe_subscription_filter", response) - - def check_invocation(): - events = testutil.list_all_log_events( - log_group_name=f"/aws/lambda/{test_lambda_name}", logs_client=aws_client.logs - ) - # we only are interested in events that contain "awslogs" - filtered_events = [] - for e in events: - if "awslogs" in e["message"]: - # the message will look like this: - # {"messageType":"DATA_MESSAGE","owner":"000000000000","logGroup":"log-group", - # "logStream":"log-stream","subscriptionFilters":["test"], - # "logEvents":[{"id":"7","timestamp":1679056073581,"message":"test"}, - # {"id":"8","timestamp":1679056073581,"message":"test 2"}]} - data = json.loads(e["message"])["awslogs"]["data"].encode("utf-8") - decoded_data = gzip.decompress(base64.b64decode(data)).decode("utf-8") - for log_event in json.loads(decoded_data)["logEvents"]: - filtered_events.append(log_event) - assert len(filtered_events) == 2 - - filtered_events.sort(key=lambda k: k.get("message")) - snapshot.match("list_all_log_events", filtered_events) - - retry(check_invocation, retries=6, sleep=3.0) - - @markers.aws.validated - def test_put_subscription_filter_firehose( - self, logs_log_group, logs_log_stream, s3_bucket, create_iam_role_with_policy, aws_client - ): - try: - firehose_name = f"test-firehose-{short_uid()}" - s3_bucket_arn = f"arn:aws:s3:::{s3_bucket}" - - role = f"test-firehose-s3-role-{short_uid()}" - policy_name = f"test-firehose-s3-role-policy-{short_uid()}" - role_arn = create_iam_role_with_policy( - RoleName=role, - PolicyName=policy_name, - RoleDefinition=s3_firehose_role, - PolicyDefinition=s3_firehose_permission, - ) - - # TODO AWS has troubles creating the delivery stream the first time - # policy is not accepted at first, so we try again - def create_delivery_stream(): - aws_client.firehose.create_delivery_stream( - DeliveryStreamName=firehose_name, - S3DestinationConfiguration={ - "BucketARN": s3_bucket_arn, - "RoleARN": role_arn, - "BufferingHints": {"SizeInMBs": 1, "IntervalInSeconds": 60}, - }, - ) - - retry(create_delivery_stream, retries=5, sleep=10.0) - - response = aws_client.firehose.describe_delivery_stream( - DeliveryStreamName=firehose_name - ) - firehose_arn = response["DeliveryStreamDescription"]["DeliveryStreamARN"] - - role = f"test-firehose-role-{short_uid()}" - policy_name = f"test-firehose-role-policy-{short_uid()}" - role_arn_logs = create_iam_role_with_policy( - RoleName=role, - PolicyName=policy_name, - RoleDefinition=logs_role, - PolicyDefinition=firehose_permission, - ) - - def check_stream_active(): - state = aws_client.firehose.describe_delivery_stream( - DeliveryStreamName=firehose_name - )["DeliveryStreamDescription"]["DeliveryStreamStatus"] - if state != "ACTIVE": - raise Exception(f"DeliveryStreamStatus is {state}") - - retry(check_stream_active, retries=60, sleep=30.0) - - aws_client.logs.put_subscription_filter( - logGroupName=logs_log_group, - filterName="Destination", - filterPattern="", - destinationArn=firehose_arn, - roleArn=role_arn_logs, - ) - - aws_client.logs.put_log_events( - logGroupName=logs_log_group, - logStreamName=logs_log_stream, - logEvents=[ - {"timestamp": now_utc(millis=True), "message": "test"}, - {"timestamp": now_utc(millis=True), "message": "test 2"}, - ], - ) - - def list_objects(): - response = aws_client.s3.list_objects(Bucket=s3_bucket) - assert len(response["Contents"]) >= 1 - - retry(list_objects, retries=60, sleep=30.0) - response = aws_client.s3.list_objects(Bucket=s3_bucket) - key = response["Contents"][-1]["Key"] - response = aws_client.s3.get_object(Bucket=s3_bucket, Key=key) - content = gzip.decompress(response["Body"].read()).decode("utf-8") - assert "DATA_MESSAGE" in content - assert "test" in content - assert "test 2" in content - - finally: - # clean up - aws_client.firehose.delete_delivery_stream( - DeliveryStreamName=firehose_name, AllowForceDelete=True - ) - - @markers.aws.validated - def test_put_subscription_filter_kinesis( - self, logs_log_group, logs_log_stream, create_iam_role_with_policy, aws_client - ): - kinesis_name = f"test-kinesis-{short_uid()}" - filter_name = "Destination" - aws_client.kinesis.create_stream(StreamName=kinesis_name, ShardCount=1) - - try: - result = aws_client.kinesis.describe_stream(StreamName=kinesis_name)[ - "StreamDescription" - ] - kinesis_arn = result["StreamARN"] - role = f"test-kinesis-role-{short_uid()}" - policy_name = f"test-kinesis-role-policy-{short_uid()}" - role_arn = create_iam_role_with_policy( - RoleName=role, - PolicyName=policy_name, - RoleDefinition=logs_role, - PolicyDefinition=kinesis_permission, - ) - - # wait for stream-status "ACTIVE" - status = result["StreamStatus"] - if status != "ACTIVE": - - def check_stream_active(): - state = aws_client.kinesis.describe_stream(StreamName=kinesis_name)[ - "StreamDescription" - ]["StreamStatus"] - if state != "ACTIVE": - raise Exception(f"StreamStatus is {state}") - - retry(check_stream_active, retries=6, sleep=1.0, sleep_before=2.0) - - def put_subscription_filter(): - aws_client.logs.put_subscription_filter( - logGroupName=logs_log_group, - filterName=filter_name, - filterPattern="", - destinationArn=kinesis_arn, - roleArn=role_arn, - ) - - # for a weird reason the put_subscription_filter fails on AWS the first time, - # even-though we check for ACTIVE state... - retry(put_subscription_filter, retries=6, sleep=3.0) - - def put_event(): - aws_client.logs.put_log_events( - logGroupName=logs_log_group, - logStreamName=logs_log_stream, - logEvents=[ - {"timestamp": now_utc(millis=True), "message": "test"}, - {"timestamp": now_utc(millis=True), "message": "test 2"}, - ], - ) - - retry(put_event, retries=6, sleep=3.0) - - shard_iterator = aws_client.kinesis.get_shard_iterator( - StreamName=kinesis_name, - ShardId="shardId-000000000000", - ShardIteratorType="TRIM_HORIZON", - )["ShardIterator"] - - response = aws_client.kinesis.get_records(ShardIterator=shard_iterator) - # AWS sends messages as health checks - assert len(response["Records"]) >= 1 - found = False - for record in response["Records"]: - data = record["Data"] - unzipped_data = gzip.decompress(data) - json_data = json.loads(unzipped_data) - if "test" in json.dumps(json_data["logEvents"]): - assert len(json_data["logEvents"]) == 2 - assert json_data["logEvents"][0]["message"] == "test" - assert json_data["logEvents"][1]["message"] == "test 2" - found = True - - assert found - # clean up - finally: - aws_client.kinesis.delete_stream(StreamName=kinesis_name, EnforceConsumerDeletion=True) - aws_client.logs.delete_subscription_filter( - logGroupName=logs_log_group, filterName=filter_name - ) - - @pytest.mark.skip("TODO: failing against community - filters are only in pro -> move test?") - @markers.aws.validated - def test_metric_filters(self, logs_log_group, logs_log_stream, aws_client): - basic_filter_name = f"test-filter-basic-{short_uid()}" - json_filter_name = f"test-filter-json-{short_uid()}" - namespace_name = f"test-metric-namespace-{short_uid()}" - basic_metric_name = f"test-basic-metric-{short_uid()}" - json_metric_name = f"test-json-metric-{short_uid()}" - basic_transforms = { - "metricNamespace": namespace_name, - "metricName": basic_metric_name, - "metricValue": "1", - "defaultValue": 0, - } - json_transforms = { - "metricNamespace": namespace_name, - "metricName": json_metric_name, - "metricValue": "1", - "defaultValue": 0, - } - aws_client.logs.put_metric_filter( - logGroupName=logs_log_group, - filterName=basic_filter_name, - filterPattern=" ", - metricTransformations=[basic_transforms], - ) - aws_client.logs.put_metric_filter( - logGroupName=logs_log_group, - filterName=json_filter_name, - filterPattern='{$.message = "test"}', - metricTransformations=[json_transforms], - ) - - response = aws_client.logs.describe_metric_filters( - logGroupName=logs_log_group, filterNamePrefix="test-filter-" - ) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - filter_names = [_filter["filterName"] for _filter in response["metricFilters"]] - assert basic_filter_name in filter_names - assert json_filter_name in filter_names - - # put log events and assert metrics being published - events = [ - {"timestamp": now_utc(millis=True), "message": "log message 1"}, - {"timestamp": now_utc(millis=True), "message": "log message 2"}, - ] - aws_client.logs.put_log_events( - logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events - ) - - # list metrics - def list_metrics(): - res = aws_client.cloudwatch.list_metrics(Namespace=namespace_name) - assert len(res["Metrics"]) == 2 - - retry( - list_metrics, - retries=20 if is_aws() else 3, - sleep=5 if is_aws() else 1, - sleep_before=3 if is_aws() else 0, - ) - - # delete filters - aws_client.logs.delete_metric_filter( - logGroupName=logs_log_group, filterName=basic_filter_name - ) - aws_client.logs.delete_metric_filter( - logGroupName=logs_log_group, filterName=json_filter_name - ) - - response = aws_client.logs.describe_metric_filters( - logGroupName=logs_log_group, filterNamePrefix="test-filter-" - ) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - filter_names = [_filter["filterName"] for _filter in response["metricFilters"]] - assert basic_filter_name not in filter_names - assert json_filter_name not in filter_names - - @markers.aws.needs_fixing - def test_delivery_logs_for_sns(self, sns_create_topic, sns_subscription, aws_client): - topic_name = f"test-logs-{short_uid()}" - contact = "+10123456789" - - topic_arn = sns_create_topic(Name=topic_name)["TopicArn"] - sns_subscription(TopicArn=topic_arn, Protocol="sms", Endpoint=contact) - - message = "Good news everyone!" - aws_client.sns.publish(Message=message, TopicArn=topic_arn) - logs_group_name = topic_arn.replace("arn:aws:", "").replace(":", "/") - - def log_group_exists(): - # TODO on AWS the log group is not created, probably need iam role - # see also https://repost.aws/knowledge-center/monitor-sns-texts-cloudwatch - response = aws_client.logs.describe_log_streams(logGroupName=logs_group_name) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - - retry( - log_group_exists, - retries=20 if is_aws() else 3, - sleep=5 if is_aws() else 1, - sleep_before=3 if is_aws() else 0, - ) diff --git a/tests/aws/services/logs/test_logs.snapshot.json b/tests/aws/services/logs/test_logs.snapshot.json deleted file mode 100644 index 9f6e7b429a931..0000000000000 --- a/tests/aws/services/logs/test_logs.snapshot.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_lambda": { - "recorded-date": "17-03-2023, 13:55:00", - "recorded-content": { - "add_permission": { - "Statement": { - "Sid": "", - "Effect": "Allow", - "Principal": { - "Service": "logs..amazonaws.com" - }, - "Action": "lambda:InvokeFunction", - "Resource": "arn::lambda::111111111111:function:", - "Condition": { - "StringEquals": { - "AWS:SourceAccount": "111111111111" - }, - "ArnLike": { - "AWS:SourceArn": "arn::logs::111111111111:log-group::" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 201 - } - }, - "put_subscription_filter": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_subscription_filter": { - "subscriptionFilters": [ - { - "creationTime": "timestamp", - "destinationArn": "arn::lambda::111111111111:function:", - "distribution": "ByLogStream", - "filterName": "test", - "filterPattern": "", - "logGroupName": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_all_log_events": [ - { - "id": "id", - "timestamp": "timestamp", - "message": "test" - }, - { - "id": "id", - "timestamp": "timestamp", - "message": "test 2" - } - ] - } - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_list_tags_log_group": { - "recorded-date": "22-12-2022, 17:46:54", - "recorded-content": { - "list_tags_after_create_log_group": { - "tags": { - "env": "testing1" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_log_group_after_tag_resource": { - "tags": { - "env": "testing1", - "test1": "val1", - "test2": "val2" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_for_resource_after_tag_resource": { - "tags": { - "env": "testing1", - "test1": "val1", - "test2": "val2" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_log_group_after_tag_log_group": { - "tags": { - "env": "testing1", - "test1": "val1", - "test2": "val2", - "test3": "val3" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_for_resource_after_tag_log_group": { - "tags": { - "env": "testing1", - "test1": "val1", - "test2": "val2", - "test3": "val3" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_log_group_after_untag": { - "tags": { - "test2": "val2" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_for_resource_after_untag": { - "tags": { - "test2": "val2" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_stream": { - "recorded-date": "06-04-2023, 11:42:42", - "recorded-content": { - "describe-log-groups-prefix": { - "logGroups": [ - { - "arn": "arn::logs::111111111111:log-group::*", - "creationTime": "timestamp", - "logGroupName": "", - "metricFilterCount": 0, - "storedBytes": 0 - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe-log-groups-pattern": { - "logGroups": [ - { - "arn": "arn::logs::111111111111:log-group::*", - "creationTime": "timestamp", - "logGroupName": "" - } - ], - "nextToken": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "error-describe-logs-group": { - "Error": { - "Code": "InvalidParameterException", - "Message": "LogGroup name prefix and LogGroup name pattern are mutually exclusive parameters." - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "logs_log_group": [ - { - "logStreamName": "", - "creationTime": "timestamp", - "arn": "arn::logs::111111111111:log-group::log-stream:", - "storedBytes": 0 - } - ], - "error-describe-logs-streams": { - "Error": { - "Code": "ValidationException", - "Message": "LogGroup name and LogGroup ARN are mutually exclusive parameters." - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "log_group_identifier": [ - { - "logStreamName": "", - "creationTime": "timestamp", - "arn": "arn::logs::111111111111:log-group::log-stream:", - "storedBytes": 0 - } - ], - "log_group_identifier-arn": [ - { - "logStreamName": "", - "creationTime": "timestamp", - "arn": "arn::logs::111111111111:log-group::log-stream:", - "storedBytes": 0 - } - ] - } - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_resource_does_not_exist": { - "recorded-date": "05-09-2024, 16:43:20", - "recorded-content": { - "error-log-group-does-not-exist": { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "The specified log group does not exist." - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "error-log-stream-does-not-exist": { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "The specified log stream does not exist." - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/logs/test_logs.validation.json b/tests/aws/services/logs/test_logs.validation.json deleted file mode 100644 index 4be0d1979501c..0000000000000 --- a/tests/aws/services/logs/test_logs.validation.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_group": { - "last_validated_date": "2024-05-24T13:57:11+00:00" - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_stream": { - "last_validated_date": "2023-04-06T09:42:42+00:00" - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_filter_log_events_response_header": { - "last_validated_date": "2024-05-24T13:58:30+00:00" - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_list_tags_log_group": { - "last_validated_date": "2022-12-22T16:46:54+00:00" - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_metric_filters": { - "last_validated_date": "2024-05-24T14:17:33+00:00" - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_events_multi_bytes_msg": { - "last_validated_date": "2024-05-24T14:23:19+00:00" - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_lambda": { - "last_validated_date": "2023-03-17T12:55:00+00:00" - }, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_resource_does_not_exist": { - "last_validated_date": "2024-09-05T16:43:20+00:00" - } -} diff --git a/tests/aws/services/logs/test_logs_delivery.py b/tests/aws/services/logs/test_logs_delivery.py new file mode 100644 index 0000000000000..95fe9b4901f44 --- /dev/null +++ b/tests/aws/services/logs/test_logs_delivery.py @@ -0,0 +1,549 @@ +"""Tests for CloudWatch Logs - Delivery operations (vended logs delivery).""" + +import json + +import pytest + +from localstack.testing.pytest import markers +from localstack.utils.common import short_uid + + +def get_delivery_destination_policy(region: str, account_id: str) -> str: + """Generate a delivery destination policy document.""" + return json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowLogDeliveryActions", + "Effect": "Allow", + "Principal": {"AWS": f"arn:aws:iam::{account_id}:root"}, + "Action": "logs:CreateDelivery", + "Resource": [ + f"arn:aws:logs:{region}:{account_id}:delivery-source:*", + f"arn:aws:logs:{region}:{account_id}:delivery:*", + f"arn:aws:logs:{region}:{account_id}:delivery-destination:*", + ], + } + ], + } + ) + + +class TestDeliveryDestinations: + """Tests for delivery destination operations.""" + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..deliveryDestination.tags"]) + def test_put_delivery_destination(self, aws_client, snapshot, cleanups, s3_bucket): + """Test creating a delivery destination.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + destination_name = f"test-dd-{short_uid()}" + + response = aws_client.logs.put_delivery_destination( + name=destination_name, + outputFormat="json", + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + tags={"key1": "value1"}, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer(snapshot.transform.regex(s3_bucket, "")) + snapshot.match("put-delivery-destination", response) + + @markers.aws.validated + def test_put_delivery_destination_invalid_format(self, aws_client, snapshot, s3_bucket): + """Test creating a delivery destination with invalid output format.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.put_delivery_destination( + name=f"test-dd-{short_uid()}", + outputFormat="foobar", # Invalid format + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + snapshot.match("error-invalid-format", ctx.value.response) + + @markers.aws.validated + def test_put_delivery_destination_update( + self, aws_client, snapshot, cleanups, s3_bucket, s3_create_bucket + ): + """Test updating a delivery destination.""" + destination_name = f"test-dd-{short_uid()}" + second_bucket = s3_create_bucket() + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer(snapshot.transform.regex(second_bucket, "")) + + # Create initial destination + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + # Update destination resource + response = aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{second_bucket}" + }, + ) + snapshot.match("put-delivery-destination-update", response) + + @markers.aws.validated + def test_get_delivery_destination(self, aws_client, snapshot, cleanups, s3_bucket): + """Test getting a delivery destination.""" + + destination_name = f"test-dd-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer(snapshot.transform.regex(s3_bucket, "")) + + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + response = aws_client.logs.get_delivery_destination(name=destination_name) + snapshot.match("get-delivery-destination", response) + assert response["deliveryDestination"]["name"] == destination_name + + @markers.aws.validated + def test_get_delivery_destination_not_found(self, aws_client, snapshot): + """Test getting a non-existent delivery destination.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.get_delivery_destination(name="foobar") + snapshot.match("error-not-found", ctx.value.response) + + @markers.aws.validated + def test_describe_delivery_destinations(self, aws_client, snapshot, cleanups, s3_bucket): + """Test describing delivery destinations.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(s3_bucket, "")) + destinations = [] + + for i in range(2): + destination_name = f"test-dd-{short_uid()}-{i}" + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + destinations.append(destination_name) + cleanups.append( + lambda dn=destination_name: aws_client.logs.delete_delivery_destination(name=dn) + ) + snapshot.add_transformer( + snapshot.transform.regex(destination_name, f"") + ) + + response = aws_client.logs.describe_delivery_destinations() + descriptions = [ + desc for desc in response["deliveryDestinations"] if desc["name"] in destinations + ] + + snapshot.match("describe-delivery-destinations", descriptions) + + @markers.aws.validated + def test_delete_delivery_destination(self, aws_client, snapshot, cleanups, s3_bucket): + """Test deleting a delivery destination.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + destination_name = f"test-dd-{short_uid()}" + + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + + # Delete destination + aws_client.logs.delete_delivery_destination(name=destination_name) + + # Verify deletion + with pytest.raises(Exception) as ctx: + aws_client.logs.get_delivery_destination(name=destination_name) + snapshot.match("error-after-delete", ctx.value.response) + + @markers.aws.validated + def test_delete_delivery_destination_not_found(self, aws_client, snapshot): + """Test deleting a non-existent delivery destination.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.delete_delivery_destination(name="foobar") + snapshot.match("error-not-found", ctx.value.response) + + +class TestDeliveryDestinationPolicies: + """Tests for delivery destination policy operations.""" + + @markers.aws.validated + def test_put_delivery_destination_policy( + self, aws_client, account_id, s3_bucket, region_name, snapshot, cleanups + ): + """Test putting a policy on a delivery destination.""" + destination_name = f"test-dd-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(s3_bucket, "")) + + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + policy = get_delivery_destination_policy(region_name, account_id) + response = aws_client.logs.put_delivery_destination_policy( + deliveryDestinationName=destination_name, + deliveryDestinationPolicy=policy, + ) + snapshot.match("put-delivery-destination-policy", response) + + @markers.aws.validated + def test_get_delivery_destination_policy( + self, aws_client, account_id, s3_bucket, region_name, snapshot, cleanups + ): + """Test getting a delivery destination policy.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + destination_name = f"test-dd-{short_uid()}" + + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + policy = get_delivery_destination_policy(region_name, account_id) + aws_client.logs.put_delivery_destination_policy( + deliveryDestinationName=destination_name, + deliveryDestinationPolicy=policy, + ) + + response = aws_client.logs.get_delivery_destination_policy( + deliveryDestinationName=destination_name + ) + snapshot.match("get-delivery-destination-policy", response) + + @markers.aws.validated + def test_delete_delivery_destination_policy( + self, aws_client, account_id, s3_bucket, region_name, snapshot, cleanups + ): + """Test deleting a delivery destination policy.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + destination_name = f"test-dd-{short_uid()}" + + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + policy = get_delivery_destination_policy(region_name, account_id) + aws_client.logs.put_delivery_destination_policy( + deliveryDestinationName=destination_name, + deliveryDestinationPolicy=policy, + ) + + # Delete policy + aws_client.logs.delete_delivery_destination_policy(deliveryDestinationName=destination_name) + + # Verify deletion + response = aws_client.logs.get_delivery_destination_policy( + deliveryDestinationName=destination_name + ) + assert response["policy"] == {} + + +class TestDeliverySources: + """Tests for delivery source operations.""" + + @markers.aws.needs_fixing # requires pro services + def test_put_delivery_source(self, aws_client, account_id, snapshot, cleanups): + """Test creating a delivery source.""" + source_name = f"test-ds-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(source_name, "")) + + response = aws_client.logs.put_delivery_source( + name=source_name, + resourceArn=f"arn:aws:cloudfront::{account_id}:distribution/E1Q5F5862X9VJ5", + logType="ACCESS_LOGS", + tags={"key1": "value1"}, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_source(name=source_name)) + snapshot.match("put-delivery-source", response) + + @markers.aws.validated + def test_put_delivery_source_invalid_resource(self, aws_client, snapshot): + """Test creating a delivery source with invalid resource ARN.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.put_delivery_source( + name=f"test-ds-{short_uid()}", + resourceArn="arn:aws:s3:::test-s3-bucket", # S3 cannot be a source + logType="ACCESS_LOGS", + ) + snapshot.match("error-invalid-resource", ctx.value.response) + + @markers.aws.needs_fixing # requires pro services + def test_get_delivery_source(self, aws_client, snapshot, cleanups, account_id): + """Test getting a delivery source.""" + source_name = f"test-ds-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(source_name, "")) + + aws_client.logs.put_delivery_source( + name=source_name, + resourceArn=f"arn:aws:cloudfront::{account_id}:distribution/E1Q5F5862X9VJ5", + logType="ACCESS_LOGS", + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_source(name=source_name)) + + response = aws_client.logs.get_delivery_source(name=source_name) + snapshot.match("get-delivery-source", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_get_delivery_source_not_found(self, aws_client, snapshot): + """Test getting a non-existent delivery source.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.get_delivery_source(name="foobar") + snapshot.match("error-not-found", ctx.value.response) + + @markers.aws.needs_fixing # requires pro services + def test_describe_delivery_sources(self, aws_client, snapshot, cleanups): + """Test describing delivery sources.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + sources = [] + + for i in range(2): + source_name = f"test-ds-{short_uid()}-{i}" + aws_client.logs.put_delivery_source( + name=source_name, + resourceArn="arn:aws:cloudfront::123456789012:distribution/E19DL18TOXN9JU", + logType="ACCESS_LOGS", + ) + sources.append(source_name) + snapshot.add_transformer(snapshot.transform.regex(source_name, f"")) + cleanups.append(lambda sn=source_name: aws_client.logs.delete_delivery_source(name=sn)) + + response = aws_client.logs.describe_delivery_sources() + snapshot.match("describe-delivery-sources", response) + assert len(response["deliverySources"]) >= 2 + + @markers.aws.needs_fixing # requires pro services + def test_delete_delivery_source(self, aws_client, snapshot, cleanups): + """Test deleting a delivery source.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + source_name = f"test-ds-{short_uid()}" + + aws_client.logs.put_delivery_source( + name=source_name, + resourceArn="arn:aws:cloudfront::123456789012:distribution/E1Q5F5862X9VJ5", + logType="ACCESS_LOGS", + ) + + # Delete source + aws_client.logs.delete_delivery_source(name=source_name) + + # Verify deletion + with pytest.raises(Exception) as ctx: + aws_client.logs.get_delivery_source(name=source_name) + snapshot.match("error-after-delete", ctx.value.response) + + +class TestDeliveries: + """Tests for delivery operations (linking sources to destinations).""" + + @markers.aws.needs_fixing # requires pro services + def test_create_delivery( + self, aws_client, account_id, s3_bucket, region_name, snapshot, cleanups + ): + """Test creating a delivery.""" + source_name = f"test-ds-{short_uid()}" + destination_name = f"test-dd-{short_uid()}" + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.key_value("id")) + snapshot.add_transformer(snapshot.transform.regex(source_name, "")) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + + # Create source + aws_client.logs.put_delivery_source( + name=source_name, + resourceArn=f"arn:aws:cloudfront::{account_id}:distribution/E19DL18TOXN9JU", + logType="ACCESS_LOGS", + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_source(name=source_name)) + + # Create destination + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + # Create delivery + destination_arn = ( + f"arn:aws:logs:{region_name}:{account_id}:delivery-destination:{destination_name}" + ) + response = aws_client.logs.create_delivery( + deliverySourceName=source_name, + deliveryDestinationArn=destination_arn, + recordFields=["date"], + fieldDelimiter=",", + s3DeliveryConfiguration={ + "suffixPath": f"AWSLogs/{account_id}/CloudFront/", + "enableHiveCompatiblePath": True, + }, + tags={"key1": "value1"}, + ) + delivery_id = response["delivery"]["id"] + cleanups.append(lambda: aws_client.logs.delete_delivery(id=delivery_id)) + snapshot.match("create-delivery", response) + + @markers.aws.needs_fixing # requires pro services + def test_get_delivery(self, aws_client, account_id, s3_bucket, region_name, snapshot, cleanups): + """Test getting a delivery.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + source_name = f"test-ds-{short_uid()}" + destination_name = f"test-dd-{short_uid()}" + + snapshot.add_transformer(snapshot.transform.key_value("id")) + snapshot.add_transformer(snapshot.transform.regex(source_name, "")) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + + # Create source and destination + aws_client.logs.put_delivery_source( + name=source_name, + resourceArn=f"arn:aws:cloudfront::{account_id}:distribution/E19DL18TOXN9JU", + logType="ACCESS_LOGS", + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_source(name=source_name)) + + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + # Create delivery + destination_arn = ( + f"arn:aws:logs:{region_name}:{account_id}:delivery-destination:{destination_name}" + ) + create_response = aws_client.logs.create_delivery( + deliverySourceName=source_name, + deliveryDestinationArn=destination_arn, + ) + delivery_id = create_response["delivery"]["id"] + cleanups.append(lambda: aws_client.logs.delete_delivery(id=delivery_id)) + + # Get delivery + response = aws_client.logs.get_delivery(id=delivery_id) + snapshot.match("get-delivery", response) + + @markers.aws.needs_fixing # requires pro services + def test_describe_deliveries( + self, aws_client, account_id, s3_bucket, region_name, snapshot, cleanups + ): + """Test describing deliveries.""" + source_name = f"test-ds-{short_uid()}" + destination_name = f"test-dd-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.key_value("id")) + snapshot.add_transformer(snapshot.transform.regex(source_name, "")) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + + # Create source + aws_client.logs.put_delivery_source( + name=source_name, + resourceArn=f"arn:aws:cloudfront::{account_id}:distribution/E19DL18TOXN9JU", + logType="ACCESS_LOGS", + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_source(name=source_name)) + + # Create two destinations and deliveries + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append( + lambda dn=destination_name: aws_client.logs.delete_delivery_destination(name=dn) + ) + + destination_arn = ( + f"arn:aws:logs:{region_name}:{account_id}:delivery-destination:{destination_name}" + ) + create_response = aws_client.logs.create_delivery( + deliverySourceName=source_name, + deliveryDestinationArn=destination_arn, + ) + delivery_id = create_response["delivery"]["id"] + cleanups.append(lambda: aws_client.logs.delete_delivery(id=delivery_id)) + + response = aws_client.logs.describe_deliveries() + descriptions = [desc for desc in response["deliveries"] if desc["id"] == delivery_id] + snapshot.match("describe-deliveries", descriptions) + + @markers.aws.needs_fixing # requires pro services + def test_delete_delivery( + self, aws_client, account_id, s3_bucket, region_name, snapshot, cleanups + ): + """Test deleting a delivery.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + source_name = f"test-ds-{short_uid()}" + destination_name = f"test-dd-{short_uid()}" + + # Create source and destination + aws_client.logs.put_delivery_source( + name=source_name, + resourceArn=f"arn:aws:cloudfront::{account_id}:distribution/E19DL18TOXN9JU", + logType="ACCESS_LOGS", + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_source(name=source_name)) + + aws_client.logs.put_delivery_destination( + name=destination_name, + deliveryDestinationConfiguration={ + "destinationResourceArn": f"arn:aws:s3:::{s3_bucket}" + }, + ) + cleanups.append(lambda: aws_client.logs.delete_delivery_destination(name=destination_name)) + + # Create delivery + destination_arn = ( + f"arn:aws:logs:{region_name}:{account_id}:delivery-destination:{destination_name}" + ) + create_response = aws_client.logs.create_delivery( + deliverySourceName=source_name, + deliveryDestinationArn=destination_arn, + ) + delivery_id = create_response["delivery"]["id"] + + # Delete delivery + aws_client.logs.delete_delivery(id=delivery_id) + + # Verify deletion + with pytest.raises(Exception) as ctx: + aws_client.logs.get_delivery(id=delivery_id) + snapshot.match("error-after-delete", ctx.value.response) diff --git a/tests/aws/services/logs/test_logs_delivery.snapshot.json b/tests/aws/services/logs/test_logs_delivery.snapshot.json new file mode 100644 index 0000000000000..d32aff83be339 --- /dev/null +++ b/tests/aws/services/logs/test_logs_delivery.snapshot.json @@ -0,0 +1,482 @@ +{ + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_put_delivery_destination": { + "recorded-date": "04-02-2026, 18:41:17", + "recorded-content": { + "put-delivery-destination": { + "deliveryDestination": { + "arn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationConfiguration": { + "destinationResourceArn": "arn::s3:::" + }, + "deliveryDestinationType": "S3", + "name": "", + "outputFormat": "json" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_put_delivery_destination_invalid_format": { + "recorded-date": "04-02-2026, 18:42:27", + "recorded-content": { + "error-invalid-format": { + "Error": { + "Code": "ValidationException", + "Message": "1 validation error detected: Value 'foobar' at 'outputFormat' failed to satisfy constraint: Member must satisfy enum value set: [w3c, raw, json, plain, parquet]" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_put_delivery_destination_update": { + "recorded-date": "04-02-2026, 18:45:52", + "recorded-content": { + "put-delivery-destination-update": { + "deliveryDestination": { + "arn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationConfiguration": { + "destinationResourceArn": "arn::s3:::" + }, + "deliveryDestinationType": "S3", + "name": "" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_get_delivery_destination": { + "recorded-date": "04-02-2026, 18:47:50", + "recorded-content": { + "get-delivery-destination": { + "deliveryDestination": { + "arn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationConfiguration": { + "destinationResourceArn": "arn::s3:::" + }, + "deliveryDestinationType": "S3", + "name": "" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_get_delivery_destination_not_found": { + "recorded-date": "04-02-2026, 18:48:32", + "recorded-content": { + "error-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested Delivery Destination does not exist in this account." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_describe_delivery_destinations": { + "recorded-date": "04-02-2026, 18:54:13", + "recorded-content": { + "describe-delivery-destinations": [ + { + "name": "", + "arn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationType": "S3", + "deliveryDestinationConfiguration": { + "destinationResourceArn": "arn::s3:::" + } + }, + { + "name": "", + "arn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationType": "S3", + "deliveryDestinationConfiguration": { + "destinationResourceArn": "arn::s3:::" + } + } + ] + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_delete_delivery_destination": { + "recorded-date": "04-02-2026, 18:57:39", + "recorded-content": { + "error-after-delete": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested Delivery Destination does not exist in this account." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_delete_delivery_destination_not_found": { + "recorded-date": "04-02-2026, 18:58:37", + "recorded-content": { + "error-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested Delivery Destination does not exist in this account." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinationPolicies::test_put_delivery_destination_policy": { + "recorded-date": "04-02-2026, 19:02:04", + "recorded-content": { + "put-delivery-destination-policy": { + "policy": { + "deliveryDestinationPolicy": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowLogDeliveryActions", + "Effect": "Allow", + "Principal": { + "AWS": "arn::iam::111111111111:root" + }, + "Action": "logs:CreateDelivery", + "Resource": [ + "arn::logs::111111111111:delivery-source:*", + "arn::logs::111111111111:delivery:*", + "arn::logs::111111111111:delivery-destination:*" + ] + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinationPolicies::test_get_delivery_destination_policy": { + "recorded-date": "04-02-2026, 19:03:14", + "recorded-content": { + "get-delivery-destination-policy": { + "policy": { + "deliveryDestinationPolicy": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowLogDeliveryActions", + "Effect": "Allow", + "Principal": { + "AWS": "arn::iam::111111111111:root" + }, + "Action": "logs:CreateDelivery", + "Resource": [ + "arn::logs::111111111111:delivery-source:*", + "arn::logs::111111111111:delivery:*", + "arn::logs::111111111111:delivery-destination:*" + ] + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinationPolicies::test_delete_delivery_destination_policy": { + "recorded-date": "04-02-2026, 19:09:00", + "recorded-content": {} + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_put_delivery_source": { + "recorded-date": "04-02-2026, 19:21:24", + "recorded-content": { + "put-delivery-source": { + "deliverySource": { + "arn": "arn::logs::111111111111:delivery-source:", + "logType": "ACCESS_LOGS", + "name": "", + "resourceArns": [ + "arn::cloudfront::111111111111:distribution/E1Q5F5862X9VJ5" + ], + "service": "cloudfront", + "tags": { + "key1": "value1" + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_put_delivery_source_invalid_resource": { + "recorded-date": "04-02-2026, 19:22:17", + "recorded-content": { + "error-invalid-resource": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Cannot access provided service." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_get_delivery_source": { + "recorded-date": "04-02-2026, 19:28:05", + "recorded-content": { + "get-delivery-source": { + "deliverySource": { + "arn": "arn::logs::111111111111:delivery-source:", + "logType": "ACCESS_LOGS", + "name": "", + "resourceArns": [ + "arn::cloudfront::111111111111:distribution/E1Q5F5862X9VJ5" + ], + "service": "cloudfront" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_get_delivery_source_not_found": { + "recorded-date": "04-02-2026, 19:28:39", + "recorded-content": { + "error-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested Delivery Source does not exist in this account." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_describe_delivery_sources": { + "recorded-date": "04-02-2026, 19:32:38", + "recorded-content": { + "describe-delivery-sources": { + "deliverySources": [ + { + "arn": "arn::logs::111111111111:delivery-source:", + "logType": "ACCESS_LOGS", + "name": "", + "resourceArns": [ + "arn::cloudfront::123456789012:distribution/E19DL18TOXN9JU" + ], + "service": "cloudfront" + }, + { + "arn": "arn::logs::111111111111:delivery-source:", + "logType": "ACCESS_LOGS", + "name": "", + "resourceArns": [ + "arn::cloudfront::123456789012:distribution/E19DL18TOXN9JU" + ], + "service": "cloudfront" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_delete_delivery_source": { + "recorded-date": "04-02-2026, 19:33:56", + "recorded-content": { + "error-after-delete": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested Delivery Source does not exist in this account.." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveries::test_create_delivery": { + "recorded-date": "04-02-2026, 19:50:15", + "recorded-content": { + "create-delivery": { + "delivery": { + "arn": "arn::logs::111111111111:delivery:", + "deliveryDestinationArn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationType": "S3", + "deliverySourceName": "", + "fieldDelimiter": ",", + "id": "", + "recordFields": [ + "date" + ], + "s3DeliveryConfiguration": { + "enableHiveCompatiblePath": true, + "suffixPath": "AWSLogs/111111111111/CloudFront/" + }, + "tags": { + "key1": "value1" + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveries::test_get_delivery": { + "recorded-date": "04-02-2026, 19:51:24", + "recorded-content": { + "get-delivery": { + "delivery": { + "arn": "arn::logs::111111111111:delivery:", + "deliveryDestinationArn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationType": "S3", + "deliverySourceName": "", + "id": "", + "recordFields": [ + "date", + "time", + "x-edge-location", + "sc-bytes", + "c-ip", + "cs-method", + "cs(Host)", + "cs-uri-stem", + "sc-status", + "cs(Referer)", + "cs(User-Agent)", + "cs-uri-query", + "cs(Cookie)", + "x-edge-result-type", + "x-edge-request-id", + "x-host-header", + "cs-protocol", + "cs-bytes", + "time-taken", + "x-forwarded-for", + "ssl-protocol", + "ssl-cipher", + "x-edge-response-result-type", + "cs-protocol-version", + "fle-status", + "fle-encrypted-fields", + "c-port", + "time-to-first-byte", + "x-edge-detailed-result-type", + "sc-content-type", + "sc-content-len", + "sc-range-start", + "sc-range-end" + ], + "s3DeliveryConfiguration": { + "enableHiveCompatiblePath": false, + "suffixPath": "AWSLogs/{account-id}/CloudFront/" + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveries::test_describe_deliveries": { + "recorded-date": "04-02-2026, 20:05:35", + "recorded-content": { + "describe-deliveries": [ + { + "id": "", + "arn": "arn::logs::111111111111:delivery:", + "deliverySourceName": "", + "deliveryDestinationArn": "arn::logs::111111111111:delivery-destination:", + "deliveryDestinationType": "S3", + "recordFields": [ + "date", + "time", + "x-edge-location", + "sc-bytes", + "c-ip", + "cs-method", + "cs(Host)", + "cs-uri-stem", + "sc-status", + "cs(Referer)", + "cs(User-Agent)", + "cs-uri-query", + "cs(Cookie)", + "x-edge-result-type", + "x-edge-request-id", + "x-host-header", + "cs-protocol", + "cs-bytes", + "time-taken", + "x-forwarded-for", + "ssl-protocol", + "ssl-cipher", + "x-edge-response-result-type", + "cs-protocol-version", + "fle-status", + "fle-encrypted-fields", + "c-port", + "time-to-first-byte", + "x-edge-detailed-result-type", + "sc-content-type", + "sc-content-len", + "sc-range-start", + "sc-range-end" + ], + "s3DeliveryConfiguration": { + "suffixPath": "AWSLogs/{account-id}/CloudFront/", + "enableHiveCompatiblePath": false + } + } + ] + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveries::test_delete_delivery": { + "recorded-date": "04-02-2026, 20:06:50", + "recorded-content": { + "error-after-delete": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Requested Delivery does not exist in this account." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_delivery.validation.json b/tests/aws/services/logs/test_logs_delivery.validation.json new file mode 100644 index 0000000000000..4087640cdd050 --- /dev/null +++ b/tests/aws/services/logs/test_logs_delivery.validation.json @@ -0,0 +1,119 @@ +{ + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinationPolicies::test_delete_delivery_destination_policy": { + "last_validated_date": "2026-02-04T19:09:01+00:00", + "durations_in_seconds": { + "setup": 0.82, + "call": 0.9, + "teardown": 0.57, + "total": 2.29 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinationPolicies::test_get_delivery_destination_policy": { + "last_validated_date": "2026-02-04T19:03:14+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 0.73, + "teardown": 0.68, + "total": 2.36 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinationPolicies::test_put_delivery_destination_policy": { + "last_validated_date": "2026-02-04T19:02:04+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 0.62, + "teardown": 0.74, + "total": 2.24 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_delete_delivery_destination": { + "last_validated_date": "2026-02-04T18:57:39+00:00", + "durations_in_seconds": { + "setup": 0.89, + "call": 0.78, + "teardown": 0.49, + "total": 2.16 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_delete_delivery_destination_not_found": { + "last_validated_date": "2026-02-04T18:58:37+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 0.44, + "teardown": 0.0, + "total": 0.81 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_describe_delivery_destinations": { + "last_validated_date": "2026-02-04T18:54:13+00:00", + "durations_in_seconds": { + "setup": 0.85, + "call": 0.82, + "teardown": 0.82, + "total": 2.49 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_get_delivery_destination": { + "last_validated_date": "2026-02-04T18:47:50+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 0.64, + "teardown": 0.67, + "total": 2.18 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_get_delivery_destination_not_found": { + "last_validated_date": "2026-02-04T18:48:32+00:00", + "durations_in_seconds": { + "setup": 0.36, + "call": 0.42, + "teardown": 0.0, + "total": 0.78 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_put_delivery_destination": { + "last_validated_date": "2026-02-04T18:41:17+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 0.49, + "teardown": 0.69, + "total": 2.06 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_put_delivery_destination_invalid_format": { + "last_validated_date": "2026-02-04T18:42:27+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 0.39, + "teardown": 0.52, + "total": 1.85 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliveryDestinations::test_put_delivery_destination_update": { + "last_validated_date": "2026-02-04T18:45:52+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 1.09, + "teardown": 1.18, + "total": 3.14 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_get_delivery_source_not_found": { + "last_validated_date": "2026-02-04T19:28:51+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 0.43, + "teardown": 0.0, + "total": 0.8 + } + }, + "tests/aws/services/logs/test_logs_delivery.py::TestDeliverySources::test_put_delivery_source_invalid_resource": { + "last_validated_date": "2026-02-04T19:22:17+00:00", + "durations_in_seconds": { + "setup": 0.39, + "call": 0.43, + "teardown": 0.0, + "total": 0.82 + } + } +} diff --git a/tests/aws/services/logs/test_logs_destinations.py b/tests/aws/services/logs/test_logs_destinations.py new file mode 100644 index 0000000000000..9b3d447c3d232 --- /dev/null +++ b/tests/aws/services/logs/test_logs_destinations.py @@ -0,0 +1,313 @@ +"""Tests for CloudWatch Logs - Destination operations (cross-account log delivery).""" + +import json + +import pytest + +from localstack.testing.aws.util import is_aws_cloud +from localstack.testing.pytest import markers +from localstack.utils.common import short_uid +from localstack.utils.sync import retry + +ACCESS_POLICY_DOC = json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": "logs.amazonaws.com"}, + "Action": "logs:PutSubscriptionFilter", + "Resource": "destination_arn", + } + ], + } +) + + +@pytest.fixture +def kinesis_stream_arn(aws_client, kinesis_create_stream, wait_for_stream_ready) -> str: + stream_name = kinesis_create_stream() + wait_for_stream_ready(stream_name) + return aws_client.kinesis.describe_stream(StreamName=stream_name)["StreamDescription"][ + "StreamARN" + ] + + +@pytest.fixture +def destination_role(aws_client, create_iam_role_with_policy, kinesis_stream_arn): + role = create_iam_role_with_policy( + RoleName=f"role-logs-{short_uid()}", + PolicyName=f"policy-logs-{short_uid()}", + RoleDefinition={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "logs.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + }, + PolicyDefinition={ + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "kinesis:*", "Resource": kinesis_stream_arn} + ], + }, + ) + + if is_aws_cloud(): + # This operation is to confirm role propagation withing AWS + def _assume_role(): + try: + aws_client.sts.assume_role( + RoleArn=role, + RoleSessionName="check", + ) + return True + except Exception: + # AccessDenied means role exists but we can't assume it (expected for service roles) + # Other errors might mean role not propagated yet + return False + + retry(_assume_role, sleep_before=1, sleep=2, retries=10) + return role + + +def _retry_put_destination(aws_client, **kwargs): + def put_destination(): + resp = aws_client.logs.put_destination( + destinationName=kwargs["destinationName"], + targetArn=kwargs["targetArn"], + roleArn=kwargs["roleArn"], + tags=kwargs.get("tags", {"tag": "test"}), + ) + return resp + + return retry(put_destination, retries=5, sleep=3 if is_aws_cloud() else 1) + + +class TestDestinations: + """Tests for destination operations.""" + + @markers.aws.validated + def test_put_destination( + self, aws_client, snapshot, cleanups, kinesis_stream_arn, destination_role + ): + """Test creating a destination.""" + destination_name = f"test-destination-{short_uid()}" + role_arn = destination_role + target_arn = kinesis_stream_arn + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer(snapshot.transform.regex(role_arn.split("/")[-1], "")) + snapshot.add_transformer( + snapshot.transform.regex(target_arn.split("/")[-1], "") + ) + + response = _retry_put_destination( + aws_client, + destinationName=destination_name, + targetArn=target_arn, + roleArn=role_arn, + tags={"Name": destination_name}, + ) + cleanups.append( + lambda: aws_client.logs.delete_destination(destinationName=destination_name) + ) + + # IAM Role takes time to propagate in AWS causing a client error + snapshot.match("put-destination", response) + + @markers.aws.validated + def test_describe_destinations_empty(self, aws_client, snapshot): + """Test describing destinations when none exist with a given prefix.""" + response = aws_client.logs.describe_destinations( + DestinationNamePrefix=f"non-existent-{short_uid()}" + ) + snapshot.match("describe-destinations-empty", response) + + @markers.aws.validated + def test_describe_destinations_with_prefix( + self, aws_client, snapshot, destination_role, kinesis_stream_arn, cleanups + ): + """Test describing destinations with prefix filter.""" + prefix = f"test-dest-{short_uid()}" + role_arn = destination_role + target_arn = kinesis_stream_arn + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(prefix, "")) + snapshot.add_transformer(snapshot.transform.regex(role_arn.split("/")[-1], "")) + snapshot.add_transformer( + snapshot.transform.regex(target_arn.split("/")[-1], "") + ) + + _retry_put_destination( + aws_client, + destinationName=prefix, + targetArn=target_arn, + roleArn=role_arn, + tags={"Name": prefix}, + ) + cleanups.append(lambda: aws_client.logs.delete_destination(destinationName=prefix)) + + response = aws_client.logs.describe_destinations(DestinationNamePrefix=prefix) + snapshot.match("describe-destinations", response) + + @markers.aws.validated + def test_update_destination( + self, aws_client, snapshot, destination_role, kinesis_stream_arn, cleanups + ): + """Test updating a destination's target and role ARNs.""" + destination_name = f"test-destination-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer( + snapshot.transform.regex(destination_role.split("/")[-1], "") + ) + snapshot.add_transformer( + snapshot.transform.regex(kinesis_stream_arn.split("/")[-1], "") + ) + + _retry_put_destination( + aws_client, + destinationName=destination_name, + roleArn=destination_role, + targetArn=kinesis_stream_arn, + ) + cleanups.append( + lambda: aws_client.logs.delete_destination(destinationName=destination_name) + ) + + response = aws_client.logs.describe_destinations(DestinationNamePrefix=destination_name) + snapshot.match("original-description", response) + + # Update destination + _retry_put_destination( + aws_client, + destinationName=destination_name, + roleArn=destination_role, + targetArn=kinesis_stream_arn, + tags={"tag1": "value1"}, + ) + + # Verify update + response = aws_client.logs.describe_destinations(DestinationNamePrefix=destination_name) + snapshot.match("updated-description", response) + + @markers.aws.validated + def test_put_destination_policy( + self, aws_client, snapshot, destination_role, kinesis_stream_arn, cleanups + ): + """Test setting an access policy on a destination.""" + destination_name = f"test-destination-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer( + snapshot.transform.regex(destination_role.split("/")[-1], "") + ) + snapshot.add_transformer( + snapshot.transform.regex(kinesis_stream_arn.split("/")[-1], "") + ) + + _retry_put_destination( + aws_client, + destinationName=destination_name, + targetArn=kinesis_stream_arn, + roleArn=destination_role, + ) + cleanups.append( + lambda: aws_client.logs.delete_destination(destinationName=destination_name) + ) + + # Put access policy + policy = ACCESS_POLICY_DOC.replace("destination_arn", kinesis_stream_arn) + + response = aws_client.logs.put_destination_policy( + destinationName=destination_name, accessPolicy=policy + ) + snapshot.match("put-destination-policy", response) + + # Verify policy is set + response = aws_client.logs.describe_destinations(DestinationNamePrefix=destination_name) + snapshot.match("destination-with-policy", response) + + @markers.aws.validated + def test_delete_destination( + self, aws_client, destination_role, kinesis_stream_arn, snapshot, cleanups + ): + """Test deleting a destination.""" + destination_name = f"test-dest-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer( + snapshot.transform.regex(destination_role.split("/")[-1], "") + ) + snapshot.add_transformer( + snapshot.transform.regex(kinesis_stream_arn.split("/")[-1], "") + ) + + _retry_put_destination( + aws_client, + destinationName=destination_name, + targetArn=kinesis_stream_arn, + roleArn=destination_role, + ) + + # Delete destination + response = aws_client.logs.delete_destination(destinationName=destination_name) + snapshot.match("delete-destination", response) + + # Verify deletion + response = aws_client.logs.describe_destinations(DestinationNamePrefix=destination_name) + snapshot.match("no-destination-found", response) + + +class TestDestinationsTags: + """Tests for destination tagging operations.""" + + @markers.aws.validated + @pytest.mark.skip(reason="TODO list tags returns empty") + def test_destination_tags( + self, aws_client, snapshot, cleanups, destination_role, kinesis_stream_arn + ): + """Test tagging operations on destinations.""" + destination_name = f"test-destination-{short_uid()}" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(destination_name, "")) + snapshot.add_transformer( + snapshot.transform.regex(destination_role.split("/")[-1], "") + ) + snapshot.add_transformer( + snapshot.transform.regex(kinesis_stream_arn.split("/")[-1], "") + ) + + # Create destination with initial tag + response = _retry_put_destination( + aws_client, + destinationName=destination_name, + targetArn=kinesis_stream_arn, + roleArn=destination_role, + tags={"key1": "val1"}, + ) + destination_arn = response["destination"]["arn"] + cleanups.append( + lambda: aws_client.logs.delete_destination(destinationName=destination_name) + ) + + # Add more tags + aws_client.logs.tag_resource(resourceArn=destination_arn, tags={"key2": "val2"}) + + # List tags + response = aws_client.logs.list_tags_for_resource(resourceArn=destination_arn) + snapshot.match("list-tags-after-add", response) + assert response["tags"] == {"key1": "val1", "key2": "val2"} + + # Remove a tag + aws_client.logs.untag_resource(resourceArn=destination_arn, tagKeys=["key2"]) + + response = aws_client.logs.list_tags_for_resource(resourceArn=destination_arn) + snapshot.match("list-tags-after-remove", response) + assert response["tags"] == {"key1": "val1"} diff --git a/tests/aws/services/logs/test_logs_destinations.snapshot.json b/tests/aws/services/logs/test_logs_destinations.snapshot.json new file mode 100644 index 0000000000000..63dd3ac37e65f --- /dev/null +++ b/tests/aws/services/logs/test_logs_destinations.snapshot.json @@ -0,0 +1,168 @@ +{ + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_put_destination": { + "recorded-date": "05-02-2026, 15:14:39", + "recorded-content": { + "put-destination": { + "destination": { + "arn": "arn::logs::111111111111:destination:", + "creationTime": "timestamp", + "destinationName": "", + "roleArn": "arn::iam::111111111111:role/", + "targetArn": "arn::kinesis::111111111111:stream/" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_describe_destinations_empty": { + "recorded-date": "04-02-2026, 21:49:47", + "recorded-content": { + "describe-destinations-empty": { + "destinations": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_describe_destinations_with_prefix": { + "recorded-date": "05-02-2026, 15:15:49", + "recorded-content": { + "describe-destinations": { + "destinations": [ + { + "arn": "arn::logs::111111111111:destination:", + "creationTime": "timestamp", + "destinationName": "", + "roleArn": "arn::iam::111111111111:role/", + "targetArn": "arn::kinesis::111111111111:stream/" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_update_destination": { + "recorded-date": "05-02-2026, 15:16:48", + "recorded-content": { + "original-description": { + "destinations": [ + { + "arn": "arn::logs::111111111111:destination:", + "creationTime": "timestamp", + "destinationName": "", + "roleArn": "arn::iam::111111111111:role/", + "targetArn": "arn::kinesis::111111111111:stream/" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "updated-description": { + "destinations": [ + { + "arn": "arn::logs::111111111111:destination:", + "creationTime": "timestamp", + "destinationName": "", + "roleArn": "arn::iam::111111111111:role/", + "targetArn": "arn::kinesis::111111111111:stream/" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_put_destination_policy": { + "recorded-date": "18-02-2026, 19:55:08", + "recorded-content": { + "put-destination-policy": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "destination-with-policy": { + "destinations": [ + { + "accessPolicy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "logs.amazonaws.com" + }, + "Action": "logs:PutSubscriptionFilter", + "Resource": "arn::kinesis::111111111111:stream/" + } + ] + }, + "arn": "arn::logs::111111111111:destination:", + "creationTime": "timestamp", + "destinationName": "", + "roleArn": "arn::iam::111111111111:role/", + "targetArn": "arn::kinesis::111111111111:stream/" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_delete_destination": { + "recorded-date": "05-02-2026, 15:41:43", + "recorded-content": { + "delete-destination": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "no-destination-found": { + "destinations": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinationsTags::test_destination_tags": { + "recorded-date": "05-02-2026, 15:45:33", + "recorded-content": { + "list-tags-after-add": { + "tags": { + "key1": "val1", + "key2": "val2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-after-remove": { + "tags": { + "key1": "val1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_destinations.validation.json b/tests/aws/services/logs/test_logs_destinations.validation.json new file mode 100644 index 0000000000000..ef5e13e7e66d7 --- /dev/null +++ b/tests/aws/services/logs/test_logs_destinations.validation.json @@ -0,0 +1,65 @@ +{ + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_delete_destination": { + "last_validated_date": "2026-02-05T15:41:43+00:00", + "durations_in_seconds": { + "setup": 7.86, + "call": 10.18, + "teardown": 0.68, + "total": 18.72 + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_describe_destinations_empty": { + "last_validated_date": "2026-02-04T21:49:47+00:00", + "durations_in_seconds": { + "setup": 0.38, + "call": 0.38, + "teardown": 0.0, + "total": 0.76 + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_describe_destinations_with_prefix": { + "last_validated_date": "2026-02-05T15:15:49+00:00", + "durations_in_seconds": { + "setup": 6.65, + "call": 10.14, + "teardown": 0.81, + "total": 17.6 + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_put_destination": { + "last_validated_date": "2026-02-05T15:14:39+00:00", + "durations_in_seconds": { + "setup": 7.3, + "call": 6.85, + "teardown": 0.83, + "total": 14.98 + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_put_destination_policy": { + "last_validated_date": "2026-02-18T19:55:08+00:00", + "durations_in_seconds": { + "setup": 6.6, + "call": 10.21, + "teardown": 0.76, + "total": 17.57 + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinations::test_update_destination": { + "last_validated_date": "2026-02-05T15:16:48+00:00", + "durations_in_seconds": { + "setup": 6.66, + "call": 7.16, + "teardown": 0.95, + "total": 14.77 + } + }, + "tests/aws/services/logs/test_logs_destinations.py::TestDestinationsTags::test_destination_tags": { + "last_validated_date": "2026-02-05T15:45:33+00:00", + "durations_in_seconds": { + "setup": 6.12, + "call": 7.15, + "teardown": 0.94, + "total": 14.21 + } + } +} diff --git a/tests/aws/services/logs/test_logs_events.py b/tests/aws/services/logs/test_logs_events.py new file mode 100644 index 0000000000000..97a44bc704b38 --- /dev/null +++ b/tests/aws/services/logs/test_logs_events.py @@ -0,0 +1,270 @@ +"""Tests for CloudWatch Logs - Log Event operations.""" + +import pytest +from localstack_snapshot.pytest.snapshot import is_aws + +from localstack.testing.pytest import markers +from localstack.utils.common import now_utc, retry, short_uid + + +class TestLogsEvents: + """Tests for put and get log event operations.""" + + @markers.aws.validated + def test_put_log_events_basic(self, logs_log_group, logs_log_stream, aws_client): + """Test basic log event insertion.""" + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp, "message": "hello"}, + {"timestamp": timestamp, "message": "world"}, + ] + + response = aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + # Verify nextSequenceToken is returned + next_sequence_token = response.get("nextSequenceToken") + if next_sequence_token: + assert isinstance(next_sequence_token, str) + + @markers.aws.validated + def test_put_events_multi_bytes_msg(self, logs_log_group, logs_log_stream, aws_client): + """Test log events with multi-byte characters (Unicode).""" + body_msg = "🙀 - 参よ - 日本語" + events = [{"timestamp": now_utc(millis=True), "message": body_msg}] + + response = aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events + ) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + + def get_log_events(): + events = aws_client.logs.get_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream + )["events"] + assert events[0]["message"] == body_msg + + retry( + get_log_events, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_put_log_events_to_invalid_stream(self, logs_log_group, aws_client, snapshot): + """Test that putting events to non-existent stream raises error.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.put_log_events( + logGroupName=logs_log_group, + logStreamName="invalid-stream", + logEvents=[{"timestamp": now_utc(millis=True), "message": "test"}], + ) + snapshot.match("error-invalid-stream", ctx.value.response) + + @markers.aws.validated + def test_put_log_events_wrong_order( + self, logs_log_group, logs_log_stream, aws_client, snapshot + ): + """Test that events in wrong chronological order are rejected.""" + ts_1 = now_utc(millis=True) + ts_2 = now_utc(millis=True) - 86400000 * 2 # 2 days ago + + messages = [ + {"message": "Message 0", "timestamp": ts_1}, + {"message": "Message 1", "timestamp": ts_2}, # Older timestamp after newer one + ] + + with pytest.raises(Exception) as ctx: + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + snapshot.match("error-wrong-order", ctx.value.response) + + @markers.aws.validated + def test_put_log_events_too_old(self, logs_log_group, logs_log_stream, aws_client, snapshot): + """Test that events with timestamps too far in the past are rejected.""" + # Event from 15 days ago + timestamp = now_utc(millis=True) - 86400000 * 30 + + messages = [{"message": "Old message", "timestamp": timestamp}] + + response = aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + snapshot.add_transformer(snapshot.transform.key_value("nextSequenceToken")) + snapshot.add_transformer( + snapshot.transform.key_value("tooOldLogEventEndIndex", reference_replacement=False) + ) + snapshot.match("response-too-old", response) + + @markers.aws.validated + def test_put_log_events_too_new(self, logs_log_group, logs_log_stream, aws_client, snapshot): + """Test that events with timestamps too far in the future are rejected.""" + # Event 3 hours in the future (>180 minutes) + timestamp = now_utc(millis=True) + 86400000 * 1 + + messages = [{"message": "Future message", "timestamp": timestamp}] + + response = aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + snapshot.add_transformer(snapshot.transform.key_value("nextSequenceToken")) + snapshot.add_transformer( + snapshot.transform.key_value("tooNewLogEventStartIndex", reference_replacement=False) + ) + snapshot.match("response-too-new", response) + + @markers.aws.validated + def test_get_log_events_basic(self, logs_log_group, logs_log_stream, aws_client): + """Test basic log event retrieval.""" + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp, "message": "message 1"}, + {"timestamp": timestamp + 100, "message": "message 2"}, + ] + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + + def verify_events(): + response = aws_client.logs.get_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream + ) + events = response["events"] + assert len(events) == 2 + assert events[0]["message"] == "message 1" + assert events[1]["message"] == "message 2" + + retry( + verify_events, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_get_log_events_start_from_head(self, logs_log_group, logs_log_stream, aws_client): + """Test log event retrieval with startFromHead=True.""" + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp + i * 100, "message": f"message {i}"} for i in range(20) + ] + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + + def verify_events(): + response = aws_client.logs.get_log_events( + logGroupName=logs_log_group, + logStreamName=logs_log_stream, + limit=10, + startFromHead=True, + ) + events = response["events"] + assert len(events) == 10 + # First event should be message 0 when starting from head + assert events[0]["message"] == "message 0" + return response + + retry( + verify_events, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_get_log_events_invalid_token( + self, logs_log_group, logs_log_stream, aws_client, snapshot + ): + """Test that invalid nextToken is rejected.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.get_log_events( + logGroupName=logs_log_group, + logStreamName=logs_log_stream, + nextToken="invalid-token", + ) + snapshot.match("error-invalid-token", ctx.value.response) + + @markers.aws.validated + def test_get_log_events_limit_validation( + self, logs_log_group, logs_log_stream, aws_client, snapshot + ): + """Test that limit over 10000 is rejected.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.get_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, limit=10001 + ) + snapshot.match("error-limit-exceeded", ctx.value.response) + + @markers.aws.validated + def test_get_log_events_using_log_group_identifier( + self, logs_log_group, logs_log_stream, aws_client, region_name, account_id + ): + """Test getting log events using logGroupIdentifier parameter.""" + timestamp = now_utc(millis=True) + messages = [{"timestamp": timestamp, "message": "test message"}] + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + + def verify_events(): + # Using logGroupName + response1 = aws_client.logs.get_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream + ) + assert len(response1["events"]) >= 1 + + # Using logGroupIdentifier with name + response2 = aws_client.logs.get_log_events( + logGroupIdentifier=logs_log_group, logStreamName=logs_log_stream + ) + assert len(response2["events"]) >= 1 + + # Using logGroupIdentifier with ARN + log_group_arn = f"arn:aws:logs:{region_name}:{account_id}:log-group:{logs_log_group}" + response3 = aws_client.logs.get_log_events( + logGroupIdentifier=log_group_arn, logStreamName=logs_log_stream + ) + assert len(response3["events"]) >= 1 + + retry( + verify_events, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + +class TestLogsEventsErrors: + """Tests for log event error handling.""" + + @markers.aws.validated + def test_resource_does_not_exist(self, aws_client, snapshot, cleanups): + """Test error handling when log group or stream doesn't exist.""" + log_group_name = f"log-group-{short_uid()}" + log_stream_name = f"log-stream-{short_uid()}" + + # Log group doesn't exist + with pytest.raises(Exception) as ctx: + aws_client.logs.get_log_events( + logGroupName=log_group_name, logStreamName=log_stream_name + ) + snapshot.match("error-log-group-does-not-exist", ctx.value.response) + + # Create log group + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + # Log stream doesn't exist + with pytest.raises(Exception) as ctx: + aws_client.logs.get_log_events( + logGroupName=log_group_name, logStreamName=log_stream_name + ) + snapshot.match("error-log-stream-does-not-exist", ctx.value.response) diff --git a/tests/aws/services/logs/test_logs_events.snapshot.json b/tests/aws/services/logs/test_logs_events.snapshot.json new file mode 100644 index 0000000000000..c0e7fa7fd67cd --- /dev/null +++ b/tests/aws/services/logs/test_logs_events.snapshot.json @@ -0,0 +1,117 @@ +{ + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_to_invalid_stream": { + "recorded-date": "03-02-2026, 21:36:32", + "recorded-content": { + "error-invalid-stream": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified log stream does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_wrong_order": { + "recorded-date": "03-02-2026, 21:36:33", + "recorded-content": { + "error-wrong-order": { + "Error": { + "Code": "InvalidParameterException", + "Message": "Log events in a single PutLogEvents request must be in chronological order." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_too_old": { + "recorded-date": "11-02-2026, 21:19:57", + "recorded-content": { + "response-too-old": { + "nextSequenceToken": "", + "rejectedLogEventsInfo": { + "tooOldLogEventEndIndex": "too-old-log-event-end-index" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_too_new": { + "recorded-date": "11-02-2026, 21:20:15", + "recorded-content": { + "response-too-new": { + "nextSequenceToken": "", + "rejectedLogEventsInfo": { + "tooNewLogEventStartIndex": "too-new-log-event-start-index" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_get_log_events_invalid_token": { + "recorded-date": "03-02-2026, 21:36:41", + "recorded-content": { + "error-invalid-token": { + "Error": { + "Code": "InvalidParameterException", + "Message": "The specified nextToken is invalid." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_get_log_events_limit_validation": { + "recorded-date": "03-02-2026, 21:36:42", + "recorded-content": { + "error-limit-exceeded": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value '10001' at 'limit' failed to satisfy constraint: Member must have value less than or equal to 10000" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEventsErrors::test_resource_does_not_exist": { + "recorded-date": "03-02-2026, 21:37:12", + "recorded-content": { + "error-log-group-does-not-exist": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified log group does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "error-log-stream-does-not-exist": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified log stream does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_events.validation.json b/tests/aws/services/logs/test_logs_events.validation.json new file mode 100644 index 0000000000000..7a8e1d16ec80f --- /dev/null +++ b/tests/aws/services/logs/test_logs_events.validation.json @@ -0,0 +1,110 @@ +{ + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_get_log_events_basic": { + "last_validated_date": "2026-02-03T21:36:38+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 3.2, + "teardown": 0.21, + "total": 3.59 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_get_log_events_invalid_token": { + "last_validated_date": "2026-02-03T21:36:42+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 0.07, + "teardown": 0.21, + "total": 0.46 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_get_log_events_limit_validation": { + "last_validated_date": "2026-02-03T21:36:42+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 0.07, + "teardown": 0.49, + "total": 0.74 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_get_log_events_start_from_head": { + "last_validated_date": "2026-02-03T21:36:41+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 3.19, + "teardown": 0.21, + "total": 3.58 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_get_log_events_using_log_group_identifier": { + "last_validated_date": "2026-02-03T21:36:46+00:00", + "durations_in_seconds": { + "setup": 0.19, + "call": 3.7, + "teardown": 0.22, + "total": 4.11 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_events_multi_bytes_msg": { + "last_validated_date": "2026-02-03T21:36:32+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 3.2, + "teardown": 0.21, + "total": 3.59 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_basic": { + "last_validated_date": "2026-02-03T21:36:28+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.09, + "teardown": 0.23, + "total": 0.81 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_to_invalid_stream": { + "last_validated_date": "2026-02-03T21:36:33+00:00", + "durations_in_seconds": { + "setup": 0.46, + "call": 0.08, + "teardown": 0.11, + "total": 0.65 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_too_new": { + "last_validated_date": "2026-02-11T21:20:16+00:00", + "durations_in_seconds": { + "setup": 1.14, + "call": 0.16, + "teardown": 0.28, + "total": 1.58 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_too_old": { + "last_validated_date": "2026-02-11T21:19:58+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 0.1, + "teardown": 0.3, + "total": 1.47 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEvents::test_put_log_events_wrong_order": { + "last_validated_date": "2026-02-03T21:36:33+00:00", + "durations_in_seconds": { + "setup": 0.19, + "call": 0.07, + "teardown": 0.21, + "total": 0.47 + } + }, + "tests/aws/services/logs/test_logs_events.py::TestLogsEventsErrors::test_resource_does_not_exist": { + "last_validated_date": "2026-02-03T21:37:12+00:00", + "durations_in_seconds": { + "setup": 0.35, + "call": 0.54, + "teardown": 0.14, + "total": 1.03 + } + } +} diff --git a/tests/aws/services/logs/test_logs_export_tasks.py b/tests/aws/services/logs/test_logs_export_tasks.py new file mode 100644 index 0000000000000..3f39b47454d68 --- /dev/null +++ b/tests/aws/services/logs/test_logs_export_tasks.py @@ -0,0 +1,225 @@ +"""Tests for CloudWatch Logs - Export Task operations.""" + +import copy +import json + +import pytest +from botocore.exceptions import ClientError + +from localstack.testing.pytest import markers +from localstack.utils.common import now_utc, short_uid + +S3_POLICY = { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "s3:GetBucketAcl", + "Effect": "Allow", + "Resource": "arn:aws:s3:::{BUCKET_NAME}", + "Principal": {"Service": "logs.amazonaws.com"}, + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": "arn:aws:s3:::{BUCKET_NAME}/*", + "Principal": {"Service": "logs.amazonaws.com"}, + }, + ], +} + + +@pytest.fixture +def export_bucket(aws_client, s3_bucket, region_name): + """Create an S3 bucket configured for CloudWatch Logs export.""" + bucket_name = s3_bucket + + # Get account ID for policy + + # Configure bucket policy for CloudWatch Logs access + policy = copy.deepcopy(S3_POLICY) + policy["Statement"][0]["Resource"] = f"arn:aws:s3:::{bucket_name}" + policy["Statement"][1]["Resource"] = f"arn:aws:s3:::{bucket_name}/*" + + aws_client.s3.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy)) + + return bucket_name + + +@pytest.mark.skip(reason="TODO: Moto logs searches in memory the bucket which is impossible") +class TestExportTasks: + """Tests for export task operations.""" + + @markers.aws.validated + def test_create_export_task(self, logs_log_group, export_bucket, aws_client, snapshot): + """Test creating an export task.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + from_time = 1611316574 # Fixed timestamp for reproducibility + to_time = 1642852574 + + response = aws_client.logs.create_export_task( + logGroupName=logs_log_group, + fromTime=from_time, + to=to_time, + destination=export_bucket, + ) + snapshot.match("create-export-task", response) + + task_id = response["taskId"] + + # Try to cancel the task (cleanup) + try: + aws_client.logs.cancel_export_task(taskId=task_id) + except ClientError as exc: + # Task might have already finished + if "already finished" not in exc.response["Error"]["Message"]: + raise + + @markers.aws.validated + def test_create_export_task_with_prefix( + self, logs_log_group, export_bucket, aws_client, snapshot + ): + """Test creating an export task with a destination prefix.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + from_time = 1611316574 + to_time = 1642852574 + prefix = f"custom-prefix-{short_uid()}" + + response = aws_client.logs.create_export_task( + logGroupName=logs_log_group, + fromTime=from_time, + to=to_time, + destination=export_bucket, + destinationPrefix=prefix, + ) + snapshot.match("create-export-task-with-prefix", response) + + task_id = response["taskId"] + + # Try to cancel the task (cleanup) + try: + aws_client.logs.cancel_export_task(taskId=task_id) + except ClientError: + pass + + @markers.aws.validated + def test_create_export_task_bucket_not_found(self, logs_log_group, aws_client, snapshot): + """Test that creating an export task with non-existent bucket fails.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.create_export_task( + logGroupName=logs_log_group, + fromTime=1611316574, + to=1642852574, + destination=f"non-existent-bucket-{short_uid()}", + ) + snapshot.match("error-bucket-not-found", ctx.value.response) + + @markers.aws.validated + def test_create_export_task_log_group_not_found(self, export_bucket, aws_client, snapshot): + """Test that creating an export task with non-existent log group fails.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.create_export_task( + logGroupName=f"non-existent-log-group-{short_uid()}", + fromTime=1611316574, + to=1642852574, + destination=export_bucket, + ) + snapshot.match("error-log-group-not-found", ctx.value.response) + + @markers.aws.validated + def test_describe_export_tasks(self, logs_log_group, export_bucket, aws_client, snapshot): + """Test describing export tasks.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + from_time = 1611316574 + to_time = 1642852574 + + # Create an export task + response = aws_client.logs.create_export_task( + logGroupName=logs_log_group, + fromTime=from_time, + to=to_time, + destination=export_bucket, + ) + task_id = response["taskId"] + + # Describe export tasks + response = aws_client.logs.describe_export_tasks(taskId=task_id) + snapshot.match("describe-export-tasks", response) + + # Cleanup + try: + aws_client.logs.cancel_export_task(taskId=task_id) + except ClientError: + pass + + @markers.aws.validated + def test_describe_export_tasks_not_found(self, aws_client, snapshot): + """Test describing a non-existent export task.""" + response = aws_client.logs.describe_export_tasks(taskId="non-existent-task-id") + snapshot.match("task-not-found", response) + + @markers.aws.validated + def test_cancel_export_task_not_found(self, aws_client, snapshot): + """Test cancelling a non-existent export task.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.cancel_export_task(taskId=f"{short_uid()}-{short_uid()}") + snapshot.match("error-cancel-not-found", ctx.value.response) + + +@pytest.mark.skip(reason="TODO: Moto logs searches in memory the bucket which is impossible") +class TestExportTasksWithLogs: + """Tests for export tasks with actual log data.""" + + @markers.aws.validated + def test_create_export_task_with_logs( + self, logs_log_group, export_bucket, aws_client, snapshot + ): + """Test exporting actual log events to S3.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + + # Create log stream and put events + log_stream_name = f"test-stream-{short_uid()}" + aws_client.logs.create_log_stream( + logGroupName=logs_log_group, logStreamName=log_stream_name + ) + + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp + i * 100, "message": f"test message {i}"} for i in range(10) + ] + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=log_stream_name, logEvents=messages + ) + + # Create export task with time range that includes our logs + from_time = timestamp - 86400000 # 1 day before + to_time = timestamp + 86400000 # 1 day after + + response = aws_client.logs.create_export_task( + logGroupName=logs_log_group, + fromTime=from_time, + to=to_time, + destination=export_bucket, + ) + task_id = response["taskId"] + snapshot.match("create-export-with-logs", response) + + # Describe the task + response = aws_client.logs.describe_export_tasks(taskId=task_id) + snapshot.match("describe-export-with-logs", response) + task = response["exportTasks"][0] + + # Status should be one of: PENDING, RUNNING, COMPLETED, CANCELLED, FAILED + assert task["status"]["code"] in [ + "PENDING", + "RUNNING", + "COMPLETED", + "CANCELLED", + "FAILED", + "active", + ] + + # Cleanup + try: + aws_client.logs.cancel_export_task(taskId=task_id) + except ClientError: + pass diff --git a/tests/aws/services/logs/test_logs_export_tasks.snapshot.json b/tests/aws/services/logs/test_logs_export_tasks.snapshot.json new file mode 100644 index 0000000000000..41c2db1832aac --- /dev/null +++ b/tests/aws/services/logs/test_logs_export_tasks.snapshot.json @@ -0,0 +1,105 @@ +{ + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_create_export_task": { + "recorded-date": "05-02-2026, 16:27:17", + "recorded-content": { + "create-export-task": { + "taskId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_create_export_task_bucket_not_found": { + "recorded-date": "05-02-2026, 17:16:29", + "recorded-content": { + "error-bucket-not-found": { + "Error": { + "Code": "InvalidParameterException", + "Message": "The given bucket does not exist. Please make sure the bucket is valid." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_create_export_task_log_group_not_found": { + "recorded-date": "05-02-2026, 17:17:07", + "recorded-content": { + "error-log-group-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified log group does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_describe_export_tasks_not_found": { + "recorded-date": "05-02-2026, 17:28:02", + "recorded-content": { + "task-not-found": { + "exportTasks": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_cancel_export_task_not_found": { + "recorded-date": "05-02-2026, 17:30:40", + "recorded-content": { + "error-cancel-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified export task does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasksWithLogs::test_create_export_task_with_logs": { + "recorded-date": "05-02-2026, 17:32:58", + "recorded-content": { + "create-export-with-logs": { + "taskId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-export-with-logs": { + "exportTasks": [ + { + "destination": "test-bucket-d5301391", + "destinationPrefix": "exportedlogs", + "executionInfo": { + "creationTime": "timestamp" + }, + "from": 1770226377833, + "logGroupName": "", + "status": { + "code": "PENDING" + }, + "taskId": "", + "to": 1770399177833 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_export_tasks.validation.json b/tests/aws/services/logs/test_logs_export_tasks.validation.json new file mode 100644 index 0000000000000..e2992f774a5d2 --- /dev/null +++ b/tests/aws/services/logs/test_logs_export_tasks.validation.json @@ -0,0 +1,56 @@ +{ + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_cancel_export_task_not_found": { + "last_validated_date": "2026-02-05T17:30:40+00:00", + "durations_in_seconds": { + "setup": 0.36, + "call": 0.34, + "teardown": 0.0, + "total": 0.7 + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_create_export_task": { + "last_validated_date": "2026-02-05T16:27:18+00:00", + "durations_in_seconds": { + "setup": 1.37, + "call": 0.42, + "teardown": 0.89, + "total": 2.68 + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_create_export_task_bucket_not_found": { + "last_validated_date": "2026-02-05T17:16:29+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.74, + "teardown": 0.14, + "total": 1.62 + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_create_export_task_log_group_not_found": { + "last_validated_date": "2026-02-05T17:17:08+00:00", + "durations_in_seconds": { + "setup": 1.08, + "call": 0.38, + "teardown": 0.49, + "total": 1.95 + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasks::test_describe_export_tasks_not_found": { + "last_validated_date": "2026-02-05T17:28:02+00:00", + "durations_in_seconds": { + "setup": 0.65, + "call": 0.39, + "teardown": 0.0, + "total": 1.04 + } + }, + "tests/aws/services/logs/test_logs_export_tasks.py::TestExportTasksWithLogs::test_create_export_task_with_logs": { + "last_validated_date": "2026-02-05T17:32:59+00:00", + "durations_in_seconds": { + "setup": 1.4, + "call": 0.7, + "teardown": 0.93, + "total": 3.03 + } + } +} diff --git a/tests/aws/services/logs/test_logs_filter_events.py b/tests/aws/services/logs/test_logs_filter_events.py new file mode 100644 index 0000000000000..3d2ea0b7ad68c --- /dev/null +++ b/tests/aws/services/logs/test_logs_filter_events.py @@ -0,0 +1,290 @@ +"""Tests for CloudWatch Logs - Filter Log Events operations.""" + +import pytest +from botocore.exceptions import ClientError +from localstack_snapshot.pytest.snapshot import is_aws + +from localstack.constants import APPLICATION_AMZ_JSON_1_1 +from localstack.testing.pytest import markers +from localstack.utils.common import now_utc, retry + + +class TestFilterLogEvents: + """Tests for filter_log_events operation.""" + + @markers.aws.validated + def test_filter_log_events_basic(self, logs_log_group, logs_log_stream, aws_client): + """Test basic filter log events operation.""" + timestamp = now_utc(millis=True) + events = [ + {"timestamp": timestamp, "message": "log message 1"}, + {"timestamp": timestamp + 100, "message": "log message 2"}, + ] + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events + ) + + def verify_filter(): + response = aws_client.logs.filter_log_events(logGroupName=logs_log_group) + assert len(response["events"]) >= 2 + + retry( + verify_filter, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_filter_log_events_response_header(self, logs_log_group, logs_log_stream, aws_client): + """Test that filter_log_events returns correct content-type header.""" + events = [ + {"timestamp": now_utc(millis=True), "message": "log message 1"}, + {"timestamp": now_utc(millis=True), "message": "log message 2"}, + ] + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events + ) + + response = aws_client.logs.filter_log_events(logGroupName=logs_log_group) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + assert ( + response["ResponseMetadata"]["HTTPHeaders"]["content-type"] == APPLICATION_AMZ_JSON_1_1 + ) + + @markers.aws.validated + def test_filter_log_events_with_log_stream_names( + self, logs_log_group, logs_log_stream, aws_client + ): + """Test filtering by specific log stream names.""" + timestamp = now_utc(millis=True) + events = [ + {"timestamp": timestamp, "message": "test message"}, + ] + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events + ) + + def verify_filter(): + response = aws_client.logs.filter_log_events( + logGroupName=logs_log_group, + logStreamNames=[logs_log_stream], + ) + assert len(response["events"]) >= 1 + + retry( + verify_filter, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_filter_log_events_interleaved(self, logs_log_group, logs_log_stream, aws_client): + """Test filter log events with interleaved parameter.""" + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp, "message": "hello"}, + {"timestamp": timestamp + 100, "message": "world"}, + ] + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + + def verify_filter(): + response = aws_client.logs.filter_log_events( + logGroupName=logs_log_group, + logStreamNames=[logs_log_stream], + interleaved=True, + ) + events = response["events"] + assert len(events) >= 2 + for event in events: + assert "eventId" in event + assert "timestamp" in event + assert "message" in event + + retry( + verify_filter, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_filter_log_events_pagination(self, logs_log_group, logs_log_stream, aws_client): + """Test filter log events pagination.""" + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp + i * 100, "message": f"Message number {i}"} for i in range(25) + ] + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + + def verify_pagination(): + # Get first page + response = aws_client.logs.filter_log_events( + logGroupName=logs_log_group, + logStreamNames=[logs_log_stream], + limit=20, + ) + events = response["events"] + assert len(events) == 20 + assert "nextToken" in response + + # Get second page + response2 = aws_client.logs.filter_log_events( + logGroupName=logs_log_group, + logStreamNames=[logs_log_stream], + limit=20, + nextToken=response["nextToken"], + ) + events2 = response2["events"] + assert len(events + events2) == 25 + + retry( + verify_pagination, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + @pytest.mark.skip(reason="TODO Raise error") + def test_filter_log_events_unknown_token( + self, logs_log_group, logs_log_stream, aws_client, snapshot + ): + """Test filter log events with unknown/invalid token.""" + with pytest.raises(ClientError) as ctx: + aws_client.logs.filter_log_events( + logGroupName=logs_log_group, + logStreamNames=[logs_log_stream], + limit=20, + nextToken="invalid-token", + ) + snapshot.match("invalid-token", ctx.value.response) + + @markers.aws.validated + def test_filter_log_events_limit_validation(self, logs_log_group, aws_client, snapshot): + """Test that limit over 10000 is rejected.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.filter_log_events(logGroupName=logs_log_group, limit=10001) + snapshot.match("error-limit-exceeded", ctx.value.response) + + +@pytest.mark.skip(reason="filtering is only supported in pro") +class TestFilterLogEventsPatterns: + """Tests for filter log events with filter patterns.""" + + @pytest.fixture(autouse=True) + def setup_log_events(self, logs_log_group, logs_log_stream, aws_client): + """Set up common log events for pattern testing.""" + self.log_group = logs_log_group + self.log_stream = logs_log_stream + self.client = aws_client.logs + + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp, "message": "hello"}, + {"timestamp": timestamp + 100, "message": "world"}, + {"timestamp": timestamp + 200, "message": "hello world"}, + {"timestamp": timestamp + 300, "message": "goodbye world"}, + {"timestamp": timestamp + 400, "message": "hello cruela"}, + {"timestamp": timestamp + 500, "message": "goodbye cruel world"}, + ] + self.client.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=messages + ) + + @markers.aws.validated + def test_filter_simple_word_pattern(self): + """Test filtering with a simple word pattern.""" + + def verify_filter(): + events = self.client.filter_log_events( + logGroupName=self.log_group, + logStreamNames=[self.log_stream], + filterPattern="hello", + )["events"] + messages = [e["message"] for e in events] + assert "hello" in messages or "hello world" in messages or "hello cruela" in messages + # Only messages containing "hello" should be returned + for msg in messages: + assert "hello" in msg + + retry( + verify_filter, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_filter_multiple_words_pattern(self): + """Test filtering with multiple words pattern.""" + + def verify_filter(): + events = self.client.filter_log_events( + logGroupName=self.log_group, + logStreamNames=[self.log_stream], + filterPattern="goodbye world", + )["events"] + messages = [e["message"] for e in events] + # Messages containing both "goodbye" and "world" should be returned + for msg in messages: + assert "goodbye" in msg and "world" in msg + + retry( + verify_filter, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_filter_quoted_pattern(self): + """Test filtering with a quoted phrase pattern.""" + + def verify_filter(): + events = self.client.filter_log_events( + logGroupName=self.log_group, + logStreamNames=[self.log_stream], + filterPattern='"hello cruel"', + )["events"] + messages = [e["message"] for e in events] + # Only exact phrase matches should be returned + for msg in messages: + assert "hello cruel" in msg + + retry( + verify_filter, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.validated + def test_filter_json_pattern(self): + """Test filtering with a JSON-style pattern (treated as no-filter in Moto).""" + + def verify_filter(): + events = self.client.filter_log_events( + logGroupName=self.log_group, + logStreamNames=[self.log_stream], + filterPattern='{$.message = "hello"}', + )["events"] + # JSON patterns may or may not be fully supported + # Just verify that the call succeeds + assert isinstance(events, list) + + retry( + verify_filter, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) diff --git a/tests/aws/services/logs/test_logs_filter_events.snapshot.json b/tests/aws/services/logs/test_logs_filter_events.snapshot.json new file mode 100644 index 0000000000000..df94ade53375a --- /dev/null +++ b/tests/aws/services/logs/test_logs_filter_events.snapshot.json @@ -0,0 +1,32 @@ +{ + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_limit_validation": { + "recorded-date": "05-02-2026, 18:50:36", + "recorded-content": { + "error-limit-exceeded": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value '10001' at 'limit' failed to satisfy constraint: Member must have value less than or equal to 10000" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_unknown_token": { + "recorded-date": "05-02-2026, 18:50:36", + "recorded-content": { + "invalid-token": { + "Error": { + "Code": "InvalidParameterException", + "Message": "The specified nextToken is invalid." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_filter_events.validation.json b/tests/aws/services/logs/test_logs_filter_events.validation.json new file mode 100644 index 0000000000000..5f9a70a1e9294 --- /dev/null +++ b/tests/aws/services/logs/test_logs_filter_events.validation.json @@ -0,0 +1,101 @@ +{ + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_basic": { + "last_validated_date": "2026-02-05T18:50:23+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 3.25, + "teardown": 0.22, + "total": 3.95 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_interleaved": { + "last_validated_date": "2026-02-05T18:50:31+00:00", + "durations_in_seconds": { + "setup": 0.19, + "call": 3.2, + "teardown": 0.22, + "total": 3.61 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_limit_validation": { + "last_validated_date": "2026-02-05T18:50:36+00:00", + "durations_in_seconds": { + "setup": 0.1, + "call": 0.07, + "teardown": 0.11, + "total": 0.28 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_pagination": { + "last_validated_date": "2026-02-05T18:50:35+00:00", + "durations_in_seconds": { + "setup": 0.24, + "call": 3.3, + "teardown": 0.24, + "total": 3.78 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_response_header": { + "last_validated_date": "2026-02-05T18:50:24+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 0.25, + "teardown": 0.2, + "total": 0.63 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_unknown_token": { + "last_validated_date": "2026-02-05T18:50:36+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 0.08, + "teardown": 0.22, + "total": 0.89 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEvents::test_filter_log_events_with_log_stream_names": { + "last_validated_date": "2026-02-05T18:50:27+00:00", + "durations_in_seconds": { + "setup": 0.24, + "call": 3.2, + "teardown": 0.2, + "total": 3.64 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEventsPatterns::test_filter_json_pattern": { + "last_validated_date": "2026-02-05T18:52:08+00:00", + "durations_in_seconds": { + "setup": 0.27, + "call": 3.11, + "teardown": 0.24, + "total": 3.62 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEventsPatterns::test_filter_multiple_words_pattern": { + "last_validated_date": "2026-02-05T18:52:01+00:00", + "durations_in_seconds": { + "setup": 0.28, + "call": 3.11, + "teardown": 0.2, + "total": 3.59 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEventsPatterns::test_filter_quoted_pattern": { + "last_validated_date": "2026-02-05T18:52:05+00:00", + "durations_in_seconds": { + "setup": 0.28, + "call": 3.12, + "teardown": 0.22, + "total": 3.62 + } + }, + "tests/aws/services/logs/test_logs_filter_events.py::TestFilterLogEventsPatterns::test_filter_simple_word_pattern": { + "last_validated_date": "2026-02-05T18:51:57+00:00", + "durations_in_seconds": { + "setup": 0.61, + "call": 3.1, + "teardown": 0.21, + "total": 3.92 + } + } +} diff --git a/tests/aws/services/logs/test_logs_groups.py b/tests/aws/services/logs/test_logs_groups.py new file mode 100644 index 0000000000000..a839b48ba39a0 --- /dev/null +++ b/tests/aws/services/logs/test_logs_groups.py @@ -0,0 +1,344 @@ +"""Tests for CloudWatch Logs - Log Group operations.""" + +import json + +import pytest + +from localstack.testing.pytest import markers +from localstack.utils.common import poll_condition, short_uid + + +def _log_group_exists(log_groups: list, name: str) -> bool: + """Check if a log group with the given name exists in the list.""" + return any(lg["logGroupName"] == name for lg in log_groups) + + +class TestLogsGroups: + """Tests for log group create, delete, describe operations.""" + + @markers.aws.validated + def test_create_and_delete_log_group(self, aws_client): + """Test basic log group creation and deletion.""" + test_name = f"test-log-group-{short_uid()}" + + aws_client.logs.create_log_group(logGroupName=test_name) + + def log_group_created(): + log_groups = aws_client.logs.describe_log_groups(logGroupNamePrefix=test_name).get( + "logGroups", [] + ) + return _log_group_exists(log_groups, test_name) + + assert poll_condition(log_group_created, timeout=5.0, interval=0.5) + + aws_client.logs.delete_log_group(logGroupName=test_name) + + def log_group_deleted(): + log_groups = aws_client.logs.describe_log_groups(logGroupNamePrefix=test_name).get( + "logGroups", [] + ) + return not _log_group_exists(log_groups, test_name) + + assert poll_condition(log_group_deleted, timeout=5.0, interval=0.5) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..logGroups..deletionProtectionEnabled"]) + def test_create_log_group_with_kms_key( + self, aws_client, snapshot, cleanups, kms_create_key, region_name, account_id + ): + """Test log group creation with KMS encryption.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + log_group_name = f"test-log-group-kms-{short_uid()}" + + kms_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": "kms:*", + "Resource": "*", + }, + ], + } + + kms_key = kms_create_key(Policy=json.dumps(kms_policy)) + snapshot.add_transformer(snapshot.transform.regex(kms_key["KeyId"], "")) + + aws_client.logs.create_log_group(logGroupName=log_group_name, kmsKeyId=kms_key["Arn"]) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name) + snapshot.match("describe-log-groups-with-kms", response) + + assert _log_group_exists(response["logGroups"], log_group_name) + log_group = next(lg for lg in response["logGroups"] if lg["logGroupName"] == log_group_name) + assert "kmsKeyId" in log_group + + @markers.aws.validated + def test_create_log_group_duplicate_error(self, aws_client, snapshot, cleanups): + """Test that creating a duplicate log group raises an error.""" + log_group_name = f"test-log-group-{short_uid()}" + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + with pytest.raises(Exception) as ctx: + aws_client.logs.create_log_group(logGroupName=log_group_name) + snapshot.match("error-duplicate-log-group", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_create_log_group_invalid_name_length(self, aws_client, snapshot): + """Test that log group names over 512 characters are rejected.""" + log_group_name = "a" * 513 + + with pytest.raises(Exception) as ctx: + aws_client.logs.create_log_group(logGroupName=log_group_name) + snapshot.match("error-invalid-name-length", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..logGroups..deletionProtectionEnabled"]) + def test_describe_log_groups_with_prefix(self, aws_client, snapshot, cleanups): + """Test describing log groups with prefix filter.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + prefix = f"test-prefix-{short_uid()}" + log_group_names = [f"{prefix}-group-{i}" for i in range(3)] + + for name in log_group_names: + aws_client.logs.create_log_group(logGroupName=name) + cleanups.append(lambda n=name: aws_client.logs.delete_log_group(logGroupName=n)) + + response = aws_client.logs.describe_log_groups(logGroupNamePrefix=prefix) + snapshot.match("describe-log-groups-prefix", response) + for name in log_group_names: + assert _log_group_exists(response["logGroups"], name) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..describe-log-groups-pattern.logGroups..storedBytes", + "$..describe-log-groups-pattern.nextToken", + ] + ) + def test_describe_log_groups_with_pattern(self, aws_client, snapshot, cleanups): + """Test describing log groups with pattern filter.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + unique_id = short_uid() + log_group_name = f"test-pattern-{unique_id}" + + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + # Pattern matching may take time on AWS + def log_group_found_by_pattern(): + log_groups = aws_client.logs.describe_log_groups(logGroupNamePattern=unique_id).get( + "logGroups", [] + ) + return _log_group_exists(log_groups, log_group_name) + + assert poll_condition(log_group_found_by_pattern, timeout=5.0, interval=0.5) + + response = aws_client.logs.describe_log_groups(logGroupNamePattern=unique_id) + snapshot.match("describe-log-groups-pattern", response) + + @markers.aws.validated + def test_describe_log_groups_prefix_and_pattern_error(self, aws_client, snapshot, cleanups): + """Test that using both prefix and pattern raises an error.""" + log_group_name = f"test-log-group-{short_uid()}" + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + with pytest.raises(Exception) as ctx: + aws_client.logs.describe_log_groups( + logGroupNamePattern=log_group_name, logGroupNamePrefix=log_group_name + ) + snapshot.match("error-prefix-and-pattern", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..nextToken", "$..storedBytes"]) + def test_list_log_groups_with_pattern(self, aws_client, snapshot, cleanups): + """Test listing log groups with pattern filter.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + unique_id = short_uid() + log_group_name = f"test-list-{unique_id}" + + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + response = aws_client.logs.list_log_groups(logGroupNamePattern="no-such-group") + assert not _log_group_exists(response.get("logGroups", []), log_group_name) + snapshot.match("list-log-groups-no-match", response) + + def log_group_found_by_pattern(): + log_groups = aws_client.logs.describe_log_groups(logGroupNamePattern=unique_id).get( + "logGroups", [] + ) + return _log_group_exists(log_groups, log_group_name) + + assert poll_condition(log_group_found_by_pattern, interval=0.5, timeout=5) + response = aws_client.logs.describe_log_groups(logGroupNamePattern=unique_id).get( + "logGroups", [] + ) + snapshot.match("list-log-groups-match", response) + + +class TestLogsGroupsRetention: + """Tests for log group retention policy operations.""" + + @markers.aws.validated + def test_put_retention_policy(self, aws_client, cleanups): + """Test setting retention policy on a log group.""" + log_group_name = f"test-retention-{short_uid()}" + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + aws_client.logs.put_retention_policy(logGroupName=log_group_name, retentionInDays=7) + + response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name) + assert response["logGroups"][0].get("retentionInDays") == 7 + + @markers.aws.validated + def test_delete_retention_policy(self, aws_client, cleanups): + """Test deleting retention policy from a log group.""" + log_group_name = f"test-retention-{short_uid()}" + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + # Set retention policy + aws_client.logs.put_retention_policy(logGroupName=log_group_name, retentionInDays=7) + + response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name) + assert response["logGroups"][0].get("retentionInDays") == 7 + + # Delete retention policy + aws_client.logs.delete_retention_policy(logGroupName=log_group_name) + + response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name) + assert response["logGroups"][0].get("retentionInDays") is None + + +class TestLogsGroupsTags: + """Tests for log group tagging operations.""" + + @markers.aws.validated + def test_create_log_group_with_tags(self, aws_client, snapshot, cleanups): + """Test creating a log group with initial tags.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + log_group_name = f"test-tags-{short_uid()}" + + aws_client.logs.create_log_group(logGroupName=log_group_name, tags={"env": "testing"}) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + snapshot.match("list-tags-after-create", response) + assert response["tags"] == {"env": "testing"} + + @markers.aws.validated + @pytest.mark.skip(reason="TODO list tags returns empty") + def test_tag_log_group(self, aws_client, snapshot, cleanups): + """Test adding tags to an existing log group using tag_log_group API.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + log_group_name = f"test-tags-{short_uid()}" + + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + # Add tags + aws_client.logs.tag_log_group( + logGroupName=log_group_name, tags={"tag_key_1": "tag_value_1"} + ) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + snapshot.match("list-tags-after-tag", response) + assert response["tags"] == {"tag_key_1": "tag_value_1"} + + # Add more tags + aws_client.logs.tag_log_group( + logGroupName=log_group_name, tags={"tag_key_2": "tag_value_2"} + ) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + assert response["tags"] == {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"} + + # Update existing tag + aws_client.logs.tag_log_group( + logGroupName=log_group_name, tags={"tag_key_1": "tag_value_XX"} + ) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + assert response["tags"] == {"tag_key_1": "tag_value_XX", "tag_key_2": "tag_value_2"} + + @markers.aws.validated + @pytest.mark.skip(reason="TODO list tags returns empty") + def test_untag_log_group(self, aws_client, snapshot, cleanups): + """Test removing tags from a log group using untag_log_group API.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + log_group_name = f"test-tags-{short_uid()}" + + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + # Add tags + tags = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"} + aws_client.logs.tag_log_group(logGroupName=log_group_name, tags=tags) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + assert response["tags"] == tags + + # Remove one tag + aws_client.logs.untag_log_group(logGroupName=log_group_name, tags=["tag_key_1"]) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + snapshot.match("list-tags-after-untag", response) + assert response["tags"] == {"tag_key_2": "tag_value_2"} + + @markers.aws.validated + def test_tag_resource_api(self, aws_client, snapshot, cleanups): + """Test tagging log group using the new tag_resource/untag_resource APIs.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + log_group_name = f"test-tags-{short_uid()}" + + aws_client.logs.create_log_group(logGroupName=log_group_name, tags={"env": "testing1"}) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + snapshot.match("list-tags-after-create", response) + + # Get the log group ARN (without trailing :*) + log_group_arn = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name)[ + "logGroups" + ][0]["arn"].rstrip(":*") + + # Add tags using new API + aws_client.logs.tag_resource( + resourceArn=log_group_arn, tags={"test1": "val1", "test2": "val2"} + ) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + response_2 = aws_client.logs.list_tags_for_resource(resourceArn=log_group_arn) + + snapshot.match("list-tags-log-group-after-tag-resource", response) + snapshot.match("list-tags-for-resource-after-tag-resource", response_2) + # Values should be the same + assert response["tags"] == response_2["tags"] + + # Add a tag using old API + aws_client.logs.tag_log_group(logGroupName=log_group_name, tags={"test3": "val3"}) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + response_2 = aws_client.logs.list_tags_for_resource(resourceArn=log_group_arn) + + snapshot.match("list-tags-log-group-after-tag-log-group", response) + snapshot.match("list-tags-for-resource-after-tag-log-group", response_2) + assert response["tags"] == response_2["tags"] + + # Untag using both APIs + aws_client.logs.untag_log_group(logGroupName=log_group_name, tags=["test3"]) + aws_client.logs.untag_resource(resourceArn=log_group_arn, tagKeys=["env", "test1"]) + + response = aws_client.logs.list_tags_log_group(logGroupName=log_group_name) + response_2 = aws_client.logs.list_tags_for_resource(resourceArn=log_group_arn) + snapshot.match("list-tags-log-group-after-untag", response) + snapshot.match("list-tags-for-resource-after-untag", response_2) + + assert response["tags"] == response_2["tags"] diff --git a/tests/aws/services/logs/test_logs_groups.snapshot.json b/tests/aws/services/logs/test_logs_groups.snapshot.json new file mode 100644 index 0000000000000..a54b3e22915cc --- /dev/null +++ b/tests/aws/services/logs/test_logs_groups.snapshot.json @@ -0,0 +1,278 @@ +{ + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_create_log_group_with_kms_key": { + "recorded-date": "03-02-2026, 21:00:51", + "recorded-content": { + "describe-log-groups-with-kms": { + "logGroups": [ + { + "arn": "arn::logs::111111111111:log-group::*", + "creationTime": "timestamp", + "deletionProtectionEnabled": false, + "kmsKeyId": "arn::kms::111111111111:key/", + "logGroupArn": "arn::logs::111111111111:log-group:", + "logGroupClass": "STANDARD", + "logGroupName": "", + "metricFilterCount": 0, + "storedBytes": 0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_create_log_group_duplicate_error": { + "recorded-date": "03-02-2026, 20:57:35", + "recorded-content": { + "error-duplicate-log-group": { + "Error": { + "Code": "ResourceAlreadyExistsException", + "Message": "The specified log group already exists" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_create_log_group_invalid_name_length": { + "recorded-date": "03-02-2026, 20:57:35", + "recorded-content": { + "error-invalid-name-length": { + "Error": { + "Code": "InvalidParameterException", + "Message": "Invalid LogGroup or LogStream, both must have length less than or equal to 512." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_describe_log_groups_with_prefix": { + "recorded-date": "03-02-2026, 20:57:36", + "recorded-content": { + "describe-log-groups-prefix": { + "logGroups": [ + { + "arn": "arn::logs::111111111111:log-group::*", + "creationTime": "timestamp", + "deletionProtectionEnabled": false, + "logGroupArn": "arn::logs::111111111111:log-group:", + "logGroupClass": "STANDARD", + "logGroupName": "", + "metricFilterCount": 0, + "storedBytes": 0 + }, + { + "arn": "arn::logs::111111111111:log-group::*", + "creationTime": "timestamp", + "deletionProtectionEnabled": false, + "logGroupArn": "arn::logs::111111111111:log-group:", + "logGroupClass": "STANDARD", + "logGroupName": "", + "metricFilterCount": 0, + "storedBytes": 0 + }, + { + "arn": "arn::logs::111111111111:log-group::*", + "creationTime": "timestamp", + "deletionProtectionEnabled": false, + "logGroupArn": "arn::logs::111111111111:log-group:", + "logGroupClass": "STANDARD", + "logGroupName": "", + "metricFilterCount": 0, + "storedBytes": 0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_describe_log_groups_with_pattern": { + "recorded-date": "03-02-2026, 20:57:37", + "recorded-content": { + "describe-log-groups-pattern": { + "logGroups": [ + { + "arn": "arn::logs::111111111111:log-group::*", + "creationTime": "timestamp", + "logGroupArn": "arn::logs::111111111111:log-group:", + "logGroupClass": "STANDARD", + "logGroupName": "", + "metricFilterCount": 0 + } + ], + "nextToken": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_describe_log_groups_prefix_and_pattern_error": { + "recorded-date": "03-02-2026, 20:57:38", + "recorded-content": { + "error-prefix-and-pattern": { + "Error": { + "Code": "InvalidParameterException", + "Message": "LogGroup name prefix and LogGroup name pattern are mutually exclusive parameters." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_list_log_groups_with_pattern": { + "recorded-date": "03-02-2026, 20:57:39", + "recorded-content": { + "list-log-groups-no-match": { + "logGroups": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-log-groups-match": [ + { + "logGroupName": "", + "creationTime": "timestamp", + "metricFilterCount": 0, + "arn": "arn::logs::111111111111:log-group::*", + "logGroupClass": "STANDARD", + "logGroupArn": "arn::logs::111111111111:log-group:" + } + ] + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_create_log_group_with_tags": { + "recorded-date": "03-02-2026, 21:03:36", + "recorded-content": { + "list-tags-after-create": { + "tags": { + "env": "testing" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_tag_log_group": { + "recorded-date": "03-02-2026, 21:03:37", + "recorded-content": { + "list-tags-after-tag": { + "tags": { + "tag_key_1": "tag_value_1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_untag_log_group": { + "recorded-date": "03-02-2026, 21:03:38", + "recorded-content": { + "list-tags-after-untag": { + "tags": { + "tag_key_2": "tag_value_2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_tag_resource_api": { + "recorded-date": "03-02-2026, 21:03:39", + "recorded-content": { + "list-tags-after-create": { + "tags": { + "env": "testing1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-log-group-after-tag-resource": { + "tags": { + "env": "testing1", + "test1": "val1", + "test2": "val2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-for-resource-after-tag-resource": { + "tags": { + "env": "testing1", + "test1": "val1", + "test2": "val2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-log-group-after-tag-log-group": { + "tags": { + "env": "testing1", + "test1": "val1", + "test2": "val2", + "test3": "val3" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-for-resource-after-tag-log-group": { + "tags": { + "env": "testing1", + "test1": "val1", + "test2": "val2", + "test3": "val3" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-log-group-after-untag": { + "tags": { + "test2": "val2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-tags-for-resource-after-untag": { + "tags": { + "test2": "val2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_groups.validation.json b/tests/aws/services/logs/test_logs_groups.validation.json new file mode 100644 index 0000000000000..3d75b348d5cd0 --- /dev/null +++ b/tests/aws/services/logs/test_logs_groups.validation.json @@ -0,0 +1,128 @@ +{ + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_create_and_delete_log_group": { + "last_validated_date": "2026-02-03T20:57:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.78, + "teardown": 0.0, + "total": 0.78 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_create_log_group_duplicate_error": { + "last_validated_date": "2026-02-03T20:57:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.18, + "teardown": 0.1, + "total": 0.28 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_create_log_group_invalid_name_length": { + "last_validated_date": "2026-02-03T20:57:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.07, + "teardown": 0.0, + "total": 0.07 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_create_log_group_with_kms_key": { + "last_validated_date": "2026-02-03T21:00:51+00:00", + "durations_in_seconds": { + "setup": 0.39, + "call": 0.94, + "teardown": 0.21, + "total": 1.54 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_describe_log_groups_prefix_and_pattern_error": { + "last_validated_date": "2026-02-03T20:57:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.16, + "teardown": 0.11, + "total": 0.27 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_describe_log_groups_with_pattern": { + "last_validated_date": "2026-02-03T20:57:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.46, + "teardown": 0.1, + "total": 1.56 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_describe_log_groups_with_prefix": { + "last_validated_date": "2026-02-03T20:57:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.41, + "teardown": 0.31, + "total": 0.72 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroups::test_list_log_groups_with_pattern": { + "last_validated_date": "2026-02-03T20:57:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.52, + "teardown": 0.1, + "total": 1.62 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsRetention::test_delete_retention_policy": { + "last_validated_date": "2026-02-03T21:02:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.43, + "teardown": 0.11, + "total": 0.54 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsRetention::test_put_retention_policy": { + "last_validated_date": "2026-02-03T21:02:40+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.59, + "teardown": 0.13, + "total": 0.73 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_create_log_group_with_tags": { + "last_validated_date": "2026-02-03T21:03:36+00:00", + "durations_in_seconds": { + "setup": 0.38, + "call": 0.51, + "teardown": 0.18, + "total": 1.07 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_tag_log_group": { + "last_validated_date": "2026-02-03T21:03:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.63, + "teardown": 0.14, + "total": 0.77 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_tag_resource_api": { + "last_validated_date": "2026-02-03T21:03:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.17, + "teardown": 0.13, + "total": 1.3 + } + }, + "tests/aws/services/logs/test_logs_groups.py::TestLogsGroupsTags::test_untag_log_group": { + "last_validated_date": "2026-02-03T21:03:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.46, + "teardown": 0.13, + "total": 0.59 + } + } +} diff --git a/tests/aws/services/logs/test_logs_metric_filters.py b/tests/aws/services/logs/test_logs_metric_filters.py new file mode 100644 index 0000000000000..d149883b746f9 --- /dev/null +++ b/tests/aws/services/logs/test_logs_metric_filters.py @@ -0,0 +1,460 @@ +"""Tests for CloudWatch Logs - Metric Filter operations.""" + +import pytest +from localstack_snapshot.pytest.snapshot import is_aws + +from localstack.testing.pytest import markers +from localstack.utils.common import now_utc, retry, short_uid + + +class TestMetricFilters: + """Tests for metric filter operations.""" + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..metricFilters..applyOnTransformedLogs", + "$..metricFilters..creationTime", + "$..metricFilters..metricTransformations..defaultValue", + ] + ) + def test_put_metric_filter_basic(self, logs_log_group, aws_client, snapshot, cleanups): + """Test basic metric filter creation.""" + filter_name = f"test-filter-{short_uid()}" + namespace = f"test-namespace-{short_uid()}" + metric_name = f"test-metric-{short_uid()}" + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.key_value("filterName")) + snapshot.add_transformer(snapshot.transform.key_value("metricName")) + snapshot.add_transformer(snapshot.transform.key_value("metricNamespace")) + + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName=filter_name, + filterPattern=" ", + metricTransformations=[ + { + "metricNamespace": namespace, + "metricName": metric_name, + "metricValue": "1", + "defaultValue": 0, + }, + ], + ) + cleanups.append( + lambda: aws_client.logs.delete_metric_filter( + logGroupName=logs_log_group, filterName=filter_name + ) + ) + + response = aws_client.logs.describe_metric_filters( + logGroupName=logs_log_group, filterNamePrefix=filter_name + ) + snapshot.match("describe-metric-filters", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..metricFilters..applyOnTransformedLogs", + "$..metricFilters..creationTime", + "$..metricFilters..metricTransformations..defaultValue", + ] + ) + def test_put_metric_filter_json_pattern(self, logs_log_group, aws_client, snapshot, cleanups): + """Test metric filter with JSON filter pattern.""" + filter_name = f"test-filter-json-{short_uid()}" + namespace = f"test-namespace-{short_uid()}" + metric_name = f"test-metric-{short_uid()}" + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.key_value("filterName")) + snapshot.add_transformer(snapshot.transform.key_value("metricName")) + snapshot.add_transformer(snapshot.transform.key_value("metricNamespace")) + + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName=filter_name, + filterPattern='{$.message = "test"}', + metricTransformations=[ + { + "metricNamespace": namespace, + "metricName": metric_name, + "metricValue": "1", + "defaultValue": 0, + }, + ], + ) + cleanups.append( + lambda: aws_client.logs.delete_metric_filter( + logGroupName=logs_log_group, filterName=filter_name + ) + ) + + response = aws_client.logs.describe_metric_filters( + logGroupName=logs_log_group, filterNamePrefix=filter_name + ) + snapshot.match("describe-metric-filters-json", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..metricFilters..applyOnTransformedLogs", "$..metricFilters..creationTime"] + ) + def test_describe_metric_filters_by_prefix(self, aws_client, snapshot, cleanups): + """Test describing metric filters by name prefix.""" + + prefix = f"test-filter-prefix-{short_uid()}" + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.key_value("filterName")) + snapshot.add_transformer(snapshot.transform.key_value("metricName")) + snapshot.add_transformer(snapshot.transform.key_value("metricNamespace")) + + log_groups = [] + for i in range(2): + log_group = f"test-log-group-{short_uid()}" + aws_client.logs.create_log_group(logGroupName=log_group) + log_groups.append(log_group) + cleanups.append(lambda lg=log_group: aws_client.logs.delete_log_group(logGroupName=lg)) + + aws_client.logs.put_metric_filter( + logGroupName=log_group, + filterName=f"{prefix}-{i}", + filterPattern=f"filterPattern{i}", + metricTransformations=[ + { + "metricNamespace": f"namespace{i}", + "metricName": f"metric{i}", + "metricValue": str(i), + }, + ], + ) + cleanups.append( + lambda lg=log_group, fn=f"{prefix}-{i}": aws_client.logs.delete_metric_filter( + logGroupName=lg, filterName=fn + ) + ) + + response = aws_client.logs.describe_metric_filters(filterNamePrefix=prefix) + snapshot.match("describe-by-prefix", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..applyOnTransformedLogs", "$..creationTime"]) + def test_describe_metric_filters_by_log_group(self, aws_client, snapshot, cleanups): + """Test describing metric filters by log group name.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.key_value("filterName")) + + log_group1 = f"test-log-group-{short_uid()}" + log_group2 = f"test-log-group-{short_uid()}" + + for log_group in [log_group1, log_group2]: + aws_client.logs.create_log_group(logGroupName=log_group) + cleanups.append(lambda lg=log_group: aws_client.logs.delete_log_group(logGroupName=lg)) + + aws_client.logs.put_metric_filter( + logGroupName=log_group, + filterName=f"filter-{short_uid()}", + filterPattern="pattern", + metricTransformations=[ + { + "metricNamespace": "namespace", + "metricName": "metric", + "metricValue": "1", + }, + ], + ) + + response = aws_client.logs.describe_metric_filters(logGroupName=log_group1) + snapshot.match("describe-by-log-group", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..applyOnTransformedLogs", "$..creationTime"]) + def test_describe_metric_filters_by_metric_name( + self, aws_client, snapshot, cleanups, logs_log_group + ): + """Test describing metric filters by metric name and namespace.""" + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.key_value("filterName")) + snapshot.add_transformer(snapshot.transform.key_value("metricName")) + snapshot.add_transformer(snapshot.transform.key_value("metricNamespace")) + + log_group = logs_log_group + + metric_name = f"test-metric-{short_uid()}" + namespace = f"test-namespace-{short_uid()}" + + aws_client.logs.put_metric_filter( + logGroupName=log_group, + filterName=f"filter-{short_uid()}", + filterPattern="pattern", + metricTransformations=[ + { + "metricNamespace": namespace, + "metricName": metric_name, + "metricValue": "1", + }, + ], + ) + + response = aws_client.logs.describe_metric_filters( + metricName=metric_name, metricNamespace=namespace + ) + snapshot.match("describe-by-metric-name", response) + + @markers.aws.validated + def test_delete_metric_filter(self, logs_log_group, aws_client): + """Test deleting a metric filter.""" + filter_name = f"test-filter-{short_uid()}" + + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName=filter_name, + filterPattern="{ $.val = * }", + metricTransformations=[ + { + "metricNamespace": "namespace", + "metricName": "metric", + "metricValue": "$.value", + }, + ], + ) + + response = aws_client.logs.describe_metric_filters( + logGroupName=logs_log_group, filterNamePrefix=filter_name + ) + assert len(response["metricFilters"]) == 1 + + # Delete the filter + aws_client.logs.delete_metric_filter(logGroupName=logs_log_group, filterName=filter_name) + + response = aws_client.logs.describe_metric_filters( + logGroupName=logs_log_group, filterNamePrefix=filter_name + ) + assert len(response["metricFilters"]) == 0 + + @markers.aws.validated + def test_put_metric_filter_with_special_namespace(self, logs_log_group, aws_client, cleanups): + """Test metric filter with special characters in namespace.""" + filter_name = f"test-filter-{short_uid()}" + namespace = "A.B-c_d/1#2:metricNamespace" # Valid namespace with special chars + + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName=filter_name, + filterPattern="filterPattern", + metricTransformations=[ + { + "metricNamespace": namespace, + "metricName": "metricName", + "metricValue": "1", + }, + ], + ) + cleanups.append( + lambda: aws_client.logs.delete_metric_filter( + logGroupName=logs_log_group, filterName=filter_name + ) + ) + + response = aws_client.logs.describe_metric_filters( + metricName="metricName", metricNamespace=namespace + ) + assert len(response["metricFilters"]) == 1 + assert response["metricFilters"][0]["filterName"] == filter_name + + +class TestMetricFiltersValidation: + """Tests for metric filter validation.""" + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_put_metric_filter_invalid_filter_name(self, logs_log_group, aws_client, snapshot): + """Test that filter names over 512 characters are rejected.""" + invalid_filter_name = "X" * 513 + + with pytest.raises(Exception) as ctx: + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName=invalid_filter_name, + filterPattern="pattern", + metricTransformations=[ + { + "metricNamespace": "namespace", + "metricName": "metric", + "metricValue": "1", + }, + ], + ) + snapshot.match("error-invalid-filter-name", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_put_metric_filter_invalid_filter_pattern(self, logs_log_group, aws_client, snapshot): + """Test that filter patterns over 1024 characters are rejected.""" + invalid_filter_pattern = "X" * 1025 + + with pytest.raises(Exception) as ctx: + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName="valid-filter", + filterPattern=invalid_filter_pattern, + metricTransformations=[ + { + "metricNamespace": "namespace", + "metricName": "metric", + "metricValue": "1", + }, + ], + ) + snapshot.match("error-invalid-filter-pattern", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_put_metric_filter_too_many_transformations(self, logs_log_group, aws_client, snapshot): + """Test that more than 1 metric transformation is rejected.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName="valid-filter", + filterPattern="pattern", + metricTransformations=[ + { + "metricNamespace": "namespace1", + "metricName": "metric1", + "metricValue": "1", + }, + { + "metricNamespace": "namespace2", + "metricName": "metric2", + "metricValue": "1", + }, + ], + ) + snapshot.match("error-too-many-transformations", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_delete_metric_filter_invalid_filter_name(self, logs_log_group, aws_client, snapshot): + """Test delete metric filter with invalid filter name.""" + invalid_filter_name = "X" * 513 + + with pytest.raises(Exception) as ctx: + aws_client.logs.delete_metric_filter( + logGroupName=logs_log_group, filterName=invalid_filter_name + ) + snapshot.match("error-invalid-filter-name", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_delete_metric_filter_invalid_log_group_name(self, aws_client, snapshot): + """Test delete metric filter with invalid log group name.""" + invalid_log_group_name = "X" * 513 + + with pytest.raises(Exception) as ctx: + aws_client.logs.delete_metric_filter( + logGroupName=invalid_log_group_name, filterName="valid-filter" + ) + snapshot.match("error-invalid-log-group-name", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_describe_metric_filters_invalid_parameters(self, aws_client, snapshot): + """Test describe metric filters with invalid parameter lengths.""" + # Invalid filter name prefix (over 512 chars) + with pytest.raises(Exception) as ctx: + aws_client.logs.describe_metric_filters(filterNamePrefix="X" * 513) + snapshot.match("error-invalid-filter-prefix", ctx.value.response) + + # Invalid metric name (over 255 chars) + with pytest.raises(Exception) as ctx: + aws_client.logs.describe_metric_filters(metricName="X" * 256, metricNamespace="valid") + snapshot.match("error-invalid-metric-name", ctx.value.response) + + +class TestMetricFiltersCloudWatchIntegration: + """Tests for metric filter integration with CloudWatch metrics.""" + + @markers.aws.validated + @pytest.mark.skip(reason="TODO - Fails against pro") + def test_metric_filters_publish_to_cloudwatch( + self, logs_log_group, logs_log_stream, aws_client + ): + """Test that metric filters publish metrics to CloudWatch.""" + basic_filter_name = f"test-filter-basic-{short_uid()}" + json_filter_name = f"test-filter-json-{short_uid()}" + namespace_name = f"test-metric-namespace-{short_uid()}" + basic_metric_name = f"test-basic-metric-{short_uid()}" + json_metric_name = f"test-json-metric-{short_uid()}" + + basic_transforms = { + "metricNamespace": namespace_name, + "metricName": basic_metric_name, + "metricValue": "1", + "defaultValue": 0, + } + json_transforms = { + "metricNamespace": namespace_name, + "metricName": json_metric_name, + "metricValue": "1", + "defaultValue": 0, + } + + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName=basic_filter_name, + filterPattern=" ", + metricTransformations=[basic_transforms], + ) + aws_client.logs.put_metric_filter( + logGroupName=logs_log_group, + filterName=json_filter_name, + filterPattern='{$.message = "test"}', + metricTransformations=[json_transforms], + ) + + response = aws_client.logs.describe_metric_filters( + logGroupName=logs_log_group, filterNamePrefix="test-filter-" + ) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + filter_names = [_filter["filterName"] for _filter in response["metricFilters"]] + assert basic_filter_name in filter_names + assert json_filter_name in filter_names + + # Put log events and assert metrics being published + events = [ + {"timestamp": now_utc(millis=True), "message": "log message 1"}, + {"timestamp": now_utc(millis=True), "message": "log message 2"}, + ] + aws_client.logs.put_log_events( + logGroupName=logs_log_group, logStreamName=logs_log_stream, logEvents=events + ) + + # List metrics + def list_metrics(): + res = aws_client.cloudwatch.list_metrics(Namespace=namespace_name) + assert len(res["Metrics"]) == 2 + + retry( + list_metrics, + retries=20 if is_aws() else 5, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + # Delete filters + aws_client.logs.delete_metric_filter( + logGroupName=logs_log_group, filterName=basic_filter_name + ) + aws_client.logs.delete_metric_filter( + logGroupName=logs_log_group, filterName=json_filter_name + ) + + response = aws_client.logs.describe_metric_filters( + logGroupName=logs_log_group, filterNamePrefix="test-filter-" + ) + assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 + filter_names = [_filter["filterName"] for _filter in response["metricFilters"]] + assert basic_filter_name not in filter_names + assert json_filter_name not in filter_names diff --git a/tests/aws/services/logs/test_logs_metric_filters.snapshot.json b/tests/aws/services/logs/test_logs_metric_filters.snapshot.json new file mode 100644 index 0000000000000..d2b65cb4adfdd --- /dev/null +++ b/tests/aws/services/logs/test_logs_metric_filters.snapshot.json @@ -0,0 +1,253 @@ +{ + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_put_metric_filter_basic": { + "recorded-date": "05-02-2026, 21:13:19", + "recorded-content": { + "describe-metric-filters": { + "metricFilters": [ + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "filterName": "", + "filterPattern": " ", + "logGroupName": "", + "metricTransformations": [ + { + "defaultValue": 0.0, + "metricName": "", + "metricNamespace": "", + "metricValue": "1" + } + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_put_metric_filter_json_pattern": { + "recorded-date": "05-02-2026, 21:15:47", + "recorded-content": { + "describe-metric-filters-json": { + "metricFilters": [ + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "filterName": "", + "filterPattern": "{$.message = \"test\"}", + "logGroupName": "", + "metricTransformations": [ + { + "defaultValue": 0.0, + "metricName": "", + "metricNamespace": "", + "metricValue": "1" + } + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_describe_metric_filters_by_prefix": { + "recorded-date": "05-02-2026, 21:17:23", + "recorded-content": { + "describe-by-prefix": { + "metricFilters": [ + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "filterName": "", + "filterPattern": "filterPattern0", + "logGroupName": "", + "metricTransformations": [ + { + "metricName": "", + "metricNamespace": "", + "metricValue": "0" + } + ] + }, + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "filterName": "", + "filterPattern": "filterPattern1", + "logGroupName": "", + "metricTransformations": [ + { + "metricName": "", + "metricNamespace": "", + "metricValue": "1" + } + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_describe_metric_filters_by_log_group": { + "recorded-date": "05-02-2026, 21:26:37", + "recorded-content": { + "describe-by-log-group": { + "metricFilters": [ + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "filterName": "", + "filterPattern": "pattern", + "logGroupName": "", + "metricTransformations": [ + { + "metricName": "metric", + "metricNamespace": "namespace", + "metricValue": "1" + } + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_describe_metric_filters_by_metric_name": { + "recorded-date": "05-02-2026, 21:28:35", + "recorded-content": { + "describe-by-metric-name": { + "metricFilters": [ + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "filterName": "", + "filterPattern": "pattern", + "logGroupName": "", + "metricTransformations": [ + { + "metricName": "", + "metricNamespace": "", + "metricValue": "1" + } + ] + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_put_metric_filter_invalid_filter_name": { + "recorded-date": "05-02-2026, 21:30:38", + "recorded-content": { + "error-invalid-filter-name": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' at 'filterName' failed to satisfy constraint: Member must have length less than or equal to 512" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_put_metric_filter_invalid_filter_pattern": { + "recorded-date": "05-02-2026, 21:31:40", + "recorded-content": { + "error-invalid-filter-pattern": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' at 'filterPattern' failed to satisfy constraint: Member must have length less than or equal to 1024" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_put_metric_filter_too_many_transformations": { + "recorded-date": "05-02-2026, 21:32:47", + "recorded-content": { + "error-too-many-transformations": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value '[MetricTransformation(metricName=metric1, metricNamespace=namespace1, metricValue=1, defaultValue=null, unit=null, dimensions=null), MetricTransformation(metricName=metric2, metricNamespace=namespace2, metricValue=1, defaultValue=null, unit=null, dimensions=null)]' at 'metricTransformations' failed to satisfy constraint: Member must have length less than or equal to 1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_delete_metric_filter_invalid_filter_name": { + "recorded-date": "05-02-2026, 21:33:33", + "recorded-content": { + "error-invalid-filter-name": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' at 'filterName' failed to satisfy constraint: Member must have length less than or equal to 512" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_delete_metric_filter_invalid_log_group_name": { + "recorded-date": "05-02-2026, 21:34:19", + "recorded-content": { + "error-invalid-log-group-name": { + "Error": { + "Code": "InvalidParameterException", + "Message": "Invalid LogGroup or LogStream, both must have length less than or equal to 512." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_describe_metric_filters_invalid_parameters": { + "recorded-date": "05-02-2026, 21:34:54", + "recorded-content": { + "error-invalid-filter-prefix": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' at 'filterNamePrefix' failed to satisfy constraint: Member must have length less than or equal to 512" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "error-invalid-metric-name": { + "Error": { + "Code": "InvalidParameterException", + "Message": "1 validation error detected: Value 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' at 'metricName' failed to satisfy constraint: Member must have length less than or equal to 255" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_metric_filters.validation.json b/tests/aws/services/logs/test_logs_metric_filters.validation.json new file mode 100644 index 0000000000000..8a6222bcada1f --- /dev/null +++ b/tests/aws/services/logs/test_logs_metric_filters.validation.json @@ -0,0 +1,119 @@ +{ + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_describe_metric_filters_by_log_group": { + "last_validated_date": "2026-02-05T21:26:37+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 0.79, + "teardown": 0.28, + "total": 1.44 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_describe_metric_filters_by_metric_name": { + "last_validated_date": "2026-02-05T21:28:35+00:00", + "durations_in_seconds": { + "setup": 0.76, + "call": 0.23, + "teardown": 0.13, + "total": 1.12 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_describe_metric_filters_by_prefix": { + "last_validated_date": "2026-02-05T21:17:23+00:00", + "durations_in_seconds": { + "setup": 0.36, + "call": 0.8, + "teardown": 0.44, + "total": 1.6 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_put_metric_filter_basic": { + "last_validated_date": "2026-02-05T21:13:19+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.21, + "teardown": 0.24, + "total": 1.19 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_put_metric_filter_json_pattern": { + "last_validated_date": "2026-02-05T21:15:47+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.21, + "teardown": 0.24, + "total": 1.19 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFilters::test_put_metric_filter_with_special_namespace": { + "last_validated_date": "2026-02-05T21:29:55+00:00", + "durations_in_seconds": { + "setup": 0.43, + "call": 0.29, + "teardown": 0.23, + "total": 0.95 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersCloudWatchIntegration::test_metric_filters_publish_to_cloudwatch": { + "last_validated_date": "2026-02-09T19:26:41+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 4.09, + "teardown": 0.23, + "total": 4.81 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_delete_metric_filter_invalid_filter_name": { + "last_validated_date": "2026-02-05T21:33:33+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.08, + "teardown": 0.14, + "total": 0.96 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_delete_metric_filter_invalid_log_group_name": { + "last_validated_date": "2026-02-05T21:34:19+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 0.32, + "teardown": 0.0, + "total": 0.69 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_describe_metric_filters_invalid_parameters": { + "last_validated_date": "2026-02-05T21:34:54+00:00", + "durations_in_seconds": { + "setup": 0.35, + "call": 0.43, + "teardown": 0.0, + "total": 0.78 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_put_metric_filter_invalid_filter_name": { + "last_validated_date": "2026-02-05T21:30:38+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.08, + "teardown": 0.12, + "total": 0.94 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_put_metric_filter_invalid_filter_pattern": { + "last_validated_date": "2026-02-05T21:31:40+00:00", + "durations_in_seconds": { + "setup": 0.75, + "call": 0.08, + "teardown": 0.14, + "total": 0.97 + } + }, + "tests/aws/services/logs/test_logs_metric_filters.py::TestMetricFiltersValidation::test_put_metric_filter_too_many_transformations": { + "last_validated_date": "2026-02-05T21:32:47+00:00", + "durations_in_seconds": { + "setup": 0.75, + "call": 0.08, + "teardown": 0.27, + "total": 1.1 + } + } +} diff --git a/tests/aws/services/logs/test_logs_queries.py b/tests/aws/services/logs/test_logs_queries.py new file mode 100644 index 0000000000000..5858a21743f2a --- /dev/null +++ b/tests/aws/services/logs/test_logs_queries.py @@ -0,0 +1,215 @@ +"""Tests for CloudWatch Logs - Insights Query operations.""" + +import time + +import pytest +from localstack_snapshot.pytest.snapshot import is_aws + +from localstack.testing.pytest import markers +from localstack.utils.common import now_utc, retry, short_uid + + +def _get_query_results(aws_client, query_id): + def _assert_query_status(): + query_results = aws_client.logs.get_query_results(queryId=query_id) + assert query_results["status"] in ["Complete"] + return query_results + + return retry( + _assert_query_status, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + +class TestCloudWatchLogsInsightsQueries: + """Tests for CloudWatch Logs Insights query operations.""" + + @markers.aws.validated + def test_start_query_basic(self, logs_log_group, aws_client, snapshot): + """Test starting a basic query.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + + response = aws_client.logs.start_query( + logGroupName=logs_log_group, + startTime=int(time.time()) - 3600, # 1 hour ago + endTime=int(time.time()) + 300, # 5 minutes from now + queryString="fields @message", + ) + snapshot.match("start-query", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_start_query_log_group_not_found(self, aws_client, snapshot): + """Test starting a query on a non-existent log group.""" + log_group = f"non-existent-{short_uid()}" + with pytest.raises(Exception) as ctx: + aws_client.logs.start_query( + logGroupName=log_group, + startTime=int(time.time()) - 3600, + endTime=int(time.time()) + 300, + queryString="fields @message", + ) + snapshot.add_transformer(snapshot.transform.regex(log_group, "")) + snapshot.match("error-log-group-not-found", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..queries..createTime", + "$..queries..queryLanguage", + "$..queries..queryString", + "$..queries..status", + ] + ) + def test_describe_queries(self, logs_log_group, aws_client, snapshot): + """Test describing queries for a log group.""" + + start_time = int(time.time()) - 3600 + end_time = int(time.time()) + 300 + + # Start a query + aws_client.logs.start_query( + logGroupName=logs_log_group, + startTime=start_time, + endTime=end_time, + queryString="fields @message", + ) + + # Describe queries + response = aws_client.logs.describe_queries(logGroupName=logs_log_group) + + snapshot.add_transformer(snapshot.transform.key_value("logGroupName")) + snapshot.match("describe-queries", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..queries..queryLanguage", "$..queries..queryString"] + ) + def test_describe_queries_with_status_filter(self, logs_log_group, aws_client, snapshot): + """Test decscribing queries with status filter.""" + + start_time = int(time.time()) - 3600 + end_time = int(time.time()) + 300 + + # Start a query + aws_client.logs.start_query( + logGroupName=logs_log_group, + startTime=start_time, + endTime=end_time, + queryString="fields @message", + ) + + # Describe queries with Complete status + def describe_complete_queries(): + response = aws_client.logs.describe_queries( + logGroupName=logs_log_group, status="Complete" + ) + return response + + response = retry( + describe_complete_queries, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer( + snapshot.transform.key_value("createTime", reference_replacement=False) + ) + snapshot.add_transformer(snapshot.transform.key_value("status")) + snapshot.add_transformer(snapshot.transform.regex(str(start_time), "")) + snapshot.add_transformer(snapshot.transform.regex(str(end_time), "")) + snapshot.add_transformer(snapshot.transform.regex(logs_log_group, "")) + + snapshot.match("describe-queries-complete", response) + + # Query with Scheduled status should return different results + response = aws_client.logs.describe_queries(logGroupName=logs_log_group, status="Scheduled") + snapshot.match("describe-queries-scheduled", response) + + @markers.aws.validated + def test_describe_queries_empty(self, aws_client, cleanups): + """Test describing queries for a log group with no queries.""" + log_group_name = f"test-log-group-{short_uid()}" + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + response = aws_client.logs.describe_queries(logGroupName=log_group_name) + assert response["queries"] == [] + + +class TestCloudWatchLogsInsightsQueryStrings: + """Tests for various CloudWatch Logs Insights query string patterns.""" + + def _populate_log_group(self, aws_client, log_group_name, log_stream_name): + # Put some log events + timestamp = now_utc(millis=True) + messages = [ + {"timestamp": timestamp - (i * 1000), "message": f"event nr {i}"} for i in range(5) + ] + messages.reverse() + aws_client.logs.put_log_events( + logGroupName=log_group_name, logStreamName=log_stream_name, logEvents=messages + ) + + def _query_results(self, aws_client, log_group_name, query): + # Start a query + start_time = now_utc() - 600 # 10 minutes ago + end_time = start_time + 1200 # 10 minutes from now + + def run_query_and_get_results(): + # Get query results (may need to wait for completion) + query_id = aws_client.logs.start_query( + logGroupName=log_group_name, + startTime=start_time, + endTime=end_time, + queryString=query, + )["queryId"] + results = _get_query_results(aws_client, query_id) + assert len(results["results"]) + return results + + return retry( + run_query_and_get_results, + retries=20 if is_aws() else 3, + sleep=5 if is_aws() else 1, + sleep_before=3 if is_aws() else 0, + ) + + @markers.aws.needs_fixing # The query results are not matching + def test_get_query_results(self, logs_log_group, logs_log_stream, aws_client): + """Test getting query results.""" + + self._populate_log_group(aws_client, logs_log_group, logs_log_stream) + response = self._query_results(aws_client, logs_log_group, "fields @message") + + # TODO FIX issue with Logs returning only one message + assert len(response["results"]) >= 1 + + fields = {row["field"] for result in response["results"] for row in result} + assert "@message" in fields or "@ptr" in fields + + @markers.aws.needs_fixing + def test_query_with_limit(self, logs_log_group, logs_log_stream, aws_client): + """Test query with limit clause.""" + + self._populate_log_group(aws_client, logs_log_group, logs_log_stream) + response = self._query_results(aws_client, logs_log_group, "fields @message | limit 5") + + # TODO FIX issue with Logs returning only one message + assert len(response["results"]) <= 5 + + @markers.aws.needs_fixing + def test_query_with_sort(self, logs_log_group, logs_log_stream, aws_client): + """Test query with sort clause.""" + + self._populate_log_group(aws_client, logs_log_group, logs_log_stream) + response = self._query_results( + aws_client, logs_log_group, "fields @timestamp, @message | sort @timestamp desc" + ) + + # TODO FIX issue with Logs returning only one message + assert len(response["results"]) diff --git a/tests/aws/services/logs/test_logs_queries.snapshot.json b/tests/aws/services/logs/test_logs_queries.snapshot.json new file mode 100644 index 0000000000000..e99acca475917 --- /dev/null +++ b/tests/aws/services/logs/test_logs_queries.snapshot.json @@ -0,0 +1,78 @@ +{ + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_start_query_basic": { + "recorded-date": "06-02-2026, 14:49:07", + "recorded-content": { + "start-query": { + "queryId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_start_query_log_group_not_found": { + "recorded-date": "06-02-2026, 14:51:58", + "recorded-content": { + "error-log-group-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Log group '' does not exist for account ID '111111111111' (Service: AWSLogs; Status Code: 400; Error Code: ResourceNotFoundException; Request ID: b04848e1-f91e-4ed5-8a00-413d7c60de9e; Proxy: null)" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_describe_queries": { + "recorded-date": "06-02-2026, 15:47:17", + "recorded-content": { + "describe-queries": { + "queries": [ + { + "createTime": "create-time", + "logGroupName": "", + "queryId": "", + "queryLanguage": "CWLI", + "queryString": "SOURCE \"\" START=000 END=999 | fields @message", + "status": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_describe_queries_with_status_filter": { + "recorded-date": "06-02-2026, 18:09:24", + "recorded-content": { + "describe-queries-complete": { + "queries": [ + { + "createTime": "create-time", + "logGroupName": "", + "queryId": "", + "queryLanguage": "CWLI", + "queryString": "SOURCE \"\" START=000 END=999 | fields @message", + "status": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-queries-scheduled": { + "queries": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_queries.validation.json b/tests/aws/services/logs/test_logs_queries.validation.json new file mode 100644 index 0000000000000..60b8783165777 --- /dev/null +++ b/tests/aws/services/logs/test_logs_queries.validation.json @@ -0,0 +1,65 @@ +{ + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_describe_queries": { + "last_validated_date": "2026-02-06T15:47:17+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.25, + "teardown": 0.13, + "total": 1.12 + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_describe_queries_empty": { + "last_validated_date": "2026-02-06T18:09:28+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.5, + "teardown": 0.12, + "total": 0.63 + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_describe_queries_with_status_filter": { + "last_validated_date": "2026-02-06T18:09:24+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 3.37, + "teardown": 0.13, + "total": 4.24 + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_start_query_basic": { + "last_validated_date": "2026-02-06T14:49:07+00:00", + "durations_in_seconds": { + "setup": 0.75, + "call": 0.12, + "teardown": 0.12, + "total": 0.99 + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueries::test_start_query_log_group_not_found": { + "last_validated_date": "2026-02-06T14:51:58+00:00", + "durations_in_seconds": { + "setup": 0.63, + "call": 0.43, + "teardown": 0.0, + "total": 1.06 + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueryStrings::test_query_fields_message": { + "last_validated_date": "2026-02-06T18:15:06+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 122.55, + "teardown": 0.27, + "total": 123.32 + } + }, + "tests/aws/services/logs/test_logs_queries.py::TestCloudWatchLogsInsightsQueryStrings::test_query_with_sort": { + "last_validated_date": "2026-02-06T18:43:06+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 130.93, + "teardown": 0.25, + "total": 131.67 + } + } +} diff --git a/tests/aws/services/logs/test_logs_resource_policies.py b/tests/aws/services/logs/test_logs_resource_policies.py new file mode 100644 index 0000000000000..9444e02f0b2ea --- /dev/null +++ b/tests/aws/services/logs/test_logs_resource_policies.py @@ -0,0 +1,219 @@ +"""Tests for CloudWatch Logs - Resource Policy operations.""" + +import json + +import pytest + +from localstack.testing.pytest import markers +from localstack.utils.common import short_uid + +# Maximum number of resource policies per region (AWS limit) +MAX_RESOURCE_POLICIES_PER_REGION = 10 + +JSON_POLICY_DOC = json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": {"Service": ["route53.amazonaws.com"]}, + "Action": "logs:PutLogEvents", + "Resource": "log_arn", + } + ], + } +) + + +class TestResourcePolicies: + """Tests for resource policy operations.""" + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..policyScope"]) + def test_put_resource_policy(self, aws_client, snapshot, cleanups): + """Test creating a resource policy.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + log_group_name = f"test-log-group-{short_uid()}" + policy_name = f"test-policy-{short_uid()}" + + # Create a log group to use its ARN in the policy + aws_client.logs.create_log_group(logGroupName=log_group_name) + cleanups.append(lambda: aws_client.logs.delete_log_group(logGroupName=log_group_name)) + + log_group_info = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name) + log_group_arn = log_group_info["logGroups"][0]["arn"] + + policy_doc = json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": {"Service": ["route53.amazonaws.com"]}, + "Action": "logs:PutLogEvents", + "Resource": log_group_arn, + } + ], + } + ) + + response = aws_client.logs.put_resource_policy( + policyName=policy_name, policyDocument=policy_doc + ) + cleanups.append(lambda: aws_client.logs.delete_resource_policy(policyName=policy_name)) + + snapshot.add_transformer(snapshot.transform.key_value("policyName")) + snapshot.add_transformer( + snapshot.transform.key_value("lastUpdatedTime", reference_replacement=False) + ) + snapshot.add_transformer(snapshot.transform.regex(log_group_arn, "")) + snapshot.match("put-resource-policy", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..policyDocument.Statement..Principal.Service", + "$..policyDocument.Version", + "$..policyScope", + ] + ) + def test_put_resource_policy_update(self, aws_client, snapshot, cleanups): + """Test updating an existing resource policy.""" + policy_name = f"test-policy-{short_uid()}" + + # Create initial policy + aws_client.logs.put_resource_policy(policyName=policy_name, policyDocument=JSON_POLICY_DOC) + cleanups.append(lambda: aws_client.logs.delete_resource_policy(policyName=policy_name)) + + response = aws_client.logs.describe_resource_policies() + policies = [p for p in response["resourcePolicies"] if p["policyName"] == policy_name] + assert len(policies) == 1 + created_time = policies[0]["lastUpdatedTime"] + + # Update the policy with different document + new_document = '{"Statement":[{"Action":"logs:*","Effect":"Allow","Principal":{"Service": ["route53.amazonaws.com"]},"Resource":"*"}]}' + aws_client.logs.put_resource_policy(policyName=policy_name, policyDocument=new_document) + response = aws_client.logs.describe_resource_policies() + policies = [p for p in response["resourcePolicies"] if p["policyName"] == policy_name] + + assert created_time != policies[0]["lastUpdatedTime"] + + snapshot.add_transformer(snapshot.transform.key_value("policyName")) + snapshot.add_transformer( + snapshot.transform.key_value("lastUpdatedTime", reference_replacement=False) + ) + snapshot.match("updated-policy", policies[0]) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..policyDocument.Statement..Principal.Service", + "$..policyDocument.Version", + "$..policyScope", + ] + ) + def test_describe_resource_policies(self, aws_client, snapshot, cleanups): + """Test describing resource policies.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + prefix = f"test-policy-{short_uid()}" + + # Create multiple policies + for i in range(3): + policy_name = f"{prefix}-{i}" + aws_client.logs.put_resource_policy( + policyName=policy_name, policyDocument=JSON_POLICY_DOC + ) + cleanups.append( + lambda pn=policy_name: aws_client.logs.delete_resource_policy(policyName=pn) + ) + + response = aws_client.logs.describe_resource_policies() + snapshot.match("describe-resource-policies", response) + + # Should contain at least the 3 we created + policies = [p for p in response["resourcePolicies"] if p["policyName"].startswith(prefix)] + snapshot.add_transformer(snapshot.transform.key_value("policyName")) + snapshot.add_transformer( + snapshot.transform.key_value("lastUpdatedTime", reference_replacement=False) + ) + snapshot.match("policies", policies) + + @markers.aws.validated + def test_delete_resource_policy(self, aws_client, cleanups): + """Test deleting a resource policy.""" + policy_name = f"test-policy-{short_uid()}" + + aws_client.logs.put_resource_policy(policyName=policy_name, policyDocument=JSON_POLICY_DOC) + + # Verify it exists + response = aws_client.logs.describe_resource_policies() + policy_names = [p["policyName"] for p in response["resourcePolicies"]] + assert policy_name in policy_names + + # Delete the policy + aws_client.logs.delete_resource_policy(policyName=policy_name) + + # Verify it's deleted + response = aws_client.logs.describe_resource_policies() + policy_names = [p["policyName"] for p in response["resourcePolicies"]] + assert policy_name not in policy_names + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_delete_resource_policy_not_found(self, aws_client, snapshot): + """Test deleting a non-existent resource policy.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.delete_resource_policy(policyName="non-existent") + snapshot.match("error-policy-not-found", ctx.value.response) + + +class TestResourcePoliciesLimits: + """Tests for resource policy limit enforcement.""" + + @markers.aws.validated + def test_put_resource_policy_limit_exceeded(self, aws_client, snapshot, cleanups): + """Test that creating more than MAX_RESOURCE_POLICIES_PER_REGION fails.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + prefix = f"test-policy-limit-{short_uid()}" + + # Create the maximum number of resource policies + for idx in range(MAX_RESOURCE_POLICIES_PER_REGION): + policy_name = f"{prefix}-{idx}" + aws_client.logs.put_resource_policy( + policyName=policy_name, policyDocument=JSON_POLICY_DOC + ) + cleanups.append( + lambda pn=policy_name: aws_client.logs.delete_resource_policy(policyName=pn) + ) + + # Try to create one more - should fail + with pytest.raises(Exception) as ctx: + aws_client.logs.put_resource_policy( + policyName=f"{prefix}-too-many", policyDocument=JSON_POLICY_DOC + ) + snapshot.match("error-limit-exceeded", ctx.value.response) + + @markers.aws.validated + def test_put_resource_policy_update_existing_when_at_limit(self, aws_client, cleanups): + """Test that updating existing policy works even when at limit.""" + prefix = f"test-policy-limit-{short_uid()}" + + # Create the maximum number of resource policies + for idx in range(MAX_RESOURCE_POLICIES_PER_REGION): + policy_name = f"{prefix}-{idx}" + aws_client.logs.put_resource_policy( + policyName=policy_name, policyDocument=JSON_POLICY_DOC + ) + cleanups.append( + lambda pn=policy_name: aws_client.logs.delete_resource_policy(policyName=pn) + ) + + # Update an existing policy - should succeed + new_document = '{"Statement":[{"Action":"logs:*","Effect":"Allow","Principal":{"Service":"logs.amazonaws.com"},"Resource":"*"}]}' + response = aws_client.logs.put_resource_policy( + policyName=f"{prefix}-1", policyDocument=new_document + ) + + assert response["resourcePolicy"]["policyDocument"] == new_document diff --git a/tests/aws/services/logs/test_logs_resource_policies.snapshot.json b/tests/aws/services/logs/test_logs_resource_policies.snapshot.json new file mode 100644 index 0000000000000..9118f990627fd --- /dev/null +++ b/tests/aws/services/logs/test_logs_resource_policies.snapshot.json @@ -0,0 +1,216 @@ +{ + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_put_resource_policy": { + "recorded-date": "06-02-2026, 19:30:22", + "recorded-content": { + "put-resource-policy": { + "resourcePolicy": { + "lastUpdatedTime": "last-updated-time", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": [ + "route53.amazonaws.com" + ] + }, + "Action": "logs:PutLogEvents", + "Resource": "*" + } + ] + }, + "policyName": "", + "policyScope": "ACCOUNT" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_put_resource_policy_update": { + "recorded-date": "06-02-2026, 19:29:16", + "recorded-content": { + "updated-policy": { + "lastUpdatedTime": "last-updated-time", + "policyDocument": { + "Version": "2008-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:*", + "Resource": "*" + } + ] + }, + "policyName": "", + "policyScope": "ACCOUNT" + } + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_describe_resource_policies": { + "recorded-date": "06-02-2026, 19:33:10", + "recorded-content": { + "describe-resource-policies": { + "resourcePolicies": [ + { + "lastUpdatedTime": "last-updated-time", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": "log_arn" + } + ] + }, + "policyName": "", + "policyScope": "ACCOUNT" + }, + { + "lastUpdatedTime": "last-updated-time", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": "log_arn" + } + ] + }, + "policyName": "", + "policyScope": "ACCOUNT" + }, + { + "lastUpdatedTime": "last-updated-time", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": "log_arn" + } + ] + }, + "policyName": "", + "policyScope": "ACCOUNT" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "policies": [ + { + "policyName": "", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": "log_arn" + } + ] + }, + "lastUpdatedTime": "last-updated-time", + "policyScope": "ACCOUNT" + }, + { + "policyName": "", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": "log_arn" + } + ] + }, + "lastUpdatedTime": "last-updated-time", + "policyScope": "ACCOUNT" + }, + { + "policyName": "", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53LogsToCloudWatchLogs", + "Effect": "Allow", + "Principal": { + "Service": "route53.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": "log_arn" + } + ] + }, + "lastUpdatedTime": "last-updated-time", + "policyScope": "ACCOUNT" + } + ] + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_delete_resource_policy_not_found": { + "recorded-date": "06-02-2026, 19:36:10", + "recorded-content": { + "error-policy-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "Policy with name [non-existent] does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePoliciesLimits::test_put_resource_policy_limit_exceeded": { + "recorded-date": "06-02-2026, 19:37:18", + "recorded-content": { + "error-limit-exceeded": { + "Error": { + "Code": "LimitExceededException", + "Message": "Resource limit exceeded." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_resource_policies.validation.json b/tests/aws/services/logs/test_logs_resource_policies.validation.json new file mode 100644 index 0000000000000..b0402a0a7c98a --- /dev/null +++ b/tests/aws/services/logs/test_logs_resource_policies.validation.json @@ -0,0 +1,65 @@ +{ + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_delete_resource_policy": { + "last_validated_date": "2026-02-06T19:34:19+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.65, + "teardown": 0.0, + "total": 0.66 + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_delete_resource_policy_not_found": { + "last_validated_date": "2026-02-06T19:36:10+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 0.36, + "teardown": 0.0, + "total": 0.73 + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_describe_resource_policies": { + "last_validated_date": "2026-02-06T19:33:10+00:00", + "durations_in_seconds": { + "setup": 0.38, + "call": 0.65, + "teardown": 0.27, + "total": 1.3 + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_put_resource_policy": { + "last_validated_date": "2026-02-06T19:30:22+00:00", + "durations_in_seconds": { + "setup": 0.38, + "call": 0.57, + "teardown": 0.22, + "total": 1.17 + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePolicies::test_put_resource_policy_update": { + "last_validated_date": "2026-02-06T19:29:16+00:00", + "durations_in_seconds": { + "setup": 0.38, + "call": 0.67, + "teardown": 0.09, + "total": 1.14 + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePoliciesLimits::test_put_resource_policy_limit_exceeded": { + "last_validated_date": "2026-02-06T19:37:18+00:00", + "durations_in_seconds": { + "setup": 0.38, + "call": 1.47, + "teardown": 0.92, + "total": 2.77 + } + }, + "tests/aws/services/logs/test_logs_resource_policies.py::TestResourcePoliciesLimits::test_put_resource_policy_update_existing_when_at_limit": { + "last_validated_date": "2026-02-06T19:38:45+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.34, + "teardown": 0.97, + "total": 2.32 + } + } +} diff --git a/tests/aws/services/logs/test_logs_streams.py b/tests/aws/services/logs/test_logs_streams.py new file mode 100644 index 0000000000000..0ecd63ee44870 --- /dev/null +++ b/tests/aws/services/logs/test_logs_streams.py @@ -0,0 +1,135 @@ +"""Tests for CloudWatch Logs - Log Stream operations.""" + +import pytest + +from localstack.testing.pytest import markers +from localstack.utils.aws import arns +from localstack.utils.common import short_uid + + +def _log_stream_exists(log_streams: list, name: str) -> bool: + """Check if a log stream with the given name exists in the list.""" + return any(ls["logStreamName"] == name for ls in log_streams) + + +class TestLogsStreams: + """Tests for log stream create, delete, describe operations.""" + + @markers.aws.validated + def test_create_and_delete_log_stream(self, logs_log_group, aws_client, snapshot): + """Test basic log stream creation and deletion.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + stream_name = f"test-log-stream-{short_uid()}" + + # Create log stream + aws_client.logs.create_log_stream(logGroupName=logs_log_group, logStreamName=stream_name) + snapshot.add_transformer(snapshot.transform.regex(logs_log_group, "")) + + # Verify stream exists + response = aws_client.logs.describe_log_streams(logGroupName=logs_log_group) + snapshot.match("describe-log-streams-after-create", response) + assert _log_stream_exists(response["logStreams"], stream_name) + + # Delete log stream + aws_client.logs.delete_log_stream(logGroupName=logs_log_group, logStreamName=stream_name) + + # Verify stream is deleted + response = aws_client.logs.describe_log_streams(logGroupName=logs_log_group) + assert not _log_stream_exists(response.get("logStreams", []), stream_name) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message"]) + def test_create_log_stream_duplicate_error( + self, logs_log_group, aws_client, snapshot, cleanups + ): + """Test that creating a duplicate log stream raises an error.""" + stream_name = f"test-log-stream-{short_uid()}" + aws_client.logs.create_log_stream(logGroupName=logs_log_group, logStreamName=stream_name) + cleanups.append( + lambda: aws_client.logs.delete_log_stream( + logGroupName=logs_log_group, logStreamName=stream_name + ) + ) + + with pytest.raises(Exception) as ctx: + aws_client.logs.create_log_stream( + logGroupName=logs_log_group, logStreamName=stream_name + ) + snapshot.match("error-duplicate-log-stream", ctx.value.response) + + @markers.aws.validated + def test_describe_log_streams_with_log_group_identifier( + self, logs_log_group, aws_client, region_name, snapshot + ): + """Test describing log streams using logGroupIdentifier parameter.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(logs_log_group, "")) + stream_name = f"test-log-stream-{short_uid()}" + aws_client.logs.create_log_stream(logGroupName=logs_log_group, logStreamName=stream_name) + + # Using logGroupIdentifier with name + response = aws_client.logs.describe_log_streams(logGroupIdentifier=logs_log_group) + snapshot.match("describe-streams-identifier-name", response) + assert _log_stream_exists(response["logStreams"], stream_name) + + # Using logGroupIdentifier with ARN + account_id = aws_client.sts.get_caller_identity()["Account"] + log_group_arn = arns.log_group_arn( + logs_log_group, + account_id=account_id, + region_name=region_name, + ) + response = aws_client.logs.describe_log_streams(logGroupIdentifier=log_group_arn) + snapshot.match("describe-streams-identifier-arn", response) + assert _log_stream_exists(response["logStreams"], stream_name) + + # Using both logGroupName and logGroupIdentifier should raise error + with pytest.raises(Exception) as ctx: + aws_client.logs.describe_log_streams( + logGroupName=logs_log_group, logGroupIdentifier=logs_log_group + ) + snapshot.match("error-both-name-and-identifier", ctx.value.response) + + @markers.aws.validated + def test_describe_log_streams_with_prefix(self, logs_log_group, aws_client, snapshot, cleanups): + """Test describing log streams with prefix filter.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(logs_log_group, "")) + prefix = f"test-prefix-{short_uid()}" + stream_names = [f"{prefix}-stream-{i}" for i in range(3)] + + for name in stream_names: + aws_client.logs.create_log_stream(logGroupName=logs_log_group, logStreamName=name) + cleanups.append( + lambda n=name: aws_client.logs.delete_log_stream( + logGroupName=logs_log_group, logStreamName=n + ) + ) + + response = aws_client.logs.describe_log_streams( + logGroupName=logs_log_group, logStreamNamePrefix=prefix + ) + snapshot.match("describe-streams-with-prefix", response) + for name in stream_names: + assert _log_stream_exists(response["logStreams"], name) + + @markers.aws.validated + def test_log_stream_arn_format(self, logs_log_group, aws_client, region_name, snapshot): + """Test that log stream ARN is in the correct format.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + snapshot.add_transformer(snapshot.transform.regex(logs_log_group, "")) + stream_name = f"test-arn-{short_uid()}" + aws_client.logs.create_log_stream(logGroupName=logs_log_group, logStreamName=stream_name) + + response = aws_client.logs.describe_log_streams(logGroupName=logs_log_group) + snapshot.match("describe-log-streams-with-arn", response) + + stream = response["logStreams"][0] + account_id = aws_client.sts.get_caller_identity()["Account"] + + # Verify ARN format + expected_arn = ( + f"arn:aws:logs:{region_name}:{account_id}:" + f"log-group:{logs_log_group}:log-stream:{stream_name}" + ) + assert stream["arn"] == expected_arn diff --git a/tests/aws/services/logs/test_logs_streams.snapshot.json b/tests/aws/services/logs/test_logs_streams.snapshot.json new file mode 100644 index 0000000000000..4305a7837c068 --- /dev/null +++ b/tests/aws/services/logs/test_logs_streams.snapshot.json @@ -0,0 +1,129 @@ +{ + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_create_and_delete_log_stream": { + "recorded-date": "03-02-2026, 21:22:58", + "recorded-content": { + "describe-log-streams-after-create": { + "logStreams": [ + { + "arn": "arn::logs::111111111111:log-group::log-stream:", + "creationTime": "timestamp", + "logStreamName": "", + "storedBytes": 0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_create_log_stream_duplicate_error": { + "recorded-date": "03-02-2026, 21:22:58", + "recorded-content": { + "error-duplicate-log-stream": { + "Error": { + "Code": "ResourceAlreadyExistsException", + "Message": "The specified log stream already exists" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_describe_log_streams_with_log_group_identifier": { + "recorded-date": "03-02-2026, 21:22:59", + "recorded-content": { + "describe-streams-identifier-name": { + "logStreams": [ + { + "arn": "arn::logs::111111111111:log-group::log-stream:", + "creationTime": "timestamp", + "logStreamName": "", + "storedBytes": 0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-streams-identifier-arn": { + "logStreams": [ + { + "arn": "arn::logs::111111111111:log-group::log-stream:", + "creationTime": "timestamp", + "logStreamName": "", + "storedBytes": 0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "error-both-name-and-identifier": { + "Error": { + "Code": "ValidationException", + "Message": "LogGroup name and LogGroup ARN are mutually exclusive parameters." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_describe_log_streams_with_prefix": { + "recorded-date": "03-02-2026, 21:23:00", + "recorded-content": { + "describe-streams-with-prefix": { + "logStreams": [ + { + "arn": "arn::logs::111111111111:log-group::log-stream:", + "creationTime": "timestamp", + "logStreamName": "", + "storedBytes": 0 + }, + { + "arn": "arn::logs::111111111111:log-group::log-stream:", + "creationTime": "timestamp", + "logStreamName": "", + "storedBytes": 0 + }, + { + "arn": "arn::logs::111111111111:log-group::log-stream:", + "creationTime": "timestamp", + "logStreamName": "", + "storedBytes": 0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_log_stream_arn_format": { + "recorded-date": "03-02-2026, 21:23:00", + "recorded-content": { + "describe-log-streams-with-arn": { + "logStreams": [ + { + "arn": "arn::logs::111111111111:log-group::log-stream:", + "creationTime": "timestamp", + "logStreamName": "", + "storedBytes": 0 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_streams.validation.json b/tests/aws/services/logs/test_logs_streams.validation.json new file mode 100644 index 0000000000000..a624becfdbe44 --- /dev/null +++ b/tests/aws/services/logs/test_logs_streams.validation.json @@ -0,0 +1,47 @@ +{ + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_create_and_delete_log_stream": { + "last_validated_date": "2026-02-03T21:22:58+00:00", + "durations_in_seconds": { + "setup": 0.76, + "call": 0.38, + "teardown": 0.11, + "total": 1.25 + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_create_log_stream_duplicate_error": { + "last_validated_date": "2026-02-03T21:22:58+00:00", + "durations_in_seconds": { + "setup": 0.1, + "call": 0.16, + "teardown": 0.25, + "total": 0.51 + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_describe_log_streams_with_log_group_identifier": { + "last_validated_date": "2026-02-03T21:22:59+00:00", + "durations_in_seconds": { + "setup": 0.1, + "call": 0.41, + "teardown": 0.11, + "total": 0.62 + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_describe_log_streams_with_prefix": { + "last_validated_date": "2026-02-03T21:23:00+00:00", + "durations_in_seconds": { + "setup": 0.12, + "call": 0.34, + "teardown": 0.42, + "total": 0.88 + } + }, + "tests/aws/services/logs/test_logs_streams.py::TestLogsStreams::test_log_stream_arn_format": { + "last_validated_date": "2026-02-03T21:23:00+00:00", + "durations_in_seconds": { + "setup": 0.1, + "call": 0.25, + "teardown": 0.11, + "total": 0.46 + } + } +} diff --git a/tests/aws/services/logs/test_logs_subscription_filters.py b/tests/aws/services/logs/test_logs_subscription_filters.py new file mode 100644 index 0000000000000..483fda7f2055f --- /dev/null +++ b/tests/aws/services/logs/test_logs_subscription_filters.py @@ -0,0 +1,531 @@ +"""Tests for CloudWatch Logs - Subscription Filter operations.""" + +import base64 +import gzip +import json +import re + +import pytest +from localstack_snapshot.snapshots.transformer import KeyValueBasedTransformer + +from localstack.aws.api.lambda_ import Runtime +from localstack.testing.config import TEST_AWS_REGION_NAME +from localstack.testing.pytest import markers +from localstack.utils import testutil +from localstack.utils.aws.arns import get_partition +from localstack.utils.common import now_utc, retry, short_uid +from tests.aws.services.lambda_.test_lambda import TEST_LAMBDA_PYTHON_ECHO + +# IAM role and policy definitions for cross-service integration +logs_role = { + "Statement": { + "Effect": "Allow", + "Principal": {"Service": f"logs.{TEST_AWS_REGION_NAME}.amazonaws.com"}, + "Action": "sts:AssumeRole", + } +} + +kinesis_permission = { + "Version": "2012-10-17", + "Statement": [{"Effect": "Allow", "Action": "kinesis:PutRecord", "Resource": "*"}], +} + +s3_firehose_role = { + "Statement": { + "Sid": "", + "Effect": "Allow", + "Principal": {"Service": "firehose.amazonaws.com"}, + "Action": "sts:AssumeRole", + } +} + +s3_firehose_permission = { + "Version": "2012-10-17", + "Statement": [{"Effect": "Allow", "Action": ["s3:*", "s3-object-lambda:*"], "Resource": "*"}], +} + +firehose_permission = { + "Version": "2012-10-17", + "Statement": [{"Effect": "Allow", "Action": ["firehose:*"], "Resource": "*"}], +} + + +def _subscription_filter_exists(filters: list, name: str) -> bool: + """Check if a subscription filter with the given name exists in the list.""" + return any(f["filterName"] == name for f in filters) + + +class TestSubscriptionFilters: + """Tests for subscription filter operations.""" + + @markers.aws.validated + def test_describe_subscription_filters_empty(self, logs_log_group, aws_client, snapshot): + """Test describing subscription filters when none exist.""" + snapshot.add_transformer(snapshot.transform.logs_api()) + response = aws_client.logs.describe_subscription_filters(logGroupName=logs_log_group) + snapshot.match("describe-subscription-filters-empty", response) + + @markers.aws.validated + def test_describe_subscription_filters_log_group_not_found(self, aws_client, snapshot): + """Test describing subscription filters for non-existent log group.""" + with pytest.raises(Exception) as ctx: + aws_client.logs.describe_subscription_filters(logGroupName="not-existing-log-group") + snapshot.match("error-log-group-not-found", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Statement.Condition.StringEquals", + "$..add_permission.ResponseMetadata.HTTPStatusCode", + "$..subscriptionFilters..applyOnTransformedLogs", + ] + ) + def test_put_subscription_filter_lambda( + self, + logs_log_group, + logs_log_stream, + create_lambda_function, + snapshot, + aws_client, + region_name, + ): + """Test putting a subscription filter with Lambda destination.""" + snapshot.add_transformer(snapshot.transform.lambda_api()) + snapshot.add_transformer(snapshot.transform.key_value("logGroupName")) + snapshot.add_transformer(snapshot.transform.key_value("logStreamName")) + snapshot.add_transformer( + KeyValueBasedTransformer( + lambda k, v: ( + v + if k == "id" and (isinstance(v, str) and re.match(re.compile(r"^[0-9]+$"), v)) + else None + ), + replacement="id", + replace_reference=False, + ), + ) + + test_lambda_name = f"test-lambda-function-{short_uid()}" + func_arn = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=test_lambda_name, + runtime=Runtime.python3_12, + )["CreateFunctionResponse"]["FunctionArn"] + + aws_client.lambda_.invoke(FunctionName=test_lambda_name, Payload=b"{}") + + # Get account-id to set the correct policy + account_id = aws_client.sts.get_caller_identity()["Account"] + result = aws_client.lambda_.add_permission( + FunctionName=test_lambda_name, + StatementId=test_lambda_name, + Principal=f"logs.{region_name}.amazonaws.com", + Action="lambda:InvokeFunction", + SourceArn=f"arn:{get_partition(region_name)}:logs:{region_name}:{account_id}:log-group:{logs_log_group}:*", + SourceAccount=account_id, + ) + snapshot.match("add_permission", result) + + result = aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test", + filterPattern="", + destinationArn=func_arn, + ) + snapshot.match("put_subscription_filter", result) + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, + logStreamName=logs_log_stream, + logEvents=[ + {"timestamp": now_utc(millis=True), "message": "test"}, + {"timestamp": now_utc(millis=True), "message": "test 2"}, + ], + ) + + response = aws_client.logs.describe_subscription_filters(logGroupName=logs_log_group) + assert len(response["subscriptionFilters"]) == 1 + snapshot.match("describe_subscription_filter", response) + + def check_invocation(): + events = testutil.list_all_log_events( + log_group_name=f"/aws/lambda/{test_lambda_name}", logs_client=aws_client.logs + ) + # We only are interested in events that contain "awslogs" + filtered_events = [] + for e in events: + if "awslogs" in e["message"]: + data = json.loads(e["message"])["awslogs"]["data"].encode("utf-8") + decoded_data = gzip.decompress(base64.b64decode(data)).decode("utf-8") + for log_event in json.loads(decoded_data)["logEvents"]: + filtered_events.append(log_event) + assert len(filtered_events) == 2 + + filtered_events.sort(key=lambda k: k.get("message")) + snapshot.match("list_all_log_events", filtered_events) + + retry(check_invocation, retries=6, sleep=3.0) + + @markers.aws.validated + def test_put_subscription_filter_kinesis( + self, logs_log_group, logs_log_stream, create_iam_role_with_policy, aws_client + ): + """Test putting a subscription filter with Kinesis destination.""" + kinesis_name = f"test-kinesis-{short_uid()}" + filter_name = "Destination" + aws_client.kinesis.create_stream(StreamName=kinesis_name, ShardCount=1) + + try: + result = aws_client.kinesis.describe_stream(StreamName=kinesis_name)[ + "StreamDescription" + ] + kinesis_arn = result["StreamARN"] + role = f"test-kinesis-role-{short_uid()}" + policy_name = f"test-kinesis-role-policy-{short_uid()}" + role_arn = create_iam_role_with_policy( + RoleName=role, + PolicyName=policy_name, + RoleDefinition=logs_role, + PolicyDefinition=kinesis_permission, + ) + + # Wait for stream-status "ACTIVE" + status = result["StreamStatus"] + if status != "ACTIVE": + + def check_stream_active(): + state = aws_client.kinesis.describe_stream(StreamName=kinesis_name)[ + "StreamDescription" + ]["StreamStatus"] + if state != "ACTIVE": + raise Exception(f"StreamStatus is {state}") + + retry(check_stream_active, retries=6, sleep=1.0, sleep_before=2.0) + + def put_subscription_filter(): + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName=filter_name, + filterPattern="", + destinationArn=kinesis_arn, + roleArn=role_arn, + ) + + retry(put_subscription_filter, retries=6, sleep=3.0) + + def put_event(): + aws_client.logs.put_log_events( + logGroupName=logs_log_group, + logStreamName=logs_log_stream, + logEvents=[ + {"timestamp": now_utc(millis=True), "message": "test"}, + {"timestamp": now_utc(millis=True), "message": "test 2"}, + ], + ) + + retry(put_event, retries=6, sleep=3.0) + + shard_iterator = aws_client.kinesis.get_shard_iterator( + StreamName=kinesis_name, + ShardId="shardId-000000000000", + ShardIteratorType="TRIM_HORIZON", + )["ShardIterator"] + + response = aws_client.kinesis.get_records(ShardIterator=shard_iterator) + # AWS sends messages as health checks + assert len(response["Records"]) >= 1 + found = False + for record in response["Records"]: + data = record["Data"] + unzipped_data = gzip.decompress(data) + json_data = json.loads(unzipped_data) + if "test" in json.dumps(json_data["logEvents"]): + assert len(json_data["logEvents"]) == 2 + assert json_data["logEvents"][0]["message"] == "test" + assert json_data["logEvents"][1]["message"] == "test 2" + found = True + + assert found + finally: + aws_client.kinesis.delete_stream(StreamName=kinesis_name, EnforceConsumerDeletion=True) + aws_client.logs.delete_subscription_filter( + logGroupName=logs_log_group, filterName=filter_name + ) + + @markers.aws.validated + def test_put_subscription_filter_firehose( + self, logs_log_group, logs_log_stream, s3_bucket, create_iam_role_with_policy, aws_client + ): + """Test putting a subscription filter with Firehose destination.""" + try: + firehose_name = f"test-firehose-{short_uid()}" + s3_bucket_arn = f"arn:aws:s3:::{s3_bucket}" + + role = f"test-firehose-s3-role-{short_uid()}" + policy_name = f"test-firehose-s3-role-policy-{short_uid()}" + role_arn = create_iam_role_with_policy( + RoleName=role, + PolicyName=policy_name, + RoleDefinition=s3_firehose_role, + PolicyDefinition=s3_firehose_permission, + ) + + # AWS has troubles creating the delivery stream the first time + def create_delivery_stream(): + aws_client.firehose.create_delivery_stream( + DeliveryStreamName=firehose_name, + S3DestinationConfiguration={ + "BucketARN": s3_bucket_arn, + "RoleARN": role_arn, + "BufferingHints": {"SizeInMBs": 1, "IntervalInSeconds": 60}, + }, + ) + + retry(create_delivery_stream, retries=5, sleep=10.0) + + response = aws_client.firehose.describe_delivery_stream( + DeliveryStreamName=firehose_name + ) + firehose_arn = response["DeliveryStreamDescription"]["DeliveryStreamARN"] + + role = f"test-firehose-role-{short_uid()}" + policy_name = f"test-firehose-role-policy-{short_uid()}" + role_arn_logs = create_iam_role_with_policy( + RoleName=role, + PolicyName=policy_name, + RoleDefinition=logs_role, + PolicyDefinition=firehose_permission, + ) + + def check_stream_active(): + state = aws_client.firehose.describe_delivery_stream( + DeliveryStreamName=firehose_name + )["DeliveryStreamDescription"]["DeliveryStreamStatus"] + if state != "ACTIVE": + raise Exception(f"DeliveryStreamStatus is {state}") + + retry(check_stream_active, retries=60, sleep=30.0) + + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="Destination", + filterPattern="", + destinationArn=firehose_arn, + roleArn=role_arn_logs, + ) + + aws_client.logs.put_log_events( + logGroupName=logs_log_group, + logStreamName=logs_log_stream, + logEvents=[ + {"timestamp": now_utc(millis=True), "message": "test"}, + {"timestamp": now_utc(millis=True), "message": "test 2"}, + ], + ) + + def list_objects(): + response = aws_client.s3.list_objects(Bucket=s3_bucket) + assert len(response["Contents"]) >= 1 + + retry(list_objects, retries=60, sleep=30.0) + response = aws_client.s3.list_objects(Bucket=s3_bucket) + key = response["Contents"][-1]["Key"] + response = aws_client.s3.get_object(Bucket=s3_bucket, Key=key) + content = gzip.decompress(response["Body"].read()).decode("utf-8") + assert "DATA_MESSAGE" in content + assert "test" in content + assert "test 2" in content + + finally: + aws_client.firehose.delete_delivery_stream( + DeliveryStreamName=firehose_name, AllowForceDelete=True + ) + + +class TestSubscriptionFilterUpdates: + """Tests for subscription filter update and delete operations.""" + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..subscriptionFilters..applyOnTransformedLogs"]) + def test_put_subscription_filter_update( + self, logs_log_group, create_lambda_function, aws_client, region_name, snapshot + ): + """Test updating a subscription filter.""" + test_lambda_name = f"test-lambda-{short_uid()}" + func_arn = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=test_lambda_name, + runtime=Runtime.python3_12, + )["CreateFunctionResponse"]["FunctionArn"] + + account_id = aws_client.sts.get_caller_identity()["Account"] + aws_client.lambda_.add_permission( + FunctionName=test_lambda_name, + StatementId=test_lambda_name, + Principal=f"logs.{region_name}.amazonaws.com", + Action="lambda:InvokeFunction", + SourceArn=f"arn:{get_partition(region_name)}:logs:{region_name}:{account_id}:log-group:{logs_log_group}:*", + SourceAccount=account_id, + ) + + # Create initial subscription filter + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test", + filterPattern="", + destinationArn=func_arn, + ) + + response = aws_client.logs.describe_subscription_filters(logGroupName=logs_log_group) + assert len(response["subscriptionFilters"]) == 1 + + # Update subscription filter (same filterName) + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test", + filterPattern="[]", + destinationArn=func_arn, + ) + + response = aws_client.logs.describe_subscription_filters(logGroupName=logs_log_group) + snapshot.add_transformer(snapshot.transform.regex(func_arn, "")) + snapshot.add_transformer(snapshot.transform.regex(logs_log_group, "")) + snapshot.match("updated-filter", response) + + @markers.aws.validated + def test_put_subscription_filter_limit_exceeded( + self, logs_log_group, create_lambda_function, aws_client, region_name, snapshot, account_id + ): + """Test that only 2 subscription filters can be associated with a log group.""" + test_lambda_name = f"test-lambda-{short_uid()}" + func_arn = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=test_lambda_name, + runtime=Runtime.python3_12, + )["CreateFunctionResponse"]["FunctionArn"] + + aws_client.lambda_.add_permission( + FunctionName=test_lambda_name, + StatementId=test_lambda_name, + Principal=f"logs.{region_name}.amazonaws.com", + Action="lambda:InvokeFunction", + SourceArn=f"arn:{get_partition(region_name)}:logs:{region_name}:{account_id}:log-group:{logs_log_group}:*", + SourceAccount=account_id, + ) + + # Create first subscription filter + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test-1", + filterPattern="", + destinationArn=func_arn, + ) + + # Create second subscription filter + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test-2", + filterPattern="[]", + destinationArn=func_arn, + ) + + # Third should fail + with pytest.raises(Exception) as ctx: + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test-3", + filterPattern="", + destinationArn=func_arn, + ) + snapshot.match("error-limit-exceeded", ctx.value.response) + + @markers.aws.validated + def test_delete_subscription_filter( + self, logs_log_group, create_lambda_function, aws_client, region_name + ): + """Test deleting a subscription filter.""" + test_lambda_name = f"test-lambda-{short_uid()}" + func_arn = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=test_lambda_name, + runtime=Runtime.python3_12, + )["CreateFunctionResponse"]["FunctionArn"] + + account_id = aws_client.sts.get_caller_identity()["Account"] + aws_client.lambda_.add_permission( + FunctionName=test_lambda_name, + StatementId=test_lambda_name, + Principal=f"logs.{region_name}.amazonaws.com", + Action="lambda:InvokeFunction", + SourceArn=f"arn:{get_partition(region_name)}:logs:{region_name}:{account_id}:log-group:{logs_log_group}:*", + SourceAccount=account_id, + ) + + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test", + filterPattern="", + destinationArn=func_arn, + ) + + response = aws_client.logs.describe_subscription_filters(logGroupName=logs_log_group) + assert len(response["subscriptionFilters"]) == 1 + + # Delete subscription filter + aws_client.logs.delete_subscription_filter(logGroupName=logs_log_group, filterName="test") + + response = aws_client.logs.describe_subscription_filters(logGroupName=logs_log_group) + assert len(response["subscriptionFilters"]) == 0 + + @markers.aws.validated + def test_delete_subscription_filter_errors(self, logs_log_group, aws_client, snapshot): + """Test delete subscription filter error handling.""" + # Non-existent log group + with pytest.raises(Exception) as ctx: + aws_client.logs.delete_subscription_filter( + logGroupName="not-existing-log-group", filterName="test" + ) + snapshot.match("error-log-group-not-found", ctx.value.response) + + # Non-existent filter + with pytest.raises(Exception) as ctx: + aws_client.logs.delete_subscription_filter( + logGroupName=logs_log_group, filterName="wrong-filter-name" + ) + snapshot.match("error-filter-not-found", ctx.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Code", "$..Error.Message"]) + def test_put_subscription_filter_errors( + self, logs_log_group, create_lambda_function, aws_client, snapshot + ): + """Test put subscription filter error handling.""" + # Non-existent log group + with pytest.raises(Exception) as ctx: + aws_client.logs.put_subscription_filter( + logGroupName="not-existing-log-group", + filterName="test", + filterPattern="", + destinationArn="arn:aws:lambda:us-east-1:123456789012:function:test", + ) + snapshot.match("error-log-group-not-found", ctx.value.response) + + # Non-existent Lambda function + with pytest.raises(Exception) as ctx: + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test", + filterPattern="", + destinationArn="arn:aws:lambda:us-east-1:123456789012:function:not-existing", + ) + snapshot.match("error-lambda-not-found", ctx.value.response) + + # Non-existent Kinesis stream + with pytest.raises(Exception) as ctx: + aws_client.logs.put_subscription_filter( + logGroupName=logs_log_group, + filterName="test", + filterPattern="", + destinationArn="arn:aws:kinesis:us-east-1:123456789012:stream/unknown-stream", + ) + snapshot.match("error-kinesis-not-found", ctx.value.response) diff --git a/tests/aws/services/logs/test_logs_subscription_filters.snapshot.json b/tests/aws/services/logs/test_logs_subscription_filters.snapshot.json new file mode 100644 index 0000000000000..39f0dc6df28f2 --- /dev/null +++ b/tests/aws/services/logs/test_logs_subscription_filters.snapshot.json @@ -0,0 +1,189 @@ +{ + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_describe_subscription_filters_empty": { + "recorded-date": "06-02-2026, 20:03:19", + "recorded-content": { + "describe-subscription-filters-empty": { + "subscriptionFilters": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_describe_subscription_filters_log_group_not_found": { + "recorded-date": "06-02-2026, 20:07:39", + "recorded-content": { + "error-log-group-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified log group does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_put_subscription_filter_lambda": { + "recorded-date": "06-02-2026, 20:12:46", + "recorded-content": { + "add_permission": { + "Statement": { + "Sid": "", + "Effect": "Allow", + "Principal": { + "Service": "logs..amazonaws.com" + }, + "Action": "lambda:InvokeFunction", + "Resource": "arn::lambda::111111111111:function:", + "Condition": { + "StringEquals": { + "AWS:SourceAccount": "111111111111" + }, + "ArnLike": { + "AWS:SourceArn": "arn::logs::111111111111:log-group::" + } + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 201 + } + }, + "put_subscription_filter": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_subscription_filter": { + "subscriptionFilters": [ + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "destinationArn": "arn::lambda::111111111111:function:", + "distribution": "ByLogStream", + "filterName": "test", + "filterPattern": "", + "logGroupName": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list_all_log_events": [ + { + "id": "id", + "timestamp": "timestamp", + "message": "test" + }, + { + "id": "id", + "timestamp": "timestamp", + "message": "test 2" + } + ] + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_put_subscription_filter_update": { + "recorded-date": "06-02-2026, 20:46:56", + "recorded-content": { + "updated-filter": { + "subscriptionFilters": [ + { + "applyOnTransformedLogs": false, + "creationTime": "timestamp", + "destinationArn": "", + "distribution": "ByLogStream", + "filterName": "test", + "filterPattern": "[]", + "logGroupName": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_put_subscription_filter_limit_exceeded": { + "recorded-date": "06-02-2026, 20:49:45", + "recorded-content": { + "error-limit-exceeded": { + "Error": { + "Code": "LimitExceededException", + "Message": "Resource limit exceeded." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_delete_subscription_filter_errors": { + "recorded-date": "06-02-2026, 20:51:38", + "recorded-content": { + "error-log-group-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified log group does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "error-filter-not-found": { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "The specified subscription filter does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_put_subscription_filter_errors": { + "recorded-date": "06-02-2026, 20:52:39", + "recorded-content": { + "error-log-group-not-found": { + "Error": { + "Code": "AccessDeniedException", + "Message": "Cross-account lambda invocation passing is not allowed. You must use DestinationPolicies to create cross account lambda triggers." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "error-lambda-not-found": { + "Error": { + "Code": "AccessDeniedException", + "Message": "Cross-account lambda invocation passing is not allowed. You must use DestinationPolicies to create cross account lambda triggers." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "error-kinesis-not-found": { + "Error": { + "Code": "InvalidParameterException", + "Message": "destinationArn for vendor kinesis cannot be used without roleArn" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/logs/test_logs_subscription_filters.validation.json b/tests/aws/services/logs/test_logs_subscription_filters.validation.json new file mode 100644 index 0000000000000..d3cf8f0d62fbb --- /dev/null +++ b/tests/aws/services/logs/test_logs_subscription_filters.validation.json @@ -0,0 +1,92 @@ +{ + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_delete_subscription_filter": { + "last_validated_date": "2026-02-06T20:51:25+00:00", + "durations_in_seconds": { + "setup": 11.08, + "call": 2.96, + "teardown": 0.9, + "total": 14.94 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_delete_subscription_filter_errors": { + "last_validated_date": "2026-02-06T20:51:38+00:00", + "durations_in_seconds": { + "setup": 0.73, + "call": 0.19, + "teardown": 0.13, + "total": 1.05 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_put_subscription_filter_errors": { + "last_validated_date": "2026-02-06T20:52:40+00:00", + "durations_in_seconds": { + "setup": 11.39, + "call": 0.22, + "teardown": 0.54, + "total": 12.15 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_put_subscription_filter_limit_exceeded": { + "last_validated_date": "2026-02-06T20:49:46+00:00", + "durations_in_seconds": { + "setup": 11.38, + "call": 2.59, + "teardown": 0.91, + "total": 14.88 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilterUpdates::test_put_subscription_filter_update": { + "last_validated_date": "2026-02-06T20:46:57+00:00", + "durations_in_seconds": { + "setup": 11.41, + "call": 2.81, + "teardown": 0.92, + "total": 15.14 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_describe_subscription_filters_empty": { + "last_validated_date": "2026-02-06T20:03:19+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.1, + "teardown": 0.13, + "total": 0.97 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_describe_subscription_filters_log_group_not_found": { + "last_validated_date": "2026-02-06T20:07:39+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 0.34, + "teardown": 0.0, + "total": 0.71 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_put_subscription_filter_firehose": { + "last_validated_date": "2026-02-06T20:11:21+00:00", + "durations_in_seconds": { + "setup": 0.97, + "call": 134.54, + "teardown": 2.17, + "total": 137.68 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_put_subscription_filter_kinesis": { + "last_validated_date": "2026-02-06T20:08:43+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 12.6, + "teardown": 0.5, + "total": 13.61 + } + }, + "tests/aws/services/logs/test_logs_subscription_filters.py::TestSubscriptionFilters::test_put_subscription_filter_lambda": { + "last_validated_date": "2026-02-06T20:12:47+00:00", + "durations_in_seconds": { + "setup": 11.6, + "call": 9.74, + "teardown": 1.04, + "total": 22.38 + } + } +} diff --git a/tests/aws/services/opensearch/test_opensearch.py b/tests/aws/services/opensearch/test_opensearch.py index b62dfce58f93b..4a378693b08e9 100644 --- a/tests/aws/services/opensearch/test_opensearch.py +++ b/tests/aws/services/opensearch/test_opensearch.py @@ -15,6 +15,8 @@ from localstack import config from localstack.aws.api.opensearch import ( AdvancedSecurityOptionsInput, + AutoTuneDesiredState, + AutoTuneState, ClusterConfig, DomainEndpointOptions, EBSOptions, @@ -24,11 +26,6 @@ OpenSearchPartitionInstanceType, VolumeType, ) -from localstack.constants import ( - ELASTICSEARCH_DEFAULT_VERSION, - OPENSEARCH_DEFAULT_VERSION, - OPENSEARCH_PLUGIN_LIST, -) from localstack.services.opensearch import provider from localstack.services.opensearch.cluster import CustomEndpoint, EdgeProxiedOpensearchCluster from localstack.services.opensearch.cluster_manager import ( @@ -39,7 +36,13 @@ SingletonClusterManager, create_cluster_manager, ) -from localstack.services.opensearch.packages import opensearch_package +from localstack.services.opensearch.packages import ( + ELASTICSEARCH_DEFAULT_VERSION, + OPENSEARCH_DEFAULT_VERSION, + OPENSEARCH_PLUGIN_LIST, + opensearch_package, +) +from localstack.testing import config as test_config from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.common import call_safe, poll_condition, retry, short_uid, start_worker_thread @@ -79,11 +82,14 @@ def run_install(*args): @pytest.fixture(autouse=True) def opensearch(): + if is_aws_cloud() or test_config.TEST_SKIP_LOCALSTACK_START: + # we don't install the dependencies if LocalStack is not running in process + return + if not installed.is_set(): install_async() assert installed.wait(timeout=5 * 60), "gave up waiting for opensearch to install" - yield def try_cluster_health(cluster_url: str): @@ -99,6 +105,21 @@ def try_cluster_health(cluster_url: str): ], "expected cluster state to be in a valid state" +@pytest.fixture(autouse=True) +def disable_ssl_validation_for_unsupported_regions(region_name, monkeypatch): + # list of regions supported in our certificate + # prevents SSL verification for regional hostnames not present in the SAN list of the certificate + if region_name not in { + "eu-central-1", + "eu-west-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + }: + monkeypatch.setattr(requests, "verify_ssl", False) + + @markers.skip_offline class TestOpensearchProvider: """ @@ -179,6 +200,7 @@ def test_get_compatible_version_for_domain(self, opensearch_create_domain, aws_c "$..SnapshotOptions.Options.AutomatedSnapshotStartHour", "$..SnapshotOptions.Status.UpdateVersion", "$..SoftwareUpdateOptions", + "$..Status.UpdateVersion", "$..VPCOptions.Status.UpdateVersion", ] ) @@ -259,7 +281,11 @@ def test_domain_lifecycle( plugins_response = http_client.get(plugins_url, headers={"Accept": "application/json"}) else: # TODO fix ssl validation error when using the signed request for the elastic search domain - plugins_response = requests.get(plugins_url, headers={"Accept": "application/json"}) + plugins_response = requests.get( + plugins_url, + headers={"Accept": "application/json"}, + verify=False, + ) installed_plugins = {plugin["component"] for plugin in plugins_response.json()} requested_plugins = set(OPENSEARCH_PLUGIN_LIST) @@ -268,6 +294,59 @@ def test_domain_lifecycle( delete_response = aws_client.opensearch.delete_domain(DomainName=domain_name) snapshot.match("delete-response", delete_response["DomainStatus"]) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Status.UpdateVersion", + ] + ) + def test_autotune_state_transitions(self, opensearch_create_domain, aws_client, snapshot): + domain_name = opensearch_create_domain( + AutoTuneOptions={"DesiredState": AutoTuneDesiredState.ENABLED}, + ClusterConfig={ + "InstanceType": "m5.large.search", + "InstanceCount": 1, + }, + EBSOptions={ + "EBSEnabled": True, + "VolumeType": "gp2", + "VolumeSize": 10, + }, + ) + + timeout = 25 * 60 if is_aws_cloud() else 2 * 60 + interval = 10 if is_aws_cloud() else 0.5 + + def wait_for_autotune_state(expected_state: AutoTuneState) -> dict: + def _state_matches(): + status = aws_client.opensearch.describe_domain(DomainName=domain_name) + autotune = status["DomainStatus"].get("AutoTuneOptions") or {} + return autotune.get("State") == expected_state + + assert poll_condition(_state_matches, timeout=timeout, interval=interval) + final_status = aws_client.opensearch.describe_domain(DomainName=domain_name) + return final_status["DomainStatus"].get("AutoTuneOptions") or {} + + enabled_status = wait_for_autotune_state(AutoTuneState.ENABLED) + snapshot.match("autotune_enabled_status", enabled_status) + + aws_client.opensearch.update_domain_config( + DomainName=domain_name, AutoTuneOptions={"DesiredState": AutoTuneDesiredState.DISABLED} + ) + + disabled_status = wait_for_autotune_state(AutoTuneState.DISABLED) + snapshot.match("autotune_disabled_status", disabled_status) + + def _config_matches(): + config = aws_client.opensearch.describe_domain_config(DomainName=domain_name) + options = (config["DomainConfig"].get("AutoTuneOptions") or {}).get("Options") or {} + return options.get("DesiredState") == AutoTuneDesiredState.DISABLED + + assert poll_condition(_config_matches, timeout=timeout, interval=interval) + final_config = aws_client.opensearch.describe_domain_config(DomainName=domain_name) + config_autotune = final_config["DomainConfig"].get("AutoTuneOptions") or {} + snapshot.match("autotune_domain_config", config_autotune) + @markers.aws.only_localstack def test_security_plugin(self, opensearch_create_domain, aws_client): master_user_auth = ("master-user", "1[D3&2S)u9[G") @@ -636,6 +715,7 @@ def test_search(self, opensearch_endpoint, opensearch_document_path): f"search unsuccessful({response.status_code}): {response.text}" ) + @markers.requires_in_process @markers.aws.only_localstack def test_endpoint_strategy_path(self, monkeypatch, opensearch_create_domain, aws_client): monkeypatch.setattr(config, "OPENSEARCH_ENDPOINT_STRATEGY", "path") @@ -649,6 +729,7 @@ def test_endpoint_strategy_path(self, monkeypatch, opensearch_create_domain, aws endpoint = status["Endpoint"] assert endpoint.endswith(f"/{domain_name}") + @markers.requires_in_process @markers.aws.only_localstack def test_endpoint_strategy_port(self, monkeypatch, opensearch_create_domain, aws_client): monkeypatch.setattr(config, "OPENSEARCH_ENDPOINT_STRATEGY", "port") @@ -684,6 +765,7 @@ def test_cloudformation_deployment(self, deploy_cfn_template, aws_client): @markers.skip_offline class TestEdgeProxiedOpensearchCluster: + @markers.requires_in_process @markers.aws.only_localstack def test_route_through_edge(self): cluster_id = f"domain-{short_uid()}" @@ -697,7 +779,6 @@ def test_route_through_edge(self): response = requests.get(cluster_url) assert response.ok, f"cluster endpoint returned an error: {response.text}" - assert response.json()["version"]["number"] == "2.11.1" response = requests.get(f"{cluster_url}/_cluster/health") assert response.ok, f"cluster health endpoint returned an error: {response.text}" @@ -781,6 +862,7 @@ def test_custom_endpoint_disabled( @markers.skip_offline class TestMultiClusterManager: + @markers.requires_in_process @markers.aws.only_localstack def test_multi_cluster(self, account_id, monkeypatch): monkeypatch.setattr(config, "OPENSEARCH_ENDPOINT_STRATEGY", "domain") @@ -829,6 +911,7 @@ def test_multi_cluster(self, account_id, monkeypatch): @markers.skip_offline class TestMultiplexingClusterManager: + @markers.requires_in_process @markers.aws.only_localstack def test_multiplexing_cluster(self, account_id, monkeypatch): monkeypatch.setattr(config, "OPENSEARCH_ENDPOINT_STRATEGY", "domain") @@ -877,6 +960,7 @@ def test_multiplexing_cluster(self, account_id, monkeypatch): @markers.skip_offline class TestSingletonClusterManager: + @markers.requires_in_process @markers.aws.only_localstack def test_endpoint_strategy_port_singleton_cluster(self, account_id, monkeypatch): monkeypatch.setattr(config, "OPENSEARCH_ENDPOINT_STRATEGY", "port") @@ -923,6 +1007,7 @@ def test_endpoint_strategy_port_singleton_cluster(self, account_id, monkeypatch) @markers.skip_offline class TestCustomBackendManager: + @markers.requires_in_process @markers.aws.only_localstack def test_custom_backend(self, account_id, httpserver, monkeypatch): monkeypatch.setattr(config, "OPENSEARCH_ENDPOINT_STRATEGY", "domain") @@ -989,6 +1074,7 @@ def test_custom_backend(self, account_id, httpserver, monkeypatch): httpserver.check() + @markers.requires_in_process @markers.aws.only_localstack def test_custom_backend_with_custom_endpoint( self, diff --git a/tests/aws/services/opensearch/test_opensearch.snapshot.json b/tests/aws/services/opensearch/test_opensearch.snapshot.json index 61a80b9be103f..3ae43475a36d0 100644 --- a/tests/aws/services/opensearch/test_opensearch.snapshot.json +++ b/tests/aws/services/opensearch/test_opensearch.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_versions": { - "recorded-date": "16-07-2024, 13:05:15", + "recorded-date": "12-09-2025, 10:51:06", "recorded-content": { "source_versions": [ "Elasticsearch_5.1", @@ -25,6 +25,10 @@ "OpenSearch_1.2", "OpenSearch_1.3", "OpenSearch_2.11", + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19", "OpenSearch_2.3", "OpenSearch_2.5", "OpenSearch_2.7", @@ -159,37 +163,70 @@ "OpenSearch_2.7", "OpenSearch_2.9", "OpenSearch_2.11", - "OpenSearch_2.13" + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19" ], "source_opensearch_2.11": [ - "OpenSearch_2.13" + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19" + ], + "source_opensearch_2.13": [ + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19" + ], + "source_opensearch_2.15": [ + "OpenSearch_2.17", + "OpenSearch_2.19" + ], + "source_opensearch_2.17": [ + "OpenSearch_2.19" + ], + "source_opensearch_2.19": [ + "OpenSearch_3.1" ], "source_opensearch_2.3": [ "OpenSearch_2.5", "OpenSearch_2.7", "OpenSearch_2.9", "OpenSearch_2.11", - "OpenSearch_2.13" + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19" ], "source_opensearch_2.5": [ "OpenSearch_2.7", "OpenSearch_2.9", "OpenSearch_2.11", - "OpenSearch_2.13" + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19" ], "source_opensearch_2.7": [ "OpenSearch_2.9", "OpenSearch_2.11", - "OpenSearch_2.13" + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19" ], "source_opensearch_2.9": [ "OpenSearch_2.11", - "OpenSearch_2.13" + "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19" ] } }, "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_list_versions": { - "recorded-date": "16-07-2024, 13:18:18", + "recorded-date": "12-09-2025, 07:47:04", "recorded-content": { "versions": [ "Elasticsearch_5.1", @@ -215,23 +252,29 @@ "OpenSearch_1.3", "OpenSearch_2.11", "OpenSearch_2.13", + "OpenSearch_2.15", + "OpenSearch_2.17", + "OpenSearch_2.19", "OpenSearch_2.3", "OpenSearch_2.5", "OpenSearch_2.7", - "OpenSearch_2.9" + "OpenSearch_2.9", + "OpenSearch_3.1" ] } }, "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_sql_plugin": { - "recorded-date": "03-12-2024, 21:07:16", + "recorded-date": "12-09-2025, 07:44:41", "recorded-content": { - "sql_plugin_installed": true, "sql_query_response": { "datarows": [ [ "I'm just a simple man, trying to make my way in the universe.", "Fett", - "mandalorian armor", + [ + "mandalorian armor", + "tusken culture" + ], "Boba", 41 ] @@ -265,7 +308,7 @@ } }, "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_lifecycle": { - "recorded-date": "07-07-2025, 23:26:58", + "recorded-date": "12-09-2025, 08:04:43", "recorded-content": { "account-arn": "", "create-response": { @@ -273,6 +316,9 @@ "NaturalLanguageQueryGenerationOptions": { "CurrentState": "NOT_ENABLED", "DesiredState": "DISABLED" + }, + "S3VectorsEngine": { + "Enabled": false } }, "ARN": "arn::es::111111111111:domain/", @@ -342,6 +388,14 @@ }, "ValueType": "STRINGIFIED_JSON" }, + { + "ActiveValue": "", + "Name": "AIMLOptions.S3VectorsEngine", + "PendingValue": { + "Enabled": false + }, + "ValueType": "STRINGIFIED_JSON" + }, { "ActiveValue": "", "Name": "AdvancedOptions", @@ -363,6 +417,12 @@ "PendingValue": "false", "ValueType": "PLAIN_TEXT" }, + { + "ActiveValue": "", + "Name": "AdvancedSecurityOptions.IAMFederationOptions", + "PendingValue": "false", + "ValueType": "PLAIN_TEXT" + }, { "ActiveValue": "", "Name": "AdvancedSecurityOptions.InternalUserDatabaseEnabled", @@ -582,6 +642,9 @@ "NaturalLanguageQueryGenerationOptions": { "CurrentState": "NOT_ENABLED", "DesiredState": "DISABLED" + }, + "S3VectorsEngine": { + "Enabled": false } }, "ARN": "arn::es::111111111111:domain/", @@ -680,6 +743,9 @@ "NaturalLanguageQueryGenerationOptions": { "CurrentState": "NOT_ENABLED", "DesiredState": "DISABLED" + }, + "S3VectorsEngine": { + "Enabled": false } }, "Status": { @@ -883,7 +949,7 @@ { "Effect": "Allow", "Principal": { - "AWS": "111111111111" + "AWS": "AIDAWJDXAOVI2A5WATHZF" }, "Action": "es:*", "Resource": "arn::es::111111111111:domain//*" @@ -963,6 +1029,9 @@ "NaturalLanguageQueryGenerationOptions": { "CurrentState": "NOT_ENABLED", "DesiredState": "DISABLED" + }, + "S3VectorsEngine": { + "Enabled": false } }, "ARN": "arn::es::111111111111:domain/", @@ -1072,6 +1141,9 @@ "NaturalLanguageQueryGenerationOptions": { "CurrentState": "NOT_ENABLED", "DesiredState": "DISABLED" + }, + "S3VectorsEngine": { + "Enabled": false } }, "ARN": "arn::es::111111111111:domain/", @@ -1176,5 +1248,33 @@ "UpgradeProcessing": false } } + }, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_autotune_state_transitions": { + "recorded-date": "19-11-2025, 13:12:26", + "recorded-content": { + "autotune_enabled_status": { + "State": "ENABLED", + "UseOffPeakWindow": false + }, + "autotune_disabled_status": { + "State": "DISABLED", + "UseOffPeakWindow": false + }, + "autotune_domain_config": { + "Options": { + "DesiredState": "DISABLED", + "MaintenanceSchedules": [], + "RollbackOnDisable": "NO_ROLLBACK", + "UseOffPeakWindow": false + }, + "Status": { + "CreationDate": "", + "PendingDeletion": false, + "State": "DISABLED", + "UpdateDate": "", + "UpdateVersion": 15 + } + } + } } } diff --git a/tests/aws/services/opensearch/test_opensearch.validation.json b/tests/aws/services/opensearch/test_opensearch.validation.json index e7cbb17ad097b..5d9a294e9256f 100644 --- a/tests/aws/services/opensearch/test_opensearch.validation.json +++ b/tests/aws/services/opensearch/test_opensearch.validation.json @@ -1,20 +1,47 @@ { + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_autotune_state_transitions": { + "last_validated_date": "2025-11-19T13:12:26+00:00", + "durations_in_seconds": { + "setup": 0.62, + "call": 1105.63, + "teardown": 0.34, + "total": 1106.59 + } + }, "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_lifecycle": { - "last_validated_date": "2025-07-07T23:26:58+00:00", + "last_validated_date": "2025-09-12T08:04:43+00:00", "durations_in_seconds": { - "setup": 0.53, - "call": 839.91, - "teardown": 0.25, - "total": 840.69 + "setup": 0.74, + "call": 839.36, + "teardown": 0.22, + "total": 840.32 } }, "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_versions": { - "last_validated_date": "2024-07-16T13:05:15+00:00" + "last_validated_date": "2025-09-12T10:51:06+00:00", + "durations_in_seconds": { + "setup": 0.8, + "call": 0.82, + "teardown": 0.01, + "total": 1.63 + } }, "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_list_versions": { - "last_validated_date": "2024-07-16T13:18:18+00:00" + "last_validated_date": "2025-09-12T07:47:04+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 0.92, + "teardown": 0.01, + "total": 1.77 + } }, "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_sql_plugin": { - "last_validated_date": "2024-12-03T21:07:16+00:00" + "last_validated_date": "2025-09-12T07:44:42+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 1317.86, + "teardown": 0.96, + "total": 1319.76 + } } } diff --git a/tests/aws/services/redshift/test_redshift.py b/tests/aws/services/redshift/test_redshift.py index 1b242080bb1e0..9ef30cdbff717 100644 --- a/tests/aws/services/redshift/test_redshift.py +++ b/tests/aws/services/redshift/test_redshift.py @@ -8,7 +8,7 @@ class TestRedshift: # only runs in Docker when run against Pro (since it needs postgres on the system) - @markers.only_in_docker + @markers.requires_in_container @markers.aws.validated def test_create_clusters(self, aws_client): # create diff --git a/tests/aws/services/resource_groups/test_resource_groups.py b/tests/aws/services/resource_groups/test_resource_groups.py index c9fa7a10163be..9fa963f28cdf4 100644 --- a/tests/aws/services/resource_groups/test_resource_groups.py +++ b/tests/aws/services/resource_groups/test_resource_groups.py @@ -35,7 +35,7 @@ def sqs_create_queue_in_region(aws_client_factory): def factory(region, **kwargs): if "QueueName" not in kwargs: - kwargs["QueueName"] = "test-queue-%s" % short_uid() + kwargs["QueueName"] = f"test-queue-{short_uid()}" response = aws_client_factory(region_name=region).sqs.create_queue(**kwargs) url = response["QueueUrl"] region_queue_urls.setdefault(region, []).append(url) diff --git a/tests/aws/services/resourcegroupstaggingapi/test_rgsa.py b/tests/aws/services/resourcegroupstaggingapi/test_rgsa.py index 60e85f094c495..83c5a0b8102c8 100644 --- a/tests/aws/services/resourcegroupstaggingapi/test_rgsa.py +++ b/tests/aws/services/resourcegroupstaggingapi/test_rgsa.py @@ -1,7 +1,12 @@ +import pytest + from localstack.testing.pytest import markers +from localstack.utils.analytics.metadata import is_license_activated class TestRGSAIntegrations: + # TODO: figure out a better way, maybe via marker? e.g. @markers.localstack.ext + @pytest.mark.skipif(condition=not is_license_activated(), reason="integration test with pro") @markers.aws.validated @markers.snapshot.skip_snapshot_verify(paths=["$..PaginationToken"]) def test_get_resources(self, aws_client, cleanups, snapshot): diff --git a/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.py b/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.py new file mode 100644 index 0000000000000..38a8601781c5c --- /dev/null +++ b/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.py @@ -0,0 +1,22 @@ +import os + +from localstack.testing.pytest import markers + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=["$..HealthCheckConfig.EnableSNI", "$..HealthCheckVersion"] +) +def test_create_health_check(deploy_cfn_template, aws_client, snapshot): + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/route53_healthcheck.yml", + ), + ) + health_check_id = stack.outputs["HealthCheckId"] + health_check = aws_client.route53.get_health_check(HealthCheckId=health_check_id) + + snapshot.add_transformer(snapshot.transform.key_value("Id", "id")) + snapshot.add_transformer(snapshot.transform.key_value("CallerReference", "caller-reference")) + snapshot.match("HealthCheck", health_check["HealthCheck"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.snapshot.json b/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.snapshot.json similarity index 85% rename from tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.snapshot.json rename to tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.snapshot.json index 46eb1e650d88c..8c3e241e6575d 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.snapshot.json +++ b/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_health_check": { + "tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.py::test_create_health_check": { "recorded-date": "22-09-2023, 13:50:49", "recorded-content": { "HealthCheck": { diff --git a/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.validation.json b/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.validation.json new file mode 100644 index 0000000000000..1e9af599b11be --- /dev/null +++ b/tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.validation.json @@ -0,0 +1,5 @@ +{ + "tests/aws/services/route53/resource_providers/test_aws_route53_healthcheck.py::test_create_health_check": { + "last_validated_date": "2023-09-22T11:50:49+00:00" + } +} diff --git a/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py b/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py new file mode 100644 index 0000000000000..1da7bd0d8bc50 --- /dev/null +++ b/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py @@ -0,0 +1,275 @@ +import os +from typing import Literal + +import pytest + +from localstack.config import S3_STATIC_WEBSITE_HOSTNAME +from localstack.constants import AWS_REGION_US_EAST_1 +from localstack.testing.aws.util import is_aws_cloud +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid +from localstack.utils.urls import localstack_host + + +@pytest.fixture +def record_set_transformers(snapshot): + snapshot.add_transformers_list( + [ + snapshot.transform.jsonpath( + "$..ResourceRecords..Value.[*]", value_replacement="resource-record" + ), + snapshot.transform.jsonpath( + "$..ResourceRecordSets..Name", value_replacement="record-name" + ), + ] + ) + + +@pytest.fixture +def get_s3_website_host_and_hosted_zone_id(aws_client_factory, s3_create_bucket_with_client): + # to be able to deploy an AliasTarget in AWS, we need a valid target. We will use an S3 bucket here + # The HostedZoneId field depends on the region the bucket is deployed in + # See https://docs.aws.amazon.com/general/latest/gr/s3.html#auto-endpoints-website-s3 + + s3_hosted_zone_ids = { + AWS_REGION_US_EAST_1: "Z3AQBSTGFYJSTF", + "us-west-1": "Z2F56UZL2M1ACD", + } + + def _create_s3_website(region_name: Literal["us-east-1", "us-west-1"]): + bucket_name = f"bucket-cfn-{short_uid()}" + hosted_zone_id = s3_hosted_zone_ids[region_name] + + s3_client = aws_client_factory(region_name=region_name).s3 + s3_params = {} + if region_name != AWS_REGION_US_EAST_1: + s3_params["CreateBucketConfiguration"] = {"LocationConstraint": region_name} + s3_create_bucket_with_client(s3_client, Bucket=bucket_name, **s3_params) + s3_client.put_bucket_website( + Bucket=bucket_name, + WebsiteConfiguration={ + "IndexDocument": {"Suffix": "index.html"}, + }, + ) + if is_aws_cloud(): + # beware if adding more regions, it can be `-` or `.` between the `s3-website` and the region name + s3_website_url = f"{bucket_name}.s3-website-{region_name}.amazonaws.com" + else: + s3_website_url = f"{bucket_name}.{S3_STATIC_WEBSITE_HOSTNAME}:{localstack_host().port}" + + return s3_website_url, hosted_zone_id + + yield _create_s3_website + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + # Moto returns a different value (300 instead of 100) when not provided + "$..MaxItems", + # Different hardcoded value in the SOA record compared to Amazon + "$..ResourceRecordSets.[2].ResourceRecords.[0].Value", + ] +) +def test_create_record_set_via_id( + route53_hosted_zone, aws_client, deploy_cfn_template, snapshot, record_set_transformers +): + create_zone_response = route53_hosted_zone() + hosted_zone_id = create_zone_response["HostedZone"]["Id"] + route53_name = create_zone_response["HostedZone"]["Name"] + parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/route53_hostedzoneid_template.yaml" + ), + parameters=parameters, + max_wait=300, + ) + rr_sets = aws_client.route53.list_resource_record_sets(HostedZoneId=hosted_zone_id) + snapshot.match("record-sets", rr_sets) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + # Moto returns a different value (300 instead of 100) when not provided + "$..MaxItems", + # Different hardcoded value in the SOA record compared to Amazon + "$..ResourceRecordSets.[2].ResourceRecords.[0].Value", + ] +) +def test_create_record_set_via_name( + deploy_cfn_template, aws_client, route53_hosted_zone, snapshot, record_set_transformers +): + create_zone_response = route53_hosted_zone() + route53_name = create_zone_response["HostedZone"]["Name"] + parameters = {"HostedZoneName": route53_name, "Name": route53_name} + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/route53_hostedzonename_template.yaml" + ), + parameters=parameters, + ) + rr_sets = aws_client.route53.list_resource_record_sets( + HostedZoneId=create_zone_response["HostedZone"]["Id"] + ) + snapshot.match("record-sets", rr_sets) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + # Moto returns a different value (300 instead of 100) when not provided + "$..MaxItems", + # Different hardcoded value in the SOA record compared to Amazon + "$..ResourceRecordSets.[2].ResourceRecords.[0].Value", + ] +) +def test_create_record_set_without_resource_record( + deploy_cfn_template, aws_client, route53_hosted_zone, snapshot, record_set_transformers +): + create_zone_response = route53_hosted_zone() + hosted_zone_id = create_zone_response["HostedZone"]["Id"] + route53_name = create_zone_response["HostedZone"]["Name"] + parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/route53_recordset_without_resource_records.yaml", + ), + parameters=parameters, + ) + rr_sets = aws_client.route53.list_resource_record_sets(HostedZoneId=hosted_zone_id) + snapshot.match("record-sets", rr_sets) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + # Moto returns a different value (300 instead of 100) when not provided + "$..MaxItems", + # Different hardcoded value in the SOA record compared to Amazon + "$..ResourceRecordSets.[3].ResourceRecords.[0].Value", + ] +) +def test_create_multiple_weighted_alias_target_record_sets( + route53_hosted_zone, + aws_client, + deploy_cfn_template, + snapshot, + record_set_transformers, + get_s3_website_host_and_hosted_zone_id, +): + snapshot.add_transformer(snapshot.transform.key_value("DNSName")) + bucket_1_host, bucket_1_hosted_zone_id = get_s3_website_host_and_hosted_zone_id( + AWS_REGION_US_EAST_1 + ) + bucket_2_host, bucket_2_hosted_zone_id = get_s3_website_host_and_hosted_zone_id("us-west-1") + create_zone_response = route53_hosted_zone() + hosted_zone_id = create_zone_response["HostedZone"]["Id"] + route53_name = create_zone_response["HostedZone"]["Name"] + parameters = { + "HostedZoneId": hosted_zone_id, + "Name": route53_name, + "BucketRegionOneHost": bucket_1_host, + "BucketRegionOneHostedZoneId": bucket_1_hosted_zone_id, + "BucketRegionTwoHost": bucket_2_host, + "BucketRegionTwoHostedZoneId": bucket_2_hosted_zone_id, + } + + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/route53_hostedzoneid_weighted_template.yaml", + ), + parameters=parameters, + max_wait=300 if is_aws_cloud() else 60, + ) + rr_sets = aws_client.route53.list_resource_record_sets(HostedZoneId=hosted_zone_id) + snapshot.match("record-sets", rr_sets) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + # Moto returns a different value (300 instead of 100) when not provided + "$..MaxItems", + # Different hardcoded value in the SOA record compared to Amazon + "$..ResourceRecordSets.[3].ResourceRecords.[0].Value", + ] +) +def test_update_multiple_weighted_alias_target_record_sets( + route53_hosted_zone, + aws_client, + deploy_cfn_template, + snapshot, + record_set_transformers, + get_s3_website_host_and_hosted_zone_id, +): + snapshot.add_transformer(snapshot.transform.key_value("DNSName")) + bucket_1_host, bucket_1_hosted_zone_id = get_s3_website_host_and_hosted_zone_id( + AWS_REGION_US_EAST_1 + ) + bucket_2_host, bucket_2_hosted_zone_id = get_s3_website_host_and_hosted_zone_id("us-west-1") + create_zone_response = route53_hosted_zone() + hosted_zone_id = create_zone_response["HostedZone"]["Id"] + route53_name = create_zone_response["HostedZone"]["Name"] + parameters = { + "HostedZoneId": hosted_zone_id, + "Name": route53_name, + "BucketRegionOneHost": bucket_1_host, + "BucketRegionOneHostedZoneId": bucket_1_hosted_zone_id, + "BucketRegionTwoHost": bucket_2_host, + "BucketRegionTwoHostedZoneId": bucket_2_hosted_zone_id, + } + template_path = os.path.join( + os.path.dirname(__file__), + "../../../templates/route53_hostedzoneid_weighted_template.yaml", + ) + + result = deploy_cfn_template( + template_path=template_path, + parameters=parameters, + max_wait=300 if is_aws_cloud() else 60, + ) + stack_name = result.stack_name + rr_sets = aws_client.route53.list_resource_record_sets(HostedZoneId=hosted_zone_id) + snapshot.match("record-sets", rr_sets) + + parameters["WeightBucketOne"] = "50" + parameters["WeightBucketTwo"] = "100" + + deploy_cfn_template( + template_path=template_path, + parameters=parameters, + max_wait=300 if is_aws_cloud() else 60, + is_update=True, + stack_name=stack_name, + ) + rr_sets = aws_client.route53.list_resource_record_sets(HostedZoneId=hosted_zone_id) + snapshot.match("record-sets-update-weights", rr_sets) + + parameters["BucketRegionTwoHost"] = bucket_1_host + parameters["BucketRegionTwoHostedZoneId"] = bucket_1_hosted_zone_id + + deploy_cfn_template( + template_path=template_path, + parameters=parameters, + max_wait=300 if is_aws_cloud() else 60, + is_update=True, + stack_name=stack_name, + ) + rr_sets = aws_client.route53.list_resource_record_sets(HostedZoneId=hosted_zone_id) + snapshot.match("record-sets-update-alias-target", rr_sets) + + parameters["BucketTwoSetIdentifier"] = "region-3" + + deploy_cfn_template( + template_path=template_path, + parameters=parameters, + max_wait=300 if is_aws_cloud() else 60, + is_update=True, + stack_name=stack_name, + ) + rr_sets = aws_client.route53.list_resource_record_sets(HostedZoneId=hosted_zone_id) + snapshot.match("record-sets-update-set-identifier", rr_sets) diff --git a/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.snapshot.json b/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.snapshot.json new file mode 100644 index 0000000000000..8f67670f39633 --- /dev/null +++ b/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.snapshot.json @@ -0,0 +1,479 @@ +{ + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_record_set_via_id": { + "recorded-date": "15-01-2026, 17:26:46", + "recorded-content": { + "record-sets": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + } + ], + "TTL": 900, + "Type": "A" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_record_set_via_name": { + "recorded-date": "15-01-2026, 17:31:53", + "recorded-content": { + "record-sets": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + } + ], + "TTL": 900, + "Type": "A" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_record_set_without_resource_record": { + "recorded-date": "15-01-2026, 17:36:56", + "recorded-content": { + "record-sets": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + } + ], + "TTL": 900, + "Type": "A" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_multiple_weighted_alias_target_record_sets": { + "recorded-date": "26-01-2026, 18:57:04", + "recorded-content": { + "record-sets": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z3AQBSTGFYJSTF" + }, + "Name": "", + "SetIdentifier": "region-1", + "Type": "A", + "Weight": 255 + }, + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z2F56UZL2M1ACD" + }, + "Name": "", + "SetIdentifier": "region-2", + "Type": "A", + "Weight": 0 + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_update_multiple_weighted_alias_target_record_sets": { + "recorded-date": "26-01-2026, 18:51:15", + "recorded-content": { + "record-sets": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z3AQBSTGFYJSTF" + }, + "Name": "", + "SetIdentifier": "region-1", + "Type": "A", + "Weight": 255 + }, + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z2F56UZL2M1ACD" + }, + "Name": "", + "SetIdentifier": "region-2", + "Type": "A", + "Weight": 0 + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "record-sets-update-weights": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z3AQBSTGFYJSTF" + }, + "Name": "", + "SetIdentifier": "region-1", + "Type": "A", + "Weight": 50 + }, + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z2F56UZL2M1ACD" + }, + "Name": "", + "SetIdentifier": "region-2", + "Type": "A", + "Weight": 100 + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "record-sets-update-alias-target": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z3AQBSTGFYJSTF" + }, + "Name": "", + "SetIdentifier": "region-1", + "Type": "A", + "Weight": 50 + }, + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z3AQBSTGFYJSTF" + }, + "Name": "", + "SetIdentifier": "region-2", + "Type": "A", + "Weight": 100 + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "record-sets-update-set-identifier": { + "IsTruncated": false, + "MaxItems": "100", + "ResourceRecordSets": [ + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z3AQBSTGFYJSTF" + }, + "Name": "", + "SetIdentifier": "region-1", + "Type": "A", + "Weight": 50 + }, + { + "AliasTarget": { + "DNSName": "", + "EvaluateTargetHealth": false, + "HostedZoneId": "Z3AQBSTGFYJSTF" + }, + "Name": "", + "SetIdentifier": "region-3", + "Type": "A", + "Weight": 100 + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + }, + { + "Value": "" + } + ], + "TTL": 172800, + "Type": "NS" + }, + { + "Name": "", + "ResourceRecords": [ + { + "Value": " awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400" + } + ], + "TTL": 900, + "Type": "SOA" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.validation.json b/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.validation.json new file mode 100644 index 0000000000000..5674342db3010 --- /dev/null +++ b/tests/aws/services/route53/resource_providers/test_aws_route53_recordset.validation.json @@ -0,0 +1,47 @@ +{ + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_multiple_weighted_alias_target_record_sets": { + "last_validated_date": "2026-01-26T18:57:39+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 44.54, + "teardown": 37.78, + "total": 82.83 + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_record_set_via_id": { + "last_validated_date": "2026-01-15T17:27:21+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 43.43, + "teardown": 35.61, + "total": 79.53 + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_record_set_via_name": { + "last_validated_date": "2026-01-15T17:32:28+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 40.54, + "teardown": 34.81, + "total": 75.82 + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_create_record_set_without_resource_record": { + "last_validated_date": "2026-01-15T17:37:31+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 42.68, + "teardown": 34.81, + "total": 77.96 + } + }, + "tests/aws/services/route53/resource_providers/test_aws_route53_recordset.py::test_update_multiple_weighted_alias_target_record_sets": { + "last_validated_date": "2026-01-26T18:51:51+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 174.99, + "teardown": 38.78, + "total": 214.26 + } + } +} diff --git a/tests/aws/services/route53/test_route53.py b/tests/aws/services/route53/test_route53.py index c706f784e0d9c..942b5ffe9e063 100644 --- a/tests/aws/services/route53/test_route53.py +++ b/tests/aws/services/route53/test_route53.py @@ -1,6 +1,7 @@ from urllib.parse import urlparse import pytest +from botocore.exceptions import ClientError from localstack.testing.pytest import markers from localstack.utils.common import short_uid @@ -54,7 +55,7 @@ def test_crud_health_check(self, echo_http_server_post, echo_http_server, aws_cl assert response["HealthCheck"]["Id"] == health_check_id response = aws_client.route53.delete_health_check(HealthCheckId=health_check_id) assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - with pytest.raises(Exception) as ctx: + with pytest.raises(ClientError) as ctx: aws_client.route53.delete_health_check(HealthCheckId=health_check_id) assert "NoSuchHealthCheck" in str(ctx.value) @@ -145,7 +146,7 @@ def test_associate_vpc_with_hosted_zone( ] expected = { "HostedZoneId": zone_id, - "Name": "%s." % name, + "Name": f"{name}.", "Owner": {"OwningAccount": account_id}, } assert expected in result @@ -153,9 +154,7 @@ def test_associate_vpc_with_hosted_zone( # list zones by name result = aws_client.route53.list_hosted_zones_by_name(DNSName=name).get("HostedZones") assert result[0]["Name"] == "zone123." - result = aws_client.route53.list_hosted_zones_by_name(DNSName="%s." % name).get( - "HostedZones" - ) + result = aws_client.route53.list_hosted_zones_by_name(DNSName=f"{name}.").get("HostedZones") assert result[0]["Name"] == "zone123." # assert that VPC is attached in Zone response @@ -171,7 +170,7 @@ def test_associate_vpc_with_hosted_zone( ) assert response["ResponseMetadata"]["HTTPStatusCode"] in [200, 201] # subsequent call (after disassociation) should fail with 404 error - with pytest.raises(Exception): + with pytest.raises(ClientError): aws_client.route53.disassociate_vpc_from_hosted_zone( HostedZoneId=zone_id, VPC={"VPCRegion": vpc_region, "VPCId": vpc2_id}, @@ -193,13 +192,13 @@ def test_reusable_delegation_sets(self, aws_client): sets_before = client.list_reusable_delegation_sets().get("DelegationSets", []) - call_ref_1 = "c-%s" % short_uid() + call_ref_1 = f"c-{short_uid()}" result_1 = client.create_reusable_delegation_set(CallerReference=call_ref_1)[ "DelegationSet" ] set_id_1 = result_1["Id"] - call_ref_2 = "c-%s" % short_uid() + call_ref_2 = f"c-{short_uid()}" result_2 = client.create_reusable_delegation_set(CallerReference=call_ref_2)[ "DelegationSet" ] @@ -223,3 +222,17 @@ def test_reusable_delegation_sets(self, aws_client): with pytest.raises(Exception) as ctx: client.get_reusable_delegation_set(Id=set_id_1) assert "NoSuchDelegationSet" in str(ctx.value) + + @markers.aws.validated + def test_delete_hosted_zone(self, aws_client, hosted_zone, snapshot): + hosted_zone_response = hosted_zone(Name=f"zone-{short_uid()}.com") + hosted_zone_id = hosted_zone_response["HostedZone"]["Id"].split("/")[-1] + + snapshot.add_transformer(snapshot.transform.regex(hosted_zone_id, "")) + + with pytest.raises(ClientError) as e: + aws_client.route53.delete_hosted_zone(Id=hosted_zone_id + "asdf1234") + snapshot.match("no-such-hosted-zone-error", e.value.response) + + delete_hosted_zone_response = aws_client.route53.delete_hosted_zone(Id=hosted_zone_id) + snapshot.match("delete-hosted-zone-response", delete_hosted_zone_response) diff --git a/tests/aws/services/route53/test_route53.snapshot.json b/tests/aws/services/route53/test_route53.snapshot.json index e82bcf05108b1..4572f18077026 100644 --- a/tests/aws/services/route53/test_route53.snapshot.json +++ b/tests/aws/services/route53/test_route53.snapshot.json @@ -142,5 +142,32 @@ } } } + }, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_delete_hosted_zone": { + "recorded-date": "12-01-2026, 00:07:24", + "recorded-content": { + "no-such-hosted-zone-error": { + "Error": { + "Code": "NoSuchHostedZone", + "Message": "No hosted zone found with ID: asdf1234", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "delete-hosted-zone-response": { + "ChangeInfo": { + "Id": "/change/", + "Status": "", + "SubmittedAt": "datetime" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/route53/test_route53.validation.json b/tests/aws/services/route53/test_route53.validation.json index 5a7b0037f1b09..731f390ce6725 100644 --- a/tests/aws/services/route53/test_route53.validation.json +++ b/tests/aws/services/route53/test_route53.validation.json @@ -14,6 +14,15 @@ "tests/aws/services/route53/test_route53.py::TestRoute53::test_crud_health_check": { "last_validated_date": "2024-06-13T08:05:47+00:00" }, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_delete_hosted_zone": { + "last_validated_date": "2026-01-12T00:07:24+00:00", + "durations_in_seconds": { + "setup": 0.66, + "call": 0.74, + "teardown": 0.1, + "total": 1.5 + } + }, "tests/aws/services/route53/test_route53.py::TestRoute53::test_reusable_delegation_sets": { "last_validated_date": "2024-06-13T07:43:08+00:00" } diff --git a/tests/aws/services/route53resolver/test_route53resolver.py b/tests/aws/services/route53resolver/test_route53resolver.py index 778e8ec500001..16ccb0b79bfd2 100644 --- a/tests/aws/services/route53resolver/test_route53resolver.py +++ b/tests/aws/services/route53resolver/test_route53resolver.py @@ -808,10 +808,10 @@ def test_list_firewall_rules( Name=firewall_rule_group_name ) cleanups.append( - lambda rule_group_id=rule_group_response["FirewallRuleGroup"][ - "Id" - ]: aws_client.route53resolver.delete_firewall_rule_group( - FirewallRuleGroupId=rule_group_id + lambda rule_group_id=rule_group_response["FirewallRuleGroup"]["Id"]: ( + aws_client.route53resolver.delete_firewall_rule_group( + FirewallRuleGroupId=rule_group_id + ) ) ) # Parameters for creating resources @@ -823,10 +823,10 @@ def test_list_firewall_rules( Name=f"fw-domain-list-{short_uid()}" ) cleanups.append( - lambda domain_list_id=domain_list_response["FirewallDomainList"][ - "Id" - ]: aws_client.route53resolver.delete_firewall_domain_list( - FirewallDomainListId=domain_list_id + lambda domain_list_id=domain_list_response["FirewallDomainList"]["Id"]: ( + aws_client.route53resolver.delete_firewall_domain_list( + FirewallDomainListId=domain_list_id + ) ) ) create_firewall_rule( diff --git a/tests/aws/services/s3/test_s3.py b/tests/aws/services/s3/test_s3.py index d94d381fffa88..a26ef69ce8d5f 100644 --- a/tests/aws/services/s3/test_s3.py +++ b/tests/aws/services/s3/test_s3.py @@ -7,12 +7,15 @@ import json import logging import os +import random +import re import shutil import tempfile import time from importlib.util import find_spec from io import BytesIO from operator import itemgetter +from string import punctuation as ascii_punctuation from typing import TYPE_CHECKING from urllib.parse import SplitResult, parse_qs, quote, urlencode, urlparse, urlunsplit from zoneinfo import ZoneInfo @@ -21,6 +24,7 @@ import botocore import pytest import requests +import urllib3 import xmltodict from boto3.s3.transfer import KB, TransferConfig from botocore import UNSIGNED @@ -28,9 +32,9 @@ from botocore.client import Config from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import RegexTransformer +from urllib3 import HTTPHeaderDict -import localstack.config -from localstack import config +from localstack import config, constants from localstack.aws.api.lambda_ import Runtime from localstack.aws.api.s3 import StorageClass, TransitionDefaultMinimumObjectSize from localstack.config import S3_VIRTUAL_HOSTNAME @@ -39,13 +43,15 @@ LOCALHOST_HOSTNAME, ) from localstack.services.s3 import constants as s3_constants +from localstack.services.s3.headers import encode_header_rfc2047 from localstack.services.s3.utils import ( RFC1123, etag_to_base_64_content_md5, + get_bucket_location_xml, parse_expiration_header, rfc_1123_datetime, ) -from localstack.testing.aws.util import in_default_partition, is_aws_cloud +from localstack.testing.aws.util import create_client_with_keys, in_default_partition, is_aws_cloud from localstack.testing.config import ( SECONDARY_TEST_AWS_ACCESS_KEY_ID, SECONDARY_TEST_AWS_SECRET_ACCESS_KEY, @@ -284,6 +290,11 @@ def _simple_bucket_policy(s3_bucket: str) -> dict: } +def get_xml_content(http_response_content: bytes) -> bytes: + # just format a bit the XML, nothing bad parity wise, but allow the test to run against AWS + return http_response_content.replace(b"'", b'"').replace(b"utf", b"UTF") + + class TestS3: @pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="KMS not enabled in S3 image") @markers.aws.validated @@ -429,6 +440,70 @@ def test_put_and_get_object_with_content_language_disposition( snapshot.match("get-object-headers", response["ResponseMetadata"]) assert response["Body"].read() == b"abc123" + @markers.aws.validated + def test_system_metadata_with_unicode(self, s3_bucket, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.s3_api()) + response = aws_client.s3.put_object( + Bucket=s3_bucket, + Key="test", + ContentLanguage="de", + ContentDisposition='attachment; filename="test_—_file%E2%80%94_é_2.pdf"', + CacheControl="ÄMÄZÕÑ S3", + ) + snapshot.match("put-object", response) + + response = aws_client.s3.get_object(Bucket=s3_bucket, Key="test") + snapshot.match("get-object", response) + + @markers.aws.validated + def test_user_metadata_rfc2047_encoded(self, s3_bucket, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.s3_api()) + non_ascii = "test_—_file%E2%80%94_é_2?.pdf" + non_ascii_2 = "ÄMÄZÕÑ S3" + utf_metadata = "\x00\x01\x02\x03" + replacement_chars = "�������" + safe_chars = ascii_punctuation + " \t" + response = aws_client.s3.put_object( + Bucket=s3_bucket, + Key="test", + Metadata={ + "non-ascii": encode_header_rfc2047(non_ascii), + "non-ascii-2": encode_header_rfc2047(non_ascii_2), + "non-ascii-binary": encode_header_rfc2047(utf_metadata), + "replacement-chars": encode_header_rfc2047(replacement_chars), + # test if it will decode RFC 2047 looking data and return it decoded + "fake-encoded": "=?UTF-8?Q?actually-ascii?=", + "asciib64-encoded": "=?UTF-8?B?YWJj?=", + "safe-chars": safe_chars, + }, + ) + snapshot.match("put-object", response) + + response = aws_client.s3.get_object(Bucket=s3_bucket, Key="test") + snapshot.match("get-object", response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + # AWS returns broken encoded data, so we use replacement chars instead to indicate encoding error, if the + # padding is wrong + paths=["$..Metadata.bad-b64-encoded"], + ) + def test_user_metadata_rfc2047_bad_b64_encoded(self, s3_bucket, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.s3_api()) + response = aws_client.s3.put_object( + Bucket=s3_bucket, + Key="test", + Metadata={ + # this b64 value has a wrong padding + "bad-b64-encoded": "=?UTF-8?B?=GGG?=" + }, + ) + # AWS does not fail on invalid b64, but it returns gibberish, which we can't really have parity with (not + # worth it, so we return what we received) + snapshot.match("put-object", response) + response = aws_client.s3.get_object(Bucket=s3_bucket, Key="test") + snapshot.match("get-object", response) + @markers.aws.validated @pytest.mark.parametrize( "use_virtual_address", @@ -542,6 +617,44 @@ def test_put_get_object_single_character_trailing_slash(self, s3_bucket, aws_cli resp = aws_client.s3.list_objects_v2(Bucket=s3_bucket) snapshot.match("list-objects-single-char", resp) + @markers.aws.validated + def test_multipart_with_unicode_character_location(self, s3_bucket, aws_client, snapshot): + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value( + "ID", value_replacement="owner-id", reference_replacement=False + ), + ] + ) + + key_name = "test-unicode_—_file" + response = aws_client.s3.create_multipart_upload(Bucket=s3_bucket, Key=key_name) + snapshot.match("create-multipart", response) + upload_id = response["UploadId"] + + upload_part = aws_client.s3.upload_part( + Bucket=s3_bucket, + Key=key_name, + Body="upload-part-1", + PartNumber=1, + UploadId=upload_id, + ) + + multipart_upload_parts = [{"ETag": upload_part["ETag"], "PartNumber": 1}] + + response = aws_client.s3.complete_multipart_upload( + Bucket=s3_bucket, + Key=key_name, + MultipartUpload={"Parts": multipart_upload_parts}, + UploadId=upload_id, + ) + snapshot.match("complete-multipart", response) + location_prefix = response["Location"].split("/test-unicode")[0] + snapshot.add_transformer(snapshot.transform.regex(location_prefix, "")) + @markers.aws.validated def test_copy_object_special_character(self, s3_bucket, s3_create_bucket, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.s3_api()) @@ -996,6 +1109,83 @@ def test_create_bucket_via_host_name(self, s3_vhost_client, aws_client, region_n finally: s3_vhost_client.delete_bucket(Bucket=bucket_name) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..Owner.DisplayName", "$..Buckets..BucketArn"] + ) # Bucket ARN is by AWS in this scenario but not for others. + def test_create_bucket_with_eu_location_constraint( + self, s3_create_bucket_with_client, aws_client_factory, snapshot + ): + snapshot.add_transformer( + [ + snapshot.transform.key_value("ID"), + snapshot.transform.key_value("Name"), + snapshot.transform.key_value("BucketOwner"), + snapshot.transform.key_value("BucketRegion"), + ] + ) + client_eu_west_1 = aws_client_factory(region_name="eu-west-1").s3 + + bucket_name = f"test-eu-region-{short_uid()}" + s3_create_bucket_with_client( + client_eu_west_1, + Bucket=bucket_name, + CreateBucketConfiguration={ + "LocationConstraint": "EU" + }, # EU is just an alias for eu-west-1. + ) + get_bucket_location_response = client_eu_west_1.get_bucket_location(Bucket=bucket_name) + assert get_bucket_location_response["LocationConstraint"] == "EU" + snapshot.match("get-bucket-location", get_bucket_location_response) + + list_buckets_response = client_eu_west_1.list_buckets(Prefix=bucket_name) + assert list_buckets_response["Buckets"][0]["BucketRegion"] == "eu-west-1" + snapshot.match("list-bucket-response", list_buckets_response) + + @markers.aws.validated + def test_create_bucket_with_eu_location_constraint_raises( + self, s3_create_bucket_with_client, aws_client_factory, snapshot + ): + # Should fail for a region that isn't eu-west-1 (not including us-east-1) + client_us_east_2 = aws_client_factory(region_name="us-east-2").s3 + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + client_us_east_2, + Bucket=f"test-eu-region-{short_uid()}", + CreateBucketConfiguration={"LocationConstraint": "EU"}, + ) + snapshot.match("eu-location-constraint-error", e.value.response) + + @markers.aws.validated + @pytest.mark.parametrize( + "client_region, location_constraint", + [("us-east-1", "us-east-1"), ("us-east-1", "foo"), ("eu-west-1", "bar")], + ) + def test_create_bucket_with_invalid_location_constraint( + self, + s3_create_bucket_with_client, + aws_client_factory, + snapshot, + client_region, + location_constraint, + ): + # Different error messages are raised for us-east-1. + snapshot.add_transformer( + snapshot.transform.key_value( + "LocationConstraint", value_replacement="location-constraint" + ) + ) + + client = aws_client_factory(region_name=client_region).s3 + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + client, + CreateBucketConfiguration={"LocationConstraint": location_constraint}, + ) + snapshot.match( + f"{client_region}-location-constraint-{location_constraint}-error", e.value.response + ) + @markers.aws.validated def test_get_bucket_policy(self, s3_bucket, snapshot, aws_client, allow_bucket_acl, account_id): snapshot.add_transformer(snapshot.transform.key_value("Resource")) @@ -2029,102 +2219,6 @@ def test_s3_copy_object_with_default_checksum(self, s3_bucket, snapshot, aws_cli ) snapshot.match("dest-object-attrs-after-copy", object_attrs) - @markers.aws.validated - def test_s3_copy_object_preconditions(self, s3_bucket, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.s3_api()) - object_key = "source-object" - dest_key = "dest-object" - # create key with no checksum - put_object = aws_client.s3.put_object( - Bucket=s3_bucket, - Key=object_key, - Body=b"data", - ) - head_obj = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) - snapshot.match("head-object", head_obj) - - # wait a bit for the `unmodified_since` value so that it's unvalid. - # S3 compares it the last-modified field, but you can't set the value in the future otherwise it ignores it - # It needs to be now or less, but the object needs to be a bit more recent than that. - time.sleep(3) - - # we're testing the order of validation at the same time by validating all of them at once, by elimination - now = datetime.datetime.now().astimezone(tz=ZoneInfo("GMT")) - wrong_unmodified_since = now - datetime.timedelta(days=1) - - with pytest.raises(ClientError) as e: - aws_client.s3.copy_object( - Bucket=s3_bucket, - CopySource=f"{s3_bucket}/{object_key}", - Key=dest_key, - CopySourceIfModifiedSince=now, - CopySourceIfUnmodifiedSince=wrong_unmodified_since, - CopySourceIfMatch="etag123", - CopySourceIfNoneMatch=put_object["ETag"], - ) - snapshot.match("copy-precondition-if-match", e.value.response) - - with pytest.raises(ClientError) as e: - aws_client.s3.copy_object( - Bucket=s3_bucket, - CopySource=f"{s3_bucket}/{object_key}", - Key=dest_key, - CopySourceIfModifiedSince=now, - CopySourceIfUnmodifiedSince=wrong_unmodified_since, - CopySourceIfNoneMatch=put_object["ETag"], - ) - snapshot.match("copy-precondition-if-unmodified-since", e.value.response) - - with pytest.raises(ClientError) as e: - aws_client.s3.copy_object( - Bucket=s3_bucket, - CopySource=f"{s3_bucket}/{object_key}", - Key=dest_key, - CopySourceIfModifiedSince=now, - CopySourceIfNoneMatch=put_object["ETag"], - ) - snapshot.match("copy-precondition-if-none-match", e.value.response) - - with pytest.raises(ClientError) as e: - aws_client.s3.copy_object( - Bucket=s3_bucket, - CopySource=f"{s3_bucket}/{object_key}", - Key=dest_key, - CopySourceIfModifiedSince=now, - ) - snapshot.match("copy-precondition-if-modified-since", e.value.response) - - # AWS will ignore the value if it's in the future - copy_obj = aws_client.s3.copy_object( - Bucket=s3_bucket, - CopySource=f"{s3_bucket}/{object_key}", - Key=dest_key, - CopySourceIfModifiedSince=now + datetime.timedelta(days=1), - ) - snapshot.match("copy-ignore-future-modified-since", copy_obj) - - # AWS will ignore the missing quotes around the ETag and still reject the request - with pytest.raises(ClientError) as e: - aws_client.s3.copy_object( - Bucket=s3_bucket, - CopySource=f"{s3_bucket}/{object_key}", - Key=dest_key, - CopySourceIfNoneMatch=put_object["ETag"].strip('"'), - ) - snapshot.match("copy-etag-missing-quotes", e.value.response) - - # Positive tests with all conditions checked - copy_obj_all_positive = aws_client.s3.copy_object( - Bucket=s3_bucket, - CopySource=f"{s3_bucket}/{object_key}", - Key=dest_key, - CopySourceIfMatch=put_object["ETag"].strip('"'), - CopySourceIfNoneMatch="etag123", - CopySourceIfModifiedSince=now - datetime.timedelta(days=1), - CopySourceIfUnmodifiedSince=now, - ) - snapshot.match("copy-success", copy_obj_all_positive) - @markers.aws.validated def test_s3_copy_object_wrong_format(self, s3_bucket, snapshot, aws_client): snapshot.add_transformer(snapshot.transform.s3_api()) @@ -2641,57 +2735,59 @@ def test_s3_object_acl_exceptions(self, s3_bucket, snapshot, aws_client): snapshot.match("put-object-two-type-acl-acp", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..Restore"]) - def test_s3_object_expiry(self, s3_bucket, snapshot, aws_client): - # AWS only cleans up S3 expired object once a day usually - # the object stays accessible for quite a while after being expired - # https://stackoverflow.com/questions/38851456/aws-s3-object-expiration-less-than-24-hours - # handle s3 object expiry - # https://github.com/localstack/localstack/issues/1685 - # TODO: should we have a config var to not deleted immediately in the new provider? and schedule it? + def test_s3_object_expires(self, s3_bucket, snapshot, aws_client): + """ + `Expires` header indicates the date and time at which the object is no longer cacheable, and is not linked to + Object Expiration. + https://www.rfc-editor.org/rfc/rfc7234#section-5.3 + """ snapshot.add_transformer(snapshot.transform.s3_api()) snapshot.add_transformer( snapshot.transform.key_value( "ExpiresString", reference_replacement=False, value_replacement="" ) ) - # put object - short_expire = datetime.datetime.now(ZoneInfo("GMT")) + datetime.timedelta(seconds=1) - object_key_expired = "key-object-expired" - object_key_not_expired = "key-object-not-expired" - aws_client.s3.put_object( + now = datetime.datetime.now(tz=datetime.UTC) + expires_in_future = now + datetime.timedelta(days=1) + object_key_expires_future = "key-object-future" + object_key_expires_past = "key-object-past" + + put_obj_future = aws_client.s3.put_object( Bucket=s3_bucket, - Key=object_key_expired, + Key=object_key_expires_future, Body="foo", - Expires=short_expire, + Expires=expires_in_future, ) - # sleep so it expires - time.sleep(3) - # head_object does not raise an error for now in LS - response = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key_expired) - assert response["Expires"] < datetime.datetime.now(ZoneInfo("GMT")) - snapshot.match("head-object-expired", response) + snapshot.match("put-object-expires-future", put_obj_future) - # try to fetch an object which is already expired - if not is_aws_cloud(): # fixme for now behaviour differs, have a look at it and discuss - with pytest.raises(Exception) as e: # this does not raise in AWS - aws_client.s3.get_object(Bucket=s3_bucket, Key=object_key_expired) + response = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key_expires_future) + assert response["Expires"] > now + assert re.match( + r"^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$", + response["ExpiresString"], + ) + snapshot.match("head-object-expires-future", response) - e.match("NoSuchKey") + get_object = aws_client.s3.get_object(Bucket=s3_bucket, Key=object_key_expires_future) + assert response["Expires"] > now + snapshot.match("get-object-expires-future", get_object) - aws_client.s3.put_object( + put_obj_past = aws_client.s3.put_object( Bucket=s3_bucket, - Key=object_key_not_expired, + Key=object_key_expires_past, Body="foo", - Expires=datetime.datetime.now(ZoneInfo("GMT")) + datetime.timedelta(hours=1), + Expires=now - datetime.timedelta(days=1), ) + snapshot.match("put-object-expires-past", put_obj_past) + + response = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key_expires_past) + assert response["Expires"] < now + snapshot.match("head-object-expires-past", response) - # try to fetch has not been expired yet. - resp = aws_client.s3.get_object(Bucket=s3_bucket, Key=object_key_not_expired) - assert "Expires" in resp - assert resp["Expires"] > datetime.datetime.now(ZoneInfo("GMT")) - snapshot.match("get-object-not-yet-expired", resp) + get_object = aws_client.s3.get_object(Bucket=s3_bucket, Key=object_key_expires_past) + assert response["Expires"] < now + snapshot.match("get-object-expires-past", get_object) @markers.aws.validated def test_upload_file_with_xml_preamble(self, s3_bucket, snapshot, aws_client): @@ -2729,9 +2825,18 @@ def test_different_location_constraint( region_us_east_2 = "us-east-2" region_us_west_1 = "us-west-1" + client_us_east_1 = aws_client_factory( + region_name=AWS_REGION_US_EAST_1, config=Config(parameter_validation=False) + ).s3 + client_us_east_2 = aws_client_factory( + region_name=region_us_east_2, config=Config(parameter_validation=False) + ).s3 + client_us_west_1 = aws_client_factory(region_name=region_us_west_1).s3 + snapshot.add_transformer(snapshot.transform.s3_api()) snapshot.add_transformers_list( [ + snapshot.transform.key_value("BucketArn"), snapshot.transform.key_value("Location", "", reference_replacement=False), snapshot.transform.key_value( "LocationConstraint", "", reference_replacement=False @@ -2742,9 +2847,6 @@ def test_different_location_constraint( ] ) bucket_us_east_1 = f"bucket-{short_uid()}" - client_us_east_1 = aws_client_factory( - region_name=AWS_REGION_US_EAST_1, config=Config(parameter_validation=False) - ).s3 s3_create_bucket_with_client( client_us_east_1, Bucket=bucket_us_east_1, @@ -2760,15 +2862,13 @@ def test_different_location_constraint( ) snapshot.match("create-bucket-constraint-us-east-1", exc.value.response) - # assert creation fails with location constraint with the region unset - with pytest.raises(ClientError) as exc: - client_us_east_1.create_bucket( - Bucket=f"bucket-{short_uid()}", - CreateBucketConfiguration={"LocationConstraint": None}, - ) - snapshot.match("create-bucket-constraint-us-east-1-with-None", exc.value.response) + create_bucket_no_location = s3_create_bucket_with_client( + client_us_east_1, + Bucket=f"bucket-{short_uid()}", + CreateBucketConfiguration={"LocationConstraint": None}, + ) + snapshot.match("create-bucket-constraint-us-east-1-with-None", create_bucket_no_location) - client_us_east_2 = aws_client_factory(region_name=region_us_east_2).s3 bucket_us_east_2 = f"bucket-{short_uid()}" s3_create_bucket_with_client( client_us_east_2, @@ -2783,6 +2883,14 @@ def test_different_location_constraint( client_us_east_2.create_bucket(Bucket=f"bucket-{short_uid()}") snapshot.match("create-bucket-us-east-2-no-constraint-exc", exc.value.response) + # assert creation fails without location constraint for us-east-2 region + with pytest.raises(ClientError) as exc: + client_us_east_2.create_bucket( + Bucket=f"bucket-{short_uid()}", + CreateBucketConfiguration={"LocationConstraint": None}, + ) + snapshot.match("create-bucket-constraint-us-east-2-with-None", exc.value.response) + # assert creation fails with wrong location constraint from us-east-2 region to us-west-1 region with pytest.raises(ClientError) as exc: client_us_east_2.create_bucket( @@ -2791,8 +2899,6 @@ def test_different_location_constraint( ) snapshot.match("create-bucket-us-east-2-constraint-to-us-west-1", exc.value.response) - client_us_west_1 = aws_client_factory(region_name=region_us_west_1).s3 - with pytest.raises(ClientError) as exc: client_us_west_1.create_bucket( Bucket=f"bucket-{short_uid()}", @@ -3710,9 +3816,9 @@ def test_s3_invalid_content_md5(self, s3_bucket, snapshot, aws_client): base_64_content_md5 = etag_to_base_64_content_md5(response["ETag"]) assert content_md5 == base_64_content_md5 - bad_digest_md5 = base64.b64encode( - hashlib.md5(f"{content}1".encode("utf-8")).digest() - ).decode("utf-8") + bad_digest_md5 = base64.b64encode(hashlib.md5(f"{content}1".encode()).digest()).decode( + "utf-8" + ) hashes = [ "__invalid__", @@ -3978,6 +4084,7 @@ def test_get_object_part_checksum(self, s3_bucket, snapshot, aws_client, checksu snapshot.match("get-object-part", get_object_part) @markers.aws.validated + @markers.requires_in_process # Patches LOCALSTACK_HOST def test_set_external_hostname( self, s3_bucket, allow_bucket_acl, s3_multipart_upload, monkeypatch, snapshot, aws_client ): @@ -4143,6 +4250,106 @@ def test_access_bucket_different_region(self, s3_create_bucket, s3_vhost_client, assert response.status_code == 200 assert response.history[0].status_code == 307 + @markers.aws.validated + @markers.requires_in_process # we're monkeypatching the handler chain + @markers.snapshot.skip_snapshot_verify( + # missing from `HeadBucket` call + paths=["$..AccessPointAlias"], + ) + def test_create_bucket_aws_global( + self, + aws_client_factory, + cleanups, + aws_client, + snapshot, + aws_http_client_factory, + monkeypatch, + ): + """ + Some tools use the `aws-global` region instead of `us-east-1` when no region are defined. It is considered + a valid region by Botocore too when creating the client, as it is a pseudo-region used for endpoint resolving. + The client is however supposed to then use `us-east-1` to sign the request, not `aws-global`. + Using `aws-global` to sign the request results in an error in AWS. + It seems some tools like the Go SDK used by Terraform might have the wrong logic, and skip the part where + they are supposed to sign with `us-east-1` if you override the endpoint url and skip the endpoint + resolving part, which is how we end up with those kind of requests. + """ + # we need to patch the `DefaultRegionRewriterStrategy` as it wil replace `aws-global` by `us-east-1`, which + # is its default region + from localstack.aws.handlers.region import DefaultRegionRewriterStrategy + + monkeypatch.setattr(DefaultRegionRewriterStrategy, "apply", lambda *_, **__: None) + + global_region = "aws-global" + bucket_prefix = f"global-bucket-{short_uid()}" + bucket_name_1 = f"{bucket_prefix}-{short_uid()}" + headers = {"x-amz-content-sha256": "UNSIGNED-PAYLOAD"} + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(bucket_name_1, ""), + snapshot.transform.regex(bucket_prefix, ""), + snapshot.transform.regex(AWS_REGION_US_EAST_1, ""), + snapshot.transform.key_value("DisplayName"), + snapshot.transform.key_value("ID"), + snapshot.transform.key_value("HostId"), + snapshot.transform.key_value("RequestId"), + ] + ) + http_client = aws_http_client_factory( + service="s3", signer_factory=SigV4Auth, region=global_region + ) + # use the global endpoint with no region + base_endpoint = _endpoint_url(region=AWS_REGION_US_EAST_1) + response = http_client.put(f"{base_endpoint}/{bucket_name_1}", headers=headers) + if response.ok: + # we still clean up even if we expect it to fail, just in case it doesn't fail in AWS + cleanups.append(lambda: aws_client.s3.delete_bucket(Bucket=bucket_name_1)) + + assert response.status_code == 400 + xml_error = xmltodict.parse(response.content) + snapshot.match("xml-error-create-bucket", xml_error) + + # botocore is automatically signing the request with `us-east-1`, so we don't have issues + s3_client = aws_client_factory(region_name=global_region).s3 + create_bucket = s3_client.create_bucket(Bucket=bucket_name_1) + cleanups.append(lambda: aws_client.s3.delete_bucket(Bucket=bucket_name_1)) + snapshot.match("create-bucket-global", create_bucket) + + head_bucket = s3_client.head_bucket(Bucket=bucket_name_1) + snapshot.match("head-bucket-global", head_bucket) + + get_location_1 = aws_client.s3.get_bucket_location(Bucket=bucket_name_1) + # verify that the bucket 1 is created in `us-east-1` + snapshot.match("get-location-1", get_location_1) + + list_buckets_per_region = aws_client.s3.list_buckets( + BucketRegion=AWS_REGION_US_EAST_1, Prefix=bucket_prefix + ) + snapshot.match("list-buckets", list_buckets_per_region) + + # test that HeadBucket also raises an exception + head_response = http_client.head(f"{base_endpoint}/{bucket_name_1}", headers=headers) + + assert head_response.status_code == 400 + assert not head_response.content + + @markers.aws.validated + @pytest.mark.parametrize("client_region", [AWS_REGION_US_EAST_1, "us-west-1"]) + def test_bucket_constraint_aws_global( + self, s3_create_bucket_with_client, snapshot, aws_client_factory, client_region + ): + snapshot.add_transformer(snapshot.transform.s3_api()) + bucket_name = f"bucket-{short_uid()}" + us_east_1_client = aws_client_factory(region_name=client_region).s3 + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + us_east_1_client, + Bucket=bucket_name, + CreateBucketConfiguration={"LocationConstraint": "aws-global"}, + ) + snapshot.match("aws-global-constraint", e.value.response) + @markers.aws.validated def test_bucket_does_not_exist(self, s3_vhost_client, snapshot, aws_client): snapshot.add_transformer(snapshot.transform.s3_api()) @@ -4651,6 +4858,7 @@ def test_virtual_host_proxying_headers(self, s3_bucket, aws_client): ) @pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="KMS not enabled in S3 image") @markers.aws.validated + @markers.requires_in_process # Patches the kms validation def test_s3_sse_validate_kms_key( self, aws_client_factory, @@ -4663,6 +4871,7 @@ def test_s3_sse_validate_kms_key( region_us_west_2 = "us-west-2" snapshot.add_transformers_list( [ + snapshot.transform.key_value("CurrentKeyMaterialId"), snapshot.transform.key_value("Description"), snapshot.transform.regex(region_us_east_2, ""), snapshot.transform.regex(region_us_west_2, ""), @@ -4802,10 +5011,16 @@ def test_s3_sse_validate_kms_key( "$..ETag", # the ETag is different as we don't encrypt the object with the KMS key ] ) + @markers.requires_in_process # Patches the kms validation def test_s3_sse_validate_kms_key_state( self, s3_bucket, kms_create_key, monkeypatch, snapshot, aws_client ): - snapshot.add_transformer(snapshot.transform.key_value("Description")) + snapshot.add_transformer( + [ + snapshot.transform.key_value("Description"), + snapshot.transform.key_value("CurrentKeyMaterialId"), + ] + ) data = b"test-sse" # create key in the same region as the bucket @@ -5047,10 +5262,6 @@ def test_response_structure(self, aws_http_client_factory, s3_bucket, aws_client s3_http_client = aws_http_client_factory("s3", signer_factory=SigV4Auth) - def get_xml_content(http_response_content: bytes) -> bytes: - # just format a bit the XML, nothing bad parity wise, but allow the test to run against AWS - return http_response_content.replace(b"'", b'"').replace(b"utf", b"UTF") - # Lists all buckets endpoint_url = _endpoint_url() resp = s3_http_client.get(endpoint_url, headers=headers) @@ -5184,6 +5395,163 @@ def get_xml_content(http_response_content: bytes) -> bytes: assert resp.headers.get("Content-Type") is None assert resp.headers.get("Content-Length") is None + @markers.aws.validated + @pytest.mark.parametrize( + "client_region, location_constraint, expected_result", + [ + ("us-east-1", None, ""), + ("eu-west-1", "EU", "EU"), + ("eu-central-1", "eu-central-1", "eu-central-1"), + ], + ) + def test_response_structure_get_bucket_location( + self, + aws_http_client_factory, + s3_create_bucket_with_client, + aws_client_factory, + client_region, + location_constraint, + expected_result, + ): + s3_http_client = aws_http_client_factory("s3", signer_factory=SigV4Auth) + s3_client = aws_client_factory(region_name=client_region).s3 + bucket_name = f"get-bucket-location-response-test-{short_uid()}" + + bucket_config = ( + {"CreateBucketConfiguration": {"LocationConstraint": location_constraint}} + if location_constraint + else {} + ) + s3_create_bucket_with_client(s3_client, Bucket=bucket_name, **bucket_config) + get_bucket_location_response = s3_http_client.get( + f"{_bucket_url(bucket_name)}?location", + headers={"x-amz-content-sha256": "UNSIGNED-PAYLOAD"}, + ) + xml_content = get_xml_content(get_bucket_location_response.content) + assert get_bucket_location_xml(expected_result).encode() == xml_content + + @markers.aws.validated + def test_response_structure_get_obj_attrs(self, aws_http_client_factory, s3_bucket, aws_client): + """ + Test that the response structure is correct for the S3 API for GetObjectAttributes + The order is important for the Java SDK + """ + key_name = "get-obj-attrs" + aws_client.s3.put_object(Bucket=s3_bucket, Key=key_name, Body="test") + headers = {"x-amz-content-sha256": "UNSIGNED-PAYLOAD"} + + s3_http_client = aws_http_client_factory("s3", signer_factory=SigV4Auth) + bucket_url = _bucket_url(s3_bucket) + + possible_attrs = ["StorageClass", "ETag", "ObjectSize", "ObjectParts", "Checksum"] + + # GetObjectAttributes + get_object_attributes_url = f"{bucket_url}/{key_name}?attributes" + headers["x-amz-object-attributes"] = ",".join(possible_attrs) + resp = s3_http_client.get(get_object_attributes_url, headers=headers) + + # shuffle the original list + shuffled_attrs = possible_attrs.copy() + while shuffled_attrs == possible_attrs: + random.shuffle(shuffled_attrs) + + assert shuffled_attrs != possible_attrs + + # check that the order of Attributes in the request should not affect the order in the response + headers["x-amz-object-attributes"] = ",".join(shuffled_attrs) + resp_randomized = s3_http_client.get(get_object_attributes_url, headers=headers) + assert resp_randomized.content == resp.content + + def get_ordered_keys(content: bytes) -> list[str]: + resp_dict = xmltodict.parse(content) + get_attrs_response = resp_dict["GetObjectAttributesResponse"] + get_attrs_response.pop("@xmlns", None) + return list(get_attrs_response.keys()) + + ordered_keys = get_ordered_keys(resp.content) + assert ordered_keys[0] == "ETag" + assert ordered_keys[1] == "Checksum" + assert ordered_keys[2] == "StorageClass" + assert ordered_keys[3] == "ObjectSize" + + # create a Multipart Upload to validate the `ObjectParts` field order + multipart_key = "multipart-key" + create_multipart = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multipart_key + ) + upload_id = create_multipart["UploadId"] + upload_part = aws_client.s3.upload_part( + Bucket=s3_bucket, + Key=multipart_key, + Body="test", + PartNumber=1, + UploadId=upload_id, + ) + aws_client.s3.complete_multipart_upload( + Bucket=s3_bucket, + Key=multipart_key, + MultipartUpload={"Parts": [{"ETag": upload_part["ETag"], "PartNumber": 1}]}, + UploadId=upload_id, + ) + + get_object_attributes_url = f"{bucket_url}/{multipart_key}?attributes" + headers["x-amz-object-attributes"] = ",".join(possible_attrs) + resp = s3_http_client.get(get_object_attributes_url, headers=headers) + mpu_ordered_keys = get_ordered_keys(resp.content) + assert mpu_ordered_keys[0] == "ETag" + assert mpu_ordered_keys[1] == "Checksum" + assert mpu_ordered_keys[2] == "ObjectParts" + assert mpu_ordered_keys[3] == "StorageClass" + assert mpu_ordered_keys[4] == "ObjectSize" + + @markers.aws.only_localstack + def test_get_obj_attrs_multi_headers_behavior(self, s3_bucket, aws_client, region_name): + """ + The Botocore serializer will by default encode the header list by concatenating it in a comma-separated string + Some different serializers, like the Java SDK or Go, will add a header entry for each value. + See https://github.com/aws/aws-sdk-go-v2/issues/1620 for example + We validate that we can properly parse the request when receiving the following: + X-Amz-Object-Attributes: Checksum + X-Amz-Object-Attributes: ObjectParts + + Botocore default behavior would be: + X-Amz-Object-Attributes: Checksum,ObjectParts + """ + key_name = "get-obj-attrs" + aws_client.s3.put_object(Bucket=s3_bucket, Key=key_name, Body="test") + + bucket_url = _bucket_url(s3_bucket) + + def get_urllib_headers_for_attributes(attributes: list[str]) -> HTTPHeaderDict: + _headers = mock_aws_request_headers( + "s3", + aws_access_key_id=TEST_AWS_ACCESS_KEY_ID, + region_name=region_name, + ) + + urllib_headers = HTTPHeaderDict(headers=_headers) + for attr in attributes: + urllib_headers.add("x-amz-object-attributes", attr) + return urllib_headers + + get_object_attributes_url = f"{bucket_url}/{key_name}?attributes" + + possible_attrs = ["StorageClass", "ETag", "ObjectSize", "ObjectParts", "Checksum"] + # we use the low level `urllib3` and `HTTPHeaderDict` to make sure the headers are properly serialized + # as distinct headers and not a concatenated value + headers = get_urllib_headers_for_attributes(possible_attrs) + resp = urllib3.request(method="GET", url=get_object_attributes_url, headers=headers) + + resp_dict = xmltodict.parse(resp.data) + get_attrs_response = resp_dict["GetObjectAttributesResponse"] + get_attrs_response.pop("@xmlns", None) + attributes_keys = list(get_attrs_response.keys()) + + assert attributes_keys[0] == "ETag" + assert attributes_keys[1] == "Checksum" + assert attributes_keys[2] == "StorageClass" + assert attributes_keys[3] == "ObjectSize" + @markers.aws.validated def test_s3_timestamp_precision(self, s3_bucket, aws_client, aws_http_client_factory): object_key = "test-key" @@ -5383,9 +5751,6 @@ def test_s3_sse_bucket_key_default( @pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="KMS not enabled in S3 image") @markers.aws.validated - @pytest.mark.skip( - reason="Behaviour not implemented yet: https://github.com/localstack/localstack/issues/6882" - ) # there is currently no server side encryption is place in LS, ETag will be different @markers.snapshot.skip_snapshot_verify(paths=["$..ETag"]) def test_s3_sse_default_kms_key( @@ -6070,6 +6435,7 @@ class TestS3PresignedUrl: # # Note: This test may have side effects (via `s3_client.meta.events.register(..)`) and # # may not be suitable for parallel execution @markers.aws.validated + @markers.requires_in_process # monkeypatches signature validation def test_presign_with_additional_query_params( self, s3_bucket, patch_s3_skip_signature_validation_false, aws_client ): @@ -6096,6 +6462,7 @@ def add_query_param(request, **kwargs): finally: s3_presigned_client.meta.events.unregister("before-sign.s3.GetObject", add_query_param) + @markers.requires_in_process # Patches skip signature validation @markers.aws.only_localstack def test_presign_check_signature_validation_for_port_permutation( self, s3_bucket, patch_s3_skip_signature_validation_false, aws_client @@ -6138,6 +6505,7 @@ def test_put_object(self, s3_bucket, snapshot, aws_client): assert response["Body"].read() == b"something" snapshot.match("get_object", response) + @markers.requires_in_process # Patches skip signature validation @markers.aws.only_localstack def test_get_request_expires_ignored_if_validation_disabled( self, s3_bucket, monkeypatch, patch_s3_skip_signature_validation_false, aws_client @@ -6211,7 +6579,9 @@ def test_head_has_correct_content_length_header(self, s3_bucket, aws_client): assert response.headers.get("content-length") == str(len(body)) @markers.aws.validated - @pytest.mark.parametrize("verify_signature", (True, False)) + @pytest.mark.parametrize( + "verify_signature", (pytest.param(True, marks=markers.requires_in_process), False) + ) def test_put_url_metadata_with_sig_s3v4( self, s3_bucket, @@ -6286,7 +6656,9 @@ def test_put_url_metadata_with_sig_s3v4( assert "wrong" not in head_object["Metadata"] @markers.aws.validated - @pytest.mark.parametrize("verify_signature", (True, False)) + @pytest.mark.parametrize( + "verify_signature", (pytest.param(True, marks=markers.requires_in_process), False) + ) def test_put_url_metadata_with_sig_s3( self, s3_bucket, @@ -6345,6 +6717,77 @@ def test_put_url_metadata_with_sig_s3( else: assert "wrong" not in head_object["Metadata"] + @markers.aws.validated + def test_get_response_overrides_unicode_metadata_with_sig_s3( + self, + s3_bucket, + snapshot, + aws_client, + presigned_snapshot_transformers, + ): + snapshot.add_transformer(snapshot.transform.s3_api()) + presigned_client = _s3_client_pre_signed_client( + Config(signature_version="s3"), + endpoint_url=_endpoint_url(), + ) + object_key = "key-non-ascii" + aws_client.s3.put_object(Bucket=s3_bucket, Key=object_key) + + url = presigned_client.generate_presigned_url( + "get_object", + Params={ + "Bucket": s3_bucket, + "Key": object_key, + "ResponseCacheControl": "non-ascii-%E2%80%94_—_é_", + "ResponseContentDisposition": 'filename="test_—_file%E2%80%94_é_2.pdf"', + }, + ) + response = requests.get(url, verify=False) + assert response.status_code == 400 + + response_content = xmltodict.parse(response.content) + snapshot.match("unicode-error", response_content) + + @markers.aws.validated + def test_put_unicode_metadata_with_sig_s3( + self, + s3_bucket, + snapshot, + aws_client, + presigned_snapshot_transformers, + ): + # we need to import the internal handlers of botocore and remove it, because it interferes with testing, and + # other SDKs do not have that validation and will accept non-ascii metadata + from botocore.handlers import validate_ascii_metadata + + snapshot.add_transformer(snapshot.transform.s3_api()) + presigned_client = _s3_client_pre_signed_client( + Config(signature_version="s3"), + endpoint_url=_endpoint_url(), + ) + # remove the builtin handler that validate ascii in the metadata, because AWS actually accepts it if it is in + # pre-signed URLs. + presigned_client.meta.events.unregister( + "before-parameter-build.s3.PutObject", + validate_ascii_metadata, + ) + + object_key = "key-non-ascii" + + metadata = {"foo": "non-ascii-%E2%80%94_—_é_"} + + # put object via presigned URL with metadata + url = presigned_client.generate_presigned_url( + "put_object", + Params={"Bucket": s3_bucket, "Key": object_key, "Metadata": metadata}, + ) + + response = requests.put(url, verify=False) + assert response.ok + + response = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head_object", response) + @markers.aws.validated def test_get_object_ignores_request_body(self, s3_bucket, aws_client): key = "foo-key" @@ -6387,9 +6830,9 @@ def test_presigned_double_encoded_credentials( @pytest.mark.parametrize( "signature_version, verify_signature", [ - ("s3", True), + pytest.param("s3", True, marks=markers.requires_in_process), ("s3", False), - ("s3v4", True), + pytest.param("s3v4", True, marks=markers.requires_in_process), ("s3v4", False), ], ) @@ -6479,6 +6922,7 @@ def test_s3_get_response_default_content_type(self, s3_bucket, aws_client): @markers.aws.validated @pytest.mark.parametrize("signature_version", ["s3", "s3v4"]) + @markers.requires_in_process # Patches skip signature validation def test_s3_presigned_url_expired( self, s3_bucket, @@ -6526,6 +6970,7 @@ def test_s3_presigned_url_expired( @markers.aws.validated @pytest.mark.parametrize("signature_version", ["s3", "s3v4"]) + @markers.requires_in_process # Patches skip signature validation def test_s3_put_presigned_url_with_different_headers( self, s3_bucket, @@ -6633,6 +7078,7 @@ def test_s3_put_presigned_url_with_different_headers( snapshot.match("wrong-content-encoding-response", exception) @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_s3_put_presigned_url_same_header_and_qs_parameter( self, s3_bucket, @@ -6694,6 +7140,7 @@ def test_s3_put_presigned_url_same_header_and_qs_parameter( exception["StatusCode"] = response.status_code snapshot.match("override-signed-qs", exception) + @markers.requires_in_process # Patches skip signature validation @markers.aws.validated @pytest.mark.parametrize("signature_version", ["s3", "s3v4"]) def test_s3_put_presigned_url_missing_sig_param( @@ -6769,6 +7216,7 @@ def test_s3_get_response_content_type_same_as_upload_and_range(self, s3_bucket, @pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="STS not enabled in S3 image") @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_presigned_url_with_session_token( self, s3_create_bucket_with_client, @@ -6808,6 +7256,7 @@ def test_presigned_url_with_session_token( @pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="STS not enabled in S3 image") @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_presigned_url_with_different_user_credentials( self, aws_client, @@ -6879,6 +7328,7 @@ def test_presigned_url_with_different_user_credentials( assert response._content == b"test-value" @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation @pytest.mark.parametrize("signature_version", ["s3", "s3v4"]) def test_s3_get_response_header_overrides( self, s3_bucket, signature_version, patch_s3_skip_signature_validation_false, aws_client @@ -6982,6 +7432,7 @@ def test_s3_get_response_case_sensitive_headers(self, s3_bucket, aws_client): ], ) @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_presigned_url_signature_authentication_expired( self, s3_create_bucket, @@ -7023,6 +7474,7 @@ def test_presigned_url_signature_authentication_expired( ], ) @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_presigned_url_signature_authentication( self, s3_create_bucket, @@ -7133,6 +7585,7 @@ def test_presigned_url_signature_authentication( ], ) @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_presigned_url_signature_authentication_multi_part( self, s3_create_bucket, @@ -7194,6 +7647,7 @@ def test_presigned_url_signature_authentication_multi_part( @pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="Lambda not enabled in S3 image") @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_presigned_url_v4_x_amz_in_qs( self, s3_bucket, @@ -7285,6 +7739,7 @@ def test_presigned_url_v4_x_amz_in_qs( @pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="Lambda not enabled in S3 image") @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_presigned_url_v4_signed_headers_in_qs( self, s3_bucket, @@ -7361,6 +7816,7 @@ def test_presigned_url_v4_signed_headers_in_qs( assert response.status_code == 200 @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_pre_signed_url_forward_slash_bucket( self, s3_bucket, patch_s3_skip_signature_validation_false, aws_client ): @@ -7394,6 +7850,7 @@ def test_pre_signed_url_forward_slash_bucket( ["s3", "s3v4"], ) @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_s3_presign_url_encoding( self, aws_client, s3_bucket, signature_version, patch_s3_skip_signature_validation_false ): @@ -7416,6 +7873,7 @@ def test_s3_presign_url_encoding( assert req.content == b"123" @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation def test_s3_ignored_special_headers( self, s3_bucket, @@ -7576,6 +8034,40 @@ def test_pre_signed_url_if_match(self, s3_bucket, aws_client, aws_session): response = requests.put(req.url) assert response.status_code == 412 + @markers.aws.only_localstack + @markers.requires_in_process # Patches skip signature validation + @pytest.mark.parametrize("signature_version", ["s3", "s3v4"]) + def test_pre_signed_url_validation_works_on_internal_account( + self, + aws_client, + account_id, + s3_create_bucket_with_client, + signature_version, + patch_s3_skip_signature_validation_false, + ): + bucket_name = f"bucket-{short_uid()}" + key_name = "test-key" + credentials = { + "AccessKeyId": config.INTERNAL_RESOURCE_ACCOUNT, + "SecretAccessKey": constants.INTERNAL_AWS_SECRET_ACCESS_KEY, + } + s3_client = create_client_with_keys( + service="s3", + region_name=AWS_REGION_US_EAST_1, + keys=credentials, + client_config=Config(signature_version=signature_version), + ) + + s3_create_bucket_with_client(s3_client=s3_client, Bucket=bucket_name) + s3_client.put_object(Body="test-value", Bucket=bucket_name, Key=key_name) + pre_signed_url = s3_client.generate_presigned_url( + ClientMethod="get_object", + Params={"Bucket": bucket_name, "Key": key_name}, + ExpiresIn=600, + ) + response = requests.get(pre_signed_url) + assert response._content == b"test-value" + class TestS3DeepArchive: """ @@ -10422,6 +10914,7 @@ def test_post_request_expires( "signature_version", ["s3", "s3v4"], ) + @markers.requires_in_process # Patches skip signature validation def test_post_request_malformed_policy( self, s3_bucket, @@ -10464,6 +10957,7 @@ def test_post_request_malformed_policy( assert exception["Error"]["StringToSign"] == presigned_request["fields"]["policy"] @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation @pytest.mark.parametrize( "signature_version", ["s3", "s3v4"], @@ -10505,6 +10999,7 @@ def test_post_request_missing_signature( snapshot.match("exception-missing-signature", exception) @markers.aws.validated + @markers.requires_in_process # Patches skip signature validation @pytest.mark.parametrize( "signature_version", ["s3", "s3v4"], @@ -10756,6 +11251,76 @@ def test_post_object_with_metadata(self, s3_bucket, aws_client, snapshot): head_object = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) snapshot.match("head-object", head_object) + @markers.aws.validated + def test_post_object_with_unicode_metadata(self, s3_bucket, aws_client, snapshot): + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html + # To avoid issues related to the presentation of these metadata values, you should conform to using US-ASCII + # characters when using REST and UTF-8 when using SOAP or browser-based uploads through POST. + # When using non-US-ASCII characters in your metadata values, the provided Unicode string is examined for + # non-US-ASCII characters. Values of such headers are character decoded as per RFC 2047 before storing and + # encoded as per RFC 2047 to make them mail-safe before returning. If the string contains only US-ASCII + # characters, it is presented as is. + + snapshot.add_transformer(snapshot.transform.key_value("Bucket"), priority=10) + object_key = "test_unicode—_file.pdf" + content_disposition = 'filename="test_—_file%E2%80%94_é_2-.pdf"' + cache_control = "non-ascii-%E2%80%94_—_é_" + user_metadata = "ÄMÄZÕÑ S3" + user_metadata_2 = "test_—_file%E2%80%94_é_2👑.pdf" + test_safe_chars = "! \"#$%&'()*+,-./0123456789:;<>'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t" + utf_8_byte_string = "\x00\x01\x02\x03\x04" + rfc_2047_q_encoded = "=?UTF-8?Q?actually-ascii?=" + rfc_2047_b_encoded = "=?UTF-8?B?YWJj?=" + + presigned_request = aws_client.s3.generate_presigned_post( + Bucket=s3_bucket, + Key=object_key, + ExpiresIn=60, + Fields={ + "Content-Disposition": content_disposition, + "Cache-Control": cache_control, + "x-amz-meta-nonascii": user_metadata, + "x-amz-meta-nonascii-2": user_metadata_2, + "x-amz-meta-safe-chars": test_safe_chars, + "x-amz-meta-utf-8": utf_8_byte_string, + "x-amz-meta-q-encoded": rfc_2047_q_encoded, + "x-amz-meta-b-encoded": rfc_2047_b_encoded, + # we require the 201 status code to get more information from the response + "success_action_status": "201", + }, + Conditions=[ + {"bucket": s3_bucket}, + ["eq", "$Content-Disposition", content_disposition], + ["eq", "$Cache-Control", cache_control], + ["eq", "$x-amz-meta-nonascii", user_metadata], + ["eq", "$x-amz-meta-nonascii-2", user_metadata_2], + ["eq", "$x-amz-meta-safe-chars", test_safe_chars], + ["eq", "$x-amz-meta-utf-8", utf_8_byte_string], + ["eq", "$x-amz-meta-q-encoded", rfc_2047_q_encoded], + ["eq", "$x-amz-meta-b-encoded", rfc_2047_b_encoded], + ["eq", "$success_action_status", "201"], + ], + ) + # PostObject + response = requests.post( + presigned_request["url"], + data=presigned_request["fields"], + files={"file": "test-body-tagging"}, + verify=False, + ) + assert response.status_code == 201 + response_content = xmltodict.parse(response.content) + location_header = response.headers.get("Location") + response_content["LocationHeader"] = location_header + response_content["PostResponse"].pop("@xmlns", None) + snapshot.match("post-object", response_content) + + location_prefix = location_header.split("/test_unicode")[0] + snapshot.add_transformer(snapshot.transform.regex(location_prefix, "")) + + head_object = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-object", head_object) + @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ @@ -12392,8 +12957,11 @@ def test_s3_checksum_no_automatic_sdk_calculation( class TestS3MultipartUploadChecksum: @markers.aws.validated @markers.snapshot.skip_snapshot_verify( - # it seems the PartNumber might not be deterministic, possibly parallelized on S3 side? - paths=["$.complete-multipart-wrong-parts-checksum.Error.PartNumber"] + # it seems the PartNumber (and ETag by extension) might not be deterministic, possibly parallelized on S3 side? + paths=[ + "$.complete-multipart-wrong-parts-checksum.Error.PartNumber", + "$.complete-multipart-wrong-parts-checksum.Error.ETag", + ] ) @pytest.mark.parametrize("algorithm", ["CRC32", "CRC32C", "SHA1", "SHA256"]) def test_complete_multipart_parts_checksum_composite( @@ -13362,9 +13930,7 @@ def _website_bucket_url(bucket_name: str): if is_aws_cloud(): region = AWS_REGION_US_EAST_1 return f"http://{bucket_name}.s3-website-{region}.amazonaws.com" - return _bucket_url_vhost( - bucket_name, localstack_host=localstack.config.S3_STATIC_WEBSITE_HOSTNAME - ) + return _bucket_url_vhost(bucket_name, localstack_host=config.S3_STATIC_WEBSITE_HOSTNAME) def _bucket_url_vhost(bucket_name: str, region: str = "", localstack_host: str = None) -> str: diff --git a/tests/aws/services/s3/test_s3.snapshot.json b/tests/aws/services/s3/test_s3.snapshot.json index 349879b0c4896..e769bde07e748 100644 --- a/tests/aws/services/s3/test_s3.snapshot.json +++ b/tests/aws/services/s3/test_s3.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_with_content": { - "recorded-date": "21-01-2025, 18:26:26", + "recorded-date": "21-02-2026, 00:17:01", "recorded-content": { "list-objects": { "Contents": [ @@ -13,7 +13,6 @@ "Key": "test-key-0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -28,7 +27,6 @@ "Key": "test-key-1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -43,7 +41,6 @@ "Key": "test-key-2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -58,7 +55,6 @@ "Key": "test-key-3", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -73,7 +69,6 @@ "Key": "test-key-4", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -88,7 +83,6 @@ "Key": "test-key-5", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -103,7 +97,6 @@ "Key": "test-key-6", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -118,7 +111,6 @@ "Key": "test-key-7", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -133,7 +125,6 @@ "Key": "test-key-8", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -148,7 +139,6 @@ "Key": "test-key-9", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 6, @@ -169,20 +159,12 @@ "list-buckets": { "Buckets": [ { + "BucketArn": "arn::s3:::", "CreationDate": "datetime", "Name": "" - }, - { - "CreationDate": "datetime", - "Name": "" - }, - { - "CreationDate": "datetime", - "Name": "" } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -193,7 +175,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_utf8_key": { - "recorded-date": "21-01-2025, 18:26:27", + "recorded-date": "21-02-2026, 00:17:02", "recorded-content": { "put-object": { "ChecksumCRC32": "zwK7XA==", @@ -224,7 +206,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_metadata_header_character_decoding": { - "recorded-date": "21-01-2025, 18:26:37", + "recorded-date": "21-02-2026, 00:17:18", "recorded-content": { "head-object": { "AcceptRanges": "bytes", @@ -245,7 +227,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_multipart": { - "recorded-date": "21-01-2025, 18:26:40", + "recorded-date": "21-02-2026, 00:17:20", "recorded-content": { "get_object": { "AcceptRanges": "bytes", @@ -266,7 +248,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_no_such_bucket": { - "recorded-date": "21-01-2025, 18:27:10", + "recorded-date": "21-02-2026, 00:17:57", "recorded-content": { "expected_error": { "Error": { @@ -282,7 +264,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_no_such_bucket": { - "recorded-date": "21-01-2025, 18:27:11", + "recorded-date": "21-02-2026, 00:17:57", "recorded-content": { "expected_error": { "Error": { @@ -298,7 +280,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_notification_configuration_no_such_bucket": { - "recorded-date": "21-01-2025, 18:27:11", + "recorded-date": "21-02-2026, 00:17:58", "recorded-content": { "expected_error": { "Error": { @@ -314,7 +296,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes": { - "recorded-date": "17-03-2025, 20:02:49", + "recorded-date": "21-02-2026, 00:18:02", "recorded-content": { "object-attrs": { "Checksum": { @@ -373,7 +355,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_hash_prefix": { - "recorded-date": "21-01-2025, 18:27:44", + "recorded-date": "21-02-2026, 00:18:18", "recorded-content": { "put-object": { "ChecksumCRC32": "wmkP3w==", @@ -404,7 +386,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_invalid_range_error": { - "recorded-date": "21-01-2025, 18:27:45", + "recorded-date": "21-02-2026, 00:18:20", "recorded-content": { "exc": { "Error": { @@ -421,7 +403,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_range_key_not_exists": { - "recorded-date": "21-01-2025, 18:27:47", + "recorded-date": "21-02-2026, 00:18:22", "recorded-content": { "exc": { "Error": { @@ -437,7 +419,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_tagging_empty_list": { - "recorded-date": "21-01-2025, 18:28:08", + "recorded-date": "21-02-2026, 00:18:51", "recorded-content": { "created-object-tags": { "TagSet": [], @@ -472,7 +454,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_after_deleted_in_versioned_bucket": { - "recorded-date": "21-01-2025, 18:28:12", + "recorded-date": "21-02-2026, 00:18:55", "recorded-content": { "get-object": { "AcceptRanges": "bytes", @@ -505,7 +487,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32]": { - "recorded-date": "17-03-2025, 18:27:45", + "recorded-date": "21-02-2026, 00:37:12", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -589,7 +571,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32C]": { - "recorded-date": "17-03-2025, 18:27:58", + "recorded-date": "21-02-2026, 00:37:27", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -673,7 +655,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA1]": { - "recorded-date": "17-03-2025, 18:28:09", + "recorded-date": "21-02-2026, 00:37:34", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -757,7 +739,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA256]": { - "recorded-date": "17-03-2025, 18:28:24", + "recorded-date": "21-02-2026, 00:37:49", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -841,7 +823,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_replace": { - "recorded-date": "21-01-2025, 18:28:50", + "recorded-date": "21-02-2026, 00:18:57", "recorded-content": { "put_object": { "ChecksumCRC32": "MzVIGw==", @@ -872,6 +854,7 @@ "copy_object": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -899,7 +882,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_content_type_and_metadata": { - "recorded-date": "21-01-2025, 18:29:13", + "recorded-date": "21-02-2026, 00:19:21", "recorded-content": { "put_object": { "ChecksumCRC32": "MzVIGw==", @@ -929,6 +912,7 @@ "copy_object": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -956,6 +940,7 @@ "copy_object_second": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -983,13 +968,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_acls": { - "recorded-date": "21-01-2025, 18:30:13", + "recorded-date": "21-02-2026, 00:20:32", "recorded-content": { "bucket-acl": { "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -1004,7 +988,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -1016,7 +999,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -1024,7 +1006,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -1036,7 +1017,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -1044,7 +1024,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -1056,7 +1035,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -1078,7 +1056,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -1088,46 +1065,8 @@ } } }, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expiry": { - "recorded-date": "21-01-2025, 18:30:37", - "recorded-content": { - "head-object-expired": { - "AcceptRanges": "bytes", - "ContentLength": 3, - "ContentType": "binary/octet-stream", - "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", - "Expires": "datetime", - "ExpiresString": "", - "LastModified": "datetime", - "Metadata": {}, - "ServerSideEncryption": "AES256", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-object-not-yet-expired": { - "AcceptRanges": "bytes", - "Body": "foo", - "ChecksumCRC32": "jHNlIQ==", - "ChecksumType": "FULL_OBJECT", - "ContentLength": 3, - "ContentType": "binary/octet-stream", - "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", - "Expires": "datetime", - "ExpiresString": "", - "LastModified": "datetime", - "Metadata": {}, - "ServerSideEncryption": "AES256", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_with_xml_preamble": { - "recorded-date": "21-01-2025, 18:30:40", + "recorded-date": "21-02-2026, 00:20:56", "recorded-content": { "get_object": { "AcceptRanges": "bytes", @@ -1148,7 +1087,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_availability": { - "recorded-date": "21-01-2025, 18:30:41", + "recorded-date": "21-02-2026, 00:20:57", "recorded-content": { "bucket-lifecycle": { "Error": { @@ -1175,7 +1114,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_different_location_constraint": { - "recorded-date": "21-01-2025, 18:30:47", + "recorded-date": "23-02-2026, 12:30:37", "recorded-content": { "get-bucket-location-bucket-us-east-1": { "LocationConstraint": null, @@ -1196,13 +1135,11 @@ } }, "create-bucket-constraint-us-east-1-with-None": { - "Error": { - "Code": "MalformedXML", - "Message": "The XML you provided was not well-formed or did not validate against our published schema" - }, + "BucketArn": "", + "Location": "", "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 400 + "HTTPStatusCode": 200 } }, "get-bucket-location-bucket-us-east-2": { @@ -1222,6 +1159,16 @@ "HTTPStatusCode": 400 } }, + "create-bucket-constraint-us-east-2-with-None": { + "Error": { + "Code": "IllegalLocationConstraintException", + "Message": "The unspecified location constraint is incompatible for the region specific endpoint this request was sent to." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, "create-bucket-us-east-2-constraint-to-us-west-1": { "Error": { "Code": "IllegalLocationConstraintException", @@ -1267,7 +1214,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_with_anon_credentials": { - "recorded-date": "21-01-2025, 18:30:54", + "recorded-date": "21-02-2026, 00:21:11", "recorded-content": { "get_object": { "AcceptRanges": "bytes", @@ -1288,7 +1235,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_putobject_with_multiple_keys": { - "recorded-date": "21-01-2025, 18:30:56", + "recorded-date": "21-02-2026, 00:21:13", "recorded-content": { "get_object": { "AcceptRanges": "bytes", @@ -1309,7 +1256,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_bucket_lifecycle_configuration": { - "recorded-date": "21-01-2025, 18:18:18", + "recorded-date": "21-02-2026, 00:33:15", "recorded-content": { "get-bucket-lifecycle-exc-1": { "Error": { @@ -1361,7 +1308,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_lifecycle_configuration_on_bucket_deletion": { - "recorded-date": "21-01-2025, 18:18:20", + "recorded-date": "21-02-2026, 00:33:18", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -1396,7 +1343,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry": { - "recorded-date": "21-01-2025, 18:18:28", + "recorded-date": "21-02-2026, 00:33:26", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -1449,7 +1396,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_range_header_body_length": { - "recorded-date": "21-01-2025, 18:30:58", + "recorded-date": "21-02-2026, 00:21:15", "recorded-content": { "get-object": { "AcceptRanges": "bytes", @@ -1484,7 +1431,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_object_tagging": { - "recorded-date": "21-01-2025, 18:31:28", + "recorded-date": "21-02-2026, 00:21:26", "recorded-content": { "get-obj": { "AcceptRanges": "bytes", @@ -1521,7 +1468,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys": { - "recorded-date": "21-01-2025, 18:31:31", + "recorded-date": "21-02-2026, 00:21:30", "recorded-content": { "deleted-resp": { "Deleted": [ @@ -1543,7 +1490,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_in_non_existing_bucket": { - "recorded-date": "21-01-2025, 18:31:36", + "recorded-date": "21-02-2026, 00:21:34", "recorded-content": { "error-non-existent-bucket": { "Error": { @@ -1559,7 +1506,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer": { - "recorded-date": "21-01-2025, 18:31:42", + "recorded-date": "21-02-2026, 00:21:40", "recorded-content": { "put-bucket-request-payment": { "ResponseMetadata": { @@ -1577,7 +1524,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer_exceptions": { - "recorded-date": "21-01-2025, 18:31:43", + "recorded-date": "21-02-2026, 00:21:42", "recorded-content": { "wrong-payer-type": { "Error": { @@ -1603,7 +1550,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_exists": { - "recorded-date": "21-01-2025, 18:31:45", + "recorded-date": "21-02-2026, 00:21:44", "recorded-content": { "get-bucket-cors": { "CORSRules": [ @@ -1628,7 +1575,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -1636,7 +1582,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -1658,7 +1603,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_key_names": { - "recorded-date": "21-01-2025, 18:31:47", + "recorded-date": "21-02-2026, 00:21:46", "recorded-content": { "response": { "AcceptRanges": "bytes", @@ -1690,7 +1635,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_precondition_failed_error": { - "recorded-date": "21-01-2025, 18:32:22", + "recorded-date": "21-02-2026, 00:22:22", "recorded-content": { "get-object-if-match": { "Error": { @@ -1706,7 +1651,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_invalid_content_md5": { - "recorded-date": "21-01-2025, 18:32:49", + "recorded-date": "21-02-2026, 00:22:47", "recorded-content": { "md5-error-0": { "Error": { @@ -1864,7 +1809,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_upload_download_gzip": { - "recorded-date": "21-01-2025, 18:32:51", + "recorded-date": "21-02-2026, 00:22:49", "recorded-content": { "put-object": { "ChecksumCRC32": "KBARJw==", @@ -1896,7 +1841,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_copy_object_etag": { - "recorded-date": "17-03-2025, 23:02:42", + "recorded-date": "21-02-2026, 00:22:54", "recorded-content": { "multipart-upload": { "Bucket": "", @@ -1914,6 +1859,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC64NVME": "BsLNlKumA5I=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"eee506dd7ada7ded524c77e359a0e7c6\"", "LastModified": "datetime" }, @@ -1926,6 +1872,7 @@ "copy-object-in-place": { "CopyObjectResult": { "ChecksumCRC64NVME": "BsLNlKumA5I=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"eee506dd7ada7ded524c77e359a0e7c6\"", "LastModified": "datetime" }, @@ -1953,7 +1900,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_set_external_hostname": { - "recorded-date": "17-03-2025, 21:30:10", + "recorded-date": "21-02-2026, 00:23:06", "recorded-content": { "multipart-upload": { "Bucket": "", @@ -1984,7 +1931,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_lambda_integration": { - "recorded-date": "21-01-2025, 18:34:07", + "recorded-date": "21-02-2026, 00:23:21", "recorded-content": { "head_object": { "AcceptRanges": "bytes", @@ -2002,7 +1949,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_bucket_name": { - "recorded-date": "21-01-2025, 18:34:09", + "recorded-date": "21-02-2026, 00:23:23", "recorded-content": { "uppercase-bucket": { "Error": { @@ -2018,7 +1965,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_existing_name": { - "recorded-date": "21-01-2025, 18:34:10", + "recorded-date": "21-02-2026, 00:23:25", "recorded-content": { "create-bucket-us-west-1": { "Error": { @@ -2045,7 +1992,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_does_not_exist": { - "recorded-date": "21-01-2025, 18:34:13", + "recorded-date": "21-02-2026, 00:23:31", "recorded-content": { "list_object": { "Error": { @@ -2072,9 +2019,10 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_head_bucket": { - "recorded-date": "21-01-2025, 18:34:17", + "recorded-date": "21-02-2026, 00:23:35", "recorded-content": { "create_bucket": { + "BucketArn": "arn::s3:::", "Location": "/", "ResponseMetadata": { "HTTPHeaders": {}, @@ -2082,6 +2030,7 @@ } }, "create_bucket_location_constraint": { + "BucketArn": "arn::s3:::", "Location": "http://./", "ResponseMetadata": { "HTTPHeaders": {}, @@ -2090,6 +2039,7 @@ }, "head_bucket": { "AccessPointAlias": false, + "BucketArn": "arn::s3:::", "BucketRegion": "", "ResponseMetadata": { "HTTPHeaders": {}, @@ -2099,12 +2049,14 @@ "head_bucket_filtered_header": { "content-type": "application/xml", "x-amz-access-point-alias": "false", + "x-amz-bucket-arn": "arn::s3:::", "x-amz-bucket-region": "", "x-amz-id-2": "x-amz-id-2", "x-amz-request-id": "x-amz-request-id" }, "head_bucket_2": { "AccessPointAlias": false, + "BucketArn": "arn::s3:::", "BucketRegion": "", "ResponseMetadata": { "HTTPHeaders": {}, @@ -2114,6 +2066,7 @@ "head_bucket_2_filtered_header": { "content-type": "application/xml", "x-amz-access-point-alias": "false", + "x-amz-bucket-arn": "arn::s3:::", "x-amz-bucket-region": "", "x-amz-id-2": "x-amz-id-2", "x-amz-request-id": "x-amz-request-id" @@ -2131,7 +2084,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_name_with_dots": { - "recorded-date": "21-01-2025, 18:34:19", + "recorded-date": "21-02-2026, 00:23:37", "recorded-content": { "list-objects": { "Contents": [ @@ -2144,7 +2097,6 @@ "Key": "my-content", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -2203,7 +2155,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_more_than_1000_items": { - "recorded-date": "21-01-2025, 18:38:06", + "recorded-date": "21-02-2026, 00:27:23", "recorded-content": { "get_object-1009": { "AcceptRanges": "bytes", @@ -2263,7 +2215,6 @@ "Key": "test-key-990", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2278,7 +2229,6 @@ "Key": "test-key-991", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2293,7 +2243,6 @@ "Key": "test-key-992", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2308,7 +2257,6 @@ "Key": "test-key-993", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2323,7 +2271,6 @@ "Key": "test-key-994", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2338,7 +2285,6 @@ "Key": "test-key-995", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2353,7 +2299,6 @@ "Key": "test-key-996", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2368,7 +2313,6 @@ "Key": "test-key-997", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2383,7 +2327,6 @@ "Key": "test-key-998", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2398,7 +2341,6 @@ "Key": "test-key-999", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 8, @@ -2419,7 +2361,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_upload_big_file": { - "recorded-date": "21-01-2025, 18:39:01", + "recorded-date": "21-02-2026, 00:27:29", "recorded-content": { "put_object_key1": { "ChecksumCRC32": "eH3dJA==", @@ -2470,7 +2412,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_versioning_order": { - "recorded-date": "21-01-2025, 18:39:04", + "recorded-date": "21-02-2026, 00:27:32", "recorded-content": { "list_object_versions_before": { "EncodingType": "url", @@ -2517,7 +2459,6 @@ "Key": "test", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 4, @@ -2534,7 +2475,6 @@ "Key": "test", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 4, @@ -2551,7 +2491,6 @@ "Key": "test2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 4, @@ -2567,7 +2506,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_etag_on_get_object_call": { - "recorded-date": "21-01-2025, 18:39:07", + "recorded-date": "21-02-2026, 00:27:34", "recorded-content": { "get_object": { "AcceptRanges": "bytes", @@ -2603,7 +2542,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_object_with_version_id": { - "recorded-date": "21-01-2025, 18:39:10", + "recorded-date": "21-02-2026, 00:27:38", "recorded-content": { "get_bucket_versioning": { "Status": "Enabled", @@ -2643,7 +2582,6 @@ "Key": "aws/s3/testkey2.txt", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 960, @@ -2670,7 +2608,7 @@ "recorded-content": {} }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_public_objects_using_requests": { - "recorded-date": "21-01-2025, 19:48:17", + "recorded-date": "21-02-2026, 00:27:47", "recorded-content": { "multi-delete-with-requests": { "DeleteResult": { @@ -2700,7 +2638,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects": { - "recorded-date": "21-01-2025, 18:39:22", + "recorded-date": "21-02-2026, 00:27:50", "recorded-content": { "batch-delete": { "Deleted": [ @@ -2740,7 +2678,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object": { - "recorded-date": "17-03-2025, 21:31:27", + "recorded-date": "21-02-2026, 00:29:26", "recorded-content": { "get_object": { "AcceptRanges": "bytes", @@ -2761,11 +2699,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_copy_md5": { - "recorded-date": "21-01-2025, 18:24:04", + "recorded-date": "21-02-2026, 00:30:50", "recorded-content": { "copy-obj": { "CopyObjectResult": { "ChecksumCRC32": "Cdox+w==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"437b930db84b8079c2dd804a71936b5f\"", "LastModified": "datetime" }, @@ -2778,7 +2717,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_s3_get_deep_archive_object_restore": { - "recorded-date": "14-08-2023, 22:35:53", + "recorded-date": "21-02-2026, 00:32:22", "recorded-content": { "get-object-invalid-state": { "Error": { @@ -2813,7 +2752,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_head_object_fields": { - "recorded-date": "21-01-2025, 18:28:10", + "recorded-date": "21-02-2026, 00:18:52", "recorded-content": { "head-object": { "AcceptRanges": "bytes", @@ -2841,13 +2780,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl": { - "recorded-date": "21-01-2025, 18:30:17", + "recorded-date": "21-02-2026, 00:20:36", "recorded-content": { "get-bucket-acl": { "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -2862,7 +2800,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -2874,7 +2811,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -2882,7 +2818,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -2901,7 +2836,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -2913,7 +2847,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -2928,7 +2861,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -2939,7 +2871,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl_exceptions": { - "recorded-date": "21-01-2025, 18:30:22", + "recorded-date": "21-02-2026, 00:20:41", "recorded-content": { "put-bucket-canned-acl": { "Error": { @@ -3099,7 +3031,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_header_overrides": { - "recorded-date": "21-01-2025, 18:39:23", + "recorded-date": "21-02-2026, 00:27:51", "recorded-content": { "get-object": { "AcceptRanges": "bytes", @@ -3126,7 +3058,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_object_versioned": { - "recorded-date": "21-01-2025, 18:39:15", + "recorded-date": "21-02-2026, 00:27:43", "recorded-content": { "put-pre-versioned": { "ChecksumCRC32": "uQZ0CQ==", @@ -3173,7 +3105,6 @@ "Key": "non-version-bucket-key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 17, @@ -3261,7 +3192,6 @@ "Key": "non-version-bucket-key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 17, @@ -3278,7 +3208,6 @@ "Key": "versioned-bucket-key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 21, @@ -3295,7 +3224,6 @@ "Key": "versioned-bucket-key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 13, @@ -3327,7 +3255,6 @@ "Key": "non-version-bucket-key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 17, @@ -3344,7 +3271,6 @@ "Key": "versioned-bucket-key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 21, @@ -3361,7 +3287,6 @@ "Key": "versioned-bucket-key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 13, @@ -3438,7 +3363,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-True]": { - "recorded-date": "21-01-2025, 18:23:05", + "recorded-date": "21-02-2026, 00:29:51", "recorded-content": { "with-decoded-content-length": { "Error": { @@ -3467,11 +3392,11 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-False]": { - "recorded-date": "21-01-2025, 18:23:07", + "recorded-date": "21-02-2026, 00:29:53", "recorded-content": {} }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-True]": { - "recorded-date": "21-01-2025, 18:23:09", + "recorded-date": "21-02-2026, 00:29:55", "recorded-content": { "with-decoded-content-length": { "Error": { @@ -3494,11 +3419,11 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-False]": { - "recorded-date": "21-01-2025, 18:23:10", + "recorded-date": "21-02-2026, 00:29:57", "recorded-content": {} }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3]": { - "recorded-date": "21-01-2025, 18:23:17", + "recorded-date": "21-02-2026, 00:30:04", "recorded-content": { "expired-exception": { "Error": { @@ -3513,7 +3438,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3v4]": { - "recorded-date": "21-01-2025, 18:23:23", + "recorded-date": "21-02-2026, 00:30:09", "recorded-content": { "expired-exception": { "Error": { @@ -3529,7 +3454,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-False]": { - "recorded-date": "21-01-2025, 18:24:08", + "recorded-date": "21-02-2026, 00:30:54", "recorded-content": { "expired": { "Error": { @@ -3544,7 +3469,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-True]": { - "recorded-date": "21-01-2025, 18:24:12", + "recorded-date": "21-02-2026, 00:30:58", "recorded-content": { "expired": { "Error": { @@ -3559,7 +3484,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-False]": { - "recorded-date": "21-01-2025, 18:24:16", + "recorded-date": "21-02-2026, 00:31:02", "recorded-content": { "expired": { "Error": { @@ -3575,7 +3500,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-True]": { - "recorded-date": "21-01-2025, 18:24:20", + "recorded-date": "21-02-2026, 00:31:06", "recorded-content": { "expired": { "Error": { @@ -3591,7 +3516,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-False]": { - "recorded-date": "21-01-2025, 18:24:24", + "recorded-date": "21-02-2026, 00:31:10", "recorded-content": { "invalid-get-1": { "Error": { @@ -3620,7 +3545,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-True]": { - "recorded-date": "21-01-2025, 18:24:28", + "recorded-date": "21-02-2026, 00:31:14", "recorded-content": { "invalid-get-1": { "Error": { @@ -3649,7 +3574,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-False]": { - "recorded-date": "21-01-2025, 18:24:31", + "recorded-date": "21-02-2026, 00:31:18", "recorded-content": { "invalid-get-1": { "Error": { @@ -3682,7 +3607,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-True]": { - "recorded-date": "21-01-2025, 18:24:35", + "recorded-date": "21-02-2026, 00:31:21", "recorded-content": { "invalid-get-1": { "Error": { @@ -3715,7 +3640,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3]": { - "recorded-date": "21-01-2025, 18:23:35", + "recorded-date": "21-02-2026, 00:30:22", "recorded-content": { "missing-param-exception": { "Error": { @@ -3729,7 +3654,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3v4]": { - "recorded-date": "21-01-2025, 18:23:37", + "recorded-date": "21-02-2026, 00:30:24", "recorded-content": { "missing-param-exception": { "Error": { @@ -3743,7 +3668,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3]": { - "recorded-date": "21-01-2025, 18:23:27", + "recorded-date": "21-02-2026, 00:30:14", "recorded-content": { "content-type-exception": { "Error": { @@ -3777,7 +3702,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3v4]": { - "recorded-date": "21-01-2025, 18:23:31", + "recorded-date": "21-02-2026, 00:30:17", "recorded-content": { "content-type-exception": { "Error": { @@ -3815,7 +3740,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_same_header_and_qs_parameter": { - "recorded-date": "21-01-2025, 18:23:33", + "recorded-date": "21-02-2026, 00:30:20", "recorded-content": { "double-header-query-string": { "Error": { @@ -3845,7 +3770,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_expires": { - "recorded-date": "17-03-2025, 20:16:24", + "recorded-date": "21-02-2026, 00:35:25", "recorded-content": { "exception": { "Error": { @@ -3859,7 +3784,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3]": { - "recorded-date": "17-03-2025, 20:16:26", + "recorded-date": "21-02-2026, 00:35:27", "recorded-content": { "exception-policy": { "Error": { @@ -3877,7 +3802,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3v4]": { - "recorded-date": "17-03-2025, 20:16:27", + "recorded-date": "21-02-2026, 00:35:28", "recorded-content": { "exception-policy": { "Error": { @@ -3895,7 +3820,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3]": { - "recorded-date": "17-03-2025, 20:16:29", + "recorded-date": "21-02-2026, 00:35:30", "recorded-content": { "exception-missing-signature": { "Error": { @@ -3911,7 +3836,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3v4]": { - "recorded-date": "17-03-2025, 20:16:30", + "recorded-date": "21-02-2026, 00:35:31", "recorded-content": { "exception-missing-signature": { "Error": { @@ -3927,7 +3852,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3]": { - "recorded-date": "17-03-2025, 20:16:32", + "recorded-date": "21-02-2026, 00:35:33", "recorded-content": { "exception-missing-fields": { "Error": { @@ -3952,7 +3877,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3v4]": { - "recorded-date": "17-03-2025, 20:16:34", + "recorded-date": "21-02-2026, 00:35:35", "recorded-content": { "exception-missing-fields": { "Error": { @@ -3977,7 +3902,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_validate_website_configuration": { - "recorded-date": "26-08-2023, 00:30:03", + "recorded-date": "21-02-2026, 00:33:06", "recorded-content": { "invalid-website-conf-0": { "Error": { @@ -4070,7 +3995,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_crud_website_configuration": { - "recorded-date": "26-08-2023, 00:29:24", + "recorded-date": "21-02-2026, 00:33:08", "recorded-content": { "get-no-such-website-config": { "Error": { @@ -4107,7 +4032,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_http_methods": { - "recorded-date": "26-08-2023, 00:30:55", + "recorded-date": "21-02-2026, 00:32:36", "recorded-content": { "not-allowed-post": { "content": "\n405 Method Not Allowed\n\n

405 Method Not Allowed

\n
    \n
  • Code: MethodNotAllowed
  • \n
  • Message: The specified method is not allowed against this resource.
  • \n
  • Method: POST
  • \n
  • ResourceType: OBJECT
  • \n
  • RequestId:
  • \n
  • HostId:
  • \n
\n
\n\n\n" @@ -4118,21 +4043,21 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_index_lookup": { - "recorded-date": "26-08-2023, 00:31:15", + "recorded-date": "21-02-2026, 00:32:40", "recorded-content": { "404-no-trailing-slash": "\n404 Not Found\n\n

404 Not Found

\n
    \n
  • Code: NoSuchKey
  • \n
  • Message: The specified key does not exist.
  • \n
  • Key: directory-wrong
  • \n
  • RequestId:
  • \n
  • HostId:
  • \n
\n
\n\n\n", "404-with-trailing-slash": "\n404 Not Found\n\n

404 Not Found

\n
    \n
  • Code: NoSuchKey
  • \n
  • Message: The specified key does not exist.
  • \n
  • Key: directory-wrong/index.html
  • \n
  • RequestId:
  • \n
  • HostId:
  • \n
\n
\n\n\n" } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_404": { - "recorded-date": "26-08-2023, 00:30:33", + "recorded-date": "21-02-2026, 00:32:43", "recorded-content": { "404-no-such-key": "\n404 Not Found\n\n

404 Not Found

\n
    \n
  • Code: NoSuchKey
  • \n
  • Message: The specified key does not exist.
  • \n
  • Key: index.html
  • \n
  • RequestId:
  • \n
  • HostId:
  • \n
\n
\n\n\n", "404-no-such-key-nor-custom": "\n404 Not Found\n\n

404 Not Found

\n
    \n
  • Code: NoSuchKey
  • \n
  • Message: The specified key does not exist.
  • \n
  • Key: index.html
  • \n
  • RequestId:
  • \n
  • HostId:
  • \n
\n

An Error Occurred While Attempting to Retrieve a Custom Error Document

\n
    \n
  • Code: NoSuchKey
  • \n
  • Message: The specified key does not exist.
  • \n
  • Key: error.html
  • \n
\n
\n\n\n" } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_no_such_website": { - "recorded-date": "26-08-2023, 00:31:30", + "recorded-date": "21-02-2026, 00:32:33", "recorded-content": { "no-such-bucket": "\n404 Not Found\n\n

404 Not Found

\n
    \n
  • Code: NoSuchBucket
  • \n
  • Message: The specified bucket does not exist
  • \n
  • BucketName:
  • \n
  • RequestId:
  • \n
  • HostId:
  • \n
\n
\n\n\n", "no-such-website-config": "\n404 Not Found\n\n

404 Not Found

\n
    \n
  • Code: NoSuchWebsiteConfiguration
  • \n
  • Message: The specified bucket does not have a website configuration
  • \n
  • BucketName:
  • \n
  • RequestId:
  • \n
  • HostId:
  • \n
\n
\n\n\n", @@ -4140,7 +4065,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_and_list_parts": { - "recorded-date": "17-03-2025, 21:28:50", + "recorded-date": "21-02-2026, 00:18:10", "recorded-content": { "create-multipart": { "Bucket": "bucket", @@ -4163,7 +4088,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 0, "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "PartNumberMarker": 0, @@ -4191,7 +4115,6 @@ }, "Key": "test-list-parts", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -4223,7 +4146,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "PartNumberMarker": 0, @@ -4259,7 +4181,6 @@ }, "Key": "test-list-parts", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -4342,7 +4263,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_content_language_disposition": { - "recorded-date": "21-01-2025, 18:26:29", + "recorded-date": "21-02-2026, 00:17:04", "recorded-content": { "put-object": { "ChecksumCRC32": "zwK7XA==", @@ -4417,7 +4338,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_kms": { - "recorded-date": "21-01-2025, 18:26:17", + "recorded-date": "21-02-2026, 00:16:52", "recorded-content": { "get-object": { "AcceptRanges": "bytes", @@ -4439,6 +4360,7 @@ "BucketKeyEnabled": true, "CopyObjectResult": { "ChecksumCRC32": "DUoRhQ==", + "ChecksumType": "FULL_OBJECT", "ETag": "copy-etag", "LastModified": "datetime" }, @@ -4470,7 +4392,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config": { - "recorded-date": "29-08-2024, 14:09:55", + "recorded-date": "21-02-2026, 00:35:17", "recorded-content": { "expected_error_no_replication_set": { "Error": { @@ -4549,7 +4471,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_quiet": { - "recorded-date": "21-01-2025, 18:31:30", + "recorded-date": "21-02-2026, 00:21:28", "recorded-content": { "deleted-resp": { "ResponseMetadata": { @@ -4560,7 +4482,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config_without_filter": { - "recorded-date": "03-08-2023, 04:13:02", + "recorded-date": "21-02-2026, 00:35:10", "recorded-content": { "expected_error_dest_does_not_exist": { "Error": { @@ -4627,12 +4549,13 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key": { - "recorded-date": "21-01-2025, 18:39:28", + "recorded-date": "21-02-2026, 00:27:56", "recorded-content": { "create-kms-key": { "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "", "Enabled": true, @@ -4720,7 +4643,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_complete_multipart_parts_order": { - "recorded-date": "17-03-2025, 21:30:44", + "recorded-date": "21-02-2026, 00:28:13", "recorded-content": { "upload-part-negative-part-number": { "Error": { @@ -4774,7 +4697,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD-True]": { - "recorded-date": "21-01-2025, 18:41:18", + "recorded-date": "21-02-2026, 00:28:15", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -4803,7 +4726,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD_IA-True]": { - "recorded-date": "21-01-2025, 18:41:20", + "recorded-date": "21-02-2026, 00:28:17", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -4833,7 +4756,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER-False]": { - "recorded-date": "21-01-2025, 18:41:22", + "recorded-date": "21-02-2026, 00:28:19", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -4858,7 +4781,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER_IR-True]": { - "recorded-date": "21-01-2025, 18:41:24", + "recorded-date": "21-02-2026, 00:28:21", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -4888,7 +4811,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[ONEZONE_IA-True]": { - "recorded-date": "21-01-2025, 18:41:28", + "recorded-date": "21-02-2026, 00:28:25", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -4918,7 +4841,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[INTELLIGENT_TIERING-True]": { - "recorded-date": "21-01-2025, 18:41:30", + "recorded-date": "21-02-2026, 00:28:27", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -4948,7 +4871,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[DEEP_ARCHIVE-False]": { - "recorded-date": "21-01-2025, 18:41:32", + "recorded-date": "21-02-2026, 00:28:29", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -4973,7 +4896,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[REDUCED_REDUNDANCY-True]": { - "recorded-date": "21-01-2025, 18:41:26", + "recorded-date": "21-02-2026, 00:28:23", "recorded-content": { "get-object-storage-class": { "LastModified": "datetime", @@ -5003,7 +4926,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class_outposts": { - "recorded-date": "21-01-2025, 18:41:34", + "recorded-date": "21-02-2026, 00:28:31", "recorded-content": { "put-object-outposts": { "Error": { @@ -5030,7 +4953,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA256]": { - "recorded-date": "17-03-2025, 18:28:54", + "recorded-date": "21-02-2026, 00:38:16", "recorded-content": { "put-object": { "ChecksumSHA256": "1YQo81vx2VFUl0q5ccWISq8AkSBQQ0WO80S82TmfdIQ=", @@ -5104,7 +5027,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[None]": { - "recorded-date": "17-03-2025, 18:29:00", + "recorded-date": "21-02-2026, 00:38:22", "recorded-content": { "put-object": { "ChecksumCRC32": "lVk/nw==", @@ -5178,7 +5101,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_with_content_encoding": { - "recorded-date": "17-03-2025, 18:29:02", + "recorded-date": "21-02-2026, 00:38:24", "recorded-content": { "put-object": { "ChecksumSHA256": "WO7lLNG8Mn/d4GkX4DqZXqeaVHJCN+BxvMNJXLOhukg=", @@ -5238,12 +5161,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_composite": { - "recorded-date": "15-06-2025, 17:11:24", + "recorded-date": "21-02-2026, 00:40:43", "recorded-content": { "create-mpu-wrong-checksum-algo": { "Error": { "Code": "InvalidRequest", - "Message": "Checksum algorithm provided is unsupported. Please try again with any of the valid types: [CRC32, CRC32C, SHA1, SHA256]" + "Message": "Checksum algorithm provided is unsupported. Please try again with any of the valid types: [CRC32, CRC32C, CRC64NVME, SHA1, SHA256]" }, "ResponseMetadata": { "HTTPHeaders": {}, @@ -5291,7 +5214,6 @@ }, "Key": "test-multipart-checksum-exc", "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "StorageClass": "STANDARD", @@ -5357,12 +5279,13 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key_state": { - "recorded-date": "21-01-2025, 18:39:38", + "recorded-date": "21-02-2026, 00:28:06", "recorded-content": { "create-kms-key": { "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "", "Enabled": true, @@ -5380,7 +5303,7 @@ "success-put-object-sse": { "ChecksumCRC32": "KHcEKQ==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"e1ae6a8d27c2b77e7dcd9b4c8a3b579d\"", + "ETag": "\"82d933006b334fd100179a6307217cce\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -5395,7 +5318,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"e1ae6a8d27c2b77e7dcd9b4c8a3b579d\"", + "ETag": "\"82d933006b334fd100179a6307217cce\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -5438,7 +5361,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_keys_in_versioned_bucket": { - "recorded-date": "21-01-2025, 18:31:34", + "recorded-date": "21-02-2026, 00:21:33", "recorded-content": { "list-objects-v2": { "Contents": [ @@ -5485,7 +5408,6 @@ "Key": "test-key-versioned", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -5509,7 +5431,6 @@ "Key": "test-key-versioned", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 12, @@ -5526,7 +5447,6 @@ "Key": "test-key-versioned", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -5543,11 +5463,11 @@ "Deleted": [ { "Key": "test-key-versioned", - "VersionId": "" + "VersionId": "" }, { "Key": "test-key-versioned", - "VersionId": "" + "VersionId": "" } ], "ResponseMetadata": { @@ -5574,7 +5494,6 @@ "Key": "test-key-versioned", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -5609,7 +5528,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_acl_on_delete_marker": { - "recorded-date": "21-01-2025, 18:31:40", + "recorded-date": "21-02-2026, 00:21:38", "recorded-content": { "put-obj-1": { "ChecksumCRC32": "Cdox+w==", @@ -5691,7 +5610,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_versioned": { - "recorded-date": "21-01-2025, 18:27:33", + "recorded-date": "21-02-2026, 00:18:07", "recorded-content": { "put-obj-v1": { "ChecksumCRC32": "WC+ANw==", @@ -5884,7 +5803,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_sse": { - "recorded-date": "17-03-2025, 21:31:09", + "recorded-date": "21-02-2026, 00:28:49", "recorded-content": { "multi-sse-create-multipart": { "Bucket": "", @@ -5901,7 +5820,7 @@ "multi-sse-upload-part": { "BucketKeyEnabled": true, "ChecksumCRC32": "KHcEKQ==", - "ETag": "\"f14c3faa9237b95312866412ecf80f93\"", + "ETag": "\"1a9f0234d9c670a8c2f8b1ceda267641\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -5912,7 +5831,7 @@ "multi-sse-compete-multipart": { "Bucket": "", "BucketKeyEnabled": true, - "ETag": "\"a35f284c9ce41d60640bd70f8069a276-1\"", + "ETag": "\"93a0cb8ec20934211e19adabef9a6407-1\"", "Key": "test-sse-field-multipart", "Location": "", "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -5930,7 +5849,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"a35f284c9ce41d60640bd70f8069a276-1\"", + "ETag": "\"93a0cb8ec20934211e19adabef9a6407-1\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -5943,7 +5862,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_bucket_key_default": { - "recorded-date": "21-01-2025, 18:42:37", + "recorded-date": "21-02-2026, 00:28:53", "recorded-content": { "put-obj-default-before-setting": { "ChecksumCRC32": "KHcEKQ==", @@ -6014,7 +5933,7 @@ "BucketKeyEnabled": true, "ChecksumCRC32": "KHcEKQ==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"7ebdc638d81c4fcc1479f682db3c21d3\"", + "ETag": "\"69a4214580de172e3b6dbfaf0b1aaf4a\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -6030,7 +5949,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"7ebdc638d81c4fcc1479f682db3c21d3\"", + "ETag": "\"69a4214580de172e3b6dbfaf0b1aaf4a\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -6043,10 +5962,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_default_kms_key": { - "recorded-date": "03-04-2023, 22:16:19", + "recorded-date": "21-02-2026, 00:43:37", "recorded-content": { "put-obj-default-kms-s3-key": { - "ETag": "\"dbcc38f7b88c4c92ee5f9484d181ff51\"", + "ChecksumCRC32": "KHcEKQ==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"8c4f00a5f79a9d26aaf8462d148bd5bf\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -6057,9 +5978,11 @@ "get-obj-default-kms-s3-key": { "AcceptRanges": "bytes", "Body": "test-sse", + "ChecksumCRC32": "KHcEKQ==", + "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"dbcc38f7b88c4c92ee5f9484d181ff51\"", + "ETag": "\"8c4f00a5f79a9d26aaf8462d148bd5bf\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -6070,7 +5993,9 @@ } }, "put-obj-default-kms-s3-key-bucket-2": { - "ETag": "\"9c8f3cc18cd06e60966725b1c5996554\"", + "ChecksumCRC32": "KHcEKQ==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"59252a09a794c20170ced454936d6299\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -6081,9 +6006,11 @@ "get-obj-default-kms-s3-key-bucket-2": { "AcceptRanges": "bytes", "Body": "test-sse", + "ChecksumCRC32": "KHcEKQ==", + "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"9c8f3cc18cd06e60966725b1c5996554\"", + "ETag": "\"59252a09a794c20170ced454936d6299\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -6117,7 +6044,9 @@ }, "put-obj-default-kms-s3-key-from-bucket": { "BucketKeyEnabled": true, - "ETag": "\"671ef6aeba0f69c2391cf0a1b094d0aa\"", + "ChecksumCRC32": "KHcEKQ==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"8a33ac88b9e72b00c035aac8388183f5\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -6129,9 +6058,11 @@ "AcceptRanges": "bytes", "Body": "test-sse", "BucketKeyEnabled": true, + "ChecksumCRC32": "KHcEKQ==", + "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"671ef6aeba0f69c2391cf0a1b094d0aa\"", + "ETag": "\"8a33ac88b9e72b00c035aac8388183f5\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -6144,7 +6075,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place": { - "recorded-date": "21-01-2025, 18:29:17", + "recorded-date": "21-02-2026, 00:19:24", "recorded-content": { "put_object": { "ChecksumCRC32": "MzVIGw==", @@ -6192,6 +6123,7 @@ "copy-object-in-place-with-storage-class": { "CopyObjectResult": { "ChecksumSHA256": "lyTB4g5uPk1/V+0l+dTvsAblCFkNUoyQ2ll/andcE+U=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -6213,7 +6145,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -6221,7 +6152,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -6242,7 +6172,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_metadata_directive": { - "recorded-date": "21-01-2025, 18:29:37", + "recorded-date": "21-02-2026, 00:19:45", "recorded-content": { "put_object": { "ChecksumCRC32": "MzVIGw==", @@ -6282,6 +6212,7 @@ "copy-replace-directive": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -6309,6 +6240,7 @@ "copy-copy-directive": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -6336,6 +6268,7 @@ "copy-copy-directive-ignore": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -6363,6 +6296,7 @@ "copy-replace-directive-empty": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -6388,7 +6322,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_storage_class": { - "recorded-date": "21-01-2025, 18:29:28", + "recorded-date": "21-02-2026, 00:19:36", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -6424,6 +6358,7 @@ "copy-object-in-place-with-storage-class": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -6444,13 +6379,13 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_with_encryption": { - "recorded-date": "21-01-2025, 18:29:32", + "recorded-date": "21-02-2026, 00:19:40", "recorded-content": { "put-object-with-kms-encryption": { "BucketKeyEnabled": true, "ChecksumCRC32": "2H9+DA==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"526373ef70063d48e68f588bbdfec7ef\"", + "ETag": "\"b00b3bfc95beb7094e6a43eaa2ca08ab\"", "SSEKMSKeyId": "", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -6463,7 +6398,7 @@ "BucketKeyEnabled": true, "ContentLength": 4, "ContentType": "binary/octet-stream", - "ETag": "\"526373ef70063d48e68f588bbdfec7ef\"", + "ETag": "\"b00b3bfc95beb7094e6a43eaa2ca08ab\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "", @@ -6476,7 +6411,8 @@ "copy-object-in-place-with-sse": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", - "ETag": "\"d83cbdabd3c2ff281f17810fd677232c\"", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"a92b4ea5fde51dc81ab84292dfee0367\"", "LastModified": "datetime" }, "SSEKMSKeyId": "", @@ -6490,7 +6426,7 @@ "AcceptRanges": "bytes", "ContentLength": 4, "ContentType": "binary/octet-stream", - "ETag": "\"d83cbdabd3c2ff281f17810fd677232c\"", + "ETag": "\"a92b4ea5fde51dc81ab84292dfee0367\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "", @@ -6503,6 +6439,7 @@ "copy-object-in-place-without-kms-sse": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -6528,6 +6465,7 @@ "copy-object-in-place-with-aes": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -6553,7 +6491,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_storage_class": { - "recorded-date": "21-01-2025, 18:29:41", + "recorded-date": "21-02-2026, 00:19:50", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -6590,6 +6528,7 @@ "copy-object-in-place-with-storage-class": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -6620,7 +6559,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_directive_copy": { - "recorded-date": "21-01-2025, 18:28:52", + "recorded-date": "21-02-2026, 00:18:59", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -6651,6 +6590,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -6679,7 +6619,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_website_redirect_location": { - "recorded-date": "21-01-2025, 18:29:39", + "recorded-date": "21-02-2026, 00:19:47", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -6708,6 +6648,7 @@ "copy-object-in-place-with-website-redirection": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -6734,7 +6675,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_copy_in_place_with_bucket_encryption": { - "recorded-date": "21-01-2025, 18:29:34", + "recorded-date": "21-02-2026, 00:19:42", "recorded-content": { "put-bucket-encryption": { "ResponseMetadata": { @@ -6755,6 +6696,7 @@ "copy-obj": { "CopyObjectResult": { "ChecksumCRC32": "AAAAAA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", "LastModified": "datetime" }, @@ -6767,7 +6709,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32]": { - "recorded-date": "17-03-2025, 18:28:46", + "recorded-date": "21-02-2026, 00:38:07", "recorded-content": { "put-object": { "ChecksumCRC32": "lVk/nw==", @@ -6841,7 +6783,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32C]": { - "recorded-date": "17-03-2025, 18:28:48", + "recorded-date": "21-02-2026, 00:38:10", "recorded-content": { "put-object": { "ChecksumCRC32C": "Fz3epA==", @@ -6915,7 +6857,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA1]": { - "recorded-date": "17-03-2025, 18:28:51", + "recorded-date": "21-02-2026, 00:38:13", "recorded-content": { "put-object": { "ChecksumSHA1": "jbXkHAsXUrubtL3dqDQ4w+7WXc0=", @@ -6989,7 +6931,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_analytics_configurations": { - "recorded-date": "21-01-2025, 18:42:41", + "recorded-date": "21-02-2026, 00:28:57", "recorded-content": { "put_config_with_storage_analysis_err": { "Error": { @@ -7174,7 +7116,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_intelligent_tier_config": { - "recorded-date": "21-01-2025, 19:48:46", + "recorded-date": "21-02-2026, 00:29:00", "recorded-content": { "put_bucket_intelligent_tiering_configuration_err_1`": { "Error": { @@ -7327,7 +7269,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_no_such_upload": { - "recorded-date": "21-01-2025, 18:27:38", + "recorded-date": "21-02-2026, 00:18:12", "recorded-content": { "upload-exc": { "Error": { @@ -7365,7 +7307,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32]": { - "recorded-date": "24-01-2025, 19:06:29", + "recorded-date": "21-02-2026, 00:19:52", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC32": "MzVIGw==", @@ -7391,6 +7333,7 @@ "copy-object-in-place-with-checksum": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7414,6 +7357,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7426,7 +7370,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32C]": { - "recorded-date": "24-01-2025, 19:06:31", + "recorded-date": "21-02-2026, 00:19:55", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC32": "MzVIGw==", @@ -7452,6 +7396,7 @@ "copy-object-in-place-with-checksum": { "CopyObjectResult": { "ChecksumCRC32C": "078Ilw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7475,6 +7420,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumCRC32C": "078Ilw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7487,7 +7433,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA1]": { - "recorded-date": "24-01-2025, 19:06:34", + "recorded-date": "21-02-2026, 00:19:57", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC32": "MzVIGw==", @@ -7513,6 +7459,7 @@ "copy-object-in-place-with-checksum": { "CopyObjectResult": { "ChecksumSHA1": "5zXdjmjYk4EJ8Cw4PMnQVslCpRQ=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7536,6 +7483,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumSHA1": "5zXdjmjYk4EJ8Cw4PMnQVslCpRQ=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7548,7 +7496,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA256]": { - "recorded-date": "24-01-2025, 19:06:36", + "recorded-date": "21-02-2026, 00:19:59", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC32": "MzVIGw==", @@ -7574,6 +7522,7 @@ "copy-object-in-place-with-checksum": { "CopyObjectResult": { "ChecksumSHA256": "lyTB4g5uPk1/V+0l+dTvsAblCFkNUoyQ2ll/andcE+U=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7597,6 +7546,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumSHA256": "lyTB4g5uPk1/V+0l+dTvsAblCFkNUoyQ2ll/andcE+U=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -7609,7 +7559,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[file%2Fname]": { - "recorded-date": "21-01-2025, 18:26:42", + "recorded-date": "21-02-2026, 00:17:23", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -7671,7 +7621,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test@key/]": { - "recorded-date": "21-01-2025, 18:26:44", + "recorded-date": "21-02-2026, 00:17:25", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -7733,7 +7683,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123]": { - "recorded-date": "21-01-2025, 18:26:46", + "recorded-date": "21-02-2026, 00:17:27", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -7795,7 +7745,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%percent]": { - "recorded-date": "21-01-2025, 18:26:48", + "recorded-date": "21-02-2026, 00:17:29", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -7857,7 +7807,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key/]": { - "recorded-date": "21-01-2025, 18:26:50", + "recorded-date": "21-02-2026, 00:17:31", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -7919,7 +7869,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key//]": { - "recorded-date": "21-01-2025, 18:26:52", + "recorded-date": "21-02-2026, 00:17:33", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -7981,7 +7931,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123/]": { - "recorded-date": "21-01-2025, 18:26:54", + "recorded-date": "21-02-2026, 00:17:35", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -8043,7 +7993,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[a/%F0%9F%98%80/]": { - "recorded-date": "21-01-2025, 18:26:56", + "recorded-date": "21-02-2026, 00:17:37", "recorded-content": { "put-object-special-char": { "ChecksumCRC32": "2H9+DA==", @@ -8105,7 +8055,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_headers": { - "recorded-date": "21-01-2025, 18:42:49", + "recorded-date": "21-02-2026, 00:29:04", "recorded-content": { "if_none_match_err_1": { "Code": "304", @@ -8125,105 +8075,8 @@ } } }, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_preconditions": { - "recorded-date": "21-01-2025, 18:29:56", - "recorded-content": { - "head-object": { - "AcceptRanges": "bytes", - "ContentLength": 4, - "ContentType": "binary/octet-stream", - "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", - "LastModified": "datetime", - "Metadata": {}, - "ServerSideEncryption": "AES256", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "copy-precondition-if-match": { - "Error": { - "Code": "PreconditionFailed", - "Condition": "x-amz-copy-source-If-Match", - "Message": "At least one of the pre-conditions you specified did not hold" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 412 - } - }, - "copy-precondition-if-unmodified-since": { - "Error": { - "Code": "PreconditionFailed", - "Condition": "x-amz-copy-source-If-Unmodified-Since", - "Message": "At least one of the pre-conditions you specified did not hold" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 412 - } - }, - "copy-precondition-if-none-match": { - "Error": { - "Code": "PreconditionFailed", - "Condition": "x-amz-copy-source-If-None-Match", - "Message": "At least one of the pre-conditions you specified did not hold" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 412 - } - }, - "copy-precondition-if-modified-since": { - "Error": { - "Code": "PreconditionFailed", - "Condition": "x-amz-copy-source-If-Modified-Since", - "Message": "At least one of the pre-conditions you specified did not hold" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 412 - } - }, - "copy-ignore-future-modified-since": { - "CopyObjectResult": { - "ChecksumCRC32": "rfPzYw==", - "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", - "LastModified": "datetime" - }, - "ServerSideEncryption": "AES256", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "copy-etag-missing-quotes": { - "Error": { - "Code": "PreconditionFailed", - "Condition": "x-amz-copy-source-If-None-Match", - "Message": "At least one of the pre-conditions you specified did not hold" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 412 - } - }, - "copy-success": { - "CopyObjectResult": { - "ChecksumCRC32": "rfPzYw==", - "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", - "LastModified": "datetime" - }, - "ServerSideEncryption": "AES256", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging": { - "recorded-date": "12-08-2023, 19:54:07", + "recorded-date": "21-02-2026, 00:34:53", "recorded-content": { "get-bucket-logging-default": { "ResponseMetadata": { @@ -8235,7 +8088,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "display-name", "ID": "owner-id", "Type": "CanonicalUser" }, @@ -8243,7 +8095,6 @@ } ], "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "ResponseMetadata": { @@ -8276,7 +8127,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_accept_wrong_grants": { - "recorded-date": "03-08-2023, 04:26:11", + "recorded-date": "21-02-2026, 00:34:57", "recorded-content": { "put-bucket-logging": { "ResponseMetadata": { @@ -8313,7 +8164,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_wrong_target": { - "recorded-date": "30-08-2024, 11:31:48", + "recorded-date": "21-02-2026, 00:35:00", "recorded-content": { "put-bucket-logging-different-regions": { "Error": { @@ -8340,7 +8191,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry_versioned": { - "recorded-date": "21-01-2025, 18:18:31", + "recorded-date": "21-02-2026, 00:33:29", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -8485,7 +8336,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_multiple_rules": { - "recorded-date": "21-01-2025, 18:18:36", + "recorded-date": "21-02-2026, 00:33:34", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -8555,7 +8406,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_put_bucket_lifecycle_conf_exc": { - "recorded-date": "21-01-2025, 18:18:24", + "recorded-date": "21-02-2026, 00:33:22", "recorded-content": { "missing-id": { "Error": { @@ -8642,7 +8493,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_date": { - "recorded-date": "21-01-2025, 18:18:26", + "recorded-date": "21-02-2026, 00:33:24", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -8664,7 +8515,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_object_size_rules": { - "recorded-date": "21-01-2025, 18:18:38", + "recorded-date": "21-02-2026, 00:33:37", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -8726,7 +8577,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_tag_rules": { - "recorded-date": "21-01-2025, 18:18:42", + "recorded-date": "21-02-2026, 00:33:41", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -8881,7 +8732,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_object_expiry_after_bucket_lifecycle_configuration": { - "recorded-date": "21-01-2025, 18:18:33", + "recorded-date": "21-02-2026, 00:33:32", "recorded-content": { "put-object-before": { "ChecksumCRC32": "2H9+DA==", @@ -8950,7 +8801,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_inventory_report_crud": { - "recorded-date": "21-01-2025, 18:42:52", + "recorded-date": "21-02-2026, 00:29:07", "recorded-content": { "put-inventory-config": { "ResponseMetadata": { @@ -9035,7 +8886,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_inventory_report_exceptions": { - "recorded-date": "21-01-2025, 18:42:57", + "recorded-date": "21-02-2026, 00:29:11", "recorded-content": { "wrong-id": { "Error": { @@ -9100,7 +8951,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_inventory_config_order": { - "recorded-date": "21-01-2025, 18:43:00", + "recorded-date": "21-02-2026, 00:29:15", "recorded-content": { "list-inventory-config": { "InventoryConfigurationList": [ @@ -9199,7 +9050,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_lifecycle_expired_object_delete_marker": { - "recorded-date": "21-01-2025, 18:18:44", + "recorded-date": "21-02-2026, 00:33:43", "recorded-content": { "get-bucket-lifecycle-conf": { "Rules": [ @@ -9242,7 +9093,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_too_small": { - "recorded-date": "21-01-2025, 18:27:40", + "recorded-date": "21-02-2026, 00:18:14", "recorded-content": { "upload-part1": { "ChecksumCRC32": "rfPzYw==", @@ -9289,7 +9140,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_wrong_part": { - "recorded-date": "21-01-2025, 18:27:42", + "recorded-date": "21-02-2026, 00:18:17", "recorded-content": { "complete-exc-wrong-part-number": { "Error": { @@ -9320,7 +9171,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[COPY]": { - "recorded-date": "21-01-2025, 18:28:54", + "recorded-date": "21-02-2026, 00:19:02", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -9347,6 +9198,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -9371,6 +9223,7 @@ "copy-object-tag-empty": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -9395,7 +9248,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[REPLACE]": { - "recorded-date": "21-01-2025, 18:28:57", + "recorded-date": "21-02-2026, 00:19:04", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -9422,6 +9275,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -9446,6 +9300,7 @@ "copy-object-tag-empty": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -9465,7 +9320,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[None]": { - "recorded-date": "21-01-2025, 18:28:59", + "recorded-date": "21-02-2026, 00:19:06", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -9492,6 +9347,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -9516,6 +9372,7 @@ "copy-object-tag-empty": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -9540,7 +9397,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[True]": { - "recorded-date": "21-01-2025, 18:43:02", + "recorded-date": "21-02-2026, 00:29:17", "recorded-content": { "get-obj-content-len-headers": { "accept-ranges": "bytes", @@ -9557,7 +9414,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[False]": { - "recorded-date": "21-01-2025, 18:43:04", + "recorded-date": "21-02-2026, 00:29:19", "recorded-content": { "get-obj-content-len-headers": { "accept-ranges": "bytes", @@ -9574,7 +9431,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_exc": { - "recorded-date": "20-06-2025, 16:29:06", + "recorded-date": "21-02-2026, 00:33:51", "recorded-content": { "put-object-retention-no-bucket": { "Error": { @@ -9673,7 +9530,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_copy_object_retention_lock": { - "recorded-date": "21-01-2025, 18:18:00", + "recorded-date": "21-02-2026, 00:34:08", "recorded-content": { "put-source-object": { "ChecksumCRC32": "MzVIGw==", @@ -9705,6 +9562,7 @@ "copy-lock": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -9733,7 +9591,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention": { - "recorded-date": "20-06-2025, 17:02:01", + "recorded-date": "21-02-2026, 00:34:06", "recorded-content": { "put-obj-locked-1": { "ChecksumCRC32": "2H9+DA==", @@ -9831,7 +9689,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_bucket_config_default_retention": { - "recorded-date": "20-06-2025, 17:35:52", + "recorded-date": "21-02-2026, 00:34:11", "recorded-content": { "put-lock-config": { "ResponseMetadata": { @@ -9907,7 +9765,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_delete_markers": { - "recorded-date": "21-01-2025, 18:18:05", + "recorded-date": "21-02-2026, 00:34:13", "recorded-content": { "put-object-with-lock": { "ChecksumCRC32": "l5fLBg==", @@ -9981,7 +9839,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_extend_duration": { - "recorded-date": "21-01-2025, 18:18:07", + "recorded-date": "21-02-2026, 00:34:15", "recorded-content": { "put-object-with-lock": { "ChecksumCRC32": "l5fLBg==", @@ -10045,7 +9903,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_get_object_legal_hold": { - "recorded-date": "21-01-2025, 18:17:06", + "recorded-date": "21-02-2026, 00:34:34", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -10107,7 +9965,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_legal_hold_exc": { - "recorded-date": "21-01-2025, 18:17:12", + "recorded-date": "21-02-2026, 00:34:40", "recorded-content": { "put-object-legal-hold-no-bucket": { "Error": { @@ -10164,7 +10022,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_delete_locked_object": { - "recorded-date": "21-01-2025, 18:17:15", + "recorded-date": "21-02-2026, 00:34:43", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -10216,7 +10074,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_legal_hold_lock_versioned": { - "recorded-date": "21-01-2025, 18:17:18", + "recorded-date": "21-02-2026, 00:34:47", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -10313,7 +10171,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_copy_object_legal_hold": { - "recorded-date": "21-01-2025, 18:17:21", + "recorded-date": "21-02-2026, 00:34:50", "recorded-content": { "put-object": { "ChecksumCRC32": "MzVIGw==", @@ -10344,6 +10202,7 @@ "copy-legal-hold": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -10372,7 +10231,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_with_legal_hold": { - "recorded-date": "21-01-2025, 18:17:08", + "recorded-date": "21-02-2026, 00:34:36", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -10409,7 +10268,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[get_object]": { - "recorded-date": "21-01-2025, 18:30:04", + "recorded-date": "21-02-2026, 00:20:22", "recorded-content": { "precondition-if-match": { "Error": { @@ -10524,7 +10383,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[head_object]": { - "recorded-date": "21-01-2025, 18:30:10", + "recorded-date": "21-02-2026, 00:20:28", "recorded-content": { "precondition-if-match": { "Error": { @@ -10628,7 +10487,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part": { - "recorded-date": "07-07-2025, 17:56:14", + "recorded-date": "21-02-2026, 00:22:58", "recorded-content": { "multipart-upload": { "Bucket": "", @@ -10744,7 +10603,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[single]": { - "recorded-date": "17-03-2025, 20:16:38", + "recorded-date": "21-02-2026, 00:35:40", "recorded-content": { "get-tagging": { "TagSet": [ @@ -10761,7 +10620,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[list]": { - "recorded-date": "17-03-2025, 20:16:39", + "recorded-date": "21-02-2026, 00:35:43", "recorded-content": { "get-tagging": { "TagSet": [ @@ -10782,7 +10641,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[invalid]": { - "recorded-date": "17-03-2025, 20:16:41", + "recorded-date": "21-02-2026, 00:35:44", "recorded-content": { "get-tagging": { "TagSet": [], @@ -10794,7 +10653,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[notxml]": { - "recorded-date": "17-03-2025, 20:16:43", + "recorded-date": "21-02-2026, 00:35:46", "recorded-content": { "tagging-error": { "Error": { @@ -10807,7 +10666,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_metadata": { - "recorded-date": "17-03-2025, 21:56:03", + "recorded-date": "21-02-2026, 00:35:48", "recorded-content": { "head-object": { "AcceptRanges": "bytes", @@ -10830,7 +10689,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_storage_class": { - "recorded-date": "17-03-2025, 20:16:47", + "recorded-date": "21-02-2026, 00:35:52", "recorded-content": { "head-object": { "AcceptRanges": "bytes", @@ -10858,7 +10717,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl": { - "recorded-date": "21-01-2025, 18:30:26", + "recorded-date": "21-02-2026, 00:20:45", "recorded-content": { "put-object-default-acl": { "ChecksumCRC32": "AAAAAA==", @@ -10874,7 +10733,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -10882,7 +10740,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -10900,7 +10757,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -10915,7 +10771,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -10934,7 +10789,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -10946,7 +10800,6 @@ "Grants": [ { "Grantee": { - "DisplayName": "", "ID": "", "Type": "CanonicalUser" }, @@ -10961,7 +10814,6 @@ } ], "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -10972,7 +10824,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl_exceptions": { - "recorded-date": "21-01-2025, 18:30:32", + "recorded-date": "21-02-2026, 00:20:51", "recorded-content": { "put-object-canned-acl": { "Error": { @@ -11144,7 +10996,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_empty_bucket_fixture": { - "recorded-date": "21-01-2025, 18:43:07", + "recorded-date": "21-02-2026, 00:29:22", "recorded-content": { "list-obj": { "Contents": [ @@ -11208,7 +11060,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_operation_between_regions": { - "recorded-date": "21-01-2025, 18:30:52", + "recorded-date": "21-02-2026, 00:21:08", "recorded-content": { "put-website-config-region-1": { "ResponseMetadata": { @@ -11250,7 +11102,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_overwrite_key": { - "recorded-date": "17-03-2025, 21:29:05", + "recorded-date": "21-02-2026, 00:22:51", "recorded-content": { "put-object": { "ChecksumCRC32": "B18g1g==", @@ -11278,7 +11130,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_objects_encoding": { - "recorded-date": "21-01-2025, 18:31:37", + "recorded-date": "21-02-2026, 00:21:36", "recorded-content": { "list-objects-before-delete": { "Contents": [ @@ -11472,7 +11324,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[True]": { - "recorded-date": "21-01-2025, 18:27:06", + "recorded-date": "21-02-2026, 00:17:52", "recorded-content": { "list-object-encoded-char": { "Contents": [ @@ -11524,7 +11376,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[False]": { - "recorded-date": "21-01-2025, 18:27:09", + "recorded-date": "21-02-2026, 00:17:56", "recorded-content": { "list-object-encoded-char": { "Contents": [ @@ -11641,7 +11493,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[True]": { - "recorded-date": "21-01-2025, 18:22:52", + "recorded-date": "21-02-2026, 00:29:33", "recorded-content": { "no-meta-headers": { "Error": { @@ -11684,7 +11536,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[False]": { - "recorded-date": "21-01-2025, 18:22:55", + "recorded-date": "21-02-2026, 00:29:36", "recorded-content": { "no-meta-headers": { "Error": { @@ -11727,7 +11579,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[True]": { - "recorded-date": "21-01-2025, 18:22:57", + "recorded-date": "21-02-2026, 00:29:39", "recorded-content": { "head_object": { "AcceptRanges": "bytes", @@ -11759,7 +11611,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[False]": { - "recorded-date": "21-01-2025, 18:22:59", + "recorded-date": "21-02-2026, 00:29:41", "recorded-content": { "head_object": { "AcceptRanges": "bytes", @@ -11791,7 +11643,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_x_amz_in_qs": { - "recorded-date": "17-03-2025, 21:32:11", + "recorded-date": "21-02-2026, 00:31:46", "recorded-content": { "head-object": { "AcceptRanges": "bytes", @@ -11813,7 +11665,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[True]": { - "recorded-date": "21-01-2025, 18:26:32", + "recorded-date": "21-02-2026, 00:17:13", "recorded-content": { "list-objects-slashes": { "Contents": [ @@ -11865,7 +11717,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[False]": { - "recorded-date": "21-01-2025, 18:26:36", + "recorded-date": "21-02-2026, 00:17:16", "recorded-content": { "list-objects-slashes": { "Contents": [ @@ -11917,7 +11769,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_cross_locations": { - "recorded-date": "29-08-2024, 15:58:14", + "recorded-date": "21-02-2026, 00:35:04", "recorded-content": { "put-bucket-logging-cross-us-east-1": { "Error": { @@ -11945,7 +11797,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character": { - "recorded-date": "21-01-2025, 18:27:00", + "recorded-date": "21-02-2026, 00:17:46", "recorded-content": { "put-object-src-special-char-file%2Fname": { "ChecksumCRC32": "2H9+DA==", @@ -11960,6 +11812,7 @@ "copy-object-special-char-file%2Fname": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -11982,6 +11835,7 @@ "copy-object-special-char-test@key/": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -12004,6 +11858,7 @@ "copy-object-special-char-test key/": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -12026,6 +11881,7 @@ "copy-object-special-char-test key//": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -12048,6 +11904,7 @@ "copy-object-special-char-a/%F0%9F%98%80/": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -12070,6 +11927,7 @@ "copy-object-special-char-test+key": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -12162,7 +12020,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_wrong_content_type": { - "recorded-date": "17-03-2025, 20:16:49", + "recorded-date": "21-02-2026, 00:35:54", "recorded-content": { "invalid-content-type-error": { "Error": { @@ -12176,7 +12034,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_file_as_string": { - "recorded-date": "17-03-2025, 20:16:51", + "recorded-date": "21-02-2026, 00:35:58", "recorded-content": { "head-object": { "AcceptRanges": "bytes", @@ -12230,7 +12088,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_wrong_format": { - "recorded-date": "21-01-2025, 18:29:58", + "recorded-date": "21-02-2026, 00:20:16", "recorded-content": { "copy-object-wrong-copy-source": { "Error": { @@ -12247,7 +12105,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_versioned": { - "recorded-date": "21-01-2025, 18:29:22", + "recorded-date": "21-02-2026, 00:19:29", "recorded-content": { "put_object": { "ChecksumCRC32": "MzVIGw==", @@ -12298,6 +12156,7 @@ "copy-in-place-versioned": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -12345,6 +12204,7 @@ "copy-in-place-versioned-suspended": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -12406,6 +12266,7 @@ "copy-in-place-versioned-re-enabled": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -12419,7 +12280,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": { - "recorded-date": "17-03-2025, 20:17:02", + "recorded-date": "21-02-2026, 00:36:08", "recorded-content": { "invalid-content-length-too-big": { "Error": { @@ -12468,7 +12329,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": { - "recorded-date": "17-03-2025, 20:16:55", + "recorded-date": "21-02-2026, 00:36:02", "recorded-content": { "invalid-condition-eq": { "Error": { @@ -12539,7 +12400,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": { - "recorded-date": "17-03-2025, 20:16:58", + "recorded-date": "21-02-2026, 00:36:05", "recorded-content": { "invalid-condition-starts-with": { "Error": { @@ -12592,7 +12453,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_presigned_post_with_different_user_credentials": { - "recorded-date": "17-03-2025, 20:17:16", + "recorded-date": "21-02-2026, 00:36:22", "recorded-content": { "get-obj": { "AcceptRanges": "bytes", @@ -12613,7 +12474,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_double_encoded_credentials": { - "recorded-date": "21-01-2025, 18:23:03", + "recorded-date": "21-02-2026, 00:29:49", "recorded-content": { "error-malformed": { "Error": { @@ -12626,7 +12487,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_suspended_only": { - "recorded-date": "21-01-2025, 18:29:26", + "recorded-date": "21-02-2026, 00:19:34", "recorded-content": { "put_object": { "ChecksumCRC32": "MzVIGw==", @@ -12656,6 +12517,7 @@ "copy-in-place-non-versioned": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -12699,6 +12561,7 @@ "copy-in-place-versioned-suspended": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -12745,6 +12608,7 @@ "copy-in-place-versioned-suspended-twice": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -12758,7 +12622,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_with_space": { - "recorded-date": "21-01-2025, 18:27:30", + "recorded-date": "21-02-2026, 00:18:04", "recorded-content": { "get-attrs-with-whitespace": { "GetObjectAttributesResponse": { @@ -12787,7 +12651,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_algorithm": { - "recorded-date": "17-03-2025, 18:29:05", + "recorded-date": "21-02-2026, 00:38:27", "recorded-content": { "put-wrong-checksum": { "Error": { @@ -12837,7 +12701,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_validation_sse_c": { - "recorded-date": "21-01-2025, 18:16:18", + "recorded-date": "21-02-2026, 00:36:33", "recorded-content": { "put-obj-sse-c-both-encryption": { "Error": { @@ -12866,7 +12730,6 @@ "put-obj-sse-c-no-algo": { "Error": { "ArgumentName": "x-amz-server-side-encryption", - "ArgumentValue": "null", "Code": "InvalidArgument", "Message": "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm." }, @@ -12878,7 +12741,6 @@ "put-obj-sse-c-no-key": { "Error": { "ArgumentName": "x-amz-server-side-encryption", - "ArgumentValue": "null", "Code": "InvalidArgument", "Message": "Requests specifying Server Side Encryption with Customer provided keys must provide an appropriate secret key." }, @@ -12890,7 +12752,6 @@ "put-obj-sse-c-no-md5": { "Error": { "ArgumentName": "x-amz-server-side-encryption", - "ArgumentValue": "null", "Code": "InvalidArgument", "Message": "The secret key was invalid for the specified algorithm." }, @@ -12902,7 +12763,6 @@ "put-obj-sse-c-wrong-key-size": { "Error": { "ArgumentName": "x-amz-server-side-encryption", - "ArgumentValue": "null", "Code": "InvalidArgument", "Message": "The secret key was invalid for the specified algorithm." }, @@ -12914,7 +12774,6 @@ "put-obj-sse-c-bad-md5": { "Error": { "ArgumentName": "x-amz-server-side-encryption", - "ArgumentValue": "null", "Code": "InvalidArgument", "Message": "The calculated MD5 hash of the key did not match the hash that was provided." }, @@ -12926,12 +12785,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_object_retrieval_sse_c": { - "recorded-date": "22-01-2025, 14:21:49", + "recorded-date": "21-02-2026, 00:36:37", "recorded-content": { "put-obj-sse-c": { "ChecksumCRC32": "qIrZrA==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"7f021303b8ca8e5af2c5ee7bf1e96a18\"", + "ETag": "\"b68bda5295f8e709f07a0ab89d752f16\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "JMwgiexXqwuPqIPjYFmIZQ==", "ResponseMetadata": { @@ -13016,7 +12875,6 @@ "get-obj-sse-c-wrong-key-size": { "Error": { "ArgumentName": "x-amz-server-side-encryption", - "ArgumentValue": "null", "Code": "InvalidArgument", "Message": "The secret key was invalid for the specified algorithm." }, @@ -13028,7 +12886,6 @@ "get-obj-sse-c-bad-md5": { "Error": { "ArgumentName": "x-amz-server-side-encryption", - "ArgumentValue": "null", "Code": "InvalidArgument", "Message": "The calculated MD5 hash of the key did not match the hash that was provided." }, @@ -13040,12 +12897,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_copy_object_with_sse_c": { - "recorded-date": "21-01-2025, 18:16:26", + "recorded-date": "21-02-2026, 00:36:41", "recorded-content": { "put-obj-sse-c": { "ChecksumCRC32": "qIrZrA==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"9e12596cb25a080bf57d9655b61cce93\"", + "ETag": "\"b876fdc9f53c719dd2a6130ba9715a69\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "JMwgiexXqwuPqIPjYFmIZQ==", "ResponseMetadata": { @@ -13056,6 +12913,7 @@ "copy-obj-sse-c-target-no-sse-c": { "CopyObjectResult": { "ChecksumCRC32": "qIrZrA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"6af8307c2460f2d208ad254f04be4b0d\"", "LastModified": "datetime" }, @@ -13068,7 +12926,8 @@ "copy-obj-sse-c": { "CopyObjectResult": { "ChecksumCRC32": "qIrZrA==", - "ETag": "\"f8c18b77e4724f2b67755eb07ca0d417\"", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"5e04d65f8c04ad2392df8d14c98443a5\"", "LastModified": "datetime" }, "SSECustomerAlgorithm": "AES256", @@ -13137,7 +12996,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c": { - "recorded-date": "17-03-2025, 22:55:35", + "recorded-date": "21-02-2026, 00:36:47", "recorded-content": { "create-mpu-sse-c": { "Bucket": "bucket", @@ -13152,7 +13011,7 @@ }, "upload-part-0": { "ChecksumCRC32": "NRU+Sw==", - "ETag": "\"55725a011e3346d563c0704e1619e91c\"", + "ETag": "\"cf96b8ba9d9e9e6349399eb3fc08a3f7\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "JMwgiexXqwuPqIPjYFmIZQ==", "ResponseMetadata": { @@ -13162,7 +13021,7 @@ }, "upload-part-1": { "ChecksumCRC32": "NRU+Sw==", - "ETag": "\"5a89fb15ffa5db577508d72fe9d5b61d\"", + "ETag": "\"baa1d9ab47364489d4954efe66a37864\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "JMwgiexXqwuPqIPjYFmIZQ==", "ResponseMetadata": { @@ -13172,7 +13031,7 @@ }, "upload-part-2": { "ChecksumCRC32": "NRU+Sw==", - "ETag": "\"000a293bf05bdb2787a36ffe787ba40e\"", + "ETag": "\"6e7021813aee13e435eeaff924b9160c\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "JMwgiexXqwuPqIPjYFmIZQ==", "ResponseMetadata": { @@ -13191,25 +13050,24 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, "Parts": [ { - "ETag": "\"55725a011e3346d563c0704e1619e91c\"", + "ETag": "\"cf96b8ba9d9e9e6349399eb3fc08a3f7\"", "LastModified": "datetime", "PartNumber": 1, "Size": 5242881 }, { - "ETag": "\"5a89fb15ffa5db577508d72fe9d5b61d\"", + "ETag": "\"baa1d9ab47364489d4954efe66a37864\"", "LastModified": "datetime", "PartNumber": 2, "Size": 5242881 }, { - "ETag": "\"000a293bf05bdb2787a36ffe787ba40e\"", + "ETag": "\"6e7021813aee13e435eeaff924b9160c\"", "LastModified": "datetime", "PartNumber": 3, "Size": 5242881 @@ -13224,7 +13082,7 @@ }, "complete-multipart-checksum": { "Bucket": "bucket", - "ETag": "\"8d8dff3df79d195957f14d81d054538e-3\"", + "ETag": "\"2d296eb6540ecd21430c385336c5dc9c-3\"", "Key": "test-sse-c-multipart", "Location": "", "ResponseMetadata": { @@ -13247,7 +13105,7 @@ "Body": "", "ContentLength": 15728643, "ContentType": "binary/octet-stream", - "ETag": "\"8d8dff3df79d195957f14d81d054538e-3\"", + "ETag": "\"2d296eb6540ecd21430c385336c5dc9c-3\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -13260,7 +13118,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c_validation": { - "recorded-date": "21-01-2025, 18:16:43", + "recorded-date": "21-02-2026, 00:36:50", "recorded-content": { "create-mpu-no-sse-c": { "Bucket": "bucket", @@ -13316,12 +13174,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_lifecycle_with_sse_c": { - "recorded-date": "21-01-2025, 18:16:15", + "recorded-date": "21-02-2026, 00:36:30", "recorded-content": { "put-obj-sse-c": { "ChecksumCRC32": "qIrZrA==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"1339c8b8d4cf4416490531cabb5b5963\"", + "ETag": "\"b84c1aee7b2787381547d4277d117b01\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "JMwgiexXqwuPqIPjYFmIZQ==", "ResponseMetadata": { @@ -13333,7 +13191,7 @@ "AcceptRanges": "bytes", "ContentLength": 9, "ContentType": "binary/octet-stream", - "ETag": "\"1339c8b8d4cf4416490531cabb5b5963\"", + "ETag": "\"b84c1aee7b2787381547d4277d117b01\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -13350,7 +13208,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 9, "ContentType": "binary/octet-stream", - "ETag": "\"1339c8b8d4cf4416490531cabb5b5963\"", + "ETag": "\"b84c1aee7b2787381547d4277d117b01\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -13361,7 +13219,7 @@ } }, "get-obj-attrs-sse-c": { - "ETag": "1339c8b8d4cf4416490531cabb5b5963", + "ETag": "b84c1aee7b2787381547d4277d117b01", "LastModified": "datetime", "ObjectSize": 9, "ResponseMetadata": { @@ -13378,12 +13236,12 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_sse_c_with_versioning": { - "recorded-date": "21-01-2025, 18:16:46", + "recorded-date": "21-02-2026, 00:36:53", "recorded-content": { "put-obj-sse-c-version-1": { "ChecksumCRC32": "gQ5gbg==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"2e00061193ff6efbafd20ee93b0898f2\"", + "ETag": "\"5e8209fe441f4c01aa50431ca1dbbc59\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "JMwgiexXqwuPqIPjYFmIZQ==", "VersionId": "", @@ -13395,7 +13253,7 @@ "put-obj-sse-c-version-2": { "ChecksumCRC32": "GAcx1A==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"5e5d63b0148e2c6dc33e7d3316be8581\"", + "ETag": "\"715fb6e9472d58a780ca13f8b0ac891e\"", "SSECustomerAlgorithm": "AES256", "SSECustomerKeyMD5": "KoitZ78ZSAQHz4+gxDpJqQ==", "VersionId": "", @@ -13411,7 +13269,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"5e5d63b0148e2c6dc33e7d3316be8581\"", + "ETag": "\"715fb6e9472d58a780ca13f8b0ac891e\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -13429,7 +13287,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"5e5d63b0148e2c6dc33e7d3316be8581\"", + "ETag": "\"715fb6e9472d58a780ca13f8b0ac891e\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -13457,7 +13315,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 8, "ContentType": "binary/octet-stream", - "ETag": "\"2e00061193ff6efbafd20ee93b0898f2\"", + "ETag": "\"5e8209fe441f4c01aa50431ca1dbbc59\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -13471,7 +13329,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[COPY]": { - "recorded-date": "21-01-2025, 18:29:02", + "recorded-date": "21-02-2026, 00:19:10", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -13524,6 +13382,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC32": "BnpCOA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"c814444dd0b31747f0a59e12a5351daa\"", "LastModified": "datetime" }, @@ -13551,6 +13410,7 @@ "copy-object-tag-empty": { "CopyObjectResult": { "ChecksumCRC32": "BnpCOA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"c814444dd0b31747f0a59e12a5351daa\"", "LastModified": "datetime" }, @@ -13578,6 +13438,7 @@ "copy-object-v1": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -13605,6 +13466,7 @@ "copy-object-tag-empty-v1": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -13632,7 +13494,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[REPLACE]": { - "recorded-date": "21-01-2025, 18:29:06", + "recorded-date": "21-02-2026, 00:19:14", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -13685,6 +13547,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC32": "BnpCOA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"c814444dd0b31747f0a59e12a5351daa\"", "LastModified": "datetime" }, @@ -13712,6 +13575,7 @@ "copy-object-tag-empty": { "CopyObjectResult": { "ChecksumCRC32": "BnpCOA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"c814444dd0b31747f0a59e12a5351daa\"", "LastModified": "datetime" }, @@ -13734,6 +13598,7 @@ "copy-object-v1": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -13761,6 +13626,7 @@ "copy-object-tag-empty-v1": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -13783,7 +13649,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[None]": { - "recorded-date": "21-01-2025, 18:29:10", + "recorded-date": "21-02-2026, 00:19:18", "recorded-content": { "put-object": { "ChecksumCRC32": "2H9+DA==", @@ -13836,6 +13702,7 @@ "copy-object": { "CopyObjectResult": { "ChecksumCRC32": "BnpCOA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"c814444dd0b31747f0a59e12a5351daa\"", "LastModified": "datetime" }, @@ -13863,6 +13730,7 @@ "copy-object-tag-empty": { "CopyObjectResult": { "ChecksumCRC32": "BnpCOA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"c814444dd0b31747f0a59e12a5351daa\"", "LastModified": "datetime" }, @@ -13890,6 +13758,7 @@ "copy-object-v1": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -13917,6 +13786,7 @@ "copy-object-tag-empty-v1": { "CopyObjectResult": { "ChecksumCRC32": "2H9+DA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"098f6bcd4621d373cade4e832627b4f6\"", "LastModified": "datetime" }, @@ -13944,10 +13814,11 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_region_header_exists_outside_us_east_1": { - "recorded-date": "21-01-2025, 18:26:20", + "recorded-date": "21-02-2026, 00:16:55", "recorded-content": { "head_bucket": { "AccessPointAlias": false, + "BucketArn": "arn::s3:::", "BucketRegion": "", "ResponseMetadata": { "HTTPHeaders": {}, @@ -13969,7 +13840,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy": { - "recorded-date": "21-01-2025, 18:27:51", + "recorded-date": "21-02-2026, 00:18:28", "recorded-content": { "get-bucket-policy-no-such-bucket-policy": { "Error": { @@ -14033,7 +13904,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy": { - "recorded-date": "21-01-2025, 18:28:06", + "recorded-date": "21-02-2026, 00:18:46", "recorded-content": { "delete-bucket-policy": { "ResponseMetadata": { @@ -14055,7 +13926,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy": { - "recorded-date": "21-01-2025, 18:27:58", + "recorded-date": "21-02-2026, 00:18:36", "recorded-content": { "put-bucket-policy": { "ResponseMetadata": { @@ -14085,7 +13956,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy_expected_bucket_owner": { - "recorded-date": "21-01-2025, 18:50:24", + "recorded-date": "21-02-2026, 00:18:49", "recorded-content": { "delete-bucket-policy-with-expected-bucket-owner-error": { "Error": { @@ -14116,7 +13987,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_expected_bucket_owner": { - "recorded-date": "21-01-2025, 18:50:21", + "recorded-date": "21-02-2026, 00:18:39", "recorded-content": { "put-bucket-policy-with-expected-bucket-owner-error": { "Error": { @@ -14137,7 +14008,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000]": { - "recorded-date": "21-01-2025, 18:27:52", + "recorded-date": "21-02-2026, 00:18:30", "recorded-content": { "get-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14152,7 +14023,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000000000020]": { - "recorded-date": "21-01-2025, 18:27:53", + "recorded-date": "21-02-2026, 00:18:31", "recorded-content": { "get-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14167,7 +14038,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[abcd]": { - "recorded-date": "21-01-2025, 18:27:55", + "recorded-date": "21-02-2026, 00:18:33", "recorded-content": { "get-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14182,7 +14053,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[aa000000000$]": { - "recorded-date": "21-01-2025, 18:27:56", + "recorded-date": "21-02-2026, 00:18:34", "recorded-content": { "get-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14197,7 +14068,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000]": { - "recorded-date": "21-01-2025, 18:28:00", + "recorded-date": "21-02-2026, 00:18:40", "recorded-content": { "put-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14212,7 +14083,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000000000020]": { - "recorded-date": "21-01-2025, 18:28:01", + "recorded-date": "21-02-2026, 00:18:41", "recorded-content": { "put-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14227,7 +14098,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[abcd]": { - "recorded-date": "21-01-2025, 18:28:03", + "recorded-date": "21-02-2026, 00:18:43", "recorded-content": { "put-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14242,7 +14113,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[aa000000000$]": { - "recorded-date": "21-01-2025, 18:28:04", + "recorded-date": "21-02-2026, 00:18:44", "recorded-content": { "put-bucket-policy-invalid-bucket-owner": { "Error": { @@ -14257,7 +14128,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC64NVME]": { - "recorded-date": "17-03-2025, 18:28:57", + "recorded-date": "21-02-2026, 00:38:19", "recorded-content": { "put-object": { "ChecksumCRC64NVME": "1f2xscCCZCU=", @@ -14331,7 +14202,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_single_character_trailing_slash": { - "recorded-date": "22-01-2025, 19:05:31", + "recorded-date": "21-02-2026, 00:17:40", "recorded-content": { "put-object-single-char-a/": { "ChecksumCRC32": "2H9+DA==", @@ -14461,7 +14332,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC64NVME]": { - "recorded-date": "24-01-2025, 19:06:38", + "recorded-date": "21-02-2026, 00:20:02", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC32": "MzVIGw==", @@ -14487,6 +14358,7 @@ "copy-object-in-place-with-checksum": { "CopyObjectResult": { "ChecksumCRC64NVME": "pX30eiUx5C0=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -14510,6 +14382,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumCRC64NVME": "pX30eiUx5C0=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -14522,7 +14395,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC64NVME]": { - "recorded-date": "17-03-2025, 18:28:43", + "recorded-date": "21-02-2026, 00:38:04", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -14606,7 +14479,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_automatic_sdk_calculation": { - "recorded-date": "17-03-2025, 22:25:43", + "recorded-date": "21-02-2026, 00:38:31", "recorded-content": { "wrong-checksum": { "Error": { @@ -14690,6 +14563,7 @@ "copy-obj-default-checksum": { "CopyObjectResult": { "ChecksumCRC64NVME": "qUVrWYOrIAM=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"e6d9226c2a86b7232933663c13467527\"", "LastModified": "datetime" }, @@ -14713,7 +14587,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32]": { - "recorded-date": "07-07-2025, 17:39:49", + "recorded-date": "21-02-2026, 00:38:39", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -14767,7 +14641,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -14806,7 +14679,7 @@ "Code": "InvalidPart", "ETag": "c4c753e69bb853187f5854c46cf801c6", "Message": "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.", - "PartNumber": "2", + "PartNumber": "1", "UploadId": "" }, "ResponseMetadata": { @@ -14907,6 +14780,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumCRC32": "qSEQSA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"a0397ee2df08832642af5d7e57c5760c\"", "LastModified": "datetime" }, @@ -14949,7 +14823,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32C]": { - "recorded-date": "07-07-2025, 17:40:10", + "recorded-date": "21-02-2026, 00:38:48", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15003,7 +14877,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -15042,7 +14915,7 @@ "Code": "InvalidPart", "ETag": "c4c753e69bb853187f5854c46cf801c6", "Message": "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.", - "PartNumber": "1", + "PartNumber": "2", "UploadId": "" }, "ResponseMetadata": { @@ -15143,6 +15016,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumCRC32C": "eTdAQA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"a0397ee2df08832642af5d7e57c5760c\"", "LastModified": "datetime" }, @@ -15185,7 +15059,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA1]": { - "recorded-date": "07-07-2025, 17:40:50", + "recorded-date": "21-02-2026, 00:38:55", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15239,7 +15113,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -15276,9 +15149,9 @@ "complete-multipart-wrong-parts-checksum": { "Error": { "Code": "InvalidPart", - "ETag": "c4c753e69bb853187f5854c46cf801c6", + "ETag": "e09c80c42fda55f9d992e59ca6b3307d", "Message": "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.", - "PartNumber": "2", + "PartNumber": "3", "UploadId": "" }, "ResponseMetadata": { @@ -15379,6 +15252,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumSHA1": "eIC+AqBqApUmeqCBQ+9n8OVTP+8=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"a0397ee2df08832642af5d7e57c5760c\"", "LastModified": "datetime" }, @@ -15421,7 +15295,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA256]": { - "recorded-date": "07-07-2025, 17:41:10", + "recorded-date": "21-02-2026, 00:39:02", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15475,7 +15349,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -15512,9 +15385,9 @@ "complete-multipart-wrong-parts-checksum": { "Error": { "Code": "InvalidPart", - "ETag": "c4c753e69bb853187f5854c46cf801c6", + "ETag": "e09c80c42fda55f9d992e59ca6b3307d", "Message": "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.", - "PartNumber": "1", + "PartNumber": "3", "UploadId": "" }, "ResponseMetadata": { @@ -15615,6 +15488,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumSHA256": "0+991zqhqOQ5J2EdwChmHIeC1dXXuJzaCritTzqVGDw=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"a0397ee2df08832642af5d7e57c5760c\"", "LastModified": "datetime" }, @@ -15657,7 +15531,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32]": { - "recorded-date": "15-06-2025, 17:09:49", + "recorded-date": "21-02-2026, 00:39:03", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15674,7 +15548,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32C]": { - "recorded-date": "15-06-2025, 17:09:50", + "recorded-date": "21-02-2026, 00:39:05", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15691,7 +15565,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA1]": { - "recorded-date": "15-06-2025, 17:09:52", + "recorded-date": "21-02-2026, 00:39:06", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15708,7 +15582,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA256]": { - "recorded-date": "15-06-2025, 17:09:53", + "recorded-date": "21-02-2026, 00:39:07", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15725,7 +15599,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC64NVME]": { - "recorded-date": "15-06-2025, 17:09:54", + "recorded-date": "21-02-2026, 00:39:08", "recorded-content": { "create-mpu-checksum-exc": { "Error": { @@ -15740,7 +15614,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32]": { - "recorded-date": "15-06-2025, 17:09:56", + "recorded-date": "21-02-2026, 00:39:10", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15757,7 +15631,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32C]": { - "recorded-date": "15-06-2025, 17:09:57", + "recorded-date": "21-02-2026, 00:39:11", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15774,7 +15648,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA1]": { - "recorded-date": "15-06-2025, 17:09:58", + "recorded-date": "21-02-2026, 00:39:12", "recorded-content": { "create-mpu-checksum-exc": { "Error": { @@ -15789,7 +15663,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA256]": { - "recorded-date": "15-06-2025, 17:10:00", + "recorded-date": "21-02-2026, 00:39:14", "recorded-content": { "create-mpu-checksum-exc": { "Error": { @@ -15804,7 +15678,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC64NVME]": { - "recorded-date": "15-06-2025, 17:10:01", + "recorded-date": "21-02-2026, 00:39:16", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15821,7 +15695,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32]": { - "recorded-date": "15-06-2025, 17:10:03", + "recorded-date": "21-02-2026, 00:39:17", "recorded-content": { "create-mpu-default-checksum-type": { "Bucket": "bucket", @@ -15838,7 +15712,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32C]": { - "recorded-date": "15-06-2025, 17:10:04", + "recorded-date": "21-02-2026, 00:39:18", "recorded-content": { "create-mpu-default-checksum-type": { "Bucket": "bucket", @@ -15855,7 +15729,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA1]": { - "recorded-date": "15-06-2025, 17:10:05", + "recorded-date": "21-02-2026, 00:39:19", "recorded-content": { "create-mpu-default-checksum-type": { "Bucket": "bucket", @@ -15872,7 +15746,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA256]": { - "recorded-date": "15-06-2025, 17:10:07", + "recorded-date": "21-02-2026, 00:39:21", "recorded-content": { "create-mpu-default-checksum-type": { "Bucket": "bucket", @@ -15889,7 +15763,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC64NVME]": { - "recorded-date": "15-06-2025, 17:10:08", + "recorded-date": "21-02-2026, 00:39:22", "recorded-content": { "create-mpu-default-checksum-type": { "Bucket": "bucket", @@ -15906,7 +15780,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32]": { - "recorded-date": "07-07-2025, 17:41:34", + "recorded-date": "21-02-2026, 00:40:50", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -15960,7 +15834,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -16069,6 +15942,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumCRC32": "qSEQSA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"a0397ee2df08832642af5d7e57c5760c\"", "LastModified": "datetime" }, @@ -16109,7 +15983,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32C]": { - "recorded-date": "07-07-2025, 17:41:53", + "recorded-date": "21-02-2026, 00:40:57", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -16163,7 +16037,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -16202,7 +16075,7 @@ "Code": "InvalidPart", "ETag": "c4c753e69bb853187f5854c46cf801c6", "Message": "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.", - "PartNumber": "2", + "PartNumber": "1", "UploadId": "" }, "ResponseMetadata": { @@ -16272,6 +16145,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumCRC32C": "eTdAQA==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"a0397ee2df08832642af5d7e57c5760c\"", "LastModified": "datetime" }, @@ -16312,7 +16186,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC64NVME]": { - "recorded-date": "07-07-2025, 17:42:04", + "recorded-date": "21-02-2026, 00:41:04", "recorded-content": { "create-mpu-checksum": { "Bucket": "bucket", @@ -16366,7 +16240,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -16475,6 +16348,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumCRC64NVME": "ZMNX55lZurA=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"a0397ee2df08832642af5d7e57c5760c\"", "LastModified": "datetime" }, @@ -16515,7 +16389,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_full_object": { - "recorded-date": "15-06-2025, 17:12:51", + "recorded-date": "21-02-2026, 00:42:04", "recorded-content": { "create-mpu-no-checksum-algo-with-type": { "Error": { @@ -16558,7 +16432,6 @@ }, "Key": "test-multipart-checksum-exc", "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "StorageClass": "STANDARD", @@ -16677,7 +16550,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_default": { - "recorded-date": "15-06-2025, 17:12:56", + "recorded-date": "21-02-2026, 00:42:09", "recorded-content": { "create-mpu-no-checksum": { "Bucket": "bucket", @@ -16706,7 +16579,6 @@ }, "Key": "test-multipart-checksum", "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "StorageClass": "STANDARD", @@ -16738,7 +16610,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -16865,6 +16736,7 @@ "copy-obj-checksum": { "CopyObjectResult": { "ChecksumCRC64NVME": "Rnr2/5P7Gsk=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"47bce5c74f589f4867dbd57e9ca9f808\"", "LastModified": "datetime" }, @@ -16889,7 +16761,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_size_validation": { - "recorded-date": "15-06-2025, 17:13:01", + "recorded-date": "21-02-2026, 00:42:14", "recorded-content": { "create-mpu": { "Bucket": "bucket", @@ -16948,7 +16820,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32]": { - "recorded-date": "15-06-2025, 17:10:17", + "recorded-date": "21-02-2026, 00:39:37", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -16973,7 +16845,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32C]": { - "recorded-date": "15-06-2025, 17:10:27", + "recorded-date": "21-02-2026, 00:39:48", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -16998,7 +16870,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA1]": { - "recorded-date": "15-06-2025, 17:10:44", + "recorded-date": "21-02-2026, 00:40:02", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -17023,7 +16895,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA256]": { - "recorded-date": "15-06-2025, 17:10:59", + "recorded-date": "21-02-2026, 00:40:15", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -17048,7 +16920,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC64NVME]": { - "recorded-date": "15-06-2025, 17:11:10", + "recorded-date": "21-02-2026, 00:40:29", "recorded-content": { "put-wrong-checksum-no-b64": { "Error": { @@ -17073,7 +16945,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_s3_transition_default_minimum_object_size": { - "recorded-date": "03-02-2025, 10:15:23", + "recorded-date": "21-02-2026, 00:33:46", "recorded-content": { "varies-by-storage": { "TransitionDefaultMinimumObjectSize": "varies_by_storage_class", @@ -17166,7 +17038,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object_default": { - "recorded-date": "15-06-2025, 17:12:58", + "recorded-date": "21-02-2026, 00:42:11", "recorded-content": { "create-mpu-checksum-crc64": { "Bucket": "bucket", @@ -17251,7 +17123,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_default_checksum": { - "recorded-date": "17-03-2025, 21:46:24", + "recorded-date": "21-02-2026, 00:35:55", "recorded-content": { "head-object": { "AcceptRanges": "bytes", @@ -17271,7 +17143,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32]": { - "recorded-date": "17-03-2025, 22:17:03", + "recorded-date": "21-02-2026, 00:20:04", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC32": "MzVIGw==", @@ -17297,6 +17169,7 @@ "copy-object-in-place-with-no-checksum": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17320,6 +17193,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumCRC32": "MzVIGw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17343,7 +17217,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32C]": { - "recorded-date": "17-03-2025, 22:17:06", + "recorded-date": "21-02-2026, 00:20:07", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC32C": "078Ilw==", @@ -17369,6 +17243,7 @@ "copy-object-in-place-with-no-checksum": { "CopyObjectResult": { "ChecksumCRC32C": "078Ilw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17392,6 +17267,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumCRC32C": "078Ilw==", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17415,7 +17291,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA1]": { - "recorded-date": "17-03-2025, 22:17:08", + "recorded-date": "21-02-2026, 00:20:09", "recorded-content": { "put-object-no-checksum": { "ChecksumSHA1": "5zXdjmjYk4EJ8Cw4PMnQVslCpRQ=", @@ -17441,6 +17317,7 @@ "copy-object-in-place-with-no-checksum": { "CopyObjectResult": { "ChecksumSHA1": "5zXdjmjYk4EJ8Cw4PMnQVslCpRQ=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17464,6 +17341,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumSHA1": "5zXdjmjYk4EJ8Cw4PMnQVslCpRQ=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17487,7 +17365,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA256]": { - "recorded-date": "17-03-2025, 22:17:11", + "recorded-date": "21-02-2026, 00:20:12", "recorded-content": { "put-object-no-checksum": { "ChecksumSHA256": "lyTB4g5uPk1/V+0l+dTvsAblCFkNUoyQ2ll/andcE+U=", @@ -17513,6 +17391,7 @@ "copy-object-in-place-with-no-checksum": { "CopyObjectResult": { "ChecksumSHA256": "lyTB4g5uPk1/V+0l+dTvsAblCFkNUoyQ2ll/andcE+U=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17536,6 +17415,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumSHA256": "lyTB4g5uPk1/V+0l+dTvsAblCFkNUoyQ2ll/andcE+U=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17559,7 +17439,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC64NVME]": { - "recorded-date": "17-03-2025, 22:17:13", + "recorded-date": "21-02-2026, 00:20:14", "recorded-content": { "put-object-no-checksum": { "ChecksumCRC64NVME": "pX30eiUx5C0=", @@ -17585,6 +17465,7 @@ "copy-object-in-place-with-no-checksum": { "CopyObjectResult": { "ChecksumCRC64NVME": "pX30eiUx5C0=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17608,6 +17489,7 @@ "copy-object-to-dest-keep-checksum": { "CopyObjectResult": { "ChecksumCRC64NVME": "pX30eiUx5C0=", + "ChecksumType": "FULL_OBJECT", "ETag": "\"88bac95f31528d13a072c05f2a1cf371\"", "LastModified": "datetime" }, @@ -17631,7 +17513,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_default_checksum_with_sse_c": { - "recorded-date": "17-03-2025, 23:22:53", + "recorded-date": "21-02-2026, 00:36:56", "recorded-content": { "head-obj-sse-c": { "AcceptRanges": "bytes", @@ -17639,7 +17521,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 11, "ContentType": "binary/octet-stream", - "ETag": "\"14838aba23aac65c8befbb53acf51014\"", + "ETag": "\"a5d8837e2a881c286b90c0586f2869de\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -17656,7 +17538,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 11, "ContentType": "binary/octet-stream", - "ETag": "\"14838aba23aac65c8befbb53acf51014\"", + "ETag": "\"a5d8837e2a881c286b90c0586f2869de\"", "LastModified": "datetime", "Metadata": {}, "SSECustomerAlgorithm": "AES256", @@ -17671,7 +17553,7 @@ "ChecksumCRC64NVME": "qUVrWYOrIAM=", "ChecksumType": "FULL_OBJECT" }, - "ETag": "14838aba23aac65c8befbb53acf51014", + "ETag": "a5d8837e2a881c286b90c0586f2869de", "LastModified": "datetime", "ResponseMetadata": { "HTTPHeaders": {}, @@ -17681,7 +17563,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[COMPOSITE]": { - "recorded-date": "16-06-2025, 10:53:39", + "recorded-date": "21-02-2026, 00:42:17", "recorded-content": { "put-object": { "ChecksumCRC32": "nG7pIA==", @@ -17730,7 +17612,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -17823,7 +17704,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[FULL_OBJECT]": { - "recorded-date": "16-06-2025, 10:53:42", + "recorded-date": "21-02-2026, 00:42:20", "recorded-content": { "put-object": { "ChecksumCRC32": "nG7pIA==", @@ -17872,7 +17753,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -17954,7 +17834,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_compliance_mode": { - "recorded-date": "20-06-2025, 17:19:56", + "recorded-date": "21-02-2026, 00:34:29", "recorded-content": { "put-obj-locked-1": { "ChecksumCRC32": "2H9+DA==", @@ -18031,7 +17911,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_lock_mode_validation": { - "recorded-date": "20-06-2025, 17:33:40", + "recorded-date": "21-02-2026, 00:34:32", "recorded-content": { "put-obj-locked-error-no-retain-date": { "Error": { @@ -18082,7 +17962,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[COMPOSITE]": { - "recorded-date": "07-07-2025, 18:23:54", + "recorded-date": "21-02-2026, 00:23:01", "recorded-content": { "create-mpu-checksum": { "Bucket": "", @@ -18156,7 +18036,7 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[FULL_OBJECT]": { - "recorded-date": "07-07-2025, 18:23:57", + "recorded-date": "21-02-2026, 00:23:03", "recorded-content": { "create-mpu-checksum": { "Bucket": "", @@ -18228,5 +18108,480 @@ } } } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expires": { + "recorded-date": "21-02-2026, 00:20:54", + "recorded-content": { + "put-object-expires-future": { + "ChecksumCRC32": "jHNlIQ==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "head-object-expires-future": { + "AcceptRanges": "bytes", + "ContentLength": 3, + "ContentType": "binary/octet-stream", + "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", + "Expires": "datetime", + "ExpiresString": "", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-object-expires-future": { + "AcceptRanges": "bytes", + "Body": "foo", + "ChecksumCRC32": "jHNlIQ==", + "ChecksumType": "FULL_OBJECT", + "ContentLength": 3, + "ContentType": "binary/octet-stream", + "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", + "Expires": "datetime", + "ExpiresString": "", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "put-object-expires-past": { + "ChecksumCRC32": "jHNlIQ==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "head-object-expires-past": { + "AcceptRanges": "bytes", + "ContentLength": 3, + "ContentType": "binary/octet-stream", + "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", + "Expires": "datetime", + "ExpiresString": "", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-object-expires-past": { + "AcceptRanges": "bytes", + "Body": "foo", + "ChecksumCRC32": "jHNlIQ==", + "ChecksumType": "FULL_OBJECT", + "ContentLength": 3, + "ContentType": "binary/octet-stream", + "ETag": "\"acbd18db4cc2f85cedef654fccc4a4d8\"", + "Expires": "datetime", + "ExpiresString": "", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_aws_global": { + "recorded-date": "21-02-2026, 00:23:28", + "recorded-content": { + "xml-error-create-bucket": { + "Error": { + "Code": "AuthorizationHeaderMalformed", + "HostId": "", + "Message": "The authorization header is malformed; the region 'aws-global' is wrong; expecting ''", + "Region": "", + "RequestId": "" + } + }, + "create-bucket-global": { + "BucketArn": "arn::s3:::", + "Location": "/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "head-bucket-global": { + "AccessPointAlias": false, + "BucketArn": "arn::s3:::", + "BucketRegion": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-location-1": { + "LocationConstraint": null, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-buckets": { + "Buckets": [ + { + "BucketArn": "arn::s3:::", + "BucketRegion": "", + "CreationDate": "datetime", + "Name": "" + } + ], + "Owner": { + "ID": "" + }, + "Prefix": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_constraint_aws_global[us-east-1]": { + "recorded-date": "21-02-2026, 00:23:29", + "recorded-content": { + "aws-global-constraint": { + "Error": { + "Code": "InvalidLocationConstraint", + "LocationConstraint": "aws-global", + "Message": "The specified location-constraint is not valid" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_constraint_aws_global[us-west-1]": { + "recorded-date": "21-02-2026, 00:23:30", + "recorded-content": { + "aws-global-constraint": { + "Error": { + "Code": "IllegalLocationConstraintException", + "Message": "The aws-global location constraint is incompatible for the region specific endpoint this request was sent to." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_eu_location_constraint": { + "recorded-date": "21-02-2026, 00:18:25", + "recorded-content": { + "get-bucket-location": { + "LocationConstraint": "EU", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-bucket-response": { + "Buckets": [ + { + "BucketArn": "arn::s3:::", + "BucketRegion": "", + "CreationDate": "datetime", + "Name": "" + } + ], + "Owner": { + "ID": "" + }, + "Prefix": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_eu_location_constraint_raises": { + "recorded-date": "21-02-2026, 00:18:26", + "recorded-content": { + "eu-location-constraint-error": { + "Error": { + "Code": "IllegalLocationConstraintException", + "Message": "The EU location constraint is incompatible for the region specific endpoint this request was sent to." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_invalid_location_constraint[us-east-1-us-east-1]": { + "recorded-date": "21-02-2026, 00:18:26", + "recorded-content": { + "us-east-1-location-constraint-us-east-1-error": { + "Error": { + "Code": "InvalidLocationConstraint", + "LocationConstraint": "", + "Message": "The specified location-constraint is not valid" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_invalid_location_constraint[us-east-1-foo]": { + "recorded-date": "21-02-2026, 00:18:27", + "recorded-content": { + "us-east-1-location-constraint-foo-error": { + "Error": { + "Code": "InvalidLocationConstraint", + "LocationConstraint": "", + "Message": "The specified location-constraint is not valid" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_invalid_location_constraint[eu-west-1-bar]": { + "recorded-date": "21-02-2026, 00:18:27", + "recorded-content": { + "eu-west-1-location-constraint-bar-error": { + "Error": { + "Code": "IllegalLocationConstraintException", + "Message": "The bar location constraint is incompatible for the region specific endpoint this request was sent to." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_unicode_metadata": { + "recorded-date": "21-02-2026, 00:35:50", + "recorded-content": { + "post-object": { + "LocationHeader": "/test_unicode%E2%80%94_file.pdf", + "PostResponse": { + "Bucket": "", + "ETag": "\"a7d8531d918474360de3e2eaeb110cda\"", + "Key": "test_unicode\u2014_file.pdf", + "Location": "/test_unicode%E2%80%94_file.pdf" + } + }, + "head-object": { + "AcceptRanges": "bytes", + "CacheControl": "non-ascii-%E2%80%94_ _\u00e9_", + "ContentDisposition": "filename=\"test_ _file%E2%80%94_\u00e9_2-.pdf\"", + "ContentLength": 17, + "ContentType": "binary/octet-stream", + "ETag": "\"a7d8531d918474360de3e2eaeb110cda\"", + "LastModified": "datetime", + "Metadata": { + "b-encoded": "=?UTF-8?B?YWJj?=", + "nonascii": "=?UTF-8?Q?=C3=84M=C3=84Z=C3=95=C3=91_S3?=", + "nonascii-2": "=?UTF-8?Q?test=5F=E2=80=94=5Ffile%E2%80%94=5F=C3=A9=5F2=F0=9F=91=91.pdf?=", + "q-encoded": "=?UTF-8?Q?actually-ascii?=", + "safe-chars": "! \"#$%&'()*+,-./0123456789:;<>'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t", + "utf-8": "=?UTF-8?B?AAECAwQ=?=" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_system_metadata_with_unicode": { + "recorded-date": "21-02-2026, 00:17:06", + "recorded-content": { + "put-object": { + "ChecksumCRC32": "AAAAAA==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-object": { + "AcceptRanges": "bytes", + "Body": "", + "CacheControl": "\u00c3\u0084M\u00c3\u0084Z\u00c3\u0095\u00c3\u0091 S3", + "ChecksumCRC32": "AAAAAA==", + "ChecksumType": "FULL_OBJECT", + "ContentDisposition": "attachment; filename=\"test_\u00e2\u0080\u0094_file%E2%80%94_\u00c3\u00a9_2.pdf\"", + "ContentLanguage": "de", + "ContentLength": 0, + "ContentType": "binary/octet-stream", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_response_overrides_unicode_metadata_with_sig_s3": { + "recorded-date": "21-02-2026, 00:29:43", + "recorded-content": { + "unicode-error": { + "Error": { + "ArgumentName": "response-cache-control", + "ArgumentValue": "non-ascii-%E2%80%94_\u2014_\u00e9_", + "Code": "InvalidArgument", + "HostId": "host-id", + "Message": "Header value cannot be represented using ISO-8859-1.", + "RequestId": "" + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_unicode_metadata_with_sig_s3": { + "recorded-date": "21-02-2026, 00:29:45", + "recorded-content": { + "head_object": { + "AcceptRanges": "bytes", + "ContentLength": 0, + "ContentType": "binary/octet-stream", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "LastModified": "datetime", + "Metadata": { + "foo": "=?UTF-8?Q?non-ascii-%E2%80%94=5F=E2=80=94=5F=C3=A9=5F?=" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_with_unicode_character_location": { + "recorded-date": "21-02-2026, 00:17:42", + "recorded-content": { + "create-multipart": { + "Bucket": "bucket", + "Key": "test-unicode_\u2014_file", + "ServerSideEncryption": "AES256", + "UploadId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "complete-multipart": { + "Bucket": "bucket", + "ChecksumCRC64NVME": "LPvyvt6AWlQ=", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"209b3f7345bfc0220995968302e3e9a8-1\"", + "Key": "test-unicode_\u2014_file", + "Location": "/test-unicode_%E2%80%94_file", + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_user_metadata_rfc2047_encoded": { + "recorded-date": "21-02-2026, 00:17:08", + "recorded-content": { + "put-object": { + "ChecksumCRC32": "AAAAAA==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-object": { + "AcceptRanges": "bytes", + "Body": "", + "ChecksumCRC32": "AAAAAA==", + "ChecksumType": "FULL_OBJECT", + "ContentLength": 0, + "ContentType": "binary/octet-stream", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "LastModified": "datetime", + "Metadata": { + "asciib64-encoded": "abc", + "fake-encoded": "actually-ascii", + "non-ascii": "=?UTF-8?Q?test=5F=E2=80=94=5Ffile%E2%80%94=5F=C3=A9=5F2=3F.pdf?=", + "non-ascii-2": "=?UTF-8?Q?=C3=84M=C3=84Z=C3=95=C3=91_S3?=", + "non-ascii-binary": "=?UTF-8?B?AAECAw==?=", + "replacement-chars": "=?UTF-8?B?77+977+977+977+977+977+977+9?=", + "safe-chars": "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_user_metadata_rfc2047_bad_b64_encoded": { + "recorded-date": "21-02-2026, 00:17:10", + "recorded-content": { + "put-object": { + "ChecksumCRC32": "AAAAAA==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-object": { + "AcceptRanges": "bytes", + "Body": "", + "ChecksumCRC32": "AAAAAA==", + "ChecksumType": "FULL_OBJECT", + "ContentLength": 0, + "ContentType": "binary/octet-stream", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "LastModified": "datetime", + "Metadata": { + "bad-b64-encoded": "=?UTF-8?B?77+9Ye+/vQ==?=" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/s3/test_s3.validation.json b/tests/aws/services/s3/test_s3.validation.json index f2eadc435b21a..b2a80621e1be5 100644 --- a/tests/aws/services/s3/test_s3.validation.json +++ b/tests/aws/services/s3/test_s3.validation.json @@ -1,152 +1,470 @@ { "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_availability": { - "last_validated_date": "2025-01-21T18:30:41+00:00" + "last_validated_date": "2026-02-21T00:20:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.44, + "teardown": 0.01, + "total": 0.46 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_constraint_aws_global[us-east-1]": { + "last_validated_date": "2026-02-21T00:23:29+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.46, + "teardown": 0.0, + "total": 0.47 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_constraint_aws_global[us-west-1]": { + "last_validated_date": "2026-02-21T00:23:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.63, + "teardown": 0.01, + "total": 0.64 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_does_not_exist": { - "last_validated_date": "2025-01-21T18:34:13+00:00" + "last_validated_date": "2026-02-21T00:23:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.54, + "teardown": 0.0, + "total": 1.54 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_exists": { - "last_validated_date": "2025-01-21T18:31:45+00:00" + "last_validated_date": "2026-02-21T00:21:44+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.85, + "teardown": 0.65, + "total": 2.03 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_name_with_dots": { - "last_validated_date": "2025-01-21T18:34:19+00:00" + "last_validated_date": "2026-02-21T00:23:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.94, + "teardown": 0.94, + "total": 2.88 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_operation_between_regions": { - "last_validated_date": "2025-01-21T18:30:52+00:00" + "last_validated_date": "2026-02-21T00:21:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.78, + "teardown": 0.98, + "total": 3.76 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_complete_multipart_parts_order": { - "last_validated_date": "2025-03-17T21:30:44+00:00" + "last_validated_date": "2026-02-21T00:28:14+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 5.25, + "teardown": 0.99, + "total": 6.78 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_copy_in_place_with_bucket_encryption": { - "last_validated_date": "2025-01-21T18:29:34+00:00" + "last_validated_date": "2026-02-21T00:19:43+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.61, + "teardown": 0.95, + "total": 2.08 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_kms": { - "last_validated_date": "2025-01-21T18:26:17+00:00" + "last_validated_date": "2026-02-21T00:16:53+00:00", + "durations_in_seconds": { + "setup": 1.09, + "call": 1.4, + "teardown": 1.07, + "total": 3.56 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character": { - "last_validated_date": "2025-01-21T18:27:00+00:00" + "last_validated_date": "2026-02-21T00:17:48+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 2.8, + "teardown": 2.11, + "total": 5.41 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character_plus_for_space": { - "last_validated_date": "2025-01-21T18:27:03+00:00" + "last_validated_date": "2026-02-21T00:17:51+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.8, + "teardown": 1.02, + "total": 2.33 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_aws_global": { + "last_validated_date": "2026-02-21T00:23:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.79, + "teardown": 0.54, + "total": 2.33 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_head_bucket": { - "last_validated_date": "2025-01-21T18:34:17+00:00" + "last_validated_date": "2026-02-21T00:23:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.8, + "teardown": 0.01, + "total": 3.81 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_via_host_name": { - "last_validated_date": "2025-01-21T18:27:49+00:00" + "last_validated_date": "2026-02-21T00:18:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.64, + "teardown": 0.0, + "total": 1.64 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_eu_location_constraint": { + "last_validated_date": "2026-02-21T00:18:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.83, + "teardown": 0.79, + "total": 1.62 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_eu_location_constraint_raises": { + "last_validated_date": "2026-02-21T00:18:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.42, + "teardown": 0.01, + "total": 0.43 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_existing_name": { - "last_validated_date": "2025-01-21T18:34:10+00:00" + "last_validated_date": "2026-02-21T00:23:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.36, + "teardown": 1.47, + "total": 2.83 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_invalid_location_constraint[eu-west-1-bar]": { + "last_validated_date": "2026-02-21T00:18:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.01, + "total": 0.14 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_invalid_location_constraint[us-east-1-foo]": { + "last_validated_date": "2026-02-21T00:18:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.33, + "teardown": 0.01, + "total": 0.34 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_invalid_location_constraint[us-east-1-us-east-1]": { + "last_validated_date": "2026-02-21T00:18:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.36, + "teardown": 0.01, + "total": 0.37 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_no_such_bucket": { - "last_validated_date": "2025-01-21T18:27:11+00:00" + "last_validated_date": "2026-02-21T00:17:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.33, + "teardown": 0.01, + "total": 0.34 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy": { - "last_validated_date": "2025-01-21T18:28:06+00:00" + "last_validated_date": "2026-02-21T00:18:47+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 0.51, + "teardown": 0.56, + "total": 1.98 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy_expected_bucket_owner": { - "last_validated_date": "2025-01-21T18:50:24+00:00" + "last_validated_date": "2026-02-21T00:18:49+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 0.9, + "teardown": 0.62, + "total": 2.39 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_with_content": { - "last_validated_date": "2025-01-21T18:26:26+00:00" + "last_validated_date": "2026-02-21T00:17:01+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 3.65, + "teardown": 0.23, + "total": 4.37 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_keys_in_versioned_bucket": { - "last_validated_date": "2025-01-21T18:31:34+00:00" + "last_validated_date": "2026-02-21T00:21:34+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 2.04, + "teardown": 0.58, + "total": 3.12 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys": { - "last_validated_date": "2025-01-21T18:31:31+00:00" + "last_validated_date": "2026-02-21T00:21:31+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.37, + "teardown": 0.81, + "total": 1.71 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_in_non_existing_bucket": { - "last_validated_date": "2025-01-21T18:31:36+00:00" + "last_validated_date": "2026-02-21T00:21:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.33, + "teardown": 0.01, + "total": 0.34 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_quiet": { - "last_validated_date": "2025-01-21T18:31:30+00:00" + "last_validated_date": "2026-02-21T00:21:29+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.43, + "teardown": 0.85, + "total": 1.81 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_object_tagging": { - "last_validated_date": "2025-01-21T18:31:28+00:00" + "last_validated_date": "2026-02-21T00:21:27+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.78, + "teardown": 0.95, + "total": 2.21 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_delete_objects_encoding": { - "last_validated_date": "2025-01-21T18:31:37+00:00" + "last_validated_date": "2026-02-21T00:21:36+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.0, + "teardown": 0.59, + "total": 2.08 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_different_location_constraint": { - "last_validated_date": "2025-01-21T18:30:47+00:00" + "last_validated_date": "2026-02-23T12:30:40+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 5.45, + "teardown": 2.88, + "total": 8.82 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_download_fileobj_multiple_range_requests": { - "last_validated_date": "2025-01-21T18:31:23+00:00" + "last_validated_date": "2026-02-21T00:21:23+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 5.16, + "teardown": 0.98, + "total": 6.66 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_empty_bucket_fixture": { - "last_validated_date": "2025-01-21T18:43:07+00:00" + "last_validated_date": "2026-02-21T00:29:23+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.6, + "teardown": 0.59, + "total": 2.68 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_etag_on_get_object_call": { - "last_validated_date": "2025-01-21T18:39:07+00:00" + "last_validated_date": "2026-02-21T00:27:35+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.8, + "teardown": 0.97, + "total": 2.3 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_notification_configuration_no_such_bucket": { - "last_validated_date": "2025-01-21T18:27:11+00:00" + "last_validated_date": "2026-02-21T00:17:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.33, + "teardown": 0.01, + "total": 0.35 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy": { - "last_validated_date": "2025-01-21T18:27:51+00:00" + "last_validated_date": "2026-02-21T00:18:29+00:00", + "durations_in_seconds": { + "setup": 0.86, + "call": 0.72, + "teardown": 0.59, + "total": 2.17 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000000000020]": { - "last_validated_date": "2025-01-21T18:27:53+00:00" + "last_validated_date": "2026-02-21T00:18:32+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.1, + "teardown": 0.89, + "total": 1.52 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000]": { - "last_validated_date": "2025-01-21T18:27:52+00:00" + "last_validated_date": "2026-02-21T00:18:30+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.11, + "teardown": 0.8, + "total": 1.41 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[aa000000000$]": { - "last_validated_date": "2025-01-21T18:27:56+00:00" + "last_validated_date": "2026-02-21T00:18:35+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.1, + "teardown": 0.8, + "total": 1.42 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[abcd]": { - "last_validated_date": "2025-01-21T18:27:55+00:00" + "last_validated_date": "2026-02-21T00:18:33+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.1, + "teardown": 0.83, + "total": 1.44 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_versioning_order": { - "last_validated_date": "2025-01-21T18:39:04+00:00" + "last_validated_date": "2026-02-21T00:27:33+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 1.3, + "teardown": 1.16, + "total": 3.01 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_after_deleted_in_versioned_bucket": { - "last_validated_date": "2025-01-21T18:28:12+00:00" + "last_validated_date": "2026-02-21T00:18:56+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.04, + "teardown": 0.76, + "total": 2.3 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes": { - "last_validated_date": "2025-03-17T20:02:49+00:00" + "last_validated_date": "2026-02-21T00:18:03+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 3.92, + "teardown": 1.03, + "total": 5.47 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_versioned": { - "last_validated_date": "2025-01-21T18:27:33+00:00" + "last_validated_date": "2026-02-21T00:18:08+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.5, + "teardown": 1.03, + "total": 3.05 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_with_space": { - "last_validated_date": "2025-01-21T18:27:30+00:00" + "last_validated_date": "2026-02-21T00:18:05+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.69, + "teardown": 1.0, + "total": 2.2 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[False]": { - "last_validated_date": "2025-01-21T18:43:04+00:00" + "last_validated_date": "2026-02-21T00:29:20+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.58, + "teardown": 1.0, + "total": 2.12 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[True]": { - "last_validated_date": "2025-01-21T18:43:02+00:00" + "last_validated_date": "2026-02-21T00:29:18+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.55, + "teardown": 0.95, + "total": 2.03 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_no_such_bucket": { - "last_validated_date": "2025-01-21T18:27:10+00:00" + "last_validated_date": "2026-02-21T00:17:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.33, + "teardown": 0.01, + "total": 0.35 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part": { - "last_validated_date": "2025-07-07T17:56:15+00:00", + "last_validated_date": "2026-02-21T00:22:59+00:00", "durations_in_seconds": { - "setup": 1.4, - "call": 10.69, - "teardown": 1.07, - "total": 13.16 + "setup": 0.52, + "call": 3.36, + "teardown": 0.97, + "total": 4.85 } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[COMPOSITE]": { - "last_validated_date": "2025-07-07T18:23:55+00:00", + "last_validated_date": "2026-02-21T00:23:02+00:00", "durations_in_seconds": { - "setup": 1.05, - "call": 0.85, - "teardown": 1.02, - "total": 2.92 + "setup": 0.51, + "call": 0.75, + "teardown": 0.93, + "total": 2.19 } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[FULL_OBJECT]": { - "last_validated_date": "2025-07-07T18:23:58+00:00", + "last_validated_date": "2026-02-21T00:23:04+00:00", "durations_in_seconds": { - "setup": 0.6, - "call": 0.85, - "teardown": 1.17, - "total": 2.62 + "setup": 0.61, + "call": 0.82, + "teardown": 0.95, + "total": 2.38 } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum_composite": { @@ -159,106 +477,328 @@ } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_with_anon_credentials": { - "last_validated_date": "2025-01-21T18:30:54+00:00" + "last_validated_date": "2026-02-21T00:21:12+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 0.87, + "teardown": 1.03, + "total": 2.82 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_get_range_object_headers": { - "last_validated_date": "2025-01-21T18:31:25+00:00" + "last_validated_date": "2026-02-21T00:21:25+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.33, + "teardown": 1.21, + "total": 2.03 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_head_object_fields": { - "last_validated_date": "2025-01-21T18:28:10+00:00" + "last_validated_date": "2026-02-21T00:18:53+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.43, + "teardown": 0.97, + "total": 1.92 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_invalid_range_error": { - "last_validated_date": "2025-01-21T18:27:45+00:00" + "last_validated_date": "2026-02-21T00:18:21+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.32, + "teardown": 0.99, + "total": 1.81 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_metadata_header_character_decoding": { - "last_validated_date": "2025-01-21T18:26:37+00:00" + "last_validated_date": "2026-02-21T00:17:19+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.43, + "teardown": 0.95, + "total": 1.88 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_metadata_header_character_unicode_encoding": { + "last_validated_date": "2026-01-28T17:21:11+00:00", + "durations_in_seconds": { + "setup": 1.08, + "call": 0.32, + "teardown": 0.96, + "total": 2.36 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_and_list_parts": { - "last_validated_date": "2025-03-17T21:28:50+00:00" + "last_validated_date": "2026-02-21T00:18:11+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.51, + "teardown": 0.74, + "total": 2.77 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_too_small": { - "last_validated_date": "2025-01-21T18:27:40+00:00" + "last_validated_date": "2026-02-21T00:18:15+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.05, + "teardown": 0.83, + "total": 2.4 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_wrong_part": { - "last_validated_date": "2025-01-21T18:27:42+00:00" + "last_validated_date": "2026-02-21T00:18:17+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.82, + "teardown": 0.8, + "total": 2.13 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_copy_object_etag": { - "last_validated_date": "2025-03-17T23:02:42+00:00" + "last_validated_date": "2026-02-21T00:22:55+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.04, + "teardown": 1.01, + "total": 2.59 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_no_such_upload": { - "last_validated_date": "2025-01-21T18:27:38+00:00" + "last_validated_date": "2026-02-21T00:18:13+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.54, + "teardown": 0.61, + "total": 1.69 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_overwrite_key": { - "last_validated_date": "2025-03-17T21:29:05+00:00" + "last_validated_date": "2026-02-21T00:22:52+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.91, + "teardown": 0.97, + "total": 2.4 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_with_unicode_character_location": { + "last_validated_date": "2026-02-21T00:17:43+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.56, + "teardown": 0.74, + "total": 1.83 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[False]": { - "last_validated_date": "2025-01-21T18:26:36+00:00" + "last_validated_date": "2026-02-21T00:17:17+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.67, + "teardown": 0.97, + "total": 3.14 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[True]": { - "last_validated_date": "2025-01-21T18:26:32+00:00" + "last_validated_date": "2026-02-21T00:17:14+00:00", + "durations_in_seconds": { + "setup": 0.56, + "call": 1.64, + "teardown": 0.96, + "total": 3.16 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_precondition_failed_error": { - "last_validated_date": "2025-01-21T18:32:22+00:00" + "last_validated_date": "2026-02-21T00:22:23+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.33, + "teardown": 0.96, + "total": 1.81 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_content_language_disposition": { - "last_validated_date": "2025-01-21T18:26:29+00:00" + "last_validated_date": "2026-02-21T00:17:05+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 0.47, + "teardown": 0.95, + "total": 1.97 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_hash_prefix": { - "last_validated_date": "2025-01-21T18:27:44+00:00" + "last_validated_date": "2026-02-21T00:18:19+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.49, + "teardown": 0.92, + "total": 1.93 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_utf8_key": { - "last_validated_date": "2025-01-21T18:26:27+00:00" + "last_validated_date": "2026-02-21T00:17:03+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.44, + "teardown": 0.98, + "total": 1.93 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_inventory_config_order": { - "last_validated_date": "2025-01-21T18:43:00+00:00" + "last_validated_date": "2026-02-21T00:29:16+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.86, + "teardown": 1.23, + "total": 3.1 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy": { - "last_validated_date": "2025-01-21T18:27:58+00:00" + "last_validated_date": "2026-02-21T00:18:37+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 0.35, + "teardown": 0.6, + "total": 1.82 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_expected_bucket_owner": { - "last_validated_date": "2025-01-21T18:50:21+00:00" + "last_validated_date": "2026-02-21T00:18:39+00:00", + "durations_in_seconds": { + "setup": 1.34, + "call": 0.54, + "teardown": 0.56, + "total": 2.44 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000000000020]": { - "last_validated_date": "2025-01-21T18:28:01+00:00" + "last_validated_date": "2026-02-21T00:18:42+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.1, + "teardown": 0.8, + "total": 1.38 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000]": { - "last_validated_date": "2025-01-21T18:28:00+00:00" + "last_validated_date": "2026-02-21T00:18:41+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.1, + "teardown": 0.78, + "total": 1.37 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[aa000000000$]": { - "last_validated_date": "2025-01-21T18:28:04+00:00" + "last_validated_date": "2026-02-21T00:18:45+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.1, + "teardown": 0.8, + "total": 1.42 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[abcd]": { - "last_validated_date": "2025-01-21T18:28:03+00:00" + "last_validated_date": "2026-02-21T00:18:43+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.1, + "teardown": 0.8, + "total": 1.4 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_single_character_trailing_slash": { - "last_validated_date": "2025-01-22T19:05:31+00:00" + "last_validated_date": "2026-02-21T00:17:41+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.83, + "teardown": 1.0, + "total": 3.34 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[a/%F0%9F%98%80/]": { - "last_validated_date": "2025-01-21T18:26:56+00:00" + "last_validated_date": "2026-02-21T00:17:38+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.9, + "teardown": 0.59, + "total": 2.0 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[file%2Fname]": { - "last_validated_date": "2025-01-21T18:26:42+00:00" + "last_validated_date": "2026-02-21T00:17:23+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.93, + "teardown": 0.56, + "total": 1.99 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key//]": { - "last_validated_date": "2025-01-21T18:26:52+00:00" + "last_validated_date": "2026-02-21T00:17:34+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.9, + "teardown": 0.63, + "total": 2.06 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key/]": { - "last_validated_date": "2025-01-21T18:26:50+00:00" - }, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123/]": { - "last_validated_date": "2025-01-21T18:26:54+00:00" + "last_validated_date": "2026-02-21T00:17:32+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.9, + "teardown": 0.61, + "total": 2.02 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123/]": { + "last_validated_date": "2026-02-21T00:17:36+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.89, + "teardown": 0.6, + "total": 2.01 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123]": { - "last_validated_date": "2025-01-21T18:26:46+00:00" + "last_validated_date": "2026-02-21T00:17:28+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.97, + "teardown": 0.59, + "total": 2.09 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%percent]": { - "last_validated_date": "2025-01-21T18:26:48+00:00" + "last_validated_date": "2026-02-21T00:17:30+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.89, + "teardown": 0.61, + "total": 2.04 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test@key/]": { - "last_validated_date": "2025-01-21T18:26:44+00:00" + "last_validated_date": "2026-02-21T00:17:25+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.87, + "teardown": 0.62, + "total": 2.01 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_acl_on_delete_marker": { - "last_validated_date": "2025-01-21T18:31:40+00:00" + "last_validated_date": "2026-02-21T00:21:39+00:00", + "durations_in_seconds": { + "setup": 0.86, + "call": 1.31, + "teardown": 0.97, + "total": 3.14 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_checksum[CRC32C]": { "last_validated_date": "2025-01-21T18:28:18+00:00" @@ -273,67 +813,223 @@ "last_validated_date": "2025-01-21T18:28:23+00:00" }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[DEEP_ARCHIVE-False]": { - "last_validated_date": "2025-01-21T18:41:32+00:00" + "last_validated_date": "2026-02-21T00:28:30+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.46, + "teardown": 0.97, + "total": 1.96 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER-False]": { - "last_validated_date": "2025-01-21T18:41:22+00:00" + "last_validated_date": "2026-02-21T00:28:20+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.47, + "teardown": 1.0, + "total": 2.01 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER_IR-True]": { - "last_validated_date": "2025-01-21T18:41:24+00:00" + "last_validated_date": "2026-02-21T00:28:22+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.48, + "teardown": 0.75, + "total": 1.76 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[INTELLIGENT_TIERING-True]": { - "last_validated_date": "2025-01-21T18:41:30+00:00" + "last_validated_date": "2026-02-21T00:28:28+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.52, + "teardown": 0.99, + "total": 2.02 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[ONEZONE_IA-True]": { - "last_validated_date": "2025-01-21T18:41:28+00:00" + "last_validated_date": "2026-02-21T00:28:26+00:00", + "durations_in_seconds": { + "setup": 0.58, + "call": 0.52, + "teardown": 0.99, + "total": 2.09 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[REDUCED_REDUNDANCY-True]": { - "last_validated_date": "2025-01-21T18:41:26+00:00" + "last_validated_date": "2026-02-21T00:28:24+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.55, + "teardown": 1.06, + "total": 2.12 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD-True]": { - "last_validated_date": "2025-01-21T18:41:18+00:00" + "last_validated_date": "2026-02-21T00:28:16+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.49, + "teardown": 0.97, + "total": 1.98 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD_IA-True]": { - "last_validated_date": "2025-01-21T18:41:20+00:00" + "last_validated_date": "2026-02-21T00:28:18+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.47, + "teardown": 0.97, + "total": 1.97 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class_outposts": { - "last_validated_date": "2025-01-21T18:41:34+00:00" + "last_validated_date": "2026-02-21T00:28:32+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.45, + "teardown": 0.84, + "total": 1.83 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_tagging_empty_list": { - "last_validated_date": "2025-01-21T18:28:08+00:00" + "last_validated_date": "2026-02-21T00:18:52+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.88, + "teardown": 0.99, + "total": 2.39 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_putobject_with_multiple_keys": { - "last_validated_date": "2025-01-21T18:30:56+00:00" + "last_validated_date": "2026-02-21T00:21:14+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.45, + "teardown": 1.0, + "total": 1.98 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_range_header_body_length": { - "last_validated_date": "2025-01-21T18:30:58+00:00" + "last_validated_date": "2026-02-21T00:21:16+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.47, + "teardown": 0.94, + "total": 1.92 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_range_key_not_exists": { - "last_validated_date": "2025-01-21T18:27:47+00:00" + "last_validated_date": "2026-02-21T00:18:22+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.11, + "teardown": 0.58, + "total": 1.19 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_region_header_exists_outside_us_east_1": { - "last_validated_date": "2025-01-21T18:26:20+00:00" + "last_validated_date": "2026-02-21T00:16:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.27, + "teardown": 1.59, + "total": 3.86 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure": { - "last_validated_date": "2025-01-21T18:41:38+00:00" + "last_validated_date": "2026-02-21T00:28:36+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.36, + "teardown": 1.03, + "total": 3.91 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure_get_bucket_location[eu-central-1-eu-central-1-eu-central-1]": { + "last_validated_date": "2026-02-21T00:28:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.12, + "teardown": 0.85, + "total": 1.97 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure_get_bucket_location[eu-west-1-EU-EU]": { + "last_validated_date": "2026-02-21T00:28:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.01, + "teardown": 0.86, + "total": 1.87 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure_get_bucket_location[us-east-1-None-]": { + "last_validated_date": "2026-02-21T00:28:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.89, + "teardown": 0.61, + "total": 1.5 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure_get_obj_attrs": { + "last_validated_date": "2026-02-21T00:28:45+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.6, + "teardown": 1.08, + "total": 3.2 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_analytics_configurations": { - "last_validated_date": "2025-01-21T18:42:41+00:00" + "last_validated_date": "2026-02-21T00:28:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.73, + "teardown": 1.23, + "total": 3.97 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects": { - "last_validated_date": "2025-01-21T18:39:22+00:00" + "last_validated_date": "2026-02-21T00:27:50+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.74, + "teardown": 0.61, + "total": 2.85 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects_using_requests_with_acl": { "last_validated_date": "2023-08-03T02:23:41+00:00" }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_public_objects_using_requests": { - "last_validated_date": "2025-01-21T19:48:17+00:00" + "last_validated_date": "2026-02-21T00:27:48+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 1.41, + "teardown": 0.57, + "total": 2.93 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl": { - "last_validated_date": "2025-01-21T18:30:17+00:00" + "last_validated_date": "2026-02-21T00:20:36+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 1.38, + "teardown": 0.57, + "total": 2.86 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl_exceptions": { - "last_validated_date": "2025-01-21T18:30:22+00:00" + "last_validated_date": "2026-02-21T00:20:42+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 4.33, + "teardown": 0.81, + "total": 5.63 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_checksum_no_algorithm": { "last_validated_date": "2025-01-21T18:28:45+00:00" @@ -345,97 +1041,274 @@ "last_validated_date": "2025-01-21T18:28:42+00:00" }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_content_type_and_metadata": { - "last_validated_date": "2025-01-21T18:29:13+00:00" + "last_validated_date": "2026-02-21T00:19:22+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.2, + "teardown": 0.96, + "total": 2.66 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_directive_copy": { - "last_validated_date": "2025-01-21T18:28:52+00:00" + "last_validated_date": "2026-02-21T00:19:00+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.64, + "teardown": 0.97, + "total": 2.11 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_replace": { - "last_validated_date": "2025-01-21T18:28:50+00:00" + "last_validated_date": "2026-02-21T00:18:58+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.71, + "teardown": 0.97, + "total": 2.2 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place": { - "last_validated_date": "2025-01-21T18:29:17+00:00" + "last_validated_date": "2026-02-21T00:19:25+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 1.42, + "teardown": 1.16, + "total": 3.49 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_metadata_directive": { - "last_validated_date": "2025-01-21T18:29:37+00:00" + "last_validated_date": "2026-02-21T00:19:46+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.74, + "teardown": 0.95, + "total": 3.18 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_storage_class": { - "last_validated_date": "2025-01-21T18:29:28+00:00" + "last_validated_date": "2026-02-21T00:19:37+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.83, + "teardown": 0.92, + "total": 2.24 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_suspended_only": { - "last_validated_date": "2025-01-21T18:29:26+00:00" + "last_validated_date": "2026-02-21T00:19:35+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 1.95, + "teardown": 1.28, + "total": 4.15 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_versioned": { - "last_validated_date": "2025-01-21T18:29:22+00:00" + "last_validated_date": "2026-02-21T00:19:31+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 3.03, + "teardown": 1.33, + "total": 5.29 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_website_redirect_location": { - "last_validated_date": "2025-01-21T18:29:39+00:00" + "last_validated_date": "2026-02-21T00:19:48+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.65, + "teardown": 0.97, + "total": 2.11 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_with_encryption": { - "last_validated_date": "2025-01-21T18:29:31+00:00" - }, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_preconditions": { - "last_validated_date": "2025-01-21T18:29:56+00:00" + "last_validated_date": "2026-02-21T00:19:41+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.82, + "teardown": 1.12, + "total": 3.44 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_storage_class": { - "last_validated_date": "2025-01-21T18:29:41+00:00" + "last_validated_date": "2026-02-21T00:19:51+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.04, + "teardown": 1.16, + "total": 2.69 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32C]": { - "last_validated_date": "2025-01-24T19:06:31+00:00" + "last_validated_date": "2026-02-21T00:19:56+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.99, + "teardown": 0.98, + "total": 2.47 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32]": { - "last_validated_date": "2025-01-24T19:06:29+00:00" + "last_validated_date": "2026-02-21T00:19:53+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.94, + "teardown": 0.95, + "total": 2.4 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC64NVME]": { - "last_validated_date": "2025-01-24T19:06:38+00:00" + "last_validated_date": "2026-02-21T00:20:03+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.83, + "teardown": 0.93, + "total": 2.25 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA1]": { - "last_validated_date": "2025-01-24T19:06:34+00:00" + "last_validated_date": "2026-02-21T00:19:58+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.95, + "teardown": 0.97, + "total": 2.45 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA256]": { - "last_validated_date": "2025-01-24T19:06:36+00:00" + "last_validated_date": "2026-02-21T00:20:00+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.84, + "teardown": 0.94, + "total": 2.29 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32C]": { - "last_validated_date": "2025-03-17T22:17:06+00:00" + "last_validated_date": "2026-02-21T00:20:08+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.95, + "teardown": 0.92, + "total": 2.39 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32]": { - "last_validated_date": "2025-03-17T22:17:03+00:00" + "last_validated_date": "2026-02-21T00:20:05+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.19, + "teardown": 0.94, + "total": 2.66 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC64NVME]": { - "last_validated_date": "2025-03-17T22:17:13+00:00" + "last_validated_date": "2026-02-21T00:20:15+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.97, + "teardown": 0.96, + "total": 2.42 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA1]": { - "last_validated_date": "2025-03-17T22:17:08+00:00" + "last_validated_date": "2026-02-21T00:20:10+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.13, + "teardown": 0.93, + "total": 2.56 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA256]": { - "last_validated_date": "2025-03-17T22:17:11+00:00" + "last_validated_date": "2026-02-21T00:20:13+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.13, + "teardown": 0.96, + "total": 2.58 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_wrong_format": { - "last_validated_date": "2025-01-21T18:29:58+00:00" + "last_validated_date": "2026-02-21T00:20:17+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.22, + "teardown": 0.82, + "total": 1.57 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[COPY]": { - "last_validated_date": "2025-01-21T18:28:54+00:00" + "last_validated_date": "2026-02-21T00:19:03+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.97, + "teardown": 0.97, + "total": 2.43 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[None]": { - "last_validated_date": "2025-01-21T18:28:59+00:00" + "last_validated_date": "2026-02-21T00:19:07+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.87, + "teardown": 1.01, + "total": 2.36 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[REPLACE]": { - "last_validated_date": "2025-01-21T18:28:57+00:00" + "last_validated_date": "2026-02-21T00:19:05+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.91, + "teardown": 0.97, + "total": 2.39 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[COPY]": { - "last_validated_date": "2025-01-21T18:29:02+00:00" + "last_validated_date": "2026-02-21T00:19:11+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.91, + "teardown": 1.58, + "total": 3.98 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[None]": { - "last_validated_date": "2025-01-21T18:29:10+00:00" + "last_validated_date": "2026-02-21T00:19:19+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 2.16, + "teardown": 1.45, + "total": 4.11 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[REPLACE]": { - "last_validated_date": "2025-01-21T18:29:06+00:00" + "last_validated_date": "2026-02-21T00:19:15+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.88, + "teardown": 1.52, + "total": 3.93 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_object_with_version_id": { - "last_validated_date": "2025-01-21T18:39:10+00:00" + "last_validated_date": "2026-02-21T00:27:39+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 1.72, + "teardown": 1.31, + "total": 3.77 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_download_object_with_lambda": { - "last_validated_date": "2025-01-21T18:32:19+00:00" + "last_validated_date": "2026-02-21T00:22:21+00:00", + "durations_in_seconds": { + "setup": 11.31, + "call": 21.61, + "teardown": 1.89, + "total": 34.81 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_checksum[CRC32C]": { "last_validated_date": "2025-01-21T18:28:29+00:00" @@ -456,831 +1329,1938 @@ "last_validated_date": "2025-01-21T18:28:35+00:00" }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_header_overrides": { - "last_validated_date": "2025-01-21T18:39:23+00:00" + "last_validated_date": "2026-02-21T00:27:52+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.34, + "teardown": 0.99, + "total": 1.86 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_headers": { - "last_validated_date": "2025-01-21T18:42:49+00:00" + "last_validated_date": "2026-02-21T00:29:05+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 2.33, + "teardown": 1.08, + "total": 3.96 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[get_object]": { - "last_validated_date": "2025-01-21T18:30:04+00:00" + "last_validated_date": "2026-02-21T00:20:23+00:00", + "durations_in_seconds": { + "setup": 0.61, + "call": 4.99, + "teardown": 0.97, + "total": 6.57 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[head_object]": { - "last_validated_date": "2025-01-21T18:30:10+00:00" + "last_validated_date": "2026-02-21T00:20:29+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 4.53, + "teardown": 0.94, + "total": 5.99 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_intelligent_tier_config": { - "last_validated_date": "2025-01-21T19:48:46+00:00" + "last_validated_date": "2026-02-21T00:29:01+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.95, + "teardown": 0.62, + "total": 3.1 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_invalid_content_md5": { - "last_validated_date": "2025-01-21T18:32:49+00:00" + "last_validated_date": "2026-02-21T00:22:48+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 22.94, + "teardown": 0.99, + "total": 24.46 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_inventory_report_crud": { - "last_validated_date": "2025-01-21T18:42:52+00:00" + "last_validated_date": "2026-02-21T00:29:08+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.07, + "teardown": 1.19, + "total": 3.27 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_lambda_integration": { - "last_validated_date": "2025-01-21T18:34:07+00:00" + "last_validated_date": "2026-02-21T00:23:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.0, + "teardown": 1.95, + "total": 15.95 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_acls": { - "last_validated_date": "2025-01-21T18:30:13+00:00" + "last_validated_date": "2026-02-21T00:20:33+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 1.97, + "teardown": 0.97, + "total": 3.88 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_sse": { - "last_validated_date": "2025-03-17T21:31:09+00:00" + "last_validated_date": "2026-02-21T00:28:50+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.27, + "teardown": 1.11, + "total": 2.91 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl": { - "last_validated_date": "2025-01-21T18:30:26+00:00" + "last_validated_date": "2026-02-21T00:20:46+00:00", + "durations_in_seconds": { + "setup": 0.92, + "call": 2.26, + "teardown": 0.93, + "total": 4.11 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl_exceptions": { - "last_validated_date": "2025-01-21T18:30:32+00:00" + "last_validated_date": "2026-02-21T00:20:52+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 4.84, + "teardown": 1.13, + "total": 6.47 + } }, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expiry": { - "last_validated_date": "2025-01-21T18:30:37+00:00" + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expires": { + "last_validated_date": "2026-02-21T00:20:55+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.19, + "teardown": 0.94, + "total": 2.63 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_inventory_report_exceptions": { - "last_validated_date": "2025-01-21T18:42:57+00:00" + "last_validated_date": "2026-02-21T00:29:13+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.11, + "teardown": 1.44, + "total": 4.56 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_more_than_1000_items": { - "last_validated_date": "2025-01-21T18:38:06+00:00" + "last_validated_date": "2026-02-21T00:27:26+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 224.74, + "teardown": 3.2, + "total": 228.46 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_object_versioned": { - "last_validated_date": "2025-01-21T18:39:15+00:00" + "last_validated_date": "2026-02-21T00:27:45+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 3.69, + "teardown": 1.47, + "total": 5.69 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer": { - "last_validated_date": "2025-01-21T18:31:42+00:00" + "last_validated_date": "2026-02-21T00:21:41+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.29, + "teardown": 0.6, + "total": 1.4 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer_exceptions": { - "last_validated_date": "2025-01-21T18:31:43+00:00" + "last_validated_date": "2026-02-21T00:21:42+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.44, + "teardown": 0.8, + "total": 1.73 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_bucket_key_default": { - "last_validated_date": "2025-01-21T18:42:37+00:00" + "last_validated_date": "2026-02-21T00:28:54+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.92, + "teardown": 1.1, + "total": 3.53 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_default_kms_key": { - "last_validated_date": "2023-04-03T20:16:19+00:00" + "last_validated_date": "2026-02-21T00:43:39+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 2.77, + "teardown": 1.97, + "total": 5.22 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key": { - "last_validated_date": "2025-01-21T18:39:28+00:00" + "last_validated_date": "2026-02-21T00:27:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.01, + "teardown": 1.81, + "total": 5.82 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key_state": { - "last_validated_date": "2025-01-21T18:39:38+00:00" + "last_validated_date": "2026-02-21T00:28:08+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 7.57, + "teardown": 1.35, + "total": 9.45 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_timestamp_precision": { - "last_validated_date": "2025-01-21T18:41:40+00:00" + "last_validated_date": "2026-02-21T00:28:47+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.06, + "teardown": 1.0, + "total": 2.59 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_upload_download_gzip": { - "last_validated_date": "2025-01-21T18:32:51+00:00" + "last_validated_date": "2026-02-21T00:22:50+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.34, + "teardown": 0.96, + "total": 1.82 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_bucket_name": { - "last_validated_date": "2025-01-21T18:34:09+00:00" + "last_validated_date": "2026-02-21T00:23:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.41, + "teardown": 0.01, + "total": 0.42 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_key_names": { - "last_validated_date": "2025-01-21T18:31:47+00:00" + "last_validated_date": "2026-02-21T00:21:47+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.18, + "teardown": 0.96, + "total": 2.15 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_set_external_hostname": { - "last_validated_date": "2025-03-17T21:30:10+00:00" + "last_validated_date": "2026-02-21T00:23:07+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 1.05, + "teardown": 0.92, + "total": 2.9 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_system_metadata_with_unicode": { + "last_validated_date": "2026-02-21T00:17:07+00:00", + "durations_in_seconds": { + "setup": 0.58, + "call": 0.42, + "teardown": 0.99, + "total": 1.99 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_upload_big_file": { - "last_validated_date": "2025-01-21T18:39:01+00:00" + "last_validated_date": "2026-02-21T00:27:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.52, + "teardown": 0.97, + "total": 3.49 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_multipart": { - "last_validated_date": "2025-01-21T18:26:40+00:00" + "last_validated_date": "2026-02-21T00:17:21+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.81, + "teardown": 0.94, + "total": 2.25 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_with_xml_preamble": { - "last_validated_date": "2025-01-21T18:30:40+00:00" + "last_validated_date": "2026-02-21T00:20:57+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.42, + "teardown": 0.99, + "total": 1.94 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[False]": { - "last_validated_date": "2025-01-21T18:27:09+00:00" + "last_validated_date": "2026-02-21T00:17:57+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.43, + "teardown": 1.01, + "total": 2.96 + } }, "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[True]": { - "last_validated_date": "2025-01-21T18:27:06+00:00" + "last_validated_date": "2026-02-21T00:17:54+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.35, + "teardown": 1.17, + "total": 3.03 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_user_metadata_rfc2047_bad_b64_encoded": { + "last_validated_date": "2026-02-21T00:17:11+00:00", + "durations_in_seconds": { + "setup": 0.72, + "call": 0.33, + "teardown": 0.75, + "total": 1.8 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3::test_user_metadata_rfc2047_encoded": { + "last_validated_date": "2026-02-21T00:17:09+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.34, + "teardown": 0.98, + "total": 1.83 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_date": { - "last_validated_date": "2025-01-21T18:18:26+00:00" + "last_validated_date": "2026-02-21T00:33:24+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.39, + "teardown": 0.63, + "total": 1.54 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry": { - "last_validated_date": "2025-01-21T18:18:28+00:00" + "last_validated_date": "2026-02-21T00:33:27+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.84, + "teardown": 1.02, + "total": 2.38 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry_versioned": { - "last_validated_date": "2025-01-21T18:18:31+00:00" + "last_validated_date": "2026-02-21T00:33:30+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.54, + "teardown": 1.45, + "total": 3.49 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_multiple_rules": { - "last_validated_date": "2025-01-21T18:18:36+00:00" + "last_validated_date": "2026-02-21T00:33:35+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.09, + "teardown": 1.08, + "total": 2.69 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_object_size_rules": { - "last_validated_date": "2025-01-21T18:18:38+00:00" + "last_validated_date": "2026-02-21T00:33:38+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.08, + "teardown": 1.04, + "total": 2.64 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_tag_rules": { - "last_validated_date": "2025-01-21T18:18:42+00:00" + "last_validated_date": "2026-02-21T00:33:42+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 2.53, + "teardown": 1.08, + "total": 4.14 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_bucket_lifecycle_configuration": { - "last_validated_date": "2025-01-21T18:18:18+00:00" + "last_validated_date": "2026-02-21T00:33:16+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.01, + "teardown": 0.6, + "total": 2.11 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_lifecycle_configuration_on_bucket_deletion": { - "last_validated_date": "2025-01-21T18:18:20+00:00" + "last_validated_date": "2026-02-21T00:33:18+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.68, + "teardown": 0.86, + "total": 2.55 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_lifecycle_expired_object_delete_marker": { - "last_validated_date": "2025-01-21T18:18:44+00:00" + "last_validated_date": "2026-02-21T00:33:44+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.72, + "teardown": 1.0, + "total": 2.24 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_object_expiry_after_bucket_lifecycle_configuration": { - "last_validated_date": "2025-01-21T18:18:33+00:00" + "last_validated_date": "2026-02-21T00:33:33+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.06, + "teardown": 1.03, + "total": 2.6 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_put_bucket_lifecycle_conf_exc": { - "last_validated_date": "2025-01-21T18:18:24+00:00" + "last_validated_date": "2026-02-21T00:33:23+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.83, + "teardown": 0.84, + "total": 4.19 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_s3_transition_default_minimum_object_size": { - "last_validated_date": "2025-02-03T10:15:22+00:00" + "last_validated_date": "2026-02-21T00:33:47+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.35, + "teardown": 0.84, + "total": 2.71 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging": { - "last_validated_date": "2023-08-12T17:54:07+00:00" + "last_validated_date": "2026-02-21T00:34:55+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.77, + "teardown": 2.27, + "total": 4.05 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_accept_wrong_grants": { - "last_validated_date": "2023-08-03T02:26:11+00:00" + "last_validated_date": "2026-02-21T00:34:58+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.63, + "teardown": 1.2, + "total": 2.84 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_cross_locations": { - "last_validated_date": "2024-08-29T15:58:14+00:00" + "last_validated_date": "2026-02-21T00:35:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.52, + "teardown": 2.68, + "total": 5.2 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_wrong_target": { - "last_validated_date": "2024-08-30T11:31:48+00:00" + "last_validated_date": "2026-02-21T00:35:02+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.54, + "teardown": 2.27, + "total": 3.82 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config": { - "last_validated_date": "2024-08-29T14:09:55+00:00" + "last_validated_date": "2026-02-21T00:35:19+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 4.93, + "teardown": 1.95, + "total": 6.89 + } }, "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config_without_filter": { - "last_validated_date": "2023-08-03T02:13:02+00:00" + "last_validated_date": "2026-02-21T00:35:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.21, + "teardown": 1.59, + "total": 4.8 + } }, "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_s3_get_deep_archive_object_restore": { - "last_validated_date": "2023-08-14T20:35:53+00:00" + "last_validated_date": "2026-02-21T00:32:23+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.43, + "teardown": 0.95, + "total": 2.39 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_storage_class_deep_archive": { + "last_validated_date": "2026-02-21T00:32:21+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.48, + "teardown": 0.96, + "total": 2.95 + } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32C]": { - "last_validated_date": "2025-07-07T17:40:11+00:00", + "last_validated_date": "2026-02-21T00:38:48+00:00", "durations_in_seconds": { - "setup": 0.63, - "call": 18.57, - "teardown": 1.06, - "total": 20.26 + "setup": 0.5, + "call": 7.11, + "teardown": 0.77, + "total": 8.38 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32]": { - "last_validated_date": "2025-07-07T17:39:51+00:00", + "last_validated_date": "2026-02-21T00:38:40+00:00", "durations_in_seconds": { - "setup": 0.99, - "call": 17.13, - "teardown": 1.18, - "total": 19.3 + "setup": 0.57, + "call": 6.55, + "teardown": 1.01, + "total": 8.13 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA1]": { - "last_validated_date": "2025-07-07T17:40:51+00:00", + "last_validated_date": "2026-02-21T00:38:55+00:00", "durations_in_seconds": { "setup": 0.56, - "call": 38.2, - "teardown": 1.57, - "total": 40.33 + "call": 5.85, + "teardown": 0.76, + "total": 7.17 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA256]": { - "last_validated_date": "2025-07-07T17:41:11+00:00", + "last_validated_date": "2026-02-21T00:39:03+00:00", "durations_in_seconds": { - "setup": 0.67, - "call": 17.67, - "teardown": 1.12, - "total": 19.46 + "setup": 0.51, + "call": 5.75, + "teardown": 1.0, + "total": 7.26 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_default": { - "last_validated_date": "2025-06-15T17:12:57+00:00", + "last_validated_date": "2026-02-21T00:42:10+00:00", "durations_in_seconds": { - "setup": 0.59, - "call": 2.92, - "teardown": 0.99, - "total": 4.5 + "setup": 0.55, + "call": 2.97, + "teardown": 0.97, + "total": 4.49 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32C]": { - "last_validated_date": "2025-07-07T17:41:54+00:00", + "last_validated_date": "2026-02-21T00:40:58+00:00", "durations_in_seconds": { - "setup": 0.74, - "call": 17.12, - "teardown": 1.01, - "total": 18.87 + "setup": 0.54, + "call": 5.34, + "teardown": 1.04, + "total": 6.92 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32]": { - "last_validated_date": "2025-07-07T17:41:35+00:00", + "last_validated_date": "2026-02-21T00:40:51+00:00", "durations_in_seconds": { - "setup": 1.26, - "call": 16.39, - "teardown": 1.06, - "total": 18.71 + "setup": 0.53, + "call": 5.27, + "teardown": 1.01, + "total": 6.81 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC64NVME]": { - "last_validated_date": "2025-07-07T17:42:05+00:00", + "last_validated_date": "2026-02-21T00:41:05+00:00", "durations_in_seconds": { - "setup": 0.66, - "call": 9.59, - "teardown": 1.14, - "total": 11.39 + "setup": 0.54, + "call": 5.8, + "teardown": 1.0, + "total": 7.34 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object_default": { - "last_validated_date": "2025-06-15T17:12:59+00:00", + "last_validated_date": "2026-02-21T00:42:12+00:00", "durations_in_seconds": { - "setup": 0.5, - "call": 0.92, - "teardown": 1.0, - "total": 2.42 + "setup": 0.51, + "call": 0.94, + "teardown": 0.95, + "total": 2.4 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32C]": { - "last_validated_date": "2025-06-15T17:09:51+00:00", + "last_validated_date": "2026-02-21T00:39:05+00:00", "durations_in_seconds": { - "setup": 0.5, + "setup": 0.51, "call": 0.12, - "teardown": 0.61, + "teardown": 0.6, "total": 1.23 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32]": { - "last_validated_date": "2025-06-15T17:09:50+00:00", + "last_validated_date": "2026-02-21T00:39:04+00:00", "durations_in_seconds": { - "setup": 0.48, + "setup": 0.54, "call": 0.12, - "teardown": 0.56, - "total": 1.16 + "teardown": 0.59, + "total": 1.25 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC64NVME]": { - "last_validated_date": "2025-06-15T17:09:55+00:00", + "last_validated_date": "2026-02-21T00:39:09+00:00", "durations_in_seconds": { - "setup": 0.56, + "setup": 0.52, "call": 0.11, - "teardown": 0.77, - "total": 1.44 + "teardown": 0.82, + "total": 1.45 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA1]": { - "last_validated_date": "2025-06-15T17:09:52+00:00", + "last_validated_date": "2026-02-21T00:39:07+00:00", "durations_in_seconds": { - "setup": 0.57, + "setup": 0.51, "call": 0.14, - "teardown": 0.58, - "total": 1.29 + "teardown": 0.62, + "total": 1.27 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA256]": { - "last_validated_date": "2025-06-15T17:09:54+00:00", + "last_validated_date": "2026-02-21T00:39:08+00:00", "durations_in_seconds": { - "setup": 0.48, - "call": 0.14, - "teardown": 0.57, - "total": 1.19 + "setup": 0.58, + "call": 0.13, + "teardown": 0.59, + "total": 1.3 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32C]": { - "last_validated_date": "2025-06-15T17:09:58+00:00", + "last_validated_date": "2026-02-21T00:39:12+00:00", "durations_in_seconds": { - "setup": 0.64, + "setup": 0.5, "call": 0.13, - "teardown": 0.64, - "total": 1.41 + "teardown": 0.63, + "total": 1.26 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32]": { - "last_validated_date": "2025-06-15T17:09:56+00:00", + "last_validated_date": "2026-02-21T00:39:11+00:00", "durations_in_seconds": { "setup": 0.51, - "call": 0.13, - "teardown": 0.64, - "total": 1.28 + "call": 0.14, + "teardown": 0.6, + "total": 1.25 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC64NVME]": { - "last_validated_date": "2025-06-15T17:10:02+00:00", + "last_validated_date": "2026-02-21T00:39:16+00:00", "durations_in_seconds": { - "setup": 0.51, - "call": 0.15, - "teardown": 0.64, - "total": 1.3 + "setup": 0.53, + "call": 0.13, + "teardown": 0.65, + "total": 1.31 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA1]": { - "last_validated_date": "2025-06-15T17:09:59+00:00", + "last_validated_date": "2026-02-21T00:39:13+00:00", "durations_in_seconds": { - "setup": 0.51, - "call": 0.11, - "teardown": 0.91, - "total": 1.53 + "setup": 0.5, + "call": 0.12, + "teardown": 0.94, + "total": 1.56 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA256]": { - "last_validated_date": "2025-06-15T17:10:01+00:00", + "last_validated_date": "2026-02-21T00:39:15+00:00", "durations_in_seconds": { "setup": 0.51, - "call": 0.12, + "call": 0.14, "teardown": 0.83, - "total": 1.46 + "total": 1.48 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32C]": { - "last_validated_date": "2025-06-15T17:10:05+00:00", + "last_validated_date": "2026-02-21T00:39:19+00:00", "durations_in_seconds": { - "setup": 0.57, - "call": 0.13, + "setup": 0.53, + "call": 0.14, "teardown": 0.63, - "total": 1.33 + "total": 1.3 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32]": { - "last_validated_date": "2025-06-15T17:10:03+00:00", + "last_validated_date": "2026-02-21T00:39:17+00:00", "durations_in_seconds": { - "setup": 0.49, - "call": 0.12, - "teardown": 0.6, - "total": 1.21 + "setup": 0.54, + "call": 0.13, + "teardown": 0.65, + "total": 1.32 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC64NVME]": { - "last_validated_date": "2025-06-15T17:10:08+00:00", + "last_validated_date": "2026-02-21T00:39:23+00:00", "durations_in_seconds": { - "setup": 0.47, - "call": 0.11, - "teardown": 0.57, - "total": 1.15 + "setup": 0.58, + "call": 0.14, + "teardown": 0.72, + "total": 1.44 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA1]": { - "last_validated_date": "2025-06-15T17:10:06+00:00", + "last_validated_date": "2026-02-21T00:39:20+00:00", "durations_in_seconds": { - "setup": 0.59, - "call": 0.14, - "teardown": 0.6, - "total": 1.33 + "setup": 0.53, + "call": 0.12, + "teardown": 0.59, + "total": 1.24 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA256]": { - "last_validated_date": "2025-06-15T17:10:07+00:00", + "last_validated_date": "2026-02-21T00:39:21+00:00", "durations_in_seconds": { - "setup": 0.5, - "call": 0.11, - "teardown": 0.57, - "total": 1.18 + "setup": 0.51, + "call": 0.13, + "teardown": 0.61, + "total": 1.25 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_composite": { - "last_validated_date": "2025-06-15T17:11:25+00:00", + "last_validated_date": "2026-02-21T00:40:44+00:00", "durations_in_seconds": { - "setup": 0.52, - "call": 12.45, - "teardown": 0.89, - "total": 13.86 + "setup": 0.55, + "call": 13.23, + "teardown": 0.86, + "total": 14.64 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_full_object": { - "last_validated_date": "2025-06-15T17:12:52+00:00", + "last_validated_date": "2026-02-21T00:42:06+00:00", "durations_in_seconds": { - "setup": 0.49, - "call": 42.48, - "teardown": 1.16, - "total": 44.13 + "setup": 0.52, + "call": 58.81, + "teardown": 1.05, + "total": 60.38 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_size_validation": { - "last_validated_date": "2025-06-15T17:13:02+00:00", + "last_validated_date": "2026-02-21T00:42:15+00:00", "durations_in_seconds": { - "setup": 0.53, - "call": 1.14, - "teardown": 1.03, - "total": 2.7 + "setup": 0.51, + "call": 1.15, + "teardown": 0.95, + "total": 2.61 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32C]": { - "last_validated_date": "2025-06-15T17:10:28+00:00", + "last_validated_date": "2026-02-21T00:39:49+00:00", "durations_in_seconds": { - "setup": 0.47, - "call": 9.47, - "teardown": 0.9, - "total": 10.84 + "setup": 0.53, + "call": 9.99, + "teardown": 0.85, + "total": 11.37 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32]": { - "last_validated_date": "2025-06-15T17:10:18+00:00", + "last_validated_date": "2026-02-21T00:39:38+00:00", "durations_in_seconds": { - "setup": 0.46, - "call": 8.02, - "teardown": 0.84, - "total": 9.32 + "setup": 0.55, + "call": 13.52, + "teardown": 0.85, + "total": 14.92 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC64NVME]": { - "last_validated_date": "2025-06-15T17:11:11+00:00", + "last_validated_date": "2026-02-21T00:40:29+00:00", "durations_in_seconds": { - "setup": 0.52, - "call": 9.39, - "teardown": 0.81, - "total": 10.72 + "setup": 0.54, + "call": 11.7, + "teardown": 0.86, + "total": 13.1 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA1]": { - "last_validated_date": "2025-06-15T17:10:44+00:00", + "last_validated_date": "2026-02-21T00:40:03+00:00", "durations_in_seconds": { - "setup": 0.52, - "call": 14.71, - "teardown": 0.86, - "total": 16.09 + "setup": 0.53, + "call": 12.43, + "teardown": 0.9, + "total": 13.86 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA256]": { - "last_validated_date": "2025-06-15T17:11:00+00:00", + "last_validated_date": "2026-02-21T00:40:16+00:00", "durations_in_seconds": { - "setup": 0.62, - "call": 14.22, - "teardown": 0.81, - "total": 15.65 + "setup": 0.55, + "call": 11.88, + "teardown": 0.86, + "total": 13.29 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[COMPOSITE]": { - "last_validated_date": "2025-06-16T10:53:40+00:00", + "last_validated_date": "2026-02-21T00:42:18+00:00", "durations_in_seconds": { - "setup": 0.97, - "call": 1.5, - "teardown": 1.06, - "total": 3.53 + "setup": 0.52, + "call": 1.46, + "teardown": 0.98, + "total": 2.96 } }, "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[FULL_OBJECT]": { - "last_validated_date": "2025-06-16T10:53:43+00:00", + "last_validated_date": "2026-02-21T00:42:22+00:00", "durations_in_seconds": { - "setup": 0.52, - "call": 1.4, - "teardown": 0.94, - "total": 2.86 + "setup": 0.55, + "call": 1.55, + "teardown": 1.98, + "total": 4.08 } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_delete_locked_object": { - "last_validated_date": "2025-01-21T18:17:15+00:00" + "last_validated_date": "2026-02-21T00:34:45+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.49, + "teardown": 1.34, + "total": 2.84 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_get_object_legal_hold": { - "last_validated_date": "2025-01-21T18:17:06+00:00" + "last_validated_date": "2026-02-21T00:34:35+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.33, + "teardown": 1.22, + "total": 2.56 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_legal_hold_exc": { - "last_validated_date": "2025-01-21T18:17:12+00:00" + "last_validated_date": "2026-02-21T00:34:42+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.56, + "teardown": 1.83, + "total": 4.4 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_with_legal_hold": { - "last_validated_date": "2025-01-21T18:17:08+00:00" + "last_validated_date": "2026-02-21T00:34:38+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.01, + "teardown": 1.39, + "total": 2.41 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_copy_object_legal_hold": { - "last_validated_date": "2025-01-21T18:17:21+00:00" + "last_validated_date": "2026-02-21T00:34:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.46, + "teardown": 1.49, + "total": 2.95 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_legal_hold_lock_versioned": { - "last_validated_date": "2025-01-21T18:17:18+00:00" + "last_validated_date": "2026-02-21T00:34:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.03, + "teardown": 1.42, + "total": 3.45 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_bucket_config_default_retention": { - "last_validated_date": "2025-06-20T17:35:53+00:00", + "last_validated_date": "2026-02-21T00:34:12+00:00", "durations_in_seconds": { - "setup": 0.48, - "call": 1.55, - "teardown": 1.78, - "total": 3.81 + "setup": 0.0, + "call": 1.47, + "teardown": 1.55, + "total": 3.02 } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_delete_markers": { - "last_validated_date": "2025-01-21T18:18:05+00:00" + "last_validated_date": "2026-02-21T00:34:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.26, + "teardown": 0.98, + "total": 2.24 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_extend_duration": { - "last_validated_date": "2025-01-21T18:18:07+00:00" + "last_validated_date": "2026-02-21T00:34:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.16, + "teardown": 1.28, + "total": 2.44 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_copy_object_retention_lock": { - "last_validated_date": "2025-01-21T18:18:00+00:00" + "last_validated_date": "2026-02-21T00:34:09+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.18, + "teardown": 1.49, + "total": 2.68 + } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_lock_mode_validation": { - "last_validated_date": "2025-06-20T17:33:41+00:00", + "last_validated_date": "2026-02-21T00:34:33+00:00", "durations_in_seconds": { - "setup": 0.43, - "call": 1.68, - "teardown": 0.86, - "total": 2.97 + "setup": 0.01, + "call": 2.11, + "teardown": 0.87, + "total": 2.99 } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention": { - "last_validated_date": "2025-06-20T17:02:02+00:00", + "last_validated_date": "2026-02-21T00:34:06+00:00", "durations_in_seconds": { - "setup": 0.47, - "call": 12.42, + "setup": 0.0, + "call": 12.36, "teardown": 0.62, - "total": 13.51 + "total": 12.98 } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_compliance_mode": { - "last_validated_date": "2025-06-20T17:19:57+00:00", + "last_validated_date": "2026-02-21T00:34:30+00:00", "durations_in_seconds": { - "setup": 0.44, - "call": 11.7, - "teardown": 1.29, - "total": 13.43 + "setup": 0.0, + "call": 11.75, + "teardown": 1.02, + "total": 12.77 } }, "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_exc": { - "last_validated_date": "2025-06-20T16:29:08+00:00", + "last_validated_date": "2026-02-21T00:33:53+00:00", "durations_in_seconds": { - "setup": 0.5, - "call": 3.38, - "teardown": 2.69, - "total": 6.57 + "setup": 0.0, + "call": 3.57, + "teardown": 2.64, + "total": 6.21 } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_default_checksum": { - "last_validated_date": "2025-03-17T21:46:24+00:00" + "last_validated_date": "2026-02-21T00:35:56+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.45, + "teardown": 0.95, + "total": 1.91 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3]": { - "last_validated_date": "2025-03-28T19:11:34+00:00" + "last_validated_date": "2026-02-21T00:36:26+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.71, + "teardown": 0.95, + "total": 2.17 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3v4]": { - "last_validated_date": "2025-03-28T19:11:36+00:00" + "last_validated_date": "2026-02-21T00:36:28+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.72, + "teardown": 0.96, + "total": 2.42 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": { - "last_validated_date": "2025-03-17T20:16:55+00:00" + "last_validated_date": "2026-02-21T00:36:03+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 2.5, + "teardown": 0.93, + "total": 3.93 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": { - "last_validated_date": "2025-03-17T20:16:58+00:00" + "last_validated_date": "2026-02-21T00:36:06+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.77, + "teardown": 0.92, + "total": 3.19 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": { - "last_validated_date": "2025-03-17T20:17:02+00:00" + "last_validated_date": "2026-02-21T00:36:09+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 2.07, + "teardown": 0.91, + "total": 3.47 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_file_as_string": { - "last_validated_date": "2025-03-17T20:16:51+00:00" + "last_validated_date": "2026-02-21T00:35:59+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.89, + "teardown": 0.93, + "total": 2.32 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_files": { + "last_validated_date": "2026-02-21T00:35:21+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 0.72, + "teardown": 0.91, + "total": 2.47 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_metadata": { - "last_validated_date": "2025-03-17T21:56:03+00:00" + "last_validated_date": "2026-02-21T00:35:49+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.44, + "teardown": 0.93, + "total": 1.88 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_storage_class": { - "last_validated_date": "2025-03-17T20:16:47+00:00" + "last_validated_date": "2026-02-21T00:35:53+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.78, + "teardown": 0.94, + "total": 2.22 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[invalid]": { - "last_validated_date": "2025-03-17T20:16:41+00:00" + "last_validated_date": "2026-02-21T00:35:45+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.47, + "teardown": 0.95, + "total": 1.95 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[list]": { - "last_validated_date": "2025-03-17T20:16:39+00:00" + "last_validated_date": "2026-02-21T00:35:43+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.48, + "teardown": 0.97, + "total": 1.96 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[notxml]": { - "last_validated_date": "2025-03-17T20:16:43+00:00" + "last_validated_date": "2026-02-21T00:35:47+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.44, + "teardown": 0.58, + "total": 1.54 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[single]": { - "last_validated_date": "2025-03-17T20:16:38+00:00" + "last_validated_date": "2026-02-21T00:35:42+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.49, + "teardown": 1.07, + "total": 2.06 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_unicode_metadata": { + "last_validated_date": "2026-02-21T00:35:51+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.51, + "teardown": 0.92, + "total": 1.92 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_wrong_content_type": { - "last_validated_date": "2025-03-17T20:16:49+00:00" + "last_validated_date": "2026-02-21T00:35:54+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.3, + "teardown": 0.58, + "total": 1.39 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_expires": { - "last_validated_date": "2025-03-17T20:16:24+00:00" + "last_validated_date": "2026-02-21T00:35:26+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 3.33, + "teardown": 0.6, + "total": 4.41 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3]": { - "last_validated_date": "2025-03-17T20:16:26+00:00" + "last_validated_date": "2026-02-21T00:35:27+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.37, + "teardown": 0.59, + "total": 1.48 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3v4]": { - "last_validated_date": "2025-03-17T20:16:27+00:00" + "last_validated_date": "2026-02-21T00:35:29+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.36, + "teardown": 0.59, + "total": 1.46 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3]": { - "last_validated_date": "2025-03-17T20:16:32+00:00" + "last_validated_date": "2026-02-21T00:35:34+00:00", + "durations_in_seconds": { + "setup": 0.89, + "call": 0.61, + "teardown": 0.61, + "total": 2.11 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3v4]": { - "last_validated_date": "2025-03-17T20:16:34+00:00" + "last_validated_date": "2026-02-21T00:35:36+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.74, + "teardown": 0.57, + "total": 1.81 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3]": { - "last_validated_date": "2025-03-17T20:16:29+00:00" + "last_validated_date": "2026-02-21T00:35:30+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.33, + "teardown": 0.59, + "total": 1.43 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3v4]": { - "last_validated_date": "2025-03-17T20:16:30+00:00" + "last_validated_date": "2026-02-21T00:35:32+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.35, + "teardown": 0.59, + "total": 1.47 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_presigned_post_with_different_user_credentials": { - "last_validated_date": "2025-03-17T20:17:16+00:00" + "last_validated_date": "2026-02-21T00:36:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.48, + "teardown": 1.9, + "total": 14.38 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_redirect": { - "last_validated_date": "2025-03-17T20:16:36+00:00" + "last_validated_date": "2026-02-21T00:35:39+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.67, + "teardown": 0.93, + "total": 2.09 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_status_201_response": { + "last_validated_date": "2026-02-21T00:35:37+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.34, + "teardown": 0.97, + "total": 1.8 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_delete_has_empty_content_length_header": { - "last_validated_date": "2025-01-21T18:22:48+00:00" + "last_validated_date": "2026-02-21T00:29:29+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 1.13, + "teardown": 0.58, + "total": 2.26 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_object_ignores_request_body": { - "last_validated_date": "2025-01-21T18:23:01+00:00" + "last_validated_date": "2026-02-21T00:29:48+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.54, + "teardown": 0.93, + "total": 1.97 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_response_overrides_unicode_metadata_with_sig_s3": { + "last_validated_date": "2026-02-21T00:29:44+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.55, + "teardown": 0.96, + "total": 2.02 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_head_has_correct_content_length_header": { - "last_validated_date": "2025-01-21T18:22:49+00:00" + "last_validated_date": "2026-02-21T00:29:31+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.56, + "teardown": 1.0, + "total": 2.08 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_forward_slash_bucket": { - "last_validated_date": "2025-01-21T18:25:38+00:00" + "last_validated_date": "2026-02-21T00:32:06+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.54, + "teardown": 0.97, + "total": 2.01 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_match": { - "last_validated_date": "2025-05-15T13:08:44+00:00" + "last_validated_date": "2026-02-21T00:32:18+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.57, + "teardown": 0.97, + "total": 2.07 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_none_match": { - "last_validated_date": "2025-05-15T12:51:09+00:00" + "last_validated_date": "2026-02-21T00:32:16+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.71, + "teardown": 0.99, + "total": 2.24 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_with_additional_query_params": { - "last_validated_date": "2025-01-21T18:22:43+00:00" + "last_validated_date": "2026-02-21T00:29:25+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.71, + "teardown": 0.94, + "total": 2.16 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_double_encoded_credentials": { - "last_validated_date": "2025-01-21T18:23:03+00:00" + "last_validated_date": "2026-02-21T00:29:50+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.54, + "teardown": 0.95, + "total": 2.0 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-False]": { - "last_validated_date": "2025-01-21T18:24:24+00:00" + "last_validated_date": "2026-02-21T00:31:11+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.26, + "teardown": 0.59, + "total": 3.86 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-True]": { - "last_validated_date": "2025-01-21T18:24:28+00:00" + "last_validated_date": "2026-02-21T00:31:15+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.19, + "teardown": 0.57, + "total": 3.77 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-False]": { - "last_validated_date": "2025-01-21T18:24:31+00:00" + "last_validated_date": "2026-02-21T00:31:18+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.09, + "teardown": 0.58, + "total": 3.68 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-True]": { - "last_validated_date": "2025-01-21T18:24:35+00:00" + "last_validated_date": "2026-02-21T00:31:22+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.02, + "teardown": 0.58, + "total": 3.61 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-False]": { - "last_validated_date": "2025-01-21T18:24:08+00:00" + "last_validated_date": "2026-02-21T00:30:55+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.11, + "teardown": 0.97, + "total": 4.09 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-True]": { - "last_validated_date": "2025-01-21T18:24:12+00:00" + "last_validated_date": "2026-02-21T00:30:59+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.1, + "teardown": 0.98, + "total": 4.09 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-False]": { - "last_validated_date": "2025-01-21T18:24:16+00:00" + "last_validated_date": "2026-02-21T00:31:03+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.13, + "teardown": 0.96, + "total": 4.1 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-True]": { - "last_validated_date": "2025-01-21T18:24:20+00:00" + "last_validated_date": "2026-02-21T00:31:07+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.08, + "teardown": 0.72, + "total": 3.81 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-False]": { - "last_validated_date": "2025-01-21T18:24:37+00:00" + "last_validated_date": "2026-02-21T00:31:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.74, + "teardown": 0.96, + "total": 2.7 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-True]": { - "last_validated_date": "2025-01-21T18:24:40+00:00" + "last_validated_date": "2026-02-21T00:31:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.8, + "teardown": 0.94, + "total": 2.74 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-False]": { - "last_validated_date": "2025-01-21T18:24:43+00:00" + "last_validated_date": "2026-02-21T00:31:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.79, + "teardown": 1.04, + "total": 2.83 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-True]": { - "last_validated_date": "2025-01-21T18:24:45+00:00" + "last_validated_date": "2026-02-21T00:31:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.87, + "teardown": 0.93, + "total": 2.8 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_signed_headers_in_qs": { - "last_validated_date": "2025-01-21T18:25:34+00:00" + "last_validated_date": "2026-02-21T00:32:04+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 11.74, + "teardown": 2.82, + "total": 15.06 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_x_amz_in_qs": { - "last_validated_date": "2025-03-17T21:32:11+00:00" + "last_validated_date": "2026-02-21T00:31:49+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 12.4, + "teardown": 2.76, + "total": 15.65 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_different_user_credentials": { - "last_validated_date": "2025-01-21T18:23:55+00:00" + "last_validated_date": "2026-02-21T00:30:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.33, + "teardown": 2.06, + "total": 14.39 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_session_token": { - "last_validated_date": "2025-01-21T18:23:42+00:00" + "last_validated_date": "2026-02-21T00:30:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.47, + "teardown": 1.22, + "total": 2.69 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object": { - "last_validated_date": "2025-03-17T21:31:27+00:00" + "last_validated_date": "2026-02-21T00:29:27+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.61, + "teardown": 1.01, + "total": 2.13 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-False]": { - "last_validated_date": "2025-01-21T18:23:07+00:00" + "last_validated_date": "2026-02-21T00:29:54+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.62, + "teardown": 0.59, + "total": 1.71 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-True]": { - "last_validated_date": "2025-01-21T18:23:05+00:00" + "last_validated_date": "2026-02-21T00:29:52+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.63, + "teardown": 0.61, + "total": 1.74 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-False]": { - "last_validated_date": "2025-01-21T18:23:10+00:00" + "last_validated_date": "2026-02-21T00:29:57+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.73, + "teardown": 0.57, + "total": 1.79 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-True]": { - "last_validated_date": "2025-01-21T18:23:09+00:00" + "last_validated_date": "2026-02-21T00:29:55+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.62, + "teardown": 0.59, + "total": 1.7 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_unicode_metadata_with_sig_s3": { + "last_validated_date": "2026-02-21T00:29:46+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.56, + "teardown": 0.93, + "total": 2.0 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[False]": { - "last_validated_date": "2025-01-21T18:22:59+00:00" + "last_validated_date": "2026-02-21T00:29:42+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.02, + "teardown": 0.94, + "total": 2.49 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[True]": { - "last_validated_date": "2025-01-21T18:22:57+00:00" + "last_validated_date": "2026-02-21T00:29:40+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.01, + "teardown": 0.95, + "total": 2.46 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[False]": { - "last_validated_date": "2025-01-21T18:22:55+00:00" + "last_validated_date": "2026-02-21T00:29:37+00:00", + "durations_in_seconds": { + "setup": 0.77, + "call": 1.33, + "teardown": 0.94, + "total": 3.04 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[True]": { - "last_validated_date": "2025-01-21T18:22:52+00:00" + "last_validated_date": "2026-02-21T00:29:34+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.31, + "teardown": 0.97, + "total": 2.79 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_copy_md5": { - "last_validated_date": "2025-01-21T18:24:04+00:00" + "last_validated_date": "2026-02-21T00:30:51+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.72, + "teardown": 1.02, + "total": 2.26 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_content_type_same_as_upload_and_range": { - "last_validated_date": "2025-01-21T18:23:40+00:00" + "last_validated_date": "2026-02-21T00:30:27+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.94, + "teardown": 0.97, + "total": 2.43 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_default_content_type": { - "last_validated_date": "2025-01-21T18:23:12+00:00" + "last_validated_date": "2026-02-21T00:29:59+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.54, + "teardown": 0.9, + "total": 1.96 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3]": { - "last_validated_date": "2025-01-21T18:23:58+00:00" + "last_validated_date": "2026-02-21T00:30:46+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.56, + "teardown": 1.05, + "total": 2.11 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3v4]": { - "last_validated_date": "2025-01-21T18:24:00+00:00" + "last_validated_date": "2026-02-21T00:30:49+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.6, + "teardown": 1.03, + "total": 2.16 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_ignored_special_headers": { - "last_validated_date": "2025-01-21T18:25:45+00:00" + "last_validated_date": "2026-02-21T00:32:13+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.66, + "teardown": 0.97, + "total": 3.16 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3]": { - "last_validated_date": "2025-01-21T18:25:40+00:00" + "last_validated_date": "2026-02-21T00:32:08+00:00", + "durations_in_seconds": { + "setup": 0.6, + "call": 0.56, + "teardown": 1.05, + "total": 2.21 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3v4]": { - "last_validated_date": "2025-01-21T18:25:42+00:00" + "last_validated_date": "2026-02-21T00:32:10+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.59, + "teardown": 0.97, + "total": 2.08 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3]": { - "last_validated_date": "2025-01-21T18:23:17+00:00" + "last_validated_date": "2026-02-21T00:30:05+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 4.2, + "teardown": 0.93, + "total": 5.62 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3v4]": { - "last_validated_date": "2025-01-21T18:23:23+00:00" + "last_validated_date": "2026-02-21T00:30:10+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 4.28, + "teardown": 0.92, + "total": 5.69 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3]": { - "last_validated_date": "2025-01-21T18:23:35+00:00" + "last_validated_date": "2026-02-21T00:30:23+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.55, + "teardown": 0.95, + "total": 2.03 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3v4]": { - "last_validated_date": "2025-01-21T18:23:37+00:00" + "last_validated_date": "2026-02-21T00:30:25+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.55, + "teardown": 1.0, + "total": 2.05 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_same_header_and_qs_parameter": { - "last_validated_date": "2025-01-21T18:23:33+00:00" + "last_validated_date": "2026-02-21T00:30:21+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.87, + "teardown": 0.98, + "total": 2.37 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3]": { - "last_validated_date": "2025-01-21T18:23:27+00:00" + "last_validated_date": "2026-02-21T00:30:15+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.98, + "teardown": 0.95, + "total": 4.45 + } }, "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3v4]": { - "last_validated_date": "2025-01-21T18:23:31+00:00" + "last_validated_date": "2026-02-21T00:30:18+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 2.02, + "teardown": 0.96, + "total": 3.52 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32C]": { - "last_validated_date": "2025-03-17T18:27:58+00:00" + "last_validated_date": "2026-02-21T00:37:28+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 13.51, + "teardown": 0.99, + "total": 15.04 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32]": { - "last_validated_date": "2025-03-17T18:27:45+00:00" + "last_validated_date": "2026-02-21T00:37:13+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 14.48, + "teardown": 0.98, + "total": 15.99 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC64NVME]": { - "last_validated_date": "2025-03-17T18:28:43+00:00" + "last_validated_date": "2026-02-21T00:38:05+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 13.04, + "teardown": 1.04, + "total": 14.6 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA1]": { - "last_validated_date": "2025-03-17T18:28:09+00:00" + "last_validated_date": "2026-02-21T00:37:35+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 5.99, + "teardown": 0.97, + "total": 7.48 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA256]": { - "last_validated_date": "2025-03-17T18:28:24+00:00" + "last_validated_date": "2026-02-21T00:37:50+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 13.58, + "teardown": 0.76, + "total": 14.85 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_algorithm": { - "last_validated_date": "2025-03-17T18:29:05+00:00" + "last_validated_date": "2026-02-21T00:38:28+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.01, + "teardown": 0.98, + "total": 2.51 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_automatic_sdk_calculation": { - "last_validated_date": "2025-03-17T22:25:43+00:00" + "last_validated_date": "2026-02-21T00:38:32+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 2.05, + "teardown": 1.03, + "total": 3.61 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_with_content_encoding": { - "last_validated_date": "2025-03-17T18:29:02+00:00" + "last_validated_date": "2026-02-21T00:38:26+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.59, + "teardown": 1.11, + "total": 2.24 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32C]": { - "last_validated_date": "2025-03-17T18:28:48+00:00" + "last_validated_date": "2026-02-21T00:38:11+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.46, + "teardown": 0.98, + "total": 2.97 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32]": { - "last_validated_date": "2025-03-17T18:28:46+00:00" + "last_validated_date": "2026-02-21T00:38:08+00:00", + "durations_in_seconds": { + "setup": 0.9, + "call": 1.42, + "teardown": 0.98, + "total": 3.3 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC64NVME]": { - "last_validated_date": "2025-03-17T18:28:57+00:00" + "last_validated_date": "2026-02-21T00:38:20+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.4, + "teardown": 1.3, + "total": 3.22 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[None]": { - "last_validated_date": "2025-03-17T18:29:00+00:00" + "last_validated_date": "2026-02-21T00:38:23+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 1.5, + "teardown": 0.97, + "total": 3.02 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA1]": { - "last_validated_date": "2025-03-17T18:28:51+00:00" + "last_validated_date": "2026-02-21T00:38:14+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.42, + "teardown": 0.98, + "total": 2.94 + } }, "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA256]": { - "last_validated_date": "2025-03-17T18:28:54+00:00" + "last_validated_date": "2026-02-21T00:38:17+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.48, + "teardown": 0.97, + "total": 2.98 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_copy_object_with_sse_c": { - "last_validated_date": "2025-01-21T18:16:26+00:00" + "last_validated_date": "2026-02-21T00:36:42+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.94, + "teardown": 1.22, + "total": 3.68 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c": { - "last_validated_date": "2025-03-17T22:55:35+00:00" + "last_validated_date": "2026-02-21T00:36:48+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 4.37, + "teardown": 0.97, + "total": 5.85 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c_validation": { - "last_validated_date": "2025-01-21T18:16:43+00:00" + "last_validated_date": "2026-02-21T00:36:51+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.2, + "teardown": 0.85, + "total": 2.56 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_object_retrieval_sse_c": { - "last_validated_date": "2025-01-22T14:21:48+00:00" + "last_validated_date": "2026-02-21T00:36:39+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 2.92, + "teardown": 1.2, + "total": 4.65 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_default_checksum_with_sse_c": { - "last_validated_date": "2025-03-17T23:22:52+00:00" + "last_validated_date": "2026-02-21T00:36:57+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.0, + "teardown": 0.99, + "total": 2.52 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_lifecycle_with_sse_c": { - "last_validated_date": "2025-01-21T18:16:15+00:00" + "last_validated_date": "2026-02-21T00:36:30+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.02, + "teardown": 0.61, + "total": 2.14 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_validation_sse_c": { - "last_validated_date": "2025-01-21T18:16:18+00:00" + "last_validated_date": "2026-02-21T00:36:34+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 2.14, + "teardown": 0.86, + "total": 3.53 + } }, "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_sse_c_with_versioning": { - "last_validated_date": "2025-01-21T18:16:46+00:00" + "last_validated_date": "2026-02-21T00:36:54+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.64, + "teardown": 1.38, + "total": 3.54 + } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_crud_website_configuration": { - "last_validated_date": "2023-08-25T22:29:24+00:00" + "last_validated_date": "2026-02-21T00:33:08+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.71, + "teardown": 0.59, + "total": 1.82 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_object_website_redirect_location": { + "last_validated_date": "2026-02-21T00:32:47+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 1.42, + "teardown": 1.05, + "total": 3.41 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_conditions": { + "last_validated_date": "2026-02-21T00:32:52+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 2.78, + "teardown": 0.96, + "total": 4.62 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_empty_replace_prefix": { + "last_validated_date": "2026-02-21T00:32:59+00:00", + "durations_in_seconds": { + "setup": 0.89, + "call": 2.42, + "teardown": 0.97, + "total": 4.28 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_order": { + "last_validated_date": "2026-02-21T00:33:03+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 1.75, + "teardown": 0.96, + "total": 3.59 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_redirects": { + "last_validated_date": "2026-02-21T00:32:55+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 1.18, + "teardown": 0.63, + "total": 2.69 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_hosting": { + "last_validated_date": "2026-02-21T00:32:31+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 3.35, + "teardown": 1.01, + "total": 5.32 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_index": { + "last_validated_date": "2026-02-21T00:32:26+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 0.8, + "teardown": 0.95, + "total": 2.7 + } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_validate_website_configuration": { - "last_validated_date": "2023-08-25T22:30:03+00:00" + "last_validated_date": "2026-02-21T00:33:06+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.38, + "teardown": 0.83, + "total": 3.73 + } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_404": { - "last_validated_date": "2023-08-25T22:30:33+00:00" + "last_validated_date": "2026-02-21T00:32:44+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 1.39, + "teardown": 1.04, + "total": 3.37 + } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_http_methods": { - "last_validated_date": "2023-08-25T22:30:55+00:00" + "last_validated_date": "2026-02-21T00:32:37+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 1.34, + "teardown": 1.06, + "total": 3.35 + } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_index_lookup": { - "last_validated_date": "2023-08-25T22:31:15+00:00" + "last_validated_date": "2026-02-21T00:32:41+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 1.88, + "teardown": 0.98, + "total": 3.79 + } }, "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_no_such_website": { - "last_validated_date": "2023-08-25T22:31:30+00:00" + "last_validated_date": "2026-02-21T00:32:33+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 0.8, + "teardown": 0.61, + "total": 2.35 + } + }, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_redirect_all": { + "last_validated_date": "2026-02-21T00:33:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 4.07, + "teardown": 1.54, + "total": 5.61 + } } } diff --git a/tests/aws/services/s3/test_s3_api.py b/tests/aws/services/s3/test_s3_api.py index c7d39a672c077..0d33b6b02eea3 100644 --- a/tests/aws/services/s3/test_s3_api.py +++ b/tests/aws/services/s3/test_s3_api.py @@ -2,12 +2,16 @@ import json import string from operator import itemgetter +from time import sleep from urllib.parse import urlencode import pytest +from botocore.config import Config from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import SortingTransformer +from moto.wafv2.models import US_EAST_1_REGION +from localstack.aws.api.s3 import StorageClass from localstack.testing.pytest import markers from localstack.utils.strings import long_uid, short_uid from tests.aws.services.s3.conftest import TEST_S3_IMAGE @@ -490,8 +494,6 @@ def test_get_object_range(self, aws_client, s3_bucket, snapshot): class TestS3Multipart: - # TODO: write a validated test for UploadPartCopy preconditions - @markers.aws.validated @markers.snapshot.skip_snapshot_verify(paths=["$..PartNumberMarker"]) # TODO: investigate this def test_upload_part_copy_range(self, aws_client, s3_bucket, snapshot): @@ -612,6 +614,514 @@ def test_upload_part_copy_no_copy_source_range(self, aws_client, s3_bucket, snap list_parts = aws_client.s3.list_parts(Bucket=s3_bucket, Key=key, UploadId=upload_id) snapshot.match("list-parts", list_parts) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..Owner.DisplayName"], + ) + def test_upload_part_copy_with_copy_source_if_match_success( + self, aws_client, s3_bucket, snapshot + ): + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + put_source_object = aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + # Uploading shouldn't raise an exception + upload_part_copy = aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfMatch=put_source_object["ETag"], + ) + snapshot.match("upload-part-copy-if-match", upload_part_copy) + + # Check parts were uploaded successfully + list_parts = aws_client.s3.list_parts( + Bucket=s3_bucket, Key=multi_part_upload_key, UploadId=upload_id + ) + snapshot.match("list-parts", list_parts) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..Owner.DisplayName"], + ) + def test_upload_part_copy_with_copy_source_if_match_none_success( + self, aws_client, s3_bucket, snapshot + ): + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + # Uploading shouldn't raise an exception + upload_part_copy = aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfNoneMatch="none-match", + ) + snapshot.match("upload-part-copy-if-none-match", upload_part_copy) + + # Check parts were uploaded successfully + list_parts = aws_client.s3.list_parts( + Bucket=s3_bucket, Key=multi_part_upload_key, UploadId=upload_id + ) + snapshot.match("list-parts", list_parts) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..Owner.DisplayName"], + ) + def test_upload_part_copy_with_copy_source_if_unmodified_since_success( + self, aws_client, s3_bucket, snapshot + ): + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + # Uploading shouldn't raise an exception + later_datetime = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=1) + upload_part_copy = aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfUnmodifiedSince=later_datetime, + ) + snapshot.match("upload-part-copy-if-unmodified-since", upload_part_copy) + + # Check parts were uploaded successfully + list_parts = aws_client.s3.list_parts( + Bucket=s3_bucket, Key=multi_part_upload_key, UploadId=upload_id + ) + snapshot.match("list-parts", list_parts) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..Owner.DisplayName"], + ) + def test_upload_part_copy_with_copy_source_if_modified_since_success( + self, aws_client, s3_bucket, snapshot + ): + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + # Uploading shouldn't raise an exception + earlier_datetime = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=1) + upload_part_copy = aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfModifiedSince=earlier_datetime, + ) + snapshot.match("upload-part-copy-if-modified-since", upload_part_copy) + + # Check parts were uploaded successfully + list_parts = aws_client.s3.list_parts( + Bucket=s3_bucket, Key=multi_part_upload_key, UploadId=upload_id + ) + snapshot.match("list-parts", list_parts) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..Owner.DisplayName"], + ) + def test_upload_part_copy_with_copy_source_if_modified_since_in_future_success( + self, aws_client, s3_bucket, snapshot + ): + """ + Providing CopyIfModifiedSince with a datetime which is in the future will always pass (even though it + evaluates to false). This is AWS defined behaviour. + """ + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + # Uploading shouldn't raise an exception + later_datetime = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=1) + upload_part_copy = aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfModifiedSince=later_datetime, + ) + snapshot.match("upload-part-copy-if-modified-since-in-future", upload_part_copy) + + # Check parts were uploaded successfully + list_parts = aws_client.s3.list_parts( + Bucket=s3_bucket, Key=multi_part_upload_key, UploadId=upload_id + ) + snapshot.match("list-parts", list_parts) + + @markers.aws.validated + def test_upload_part_copy_with_copy_source_if_match_failed( + self, aws_client, s3_bucket, snapshot + ): + """ + Providing CopySourceIfMatch with an ETag which doesn't match with the specified source key ETag should fail. + """ + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + with pytest.raises(ClientError) as error: + aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfMatch="not-matching", + ) + snapshot.match("upload-part-copy-source-if-match", error.value.response) + + @markers.aws.validated + def test_upload_part_copy_with_copy_source_if_none_match_failed( + self, aws_client, s3_bucket, snapshot + ): + """ + Providing CopySourceIfNoneMatch with an ETag which does match with the specified source key ETag should fail. + """ + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + put_source_object = aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + with pytest.raises(ClientError) as error: + # Provide the correct ETag to cause a failure + aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfNoneMatch=put_source_object["ETag"], + ) + snapshot.match("upload-part-copy-source-if-none-match", error.value.response) + + @markers.aws.validated + def test_upload_part_copy_with_copy_source_if_unmodified_since_match_failed( + self, aws_client, s3_bucket, snapshot + ): + """ + Providing CopySourceIfUnmodifiedSince with a datetime where the object has been modified since + this datetime should fail. + """ + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + earlier_datetime = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=1) + with pytest.raises(ClientError) as error: + # Provide a time earler than it's last modified time which should fail. + aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfUnmodifiedSince=earlier_datetime, + ) + snapshot.match("upload-part-copy-source-unmodified-since-match", error.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=["$..Owner.DisplayName"], + ) + def test_upload_part_copy_with_copy_source_if_match_and_if_unmodified_since_match( + self, aws_client, s3_bucket, snapshot + ): + """ + If CopySourceIfMatch is provided with CopySourceIfUnmodifiedSince it should proceed even if the latter + evaluates to false. + See documentation: https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html + """ + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + put_source_object = aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + # Uploading shouldn't raise an exception + earlier_datetime = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=1) + upload_part_copy = aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfMatch=put_source_object["ETag"], + CopySourceIfUnmodifiedSince=earlier_datetime, + ) + snapshot.match("upload-part-copy", upload_part_copy) + + # Check parts were uploaded successfully + list_parts = aws_client.s3.list_parts( + Bucket=s3_bucket, Key=multi_part_upload_key, UploadId=upload_id + ) + snapshot.match("list-parts", list_parts) + + @markers.aws.validated + def test_upload_part_copy_with_copy_source_if_none_match_and_if_unmodified_since_match_failed( + self, aws_client, s3_bucket, snapshot + ): + """ + If CopySourceIfNoneMatch evaluates to true, we should fail if CopySourceIfUnmodifiedSince evaluates to false. + """ + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + earlier_datetime = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=1) + with pytest.raises(ClientError) as error: + aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfNoneMatch="not-matching", + CopySourceIfUnmodifiedSince=earlier_datetime, + ) + snapshot.match( + "upload-part-copy-source-unmodified-since-and-if-none-match", error.value.response + ) + + @markers.aws.validated + def test_upload_part_copy_with_if_modified_since_failed(self, aws_client, s3_bucket, snapshot): + snapshot.add_transformer( + [ + snapshot.transform.key_value("Bucket", reference_replacement=False), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("UploadId"), + snapshot.transform.key_value("DisplayName", reference_replacement=False), + snapshot.transform.key_value("ID", reference_replacement=False), + snapshot.transform.key_value("ETag"), + ] + ) + + # Set up the source object. + source_key = "source_file.txt" + content = "0123456789" + aws_client.s3.put_object(Bucket=s3_bucket, Key=source_key, Body=content) + + # Set up the multi-part upload. + multi_part_upload_key = "destination_file.txt" + create_multipart_upload = aws_client.s3.create_multipart_upload( + Bucket=s3_bucket, Key=multi_part_upload_key + ) + upload_id = create_multipart_upload["UploadId"] + + # Don't do this, but required for behaviour with CopySourceIfModifiedSince which will pass + # if a future time is provided. + sleep(1) + + with pytest.raises(ClientError) as error: + aws_client.s3.upload_part_copy( + Bucket=s3_bucket, + UploadId=upload_id, + Key=multi_part_upload_key, + PartNumber=1, + CopySource=f"{s3_bucket}/{source_key}", + CopySourceIfModifiedSince=datetime.datetime.now(tz=datetime.UTC), + ) + snapshot.match("upload-part-copy-source-modified-since-match", error.value.response) + class TestS3BucketVersioning: @markers.aws.validated @@ -864,6 +1374,7 @@ def test_s3_bucket_encryption_sse_kms(self, s3_bucket, kms_key, aws_client, snap ] ) def test_s3_bucket_encryption_sse_kms_aws_managed_key(self, s3_bucket, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.key_value("CurrentKeyMaterialId")) # if you don't provide a KMS key, AWS will use an AWS managed one. put_bucket_enc = aws_client.s3.put_bucket_encryption( Bucket=s3_bucket, @@ -945,20 +1456,240 @@ def test_bucket_tagging_crud(self, s3_bucket, aws_client, snapshot): e.match("NoSuchTagSet") @markers.aws.validated - def test_bucket_tagging_exc(self, s3_bucket, aws_client, snapshot): + def test_create_bucket_with_tags( + self, s3_create_bucket_with_client, region_name, aws_client, snapshot + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("BucketArn"), + snapshot.transform.key_value("Location"), + ] + ) + additional_config = {} + if region_name != US_EAST_1_REGION: + additional_config["LocationConstraint"] = region_name + + bucket_name = f"test-bucket-tag-{short_uid()}" + create_bucket = s3_create_bucket_with_client( + aws_client.s3, + Bucket=bucket_name, + CreateBucketConfiguration={ + "Tags": [{"Key": "tag1", "Value": "value1"}], + **additional_config, + }, + ) + snapshot.match("create-bucket-with-tags", create_bucket) + + get_bucket_tags = aws_client.s3.get_bucket_tagging(Bucket=bucket_name) + snapshot.match("get-bucket-tags", get_bucket_tags) + + @markers.aws.validated + def test_create_bucket_with_empty_tags( + self, s3_create_bucket_with_client, aws_client_factory, snapshot + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("BucketName"), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("BucketArn"), + ] + ) + + s3_client = aws_client_factory( + region_name=US_EAST_1_REGION, config=Config(parameter_validation=False) + ).s3 + + bucket_name = f"test-bucket-tag-{short_uid()}" + create_bucket = s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": []}, + ) + snapshot.match("create-bucket-with-empty-tags", create_bucket) + + with pytest.raises(ClientError) as e: + s3_client.get_bucket_tagging(Bucket=bucket_name) + snapshot.match("get-bucket-tags", e.value.response) + + create_bucket_none = s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": None}, + ) + snapshot.match("create-bucket-with-none-tags", create_bucket_none) + + @markers.aws.validated + def test_create_bucket_with_tags_us_east_1_idempotency( + self, s3_create_bucket_with_client, aws_client_factory, snapshot + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("BucketName"), + snapshot.transform.key_value("Location"), + snapshot.transform.key_value("BucketArn"), + ] + ) + us_east_1_s3_client = aws_client_factory( + region_name=US_EAST_1_REGION, config=Config(parameter_validation=False) + ).s3 + + bucket_name = f"test-bucket-tag-{short_uid()}" + create_bucket = s3_create_bucket_with_client( + us_east_1_s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": "tag1", "Value": "value1"}]}, + ) + snapshot.match("create-bucket-with-tags", create_bucket) + + get_bucket_tags = us_east_1_s3_client.get_bucket_tagging(Bucket=bucket_name) + snapshot.match("get-bucket-tags", get_bucket_tags) + + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + us_east_1_s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": "tag2", "Value": "value2"}]}, + ) + snapshot.match("create-bucket-different-tags", e.value.response) + + create_bucket = s3_create_bucket_with_client( + us_east_1_s3_client, + Bucket=bucket_name, + ) + snapshot.match("create-bucket-with-no-tags", create_bucket) + + get_bucket_tags = us_east_1_s3_client.get_bucket_tagging(Bucket=bucket_name) + snapshot.match("get-bucket-tags-after-re-create", get_bucket_tags) + + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + us_east_1_s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": "tag1", "Value": "value1"}]}, + ) + snapshot.match("create-bucket-with-same-tags", e.value.response) + + create_bucket_empty_tags = s3_create_bucket_with_client( + us_east_1_s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": []}, + ) + snapshot.match("create-bucket-with-empty-tags", create_bucket_empty_tags) + + get_bucket_tags = us_east_1_s3_client.get_bucket_tagging(Bucket=bucket_name) + snapshot.match("get-bucket-tags-after-create-empty", get_bucket_tags) + + @markers.aws.validated + def test_bucket_tagging_exc(self, s3_bucket, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.key_value("BucketName")) + fake_bucket = f"fake-bucket-{short_uid()}-{short_uid()}" + with pytest.raises(ClientError) as e: + aws_client.s3.get_bucket_tagging(Bucket=fake_bucket) + snapshot.match("get-no-bucket-tags", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.s3.delete_bucket_tagging(Bucket=fake_bucket) + snapshot.match("delete-no-bucket-tags", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.s3.put_bucket_tagging(Bucket=fake_bucket, Tagging={"TagSet": []}) + snapshot.match("put-no-bucket-tags", e.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # FIXME: regenerate snapshot when AWS is not raising a 500 error anymore + "$.tags-key-aws-duplicated-key.*" + ] + ) + def test_create_bucket_with_tags_exc( + self, s3_create_bucket_with_client, aws_client_factory, snapshot + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("BucketName"), + snapshot.transform.key_value("Location"), + ] + ) + s3_client = aws_client_factory( + region_name=US_EAST_1_REGION, config=Config(parameter_validation=False) + ).s3 + + bucket_name = f"test-bucket-tag-{short_uid()}" + + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": None, "Value": "value1"}]}, + ) + snapshot.match("tags-key-none", e.value.response) + + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": "key1", "Value": None}]}, + ) + snapshot.match("tags-value-none", e.value.response) + + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": "aws:test", "Value": "value1"}]}, + ) + snapshot.match("tags-key-aws-prefix", e.value.response) + + # FIXME: re-generate the snapshot later, AWS is returning a 500 error as of now (27 Nov 2025) + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={ + "Tags": [ + {"Key": "key1", "Value": "value1"}, + {"Key": "key1", "Value": "value1"}, + ] + }, + ) + snapshot.match("tags-key-aws-duplicated-key", e.value.response) + + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": "test", "Value": "value1,value2"}]}, + ) + snapshot.match("tags-key-aws-bad-value", e.value.response) + + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + s3_client, + Bucket=bucket_name, + CreateBucketConfiguration={"Tags": [{"Key": "test,test2", "Value": "value1"}]}, + ) + snapshot.match("tags-key-aws-bad-key", e.value.response) + + @markers.aws.validated + def test_put_bucket_tagging_none_value( + self, s3_bucket, aws_client_factory, snapshot, region_name + ): snapshot.add_transformer(snapshot.transform.key_value("BucketName")) - fake_bucket = f"fake-bucket-{short_uid()}-{short_uid()}" - with pytest.raises(ClientError) as e: - aws_client.s3.get_bucket_tagging(Bucket=fake_bucket) - snapshot.match("get-no-bucket-tags", e.value.response) + s3_client = aws_client_factory( + region_name=region_name, config=Config(parameter_validation=False) + ).s3 + tag_set_origin = {"TagSet": [{"Key": "tag3", "Value": "tag3"}]} - with pytest.raises(ClientError) as e: - aws_client.s3.delete_bucket_tagging(Bucket=fake_bucket) - snapshot.match("delete-no-bucket-tags", e.value.response) + put_bucket_tags = s3_client.put_bucket_tagging(Bucket=s3_bucket, Tagging=tag_set_origin) + snapshot.match("put-bucket-tags-origin", put_bucket_tags) + + put_obj_tagging = s3_client.put_bucket_tagging(Bucket=s3_bucket, Tagging={"TagSet": None}) + snapshot.match("put-none-tag-set", put_obj_tagging) with pytest.raises(ClientError) as e: - aws_client.s3.put_bucket_tagging(Bucket=fake_bucket, Tagging={"TagSet": []}) - snapshot.match("put-no-bucket-tags", e.value.response) + s3_client.get_bucket_tagging(Bucket=s3_bucket) + snapshot.match("no-such-tag-set", e.value.response) @markers.aws.validated def test_object_tagging_crud(self, s3_bucket, aws_client, snapshot): @@ -1043,6 +1774,24 @@ def test_object_tagging_exc(self, s3_bucket, aws_client, snapshot): aws_client.s3.put_object(Bucket=s3_bucket, Key=fake_key, Body="", Tagging=tagging) snapshot.match("put-obj-wrong-format", e.value.response) + @markers.aws.validated + def test_put_object_tagging_none_value( + self, s3_bucket, aws_client_factory, snapshot, region_name + ): + s3_client = aws_client_factory( + region_name=region_name, config=Config(parameter_validation=False) + ).s3 + object_key = "test-empty-tag-set" + s3_client.put_object(Bucket=s3_bucket, Key=object_key, Body="test-tagging") + + put_obj_tagging = s3_client.put_object_tagging( + Bucket=s3_bucket, Key=object_key, Tagging={"TagSet": None} + ) + snapshot.match("put-none-tag-set", put_obj_tagging) + + get_object_tags = s3_client.get_object_tagging(Bucket=s3_bucket, Key=object_key) + snapshot.match("get-object-tags-none", get_object_tags) + @markers.aws.validated def test_object_tagging_versioned(self, s3_bucket, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.key_value("VersionId")) @@ -1069,32 +1818,44 @@ def test_object_tagging_versioned(self, s3_bucket, aws_client, snapshot): tag_set_2 = {"TagSet": [{"Key": "tag3", "Value": "tag3"}]} # test without specifying a VersionId - put_bucket_tags = aws_client.s3.put_object_tagging( + put_object_tags = aws_client.s3.put_object_tagging( Bucket=s3_bucket, Key=object_key, Tagging=tag_set_2 ) - snapshot.match("put-object-tags-current-version", put_bucket_tags) - assert put_bucket_tags["VersionId"] == version_id_2 + snapshot.match("put-object-tags-current-version", put_object_tags) + assert put_object_tags["VersionId"] == version_id_2 - get_bucket_tags = aws_client.s3.get_object_tagging(Bucket=s3_bucket, Key=object_key) - snapshot.match("get-object-tags-current-version", get_bucket_tags) + put_object_tags = aws_client.s3.get_object_tagging(Bucket=s3_bucket, Key=object_key) + snapshot.match("get-object-tags-current-version", put_object_tags) - get_bucket_tags = aws_client.s3.get_object_tagging( + put_object_tags = aws_client.s3.get_object_tagging( Bucket=s3_bucket, Key=object_key, VersionId=version_id_1 ) - snapshot.match("get-object-tags-previous-version", get_bucket_tags) + snapshot.match("get-object-tags-previous-version", put_object_tags) tag_set_2 = {"TagSet": [{"Key": "tag1", "Value": "tag1"}]} # test by specifying a VersionId to Version1 - put_bucket_tags = aws_client.s3.put_object_tagging( + put_object_tags = aws_client.s3.put_object_tagging( Bucket=s3_bucket, Key=object_key, VersionId=version_id_1, Tagging=tag_set_2 ) - snapshot.match("put-object-tags-previous-version", put_bucket_tags) - assert put_bucket_tags["VersionId"] == version_id_1 + snapshot.match("put-object-tags-previous-version", put_object_tags) + assert put_object_tags["VersionId"] == version_id_1 - get_bucket_tags = aws_client.s3.get_object_tagging( + get_object_tags = aws_client.s3.get_object_tagging( Bucket=s3_bucket, Key=object_key, VersionId=version_id_1 ) - snapshot.match("get-object-tags-previous-version-again", get_bucket_tags) + snapshot.match("get-object-tags-previous-version-again", get_object_tags) + + # delete tagging on current object + aws_client.s3.delete_object_tagging(Bucket=s3_bucket, Key=object_key) + get_object_tags = aws_client.s3.get_object_tagging(Bucket=s3_bucket, Key=object_key) + snapshot.match("get-object-tags-deleted-current", get_object_tags) + + # delete object tagging on previous version too + aws_client.s3.delete_object_tagging( + Bucket=s3_bucket, Key=object_key, VersionId=version_id_1 + ) + get_object_tags = aws_client.s3.get_object_tagging(Bucket=s3_bucket, Key=object_key) + snapshot.match("get-object-tags-previous-version-deleted", get_object_tags) # Put a DeleteMarker on top of the stack delete_current = aws_client.s3.delete_object(Bucket=s3_bucket, Key=object_key) @@ -1186,6 +1947,44 @@ def test_put_object_with_tags(self, s3_bucket, aws_client, snapshot): get_object_tags = aws_client.s3.get_object_tagging(Bucket=s3_bucket, Key=object_key) snapshot.match("get-object-tags-wrong-format-qs-2", get_object_tags) + @markers.aws.validated + def test_head_object_with_tags(self, s3_bucket, aws_client, snapshot): + object_key = "test-put-object-tagging" + # tagging must be a URL encoded string directly + tag_set = "tag1=tag1&tag2=tag2&tag=" + put_object = aws_client.s3.put_object( + Bucket=s3_bucket, Key=object_key, Body="test-tagging", Tagging=tag_set + ) + snapshot.match("put-object", put_object) + + head_object = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-obj-after-create", head_object) + + tag_set_2 = {"TagSet": [{"Key": "tag3", "Value": "tag3"}]} + put_bucket_tags = aws_client.s3.put_object_tagging( + Bucket=s3_bucket, Key=object_key, Tagging=tag_set_2 + ) + snapshot.match("put-object-tags", put_bucket_tags) + + head_object = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-obj-after-tagging", head_object) + + tag_set_2 = { + "TagSet": [ + {"Key": "tag3", "Value": "tag3"}, + {"Key": "tag4", "Value": "tag4"}, + {"Key": "tag5", "Value": "tag5"}, + ] + } + aws_client.s3.put_object_tagging(Bucket=s3_bucket, Key=object_key, Tagging=tag_set_2) + head_object = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-obj-after-overwrite", head_object) + + aws_client.s3.put_object_tagging(Bucket=s3_bucket, Key=object_key, Tagging={"TagSet": []}) + + head_object = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-obj-after-removal", head_object) + @markers.aws.validated def test_object_tags_delete_or_overwrite_object(self, s3_bucket, aws_client, snapshot): # verify that tags aren't kept after object deletion @@ -1213,12 +2012,17 @@ def test_object_tags_delete_or_overwrite_object(self, s3_bucket, aws_client, sna snapshot.match("get-object-after-recreation", get_bucket_tags) @markers.aws.validated - def test_tagging_validation(self, s3_bucket, aws_client, snapshot): + def test_tagging_validation( + self, s3_bucket, aws_client, aws_client_factory, region_name, snapshot + ): object_key = "tagging-validation" aws_client.s3.put_object(Bucket=s3_bucket, Key=object_key, Body=b"") + s3_client = aws_client_factory( + region_name=region_name, config=Config(parameter_validation=False) + ).s3 with pytest.raises(ClientError) as e: - aws_client.s3.put_bucket_tagging( + s3_client.put_bucket_tagging( Bucket=s3_bucket, Tagging={ "TagSet": [ @@ -1230,7 +2034,7 @@ def test_tagging_validation(self, s3_bucket, aws_client, snapshot): snapshot.match("put-bucket-tags-duplicate-keys", e.value.response) with pytest.raises(ClientError) as e: - aws_client.s3.put_bucket_tagging( + s3_client.put_bucket_tagging( Bucket=s3_bucket, Tagging={ "TagSet": [ @@ -1241,7 +2045,18 @@ def test_tagging_validation(self, s3_bucket, aws_client, snapshot): snapshot.match("put-bucket-tags-invalid-key", e.value.response) with pytest.raises(ClientError) as e: - aws_client.s3.put_bucket_tagging( + s3_client.put_bucket_tagging( + Bucket=s3_bucket, + Tagging={ + "TagSet": [ + {"Key": None, "Value": "Val1"}, + ] + }, + ) + snapshot.match("put-bucket-tags-none-key", e.value.response) + + with pytest.raises(ClientError) as e: + s3_client.put_bucket_tagging( Bucket=s3_bucket, Tagging={ "TagSet": [ @@ -1252,7 +2067,18 @@ def test_tagging_validation(self, s3_bucket, aws_client, snapshot): snapshot.match("put-bucket-tags-invalid-value", e.value.response) with pytest.raises(ClientError) as e: - aws_client.s3.put_bucket_tagging( + s3_client.put_bucket_tagging( + Bucket=s3_bucket, + Tagging={ + "TagSet": [ + {"Key": "key1", "Value": None}, + ] + }, + ) + snapshot.match("put-bucket-tags-none-value", e.value.response) + + with pytest.raises(ClientError) as e: + s3_client.put_bucket_tagging( Bucket=s3_bucket, Tagging={ "TagSet": [ @@ -1263,7 +2089,7 @@ def test_tagging_validation(self, s3_bucket, aws_client, snapshot): snapshot.match("put-bucket-tags-aws-prefixed", e.value.response) with pytest.raises(ClientError) as e: - aws_client.s3.put_object_tagging( + s3_client.put_object_tagging( Bucket=s3_bucket, Key=object_key, Tagging={ @@ -1277,7 +2103,7 @@ def test_tagging_validation(self, s3_bucket, aws_client, snapshot): snapshot.match("put-object-tags-duplicate-keys", e.value.response) with pytest.raises(ClientError) as e: - aws_client.s3.put_object_tagging( + s3_client.put_object_tagging( Bucket=s3_bucket, Key=object_key, Tagging={"TagSet": [{"Key": "Key1,Key2", "Value": "Val1"}]}, @@ -1286,13 +2112,29 @@ def test_tagging_validation(self, s3_bucket, aws_client, snapshot): snapshot.match("put-object-tags-invalid-field", e.value.response) with pytest.raises(ClientError) as e: - aws_client.s3.put_object_tagging( + s3_client.put_object_tagging( Bucket=s3_bucket, Key=object_key, Tagging={"TagSet": [{"Key": "aws:prefixed", "Value": "Val1"}]}, ) snapshot.match("put-object-tags-aws-prefixed", e.value.response) + with pytest.raises(ClientError) as e: + s3_client.put_object_tagging( + Bucket=s3_bucket, + Key=object_key, + Tagging={"TagSet": [{"Key": None, "Value": "Val1"}]}, + ) + snapshot.match("put-object-tags-none-key", e.value.response) + + with pytest.raises(ClientError) as e: + s3_client.put_object_tagging( + Bucket=s3_bucket, + Key=object_key, + Tagging={"TagSet": [{"Key": "key1", "Value": None}]}, + ) + snapshot.match("put-object-tags-none-value", e.value.response) + class TestS3ObjectLock: @markers.aws.validated @@ -1350,13 +2192,13 @@ def test_put_object_lock_configuration_on_existing_bucket( @markers.aws.validated def test_get_put_object_lock_configuration(self, s3_create_bucket, aws_client, snapshot): - s3_bucket = s3_create_bucket(ObjectLockEnabledForBucket=True) + bucket_name = s3_create_bucket(ObjectLockEnabledForBucket=True) - get_lock_config = aws_client.s3.get_object_lock_configuration(Bucket=s3_bucket) + get_lock_config = aws_client.s3.get_object_lock_configuration(Bucket=bucket_name) snapshot.match("get-lock-config-start", get_lock_config) put_lock_config = aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={ "ObjectLockEnabled": "Enabled", "Rule": { @@ -1369,26 +2211,26 @@ def test_get_put_object_lock_configuration(self, s3_create_bucket, aws_client, s ) snapshot.match("put-lock-config", put_lock_config) - get_lock_config = aws_client.s3.get_object_lock_configuration(Bucket=s3_bucket) + get_lock_config = aws_client.s3.get_object_lock_configuration(Bucket=bucket_name) snapshot.match("get-lock-config", get_lock_config) put_lock_config_enabled = aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={ "ObjectLockEnabled": "Enabled", }, ) snapshot.match("put-lock-config-enabled", put_lock_config_enabled) - get_lock_config = aws_client.s3.get_object_lock_configuration(Bucket=s3_bucket) + get_lock_config = aws_client.s3.get_object_lock_configuration(Bucket=bucket_name) snapshot.match("get-lock-config-only-enabled", get_lock_config) @markers.aws.validated def test_put_object_lock_configuration_exc(self, s3_create_bucket, aws_client, snapshot): - s3_bucket = s3_create_bucket(ObjectLockEnabledForBucket=True) + bucket_name = s3_create_bucket(ObjectLockEnabledForBucket=True) with pytest.raises(ClientError) as e: aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={ "Rule": { "DefaultRetention": { @@ -1402,20 +2244,20 @@ def test_put_object_lock_configuration_exc(self, s3_create_bucket, aws_client, s with pytest.raises(ClientError) as e: aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, ObjectLockConfiguration={} + Bucket=bucket_name, ObjectLockConfiguration={} ) snapshot.match("put-lock-config-empty", e.value.response) with pytest.raises(ClientError) as e: aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={"ObjectLockEnabled": "Enabled", "Rule": {}}, ) snapshot.match("put-lock-config-empty-rule", e.value.response) with pytest.raises(ClientError) as e: aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={ "ObjectLockEnabled": "Enabled", "Rule": {"DefaultRetention": {}}, @@ -1425,7 +2267,7 @@ def test_put_object_lock_configuration_exc(self, s3_create_bucket, aws_client, s with pytest.raises(ClientError) as e: aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={ "ObjectLockEnabled": "Enabled", "Rule": { @@ -1439,7 +2281,7 @@ def test_put_object_lock_configuration_exc(self, s3_create_bucket, aws_client, s with pytest.raises(ClientError) as e: aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={ "ObjectLockEnabled": "Enabled", "Rule": { @@ -1454,7 +2296,7 @@ def test_put_object_lock_configuration_exc(self, s3_create_bucket, aws_client, s with pytest.raises(ClientError) as e: aws_client.s3.put_object_lock_configuration( - Bucket=s3_bucket, + Bucket=bucket_name, ObjectLockConfiguration={ "ObjectLockEnabled": "Enabled", "Rule": { @@ -1548,8 +2390,8 @@ def test_crud_bucket_ownership_controls(self, s3_create_bucket, aws_client, snap delete_idempotent = aws_client.s3.delete_bucket_ownership_controls(Bucket=default_s3_bucket) snapshot.match("delete-ownership-after-delete", delete_idempotent) - s3_bucket = s3_create_bucket(ObjectOwnership="BucketOwnerPreferred") - get_ownership_at_creation = aws_client.s3.get_bucket_ownership_controls(Bucket=s3_bucket) + bucket_name = s3_create_bucket(ObjectOwnership="BucketOwnerPreferred") + get_ownership_at_creation = aws_client.s3.get_bucket_ownership_controls(Bucket=bucket_name) snapshot.match("get-ownership-at-creation", get_ownership_at_creation) @markers.aws.validated @@ -2202,6 +3044,294 @@ def test_multipart_if_match_etag(self, s3_bucket, aws_client, snapshot): ) snapshot.match("complete-multipart-if-match-overwrite-multipart", complete_multipart_1) + @markers.aws.validated + def test_copy_object_if_none_match(self, s3_bucket, aws_client, snapshot): + """Test CopyObject with If-None-Match header (destination conditional)""" + src_key = "source-object" + dest_key = "dest-object" + + # Create source object + aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="source content") + + # Copy should succeed when destination doesn't exist + copy_result = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfNoneMatch="*", + ) + snapshot.match("copy-obj", copy_result) + + # Copy should fail when destination already exists + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfNoneMatch="*", + ) + snapshot.match("copy-obj-if-none-match-fail", e.value.response) + + @markers.aws.validated + def test_copy_object_if_match(self, s3_bucket, aws_client, snapshot): + """Test CopyObject with If-Match header (destination conditional)""" + src_key = "source-object" + dest_key = "dest-object" + + # Create source object + aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="source content") + + # Create initial destination object + put_result = aws_client.s3.put_object( + Bucket=s3_bucket, Key=dest_key, Body="initial content" + ) + dest_etag = put_result["ETag"] + + # Copy should succeed when destination ETag matches + copy_result = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfMatch=dest_etag, + ) + snapshot.match("copy-obj-if-match", copy_result) + + # Copy should fail when destination ETag doesn't match (wrong ETag) + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfMatch='"wrong-etag"', + ) + snapshot.match("copy-obj-if-match-fail-wrong-etag", e.value.response) + + # Copy should fail when destination doesn't exist + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key="non-existent-key", + IfMatch=dest_etag, + ) + snapshot.match("copy-obj-if-match-fail-no-object", e.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: AWS started added ChecksumType to CopyObjectResult + "$..CopyObjectResult.ChecksumType", + # TODO: AWS stopped returning DisplayName (finally) + "$..Owner.DisplayName", + ] + ) + def test_copy_object_if_none_match_versioned_bucket(self, s3_bucket, aws_client, snapshot): + """Test CopyObject with If-None-Match header in a versioned bucket. + + For buckets with versioning enabled, S3 checks for the presence of a current object version. + If the current object version is a delete marker, the copy should succeed with If-None-Match. + """ + aws_client.s3.put_bucket_versioning( + Bucket=s3_bucket, VersioningConfiguration={"Status": "Enabled"} + ) + + src_key = "source-object" + dest_key = "dest-object" + + # Create source object + aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="source content") + + # Copy should succeed when destination doesn't exist + copy_result = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfNoneMatch="*", + ) + snapshot.match("copy-obj", copy_result) + + # Copy should fail when destination already exists + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfNoneMatch="*", + ) + snapshot.match("copy-obj-if-none-match-fail", e.value.response) + + # Delete the destination object (creates a delete marker in versioned bucket) + del_obj = aws_client.s3.delete_object(Bucket=s3_bucket, Key=dest_key) + snapshot.match("del-obj", del_obj) + + # Copy should succeed when current version is a delete marker + copy_after_del = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfNoneMatch="*", + ) + snapshot.match("copy-obj-after-del", copy_after_del) + + list_object_versions = aws_client.s3.list_object_versions(Bucket=s3_bucket) + snapshot.match("list-object-versions", list_object_versions) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: AWS started added ChecksumType to CopyObjectResult + "$..CopyObjectResult.ChecksumType", + # TODO: AWS stopped returning DisplayName (finally) + "$..Owner.DisplayName", + ] + ) + def test_copy_object_if_match_versioned_bucket(self, s3_bucket, aws_client, snapshot): + """Test CopyObject with If-Match header in a versioned bucket. + + For versioned buckets, If-Match checks against the current object version's ETag. + If the current version is a delete marker, the copy should fail. + """ + aws_client.s3.put_bucket_versioning( + Bucket=s3_bucket, VersioningConfiguration={"Status": "Enabled"} + ) + + src_key = "source-object" + dest_key = "dest-object" + + # Create source object + aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="source content") + + # Create initial destination object + put_result = aws_client.s3.put_object( + Bucket=s3_bucket, Key=dest_key, Body="initial content" + ) + snapshot.match("put-obj", put_result) + dest_etag_1 = put_result["ETag"] + + # Copy should fail with wrong ETag + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfMatch='"wrong-etag"', + ) + snapshot.match("copy-obj-if-match-wrong-etag", e.value.response) + + # Delete the destination object (creates a delete marker) + del_obj = aws_client.s3.delete_object(Bucket=s3_bucket, Key=dest_key) + snapshot.match("del-obj", del_obj) + + # Copy should fail when current version is a delete marker (even with correct old ETag) + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfMatch=dest_etag_1, + ) + snapshot.match("copy-obj-after-del-exc", e.value.response) + + # Create a new destination object + put_result_2 = aws_client.s3.put_object( + Bucket=s3_bucket, Key=dest_key, Body="new content after delete" + ) + snapshot.match("put-obj-after-del", put_result_2) + dest_etag_2 = put_result_2["ETag"] + + # Copy should succeed with correct current ETag + copy_result = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfMatch=dest_etag_2, + ) + snapshot.match("copy-obj-if-match", copy_result) + + list_object_versions = aws_client.s3.list_object_versions(Bucket=s3_bucket) + snapshot.match("list-object-versions", list_object_versions) + + @markers.aws.validated + def test_copy_object_precondition_write_validation(self, s3_bucket, aws_client, snapshot): + src_key = "source-object" + dest_key = "dest-object" + + # Create source object + put_obj = aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="source content") + + # assert that IfNoneMatch only accept '*' + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfNoneMatch=put_obj["ETag"], + ) + snapshot.match("copy-obj-none-match-etag", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfMatch="*", + ) + snapshot.match("copy-obj-if-match-star", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=dest_key, + IfMatch="*", + IfNoneMatch=put_obj["ETag"], + ) + snapshot.match("copy-obj-both", e.value.response) + + @markers.aws.validated + def test_copy_object_if_none_match_in_place(self, s3_bucket, aws_client, snapshot): + """Test CopyObject with If-None-Match header (destination conditional)""" + src_key = "source-object" + + # Create source object + aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="source content") + + # Copy should fail when destination already exists (in place) + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=src_key, + IfNoneMatch="*", + StorageClass=StorageClass.STANDARD, + ) + snapshot.match("copy-obj-if-none-match-fail", e.value.response) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: AWS started added ChecksumType to CopyObjectResult + "$..CopyObjectResult.ChecksumType", + ] + ) + def test_copy_object_if_match_in_place(self, s3_bucket, aws_client, snapshot): + """Test CopyObject with If-None-Match header (destination conditional)""" + src_key = "source-object" + + # Create source object + put_obj = aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="source content") + + # Copy should fail when destination already exists (in place) + # with pytest.raises(ClientError) as e: + copy_obj = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{src_key}", + Key=src_key, + IfMatch=put_obj["ETag"], + StorageClass=StorageClass.STANDARD, + ) + snapshot.match("copy-obj-if-match-in-place", copy_obj) + class TestS3MetricsConfiguration: @markers.aws.validated @@ -2364,6 +3494,9 @@ def test_delete_metrics_configuration_twice(self, s3_bucket, aws_client, snapsho snapshot.match("delete_bucket_metrics_configuration_2", delete_err.value.response) +@pytest.mark.skip( + reason="Behavior is not in line anymore with AWS: implement IfMatch in DeleteObject" +) class TestS3DeletePrecondition: @markers.aws.validated def test_delete_object_if_match_non_express(self, s3_bucket, aws_client, snapshot): @@ -2409,3 +3542,79 @@ def test_delete_object_if_match_all_non_express(self, s3_bucket, aws_client, sna IfMatchLastModifiedTime=earlier, ) snapshot.match("delete-obj-if-match-all", e.value.response) + + +@pytest.mark.skipif(condition=TEST_S3_IMAGE, reason="SQS not enabled in S3 image") +class TestS3BucketNotificationConfiguration: + @markers.aws.validated + def test_bucket_notification_with_missing_values_in_rule( + self, + s3_bucket, + sqs_create_queue, + snapshot, + aws_client, + aws_client_factory, + region_name, + ): + s3_client = aws_client_factory( + region_name=region_name, config=Config(parameter_validation=False) + ).s3 + queue_url = sqs_create_queue() + queue_attributes = aws_client.sqs.get_queue_attributes( + QueueUrl=queue_url, AttributeNames=["QueueArn"] + ) + filter_rule = {} + cfg = { + "QueueConfigurations": [ + { + "QueueArn": queue_attributes["Attributes"]["QueueArn"], + "Events": ["s3:ObjectCreated:*"], + "Filter": {"Key": {"FilterRules": [filter_rule]}}, + } + ] + } + with pytest.raises(ClientError) as e: + s3_client.put_bucket_notification_configuration( + Bucket=s3_bucket, NotificationConfiguration=cfg + ) + snapshot.match("invalid-rule-no-values", e.value.response) + + filter_rule["Name"] = "prefix" + with pytest.raises(ClientError) as e: + s3_client.put_bucket_notification_configuration( + Bucket=s3_bucket, NotificationConfiguration=cfg + ) + snapshot.match("invalid-rule-no-value", e.value.response) + + filter_rule["Value"] = "test" + del filter_rule["Name"] + with pytest.raises(ClientError) as e: + s3_client.put_bucket_notification_configuration( + Bucket=s3_bucket, NotificationConfiguration=cfg + ) + snapshot.match("invalid-rule-no-name", e.value.response) + + @markers.aws.validated + def test_bucket_notification_with_invalid_filter_rules( + self, s3_bucket, sqs_create_queue, snapshot, aws_client + ): + queue_url = sqs_create_queue() + queue_attributes = aws_client.sqs.get_queue_attributes( + QueueUrl=queue_url, AttributeNames=["QueueArn"] + ) + cfg = { + "QueueConfigurations": [ + { + "QueueArn": queue_attributes["Attributes"]["QueueArn"], + "Events": ["s3:ObjectCreated:*"], + "Filter": { + "Key": {"FilterRules": [{"Name": "INVALID", "Value": "does not matter"}]} + }, + } + ] + } + with pytest.raises(ClientError) as e: + aws_client.s3.put_bucket_notification_configuration( + Bucket=s3_bucket, NotificationConfiguration=cfg + ) + snapshot.match("invalid_filter_name", e.value.response) diff --git a/tests/aws/services/s3/test_s3_api.snapshot.json b/tests/aws/services/s3/test_s3_api.snapshot.json index 34d66622f8dae..b6326b0383f03 100644 --- a/tests/aws/services/s3/test_s3_api.snapshot.json +++ b/tests/aws/services/s3/test_s3_api.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_versioned": { - "recorded-date": "21-01-2025, 18:09:37", + "recorded-date": "21-02-2026, 01:01:45", "recorded-content": { "put-object": { "ChecksumCRC32": "1jy6qw==", @@ -76,7 +76,6 @@ "Key": "test-delete", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -86,7 +85,6 @@ "Key": "test-delete", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -110,7 +108,6 @@ "Key": "test-delete", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -173,7 +170,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object": { - "recorded-date": "21-01-2025, 18:09:31", + "recorded-date": "21-02-2026, 01:01:39", "recorded-content": { "put-object": { "ChecksumCRC32": "1jy6qw==", @@ -212,7 +209,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_versioned_bucket_with_objects": { - "recorded-date": "01-08-2023, 16:54:38", + "recorded-date": "21-02-2026, 01:01:38", "recorded-content": { "delete-with-obj-and-delete-marker": { "Error": { @@ -260,7 +257,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_with_version_unversioned_bucket": { - "recorded-date": "21-01-2025, 18:09:42", + "recorded-date": "21-02-2026, 01:01:51", "recorded-content": { "put-object": { "ChecksumCRC32": "jSiR5g==", @@ -303,7 +300,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_list_object_versions_order_unversioned": { - "recorded-date": "21-01-2025, 18:09:52", + "recorded-date": "21-02-2026, 01:02:01", "recorded-content": { "list-empty": { "EncodingType": "url", @@ -367,7 +364,6 @@ "Key": "a-test-object-1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 13, @@ -384,7 +380,6 @@ "Key": "b-test-object-2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 13, @@ -401,7 +396,6 @@ "Key": "c-test-object-3", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 13, @@ -417,7 +411,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_bucket_with_objects": { - "recorded-date": "27-07-2023, 00:25:16", + "recorded-date": "21-02-2026, 01:01:36", "recorded-content": { "delete-with-obj": { "Error": { @@ -445,7 +439,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects": { - "recorded-date": "21-01-2025, 18:09:33", + "recorded-date": "21-02-2026, 01:01:42", "recorded-content": { "put-object": { "ChecksumCRC32": "1jy6qw==", @@ -491,7 +485,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects_versioned": { - "recorded-date": "21-01-2025, 18:09:40", + "recorded-date": "23-02-2026, 11:04:21", "recorded-content": { "put-object": { "ChecksumCRC32": "1jy6qw==", @@ -576,7 +570,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_bucket_versioning_crud": { - "recorded-date": "21-01-2025, 18:10:29", + "recorded-date": "21-02-2026, 01:02:46", "recorded-content": { "get-versioning-before": { "ResponseMetadata": { @@ -671,7 +665,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_put_object_on_suspended_bucket": { - "recorded-date": "21-01-2025, 18:09:46", + "recorded-date": "21-02-2026, 01:01:55", "recorded-content": { "put-object-0": { "ChecksumCRC32": "yAYCLA==", @@ -725,7 +719,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -742,7 +735,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -759,7 +751,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -791,7 +782,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -808,7 +798,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -825,7 +814,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -867,7 +855,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 22, @@ -884,7 +871,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -901,7 +887,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -918,7 +903,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -960,7 +944,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 22, @@ -977,7 +960,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -994,7 +976,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1011,7 +992,6 @@ "Key": "test-version", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1044,7 +1024,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_on_suspended_bucket": { - "recorded-date": "21-01-2025, 18:09:50", + "recorded-date": "21-02-2026, 01:01:59", "recorded-content": { "put-object-0": { "ChecksumCRC32": "yAYCLA==", @@ -1087,7 +1067,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1104,7 +1083,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1132,7 +1110,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1156,7 +1133,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1173,7 +1149,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1215,7 +1190,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 35, @@ -1232,7 +1206,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1249,7 +1222,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1277,7 +1249,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1301,7 +1272,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1318,7 +1288,6 @@ "Key": "test-delete-suspended", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -1334,7 +1303,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption": { - "recorded-date": "21-01-2025, 18:10:45", + "recorded-date": "21-02-2026, 01:02:50", "recorded-content": { "default-bucket-encryption": { "ServerSideEncryptionConfiguration": { @@ -1373,7 +1342,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption_exc": { - "recorded-date": "21-01-2025, 18:10:47", + "recorded-date": "21-02-2026, 01:02:53", "recorded-content": { "get-bucket-enc-no-bucket": { "Error": { @@ -1442,7 +1411,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_s3": { - "recorded-date": "21-01-2025, 18:10:49", + "recorded-date": "21-02-2026, 01:02:55", "recorded-content": { "put-bucket-enc": { "ResponseMetadata": { @@ -1492,7 +1461,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms_aws_managed_key": { - "recorded-date": "21-01-2025, 18:10:55", + "recorded-date": "21-02-2026, 01:03:01", "recorded-content": { "put-bucket-enc": { "ResponseMetadata": { @@ -1520,7 +1489,7 @@ "BucketKeyEnabled": true, "ChecksumCRC32": "J1mCHA==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"08d16e16e9b2006587e811c5d81ea74f\"", + "ETag": "\"ec9f92ff18167a8c0cbff69597b6abf1\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -1533,6 +1502,7 @@ "AWSAccountId": "111111111111", "Arn": "arn::kms::111111111111:key/", "CreationDate": "datetime", + "CurrentKeyMaterialId": "", "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", "Description": "Default key that protects my S3 objects when no other key is defined", "Enabled": true, @@ -1557,7 +1527,7 @@ "BucketKeyEnabled": true, "ContentLength": 14, "ContentType": "binary/octet-stream", - "ETag": "\"08d16e16e9b2006587e811c5d81ea74f\"", + "ETag": "\"ec9f92ff18167a8c0cbff69597b6abf1\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -1575,7 +1545,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 14, "ContentType": "binary/octet-stream", - "ETag": "\"08d16e16e9b2006587e811c5d81ea74f\"", + "ETag": "\"ec9f92ff18167a8c0cbff69597b6abf1\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -1588,7 +1558,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms": { - "recorded-date": "21-01-2025, 18:10:53", + "recorded-date": "21-02-2026, 01:02:58", "recorded-content": { "put-bucket-enc": { "ResponseMetadata": { @@ -1617,7 +1587,7 @@ "BucketKeyEnabled": true, "ChecksumCRC32": "J1mCHA==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"ed93a03fee21ae796b5619dfb8afbe13\"", + "ETag": "\"88206b4eb968f907bf407d28fb04ceff\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -1630,7 +1600,7 @@ "BucketKeyEnabled": true, "ContentLength": 14, "ContentType": "binary/octet-stream", - "ETag": "\"ed93a03fee21ae796b5619dfb8afbe13\"", + "ETag": "\"88206b4eb968f907bf407d28fb04ceff\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -1648,7 +1618,7 @@ "ChecksumType": "FULL_OBJECT", "ContentLength": 14, "ContentType": "binary/octet-stream", - "ETag": "\"ed93a03fee21ae796b5619dfb8afbe13\"", + "ETag": "\"88206b4eb968f907bf407d28fb04ceff\"", "LastModified": "datetime", "Metadata": {}, "SSEKMSKeyId": "arn::kms::111111111111:key/", @@ -1667,7 +1637,7 @@ "put-object-encrypted-bucket-key-disabled": { "ChecksumCRC32": "J1mCHA==", "ChecksumType": "FULL_OBJECT", - "ETag": "\"0b507d4ef8c3b14da00a61984206ca0d\"", + "ETag": "\"7dfb290d8f2cc85b35a86adf9bceb32a\"", "SSEKMSKeyId": "arn::kms::111111111111:key/", "ServerSideEncryption": "aws:kms", "ResponseMetadata": { @@ -1678,7 +1648,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_crud": { - "recorded-date": "21-01-2025, 18:11:06", + "recorded-date": "23-02-2026, 11:08:45", "recorded-content": { "get-bucket-tags-empty": { "Error": { @@ -1746,7 +1716,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_crud": { - "recorded-date": "21-01-2025, 18:11:10", + "recorded-date": "23-02-2026, 11:09:07", "recorded-content": { "put-object": { "ChecksumCRC32": "lpqTBg==", @@ -1854,7 +1824,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_with_tags": { - "recorded-date": "21-01-2025, 18:11:19", + "recorded-date": "23-02-2026, 11:09:19", "recorded-content": { "put-object": { "ChecksumCRC32": "lpqTBg==", @@ -1912,6 +1882,7 @@ "LastModified": "datetime", "Metadata": {}, "ServerSideEncryption": "AES256", + "TagCount": 1, "ResponseMetadata": { "HTTPHeaders": {}, "HTTPStatusCode": 200 @@ -1969,7 +1940,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_exc": { - "recorded-date": "21-01-2025, 18:11:07", + "recorded-date": "23-02-2026, 11:08:53", "recorded-content": { "get-no-bucket-tags": { "Error": { @@ -2007,7 +1978,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": { - "recorded-date": "21-01-2025, 18:11:16", + "recorded-date": "23-02-2026, 11:09:16", "recorded-content": { "put-obj-0": { "ChecksumCRC32": "XCKz9A==", @@ -2084,6 +2055,22 @@ "HTTPStatusCode": 200 } }, + "get-object-tags-deleted-current": { + "TagSet": [], + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-object-tags-previous-version-deleted": { + "TagSet": [], + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, "put-delete-marker": { "DeleteMarker": true, "VersionId": "", @@ -2167,7 +2154,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_exc": { - "recorded-date": "21-01-2025, 18:11:13", + "recorded-date": "23-02-2026, 11:09:10", "recorded-content": { "get-no-bucket-tags": { "Error": { @@ -2262,7 +2249,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tags_delete_or_overwrite_object": { - "recorded-date": "21-01-2025, 18:11:22", + "recorded-date": "23-02-2026, 11:09:25", "recorded-content": { "get-object-after-creation": { "TagSet": [ @@ -2293,7 +2280,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_tagging_validation": { - "recorded-date": "21-01-2025, 18:11:25", + "recorded-date": "23-02-2026, 11:09:30", "recorded-content": { "put-bucket-tags-duplicate-keys": { "Error": { @@ -2317,6 +2304,16 @@ "HTTPStatusCode": 400 } }, + "put-bucket-tags-none-key": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, "put-bucket-tags-invalid-value": { "Error": { "Code": "InvalidTag", @@ -2329,6 +2326,16 @@ "HTTPStatusCode": 400 } }, + "put-bucket-tags-none-value": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, "put-bucket-tags-aws-prefixed": { "Error": { "Code": "InvalidTag", @@ -2372,11 +2379,31 @@ "HTTPHeaders": {}, "HTTPStatusCode": 400 } + }, + "put-object-tags-none-key": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "put-object-tags-none-value": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } } } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_on_existing_bucket": { - "recorded-date": "21-01-2025, 18:11:36", + "recorded-date": "21-02-2026, 01:03:59", "recorded-content": { "get-object-lock-existing-bucket-no-config": { "Error": { @@ -2439,7 +2466,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_put_object_lock_configuration": { - "recorded-date": "21-01-2025, 18:11:37", + "recorded-date": "21-02-2026, 01:04:00", "recorded-content": { "get-lock-config-start": { "ObjectLockConfiguration": { @@ -2489,7 +2516,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_exc": { - "recorded-date": "20-06-2025, 17:03:24", + "recorded-date": "21-02-2026, 01:04:04", "recorded-content": { "put-lock-config-no-enabled": { "Error": { @@ -2564,7 +2591,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_object_lock_configuration_exc": { - "recorded-date": "21-01-2025, 18:11:42", + "recorded-date": "21-02-2026, 01:04:06", "recorded-content": { "get-lock-config-no-enabled": { "Error": { @@ -2591,7 +2618,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_disable_versioning_on_locked_bucket": { - "recorded-date": "21-01-2025, 18:11:43", + "recorded-date": "21-02-2026, 01:04:07", "recorded-content": { "disable-versioning-on-locked-bucket": { "Error": { @@ -2612,7 +2639,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_crud_bucket_ownership_controls": { - "recorded-date": "10-08-2023, 02:57:08", + "recorded-date": "21-02-2026, 01:04:13", "recorded-content": { "default-ownership": { "OwnershipControls": { @@ -2685,7 +2712,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_bucket_ownership_controls_exc": { - "recorded-date": "10-08-2023, 03:08:54", + "recorded-date": "21-02-2026, 01:04:17", "recorded-content": { "default-ownership": { "OwnershipControls": { @@ -2755,7 +2782,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3PublicAccessBlock::test_crud_public_access_block": { - "recorded-date": "10-08-2023, 03:29:18", + "recorded-date": "21-02-2026, 01:04:19", "recorded-content": { "get-default-public-access-block": { "PublicAccessBlockConfiguration": { @@ -2817,7 +2844,7 @@ "recorded-content": {} }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_crud": { - "recorded-date": "20-10-2023, 17:31:38", + "recorded-date": "21-02-2026, 01:04:22", "recorded-content": { "get-bucket-default-policy": { "Error": { @@ -2881,7 +2908,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_exc": { - "recorded-date": "10-08-2023, 17:35:26", + "recorded-date": "21-02-2026, 01:04:24", "recorded-content": { "put-empty-bucket-policy": { "Error": { @@ -2916,7 +2943,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_crud": { - "recorded-date": "10-08-2023, 18:26:06", + "recorded-date": "21-02-2026, 01:04:28", "recorded-content": { "get-bucket-default-accelerate-config": { "ResponseMetadata": { @@ -2953,7 +2980,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_exc": { - "recorded-date": "10-08-2023, 18:01:50", + "recorded-date": "21-02-2026, 01:04:30", "recorded-content": { "put-bucket-accelerate-config-lowercase": { "Error": { @@ -2988,7 +3015,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_range": { - "recorded-date": "07-07-2025, 17:50:03", + "recorded-date": "21-02-2026, 01:02:09", "recorded-content": { "get-0-8": { "AcceptRanges": "bytes", @@ -3262,7 +3289,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_range": { - "recorded-date": "13-06-2025, 12:42:54", + "recorded-date": "21-02-2026, 01:02:15", "recorded-content": { "put-src-object": { "ChecksumCRC32": "poTHxg==", @@ -3328,7 +3355,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 3, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -3504,7 +3530,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_delete_object_with_no_locking": { - "recorded-date": "21-01-2025, 18:11:45", + "recorded-date": "21-02-2026, 01:04:09", "recorded-content": { "delete-object-bypass-no-lock": { "Error": { @@ -3542,7 +3568,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_no_copy_source_range": { - "recorded-date": "13-06-2025, 12:42:57", + "recorded-date": "21-02-2026, 01:02:18", "recorded-content": { "put-src-object": { "ChecksumCRC32": "poTHxg==", @@ -3586,7 +3612,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -3608,7 +3633,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match": { - "recorded-date": "17-03-2025, 20:15:37", + "recorded-date": "21-02-2026, 01:04:34", "recorded-content": { "put-obj": { "ChecksumCRC32": "AAAAAA==", @@ -3650,7 +3675,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_validation": { - "recorded-date": "17-03-2025, 20:15:39", + "recorded-date": "21-02-2026, 01:04:36", "recorded-content": { "put-obj": { "ChecksumCRC32": "AAAAAA==", @@ -3677,7 +3702,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_versioned_bucket": { - "recorded-date": "17-03-2025, 20:15:48", + "recorded-date": "21-02-2026, 01:04:44", "recorded-content": { "put-obj": { "ChecksumCRC32": "AAAAAA==", @@ -3727,7 +3752,6 @@ "Key": "test-precondition", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -3751,7 +3775,6 @@ "Key": "test-precondition", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -3768,7 +3791,6 @@ "Key": "test-precondition", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -3784,7 +3806,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_delete": { - "recorded-date": "17-03-2025, 20:15:42", + "recorded-date": "21-02-2026, 01:04:39", "recorded-content": { "put-obj": { "ChecksumCRC32": "AAAAAA==", @@ -3851,7 +3873,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_put": { - "recorded-date": "17-03-2025, 20:15:45", + "recorded-date": "21-02-2026, 01:04:41", "recorded-content": { "create-multipart": { "Bucket": "", @@ -3887,7 +3909,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_object_version_id_format": { - "recorded-date": "21-01-2025, 18:10:31", + "recorded-date": "21-02-2026, 01:02:47", "recorded-content": { "put-object": { "ChecksumCRC32": "AAAAAA==", @@ -3903,7 +3925,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match": { - "recorded-date": "17-03-2025, 20:15:51", + "recorded-date": "21-02-2026, 01:04:47", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -3966,7 +3988,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_validation": { - "recorded-date": "17-03-2025, 20:15:53", + "recorded-date": "21-02-2026, 01:04:49", "recorded-content": { "put-obj-if-match-star-value": { "Error": { @@ -4005,7 +4027,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put": { - "recorded-date": "17-03-2025, 20:15:56", + "recorded-date": "21-02-2026, 01:04:52", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -4086,7 +4108,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put_identical": { - "recorded-date": "17-03-2025, 20:15:59", + "recorded-date": "21-02-2026, 01:04:56", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -4156,7 +4178,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_delete": { - "recorded-date": "17-03-2025, 20:16:02", + "recorded-date": "21-02-2026, 01:04:59", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -4220,7 +4242,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_versioned_bucket": { - "recorded-date": "17-03-2025, 20:16:06", + "recorded-date": "21-02-2026, 01:05:02", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -4292,7 +4314,6 @@ "Key": "test-precondition", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -4316,7 +4337,6 @@ "Key": "test-precondition", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 13, @@ -4333,7 +4353,6 @@ "Key": "test-precondition", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 14, @@ -4350,7 +4369,6 @@ "Key": "test-precondition", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 4, @@ -4366,7 +4384,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_and_if_none_match_validation": { - "recorded-date": "17-03-2025, 20:16:07", + "recorded-date": "21-02-2026, 01:05:04", "recorded-content": { "put-obj-both-precondition": { "Error": { @@ -4383,7 +4401,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_etag": { - "recorded-date": "17-03-2025, 20:16:10", + "recorded-date": "21-02-2026, 01:05:07", "recorded-content": { "put-obj": { "ChecksumCRC32": "2H9+DA==", @@ -4455,7 +4473,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_put_bucket_metrics_configuration": { - "recorded-date": "13-06-2025, 08:33:02", + "recorded-date": "21-02-2026, 01:05:25", "recorded-content": { "put_bucket_metrics_configuration": { "ResponseMetadata": { @@ -4478,7 +4496,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_overwrite_bucket_metrics_configuration": { - "recorded-date": "13-06-2025, 08:33:03", + "recorded-date": "21-02-2026, 01:05:27", "recorded-content": { "overwrite_bucket_metrics_configuration": { "ResponseMetadata": { @@ -4501,7 +4519,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations": { - "recorded-date": "13-06-2025, 08:33:04", + "recorded-date": "21-02-2026, 01:05:28", "recorded-content": { "list_bucket_metrics_configurations": { "IsTruncated": false, @@ -4527,7 +4545,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration": { - "recorded-date": "13-06-2025, 08:33:17", + "recorded-date": "21-02-2026, 01:05:44", "recorded-content": { "get_bucket_metrics_configuration": { "MetricsConfiguration": { @@ -4544,7 +4562,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration_not_exist": { - "recorded-date": "13-06-2025, 08:33:18", + "recorded-date": "21-02-2026, 01:05:45", "recorded-content": { "get_bucket_metrics_configuration": { "Error": { @@ -4559,7 +4577,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration": { - "recorded-date": "13-06-2025, 08:33:19", + "recorded-date": "21-02-2026, 01:05:47", "recorded-content": { "delete_bucket_metrics_configuration": { "ResponseMetadata": { @@ -4580,7 +4598,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations_paginated": { - "recorded-date": "13-06-2025, 08:33:16", + "recorded-date": "21-02-2026, 01:05:43", "recorded-content": { "list_bucket_metrics_configurations_page_2": { "ContinuationToken": "", @@ -4607,7 +4625,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration_twice": { - "recorded-date": "13-06-2025, 08:33:20", + "recorded-date": "21-02-2026, 01:05:48", "recorded-content": { "delete_bucket_metrics_configuration_1": { "ResponseMetadata": { @@ -4628,23 +4646,23 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": { - "recorded-date": "23-06-2025, 08:53:20", + "recorded-date": "21-02-2026, 01:05:50", "recorded-content": { "delete-obj-if-match": { "Error": { - "Code": "NotImplemented", - "Header": "If-Match", - "Message": "A header you provided implies functionality that is not implemented" + "Code": "PreconditionFailed", + "Condition": "If-Match", + "Message": "At least one of the pre-conditions you specified did not hold" }, "ResponseMetadata": { "HTTPHeaders": {}, - "HTTPStatusCode": 501 + "HTTPStatusCode": 412 } } } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": { - "recorded-date": "23-06-2025, 09:05:52", + "recorded-date": "21-02-2026, 01:05:52", "recorded-content": { "delete-obj-if-match-last-modified": { "Error": { @@ -4660,7 +4678,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": { - "recorded-date": "23-06-2025, 09:05:59", + "recorded-date": "21-02-2026, 01:05:54", "recorded-content": { "delete-obj-if-match-size": { "Error": { @@ -4676,7 +4694,7 @@ } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": { - "recorded-date": "23-06-2025, 09:06:41", + "recorded-date": "21-02-2026, 01:05:56", "recorded-content": { "delete-obj-if-match-all": { "Error": { @@ -4690,5 +4708,1175 @@ } } } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketNotificationConfiguration::test_bucket_notification_with_missing_values_in_rule": { + "recorded-date": "21-02-2026, 01:05:59", + "recorded-content": { + "invalid-rule-no-values": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-rule-no-value": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-rule-no-name": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketNotificationConfiguration::test_bucket_notification_with_invalid_filter_rules": { + "recorded-date": "21-02-2026, 01:06:01", + "recorded-content": { + "invalid_filter_name": { + "Error": { + "ArgumentName": "FilterRule.Name", + "ArgumentValue": "INVALID", + "Code": "InvalidArgument", + "Message": "filter rule name must be either prefix or suffix" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_tagging_none_value": { + "recorded-date": "23-02-2026, 11:09:12", + "recorded-content": { + "put-none-tag-set": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-object-tags-none": { + "TagSet": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_bucket_tagging_none_value": { + "recorded-date": "23-02-2026, 11:09:04", + "recorded-content": { + "put-bucket-tags-origin": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + }, + "put-none-tag-set": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + }, + "no-such-tag-set": { + "Error": { + "BucketName": "", + "Code": "NoSuchTagSet", + "Message": "The TagSet does not exist" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_and_if_unmodified_since_match": { + "recorded-date": "21-02-2026, 01:02:36", + "recorded-content": { + "upload-part-copy": { + "CopyPartResult": { + "ETag": "", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-parts": { + "Bucket": "bucket", + "Initiator": { + "DisplayName": "display-name", + "ID": "i-d" + }, + "IsTruncated": false, + "Key": "destination_file.txt", + "MaxParts": 1000, + "NextPartNumberMarker": 1, + "Owner": { + "ID": "i-d" + }, + "PartNumberMarker": 0, + "Parts": [ + { + "ETag": "", + "LastModified": "datetime", + "PartNumber": 1, + "Size": 10 + } + ], + "StorageClass": "STANDARD", + "UploadId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_none_match_and_if_unmodified_since_match_failed": { + "recorded-date": "21-02-2026, 01:02:38", + "recorded-content": { + "upload-part-copy-source-unmodified-since-and-if-none-match": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Unmodified-Since", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_if_modified_since_failed": { + "recorded-date": "21-02-2026, 01:02:42", + "recorded-content": { + "upload-part-copy-source-modified-since-match": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Modified-Since", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_failed": { + "recorded-date": "21-02-2026, 01:02:30", + "recorded-content": { + "upload-part-copy-source-if-match": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_none_match_failed": { + "recorded-date": "21-02-2026, 01:02:32", + "recorded-content": { + "upload-part-copy-source-if-none-match": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-None-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_unmodified_since_match_failed": { + "recorded-date": "21-02-2026, 01:02:34", + "recorded-content": { + "upload-part-copy-source-unmodified-since-match": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Unmodified-Since", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_success": { + "recorded-date": "21-02-2026, 01:02:20", + "recorded-content": { + "upload-part-copy-if-match": { + "CopyPartResult": { + "ETag": "", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-parts": { + "Bucket": "bucket", + "Initiator": { + "DisplayName": "display-name", + "ID": "i-d" + }, + "IsTruncated": false, + "Key": "destination_file.txt", + "MaxParts": 1000, + "NextPartNumberMarker": 1, + "Owner": { + "ID": "i-d" + }, + "PartNumberMarker": 0, + "Parts": [ + { + "ETag": "", + "LastModified": "datetime", + "PartNumber": 1, + "Size": 10 + } + ], + "StorageClass": "STANDARD", + "UploadId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_none_success": { + "recorded-date": "21-02-2026, 01:02:22", + "recorded-content": { + "upload-part-copy-if-none-match": { + "CopyPartResult": { + "ETag": "", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-parts": { + "Bucket": "bucket", + "Initiator": { + "DisplayName": "display-name", + "ID": "i-d" + }, + "IsTruncated": false, + "Key": "destination_file.txt", + "MaxParts": 1000, + "NextPartNumberMarker": 1, + "Owner": { + "ID": "i-d" + }, + "PartNumberMarker": 0, + "Parts": [ + { + "ETag": "", + "LastModified": "datetime", + "PartNumber": 1, + "Size": 10 + } + ], + "StorageClass": "STANDARD", + "UploadId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_unmodified_since_success": { + "recorded-date": "21-02-2026, 01:02:24", + "recorded-content": { + "upload-part-copy-if-unmodified-since": { + "CopyPartResult": { + "ETag": "", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-parts": { + "Bucket": "bucket", + "Initiator": { + "DisplayName": "display-name", + "ID": "i-d" + }, + "IsTruncated": false, + "Key": "destination_file.txt", + "MaxParts": 1000, + "NextPartNumberMarker": 1, + "Owner": { + "ID": "i-d" + }, + "PartNumberMarker": 0, + "Parts": [ + { + "ETag": "", + "LastModified": "datetime", + "PartNumber": 1, + "Size": 10 + } + ], + "StorageClass": "STANDARD", + "UploadId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_modified_since_success": { + "recorded-date": "21-02-2026, 01:02:26", + "recorded-content": { + "upload-part-copy-if-modified-since": { + "CopyPartResult": { + "ETag": "", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-parts": { + "Bucket": "bucket", + "Initiator": { + "DisplayName": "display-name", + "ID": "i-d" + }, + "IsTruncated": false, + "Key": "destination_file.txt", + "MaxParts": 1000, + "NextPartNumberMarker": 1, + "Owner": { + "ID": "i-d" + }, + "PartNumberMarker": 0, + "Parts": [ + { + "ETag": "", + "LastModified": "datetime", + "PartNumber": 1, + "Size": 10 + } + ], + "StorageClass": "STANDARD", + "UploadId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_modified_since_in_future_success": { + "recorded-date": "21-02-2026, 01:02:28", + "recorded-content": { + "upload-part-copy-if-modified-since-in-future": { + "CopyPartResult": { + "ETag": "", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-parts": { + "Bucket": "bucket", + "Initiator": { + "DisplayName": "display-name", + "ID": "i-d" + }, + "IsTruncated": false, + "Key": "destination_file.txt", + "MaxParts": 1000, + "NextPartNumberMarker": 1, + "Owner": { + "ID": "i-d" + }, + "PartNumberMarker": 0, + "Parts": [ + { + "ETag": "", + "LastModified": "datetime", + "PartNumber": 1, + "Size": 10 + } + ], + "StorageClass": "STANDARD", + "UploadId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_tags": { + "recorded-date": "23-02-2026, 11:08:46", + "recorded-content": { + "create-bucket-with-tags": { + "BucketArn": "", + "Location": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-bucket-tags": { + "TagSet": [ + { + "Key": "tag1", + "Value": "value1" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_tags_us_east_1_idempotency": { + "recorded-date": "23-02-2026, 11:12:03", + "recorded-content": { + "create-bucket-with-tags": { + "BucketArn": "arn::s3:::", + "Location": "/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-bucket-tags": { + "TagSet": [ + { + "Key": "tag1", + "Value": "value1" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "create-bucket-different-tags": { + "Error": { + "BucketName": "", + "Code": "BucketAlreadyOwnedByYou", + "Message": "Your previous request to create the named bucket succeeded and you already own it." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 409 + } + }, + "create-bucket-with-no-tags": { + "BucketArn": "arn::s3:::", + "Location": "/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-bucket-tags-after-re-create": { + "TagSet": [ + { + "Key": "tag1", + "Value": "value1" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "create-bucket-with-same-tags": { + "Error": { + "BucketName": "", + "Code": "BucketAlreadyOwnedByYou", + "Message": "Your previous request to create the named bucket succeeded and you already own it." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 409 + } + }, + "create-bucket-with-empty-tags": { + "BucketArn": "arn::s3:::", + "Location": "/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-bucket-tags-after-create-empty": { + "TagSet": [ + { + "Key": "tag1", + "Value": "value1" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_tags_exc": { + "recorded-date": "23-02-2026, 11:09:02", + "recorded-content": { + "tags-key-none": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-value-none": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-key-aws-prefix": { + "Error": { + "Code": "InvalidTag", + "Message": "User-defined tag keys can't start with \"aws:\". This prefix is reserved for system tags. Remove \"aws:\" from your tag keys and try again." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-key-aws-duplicated-key": { + "Error": { + "Code": "InternalError", + "Message": "We encountered an internal error. Please try again." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 500 + } + }, + "tags-key-aws-bad-value": { + "Error": { + "Code": "InvalidTag", + "Message": "The TagValue you have provided is invalid", + "TagKey": "test", + "TagValue": "value1,value2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-key-aws-bad-key": { + "Error": { + "Code": "InvalidTag", + "Message": "The TagKey you have provided is invalid", + "TagKey": "test,test2" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_empty_tags": { + "recorded-date": "23-02-2026, 11:11:49", + "recorded-content": { + "create-bucket-with-empty-tags": { + "BucketArn": "arn::s3:::", + "Location": "/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-bucket-tags": { + "Error": { + "BucketName": "", + "Code": "NoSuchTagSet", + "Message": "The TagSet does not exist" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "create-bucket-with-none-tags": { + "BucketArn": "arn::s3:::", + "Location": "/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_head_object_with_tags": { + "recorded-date": "23-02-2026, 11:09:22", + "recorded-content": { + "put-object": { + "ChecksumCRC32": "lpqTBg==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"b635a7fc30aa9091e0d236bee77e6844\"", + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "head-obj-after-create": { + "AcceptRanges": "bytes", + "ContentLength": 12, + "ContentType": "binary/octet-stream", + "ETag": "\"b635a7fc30aa9091e0d236bee77e6844\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "TagCount": 3, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "put-object-tags": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "head-obj-after-tagging": { + "AcceptRanges": "bytes", + "ContentLength": 12, + "ContentType": "binary/octet-stream", + "ETag": "\"b635a7fc30aa9091e0d236bee77e6844\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "TagCount": 1, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "head-obj-after-overwrite": { + "AcceptRanges": "bytes", + "ContentLength": 12, + "ContentType": "binary/octet-stream", + "ETag": "\"b635a7fc30aa9091e0d236bee77e6844\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "TagCount": 3, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "head-obj-after-removal": { + "AcceptRanges": "bytes", + "ContentLength": 12, + "ContentType": "binary/octet-stream", + "ETag": "\"b635a7fc30aa9091e0d236bee77e6844\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_none_match": { + "recorded-date": "21-02-2026, 01:05:09", + "recorded-content": { + "copy-obj": { + "CopyObjectResult": { + "ChecksumCRC32": "wmoL/Q==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-obj-if-none-match-fail": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "If-None-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_match": { + "recorded-date": "21-02-2026, 01:05:11", + "recorded-content": { + "copy-obj-if-match": { + "CopyObjectResult": { + "ChecksumCRC32": "wmoL/Q==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-obj-if-match-fail-wrong-etag": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "If-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "copy-obj-if-match-fail-no-object": { + "Error": { + "Code": "NoSuchKey", + "Key": "non-existent-key", + "Message": "The specified key does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_none_match_versioned_bucket": { + "recorded-date": "21-02-2026, 01:05:14", + "recorded-content": { + "copy-obj": { + "CopyObjectResult": { + "ChecksumCRC32": "wmoL/Q==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "LastModified": "datetime" + }, + "CopySourceVersionId": "", + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-obj-if-none-match-fail": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "If-None-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "del-obj": { + "DeleteMarker": true, + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + }, + "copy-obj-after-del": { + "CopyObjectResult": { + "ChecksumCRC32": "wmoL/Q==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "LastModified": "datetime" + }, + "CopySourceVersionId": "", + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-object-versions": { + "DeleteMarkers": [ + { + "IsLatest": false, + "Key": "dest-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "VersionId": "" + } + ], + "EncodingType": "url", + "IsTruncated": false, + "KeyMarker": "", + "MaxKeys": 1000, + "Name": "", + "Prefix": "", + "VersionIdMarker": "", + "Versions": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "IsLatest": true, + "Key": "dest-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "Size": 14, + "StorageClass": "STANDARD", + "VersionId": "" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "IsLatest": false, + "Key": "dest-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "Size": 14, + "StorageClass": "STANDARD", + "VersionId": "" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "IsLatest": true, + "Key": "source-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "Size": 14, + "StorageClass": "STANDARD", + "VersionId": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_match_versioned_bucket": { + "recorded-date": "21-02-2026, 01:05:18", + "recorded-content": { + "put-obj": { + "ChecksumCRC32": "P8fZPQ==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"38370d56e3e114d73ebedd6ac7b19028\"", + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-obj-if-match-wrong-etag": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "If-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "del-obj": { + "DeleteMarker": true, + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + }, + "copy-obj-after-del-exc": { + "Error": { + "Code": "NoSuchKey", + "Key": "dest-object", + "Message": "The specified key does not exist." + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "put-obj-after-del": { + "ChecksumCRC32": "ORgJnw==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"06becddd7457b4bad77f1d3bca3a3ad1\"", + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-obj-if-match": { + "CopyObjectResult": { + "ChecksumCRC32": "wmoL/Q==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "LastModified": "datetime" + }, + "CopySourceVersionId": "", + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-object-versions": { + "DeleteMarkers": [ + { + "IsLatest": false, + "Key": "dest-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "VersionId": "" + } + ], + "EncodingType": "url", + "IsTruncated": false, + "KeyMarker": "", + "MaxKeys": 1000, + "Name": "", + "Prefix": "", + "VersionIdMarker": "", + "Versions": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "IsLatest": true, + "Key": "dest-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "Size": 14, + "StorageClass": "STANDARD", + "VersionId": "" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"06becddd7457b4bad77f1d3bca3a3ad1\"", + "IsLatest": false, + "Key": "dest-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "Size": 24, + "StorageClass": "STANDARD", + "VersionId": "" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"38370d56e3e114d73ebedd6ac7b19028\"", + "IsLatest": false, + "Key": "dest-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "Size": 15, + "StorageClass": "STANDARD", + "VersionId": "" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "IsLatest": true, + "Key": "source-object", + "LastModified": "datetime", + "Owner": { + "ID": "" + }, + "Size": 14, + "StorageClass": "STANDARD", + "VersionId": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_precondition_write_validation": { + "recorded-date": "21-02-2026, 01:05:21", + "recorded-content": { + "copy-obj-none-match-etag": { + "Error": { + "Code": "NotImplemented", + "Header": "If-None-Match", + "Message": "A header you provided implies functionality that is not implemented", + "additionalMessage": "We don't accept the provided value of If-None-Match header for this API" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 501 + } + }, + "copy-obj-if-match-star": { + "Error": { + "Code": "NotImplemented", + "Header": "If-Match", + "Message": "A header you provided implies functionality that is not implemented", + "additionalMessage": "We don't accept the provided value of If-Match header for this API" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 501 + } + }, + "copy-obj-both": { + "Error": { + "Code": "NotImplemented", + "Header": "If-Match,If-None-Match", + "Message": "A header you provided implies functionality that is not implemented", + "additionalMessage": "Multiple conditional request headers present in the request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 501 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_none_match_in_place": { + "recorded-date": "21-02-2026, 01:05:22", + "recorded-content": { + "copy-obj-if-none-match-fail": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "If-None-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_match_in_place": { + "recorded-date": "21-02-2026, 01:05:24", + "recorded-content": { + "copy-obj-if-match-in-place": { + "CopyObjectResult": { + "ChecksumCRC32": "wmoL/Q==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"4d96fd1280dd67fe2eac895339a0a8e3\"", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/s3/test_s3_api.validation.json b/tests/aws/services/s3/test_s3_api.validation.json index afbcc78afc0df..551ab03f95da0 100644 --- a/tests/aws/services/s3/test_s3_api.validation.json +++ b/tests/aws/services/s3/test_s3_api.validation.json @@ -1,248 +1,839 @@ { "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_crud": { - "last_validated_date": "2023-08-10T16:26:06+00:00" + "last_validated_date": "2026-02-21T01:04:28+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.41, + "teardown": 0.6, + "total": 3.53 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_exc": { - "last_validated_date": "2023-08-10T16:01:50+00:00" + "last_validated_date": "2026-02-21T01:04:32+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.07, + "teardown": 1.68, + "total": 3.26 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_bucket_with_objects": { - "last_validated_date": "2023-07-26T22:25:16+00:00" + "last_validated_date": "2026-02-21T01:01:36+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 0.91, + "teardown": 0.21, + "total": 2.19 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_versioned_bucket_with_objects": { - "last_validated_date": "2024-08-29T13:20:49+00:00" + "last_validated_date": "2026-02-21T01:01:38+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.42, + "teardown": 0.21, + "total": 2.17 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms": { - "last_validated_date": "2025-01-21T18:10:53+00:00" + "last_validated_date": "2026-02-21T01:03:00+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 1.58, + "teardown": 1.14, + "total": 3.79 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms_aws_managed_key": { - "last_validated_date": "2025-01-21T18:10:55+00:00" + "last_validated_date": "2026-02-21T01:03:02+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.03, + "teardown": 1.01, + "total": 2.56 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_s3": { - "last_validated_date": "2025-01-21T18:10:49+00:00" + "last_validated_date": "2026-02-21T01:02:56+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.7, + "teardown": 0.99, + "total": 2.22 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption": { - "last_validated_date": "2025-01-21T18:10:45+00:00" + "last_validated_date": "2026-02-21T01:02:51+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.62, + "teardown": 0.62, + "total": 1.77 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption_exc": { - "last_validated_date": "2025-01-21T18:10:47+00:00" + "last_validated_date": "2026-02-21T01:02:53+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.48, + "teardown": 0.97, + "total": 2.97 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketNotificationConfiguration::test_bucket_notification_with_invalid_filter_rules": { + "last_validated_date": "2026-02-21T01:06:02+00:00", + "durations_in_seconds": { + "setup": 0.6, + "call": 0.37, + "teardown": 1.0, + "total": 1.97 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketNotificationConfiguration::test_bucket_notification_with_missing_values_in_rule": { + "last_validated_date": "2026-02-21T01:06:00+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.6, + "teardown": 0.82, + "total": 2.92 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_crud": { - "last_validated_date": "2025-01-21T18:11:06+00:00" + "last_validated_date": "2026-02-23T11:08:45+00:00", + "durations_in_seconds": { + "setup": 1.03, + "call": 1.67, + "teardown": 0.62, + "total": 3.32 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_exc": { - "last_validated_date": "2025-06-12T23:32:16+00:00" + "last_validated_date": "2026-02-23T11:08:54+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 0.56, + "teardown": 0.71, + "total": 1.82 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_empty_tags": { + "last_validated_date": "2026-02-23T11:11:50+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.99, + "teardown": 1.08, + "total": 2.55 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_tags": { + "last_validated_date": "2026-02-23T11:08:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.78, + "teardown": 0.62, + "total": 1.4 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_tags_exc": { + "last_validated_date": "2026-02-23T11:09:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 8.66, + "teardown": 0.01, + "total": 8.67 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_create_bucket_with_tags_us_east_1_idempotency": { + "last_validated_date": "2026-02-23T11:12:04+00:00", + "durations_in_seconds": { + "setup": 0.47, + "call": 1.71, + "teardown": 1.24, + "total": 3.42 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_head_object_with_tags": { + "last_validated_date": "2026-02-23T11:09:23+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.13, + "teardown": 1.02, + "total": 2.68 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_crud": { - "last_validated_date": "2025-01-21T18:11:10+00:00" + "last_validated_date": "2026-02-23T11:09:08+00:00", + "durations_in_seconds": { + "setup": 0.6, + "call": 1.73, + "teardown": 0.99, + "total": 3.32 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_exc": { - "last_validated_date": "2025-01-21T18:11:13+00:00" + "last_validated_date": "2026-02-23T11:09:10+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.31, + "teardown": 0.86, + "total": 2.69 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": { - "last_validated_date": "2025-01-21T18:11:16+00:00" + "last_validated_date": "2026-02-23T11:09:17+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 2.59, + "teardown": 1.01, + "total": 4.19 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tags_delete_or_overwrite_object": { - "last_validated_date": "2025-01-21T18:11:22+00:00" + "last_validated_date": "2026-02-23T11:09:26+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.25, + "teardown": 0.98, + "total": 2.75 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_bucket_tagging_none_value": { + "last_validated_date": "2026-02-23T11:09:04+00:00", + "durations_in_seconds": { + "setup": 0.58, + "call": 0.82, + "teardown": 0.61, + "total": 2.01 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_tagging_none_value": { + "last_validated_date": "2026-02-23T11:09:13+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 0.73, + "teardown": 1.06, + "total": 2.34 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_with_tags": { - "last_validated_date": "2025-01-21T18:11:19+00:00" + "last_validated_date": "2026-02-23T11:09:20+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.73, + "teardown": 0.96, + "total": 3.22 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_tagging_validation": { - "last_validated_date": "2025-01-21T18:11:25+00:00" + "last_validated_date": "2026-02-23T11:09:31+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 4.09, + "teardown": 1.01, + "total": 5.63 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_bucket_ownership_controls_exc": { - "last_validated_date": "2023-08-10T01:08:54+00:00" + "last_validated_date": "2026-02-21T01:04:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.38, + "teardown": 0.85, + "total": 3.23 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_crud_bucket_ownership_controls": { - "last_validated_date": "2023-08-10T00:57:08+00:00" + "last_validated_date": "2026-02-21T01:04:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.44, + "teardown": 1.19, + "total": 3.63 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_crud": { - "last_validated_date": "2023-10-20T15:31:38+00:00" + "last_validated_date": "2026-02-21T01:04:22+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.44, + "teardown": 0.62, + "total": 2.59 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_exc": { - "last_validated_date": "2023-08-10T15:35:26+00:00" + "last_validated_date": "2026-02-21T01:04:25+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.35, + "teardown": 0.86, + "total": 2.74 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_bucket_versioning_crud": { - "last_validated_date": "2025-01-21T18:10:29+00:00" + "last_validated_date": "2026-02-21T01:02:47+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.55, + "teardown": 0.84, + "total": 3.91 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_object_version_id_format": { - "last_validated_date": "2025-01-21T18:10:31+00:00" + "last_validated_date": "2026-02-21T01:02:49+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.34, + "teardown": 1.37, + "total": 2.23 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": { - "last_validated_date": "2025-06-23T09:06:43+00:00", + "last_validated_date": "2026-02-21T01:05:57+00:00", "durations_in_seconds": { - "setup": 0.96, - "call": 0.24, - "teardown": 1.13, - "total": 2.33 + "setup": 0.55, + "call": 0.22, + "teardown": 1.18, + "total": 1.95 } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": { - "last_validated_date": "2025-06-23T09:05:53+00:00", + "last_validated_date": "2026-02-21T01:05:53+00:00", "durations_in_seconds": { - "setup": 1.05, - "call": 0.23, - "teardown": 1.16, - "total": 2.44 + "setup": 0.52, + "call": 0.25, + "teardown": 1.23, + "total": 2.0 } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": { - "last_validated_date": "2025-06-23T08:53:21+00:00", + "last_validated_date": "2026-02-21T01:05:51+00:00", "durations_in_seconds": { - "setup": 1.05, - "call": 0.24, - "teardown": 1.1, - "total": 2.39 + "setup": 0.56, + "call": 0.25, + "teardown": 0.97, + "total": 1.78 } }, "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": { - "last_validated_date": "2025-06-23T09:06:00+00:00", + "last_validated_date": "2026-02-21T01:05:55+00:00", "durations_in_seconds": { - "setup": 0.98, - "call": 0.22, - "teardown": 1.17, - "total": 2.37 + "setup": 0.53, + "call": 0.24, + "teardown": 1.24, + "total": 2.01 } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration": { - "last_validated_date": "2025-06-13T08:33:19+00:00" + "last_validated_date": "2026-02-21T01:05:47+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.37, + "teardown": 0.6, + "total": 1.49 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration_twice": { - "last_validated_date": "2025-06-13T08:33:20+00:00" + "last_validated_date": "2026-02-21T01:05:49+00:00", + "durations_in_seconds": { + "setup": 0.63, + "call": 0.36, + "teardown": 0.61, + "total": 1.6 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration": { - "last_validated_date": "2025-06-13T08:33:17+00:00" + "last_validated_date": "2026-02-21T01:05:45+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.24, + "teardown": 0.67, + "total": 1.42 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration_not_exist": { - "last_validated_date": "2025-06-13T08:33:18+00:00" + "last_validated_date": "2026-02-21T01:05:46+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.12, + "teardown": 0.61, + "total": 1.26 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations": { - "last_validated_date": "2025-06-13T08:33:04+00:00" + "last_validated_date": "2026-02-21T01:05:29+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.41, + "teardown": 0.63, + "total": 1.58 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations_paginated": { - "last_validated_date": "2025-06-13T08:33:16+00:00" + "last_validated_date": "2026-02-21T01:05:43+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 13.27, + "teardown": 0.57, + "total": 14.35 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_overwrite_bucket_metrics_configuration": { - "last_validated_date": "2025-06-13T08:33:03+00:00" + "last_validated_date": "2026-02-21T01:05:27+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.37, + "teardown": 0.61, + "total": 1.51 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_put_bucket_metrics_configuration": { - "last_validated_date": "2025-06-13T08:33:02+00:00" + "last_validated_date": "2026-02-21T01:05:26+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.25, + "teardown": 0.59, + "total": 1.36 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_no_copy_source_range": { - "last_validated_date": "2025-06-13T12:42:58+00:00", + "last_validated_date": "2026-02-21T01:02:19+00:00", "durations_in_seconds": { - "setup": 0.55, - "call": 0.66, - "teardown": 1.07, - "total": 2.28 + "setup": 0.51, + "call": 0.64, + "teardown": 1.02, + "total": 2.17 } }, "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_range": { - "last_validated_date": "2025-06-13T12:42:55+00:00", + "last_validated_date": "2026-02-21T01:02:17+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 4.83, + "teardown": 1.19, + "total": 6.52 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_and_if_unmodified_since_match": { + "last_validated_date": "2026-02-21T01:02:37+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.64, + "teardown": 1.0, + "total": 2.17 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_failed": { + "last_validated_date": "2026-02-21T01:02:31+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.46, + "teardown": 0.98, + "total": 1.94 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_none_success": { + "last_validated_date": "2026-02-21T01:02:23+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.63, + "teardown": 1.02, + "total": 2.17 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_match_success": { + "last_validated_date": "2026-02-21T01:02:21+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.61, + "teardown": 0.99, + "total": 2.11 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_modified_since_in_future_success": { + "last_validated_date": "2026-02-21T01:02:29+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.61, + "teardown": 0.97, + "total": 2.08 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_modified_since_success": { + "last_validated_date": "2026-02-21T01:02:27+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.62, + "teardown": 0.95, + "total": 2.07 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_none_match_and_if_unmodified_since_match_failed": { + "last_validated_date": "2026-02-21T01:02:40+00:00", "durations_in_seconds": { - "setup": 1.02, - "call": 5.28, - "teardown": 1.54, - "total": 7.84 + "setup": 0.53, + "call": 0.51, + "teardown": 1.12, + "total": 2.16 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_none_match_failed": { + "last_validated_date": "2026-02-21T01:02:33+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.48, + "teardown": 0.98, + "total": 1.99 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_unmodified_since_match_failed": { + "last_validated_date": "2026-02-21T01:02:35+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.47, + "teardown": 0.98, + "total": 1.96 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_copy_source_if_unmodified_since_success": { + "last_validated_date": "2026-02-21T01:02:25+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.61, + "teardown": 1.08, + "total": 2.21 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_with_if_modified_since_failed": { + "last_validated_date": "2026-02-21T01:02:43+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.48, + "teardown": 0.98, + "total": 2.98 } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object": { - "last_validated_date": "2025-01-21T18:09:31+00:00" + "last_validated_date": "2026-02-21T01:01:40+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 0.58, + "teardown": 0.8, + "total": 1.93 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_on_suspended_bucket": { - "last_validated_date": "2025-01-21T18:09:50+00:00" + "last_validated_date": "2026-02-21T01:02:00+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.9, + "teardown": 0.98, + "total": 3.42 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_versioned": { - "last_validated_date": "2025-01-21T18:09:37+00:00" + "last_validated_date": "2026-02-21T01:01:46+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 2.32, + "teardown": 1.18, + "total": 4.03 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects": { - "last_validated_date": "2025-01-21T18:09:33+00:00" + "last_validated_date": "2026-02-21T01:01:42+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.74, + "teardown": 0.9, + "total": 2.15 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects_versioned": { - "last_validated_date": "2025-01-21T18:09:40+00:00" + "last_validated_date": "2026-02-23T11:04:22+00:00", + "durations_in_seconds": { + "setup": 1.03, + "call": 1.95, + "teardown": 1.23, + "total": 4.21 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_range": { - "last_validated_date": "2025-07-07T17:50:04+00:00", + "last_validated_date": "2026-02-21T01:02:10+00:00", "durations_in_seconds": { - "setup": 1.13, - "call": 6.21, - "teardown": 0.99, - "total": 8.33 + "setup": 0.51, + "call": 6.12, + "teardown": 0.96, + "total": 7.59 } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_with_version_unversioned_bucket": { - "last_validated_date": "2025-01-21T18:09:42+00:00" + "last_validated_date": "2026-02-21T01:01:52+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.79, + "teardown": 0.76, + "total": 2.08 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_list_object_versions_order_unversioned": { - "last_validated_date": "2025-01-21T18:09:52+00:00" + "last_validated_date": "2026-02-21T01:02:02+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.96, + "teardown": 0.96, + "total": 2.43 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_put_object_on_suspended_bucket": { - "last_validated_date": "2025-01-21T18:09:46+00:00" + "last_validated_date": "2026-02-21T01:01:57+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 2.45, + "teardown": 1.43, + "total": 4.41 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_delete_object_with_no_locking": { - "last_validated_date": "2025-01-21T18:11:45+00:00" + "last_validated_date": "2026-02-21T01:04:11+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.02, + "teardown": 1.35, + "total": 2.91 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_disable_versioning_on_locked_bucket": { - "last_validated_date": "2025-01-21T18:11:43+00:00" + "last_validated_date": "2026-02-21T01:04:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.87, + "teardown": 0.61, + "total": 1.48 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_object_lock_configuration_exc": { - "last_validated_date": "2025-01-21T18:11:42+00:00" + "last_validated_date": "2026-02-21T01:04:06+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.47, + "teardown": 0.61, + "total": 1.61 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_put_object_lock_configuration": { - "last_validated_date": "2025-01-21T18:11:37+00:00" + "last_validated_date": "2026-02-21T01:04:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.25, + "teardown": 0.6, + "total": 1.85 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_exc": { - "last_validated_date": "2025-06-20T17:03:25+00:00", + "last_validated_date": "2026-02-21T01:04:05+00:00", "durations_in_seconds": { - "setup": 0.55, - "call": 2.64, - "teardown": 0.83, - "total": 4.02 + "setup": 0.0, + "call": 2.79, + "teardown": 0.88, + "total": 3.67 } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_on_existing_bucket": { - "last_validated_date": "2025-01-21T18:11:36+00:00" + "last_validated_date": "2026-02-21T01:03:59+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.49, + "teardown": 0.64, + "total": 2.65 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_match": { + "last_validated_date": "2026-02-21T01:05:11+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.81, + "teardown": 1.02, + "total": 2.36 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_match_in_place": { + "last_validated_date": "2026-02-21T01:05:24+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.37, + "teardown": 0.97, + "total": 1.87 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_match_versioned_bucket": { + "last_validated_date": "2026-02-21T01:05:18+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.49, + "teardown": 1.51, + "total": 3.51 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_none_match": { + "last_validated_date": "2026-02-21T01:05:09+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.48, + "teardown": 1.04, + "total": 2.04 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_none_match_in_place": { + "last_validated_date": "2026-02-21T01:05:22+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.33, + "teardown": 1.02, + "total": 1.86 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_if_none_match_versioned_bucket": { + "last_validated_date": "2026-02-21T01:05:14+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.11, + "teardown": 1.39, + "total": 3.02 + } + }, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_copy_object_precondition_write_validation": { + "last_validated_date": "2026-02-21T01:05:21+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.0, + "teardown": 1.28, + "total": 2.8 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_etag": { - "last_validated_date": "2025-03-17T20:16:09+00:00" + "last_validated_date": "2026-02-21T01:05:07+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.49, + "teardown": 1.01, + "total": 3.04 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_delete": { - "last_validated_date": "2025-03-17T20:16:01+00:00" + "last_validated_date": "2026-02-21T01:04:59+00:00", + "durations_in_seconds": { + "setup": 0.58, + "call": 1.37, + "teardown": 1.03, + "total": 2.98 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put": { - "last_validated_date": "2025-03-17T20:15:55+00:00" + "last_validated_date": "2026-02-21T01:04:52+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.7, + "teardown": 1.01, + "total": 3.23 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put_identical": { - "last_validated_date": "2025-03-17T20:15:58+00:00" + "last_validated_date": "2026-02-21T01:04:56+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.59, + "teardown": 1.02, + "total": 3.14 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_delete": { - "last_validated_date": "2025-03-17T20:15:41+00:00" + "last_validated_date": "2026-02-21T01:04:39+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.49, + "teardown": 0.98, + "total": 2.97 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_put": { - "last_validated_date": "2025-03-17T20:15:44+00:00" + "last_validated_date": "2026-02-21T01:04:41+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.63, + "teardown": 0.99, + "total": 2.14 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match": { - "last_validated_date": "2025-03-17T20:15:50+00:00" + "last_validated_date": "2026-02-21T01:04:47+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.25, + "teardown": 1.03, + "total": 2.78 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_and_if_none_match_validation": { - "last_validated_date": "2025-06-12T23:32:49+00:00" + "last_validated_date": "2026-02-21T01:05:04+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.11, + "teardown": 0.83, + "total": 1.46 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_validation": { - "last_validated_date": "2025-03-17T20:15:52+00:00" + "last_validated_date": "2026-02-21T01:04:49+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.89, + "teardown": 0.87, + "total": 2.28 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_versioned_bucket": { - "last_validated_date": "2025-03-17T20:16:04+00:00" + "last_validated_date": "2026-02-21T01:05:02+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 1.76, + "teardown": 1.44, + "total": 3.75 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match": { - "last_validated_date": "2025-03-17T20:15:36+00:00" + "last_validated_date": "2026-02-21T01:04:34+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.72, + "teardown": 1.01, + "total": 2.26 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_validation": { - "last_validated_date": "2025-03-17T20:15:38+00:00" + "last_validated_date": "2026-02-21T01:04:36+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.24, + "teardown": 1.22, + "total": 1.99 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_versioned_bucket": { - "last_validated_date": "2025-03-17T20:15:46+00:00" + "last_validated_date": "2026-02-21T01:04:44+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.11, + "teardown": 1.48, + "total": 3.13 + } }, "tests/aws/services/s3/test_s3_api.py::TestS3PublicAccessBlock::test_crud_public_access_block": { - "last_validated_date": "2023-08-10T01:29:18+00:00" + "last_validated_date": "2026-02-21T01:04:20+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.88, + "teardown": 0.6, + "total": 2.0 + } } } diff --git a/tests/aws/services/s3/test_s3_list_operations.py b/tests/aws/services/s3/test_s3_list_operations.py index f081782c0ecb3..e8f9a27c5e411 100644 --- a/tests/aws/services/s3/test_s3_list_operations.py +++ b/tests/aws/services/s3/test_s3_list_operations.py @@ -5,6 +5,7 @@ import datetime from io import BytesIO +from urllib.parse import quote import pytest import xmltodict @@ -160,6 +161,53 @@ def test_list_buckets_with_continuation_token(self, s3_create_bucket, aws_client snapshot.match("list-objects-with-continuation", response) + @markers.aws.validated + def test_list_buckets_region_validation(self, aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.s3.list_buckets(BucketRegion="eu-east-1") + snapshot.match("bad-region", e.value.response) + + @markers.aws.only_localstack + @markers.requires_in_process + def test_region_validation_non_standard_regions_enabled( + self, snapshot, monkeypatch, aws_client_factory, s3_create_bucket_with_client + ): + monkeypatch.setattr(config, "ALLOW_NONSTANDARD_REGIONS", True) + # we need to patch the `DefaultRegionRewriterStrategy` as it wil replace `eu-east-1` by `us-east-1`, which + # is its default region + from localstack.aws.handlers.region import DefaultRegionRewriterStrategy + + monkeypatch.setattr(DefaultRegionRewriterStrategy, "apply", lambda *_, **__: None) + + bad_region = "eu-east-1" + + # A client created with us-east-1 can create buckets in any region, including the bad region. + client_us_east_1 = aws_client_factory(region_name=AWS_REGION_US_EAST_1).s3 + s3_create_bucket_with_client( + client_us_east_1, + CreateBucketConfiguration={"LocationConstraint": bad_region}, + ) + + # A client created in the bad region should can only create buckets in this region. + client_bad_region = aws_client_factory(region_name=bad_region).s3 + s3_create_bucket_with_client( + client_bad_region, + CreateBucketConfiguration={"LocationConstraint": bad_region}, + ) + with pytest.raises(ClientError) as e: + s3_create_bucket_with_client( + client_bad_region, + CreateBucketConfiguration={"LocationConstraint": AWS_REGION_US_EAST_1}, + ) + assert ( + e.value.response["Error"]["Message"] + == "The us-east-1 location constraint is incompatible for the region specific endpoint this request was sent to." + ) + + list_buckets = client_us_east_1.list_buckets(BucketRegion=bad_region) + for bucket in list_buckets["Buckets"]: + assert bucket["BucketRegion"] == bad_region + class TestS3ListObjects: @markers.aws.validated @@ -430,6 +478,114 @@ def test_list_objects_v2_continuation_common_prefixes(self, s3_bucket, snapshot, snapshot.match("list-objects-v2-end", response) assert "NextContinuationToken" not in response + @markers.aws.validated + def test_list_objects_v2_continuation_token_safe_chars( + self, s3_bucket, snapshot, aws_client_factory + ): + snapshot.add_transformer(snapshot.transform.s3_api()) + # S3 does not have a consistent ContinuationToken, must be based on a paginator + snapshot.add_transformer( + snapshot.transform.key_value("NextContinuationToken", reference_replacement=False) + ) + snapshot.add_transformer( + snapshot.transform.key_value("ContinuationToken", reference_replacement=False) + ) + # we disable validation on the client to be able to set EncodingType to None (only valid value is `url`) + s3_client = aws_client_factory(config=Config(parameter_validation=False)).s3 + keys = [ + "file%2Fname", + "test@key/", + "test%123", + "test%percent", + "test key/", + "test key//", + "test%123/", + "a/%F0%9F%98%80/", + "date=2026-01-01/", + "date=2026-02-01/", + "date=2026-03-01/", + "date=2026-05-01/", + ] + for key in keys: + s3_client.put_object(Bucket=s3_bucket, Key=key) + + response = s3_client.list_objects_v2(Bucket=s3_bucket, MaxKeys=5) + snapshot.match("list-objects-v2-max-5", response) + + continuation_token = response["NextContinuationToken"] + + response = s3_client.list_objects_v2(Bucket=s3_bucket, ContinuationToken=continuation_token) + snapshot.match("list-objects-v2-rest", response) + + start_after = keys[-3] + # forcing the EncodingType to be `None` to verify behavior + response = s3_client.list_objects_v2( + Bucket=s3_bucket, + StartAfter=start_after, + MaxKeys=2, + EncodingType=None, + ) + snapshot.match("list-objects-start-after", response) + + response = s3_client.list_objects_v2( + Bucket=s3_bucket, + StartAfter=start_after, + MaxKeys=2, + EncodingType="url", + ) + snapshot.match("list-objects-start-after-url-type", response) + + url_safe_key = quote(start_after) + response = s3_client.list_objects_v2(Bucket=s3_bucket, StartAfter=url_safe_key, MaxKeys=2) + snapshot.match("list-objects-start-after-encoded", response) + continuation_token_2 = response["NextContinuationToken"] + + response = s3_client.list_objects_v2( + Bucket=s3_bucket, + StartAfter=start_after, + ContinuationToken=continuation_token_2, + MaxKeys=2, + ) + snapshot.match("list-objects-start-after-token", response) + + response = s3_client.list_objects_v2( + Bucket=s3_bucket, + ContinuationToken=continuation_token_2, + MaxKeys=2, + ) + snapshot.match("list-objects-continuation-token-2", response) + + @markers.aws.validated + @pytest.mark.parametrize( + "s3_operation", + ( + "list_objects_v2", + "list_objects", + "list_object_versions", + "list_multipart_uploads", + ), + ) + def test_list_ops_encoding_type_validation( + self, s3_bucket, snapshot, aws_client_factory, s3_operation + ): + # we disable validation on the client to be able to set EncodingType to None (only valid value is `url`) + s3_client = aws_client_factory(config=Config(parameter_validation=False)).s3 + if s3_operation == "list_object_versions": + # to test list_object_versions, we need the bucket to be versioned + s3_client.put_bucket_versioning( + Bucket=s3_bucket, VersioningConfiguration={"Status": "Enabled"} + ) + + client_method = getattr(s3_client, s3_operation) + + with pytest.raises(ClientError) as e: + client_method(Bucket=s3_bucket, EncodingType="value") + snapshot.match(f"{s3_operation}-error-wrong-value", e.value.response) + + with pytest.raises(ClientError) as e: + client_method(Bucket=s3_bucket, EncodingType="") + snapshot.match(f"{s3_operation}-error-empty-value", e.value.response) + class TestS3ListObjectVersions: @markers.aws.validated diff --git a/tests/aws/services/s3/test_s3_list_operations.snapshot.json b/tests/aws/services/s3/test_s3_list_operations.snapshot.json index ab827dee03cff..aebc0f6e7f64e 100644 --- a/tests/aws/services/s3/test_s3_list_operations.snapshot.json +++ b/tests/aws/services/s3/test_s3_list_operations.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[]": { - "recorded-date": "21-01-2025, 18:14:22", + "recorded-date": "21-02-2026, 00:05:13", "recorded-content": { "list-objects": { "Contents": [ @@ -13,7 +13,6 @@ "Key": "test/foo/bar/123", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -34,7 +33,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[/]": { - "recorded-date": "21-01-2025, 18:14:24", + "recorded-date": "21-02-2026, 00:05:14", "recorded-content": { "list-objects": { "CommonPrefixes": [ @@ -57,7 +56,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[%2F]": { - "recorded-date": "21-01-2025, 18:14:26", + "recorded-date": "21-02-2026, 00:05:17", "recorded-content": { "list-objects": { "Contents": [ @@ -70,7 +69,6 @@ "Key": "test/foo/bar/123", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -105,7 +103,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_next_marker": { - "recorded-date": "21-01-2025, 18:14:29", + "recorded-date": "21-02-2026, 00:05:19", "recorded-content": { "list-objects-all": { "Contents": [ @@ -118,7 +116,6 @@ "Key": "", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -133,7 +130,6 @@ "Key": "", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -148,7 +144,6 @@ "Key": "", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -177,7 +172,6 @@ "Key": "", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -208,7 +202,6 @@ "Key": "", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -237,7 +230,6 @@ "Key": "", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -258,7 +250,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_empty_marker": { - "recorded-date": "21-01-2025, 18:14:31", + "recorded-date": "21-02-2026, 00:05:21", "recorded-content": { "list-objects": { "EncodingType": "url", @@ -275,7 +267,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix": { - "recorded-date": "21-01-2025, 18:14:40", + "recorded-date": "21-02-2026, 00:05:31", "recorded-content": { "list-objects-v2-1": { "Contents": [ @@ -428,7 +420,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix_and_delimiter": { - "recorded-date": "21-01-2025, 18:14:43", + "recorded-date": "21-02-2026, 00:05:33", "recorded-content": { "list-objects-v2-1": { "CommonPrefixes": [ @@ -509,7 +501,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_start_after": { - "recorded-date": "21-01-2025, 18:14:48", + "recorded-date": "21-02-2026, 00:05:38", "recorded-content": { "list-objects-v2-max-5": { "Contents": [ @@ -816,7 +808,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_common_prefixes": { - "recorded-date": "21-01-2025, 18:14:51", + "recorded-date": "21-02-2026, 00:05:41", "recorded-content": { "list-objects-v2-all-keys": { "Contents": [ @@ -953,7 +945,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_markers": { - "recorded-date": "21-01-2025, 18:14:56", + "recorded-date": "21-02-2026, 00:05:57", "recorded-content": { "version-order": { "Versions": [ @@ -993,7 +985,6 @@ "Key": "test_0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1003,7 +994,6 @@ "Key": "test_1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1013,7 +1003,6 @@ "Key": "test_2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1037,7 +1026,6 @@ "Key": "test_0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1054,7 +1042,6 @@ "Key": "test_0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1071,7 +1058,6 @@ "Key": "test_1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1088,7 +1074,6 @@ "Key": "test_1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1105,7 +1090,6 @@ "Key": "test_2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1122,7 +1106,6 @@ "Key": "test_2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1142,7 +1125,6 @@ "Key": "test_0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1152,7 +1134,6 @@ "Key": "test_1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1178,7 +1159,6 @@ "Key": "test_0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1195,7 +1175,6 @@ "Key": "test_0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1212,7 +1191,6 @@ "Key": "test_1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1232,7 +1210,6 @@ "Key": "test_2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "VersionId": "" @@ -1298,7 +1275,6 @@ "Key": "test_1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1330,7 +1306,6 @@ "Key": "test_2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1364,7 +1339,6 @@ "Key": "test_0", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 9, @@ -1380,7 +1354,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_object_versions_pagination_common_prefixes": { - "recorded-date": "21-01-2025, 18:14:59", + "recorded-date": "21-02-2026, 00:06:01", "recorded-content": { "list-object-versions-all-keys": { "EncodingType": "url", @@ -1401,7 +1375,6 @@ "Key": "folder/aSubfolder/subFile1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -1418,7 +1391,6 @@ "Key": "folder/aSubfolder/subFile2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -1435,7 +1407,6 @@ "Key": "folder/file1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -1452,7 +1423,6 @@ "Key": "folder/file2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -1507,7 +1477,6 @@ "Key": "folder/file1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -1540,7 +1509,6 @@ "Key": "folder/file2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -1575,7 +1543,6 @@ "Key": "folder/file1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -1591,7 +1558,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix": { - "recorded-date": "21-01-2025, 18:15:03", + "recorded-date": "21-02-2026, 00:06:05", "recorded-content": { "list-object-version-1": { "CommonPrefixes": [ @@ -1618,7 +1585,6 @@ "Key": "dir/test", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1635,7 +1601,6 @@ "Key": "dir/test", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1687,7 +1652,6 @@ "Key": "dir/test", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1704,7 +1668,6 @@ "Key": "dir/test", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1756,7 +1719,6 @@ "Key": "dir/subdir/test2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1773,7 +1735,6 @@ "Key": "dir/subdir/test2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1806,7 +1767,6 @@ "Key": "dir/subdir/test2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1823,7 +1783,6 @@ "Key": "dir/subdir/test2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 15, @@ -1853,7 +1812,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_next_marker": { - "recorded-date": "21-01-2025, 18:15:10", + "recorded-date": "21-02-2026, 00:06:32", "recorded-content": { "list-multiparts-empty": { "Bucket": "", @@ -1904,7 +1863,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -1918,7 +1876,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -1932,7 +1889,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -1946,7 +1902,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -1960,7 +1915,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -1989,7 +1943,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2018,7 +1971,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2060,7 +2012,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2089,7 +2040,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2118,7 +2068,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2147,7 +2096,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2188,7 +2136,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2203,7 +2150,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_with_prefix_and_delimiter": { - "recorded-date": "21-01-2025, 18:15:12", + "recorded-date": "21-02-2026, 00:06:34", "recorded-content": { "list-multiparts-1": { "Bucket": "", @@ -2289,7 +2236,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2303,7 +2249,6 @@ }, "Key": "", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2316,7 +2261,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_pagination": { - "recorded-date": "21-01-2025, 18:15:18", + "recorded-date": "21-02-2026, 00:06:40", "recorded-content": { "list-parts-empty": { "Bucket": "bucket", @@ -2329,7 +2274,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 0, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -2351,7 +2295,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 2, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -2387,7 +2330,6 @@ "MaxParts": 1, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -2417,7 +2359,6 @@ "MaxParts": 1, "NextPartNumberMarker": 2, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 1, @@ -2447,7 +2388,6 @@ "MaxParts": 1, "NextPartNumberMarker": 0, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 10, @@ -2461,7 +2401,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_marker_common_prefixes": { - "recorded-date": "21-01-2025, 18:14:33", + "recorded-date": "21-02-2026, 00:05:24", "recorded-content": { "list-objects-all-keys": { "Contents": [ @@ -2474,7 +2414,6 @@ "Key": "folder/aSubfolder/subFile1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -2489,7 +2428,6 @@ "Key": "folder/aSubfolder/subFile2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -2504,7 +2442,6 @@ "Key": "folder/file1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -2519,7 +2456,6 @@ "Key": "folder/file2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -2567,7 +2503,6 @@ "Key": "folder/file1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -2598,7 +2533,6 @@ "Key": "folder/file2", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -2628,7 +2562,6 @@ "Key": "folder/file1", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 11, @@ -2651,7 +2584,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multipart_uploads_marker_common_prefixes": { - "recorded-date": "21-01-2025, 18:15:14", + "recorded-date": "21-02-2026, 00:06:36", "recorded-content": { "list-multiparts-start": { "Bucket": "", @@ -2692,7 +2625,6 @@ }, "Key": "folder/file1", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2723,7 +2655,6 @@ }, "Key": "folder/file1", "Owner": { - "DisplayName": "display-name", "ID": "owner-id" }, "StorageClass": "STANDARD", @@ -2738,7 +2669,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_empty_part_number_marker": { - "recorded-date": "21-01-2025, 18:15:20", + "recorded-date": "21-02-2026, 00:06:42", "recorded-content": { "list-parts-empty-marker": { "Bucket": "bucket", @@ -2751,7 +2682,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -2781,7 +2711,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 1, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -2803,7 +2732,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination": { - "recorded-date": "13-02-2025, 03:52:21", + "recorded-date": "21-02-2026, 00:06:10", "recorded-content": { "list-object-version-prefix-full": { "EncodingType": "url", @@ -2824,7 +2753,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -2841,7 +2769,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -2858,7 +2785,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -2875,7 +2801,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -2892,7 +2817,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -2926,7 +2850,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -2943,7 +2866,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -2960,7 +2882,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -3005,7 +2926,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -3022,7 +2942,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -3054,7 +2973,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -3071,7 +2989,6 @@ "Key": "prefixed_key", "LastModified": "datetime", "Owner": { - "DisplayName": "", "ID": "" }, "Size": 0, @@ -3087,11 +3004,12 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_max_buckets": { - "recorded-date": "14-05-2025, 09:10:49", + "recorded-date": "21-02-2026, 00:04:59", "recorded-content": { "list-objects-with-max-buckets": { "Buckets": [ { + "BucketArn": "arn::s3:::", "BucketRegion": "", "CreationDate": "datetime", "Name": "" @@ -3099,7 +3017,6 @@ ], "ContinuationToken": "", "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -3110,11 +3027,12 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_continuation_token": { - "recorded-date": "14-05-2025, 09:10:59", + "recorded-date": "21-02-2026, 00:05:10", "recorded-content": { "list-objects-with-continuation": { "Buckets": [ { + "BucketArn": "arn::s3:::", "BucketRegion": "", "CreationDate": "datetime", "Name": "" @@ -3122,7 +3040,6 @@ ], "ContinuationToken": "", "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -3133,11 +3050,12 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_when_continuation_token_is_empty": { - "recorded-date": "14-05-2025, 09:10:50", + "recorded-date": "21-02-2026, 00:05:02", "recorded-content": { "list-objects-with-empty-continuation-token": { "Buckets": [ { + "BucketArn": "arn::s3:::", "BucketRegion": "", "CreationDate": "datetime", "Name": "" @@ -3145,7 +3063,6 @@ ], "ContinuationToken": "", "Owner": { - "DisplayName": "", "ID": "" }, "ResponseMetadata": { @@ -3156,12 +3073,11 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_prefix_with_case_sensitivity": { - "recorded-date": "14-05-2025, 09:10:46", + "recorded-date": "21-02-2026, 00:04:57", "recorded-content": { "list-objects-by-prefix-empty": { "Buckets": [], "Owner": { - "DisplayName": "", "ID": "" }, "Prefix": "", @@ -3173,13 +3089,13 @@ "list-objects-by-prefix-not-empty": { "Buckets": [ { + "BucketArn": "arn::s3:::", "BucketRegion": "", "CreationDate": "datetime", "Name": "" } ], "Owner": { - "DisplayName": "", "ID": "" }, "Prefix": "", @@ -3191,7 +3107,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_bucket_region": { - "recorded-date": "14-05-2025, 09:10:54", + "recorded-date": "21-02-2026, 00:05:05", "recorded-content": { "list-objects-by-bucket-region-empty": { "Buckets": [], @@ -3207,13 +3123,13 @@ "list-objects-by-bucket-region-not-empty": { "Buckets": [ { + "BucketArn": "arn::s3:::", "BucketRegion": "", "CreationDate": "datetime", "Name": "" } ], "Owner": { - "DisplayName": "", "ID": "" }, "Prefix": "", @@ -3225,7 +3141,7 @@ } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_via_object_attrs_pagination": { - "recorded-date": "16-06-2025, 13:47:27", + "recorded-date": "21-02-2026, 00:06:47", "recorded-content": { "list-parts": { "Bucket": "bucket", @@ -3240,7 +3156,6 @@ "MaxParts": 1000, "NextPartNumberMarker": 2, "Owner": { - "DisplayName": "display-name", "ID": "i-d" }, "PartNumberMarker": 0, @@ -3368,5 +3283,493 @@ } } } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_region_validation": { + "recorded-date": "21-02-2026, 00:05:12", + "recorded-content": { + "bad-region": { + "Error": { + "ArgumentName": "bucket-region", + "Code": "InvalidArgument", + "Message": "Argument value eu-east-1 is not a valid AWS Region" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_token_safe_chars": { + "recorded-date": "21-02-2026, 00:05:45", + "recorded-content": { + "list-objects-v2-max-5": { + "Contents": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "a/%F0%9F%98%80/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-01-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-02-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-03-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-05-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + } + ], + "EncodingType": "url", + "IsTruncated": true, + "KeyCount": 5, + "MaxKeys": 5, + "Name": "", + "NextContinuationToken": "next-continuation-token", + "Prefix": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-objects-v2-rest": { + "Contents": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "file%2Fname", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "test key/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "test key//", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "test%123", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "test%123/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "test%percent", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "test@key/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + } + ], + "ContinuationToken": "continuation-token", + "EncodingType": "url", + "IsTruncated": false, + "KeyCount": 7, + "MaxKeys": 1000, + "Name": "", + "Prefix": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-objects-start-after": { + "Contents": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-03-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-05-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + } + ], + "IsTruncated": true, + "KeyCount": 2, + "MaxKeys": 2, + "Name": "", + "NextContinuationToken": "next-continuation-token", + "Prefix": "", + "StartAfter": "date=2026-02-01/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-objects-start-after-url-type": { + "Contents": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date%3D2026-03-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date%3D2026-05-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + } + ], + "EncodingType": "url", + "IsTruncated": true, + "KeyCount": 2, + "MaxKeys": 2, + "Name": "", + "NextContinuationToken": "next-continuation-token", + "Prefix": "", + "StartAfter": "date%3D2026-02-01/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-objects-start-after-encoded": { + "Contents": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-01-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-02-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + } + ], + "EncodingType": "url", + "IsTruncated": true, + "KeyCount": 2, + "MaxKeys": 2, + "Name": "", + "NextContinuationToken": "next-continuation-token", + "Prefix": "", + "StartAfter": "date%3D2026-02-01/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-objects-start-after-token": { + "Contents": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-03-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-05-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + } + ], + "ContinuationToken": "continuation-token", + "EncodingType": "url", + "IsTruncated": true, + "KeyCount": 2, + "MaxKeys": 2, + "Name": "", + "NextContinuationToken": "next-continuation-token", + "Prefix": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-objects-continuation-token-2": { + "Contents": [ + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-03-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + }, + { + "ChecksumAlgorithm": [ + "CRC32" + ], + "ChecksumType": "FULL_OBJECT", + "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"", + "Key": "date=2026-05-01/", + "LastModified": "datetime", + "Size": 0, + "StorageClass": "STANDARD" + } + ], + "ContinuationToken": "continuation-token", + "EncodingType": "url", + "IsTruncated": true, + "KeyCount": 2, + "MaxKeys": 2, + "Name": "", + "NextContinuationToken": "next-continuation-token", + "Prefix": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_objects_v2]": { + "recorded-date": "21-02-2026, 00:05:47", + "recorded-content": { + "list_objects_v2-error-wrong-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": "value", + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "list_objects_v2-error-empty-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": null, + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_objects]": { + "recorded-date": "21-02-2026, 00:05:49", + "recorded-content": { + "list_objects-error-wrong-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": "value", + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "list_objects-error-empty-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": null, + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_object_versions]": { + "recorded-date": "21-02-2026, 00:05:51", + "recorded-content": { + "list_object_versions-error-wrong-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": "value", + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "list_object_versions-error-empty-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": null, + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_multipart_uploads]": { + "recorded-date": "21-02-2026, 00:05:53", + "recorded-content": { + "list_multipart_uploads-error-wrong-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": "value", + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "list_multipart_uploads-error-empty-value": { + "Error": { + "ArgumentName": "encoding-type", + "ArgumentValue": null, + "Code": "InvalidArgument", + "Message": "Invalid Encoding Method specified in Request" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/s3/test_s3_list_operations.validation.json b/tests/aws/services/s3/test_s3_list_operations.validation.json index 127660f3efd56..4805d1aed2925 100644 --- a/tests/aws/services/s3/test_s3_list_operations.validation.json +++ b/tests/aws/services/s3/test_s3_list_operations.validation.json @@ -1,101 +1,335 @@ { "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_bucket_region": { - "last_validated_date": "2025-05-14T09:11:19+00:00" + "last_validated_date": "2026-02-21T00:05:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.53, + "teardown": 2.28, + "total": 4.81 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_prefix_with_case_sensitivity": { - "last_validated_date": "2025-05-14T09:11:11+00:00" + "last_validated_date": "2026-02-21T00:04:58+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.65, + "teardown": 1.18, + "total": 3.32 + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_region_validation": { + "last_validated_date": "2026-02-21T00:05:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_when_continuation_token_is_empty": { - "last_validated_date": "2025-05-14T09:11:17+00:00" + "last_validated_date": "2026-02-21T00:05:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.24, + "teardown": 1.17, + "total": 2.41 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_continuation_token": { - "last_validated_date": "2025-05-14T09:11:24+00:00" + "last_validated_date": "2026-02-21T00:05:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.1, + "teardown": 1.74, + "total": 3.84 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_max_buckets": { - "last_validated_date": "2025-05-14T09:11:14+00:00" + "last_validated_date": "2026-02-21T00:05:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.31, + "teardown": 1.16, + "total": 2.47 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multipart_uploads_marker_common_prefixes": { - "last_validated_date": "2025-01-21T18:15:14+00:00" + "last_validated_date": "2026-02-21T00:06:37+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.93, + "teardown": 0.59, + "total": 2.02 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_next_marker": { - "last_validated_date": "2025-01-21T18:15:10+00:00" + "last_validated_date": "2026-02-21T00:06:32+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 2.19, + "teardown": 0.59, + "total": 3.29 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_with_prefix_and_delimiter": { - "last_validated_date": "2025-01-21T18:15:12+00:00" + "last_validated_date": "2026-02-21T00:06:35+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.24, + "teardown": 0.6, + "total": 2.34 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_s3_list_multiparts_timestamp_precision": { - "last_validated_date": "2025-01-21T18:15:16+00:00" + "last_validated_date": "2026-02-21T00:06:38+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.47, + "teardown": 0.58, + "total": 1.56 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_object_versions_pagination_common_prefixes": { - "last_validated_date": "2025-01-21T18:14:59+00:00" + "last_validated_date": "2026-02-21T00:06:02+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.73, + "teardown": 1.42, + "total": 3.66 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_markers": { - "last_validated_date": "2025-01-21T18:14:56+00:00" + "last_validated_date": "2026-02-21T00:05:59+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 3.36, + "teardown": 1.51, + "total": 5.38 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix": { - "last_validated_date": "2025-01-21T18:15:03+00:00" + "last_validated_date": "2026-02-21T00:06:07+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.28, + "teardown": 1.53, + "total": 4.33 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination": { - "last_validated_date": "2025-02-13T03:52:21+00:00" + "last_validated_date": "2026-02-21T00:06:11+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 2.35, + "teardown": 1.39, + "total": 4.24 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination_many_versions": { - "last_validated_date": "2025-02-13T20:24:26+00:00" + "last_validated_date": "2026-02-21T00:06:27+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 12.94, + "teardown": 2.22, + "total": 15.68 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_s3_list_object_versions_timestamp_precision": { - "last_validated_date": "2025-01-21T18:15:06+00:00" + "last_validated_date": "2026-02-21T00:06:29+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.9, + "teardown": 0.97, + "total": 2.38 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_marker_common_prefixes": { - "last_validated_date": "2025-01-21T18:14:33+00:00" + "last_validated_date": "2026-02-21T00:05:25+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 1.49, + "teardown": 0.99, + "total": 3.02 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_next_marker": { - "last_validated_date": "2025-01-21T18:14:29+00:00" + "last_validated_date": "2026-02-21T00:05:20+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.19, + "teardown": 1.02, + "total": 2.73 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[%2F]": { - "last_validated_date": "2025-01-21T18:14:26+00:00" + "last_validated_date": "2026-02-21T00:05:18+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.75, + "teardown": 0.93, + "total": 2.18 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[/]": { - "last_validated_date": "2025-01-21T18:14:24+00:00" + "last_validated_date": "2026-02-21T00:05:15+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.45, + "teardown": 0.91, + "total": 1.86 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[]": { - "last_validated_date": "2025-01-21T18:14:22+00:00" + "last_validated_date": "2026-02-21T00:05:13+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.42, + "teardown": 0.93, + "total": 1.86 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_empty_marker": { - "last_validated_date": "2025-01-21T18:14:31+00:00" + "last_validated_date": "2026-02-21T00:05:22+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.21, + "teardown": 0.6, + "total": 1.33 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjectsV2]": { - "last_validated_date": "2025-01-21T18:14:37+00:00" + "last_validated_date": "2026-02-21T00:05:29+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.55, + "teardown": 0.95, + "total": 2.01 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjects]": { - "last_validated_date": "2025-01-21T18:14:35+00:00" + "last_validated_date": "2026-02-21T00:05:27+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.54, + "teardown": 0.94, + "total": 2.0 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_common_prefixes": { - "last_validated_date": "2025-01-21T18:14:51+00:00" + "last_validated_date": "2026-02-21T00:05:42+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 1.36, + "teardown": 0.99, + "total": 2.86 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_start_after": { - "last_validated_date": "2025-01-21T18:14:48+00:00" + "last_validated_date": "2026-02-21T00:05:39+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 3.17, + "teardown": 1.31, + "total": 4.97 + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_token_safe_chars": { + "last_validated_date": "2026-02-21T00:05:46+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 2.63, + "teardown": 1.15, + "total": 4.3 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix": { - "last_validated_date": "2025-01-21T18:14:40+00:00" + "last_validated_date": "2026-02-21T00:05:31+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 1.41, + "teardown": 0.95, + "total": 2.85 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix_and_delimiter": { - "last_validated_date": "2025-01-21T18:14:43+00:00" + "last_validated_date": "2026-02-21T00:05:34+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 1.2, + "teardown": 0.76, + "total": 2.46 + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_multipart_uploads]": { + "last_validated_date": "2026-02-21T00:05:53+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.64, + "teardown": 0.59, + "total": 1.74 + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_object_versions]": { + "last_validated_date": "2026-02-21T00:05:52+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.94, + "teardown": 0.59, + "total": 2.04 + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_objects]": { + "last_validated_date": "2026-02-21T00:05:50+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.64, + "teardown": 0.59, + "total": 1.74 + } + }, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_ops_encoding_type_validation[list_objects_v2]": { + "last_validated_date": "2026-02-21T00:05:48+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.64, + "teardown": 0.59, + "total": 1.73 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_empty_part_number_marker": { - "last_validated_date": "2025-01-21T18:15:20+00:00" + "last_validated_date": "2026-02-21T00:06:43+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.82, + "teardown": 0.6, + "total": 1.94 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_pagination": { - "last_validated_date": "2025-01-21T18:15:18+00:00" + "last_validated_date": "2026-02-21T00:06:41+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 1.21, + "teardown": 0.59, + "total": 2.32 + } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_via_object_attrs_pagination": { - "last_validated_date": "2025-06-16T13:47:28+00:00", + "last_validated_date": "2026-02-21T00:06:48+00:00", "durations_in_seconds": { - "setup": 0.97, - "call": 10.45, + "setup": 0.53, + "call": 2.56, "teardown": 0.97, - "total": 12.39 + "total": 4.06 } }, "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_s3_list_parts_timestamp_precision": { - "last_validated_date": "2025-01-21T18:15:22+00:00" + "last_validated_date": "2026-02-21T00:06:44+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.72, + "teardown": 0.58, + "total": 1.81 + } } } diff --git a/tests/aws/services/s3/test_s3_notifications_sns.py b/tests/aws/services/s3/test_s3_notifications_sns.py index 81b6b2bd675dd..939838db68966 100644 --- a/tests/aws/services/s3/test_s3_notifications_sns.py +++ b/tests/aws/services/s3/test_s3_notifications_sns.py @@ -1,7 +1,7 @@ import json import logging from io import BytesIO -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING import pytest from botocore.exceptions import ClientError @@ -26,7 +26,7 @@ def create_sns_bucket_notification( sns_client: "SNSClient", bucket_name: str, topic_arn: str, - events: List["EventType"], + events: list["EventType"], ): """A NotificationFactory.""" bucket_arn = arns.s3_bucket_arn(bucket_name) @@ -49,20 +49,20 @@ def create_sns_bucket_notification( s3_client.put_bucket_notification_configuration( Bucket=bucket_name, - NotificationConfiguration=dict( - TopicConfigurations=[ - dict( - TopicArn=topic_arn, - Events=events, - ) + NotificationConfiguration={ + "TopicConfigurations": [ + { + "TopicArn": topic_arn, + "Events": events, + } ] - ), + }, ) def sqs_collect_sns_messages( sqs_client: "SQSClient", queue_url: str, min_messages: int, timeout: int = 10 -) -> List[Dict]: +) -> list[dict]: """ Polls the given queue for the given amount of time and extracts the received SQS messages all SNS messages (messages that have a "TopicArn" field). diff --git a/tests/aws/services/s3/test_s3_notifications_sqs.py b/tests/aws/services/s3/test_s3_notifications_sqs.py index 498c589a7150c..a2b7e251a5a2e 100644 --- a/tests/aws/services/s3/test_s3_notifications_sqs.py +++ b/tests/aws/services/s3/test_s3_notifications_sqs.py @@ -1,7 +1,7 @@ import json import logging from io import BytesIO -from typing import TYPE_CHECKING, Dict, List, Protocol +from typing import TYPE_CHECKING, Protocol import pytest import requests @@ -29,7 +29,7 @@ class NotificationFactory(Protocol): A protocol for connecting a bucket to a queue with a notification configurations and the necessary policies. """ - def __call__(self, bucket_name: str, queue_url: str, events: List["EventType"]) -> None: + def __call__(self, bucket_name: str, queue_url: str, events: list["EventType"]) -> None: """ Creates a new notification configuration and respective policies. @@ -79,15 +79,15 @@ def create_sqs_bucket_notification( sqs_client: "SQSClient", bucket_name: str, queue_url: str, - events: List["EventType"], + events: list["EventType"], ): """A NotificationFactory.""" queue_arn = set_policy_for_queue(sqs_client, queue_url, bucket_name) s3_client.put_bucket_notification_configuration( Bucket=bucket_name, - NotificationConfiguration=dict( - QueueConfigurations=[dict(QueueArn=queue_arn, Events=events)] - ), + NotificationConfiguration={ + "QueueConfigurations": [{"QueueArn": queue_arn, "Events": events}] + }, ) @@ -100,7 +100,7 @@ def s3_create_sqs_bucket_notification(aws_client) -> NotificationFactory: def factory( bucket_name: str, queue_url: str, - events: List["EventType"], + events: list["EventType"], s3_client=aws_client.s3, sqs_client=aws_client.sqs, ): @@ -111,7 +111,7 @@ def factory( def sqs_collect_s3_events( sqs_client: "SQSClient", queue_url: str, min_events: int, timeout: int = 10 -) -> List[Dict]: +) -> list[dict]: """ Polls the given queue for the given amount of time and extracts and flattens from the received messages all events (messages that have a "Records" field in their body, and where the records can be json-deserialized). @@ -158,7 +158,7 @@ def sqs_create_queue_with_client(): def factory(sqs_client, **kwargs): if "QueueName" not in kwargs: - kwargs["QueueName"] = "test-queue-%s" % short_uid() + kwargs["QueueName"] = f"test-queue-{short_uid()}" response = sqs_client.create_queue(**kwargs) url = response["QueueUrl"] @@ -221,8 +221,8 @@ def test_object_created_copy( queue_url = sqs_create_queue() s3_create_sqs_bucket_notification(s3_bucket, queue_url, ["s3:ObjectCreated:Copy"]) - src_key = "src-dest-%s" % short_uid() - dest_key = "key-dest-%s" % short_uid() + src_key = f"src-dest-{short_uid()}" + dest_key = f"key-dest-{short_uid()}" aws_client.s3.put_object(Bucket=s3_bucket, Key=src_key, Body="something") @@ -315,7 +315,7 @@ def test_delete_objects( queue_url = sqs_create_queue() s3_create_sqs_bucket_notification(s3_bucket, queue_url, ["s3:ObjectRemoved:*"]) - key = "key-%s" % short_uid() + key = f"key-{short_uid()}" aws_client.s3.put_object(Bucket=s3_bucket, Key=key, Body="something") @@ -528,7 +528,7 @@ def test_object_tagging_delete_event( queue_url = sqs_create_queue() s3_create_sqs_bucket_notification(s3_bucket, queue_url, ["s3:ObjectTagging:Delete"]) - dest_key = "key-dest-%s" % short_uid() + dest_key = f"key-dest-{short_uid()}" aws_client.s3.put_object(Bucket=s3_bucket, Key=dest_key, Body="FooBarBlitz") @@ -786,33 +786,39 @@ def test_filter_rules_case_insensitive(self, s3_bucket, sqs_create_queue, snapsh assert rules[1]["Name"] in valid snapshot.match("bucket_notification_configuration", response) - @markers.snapshot.skip_snapshot_verify( - paths=["$..Error.ArgumentName", "$..Error.ArgumentValue"], - ) # TODO: add to exception for ASF @markers.aws.validated - def test_bucket_notification_with_invalid_filter_rules( - self, s3_bucket, sqs_create_queue, snapshot, aws_client - ): - queue_url = sqs_create_queue() + def test_filter_rules_empty_value(self, s3_bucket, sqs_create_queue, snapshot, aws_client): + id = short_uid() + queue_url = sqs_create_queue(QueueName=f"my-queue-{id}") queue_attributes = aws_client.sqs.get_queue_attributes( QueueUrl=queue_url, AttributeNames=["QueueArn"] ) + snapshot.add_transformer(snapshot.transform.key_value("Id", "id")) + snapshot.add_transformer(snapshot.transform.regex(id, "")) cfg = { "QueueConfigurations": [ { "QueueArn": queue_attributes["Attributes"]["QueueArn"], "Events": ["s3:ObjectCreated:*"], "Filter": { - "Key": {"FilterRules": [{"Name": "INVALID", "Value": "does not matter"}]} + "Key": { + "FilterRules": [ + { + "Name": "suffix", + "Value": "", + }, + ] + } }, } ] } - with pytest.raises(ClientError) as e: - aws_client.s3.put_bucket_notification_configuration( - Bucket=s3_bucket, NotificationConfiguration=cfg - ) - snapshot.match("invalid_filter_name", e.value.response) + + aws_client.s3.put_bucket_notification_configuration( + Bucket=s3_bucket, NotificationConfiguration=cfg, SkipDestinationValidation=True + ) + response = aws_client.s3.get_bucket_notification_configuration(Bucket=s3_bucket) + snapshot.match("bucket_notification_configuration", response) @markers.aws.validated # AWS seems to return "ArgumentName" (without the number) if the request fails a basic verification @@ -821,10 +827,10 @@ def test_bucket_notification_with_invalid_filter_rules( # e.g. queues not existing, no permissions etc. @markers.snapshot.skip_snapshot_verify( paths=[ - "$..Error.ArgumentName1", - "$..Error.ArgumentValue1", - "$..Error.ArgumentName", - "$..Error.ArgumentValue", + "$.queue-does-not-exist.Error.ArgumentName1", + "$.queue-does-not-exist.Error.ArgumentValue1", + "$.queue-does-not-exist.Error.ArgumentName", + "$.queue-does-not-exist.Error.ArgumentValue", ], ) def test_invalid_sqs_arn(self, s3_bucket, account_id, snapshot, aws_client, region_name): diff --git a/tests/aws/services/s3/test_s3_notifications_sqs.snapshot.json b/tests/aws/services/s3/test_s3_notifications_sqs.snapshot.json index d7416b0ff3a5e..5ef42a47bd750 100644 --- a/tests/aws/services/s3/test_s3_notifications_sqs.snapshot.json +++ b/tests/aws/services/s3/test_s3_notifications_sqs.snapshot.json @@ -702,23 +702,6 @@ } } }, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_bucket_notification_with_invalid_filter_rules": { - "recorded-date": "21-01-2025, 23:22:02", - "recorded-content": { - "invalid_filter_name": { - "Error": { - "ArgumentName": "FilterRule.Name", - "ArgumentValue": "INVALID", - "Code": "InvalidArgument", - "Message": "filter rule name must be either prefix or suffix" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_invalid_sqs_arn": { "recorded-date": "21-01-2025, 23:22:05", "recorded-content": { @@ -1378,5 +1361,35 @@ } ] } + }, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_empty_value": { + "recorded-date": "02-09-2025, 21:50:09", + "recorded-content": { + "bucket_notification_configuration": { + "QueueConfigurations": [ + { + "Events": [ + "s3:ObjectCreated:*" + ], + "Filter": { + "Key": { + "FilterRules": [ + { + "Name": "Suffix", + "Value": "" + } + ] + } + }, + "Id": "", + "QueueArn": "arn::sqs::111111111111:my-queue-" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/s3/test_s3_notifications_sqs.validation.json b/tests/aws/services/s3/test_s3_notifications_sqs.validation.json index a7eca3f14f917..f07b66bbb2cd0 100644 --- a/tests/aws/services/s3/test_s3_notifications_sqs.validation.json +++ b/tests/aws/services/s3/test_s3_notifications_sqs.validation.json @@ -1,13 +1,19 @@ { - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_bucket_notification_with_invalid_filter_rules": { - "last_validated_date": "2025-01-21T23:22:02+00:00" - }, "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_delete_objects": { "last_validated_date": "2025-01-21T23:21:22+00:00" }, "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_case_insensitive": { "last_validated_date": "2025-01-21T23:22:01+00:00" }, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_empty_value": { + "last_validated_date": "2025-09-02T21:50:10+00:00", + "durations_in_seconds": { + "setup": 0.98, + "call": 1.0, + "teardown": 0.81, + "total": 2.79 + } + }, "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_invalid_sqs_arn": { "last_validated_date": "2025-01-21T23:22:05+00:00" }, diff --git a/tests/aws/services/s3/test_s3_preconditions.py b/tests/aws/services/s3/test_s3_preconditions.py new file mode 100644 index 0000000000000..0ba685631b95c --- /dev/null +++ b/tests/aws/services/s3/test_s3_preconditions.py @@ -0,0 +1,185 @@ +import datetime +import time +from zoneinfo import ZoneInfo + +import pytest +from botocore.exceptions import ClientError + +from localstack.testing.pytest import markers + + +class TestS3CopySourcePreconditions: + @markers.aws.validated + def test_s3_copy_object_preconditions(self, s3_bucket, snapshot, aws_client): + snapshot.add_transformer(snapshot.transform.s3_api()) + object_key = "source-object" + dest_key = "dest-object" + # create key with no checksum + put_object = aws_client.s3.put_object( + Bucket=s3_bucket, + Key=object_key, + Body=b"data", + ) + head_obj = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-object", head_obj) + + # wait a bit for the `modified_since` value so that it's invalid. + # S3 compares it the last-modified field, but you can't set the value in the future otherwise it ignores it. + # It needs to be now or less, but the object needs to be a bit more recent than that. + time.sleep(3) + + # we're testing the order of validation at the same time by validating all of them at once, by elimination + now = datetime.datetime.now().astimezone(tz=ZoneInfo("GMT")) + wrong_unmodified_since = now - datetime.timedelta(days=1) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfModifiedSince=now, + CopySourceIfUnmodifiedSince=wrong_unmodified_since, + CopySourceIfMatch="etag123", + CopySourceIfNoneMatch=put_object["ETag"], + ) + snapshot.match("copy-precondition-if-match", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfModifiedSince=now, + CopySourceIfUnmodifiedSince=wrong_unmodified_since, + CopySourceIfNoneMatch=put_object["ETag"], + ) + snapshot.match("copy-precondition-if-unmodified-since", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfModifiedSince=now, + CopySourceIfNoneMatch=put_object["ETag"], + ) + snapshot.match("copy-precondition-if-none-match", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfModifiedSince=now, + ) + snapshot.match("copy-precondition-if-modified-since", e.value.response) + + # AWS will ignore the value if it's in the future + copy_obj = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfModifiedSince=now + datetime.timedelta(days=1), + ) + snapshot.match("copy-ignore-future-modified-since", copy_obj) + + # AWS will ignore the missing quotes around the ETag and still reject the request + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfNoneMatch=put_object["ETag"].strip('"'), + ) + snapshot.match("copy-etag-missing-quotes", e.value.response) + + # Positive tests with all conditions checked + copy_obj_all_positive = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfMatch=put_object["ETag"].strip('"'), + CopySourceIfNoneMatch="etag123", + CopySourceIfModifiedSince=now - datetime.timedelta(days=1), + CopySourceIfUnmodifiedSince=now, + ) + snapshot.match("copy-success", copy_obj_all_positive) + + @markers.aws.validated + def test_s3_copy_object_if_source_modified_since_versioned( + self, s3_bucket, snapshot, aws_client + ): + snapshot.add_transformer(snapshot.transform.s3_api()) + aws_client.s3.put_bucket_versioning( + Bucket=s3_bucket, VersioningConfiguration={"Status": "Enabled"} + ) + object_key = "source-object" + dest_key = "dest-object" + # create key with no checksum + aws_client.s3.put_object( + Bucket=s3_bucket, + Key=object_key, + Body=b"data", + ) + head_obj = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-object", head_obj) + object_last_modified = head_obj["LastModified"] + + if_modified_since = object_last_modified - datetime.timedelta(minutes=1) + + copy_object = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfModifiedSince=if_modified_since, + ) + snapshot.match("copy-if-modified-since", copy_object) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfModifiedSince=object_last_modified, + ) + snapshot.match("copy-if-modified-since-last-modified", e.value.response) + + @markers.aws.validated + def test_s3_copy_object_if_source_unmodified_since_versioned( + self, s3_bucket, snapshot, aws_client + ): + snapshot.add_transformer(snapshot.transform.s3_api()) + aws_client.s3.put_bucket_versioning( + Bucket=s3_bucket, VersioningConfiguration={"Status": "Enabled"} + ) + object_key = "source-object" + dest_key = "dest-object" + # create key with no checksum + aws_client.s3.put_object( + Bucket=s3_bucket, + Key=object_key, + Body=b"data", + ) + head_obj = aws_client.s3.head_object(Bucket=s3_bucket, Key=object_key) + snapshot.match("head-object", head_obj) + last_modified = head_obj["LastModified"] + + copy_object = aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfUnmodifiedSince=last_modified, + ) + snapshot.match("copy-if-unmodified-since", copy_object) + + now = datetime.datetime.now().astimezone(tz=ZoneInfo("GMT")) + wrong_unmodified_since = now - datetime.timedelta(days=1) + + with pytest.raises(ClientError) as e: + aws_client.s3.copy_object( + Bucket=s3_bucket, + CopySource=f"{s3_bucket}/{object_key}", + Key=dest_key, + CopySourceIfUnmodifiedSince=wrong_unmodified_since, + ) + snapshot.match("copy-past-if-unmodified-since", e.value.response) diff --git a/tests/aws/services/s3/test_s3_preconditions.snapshot.json b/tests/aws/services/s3/test_s3_preconditions.snapshot.json new file mode 100644 index 0000000000000..1ac51a6d116ab --- /dev/null +++ b/tests/aws/services/s3/test_s3_preconditions.snapshot.json @@ -0,0 +1,191 @@ +{ + "tests/aws/services/s3/test_s3_preconditions.py::TestS3CopySourcePreconditions::test_s3_copy_object_preconditions": { + "recorded-date": "23-02-2026, 11:37:02", + "recorded-content": { + "head-object": { + "AcceptRanges": "bytes", + "ContentLength": 4, + "ContentType": "binary/octet-stream", + "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-precondition-if-match": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "copy-precondition-if-unmodified-since": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Unmodified-Since", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "copy-precondition-if-none-match": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-None-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "copy-precondition-if-modified-since": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Modified-Since", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "copy-ignore-future-modified-since": { + "CopyObjectResult": { + "ChecksumCRC32": "rfPzYw==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-etag-missing-quotes": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-None-Match", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + }, + "copy-success": { + "CopyObjectResult": { + "ChecksumCRC32": "rfPzYw==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", + "LastModified": "datetime" + }, + "ServerSideEncryption": "AES256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/s3/test_s3_preconditions.py::TestS3CopySourcePreconditions::test_s3_copy_object_if_source_modified_since_versioned": { + "recorded-date": "23-02-2026, 11:37:05", + "recorded-content": { + "head-object": { + "AcceptRanges": "bytes", + "ContentLength": 4, + "ContentType": "binary/octet-stream", + "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-if-modified-since": { + "CopyObjectResult": { + "ChecksumCRC32": "rfPzYw==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", + "LastModified": "datetime" + }, + "CopySourceVersionId": "", + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-if-modified-since-last-modified": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Modified-Since", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + }, + "tests/aws/services/s3/test_s3_preconditions.py::TestS3CopySourcePreconditions::test_s3_copy_object_if_source_unmodified_since_versioned": { + "recorded-date": "23-02-2026, 11:37:08", + "recorded-content": { + "head-object": { + "AcceptRanges": "bytes", + "ContentLength": 4, + "ContentType": "binary/octet-stream", + "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", + "LastModified": "datetime", + "Metadata": {}, + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-if-unmodified-since": { + "CopyObjectResult": { + "ChecksumCRC32": "rfPzYw==", + "ChecksumType": "FULL_OBJECT", + "ETag": "\"8d777f385d3dfec8815d20f7496026dc\"", + "LastModified": "datetime" + }, + "CopySourceVersionId": "", + "ServerSideEncryption": "AES256", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "copy-past-if-unmodified-since": { + "Error": { + "Code": "PreconditionFailed", + "Condition": "x-amz-copy-source-If-Unmodified-Since", + "Message": "At least one of the pre-conditions you specified did not hold" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 412 + } + } + } + } +} diff --git a/tests/aws/services/s3/test_s3_preconditions.validation.json b/tests/aws/services/s3/test_s3_preconditions.validation.json new file mode 100644 index 0000000000000..245cd73fb59bf --- /dev/null +++ b/tests/aws/services/s3/test_s3_preconditions.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/s3/test_s3_preconditions.py::TestS3CopySourcePreconditions::test_s3_copy_object_if_source_modified_since_versioned": { + "last_validated_date": "2026-02-23T11:37:06+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 1.02, + "teardown": 1.51, + "total": 3.06 + } + }, + "tests/aws/services/s3/test_s3_preconditions.py::TestS3CopySourcePreconditions::test_s3_copy_object_if_source_unmodified_since_versioned": { + "last_validated_date": "2026-02-23T11:37:09+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.93, + "teardown": 1.48, + "total": 2.94 + } + }, + "tests/aws/services/s3/test_s3_preconditions.py::TestS3CopySourcePreconditions::test_s3_copy_object_preconditions": { + "last_validated_date": "2026-02-23T11:37:03+00:00", + "durations_in_seconds": { + "setup": 1.1, + "call": 4.4, + "teardown": 1.02, + "total": 6.52 + } + } +} diff --git a/tests/aws/services/s3control/test_s3control.py b/tests/aws/services/s3control/test_s3control.py index c7daf08fad70d..8132ef3b5ff6a 100644 --- a/tests/aws/services/s3control/test_s3control.py +++ b/tests/aws/services/s3control/test_s3control.py @@ -10,8 +10,6 @@ from localstack.utils.strings import short_uid from localstack.utils.urls import localstack_host -# TODO: this fails in CI, not sure why yet -# s3_control_endpoint = f"http://s3-control.{localstack_host()}" s3_control_endpoint = f"https://{localstack_host().host_and_port()}" @@ -421,3 +419,152 @@ def test_access_point_pagination( list_by_bucket = s3control_client.list_access_points(AccountId=account_id, Bucket=bucket_1) snapshot.match("list-by-bucket", list_by_bucket) + + +@markers.snapshot.skip_snapshot_verify( + paths=[ + # FIXME: this needs to be updated in the serializer, see https://github.com/localstack/localstack/pull/9730 + "$..HostId", + ] +) +class TestS3ControlTagging: + @markers.aws.validated + def test_tag_lifecycle( + self, s3_bucket, s3control_client, snapshot, account_id, s3control_snapshot + ): + bucket_arn = f"arn:aws:s3:::{s3_bucket}" + + list_tags_before = s3control_client.list_tags_for_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + ) + snapshot.match("list-tags-before", list_tags_before) + + tag_resource = s3control_client.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[ + {"Key": "key1", "Value": "value1"}, + {"Key": "key2", "Value": "value2"}, + ], + ) + snapshot.match("tag-resource", tag_resource) + + list_tags_1 = s3control_client.list_tags_for_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + ) + snapshot.match("list-tags-1", list_tags_1) + + tag_resource_after_update = s3control_client.tag_resource( + AccountId=account_id, ResourceArn=bucket_arn, Tags=[{"Key": "key3", "Value": "value3"}] + ) + snapshot.match("tag-resource-after-update", tag_resource_after_update) + + list_tags_after_update = s3control_client.list_tags_for_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + ) + snapshot.match("list-tags-after-update", list_tags_after_update) + + untag_resource = s3control_client.untag_resource( + AccountId=account_id, ResourceArn=bucket_arn, TagKeys=["key3"] + ) + snapshot.match("untag-resource", untag_resource) + + list_tags_after_untag = s3control_client.list_tags_for_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + ) + snapshot.match("list-tags-after-untag", list_tags_after_untag) + + untag_resource_idempotent = s3control_client.untag_resource( + AccountId=account_id, ResourceArn=bucket_arn, TagKeys=["key3"] + ) + snapshot.match("untag-resource-idempotent", untag_resource_idempotent) + + @markers.aws.validated + def test_tag_resource_validation( + self, s3_bucket, s3control_client_no_validation, account_id, snapshot, s3control_snapshot + ): + bucket_arn = f"arn:aws:s3:::{s3_bucket}" + + with pytest.raises(ClientError) as e: + s3control_client_no_validation.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[{"Key": None, "Value": "value1"}], + ) + snapshot.match("tags-key-none", e.value.response) + + with pytest.raises(ClientError) as e: + s3control_client_no_validation.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[{"Key": "key1", "Value": None}], + ) + snapshot.match("tags-value-none", e.value.response) + + with pytest.raises(ClientError) as e: + s3control_client_no_validation.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[{"Key": "aws:test", "Value": "value1"}], + ) + snapshot.match("tags-key-aws-prefix", e.value.response) + + with pytest.raises(ClientError) as e: + s3control_client_no_validation.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[ + {"Key": "key1", "Value": "value1"}, + {"Key": "key1", "Value": "value1"}, + ], + ) + snapshot.match("tags-key-aws-duplicated-key", e.value.response) + + with pytest.raises(ClientError) as e: + s3control_client_no_validation.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[{"Key": "test", "Value": "value1,value2"}], + ) + snapshot.match("tags-key-aws-bad-value", e.value.response) + + with pytest.raises(ClientError) as e: + s3control_client_no_validation.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[{"Key": "test,test2", "Value": "value1"}], + ) + snapshot.match("tags-key-aws-bad-key", e.value.response) + + @markers.aws.validated + def test_tag_operation_no_bucket( + self, s3_bucket, s3control_client, account_id, snapshot, s3control_snapshot + ): + bucket_arn = f"arn:aws:s3:::{short_uid()}-{short_uid()}" + + with pytest.raises(ClientError) as e: + s3control_client.tag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + Tags=[{"Key": "key1", "Value": "value1"}], + ) + snapshot.match("tag-resource-no-exist", e.value.response) + + with pytest.raises(ClientError) as e: + s3control_client.untag_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + TagKeys=["key1"], + ) + snapshot.match("untag-resource-no-exist", e.value.response) + + with pytest.raises(ClientError) as e: + s3control_client.list_tags_for_resource( + AccountId=account_id, + ResourceArn=bucket_arn, + ) + snapshot.match("list-resource-tags-no-exist", e.value.response) diff --git a/tests/aws/services/s3control/test_s3control.snapshot.json b/tests/aws/services/s3control/test_s3control.snapshot.json index 3165e12ab6836..32c81a9b892f3 100644 --- a/tests/aws/services/s3control/test_s3control.snapshot.json +++ b/tests/aws/services/s3control/test_s3control.snapshot.json @@ -589,5 +589,202 @@ } } } + }, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlTagging::test_tag_lifecycle": { + "recorded-date": "28-11-2025, 11:45:27", + "recorded-content": { + "list-tags-before": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "tag-resource": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + }, + "list-tags-1": { + "Tags": [ + { + "Key": "key1", + "Value": "value1" + }, + { + "Key": "key2", + "Value": "value2" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "tag-resource-after-update": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + }, + "list-tags-after-update": { + "Tags": [ + { + "Key": "key1", + "Value": "value1" + }, + { + "Key": "key2", + "Value": "value2" + }, + { + "Key": "key3", + "Value": "value3" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "untag-resource": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + }, + "list-tags-after-untag": { + "Tags": [ + { + "Key": "key1", + "Value": "value1" + }, + { + "Key": "key2", + "Value": "value2" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "untag-resource-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 204 + } + } + } + }, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlTagging::test_tag_resource_validation": { + "recorded-date": "28-11-2025, 12:06:32", + "recorded-content": { + "tags-key-none": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-value-none": { + "Error": { + "Code": "MalformedXML", + "Message": "The XML you provided was not well-formed or did not validate against our published schema" + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-key-aws-prefix": { + "Error": { + "Code": "InvalidTag", + "Message": "User-defined tag keys can't start with \"aws:\". This prefix is reserved for system tags. Remove \"aws:\" from your tag keys and try again." + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-key-aws-duplicated-key": { + "Error": { + "Code": "InvalidTag", + "Message": "There are duplicate tag keys in your request. Remove the duplicate tag keys and try again." + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-key-aws-bad-value": { + "Error": { + "Code": "InvalidTag", + "Message": "This request contains a tag key or value that isn't valid. Valid characters include the following: [a-zA-Z+-=._:/]. Tag keys can contain up to 128 characters. Tag values can contain up to 256 characters." + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "tags-key-aws-bad-key": { + "Error": { + "Code": "InvalidTag", + "Message": "This request contains a tag key or value that isn't valid. Valid characters include the following: [a-zA-Z+-=._:/]. Tag keys can contain up to 128 characters. Tag values can contain up to 256 characters." + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlTagging::test_tag_operation_no_bucket": { + "recorded-date": "28-11-2025, 11:45:32", + "recorded-content": { + "tag-resource-no-exist": { + "Error": { + "Code": "NoSuchResource", + "Message": "The specified resource doesn't exist." + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "untag-resource-no-exist": { + "Error": { + "Code": "NoSuchResource", + "Message": "The specified resource doesn't exist." + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "list-resource-tags-no-exist": { + "Error": { + "Code": "NoSuchResource", + "Message": "The specified resource doesn't exist." + }, + "HostId": "host-id", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } } } diff --git a/tests/aws/services/s3control/test_s3control.validation.json b/tests/aws/services/s3control/test_s3control.validation.json index 4fcb430b372e8..1d846d89a642c 100644 --- a/tests/aws/services/s3control/test_s3control.validation.json +++ b/tests/aws/services/s3control/test_s3control.validation.json @@ -25,5 +25,32 @@ }, "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_empty_public_access_block": { "last_validated_date": "2024-05-30T17:32:59+00:00" + }, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlTagging::test_tag_lifecycle": { + "last_validated_date": "2025-11-28T11:45:28+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 1.41, + "teardown": 0.64, + "total": 3.12 + } + }, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlTagging::test_tag_operation_no_bucket": { + "last_validated_date": "2025-11-28T11:45:33+00:00", + "durations_in_seconds": { + "setup": 0.54, + "call": 0.56, + "teardown": 0.6, + "total": 1.7 + } + }, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlTagging::test_tag_resource_validation": { + "last_validated_date": "2025-11-28T12:06:32+00:00", + "durations_in_seconds": { + "setup": 1.18, + "call": 2.18, + "teardown": 0.61, + "total": 3.97 + } } } diff --git a/tests/aws/services/scheduler/conftest.py b/tests/aws/services/scheduler/conftest.py index 3591951039e6b..21f54ebcc612c 100644 --- a/tests/aws/services/scheduler/conftest.py +++ b/tests/aws/services/scheduler/conftest.py @@ -1,7 +1,10 @@ +import json import logging +import time import pytest +from localstack.testing.aws.util import is_aws_cloud from localstack.utils.strings import short_uid LOG = logging.getLogger(__name__) @@ -27,3 +30,34 @@ def _events_scheduler_create_schedule_group(name, **kwargs): aws_client.scheduler.delete_schedule_group(ScheduleGroupArn=schedule_group_arn) except Exception: LOG.info("Failed to delete schedule group %s", schedule_group_arn) + + +@pytest.fixture(scope="module") +def scheduler_role(aws_client): + role_name = f"test-scheduler-role-{short_uid()}" + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "scheduler.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + + role = aws_client.iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=json.dumps(trust_policy), + ) + role_arn = role["Role"]["Arn"] + + if is_aws_cloud(): + time.sleep(10) + + yield role_arn + + try: + aws_client.iam.delete_role(RoleName=role_name) + except Exception: + LOG.debug("Failed to delete role %s", role_name) diff --git a/tests/aws/services/scheduler/test_scheduler.py b/tests/aws/services/scheduler/test_scheduler.py index 2a0b9ec8f1584..29503c27a2ac0 100644 --- a/tests/aws/services/scheduler/test_scheduler.py +++ b/tests/aws/services/scheduler/test_scheduler.py @@ -109,6 +109,40 @@ def tests_create_schedule_with_invalid_schedule_expression( snapshot.match("invalid-schedule-expression", e.value.response) +@markers.aws.validated +@pytest.mark.parametrize( + "schedule_expression", + [ + "cron(0-15/1 8-8 ? * 2,3,4,5,6,7 *)", + "cron(30-30 13-13 ? * 2,4,6 *)", + "cron(1-31/30 1-1 ? * 1,7 *)", + "cron(0 18 ? * MON-FRI *)", + "cron(*/15 * * * ? *)", + "cron(0-59/10 0-23/2 ? * * *)", + ], +) +def test_create_schedule_with_valid_cron_expressions( + schedule_expression, aws_client, region_name, account_id, cleanups, snapshot, scheduler_role +): + # regression test for https://github.com/localstack/localstack/issues/13572 + snapshot.add_transformer(snapshot.transform.key_value("ScheduleArn")) + scheduler_name = f"test-scheduler-{short_uid()}" + + response = aws_client.scheduler.create_schedule( + Name=scheduler_name, + ScheduleExpression=schedule_expression, + FlexibleTimeWindow={"Mode": "OFF"}, + Target={ + "Arn": f"arn:aws:lambda:{region_name}:{account_id}:function:dummy", + "RoleArn": scheduler_role, + }, + ) + + cleanups.append(lambda: aws_client.scheduler.delete_schedule(Name=scheduler_name)) + + snapshot.match("create-schedule", response) + + @markers.aws.validated def tests_create_schedule_with_valid_schedule_expression( create_role, aws_client, region_name, account_id, cleanups, snapshot diff --git a/tests/aws/services/scheduler/test_scheduler.snapshot.json b/tests/aws/services/scheduler/test_scheduler.snapshot.json index 9000ad747a3a0..900f1d8f832fc 100644 --- a/tests/aws/services/scheduler/test_scheduler.snapshot.json +++ b/tests/aws/services/scheduler/test_scheduler.snapshot.json @@ -311,5 +311,77 @@ } } } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(0-15/1 8-8 ? * 2,3,4,5,6,7 *)]": { + "recorded-date": "17-01-2026, 18:20:57", + "recorded-content": { + "create-schedule": { + "ScheduleArn": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(30-30 13-13 ? * 2,4,6 *)]": { + "recorded-date": "17-01-2026, 18:21:07", + "recorded-content": { + "create-schedule": { + "ScheduleArn": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(1-31/30 1-1 ? * 1,7 *)]": { + "recorded-date": "17-01-2026, 18:21:18", + "recorded-content": { + "create-schedule": { + "ScheduleArn": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(0 18 ? * MON-FRI *)]": { + "recorded-date": "17-01-2026, 18:21:28", + "recorded-content": { + "create-schedule": { + "ScheduleArn": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(*/15 * * * ? *)]": { + "recorded-date": "17-01-2026, 18:21:39", + "recorded-content": { + "create-schedule": { + "ScheduleArn": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(0-59/10 0-23/2 ? * * *)]": { + "recorded-date": "17-01-2026, 18:21:50", + "recorded-content": { + "create-schedule": { + "ScheduleArn": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/scheduler/test_scheduler.validation.json b/tests/aws/services/scheduler/test_scheduler.validation.json index 7f9a09fc8febe..66d8e53c4f394 100644 --- a/tests/aws/services/scheduler/test_scheduler.validation.json +++ b/tests/aws/services/scheduler/test_scheduler.validation.json @@ -1,4 +1,58 @@ { + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(*/15 * * * ? *)]": { + "last_validated_date": "2026-01-17T18:21:39+00:00", + "durations_in_seconds": { + "setup": 10.17, + "call": 0.22, + "teardown": 0.28, + "total": 10.67 + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(0 18 ? * MON-FRI *)]": { + "last_validated_date": "2026-01-17T18:21:29+00:00", + "durations_in_seconds": { + "setup": 10.17, + "call": 0.16, + "teardown": 0.23, + "total": 10.56 + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(0-15/1 8-8 ? * 2,3,4,5,6,7 *)]": { + "last_validated_date": "2026-01-17T18:20:57+00:00", + "durations_in_seconds": { + "setup": 10.9, + "call": 0.24, + "teardown": 0.25, + "total": 11.39 + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(0-59/10 0-23/2 ? * * *)]": { + "last_validated_date": "2026-01-17T18:21:50+00:00", + "durations_in_seconds": { + "setup": 10.19, + "call": 0.17, + "teardown": 0.24, + "total": 10.6 + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(1-31/30 1-1 ? * 1,7 *)]": { + "last_validated_date": "2026-01-17T18:21:18+00:00", + "durations_in_seconds": { + "setup": 10.17, + "call": 0.2, + "teardown": 0.25, + "total": 10.62 + } + }, + "tests/aws/services/scheduler/test_scheduler.py::test_create_schedule_with_valid_cron_expressions[cron(30-30 13-13 ? * 2,4,6 *)]": { + "last_validated_date": "2026-01-17T18:21:07+00:00", + "durations_in_seconds": { + "setup": 10.18, + "call": 0.22, + "teardown": 0.29, + "total": 10.69 + } + }, "tests/aws/services/scheduler/test_scheduler.py::test_list_schedules": { "last_validated_date": "2024-06-11T22:50:50+00:00" }, diff --git a/tests/aws/services/secretsmanager/test_secretsmanager.py b/tests/aws/services/secretsmanager/test_secretsmanager.py index 7a91414c6879e..996dd16ef1206 100644 --- a/tests/aws/services/secretsmanager/test_secretsmanager.py +++ b/tests/aws/services/secretsmanager/test_secretsmanager.py @@ -5,7 +5,6 @@ import uuid from datetime import datetime from math import isclose -from typing import Optional import pytest import requests @@ -163,7 +162,7 @@ def _is_secret_deleted(): def _wait_rotation(client, secret_id: str, secret_version: str): def _is_secret_rotated(): resp: dict = client.describe_secret(SecretId=secret_id) - secret_stage_tags = list() + secret_stage_tags = [] for key, tags in resp.get("VersionIdsToStages", {}).items(): if key == secret_version: secret_stage_tags = tags @@ -479,7 +478,7 @@ def test_create_multi_secrets(self, cleanups, aws_client): ) rs = aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description="testing creation of secrets", ) arns.append(rs["ARN"]) @@ -750,7 +749,7 @@ def test_put_secret_value_with_version_stages(self, sm_snapshot, secret_name, aw sm_snapshot.match("get_secret_value_res_2", get_secret_value_res_2) secret_string_v3: str = "secret_string_v3" - version_stages_v3: ["str"] = ["AWSPENDING"] + version_stages_v3: [str] = ["AWSPENDING"] pv_v3_vid: str = str(uuid.uuid4()) # put_secret_value_res_3 = aws_client.secretsmanager.put_secret_value( @@ -1747,7 +1746,7 @@ def secretsmanager_http_put_pending_secret_value_val_res( return TestSecretsManager.secretsmanager_http_put_secret_value_val_res(res, secret_name) def secretsmanager_http_put_secret_value_with( - self, secret_id: str, secret_string: str, client_request_token: Optional[str] + self, secret_id: str, secret_string: str, client_request_token: str | None ) -> requests.Response: http_body: json = { "SecretId": secret_id, @@ -1767,7 +1766,7 @@ def secretsmanager_http_put_secret_value_with_val_res( return res_json def secretsmanager_http_update_secret( - self, secret_id: str, secret_string: str, client_request_token: Optional[str] + self, secret_id: str, secret_string: str, client_request_token: str | None ): http_body: json = {"SecretId": secret_id, "SecretString": secret_string} if client_request_token: @@ -1776,7 +1775,7 @@ def secretsmanager_http_update_secret( @staticmethod def secretsmanager_http_update_secret_val_res( - res: requests.Response, secret_name: str, client_request_token: Optional[str] + res: requests.Response, secret_name: str, client_request_token: str | None ): assert res.status_code == 200 res_json: json = res.json() @@ -1789,7 +1788,7 @@ def secretsmanager_http_put_secret_value_with_version( self, secret_id: str, secret_string: str, - client_request_token: Optional[str], + client_request_token: str | None, version_stages: list[str], ) -> requests.Response: http_body: json = { @@ -1804,7 +1803,7 @@ def secretsmanager_http_put_secret_value_with_version( def secretsmanager_http_put_secret_value_with_version_val_res( res: requests.Response, secret_name: str, - client_request_token: Optional[str], + client_request_token: str | None, version_stages: list[str], ) -> json: req_version_id: str @@ -1917,6 +1916,9 @@ def test_put_secret_value_with_new_custom_client_request_token(self, secret_name ) @markers.aws.only_localstack # FIXME: all tests using the internal http utils of this class are only targeting localstack + @pytest.mark.skip( + "Test is not AWS validated and deviates from Moto behaviour. See https://github.com/getmoto/moto/pull/9192" + ) def test_http_put_secret_value_with_duplicate_client_request_token( self, secret_name, aws_client ): @@ -2270,7 +2272,7 @@ def test_secret_exists(self, secret_name, aws_client): description = "Testing secret already exists." rs = aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) self._wait_created_is_listed(aws_client.secretsmanager, secret_id=secret_name) @@ -2292,7 +2294,7 @@ def test_secret_exists(self, secret_name, aws_client): ) as res_exists_ex: aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) assert res_exists_ex.typename == "ResourceExistsException" @@ -2307,7 +2309,7 @@ def test_secret_exists_snapshots(self, secret_name, sm_snapshot, cleanups, aws_c description = "Snapshot testing secret already exists." rs = aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) self._wait_created_is_listed(aws_client.secretsmanager, secret_id=secret_name) @@ -2318,7 +2320,7 @@ def test_secret_exists_snapshots(self, secret_name, sm_snapshot, cleanups, aws_c ) as res_exists_ex: aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) sm_snapshot.match("ex_log", res_exists_ex.value.response) @@ -2456,6 +2458,39 @@ def test_secret_tags(self, aws_client, create_secret, sm_snapshot, cleanups): describe_secret_6 = aws_client.secretsmanager.describe_secret(SecretId=secret_arn) sm_snapshot.match("describe_secret_6", describe_secret_6) + @markers.aws.validated + def test_tag_untag_resource_on_deleted_secret(self, aws_client, create_secret, sm_snapshot): + """Test that tagging/untagging a secret marked for deletion raises InvalidRequestException.""" + secret_name = short_uid() + response = create_secret( + Name=secret_name, + SecretString="test-secret-value", + Tags=[{"Key": "initial-tag", "Value": "initial-value"}], + ) + + sm_snapshot.add_transformers_list( + sm_snapshot.transform.secretsmanager_secret_id_arn(response, 0) + ) + sm_snapshot.match("create_secret", response) + + secret_arn = response["ARN"] + + # Delete the secret (marks for deletion with recovery window) + delete_response = aws_client.secretsmanager.delete_secret(SecretId=secret_arn) + sm_snapshot.match("delete_secret", delete_response) + + # Attempting to tag a deleted secret should raise InvalidRequestException + with pytest.raises(ClientError) as exc_tag: + aws_client.secretsmanager.tag_resource( + SecretId=secret_arn, Tags=[{"Key": "new-tag", "Value": "new-value"}] + ) + sm_snapshot.match("tag_deleted_secret_error", exc_tag.value.response) + + # Attempting to untag a deleted secret should raise InvalidRequestException + with pytest.raises(ClientError) as exc_untag: + aws_client.secretsmanager.untag_resource(SecretId=secret_arn, TagKeys=["initial-tag"]) + sm_snapshot.match("untag_deleted_secret_error", exc_untag.value.response) + @markers.aws.validated def test_get_secret_value_errors(self, aws_client, create_secret, sm_snapshot): secret_name = short_uid() @@ -2533,6 +2568,7 @@ def test_get_secret_value( ) sm_snapshot.match("secret_value_http_response", json_response) + @markers.requires_in_process @markers.aws.only_localstack def test_create_secret_with_custom_id( self, account_id, region_name, create_secret, set_resource_custom_id @@ -2576,6 +2612,49 @@ def test_force_delete_deleted_secret(self, sm_snapshot, secret_name, aws_client) self._wait_force_deletion_completed(aws_client.secretsmanager, secret_id) + @pytest.mark.parametrize( + "put_secret_binary, update_secret_binary", + [ + (b"footest", b"updated1"), + (b"tops3cret", b"updated2"), + (b"\x00\x01\x02\xff", b"\xfe\xfd"), + ], + ) + @markers.aws.validated + def test_change_secret_value_after_creation( + self, + sm_snapshot, + create_secret, + aws_client, + put_secret_binary, + update_secret_binary, + ): + secret_name = f"test-secret-{short_uid()}" + response = create_secret(Name=secret_name, SecretBinary=b"initial") + sm_snapshot.add_transformers_list( + sm_snapshot.transform.secretsmanager_secret_id_arn(response, 0) + ) + + secret_value_response = aws_client.secretsmanager.get_secret_value(SecretId=secret_name) + sm_snapshot.match("put_secret_value_secret_response", secret_value_response) + + aws_client.secretsmanager.put_secret_value( + SecretId=secret_name, SecretBinary=put_secret_binary + ) + + secret_value_response_updated = aws_client.secretsmanager.get_secret_value( + SecretId=secret_name + ) + sm_snapshot.match("put_secret_binary_response_updated", secret_value_response_updated) + + aws_client.secretsmanager.update_secret( + SecretId=secret_name, SecretBinary=update_secret_binary + ) + secret_value_response_updated2 = aws_client.secretsmanager.get_secret_value( + SecretId=secret_name + ) + sm_snapshot.match("update_secret_binary_response_updated", secret_value_response_updated2) + class TestSecretsManagerMultiAccounts: @markers.aws.validated @@ -2666,8 +2745,9 @@ def test_cross_account_access(self, aws_client, secondary_aws_client, cleanups): # Note: when removing tags, the response will be empty list in case of AWS, # but it will be None in Localstack. To avoid failing the test, we will use the default value as list assert poll_condition( - lambda: aws_client.secretsmanager.describe_secret(SecretId=secret_arn).get("Tags", []) - == [], + lambda: ( + aws_client.secretsmanager.describe_secret(SecretId=secret_arn).get("Tags", []) == [] + ), timeout=5.0, interval=0.5, ) @@ -2773,10 +2853,10 @@ def test_cross_account_access_non_default_key(self, aws_client, secondary_aws_cl ) assert poll_condition( - lambda: aws_client.secretsmanager.describe_secret(SecretId=secret_arn).get( - "DeletedDate" - ) - is not None, + lambda: ( + aws_client.secretsmanager.describe_secret(SecretId=secret_arn).get("DeletedDate") + is not None + ), timeout=5.0, interval=0.5, ) @@ -2784,10 +2864,10 @@ def test_cross_account_access_non_default_key(self, aws_client, secondary_aws_cl secondary_aws_client.secretsmanager.restore_secret(SecretId=secret_arn) assert poll_condition( - lambda: aws_client.secretsmanager.describe_secret(SecretId=secret_arn).get( - "DeletedDate" - ) - is None, + lambda: ( + aws_client.secretsmanager.describe_secret(SecretId=secret_arn).get("DeletedDate") + is None + ), timeout=5.0, interval=0.5, ) diff --git a/tests/aws/services/secretsmanager/test_secretsmanager.snapshot.json b/tests/aws/services/secretsmanager/test_secretsmanager.snapshot.json index 8e52ed68a419c..11ba9f73dfc8e 100644 --- a/tests/aws/services/secretsmanager/test_secretsmanager.snapshot.json +++ b/tests/aws/services/secretsmanager/test_secretsmanager.snapshot.json @@ -4761,5 +4761,191 @@ } } } + }, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_tag_untag_resource_on_deleted_secret": { + "recorded-date": "29-01-2026, 22:02:37", + "recorded-content": { + "create_secret": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "Name": "", + "VersionId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_secret": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "DeletionDate": "datetime", + "Name": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "tag_deleted_secret_error": { + "Error": { + "Code": "InvalidRequestException", + "Message": "You can't perform this operation on the secret because it was marked for deletion." + }, + "Message": "You can't perform this operation on the secret because it was marked for deletion.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "untag_deleted_secret_error": { + "Error": { + "Code": "InvalidRequestException", + "Message": "You can't perform this operation on the secret because it was marked for deletion." + }, + "Message": "You can't perform this operation on the secret because it was marked for deletion.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_change_secret_value_after_creation[footest-updated1]": { + "recorded-date": "07-02-2026, 13:37:49", + "recorded-content": { + "put_secret_value_secret_response": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'initial'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "put_secret_binary_response_updated": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'footest'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update_secret_binary_response_updated": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'updated1'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_change_secret_value_after_creation[tops3cret-updated2]": { + "recorded-date": "07-02-2026, 13:37:49", + "recorded-content": { + "put_secret_value_secret_response": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'initial'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "put_secret_binary_response_updated": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'tops3cret'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update_secret_binary_response_updated": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'updated2'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_change_secret_value_after_creation[\\x00\\x01\\x02\\xff-\\xfe\\xfd]": { + "recorded-date": "07-02-2026, 13:37:49", + "recorded-content": { + "put_secret_value_secret_response": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'initial'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "put_secret_binary_response_updated": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'\\x00\\x01\\x02\\xff'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "update_secret_binary_response_updated": { + "ARN": "arn::secretsmanager::111111111111:secret:", + "CreatedDate": "datetime", + "Name": "", + "SecretBinary": "b'\\xfe\\xfd'", + "VersionId": "", + "VersionStages": [ + "AWSCURRENT" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/secretsmanager/test_secretsmanager.validation.json b/tests/aws/services/secretsmanager/test_secretsmanager.validation.json index d44fb5cb56bc5..7b193475585ab 100644 --- a/tests/aws/services/secretsmanager/test_secretsmanager.validation.json +++ b/tests/aws/services/secretsmanager/test_secretsmanager.validation.json @@ -134,6 +134,15 @@ "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_version_not_found": { "last_validated_date": "2024-06-13T08:04:35+00:00" }, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_tag_untag_resource_on_deleted_secret": { + "last_validated_date": "2026-01-29T22:02:38+00:00", + "durations_in_seconds": { + "setup": 0.79, + "call": 1.51, + "teardown": 0.23, + "total": 2.53 + } + }, "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_description": { "last_validated_date": "2024-03-15T08:12:49+00:00" }, diff --git a/tests/aws/services/ses/test_ses.py b/tests/aws/services/ses/test_ses.py index 126edfc717ded..32abe2630b1af 100644 --- a/tests/aws/services/ses/test_ses.py +++ b/tests/aws/services/ses/test_ses.py @@ -2,18 +2,18 @@ import json import os from datetime import date, datetime -from typing import Optional, Tuple import pytest import requests from botocore.exceptions import ClientError +from localstack_snapshot.snapshots.transformer import SortingTransformer import localstack.config as config from localstack.services.ses.provider import EMAILS, EMAILS_ENDPOINT from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import short_uid -from localstack.utils.sync import retry +from localstack.utils.sync import poll_condition, retry SAMPLE_TEMPLATE = { "TemplateName": "hello-world", @@ -93,8 +93,8 @@ def setup_email_addresses(ses_verify_identity): """ def inner( - sender_email_address: Optional[str] = None, recipient_email_address: Optional[str] = None - ) -> Tuple[str, str]: + sender_email_address: str | None = None, recipient_email_address: str | None = None + ) -> tuple[str, str]: if is_aws_cloud(): if sender_email_address is None: raise ValueError( @@ -288,6 +288,39 @@ def _assert_sent_quota(expected_counter: int) -> dict: _ = retry(_assert_sent_quota, expected_counter=counter + 1, retries=retries, sleep=1) # snapshot.match('get-quota-3', _) + @markers.aws.validated + def test_describe_config_set_event_destinations( + self, + aws_client, + sns_topic, + ses_configuration_set, + ses_configuration_set_sns_event_destination, + snapshot, + ): + config_set_name = f"config-set-{short_uid()}" + snapshot.add_transformer(snapshot.transform.regex(config_set_name, "")) + + topic_arn = sns_topic["Attributes"]["TopicArn"] + snapshot.add_transformer(snapshot.transform.regex(topic_arn, "")) + + ses_configuration_set(config_set_name) + event_destination_name = f"config-set-event-destination-{short_uid()}" + snapshot.add_transformer( + snapshot.transform.regex(event_destination_name, "") + ) + + snapshot.add_transformer(SortingTransformer("MatchingEventTypes")) + + ses_configuration_set_sns_event_destination( + config_set_name, event_destination_name, topic_arn + ) + + response = aws_client.ses.describe_configuration_set( + ConfigurationSetName=config_set_name, + ConfigurationSetAttributeNames=["eventDestinations"], + ) + snapshot.match("event_destinations", response) + @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ @@ -391,6 +424,75 @@ def test_clone_receipt_rule_set( assert cloned_rule_set["Rules"] == original_rule_set["Rules"] assert [x["Name"] for x in cloned_rule_set["Rules"]] == rule_names + @markers.aws.validated + @pytest.mark.parametrize("notification_type", ["Bounce", "Complaint", "Delivery"]) + @pytest.mark.parametrize("enabled", [True, False]) + def test_set_identity_headers_in_notifications_enabled_success( + self, aws_client, setup_email_addresses, snapshot, notification_type, enabled + ): + """ + Test SetIdentityHeadersInNotificationsEnabled for valid identities and notification types. + Also checks idempotency. + """ + sender_email, _ = setup_email_addresses() + + response = aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=sender_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match(f"set-headers-{notification_type.lower()}-enabled-{enabled}", response) + + # Idempotency check + response2 = aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=sender_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match( + f"set-headers-{notification_type.lower()}-enabled-{enabled}-idempotent", response2 + ) + + @markers.aws.validated + def test_set_identity_headers_in_notifications_enabled_failure_invalid_type( + self, aws_client, setup_email_addresses, snapshot + ): + """ + Test SetIdentityHeadersInNotificationsEnabled for invalid notification types. + """ + sender_email, _ = setup_email_addresses() + enabled = True + notification_type = "InvalidType" + + with pytest.raises(ClientError) as exc: + aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=sender_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match("set-headers-error-invalidtype", exc.value.response) + + @markers.aws.validated + @pytest.mark.parametrize("notification_type", ["Bounce", "Complaint", "Delivery"]) + def test_set_identity_headers_in_notifications_enabled_failure_unknown_identity( + self, aws_client, snapshot, notification_type + ): + """ + Test SetIdentityHeadersInNotificationsEnabled for unknown identity. + """ + enabled = True + unknown_email = "unknown@example.com" + + with pytest.raises(ClientError) as exc: + aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=unknown_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match( + f"set-headers-error-unknown-identity-{notification_type.lower()}", exc.value.response + ) + @markers.aws.manual_setup_required @markers.snapshot.skip_snapshot_verify( paths=[ @@ -850,9 +952,91 @@ def test_special_tags_send_email(self, tag_name, tag_value, aws_client): assert exc.match("MessageRejected") + # we cannot really introspect received emails in AWS + @markers.aws.only_localstack + def test_ses_sns_topic_integration_send_email_ses_destination( + self, + sns_topic, + sns_subscription, + ses_configuration_set, + ses_configuration_set_sns_event_destination, + setup_email_addresses, + aws_client, + ): + """ + Validates that configure Event Destinations and sending an email does not trigger an infinite loop of sending + SNS notifications that sends an email that would trigger SNS. + """ + + sender_email_address, recipient_email_address = setup_email_addresses() + config_set_name = f"config-set-{short_uid()}" + + emails_url = config.internal_service_url() + EMAILS_ENDPOINT + response = requests.delete(emails_url) + assert response.status_code == 204 + + # create subscription to get notified about SES events + topic_arn = sns_topic["Attributes"]["TopicArn"] + sns_subscription( + TopicArn=topic_arn, + Protocol="email", + Endpoint=sender_email_address, + ) + + # create the config set + ses_configuration_set(config_set_name) + event_destination_name = f"config-set-event-destination-{short_uid()}" + ses_configuration_set_sns_event_destination( + config_set_name, event_destination_name, topic_arn + ) + + # send an email to trigger the SNS message and SES message + destination = { + "ToAddresses": [recipient_email_address], + } + send_email = aws_client.ses.send_email( + Destination=destination, + Message=SAMPLE_SIMPLE_EMAIL, + ConfigurationSetName=config_set_name, + Source=sender_email_address, + Tags=[ + { + "Name": "custom-tag", + "Value": "tag-value", + } + ], + ) + + def _get_emails(): + _resp = requests.get(emails_url) + return _resp.json()["messages"] + + poll_condition(lambda: len(_get_emails()) >= 4, timeout=3) + requests.delete(emails_url, params={"id": send_email["MessageId"]}) + + emails = _get_emails() + # we assert that we only received 3 emails + assert len(emails) == 3 + + emails = sorted(emails, key=lambda x: x["Body"]["text_part"]) + # the first email is the validation of SNS confirming the SES subscription + ses_delivery_notification = emails[1] + ses_send_notification = emails[2] + + assert ses_delivery_notification["Subject"] == "SNS-Subscriber-Endpoint" + delivery_payload = json.loads(ses_delivery_notification["Body"]["text_part"]) + assert delivery_payload["eventType"] == "Delivery" + assert delivery_payload["mail"]["source"] == sender_email_address + + assert ses_send_notification["Subject"] == "SNS-Subscriber-Endpoint" + send_payload = json.loads(ses_send_notification["Body"]["text_part"]) + assert send_payload["eventType"] == "Send" + assert send_payload["mail"]["source"] == sender_email_address + @pytest.mark.usefixtures("openapi_validate") class TestSESRetrospection: + @markers.requires_in_process @markers.aws.only_localstack def test_send_email_can_retrospect(self, aws_client): # Test that sent emails can be retrospected through saved file and API access @@ -863,7 +1047,7 @@ def test_send_email_can_retrospect(self, aws_client): def _read_message_from_filesystem(message_id: str) -> dict: """Given a message ID, read the message from filesystem and deserialise it.""" data_dir = config.dirs.data or config.dirs.tmp - with open(os.path.join(data_dir, "ses", message_id + ".json"), "r") as f: + with open(os.path.join(data_dir, "ses", message_id + ".json")) as f: message = f.read() return json.loads(message) @@ -933,6 +1117,7 @@ def _read_message_from_filesystem(message_id: str) -> dict: assert requests.delete(emails_url + f"?id={message2_id}").status_code == 204 assert requests.get(emails_url).json() == {"messages": []} + @markers.requires_in_process @markers.aws.only_localstack def test_send_templated_email_can_retrospect(self, create_template, aws_client): # Test that sent emails can be retrospected through saved file and API access @@ -956,7 +1141,7 @@ def test_send_templated_email_can_retrospect(self, create_template, aws_client): ) message_id = message["MessageId"] - with open(os.path.join(data_dir, "ses", message_id + ".json"), "r") as f: + with open(os.path.join(data_dir, "ses", message_id + ".json")) as f: message = f.read() contents = json.loads(message) @@ -973,3 +1158,13 @@ def test_send_templated_email_can_retrospect(self, create_template, aws_client): assert requests.delete("http://localhost:4566/_aws/ses").status_code == 204 assert requests.get("http://localhost:4566/_aws/ses").json() == {"messages": []} + + @markers.aws.validated + def test_send_email_raises_message_rejected(self, aws_client): + raw_message_data = "From: origin@example.com\nTo: destination@example.com\nSubject: test\n\nThis is the message body.\n\n" + + with pytest.raises(ClientError) as exc: + aws_client.ses.send_raw_email( + Destinations=["invalid@example.com"], RawMessage={"Data": raw_message_data} + ) + assert exc.value.response["Error"]["Code"] == "MessageRejected" diff --git a/tests/aws/services/ses/test_ses.snapshot.json b/tests/aws/services/ses/test_ses.snapshot.json index 73336d13e1921..a0f8bbfb6c02d 100644 --- a/tests/aws/services/ses/test_ses.snapshot.json +++ b/tests/aws/services/ses/test_ses.snapshot.json @@ -806,7 +806,7 @@ } }, "tests/aws/services/ses/test_ses.py::TestSES::test_clone_receipt_rule_set": { - "recorded-date": "25-08-2023, 23:05:14", + "recorded-date": "17-09-2025, 11:56:18", "recorded-content": { "create-receipt-rule-set": { "ResponseMetadata": { @@ -915,5 +915,201 @@ } } } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Bounce]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-bounce-enabled-True": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-bounce-enabled-True-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Complaint]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-complaint-enabled-True": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-complaint-enabled-True-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Delivery]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-delivery-enabled-True": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-delivery-enabled-True-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Bounce]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-bounce-enabled-False": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-bounce-enabled-False-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Complaint]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-complaint-enabled-False": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-complaint-enabled-False-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Delivery]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-delivery-enabled-False": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-delivery-enabled-False-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_invalid_type": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-invalidtype": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Invalid notification type: InvalidType. Valid values are: Bounce, Complaint, Delivery.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Bounce]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-unknown-identity-bounce": { + "Error": { + "Code": "MessageRejected", + "Message": "Identity unknown@example.com is not verified or does not exist.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Complaint]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-unknown-identity-complaint": { + "Error": { + "Code": "MessageRejected", + "Message": "Identity unknown@example.com is not verified or does not exist.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Delivery]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-unknown-identity-delivery": { + "Error": { + "Code": "MessageRejected", + "Message": "Identity unknown@example.com is not verified or does not exist.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_describe_config_set_event_destinations": { + "recorded-date": "20-08-2025, 12:00:48", + "recorded-content": { + "event_destinations": { + "ConfigurationSet": { + "Name": "" + }, + "EventDestinations": [ + { + "Enabled": true, + "MatchingEventTypes": [ + "bounce", + "click", + "delivery", + "open", + "send" + ], + "Name": "", + "SNSDestination": { + "TopicARN": "" + } + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/ses/test_ses.validation.json b/tests/aws/services/ses/test_ses.validation.json index 2a9af7ef3a441..b344177c9dd2f 100644 --- a/tests/aws/services/ses/test_ses.validation.json +++ b/tests/aws/services/ses/test_ses.validation.json @@ -3,7 +3,13 @@ "last_validated_date": "2023-08-25T22:04:12+00:00" }, "tests/aws/services/ses/test_ses.py::TestSES::test_clone_receipt_rule_set": { - "last_validated_date": "2023-08-25T21:05:14+00:00" + "last_validated_date": "2025-09-17T11:56:18+00:00", + "durations_in_seconds": { + "setup": 1.83, + "call": 2.56, + "teardown": 2.05, + "total": 6.44 + } }, "tests/aws/services/ses/test_ses.py::TestSES::test_creating_event_destination_without_configuration_set": { "last_validated_date": "2023-08-25T22:04:35+00:00" @@ -17,6 +23,15 @@ "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set_event_destination": { "last_validated_date": "2023-08-25T22:04:53+00:00" }, + "tests/aws/services/ses/test_ses.py::TestSES::test_describe_config_set_event_destinations": { + "last_validated_date": "2025-08-20T12:00:48+00:00", + "durations_in_seconds": { + "setup": 2.09, + "call": 1.15, + "teardown": 0.58, + "total": 3.82 + } + }, "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-]": { "last_validated_date": "2024-07-30T10:18:09+00:00" }, @@ -70,5 +85,14 @@ }, "tests/aws/services/ses/test_ses.py::TestSES::test_trying_to_delete_event_destination_from_non_existent_configuration_set": { "last_validated_date": "2023-08-25T22:05:02+00:00" + }, + "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_email_raises_message_rejected": { + "last_validated_date": "2025-09-11T11:03:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.62, + "teardown": 0.0, + "total": 1.62 + } } } diff --git a/tests/aws/services/sns/test_sns.py b/tests/aws/services/sns/test_sns.py index a95b936747fec..e6ac138b7346d 100644 --- a/tests/aws/services/sns/test_sns.py +++ b/tests/aws/services/sns/test_sns.py @@ -5,6 +5,7 @@ import queue import random import time +import uuid from io import BytesIO from operator import itemgetter @@ -17,6 +18,7 @@ from cryptography import x509 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding +from localstack_snapshot.snapshots.transformer import RegexTransformer from pytest_httpserver import HTTPServer from werkzeug import Response @@ -29,12 +31,14 @@ from localstack.services.sns.constants import ( PLATFORM_ENDPOINT_MSGS_ENDPOINT, SMS_MSGS_ENDPOINT, + SMS_PHONE_NUMBER_OPT_OUT_ENDPOINT, SUBSCRIPTION_TOKENS_ENDPOINT, ) from localstack.services.sns.provider import SnsProvider from localstack.testing.aws.util import is_aws_cloud from localstack.testing.config import TEST_AWS_ACCESS_KEY_ID, TEST_AWS_SECRET_ACCESS_KEY from localstack.testing.pytest import markers +from localstack.testing.snapshots.transformer_utility import TransformerUtility from localstack.utils import testutil from localstack.utils.aws.arns import get_partition, parse_arn, sqs_queue_arn from localstack.utils.net import wait_for_port_closed, wait_for_port_open @@ -69,9 +73,17 @@ def factory(**kwargs): yield factory for platform_application in platform_applications: - endpoints = aws_client.sns.list_endpoints_by_platform_application( - PlatformApplicationArn=platform_application - ) + try: + endpoints = aws_client.sns.list_endpoints_by_platform_application( + PlatformApplicationArn=platform_application + ) + except Exception as e: + LOG.debug( + "Error cleaning up platform app '%s': %s", + platform_application, + e, + ) + return for endpoint in endpoints["Endpoints"]: try: aws_client.sns.delete_endpoint(EndpointArn=endpoint["EndpointArn"]) @@ -88,15 +100,32 @@ def factory(**kwargs): LOG.debug("Error cleaning up platform application '%s': %s", platform_application, e) +@pytest.fixture +def sns_create_platform_endpoint(aws_client): + platform_endpoints = [] + + def factory(platform_application_arn: str, token: str, **kwargs): + response = aws_client.sns.create_platform_endpoint( + PlatformApplicationArn=platform_application_arn, Token=token, **kwargs + ) + platform_endpoints.append(response["EndpointArn"]) + return response + + yield factory + + for endpoint in platform_endpoints: + try: + aws_client.sns.delete_endpoint(EndpointArn=endpoint["EndpointArn"]) + except Exception as e: + LOG.debug( + "Error cleaning up platform endpoint '%s': %s", + endpoint, + e, + ) + + class TestSNSTopicCrud: @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$.get-topic-attrs.Attributes.DeliveryPolicy", - "$.get-topic-attrs.Attributes.EffectiveDeliveryPolicy", - "$.get-topic-attrs.Attributes.Policy.Statement..Action", # SNS:Receive is added by moto but not returned in AWS - ] - ) def test_create_topic_with_attributes(self, sns_create_topic, snapshot, aws_client): create_topic = sns_create_topic( Name="topictest.fifo", @@ -161,14 +190,30 @@ def test_tags(self, sns_create_topic, snapshot, aws_client): snapshot.match("list-after-update-tags", tags) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$.get-topic-attrs.Attributes.DeliveryPolicy", - "$.get-topic-attrs.Attributes.EffectiveDeliveryPolicy", - "$.get-topic-attrs.Attributes.Policy.Statement..Action", - # SNS:Receive is added by moto but not returned in AWS - ] - ) + def test_tags_removed_after_deletion(self, aws_client, sns_create_topic, snapshot): + topic_name = f"topic-{short_uid()}" + topic = sns_create_topic(Name=topic_name) + topic_arn = topic["TopicArn"] + aws_client.sns.tag_resource( + ResourceArn=topic_arn, + Tags=[ + {"Key": "k1", "Value": "v1"}, + {"Key": "k2", "Value": "v2"}, + ], + ) + tags = aws_client.sns.list_tags_for_resource(ResourceArn=topic_arn) + tags["Tags"].sort(key=itemgetter("Key")) + snapshot.match("list-created-tags", tags) + + # delete the topic and recreate the same topic to ensure the tags no longer exist + aws_client.sns.delete_topic(TopicArn=topic_arn) + sns_create_topic(Name=topic_name) + + tags_response = aws_client.sns.list_tags_for_resource(ResourceArn=topic_arn) + assert len(tags_response["Tags"]) == 0 + snapshot.match("list-empty-tags", tags_response) + + @markers.aws.validated def test_create_topic_test_arn(self, sns_create_topic, snapshot, aws_client, account_id): topic_name = "topic-test-create" response = sns_create_topic(Name=topic_name) @@ -321,6 +366,301 @@ def test_topic_delivery_policy_crud(self, sns_create_topic, snapshot, aws_client snapshot.match("get-topic-attrs-after-delete", get_attrs_updated) +class TestSNSTopicCrudV2: + @markers.aws.validated + def test_delete_non_existent_topic(self, snapshot, aws_client, account_id, region_name): + response = aws_client.sns.delete_topic( + TopicArn=f"arn:aws:sns:{region_name}:{account_id}:non-existent-{short_uid()}" + ) + + snapshot.match("delete-non-existent-topic", response) + + @markers.aws.validated + def test_create_topic_should_be_idempotent(self, snapshot, sns_create_topic, aws_client): + topic_name = f"test-idempotent-{short_uid()}" + + resp1 = sns_create_topic(Name=topic_name) + aws_client.sns.set_topic_attributes( + TopicArn=resp1["TopicArn"], AttributeName="DisplayName", AttributeValue="AlreadySet" + ) + attrs = aws_client.sns.get_topic_attributes(TopicArn=resp1["TopicArn"]) + snapshot.match("topic-attrs-idempotent-1", attrs) + + resp2 = sns_create_topic(Name=topic_name) + attrs = aws_client.sns.get_topic_attributes(TopicArn=resp2["TopicArn"]) + snapshot.match("topic-attrs-idempotent-2", attrs) + + @markers.aws.validated + def test_create_topic_different_attrs(self, snapshot, sns_create_topic, aws_client): + topic_name = f"test-idempotent-{short_uid()}" + + create_topic_1 = sns_create_topic(Name=topic_name, Attributes={"DisplayName": "Value1"}) + snapshot.match("create-topic", create_topic_1) + + create_topic_no_attr = sns_create_topic(Name=topic_name) + snapshot.match("create-topic-no-attrs", create_topic_no_attr) + + attrs = aws_client.sns.get_topic_attributes(TopicArn=create_topic_1["TopicArn"]) + snapshot.match("topic-attrs", attrs) + + sns_create_topic(Name=topic_name, Attributes={"DisplayName": "Value1"}) + + # FifoTopic = False is a special case, seems to be stored internally + sns_create_topic(Name=topic_name, Attributes={"FifoTopic": "false"}) + + with pytest.raises(ClientError) as exc: + sns_create_topic(Name=topic_name, Attributes={"DisplayName": "Value2"}) + snapshot.match("create-topic-diff-attrs", exc.value.response) + + with pytest.raises(ClientError) as exc: + sns_create_topic(Name=topic_name, Attributes={"FifoTopic": "true"}) + snapshot.match("create-topic-new-attrs-fifo", exc.value.response) + + with pytest.raises(ClientError) as exc: + sns_create_topic(Name=topic_name, Attributes={"SignatureVersion": "2"}) + snapshot.match("create-topic-new-attrs", exc.value.response) + + with pytest.raises(ClientError) as exc: + sns_create_topic( + Name=topic_name, Attributes={"SignatureVersion": "2", "DisplayName": "Value1"} + ) + snapshot.match("create-topic-new-and-same-attrs", exc.value.response) + + @markers.aws.validated + def test_create_topic_name_constraints(self, snapshot, sns_create_topic): + # Valid names within length constraints + valid_name = "a" * 256 + resp = sns_create_topic(Name=valid_name) + snapshot.match("valid-name-max-length", resp) + + # Invalid names: too short / too long + with pytest.raises(ClientError) as e: + sns_create_topic(Name="") + snapshot.match("name-too-short", e.value.response) + + with pytest.raises(ClientError) as e: + sns_create_topic(Name="a" * 257) + snapshot.match("name-too-long", e.value.response) + + with pytest.raises(ClientError) as e: + sns_create_topic(Name="test.fifo") + snapshot.match("name-contains-fifo", e.value.response) + + with pytest.raises(ClientError) as e: + sns_create_topic(Name="test", Attributes={"FifoTopic": "true"}) + snapshot.match("fifo-name-not-contains-fifo", e.value.response) + + # Invalid chars + # TODO: the index is used because using special characters in the matched name doesn't work for all chars + # -> we should write a snapshot test to investigate further + for index, c in enumerate([":", ";", "!", "@", "|", "^", "%", " "]): + with pytest.raises(ClientError) as e: + sns_create_topic(Name=f"bad{c}name") + snapshot.match(f"name-invalid-char-{index}", e.value.response) + + @markers.aws.validated + def test_create_topic_in_multiple_regions( + self, aws_client, aws_client_factory, snapshot, region_name, secondary_region_name + ): + topic_name = f"multiregion-{short_uid()}" + snapshot.add_transformer(RegexTransformer(region_name, "")) + snapshot.add_transformer(RegexTransformer(secondary_region_name, "")) + + sns_primary_region = aws_client_factory(region_name=region_name).sns + topic_arn_primary_region = sns_primary_region.create_topic(Name=topic_name)["TopicArn"] + + sns_secondary_region = aws_client_factory(region_name=secondary_region_name).sns + topic_arn_secondary_region = sns_secondary_region.create_topic(Name=topic_name)["TopicArn"] + + list_topics_primary = sns_primary_region.list_topics() + snapshot.match("list-primary", list_topics_primary) + list_topics_secondary = sns_secondary_region.list_topics() + snapshot.match("list-secondary", list_topics_secondary) + + snapshot.match( + "topic-east", sns_primary_region.get_topic_attributes(TopicArn=topic_arn_primary_region) + ) + snapshot.match( + "topic-west", + sns_secondary_region.get_topic_attributes(TopicArn=topic_arn_secondary_region), + ) + + @markers.aws.validated + def test_list_topic_paging(self, aws_client, sns_create_topic): + topic_arns = [] + page_size = 100 + for i in range(page_size + 20): # > default page size + resp = sns_create_topic(Name=f"paging-{i}-{short_uid()}") + topic_arns.append(resp["TopicArn"]) + + resp = aws_client.sns.list_topics() + assert len(resp["Topics"]) == page_size + + assert "NextToken" in resp + token = resp["NextToken"] + + resp2 = aws_client.sns.list_topics(NextToken=token) + + # Collect all returned ARNs to ensure no duplicates / missing + all_returned_arns = [t["TopicArn"] for t in resp["Topics"]] + [ + t["TopicArn"] for t in resp2["Topics"] + ] + assert set(topic_arns).issubset(set(all_returned_arns)) + + @markers.aws.validated + def test_topic_get_attributes_with_fifo_false(self, sns_create_topic, aws_client, snapshot): + resp = sns_create_topic( + Name=f"standard-topic-{short_uid()}", Attributes={"FifoTopic": "false"} + ) + topic_arn = resp["TopicArn"] + + attrs = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + snapshot.match("get-attrs-standard-topic", attrs) + + with pytest.raises(ClientError) as e: + aws_client.sns.set_topic_attributes( + TopicArn=topic_arn, AttributeName="FifoTopic", AttributeValue="false" + ) + snapshot.match("set-fifo-false-after-creation", e.value.response) + + @markers.aws.validated + def test_topic_add_permission(self, sns_create_topic, aws_client, snapshot, account_id): + topic_arn = sns_create_topic()["TopicArn"] + resp = aws_client.sns.add_permission( + TopicArn=topic_arn, Label="test", AWSAccountId=[account_id], ActionName=["Publish"] + ) + snapshot.match("add-permission-response", resp) + + attributes_resp = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + policy_str = attributes_resp["Attributes"]["Policy"] + policy_json = json.loads(policy_str) + snapshot.match("topic-policy-after-permission", policy_json) + + @markers.aws.validated + def test_topic_add_multiple_permissions( + self, sns_create_topic, aws_client, snapshot, account_id + ): + topic_arn = sns_create_topic()["TopicArn"] + resp = aws_client.sns.add_permission( + TopicArn=topic_arn, + Label="test", + AWSAccountId=[account_id], + ActionName=["Publish", "Subscribe"], + ) + snapshot.match("add-permission-response", resp) + + attributes_resp = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + policy_str = attributes_resp["Attributes"]["Policy"] + policy_json = json.loads(policy_str) + snapshot.match("topic-policy-after-permission", policy_json) + + @markers.aws.validated + def test_topic_remove_permission(self, sns_create_topic, aws_client, snapshot, account_id): + topic_arn = sns_create_topic()["TopicArn"] + label = "test" + aws_client.sns.add_permission( + TopicArn=topic_arn, Label=label, AWSAccountId=[account_id], ActionName=["Publish"] + ) + + aws_client.sns.remove_permission(TopicArn=topic_arn, Label=label) + attributes_resp = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + policy_str = attributes_resp["Attributes"]["Policy"] + policy_json = json.loads(policy_str) + snapshot.match("topic-policy", policy_json) + + @markers.aws.validated + def test_add_permission_errors(self, snapshot, aws_client, account_id): + topic_name = f"topic-{short_uid()}" + topic_arn = aws_client.sns.create_topic(Name=topic_name)["TopicArn"] + + aws_client.sns.add_permission( + TopicArn=topic_arn, + Label="test", + AWSAccountId=[account_id], + ActionName=["Publish"], + ) + + with pytest.raises(ClientError) as e: + aws_client.sns.add_permission( + TopicArn=topic_arn, + Label="test", + AWSAccountId=[account_id], + ActionName=["AddPermission"], + ) + snapshot.match("duplicate-label", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.sns.add_permission( + TopicArn=f"{topic_arn}-not-existing", + Label="test-2", + AWSAccountId=[account_id], + ActionName=["AddPermission"], + ) + snapshot.match("topic-not-found", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.sns.add_permission( + TopicArn=topic_arn, + Label="test-2", + AWSAccountId=[account_id], + ActionName=["InvalidAction"], + ) + snapshot.match("invalid-action", e.value.response) + + @markers.aws.validated + def test_remove_permission_errors(self, snapshot, aws_client, account_id): + topic_name = f"topic-{short_uid()}" + topic_arn = aws_client.sns.create_topic(Name=topic_name)["TopicArn"] + aws_client.sns.add_permission( + TopicArn=topic_arn, + Label="test", + AWSAccountId=[account_id], + ActionName=["Publish"], + ) + + with pytest.raises(ClientError) as e: + aws_client.sns.remove_permission(TopicArn=f"{topic_arn}-not-existing", Label="test") + + snapshot.match("topic-not-found", e.value.response) + + @markers.aws.validated + def test_data_protection_policy_crud(self, snapshot, aws_client, region_name): + topic_name = f"topic-{short_uid()}" + topic_arn = aws_client.sns.create_topic(Name=topic_name)["TopicArn"] + + policy_doc = { + "Name": "data_protection_policy", + "Description": "Test Policy", + "Version": "2021-06-01", + "Statement": [ + { + "Sid": "test-statement", + "DataDirection": "Inbound", + "Principal": ["*"], + "DataIdentifier": [ + f"arn:aws:dataprotection:{region_name}::data-identifier/EmailAddress" + ], + "Operation": {"Deny": {}}, + } + ], + } + + response = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + snapshot.match("get-topic-attributes-before-policy", response) + + response = aws_client.sns.put_data_protection_policy( + ResourceArn=topic_arn, DataProtectionPolicy=json.dumps(policy_doc) + ) + snapshot.match("put-data-protection-policy", response) + + response = aws_client.sns.get_data_protection_policy(ResourceArn=topic_arn) + snapshot.match("get-data-protection-policy", response) + + # check if policy shows up in the attributes + response = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + snapshot.match("get-topic-attributes-after-policy", response) + + class TestSNSPublishCrud: """ This class contains tests related to the global `Publish` validation, not tied to a particular kind of subscription @@ -522,6 +862,21 @@ def test_topic_publish_another_region( snapshot.match("error-batch", e.value.response) + @markers.aws.validated + def test_publish_no_confirm_subscription( + self, sns_create_topic, snapshot, sns_subscription, aws_client, e_mail_address + ): + topic_arn = sns_create_topic()["TopicArn"] + sns_subscription(TopicArn=topic_arn, Protocol="email", Endpoint=e_mail_address) + + # We are not confirming the subscription + def _assert_subscription(): + subscriptions = aws_client.sns.list_subscriptions()["Subscriptions"] + sub = [s for s in subscriptions if s["TopicArn"] == topic_arn][0] + assert sub["SubscriptionArn"] == "PendingConfirmation" + + retry(_assert_subscription, retries=15, sleep=2) + @markers.aws.validated def test_publish_non_existent_target(self, sns_create_topic, snapshot, aws_client, region_name): topic_arn = sns_create_topic()["TopicArn"] @@ -666,6 +1021,92 @@ def test_publish_batch_too_long_message(self, sns_create_topic, snapshot, aws_cl ) assert publish_batch["ResponseMetadata"]["HTTPStatusCode"] == 200 + @markers.aws.validated + def test_publish_batch_invalid_entry_id(self, sns_create_topic, snapshot, aws_client): + """Test validation of batch entry ID according to AWS specifications. + + Entry IDs must: + - Only contain alphanumeric characters, hyphens, and underscores + - Be at most 80 characters long + """ + topic_arn = sns_create_topic()["TopicArn"] + + # Test 1: ID with invalid character (dot) + with pytest.raises(ClientError) as e: + aws_client.sns.publish_batch( + TopicArn=topic_arn, + PublishBatchRequestEntries=[ + { + "Id": "message.id", + "Message": "test message", + } + ], + ) + snapshot.match("invalid-char-dot", e.value.response) + + # Test 2: ID with multiple invalid characters + with pytest.raises(ClientError) as e: + aws_client.sns.publish_batch( + TopicArn=topic_arn, + PublishBatchRequestEntries=[ + { + "Id": "msg@123#test", + "Message": "test message", + } + ], + ) + snapshot.match("invalid-char-special", e.value.response) + + # Test 3: ID that is too long (81 characters) + with pytest.raises(ClientError) as e: + aws_client.sns.publish_batch( + TopicArn=topic_arn, + PublishBatchRequestEntries=[ + { + "Id": "a" * 81, + "Message": "test message", + } + ], + ) + snapshot.match("id-too-long-81", e.value.response) + + # Test 4: ID that is way too long (91 characters) + with pytest.raises(ClientError) as e: + aws_client.sns.publish_batch( + TopicArn=topic_arn, + PublishBatchRequestEntries=[ + { + "Id": "myreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongid", + "Message": "test message", + } + ], + ) + snapshot.match("id-too-long-91", e.value.response) + + # Test 5: Valid ID with exactly 80 characters (should succeed) + publish_batch_response = aws_client.sns.publish_batch( + TopicArn=topic_arn, + PublishBatchRequestEntries=[ + { + "Id": "a" * 80, + "Message": "test message", + } + ], + ) + snapshot.match("valid-id-80-chars", publish_batch_response) + + # Test 6: Valid ID with allowed characters (should succeed) + publish_batch_response = aws_client.sns.publish_batch( + TopicArn=topic_arn, + PublishBatchRequestEntries=[ + { + "Id": "valid-message_123", + "Message": "test message", + } + ], + ) + snapshot.match("valid-id-with-hyphen-underscore", publish_batch_response) + @markers.aws.validated def test_message_structure_json_exc(self, sns_create_topic, snapshot, aws_client): topic_arn = sns_create_topic()["TopicArn"] @@ -942,41 +1383,29 @@ def test_sns_confirm_subscription_wrong_token(self, sns_create_topic, snapshot, snapshot.match("token-not-exists", e.value.response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=["$.list-subscriptions.Subscriptions"], - # there could be cleanup issues and don't want to flake, manually assert - ) def test_list_subscriptions( self, sns_create_topic, sqs_create_queue, sqs_get_queue_arn, sns_subscription, - snapshot, aws_client, ): - snapshot.add_transformer(snapshot.transform.key_value("NextToken")) topic = sns_create_topic() topic_arn = topic["TopicArn"] - snapshot.match("create-topic-1", topic) topic_2 = sns_create_topic() topic_arn_2 = topic_2["TopicArn"] - snapshot.match("create-topic-2", topic_2) - sorting_list = [] + created_subscriptions = [] for i in range(3): queue_url = sqs_create_queue() queue_arn = sqs_get_queue_arn(queue_url) - subscription = sns_subscription(TopicArn=topic_arn, Protocol="sqs", Endpoint=queue_arn) - snapshot.match(f"sub-topic-1-{i}", subscription) - sorting_list.append((topic_arn, queue_arn)) + sns_subscription(TopicArn=topic_arn, Protocol="sqs", Endpoint=queue_arn) + created_subscriptions.append((topic_arn, queue_arn)) for i in range(3): queue_url = sqs_create_queue() queue_arn = sqs_get_queue_arn(queue_url) - subscription = sns_subscription( - TopicArn=topic_arn_2, Protocol="sqs", Endpoint=queue_arn - ) - snapshot.match(f"sub-topic-2-{i}", subscription) - sorting_list.append((topic_arn_2, queue_arn)) + sns_subscription(TopicArn=topic_arn_2, Protocol="sqs", Endpoint=queue_arn) + created_subscriptions.append((topic_arn_2, queue_arn)) list_subs = aws_client.sns.list_subscriptions() all_subs = list_subs["Subscriptions"] @@ -985,11 +1414,8 @@ def test_list_subscriptions( list_subs = aws_client.sns.list_subscriptions(NextToken=next_token) all_subs.extend(list_subs["Subscriptions"]) - all_subs.sort(key=lambda x: sorting_list.index((x["TopicArn"], x["Endpoint"]))) - list_subs["Subscriptions"] = all_subs - snapshot.match("list-subscriptions-aggregated", list_subs) - - assert all((sub["TopicArn"], sub["Endpoint"]) in sorting_list for sub in all_subs) + all_sub_tuples = [(sub["TopicArn"], sub["Endpoint"]) for sub in all_subs] + assert all(sub in all_sub_tuples for sub in created_subscriptions) @markers.aws.validated def test_list_subscriptions_by_topic_pagination( @@ -1120,22 +1546,31 @@ def test_unsubscribe_idempotency( snapshot.match("unsubscribe-2", unsubscribe_2) @markers.aws.validated - def test_unsubscribe_wrong_arn_format(self, snapshot, aws_client): + def test_unsubscribe_wrong_arn_format(self, snapshot, aws_client_factory, region_name): + sns_client = aws_client_factory( + region_name=region_name, config=Config(parameter_validation=False) + ).sns + with pytest.raises(ClientError) as e: - aws_client.sns.unsubscribe(SubscriptionArn="randomstring") + sns_client.unsubscribe(SubscriptionArn="randomstring") snapshot.match("invalid-unsubscribe-arn-1", e.value.response) with pytest.raises(ClientError) as e: - aws_client.sns.unsubscribe(SubscriptionArn="arn:aws:sns:us-east-1:random") + sns_client.unsubscribe(SubscriptionArn="arn:aws:sns:us-east-1:random") snapshot.match("invalid-unsubscribe-arn-2", e.value.response) with pytest.raises(ClientError) as e: - aws_client.sns.unsubscribe(SubscriptionArn="arn:aws:sns:us-east-1:111111111111:random") + sns_client.unsubscribe(SubscriptionArn="arn:aws:sns:us-east-1:111111111111:random") snapshot.match("invalid-unsubscribe-arn-3", e.value.response) + with pytest.raises(ClientError) as e: + sns_client.unsubscribe() + + snapshot.match("invalid-unsubscribe-arn-4", e.value.response) + @markers.aws.validated def test_subscribe_with_invalid_topic(self, sns_create_topic, sns_subscription, snapshot): with pytest.raises(ClientError) as e: @@ -1164,19 +1599,326 @@ def test_subscribe_with_invalid_topic(self, sns_create_topic, sns_subscription, snapshot.match("non-existent-topic", e.value.response) -class TestSNSSubscriptionLambda: +class TestSNSSubscriptionCrudV2: @markers.aws.validated - def test_python_lambda_subscribe_sns_topic( - self, - sns_create_topic, - sns_subscription, - lambda_su_role, - create_lambda_function, - snapshot, - aws_client, - ): - function_name = f"lambda-function-{short_uid()}" - permission_id = f"test-statement-{short_uid()}" + def test_subscribe_sms(self, sns_create_topic, aws_client, snapshot): + topic_name = f"test-topic-{short_uid()}" + snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) + topic_arn = sns_create_topic(Name=topic_name)["TopicArn"] + + resp = aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="sms", + Endpoint="+1234567890", + ) + snapshot.match("subscribe-sms-1", resp) + + # FIXME + @pytest.mark.skipif(not is_aws_cloud(), reason="not accepting valid sms endpoint") + @markers.aws.validated + def test_subscribe_sms_obscure_phone_number(self, sns_create_topic, aws_client, snapshot): + topic_name = f"test-topic-{short_uid()}" + snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) + topic_arn = sns_create_topic(Name=topic_name)["TopicArn"] + resp = aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="sms", + Endpoint="+12/34-567.890", + ) + snapshot.match("subscribe-sms-2", resp) + + @markers.aws.validated + def test_subscribe_unknown_topic(self, aws_client, snapshot, account_id, region_name): + with pytest.raises(ClientError) as e: + aws_client.sns.subscribe( + TopicArn=f"arn:aws:sns:{region_name}:{account_id}:unknown-{short_uid()}", + Protocol="sms", + Endpoint="+1234567890", + ) + snapshot.match("subscribe-unknown-topic", e.value.response) + + @markers.aws.validated + def test_subscribe_unknown_sqs_queue( + self, sns_create_topic, aws_client, snapshot, account_id, region_name + ): + topic_name = f"test-topic-{short_uid()}" + snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) + topic_arn = sns_create_topic(Name=topic_name)["TopicArn"] + resp = aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="sqs", + Endpoint=f"arn:aws:sqs:{region_name}:{account_id}:unknown-{short_uid()}", + ) + snapshot.match("subscribe-unknown-sqs", resp) + + with pytest.raises(ClientError) as e: + aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="sqs", + Endpoint="unknown", + ) + snapshot.match("subscribe-unknown-sqs-invalid_arn", e.value.response) + + @markers.aws.validated + def test_subscribe_sqs_queue_url( + self, sns_create_topic, sqs_create_queue, aws_client, snapshot + ): + topic_arn = sns_create_topic(Name=f"sqs-topic-{short_uid()}")["TopicArn"] + queue_url = sqs_create_queue(QueueName=f"queue-{short_uid()}") + + with pytest.raises(ClientError) as e: + aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="sqs", + Endpoint=queue_url, + ) + + snapshot.match("subscribe-sqs-via-url-error", e.value.response) + + @markers.aws.validated + def test_subscribe_invalid_sms(self, sns_create_topic, aws_client, snapshot): + topic_arn = sns_create_topic(Name=f"bad-sms-{short_uid()}")["TopicArn"] + + invalid_numbers = ["+15--551234567", "NAA+15551234567", "+15551234567.", "/+15551234567"] + + for number in invalid_numbers: + with pytest.raises(ClientError) as e: + aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="sms", + Endpoint=number, + ) + snapshot.match(f"subscribe-bad-sms-{number}", e.value.response) + + # TODO: Parametrize for email protocol as well + @markers.aws.validated + def test_creating_subscription(self, sns_create_topic, aws_client, snapshot): + topic_arn = sns_create_topic(Name=f"create-sub-{short_uid()}")["TopicArn"] + + resp = aws_client.sns.subscribe( + TopicArn=topic_arn, Protocol="http", Endpoint="http://example.com/" + ) + # TODO: investigate why it leaves the subscription in `SNS.ListSubscriptions`, we maybe need to clean up + # after deleting topics, not fully in parity as AWS cleans up instantly + snapshot.match("create-subscription", resp) + + # TODO: parametrize for email protocol + @markers.aws.validated + def test_unsubscribe_from_deleted_topic(self, sns_create_topic, aws_client, snapshot): + topic_arn = sns_create_topic(Name=f"del-topic-sub-{short_uid()}")["TopicArn"] + + sub_arn = aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="http", + Endpoint="http://example.com/", + )["SubscriptionArn"] + + aws_client.sns.delete_topic(TopicArn=topic_arn) + + with pytest.raises(ClientError) as e: + aws_client.sns.unsubscribe(SubscriptionArn=sub_arn) + snapshot.match("unsubscribe-deleted-topic", e.value.response) + + # TODO: parametrize for http and email protocol + @markers.aws.validated + def test_getting_subscriptions_by_topic( + self, sns_create_topic, sqs_create_queue, sqs_get_queue_arn, aws_client, snapshot + ): + topic_arn = sns_create_topic(Name=f"get-subs-{short_uid()}")["TopicArn"] + queue_url = sqs_create_queue(QueueName=f"queue-{short_uid()}") + + aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="sqs", + Endpoint=sqs_get_queue_arn(queue_url), + ) + + resp = aws_client.sns.list_subscriptions_by_topic(TopicArn=topic_arn) + snapshot.match("list-subscriptions-by-topic", resp) + + @markers.aws.validated + def test_subscription_paging(self, sns_create_topic, aws_client, snapshot, sns_subscription): + topic_arn = sns_create_topic(Name=f"paging-sub-{short_uid()}")["TopicArn"] + + sub_arns = [] + for i in range(120): + sub_arns.append( + sns_subscription( + TopicArn=topic_arn, + Protocol="email", + Endpoint=f"user{i}@example.com", + )["SubscriptionArn"] + ) + + resp = aws_client.sns.list_subscriptions_by_topic(TopicArn=topic_arn) + assert "NextToken" in resp + + # TODO: parametrize for http and email protocol + @markers.aws.validated + def test_subscribe_attributes( + self, + sns_create_topic, + aws_client, + snapshot, + sqs_create_queue, + sqs_get_queue_arn, + sns_subscription, + ): + topic_arn = sns_create_topic(Name=f"get-subs-{short_uid()}")["TopicArn"] + queue_url = sqs_create_queue(QueueName=f"queue-{short_uid()}") + + subscribe_response = sns_subscription( + TopicArn=topic_arn, + Protocol="sqs", + Endpoint=sqs_get_queue_arn(queue_url), + ) + sub_arn = subscribe_response["SubscriptionArn"] + + resp = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn) + snapshot.match("get-subscription-attributes", resp) + + # TODO: parametrize for http and email protocol + @markers.aws.validated + def test_creating_subscription_with_attributes( + self, + sns_create_topic, + aws_client, + snapshot, + sqs_create_queue, + sqs_get_queue_arn, + sns_subscription, + ): + topic_arn = sns_create_topic(Name=f"get-subs-{short_uid()}")["TopicArn"] + queue_url = sqs_create_queue(QueueName=f"queue-{short_uid()}") + + sub_arn = sns_subscription( + TopicArn=topic_arn, + Protocol="sqs", + Endpoint=sqs_get_queue_arn(queue_url), + Attributes={"RawMessageDelivery": "true"}, + )["SubscriptionArn"] + + attrs = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn) + snapshot.match("subscription-with-attributes", attrs) + + # TODO: parametrize for http and email protocol + @markers.aws.validated + def test_set_subscription_attributes( + self, + sns_create_topic, + aws_client, + snapshot, + sqs_create_queue, + sqs_get_queue_arn, + sns_subscription, + ): + topic_arn = sns_create_topic(Name=f"get-subs-{short_uid()}")["TopicArn"] + queue_url = sqs_create_queue(QueueName=f"queue-{short_uid()}") + + sub_arn = sns_subscription( + TopicArn=topic_arn, + Protocol="sqs", + Endpoint=sqs_get_queue_arn(queue_url), + )["SubscriptionArn"] + + aws_client.sns.set_subscription_attributes( + SubscriptionArn=sub_arn, + AttributeName="RawMessageDelivery", + AttributeValue="true", + ) + + attrs = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn) + snapshot.match("set-subscription-attributes", attrs) + + @markers.snapshot.skip_snapshot_verify( + paths=["$..Error.Message"], + # AWS adds a strange stacktrace that contains REDACTED at the end. The "regular" error message matches + ) + @markers.aws.validated + def test_subscribe_invalid_filter_policy(self, sns_create_topic, aws_client, snapshot): + topic_arn = sns_create_topic(Name=f"filter-policy-{short_uid()}")["TopicArn"] + + with pytest.raises(ClientError) as e: + aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="email", + Endpoint="test@example.com", + Attributes={"FilterPolicy": "invalid-json"}, + ) + snapshot.match("subscribe-invalid-filter-policy", e.value.response) + + # FIXME + @pytest.mark.skip( + reason="Https for aws lambda URL required but not implemented in localstack lambda" + ) + @markers.aws.validated + def test_confirm_subscription( + self, sns_create_topic, aws_client, snapshot, create_lambda_function + ): + snapshot.add_transformer(TransformerUtility.key_value("SubscriptionArn")) + topic_arn = sns_create_topic(Name=f"confirm-sub-{short_uid()}")["TopicArn"] + function_name = f"lambda-{short_uid()}" + create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=function_name, + runtime=Runtime.python3_12, + ) + + url_config = aws_client.lambda_.create_function_url_config( + FunctionName=function_name, AuthType="NONE" + ) + aws_client.lambda_.add_permission( + FunctionName=function_name, + Action="lambda:InvokeFunctionUrl", + StatementId="AllowSNSInvoke", + Principal="*", + FunctionUrlAuthType="NONE", + ) + + aws_client.sns.subscribe( + TopicArn=topic_arn, + Protocol="https", + Endpoint=url_config["FunctionUrl"], + ) + + def _get_token(): + token_message = aws_client.logs.filter_log_events( + logGroupName=f"/aws/lambda/{function_name}", filterPattern="Token=" + )["events"][0]["message"] + confirmation_url = json.loads(json.loads(token_message)["body"])["SubscribeURL"] + token = confirmation_url.split("Token=")[1] + return token + + token = retry(_get_token, retries=5, sleep=2) + resp = aws_client.sns.confirm_subscription( + TopicArn=topic_arn, Token=token, AuthenticateOnUnsubscribe="true" + ) + snapshot.match("confirm-subscription", resp) + + @markers.aws.validated + def test_get_subscription_attributes_error_not_exists( + self, aws_client, snapshot, account_id, region_name + ): + with pytest.raises(ClientError) as e: + aws_client.sns.get_subscription_attributes( + SubscriptionArn=f"arn:aws:sns:{region_name}:{account_id}:nonexistent:{uuid.uuid4()}" + ) + snapshot.match("get-sub-attrs-nonexistent", e.value.response) + + +class TestSNSSubscriptionLambda: + @markers.aws.validated + def test_python_lambda_subscribe_sns_topic( + self, + sns_create_topic, + sns_subscription, + lambda_su_role, + create_lambda_function, + snapshot, + aws_client, + ): + function_name = f"lambda-function-{short_uid()}" + permission_id = f"test-statement-{short_uid()}" subject = "[Subject] Test subject" message = "Hello world." topic_arn = sns_create_topic()["TopicArn"] @@ -2329,6 +3071,33 @@ def test_publish_sqs_verify_signature( # if the verification failed, it would raise `InvalidSignature` assert is_valid is None + @markers.aws.validated + def test_publish_message_group_id( + self, + aws_client, + sns_topic, + sqs_create_queue, + sns_create_sqs_subscription, + snapshot, + ): + topic_arn = sns_topic["Attributes"]["TopicArn"] + queue_url = sqs_create_queue() + sns_create_sqs_subscription(topic_arn=topic_arn, queue_url=queue_url) + + aws_client.sns.publish( + TopicArn=topic_arn, + Message="test signature value with attributes", + MessageGroupId="my-group-id-1", + MessageAttributes={"attr1": {"DataType": "Number", "StringValue": "1"}}, + ) + response = aws_client.sqs.receive_message( + QueueUrl=queue_url, + WaitTimeSeconds=10, + AttributeNames=["All"], + MessageAttributeNames=["All"], + ) + snapshot.match("messages", response) + class TestSNSSubscriptionSQSFifo: @markers.aws.validated @@ -2534,19 +3303,7 @@ def test_validations_for_fifo( assert e.match("MessageDeduplicationId") snapshot.match("no-msg-dedup-regular-topic", e.value.response) - with pytest.raises(ClientError) as e: - aws_client.sns.publish(TopicArn=topic_arn, Message="test", MessageGroupId=short_uid()) - assert e.match("MessageGroupId") - snapshot.match("no-msg-group-id-regular-topic", e.value.response) - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$.topic-attrs.Attributes.DeliveryPolicy", - "$.topic-attrs.Attributes.EffectiveDeliveryPolicy", - "$.topic-attrs.Attributes.Policy.Statement..Action", # SNS:Receive is added by moto but not returned in AWS - ] - ) @pytest.mark.parametrize("raw_message_delivery", [True, False]) def test_publish_fifo_messages_to_dlq( self, @@ -2704,12 +3461,9 @@ def get_messages_from_dlq(amount_msg: int): @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ - "$.topic-attrs.Attributes.DeliveryPolicy", - "$.topic-attrs.Attributes.EffectiveDeliveryPolicy", - "$.topic-attrs.Attributes.Policy.Statement..Action", # SNS:Receive is added by moto but not returned in AWS "$.republish-batch-response-fifo.Successful..MessageId", # TODO: SNS doesnt keep track of duplicate "$.republish-batch-response-fifo.Successful..SequenceNumber", # TODO: SNS doesnt keep track of duplicate - ] + ], ) @pytest.mark.parametrize("content_based_deduplication", [True, False]) def test_publish_batch_messages_from_fifo_topic_to_fifo_queue( @@ -3027,122 +3781,761 @@ def test_message_to_fifo_sqs_ordering( Attributes=topic_attributes, )["TopicArn"] - queue_attributes = {"FifoQueue": "true", "ContentBasedDeduplication": "true"} - queues = [] - queue_amount = 5 - message_amount = 10 + queue_attributes = {"FifoQueue": "true", "ContentBasedDeduplication": "true"} + queues = [] + queue_amount = 5 + message_amount = 10 + + for _ in range(queue_amount): + queue_name = f"queue-{short_uid()}.fifo" + queue_url = sqs_create_queue( + QueueName=queue_name, + Attributes=queue_attributes, + ) + sns_create_sqs_subscription( + topic_arn=topic_arn, queue_url=queue_url, Attributes={"RawMessageDelivery": "true"} + ) + queues.append(queue_url) + + for i in range(message_amount): + aws_client.sns.publish( + TopicArn=topic_arn, Message=str(i), MessageGroupId="message-group-id-1" + ) + + all_messages = [] + for queue_url in queues: + messages = sqs_collect_messages( + queue_url, + expected=message_amount, + timeout=10, + max_number_of_messages=message_amount, + ) + contents = [message["Body"] for message in messages] + all_messages.append(contents) + + # we're expecting the order to be the same across all queues + reference_order = all_messages[0] + for received_content in all_messages[1:]: + assert received_content == reference_order + + +class TestSNSSubscriptionSES: + @markers.requires_in_process + @markers.aws.only_localstack + def test_topic_email_subscription_confirmation( + self, sns_create_topic, sns_subscription, aws_client + ): + # FIXME: we do not send the token to the email endpoint, so they cannot validate it + # create AWS validated test for format + # for now, access internals + topic_arn = sns_create_topic()["TopicArn"] + subscription = sns_subscription( + TopicArn=topic_arn, + Protocol="email", + Endpoint="localstack@yopmail.com", + ) + subscription_arn = subscription["SubscriptionArn"] + parsed_arn = parse_arn(subscription_arn) + store = SnsProvider.get_store(parsed_arn["account"], parsed_arn["region"]) + + sub_attr = aws_client.sns.get_subscription_attributes(SubscriptionArn=subscription_arn) + assert sub_attr["Attributes"]["PendingConfirmation"] == "true" + + def check_subscription(): + for token, sub_arn in store.subscription_tokens.items(): + if sub_arn == subscription_arn: + aws_client.sns.confirm_subscription(TopicArn=topic_arn, Token=token) + + sub_attributes = aws_client.sns.get_subscription_attributes( + SubscriptionArn=subscription_arn + ) + assert sub_attributes["Attributes"]["PendingConfirmation"] == "false" + + retry(check_subscription, retries=PUBLICATION_RETRIES, sleep=PUBLICATION_TIMEOUT) + + @markers.requires_in_process + @markers.aws.only_localstack + def test_email_sender( + self, + sns_create_topic, + sns_subscription, + aws_client, + monkeypatch, + ): + # make sure to reset all received emails in SES + requests.delete("http://localhost:4566/_aws/ses") + + topic_arn = sns_create_topic()["TopicArn"] + sns_subscription( + TopicArn=topic_arn, + Protocol="email", + Endpoint="localstack@yopmail.com", + ) + + aws_client.sns.publish( + Message="Test message", + TopicArn=topic_arn, + ) + + def _get_messages(amount: int) -> list[dict]: + response = requests.get("http://localhost:4566/_aws/ses").json() + assert len(response["messages"]) == amount + return response["messages"] + + messages = retry(lambda: _get_messages(1), retries=PUBLICATION_RETRIES, sleep=1) + # legacy default value, should be replaced at some point + assert messages[0]["Source"] == "admin@localstack.com" + requests.delete("http://localhost:4566/_aws/ses") + + sender_address = "no-reply@sns.localstack.cloud" + monkeypatch.setattr(config, "SNS_SES_SENDER_ADDRESS", sender_address) + + aws_client.sns.publish( + Message="Test message", + TopicArn=topic_arn, + ) + messages = retry(lambda: _get_messages(1), retries=PUBLICATION_RETRIES, sleep=1) + assert messages[0]["Source"] == sender_address + + +@pytest.fixture(scope="class") +def platform_credentials() -> tuple[str, str]: + # these values need to be extracted from a real amazon developer account if tested against AWS + # https://developer.amazon.com/settings/console/securityprofile/overview.html + client_id = "dummy" + client_secret = "dummy" + return client_id, client_secret + + +@pytest.fixture(scope="class") +def e_mail_address() -> str: + # this address must be real and accessible if you want to test email subscriptions + e_mail = "test@example.com" + return e_mail + + +@pytest.fixture(scope="class") +def phone_number() -> str: + # if you want to test phone number operations against AWS and a real phone number, replace this value + # and use this fixture. + # note: you might need to verify that number first in your AWS account due to the sms sandbox + phone_number = "+430000000000" + return phone_number + + +class TestSNSPlatformApplicationCrud: + @markers.aws.manual_setup_required + def test_create_platform_application( + self, aws_client, snapshot, sns_create_platform_application, platform_credentials + ): + platform = "ADM" + # if tested against AWS, the fixture needs to contain real credentials + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} + response = sns_create_platform_application(Platform=platform, Attributes=attributes) + snapshot.match("create-platform-application", response) + + @markers.aws.manual_setup_required + def test_list_platform_applications( + self, aws_client, snapshot, sns_create_platform_application, platform_credentials + ): + name = f"platform-application-{short_uid()}" + platform = "ADM" + # if tested against AWS, the fixture needs to contain real credentials + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} + sns_create_platform_application(Name=name, Platform=platform, Attributes=attributes) + + response = aws_client.sns.list_platform_applications() + snapshot.match("list-platform-applications", response) + + @pytest.mark.parametrize( + "attributes", + [ + {}, + {"PlatformPrincipal": "dummy"}, + {"PlatformCredential": "dummy"}, + ], + ids=["no-args", "missing-credential", "missing-principal"], + ) + @markers.aws.validated + def test_create_platform_application_invalid_attributes(self, aws_client, snapshot, attributes): + # We cannot actually verify the validity of the passed credentials, which is why it is not part of this test + name = f"platform-application-{short_uid()}" + platform = "ADM" + with pytest.raises(ClientError) as e: + aws_client.sns.create_platform_application( + Name=name, Platform=platform, Attributes=attributes + ) + snapshot.match("platform-application-no-attributes", e.value.response) + + @pytest.mark.parametrize( + "name", [f"{'a' * 257}", "", "@name"], ids=["too-long", "empty", "invalid-char"] + ) + @markers.aws.validated + def test_create_platform_application_invalid_name(self, aws_client, snapshot, name): + attributes = {"PlatformPrincipal": "dummy", "PlatformCredential": "dummy"} + platform = "ADM" + with pytest.raises(ClientError) as e: + aws_client.sns.create_platform_application( + Name=name, Platform=platform, Attributes=attributes + ) + snapshot.match("invalid-application-name", e.value.response) + + @markers.aws.validated + def test_create_platform_application_invalid_platform(self, aws_client, snapshot): + attributes = {"PlatformPrincipal": "dummy", "PlatformCredential": "dummy"} + + name = f"platform-application-{short_uid()}" + invalid_platform = "AAA" + with pytest.raises(ClientError) as e: + aws_client.sns.create_platform_application( + Name=name, Platform=invalid_platform, Attributes=attributes + ) + snapshot.match("invalid-platform", e.value.response) + + @markers.aws.manual_setup_required + def test_get_platform_application_attributes( + self, aws_client, snapshot, sns_create_platform_application, platform_credentials + ): + name = f"platform-application-{short_uid()}" + platform = "ADM" + # if tested against AWS, the fixture needs to contain real credentials + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} + platform_application_arn = sns_create_platform_application( + Name=name, Platform=platform, Attributes=attributes + )["PlatformApplicationArn"] + response = aws_client.sns.get_platform_application_attributes( + PlatformApplicationArn=platform_application_arn + ) + snapshot.match("get-application-attributes", response) + + @markers.aws.validated + def test_get_platform_application_attributes_invalid_arn(self, aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.sns.get_platform_application_attributes(PlatformApplicationArn="invalid-arn") + snapshot.match("invalid-application-arn", e.value.response) + + @markers.aws.validated + def test_get_platform_application_attributes_non_existing_app( + self, aws_client, snapshot, account_id, region_name + ): + with pytest.raises(ClientError) as e: + aws_client.sns.get_platform_application_attributes( + PlatformApplicationArn=f"arn:aws:sns:{region_name}:{account_id}:app/ADM/NonExistingApp" + ) + snapshot.match("non_existing-application-arn", e.value.response) + + @markers.aws.manual_setup_required + def test_set_platform_application_attributes( + self, aws_client, snapshot, sns_create_platform_application, platform_credentials + ): + name = f"platform-application-{short_uid()}" + platform = "ADM" + # if tested against AWS, the fixture needs to contain real credentials + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} + platform_application_arn = sns_create_platform_application( + Name=name, Platform=platform, Attributes=attributes + )["PlatformApplicationArn"] + + attributes = {"SuccessFeedbackSampleRate": "50"} + aws_client.sns.set_platform_application_attributes( + Attributes=attributes, PlatformApplicationArn=platform_application_arn + ) + # give the attributes time to propagate + if is_aws_cloud(): + time.sleep(30) + response = aws_client.sns.get_platform_application_attributes( + PlatformApplicationArn=platform_application_arn + ) + snapshot.match("set-get-application-attributes", response) + + @markers.aws.validated + def test_set_platform_application_attributes_invalid_arn(self, aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.sns.set_platform_application_attributes( + PlatformApplicationArn="invalid-arn", Attributes={} + ) + snapshot.match("invalid-application-arn", e.value.response) + + @pytest.mark.parametrize( + "attributes", + [ + {}, + {"PlatformPrincipal": "dummy"}, + ], + ids=["no-args", "dummy-args"], + ) + @markers.aws.validated + def test_set_platform_application_attributes_non_existing_app( + self, aws_client, snapshot, account_id, region_name, attributes + ): + with pytest.raises(ClientError) as e: + aws_client.sns.set_platform_application_attributes( + PlatformApplicationArn=f"arn:aws:sns:{region_name}:{account_id}:app/ADM/NonExistingApp", + Attributes=attributes, + ) + snapshot.match("non_existing-application-arn", e.value.response) + + +class TestSNSPlatformEndpointCrud: + @markers.aws.manual_setup_required + def test_create_platform_endpoint( + self, + sns_create_platform_application, + sns_create_platform_endpoint, + aws_client, + account_id, + region_name, + platform_credentials, + snapshot, + ): + # if tested against AWS, the fixture needs to contain real credentials + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + response = sns_create_platform_endpoint(platform_application_arn=app_arn, token="token_1") + snapshot.match("create-platform-endpoint", response) + + @markers.aws.manual_setup_required + def test_create_platform_endpoint_idempotency( + self, + sns_create_platform_application, + aws_client, + account_id, + region_name, + platform_credentials, + sns_create_platform_endpoint, + snapshot, + ): + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + response = sns_create_platform_endpoint(platform_application_arn=app_arn, token="token_1") + snapshot.match("create-platform-endpoint", response) + + # create again with the same attributes and token + response = sns_create_platform_endpoint(platform_application_arn=app_arn, token="token_1") + snapshot.match("create-platform-endpoint-idempotent", response) + + # create again with different attributes + with pytest.raises(ClientError) as e: + sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1", Attributes={"Enabled": "false"} + ) + snapshot.match("create-platform-endpoint-different-token", e.value.response) + + @markers.aws.validated + def test_create_platform_endpoint_non_existent_app( + self, + sns_create_platform_application, + aws_client, + account_id, + region_name, + snapshot, + ): + platform_application_arn = "arn:aws:sns:%s:%s:app/ADM/non_existent_app" + with pytest.raises(ClientError) as e: + aws_client.sns.create_platform_endpoint( + PlatformApplicationArn=platform_application_arn % (region_name, account_id), + Token="token_1", + ) + snapshot.match("create-platform-endpoint", e.value.response) + + @markers.aws.manual_setup_required + def test_list_platform_endpoints( + self, + sns_create_platform_application, + aws_client, + account_id, + region_name, + platform_credentials, + sns_create_platform_endpoint, + snapshot, + ): + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + sns_create_platform_endpoint(platform_application_arn=app_arn, token="token_1") + response = aws_client.sns.list_endpoints_by_platform_application( + PlatformApplicationArn=app_arn + ) + + snapshot.match("list-endpoints-by-app", response) + + @markers.aws.manual_setup_required + def test_delete_platform_endpoint( + self, + sns_create_platform_application, + aws_client, + account_id, + region_name, + platform_credentials, + sns_create_platform_endpoint, + snapshot, + ): + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1" + )["EndpointArn"] + response = aws_client.sns.list_endpoints_by_platform_application( + PlatformApplicationArn=app_arn + ) + snapshot.match("list-endpoints-by-app-pre-delete", response) + + response = aws_client.sns.delete_endpoint(EndpointArn=endpoint_arn) + snapshot.match("delete-endpoint", response) + if is_aws_cloud(): + time.sleep(10) + response = aws_client.sns.list_endpoints_by_platform_application( + PlatformApplicationArn=app_arn + ) + snapshot.match("list-endpoints-by-app-post-delete", response) + + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..NextToken" + ], # FIXME: investigate why AWS is not deterministic here, NextToken is sometimes present, sometimes not + ) + @markers.aws.manual_setup_required + def test_delete_platform_endpoint_with_subscription( + self, + sns_create_platform_application, + sns_create_topic, + sns_subscription, + aws_client, + account_id, + region_name, + platform_credentials, + sns_create_platform_endpoint, + snapshot, + ): + # from the docs https://docs.aws.amazon.com/cli/latest/reference/sns/delete-endpoint.html + # "When you delete an endpoint that is also subscribed to a topic, then + # you must also unsubscribe the endpoint from the topic." + # This test validates this particular case + + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + snapshot.add_transformer(snapshot.transform.key_value("NextToken")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1" + )["EndpointArn"] + topic_arn = sns_create_topic()["TopicArn"] + sns_subscription(TopicArn=topic_arn, Protocol="application", Endpoint=endpoint_arn) + + # time to let the changes propagate + if is_aws_cloud(): + time.sleep(20) + + # we list subscriptions by topic, as some tests are not cleaning up properly + response = aws_client.sns.list_subscriptions_by_topic(TopicArn=topic_arn) + snapshot.match("list-subscriptions-pre-delete", response) + + response = aws_client.sns.delete_endpoint(EndpointArn=endpoint_arn) + snapshot.match("delete-endpoint", response) + + # time to let the changes propagate + if is_aws_cloud(): + time.sleep(20) + + response = aws_client.sns.list_subscriptions_by_topic(TopicArn=topic_arn) + snapshot.match("list-subscriptions-post-delete", response) - for _ in range(queue_amount): - queue_name = f"queue-{short_uid()}.fifo" - queue_url = sqs_create_queue( - QueueName=queue_name, - Attributes=queue_attributes, - ) - sns_create_sqs_subscription( - topic_arn=topic_arn, queue_url=queue_url, Attributes={"RawMessageDelivery": "true"} - ) - queues.append(queue_url) + @markers.aws.manual_setup_required + def test_delete_endpoints_of_deleted_app( + self, + sns_create_platform_application, + sns_create_topic, + sns_subscription, + aws_client, + account_id, + region_name, + platform_credentials, + sns_create_platform_endpoint, + snapshot, + ): + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1" + )["EndpointArn"] - for i in range(message_amount): - aws_client.sns.publish( - TopicArn=topic_arn, Message=str(i), MessageGroupId="message-group-id-1" - ) + response = aws_client.sns.delete_platform_application(PlatformApplicationArn=app_arn) + snapshot.match("delete-application", response) + if is_aws_cloud(): + time.sleep(10) + with pytest.raises(ClientError) as e: + aws_client.sns.list_endpoints_by_platform_application(PlatformApplicationArn=app_arn) + snapshot.match("list-endpoints-after-app-delete", e.value.response) - all_messages = [] - for queue_url in queues: - messages = sqs_collect_messages( - queue_url, - expected=message_amount, - timeout=10, - max_number_of_messages=message_amount, - ) - contents = [message["Body"] for message in messages] - all_messages.append(contents) + aws_client.sns.delete_endpoint(EndpointArn=endpoint_arn) - # we're expecting the order to be the same across all queues - reference_order = all_messages[0] - for received_content in all_messages[1:]: - assert received_content == reference_order + @markers.aws.manual_setup_required + def test_get_platform_endpoint_attributes( + self, + aws_client, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, + ): + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1" + )["EndpointArn"] + response = aws_client.sns.get_endpoint_attributes(EndpointArn=endpoint_arn) + snapshot.match("get-platform-endpoint-attributes", response) -class TestSNSSubscriptionSES: - @markers.aws.only_localstack - def test_topic_email_subscription_confirmation( - self, sns_create_topic, sns_subscription, aws_client + @markers.aws.manual_setup_required + def test_set_platform_endpoint_attributes( + self, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, + aws_client, ): - # FIXME: we do not send the token to the email endpoint, so they cannot validate it - # create AWS validated test for format - # for now, access internals - topic_arn = sns_create_topic()["TopicArn"] - subscription = sns_subscription( - TopicArn=topic_arn, - Protocol="email", - Endpoint="localstack@yopmail.com", + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=attributes + )["PlatformApplicationArn"] + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1" + )["EndpointArn"] + + new_attributes = {"Enabled": "false"} + response = aws_client.sns.set_endpoint_attributes( + EndpointArn=endpoint_arn, Attributes=new_attributes ) - subscription_arn = subscription["SubscriptionArn"] - parsed_arn = parse_arn(subscription_arn) - store = SnsProvider.get_store(parsed_arn["account"], parsed_arn["region"]) + snapshot.match("set-platform-endpoint-attributes", response) - sub_attr = aws_client.sns.get_subscription_attributes(SubscriptionArn=subscription_arn) - assert sub_attr["Attributes"]["PendingConfirmation"] == "true" + response = aws_client.sns.get_endpoint_attributes(EndpointArn=endpoint_arn) + snapshot.match("get-platform-endpoint-attributes", response) - def check_subscription(): - for token, sub_arn in store.subscription_tokens.items(): - if sub_arn == subscription_arn: - aws_client.sns.confirm_subscription(TopicArn=topic_arn, Token=token) + @markers.aws.validated + def test_get_platform_endpoint_attributes_non_existent_endpoint( + self, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, + aws_client, + account_id, + region_name, + ): + endpoint_arn = f"arn:aws:sns:{region_name}:{account_id}:endpoint/ADM/fakeapp/{uuid.uuid4()}" + with pytest.raises(ClientError) as e: + aws_client.sns.get_endpoint_attributes(EndpointArn=endpoint_arn) + snapshot.match("set-platform-endpoint-attributes-non-existent-app", e.value.response) - sub_attributes = aws_client.sns.get_subscription_attributes( - SubscriptionArn=subscription_arn - ) - assert sub_attributes["Attributes"]["PendingConfirmation"] == "false" + @markers.aws.validated + def test_set_platform_endpoint_attributes_non_existent_endpoint( + self, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, + aws_client, + account_id, + region_name, + ): + endpoint_arn = f"arn:aws:sns:{region_name}:{account_id}:endpoint/ADM/fakeapp/{uuid.uuid4()}" + attributes = {"Enabled": "false"} + with pytest.raises(ClientError) as e: + aws_client.sns.set_endpoint_attributes(EndpointArn=endpoint_arn, Attributes=attributes) + snapshot.match("set-platform-endpoint-attributes-non-existent-app", e.value.response) + + @pytest.mark.parametrize( + "attributes", + [ + {}, + {"PlatformPrincipal": "Principal"}, + {"PlatformCredential": "Credential"}, + {"InvalidKey": "Value"}, + {"CustomUserData": "A" * 2050}, + ], + ids=[ + "Empty", + "Invalid_Name_Principal", + "Invalid_Name_Credential", + "Invalid_Name_Generic", + "Data_Too_Long", + ], + ) + @markers.aws.manual_setup_required + def test_set_platform_endpoint_attributes_invalid_attributes( + self, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, + aws_client, + attributes, + ): + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + initial_attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=initial_attributes + )["PlatformApplicationArn"] + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1" + )["EndpointArn"] - retry(check_subscription, retries=PUBLICATION_RETRIES, sleep=PUBLICATION_TIMEOUT) + with pytest.raises(ClientError) as e: + aws_client.sns.set_endpoint_attributes(EndpointArn=endpoint_arn, Attributes=attributes) + snapshot.match("set-platform-endpoint-invalid-attributes", e.value.response) + + @pytest.mark.parametrize( + "attributes", + [ + {"PlatformPrincipal": "Principal"}, + {"PlatformCredential": "Credential"}, + {"InvalidKey": "Value"}, + {"CustomUserData": "A" * 2050}, + ], + ids=[ + "Invalid_Name_Principal", + "Invalid_Name_Credential", + "Invalid_Name_Generic", + "Data_Too_Long", + ], + ) + @markers.aws.manual_setup_required + def test_create_platform_endpoint_with_invalid_attributes( + self, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, + aws_client, + attributes, + ): + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + platform_attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=platform_attributes + )["PlatformApplicationArn"] + with pytest.raises(ClientError) as e: + sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1", Attributes=attributes + ) + snapshot.match("create-platform-endpoint-invalid-attr", e.value.response) - @markers.aws.only_localstack - def test_email_sender( + @markers.aws.manual_setup_required + def test_create_platform_endpoint_custom_data( self, - sns_create_topic, - sns_subscription, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, aws_client, - monkeypatch, ): - # make sure to reset all received emails in SES - requests.delete("http://localhost:4566/_aws/ses") + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + platform_attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=platform_attributes + )["PlatformApplicationArn"] - topic_arn = sns_create_topic()["TopicArn"] - sns_subscription( - TopicArn=topic_arn, - Protocol="email", - Endpoint="localstack@yopmail.com", - ) + custom_user_data = "bar" + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, token="token_1", CustomUserData=custom_user_data + )["EndpointArn"] - aws_client.sns.publish( - Message="Test message", - TopicArn=topic_arn, - ) + response = aws_client.sns.get_endpoint_attributes(EndpointArn=endpoint_arn) + snapshot.match("create-endpoint-double-custom-data", response) - def _get_messages(amount: int) -> list[dict]: - response = requests.get("http://localhost:4566/_aws/ses").json() - assert len(response["messages"]) == amount - return response["messages"] + @markers.aws.manual_setup_required + def test_create_platform_endpoint_double_custom_data( + self, + snapshot, + platform_credentials, + sns_create_platform_endpoint, + sns_create_platform_application, + aws_client, + ): + # For some reason, CustomUserData can be specified both as parameter directly and inside attributes. - messages = retry(lambda: _get_messages(1), retries=PUBLICATION_RETRIES, sleep=1) - # legacy default value, should be replaced at some point - assert messages[0]["Source"] == "admin@localstack.com" - requests.delete("http://localhost:4566/_aws/ses") + app_name = f"platform-application-{short_uid()}" + snapshot.add_transformer(RegexTransformer(app_name, "")) + # if tested against AWS, the fixture needs to contain real credentials + principal, credential = platform_credentials + platform_attributes = {"PlatformPrincipal": principal, "PlatformCredential": credential} + app_arn = sns_create_platform_application( + Name=app_name, Platform="ADM", Attributes=platform_attributes + )["PlatformApplicationArn"] - sender_address = "no-reply@sns.localstack.cloud" - monkeypatch.setattr(config, "SNS_SES_SENDER_ADDRESS", sender_address) + attributes = {"CustomUserData": "foo"} + custom_user_data = "bar" + endpoint_arn = sns_create_platform_endpoint( + platform_application_arn=app_arn, + token="token_1", + Attributes=attributes, + CustomUserData=custom_user_data, + )["EndpointArn"] - aws_client.sns.publish( - Message="Test message", - TopicArn=topic_arn, - ) - messages = retry(lambda: _get_messages(1), retries=PUBLICATION_RETRIES, sleep=1) - assert messages[0]["Source"] == sender_address + response = aws_client.sns.get_endpoint_attributes(EndpointArn=endpoint_arn) + snapshot.match("create-endpoint-double-custom-data", response) class TestSNSPlatformEndpoint: + @markers.requires_in_process @markers.aws.only_localstack def test_subscribe_platform_endpoint( self, @@ -3152,13 +4545,17 @@ def test_subscribe_platform_endpoint( aws_client, account_id, region_name, + platform_credentials, ): sns_backend = SnsProvider.get_store(account_id, region_name) topic_arn = sns_create_topic()["TopicArn"] - app_arn = sns_create_platform_application(Name="app1", Platform="p1", Attributes={})[ - "PlatformApplicationArn" - ] + platform = "ADM" + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} + app_arn = sns_create_platform_application( + Name="app1", Platform=platform, Attributes=attributes + )["PlatformApplicationArn"] platform_arn = aws_client.sns.create_platform_endpoint( PlatformApplicationArn=app_arn, Token="token_1" )["EndpointArn"] @@ -3235,11 +4632,16 @@ def test_create_platform_endpoint_check_idempotency( @markers.aws.needs_fixing # AWS validating this is hard because we need real credentials for a GCM/Apple mobile app - def test_publish_disabled_endpoint(self, sns_create_platform_application, aws_client): + def test_publish_disabled_endpoint( + self, sns_create_platform_application, aws_client, platform_credentials + ): + platform = "ADM" + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} response = sns_create_platform_application( Name=f"test-{short_uid()}", - Platform="GCM", - Attributes={"PlatformCredential": "123"}, + Platform=platform, + Attributes=attributes, ) platform_arn = response["PlatformApplicationArn"] response = aws_client.sns.create_platform_endpoint( @@ -3255,12 +4657,20 @@ def test_publish_disabled_endpoint(self, sns_create_platform_application, aws_cl EndpointArn=endpoint_arn, Attributes={"Enabled": "false"} ) - get_attrs = aws_client.sns.get_endpoint_attributes(EndpointArn=endpoint_arn) - assert get_attrs["Attributes"]["Enabled"] == "false" + retries = 3 + sleep = 1 + if is_aws_cloud(): + retries = 30 + sleep = 2 + def _assert_endpoint_disabled(): + get_attrs = aws_client.sns.get_endpoint_attributes(EndpointArn=endpoint_arn) + assert get_attrs["Attributes"]["Enabled"] == "false" + + retry(_assert_endpoint_disabled, retries=retries, sleep=sleep) with pytest.raises(ClientError) as e: message = { - "GCM": '{ "notification": {"title": "Title of notification", "body": "It works" } }' + platform: '{ "notification": {"title": "Title of notification", "body": "It works" } }' } aws_client.sns.publish( TargetArn=endpoint_arn, MessageStructure="json", Message=json.dumps(message) @@ -3297,6 +4707,7 @@ def test_publish_to_gcm(self, sns_create_platform_application, aws_client): ) assert ex.value.response["Error"]["Code"] == "InvalidParameter" + @markers.requires_in_process @markers.aws.only_localstack def test_publish_to_platform_endpoint_is_dispatched( self, @@ -3306,7 +4717,10 @@ def test_publish_to_platform_endpoint_is_dispatched( aws_client, account_id, region_name, + platform_credentials, ): + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} topic_arn = sns_create_topic()["TopicArn"] endpoints_arn = {} for platform_type in ["APNS", "GCM"]: @@ -3314,7 +4728,7 @@ def test_publish_to_platform_endpoint_is_dispatched( # Create an Apple platform application app_arn = sns_create_platform_application( - Name=application_platform_name, Platform=platform_type, Attributes={} + Name=application_platform_name, Platform=platform_type, Attributes=attributes )["PlatformApplicationArn"] endpoint_arn = aws_client.sns.create_platform_endpoint( @@ -3344,13 +4758,13 @@ def test_publish_to_platform_endpoint_is_dispatched( Message=json.dumps(message), MessageStructure="json", ) - sns_backend = SnsProvider.get_store(account_id, region_name) platform_endpoint_msgs = sns_backend.platform_endpoint_messages # assert that message has been received def check_message(): - assert len(platform_endpoint_msgs[endpoint_arn]) > 0 + for arn in endpoints_arn.values(): + assert len(platform_endpoint_msgs[arn]) > 0 retry(check_message, retries=PUBLICATION_RETRIES, sleep=PUBLICATION_TIMEOUT) @@ -3360,6 +4774,7 @@ def check_message(): class TestSNSSMS: + @markers.requires_in_process @markers.aws.only_localstack def test_publish_sms(self, aws_client, account_id, region_name): phone_number = "+33000000000" @@ -3368,8 +4783,8 @@ def test_publish_sms(self, aws_client, account_id, region_name): assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 sns_backend = SnsProvider.get_store( - account_id=account_id, - region_name=region_name, + account_id, + region_name, ) def check_messages(): @@ -3395,6 +4810,7 @@ def test_subscribe_sms_endpoint(self, sns_create_topic, sns_subscription, snapsh ) snapshot.match("subscribe-sms-attrs", sub_attrs) + @markers.requires_in_process @markers.aws.only_localstack def test_publish_sms_endpoint( self, sns_create_topic, sns_subscription, aws_client, account_id, region_name @@ -3446,6 +4862,165 @@ def test_publish_wrong_phone_format( sns_subscription(TopicArn=topic_arn, Protocol="sms", Endpoint="NAA+15551234567") snapshot.match("wrong-endpoint", e.value.response) + @markers.aws.manual_setup_required + def test_set_get_sms_attributes(self, aws_client, account_id, snapshot): + if is_aws_cloud(): + LOG.warning( + "Warning: this test will permanently set values for sms attributes in this region. \n Removing is not possible, only overwriting.\n Remove the exception if you wish to proceed" + ) + raise Exception("Check logs to enable this test against AWS") + + aws_client.sns.set_sms_attributes( + attributes={ + "DeliveryStatusSuccessSamplingRate": "100", + "DefaultSenderID": "LSTest", + "DefaultSMSType": "Promotional", + } + ) + + response = aws_client.sns.get_sms_attributes() + snapshot.match("get-sms-all-attributes", response) + + response = aws_client.sns.get_sms_attributes( + attributes=["DefaultSenderID", "DefaultSMSType"] + ) + snapshot.match("get-sms-some-attributes", response) + + @markers.aws.manual_setup_required + def test_get_sms_attributes_from_unmodified_region( + self, aws_client_factory, secondary_region_name, snapshot + ): + # if the secondary region is already modified, you can use SECONDARY_TEST_AWS_PROFILE and similar + # variables to change it + + default_spend_limit = "1" + + sns_unmodified_region = aws_client_factory(region_name=secondary_region_name).sns + response = sns_unmodified_region.get_sms_attributes() + assert response["attributes"]["MonthlySpendLimit"] == default_spend_limit + snapshot.match("get-sms-attributes_unmodified_region", response) + + @pytest.mark.parametrize( + "attribute_key_value", + [ + ("InvalidAttribute", "invalid"), + ("DefaultSendID", "a" * 12), + ("DefaultSendID", "123456789"), + ("DefaultSMSType", "invalid"), + ], + ids=["InvalidAttributeName", "TooLongID", "NoLetterID", "InvalidSMSType"], + ) + @markers.aws.validated + def test_set_invalid_sms_attributes(self, aws_client, snapshot, attribute_key_value): + with pytest.raises(ClientError) as e: + aws_client.sns.set_sms_attributes( + attributes={ + attribute_key_value[0]: attribute_key_value[1], + } + ) + snapshot.match("invalid-attribute", e.value.response) + + @markers.aws.manual_setup_required + @markers.requires_in_process + def test_is_phone_number_opted_out( + self, phone_number, aws_client, snapshot, account_id, region_name, cleanups + ): + # this test expects the fixture-provided phone number to be opted out + # if you want to test against AWS, you need to manually opt out a number + # https://us-east-1.console.aws.amazon.com/sms-voice/home?region=us-east-1#/opt-out-lists?name=Default&tab=opt-out-list-opted-out-numbers + sns_store = SnsProvider.get_store(account_id, region_name) + + def cleanup_store(): + sns_store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) + + if not is_aws_cloud(): + sns_store.PHONE_NUMBERS_OPTED_OUT.add(phone_number) + cleanups.append(cleanup_store) + + response = aws_client.sns.check_if_phone_number_is_opted_out(phoneNumber=phone_number) + snapshot.match("phone-number-opted-out", response) + + @markers.aws.manual_setup_required + @markers.requires_in_process + def test_list_phone_numbers_opted_out( + self, phone_number, aws_client, snapshot, account_id, region_name, cleanups + ): + # this test expects exactly one phone number opted out + # if you want to test against AWS, you need to manually opt out a number + # https://us-east-1.console.aws.amazon.com/sms-voice/home?region=us-east-1#/opt-out-lists?name=Default&tab=opt-out-list-opted-out-numbers + sns_store = SnsProvider.get_store(account_id, region_name) + + def cleanup_store(): + sns_store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) + + if not is_aws_cloud(): + sns_store.PHONE_NUMBERS_OPTED_OUT.add(phone_number) + cleanups.append(cleanup_store) + + snapshot.add_transformer( + TransformerUtility.jsonpath( + jsonpath="$..phoneNumbers[*]", + value_replacement="phone-number", + ) + ) + response = aws_client.sns.list_phone_numbers_opted_out() + snapshot.match("list-phone-numbers-opted-out", response) + + @markers.aws.manual_setup_required + @markers.requires_in_process + def test_opt_in_phone_number( + self, phone_number, aws_client, snapshot, account_id, region_name, cleanups + ): + # this test expects exactly one phone number opted out + # if you want to test against AWS, you need to manually opt out a number + # https://us-east-1.console.aws.amazon.com/sms-voice/home?region=us-east-1#/opt-out-lists?name=Default&tab=opt-out-list-opted-out-numbers + # IMPORTANT: a phone number can only be opted in once every 30 days on AWS. + # Make sure everything else is set up and taken care of properly before trying to validate this. + sns_store = SnsProvider.get_store(account_id, region_name) + + def cleanup_store(): + sns_store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) + + if not is_aws_cloud(): + sns_store.PHONE_NUMBERS_OPTED_OUT.add(phone_number) + cleanups.append(cleanup_store) + response = aws_client.sns.check_if_phone_number_is_opted_out(phoneNumber=phone_number) + assert response["isOptedOut"] + + response = aws_client.sns.opt_in_phone_number(phoneNumber=phone_number) + snapshot.match("opt-in-phone-number", response) + + @markers.aws.only_localstack + @markers.requires_in_process + def test_opt_out_phone_number_via_endpoint( + self, phone_number, aws_client, snapshot, account_id, cleanups + ): + response = aws_client.sns.check_if_phone_number_is_opted_out(phoneNumber=phone_number) + assert not response["isOptedOut"] + data = {"phoneNumber": phone_number, "accountId": account_id} + phone_url = config.external_service_url() + SMS_PHONE_NUMBER_OPT_OUT_ENDPOINT + requests.post(phone_url, data=json.dumps(data)) + + response = aws_client.sns.check_if_phone_number_is_opted_out(phoneNumber=phone_number) + assert response["isOptedOut"] + + @markers.aws.validated + def test_opt_in_non_existing_phone_number( + self, phone_number, aws_client, snapshot, account_id, region_name + ): + non_existing_number = "+4411111111" + response = aws_client.sns.opt_in_phone_number(phoneNumber=non_existing_number) + + snapshot.match("opt-in-non-existing-number", response) + + @markers.aws.validated + def test_opt_in_invalid_number(self, phone_number, aws_client, snapshot): + invalid_number = "invalid" + with pytest.raises(ClientError) as e: + aws_client.sns.opt_in_phone_number(phoneNumber=invalid_number) + + snapshot.match("opt-in-non-existing-number", e.value.response) + class TestSNSSubscriptionHttp: @markers.aws.validated @@ -3477,6 +5052,7 @@ def test_http_subscription_response( ) snapshot.match("subscription-with-arn", subscription_with_arn) + @markers.requires_in_process # uses pytest httpserver @markers.aws.manual_setup_required def test_redrive_policy_http_subscription( self, sns_create_topic, sqs_create_queue, sqs_get_queue_arn, sns_subscription, aws_client @@ -3525,6 +5101,7 @@ def test_redrive_policy_http_subscription( assert message["Type"] == "Notification" assert json.loads(message["Message"])["message"] == "test_redrive_policy" + @markers.requires_in_process # uses pytest httpserver @markers.aws.manual_setup_required def test_multiple_subscriptions_http_endpoint( self, sns_create_topic, sns_subscription, aws_client @@ -3610,6 +5187,7 @@ def handler(_request): for server in servers: server.stop() + @markers.requires_in_process # uses pytest httpserver @markers.aws.manual_setup_required @pytest.mark.parametrize("raw_message_delivery", [True, False]) @markers.snapshot.skip_snapshot_verify( @@ -3792,6 +5370,7 @@ def _clean_headers(response_headers: dict): ) snapshot.match("unsubscribe-request", payload) + @markers.requires_in_process # uses pytest httpserver @markers.aws.manual_setup_required @pytest.mark.parametrize("raw_message_delivery", [True, False]) def test_dlq_external_http_endpoint( @@ -3878,6 +5457,7 @@ def test_dlq_external_http_endpoint( # AWS doesn't send to the DLQ if the UnsubscribeConfirmation fails to be delivered assert "Messages" not in response or response["Messages"] == [] + @markers.requires_in_process # uses pytest httpserver @markers.aws.manual_setup_required @pytest.mark.parametrize("raw_message_delivery", [True, False]) @markers.snapshot.skip_snapshot_verify( @@ -4569,13 +6149,6 @@ def test_cross_region_delivery_sqs( class TestSNSPublishDelivery: @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Attributes.DeliveryPolicy", - "$..Attributes.EffectiveDeliveryPolicy", - "$..Attributes.Policy.Statement..Action", # SNS:Receive is added by moto but not returned in AWS - ] - ) def test_delivery_lambda( self, sns_create_topic, @@ -4742,6 +6315,7 @@ def get_log_events(): class TestSNSCertEndpoint: + @markers.requires_in_process @markers.aws.only_localstack @pytest.mark.parametrize("cert_host", ["", "sns.us-east-1.amazonaws.com"]) def test_cert_endpoint_host( @@ -4788,6 +6362,7 @@ def test_cert_endpoint_host( @pytest.mark.usefixtures("openapi_validate") class TestSNSRetrospectionEndpoints: + @markers.requires_in_process @markers.aws.only_localstack def test_publish_to_platform_endpoint_can_retrospect( self, @@ -4798,7 +6373,11 @@ def test_publish_to_platform_endpoint_can_retrospect( account_id, region_name, secondary_region_name, + platform_credentials, ): + platform = "APNS" + client_id, client_secret = platform_credentials + attributes = {"PlatformPrincipal": client_id, "PlatformCredential": client_secret} sns_backend = SnsProvider.get_store(account_id, region_name) # clean up the saved messages sns_backend_endpoint_arns = list(sns_backend.platform_endpoint_messages.keys()) @@ -4809,7 +6388,7 @@ def test_publish_to_platform_endpoint_can_retrospect( application_platform_name = f"app-platform-{short_uid()}" app_arn = sns_create_platform_application( - Name=application_platform_name, Platform="APNS", Attributes={} + Name=application_platform_name, Platform=platform, Attributes=attributes )["PlatformApplicationArn"] endpoint_arn = aws_client.sns.create_platform_endpoint( @@ -4944,6 +6523,7 @@ def check_message(): ).json() assert not msg_with_region["platform_endpoint_messages"] + @markers.requires_in_process @markers.aws.only_localstack def test_publish_sms_can_retrospect( self, @@ -5047,6 +6627,7 @@ def check_message(): msg_with_region = requests.get(msgs_url, params={"region": region_name}).json() assert not msg_with_region["sms_messages"] + @markers.requires_in_process @markers.aws.only_localstack def test_subscription_tokens_can_retrospect( self, diff --git a/tests/aws/services/sns/test_sns.snapshot.json b/tests/aws/services/sns/test_sns.snapshot.json index 5c2d7f8218b35..86884fc34df67 100644 --- a/tests/aws/services/sns/test_sns.snapshot.json +++ b/tests/aws/services/sns/test_sns.snapshot.json @@ -90,7 +90,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags": { - "recorded-date": "24-08-2023, 22:30:44", + "recorded-date": "13-10-2025, 06:50:09", "recorded-content": { "duplicate-key-error": { "Error": { @@ -146,7 +146,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_test_arn": { - "recorded-date": "24-08-2023, 22:30:45", + "recorded-date": "29-09-2025, 09:32:56", "recorded-content": { "create-topic": { "TopicArn": "arn::sns::111111111111:", @@ -235,7 +235,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_with_more_tags": { - "recorded-date": "24-08-2023, 22:30:46", + "recorded-date": "13-10-2025, 06:53:55", "recorded-content": { "exception-duplicate": { "Error": { @@ -251,7 +251,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_check_idempotency": { - "recorded-date": "24-08-2023, 22:30:47", + "recorded-date": "13-10-2025, 07:07:09", "recorded-content": { "response-created": { "TopicArn": "arn::sns::111111111111:", @@ -284,7 +284,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_after_delete_with_new_tags": { - "recorded-date": "24-08-2023, 22:30:48", + "recorded-date": "13-10-2025, 07:59:08", "recorded-content": { "topic-0": { "TopicArn": "arn::sns::111111111111:", @@ -637,7 +637,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_protocol": { - "recorded-date": "24-08-2023, 23:27:50", + "recorded-date": "06-10-2025, 10:25:11", "recorded-content": { "exception": { "Error": { @@ -653,7 +653,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_from_non_existing_subscription": { - "recorded-date": "24-08-2023, 23:27:52", + "recorded-date": "06-10-2025, 10:25:16", "recorded-content": { "empty-unsubscribe": { "ResponseMetadata": { @@ -664,7 +664,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_create_subscriptions_with_attributes": { - "recorded-date": "29-03-2024, 19:44:43", + "recorded-date": "06-10-2025, 10:25:19", "recorded-content": { "subscribe-wrong-attr": { "Error": { @@ -715,7 +715,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_not_found_error_on_set_subscription_attributes": { - "recorded-date": "24-08-2023, 23:27:55", + "recorded-date": "06-10-2025, 10:25:23", "recorded-content": { "sub": { "SubscriptionArn": "arn::sns::111111111111::", @@ -777,7 +777,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_validate_set_sub_attributes": { - "recorded-date": "29-03-2024, 19:30:24", + "recorded-date": "06-10-2025, 10:25:27", "recorded-content": { "fake-attribute": { "Error": { @@ -826,7 +826,7 @@ "invalid-json-redrive-policy": { "Error": { "Code": "InvalidParameter", - "Message": "Invalid parameter: RedrivePolicy: failed to parse JSON. Unexpected character ('i' (code 105)): was expecting double-quote to start field name\n at [Source: java.io.StringReader@469cc9aa; line: 1, column: 3]", + "Message": "Invalid parameter: RedrivePolicy: failed to parse JSON. Unexpected character ('i' (code 105)): was expecting double-quote to start field name\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]", "Type": "Sender" }, "ResponseMetadata": { @@ -837,7 +837,7 @@ "invalid-json-filter-policy": { "Error": { "Code": "InvalidParameter", - "Message": "Invalid parameter: FilterPolicy: failed to parse JSON. Unexpected character ('i' (code 105)): was expecting double-quote to start field name\n at [Source: (String)\"{invalidjson}\"; line: 1, column: 3]", + "Message": "Invalid parameter: FilterPolicy: failed to parse JSON. Unexpected character ('i' (code 105)): was expecting double-quote to start field name\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]", "Type": "Sender" }, "ResponseMetadata": { @@ -848,7 +848,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_sns_confirm_subscription_wrong_token": { - "recorded-date": "24-08-2023, 23:27:58", + "recorded-date": "06-10-2025, 10:25:28", "recorded-content": { "topic-not-exists": { "Error": { @@ -886,7 +886,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_python_lambda_subscribe_sns_topic": { - "recorded-date": "24-08-2023, 23:28:59", + "recorded-date": "06-10-2025, 11:08:48", "recorded-content": { "notification": { "Message": "Hello world.", @@ -2206,7 +2206,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[True]": { - "recorded-date": "07-12-2023, 10:13:37", + "recorded-date": "18-08-2025, 16:25:29", "recorded-content": { "messages": { "Messages": [ @@ -2248,7 +2248,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[False]": { - "recorded-date": "07-12-2023, 10:13:41", + "recorded-date": "18-08-2025, 16:25:32", "recorded-content": { "messages": { "Messages": [ @@ -2290,7 +2290,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_validations_for_fifo": { - "recorded-date": "24-08-2023, 23:55:19", + "recorded-date": "18-08-2025, 16:21:40", "recorded-content": { "not-fifo-topic": { "Error": { @@ -2357,17 +2357,6 @@ "HTTPHeaders": {}, "HTTPStatusCode": 400 } - }, - "no-msg-group-id-regular-topic": { - "Error": { - "Code": "InvalidParameter", - "Message": "Invalid parameter: MessageGroupId Reason: The request includes MessageGroupId parameter that is not valid for this topic type", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } } } }, @@ -2775,7 +2764,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[True]": { - "recorded-date": "07-12-2023, 10:10:22", + "recorded-date": "17-12-2025, 13:21:07", "recorded-content": { "topic-attrs": { "Attributes": { @@ -2980,7 +2969,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[False]": { - "recorded-date": "07-12-2023, 10:10:29", + "recorded-date": "17-12-2025, 13:21:15", "recorded-content": { "topic-attrs": { "Attributes": { @@ -3321,7 +3310,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[True]": { - "recorded-date": "24-08-2023, 23:53:59", + "recorded-date": "18-08-2025, 16:18:41", "recorded-content": { "messages": { "Messages": [ @@ -3360,7 +3349,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[False]": { - "recorded-date": "24-08-2023, 23:54:05", + "recorded-date": "18-08-2025, 16:18:46", "recorded-content": { "messages": { "Messages": [ @@ -3610,118 +3599,11 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions": { - "recorded-date": "25-08-2023, 16:23:53", - "recorded-content": { - "create-topic-1": { - "TopicArn": "arn::sns::111111111111:", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "create-topic-2": { - "TopicArn": "arn::sns::111111111111:", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "sub-topic-1-0": { - "SubscriptionArn": "arn::sns::111111111111::", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "sub-topic-1-1": { - "SubscriptionArn": "arn::sns::111111111111::", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "sub-topic-1-2": { - "SubscriptionArn": "arn::sns::111111111111::", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "sub-topic-2-0": { - "SubscriptionArn": "arn::sns::111111111111::", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "sub-topic-2-1": { - "SubscriptionArn": "arn::sns::111111111111::", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "sub-topic-2-2": { - "SubscriptionArn": "arn::sns::111111111111::", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list-subscriptions-aggregated": { - "Subscriptions": [ - { - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "Protocol": "sqs", - "SubscriptionArn": "arn::sns::111111111111::", - "TopicArn": "arn::sns::111111111111:" - }, - { - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "Protocol": "sqs", - "SubscriptionArn": "arn::sns::111111111111::", - "TopicArn": "arn::sns::111111111111:" - }, - { - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "Protocol": "sqs", - "SubscriptionArn": "arn::sns::111111111111::", - "TopicArn": "arn::sns::111111111111:" - }, - { - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "Protocol": "sqs", - "SubscriptionArn": "arn::sns::111111111111::", - "TopicArn": "arn::sns::111111111111:" - }, - { - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "Protocol": "sqs", - "SubscriptionArn": "arn::sns::111111111111::", - "TopicArn": "arn::sns::111111111111:" - }, - { - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "Protocol": "sqs", - "SubscriptionArn": "arn::sns::111111111111::", - "TopicArn": "arn::sns::111111111111:" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } + "recorded-date": "07-10-2025, 10:07:36", + "recorded-content": {} }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_idempotency": { - "recorded-date": "20-03-2025, 17:16:39", + "recorded-date": "06-10-2025, 10:26:46", "recorded-content": { "subscribe": { "SubscriptionArn": "arn::sns::111111111111::", @@ -4032,7 +3914,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_wrong_arn_format": { - "recorded-date": "20-10-2023, 12:52:36", + "recorded-date": "06-10-2025, 10:26:51", "recorded-content": { "invalid-unsubscribe-arn-1": { "Error": { @@ -4066,11 +3948,22 @@ "HTTPHeaders": {}, "HTTPStatusCode": 400 } + }, + "invalid-unsubscribe-arn-4": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: SubscriptionArn Reason: no value for required parameter", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } } } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_idempotency": { - "recorded-date": "07-11-2023, 00:36:06", + "recorded-date": "06-10-2025, 10:26:49", "recorded-content": { "unsubscribe-1": { "ResponseMetadata": { @@ -4239,7 +4132,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions_by_topic_pagination": { - "recorded-date": "16-05-2024, 10:32:16", + "recorded-date": "06-10-2025, 10:26:42", "recorded-content": { "list-sub-per-topic-page-2": { "Subscriptions": [ @@ -4951,7 +4844,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_topic": { - "recorded-date": "23-01-2025, 22:38:17", + "recorded-date": "06-10-2025, 10:26:53", "recorded-content": { "invalid-subscribe-arn-1": { "Error": { @@ -5208,7 +5101,7 @@ } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_delete_topic_idempotency": { - "recorded-date": "28-05-2025, 10:08:38", + "recorded-date": "29-09-2025, 09:46:20", "recorded-content": { "delete-topic": { "ResponseMetadata": { @@ -5223,5 +5116,2406 @@ } } } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_message_group_id": { + "recorded-date": "18-08-2025, 15:21:27", + "recorded-content": { + "messages": { + "Messages": [ + { + "Attributes": { + "ApproximateFirstReceiveTimestamp": "timestamp", + "ApproximateReceiveCount": "1", + "MessageGroupId": "my-group-id-1", + "SenderId": "", + "SentTimestamp": "timestamp" + }, + "Body": { + "Type": "Notification", + "MessageId": "", + "TopicArn": "arn::sns::111111111111:", + "Message": "test signature value with attributes", + "Timestamp": "date", + "SignatureVersion": "1", + "Signature": "", + "SigningCertURL": "/SimpleNotificationService-", + "UnsubscribeURL": "/?Action=Unsubscribe&SubscriptionArn=arn::sns::111111111111::", + "MessageAttributes": { + "attr1": { + "Type": "Number", + "Value": "1" + } + } + }, + "MD5OfBody": "", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_delete_non_existent_topic": { + "recorded-date": "29-09-2025, 10:10:56", + "recorded-content": { + "delete-non-existent-topic": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_should_be_idempotent": { + "recorded-date": "29-09-2025, 10:23:24", + "recorded-content": { + "topic-attrs-idempotent-1": { + "Attributes": { + "DisplayName": "AlreadySet", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "topic-attrs-idempotent-2": { + "Attributes": { + "DisplayName": "AlreadySet", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_name_constraints": { + "recorded-date": "06-02-2026, 19:15:23", + "recorded-content": { + "valid-name-max-length": { + "TopicArn": "arn::sns::111111111111:", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "name-too-short": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-too-long": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-contains-fifo": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "fifo-name-not-contains-fifo": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-0": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-1": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-2": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-3": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-4": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-5": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-6": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "name-invalid-char-7": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_get_attributes_with_fifo_false": { + "recorded-date": "29-09-2025, 11:10:41", + "recorded-content": { + "get-attrs-standard-topic": { + "Attributes": { + "DisplayName": "", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-fifo-false-after-creation": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: AttributeName", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_in_multiple_regions": { + "recorded-date": "08-10-2025, 08:54:10", + "recorded-content": { + "list-primary": { + "Topics": [ + { + "TopicArn": "arn::sns::111111111111:" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-secondary": { + "Topics": [ + { + "TopicArn": "arn::sns::111111111111:" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "topic-east": { + "Attributes": { + "DisplayName": "", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "topic-west": { + "Attributes": { + "DisplayName": "", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_sms": { + "recorded-date": "06-10-2025, 11:39:50", + "recorded-content": { + "subscribe-sms-1": { + "SubscriptionArn": "arn::sns::111111111111::", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_unknown_topic": { + "recorded-date": "06-10-2025, 11:43:45", + "recorded-content": { + "subscribe-unknown-topic": { + "Error": { + "Code": "NotFound", + "Message": "Topic does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_unknown_sqs_queue": { + "recorded-date": "06-10-2025, 12:03:29", + "recorded-content": { + "subscribe-unknown-sqs": { + "SubscriptionArn": "arn::sns::111111111111::", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "subscribe-unknown-sqs-invalid_arn": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: SQS endpoint ARN", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_sqs_queue_url": { + "recorded-date": "06-10-2025, 11:56:36", + "recorded-content": { + "subscribe-sqs-via-url-error": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: SQS endpoint ARN", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_double_subscription": { + "recorded-date": "06-10-2025, 11:39:56", + "recorded-content": { + "double-subscription-first": { + "SubscriptionArn": "arn::sns::111111111111:double-sub-befdfdff:", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "double-subscription-second": { + "SubscriptionArn": "arn::sns::111111111111:double-sub-befdfdff:", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_bad_sms": { + "recorded-date": "06-10-2025, 11:39:57", + "recorded-content": { + "subscribe-bad-sms": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid SMS endpoint: invalid-number", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_creating_subscription": { + "recorded-date": "07-10-2025, 07:40:45", + "recorded-content": { + "create-subscription": { + "SubscriptionArn": "pending confirmation", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_unsubscribe_from_deleted_topic": { + "recorded-date": "07-10-2025, 07:46:56", + "recorded-content": { + "unsubscribe-deleted-topic": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: SubscriptionArn Reason: An ARN must have at least 6 elements, not 1", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_getting_subscriptions_by_topic": { + "recorded-date": "07-10-2025, 08:29:43", + "recorded-content": { + "list-subscriptions-by-topic": { + "Subscriptions": [ + { + "Endpoint": "arn::sqs::111111111111:", + "Owner": "111111111111", + "Protocol": "sqs", + "SubscriptionArn": "arn::sns::111111111111::", + "TopicArn": "arn::sns::111111111111:" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_sms_obscure_phone_number": { + "recorded-date": "06-10-2025, 11:39:51", + "recorded-content": { + "subscribe-sms-2": { + "SubscriptionArn": "arn::sns::111111111111::", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_attributes": { + "recorded-date": "12-01-2026, 13:03:28", + "recorded-content": { + "get-subscription-attributes": { + "Attributes": { + "ConfirmationWasAuthenticated": "true", + "Endpoint": "arn::sqs::111111111111:", + "Owner": "111111111111", + "PendingConfirmation": "false", + "Protocol": "sqs", + "RawMessageDelivery": "false", + "SubscriptionArn": "arn::sns::111111111111::", + "SubscriptionPrincipal": "arn::iam::111111111111:user/", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_creating_subscription_with_attributes": { + "recorded-date": "12-01-2026, 13:08:24", + "recorded-content": { + "subscription-with-attributes": { + "Attributes": { + "ConfirmationWasAuthenticated": "true", + "Endpoint": "arn::sqs::111111111111:", + "Owner": "111111111111", + "PendingConfirmation": "false", + "Protocol": "sqs", + "RawMessageDelivery": "true", + "SubscriptionArn": "arn::sns::111111111111::", + "SubscriptionPrincipal": "arn::iam::111111111111:user/", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_delete_subscriptions_on_delete_topic": { + "recorded-date": "12-01-2026, 13:09:31", + "recorded-content": { + "get-sub-after-topic-delete": { + "Attributes": { + "ConfirmationWasAuthenticated": "true", + "Endpoint": "arn::sqs::111111111111:", + "Owner": "111111111111", + "PendingConfirmation": "false", + "Protocol": "sqs", + "RawMessageDelivery": "true", + "SubscriptionArn": "arn::sns::111111111111::", + "SubscriptionPrincipal": "arn::iam::111111111111:user/", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_set_subscription_attributes": { + "recorded-date": "13-01-2026, 09:21:16", + "recorded-content": { + "set-subscription-attributes": { + "Attributes": { + "ConfirmationWasAuthenticated": "true", + "Endpoint": "arn::sqs::111111111111:", + "Owner": "111111111111", + "PendingConfirmation": "false", + "Protocol": "sqs", + "RawMessageDelivery": "true", + "SubscriptionArn": "arn::sns::111111111111::", + "SubscriptionPrincipal": "arn::iam::111111111111:user/", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_invalid_filter_policy": { + "recorded-date": "13-01-2026, 09:26:43", + "recorded-content": { + "subscribe-invalid-filter-policy": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: FilterPolicy: failed to parse JSON. Unrecognized token 'invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 8]", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_check_not_opted_out": { + "recorded-date": "06-10-2025, 11:40:48", + "recorded-content": {} + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_check_opted_out": { + "recorded-date": "06-10-2025, 11:40:49", + "recorded-content": {} + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_check_opted_out_invalid": { + "recorded-date": "06-10-2025, 11:40:49", + "recorded-content": {} + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_list_opted_out": { + "recorded-date": "06-10-2025, 11:40:50", + "recorded-content": { + "list-opted-out": { + "phoneNumbers": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_opt_in": { + "recorded-date": "06-10-2025, 11:40:50", + "recorded-content": {} + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_confirm_subscription": { + "recorded-date": "14-01-2026, 09:45:12", + "recorded-content": { + "confirm-subscription": { + "SubscriptionArn": "arn::sns::111111111111:confirm-sub-e46f689a:", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_get_subscription_attributes_error_not_exists": { + "recorded-date": "14-01-2026, 10:27:15", + "recorded-content": { + "get-sub-attrs-nonexistent": { + "Error": { + "Code": "NotFound", + "Message": "Subscription does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_invalid_sms": { + "recorded-date": "06-10-2025, 12:09:25", + "recorded-content": { + "subscribe-bad-sms-+15--551234567": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid SMS endpoint: +15--551234567", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "subscribe-bad-sms-NAA+15551234567": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid SMS endpoint: NAA+15551234567", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "subscribe-bad-sms-+15551234567.": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid SMS endpoint: +15551234567.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "subscribe-bad-sms-/+15551234567": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid SMS endpoint: /+15551234567", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_get_sms_attributes": { + "recorded-date": "13-10-2025, 10:30:42", + "recorded-content": { + "get-sms-all-attributes": { + "attributes": { + "DefaultSMSType": "Promotional", + "DefaultSenderID": "LSTest", + "DeliveryStatusSuccessSamplingRate": "100", + "MonthlySpendLimit": "1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-sms-some-attributes": { + "attributes": { + "DefaultSMSType": "Promotional", + "DefaultSenderID": "LSTest" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[InvalidAttributeName]": { + "recorded-date": "13-10-2025, 10:43:41", + "recorded-content": { + "invalid-attribute": { + "Error": { + "Code": "InvalidParameter", + "Message": "InvalidAttribute is not a valid attribute", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[TooLongID]": { + "recorded-date": "13-10-2025, 10:43:45", + "recorded-content": { + "invalid-attribute": { + "Error": { + "Code": "InvalidParameter", + "Message": "DefaultSendID is not a valid attribute", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[NoLetterID]": { + "recorded-date": "13-10-2025, 10:43:52", + "recorded-content": { + "invalid-attribute": { + "Error": { + "Code": "InvalidParameter", + "Message": "DefaultSendID is not a valid attribute", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[InvalidSMSType]": { + "recorded-date": "13-10-2025, 10:43:57", + "recorded-content": { + "invalid-attribute": { + "Error": { + "Code": "InvalidParameter", + "Message": "DefaultSMSType is invalid", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_get_sms_attributes_from_unmodified_region": { + "recorded-date": "13-10-2025, 11:12:33", + "recorded-content": { + "get-sms-attributes_unmodified_region": { + "attributes": { + "MonthlySpendLimit": "1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application": { + "recorded-date": "29-10-2025, 09:14:55", + "recorded-content": { + "create-platform-application": { + "PlatformApplicationArn": "arn::sns::111111111111:app/ADM/", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_list_platform_applications": { + "recorded-date": "29-10-2025, 09:20:15", + "recorded-content": { + "list-platform-applications": { + "PlatformApplications": [ + { + "Attributes": { + "Enabled": "true" + }, + "PlatformApplicationArn": "arn::sns::111111111111:app/ADM/" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[no-args]": { + "recorded-date": "28-10-2025, 09:29:33", + "recorded-content": { + "platform-application-no-attributes": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value null at 'attributes' failed to satisfy constraint: Member must not be null", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[missing-credential]": { + "recorded-date": "28-10-2025, 09:29:33", + "recorded-content": { + "platform-application-no-attributes": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: PlatformPrincipal attribute provided without PlatformCredential", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[missing-principal]": { + "recorded-date": "28-10-2025, 09:29:34", + "recorded-content": { + "platform-application-no-attributes": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: PlatformCredential attribute provided without PlatformPrincipal", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_name[too-long]": { + "recorded-date": "28-10-2025, 09:29:34", + "recorded-content": { + "invalid-application-name": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Reason: must be at most 256 characters long", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_name[empty]": { + "recorded-date": "28-10-2025, 09:29:34", + "recorded-content": { + "invalid-application-name": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Reason: cannot be empty", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_name[invalid-char]": { + "recorded-date": "28-10-2025, 09:29:35", + "recorded-content": { + "invalid-application-name": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: @name Reason: must contain only characters 'a'-'z', 'A'-'Z', '0'-'9', '_', '-', and '.'", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_platform": { + "recorded-date": "28-10-2025, 09:29:35", + "recorded-content": { + "invalid-platform": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Platform Reason: AAA is not supported", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_get_platform_application_attributes_invalid_arn": { + "recorded-date": "28-10-2025, 09:29:37", + "recorded-content": { + "invalid-application-arn": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: PlatformApplicationArn Reason: An ARN must have at least 6 elements, not 1", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_get_platform_application_attributes": { + "recorded-date": "28-10-2025, 09:29:36", + "recorded-content": { + "get-application-attributes": { + "Attributes": { + "Enabled": "true" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes": { + "recorded-date": "28-10-2025, 09:30:09", + "recorded-content": { + "set-get-application-attributes": { + "Attributes": { + "Enabled": "true", + "SuccessFeedbackSampleRate": "50" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_get_platform_application_attributes_non_existing_app": { + "recorded-date": "28-10-2025, 09:29:37", + "recorded-content": { + "non_existing-application-arn": { + "Error": { + "Code": "NotFound", + "Message": "PlatformApplication does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_invalid_arn": { + "recorded-date": "28-10-2025, 09:30:09", + "recorded-content": { + "invalid-application-arn": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: PlatformApplicationArn Reason: An ARN must have at least 6 elements, not 1", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app": { + "recorded-date": "28-10-2025, 09:17:14", + "recorded-content": { + "non_existing-application-arn": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value null at 'attributes' failed to satisfy constraint: Member must not be null", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[no-args]": { + "recorded-date": "28-10-2025, 09:30:09", + "recorded-content": { + "non_existing-application-arn": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value null at 'attributes' failed to satisfy constraint: Member must not be null", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[missing-credential]": { + "recorded-date": "28-10-2025, 09:23:04", + "recorded-content": { + "non_existing-application-arn": { + "Error": { + "Code": "NotFound", + "Message": "PlatformApplication does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[missing-principal]": { + "recorded-date": "28-10-2025, 09:23:04", + "recorded-content": { + "non_existing-application-arn": { + "Error": { + "Code": "NotFound", + "Message": "PlatformApplication does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[dummy-args]": { + "recorded-date": "28-10-2025, 09:30:09", + "recorded-content": { + "non_existing-application-arn": { + "Error": { + "Code": "NotFound", + "Message": "PlatformApplication does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint": { + "recorded-date": "31-10-2025, 21:26:47", + "recorded-content": { + "create-platform-endpoint": { + "EndpointArn": "arn::sns::111111111111:endpoint/ADM//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_idempotency": { + "recorded-date": "31-10-2025, 21:26:49", + "recorded-content": { + "create-platform-endpoint": { + "EndpointArn": "arn::sns::111111111111:endpoint/ADM//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "create-platform-endpoint-idempotent": { + "EndpointArn": "arn::sns::111111111111:endpoint/ADM//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "create-platform-endpoint-different-token": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Token Reason: Endpoint arn::sns::111111111111:endpoint/ADM// already exists with the same Token, but different attributes.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_non_existent_app": { + "recorded-date": "31-10-2025, 21:26:50", + "recorded-content": { + "create-platform-endpoint": { + "Error": { + "Code": "NotFound", + "Message": "PlatformApplication does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_list_platform_endpoints": { + "recorded-date": "31-10-2025, 21:26:51", + "recorded-content": { + "list-endpoints-by-app": { + "Endpoints": [ + { + "Attributes": { + "Enabled": "true", + "Token": "token_1" + }, + "EndpointArn": "arn::sns::111111111111:endpoint/ADM//" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_delete_platform_endpoint": { + "recorded-date": "31-10-2025, 21:27:05", + "recorded-content": { + "list-endpoints-by-app-pre-delete": { + "Endpoints": [ + { + "Attributes": { + "Enabled": "true", + "Token": "token_1" + }, + "EndpointArn": "arn::sns::111111111111:endpoint/ADM//" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-endpoint": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-endpoints-by-app-post-delete": { + "Endpoints": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_delete_platform_endpoint_with_subscription": { + "recorded-date": "04-11-2025, 19:18:31", + "recorded-content": { + "list-subscriptions-pre-delete": { + "NextToken": "", + "Subscriptions": [ + { + "Endpoint": "arn::sns::111111111111:endpoint/ADM//", + "Owner": "111111111111", + "Protocol": "application", + "SubscriptionArn": "arn::sns::111111111111::", + "TopicArn": "arn::sns::111111111111:" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-endpoint": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-subscriptions-post-delete": { + "NextToken": "", + "Subscriptions": [ + { + "Endpoint": "arn::sns::111111111111:endpoint/ADM//", + "Owner": "111111111111", + "Protocol": "application", + "SubscriptionArn": "arn::sns::111111111111::", + "TopicArn": "arn::sns::111111111111:" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_delete_endpoints_of_deleted_app": { + "recorded-date": "31-10-2025, 21:28:02", + "recorded-content": { + "delete-application": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-endpoints-after-app-delete": { + "Error": { + "Code": "NotFound", + "Message": "PlatformApplication does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_get_platform_endpoint_attributes": { + "recorded-date": "31-10-2025, 21:28:04", + "recorded-content": { + "get-platform-endpoint-attributes": { + "Attributes": { + "Enabled": "true", + "Token": "token_1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes": { + "recorded-date": "31-10-2025, 21:28:06", + "recorded-content": { + "set-platform-endpoint-attributes": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-platform-endpoint-attributes": { + "Attributes": { + "Enabled": "false", + "Token": "token_1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_arn": { + "recorded-date": "31-10-2025, 20:20:11", + "recorded-content": { + "set-platform-endpoint-attributes-non-existent-app": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: EndpointArn Reason: Wrong number of slashes in relative portion of the ARN.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_non_existent_endpoint": { + "recorded-date": "31-10-2025, 21:28:06", + "recorded-content": { + "set-platform-endpoint-attributes-non-existent-app": { + "Error": { + "Code": "NotFound", + "Message": "Endpoint does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Invalid_Name_Principal]": { + "recorded-date": "31-10-2025, 21:31:04", + "recorded-content": { + "set-platform-endpoint-invalid-attributes": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid attribute name: PlatformPrincipal", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Invalid_Name_Credential]": { + "recorded-date": "31-10-2025, 21:31:06", + "recorded-content": { + "set-platform-endpoint-invalid-attributes": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid attribute name: PlatformCredential", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Invalid_Name_Generic]": { + "recorded-date": "31-10-2025, 21:31:07", + "recorded-content": { + "set-platform-endpoint-invalid-attributes": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid attribute name: InvalidKey", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Data_Too_Long]": { + "recorded-date": "31-10-2025, 21:31:10", + "recorded-content": { + "set-platform-endpoint-invalid-attributes": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid value for attribute: CustomUserData: must be at most 2048 bytes long in UTF-8 encoding", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_get_platform_endpoint_attributes_non_existent_endpoint": { + "recorded-date": "31-10-2025, 21:28:06", + "recorded-content": { + "set-platform-endpoint-attributes-non-existent-app": { + "Error": { + "Code": "NotFound", + "Message": "Endpoint does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Empty]": { + "recorded-date": "31-10-2025, 21:31:03", + "recorded-content": { + "set-platform-endpoint-invalid-attributes": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value null at 'attributes' failed to satisfy constraint: Member must not be null", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Empty]": { + "recorded-date": "31-10-2025, 21:25:17", + "recorded-content": {} + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Invalid_Name_Principal]": { + "recorded-date": "31-10-2025, 21:28:15", + "recorded-content": { + "create-platform-endpoint-invalid-attr": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid attribute name: PlatformPrincipal", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Invalid_Name_Credential]": { + "recorded-date": "31-10-2025, 21:28:16", + "recorded-content": { + "create-platform-endpoint-invalid-attr": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid attribute name: PlatformCredential", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Invalid_Name_Generic]": { + "recorded-date": "31-10-2025, 21:28:17", + "recorded-content": { + "create-platform-endpoint-invalid-attr": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid attribute name: InvalidKey", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Data_Too_Long]": { + "recorded-date": "31-10-2025, 21:28:18", + "recorded-content": { + "create-platform-endpoint-invalid-attr": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Invalid value for attribute: CustomUserData: must be at most 2048 bytes long in UTF-8 encoding", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_double_custom_data": { + "recorded-date": "31-10-2025, 21:41:35", + "recorded-content": { + "create-endpoint-double-custom-data": { + "Attributes": { + "CustomUserData": "foo", + "Enabled": "true", + "Token": "token_1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_custom_data": { + "recorded-date": "31-10-2025, 21:45:15", + "recorded-content": { + "create-endpoint-double-custom-data": { + "Attributes": { + "CustomUserData": "bar", + "Enabled": "true", + "Token": "token_1" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_is_phone_number_opted_out": { + "recorded-date": "01-12-2025, 19:33:36", + "recorded-content": { + "phone-number-opted-out": { + "isOptedOut": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_list_phone_numbers_opted_out": { + "recorded-date": "01-12-2025, 19:37:44", + "recorded-content": { + "list-phone-numbers-opted-out": { + "phoneNumbers": [ + "" + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_opt_in_phone_number": { + "recorded-date": "01-12-2025, 19:44:54", + "recorded-content": { + "opt-in-phone-number": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_opt_in_non_existing_phone_number": { + "recorded-date": "01-12-2025, 19:53:23", + "recorded-content": { + "opt-in-non-existing-number": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_add_permission": { + "recorded-date": "11-12-2025, 11:43:04", + "recorded-content": { + "add-permission-response": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "topic-policy-after-permission": { + "Id": "__default_policy_ID", + "Statement": [ + { + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + }, + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Resource": "arn::sns::111111111111:", + "Sid": "__default_statement_ID" + }, + { + "Action": "SNS:Publish", + "Effect": "Allow", + "Principal": { + "AWS": "arn::iam::111111111111:" + }, + "Resource": "arn::sns::111111111111:", + "Sid": "test" + } + ], + "Version": "2008-10-17" + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_remove_permission": { + "recorded-date": "11-12-2025, 12:02:07", + "recorded-content": { + "topic-policy": { + "Id": "__default_policy_ID", + "Statement": [ + { + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + }, + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Resource": "arn::sns::111111111111:", + "Sid": "__default_statement_ID" + } + ], + "Version": "2008-10-17" + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_policy_overwrite_lifecycle": { + "recorded-date": "11-12-2025, 12:39:06", + "recorded-content": {} + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_add_multiple_permissions": { + "recorded-date": "11-12-2025, 12:53:37", + "recorded-content": { + "add-permission-response": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "topic-policy-after-permission": { + "Id": "__default_policy_ID", + "Statement": [ + { + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + }, + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Resource": "arn::sns::111111111111:", + "Sid": "__default_statement_ID" + }, + { + "Action": [ + "SNS:Publish", + "SNS:Subscribe" + ], + "Effect": "Allow", + "Principal": { + "AWS": "arn::iam::111111111111:" + }, + "Resource": "arn::sns::111111111111:", + "Sid": "test" + } + ], + "Version": "2008-10-17" + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_add_permission_errors": { + "recorded-date": "11-12-2025, 13:00:18", + "recorded-content": { + "duplicate-label": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Statement already exists", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "topic-not-found": { + "Error": { + "Code": "NotFound", + "Message": "Topic does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + }, + "invalid-action": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Policy statement action out of service scope!", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_remove_permission_errors": { + "recorded-date": "11-12-2025, 13:05:40", + "recorded-content": { + "topic-not-found": { + "Error": { + "Code": "NotFound", + "Message": "Topic does not exist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 404 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_data_protection_policy_crud": { + "recorded-date": "18-12-2025, 07:54:08", + "recorded-content": { + "get-topic-attributes-before-policy": { + "Attributes": { + "DisplayName": "", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "put-data-protection-policy": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-data-protection-policy": { + "DataProtectionPolicy": { + "Name": "data_protection_policy", + "Description": "Test Policy", + "Version": "2021-06-01", + "Statement": [ + { + "Sid": "test-statement", + "DataDirection": "Inbound", + "Principal": [ + "*" + ], + "DataIdentifier": [ + "arn::dataprotection:::data-identifier/EmailAddress" + ], + "Operation": { + "Deny": {} + } + } + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-topic-attributes-after-policy": { + "Attributes": { + "DisplayName": "", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_batch_invalid_entry_id": { + "recorded-date": "23-12-2025, 17:39:15", + "recorded-content": { + "invalid-char-dot": { + "Error": { + "Code": "InvalidBatchEntryId", + "Message": "The Id of a batch entry in the batch request contains an impermissible character: message.id", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-char-special": { + "Error": { + "Code": "InvalidBatchEntryId", + "Message": "The Id of a batch entry in the batch request contains an impermissible character: msg@123#test", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "id-too-long-81": { + "Error": { + "Code": "InvalidBatchEntryId", + "Message": "The Id of a batch entry in the batch request is too long: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "id-too-long-91": { + "Error": { + "Code": "InvalidBatchEntryId", + "Message": "The Id of a batch entry in the batch request is too long: myreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongid", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "valid-id-80-chars": { + "Failed": [], + "Successful": [ + { + "Id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "MessageId": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "valid-id-with-hyphen-underscore": { + "Failed": [], + "Successful": [ + { + "Id": "valid-message_123", + "MessageId": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_opt_in_invalid_number": { + "recorded-date": "13-01-2026, 09:38:15", + "recorded-content": { + "opt-in-non-existing-number": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: PhoneNumber Reason: input incorrectly formatted", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_different_attrs": { + "recorded-date": "06-02-2026, 19:09:15", + "recorded-content": { + "create-topic": { + "TopicArn": "arn::sns::111111111111:", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "create-topic-no-attrs": { + "TopicArn": "arn::sns::111111111111:", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "topic-attrs": { + "Attributes": { + "DisplayName": "Value1", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "create-topic-diff-attrs": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Topic already exists with different attributes", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create-topic-new-attrs-fifo": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Topic Name", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create-topic-new-attrs": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Topic already exists with different attributes", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "create-topic-new-and-same-attrs": { + "Error": { + "Code": "InvalidParameter", + "Message": "Invalid parameter: Attributes Reason: Topic already exists with different attributes", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags_removed_after_deletion": { + "recorded-date": "16-02-2026, 05:26:44", + "recorded-content": { + "list-created-tags": { + "Tags": [ + { + "Key": "k1", + "Value": "v1" + }, + { + "Key": "k2", + "Value": "v2" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-empty-tags": { + "Tags": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/sns/test_sns.validation.json b/tests/aws/services/sns/test_sns.validation.json index 04ec06d7594ee..04b9e6c2cf56e 100644 --- a/tests/aws/services/sns/test_sns.validation.json +++ b/tests/aws/services/sns/test_sns.validation.json @@ -5,29 +5,626 @@ "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_delivery_sqs": { "last_validated_date": "2025-05-28T09:55:16+00:00" }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application": { + "last_validated_date": "2025-10-29T09:20:54+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 1.05, + "teardown": 0.46, + "total": 2.44 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[invalid-cred-first0]": { + "last_validated_date": "2025-10-27T11:10:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.18, + "teardown": 0.01, + "total": 0.19 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[invalid-cred-first1]": { + "last_validated_date": "2025-10-27T11:10:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.17, + "teardown": 0.0, + "total": 0.17 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[missing-credential]": { + "last_validated_date": "2025-10-29T09:20:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.0, + "total": 0.3 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[missing-principal]": { + "last_validated_date": "2025-10-29T09:20:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.14, + "teardown": 0.0, + "total": 0.14 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_attributes[no-args]": { + "last_validated_date": "2025-10-29T09:20:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.34, + "teardown": 0.0, + "total": 0.34 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_name[empty]": { + "last_validated_date": "2025-10-29T09:20:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.0, + "total": 0.13 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_name[invalid-char]": { + "last_validated_date": "2025-10-29T09:20:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_name[too-long]": { + "last_validated_date": "2025-10-29T09:20:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.25, + "teardown": 0.0, + "total": 0.26 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_application_invalid_platform": { + "last_validated_date": "2025-10-29T09:20:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.25, + "teardown": 0.0, + "total": 0.25 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_create_platform_invalid_values": { + "last_validated_date": "2025-10-27T11:36:17+00:00", + "durations_in_seconds": { + "setup": 1.03, + "call": 1.57, + "teardown": 0.01, + "total": 2.61 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_get_missing_platform_application_attributes": { + "last_validated_date": "2025-10-27T12:48:51+00:00", + "durations_in_seconds": { + "setup": 1.05, + "call": 0.91, + "teardown": 0.0, + "total": 1.96 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_get_platform_application_attributes": { + "last_validated_date": "2025-10-29T09:20:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.62, + "teardown": 0.62, + "total": 1.24 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_get_platform_application_attributes_invalid_arn": { + "last_validated_date": "2025-10-29T09:20:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.31, + "teardown": 0.0, + "total": 0.31 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_get_platform_application_attributes_non_existing_app": { + "last_validated_date": "2025-10-29T09:20:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.0, + "total": 0.3 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_list_platform_applications": { + "last_validated_date": "2025-10-29T09:20:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.46, + "teardown": 0.44, + "total": 0.9 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes": { + "last_validated_date": "2025-10-29T09:21:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 31.68, + "teardown": 0.37, + "total": 32.05 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_invalid_arn": { + "last_validated_date": "2025-10-29T09:21:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.0, + "total": 0.3 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app": { + "last_validated_date": "2025-10-28T09:17:14+00:00", + "durations_in_seconds": { + "setup": 0.98, + "call": 1.27, + "teardown": 0.0, + "total": 2.25 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[dummy-args]": { + "last_validated_date": "2025-10-29T09:21:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.0, + "total": 0.3 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[missing-credential]": { + "last_validated_date": "2025-10-28T09:23:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.0, + "total": 0.3 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[missing-principal]": { + "last_validated_date": "2025-10-28T09:23:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.0, + "total": 0.3 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformApplicationCrud::test_set_platform_application_attributes_non_existing_app[no-args]": { + "last_validated_date": "2025-10-29T09:21:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.0, + "total": 0.3 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_disabled_endpoint": { + "last_validated_date": "2025-11-27T08:34:37+00:00", + "durations_in_seconds": { + "setup": 1.83, + "call": 48.48, + "teardown": 1.6, + "total": 51.91 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint": { + "last_validated_date": "2025-10-31T21:26:47+00:00", + "durations_in_seconds": { + "setup": 0.9, + "call": 1.63, + "teardown": 0.93, + "total": 3.46 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_custom_data": { + "last_validated_date": "2025-10-31T21:45:15+00:00", + "durations_in_seconds": { + "setup": 1.1, + "call": 1.44, + "teardown": 0.61, + "total": 3.15 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_double_custom_data": { + "last_validated_date": "2025-10-31T21:41:35+00:00", + "durations_in_seconds": { + "setup": 1.26, + "call": 1.82, + "teardown": 0.62, + "total": 3.7 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_idempotency": { + "last_validated_date": "2025-10-31T21:26:49+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 1.19, + "teardown": 0.92, + "total": 2.12 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_non_existent_app": { + "last_validated_date": "2025-10-31T21:26:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.01, + "total": 0.31 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Data_Too_Long]": { + "last_validated_date": "2025-10-31T21:28:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.6, + "teardown": 0.62, + "total": 1.22 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Invalid_Name_Credential]": { + "last_validated_date": "2025-10-31T21:28:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.61, + "teardown": 0.47, + "total": 1.08 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Invalid_Name_Generic]": { + "last_validated_date": "2025-10-31T21:28:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.3, + "teardown": 0.57, + "total": 0.87 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_create_platform_endpoint_with_invalid_attributes[Invalid_Name_Principal]": { + "last_validated_date": "2025-10-31T21:28:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.36, + "teardown": 0.56, + "total": 0.92 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_delete_endpoints_of_deleted_app": { + "last_validated_date": "2025-10-31T21:28:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.49, + "teardown": 0.14, + "total": 12.63 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_delete_platform_endpoint": { + "last_validated_date": "2025-10-31T21:27:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.61, + "teardown": 0.6, + "total": 13.21 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_delete_platform_endpoint_with_subscription": { + "last_validated_date": "2025-11-04T19:18:31+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 44.2, + "teardown": 1.07, + "total": 45.28 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_get_platform_endpoint_attributes": { + "last_validated_date": "2025-10-31T21:28:04+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.86, + "teardown": 0.92, + "total": 1.79 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_get_platform_endpoint_attributes_non_existent_endpoint": { + "last_validated_date": "2025-10-31T21:28:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.14, + "teardown": 0.0, + "total": 0.14 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_list_platform_endpoint": { + "last_validated_date": "2025-10-31T18:07:43+00:00", + "durations_in_seconds": { + "setup": 1.01, + "call": 1.9, + "teardown": 0.89, + "total": 3.8 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_list_platform_endpoints": { + "last_validated_date": "2025-10-31T21:26:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.89, + "teardown": 0.92, + "total": 1.82 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes": { + "last_validated_date": "2025-10-31T21:28:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.02, + "teardown": 0.92, + "total": 1.94 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_arn": { + "last_validated_date": "2025-10-31T20:20:11+00:00", + "durations_in_seconds": { + "setup": 1.05, + "call": 0.9, + "teardown": 0.01, + "total": 1.96 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Data_Too_Long]": { + "last_validated_date": "2025-10-31T21:31:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.91, + "teardown": 1.33, + "total": 2.24 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Empty]": { + "last_validated_date": "2025-10-31T21:31:03+00:00", + "durations_in_seconds": { + "setup": 1.41, + "call": 1.81, + "teardown": 0.92, + "total": 4.14 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Invalid_Name_Credential]": { + "last_validated_date": "2025-10-31T21:31:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.86, + "teardown": 0.6, + "total": 1.46 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Invalid_Name_Generic]": { + "last_validated_date": "2025-10-31T21:31:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.62, + "teardown": 0.93, + "total": 1.55 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[Invalid_Name_Principal]": { + "last_validated_date": "2025-10-31T21:31:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.92, + "teardown": 0.78, + "total": 1.7 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[attributes0]": { + "last_validated_date": "2025-10-31T20:48:00+00:00", + "durations_in_seconds": { + "setup": 1.11, + "call": 1.64, + "teardown": 0.9, + "total": 3.65 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[attributes1]": { + "last_validated_date": "2025-10-31T20:48:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.9, + "teardown": 0.79, + "total": 1.69 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[attributes2]": { + "last_validated_date": "2025-10-31T20:48:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.74, + "teardown": 0.84, + "total": 1.58 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[attributes3]": { + "last_validated_date": "2025-10-31T20:48:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.9, + "teardown": 0.63, + "total": 1.53 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_invalid_attributes[attributes4]": { + "last_validated_date": "2025-10-31T20:48:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.9, + "teardown": 0.7, + "total": 1.6 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_non_existent_app": { + "last_validated_date": "2025-10-31T20:29:24+00:00", + "durations_in_seconds": { + "setup": 0.8, + "call": 0.93, + "teardown": 0.01, + "total": 1.74 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_set_platform_endpoint_attributes_non_existent_endpoint": { + "last_validated_date": "2025-10-31T21:28:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.26, + "teardown": 0.0, + "total": 0.26 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_subscribe_platform_endpoint": { + "last_validated_date": "2025-10-31T17:43:57+00:00", + "durations_in_seconds": { + "setup": 0.82, + "call": 1.43, + "teardown": 0.92, + "total": 3.17 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_subscribe_platform_endpoint_idempotency": { + "last_validated_date": "2025-10-31T16:53:41+00:00", + "durations_in_seconds": { + "setup": 1.05, + "call": 2.22, + "teardown": 0.92, + "total": 4.19 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_subscribe_platform_endpoint_non_existent_app": { + "last_validated_date": "2025-10-31T17:44:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.24, + "teardown": 0.0, + "total": 1.24 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_subscribe_platform_endpoint_wrong_application_arn[invalid_appname_arn]": { + "last_validated_date": "2025-10-31T17:29:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.29, + "teardown": 0.0, + "total": 0.29 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpointCrud::test_subscribe_platform_endpoint_wrong_application_arn[invalid_format_arn]": { + "last_validated_date": "2025-10-31T17:29:22+00:00", + "durations_in_seconds": { + "setup": 1.14, + "call": 0.73, + "teardown": 0.01, + "total": 1.88 + } + }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_empty_sns_message": { - "last_validated_date": "2023-08-24T20:31:48+00:00" + "last_validated_date": "2025-11-19T12:41:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.65, + "teardown": 0.59, + "total": 2.24 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_message_structure_json_exc": { - "last_validated_date": "2023-08-24T20:31:50+00:00" + "last_validated_date": "2025-11-19T12:41:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.91, + "teardown": 0.22, + "total": 1.13 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_batch_invalid_entry_id": { + "last_validated_date": "2025-12-23T17:39:15+00:00", + "durations_in_seconds": { + "setup": 5.45, + "call": 1.98, + "teardown": 0.16, + "total": 7.59 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_batch_too_long_message": { - "last_validated_date": "2024-09-04T00:48:01+00:00" + "last_validated_date": "2025-11-19T12:41:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.81, + "teardown": 0.21, + "total": 1.02 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_by_path_parameters": { - "last_validated_date": "2023-08-24T20:31:40+00:00" + "last_validated_date": "2025-11-19T12:39:58+00:00", + "durations_in_seconds": { + "setup": 0.85, + "call": 3.38, + "teardown": 0.61, + "total": 4.84 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_before_subscribe_topic": { - "last_validated_date": "2023-08-24T20:31:44+00:00" + "last_validated_date": "2025-11-19T12:40:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.9, + "teardown": 0.61, + "total": 2.51 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_by_target_arn": { - "last_validated_date": "2023-08-24T20:31:42+00:00" + "last_validated_date": "2025-11-19T12:40:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.04, + "teardown": 0.65, + "total": 2.69 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_no_confirm_subscription": { + "last_validated_date": "2026-01-30T08:41:34+00:00", + "durations_in_seconds": { + "setup": 1.18, + "call": 1.63, + "teardown": 0.61, + "total": 3.42 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_non_existent_target": { - "last_validated_date": "2023-08-24T20:31:46+00:00" + "last_validated_date": "2025-11-19T12:40:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.37, + "teardown": 0.21, + "total": 0.58 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_too_long_message": { - "last_validated_date": "2024-09-04T00:38:06+00:00" + "last_validated_date": "2025-11-19T12:41:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.29, + "teardown": 0.22, + "total": 1.51 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_verify_signature": { "last_validated_date": "2024-01-04T17:22:57+00:00" @@ -39,61 +636,478 @@ "last_validated_date": "2024-01-04T17:39:19+00:00" }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_with_empty_subject": { - "last_validated_date": "2023-08-24T20:31:46+00:00" + "last_validated_date": "2025-11-19T12:41:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.61, + "teardown": 0.23, + "total": 0.84 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_wrong_arn_format": { - "last_validated_date": "2024-03-11T10:36:34+00:00" + "last_validated_date": "2025-11-19T12:40:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.5, + "teardown": 0.0, + "total": 0.5 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_topic_publish_another_region": { - "last_validated_date": "2023-11-17T17:14:28+00:00" + "last_validated_date": "2025-11-19T12:40:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 9.79, + "teardown": 0.85, + "total": 10.64 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_unknown_topic_publish": { - "last_validated_date": "2023-08-24T20:31:45+00:00" + "last_validated_date": "2025-11-19T12:40:48+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.61, + "teardown": 0.23, + "total": 0.85 + } }, "tests/aws/services/sns/test_sns.py::TestSNSPublishDelivery::test_delivery_lambda": { "last_validated_date": "2023-11-07T10:11:37+00:00" }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_get_sms_attributes_from_unmodified_region": { + "last_validated_date": "2025-12-02T12:58:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.64, + "teardown": 0.0, + "total": 2.64 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_is_phone_number_opted_out": { + "last_validated_date": "2025-12-01T19:33:36+00:00", + "durations_in_seconds": { + "setup": 0.74, + "call": 0.63, + "teardown": 0.0, + "total": 1.37 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_list_phone_numbers_opted_out": { + "last_validated_date": "2025-12-02T12:58:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.16, + "teardown": 0.0, + "total": 0.16 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_opt_in_invalid_number": { + "last_validated_date": "2026-01-13T09:38:15+00:00", + "durations_in_seconds": { + "setup": 1.02, + "call": 0.79, + "teardown": 0.03, + "total": 1.84 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_opt_in_non_existing_phone_number": { + "last_validated_date": "2025-12-02T12:58:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.14, + "teardown": 0.0, + "total": 0.14 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_opt_in_phone_number": { + "last_validated_date": "2025-12-01T19:44:54+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 1.02, + "teardown": 0.0, + "total": 1.96 + } + }, "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_wrong_phone_format": { - "last_validated_date": "2023-08-24T22:20:12+00:00" + "last_validated_date": "2025-12-02T12:58:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.87, + "teardown": 0.25, + "total": 1.12 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_get_sms_attributes": { + "last_validated_date": "2025-10-13T10:30:42+00:00", + "durations_in_seconds": { + "setup": 0.69, + "call": 1.27, + "teardown": 0.01, + "total": 1.97 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[InvalidAttributeName]": { + "last_validated_date": "2025-12-02T12:58:45+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 0.3, + "teardown": 0.0, + "total": 0.31 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[InvalidSMSType]": { + "last_validated_date": "2025-12-02T12:58:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.16, + "teardown": 0.0, + "total": 2.16 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[NoLetterID]": { + "last_validated_date": "2025-12-02T12:58:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.38, + "teardown": 0.0, + "total": 3.38 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[TooLongID]": { + "last_validated_date": "2025-12-02T12:58:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.04, + "teardown": 0.0, + "total": 3.04 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[attribute_key_value0]": { + "last_validated_date": "2025-10-13T10:41:10+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 1.05, + "teardown": 0.06, + "total": 1.95 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[attribute_key_value1]": { + "last_validated_date": "2025-10-13T10:41:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.69, + "teardown": 0.01, + "total": 2.7 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[attribute_key_value2]": { + "last_validated_date": "2025-10-13T10:41:15+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.86, + "teardown": 0.01, + "total": 2.87 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_set_invalid_sms_attributes[attribute_key_value3]": { + "last_validated_date": "2025-10-13T10:41:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.55, + "teardown": 0.01, + "total": 2.56 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_subscribe_sms_endpoint": { - "last_validated_date": "2024-05-14T19:34:11+00:00" + "last_validated_date": "2025-12-02T12:58:41+00:00", + "durations_in_seconds": { + "setup": 0.81, + "call": 2.06, + "teardown": 0.61, + "total": 3.48 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_create_subscriptions_with_attributes": { - "last_validated_date": "2024-03-29T19:44:42+00:00" + "last_validated_date": "2025-10-06T10:25:19+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.64, + "teardown": 0.82, + "total": 3.47 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions": { "last_validated_date": "2023-08-25T14:23:53+00:00" }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions_by_topic_pagination": { - "last_validated_date": "2024-05-16T10:31:56+00:00" + "last_validated_date": "2025-10-06T10:26:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 33.24, + "teardown": 31.21, + "total": 64.45 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_not_found_error_on_set_subscription_attributes": { - "last_validated_date": "2023-08-24T21:27:55+00:00" + "last_validated_date": "2025-10-06T10:25:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.99, + "teardown": 0.8, + "total": 3.79 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_sns_confirm_subscription_wrong_token": { - "last_validated_date": "2023-08-24T21:27:58+00:00" + "last_validated_date": "2025-10-06T10:25:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.19, + "teardown": 0.32, + "total": 1.51 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_idempotency": { - "last_validated_date": "2025-03-20T17:16:39+00:00" + "last_validated_date": "2025-10-06T10:26:46+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.54, + "teardown": 0.41, + "total": 3.96 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_protocol": { - "last_validated_date": "2023-08-24T21:27:50+00:00" + "last_validated_date": "2025-10-06T10:25:11+00:00", + "durations_in_seconds": { + "setup": 1.15, + "call": 1.16, + "teardown": 0.38, + "total": 2.69 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_topic": { - "last_validated_date": "2025-01-23T22:38:16+00:00" + "last_validated_date": "2025-10-06T10:26:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.06, + "teardown": 0.32, + "total": 1.38 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_from_non_existing_subscription": { - "last_validated_date": "2023-08-24T21:27:52+00:00" + "last_validated_date": "2025-10-06T10:25:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.15, + "teardown": 0.94, + "total": 4.09 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_idempotency": { - "last_validated_date": "2023-11-06T23:36:06+00:00" + "last_validated_date": "2025-10-06T10:26:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.03, + "teardown": 0.93, + "total": 2.96 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_wrong_arn_format": { - "last_validated_date": "2023-10-20T10:52:36+00:00" + "last_validated_date": "2025-10-06T10:26:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.77, + "teardown": 0.02, + "total": 1.79 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_validate_set_sub_attributes": { - "last_validated_date": "2024-03-29T19:30:23+00:00" + "last_validated_date": "2025-10-06T10:25:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.92, + "teardown": 0.88, + "total": 3.8 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_confirm_subscription": { + "last_validated_date": "2026-01-14T09:45:12+00:00", + "durations_in_seconds": { + "setup": 49.43, + "call": 9.88, + "teardown": 1.51, + "total": 60.82 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_creating_subscription": { + "last_validated_date": "2025-10-07T07:40:45+00:00", + "durations_in_seconds": { + "setup": 1.23, + "call": 1.41, + "teardown": 0.23, + "total": 2.87 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_creating_subscription_with_attributes": { + "last_validated_date": "2026-01-12T13:08:24+00:00", + "durations_in_seconds": { + "setup": 1.2, + "call": 2.79, + "teardown": 0.72, + "total": 4.71 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_delete_subscriptions_on_delete_topic": { + "last_validated_date": "2026-01-12T13:09:31+00:00", + "durations_in_seconds": { + "setup": 0.8, + "call": 3.09, + "teardown": 0.8, + "total": 4.69 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_double_subscription": { + "last_validated_date": "2025-10-06T11:39:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.1, + "teardown": 0.4, + "total": 1.5 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_get_subscription_attributes_error_not_exists": { + "last_validated_date": "2026-01-14T10:27:15+00:00", + "durations_in_seconds": { + "setup": 1.65, + "call": 25.2, + "teardown": 0.0, + "total": 26.85 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_getting_subscriptions_by_topic": { + "last_validated_date": "2025-10-07T08:29:43+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 3.6, + "teardown": 0.53, + "total": 5.2 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_list_opted_out": { + "last_validated_date": "2025-10-06T11:40:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.27, + "teardown": 0.02, + "total": 0.29 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_set_subscription_attributes": { + "last_validated_date": "2026-01-13T09:21:16+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 2.51, + "teardown": 0.63, + "total": 4.21 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_attributes": { + "last_validated_date": "2026-01-12T13:03:28+00:00", + "durations_in_seconds": { + "setup": 1.02, + "call": 2.72, + "teardown": 0.85, + "total": 4.59 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_bad_sms": { + "last_validated_date": "2025-10-06T11:39:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.61, + "teardown": 0.32, + "total": 0.93 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_invalid_filter_policy": { + "last_validated_date": "2026-01-13T09:26:43+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 1.06, + "teardown": 0.27, + "total": 2.29 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_invalid_sms": { + "last_validated_date": "2025-10-06T12:09:25+00:00", + "durations_in_seconds": { + "setup": 1.35, + "call": 2.17, + "teardown": 0.39, + "total": 3.91 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_sms": { + "last_validated_date": "2025-10-06T11:39:50+00:00", + "durations_in_seconds": { + "setup": 1.17, + "call": 1.41, + "teardown": 0.3, + "total": 2.88 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_sms_obscure_phone_number": { + "last_validated_date": "2025-10-06T11:39:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.6, + "teardown": 0.22, + "total": 0.82 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_sqs_queue_url": { + "last_validated_date": "2025-10-06T11:56:36+00:00", + "durations_in_seconds": { + "setup": 0.99, + "call": 2.53, + "teardown": 0.44, + "total": 3.96 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_unknown_sqs_queue": { + "last_validated_date": "2025-10-06T12:03:29+00:00", + "durations_in_seconds": { + "setup": 1.09, + "call": 1.85, + "teardown": 0.45, + "total": 3.39 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscribe_unknown_topic": { + "last_validated_date": "2025-10-06T11:43:45+00:00", + "durations_in_seconds": { + "setup": 0.97, + "call": 1.32, + "teardown": 0.04, + "total": 2.33 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_subscription_paging": { + "last_validated_date": "2025-10-07T14:33:46+00:00", + "durations_in_seconds": { + "setup": 0.78, + "call": 39.01, + "teardown": 0.2, + "total": 39.99 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrudV2::test_unsubscribe_from_deleted_topic": { + "last_validated_date": "2025-10-07T07:46:56+00:00", + "durations_in_seconds": { + "setup": 0.96, + "call": 2.59, + "teardown": 0.23, + "total": 3.78 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_http_subscription_response": { "last_validated_date": "2024-05-13T21:28:15+00:00" @@ -114,13 +1128,31 @@ "last_validated_date": "2025-01-24T18:51:32+00:00" }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[1]": { - "last_validated_date": "2024-01-04T18:31:41+00:00" + "last_validated_date": "2025-11-25T08:38:27+00:00", + "durations_in_seconds": { + "setup": 13.07, + "call": 16.84, + "teardown": 2.04, + "total": 31.95 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[2]": { - "last_validated_date": "2024-01-04T18:33:46+00:00" + "last_validated_date": "2025-11-25T08:38:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.87, + "teardown": 2.97, + "total": 18.84 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_python_lambda_subscribe_sns_topic": { - "last_validated_date": "2023-08-24T21:28:59+00:00" + "last_validated_date": "2025-10-06T11:08:49+00:00", + "durations_in_seconds": { + "setup": 13.12, + "call": 15.88, + "teardown": 3.04, + "total": 32.04 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_redrive_policy_lambda_subscription": { "last_validated_date": "2023-08-24T21:33:01+00:00" @@ -152,6 +1184,15 @@ "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_without_topic": { "last_validated_date": "2023-08-24T21:36:13+00:00" }, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_message_group_id": { + "last_validated_date": "2025-08-18T15:21:27+00:00", + "durations_in_seconds": { + "setup": 1.14, + "call": 1.56, + "teardown": 0.52, + "total": 3.22 + } + }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns": { "last_validated_date": "2023-08-24T21:36:09+00:00" }, @@ -180,31 +1221,79 @@ "last_validated_date": "2023-08-24T21:36:32+00:00" }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[False]": { - "last_validated_date": "2023-08-24T21:54:05+00:00" + "last_validated_date": "2025-08-18T16:18:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.09, + "teardown": 0.56, + "total": 5.65 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[True]": { - "last_validated_date": "2023-08-24T21:53:59+00:00" + "last_validated_date": "2025-08-18T16:18:41+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 5.91, + "teardown": 0.54, + "total": 6.97 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[False]": { - "last_validated_date": "2023-11-09T20:12:07+00:00" + "last_validated_date": "2025-08-18T16:25:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.09, + "teardown": 0.53, + "total": 3.62 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[True]": { - "last_validated_date": "2023-11-09T20:12:03+00:00" + "last_validated_date": "2025-08-18T16:25:29+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 3.81, + "teardown": 0.59, + "total": 4.9 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs_ordering": { "last_validated_date": "2025-02-19T01:29:14+00:00" }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[False]": { - "last_validated_date": "2023-11-09T20:10:33+00:00" + "last_validated_date": "2025-12-17T14:14:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 7.37, + "teardown": 0.92, + "total": 8.29 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[True]": { - "last_validated_date": "2023-11-09T20:10:27+00:00" + "last_validated_date": "2025-12-17T14:14:14+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 8.94, + "teardown": 0.91, + "total": 10.78 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[False]": { - "last_validated_date": "2023-08-24T21:37:59+00:00" + "last_validated_date": "2025-11-24T12:29:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 5.28, + "teardown": 1.03, + "total": 6.31 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[True]": { - "last_validated_date": "2023-08-24T21:37:54+00:00" + "last_validated_date": "2025-11-24T12:29:49+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 7.89, + "teardown": 1.13, + "total": 10.09 + } }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_deduplication_on_topic_level": { "last_validated_date": "2023-11-09T20:07:36+00:00" @@ -216,30 +1305,204 @@ "last_validated_date": "2023-08-24T21:38:14+00:00" }, "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_validations_for_fifo": { - "last_validated_date": "2023-08-24T21:55:19+00:00" + "last_validated_date": "2025-08-18T16:21:40+00:00", + "durations_in_seconds": { + "setup": 0.46, + "call": 3.5, + "teardown": 1.25, + "total": 5.21 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_check_idempotency": { - "last_validated_date": "2023-08-24T20:30:47+00:00" + "last_validated_date": "2025-10-13T07:07:09+00:00", + "durations_in_seconds": { + "setup": 1.26, + "call": 2.02, + "teardown": 1.08, + "total": 4.36 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_with_more_tags": { - "last_validated_date": "2023-08-24T20:30:46+00:00" + "last_validated_date": "2025-10-13T06:53:55+00:00", + "durations_in_seconds": { + "setup": 1.09, + "call": 1.57, + "teardown": 0.31, + "total": 2.97 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_after_delete_with_new_tags": { - "last_validated_date": "2023-08-24T20:30:48+00:00" + "last_validated_date": "2025-10-13T07:59:08+00:00", + "durations_in_seconds": { + "setup": 0.88, + "call": 1.32, + "teardown": 0.58, + "total": 2.78 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_test_arn": { - "last_validated_date": "2023-08-24T20:30:45+00:00" + "last_validated_date": "2025-09-29T09:32:56+00:00", + "durations_in_seconds": { + "setup": 1.07, + "call": 2.33, + "teardown": 0.2, + "total": 3.6 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_with_attributes": { - "last_validated_date": "2023-10-06T18:11:02+00:00" + "last_validated_date": "2025-09-25T07:41:34+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 2.31, + "teardown": 0.3, + "total": 3.48 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_delete_topic_idempotency": { - "last_validated_date": "2025-05-28T10:08:38+00:00" + "last_validated_date": "2025-09-29T09:46:20+00:00", + "durations_in_seconds": { + "setup": 0.95, + "call": 1.72, + "teardown": 0.15, + "total": 2.82 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags": { - "last_validated_date": "2023-08-24T20:30:44+00:00" + "last_validated_date": "2025-10-13T06:50:09+00:00", + "durations_in_seconds": { + "setup": 0.86, + "call": 2.28, + "teardown": 0.34, + "total": 3.48 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags_removed_after_deletion": { + "last_validated_date": "2026-02-16T05:26:44+00:00", + "durations_in_seconds": { + "setup": 4.17, + "call": 2.64, + "teardown": 0.72, + "total": 7.53 + } }, "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_topic_delivery_policy_crud": { "last_validated_date": "2024-10-03T21:46:17+00:00" + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_add_permission_errors": { + "last_validated_date": "2025-12-11T13:00:18+00:00", + "durations_in_seconds": { + "setup": 1.16, + "call": 2.15, + "teardown": 0.0, + "total": 3.31 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_different_attrs": { + "last_validated_date": "2026-02-06T19:09:15+00:00", + "durations_in_seconds": { + "setup": 0.45, + "call": 1.36, + "teardown": 0.5, + "total": 2.31 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_in_multiple_regions": { + "last_validated_date": "2025-10-08T08:54:10+00:00", + "durations_in_seconds": { + "setup": 1.29, + "call": 5.7, + "teardown": 0.01, + "total": 7.0 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_name_constraints": { + "last_validated_date": "2026-02-06T19:15:23+00:00", + "durations_in_seconds": { + "setup": 0.46, + "call": 1.71, + "teardown": 0.21, + "total": 2.38 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_create_topic_should_be_idempotent": { + "last_validated_date": "2025-09-29T10:40:50+00:00", + "durations_in_seconds": { + "setup": 1.14, + "call": 1.89, + "teardown": 0.47, + "total": 3.5 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_data_protection_policy_crud": { + "last_validated_date": "2025-12-18T07:54:08+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 1.82, + "teardown": 0.04, + "total": 2.7 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_delete_non_existent_topic": { + "last_validated_date": "2025-09-29T10:10:56+00:00", + "durations_in_seconds": { + "setup": 1.16, + "call": 1.4, + "teardown": 0.02, + "total": 2.58 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_list_topic_paging": { + "last_validated_date": "2025-09-29T11:39:42+00:00", + "durations_in_seconds": { + "setup": 1.14, + "call": 38.91, + "teardown": 37.48, + "total": 77.53 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_remove_permission_errors": { + "last_validated_date": "2025-12-11T13:05:40+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 1.36, + "teardown": 0.01, + "total": 2.31 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_add_multiple_permissions": { + "last_validated_date": "2025-12-11T12:53:37+00:00", + "durations_in_seconds": { + "setup": 1.03, + "call": 1.84, + "teardown": 0.3, + "total": 3.17 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_add_permission": { + "last_validated_date": "2025-12-11T11:43:04+00:00", + "durations_in_seconds": { + "setup": 0.91, + "call": 1.51, + "teardown": 0.3, + "total": 2.72 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_get_attributes_with_fifo_false": { + "last_validated_date": "2025-09-29T11:10:41+00:00", + "durations_in_seconds": { + "setup": 0.94, + "call": 1.77, + "teardown": 0.31, + "total": 3.02 + } + }, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrudV2::test_topic_remove_permission": { + "last_validated_date": "2025-12-11T12:02:07+00:00", + "durations_in_seconds": { + "setup": 1.15, + "call": 2.22, + "teardown": 0.3, + "total": 3.67 + } } } diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_all_properties.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_all_properties.yml new file mode 100644 index 0000000000000..53b2962a185d1 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_all_properties.yml @@ -0,0 +1,49 @@ +# +# Define an SQS FIFO Queue with all possible properties set to valid values. +# +Resources: + FifoQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: 'MyFifoQueue.fifo' + FifoQueue: true + ContentBasedDeduplication: true + DeduplicationScope: 'messageGroup' + FifoThroughputLimit: 'perMessageGroupId' + DelaySeconds: 13 + MaximumMessageSize: 3232 + MessageRetentionPeriod: 13425 + ReceiveMessageWaitTimeSeconds: 15 + VisibilityTimeout: 42 + RedrivePolicy: + deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn + maxReceiveCount: 7 + SqsManagedSseEnabled: false + KmsMasterKeyId: 'alias/aws/sqs' + KmsDataKeyReusePeriodSeconds: 200 + Tags: + - Key: 'Environment' + Value: 'Production' + - Key: 'Department' + Value: 'Finance' + + DeadLetterQueue: + Type: 'AWS::SQS::Queue' + Properties: + FifoQueue: true + RedriveAllowPolicy: + redrivePermission: 'allowAll' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Fifo Queue' + Value: !Ref FifoQueue + QueueArn: + Description: 'ARN of the SQS Fifo Queue' + Value: !GetAtt FifoQueue.Arn + DeadLetterQueueUrl: + Description: 'URL of the Dead Letter Queue' + Value: !Ref DeadLetterQueue + DeadLetterQueueArn: + Description: 'ARN of the Dead Letter Queue' + Value: !GetAtt DeadLetterQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_required_properties.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_required_properties.yml new file mode 100644 index 0000000000000..c60e20a744287 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_required_properties.yml @@ -0,0 +1,17 @@ +# +# Define an SQS FIFO Queue with only the required properties set. All other properties will +# take their default values. +# +Resources: + FifoQueue: + Type: 'AWS::SQS::Queue' + Properties: + FifoQueue: true + +Outputs: + QueueUrl: + Description: 'URL of the SQS Fifo Queue' + Value: !Ref FifoQueue + QueueArn: + Description: 'ARN of the SQS Fifo Queue' + Value: !GetAtt FifoQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_with_name_only.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_with_name_only.yml new file mode 100644 index 0000000000000..5551da2f3e834 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_fifo_queue_with_name_only.yml @@ -0,0 +1,17 @@ +# +# Define an SQS FIFO Queue with only the QueueName attribute set. +# +Resources: + FifoQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: 'MyFifoQueue.fifo' + FifoQueue: true + +Outputs: + QueueUrl: + Description: 'URL of the SQS Fifo Queue' + Value: !Ref FifoQueue + QueueArn: + Description: 'ARN of the SQS Fifo Queue' + Value: !GetAtt FifoQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties.yml new file mode 100644 index 0000000000000..2e23499ba1746 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties.yml @@ -0,0 +1,44 @@ +# +# A standard SQS queue with all properties set, including a dead-letter queue. All properties are set to valid values. +# +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: 'MyStandardQueueWithAllProperties' + DelaySeconds: 17 + MaximumMessageSize: 1234 + MessageRetentionPeriod: 13579 + ReceiveMessageWaitTimeSeconds: 18 + VisibilityTimeout: 45 + RedrivePolicy: + deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn + maxReceiveCount: 7 + SqsManagedSseEnabled: false + KmsMasterKeyId: 'alias/aws/sqs' + KmsDataKeyReusePeriodSeconds: 297 + Tags: + - Key: 'Environment' + Value: 'Production' + - Key: 'Department' + Value: 'Finance' + + DeadLetterQueue: + Type: 'AWS::SQS::Queue' + Properties: + RedriveAllowPolicy: + redrivePermission: 'allowAll' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn + DeadLetterQueueUrl: + Description: 'URL of the Dead Letter Queue' + Value: !Ref DeadLetterQueue + DeadLetterQueueArn: + Description: 'ARN of the Dead Letter Queue' + Value: !GetAtt DeadLetterQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_variant_1.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_variant_1.yml new file mode 100644 index 0000000000000..e42ffa651e223 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_variant_1.yml @@ -0,0 +1,43 @@ +# +# Similar to sqs_standard_queue_all_properties.yml in that it defines a standard SQS queue with all properties set, +# however, the property values are different. +# +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: 'MyStandardQueueWithAllProperties' + DelaySeconds: 16 + MaximumMessageSize: 4321 + MessageRetentionPeriod: 17539 + ReceiveMessageWaitTimeSeconds: 17 + VisibilityTimeout: 40 + RedrivePolicy: + deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn + maxReceiveCount: 3 + SqsManagedSseEnabled: true + Tags: + - Key: 'Environment' + Value: 'Staging' + - Key: 'Department' + Value: 'Sales' + + DeadLetterQueue: + Type: 'AWS::SQS::Queue' + Properties: + RedriveAllowPolicy: + redrivePermission: 'allowAll' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn + DeadLetterQueueUrl: + Description: 'URL of the Dead Letter Queue' + Value: !Ref DeadLetterQueue + DeadLetterQueueArn: + Description: 'ARN of the Dead Letter Queue' + Value: !GetAtt DeadLetterQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_variant_2.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_variant_2.yml new file mode 100644 index 0000000000000..6c1da7b64661d --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_variant_2.yml @@ -0,0 +1,43 @@ +# +# Similar to sqs_standard_queue_all_properties.yml in that it defines a standard SQS queue with all properties set, +# however, the property values are different, including the Queue name. +# +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: 'MyStandardQueueWithADifferentName' + DelaySeconds: 15 + MaximumMessageSize: 4444 + MessageRetentionPeriod: 17777 + ReceiveMessageWaitTimeSeconds: 14 + VisibilityTimeout: 35 + RedrivePolicy: + deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn + maxReceiveCount: 4 + SqsManagedSseEnabled: true + Tags: + - Key: 'Environment' + Value: 'Pre-Test' + - Key: 'Department' + Value: 'Marketing' + + DeadLetterQueue: + Type: 'AWS::SQS::Queue' + Properties: + RedriveAllowPolicy: + redrivePermission: 'allowAll' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn + DeadLetterQueueUrl: + Description: 'URL of the Dead Letter Queue' + Value: !Ref DeadLetterQueue + DeadLetterQueueArn: + Description: 'ARN of the Dead Letter Queue' + Value: !GetAtt DeadLetterQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_with_error.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_with_error.yml new file mode 100644 index 0000000000000..6b60a7bd16c3d --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_all_properties_with_error.yml @@ -0,0 +1,45 @@ +# +# A standard SQS queue with all properties set, including a dead-letter queue. The ReceiveMessageWaitTimeSeconds +# property is set to an invalid value (21) to simulate an error scenario. +# +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: 'MyStandardQueueWithAllProperties' + DelaySeconds: 31 + MaximumMessageSize: 1048576 + MessageRetentionPeriod: 13579 + ReceiveMessageWaitTimeSeconds: 21 + VisibilityTimeout: 45 + RedrivePolicy: + deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn + maxReceiveCount: 7 + SqsManagedSseEnabled: false + KmsMasterKeyId: 'alias/aws/sqs' + KmsDataKeyReusePeriodSeconds: 297 + Tags: + - Key: 'Environment' + Value: 'Production' + - Key: 'Department' + Value: 'Finance' + + DeadLetterQueue: + Type: 'AWS::SQS::Queue' + Properties: + RedriveAllowPolicy: + redrivePermission: 'allowAll' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn + DeadLetterQueueUrl: + Description: 'URL of the Dead Letter Queue' + Value: !Ref DeadLetterQueue + DeadLetterQueueArn: + Description: 'ARN of the Dead Letter Queue' + Value: !GetAtt DeadLetterQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_no_resource.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_no_resource.yml new file mode 100644 index 0000000000000..c9a18dc5f149a --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_no_resource.yml @@ -0,0 +1,18 @@ +# +# A template defining only a dead-letter SQS queue without any additional properties. This template does not +# contain the main queue, so applying this template should remove any existing main queue. +# +Resources: + DeadLetterQueue: + Type: 'AWS::SQS::Queue' + Properties: + RedriveAllowPolicy: + redrivePermission: 'allowAll' + +Outputs: + DeadLetterQueueUrl: + Description: 'URL of the Dead Letter Queue' + Value: !Ref DeadLetterQueue + DeadLetterQueueArn: + Description: 'ARN of the Dead Letter Queue' + Value: !GetAtt DeadLetterQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_required_properties.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_required_properties.yml new file mode 100644 index 0000000000000..f5f8ee5c1798e --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_required_properties.yml @@ -0,0 +1,14 @@ +# +# A standard SQS queue with no properties set, including the name which will be auto-generated. +# +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_some_properties.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_some_properties.yml new file mode 100644 index 0000000000000..f8be1c306d625 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_some_properties.yml @@ -0,0 +1,38 @@ +# +# A standard SQS queue with some properties set, but not all. The properties set have valid values, and the unset +# properties will take their default values. +# +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: 'MyStandardQueueWithAllProperties' + DelaySeconds: 17 + VisibilityTimeout: 43 + RedrivePolicy: + deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn + maxReceiveCount: 7 + SqsManagedSseEnabled: false + Tags: + - Key: 'Environment' + Value: 'Pre-Production' + + DeadLetterQueue: + Type: 'AWS::SQS::Queue' + Properties: + RedriveAllowPolicy: + redrivePermission: 'allowAll' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn + DeadLetterQueueUrl: + Description: 'URL of the Dead Letter Queue' + Value: !Ref DeadLetterQueue + DeadLetterQueueArn: + Description: 'ARN of the Dead Letter Queue' + Value: !GetAtt DeadLetterQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_some_properties_without_name.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_some_properties_without_name.yml new file mode 100644 index 0000000000000..bba8bd1bed745 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_standard_queue_some_properties_without_name.yml @@ -0,0 +1,21 @@ +# +# A standard SQS queue with some properties set, but not all. The properties set have valid values, and the unset +# properties will take their default values. This is similar to sqs_standard_queue_some_properties.yml, but without +# specifying a name for the queue, so it'll be auto-generated. +# +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + Properties: + VisibilityTimeout: 30 + MaximumMessageSize: 4321 + MessageRetentionPeriod: 17539 + ReceiveMessageWaitTimeSeconds: 17 + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_with_queueinlinepolicy.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queueinlinepolicy.yml new file mode 100644 index 0000000000000..0cac63290c300 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queueinlinepolicy.yml @@ -0,0 +1,27 @@ +# +# A standard SQS queue with an associated queue inline policy allowing a few actions. +# + +Resources: + MyQueue: + Type: AWS::SQS::Queue + + Policy: + Type: AWS::SQS::QueueInlinePolicy + Properties: + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: "*" + Action: + - sqs:SendMessage + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + Resource: !GetAtt MyQueue.Arn + Queue: !Ref MyQueue + +Outputs: + QueueUrl: + Value: + Ref: MyQueue diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_with_queueinlinepolicy_variant_1.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queueinlinepolicy_variant_1.yml new file mode 100644 index 0000000000000..2b2d438d4c900 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queueinlinepolicy_variant_1.yml @@ -0,0 +1,29 @@ +# +# A standard SQS queue with an associated queue inline policy allowing a few actions. +# + +Resources: + MyQueue: + Type: AWS::SQS::Queue + + Policy: + Type: AWS::SQS::QueueInlinePolicy + Properties: + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: "*" + Action: + - sqs:SendMessage + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:DeleteMessage + - sqs:ReceiveMessage + Resource: !GetAtt MyQueue.Arn + Queue: !Ref MyQueue + +Outputs: + QueueUrl: + Value: + Ref: MyQueue diff --git a/tests/aws/templates/sqs_with_queuepolicy.yaml b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy.yml similarity index 58% rename from tests/aws/templates/sqs_with_queuepolicy.yaml rename to tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy.yml index 0fabf18902847..2d0f49423fcaa 100644 --- a/tests/aws/templates/sqs_with_queuepolicy.yaml +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy.yml @@ -1,6 +1,14 @@ +# +# A standard SQS queue with an associated queue policy allowing a few actions. +# Resources: - Queue4A7E3555: + Queue1: Type: AWS::SQS::Queue + Queue2: + Type: AWS::SQS::Queue + Queue3: + Type: AWS::SQS::Queue + QueuePolicy25439813: Type: AWS::SQS::QueuePolicy Properties: @@ -14,12 +22,18 @@ Resources: Principal: "*" Resource: Fn::GetAtt: - - Queue4A7E3555 + - Queue1 - Arn Version: "2012-10-17" Queues: - - Ref: Queue4A7E3555 + - Ref: Queue1 Outputs: - QueueUrlOutput: + Queue1Url: + Value: + Ref: Queue1 + Queue2Url: + Value: + Ref: Queue2 + Queue3Url: Value: - Ref: Queue4A7E3555 + Ref: Queue3 diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy_for_3_queues.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy_for_3_queues.yml new file mode 100644 index 0000000000000..5092a307037a4 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy_for_3_queues.yml @@ -0,0 +1,43 @@ +# +# A standard SQS queue with an associated queue policy allowing a few actions. +# +Resources: + Queue1: + Type: AWS::SQS::Queue + Queue2: + Type: AWS::SQS::Queue + Queue3: + Type: AWS::SQS::Queue + + QueuePolicy25439813: + Type: AWS::SQS::QueuePolicy + Properties: + PolicyDocument: + Statement: + - Action: + - sqs:SendMessage + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:ChangeMessageVisibility + Effect: Allow + Principal: "*" + Resource: + Fn::GetAtt: + - Queue1 + - Arn + Version: "2012-10-17" + Queues: + - Ref: Queue1 + - Ref: Queue2 + - Ref: Queue3 + +Outputs: + Queue1Url: + Value: + Ref: Queue1 + Queue2Url: + Value: + Ref: Queue2 + Queue3Url: + Value: + Ref: Queue3 diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy_updated.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy_updated.yml new file mode 100644 index 0000000000000..f6b05ab1d72bb --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_with_queuepolicy_updated.yml @@ -0,0 +1,41 @@ +# +# A standard SQS queue with an associated queue policy allowing a slightly different set of actions compared +# to sqs_with_queuepolicy.yml. +# +Resources: + Queue1: + Type: AWS::SQS::Queue + Queue2: + Type: AWS::SQS::Queue + Queue3: + Type: AWS::SQS::Queue + + QueuePolicy25439813: + Type: AWS::SQS::QueuePolicy + Properties: + PolicyDocument: + Statement: + - Action: + - sqs:SendMessage + - sqs:GetQueueAttributes + - sqs:GetQueueUrl + - sqs:DeleteMessage + Effect: Allow + Principal: "*" + Resource: + Fn::GetAtt: + - Queue1 + - Arn + Version: "2012-10-17" + Queues: + - Ref: Queue1 +Outputs: + Queue1Url: + Value: + Ref: Queue1 + Queue2Url: + Value: + Ref: Queue2 + Queue3Url: + Value: + Ref: Queue3 diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_without_queueinlinepolicy.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_without_queueinlinepolicy.yml new file mode 100644 index 0000000000000..b9a7dba45edd9 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_without_queueinlinepolicy.yml @@ -0,0 +1,12 @@ +# +# A standard SQS queue without an associated queue (inline) policy. +# + +Resources: + MyQueue: + Type: AWS::SQS::Queue + +Outputs: + QueueUrl: + Value: + Ref: MyQueue diff --git a/tests/aws/services/sqs/resource_providers/templates/sqs_without_queuepolicy.yml b/tests/aws/services/sqs/resource_providers/templates/sqs_without_queuepolicy.yml new file mode 100644 index 0000000000000..0c2661333ec77 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/templates/sqs_without_queuepolicy.yml @@ -0,0 +1,21 @@ +# +# A set of three standard SQS queues without any associated queue policies. +# +Resources: + Queue1: + Type: AWS::SQS::Queue + Queue2: + Type: AWS::SQS::Queue + Queue3: + Type: AWS::SQS::Queue + +Outputs: + Queue1Url: + Value: + Ref: Queue1 + Queue2Url: + Value: + Ref: Queue2 + Queue3Url: + Value: + Ref: Queue3 diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py new file mode 100644 index 0000000000000..a9d63fc6efbc7 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py @@ -0,0 +1,293 @@ +import os + +import pytest +from botocore.exceptions import ClientError + +from localstack.testing.pytest import markers +from localstack.testing.pytest.fixtures import StackDeployError + + +def deploy_stack(deploy_cfn_template, template_filename, **kwargs): + """ + Helper function to deploy a CloudFormation stack using a template file. This exists to reduce + boilerplate in the test cases. + """ + template_path = os.path.join(os.path.dirname(__file__), "templates", template_filename) + return deploy_cfn_template(template_path=template_path, **kwargs) + + +@markers.aws.validated +def test_create_standard_queue_with_required_properties(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_standard_queue_required_properties.yml") + (queue_url, queue_arn) = (stack.outputs["QueueUrl"], stack.outputs["QueueArn"]) + + snapshot.match( + "attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]), + ) + snapshot.add_transformer(snapshot.transform.regex(queue_arn, "")) + + # auto-generated name check + assert "StandardQueue" in queue_url + assert not queue_url.endswith(".fifo") + + +@markers.aws.validated +def test_create_standard_queue_with_all_properties(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_standard_queue_all_properties.yml") + (queue_url, queue_arn, dlq_queue_url, dlq_queue_arn) = ( + stack.outputs["QueueUrl"], + stack.outputs["QueueArn"], + stack.outputs["DeadLetterQueueUrl"], + stack.outputs["DeadLetterQueueArn"], + ) + + snapshot.match( + "attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]), + ) + snapshot.match("tags", aws_client.sqs.list_queue_tags(QueueUrl=queue_url)) + snapshot.match( + "dlq_attributes", + aws_client.sqs.get_queue_attributes( + QueueUrl=dlq_queue_url, AttributeNames=["RedriveAllowPolicy"] + ), + ) + snapshot.add_transformer(snapshot.transform.regex(queue_arn, "")) + snapshot.add_transformer(snapshot.transform.regex(dlq_queue_arn, "")) + + +@markers.aws.validated +def test_create_fifo_queue_with_required_properties(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_fifo_queue_required_properties.yml") + (queue_url, queue_arn) = (stack.outputs["QueueUrl"], stack.outputs["QueueArn"]) + + snapshot.match( + "attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]), + ) + snapshot.add_transformer(snapshot.transform.regex(queue_arn, "")) + + # auto-generated name check + assert "FifoQueue" in queue_url + assert queue_url.endswith(".fifo") + + +@markers.aws.validated +def test_create_fifo_queue_with_all_properties(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_fifo_queue_all_properties.yml") + (queue_url, queue_arn, dlq_queue_url, dlq_queue_arn) = ( + stack.outputs["QueueUrl"], + stack.outputs["QueueArn"], + stack.outputs["DeadLetterQueueUrl"], + stack.outputs["DeadLetterQueueArn"], + ) + + snapshot.match( + "attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]), + ) + snapshot.match("tags", aws_client.sqs.list_queue_tags(QueueUrl=queue_url)) + snapshot.match( + "dlq_attributes", + aws_client.sqs.get_queue_attributes( + QueueUrl=dlq_queue_url, AttributeNames=["RedriveAllowPolicy"] + ), + ) + snapshot.add_transformer(snapshot.transform.regex(queue_arn, "")) + snapshot.add_transformer(snapshot.transform.regex(dlq_queue_arn, "")) + + +@markers.aws.validated +def test_update_standard_queue_modify_properties_in_place( + deploy_cfn_template, aws_client, snapshot +): + stack = deploy_stack(deploy_cfn_template, "sqs_standard_queue_all_properties.yml") + queue_url = stack.outputs["QueueUrl"] + + # Update the stack to add optional properties + updated_stack = deploy_stack( + deploy_cfn_template, + "sqs_standard_queue_all_properties_variant_1.yml", + is_update=True, + stack_name=stack.stack_name, + ) + updated_queue_url = updated_stack.outputs["QueueUrl"] + assert queue_url == updated_queue_url + + snapshot.match( + "updated_attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=updated_queue_url, AttributeNames=["All"]), + ) + snapshot.match("updated_tags", aws_client.sqs.list_queue_tags(QueueUrl=updated_queue_url)) + snapshot.add_transformer(snapshot.transform.regex(stack.outputs["QueueArn"], "")) + snapshot.add_transformer( + snapshot.transform.regex(stack.outputs["DeadLetterQueueArn"], "") + ) + + +@markers.aws.validated +def test_update_standard_queue_add_properties_with_replacement( + deploy_cfn_template, aws_client, snapshot +): + stack = deploy_stack(deploy_cfn_template, "sqs_standard_queue_all_properties.yml") + (queue_url, dlq_queue_url) = (stack.outputs["QueueUrl"], stack.outputs["DeadLetterQueueUrl"]) + + # Update the stack to rename the queue - this will cause the resource to be replaced, rather than + # updating the existing queue in place + updated_stack = deploy_stack( + deploy_cfn_template, + "sqs_standard_queue_all_properties_variant_2.yml", + is_update=True, + stack_name=stack.stack_name, + ) + (updated_queue_url, updated_dlq_queue_url) = ( + updated_stack.outputs["QueueUrl"], + updated_stack.outputs["DeadLetterQueueUrl"], + ) + assert queue_url != updated_queue_url + assert dlq_queue_url == updated_dlq_queue_url + + snapshot.match( + "updated_attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=updated_queue_url, AttributeNames=["All"]), + ) + snapshot.match("updated_tags", aws_client.sqs.list_queue_tags(QueueUrl=updated_queue_url)) + snapshot.add_transformer(snapshot.transform.key_value("deadLetterTargetArn", "")) + + # confirm that the original queue has been deleted + with pytest.raises(ClientError) as exc: + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]) + snapshot.match("error", exc.value.response) + + +@markers.aws.validated +def test_update_standard_queue_remove_some_properties(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_standard_queue_all_properties.yml") + queue_url = stack.outputs["QueueUrl"] + + # Update the stack with modified properties + updated_stack = deploy_stack( + deploy_cfn_template, + "sqs_standard_queue_some_properties.yml", + is_update=True, + stack_name=stack.stack_name, + ) + updated_queue_url = updated_stack.outputs["QueueUrl"] + assert queue_url == updated_queue_url + + snapshot.match( + "updated_attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=updated_queue_url, AttributeNames=["All"]), + ) + snapshot.match("updated_tags", aws_client.sqs.list_queue_tags(QueueUrl=updated_queue_url)) + snapshot.add_transformer(snapshot.transform.regex(stack.outputs["QueueArn"], "")) + snapshot.add_transformer(snapshot.transform.key_value("deadLetterTargetArn", "")) + + +@markers.aws.validated +def test_update_fifo_queue_remove_all_properties(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_fifo_queue_all_properties.yml") + queue_url = stack.outputs["QueueUrl"] + + # Update the stack to remove optional properties, reverting to only the required properties + updated_stack = deploy_stack( + deploy_cfn_template, + "sqs_fifo_queue_required_properties.yml", + is_update=True, + stack_name=stack.stack_name, + ) + + # URL will change, because queue name is auto-generated when QueueName is not specified + updated_queue_url = updated_stack.outputs["QueueUrl"] + assert queue_url != updated_queue_url + + snapshot.match( + "updated_attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=updated_queue_url, AttributeNames=["All"]), + ) + snapshot.match("updated_tags", aws_client.sqs.list_queue_tags(QueueUrl=updated_queue_url)) + snapshot.add_transformer( + snapshot.transform.regex(updated_stack.outputs["QueueArn"], "") + ) + + +@markers.aws.validated +def test_update_fifo_queue_remove_all_properties_except_queuename( + deploy_cfn_template, aws_client, snapshot +): + stack = deploy_stack(deploy_cfn_template, "sqs_fifo_queue_all_properties.yml") + queue_url = stack.outputs["QueueUrl"] + + # Update the stack to remove optional properties, reverting to only providing the QueueName property + updated_stack = deploy_stack( + deploy_cfn_template, + "sqs_fifo_queue_with_name_only.yml", + is_update=True, + stack_name=stack.stack_name, + ) + + # URL will remain the same, because queue name is explicitly specified + updated_queue_url = updated_stack.outputs["QueueUrl"] + assert queue_url == updated_queue_url + + snapshot.match( + "updated_attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=updated_queue_url, AttributeNames=["All"]), + ) + snapshot.match("updated_tags", aws_client.sqs.list_queue_tags(QueueUrl=updated_queue_url)) + snapshot.add_transformer( + snapshot.transform.regex(updated_stack.outputs["QueueArn"], "") + ) + + +@markers.aws.validated +def test_update_completely_remove_resource(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_standard_queue_all_properties.yml") + queue_url = stack.outputs["QueueUrl"] + + # Delete the queue by updating the stack to remove the resource + deploy_stack( + deploy_cfn_template, + "sqs_standard_queue_no_resource.yml", + is_update=True, + stack_name=stack.stack_name, + ) + + # expect an exception to be thrown because the resource is deleted + with pytest.raises(ClientError) as exc: + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]) + snapshot.match("error", exc.value.response) + + +@markers.aws.validated +def test_update_standard_queue_without_explicit_name(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_standard_queue_required_properties.yml") + queue_url = stack.outputs["QueueUrl"] + + # Update the stack to add optional properties, but expect the same queue name to be used. + updated_stack = deploy_stack( + deploy_cfn_template, + "sqs_standard_queue_some_properties_without_name.yml", + is_update=True, + stack_name=stack.stack_name, + ) + updated_queue_url = updated_stack.outputs["QueueUrl"] + assert queue_url == updated_queue_url + + snapshot.match( + "updated_attributes", + aws_client.sqs.get_queue_attributes(QueueUrl=updated_queue_url, AttributeNames=["All"]), + ) + snapshot.add_transformer(snapshot.transform.regex(stack.outputs["QueueArn"], "")) + + +@pytest.mark.skip(reason="SQS service in LocalStack does not correctly fail on invalid parameters") +@markers.aws.needs_fixing +def test_error_invalid_parameter(deploy_cfn_template, aws_client, snapshot): + with pytest.raises(StackDeployError) as exc: + deploy_stack( + deploy_cfn_template, + "sqs_standard_queue_all_properties_with_error.yml", + ) + snapshot.match("error", exc.value) diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.snapshot.json b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.snapshot.json new file mode 100644 index 0000000000000..9a5a413c13211 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.snapshot.json @@ -0,0 +1,404 @@ +{ + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_standard_queue_with_required_properties": { + "recorded-date": "18-12-2025, 22:04:55", + "recorded-content": { + "attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_standard_queue_with_all_properties": { + "recorded-date": "18-12-2025, 22:06:15", + "recorded-content": { + "attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "17", + "KmsDataKeyReusePeriodSeconds": "297", + "KmsMasterKeyId": "alias/aws/sqs", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1234", + "MessageRetentionPeriod": "13579", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "18", + "RedrivePolicy": { + "deadLetterTargetArn": "", + "maxReceiveCount": 7 + }, + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "45" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "tags": { + "Tags": { + "Department": "Finance", + "Environment": "Production" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "dlq_attributes": { + "Attributes": { + "RedriveAllowPolicy": { + "redrivePermission": "allowAll" + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_fifo_queue_with_required_properties": { + "recorded-date": "18-12-2025, 22:08:03", + "recorded-content": { + "attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "ContentBasedDeduplication": "false", + "CreatedTimestamp": "timestamp", + "DeduplicationScope": "queue", + "DelaySeconds": "0", + "FifoQueue": "true", + "FifoThroughputLimit": "perQueue", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_fifo_queue_with_all_properties": { + "recorded-date": "18-12-2025, 22:09:23", + "recorded-content": { + "attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "ContentBasedDeduplication": "true", + "CreatedTimestamp": "timestamp", + "DeduplicationScope": "messageGroup", + "DelaySeconds": "13", + "FifoQueue": "true", + "FifoThroughputLimit": "perMessageGroupId", + "KmsDataKeyReusePeriodSeconds": "200", + "KmsMasterKeyId": "alias/aws/sqs", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "3232", + "MessageRetentionPeriod": "13425", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "15", + "RedrivePolicy": { + "deadLetterTargetArn": "", + "maxReceiveCount": 7 + }, + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "42" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "tags": { + "Tags": { + "Department": "Finance", + "Environment": "Production" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "dlq_attributes": { + "Attributes": { + "RedriveAllowPolicy": { + "redrivePermission": "allowAll" + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_modify_properties_in_place": { + "recorded-date": "18-12-2025, 22:12:01", + "recorded-content": { + "updated_attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "16", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "4321", + "MessageRetentionPeriod": "17539", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "17", + "RedrivePolicy": { + "deadLetterTargetArn": "", + "maxReceiveCount": 3 + }, + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "40" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "updated_tags": { + "Tags": { + "Department": "Sales", + "Environment": "Staging" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_add_properties_with_replacement": { + "recorded-date": "18-12-2025, 22:15:10", + "recorded-content": { + "updated_attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "15", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "4444", + "MessageRetentionPeriod": "17777", + "QueueArn": "arn::sqs::111111111111:MyStandardQueueWithADifferentName", + "ReceiveMessageWaitTimeSeconds": "14", + "RedrivePolicy": { + "deadLetterTargetArn": "<:1>", + "maxReceiveCount": 4 + }, + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "35" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "updated_tags": { + "Tags": { + "Department": "Marketing", + "Environment": "Pre-Test" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "error": { + "Error": { + "Code": "AWS.SimpleQueueService.NonExistentQueue", + "Message": "The specified queue does not exist.", + "QueryErrorCode": "QueueDoesNotExist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_remove_some_properties": { + "recorded-date": "18-12-2025, 22:17:47", + "recorded-content": { + "updated_attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "17", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "262144", + "MessageRetentionPeriod": "345600", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "0", + "RedrivePolicy": { + "deadLetterTargetArn": "<:1>", + "maxReceiveCount": 7 + }, + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "43" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "updated_tags": { + "Tags": { + "Environment": "Pre-Production" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_completely_remove_resource": { + "recorded-date": "18-12-2025, 22:20:21", + "recorded-content": { + "error": { + "Error": { + "Code": "AWS.SimpleQueueService.NonExistentQueue", + "Message": "The specified queue does not exist.", + "QueryErrorCode": "QueueDoesNotExist", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_without_explicit_name": { + "recorded-date": "18-12-2025, 22:22:23", + "recorded-content": { + "updated_attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "4321", + "MessageRetentionPeriod": "17539", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "17", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_fifo_queue_remove_all_properties": { + "recorded-date": "14-01-2026, 16:34:50", + "recorded-content": { + "updated_attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "ContentBasedDeduplication": "false", + "CreatedTimestamp": "timestamp", + "DeduplicationScope": "queue", + "DelaySeconds": "0", + "FifoQueue": "true", + "FifoThroughputLimit": "perQueue", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "updated_tags": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_fifo_queue_remove_all_properties_except_queuename": { + "recorded-date": "14-01-2026, 16:37:34", + "recorded-content": { + "updated_attributes": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "ContentBasedDeduplication": "false", + "CreatedTimestamp": "timestamp", + "DeduplicationScope": "messageGroup", + "DelaySeconds": "0", + "FifoQueue": "true", + "FifoThroughputLimit": "perMessageGroupId", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "262144", + "MessageRetentionPeriod": "345600", + "QueueArn": "", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "updated_tags": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.validation.json b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.validation.json new file mode 100644 index 0000000000000..aababcc20195c --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.validation.json @@ -0,0 +1,101 @@ +{ + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_fifo_queue_with_all_properties": { + "last_validated_date": "2025-12-18T22:10:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 45.37, + "teardown": 66.13, + "total": 111.5 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_fifo_queue_with_required_properties": { + "last_validated_date": "2025-12-18T22:08:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 42.49, + "teardown": 34.51, + "total": 77.0 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_standard_queue_with_all_properties": { + "last_validated_date": "2025-12-18T22:07:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 45.72, + "teardown": 66.09, + "total": 111.81 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_create_standard_queue_with_required_properties": { + "last_validated_date": "2025-12-18T22:05:29+00:00", + "durations_in_seconds": { + "setup": 1.03, + "call": 43.56, + "teardown": 34.51, + "total": 79.1 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_completely_remove_resource": { + "last_validated_date": "2025-12-18T22:20:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 87.4, + "teardown": 34.94, + "total": 122.34 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_fifo_queue_remove_all_properties": { + "last_validated_date": "2026-01-14T16:35:25+00:00", + "durations_in_seconds": { + "setup": 0.97, + "call": 153.78, + "teardown": 35.22, + "total": 189.97 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_fifo_queue_remove_all_properties_except_queuename": { + "last_validated_date": "2026-01-14T16:38:10+00:00", + "durations_in_seconds": { + "setup": 1.11, + "call": 122.59, + "teardown": 35.1, + "total": 158.8 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_add_properties_with_replacement": { + "last_validated_date": "2025-12-18T22:16:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 122.66, + "teardown": 66.53, + "total": 189.19 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_modify_properties_in_place": { + "last_validated_date": "2025-12-18T22:13:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 91.09, + "teardown": 66.39, + "total": 157.48 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_remove_some_properties": { + "last_validated_date": "2025-12-18T22:18:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 91.05, + "teardown": 66.57, + "total": 157.62 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queue.py::test_update_standard_queue_without_explicit_name": { + "last_validated_date": "2025-12-18T22:22:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 86.74, + "teardown": 35.07, + "total": 121.81 + } + } +} diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py new file mode 100644 index 0000000000000..2a2b8f778bf3a --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py @@ -0,0 +1,70 @@ +import os + +from localstack.testing.pytest import markers + + +def deploy_stack(deploy_cfn_template, template_filename, **kwargs): + """ + Helper function to deploy a CloudFormation stack using a template file. This exists to reduce + boilerplate in the test cases. + """ + template_path = os.path.join(os.path.dirname(__file__), "templates", template_filename) + return deploy_cfn_template(template_path=template_path, **kwargs) + + +@markers.aws.validated +def test_create_sqs_with_inlinepolicy(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_with_queueinlinepolicy.yml") + queue_url = stack.outputs["QueueUrl"] + + snapshot.match( + "policy", aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]) + ) + snapshot.add_transformer(snapshot.transform.key_value("Resource")) + + +@markers.aws.validated +def test_update_sqs_with_inlinepolicy(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_with_queueinlinepolicy.yml") + queue_url = stack.outputs["QueueUrl"] + + snapshot.match( + "policy_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]), + ) + + deploy_stack( + deploy_cfn_template, + "sqs_with_queueinlinepolicy_variant_1.yml", + is_update=True, + stack_name=stack.stack_name, + ) + snapshot.match( + "policy_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]), + ) + snapshot.add_transformer(snapshot.transform.key_value("Resource")) + + +@markers.aws.validated +def test_update_sqs_remove_inlinepolicy(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_with_queueinlinepolicy.yml") + queue_url = stack.outputs["QueueUrl"] + + snapshot.match( + "policy_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]), + ) + + deploy_stack( + deploy_cfn_template, + "sqs_without_queueinlinepolicy.yml", + is_update=True, + stack_name=stack.stack_name, + ) + + snapshot.match( + "policy_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]), + ) + snapshot.add_transformer(snapshot.transform.key_value("Resource")) diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.snapshot.json b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.snapshot.json new file mode 100644 index 0000000000000..c2d2b26eb96ea --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.snapshot.json @@ -0,0 +1,117 @@ +{ + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py::test_create_sqs_with_inlinepolicy": { + "recorded-date": "18-12-2025, 22:24:13", + "recorded-content": { + "policy": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py::test_update_sqs_with_inlinepolicy": { + "recorded-date": "18-12-2025, 22:28:18", + "recorded-content": { + "policy_before": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "policy_after": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:ReceiveMessage" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py::test_update_sqs_remove_inlinepolicy": { + "recorded-date": "18-12-2025, 22:32:22", + "recorded-content": { + "policy_before": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "policy_after": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.validation.json b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.validation.json new file mode 100644 index 0000000000000..c5edb3324071d --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py::test_create_sqs_with_inlinepolicy": { + "last_validated_date": "2025-12-18T22:25:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 75.28, + "teardown": 95.53, + "total": 170.81 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py::test_update_sqs_remove_inlinepolicy": { + "last_validated_date": "2025-12-18T22:32:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 148.04, + "teardown": 35.02, + "total": 183.06 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queueinlinepolicy.py::test_update_sqs_with_inlinepolicy": { + "last_validated_date": "2025-12-18T22:29:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 149.31, + "teardown": 95.73, + "total": 245.04 + } + } +} diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py new file mode 100644 index 0000000000000..d89c274872123 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py @@ -0,0 +1,149 @@ +import os + +from localstack.testing.pytest import markers + + +def deploy_stack(deploy_cfn_template, template_filename, **kwargs): + """ + Helper function to deploy a CloudFormation stack using a template file. This exists to reduce + boilerplate in the test cases. + """ + template_path = os.path.join(os.path.dirname(__file__), "templates", template_filename) + return deploy_cfn_template(template_path=template_path, **kwargs) + + +@markers.aws.validated +def test_sqs_queue_policy(deploy_cfn_template, aws_client, snapshot): + result = deploy_stack(deploy_cfn_template, "sqs_with_queuepolicy.yml") + queue_url = result.outputs["Queue1Url"] + + snapshot.match( + "policy", aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]) + ) + snapshot.add_transformer(snapshot.transform.key_value("Resource")) + + +@markers.aws.validated +def test_update_sqs_queuepolicy(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_with_queuepolicy.yml") + policy = aws_client.sqs.get_queue_attributes( + QueueUrl=stack.outputs["Queue1Url"], AttributeNames=["Policy"] + ) + snapshot.match("policy1", policy["Attributes"]["Policy"]) + + updated_stack = deploy_stack( + deploy_cfn_template, + "sqs_with_queuepolicy_updated.yml", + is_update=True, + stack_name=stack.stack_name, + ) + + policy = aws_client.sqs.get_queue_attributes( + QueueUrl=updated_stack.outputs["Queue1Url"], AttributeNames=["Policy"] + ) + + snapshot.match("policy2", policy["Attributes"]["Policy"]) + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + +@markers.aws.validated +def test_update_add_two_additional_queues_to_policy(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_with_queuepolicy.yml") + queue_1_url = stack.outputs["Queue1Url"] + queue_2_url = stack.outputs["Queue2Url"] + queue_3_url = stack.outputs["Queue3Url"] + snapshot.match( + "queue_1_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_1_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_2_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_2_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_3_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_3_url, AttributeNames=["Policy"]), + ) + + deploy_stack( + deploy_cfn_template, + "sqs_with_queuepolicy_for_3_queues.yml", + is_update=True, + stack_name=stack.stack_name, + ) + snapshot.match( + "queue_1_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_1_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_2_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_2_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_3_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_3_url, AttributeNames=["Policy"]), + ) + snapshot.add_transformer(snapshot.transform.key_value("Resource")) + + +@markers.aws.validated +def test_update_remove_two_queues_from_policy(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_with_queuepolicy_for_3_queues.yml") + queue_1_url = stack.outputs["Queue1Url"] + queue_2_url = stack.outputs["Queue2Url"] + queue_3_url = stack.outputs["Queue3Url"] + snapshot.match( + "queue_1_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_1_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_2_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_2_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_3_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_3_url, AttributeNames=["Policy"]), + ) + + deploy_stack( + deploy_cfn_template, + "sqs_with_queuepolicy.yml", + is_update=True, + stack_name=stack.stack_name, + ) + snapshot.match( + "queue_1_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_1_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_2_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_2_url, AttributeNames=["Policy"]), + ) + snapshot.match( + "queue_3_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_3_url, AttributeNames=["Policy"]), + ) + snapshot.add_transformer(snapshot.transform.key_value("Resource")) + + +@markers.aws.validated +def test_update_to_remove_queuepolicy_from_template(deploy_cfn_template, aws_client, snapshot): + stack = deploy_stack(deploy_cfn_template, "sqs_with_queuepolicy.yml") + queue_url = stack.outputs["Queue1Url"] + snapshot.match( + "policy_before", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]), + ) + + deploy_stack( + deploy_cfn_template, + "sqs_without_queuepolicy.yml", + is_update=True, + stack_name=stack.stack_name, + ) + + snapshot.match( + "policy_after", + aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]), + ) + snapshot.add_transformer(snapshot.transform.key_value("Resource")) diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.snapshot.json b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.snapshot.json new file mode 100644 index 0000000000000..8d625e2742349 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.snapshot.json @@ -0,0 +1,324 @@ +{ + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_sqs_queue_policy": { + "recorded-date": "18-12-2025, 22:33:40", + "recorded-content": { + "policy": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_update_sqs_queuepolicy": { + "recorded-date": "18-12-2025, 22:36:17", + "recorded-content": { + "policy1": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "arn::sqs::111111111111:" + } + ] + }, + "policy2": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:DeleteMessage" + ], + "Resource": "arn::sqs::111111111111:" + } + ] + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_update_add_two_additional_queues_to_policy": { + "recorded-date": "18-12-2025, 22:38:51", + "recorded-content": { + "queue_1_before": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_2_before": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_3_before": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_1_after": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ChangeMessageVisibility" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_2_after": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ChangeMessageVisibility" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_3_after": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ChangeMessageVisibility" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_update_remove_two_queues_from_policy": { + "recorded-date": "18-12-2025, 22:41:27", + "recorded-content": { + "queue_1_before": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ChangeMessageVisibility" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_2_before": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ChangeMessageVisibility" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_3_before": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ChangeMessageVisibility" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_1_after": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_2_after": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "queue_3_after": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_update_to_remove_queuepolicy_from_template": { + "recorded-date": "18-12-2025, 22:43:59", + "recorded-content": { + "policy_before": { + "Attributes": { + "Policy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ], + "Resource": "" + } + ] + } + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "policy_after": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.validation.json b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.validation.json new file mode 100644 index 0000000000000..1c2e7d317b5a1 --- /dev/null +++ b/tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.validation.json @@ -0,0 +1,38 @@ +{ + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_sqs_queue_policy": { + "last_validated_date": "2025-12-18T22:34:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 43.41, + "teardown": 68.6, + "total": 112.01 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_update_add_two_additional_queues_to_policy": { + "last_validated_date": "2025-12-18T22:39:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 87.32, + "teardown": 67.71, + "total": 155.03 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_update_remove_two_queues_from_policy": { + "last_validated_date": "2025-12-18T22:42:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 87.92, + "teardown": 66.76, + "total": 154.68 + } + }, + "tests/aws/services/sqs/resource_providers/test_aws_sqs_queuepolicy.py::test_update_sqs_queuepolicy": { + "last_validated_date": "2025-12-18T22:37:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 87.64, + "teardown": 67.44, + "total": 155.08 + } + } +} diff --git a/tests/aws/services/sqs/test_sqs.py b/tests/aws/services/sqs/test_sqs.py index db4cf4180f6f3..1d97b003db4d0 100644 --- a/tests/aws/services/sqs/test_sqs.py +++ b/tests/aws/services/sqs/test_sqs.py @@ -4,7 +4,7 @@ import time from queue import Empty, Queue from threading import Timer -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING import pytest import requests @@ -82,6 +82,7 @@ def aws_sqs_client(aws_client, request: str) -> "SQSClient": class TestSqsProvider: + @markers.requires_in_process @markers.aws.only_localstack def test_get_queue_url_contains_localstack_host( self, @@ -212,6 +213,7 @@ def test_create_queue_and_get_attributes(self, sqs_queue, aws_sqs_client): assert int(attrs["VisibilityTimeout"]) == 30, "visibility timeout is not the default value" @markers.aws.validated + @markers.requires_in_process def test_create_queue_recently_deleted(self, sqs_create_queue, monkeypatch, aws_sqs_client): monkeypatch.setattr(config, "SQS_DELAY_RECENTLY_DELETED", True) @@ -227,6 +229,7 @@ def test_create_queue_recently_deleted(self, sqs_create_queue, monkeypatch, aws_ "You must wait 60 seconds after deleting a queue before you can create another with the same name." ) + @markers.requires_in_process @markers.aws.only_localstack def test_create_queue_recently_deleted_cache( self, @@ -544,14 +547,22 @@ def test_send_message_batch_with_oversized_contents_with_updated_maximum_message snapshot.match("send_oversized_message_batch", response) + @pytest.mark.parametrize( + "message_group_id", + [ + pytest.param("", id="empty"), + pytest.param("a" * 129, id="too_long"), + pytest.param("group 123", id="spaces"), + ], + ) @markers.aws.validated - def test_send_message_to_standard_queue_with_empty_message_group_id( - self, sqs_create_queue, aws_client, snapshot + def test_send_message_to_standard_queue_with_invalid_message_group_id( + self, sqs_queue, aws_client, snapshot, message_group_id ): - queue = sqs_create_queue() - with pytest.raises(ClientError) as e: - aws_client.sqs.send_message(QueueUrl=queue, MessageBody="message", MessageGroupId="") + aws_client.sqs.send_message( + QueueUrl=sqs_queue, MessageBody="message", MessageGroupId=message_group_id + ) snapshot.match("error-response", e.value.response) @markers.aws.validated @@ -786,9 +797,7 @@ def test_send_message_with_delay_0_works_for_fifo(self, sqs_create_queue, aws_sq assert message_sent_hash == message_received_hash @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Detail"]) - def test_message_deduplication_id_too_long(self, sqs_create_queue, aws_client, snapshot): - # see issue https://github.com/localstack/localstack/issues/6612 + def test_message_deduplication_id_success(self, sqs_create_queue, aws_client, snapshot): queue_name = f"queue-{short_uid()}.fifo" attributes = {"FifoQueue": "true"} queue_url = sqs_create_queue(QueueName=queue_name, Attributes=attributes) @@ -800,36 +809,28 @@ def test_message_deduplication_id_too_long(self, sqs_create_queue, aws_client, s MessageDeduplicationId="a" * 128, ) - with pytest.raises(ClientError) as e: - aws_client.sqs.send_message( - QueueUrl=queue_url, - MessageBody="Hello World!", - MessageGroupId="test", - MessageDeduplicationId="a" * 129, - ) - snapshot.match("error-response", e.value.response) - + @pytest.mark.parametrize( + "deduplication_id", + [ + pytest.param("", id="empty"), + pytest.param("a" * 129, id="too_long"), + pytest.param("group 123", id="spaces"), + ], + ) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Detail"]) - def test_message_group_id_too_long(self, sqs_create_queue, aws_client, snapshot): - # see issue https://github.com/localstack/localstack/issues/6612 + def test_message_deduplication_id_invalid( + self, sqs_create_queue, aws_client, snapshot, deduplication_id + ): queue_name = f"queue-{short_uid()}.fifo" attributes = {"FifoQueue": "true"} queue_url = sqs_create_queue(QueueName=queue_name, Attributes=attributes) - aws_client.sqs.send_message( - QueueUrl=queue_url, - MessageBody="Hello World!", - MessageGroupId="a" * 128, - MessageDeduplicationId="1", - ) - with pytest.raises(ClientError) as e: aws_client.sqs.send_message( QueueUrl=queue_url, MessageBody="Hello World!", - MessageGroupId="a" * 129, - MessageDeduplicationId="2", + MessageGroupId="test", + MessageDeduplicationId=deduplication_id, ) snapshot.match("error-response", e.value.response) @@ -1080,6 +1081,132 @@ def _assert(): retry(_assert) + @markers.aws.validated + def test_approximate_number_of_messages_not_visible(self, sqs_create_queue, aws_sqs_client): + # note that this test takes a bit longer when running on AWS, because we need to wait for propagation of + # queue attributes + queue_url = sqs_create_queue( + Attributes={ + "VisibilityTimeout": "60" if is_aws_cloud() else "5", + }, + ) + + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-1") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-2") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-3") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-4") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-5") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-6") + + # receive 1 message (from message group 1), now 5 messages should be visible + aws_sqs_client.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=1) + + def _assert_attributes_before_visibility_timeout(): + _result = aws_sqs_client.get_queue_attributes( + QueueUrl=queue_url, + AttributeNames=[ + "ApproximateNumberOfMessages", + "ApproximateNumberOfMessagesNotVisible", + "ApproximateNumberOfMessagesDelayed", + ], + ) + assert _result["Attributes"] == { + "ApproximateNumberOfMessages": "5", + "ApproximateNumberOfMessagesNotVisible": "1", + "ApproximateNumberOfMessagesDelayed": "0", + } + + retry(_assert_attributes_before_visibility_timeout, retries=60, sleep=1) + + def _assert_after_visibility_timeout(): + _result = aws_sqs_client.get_queue_attributes( + QueueUrl=queue_url, + AttributeNames=[ + "ApproximateNumberOfMessages", + "ApproximateNumberOfMessagesNotVisible", + "ApproximateNumberOfMessagesDelayed", + ], + ) + assert _result["Attributes"] == { + "ApproximateNumberOfMessages": "6", + "ApproximateNumberOfMessagesNotVisible": "0", + "ApproximateNumberOfMessagesDelayed": "0", + } + + retry(_assert_after_visibility_timeout, retries=60, sleep=1) + + @markers.aws.validated + def test_fifo_approximate_number_of_messages_not_visible( + self, sqs_create_queue, aws_sqs_client + ): + # note that this test takes a bit longer when running on AWS, because we need to wait for propagation of + # queue attributes + queue_url = sqs_create_queue( + QueueName=f"queue-{short_uid()}.fifo", + Attributes={ + "FifoQueue": "True", + "ContentBasedDeduplication": "True", + "VisibilityTimeout": "60" if is_aws_cloud() else "5", + }, + ) + + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-1", MessageGroupId="1") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-2", MessageGroupId="1") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-3", MessageGroupId="2") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-4", MessageGroupId="2") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-5", MessageGroupId="3") + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="message-6", MessageGroupId="3") + + # receive 2 messages (from message group 1 and group 2), now two groups are invisible (4 messages), but only 2 + # messages should be marked as truly invisible (the ones received) + message_1 = aws_sqs_client.receive_message( + QueueUrl=queue_url, + MaxNumberOfMessages=1, + MessageSystemAttributeNames=["MessageGroupId"], + ) + message_2 = aws_sqs_client.receive_message( + QueueUrl=queue_url, + MaxNumberOfMessages=1, + MessageSystemAttributeNames=["MessageGroupId"], + ) + + assert message_1["Messages"][0]["Attributes"]["MessageGroupId"] == "1" + assert message_2["Messages"][0]["Attributes"]["MessageGroupId"] == "2" + + def _assert_attributes_before_visibility_timeout(): + _result = aws_sqs_client.get_queue_attributes( + QueueUrl=queue_url, + AttributeNames=[ + "ApproximateNumberOfMessages", + "ApproximateNumberOfMessagesNotVisible", + "ApproximateNumberOfMessagesDelayed", + ], + ) + assert _result["Attributes"] == { + "ApproximateNumberOfMessages": "4", + "ApproximateNumberOfMessagesNotVisible": "2", + "ApproximateNumberOfMessagesDelayed": "0", + } + + retry(_assert_attributes_before_visibility_timeout, retries=60, sleep=1) + + def _assert_after_visibility_timeout(): + _result = aws_sqs_client.get_queue_attributes( + QueueUrl=queue_url, + AttributeNames=[ + "ApproximateNumberOfMessages", + "ApproximateNumberOfMessagesNotVisible", + "ApproximateNumberOfMessagesDelayed", + ], + ) + assert _result["Attributes"] == { + "ApproximateNumberOfMessages": "6", + "ApproximateNumberOfMessagesNotVisible": "0", + "ApproximateNumberOfMessagesDelayed": "0", + } + + retry(_assert_after_visibility_timeout, retries=60, sleep=1) + @markers.aws.validated def test_receive_after_visibility_timeout(self, sqs_create_queue, aws_sqs_client): queue_url = sqs_create_queue(Attributes={"VisibilityTimeout": "1"}) @@ -1151,6 +1278,40 @@ def test_fifo_delete_after_visibility_timeout(self, sqs_create_queue, aws_sqs_cl aws_sqs_client.delete_message(QueueUrl=queue_url, ReceiptHandle=receipt_handle) snapshot.match("delete_after_timeout_fifo", e.value.response) + @markers.aws.validated + def test_fifo_delete_after_visibility_timeout_extended( + self, sqs_create_queue, aws_sqs_client, snapshot + ): + timeout = 1 + queue_url = sqs_create_queue( + QueueName=f"test-{short_uid()}.fifo", + Attributes={ + "VisibilityTimeout": f"{timeout}", + "FifoQueue": "True", + "ContentBasedDeduplication": "True", + }, + ) + + aws_sqs_client.send_message(QueueUrl=queue_url, MessageBody="foobar", MessageGroupId="1") + # receive the message + initial_receive = aws_sqs_client.receive_message(QueueUrl=queue_url, WaitTimeSeconds=5) + snapshot.match("received_sqs_message", initial_receive) + receipt_handle = initial_receive["Messages"][0]["ReceiptHandle"] + + # extend the visibility timeout window + aws_sqs_client.change_message_visibility( + QueueUrl=queue_url, ReceiptHandle=receipt_handle, VisibilityTimeout=5 + ) + + # exceed the original visibility timeout window but not the extended one + time.sleep(timeout + 0.5) + aws_sqs_client.delete_message(QueueUrl=queue_url, ReceiptHandle=receipt_handle) + + snapshot.match( + "delete_after_timeout_fifo_extended", + aws_sqs_client.receive_message(QueueUrl=queue_url), + ) + @markers.aws.validated def test_receive_terminate_visibility_timeout(self, sqs_queue, aws_sqs_client): queue_url = sqs_queue @@ -1252,6 +1413,7 @@ def test_terminate_visibility_timeout_after_receive(self, sqs_create_queue, aws_ assert len(response["Messages"]) == 1 @markers.aws.validated + @markers.requires_in_process def test_message_retention(self, sqs_create_queue, aws_client, monkeypatch): monkeypatch.setattr(config, "SQS_ENABLE_MESSAGE_RETENTION_PERIOD", True) # in AWS, message retention is at least 60 seconds @@ -1275,6 +1437,7 @@ def test_message_retention(self, sqs_create_queue, aws_client, monkeypatch): assert not result.get("Messages") @markers.aws.validated + @markers.requires_in_process def test_message_retention_fifo(self, sqs_create_queue, aws_client, monkeypatch): monkeypatch.setattr(config, "SQS_ENABLE_MESSAGE_RETENTION_PERIOD", True) # in AWS, message retention is at least 60 seconds @@ -1304,6 +1467,7 @@ def test_message_retention_fifo(self, sqs_create_queue, aws_client, monkeypatch) assert not result.get("Messages") @markers.aws.validated + @markers.requires_in_process def test_message_retention_with_inflight(self, sqs_create_queue, aws_client, monkeypatch): # tests whether an inflight message is correctly removed after it expires monkeypatch.setattr(config, "SQS_ENABLE_MESSAGE_RETENTION_PERIOD", True) @@ -1480,6 +1644,7 @@ def collect_messages(): bodies = {message["Body"] for message in messages} assert bodies == {"0", "1", "2", "3", "4", "5", "6", "7", "8"} + @markers.requires_in_process @markers.aws.only_localstack def test_external_endpoint(self, monkeypatch, sqs_create_queue, aws_sqs_client): external_host = "external-host" @@ -1500,6 +1665,7 @@ def test_external_endpoint(self, monkeypatch, sqs_create_queue, aws_sqs_client): receive_result = aws_sqs_client.receive_message(QueueUrl=queue_url) assert receive_result["Messages"][0]["Body"] == message_body + @markers.requires_in_process @markers.aws.only_localstack def test_external_hostname_via_host_header(self, monkeypatch, sqs_create_queue, region_name): """test making a request with a different external hostname/port being returned""" @@ -1943,6 +2109,69 @@ def test_fifo_message_group_visibility_after_change_message_visibility( assert response["Messages"][1]["Body"] == "g1-m1" assert len(response["Messages"]) == 2 + @pytest.mark.parametrize( + "message_position", [0, 1, 2], ids=["0-visible", "1-visible", "2-visible"] + ) + @markers.aws.validated + def test_fifo_group_visibility_extends_with_change_message_visibility( + self, sqs_create_queue, aws_sqs_client, snapshot, message_position + ): + initial_visibility_timeout = 1 + extended_visibility_timeout = 4 + queue_name = f"test-queue-{short_uid()}.fifo" + queue_url = sqs_create_queue( + QueueName=queue_name, + Attributes={ + "FifoQueue": "true", + "VisibilityTimeout": f"{initial_visibility_timeout}", + }, + ) + + # Send 3 messages + aws_sqs_client.send_message( + QueueUrl=queue_url, + MessageBody="foo", + MessageGroupId="1", + MessageDeduplicationId="foo", + ) + aws_sqs_client.send_message( + QueueUrl=queue_url, + MessageBody="bar", + MessageGroupId="1", + MessageDeduplicationId="bar", + ) + aws_sqs_client.send_message( + QueueUrl=queue_url, + MessageBody="baz", + MessageGroupId="1", + MessageDeduplicationId="baz", + ) + + # Receive all messages + response = aws_sqs_client.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=5) + + # We should have received all 3 messages from the group + snapshot.match("receive_all_messages", response) + + receipt_handle = response["Messages"][message_position]["ReceiptHandle"] + # extend visibility timeout of one message + aws_sqs_client.change_message_visibility( + QueueUrl=queue_url, + ReceiptHandle=receipt_handle, + VisibilityTimeout=extended_visibility_timeout, + ) + + # Wait until the other 2 message become visible again, but the extended message is still invisible + time.sleep( + initial_visibility_timeout + + (extended_visibility_timeout - initial_visibility_timeout) / 2 + ) + + response = aws_sqs_client.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=5) + + # The extended message and all messages within the same group that come after it are invisible. + snapshot.match("some-messages-invisible", response) + @markers.aws.validated def test_fifo_message_group_visibility_after_delete(self, sqs_create_queue, aws_sqs_client): queue_url = sqs_create_queue( @@ -2119,6 +2348,42 @@ def test_fifo_queue_send_message_with_delay_on_queue_works( assert messages[1]["Body"] == "message-2" assert messages[2]["Body"] == "message-3" + @markers.aws.validated + def test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay( + self, sqs_create_queue, aws_sqs_client, snapshot + ): + delay_seconds = 2 + queue_url = sqs_create_queue( + QueueName=f"queue-{short_uid()}.fifo", + Attributes={ + "FifoQueue": "true", + "ContentBasedDeduplication": "true", + "DelaySeconds": str(delay_seconds), + }, + ) + + send_result = aws_sqs_client.send_message( + QueueUrl=queue_url, MessageBody="message-1", MessageGroupId="1", DelaySeconds=0 + ) + snapshot.match("send_message_result", send_result) + + response_initial_receive = aws_sqs_client.receive_message( + QueueUrl=queue_url, WaitTimeSeconds=1 + ) + snapshot.match("receive_message_initial_result", response_initial_receive) + assert response_initial_receive.get("Messages", []) == [] + + time.sleep(delay_seconds + 1) + + response_after_delay = aws_sqs_client.receive_message( + QueueUrl=queue_url, MaxNumberOfMessages=1 + ) + snapshot.match("receive_message_after_delay_result", response_after_delay) + messages = response_after_delay["Messages"] + assert len(messages) == 1 + + assert messages[0]["Body"] == "message-1" + @markers.aws.validated def test_fifo_message_attributes(self, sqs_create_queue, snapshot, aws_sqs_client): snapshot.add_transformer(snapshot.transform.sqs_api()) @@ -2455,7 +2720,7 @@ def test_publish_get_delete_message_batch(self, sqs_create_queue, aws_sqs_client ids_received = set() for i in range(message_count): ids_sent.add(successful[i]["MessageId"]) - ids_received.add((result_recv[i]["MessageId"])) + ids_received.add(result_recv[i]["MessageId"]) assert ids_sent == ids_received @@ -2967,9 +3232,7 @@ def test_dead_letter_queue_config(self, sqs_create_queue, region_name): dl_queue_url = sqs_create_queue(QueueName=dead_letter_queue_name) url_parts = dl_queue_url.split("/") - dl_target_arn = "arn:aws:sqs:{}:{}:{}".format( - region_name, url_parts[len(url_parts) - 2], url_parts[-1] - ) + dl_target_arn = f"arn:aws:sqs:{region_name}:{url_parts[len(url_parts) - 2]}:{url_parts[-1]}" conf = {"deadLetterTargetArn": dl_target_arn, "maxReceiveCount": 50} attributes = {"RedrivePolicy": json.dumps(conf)} @@ -3737,21 +4000,19 @@ def test_dead_letter_queue_execution_lambda_mapping_preserves_id( # FIXME: message id is now preserved, but test is broken # https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html queue_name = f"queue-{short_uid()}" - dead_letter_queue_name = "dl-queue-{}".format(short_uid()) + dead_letter_queue_name = f"dl-queue-{short_uid()}" dl_queue_url = sqs_create_queue(QueueName=dead_letter_queue_name) # create arn url_parts = dl_queue_url.split("/") - dl_target_arn = "arn:aws:sqs:{}:{}:{}".format( - region_name, url_parts[len(url_parts) - 2], url_parts[-1] - ) + dl_target_arn = f"arn:aws:sqs:{region_name}:{url_parts[len(url_parts) - 2]}:{url_parts[-1]}" policy = {"deadLetterTargetArn": dl_target_arn, "maxReceiveCount": 1} queue_url = sqs_create_queue( QueueName=queue_name, Attributes={"RedrivePolicy": json.dumps(policy)} ) - lambda_name = "lambda-{}".format(short_uid()) + lambda_name = f"lambda-{short_uid()}" create_lambda_function( func_name=lambda_name, handler_file=TEST_LAMBDA_PYTHON, @@ -3759,9 +4020,7 @@ def test_dead_letter_queue_execution_lambda_mapping_preserves_id( ) # create arn url_parts = queue_url.split("/") - queue_arn = "arn:aws:sqs:{}:{}:{}".format( - region_name, url_parts[len(url_parts) - 2], url_parts[-1] - ) + queue_arn = f"arn:aws:sqs:{region_name}:{url_parts[len(url_parts) - 2]}:{url_parts[-1]}" aws_client.lambda_.create_event_source_mapping( EventSourceArn=queue_arn, FunctionName=lambda_name ) @@ -3777,8 +4036,10 @@ def test_dead_letter_queue_execution_lambda_mapping_preserves_id( else: timeout = 15.0 assert poll_condition( - lambda: "Messages" - in aws_sqs_client.receive_message(QueueUrl=dl_queue_url, VisibilityTimeout=0), + lambda: ( + "Messages" + in aws_sqs_client.receive_message(QueueUrl=dl_queue_url, VisibilityTimeout=0) + ), timeout, 1.0, ) @@ -3896,6 +4157,7 @@ def test_purge_queue_clears_fifo_deduplication_cache(self, sqs_create_queue, aws @markers.aws.validated @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Detail"]) + @markers.requires_in_process def test_successive_purge_calls_fail( self, sqs_create_queue, monkeypatch, snapshot, aws_sqs_client, aws_client ): @@ -3929,6 +4191,7 @@ def test_remove_message_with_old_receipt_handle(self, sqs_create_queue, aws_sqs_ ) assert int(approx_nr_of_messages["Attributes"]["ApproximateNumberOfMessages"]) == 0 + @markers.requires_in_process @markers.aws.only_localstack def test_list_queues_multi_region_without_endpoint_strategy( self, aws_client_factory, cleanups, monkeypatch @@ -3991,6 +4254,7 @@ def test_list_queues_multi_region_with_endpoint_strategy_standard( assert queue2_url in region2_client.list_queues().get("QueueUrls", []) @markers.aws.validated + @markers.requires_in_process def test_list_queues_multi_region_with_endpoint_strategy_domain( self, aws_client_factory, cleanups, monkeypatch ): @@ -4386,6 +4650,29 @@ def test_sse_queue_attributes(self, sqs_create_queue, snapshot, aws_sqs_client): ) snapshot.match("sse_sqs_attributes", response) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Attributes.SqsManagedSseEnabled"]) + def test_set_queue_attributes_default_values(self, sqs_create_queue, snapshot, aws_sqs_client): + queue_url = sqs_create_queue() + response = aws_sqs_client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]) + snapshot.match("get-queue-attributes-initial-values", response) + + updated_attributes = { + "KmsMasterKeyId": "testKeyId", + "KmsDataKeyReusePeriodSeconds": "6000", + } + aws_sqs_client.set_queue_attributes(QueueUrl=queue_url, Attributes=updated_attributes) + response = aws_sqs_client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]) + snapshot.match("get-queue-attributes-after-update", response) + + default_attributes = { + "KmsMasterKeyId": "", + "KmsDataKeyReusePeriodSeconds": "300", + } + aws_sqs_client.set_queue_attributes(QueueUrl=queue_url, Attributes=default_attributes) + response = aws_sqs_client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]) + snapshot.match("get-queue-attributes-after-set-to-defaults", response) + @pytest.mark.skip(reason="validation currently not implemented in localstack") @markers.aws.validated def test_sse_kms_and_sqs_are_mutually_exclusive( @@ -4624,7 +4911,7 @@ def test_delete_message_with_deleted_receipt_handle(self, sqs_queue, aws_sqs_cli def _add_error_detail_transformer(self, snapshot): """Adds a transformer to ignore {"Error": {"Detail": None, ...}} entries in snapshot error responses""" - def _remove_error_details(snapshot_content: Dict, *args) -> Dict: + def _remove_error_details(snapshot_content: dict, *args) -> dict: for response in snapshot_content.values(): response.get("Error", {}).pop("Detail", None) return snapshot_content @@ -4741,6 +5028,18 @@ def test_non_existent_queue(self, aws_client, sqs_create_queue, sqs_queue_exists aws_client.sqs_query.get_queue_attributes(QueueUrl=queue_url) snapshot.match("queue-does-not-exist-query", e.value.response) + @markers.aws.validated + def test_fair_queue_with_message_group_id(self, sqs_queue, aws_sqs_client, snapshot): + send_result = aws_sqs_client.send_message( + QueueUrl=sqs_queue, MessageBody="message", MessageGroupId="test" + ) + snapshot.match("send_message", send_result) + + response = aws_sqs_client.receive_message( + QueueUrl=sqs_queue, WaitTimeSeconds=5, AttributeNames=["All"] + ) + snapshot.match("get-message-with-deduplication-id", response) + @pytest.fixture() def sqs_http_client(aws_http_client_factory, region_name): @@ -5063,7 +5362,10 @@ def test_get_queue_url_work_for_different_queue( assert response.status_code == 200 @markers.aws.validated - @pytest.mark.parametrize("strategy", ["standard", "domain", "path", "off"]) + @pytest.mark.parametrize( + "strategy", + ["standard", "domain", "path", pytest.param("off", marks=markers.requires_in_process)], + ) def test_endpoint_strategy_with_multi_region( self, strategy, @@ -5116,6 +5418,7 @@ def test_endpoint_strategy_with_multi_region( assert response.ok assert "foobar" in response.text + @markers.requires_in_process @markers.aws.only_localstack def test_queue_url_format_path_strategy( self, sqs_create_queue, account_id, region_name, monkeypatch diff --git a/tests/aws/services/sqs/test_sqs.snapshot.json b/tests/aws/services/sqs/test_sqs.snapshot.json index 744d4972c8d56..cbf0839eaf311 100644 --- a/tests/aws/services/sqs/test_sqs.snapshot.json +++ b/tests/aws/services/sqs/test_sqs.snapshot.json @@ -34,12 +34,12 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs]": { - "recorded-date": "30-04-2024, 13:32:59", + "recorded-date": "19-08-2025, 13:29:40", "recorded-content": { "send_oversized_message": { "Error": { "Code": "InvalidParameterValue", - "Message": "One or more parameters are invalid. Reason: Message must be shorter than 262144 bytes.", + "Message": "One or more parameters are invalid. Reason: Message must be shorter than 1048576 bytes.", "QueryErrorCode": "InvalidParameterValueException", "Type": "Sender" }, @@ -51,13 +51,13 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs_query]": { - "recorded-date": "30-04-2024, 13:33:01", + "recorded-date": "19-08-2025, 13:29:43", "recorded-content": { "send_oversized_message": { "Error": { "Code": "InvalidParameterValue", "Detail": null, - "Message": "One or more parameters are invalid. Reason: Message must be shorter than 262144 bytes.", + "Message": "One or more parameters are invalid. Reason: Message must be shorter than 1048576 bytes.", "Type": "Sender" }, "ResponseMetadata": { @@ -102,12 +102,12 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs]": { - "recorded-date": "30-04-2024, 13:33:06", + "recorded-date": "19-08-2025, 13:29:46", "recorded-content": { "send_oversized_message_batch": { "Error": { "Code": "AWS.SimpleQueueService.BatchRequestTooLong", - "Message": "Batch requests cannot be longer than 262144 bytes. You have sent 262145 bytes.", + "Message": "Batch requests cannot be longer than 1048576 bytes. You have sent 1048577 bytes.", "QueryErrorCode": "BatchRequestTooLong", "Type": "Sender" }, @@ -119,13 +119,13 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs_query]": { - "recorded-date": "30-04-2024, 13:33:08", + "recorded-date": "19-08-2025, 13:29:47", "recorded-content": { "send_oversized_message_batch": { "Error": { "Code": "AWS.SimpleQueueService.BatchRequestTooLong", "Detail": null, - "Message": "Batch requests cannot be longer than 262144 bytes. You have sent 262145 bytes.", + "Message": "Batch requests cannot be longer than 1048576 bytes. You have sent 1048577 bytes.", "Type": "Sender" }, "ResponseMetadata": { @@ -279,40 +279,6 @@ } } }, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_too_long": { - "recorded-date": "30-04-2024, 13:35:34", - "recorded-content": { - "error-response": { - "Error": { - "Code": "InvalidParameterValue", - "Message": "Value aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa for parameter MessageDeduplicationId is invalid. Reason: MessageDeduplicationId can only include alphanumeric and punctuation characters. 1 to 128 in length.", - "QueryErrorCode": "InvalidParameterValueException", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_group_id_too_long": { - "recorded-date": "30-04-2024, 13:35:35", - "recorded-content": { - "error-response": { - "Error": { - "Code": "InvalidParameterValue", - "Message": "Value aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa for parameter MessageGroupId is invalid. Reason: MessageGroupId can only include alphanumeric and punctuation characters. 1 to 128 in length.", - "QueryErrorCode": "InvalidParameterValueException", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs]": { "recorded-date": "30-04-2024, 13:33:18", "recorded-content": { @@ -372,7 +338,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs]": { - "recorded-date": "30-04-2024, 13:33:21", + "recorded-date": "19-08-2025, 13:29:48", "recorded-content": { "get_queue_attributes": { "Attributes": { @@ -382,7 +348,7 @@ "CreatedTimestamp": "timestamp", "DelaySeconds": "0", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "604800", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "10", @@ -417,7 +383,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs_query]": { - "recorded-date": "30-04-2024, 13:33:22", + "recorded-date": "19-08-2025, 13:29:49", "recorded-content": { "get_queue_attributes": { "Attributes": { @@ -427,7 +393,7 @@ "CreatedTimestamp": "timestamp", "DelaySeconds": "0", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "604800", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "10", @@ -724,7 +690,7 @@ "recorded-content": {} }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs]": { - "recorded-date": "30-04-2024, 13:47:39", + "recorded-date": "19-08-2025, 13:29:49", "recorded-content": { "before-update": { "Attributes": { @@ -738,7 +704,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perQueue", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -762,7 +728,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perQueue", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -777,7 +743,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs_query]": { - "recorded-date": "30-04-2024, 13:47:41", + "recorded-date": "19-08-2025, 13:29:50", "recorded-content": { "before-update": { "Attributes": { @@ -791,7 +757,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perQueue", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -815,7 +781,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perQueue", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -974,23 +940,6 @@ } } }, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_empty_message_group_id": { - "recorded-date": "08-11-2024, 12:04:39", - "recorded-content": { - "error-response": { - "Error": { - "Code": "InvalidParameterValue", - "Message": "Value for parameter MessageGroupId is invalid. Reason: The request include parameter that is not valid for this queue type.", - "QueryErrorCode": "InvalidParameterValueException", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs_query]": { "recorded-date": "30-04-2024, 13:33:45", "recorded-content": { @@ -1869,7 +1818,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs]": { - "recorded-date": "24-05-2024, 09:41:13", + "recorded-date": "19-08-2025, 13:29:53", "recorded-content": { "same-dedup-different-grp-1": { "Messages": [ @@ -1903,7 +1852,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perMessageGroupId", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -1950,7 +1899,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs_query]": { - "recorded-date": "24-05-2024, 09:41:19", + "recorded-date": "19-08-2025, 13:29:55", "recorded-content": { "same-dedup-different-grp-1": { "Messages": [ @@ -1984,7 +1933,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perMessageGroupId", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -2031,7 +1980,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs]": { - "recorded-date": "30-04-2024, 13:52:03", + "recorded-date": "19-08-2025, 13:29:57", "recorded-content": { "same-dedup-different-grp-1": { "Messages": [ @@ -2073,7 +2022,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perQueue", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -2102,7 +2051,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs_query]": { - "recorded-date": "30-04-2024, 13:52:06", + "recorded-date": "19-08-2025, 13:29:59", "recorded-content": { "same-dedup-different-grp-1": { "Messages": [ @@ -2144,7 +2093,7 @@ "FifoQueue": "true", "FifoThroughputLimit": "perQueue", "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", + "MaximumMessageSize": "1048576", "MessageRetentionPeriod": "345600", "QueueArn": "arn::sqs::111111111111:", "ReceiveMessageWaitTimeSeconds": "0", @@ -3808,7 +3757,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs]": { - "recorded-date": "05-03-2025, 19:25:09", + "recorded-date": "22-09-2025, 16:21:08", "recorded-content": { "send_empty_message": { "Error": { @@ -3825,7 +3774,7 @@ } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs_query]": { - "recorded-date": "05-03-2025, 19:25:09", + "recorded-date": "22-09-2025, 16:21:09", "recorded-content": { "send_empty_message": { "Error": { @@ -4012,5 +3961,979 @@ "ReceiptHandle": "" } } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs]": { + "recorded-date": "18-08-2025, 15:27:03", + "recorded-content": { + "send_message": { + "MD5OfMessageBody": "78e731027d8fd50ed642340b7c9a63b3", + "MessageId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-message-with-deduplication-id": { + "Messages": [ + { + "Attributes": { + "ApproximateFirstReceiveTimestamp": "timestamp", + "ApproximateReceiveCount": "1", + "MessageGroupId": "test", + "SenderId": "", + "SentTimestamp": "timestamp" + }, + "Body": "message", + "MD5OfBody": "78e731027d8fd50ed642340b7c9a63b3", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs_query]": { + "recorded-date": "18-08-2025, 15:27:04", + "recorded-content": { + "send_message": { + "MD5OfMessageBody": "78e731027d8fd50ed642340b7c9a63b3", + "MessageId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-message-with-deduplication-id": { + "Messages": [ + { + "Attributes": { + "ApproximateFirstReceiveTimestamp": "timestamp", + "ApproximateReceiveCount": "1", + "MessageGroupId": "test", + "SenderId": "", + "SentTimestamp": "timestamp" + }, + "Body": "message", + "MD5OfBody": "78e731027d8fd50ed642340b7c9a63b3", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[empty]": { + "recorded-date": "30-07-2025, 10:01:38", + "recorded-content": { + "error-response": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Value for parameter MessageGroupId is invalid. Reason: MessageGroupId can only include alphanumeric and punctuation characters. 1 to 128 in length.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[too_long]": { + "recorded-date": "30-07-2025, 10:01:39", + "recorded-content": { + "error-response": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Value aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa for parameter MessageGroupId is invalid. Reason: MessageGroupId can only include alphanumeric and punctuation characters. 1 to 128 in length.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[spaces]": { + "recorded-date": "30-07-2025, 10:01:39", + "recorded-content": { + "error-response": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Value group 123 for parameter MessageGroupId is invalid. Reason: MessageGroupId can only include alphanumeric and punctuation characters. 1 to 128 in length.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[empty]": { + "recorded-date": "30-07-2025, 10:25:50", + "recorded-content": { + "error-response": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Value for parameter MessageDeduplicationId is invalid. Reason: MessageDeduplicationId can only include alphanumeric and punctuation characters. 1 to 128 in length.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[too_long]": { + "recorded-date": "30-07-2025, 10:25:51", + "recorded-content": { + "error-response": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Value aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa for parameter MessageDeduplicationId is invalid. Reason: MessageDeduplicationId can only include alphanumeric and punctuation characters. 1 to 128 in length.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[spaces]": { + "recorded-date": "30-07-2025, 10:25:51", + "recorded-content": { + "error-response": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Value group 123 for parameter MessageDeduplicationId is invalid. Reason: MessageDeduplicationId can only include alphanumeric and punctuation characters. 1 to 128 in length.", + "QueryErrorCode": "InvalidParameterValueException", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_success": { + "recorded-date": "30-07-2025, 10:26:48", + "recorded-content": {} + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs]": { + "recorded-date": "31-07-2025, 09:53:01", + "recorded-content": { + "send_message_result": { + "MD5OfMessageBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "SequenceNumber": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_initial_result": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_after_delay_result": { + "Messages": [ + { + "Body": "message-1", + "MD5OfBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs_query]": { + "recorded-date": "31-07-2025, 09:53:08", + "recorded-content": { + "send_message_result": { + "MD5OfMessageBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "SequenceNumber": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_initial_result": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_after_delay_result": { + "Messages": [ + { + "Body": "message-1", + "MD5OfBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_not_visible[sqs]": { + "recorded-date": "25-09-2025, 21:46:19", + "recorded-content": {} + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_not_visible[sqs_query]": { + "recorded-date": "25-09-2025, 21:47:19", + "recorded-content": {} + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approximate_number_of_messages_not_visible[sqs]": { + "recorded-date": "25-09-2025, 21:53:42", + "recorded-content": {} + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approximate_number_of_messages_not_visible[sqs_query]": { + "recorded-date": "25-09-2025, 21:54:43", + "recorded-content": {} + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout_extended[sqs]": { + "recorded-date": "05-10-2025, 14:42:35", + "recorded-content": { + "received_sqs_message": { + "Messages": [ + { + "Body": "foobar", + "MD5OfBody": "3858f62230ac3c915f300c664312c63f", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_after_timeout_fifo_extended": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout_extended[sqs_query]": { + "recorded-date": "05-10-2025, 14:42:37", + "recorded-content": { + "received_sqs_message": { + "Messages": [ + { + "Body": "foobar", + "MD5OfBody": "3858f62230ac3c915f300c664312c63f", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete_after_timeout_fifo_extended": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-all-invisible]": { + "recorded-date": "19-11-2025, 10:45:24", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-1-invisible]": { + "recorded-date": "19-11-2025, 10:45:27", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-no-invisible]": { + "recorded-date": "19-11-2025, 10:45:30", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-all-invisible]": { + "recorded-date": "19-11-2025, 10:45:34", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-1-invisible]": { + "recorded-date": "19-11-2025, 10:45:37", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-no-invisible]": { + "recorded-date": "19-11-2025, 10:45:40", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-0-visible]": { + "recorded-date": "19-11-2025, 10:47:45", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-1-visible]": { + "recorded-date": "19-11-2025, 10:47:48", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-2-visible]": { + "recorded-date": "19-11-2025, 10:47:51", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-0-visible]": { + "recorded-date": "19-11-2025, 10:47:55", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-1-visible]": { + "recorded-date": "19-11-2025, 10:47:58", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-2-visible]": { + "recorded-date": "19-11-2025, 10:48:01", + "recorded-content": { + "receive_all_messages": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "baz", + "MD5OfBody": "73feffa4b7f6bb68e44cf984c85f6e88", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "some-messages-invisible": { + "Messages": [ + { + "Body": "foo", + "MD5OfBody": "acbd18db4cc2f85cedef654fccc4a4d8", + "MessageId": "", + "ReceiptHandle": "" + }, + { + "Body": "bar", + "MD5OfBody": "37b51d194a7513e45b56f6524f2d51f2", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_attributes_default_values[sqs]": { + "recorded-date": "10-12-2025, 18:38:55", + "recorded-content": { + "get-queue-attributes-initial-values": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-queue-attributes-after-update": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "KmsDataKeyReusePeriodSeconds": "6000", + "KmsMasterKeyId": "testKeyId", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-queue-attributes-after-set-to-defaults": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_attributes_default_values[sqs_query]": { + "recorded-date": "10-12-2025, 18:38:57", + "recorded-content": { + "get-queue-attributes-initial-values": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "true", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-queue-attributes-after-update": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "KmsDataKeyReusePeriodSeconds": "6000", + "KmsMasterKeyId": "testKeyId", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "get-queue-attributes-after-set-to-defaults": { + "Attributes": { + "ApproximateNumberOfMessages": "0", + "ApproximateNumberOfMessagesDelayed": "0", + "ApproximateNumberOfMessagesNotVisible": "0", + "CreatedTimestamp": "timestamp", + "DelaySeconds": "0", + "LastModifiedTimestamp": "timestamp", + "MaximumMessageSize": "1048576", + "MessageRetentionPeriod": "345600", + "QueueArn": "arn::sqs::111111111111:", + "ReceiveMessageWaitTimeSeconds": "0", + "SqsManagedSseEnabled": "false", + "VisibilityTimeout": "30" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/sqs/test_sqs.validation.json b/tests/aws/services/sqs/test_sqs.validation.json index c74eae7b6ad37..c86ed337b2b42 100644 --- a/tests/aws/services/sqs/test_sqs.validation.json +++ b/tests/aws/services/sqs/test_sqs.validation.json @@ -1,4 +1,22 @@ { + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_not_visible[sqs]": { + "last_validated_date": "2025-09-26T16:41:28+00:00", + "durations_in_seconds": { + "setup": 0.19, + "call": 61.13, + "teardown": 0.07, + "total": 61.39 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_not_visible[sqs_query]": { + "last_validated_date": "2025-09-26T16:42:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 61.63, + "teardown": 0.09, + "total": 61.72 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs]": { "last_validated_date": "2025-06-27T10:55:55+00:00", "durations_in_seconds": { @@ -30,10 +48,22 @@ "last_validated_date": "2024-04-30T13:50:41+00:00" }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs]": { - "last_validated_date": "2024-04-30T13:33:21+00:00" + "last_validated_date": "2025-08-19T13:29:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.66, + "teardown": 0.18, + "total": 0.84 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs_query]": { - "last_validated_date": "2024-04-30T13:33:22+00:00" + "last_validated_date": "2025-08-19T13:29:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.6, + "teardown": 0.18, + "total": 0.78 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs]": { "last_validated_date": "2024-04-30T13:33:13+00:00" @@ -116,17 +146,77 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs_query]": { "last_validated_date": "2024-04-30T13:49:31+00:00" }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs]": { + "last_validated_date": "2025-08-18T15:27:03+00:00", + "durations_in_seconds": { + "setup": 1.05, + "call": 0.28, + "teardown": 0.16, + "total": 1.49 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs_query]": { + "last_validated_date": "2025-08-18T15:27:04+00:00", + "durations_in_seconds": { + "setup": 0.14, + "call": 0.56, + "teardown": 0.16, + "total": 0.86 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approximate_number_of_messages_not_visible[sqs]": { + "last_validated_date": "2025-09-26T16:46:35+00:00", + "durations_in_seconds": { + "setup": 5.18, + "call": 62.17, + "teardown": 0.08, + "total": 67.43 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approximate_number_of_messages_not_visible[sqs_query]": { + "last_validated_date": "2025-09-26T16:47:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 62.34, + "teardown": 0.07, + "total": 62.41 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs]": { - "last_validated_date": "2024-05-24T10:00:47+00:00" + "last_validated_date": "2025-08-19T13:29:53+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.37, + "teardown": 0.18, + "total": 2.55 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs_query]": { - "last_validated_date": "2024-05-24T10:00:53+00:00" + "last_validated_date": "2025-08-19T13:29:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.35, + "teardown": 0.2, + "total": 2.55 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs]": { - "last_validated_date": "2024-04-30T13:52:02+00:00" + "last_validated_date": "2025-08-19T13:29:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.51, + "teardown": 0.18, + "total": 1.69 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs_query]": { - "last_validated_date": "2024-04-30T13:52:06+00:00" + "last_validated_date": "2025-08-19T13:29:59+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 1.5, + "teardown": 0.18, + "total": 1.68 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-False]": { "last_validated_date": "2024-04-30T13:34:04+00:00" @@ -158,12 +248,156 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs_query]": { "last_validated_date": "2025-03-28T13:37:13+00:00" }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout_extended[sqs]": { + "last_validated_date": "2025-10-05T14:42:35+00:00", + "durations_in_seconds": { + "setup": 0.23, + "call": 2.01, + "teardown": 0.09, + "total": 2.33 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout_extended[sqs_query]": { + "last_validated_date": "2025-10-05T14:42:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.0, + "teardown": 0.1, + "total": 2.1 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs]": { "last_validated_date": "2024-04-30T13:46:32+00:00" }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs_query]": { "last_validated_date": "2024-04-30T13:46:34+00:00" }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-0-visible]": { + "last_validated_date": "2025-11-19T10:47:45+00:00", + "durations_in_seconds": { + "setup": 1.24, + "call": 3.6, + "teardown": 0.24, + "total": 5.08 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-1-invisible]": { + "last_validated_date": "2025-11-19T10:45:27+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.76, + "teardown": 0.2, + "total": 2.96 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-1-visible]": { + "last_validated_date": "2025-11-19T10:47:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.87, + "teardown": 0.19, + "total": 3.06 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-2-visible]": { + "last_validated_date": "2025-11-19T10:47:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.77, + "teardown": 0.19, + "total": 2.96 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-all-invisible]": { + "last_validated_date": "2025-11-19T10:45:24+00:00", + "durations_in_seconds": { + "setup": 0.69, + "call": 3.4, + "teardown": 0.19, + "total": 4.28 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs-no-invisible]": { + "last_validated_date": "2025-11-19T10:45:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.97, + "teardown": 0.19, + "total": 3.16 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs]": { + "last_validated_date": "2025-11-18T13:34:41+00:00", + "durations_in_seconds": { + "setup": 0.9, + "call": 14.49, + "teardown": 0.28, + "total": 15.67 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-0-visible]": { + "last_validated_date": "2025-11-19T10:47:55+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.34, + "teardown": 0.24, + "total": 3.59 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-1-invisible]": { + "last_validated_date": "2025-11-19T10:45:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.68, + "teardown": 0.18, + "total": 2.86 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-1-visible]": { + "last_validated_date": "2025-11-19T10:47:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.88, + "teardown": 0.19, + "total": 3.07 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-2-visible]": { + "last_validated_date": "2025-11-19T10:48:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.88, + "teardown": 0.19, + "total": 3.07 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-all-invisible]": { + "last_validated_date": "2025-11-19T10:45:34+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 3.1, + "teardown": 0.18, + "total": 3.29 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query-no-invisible]": { + "last_validated_date": "2025-11-19T10:45:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.87, + "teardown": 0.19, + "total": 3.06 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_group_visibility_extends_with_change_message_visibility[sqs_query]": { + "last_validated_date": "2025-11-18T13:34:56+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 14.52, + "teardown": 0.2, + "total": 14.73 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs]": { "last_validated_date": "2024-04-30T13:34:27+00:00" }, @@ -176,17 +410,65 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs_query]": { "last_validated_date": "2024-04-30T13:36:57+00:00" }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs]": { + "last_validated_date": "2025-11-17T08:55:04+00:00", + "durations_in_seconds": { + "setup": 1.26, + "call": 4.38, + "teardown": 0.3, + "total": 5.94 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs_query]": { + "last_validated_date": "2025-11-17T08:55:09+00:00", + "durations_in_seconds": { + "setup": 0.02, + "call": 4.77, + "teardown": 0.3, + "total": 5.09 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs]": { "last_validated_date": "2024-04-30T13:33:33+00:00" }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs_query]": { "last_validated_date": "2024-04-30T13:33:34+00:00" }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs]": { + "last_validated_date": "2025-07-31T09:53:01+00:00", + "durations_in_seconds": { + "setup": 5.06, + "call": 6.23, + "teardown": 0.33, + "total": 11.62 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs_query]": { + "last_validated_date": "2025-07-31T09:53:08+00:00", + "durations_in_seconds": { + "setup": 0.03, + "call": 6.14, + "teardown": 0.34, + "total": 6.51 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs]": { - "last_validated_date": "2024-04-30T13:47:39+00:00" + "last_validated_date": "2025-08-19T13:29:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.6, + "teardown": 0.21, + "total": 0.81 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs_query]": { - "last_validated_date": "2024-04-30T13:47:41+00:00" + "last_validated_date": "2025-08-19T13:29:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.59, + "teardown": 0.19, + "total": 0.78 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs]": { "last_validated_date": "2024-04-30T13:33:40+00:00" @@ -209,8 +491,41 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_query_protocol": { "last_validated_date": "2024-04-29T06:07:04+00:00" }, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_too_long": { - "last_validated_date": "2024-04-30T13:35:34+00:00" + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[empty]": { + "last_validated_date": "2025-07-30T10:25:50+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 0.73, + "teardown": 0.18, + "total": 1.5 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[spaces]": { + "last_validated_date": "2025-07-30T10:25:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.38, + "teardown": 0.19, + "total": 0.57 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[too_long]": { + "last_validated_date": "2025-07-30T10:25:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.31, + "teardown": 0.19, + "total": 0.5 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_success": { + "last_validated_date": "2025-07-30T10:26:48+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 0.75, + "teardown": 0.2, + "total": 1.54 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_group_id_too_long": { "last_validated_date": "2024-04-30T13:35:35+00:00" @@ -288,10 +603,22 @@ "last_validated_date": "2024-04-30T13:40:12+00:00" }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs]": { - "last_validated_date": "2025-03-05T19:25:09+00:00" + "last_validated_date": "2025-09-22T16:21:08+00:00", + "durations_in_seconds": { + "setup": 1.03, + "call": 0.11, + "teardown": 0.17, + "total": 1.31 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs_query]": { - "last_validated_date": "2025-03-05T19:25:09+00:00" + "last_validated_date": "2025-09-22T16:21:09+00:00", + "durations_in_seconds": { + "setup": 0.15, + "call": 0.43, + "teardown": 0.15, + "total": 0.73 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs]": { "last_validated_date": "2024-04-30T13:40:08+00:00" @@ -303,10 +630,22 @@ "last_validated_date": "2024-04-30T13:40:12+00:00" }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs]": { - "last_validated_date": "2024-04-30T13:33:06+00:00" + "last_validated_date": "2025-08-19T13:29:46+00:00", + "durations_in_seconds": { + "setup": 0.17, + "call": 2.42, + "teardown": 0.21, + "total": 2.8 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs_query]": { - "last_validated_date": "2024-04-30T13:33:08+00:00" + "last_validated_date": "2025-08-19T13:29:47+00:00", + "durations_in_seconds": { + "setup": 0.17, + "call": 0.56, + "teardown": 0.18, + "total": 0.91 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs]": { "last_validated_date": "2024-04-30T13:33:09+00:00" @@ -314,8 +653,32 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs_query]": { "last_validated_date": "2024-04-30T13:33:10+00:00" }, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_empty_message_group_id": { - "last_validated_date": "2024-11-08T12:08:17+00:00" + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[empty]": { + "last_validated_date": "2025-07-30T10:01:38+00:00", + "durations_in_seconds": { + "setup": 1.71, + "call": 0.23, + "teardown": 0.26, + "total": 2.2 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[spaces]": { + "last_validated_date": "2025-07-30T10:01:39+00:00", + "durations_in_seconds": { + "setup": 0.24, + "call": 0.22, + "teardown": 0.27, + "total": 0.73 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[too_long]": { + "last_validated_date": "2025-07-30T10:01:39+00:00", + "durations_in_seconds": { + "setup": 0.24, + "call": 0.22, + "teardown": 0.26, + "total": 0.72 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs]": { "last_validated_date": "2024-04-30T13:33:48+00:00" @@ -342,10 +705,22 @@ "last_validated_date": "2024-04-30T13:33:04+00:00" }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs]": { - "last_validated_date": "2024-04-30T13:32:59+00:00" + "last_validated_date": "2025-08-19T13:29:40+00:00", + "durations_in_seconds": { + "setup": 1.49, + "call": 3.69, + "teardown": 0.18, + "total": 5.36 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs_query]": { - "last_validated_date": "2024-04-30T13:33:01+00:00" + "last_validated_date": "2025-08-19T13:29:43+00:00", + "durations_in_seconds": { + "setup": 0.18, + "call": 2.55, + "teardown": 0.18, + "total": 2.91 + } }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs]": { "last_validated_date": "2024-04-30T13:32:56+00:00" @@ -380,6 +755,24 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs_query]": { "last_validated_date": "2024-08-20T14:14:11+00:00" }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_attributes_default_values[sqs]": { + "last_validated_date": "2025-12-10T18:38:55+00:00", + "durations_in_seconds": { + "setup": 2.41, + "call": 2.34, + "teardown": 0.29, + "total": 5.04 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_attributes_default_values[sqs_query]": { + "last_validated_date": "2025-12-10T18:38:57+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 2.21, + "teardown": 0.3, + "total": 2.52 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs]": { "last_validated_date": "2024-05-14T22:23:46+00:00" }, diff --git a/tests/aws/services/sqs/test_sqs_backdoor.py b/tests/aws/services/sqs/test_sqs_developer_api.py similarity index 87% rename from tests/aws/services/sqs/test_sqs_backdoor.py rename to tests/aws/services/sqs/test_sqs_developer_api.py index 39669a61f51fd..582ddfe8e2a75 100644 --- a/tests/aws/services/sqs/test_sqs_backdoor.py +++ b/tests/aws/services/sqs/test_sqs_developer_api.py @@ -35,7 +35,7 @@ def _parse_attribute_map(json_message: dict) -> dict[str, str]: # @pytest.mark.usefixtures("openapi_validate") -class TestSqsDeveloperEndpoints: +class TestSqsDeveloperApi: @markers.aws.only_localstack @pytest.mark.parametrize("strategy", ["standard", "domain", "path"]) def test_list_messages_has_no_side_effects( @@ -235,6 +235,67 @@ def test_list_messages_with_invisible_messages( assert _parse_attribute_map(messages[1])["IsVisible"] == "true" assert _parse_attribute_map(messages[2])["IsVisible"] == "true" + @markers.aws.only_localstack + @pytest.mark.parametrize("strategy", ["standard", "domain", "path"]) + def test_fifo_list_messages_with_invisible_messages( + self, + sqs_create_queue, + aws_client, + monkeypatch, + strategy, + ): + monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) + + queue_url = sqs_create_queue( + QueueName=f"queue-{short_uid()}.fifo", + Attributes={ + "FifoQueue": "true", + "ContentBasedDeduplication": "true", + "VisibilityTimeout": "120", + }, + ) + aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody="message-1", MessageGroupId="1") + aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody="message-2", MessageGroupId="1") + aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody="message-3", MessageGroupId="2") + aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody="message-4", MessageGroupId="2") + + # check out a messages + aws_client.sqs.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=1) + + response = requests.get( + "http://localhost:4566/_aws/sqs/messages", + params={"QueueUrl": queue_url, "ShowInvisible": False}, + headers={"Accept": "application/json"}, + ) + doc = response.json() + messages = doc["ReceiveMessageResponse"]["ReceiveMessageResult"]["Message"] + assert len(messages) == 2 + assert messages[0]["Body"] == "message-3" + assert messages[1]["Body"] == "message-4" + + response = requests.get( + "http://localhost:4566/_aws/sqs/messages", + params={"QueueUrl": queue_url, "ShowInvisible": True}, + headers={"Accept": "application/json"}, + ) + doc = response.json() + messages: list[dict] = doc["ReceiveMessageResponse"]["ReceiveMessageResult"]["Message"] + assert len(messages) == 4 + # there are no clear sorting rules in this scenario (fifo queues, invisible, + the way messages are collected) + messages.sort(key=lambda k: k["Body"]) + assert messages[0]["Body"] == "message-1" + assert messages[1]["Body"] == "message-2" + assert messages[2]["Body"] == "message-3" + assert messages[3]["Body"] == "message-4" + + assert _parse_attribute_map(messages[0])["IsVisible"] == "false" + # so technically the message itself IS visible, but the message *group* is invisible. implementing that this + # shows "false" for message-2 requires a bit of rework in our current implementation, so i would consider this a + # fair limitation for now, given its subject to interpretation anyway. + assert _parse_attribute_map(messages[1])["IsVisible"] == "true" + assert _parse_attribute_map(messages[2])["IsVisible"] == "true" + assert _parse_attribute_map(messages[3])["IsVisible"] == "true" + @markers.aws.only_localstack @pytest.mark.parametrize("strategy", ["standard", "domain", "path"]) def test_list_messages_with_delayed_messages( diff --git a/tests/aws/services/ssm/test_ssm.py b/tests/aws/services/ssm/test_ssm.py index b9a7ff7ff79c5..5b101eef34ea2 100644 --- a/tests/aws/services/ssm/test_ssm.py +++ b/tests/aws/services/ssm/test_ssm.py @@ -1,6 +1,7 @@ import json import pytest +from botocore.exceptions import ClientError from localstack import config from localstack.testing.config import TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME @@ -55,7 +56,7 @@ def test_hierarchical_parameter(self, create_parameter, param_name_pattern, aws_ _assert(f"/{param_a}/b/c", f"/{param_a}/b/c", aws_client.ssm) pname = param_name_pattern.replace("", param_a) - with pytest.raises(Exception) as exc: + with pytest.raises(ClientError) as exc: _assert(pname, f"/{param_a}/b/c", aws_client.ssm) exc.match("ValidationException") exc.match("sub-paths divided by slash symbol") @@ -82,7 +83,7 @@ def test_get_secret_parameter(self, create_secret, aws_client): assert ":secretsmanager:" in source_result["ARN"] # negative test for https://github.com/localstack/localstack/issues/6551 - with pytest.raises(Exception): + with pytest.raises(ClientError): aws_client.ssm.get_parameter(Name=secret_name, WithDecryption=True) @markers.aws.validated @@ -176,11 +177,11 @@ def test_trigger_event_on_systems_manager_change( ): monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) - rule_name = "rule-{}".format(short_uid()) - target_id = "target-{}".format(short_uid()) + rule_name = f"rule-{short_uid()}" + target_id = f"target-{short_uid()}" # create queue - queue_name = "queue-{}".format(short_uid()) + queue_name = f"queue-{short_uid()}" queue_url = aws_client.sqs.create_queue(QueueName=queue_name)["QueueUrl"] queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) diff --git a/tests/aws/services/cloudformation/api/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/api/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/__init__.py diff --git a/tests/aws/services/cloudformation/engine/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mock_config_files/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/engine/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mock_config_files/__init__.py diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mock_config_files/lambda_sqs_integration.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mock_config_files/lambda_sqs_integration.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mock_config_files/lambda_sqs_integration.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mock_config_files/lambda_sqs_integration.json5 diff --git a/tests/aws/services/cloudformation/resource_providers/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/__init__.py diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/callback/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/ec2/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/callback/__init__.py diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/callback/task_failure.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/callback/task_failure.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/callback/task_failure.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/callback/task_failure.json5 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/callback/task_success_string_literal.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/callback/task_success_string_literal.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/callback/task_success_string_literal.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/callback/task_success_string_literal.json5 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/dynamodb/200_get_item.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/dynamodb/200_get_item.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/dynamodb/200_get_item.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/dynamodb/200_get_item.json5 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/dynamodb/200_put_item.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/dynamodb/200_put_item.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/dynamodb/200_put_item.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/dynamodb/200_put_item.json5 diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/dynamodb/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/dynamodb/__init__.py diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/events/200_put_events.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/events/200_put_events.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/events/200_put_events.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/events/200_put_events.json5 diff --git a/tests/aws/services/cloudformation/resource_providers/iam/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/events/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/events/__init__.py diff --git a/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_status_change_between_invocations.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_status_change_between_invocations.json5 new file mode 100644 index 0000000000000..c80c826e3a995 --- /dev/null +++ b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_status_change_between_invocations.json5 @@ -0,0 +1,18 @@ +{ + "0": { + "Return": { + "StatusCode": 200, + "Payload": { + "status": "running", + } + } + }, + "1": { + "Return": { + "StatusCode": 200, + "Payload": { + "status": "completed" + } + } + } +} diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/lambda/200_string_body.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_string_body.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/lambda/200_string_body.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_string_body.json5 diff --git a/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_string_body_two_invocations.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_string_body_two_invocations.json5 new file mode 100644 index 0000000000000..03b86e74ccc60 --- /dev/null +++ b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/200_string_body_two_invocations.json5 @@ -0,0 +1,10 @@ +{ + "0-1": { + "Return": { + "StatusCode": 200, + "Payload": { + "body": "string body" + } + } + } +} diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/__init__.py diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/lambda/not_ready_timeout_200_string_body.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/not_ready_timeout_200_string_body.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/lambda/not_ready_timeout_200_string_body.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/lambda/not_ready_timeout_200_string_body.json5 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sns/200_publish.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sns/200_publish.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sns/200_publish.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sns/200_publish.json5 diff --git a/tests/aws/services/cloudformation/resource_providers/opensearch/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sns/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/opensearch/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sns/__init__.py diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sqs/200_send_message.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sqs/200_send_message.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sqs/200_send_message.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sqs/200_send_message.json5 diff --git a/tests/aws/services/cloudformation/resource_providers/scheduler/templates/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sqs/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/scheduler/templates/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/sqs/__init__.py diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/states/200_start_execution_sync.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/states/200_start_execution_sync.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/states/200_start_execution_sync.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/states/200_start_execution_sync.json5 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/states/200_start_execution_sync2.json5 b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/states/200_start_execution_sync2.json5 similarity index 100% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/states/200_start_execution_sync2.json5 rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/states/200_start_execution_sync2.json5 diff --git a/tests/aws/services/cloudformation/resource_providers/ssm/__init__.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/states/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/ssm/__init__.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_responses/states/__init__.py diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_service_integrations.py b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_service_integrations.py similarity index 83% rename from tests/aws/services/stepfunctions/mocked_service_integrations/mocked_service_integrations.py rename to tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_service_integrations.py index 2ab9f76a13f9a..72756acdd1970 100644 --- a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_service_integrations.py +++ b/tests/aws/services/stepfunctions/local_mocked_service_integrations/mocked_service_integrations.py @@ -6,13 +6,19 @@ import json5 _THIS_FOLDER: Final[str] = os.path.dirname(os.path.realpath(__file__)) -_LOAD_CACHE: Final[dict[str, dict]] = dict() +_LOAD_CACHE: Final[dict[str, dict]] = {} class MockedServiceIntegrationsLoader(abc.ABC): + MOCKED_RESPONSE_LAMBDA_200_STRING_BODY_TWO_INVOCATIONS: Final[str] = os.path.join( + _THIS_FOLDER, "mocked_responses/lambda/200_string_body_two_invocations.json5" + ) MOCKED_RESPONSE_LAMBDA_200_STRING_BODY: Final[str] = os.path.join( _THIS_FOLDER, "mocked_responses/lambda/200_string_body.json5" ) + MOCKED_RESPONSE_LAMBDA_200_STATUS_CHANGE_BETWEEN_INVOCATIONS: Final[str] = os.path.join( + _THIS_FOLDER, "mocked_responses/lambda/200_status_change_between_invocations.json5" + ) MOCKED_RESPONSE_LAMBDA_NOT_READY_TIMEOUT_200_STRING_BODY: Final[str] = os.path.join( _THIS_FOLDER, "mocked_responses/lambda/not_ready_timeout_200_string_body.json5" ) @@ -52,7 +58,7 @@ class MockedServiceIntegrationsLoader(abc.ABC): def load(file_path: str) -> dict: template = _LOAD_CACHE.get(file_path) if template is None: - with open(file_path, "r") as df: + with open(file_path) as df: template = json5.load(df) _LOAD_CACHE[file_path] = template return copy.deepcopy(template) diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mock_config_files/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mock_config_files/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/callback/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/callback/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/dynamodb/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/dynamodb/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/events/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/events/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/lambda/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/lambda/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sns/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sns/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sqs/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/sqs/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/states/__init__.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_responses/states/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/templates/evaluatejsonata/evaluate_jsonata_templates.py b/tests/aws/services/stepfunctions/templates/evaluatejsonata/evaluate_jsonata_templates.py index badc419a74228..6d07be985c9a0 100644 --- a/tests/aws/services/stepfunctions/templates/evaluatejsonata/evaluate_jsonata_templates.py +++ b/tests/aws/services/stepfunctions/templates/evaluatejsonata/evaluate_jsonata_templates.py @@ -11,6 +11,11 @@ class EvaluateJsonataTemplate(TemplateLoader): JSONATA_ARRAY_ELEMENT_EXPRESSION_DOUBLE_QUOTES = [1, "{% $number('2') %}", 3] JSONATA_ARRAY_ELEMENT_EXPRESSION = [1, '{% $number("2") %}', 3] JSONATA_STATE_INPUT_EXPRESSION = "{% $states.input.input_value %}" + JSONATA_REGEX_EXPRESSION_BASE = r'{% $contains("hello$world", /^hello\$/) %}' + JSONATA_REGEX_EXPRESSION_BASE_FALSE = r'{% $contains("hello$world", /^hello\ /) %}' + JSONATA_REGEX_EXPRESSION_BASE_SINGLE_QUOTE = r"{% $contains('hello$world', /^hello\$/) %}" + JSONATA_REGEX_EXPRESSION_BASE_SINGLE_QUOTE_FALSE = r"{% $contains('hello$world', /^hello\ /) %}" BASE_MAP = os.path.join(_THIS_FOLDER, "statemachines/base_map.json5") BASE_TASK = os.path.join(_THIS_FOLDER, "statemachines/base_task.json5") + BASE_PASS = os.path.join(_THIS_FOLDER, "statemachines/base_pass.json5") diff --git a/tests/aws/services/stepfunctions/templates/evaluatejsonata/statemachines/base_pass.json5 b/tests/aws/services/stepfunctions/templates/evaluatejsonata/statemachines/base_pass.json5 new file mode 100644 index 0000000000000..360399a9d6555 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/evaluatejsonata/statemachines/base_pass.json5 @@ -0,0 +1,12 @@ +{ + "Comment": "BASE_PASS", + "QueryLanguage": "JSONata", + "StartAt": "Start", + "States": { + "Start": { + "Type": "Pass", + "Output": "__tbd__", + "End": true + } + } +} \ No newline at end of file diff --git a/tests/aws/services/cloudformation/resources/__init__.py b/tests/aws/services/stepfunctions/templates/local_mocked/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/resources/__init__.py rename to tests/aws/services/stepfunctions/templates/local_mocked/__init__.py diff --git a/tests/aws/services/stepfunctions/templates/mocked/mocked_templates.py b/tests/aws/services/stepfunctions/templates/local_mocked/mocked_templates.py similarity index 100% rename from tests/aws/services/stepfunctions/templates/mocked/mocked_templates.py rename to tests/aws/services/stepfunctions/templates/local_mocked/mocked_templates.py diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/__init__.py b/tests/aws/services/stepfunctions/templates/local_mocked/statemachines/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/v2/ported_from_v1/api/__init__.py rename to tests/aws/services/stepfunctions/templates/local_mocked/statemachines/__init__.py diff --git a/tests/aws/services/stepfunctions/templates/mocked/statemachines/lambda_sqs_integration.json5 b/tests/aws/services/stepfunctions/templates/local_mocked/statemachines/lambda_sqs_integration.json5 similarity index 100% rename from tests/aws/services/stepfunctions/templates/mocked/statemachines/lambda_sqs_integration.json5 rename to tests/aws/services/stepfunctions/templates/local_mocked/statemachines/lambda_sqs_integration.json5 diff --git a/tests/aws/services/stepfunctions/templates/mocked/__init__.py b/tests/aws/services/stepfunctions/templates/mocked/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/templates/mocked/statemachines/__init__.py b/tests/aws/services/stepfunctions/templates/mocked/statemachines/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/templates/scenarios/scenarios_templates.py b/tests/aws/services/stepfunctions/templates/scenarios/scenarios_templates.py index 29a4a77473035..86328836114d4 100644 --- a/tests/aws/services/stepfunctions/templates/scenarios/scenarios_templates.py +++ b/tests/aws/services/stepfunctions/templates/scenarios/scenarios_templates.py @@ -227,6 +227,10 @@ class ScenariosTemplate(TemplateLoader): LAMBDA_EMPTY_RETRY: Final[str] = os.path.join( _THIS_FOLDER, "statemachines/lambda_empty_retry.json5" ) + LAMBDA_REPEAT_UNTIL_LOOP: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/lambda_repeat_until_loop.json5" + ) + LAMBDA_INVOKE_WITH_RETRY_BASE: Final[str] = os.path.join( _THIS_FOLDER, "statemachines/lambda_invoke_with_retry_base.json5" ) diff --git a/tests/aws/services/stepfunctions/templates/scenarios/statemachines/lambda_repeat_until_loop.json5 b/tests/aws/services/stepfunctions/templates/scenarios/statemachines/lambda_repeat_until_loop.json5 new file mode 100644 index 0000000000000..6a1110c4b86da --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/scenarios/statemachines/lambda_repeat_until_loop.json5 @@ -0,0 +1,31 @@ +{ + "QueryLanguage": "JSONata", + "StartAt": "LambdaState", + "States": { + "LambdaState": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Arguments": { + "FunctionName": "__tbd__", + "Payload": "__tbd__" + }, + "Assign": { + "status": "{% $states.result.Payload.status %}", + }, + "Next": "CheckCompleted" + }, + "CheckCompleted": { + "Type": "Choice", + "Choices": [ + { + "Condition": "{% $status = 'completed' %}", + "Next": "Finish" + } + ], + "Default": "LambdaState" + }, + "Finish": { + "Type": "Succeed" + } + } +} diff --git a/tests/aws/services/stepfunctions/templates/template_loader.py b/tests/aws/services/stepfunctions/templates/template_loader.py index d7e30ac48f4d8..7e84d95490ff3 100644 --- a/tests/aws/services/stepfunctions/templates/template_loader.py +++ b/tests/aws/services/stepfunctions/templates/template_loader.py @@ -4,7 +4,7 @@ import json5 -_LOAD_CACHE: Final[dict[str, dict]] = dict() +_LOAD_CACHE: Final[dict[str, dict]] = {} class TemplateLoader(abc.ABC): @@ -12,7 +12,7 @@ class TemplateLoader(abc.ABC): def load_sfn_template(file_path: str) -> dict: template = _LOAD_CACHE.get(file_path) if template is None: - with open(file_path, "r") as df: + with open(file_path) as df: template = json5.load(df) _LOAD_CACHE[file_path] = template return copy.deepcopy(template) diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_kms_encrypt.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_kms_encrypt.json5 new file mode 100644 index 0000000000000..1cc3b11be8c1f --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_kms_encrypt.json5 @@ -0,0 +1,9 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::aws-sdk:kms:encrypt", + "Parameters": { + "KeyId.$": "$.KeyId", + "Plaintext.$": "$.Plaintext" + }, + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_lambda_get_function.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_lambda_get_function.json5 new file mode 100644 index 0000000000000..71f6c13f85b03 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_lambda_get_function.json5 @@ -0,0 +1,8 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::aws-sdk:lambda:getFunction", + "Parameters": { + "FunctionName.$": "$.FunctionName", + }, + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_s3_get_object.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_s3_get_object.json5 new file mode 100644 index 0000000000000..8ff30e643a1d9 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_aws_api_s3_get_object.json5 @@ -0,0 +1,9 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::aws-sdk:s3:getObject", + "Parameters": { + "Bucket.$": "$.Bucket", + "Key.$": "$.Key" + }, + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_dynamodb_service_task_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_dynamodb_service_task_state.json5 new file mode 100644 index 0000000000000..ee6ade074ceea --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_dynamodb_service_task_state.json5 @@ -0,0 +1,9 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName.$": "$.TableName", + "Item.$": "$.Item" + }, + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_events_put_events.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_events_put_events.json5 new file mode 100644 index 0000000000000..bea87edf439f7 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_events_put_events.json5 @@ -0,0 +1,8 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::events:putEvents", + "Parameters": { + "Entries.$": "$.Entries" + }, + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_fail_state_machine.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_fail_state_machine.json5 new file mode 100644 index 0000000000000..d4ab7bd29df21 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_fail_state_machine.json5 @@ -0,0 +1,12 @@ +{ + "Comment": "BASE_FAIL_STATE_MACHINE", + "QueryLanguage": "JSONata", + "StartAt": "State0", + "States": { + "State0": { + "Type": "Fail", + "Error": "SomeFailure", + "Cause": "This state machines raises a 'SomeFailure' failure.", + }, + } +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_invalid_state_definition.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_invalid_state_definition.json5 new file mode 100644 index 0000000000000..0e1557ed50d6e --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_invalid_state_definition.json5 @@ -0,0 +1,10 @@ +{ + "Comment": "Base state machine with a state that is not a valid state definition.", + "StartAt": "ExistingButInvalidState", + "States": { + "ExistingButInvalidState": { + "Type": "TypeThatDoesNotExist", + "End": true + } + } +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_lambda_service_task_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_lambda_service_task_state.json5 index ca37fca1a5f41..a0b97ce13e8f1 100644 --- a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_lambda_service_task_state.json5 +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_lambda_service_task_state.json5 @@ -1,4 +1,5 @@ { + "Comment": "BASE_LAMBDA_SERVICE_TASK_STATE", "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Parameters": { diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state.json5 new file mode 100644 index 0000000000000..9b254bbfed0de --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state.json5 @@ -0,0 +1,27 @@ +{ + "Comment": "BASE_MAP_STATE", + "Type": "Map", + "QueryLanguage": "JSONata", + "Items": "{% $states.input.Values %}", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "TestState", + "States": { + "TestState": { + "Type": "Task", + "QueryLanguage": "JSONata", + "Resource": "arn:aws:states:::lambda:invoke", + "Arguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "End": true + } + } + }, + "Output": "{% $states %}", + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_catch.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_catch.json5 new file mode 100644 index 0000000000000..5797a349c8b37 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_catch.json5 @@ -0,0 +1,34 @@ +{ + "Comment": "BASE_MAP_STATE_CATCH", + "Type": "Map", + "QueryLanguage": "JSONata", + "Items": "{% $states.input.Values %}", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "TestState", + "States": { + "TestState": { + "Type": "Task", + "Resource": "arn:aws:lambda:your-region:your-account-id:function:yourHelloWorldFunction", + "End": true + } + } + }, + "Catch": [ + { + "ErrorEquals": ["MockException"], + "Output": "{% $states %}", + "Next": "HandleMockError" + }, + { + "ErrorEquals": ["States.ExceedToleratedFailureThreshold"], + "Output": "{% $states %}", + "Next": "HandleThreshold" + } + ], + "ToleratedFailureCount": 1, + "End": true +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine.json5 new file mode 100644 index 0000000000000..d43d32f7fe622 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine.json5 @@ -0,0 +1,25 @@ +{ + "Comment": "BASE_MAP_STATE_MACHINE", + "StartAt": "State0", + "States": { + "State0": { + "Type": "Map", + "ItemsPath": "$.Values", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "HandleItem", + "States": { + "HandleItem": { + "Type": "Pass", + "End": true + } + } + }, + "ToleratedFailureCount": 2, + "End": true + } + } +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine_choice_fail.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine_choice_fail.json5 new file mode 100644 index 0000000000000..4ffd4020899f9 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine_choice_fail.json5 @@ -0,0 +1,40 @@ +{ + "Comment": "BASE_MAP_STATE_MACHINE_CHOICE_FAIL", + "StartAt": "MapState", + "States": { + "State0": { + "Type": "Map", + "ItemsPath": "$.Values", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" + }, + "StartAt": "HandleItem", + "States": { + "RouteItem": { + "QueryLanguage": "JSONata", + "Type": "Choice", + "Comment": "The first item should always fail.", + "Choices": [ + { + "Next": "FailureHandler", + "Condition": "{% $$.Map.Item.Index = 0 %}", + }, + ], + "Default": "SucceessHandler" + }, + "FailureHandler": { + "Type": "Fail", + "Error": "SomeFailure", + "Cause": "This state machines raises a 'SomeFailure' failure." + }, + "SucceessHandler": { + "Type": "Pass", + "End": true + } + } + }, + "End": true + } + } +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine_fail.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine_fail.json5 new file mode 100644 index 0000000000000..697e550a6c6d7 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_machine_fail.json5 @@ -0,0 +1,24 @@ +{ + "Comment": "BASE_MAP_STATE_MACHINE_FAIL", + "StartAt": "State0", + "States": { + "State0": { + "Type": "Map", + "ItemsPath": "$.Values", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" + }, + "StartAt": "HandleItem", + "States": { + "HandleItem": { + "Type": "Fail", + "Error": "SomeFailure", + "Cause": "This state machines raises a 'SomeFailure' failure." + } + } + }, + "End": true + } + } +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_result_writer.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_result_writer.json5 new file mode 100644 index 0000000000000..3d62ea97cca7c --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_result_writer.json5 @@ -0,0 +1,32 @@ +{ + "Comment": "BASE_MAP_STATE", + "Type": "Map", + "ItemsPath": "$.Values", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "TestState", + "States": { + "TestState": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "foo", + "Payload": "bar" + }, + "End": true + } + } + }, + "ResultWriter": { + "Resource": "arn:aws:states:::s3:putObject", + "Parameters": { + "Bucket": "result-bucket", + "Prefix": "mapJobs" + } + }, + "Label": "TestMap", + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_retry.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_retry.json5 new file mode 100644 index 0000000000000..777468d6f1d19 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_map_state_retry.json5 @@ -0,0 +1,32 @@ +{ + "Comment": "BASE_MAP_STATE_RETRY", + "Type": "Map", + "QueryLanguage": "JSONata", + "Items": "{% $states.input.Values %}", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "TestState", + "States": { + "TestState": { + "Type": "Task", + "Resource": "arn:aws:lambda:your-region:your-account-id:function:yourHelloWorldFunction", + "End": true + } + } + }, + "Retry": [ + { + "ErrorEquals": ["MockException"], + "MaxAttempts": 3 + }, + { + "ErrorEquals": ["States.ExceedToleratedFailureThreshold"], + "MaxAttempts": 2 + } + ], + "ToleratedFailureCount": 1, + "End": true +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_multi_state_machine.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_multi_state_machine.json5 new file mode 100644 index 0000000000000..e6ea7816cc408 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_multi_state_machine.json5 @@ -0,0 +1,16 @@ +{ + "Comment": "BASE_MULTI_STATE_MACHINE", + "QueryLanguage": "JSONata", + "StartAt": "State0", + "States": { + "State0": { + "Type": "Pass", + "Next": "State1" + }, + "State1": { + "Type": "Fail", + "Error": "SomeFailure", + "Cause": "This state machines raises a 'SomeFailure' failure.", + }, + } +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_parallel_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_parallel_state.json5 new file mode 100644 index 0000000000000..adc0ad0afc803 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_parallel_state.json5 @@ -0,0 +1,24 @@ +{ + "Type": "Parallel", + "Branches": [ + { + "StartAt": "B1", + "States": { + "B1": { + "Type": "Pass", + "End": true + } + } + }, + { + "StartAt": "B2", + "States": { + "B2": { + "Type": "Pass", + "End": true + } + } + } + ], + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_pass_state_machine.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_pass_state_machine.json5 new file mode 100644 index 0000000000000..869593a6ea2a5 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_pass_state_machine.json5 @@ -0,0 +1,10 @@ +{ + "Comment": "BASE_PASS_STATE_MACHINE", + "StartAt": "State0", + "States": { + "State0": { + "Type": "Pass", + "End": true + } + } +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_sfn_start_execution.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_sfn_start_execution.json5 new file mode 100644 index 0000000000000..5f90af204e34c --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_sfn_start_execution.json5 @@ -0,0 +1,14 @@ +{ + "Type": "Task", + "QueryLanguage": "JSONata", + "Resource": "arn:aws:states:::states:startExecution", + "Arguments": { + "Input": "{% $states.input.targetInput %}", + "StateMachineArn": "{% $states.input.stateMachineArn %}", + "Name": "{% $states.input.name %}" + }, + "Output": { + "targetExecutionResult": "{% $states.result %}" + }, + "End": true, +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_sqs_send_message.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_sqs_send_message.json5 new file mode 100644 index 0000000000000..dd7b6254e0368 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_sqs_send_message.json5 @@ -0,0 +1,9 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::sqs:sendMessage", + "Parameters": { + "QueueUrl.$": "$.QueueUrl", + "MessageBody.$": "$.MessageBody" + }, + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_task_state_catch.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_task_state_catch.json5 new file mode 100644 index 0000000000000..51d12e7cec9b7 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_task_state_catch.json5 @@ -0,0 +1,23 @@ +{ + "Comment": "BASE_TASK_STATE_CATCH", + "QueryLanguage": "JSONata", + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Arguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "Catch": [ + { + "ErrorEquals": ["MockException"], + "Output": "{% $states %}", + "Next": "HandleMockError" + }, + { + "ErrorEquals": ["States.TaskFailed"], + "Output": "{% $states %}", + "Next": "HandleThreshold" + } + ], + "End": true +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_task_state_retry.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_task_state_retry.json5 new file mode 100644 index 0000000000000..d8fb2fb20dc8b --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/base_task_state_retry.json5 @@ -0,0 +1,19 @@ +{ + "Comment": "BASE_TASK_STATE_RETRY", + "QueryLanguage": "JSONata", + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Arguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "Retry": [ + { + "ErrorEquals": ["MockException"], + "IntervalSeconds": 1, + "MaxAttempts": 5, + "BackoffRate": 2.0 + }, + ], + "End": true +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_dynamodb_service_task_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_dynamodb_service_task_state.json5 new file mode 100644 index 0000000000000..6adee18d1b35f --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_dynamodb_service_task_state.json5 @@ -0,0 +1,10 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName.$": "$.TableName", + "Item.$": "$.Item" + }, + "ResultPath": "$.putItemOutput", + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_jsonata_parallel_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_jsonata_parallel_state.json5 new file mode 100644 index 0000000000000..419fb7b0415a3 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_jsonata_parallel_state.json5 @@ -0,0 +1,30 @@ +{ + "Type": "Parallel", + "QueryLanguage": "JSONata", + "Arguments": "{% $states.input.parallelInput %}", + "Output": { + "parallelInput": "{% $states.input.parallelInput %}", + "parallelResult": "{% $states.result %}" + }, + "Branches": [ + { + "StartAt": "B1", + "States": { + "B1": { + "Type": "Pass", + "End": true + } + } + }, + { + "StartAt": "B2", + "States": { + "B2": { + "Type": "Pass", + "End": true + } + } + } + ], + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_output_path_dynamodb_service_task_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_output_path_dynamodb_service_task_state.json5 new file mode 100644 index 0000000000000..1c0a72d3c9aed --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_output_path_dynamodb_service_task_state.json5 @@ -0,0 +1,11 @@ +{ + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName.$": "$.TableName", + "Item.$": "$.Item" + }, + "ResultPath": "$.putItemOutput", + "OutputPath": "$.putItemOutput", + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_parallel_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_parallel_state.json5 new file mode 100644 index 0000000000000..6817565bfeb60 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_parallel_state.json5 @@ -0,0 +1,27 @@ +{ + "Type": "Parallel", + "InputPath": "$.parallelInput", + "ResultPath": "$.parallelResult", + "OutputPath": "$", + "Branches": [ + { + "StartAt": "B1", + "States": { + "B1": { + "Type": "Pass", + "End": true + } + } + }, + { + "StartAt": "B2", + "States": { + "B2": { + "Type": "Pass", + "End": true + } + } + } + ], + "End": true +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_sqs_service_task_wait.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_sqs_service_task_wait.json5 new file mode 100644 index 0000000000000..ac1e3fdbe0ac2 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/io_sqs_service_task_wait.json5 @@ -0,0 +1,14 @@ +{ + "Comment": "IO_SQS_SERVICE_TASK_WAIT", + "Type": "Task", + "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken", + "Parameters": { + "QueueUrl": "__QUEUE_URL__", + "MessageBody": { + "Message": "Hello from Step Functions!", + "TaskToken.$": "$$.Task.Token" + } + }, + "ResultPath": "$.SQS", + "Next": "NEXT_STATE" +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/localstack_blogpost_scenario_state_machine.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/localstack_blogpost_scenario_state_machine.json5 new file mode 100644 index 0000000000000..882ca205f4368 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/localstack_blogpost_scenario_state_machine.json5 @@ -0,0 +1,63 @@ +{ + "Comment": "Cost approval workflow", + "QueryLanguage": "JSONata", + "StartAt": "Approval Required", + "States": { + "Approval Required": { + "Type": "Choice", + "Choices": [ + { + "Condition": "{% $states.input.cost < 10 %}", + "Next": "Purchase Approved" + } + ], + "Default": "Ask for Approval" + }, + "Purchase Approved": { + "Type": "Succeed" + }, + "Ask for Approval": { + "Type": "Task", + "Resource": "arn:aws:states:::apigateway:invoke", + "Arguments": { + "Method": "POST", + "ApiEndpoint": "__api_gateway_endpoint__", + "Path": "/approval", + "RequestBody": { + "cost": "{% $states.input.cost %}", + "dept_id": "12345678" + }, + "AuthType": "NO_AUTH" + }, + "Retry": [ + { + "ErrorEquals": [ + "States.ALL" + ], + "BackoffRate": 2, + "IntervalSeconds": 1, + "MaxAttempts": 3, + } + ], + "Next": "Check Approval", + "Output": { + "approval": "{% $states.result.approval %}", + "approval_code": "2387462", + "approved_by": "Mary" + } + }, + "Check Approval": { + "Type": "Choice", + "Choices": [ + { + "Next": "Purchase Approved", + "Condition": "{% $states.input.approval %}" + } + ], + "Default": "Not Approved" + }, + "Not Approved": { + "Type": "Fail" + } + }, +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_item_reader_state_machine.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_item_reader_state_machine.json5 new file mode 100644 index 0000000000000..c2ebe9d770f8f --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_item_reader_state_machine.json5 @@ -0,0 +1,35 @@ +{ + "Comment": "MAP_ITEM_READER_STATE_MACHINE", + "QueryLanguage": "JSONata", + "StartAt": "State0", + "States": { + "State0": { + "Type": "Map", + "MaxConcurrency": 1, + "ItemReader": { + "ReaderConfig": { + "InputType": "JSON", + }, + "Resource": "arn:aws:states:::s3:getObject", + "Arguments": { + "Bucket": "{% $states.input.Bucket %}", + "Key":"{% $states.input.Key %}" + }, + }, + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "HandleItem", + "States": { + "HandleItem": { + "Type": "Pass", + "End": true + } + }, + }, + "End": true + } + } +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_task_state.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_task_state.json5 new file mode 100644 index 0000000000000..bbe91f3404209 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_task_state.json5 @@ -0,0 +1,55 @@ +{ + "Comment": "MAP_TASK_STATE", + "Type": "Map", + "QueryLanguage": "JSONata", + "Items": "{% $states.input.Values %}", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "TestState", + "States": { + "TestState": { + "Type": "Task", + "QueryLanguage": "JSONata", + "Resource": "arn:aws:states:::lambda:invoke", + "Arguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "End": true + } + } + }, + "Output": { + "result": "{% $map($states.result, function($v) { $v = 1 ? 'pass' : 'failed' }) %}", + "successCount": "{% $count($states.result[$ = 1]) %}", + "failedCount": "{% $count($states.result[$ = 0]) %}" + }, + "ToleratedFailureCount": 1, + "Retry": [ + { + "ErrorEquals": ["MockException"], + "MaxAttempts": 3 + }, + { + "ErrorEquals": ["States.ExceedToleratedFailureThreshold"], + "MaxAttempts": 2 + } + ], + "Catch": [ + { + "ErrorEquals": ["States.ExceedToleratedFailureThreshold"], + "Output": "{% $states %}", + "Next": "HandleThresholdExceeded" + }, + { + "ErrorEquals": ["MockException"], + "Output": "{% $states %}", + "Next": "HandleMockException" + + } + ], + "End": true +} \ No newline at end of file diff --git a/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_task_state_machine.json5 b/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_task_state_machine.json5 new file mode 100644 index 0000000000000..bf317718b4ff6 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/test_state/statemachines/map_task_state_machine.json5 @@ -0,0 +1,31 @@ +{ + "Comment": "MAP_TASK_STATE_MACHINE", + "QueryLanguage": "JSONata", + "StartAt": "State0", + "States": { + "State0": { + "Type": "Map", + "Items": "{% $states.input.Values %}", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "DISTRIBUTED", + "ExecutionType": "STANDARD" + }, + "StartAt": "HandleItem", + "States": { + "HandleItem": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Arguments": { + "FunctionName": "function-arn", + "Payload": "{% $states.input %}", + }, + "End": true + } + } + }, + "ToleratedFailureCount": 1, + "End": true + } + } +} diff --git a/tests/aws/services/stepfunctions/templates/test_state/test_state_templates.py b/tests/aws/services/stepfunctions/templates/test_state/test_state_templates.py index 18b5aa888ffd2..37b540e1d9e16 100644 --- a/tests/aws/services/stepfunctions/templates/test_state/test_state_templates.py +++ b/tests/aws/services/stepfunctions/templates/test_state/test_state_templates.py @@ -19,6 +19,34 @@ class TestStateTemplate(TemplateLoader): BASE_RESULT_PASS_STATE: Final[str] = os.path.join( _THIS_FOLDER, "statemachines/base_result_pass_state.json5" ) + BASE_DYNAMODB_SERVICE_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_dynamodb_service_task_state.json5" + ) + BASE_DYNAMODB_SERVICE_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_dynamodb_service_task_state.json5" + ) + IO_DYNAMODB_SERVICE_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/io_dynamodb_service_task_state.json5" + ) + IO_OUTPUT_PATH_DYNAMODB_SERVICE_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/io_output_path_dynamodb_service_task_state.json5" + ) + + IO_SQS_SERVICE_TASK_WAIT: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/io_sqs_service_task_wait.json5" + ) + + BASE_MAP_STATE: Final[str] = os.path.join(_THIS_FOLDER, "statemachines/base_map_state.json5") + BASE_MAP_STATE_WITH_RESULT_WRITER: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_map_state_result_writer.json5" + ) + BASE_MAP_STATE_CATCH: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_map_state_catch.json5" + ) + BASE_MAP_STATE_RETRY: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_map_state_retry.json5" + ) + IO_PASS_STATE: Final[str] = os.path.join(_THIS_FOLDER, "statemachines/io_pass_state.json5") IO_RESULT_PASS_STATE: Final[str] = os.path.join( _THIS_FOLDER, "statemachines/io_result_pass_state.json5" @@ -33,3 +61,83 @@ class TestStateTemplate(TemplateLoader): IO_LAMBDA_SERVICE_TASK_STATE: Final[str] = os.path.join( _THIS_FOLDER, "statemachines/io_lambda_service_task_state.json5" ) + BASE_EVENTS_PUT_EVENTS_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_events_put_events.json5" + ) + BASE_SQS_SEND_MESSAGE_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_sqs_send_message.json5" + ) + BASE_SFN_START_EXECUTION_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_sfn_start_execution.json5" + ) + BASE_AWS_SDK_S3_GET_OBJECT_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_aws_api_s3_get_object.json5" + ) + BASE_AWS_SDK_KMS_ENCRYPT_TASK_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_aws_api_kms_encrypt.json5" + ) + BASE_AWS_SDK_LAMBDA_GET_FUNCTION: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_aws_api_lambda_get_function.json5" + ) + + BASE_TASK_STATE_RETRY: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_task_state_retry.json5" + ) + BASE_TASK_STATE_CATCH: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_task_state_catch.json5" + ) + + MAP_TASK_STATE: Final[str] = os.path.join(_THIS_FOLDER, "statemachines/map_task_state.json5") + + BASE_PARALLEL_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_parallel_state.json5" + ) + IO_PARALLEL_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/io_parallel_state.json5" + ) + IO_JSONATA_PARALLEL_STATE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/io_jsonata_parallel_state.json5" + ) + + +class TestStateMachineTemplate(TemplateLoader): + BASE_MULTI_STATE_MACHINE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_multi_state_machine.json5" + ) + + BASE_PASS_STATE_MACHINE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_pass_state_machine.json5" + ) + + BASE_FAIL_STATE_MACHINE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_fail_state_machine.json5" + ) + + BASE_MAP_STATE_MACHINE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_map_state_machine.json5" + ) + + MAP_TASK_STATE_MACHINE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/map_task_state_machine.json5" + ) + + MAP_ITEM_READER_STATE_MACHINE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/map_item_reader_state_machine.json5" + ) + + # TODO The below state machines need to be snapshot and included in parity tests + BASE_MAP_STATE_MACHINE_FAIL: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_map_state_machine_fail.json5" + ) + + BASE_MAP_STATE_MACHINE_CHOICE_FAIL: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_map_state_machine_choice_fail.json5" + ) + + LOCALSTACK_BLOGPOST_SCENARIO_STATE_MACHINE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/localstack_blogpost_scenario_state_machine.json5" + ) + + BASE_INVALID_STATE_DEFINITION: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/base_invalid_state_definition.json5" + ) diff --git a/tests/aws/services/stepfunctions/templates/validation/statemachines/invalid_downgrade_query_language.json5 b/tests/aws/services/stepfunctions/templates/validation/statemachines/invalid_downgrade_query_language.json5 new file mode 100644 index 0000000000000..d79d8a9e085ea --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/validation/statemachines/invalid_downgrade_query_language.json5 @@ -0,0 +1,11 @@ +{ + "StartAt": "Pass", + "States": { + "Pass": { + "Type": "Pass", + "End": true, + "QueryLanguage": "JSONPath" + } + }, + "QueryLanguage": "JSONata" +} diff --git a/tests/aws/services/stepfunctions/templates/validation/statemachines/valid_query_language_pass.json5 b/tests/aws/services/stepfunctions/templates/validation/statemachines/valid_query_language_pass.json5 new file mode 100644 index 0000000000000..9ceb6bbd32400 --- /dev/null +++ b/tests/aws/services/stepfunctions/templates/validation/statemachines/valid_query_language_pass.json5 @@ -0,0 +1,24 @@ +{ + "StartAt": "Map", + "QueryLanguage": "JSONPath", + "States": { + "Map": { + "Type": "Map", + "QueryLanguage": "JSONata", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" + }, + "StartAt": "Pass", + "States": { + "Pass": { + "QueryLanguage": "JSONPath", + "Type": "Pass", + "End": true + } + } + }, + "End": true + } + } +} diff --git a/tests/aws/services/stepfunctions/templates/validation/validation_templates.py b/tests/aws/services/stepfunctions/templates/validation/validation_templates.py index ec4243c08e07c..3250675942f86 100644 --- a/tests/aws/services/stepfunctions/templates/validation/validation_templates.py +++ b/tests/aws/services/stepfunctions/templates/validation/validation_templates.py @@ -10,4 +10,10 @@ class ValidationTemplate(TemplateLoader): INVALID_BASE_NO_STARTAT: Final[str] = os.path.join( _THIS_FOLDER, "statemachines/invalid_base_no_startat.json5" ) + INVALID_DOWNGRADE_QUERY_LANGUAGE: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/invalid_downgrade_query_language.json5" + ) VALID_BASE_PASS: Final[str] = os.path.join(_THIS_FOLDER, "statemachines/valid_base_pass.json5") + VALID_QUERY_LANGUAGE_PASS: Final[str] = os.path.join( + _THIS_FOLDER, "statemachines/valid_query_language_pass.json5" + ) diff --git a/tests/aws/services/stepfunctions/v2/base/test_base.py b/tests/aws/services/stepfunctions/v2/base/test_base.py index a124678cd42a5..19f219dccd52b 100644 --- a/tests/aws/services/stepfunctions/v2/base/test_base.py +++ b/tests/aws/services/stepfunctions/v2/base/test_base.py @@ -172,7 +172,7 @@ def test_event_bridge_events_base( template = BaseTemplate.load_sfn_template(BaseTemplate.BASE_WAIT_1_MIN) template["States"]["State_1"]["Seconds"] = 60 if is_aws_cloud() else 1 definition = json.dumps(template) - execution_input = json.dumps(dict()) + execution_input = json.dumps({}) create_and_record_events( create_state_machine_iam_role, create_state_machine, diff --git a/tests/aws/services/stepfunctions/v2/base/test_wait.py b/tests/aws/services/stepfunctions/v2/base/test_wait.py index b9c1f4a243b2e..915da792d5360 100644 --- a/tests/aws/services/stepfunctions/v2/base/test_wait.py +++ b/tests/aws/services/stepfunctions/v2/base/test_wait.py @@ -36,9 +36,7 @@ def test_timestamp_too_far_in_future_boundary( template = BaseTemplate.load_sfn_template(BaseTemplate.WAIT_TIMESTAMP_PATH) definition = json.dumps(template) - wait_timestamp = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta( - days=days - ) + wait_timestamp = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=days) timestamp = wait_timestamp.strftime("%Y-%m-%dT%H:%M:%S") full_timestamp = f"{timestamp}.000Z" @@ -82,7 +80,7 @@ def test_wait_timestamppath( template = BaseTemplate.load_sfn_template(BaseTemplate.WAIT_TIMESTAMP_PATH) definition = json.dumps(template) - wait_timestamp = datetime.datetime.now(tz=datetime.timezone.utc) + wait_timestamp = datetime.datetime.now(tz=datetime.UTC) timestamp = wait_timestamp.strftime("%Y-%m-%dT%H:%M:%S") full_timestamp = f"{timestamp}{timestamp_suffix}" diff --git a/tests/aws/services/stepfunctions/v2/callback/test_callback.py b/tests/aws/services/stepfunctions/v2/callback/test_callback.py index 90879273d2d28..231b4c2a87850 100644 --- a/tests/aws/services/stepfunctions/v2/callback/test_callback.py +++ b/tests/aws/services/stepfunctions/v2/callback/test_callback.py @@ -606,7 +606,7 @@ def test_multiple_executions_and_heartbeat_notifications( # Launch multiple execution of the same state machine. execution_count = 6 - execution_arns = list() + execution_arns = [] for _ in range(execution_count): execution_arn = aws_client.stepfunctions.start_execution( stateMachineArn=state_machine_arn, input=exec_input diff --git a/tests/aws/services/stepfunctions/v2/choice_operators/utils.py b/tests/aws/services/stepfunctions/v2/choice_operators/utils.py index 55d8830cc4c11..b9ecc4c7870f2 100644 --- a/tests/aws/services/stepfunctions/v2/choice_operators/utils.py +++ b/tests/aws/services/stepfunctions/v2/choice_operators/utils.py @@ -71,7 +71,7 @@ def create_and_test_comparison_function( COT.COMPARISON_OPERATOR_PLACEHOLDER, comparison_func_name ) - input_output_cases: list[dict[str, Any]] = list() + input_output_cases: list[dict[str, Any]] = [] for i, (variable, value) in enumerate(comparisons): exec_input = json.dumps({COT.VARIABLE_KEY: variable, COT.VALUE_KEY: value}) diff --git a/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py b/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py index 125016f96ee55..61e906ce4979c 100644 --- a/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py +++ b/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py @@ -199,7 +199,7 @@ def test_start_large_input( } definition = json.dumps(template) - exec_input = json.dumps(dict()) + exec_input = json.dumps({}) create_and_record_execution( aws_client, create_state_machine_iam_role, diff --git a/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.snapshot.json b/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.snapshot.json index 33131f7120100..34cb33686ceef 100644 --- a/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.snapshot.json @@ -904,7 +904,7 @@ } }, "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function": { - "recorded-date": "28-11-2024, 13:05:16", + "recorded-date": "25-11-2025, 15:29:42", "recorded-content": { "get_execution_history": { "events": [ @@ -969,7 +969,7 @@ "id": 5, "previousEventId": 4, "taskFailedEventDetails": { - "cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", + "cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", "error": "Lambda.ResourceNotFoundException", "resource": "invoke", "resourceType": "lambda" @@ -979,7 +979,7 @@ }, { "executionFailedEventDetails": { - "cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", + "cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", "error": "Lambda.ResourceNotFoundException" }, "id": 6, @@ -996,7 +996,7 @@ } }, "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function_catch": { - "recorded-date": "28-11-2024, 13:05:33", + "recorded-date": "25-11-2025, 21:15:38", "recorded-content": { "get_execution_history": { "events": [ @@ -1061,7 +1061,7 @@ "id": 5, "previousEventId": 4, "taskFailedEventDetails": { - "cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", + "cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", "error": "Lambda.ResourceNotFoundException", "resource": "invoke", "resourceType": "lambda" @@ -1076,7 +1076,7 @@ "name": "Start", "output": { "Error": "Lambda.ResourceNotFoundException", - "Cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" + "Cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" }, "outputDetails": { "truncated": false @@ -1091,7 +1091,7 @@ "stateEnteredEventDetails": { "input": { "Error": "Lambda.ResourceNotFoundException", - "Cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" + "Cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" }, "inputDetails": { "truncated": false @@ -1108,10 +1108,10 @@ "name": "EndWithStateTaskFailedHandler", "output": { "Error": "Lambda.ResourceNotFoundException", - "Cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", + "Cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", "task_failed_error": { "Error": "Lambda.ResourceNotFoundException", - "Cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" + "Cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" } }, "outputDetails": { @@ -1125,10 +1125,10 @@ "executionSucceededEventDetails": { "output": { "Error": "Lambda.ResourceNotFoundException", - "Cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", + "Cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)", "task_failed_error": { "Error": "Lambda.ResourceNotFoundException", - "Cause": "Function not found: arn::lambda::111111111111:function:no_such_ (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" + "Cause": "Function not found: arn::lambda::111111111111:function:no_such_:$LATEST (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: ; Proxy: null)" } }, "outputDetails": { diff --git a/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.validation.json b/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.validation.json index 8d7be471fc48c..26ee197775553 100644 --- a/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.validation.json +++ b/tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.validation.json @@ -3,10 +3,22 @@ "last_validated_date": "2024-11-28T13:05:54+00:00" }, "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function": { - "last_validated_date": "2024-11-28T13:05:16+00:00" + "last_validated_date": "2025-11-25T15:29:44+00:00", + "durations_in_seconds": { + "setup": 11.48, + "call": 16.85, + "teardown": 2.53, + "total": 30.86 + } }, "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function_catch": { - "last_validated_date": "2024-11-28T13:05:33+00:00" + "last_validated_date": "2025-11-25T21:15:40+00:00", + "durations_in_seconds": { + "setup": 11.11, + "call": 14.27, + "teardown": 1.84, + "total": 27.22 + } }, "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_custom_exception": { "last_validated_date": "2024-11-28T13:03:53+00:00" diff --git a/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py b/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py index fc1dea31cf5fc..6b9cf3885bc79 100644 --- a/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py +++ b/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py @@ -127,6 +127,67 @@ def test_base_map( execution_input=exec_input, ) + @markers.aws.validated + @pytest.mark.parametrize( + "expression_string", + [ + EJT.JSONATA_REGEX_EXPRESSION_BASE, + EJT.JSONATA_REGEX_EXPRESSION_BASE_FALSE, + EJT.JSONATA_REGEX_EXPRESSION_BASE_SINGLE_QUOTE, + EJT.JSONATA_REGEX_EXPRESSION_BASE_SINGLE_QUOTE_FALSE, + ], + ids=[ + "BASE", + "BASE_FALSE", + "BASE_SINGLE_QUOTE", + "BASE_SINGLE_QUOTE_FALSE", + ], + ) + def test_base_jsonata_regular_expressions( + self, + aws_client, + create_state_machine_iam_role, + create_state_machine, + sfn_snapshot, + expression_string, + ): + template = EJT.load_sfn_template(EJT.BASE_PASS) + template["States"]["Start"]["Output"] = expression_string + definition = json.dumps(template) + exec_input = json.dumps({}) + create_and_record_execution( + aws_client, + create_state_machine_iam_role=create_state_machine_iam_role, + create_state_machine=create_state_machine, + sfn_snapshot=sfn_snapshot, + definition=definition, + execution_input=exec_input, + ) + + @markers.aws.validated + def test_merge_with_dynamic_args( + self, + aws_client, + create_state_machine_iam_role, + create_state_machine, + sfn_snapshot, + ): + """Regression test for #13579: $merge with dynamic variable references in array literals.""" + template = EJT.load_sfn_template(EJT.BASE_PASS) + template["States"]["Start"]["Output"] = ( + "{% $merge([$states.input.part1, $states.input.part2]) %}" + ) + definition = json.dumps(template) + exec_input = json.dumps({"part1": {"hello": "world"}, "part2": {"foo": "bar"}}) + create_and_record_execution( + aws_client, + create_state_machine_iam_role=create_state_machine_iam_role, + create_state_machine=create_state_machine, + sfn_snapshot=sfn_snapshot, + definition=definition, + execution_input=exec_input, + ) + @markers.aws.validated @pytest.mark.parametrize( "field,input_value", diff --git a/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.snapshot.json b/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.snapshot.json index 522ad54d0348d..9f29e7d7c5a26 100644 --- a/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.snapshot.json @@ -1,6 +1,262 @@ { + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE]": { + "recorded-date": "22-07-2025, 13:32:38", + "recorded-content": { + "get_execution_history": { + "events": [ + { + "executionStartedEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "roleArn": "snf_role_arn" + }, + "id": 1, + "previousEventId": 0, + "timestamp": "timestamp", + "type": "ExecutionStarted" + }, + { + "id": 2, + "previousEventId": 0, + "stateEnteredEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "name": "Start" + }, + "timestamp": "timestamp", + "type": "PassStateEntered" + }, + { + "id": 3, + "previousEventId": 2, + "stateExitedEventDetails": { + "name": "Start", + "output": "true", + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "PassStateExited" + }, + { + "executionSucceededEventDetails": { + "output": "true", + "outputDetails": { + "truncated": false + } + }, + "id": 4, + "previousEventId": 3, + "timestamp": "timestamp", + "type": "ExecutionSucceeded" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE]": { + "recorded-date": "22-07-2025, 13:33:07", + "recorded-content": { + "get_execution_history": { + "events": [ + { + "executionStartedEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "roleArn": "snf_role_arn" + }, + "id": 1, + "previousEventId": 0, + "timestamp": "timestamp", + "type": "ExecutionStarted" + }, + { + "id": 2, + "previousEventId": 0, + "stateEnteredEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "name": "Start" + }, + "timestamp": "timestamp", + "type": "PassStateEntered" + }, + { + "id": 3, + "previousEventId": 2, + "stateExitedEventDetails": { + "name": "Start", + "output": "true", + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "PassStateExited" + }, + { + "executionSucceededEventDetails": { + "output": "true", + "outputDetails": { + "truncated": false + } + }, + "id": 4, + "previousEventId": 3, + "timestamp": "timestamp", + "type": "ExecutionSucceeded" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_FALSE]": { + "recorded-date": "22-07-2025, 13:32:53", + "recorded-content": { + "get_execution_history": { + "events": [ + { + "executionStartedEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "roleArn": "snf_role_arn" + }, + "id": 1, + "previousEventId": 0, + "timestamp": "timestamp", + "type": "ExecutionStarted" + }, + { + "id": 2, + "previousEventId": 0, + "stateEnteredEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "name": "Start" + }, + "timestamp": "timestamp", + "type": "PassStateEntered" + }, + { + "id": 3, + "previousEventId": 2, + "stateExitedEventDetails": { + "name": "Start", + "output": "false", + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "PassStateExited" + }, + { + "executionSucceededEventDetails": { + "output": "false", + "outputDetails": { + "truncated": false + } + }, + "id": 4, + "previousEventId": 3, + "timestamp": "timestamp", + "type": "ExecutionSucceeded" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE_FALSE]": { + "recorded-date": "22-07-2025, 13:33:22", + "recorded-content": { + "get_execution_history": { + "events": [ + { + "executionStartedEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "roleArn": "snf_role_arn" + }, + "id": 1, + "previousEventId": 0, + "timestamp": "timestamp", + "type": "ExecutionStarted" + }, + { + "id": 2, + "previousEventId": 0, + "stateEnteredEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "name": "Start" + }, + "timestamp": "timestamp", + "type": "PassStateEntered" + }, + { + "id": 3, + "previousEventId": 2, + "stateExitedEventDetails": { + "name": "Start", + "output": "false", + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "PassStateExited" + }, + { + "executionSucceededEventDetails": { + "output": "false", + "outputDetails": { + "truncated": false + } + }, + "id": 4, + "previousEventId": 3, + "timestamp": "timestamp", + "type": "ExecutionSucceeded" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[TIMEOUT_SECONDS]": { - "recorded-date": "13-11-2024, 15:36:52", + "recorded-date": "22-07-2025, 13:30:50", "recorded-content": { "get_execution_history": { "events": [ @@ -74,9 +330,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -93,13 +347,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -131,9 +385,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -150,13 +402,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -183,9 +435,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -202,13 +452,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -230,7 +480,7 @@ } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[HEARTBEAT_SECONDS]": { - "recorded-date": "13-11-2024, 15:37:14", + "recorded-date": "22-07-2025, 13:31:08", "recorded-content": { "get_execution_history": { "events": [ @@ -304,9 +554,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -323,13 +571,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -361,9 +609,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -380,13 +626,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -413,9 +659,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -432,13 +676,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -460,7 +704,7 @@ } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS]": { - "recorded-date": "13-11-2024, 15:50:15", + "recorded-date": "22-07-2025, 13:31:23", "recorded-content": { "get_execution_history": { "events": [ @@ -676,8 +920,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[MAX_CONCURRENCY]": { - "recorded-date": "13-11-2024, 15:37:45", + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS_DOUBLE_QUOTES]": { + "recorded-date": "22-07-2025, 13:31:39", "recorded-content": { "get_execution_history": { "events": [ @@ -710,7 +954,7 @@ { "id": 3, "mapStateStartedEventDetails": { - "length": 1 + "length": 3 }, "previousEventId": 2, "timestamp": "timestamp", @@ -764,16 +1008,108 @@ }, { "id": 8, - "previousEventId": 7, + "mapIterationStartedEventDetails": { + "index": 1, + "name": "Start" + }, + "previousEventId": 6, "timestamp": "timestamp", - "type": "MapStateSucceeded" + "type": "MapIterationStarted" }, { "id": 9, - "previousEventId": 7, + "previousEventId": 8, + "stateEnteredEventDetails": { + "input": "2", + "inputDetails": { + "truncated": false + }, + "name": "Process" + }, + "timestamp": "timestamp", + "type": "PassStateEntered" + }, + { + "id": 10, + "previousEventId": 9, + "stateExitedEventDetails": { + "name": "Process", + "output": "2", + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "PassStateExited" + }, + { + "id": 11, + "mapIterationSucceededEventDetails": { + "index": 1, + "name": "Start" + }, + "previousEventId": 10, + "timestamp": "timestamp", + "type": "MapIterationSucceeded" + }, + { + "id": 12, + "mapIterationStartedEventDetails": { + "index": 2, + "name": "Start" + }, + "previousEventId": 10, + "timestamp": "timestamp", + "type": "MapIterationStarted" + }, + { + "id": 13, + "previousEventId": 12, + "stateEnteredEventDetails": { + "input": "3", + "inputDetails": { + "truncated": false + }, + "name": "Process" + }, + "timestamp": "timestamp", + "type": "PassStateEntered" + }, + { + "id": 14, + "previousEventId": 13, + "stateExitedEventDetails": { + "name": "Process", + "output": "3", + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "PassStateExited" + }, + { + "id": 15, + "mapIterationSucceededEventDetails": { + "index": 2, + "name": "Start" + }, + "previousEventId": 14, + "timestamp": "timestamp", + "type": "MapIterationSucceeded" + }, + { + "id": 16, + "previousEventId": 15, + "timestamp": "timestamp", + "type": "MapStateSucceeded" + }, + { + "id": 17, + "previousEventId": 15, "stateExitedEventDetails": { "name": "Start", - "output": "[1]", + "output": "[1,2,3]", "outputDetails": { "truncated": false } @@ -783,13 +1119,13 @@ }, { "executionSucceededEventDetails": { - "output": "[1]", + "output": "[1,2,3]", "outputDetails": { "truncated": false } }, - "id": 10, - "previousEventId": 9, + "id": 18, + "previousEventId": 17, "timestamp": "timestamp", "type": "ExecutionSucceeded" } @@ -801,8 +1137,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_PERCENTAGE]": { - "recorded-date": "13-11-2024, 15:37:57", + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[MAX_CONCURRENCY]": { + "recorded-date": "22-07-2025, 13:31:53", "recorded-content": { "get_execution_history": { "events": [ @@ -926,8 +1262,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_COUNT]": { - "recorded-date": "13-11-2024, 15:38:08", + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_PERCENTAGE]": { + "recorded-date": "22-07-2025, 13:32:08", "recorded-content": { "get_execution_history": { "events": [ @@ -1051,16 +1387,14 @@ } } }, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[TIMEOUT_SECONDS]": { - "recorded-date": "13-11-2024, 15:38:32", + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_COUNT]": { + "recorded-date": "22-07-2025, 13:32:23", "recorded-content": { "get_execution_history": { "events": [ { "executionStartedEventDetails": { - "input": { - "input_value": 1 - }, + "input": {}, "inputDetails": { "truncated": false }, @@ -1075,205 +1409,98 @@ "id": 2, "previousEventId": 0, "stateEnteredEventDetails": { - "input": { - "input_value": 1 - }, + "input": {}, "inputDetails": { "truncated": false }, "name": "Start" }, "timestamp": "timestamp", - "type": "TaskStateEntered" + "type": "MapStateEntered" }, { "id": 3, - "previousEventId": 2, - "taskScheduledEventDetails": { - "parameters": { - "Payload": {}, - "FunctionName": "arn::lambda::111111111111:function:" - }, - "region": "", - "resource": "invoke", - "resourceType": "lambda", - "timeoutInSeconds": 1 + "mapStateStartedEventDetails": { + "length": 1 }, + "previousEventId": 2, "timestamp": "timestamp", - "type": "TaskScheduled" + "type": "MapStateStarted" }, { "id": 4, - "previousEventId": 3, - "taskStartedEventDetails": { - "resource": "invoke", - "resourceType": "lambda" + "mapIterationStartedEventDetails": { + "index": 0, + "name": "Start" }, + "previousEventId": 3, "timestamp": "timestamp", - "type": "TaskStarted" + "type": "MapIterationStarted" }, { "id": 5, "previousEventId": 4, - "taskSucceededEventDetails": { - "output": { - "ExecutedVersion": "$LATEST", - "Payload": {}, - "SdkHttpMetadata": { - "AllHttpHeaders": { - "X-Amz-Executed-Version": [ - "$LATEST" - ], - "x-amzn-Remapped-Content-Length": [ - "0" - ], - "Connection": [ - "keep-alive" - ], - "x-amzn-RequestId": [ - "" - ], - "Content-Length": [ - "2" - ], - "Date": "date", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", - "Content-Type": [ - "application/json" - ] - }, - "HttpHeaders": { - "Connection": "keep-alive", - "Content-Length": "2", - "Content-Type": "application/json", - "Date": "date", - "X-Amz-Executed-Version": "$LATEST", - "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" - }, - "HttpStatusCode": 200 - }, - "SdkResponseMetadata": { - "RequestId": "" - }, - "StatusCode": 200 - }, - "outputDetails": { + "stateEnteredEventDetails": { + "input": "1", + "inputDetails": { "truncated": false }, - "resource": "invoke", - "resourceType": "lambda" + "name": "Process" }, "timestamp": "timestamp", - "type": "TaskSucceeded" + "type": "PassStateEntered" }, { "id": 6, - "previousEventId": 5, - "stateExitedEventDetails": { - "name": "Start", - "output": { - "ExecutedVersion": "$LATEST", - "Payload": {}, - "SdkHttpMetadata": { - "AllHttpHeaders": { - "X-Amz-Executed-Version": [ - "$LATEST" - ], - "x-amzn-Remapped-Content-Length": [ - "0" - ], - "Connection": [ - "keep-alive" - ], - "x-amzn-RequestId": [ - "" - ], - "Content-Length": [ - "2" - ], - "Date": "date", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", - "Content-Type": [ - "application/json" - ] - }, - "HttpHeaders": { - "Connection": "keep-alive", - "Content-Length": "2", - "Content-Type": "application/json", - "Date": "date", - "X-Amz-Executed-Version": "$LATEST", - "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" - }, - "HttpStatusCode": 200 - }, - "SdkResponseMetadata": { - "RequestId": "" - }, - "StatusCode": 200 - }, + "previousEventId": 5, + "stateExitedEventDetails": { + "name": "Process", + "output": "1", "outputDetails": { "truncated": false } }, "timestamp": "timestamp", - "type": "TaskStateExited" + "type": "PassStateExited" + }, + { + "id": 7, + "mapIterationSucceededEventDetails": { + "index": 0, + "name": "Start" + }, + "previousEventId": 6, + "timestamp": "timestamp", + "type": "MapIterationSucceeded" + }, + { + "id": 8, + "previousEventId": 7, + "timestamp": "timestamp", + "type": "MapStateSucceeded" + }, + { + "id": 9, + "previousEventId": 7, + "stateExitedEventDetails": { + "name": "Start", + "output": "[1]", + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "MapStateExited" }, { "executionSucceededEventDetails": { - "output": { - "ExecutedVersion": "$LATEST", - "Payload": {}, - "SdkHttpMetadata": { - "AllHttpHeaders": { - "X-Amz-Executed-Version": [ - "$LATEST" - ], - "x-amzn-Remapped-Content-Length": [ - "0" - ], - "Connection": [ - "keep-alive" - ], - "x-amzn-RequestId": [ - "" - ], - "Content-Length": [ - "2" - ], - "Date": "date", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", - "Content-Type": [ - "application/json" - ] - }, - "HttpHeaders": { - "Connection": "keep-alive", - "Content-Length": "2", - "Content-Type": "application/json", - "Date": "date", - "X-Amz-Executed-Version": "$LATEST", - "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" - }, - "HttpStatusCode": 200 - }, - "SdkResponseMetadata": { - "RequestId": "" - }, - "StatusCode": 200 - }, + "output": "[1]", "outputDetails": { "truncated": false } }, - "id": 7, - "previousEventId": 6, + "id": 10, + "previousEventId": 9, "timestamp": "timestamp", "type": "ExecutionSucceeded" } @@ -1286,7 +1513,7 @@ } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[HEARTBEAT_SECONDS]": { - "recorded-date": "13-11-2024, 15:53:14", + "recorded-date": "22-07-2025, 13:33:41", "recorded-content": { "get_execution_history": { "events": [ @@ -1364,9 +1591,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -1383,13 +1608,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1421,9 +1646,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -1440,13 +1663,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1473,9 +1696,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -1492,13 +1713,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1520,7 +1741,7 @@ } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[ITEMS]": { - "recorded-date": "13-11-2024, 15:39:12", + "recorded-date": "22-07-2025, 13:34:05", "recorded-content": { "get_execution_history": { "events": [ @@ -1749,7 +1970,7 @@ } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[MAX_CONCURRENCY]": { - "recorded-date": "13-11-2024, 15:39:39", + "recorded-date": "22-07-2025, 13:34:23", "recorded-content": { "get_execution_history": { "events": [ @@ -1878,7 +2099,7 @@ } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_PERCENTAGE]": { - "recorded-date": "13-11-2024, 15:40:00", + "recorded-date": "22-07-2025, 13:34:40", "recorded-content": { "get_execution_history": { "events": [ @@ -2007,7 +2228,7 @@ } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_COUNT]": { - "recorded-date": "13-11-2024, 15:40:16", + "recorded-date": "22-07-2025, 13:34:58", "recorded-content": { "get_execution_history": { "events": [ @@ -2135,14 +2356,21 @@ } } }, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS_DOUBLE_QUOTES]": { - "recorded-date": "18-11-2024, 09:08:27", + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_merge_with_dynamic_args": { + "recorded-date": "11-02-2026, 07:16:48", "recorded-content": { "get_execution_history": { "events": [ { "executionStartedEventDetails": { - "input": {}, + "input": { + "part1": { + "hello": "world" + }, + "part2": { + "foo": "bar" + } + }, "inputDetails": { "truncated": false }, @@ -2157,145 +2385,31 @@ "id": 2, "previousEventId": 0, "stateEnteredEventDetails": { - "input": {}, - "inputDetails": { - "truncated": false + "input": { + "part1": { + "hello": "world" + }, + "part2": { + "foo": "bar" + } }, - "name": "Start" - }, - "timestamp": "timestamp", - "type": "MapStateEntered" - }, - { - "id": 3, - "mapStateStartedEventDetails": { - "length": 3 - }, - "previousEventId": 2, - "timestamp": "timestamp", - "type": "MapStateStarted" - }, - { - "id": 4, - "mapIterationStartedEventDetails": { - "index": 0, - "name": "Start" - }, - "previousEventId": 3, - "timestamp": "timestamp", - "type": "MapIterationStarted" - }, - { - "id": 5, - "previousEventId": 4, - "stateEnteredEventDetails": { - "input": "1", "inputDetails": { "truncated": false }, - "name": "Process" - }, - "timestamp": "timestamp", - "type": "PassStateEntered" - }, - { - "id": 6, - "previousEventId": 5, - "stateExitedEventDetails": { - "name": "Process", - "output": "1", - "outputDetails": { - "truncated": false - } - }, - "timestamp": "timestamp", - "type": "PassStateExited" - }, - { - "id": 7, - "mapIterationSucceededEventDetails": { - "index": 0, - "name": "Start" - }, - "previousEventId": 6, - "timestamp": "timestamp", - "type": "MapIterationSucceeded" - }, - { - "id": 8, - "mapIterationStartedEventDetails": { - "index": 1, "name": "Start" }, - "previousEventId": 6, - "timestamp": "timestamp", - "type": "MapIterationStarted" - }, - { - "id": 9, - "previousEventId": 8, - "stateEnteredEventDetails": { - "input": "2", - "inputDetails": { - "truncated": false - }, - "name": "Process" - }, "timestamp": "timestamp", "type": "PassStateEntered" }, { - "id": 10, - "previousEventId": 9, + "id": 3, + "previousEventId": 2, "stateExitedEventDetails": { - "name": "Process", - "output": "2", - "outputDetails": { - "truncated": false - } - }, - "timestamp": "timestamp", - "type": "PassStateExited" - }, - { - "id": 11, - "mapIterationSucceededEventDetails": { - "index": 1, - "name": "Start" - }, - "previousEventId": 10, - "timestamp": "timestamp", - "type": "MapIterationSucceeded" - }, - { - "id": 12, - "mapIterationStartedEventDetails": { - "index": 2, - "name": "Start" - }, - "previousEventId": 10, - "timestamp": "timestamp", - "type": "MapIterationStarted" - }, - { - "id": 13, - "previousEventId": 12, - "stateEnteredEventDetails": { - "input": "3", - "inputDetails": { - "truncated": false + "name": "Start", + "output": { + "hello": "world", + "foo": "bar" }, - "name": "Process" - }, - "timestamp": "timestamp", - "type": "PassStateEntered" - }, - { - "id": 14, - "previousEventId": 13, - "stateExitedEventDetails": { - "name": "Process", - "output": "3", "outputDetails": { "truncated": false } @@ -2303,44 +2417,18 @@ "timestamp": "timestamp", "type": "PassStateExited" }, - { - "id": 15, - "mapIterationSucceededEventDetails": { - "index": 2, - "name": "Start" - }, - "previousEventId": 14, - "timestamp": "timestamp", - "type": "MapIterationSucceeded" - }, - { - "id": 16, - "previousEventId": 15, - "timestamp": "timestamp", - "type": "MapStateSucceeded" - }, - { - "id": 17, - "previousEventId": 15, - "stateExitedEventDetails": { - "name": "Start", - "output": "[1,2,3]", - "outputDetails": { - "truncated": false - } - }, - "timestamp": "timestamp", - "type": "MapStateExited" - }, { "executionSucceededEventDetails": { - "output": "[1,2,3]", + "output": { + "hello": "world", + "foo": "bar" + }, "outputDetails": { "truncated": false } }, - "id": 18, - "previousEventId": 17, + "id": 4, + "previousEventId": 3, "timestamp": "timestamp", "type": "ExecutionSucceeded" } diff --git a/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.validation.json b/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.validation.json index 6827732e56c1f..a9a71a763e816 100644 --- a/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.validation.json +++ b/tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.validation.json @@ -1,41 +1,155 @@ { + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE]": { + "last_validated_date": "2025-07-22T13:32:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.58, + "teardown": 1.63, + "total": 15.21 + } + }, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_FALSE]": { + "last_validated_date": "2025-07-22T13:32:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.51, + "teardown": 1.73, + "total": 14.24 + } + }, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE]": { + "last_validated_date": "2025-07-22T13:33:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.03, + "teardown": 1.72, + "total": 14.75 + } + }, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE_FALSE]": { + "last_validated_date": "2025-07-22T13:33:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.63, + "teardown": 1.72, + "total": 14.35 + } + }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS]": { - "last_validated_date": "2024-11-18T09:18:25+00:00" + "last_validated_date": "2025-07-22T13:31:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.55, + "teardown": 1.77, + "total": 15.32 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS_DOUBLE_QUOTES]": { - "last_validated_date": "2024-11-18T09:18:48+00:00" + "last_validated_date": "2025-07-22T13:31:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.63, + "teardown": 1.6, + "total": 15.23 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[MAX_CONCURRENCY]": { - "last_validated_date": "2024-11-18T09:19:10+00:00" + "last_validated_date": "2025-07-22T13:31:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.37, + "teardown": 1.5, + "total": 13.87 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_COUNT]": { - "last_validated_date": "2024-11-18T09:19:39+00:00" + "last_validated_date": "2025-07-22T13:32:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.56, + "teardown": 1.57, + "total": 14.13 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_PERCENTAGE]": { - "last_validated_date": "2024-11-18T09:19:28+00:00" + "last_validated_date": "2025-07-22T13:32:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.51, + "teardown": 2.79, + "total": 16.3 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[ITEMS]": { - "last_validated_date": "2024-11-13T15:39:11+00:00" + "last_validated_date": "2025-07-22T13:34:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.48, + "teardown": 2.42, + "total": 22.9 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[MAX_CONCURRENCY]": { - "last_validated_date": "2024-11-13T15:39:37+00:00" + "last_validated_date": "2025-07-22T13:34:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.56, + "teardown": 2.54, + "total": 18.1 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_COUNT]": { - "last_validated_date": "2024-11-13T15:40:15+00:00" + "last_validated_date": "2025-07-22T13:35:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.69, + "teardown": 3.73, + "total": 19.42 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_PERCENTAGE]": { - "last_validated_date": "2024-11-13T15:39:58+00:00" + "last_validated_date": "2025-07-22T13:34:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.04, + "teardown": 2.58, + "total": 17.62 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[HEARTBEAT_SECONDS]": { - "last_validated_date": "2024-11-13T15:37:13+00:00" + "last_validated_date": "2025-07-22T13:31:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.0, + "teardown": 2.43, + "total": 18.43 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[TIMEOUT_SECONDS]": { - "last_validated_date": "2024-11-13T15:36:51+00:00" + "last_validated_date": "2025-07-22T13:30:51+00:00", + "durations_in_seconds": { + "setup": 11.8, + "call": 11.98, + "teardown": 2.73, + "total": 26.51 + } }, "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[HEARTBEAT_SECONDS]": { - "last_validated_date": "2024-11-13T15:53:13+00:00" + "last_validated_date": "2025-07-22T13:33:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 17.12, + "teardown": 2.73, + "total": 19.85 + } }, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[TIMEOUT_SECONDS]": { - "last_validated_date": "2024-11-13T15:38:29+00:00" + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_merge_with_dynamic_args": { + "last_validated_date": "2026-02-11T07:16:51+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 35.92, + "teardown": 2.58, + "total": 39.09 + } } } diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py index 6dd745757fb3e..acc85baba95c5 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py @@ -37,7 +37,7 @@ def test_array_2( '{"Arg1": 1, "Arg2": []}', json.loads('{"Arg1": 1, "Arg2": []}'), ] - input_values = list() + input_values = [] for value in values: input_values.append({"fst": value, "snd": value}) create_and_test_on_inputs( @@ -54,7 +54,7 @@ def test_array_partition( self, create_state_machine_iam_role, create_state_machine, sfn_snapshot, aws_client ): arrays = [list(range(i)) for i in range(5)] - input_values = list() + input_values = [] for array in arrays: for chunk_size in range(1, 6): input_values.append({"fst": array, "snd": chunk_size}) @@ -82,7 +82,7 @@ def test_array_contains( ([True, False], True), ([True, False], False), ] - input_values = list() + input_values = [] for array, value in search_bindings: input_values.append({"fst": array, "snd": value}) create_and_test_on_inputs( @@ -104,7 +104,7 @@ def test_array_range( (1, 9, 9), (1, 9, 2), ] - input_values = list() + input_values = [] for fst, lst, step in ranges: input_values.append({"fst": fst, "snd": lst, "trd": step}) create_and_test_on_inputs( diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py index 23d325683e2c7..a7b5da0092211 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py @@ -12,7 +12,7 @@ def test_array_partition( ): # TODO: test and add support for raising exception on empty array. arrays = [list(range(i)) for i in range(1, 5)] - input_values = list() + input_values = [] for array in arrays: for chunk_size in range(1, 6): input_values.append({"fst": array, "snd": chunk_size}) @@ -35,7 +35,7 @@ def test_array_range( (1, 9, 9), (1, 9, 2), ] - input_values = list() + input_values = [] for fst, lst, step in ranges: input_values.append({"fst": fst, "snd": lst, "trd": step}) create_and_test_on_inputs( diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py index b480ecf4725ee..61407ac4cd4d8 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py @@ -38,7 +38,7 @@ def test_format_2( '{"Arg1": 1, "Arg2": []}', json.loads('{"Arg1": 1, "Arg2": []}'), ] - input_values = list() + input_values = [] for value in values: input_values.append({"fst": value, "snd": value}) diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py index d3b37ad599872..ea9555f3da6d7 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py @@ -67,7 +67,7 @@ def test_json_merge( merge_bindings = [ ({"a": {"a1": 1, "a2": 2}, "b": 2, "d": 3}, {"a": {"a3": 1, "a4": 2}, "c": 3, "d": 4}), ] - input_values = list() + input_values = [] for fst, snd in merge_bindings: input_values.append({"fst": fst, "snd": snd}) create_and_test_on_inputs( diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py index 61d10ce646811..30196d09649c4 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py @@ -144,7 +144,7 @@ def test_math_add( (-2.5, 0), # python: -2, # java: -3 (-3.5, 0), # python: -4, # java: -4 ] - input_values = list() + input_values = [] for fst, snd in add_tuples: input_values.append({"fst": fst, "snd": snd}) create_and_test_on_inputs( diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/__init__.py b/tests/aws/services/stepfunctions/v2/local_mocking/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/v2/ported_from_v1/engine/__init__.py rename to tests/aws/services/stepfunctions/v2/local_mocking/__init__.py diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py b/tests/aws/services/stepfunctions/v2/local_mocking/test_aws_scenarios.py similarity index 94% rename from tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py rename to tests/aws/services/stepfunctions/v2/local_mocking/test_aws_scenarios.py index 0ced66200e798..94c13de0b7e31 100644 --- a/tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_aws_scenarios.py @@ -3,13 +3,14 @@ from localstack.testing.pytest import markers from localstack.testing.pytest.stepfunctions.utils import create_and_run_mock from localstack.utils.strings import short_uid -from tests.aws.services.stepfunctions.mocked_service_integrations.mocked_service_integrations import ( +from tests.aws.services.stepfunctions.local_mocked_service_integrations.mocked_service_integrations import ( MockedServiceIntegrationsLoader, ) -from tests.aws.services.stepfunctions.templates.mocked.mocked_templates import MockedTemplates +from tests.aws.services.stepfunctions.templates.local_mocked.mocked_templates import MockedTemplates class TestBaseScenarios: + @markers.requires_in_process @markers.aws.only_localstack def test_lambda_sqs_integration_happy_path( self, @@ -46,6 +47,7 @@ def test_lambda_sqs_integration_happy_path( event_last = events[-1] assert event_last["type"] == "ExecutionSucceeded" + @markers.requires_in_process @markers.aws.only_localstack def test_lambda_sqs_integration_retry_path( self, @@ -103,6 +105,7 @@ def test_lambda_sqs_integration_retry_path( event_last = events[-1] assert event_last["type"] == "ExecutionSucceeded" + @markers.requires_in_process @markers.aws.only_localstack def test_lambda_sqs_integration_hybrid_path( self, diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py similarity index 98% rename from tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py rename to tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py index 7273954337d03..571766f06e484 100644 --- a/tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py @@ -12,7 +12,7 @@ create_state_machine_with_iam_role, ) from localstack.utils.strings import short_uid -from tests.aws.services.stepfunctions.mocked_service_integrations.mocked_service_integrations import ( +from tests.aws.services.stepfunctions.local_mocked_service_integrations.mocked_service_integrations import ( MockedServiceIntegrationsLoader, ) from tests.aws.services.stepfunctions.templates.base.base_templates import BaseTemplate @@ -38,6 +38,7 @@ "$..events..taskSubmittedEventDetails.output", ] ) +@markers.requires_in_process class TestBaseScenarios: @markers.aws.validated @pytest.mark.parametrize( diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.snapshot.json b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.snapshot.json similarity index 96% rename from tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.snapshot.json rename to tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.snapshot.json index 4628554c854af..e99f063aefd21 100644 --- a/tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.snapshot.json @@ -1,6 +1,6 @@ { - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": { - "recorded-date": "24-04-2025, 10:05:48", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": { + "recorded-date": "28-10-2025, 09:41:39", "recorded-content": { "get_execution_history": { "events": [ @@ -77,7 +77,7 @@ "keep-alive" ], "Content-Length": [ - "164" + "161" ], "Date": "date", "Content-Type": [ @@ -86,7 +86,7 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "164", + "Content-Length": "161", "Content-Type": "application/x-amz-json-1.0", "Date": "date", "x-amzn-RequestId": "x-amzn-RequestId" @@ -208,8 +208,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": { - "recorded-date": "24-04-2025, 10:06:22", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": { + "recorded-date": "28-10-2025, 09:42:11", "recorded-content": { "get_execution_history": { "events": [ @@ -286,7 +286,7 @@ "keep-alive" ], "Content-Length": [ - "164" + "161" ], "Date": "date", "Content-Type": [ @@ -295,7 +295,7 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "164", + "Content-Length": "161", "Content-Type": "application/x-amz-json-1.0", "Date": "date", "x-amzn-RequestId": "x-amzn-RequestId" @@ -423,8 +423,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": { - "recorded-date": "29-04-2025, 10:17:01", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": { + "recorded-date": "28-10-2025, 09:42:41", "recorded-content": { "get_execution_history": { "events": [ @@ -578,8 +578,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": { - "recorded-date": "29-04-2025, 11:15:14", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": { + "recorded-date": "28-10-2025, 09:43:11", "recorded-content": { "get_execution_history": { "events": [ diff --git a/tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.validation.json b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.validation.json new file mode 100644 index 0000000000000..6ab28e711db38 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.validation.json @@ -0,0 +1,38 @@ +{ + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": { + "last_validated_date": "2025-10-28T09:42:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.55, + "teardown": 2.78, + "total": 31.33 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": { + "last_validated_date": "2025-10-28T09:41:42+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 29.71, + "teardown": 3.07, + "total": 33.29 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": { + "last_validated_date": "2025-10-28T09:42:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 27.0, + "teardown": 2.67, + "total": 29.67 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": { + "last_validated_date": "2025-10-28T09:43:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 27.62, + "teardown": 2.66, + "total": 30.28 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py similarity index 87% rename from tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py rename to tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py index a267d59cf91dc..93cc95063edfe 100644 --- a/tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py @@ -16,8 +16,9 @@ create_and_record_mocked_execution, create_and_record_mocked_sync_execution, ) +from localstack.utils.aws import arns from localstack.utils.strings import short_uid -from tests.aws.services.stepfunctions.mocked_service_integrations.mocked_service_integrations import ( +from tests.aws.services.stepfunctions.local_mocked_service_integrations.mocked_service_integrations import ( MockedServiceIntegrationsLoader, ) from tests.aws.services.stepfunctions.templates.scenarios.scenarios_templates import ( @@ -29,6 +30,7 @@ @markers.snapshot.skip_snapshot_verify( paths=["$..SdkHttpMetadata", "$..SdkResponseMetadata", "$..ExecutedVersion"] ) +@markers.requires_in_process class TestBaseScenarios: @markers.aws.validated def test_lambda_invoke( @@ -322,8 +324,8 @@ def test_sqs_send_message( if is_aws_cloud(): queue_name = f"queue-{short_uid()}" queue_url = sqs_create_queue(QueueName=queue_name) - sfn_snapshot.add_transformer(RegexTransformer(queue_name, "sqs-queue-name")) sfn_snapshot.add_transformer(RegexTransformer(queue_url, "sqs-queue-url")) + sfn_snapshot.add_transformer(RegexTransformer(queue_name, "sqs-queue-name")) exec_input = json.dumps({"QueueUrl": queue_url, "MessageBody": message_body}) create_and_record_execution( @@ -604,7 +606,7 @@ def test_map_state_lambda( state_machine_name = f"mocked_state_machine_{short_uid()}" test_name = "TestCaseName" lambda_200_string_body = MockedServiceIntegrationsLoader.load( - MockedServiceIntegrationsLoader.MOCKED_RESPONSE_LAMBDA_200_STRING_BODY + MockedServiceIntegrationsLoader.MOCKED_RESPONSE_LAMBDA_200_STRING_BODY_TWO_INVOCATIONS ) mock_config = { "StateMachines": { @@ -658,6 +660,20 @@ def test_parallel_state_lambda( sfn_snapshot.add_transformer( RegexTransformer(function_name_branch2, "function_name_branch2") ) + sfn_snapshot.add_transformer( + JsonpathTransformer( + "$..stateExitedEventDetails.output", + "stateExitedEventDetails.output", + replace_reference=False, + ) + ) + sfn_snapshot.add_transformer( + JsonpathTransformer( + "$..executionSucceededEventDetails.output", + "executionSucceededEventDetails.output", + replace_reference=False, + ) + ) exec_input = json.dumps( { @@ -726,3 +742,86 @@ def test_parallel_state_lambda( state_machine_name, test_name, ) + + @markers.aws.validated + def test_numbered_mock_responses_multiple_success_invocations( + self, + aws_client, + create_state_machine_iam_role, + create_state_machine, + create_lambda_function, + account_id, + region_name, + sfn_snapshot, + monkeypatch, + mock_config_file, + ): + """ + Test that numbered mock responses ("0", "1", etc.) iterate correctly + through multiple successful invocations of the same state, + e.g. in a repeat-until loop implemented using a choice state. + """ + template = ScenariosTemplate.load_sfn_template(ScenariosTemplate.LAMBDA_REPEAT_UNTIL_LOOP) + template["States"]["LambdaState"]["Arguments"]["Payload"] = { + "status": "{% $exists($status) ? 'completed' : 'running' %}" + } + + exec_input = json.dumps({}) + function_name = f"lambda_{short_uid()}" + sfn_snapshot.add_transformer(RegexTransformer(function_name, "lambda_function_name")) + + if is_aws_cloud(): + lambda_creation_response = create_lambda_function( + func_name=function_name, + handler_file=ServicesTemplates.LAMBDA_ID_FUNCTION, + runtime=Runtime.python3_12, + ) + lambda_arn = lambda_creation_response["CreateFunctionResponse"]["FunctionArn"] + template["States"]["LambdaState"]["Arguments"]["FunctionName"] = lambda_arn + definition = json.dumps(template) + create_and_record_execution( + aws_client, + create_state_machine_iam_role, + create_state_machine, + sfn_snapshot, + definition, + exec_input, + ) + else: + state_machine_name = f"mock_cycling_test_{short_uid()}" + test_name = "NumberedResponseCyclingTest" + + sfn_snapshot.add_transformer(RegexTransformer(state_machine_name, "state_machine_name")) + + lambda_200_loop_status = MockedServiceIntegrationsLoader.load( + MockedServiceIntegrationsLoader.MOCKED_RESPONSE_LAMBDA_200_STATUS_CHANGE_BETWEEN_INVOCATIONS + ) + mock_config = { + "StateMachines": { + state_machine_name: { + "TestCases": {test_name: {"LambdaState": "lambda_200_loop_status"}} + } + }, + "MockedResponses": {"lambda_200_loop_status": lambda_200_loop_status}, + } + + mock_config_file_path = mock_config_file(mock_config) + monkeypatch.setattr(config, "SFN_MOCK_CONFIG", mock_config_file_path) + + template["States"]["LambdaState"]["Arguments"]["FunctionName"] = ( + arns.lambda_function_arn( + function_name=function_name, account_id=account_id, region_name=region_name + ) + ) + definition = json.dumps(template) + + create_and_record_mocked_execution( + aws_client, + create_state_machine_iam_role, + create_state_machine, + sfn_snapshot, + definition, + exec_input, + state_machine_name, + test_name, + ) diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.snapshot.json b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.snapshot.json similarity index 75% rename from tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.snapshot.json rename to tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.snapshot.json index 739ec3945461f..0d50c20c34f70 100644 --- a/tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.snapshot.json @@ -1,6 +1,6 @@ { - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": { - "recorded-date": "14-04-2025, 18:51:50", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": { + "recorded-date": "28-10-2025, 09:44:00", "recorded-content": { "get_execution_history": { "events": [ @@ -436,8 +436,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": { - "recorded-date": "22-04-2025, 10:30:21", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": { + "recorded-date": "28-10-2025, 09:43:41", "recorded-content": { "get_execution_history": { "events": [ @@ -543,8 +543,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": { - "recorded-date": "22-04-2025, 19:39:14", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": { + "recorded-date": "28-10-2025, 10:04:26", "recorded-content": { "get_execution_history": { "events": [ @@ -739,8 +739,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": { - "recorded-date": "23-04-2025, 13:52:23", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": { + "recorded-date": "28-10-2025, 09:44:52", "recorded-content": { "get_execution_history": { "events": [ @@ -938,8 +938,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": { - "recorded-date": "23-04-2025, 14:28:24", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": { + "recorded-date": "28-10-2025, 09:45:08", "recorded-content": { "get_execution_history": { "events": [ @@ -1083,8 +1083,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": { - "recorded-date": "23-04-2025, 15:32:30", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": { + "recorded-date": "28-10-2025, 09:45:33", "recorded-content": { "get_execution_history": { "events": [ @@ -1646,8 +1646,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": { - "recorded-date": "24-04-2025, 11:11:05", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": { + "recorded-date": "28-10-2025, 09:45:56", "recorded-content": { "get_execution_history": { "events": [ @@ -2064,8 +2064,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": { - "recorded-date": "28-04-2025, 12:36:06", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": { + "recorded-date": "28-10-2025, 10:19:56", "recorded-content": { "get_execution_history": { "events": [ @@ -2162,49 +2162,7 @@ "previousEventId": 0, "stateExitedEventDetails": { "name": "Branch1", - "output": { - "ExecutedVersion": "$LATEST", - "Payload": [ - "string-literal" - ], - "SdkHttpMetadata": { - "AllHttpHeaders": { - "X-Amz-Executed-Version": [ - "$LATEST" - ], - "x-amzn-Remapped-Content-Length": [ - "0" - ], - "Connection": [ - "keep-alive" - ], - "x-amzn-RequestId": "x-amzn-RequestId", - "Content-Length": [ - "18" - ], - "Date": "date", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", - "Content-Type": [ - "application/json" - ] - }, - "HttpHeaders": { - "Connection": "keep-alive", - "Content-Length": "18", - "Content-Type": "application/json", - "Date": "date", - "X-Amz-Executed-Version": "$LATEST", - "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "x-amzn-RequestId", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" - }, - "HttpStatusCode": 200 - }, - "SdkResponseMetadata": { - "RequestId": "RequestId" - }, - "StatusCode": 200 - }, + "output": "stateExitedEventDetails.output", "outputDetails": { "truncated": false } @@ -2219,47 +2177,7 @@ "previousEventId": 0, "stateExitedEventDetails": { "name": "Branch2", - "output": { - "ExecutedVersion": "$LATEST", - "Payload": "input-event-['string-literal']", - "SdkHttpMetadata": { - "AllHttpHeaders": { - "X-Amz-Executed-Version": [ - "$LATEST" - ], - "x-amzn-Remapped-Content-Length": [ - "0" - ], - "Connection": [ - "keep-alive" - ], - "x-amzn-RequestId": "x-amzn-RequestId", - "Content-Length": [ - "32" - ], - "Date": "date", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", - "Content-Type": [ - "application/json" - ] - }, - "HttpHeaders": { - "Connection": "keep-alive", - "Content-Length": "32", - "Content-Type": "application/json", - "Date": "date", - "X-Amz-Executed-Version": "$LATEST", - "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "x-amzn-RequestId", - "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" - }, - "HttpStatusCode": 200 - }, - "SdkResponseMetadata": { - "RequestId": "RequestId" - }, - "StatusCode": 200 - }, + "output": "stateExitedEventDetails.output", "outputDetails": { "truncated": false } @@ -2448,7 +2366,7 @@ "previousEventId": 13, "stateExitedEventDetails": { "name": "ParallelState", - "output": "[{\"ExecutedVersion\":\"$LATEST\",\"Payload\":[\"string-literal\"],\"SdkHttpMetadata\":{\"AllHttpHeaders\":{\"X-Amz-Executed-Version\":[\"$LATEST\"],\"x-amzn-Remapped-Content-Length\":[\"0\"],\"Connection\":[\"keep-alive\"],\"x-amzn-RequestId\":[\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\"],\"Content-Length\":[\"18\"],\"Date\":[\"Mon, 28 Apr 2025 12:36:05 GMT\"],\"X-Amzn-Trace-Id\":[\"Root=1-680f7635-0182b6d14f9778376d202e25;Parent=7880c132e0e2009b;Sampled=0;Lineage=1:816810b0:0\"],\"Content-Type\":[\"application/json\"]},\"HttpHeaders\":{\"Connection\":\"keep-alive\",\"Content-Length\":\"18\",\"Content-Type\":\"application/json\",\"Date\":\"Mon, 28 Apr 2025 12:36:05 GMT\",\"X-Amz-Executed-Version\":\"$LATEST\",\"x-amzn-Remapped-Content-Length\":\"0\",\"x-amzn-RequestId\":\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\",\"X-Amzn-Trace-Id\":\"Root=1-680f7635-0182b6d14f9778376d202e25;Parent=7880c132e0e2009b;Sampled=0;Lineage=1:816810b0:0\"},\"HttpStatusCode\":200},\"SdkResponseMetadata\":{\"RequestId\":\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\"},\"StatusCode\":200},{\"ExecutedVersion\":\"$LATEST\",\"Payload\":\"input-event-['string-literal']\",\"SdkHttpMetadata\":{\"AllHttpHeaders\":{\"X-Amz-Executed-Version\":[\"$LATEST\"],\"x-amzn-Remapped-Content-Length\":[\"0\"],\"Connection\":[\"keep-alive\"],\"x-amzn-RequestId\":[\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\"],\"Content-Length\":[\"32\"],\"Date\":[\"Mon, 28 Apr 2025 12:36:05 GMT\"],\"X-Amzn-Trace-Id\":[\"Root=1-680f7635-5bd01853792e73951459090c;Parent=56aa9955dca169e9;Sampled=0;Lineage=1:786b2a01:0\"],\"Content-Type\":[\"application/json\"]},\"HttpHeaders\":{\"Connection\":\"keep-alive\",\"Content-Length\":\"32\",\"Content-Type\":\"application/json\",\"Date\":\"Mon, 28 Apr 2025 12:36:05 GMT\",\"X-Amz-Executed-Version\":\"$LATEST\",\"x-amzn-Remapped-Content-Length\":\"0\",\"x-amzn-RequestId\":\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\",\"X-Amzn-Trace-Id\":\"Root=1-680f7635-5bd01853792e73951459090c;Parent=56aa9955dca169e9;Sampled=0;Lineage=1:786b2a01:0\"},\"HttpStatusCode\":200},\"SdkResponseMetadata\":{\"RequestId\":\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\"},\"StatusCode\":200}]", + "output": "stateExitedEventDetails.output", "outputDetails": { "truncated": false } @@ -2458,7 +2376,7 @@ }, { "executionSucceededEventDetails": { - "output": "[{\"ExecutedVersion\":\"$LATEST\",\"Payload\":[\"string-literal\"],\"SdkHttpMetadata\":{\"AllHttpHeaders\":{\"X-Amz-Executed-Version\":[\"$LATEST\"],\"x-amzn-Remapped-Content-Length\":[\"0\"],\"Connection\":[\"keep-alive\"],\"x-amzn-RequestId\":[\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\"],\"Content-Length\":[\"18\"],\"Date\":[\"Mon, 28 Apr 2025 12:36:05 GMT\"],\"X-Amzn-Trace-Id\":[\"Root=1-680f7635-0182b6d14f9778376d202e25;Parent=7880c132e0e2009b;Sampled=0;Lineage=1:816810b0:0\"],\"Content-Type\":[\"application/json\"]},\"HttpHeaders\":{\"Connection\":\"keep-alive\",\"Content-Length\":\"18\",\"Content-Type\":\"application/json\",\"Date\":\"Mon, 28 Apr 2025 12:36:05 GMT\",\"X-Amz-Executed-Version\":\"$LATEST\",\"x-amzn-Remapped-Content-Length\":\"0\",\"x-amzn-RequestId\":\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\",\"X-Amzn-Trace-Id\":\"Root=1-680f7635-0182b6d14f9778376d202e25;Parent=7880c132e0e2009b;Sampled=0;Lineage=1:816810b0:0\"},\"HttpStatusCode\":200},\"SdkResponseMetadata\":{\"RequestId\":\"c455c86f-6cb5-4d5d-8a71-4f0a98b33059\"},\"StatusCode\":200},{\"ExecutedVersion\":\"$LATEST\",\"Payload\":\"input-event-['string-literal']\",\"SdkHttpMetadata\":{\"AllHttpHeaders\":{\"X-Amz-Executed-Version\":[\"$LATEST\"],\"x-amzn-Remapped-Content-Length\":[\"0\"],\"Connection\":[\"keep-alive\"],\"x-amzn-RequestId\":[\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\"],\"Content-Length\":[\"32\"],\"Date\":[\"Mon, 28 Apr 2025 12:36:05 GMT\"],\"X-Amzn-Trace-Id\":[\"Root=1-680f7635-5bd01853792e73951459090c;Parent=56aa9955dca169e9;Sampled=0;Lineage=1:786b2a01:0\"],\"Content-Type\":[\"application/json\"]},\"HttpHeaders\":{\"Connection\":\"keep-alive\",\"Content-Length\":\"32\",\"Content-Type\":\"application/json\",\"Date\":\"Mon, 28 Apr 2025 12:36:05 GMT\",\"X-Amz-Executed-Version\":\"$LATEST\",\"x-amzn-Remapped-Content-Length\":\"0\",\"x-amzn-RequestId\":\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\",\"X-Amzn-Trace-Id\":\"Root=1-680f7635-5bd01853792e73951459090c;Parent=56aa9955dca169e9;Sampled=0;Lineage=1:786b2a01:0\"},\"HttpStatusCode\":200},\"SdkResponseMetadata\":{\"RequestId\":\"bee07bb2-41e2-4c9f-9f6f-1a70696cc112\"},\"StatusCode\":200}]", + "output": "executionSucceededEventDetails.output", "outputDetails": { "truncated": false } @@ -2476,8 +2394,8 @@ } } }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": { - "recorded-date": "03-06-2025, 18:47:04", + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": { + "recorded-date": "28-10-2025, 09:44:18", "recorded-content": { "creation_response": { "creationDate": "datetime", @@ -2489,7 +2407,7 @@ }, "start_execution_sync_response": { "billingDetails": { - "billedDurationInMilliseconds": 300, + "billedDurationInMilliseconds": 400, "billedMemoryUsedInMB": 64 }, "executionArn": "arn::states::111111111111:express:::", @@ -2602,5 +2520,771 @@ } } } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_numbered_mock_responses_multiple_success_invocations": { + "recorded-date": "04-02-2026, 08:37:50", + "recorded-content": { + "get_execution_history": { + "events": [ + { + "executionStartedEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "roleArn": "snf_role_arn" + }, + "id": 1, + "previousEventId": 0, + "timestamp": "timestamp", + "type": "ExecutionStarted" + }, + { + "id": 2, + "previousEventId": 0, + "stateEnteredEventDetails": { + "input": {}, + "inputDetails": { + "truncated": false + }, + "name": "LambdaState" + }, + "timestamp": "timestamp", + "type": "TaskStateEntered" + }, + { + "id": 3, + "previousEventId": 2, + "taskScheduledEventDetails": { + "parameters": { + "FunctionName": "arn::lambda::111111111111:function:lambda_function_name", + "Payload": { + "status": "running" + } + }, + "region": "", + "resource": "invoke", + "resourceType": "lambda" + }, + "timestamp": "timestamp", + "type": "TaskScheduled" + }, + { + "id": 4, + "previousEventId": 3, + "taskStartedEventDetails": { + "resource": "invoke", + "resourceType": "lambda" + }, + "timestamp": "timestamp", + "type": "TaskStarted" + }, + { + "id": 5, + "previousEventId": 4, + "taskSucceededEventDetails": { + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "running" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "21" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "21", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + }, + "resource": "invoke", + "resourceType": "lambda" + }, + "timestamp": "timestamp", + "type": "TaskSucceeded" + }, + { + "id": 6, + "previousEventId": 5, + "stateExitedEventDetails": { + "assignedVariables": { + "status": "\"running\"" + }, + "assignedVariablesDetails": { + "truncated": false + }, + "name": "LambdaState", + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "running" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "21" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "21", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "TaskStateExited" + }, + { + "id": 7, + "previousEventId": 6, + "stateEnteredEventDetails": { + "input": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "running" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "21" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "21", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "inputDetails": { + "truncated": false + }, + "name": "CheckCompleted" + }, + "timestamp": "timestamp", + "type": "ChoiceStateEntered" + }, + { + "id": 8, + "previousEventId": 7, + "stateExitedEventDetails": { + "name": "CheckCompleted", + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "running" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "21" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "21", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "ChoiceStateExited" + }, + { + "id": 9, + "previousEventId": 8, + "stateEnteredEventDetails": { + "input": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "running" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "21" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "21", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "inputDetails": { + "truncated": false + }, + "name": "LambdaState" + }, + "timestamp": "timestamp", + "type": "TaskStateEntered" + }, + { + "id": 10, + "previousEventId": 9, + "taskScheduledEventDetails": { + "parameters": { + "FunctionName": "arn::lambda::111111111111:function:lambda_function_name", + "Payload": { + "status": "completed" + } + }, + "region": "", + "resource": "invoke", + "resourceType": "lambda" + }, + "timestamp": "timestamp", + "type": "TaskScheduled" + }, + { + "id": 11, + "previousEventId": 10, + "taskStartedEventDetails": { + "resource": "invoke", + "resourceType": "lambda" + }, + "timestamp": "timestamp", + "type": "TaskStarted" + }, + { + "id": 12, + "previousEventId": 11, + "taskSucceededEventDetails": { + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "completed" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "23" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "23", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + }, + "resource": "invoke", + "resourceType": "lambda" + }, + "timestamp": "timestamp", + "type": "TaskSucceeded" + }, + { + "id": 13, + "previousEventId": 12, + "stateExitedEventDetails": { + "assignedVariables": { + "status": "\"completed\"" + }, + "assignedVariablesDetails": { + "truncated": false + }, + "name": "LambdaState", + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "completed" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "23" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "23", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "TaskStateExited" + }, + { + "id": 14, + "previousEventId": 13, + "stateEnteredEventDetails": { + "input": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "completed" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "23" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "23", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "inputDetails": { + "truncated": false + }, + "name": "CheckCompleted" + }, + "timestamp": "timestamp", + "type": "ChoiceStateEntered" + }, + { + "id": 15, + "previousEventId": 14, + "stateExitedEventDetails": { + "name": "CheckCompleted", + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "completed" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "23" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "23", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "ChoiceStateExited" + }, + { + "id": 16, + "previousEventId": 15, + "stateEnteredEventDetails": { + "input": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "completed" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "23" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "23", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "inputDetails": { + "truncated": false + }, + "name": "Finish" + }, + "timestamp": "timestamp", + "type": "SucceedStateEntered" + }, + { + "id": 17, + "previousEventId": 16, + "stateExitedEventDetails": { + "name": "Finish", + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "completed" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "23" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "23", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + } + }, + "timestamp": "timestamp", + "type": "SucceedStateExited" + }, + { + "executionSucceededEventDetails": { + "output": { + "ExecutedVersion": "$LATEST", + "Payload": { + "status": "completed" + }, + "SdkHttpMetadata": { + "AllHttpHeaders": { + "X-Amz-Executed-Version": [ + "$LATEST" + ], + "x-amzn-Remapped-Content-Length": [ + "0" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": "x-amzn-RequestId", + "Content-Length": [ + "23" + ], + "Date": "date", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id", + "Content-Type": [ + "application/json" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "23", + "Content-Type": "application/json", + "Date": "date", + "X-Amz-Executed-Version": "$LATEST", + "x-amzn-Remapped-Content-Length": "0", + "x-amzn-RequestId": "x-amzn-RequestId", + "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "RequestId" + }, + "StatusCode": 200 + }, + "outputDetails": { + "truncated": false + } + }, + "id": 18, + "previousEventId": 17, + "timestamp": "timestamp", + "type": "ExecutionSucceeded" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.validation.json b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.validation.json new file mode 100644 index 0000000000000..62b2dc32ff1e4 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.validation.json @@ -0,0 +1,92 @@ +{ + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": { + "last_validated_date": "2025-10-28T09:45:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 23.19, + "teardown": 1.78, + "total": 24.97 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": { + "last_validated_date": "2025-10-28T09:45:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.95, + "teardown": 2.44, + "total": 16.39 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": { + "last_validated_date": "2025-10-28T09:43:43+00:00", + "durations_in_seconds": { + "setup": 10.55, + "call": 16.82, + "teardown": 2.19, + "total": 29.56 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": { + "last_validated_date": "2025-10-28T09:44:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.4, + "teardown": 2.25, + "total": 18.65 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": { + "last_validated_date": "2025-10-28T09:44:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.81, + "teardown": 1.95, + "total": 17.76 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": { + "last_validated_date": "2025-10-28T09:45:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.99, + "teardown": 2.51, + "total": 23.5 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_numbered_mock_responses_multiple_success_invocations": { + "last_validated_date": "2026-02-04T08:37:53+00:00", + "durations_in_seconds": { + "setup": 11.76, + "call": 17.42, + "teardown": 3.35, + "total": 32.53 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": { + "last_validated_date": "2025-10-28T10:20:00+00:00", + "durations_in_seconds": { + "setup": 11.86, + "call": 19.2, + "teardown": 3.44, + "total": 34.5 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": { + "last_validated_date": "2025-10-28T09:44:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.03, + "teardown": 1.98, + "total": 17.01 + } + }, + "tests/aws/services/stepfunctions/v2/local_mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": { + "last_validated_date": "2025-10-28T10:04:28+00:00", + "durations_in_seconds": { + "setup": 11.84, + "call": 15.44, + "teardown": 2.01, + "total": 29.29 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py b/tests/aws/services/stepfunctions/v2/local_mocking/test_mock_config_file.py similarity index 77% rename from tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py rename to tests/aws/services/stepfunctions/v2/local_mocking/test_mock_config_file.py index 931d66512936e..e193ffd062382 100644 --- a/tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py +++ b/tests/aws/services/stepfunctions/v2/local_mocking/test_mock_config_file.py @@ -1,10 +1,10 @@ from localstack import config -from localstack.services.stepfunctions.mocking.mock_config import ( - MockTestCase, - load_mock_test_case_for, +from localstack.services.stepfunctions.local_mocking.mock_config import ( + LocalMockTestCase, + load_local_mock_test_case_for, ) from localstack.testing.pytest import markers -from tests.aws.services.stepfunctions.mocked_service_integrations.mocked_service_integrations import ( +from tests.aws.services.stepfunctions.local_mocked_service_integrations.mocked_service_integrations import ( MockedServiceIntegrationsLoader, ) @@ -12,7 +12,7 @@ class TestMockConfigFile: @markers.aws.only_localstack def test_is_mock_config_flag_detected_unset(self, mock_config_file): - mock_test_case = load_mock_test_case_for( + mock_test_case = load_local_mock_test_case_for( state_machine_name="state_machine_name", test_case_name="test_case_name" ) assert mock_test_case is None @@ -31,7 +31,7 @@ def test_is_mock_config_flag_detected_set(self, mock_config_file, monkeypatch): } mock_config_file_path = mock_config_file(mock_config) monkeypatch.setattr(config, "SFN_MOCK_CONFIG", mock_config_file_path) - mock_test_case: MockTestCase = load_mock_test_case_for( + mock_test_case: LocalMockTestCase = load_local_mock_test_case_for( state_machine_name="S0", test_case_name="BaseTestCase" ) assert mock_test_case is not None diff --git a/tests/aws/services/stepfunctions/v2/logs/test_logs.py b/tests/aws/services/stepfunctions/v2/logs/test_logs.py index 0948cdbb54fd5..4855398705062 100644 --- a/tests/aws/services/stepfunctions/v2/logs/test_logs.py +++ b/tests/aws/services/stepfunctions/v2/logs/test_logs.py @@ -270,7 +270,7 @@ def test_log_group_with_multiple_runs( expected_events_count += len(execution_history["events"]) logs_client = aws_client.logs - log_events = list() + log_events = [] def _collect_log_events(): log_streams = logs_client.describe_log_streams(logGroupName=log_group_name)[ diff --git a/tests/aws/services/stepfunctions/v2/mocking/__init__.py b/tests/aws/services/stepfunctions/v2/mocking/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.validation.json b/tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.validation.json deleted file mode 100644 index 1151f58cdcd1e..0000000000000 --- a/tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": { - "last_validated_date": "2025-04-24T10:06:22+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": { - "last_validated_date": "2025-04-24T10:05:48+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": { - "last_validated_date": "2025-04-29T10:17:01+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": { - "last_validated_date": "2025-04-29T11:15:14+00:00" - } -} diff --git a/tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.validation.json b/tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.validation.json deleted file mode 100644 index 5836f11a2ed34..0000000000000 --- a/tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.validation.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": { - "last_validated_date": "2025-04-23T15:32:30+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": { - "last_validated_date": "2025-04-23T14:28:24+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": { - "last_validated_date": "2025-04-22T10:30:21+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": { - "last_validated_date": "2025-04-14T18:51:50+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": { - "last_validated_date": "2025-06-03T18:47:04+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": { - "last_validated_date": "2025-04-24T11:11:05+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": { - "last_validated_date": "2025-04-28T12:36:06+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": { - "last_validated_date": "2025-04-23T13:52:23+00:00" - }, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": { - "last_validated_date": "2025-04-22T19:39:14+00:00" - } -} diff --git a/tests/aws/services/stepfunctions/v2/outputdecl/test_output.snapshot.json b/tests/aws/services/stepfunctions/v2/outputdecl/test_output.snapshot.json index 3e3b4ce45dc52..fb014dc780f7a 100644 --- a/tests/aws/services/stepfunctions/v2/outputdecl/test_output.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/outputdecl/test_output.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EMPTY]": { - "recorded-date": "04-11-2024, 13:15:46", + "recorded-date": "19-02-2026, 14:47:34", "recorded-content": { "get_execution_history": { "events": [ @@ -78,7 +78,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_LITERALS]": { - "recorded-date": "04-11-2024, 13:16:06", + "recorded-date": "19-02-2026, 14:47:49", "recorded-content": { "get_execution_history": { "events": [ @@ -266,7 +266,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EXPR]": { - "recorded-date": "04-11-2024, 13:16:22", + "recorded-date": "19-02-2026, 14:48:04", "recorded-content": { "get_execution_history": { "events": [ @@ -463,7 +463,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_DIRECT_EXPR]": { - "recorded-date": "04-11-2024, 13:16:38", + "recorded-date": "19-02-2026, 14:48:17", "recorded-content": { "get_execution_history": { "events": [ @@ -587,7 +587,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_lambda[BASE_LAMBDA]": { - "recorded-date": "04-11-2024, 14:01:00", + "recorded-date": "19-02-2026, 14:48:36", "recorded-content": { "get_execution_history": { "events": [ @@ -813,7 +813,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_task_lambda[BASE_TASK_LAMBDA]": { - "recorded-date": "04-11-2024, 14:15:19", + "recorded-date": "19-02-2026, 14:48:54", "recorded-content": { "get_execution_history": { "events": [ @@ -920,9 +920,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "60" ], @@ -939,13 +937,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -996,9 +994,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "60" ], @@ -1015,13 +1011,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -1068,9 +1064,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "60" ], @@ -1087,13 +1081,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -1116,7 +1110,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[NULL]": { - "recorded-date": "20-11-2024, 18:24:00", + "recorded-date": "19-02-2026, 14:49:08", "recorded-content": { "get_execution_history": { "events": [ @@ -1184,7 +1178,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[INT]": { - "recorded-date": "20-11-2024, 18:24:15", + "recorded-date": "19-02-2026, 14:49:23", "recorded-content": { "get_execution_history": { "events": [ @@ -1252,7 +1246,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[FLOAT]": { - "recorded-date": "20-11-2024, 18:24:31", + "recorded-date": "19-02-2026, 14:49:37", "recorded-content": { "get_execution_history": { "events": [ @@ -1320,7 +1314,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[BOOL]": { - "recorded-date": "20-11-2024, 18:24:46", + "recorded-date": "19-02-2026, 14:49:51", "recorded-content": { "get_execution_history": { "events": [ @@ -1388,7 +1382,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[STR_LIT]": { - "recorded-date": "20-11-2024, 18:25:01", + "recorded-date": "19-02-2026, 14:50:05", "recorded-content": { "get_execution_history": { "events": [ @@ -1456,7 +1450,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[JSONATA_EXPR]": { - "recorded-date": "20-11-2024, 18:25:16", + "recorded-date": "19-02-2026, 14:50:20", "recorded-content": { "get_execution_history": { "events": [ @@ -1528,7 +1522,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_EMPY]": { - "recorded-date": "20-11-2024, 18:25:31", + "recorded-date": "19-02-2026, 14:50:35", "recorded-content": { "get_execution_history": { "events": [ @@ -1596,7 +1590,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_RICH]": { - "recorded-date": "20-11-2024, 18:25:47", + "recorded-date": "19-02-2026, 14:50:49", "recorded-content": { "get_execution_history": { "events": [ @@ -1664,7 +1658,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_TRUE]": { - "recorded-date": "27-12-2024, 14:50:09", + "recorded-date": "19-02-2026, 14:51:09", "recorded-content": { "get_execution_history": { "events": [ @@ -1758,7 +1752,7 @@ } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_FALSE]": { - "recorded-date": "27-12-2024, 14:50:24", + "recorded-date": "19-02-2026, 14:51:23", "recorded-content": { "get_execution_history": { "events": [ diff --git a/tests/aws/services/stepfunctions/v2/outputdecl/test_output.validation.json b/tests/aws/services/stepfunctions/v2/outputdecl/test_output.validation.json index 42c2f4701dbb8..90978192ba431 100644 --- a/tests/aws/services/stepfunctions/v2/outputdecl/test_output.validation.json +++ b/tests/aws/services/stepfunctions/v2/outputdecl/test_output.validation.json @@ -1,50 +1,146 @@ { "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_DIRECT_EXPR]": { - "last_validated_date": "2024-11-04T13:16:37+00:00" + "last_validated_date": "2026-02-19T14:48:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.29, + "teardown": 1.44, + "total": 13.73 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EMPTY]": { - "last_validated_date": "2024-11-04T13:15:44+00:00" + "last_validated_date": "2026-02-19T14:47:34+00:00", + "durations_in_seconds": { + "setup": 11.5, + "call": 12.74, + "teardown": 1.36, + "total": 25.6 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EXPR]": { - "last_validated_date": "2024-11-04T13:16:20+00:00" + "last_validated_date": "2026-02-19T14:48:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.43, + "teardown": 1.41, + "total": 14.84 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_LITERALS]": { - "last_validated_date": "2024-11-04T13:16:04+00:00" + "last_validated_date": "2026-02-19T14:47:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.3, + "teardown": 1.35, + "total": 14.65 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_lambda[BASE_LAMBDA]": { - "last_validated_date": "2024-11-04T14:00:57+00:00" + "last_validated_date": "2026-02-19T14:48:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.91, + "teardown": 2.27, + "total": 19.18 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[BOOL]": { - "last_validated_date": "2024-11-20T18:24:44+00:00" + "last_validated_date": "2026-02-19T14:49:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.32, + "teardown": 1.42, + "total": 13.74 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[FLOAT]": { - "last_validated_date": "2024-11-20T18:24:29+00:00" + "last_validated_date": "2026-02-19T14:49:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.44, + "teardown": 1.4, + "total": 14.84 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[INT]": { - "last_validated_date": "2024-11-20T18:24:13+00:00" + "last_validated_date": "2026-02-19T14:49:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.38, + "teardown": 1.44, + "total": 14.82 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[JSONATA_EXPR]": { - "last_validated_date": "2024-11-20T18:25:14+00:00" + "last_validated_date": "2026-02-19T14:50:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.41, + "teardown": 1.43, + "total": 14.84 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_EMPY]": { - "last_validated_date": "2024-11-20T18:25:29+00:00" + "last_validated_date": "2026-02-19T14:50:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.39, + "teardown": 1.4, + "total": 14.79 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_RICH]": { - "last_validated_date": "2024-11-20T18:25:45+00:00" + "last_validated_date": "2026-02-19T14:50:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.37, + "teardown": 1.45, + "total": 14.82 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[NULL]": { - "last_validated_date": "2024-11-20T18:23:58+00:00" + "last_validated_date": "2026-02-19T14:49:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.34, + "teardown": 1.39, + "total": 13.73 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[STR_LIT]": { - "last_validated_date": "2024-11-20T18:24:59+00:00" + "last_validated_date": "2026-02-19T14:50:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.31, + "teardown": 1.41, + "total": 13.72 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_task_lambda[BASE_TASK_LAMBDA]": { - "last_validated_date": "2024-11-04T14:15:16+00:00" + "last_validated_date": "2026-02-19T14:48:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.3, + "teardown": 2.36, + "total": 17.66 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_FALSE]": { - "last_validated_date": "2024-12-27T14:50:22+00:00" + "last_validated_date": "2026-02-19T14:51:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.74, + "teardown": 2.4, + "total": 15.14 + } }, "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_TRUE]": { - "last_validated_date": "2024-12-27T14:50:08+00:00" + "last_validated_date": "2026-02-19T14:51:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.03, + "teardown": 1.43, + "total": 19.46 + } } } diff --git a/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py b/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py index 568ab840aafbb..74cd5bccced5a 100644 --- a/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py +++ b/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py @@ -122,7 +122,7 @@ def test_parallel_state( ) @markers.aws.validated - @pytest.mark.parametrize("max_concurrency_value", [dict(), "NoNumber", 0, 1]) + @pytest.mark.parametrize("max_concurrency_value", [{}, "NoNumber", 0, 1]) def test_max_concurrency_path( self, aws_client, @@ -1285,7 +1285,7 @@ def test_map_state_tolerated_failure_values( ) @markers.aws.validated - @pytest.mark.parametrize("tolerated_failure_count_value", [dict(), "NoNumber", -1, 0, 1]) + @pytest.mark.parametrize("tolerated_failure_count_value", [{}, "NoNumber", -1, 0, 1]) def test_map_state_tolerated_failure_count_path( self, aws_client, @@ -1311,7 +1311,7 @@ def test_map_state_tolerated_failure_count_path( @markers.aws.validated @pytest.mark.parametrize( - "tolerated_failure_percentage_value", [dict(), "NoNumber", -1.1, -1, 0, 1, 1.1, 100, 100.1] + "tolerated_failure_percentage_value", [{}, "NoNumber", -1.1, -1, 0, 1, 1.1, 100, 100.1] ) def test_map_state_tolerated_failure_percentage_path( self, diff --git a/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.snapshot.json index ee88f11d33538..2f39639bcf9af 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_query_parameters": { - "recorded-date": "20-08-2023, 15:18:59", + "recorded-date": "06-11-2025, 12:16:28", "recorded-content": { "get_execution_history": { "events": [ @@ -259,7 +259,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_base": { - "recorded-date": "20-08-2023, 15:26:20", + "recorded-date": "06-11-2025, 12:12:42", "recorded-content": { "get_execution_history": { "events": [ @@ -449,7 +449,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[None]": { - "recorded-date": "25-08-2023, 12:40:49", + "recorded-date": "06-11-2025, 12:13:06", "recorded-content": { "get_execution_history": { "events": [ @@ -642,7 +642,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[]": { - "recorded-date": "25-08-2023, 12:41:13", + "recorded-date": "06-11-2025, 12:13:47", "recorded-content": { "get_execution_history": { "events": [ @@ -835,7 +835,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[HelloWorld]": { - "recorded-date": "25-08-2023, 12:41:49", + "recorded-date": "06-11-2025, 12:14:17", "recorded-content": { "get_execution_history": { "events": [ @@ -1800,7 +1800,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_error": { - "recorded-date": "20-08-2023, 16:15:34", + "recorded-date": "06-11-2025, 12:16:53", "recorded-content": { "get_execution_history": { "events": [ @@ -1905,7 +1905,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[request_body3]": { - "recorded-date": "25-08-2023, 12:42:12", + "recorded-date": "06-11-2025, 12:14:54", "recorded-content": { "get_execution_history": { "events": [ @@ -2181,7 +2181,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header1]": { - "recorded-date": "06-10-2024, 14:50:48", + "recorded-date": "06-11-2025, 12:15:22", "recorded-content": { "get_execution_history": { "events": [ @@ -2401,7 +2401,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header2]": { - "recorded-date": "06-10-2024, 14:51:07", + "recorded-date": "06-11-2025, 12:16:00", "recorded-content": { "get_execution_history": { "events": [ diff --git a/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.validation.json index 22773c1a8de00..266e15481a172 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.validation.json @@ -1,32 +1,86 @@ { "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_base": { - "last_validated_date": "2023-08-20T13:26:20+00:00" + "last_validated_date": "2025-11-06T12:12:46+00:00", + "durations_in_seconds": { + "setup": 12.84, + "call": 21.85, + "teardown": 4.98, + "total": 39.67 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_error": { - "last_validated_date": "2023-08-20T14:15:34+00:00" + "last_validated_date": "2025-11-06T12:17:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.14, + "teardown": 41.19, + "total": 59.33 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[HelloWorld]": { - "last_validated_date": "2023-08-25T10:41:49+00:00" + "last_validated_date": "2025-11-06T12:14:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.32, + "teardown": 17.83, + "total": 37.15 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[None]": { - "last_validated_date": "2023-08-25T10:40:49+00:00" + "last_validated_date": "2025-11-06T12:13:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.38, + "teardown": 22.02, + "total": 41.4 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[]": { - "last_validated_date": "2023-08-25T10:41:13+00:00" + "last_validated_date": "2025-11-06T12:13:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.41, + "teardown": 10.31, + "total": 29.72 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[request_body3]": { - "last_validated_date": "2023-08-25T10:42:12+00:00" + "last_validated_date": "2025-11-06T12:15:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.36, + "teardown": 8.46, + "total": 27.82 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header1]": { - "last_validated_date": "2024-10-06T14:50:48+00:00" + "last_validated_date": "2025-11-06T12:15:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.48, + "teardown": 18.67, + "total": 38.15 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header2]": { - "last_validated_date": "2024-10-06T14:51:07+00:00" + "last_validated_date": "2025-11-06T12:16:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 19.23, + "teardown": 8.24, + "total": 27.47 + } }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[singleStringHeader]": { "last_validated_date": "2024-10-06T14:50:24+00:00" }, "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_query_parameters": { - "last_validated_date": "2023-08-20T13:18:59+00:00" + "last_validated_date": "2025-11-06T12:16:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.2, + "teardown": 6.06, + "total": 26.26 + } } } diff --git a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py index 0c30e5af18d58..33148cd988fc0 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py +++ b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py @@ -30,7 +30,7 @@ def test_list_secrets( ): template = ST.load_sfn_template(ST.AWSSDK_LIST_SECRETS) definition = json.dumps(template) - exec_input = json.dumps(dict()) + exec_input = json.dumps({}) create_and_record_execution( aws_client, create_state_machine_iam_role, diff --git a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.snapshot.json index 7fd6cff9f6673..18f8adf6f654b 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_list_secrets": { - "recorded-date": "22-06-2023, 13:59:49", + "recorded-date": "06-11-2025, 12:17:53", "recorded-content": { "get_execution_history": { "events": [ @@ -142,7 +142,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_get_item": { - "recorded-date": "16-05-2023, 22:27:38", + "recorded-date": "06-11-2025, 12:18:15", "recorded-content": { "get_execution_history": { "events": [ @@ -341,11 +341,11 @@ "taskSucceededEventDetails": { "output": { "Item": { - "data": { - "S": "HelloWorld" - }, "id": { "S": "id1" + }, + "data": { + "S": "HelloWorld" } } }, @@ -381,11 +381,11 @@ "putItemOutput": {}, "getItemOutput": { "Item": { - "data": { - "S": "HelloWorld" - }, "id": { "S": "id1" + }, + "data": { + "S": "HelloWorld" } } } @@ -417,11 +417,11 @@ "putItemOutput": {}, "getItemOutput": { "Item": { - "data": { - "S": "HelloWorld" - }, "id": { "S": "id1" + }, + "data": { + "S": "HelloWorld" } } } @@ -444,7 +444,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_delete_item": { - "recorded-date": "16-05-2023, 22:28:05", + "recorded-date": "06-11-2025, 12:18:39", "recorded-content": { "get_execution_history": { "events": [ @@ -719,7 +719,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_update_get_item": { - "recorded-date": "16-05-2023, 22:28:28", + "recorded-date": "06-11-2025, 12:19:01", "recorded-content": { "get_execution_history": { "events": [ @@ -1220,7 +1220,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution": { - "recorded-date": "18-12-2023, 14:22:59", + "recorded-date": "06-11-2025, 12:20:01", "recorded-content": { "get_execution_history": { "events": [ @@ -1341,7 +1341,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution_implicit_json_serialisation": { - "recorded-date": "05-02-2024, 11:29:16", + "recorded-date": "06-11-2025, 12:20:37", "recorded-content": { "get_execution_history": { "events": [ @@ -1489,7 +1489,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template0]": { - "recorded-date": "10-04-2024, 18:55:26", + "recorded-date": "06-11-2025, 12:19:18", "recorded-content": { "get_execution_history": { "events": [ @@ -1552,7 +1552,7 @@ "id": 5, "previousEventId": 4, "taskFailedEventDetails": { - "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: )", + "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: ) (SDK Attempt Count: 1)", "error": "Sfn.InvalidTokenException", "resource": "sendTaskSuccess", "resourceType": "aws-sdk:sfn" @@ -1562,7 +1562,7 @@ }, { "executionFailedEventDetails": { - "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: )", + "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: ) (SDK Attempt Count: 1)", "error": "Sfn.InvalidTokenException" }, "id": 6, @@ -1579,7 +1579,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template1]": { - "recorded-date": "10-04-2024, 18:55:40", + "recorded-date": "06-11-2025, 12:19:33", "recorded-content": { "get_execution_history": { "events": [ @@ -1641,7 +1641,7 @@ "id": 5, "previousEventId": 4, "taskFailedEventDetails": { - "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: )", + "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: ) (SDK Attempt Count: 1)", "error": "Sfn.InvalidTokenException", "resource": "sendTaskFailure", "resourceType": "aws-sdk:sfn" @@ -1651,7 +1651,7 @@ }, { "executionFailedEventDetails": { - "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: )", + "cause": "Invalid Token: 'Invalid token' (Service: Sfn, Status Code: 400, Request ID: ) (SDK Attempt Count: 1)", "error": "Sfn.InvalidTokenException" }, "id": 6, @@ -1668,7 +1668,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_str]": { - "recorded-date": "27-01-2025, 10:17:44", + "recorded-date": "06-11-2025, 12:21:01", "recorded-content": { "get_execution_history": { "events": [ @@ -1804,7 +1804,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[str]": { - "recorded-date": "27-01-2025, 10:18:02", + "recorded-date": "06-11-2025, 12:21:21", "recorded-content": { "get_execution_history": { "events": [ @@ -1940,7 +1940,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_binary]": { - "recorded-date": "27-01-2025, 10:18:18", + "recorded-date": "06-11-2025, 12:21:40", "recorded-content": { "get_execution_history": { "events": [ @@ -2076,7 +2076,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[binary]": { - "recorded-date": "27-01-2025, 10:18:40", + "recorded-date": "06-11-2025, 12:22:00", "recorded-content": { "get_execution_history": { "events": [ @@ -2212,7 +2212,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[bytearray]": { - "recorded-date": "27-01-2025, 10:18:56", + "recorded-date": "06-11-2025, 12:22:19", "recorded-content": { "get_execution_history": { "events": [ @@ -2348,7 +2348,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[str]": { - "recorded-date": "27-01-2025, 10:29:12", + "recorded-date": "06-11-2025, 12:22:38", "recorded-content": { "get_execution_history": { "events": [ @@ -2491,7 +2491,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[dict]": { - "recorded-date": "27-01-2025, 10:29:28", + "recorded-date": "06-11-2025, 12:22:57", "recorded-content": { "get_execution_history": { "events": [ @@ -2642,7 +2642,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[list]": { - "recorded-date": "27-01-2025, 10:29:44", + "recorded-date": "06-11-2025, 12:23:16", "recorded-content": { "get_execution_history": { "events": [ @@ -2794,7 +2794,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[bool]": { - "recorded-date": "27-01-2025, 10:30:01", + "recorded-date": "06-11-2025, 12:23:35", "recorded-content": { "get_execution_history": { "events": [ @@ -2937,7 +2937,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[num]": { - "recorded-date": "27-01-2025, 10:30:17", + "recorded-date": "06-11-2025, 12:23:54", "recorded-content": { "get_execution_history": { "events": [ diff --git a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.validation.json index 53dcdf9b58d8d..8a64e4f5f1459 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.validation.json @@ -1,53 +1,164 @@ { "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_delete_item": { - "last_validated_date": "2023-05-16T20:28:05+00:00" + "last_validated_date": "2025-11-06T12:18:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.28, + "teardown": 2.13, + "total": 23.41 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_get_item": { - "last_validated_date": "2023-05-16T20:27:38+00:00" + "last_validated_date": "2025-11-06T12:18:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.9, + "teardown": 2.12, + "total": 23.02 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_update_get_item": { - "last_validated_date": "2023-05-16T20:28:28+00:00" + "last_validated_date": "2025-11-06T12:19:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.52, + "teardown": 2.44, + "total": 22.96 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_list_secrets": { - "last_validated_date": "2023-06-22T11:59:49+00:00" + "last_validated_date": "2025-11-06T12:17:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.96, + "teardown": 1.7, + "total": 20.66 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[binary]": { - "last_validated_date": "2025-01-27T10:18:40+00:00" + "last_validated_date": "2025-11-06T12:22:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.72, + "teardown": 3.57, + "total": 19.29 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[bytearray]": { - "last_validated_date": "2025-01-27T10:18:56+00:00" + "last_validated_date": "2025-11-06T12:22:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.37, + "teardown": 3.55, + "total": 18.92 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_binary]": { - "last_validated_date": "2025-01-27T10:18:18+00:00" + "last_validated_date": "2025-11-06T12:21:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.17, + "teardown": 3.74, + "total": 19.91 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_str]": { - "last_validated_date": "2025-01-27T10:17:44+00:00" + "last_validated_date": "2025-11-06T12:21:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.53, + "teardown": 3.77, + "total": 24.3 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[str]": { - "last_validated_date": "2025-01-27T10:18:02+00:00" + "last_validated_date": "2025-11-06T12:21:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.75, + "teardown": 3.73, + "total": 19.48 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[bool]": { - "last_validated_date": "2025-01-27T10:30:01+00:00" + "last_validated_date": "2025-11-06T12:23:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.98, + "teardown": 3.22, + "total": 19.2 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[dict]": { - "last_validated_date": "2025-01-27T10:29:28+00:00" + "last_validated_date": "2025-11-06T12:23:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.81, + "teardown": 3.25, + "total": 19.06 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[list]": { - "last_validated_date": "2025-01-27T10:29:44+00:00" + "last_validated_date": "2025-11-06T12:23:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.87, + "teardown": 3.25, + "total": 19.12 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[num]": { - "last_validated_date": "2025-01-27T10:30:17+00:00" + "last_validated_date": "2025-11-06T12:23:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.76, + "teardown": 3.33, + "total": 19.09 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[str]": { - "last_validated_date": "2025-01-27T10:29:12+00:00" + "last_validated_date": "2025-11-06T12:22:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.06, + "teardown": 3.18, + "total": 18.24 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template0]": { - "last_validated_date": "2024-04-10T18:55:26+00:00" + "last_validated_date": "2025-11-06T12:19:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.23, + "teardown": 1.69, + "total": 15.92 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template1]": { - "last_validated_date": "2024-04-10T18:55:40+00:00" + "last_validated_date": "2025-11-06T12:19:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.14, + "teardown": 1.73, + "total": 14.87 + } + }, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution": { + "last_validated_date": "2025-11-06T12:20:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 26.45, + "teardown": 4.09, + "total": 30.54 + } }, "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution_implicit_json_serialisation": { - "last_validated_date": "2024-02-05T11:29:16+00:00" + "last_validated_date": "2025-11-06T12:20:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 31.74, + "teardown": 3.5, + "total": 35.24 + } } } diff --git a/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py b/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py index b198625b0f1b4..3d04c929dc7a4 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py +++ b/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py @@ -1,7 +1,9 @@ import json import pytest -from localstack_snapshot.snapshots.transformer import RegexTransformer +from localstack_snapshot.snapshots.transformer import ( + RegexTransformer, +) from localstack.testing.pytest import markers from localstack.testing.pytest.stepfunctions.utils import ( diff --git a/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.snapshot.json index a3014785e3f0e..ffcc09e68dc75 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_GET_ITEM]": { - "recorded-date": "03-02-2025, 16:31:26", + "recorded-date": "06-11-2025, 23:10:57", "recorded-content": { "get_execution_history": { "events": [ @@ -334,10 +334,10 @@ "Server": [ "Server" ], + "x-amzn-requestid": "x-amzn-requestid", "Connection": [ "keep-alive" ], - "x-amzn-RequestId": "x-amzn-RequestId", "x-amz-crc32": "x-amz-crc32", "Content-Length": [ "53" @@ -354,7 +354,7 @@ "Date": "date", "Server": "Server", "x-amz-crc32": "x-amz-crc32", - "x-amzn-RequestId": "x-amzn-RequestId" + "x-amzn-requestid": "x-amzn-requestid" }, "HttpStatusCode": 200 }, @@ -445,10 +445,10 @@ "Server": [ "Server" ], + "x-amzn-requestid": "x-amzn-requestid", "Connection": [ "keep-alive" ], - "x-amzn-RequestId": "x-amzn-RequestId", "x-amz-crc32": "x-amz-crc32", "Content-Length": [ "53" @@ -465,7 +465,7 @@ "Date": "date", "Server": "Server", "x-amz-crc32": "x-amz-crc32", - "x-amzn-RequestId": "x-amzn-RequestId" + "x-amzn-requestid": "x-amzn-requestid" }, "HttpStatusCode": 200 }, @@ -552,10 +552,10 @@ "Server": [ "Server" ], + "x-amzn-requestid": "x-amzn-requestid", "Connection": [ "keep-alive" ], - "x-amzn-RequestId": "x-amzn-RequestId", "x-amz-crc32": "x-amz-crc32", "Content-Length": [ "53" @@ -572,7 +572,7 @@ "Date": "date", "Server": "Server", "x-amz-crc32": "x-amz-crc32", - "x-amzn-RequestId": "x-amzn-RequestId" + "x-amzn-requestid": "x-amzn-requestid" }, "HttpStatusCode": 200 }, @@ -599,7 +599,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_DELETE_ITEM]": { - "recorded-date": "03-02-2025, 16:31:51", + "recorded-date": "06-11-2025, 23:11:20", "recorded-content": { "get_execution_history": { "events": [ @@ -1174,7 +1174,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_UPDATE_GET_ITEM]": { - "recorded-date": "03-02-2025, 16:34:47", + "recorded-date": "06-11-2025, 23:11:42", "recorded-content": { "get_execution_history": { "events": [ @@ -1813,10 +1813,10 @@ "Server": [ "Server" ], + "x-amzn-requestid": "x-amzn-requestid", "Connection": [ "keep-alive" ], - "x-amzn-RequestId": "x-amzn-RequestId", "x-amz-crc32": "x-amz-crc32", "Content-Length": [ "83" @@ -1833,7 +1833,7 @@ "Date": "date", "Server": "Server", "x-amz-crc32": "x-amz-crc32", - "x-amzn-RequestId": "x-amzn-RequestId" + "x-amzn-requestid": "x-amzn-requestid" }, "HttpStatusCode": 200 }, @@ -1966,10 +1966,10 @@ "Server": [ "Server" ], + "x-amzn-requestid": "x-amzn-requestid", "Connection": [ "keep-alive" ], - "x-amzn-RequestId": "x-amzn-RequestId", "x-amz-crc32": "x-amz-crc32", "Content-Length": [ "83" @@ -1986,7 +1986,7 @@ "Date": "date", "Server": "Server", "x-amz-crc32": "x-amz-crc32", - "x-amzn-RequestId": "x-amzn-RequestId" + "x-amzn-requestid": "x-amzn-requestid" }, "HttpStatusCode": 200 }, @@ -2115,10 +2115,10 @@ "Server": [ "Server" ], + "x-amzn-requestid": "x-amzn-requestid", "Connection": [ "keep-alive" ], - "x-amzn-RequestId": "x-amzn-RequestId", "x-amz-crc32": "x-amz-crc32", "Content-Length": [ "83" @@ -2135,7 +2135,7 @@ "Date": "date", "Server": "Server", "x-amz-crc32": "x-amz-crc32", - "x-amzn-RequestId": "x-amzn-RequestId" + "x-amzn-requestid": "x-amzn-requestid" }, "HttpStatusCode": 200 }, @@ -2162,7 +2162,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_invalid_integration": { - "recorded-date": "03-02-2025, 16:35:03", + "recorded-date": "06-11-2025, 23:12:21", "recorded-content": { "exception": { "exception_typename": "InvalidDefinition", @@ -2171,7 +2171,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_QUERY]": { - "recorded-date": "05-02-2025, 09:50:00", + "recorded-date": "06-11-2025, 23:12:05", "recorded-content": { "get_execution_history": { "events": [ @@ -2496,11 +2496,11 @@ "Count": 1, "Items": [ { - "data": { - "S": "HelloWorld" - }, "id": { "S": "id1" + }, + "data": { + "S": "HelloWorld" } } ], @@ -2579,11 +2579,11 @@ "Count": 1, "Items": [ { - "data": { - "S": "HelloWorld" - }, "id": { "S": "id1" + }, + "data": { + "S": "HelloWorld" } } ], @@ -2658,11 +2658,11 @@ "Count": 1, "Items": [ { - "data": { - "S": "HelloWorld" - }, "id": { "S": "id1" + }, + "data": { + "S": "HelloWorld" } } ], diff --git a/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.validation.json index 690385c45fd30..b4c669758ae13 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.validation.json @@ -1,17 +1,47 @@ { "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_DELETE_ITEM]": { - "last_validated_date": "2025-02-03T16:31:51+00:00" + "last_validated_date": "2025-11-06T23:11:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.02, + "teardown": 2.18, + "total": 23.2 + } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_GET_ITEM]": { - "last_validated_date": "2025-02-03T16:31:26+00:00" + "last_validated_date": "2025-11-06T23:10:59+00:00", + "durations_in_seconds": { + "setup": 0.78, + "call": 19.92, + "teardown": 2.12, + "total": 22.82 + } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_QUERY]": { - "last_validated_date": "2025-02-05T09:50:00+00:00" + "last_validated_date": "2025-11-06T23:12:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.5, + "teardown": 2.29, + "total": 22.79 + } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_UPDATE_GET_ITEM]": { - "last_validated_date": "2025-02-03T16:34:47+00:00" + "last_validated_date": "2025-11-06T23:11:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 20.04, + "teardown": 2.16, + "total": 22.2 + } }, "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_invalid_integration": { - "last_validated_date": "2025-02-03T16:35:03+00:00" + "last_validated_date": "2025-11-06T23:12:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.66, + "teardown": 1.23, + "total": 14.89 + } } } diff --git a/tests/aws/services/stepfunctions/v2/services/test_events_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_events_task_service.snapshot.json index 70c23a96ec4fb..4aa039fff1af8 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_events_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_events_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_base": { - "recorded-date": "12-09-2023, 10:45:20", + "recorded-date": "06-11-2025, 13:30:27", "recorded-content": { "get_execution_history": { "events": [ @@ -265,7 +265,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_malformed_detail": { - "recorded-date": "12-09-2023, 10:51:57", + "recorded-date": "06-11-2025, 12:26:47", "recorded-content": { "get_execution_history": { "events": [ @@ -384,12 +384,11 @@ "HTTPHeaders": {}, "HTTPStatusCode": 200 } - }, - "stepfunctions_events": [] + } } }, "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_no_source": { - "recorded-date": "12-09-2023, 13:17:16", + "recorded-date": "06-11-2025, 12:27:36", "recorded-content": { "get_execution_history": { "events": [ @@ -576,7 +575,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_mixed_malformed_detail": { - "recorded-date": "05-12-2024, 13:56:38", + "recorded-date": "06-11-2025, 12:27:54", "recorded-content": { "get_execution_history": { "events": [ diff --git a/tests/aws/services/stepfunctions/v2/services/test_events_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_events_task_service.validation.json index c3a3a74b82377..5c4ff19bc7be8 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_events_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_events_task_service.validation.json @@ -1,14 +1,38 @@ { "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_base": { - "last_validated_date": "2023-09-12T08:45:20+00:00" + "last_validated_date": "2025-11-06T13:30:30+00:00", + "durations_in_seconds": { + "setup": 0.68, + "call": 18.5, + "teardown": 3.05, + "total": 22.23 + } }, "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_malformed_detail": { - "last_validated_date": "2024-12-05T11:30:19+00:00" + "last_validated_date": "2025-11-06T12:26:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.84, + "teardown": 2.94, + "total": 16.78 + } }, "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_mixed_malformed_detail": { - "last_validated_date": "2024-12-05T13:56:38+00:00" + "last_validated_date": "2025-11-06T12:27:57+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.32, + "teardown": 3.56, + "total": 18.88 + } }, "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_no_source": { - "last_validated_date": "2023-09-12T11:17:16+00:00" + "last_validated_date": "2025-11-06T12:27:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 45.87, + "teardown": 2.61, + "total": 48.48 + } } } diff --git a/tests/aws/services/stepfunctions/v2/services/test_lambda_task.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_lambda_task.snapshot.json index 807a6c562aab1..677371488477a 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_lambda_task.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_lambda_task.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_pipe": { - "recorded-date": "22-06-2023, 13:34:18", + "recorded-date": "06-11-2025, 12:31:08", "recorded-content": { "get_execution_history": { "events": [ @@ -170,7 +170,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_bytes_payload": { - "recorded-date": "04-08-2023, 10:45:41", + "recorded-date": "06-11-2025, 12:28:14", "recorded-content": { "get_execution_history": { "events": [ @@ -360,7 +360,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[HelloWorld]": { - "recorded-date": "04-08-2023, 16:13:17", + "recorded-date": "06-11-2025, 12:28:52", "recorded-content": { "get_execution_history": { "events": [ @@ -455,7 +455,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0.0]": { - "recorded-date": "04-08-2023, 16:13:35", + "recorded-date": "06-11-2025, 12:29:11", "recorded-content": { "get_execution_history": { "events": [ @@ -550,7 +550,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_0]": { - "recorded-date": "04-08-2023, 16:13:53", + "recorded-date": "06-11-2025, 12:29:30", "recorded-content": { "get_execution_history": { "events": [ @@ -645,7 +645,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_1]": { - "recorded-date": "04-08-2023, 16:14:12", + "recorded-date": "06-11-2025, 12:29:49", "recorded-content": { "get_execution_history": { "events": [ @@ -740,7 +740,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[True]": { - "recorded-date": "04-08-2023, 16:14:30", + "recorded-date": "06-11-2025, 12:30:09", "recorded-content": { "get_execution_history": { "events": [ @@ -835,7 +835,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value6]": { - "recorded-date": "04-08-2023, 16:15:06", + "recorded-date": "06-11-2025, 12:30:47", "recorded-content": { "get_execution_history": { "events": [ @@ -1025,7 +1025,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value5]": { - "recorded-date": "04-08-2023, 16:14:48", + "recorded-date": "06-11-2025, 12:30:28", "recorded-content": { "get_execution_history": { "events": [ @@ -1120,7 +1120,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_lambda_task_filter_parameters_input": { - "recorded-date": "22-09-2023, 22:36:51", + "recorded-date": "06-11-2025, 12:31:27", "recorded-content": { "get_execution_history": { "events": [ @@ -1275,7 +1275,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_string_payload": { - "recorded-date": "25-03-2024, 22:25:45", + "recorded-date": "06-11-2025, 12:28:33", "recorded-content": { "get_execution_history": { "events": [ diff --git a/tests/aws/services/stepfunctions/v2/services/test_lambda_task.validation.json b/tests/aws/services/stepfunctions/v2/services/test_lambda_task.validation.json index 72dad53a757ec..3886cc534292c 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_lambda_task.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_lambda_task.validation.json @@ -1,35 +1,101 @@ { "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_bytes_payload": { - "last_validated_date": "2023-08-04T08:45:41+00:00" + "last_validated_date": "2025-11-06T12:28:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.29, + "teardown": 2.83, + "total": 19.12 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0.0]": { - "last_validated_date": "2023-08-04T14:13:35+00:00" + "last_validated_date": "2025-11-06T12:29:14+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.31, + "teardown": 2.76, + "total": 19.07 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_0]": { - "last_validated_date": "2023-08-04T14:13:53+00:00" + "last_validated_date": "2025-11-06T12:29:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.43, + "teardown": 2.9, + "total": 19.33 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_1]": { - "last_validated_date": "2023-08-04T14:14:12+00:00" + "last_validated_date": "2025-11-06T12:29:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.12, + "teardown": 2.75, + "total": 18.87 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[HelloWorld]": { - "last_validated_date": "2023-08-04T14:13:17+00:00" + "last_validated_date": "2025-11-06T12:28:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.33, + "teardown": 2.86, + "total": 19.19 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[True]": { - "last_validated_date": "2023-08-04T14:14:30+00:00" + "last_validated_date": "2025-11-06T12:30:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.36, + "teardown": 2.79, + "total": 19.15 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value5]": { - "last_validated_date": "2023-08-04T14:14:48+00:00" + "last_validated_date": "2025-11-06T12:30:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.22, + "teardown": 2.91, + "total": 19.13 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value6]": { - "last_validated_date": "2023-08-04T14:15:06+00:00" + "last_validated_date": "2025-11-06T12:30:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.3, + "teardown": 2.45, + "total": 18.75 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_pipe": { - "last_validated_date": "2023-06-22T11:34:18+00:00" + "last_validated_date": "2025-11-06T12:31:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.67, + "teardown": 3.32, + "total": 21.99 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_string_payload": { - "last_validated_date": "2024-03-25T22:25:45+00:00" + "last_validated_date": "2025-11-06T12:28:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.36, + "teardown": 2.79, + "total": 19.15 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_lambda_task_filter_parameters_input": { - "last_validated_date": "2023-09-22T20:36:51+00:00" + "last_validated_date": "2025-11-06T12:31:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.31, + "teardown": 2.94, + "total": 19.25 + } } } diff --git a/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.snapshot.json index 3bb44279ebb34..528aab071322c 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke": { - "recorded-date": "22-06-2023, 13:39:30", + "recorded-date": "06-11-2025, 12:31:47", "recorded-content": { "get_execution_history": { "events": [ @@ -79,9 +79,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -98,13 +96,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -136,9 +134,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -155,13 +151,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -190,9 +186,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -209,13 +203,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -246,9 +240,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -265,13 +257,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -288,9 +280,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -307,13 +297,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -341,9 +331,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -360,13 +348,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -383,9 +371,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -402,13 +388,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -431,7 +417,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_unsupported_param": { - "recorded-date": "22-06-2023, 14:15:28", + "recorded-date": "06-11-2025, 12:32:26", "recorded-content": { "get_execution_history": { "events": [ @@ -517,9 +503,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -537,13 +521,13 @@ "X-Amz-Executed-Version": "$LATEST", "X-Amz-Log-Result": "", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -579,9 +563,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -599,13 +581,13 @@ "X-Amz-Executed-Version": "$LATEST", "X-Amz-Log-Result": "", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -638,9 +620,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -658,13 +638,13 @@ "X-Amz-Executed-Version": "$LATEST", "X-Amz-Log-Result": "", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -699,9 +679,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -719,13 +697,13 @@ "X-Amz-Executed-Version": "$LATEST", "X-Amz-Log-Result": "", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -746,9 +724,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -766,13 +742,13 @@ "X-Amz-Executed-Version": "$LATEST", "X-Amz-Log-Result": "", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -804,9 +780,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -824,13 +798,13 @@ "X-Amz-Executed-Version": "$LATEST", "X-Amz-Log-Result": "", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -851,9 +825,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -871,13 +843,13 @@ "X-Amz-Executed-Version": "$LATEST", "X-Amz-Log-Result": "", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -900,11 +872,11 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_list_functions": { - "recorded-date": "11-05-2023, 11:29:56", + "recorded-date": "06-11-2025, 12:34:55", "recorded-content": {} }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_bytes_payload": { - "recorded-date": "04-08-2023, 10:48:27", + "recorded-date": "06-11-2025, 12:32:06", "recorded-content": { "get_execution_history": { "events": [ @@ -983,9 +955,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "13" ], @@ -1002,13 +972,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1040,9 +1010,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "13" ], @@ -1059,13 +1027,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1094,9 +1062,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "13" ], @@ -1113,13 +1079,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1150,9 +1116,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "13" ], @@ -1169,13 +1133,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -1192,9 +1156,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "13" ], @@ -1211,13 +1173,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -1245,9 +1207,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "13" ], @@ -1264,13 +1224,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -1287,9 +1247,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "13" ], @@ -1306,13 +1264,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -1766,7 +1724,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[HelloWorld]": { - "recorded-date": "04-08-2023, 16:04:28", + "recorded-date": "06-11-2025, 12:32:44", "recorded-content": { "get_execution_history": { "events": [ @@ -1845,9 +1803,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "12" ], @@ -1864,13 +1820,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1902,9 +1858,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "12" ], @@ -1921,13 +1875,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1956,9 +1910,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "12" ], @@ -1975,13 +1927,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -2012,9 +1964,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "12" ], @@ -2031,13 +1981,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -2054,9 +2004,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "12" ], @@ -2073,13 +2021,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -2107,9 +2055,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "12" ], @@ -2126,13 +2072,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -2149,9 +2095,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "12" ], @@ -2168,13 +2112,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -2197,7 +2141,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0.0]": { - "recorded-date": "04-08-2023, 16:04:46", + "recorded-date": "06-11-2025, 12:33:04", "recorded-content": { "get_execution_history": { "events": [ @@ -2276,9 +2220,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "3" ], @@ -2295,13 +2237,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -2333,9 +2275,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "3" ], @@ -2352,13 +2292,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -2387,9 +2327,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "3" ], @@ -2406,13 +2344,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -2443,9 +2381,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "3" ], @@ -2462,13 +2398,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -2485,9 +2421,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "3" ], @@ -2504,13 +2438,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -2538,9 +2472,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "3" ], @@ -2557,13 +2489,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -2580,9 +2512,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "3" ], @@ -2599,13 +2529,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -2628,7 +2558,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_0]": { - "recorded-date": "04-08-2023, 16:05:04", + "recorded-date": "06-11-2025, 12:33:23", "recorded-content": { "get_execution_history": { "events": [ @@ -2707,9 +2637,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -2726,13 +2654,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -2764,9 +2692,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -2783,13 +2709,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -2818,9 +2744,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -2837,13 +2761,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -2874,9 +2798,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -2893,13 +2815,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -2916,9 +2838,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -2935,13 +2855,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -2969,9 +2889,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -2988,13 +2906,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -3011,9 +2929,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3030,13 +2946,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -3059,7 +2975,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_1]": { - "recorded-date": "04-08-2023, 16:05:23", + "recorded-date": "06-11-2025, 12:33:42", "recorded-content": { "get_execution_history": { "events": [ @@ -3138,9 +3054,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3157,13 +3071,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -3195,9 +3109,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3214,13 +3126,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -3249,9 +3161,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3268,13 +3178,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -3305,9 +3215,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3324,13 +3232,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -3347,9 +3255,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3366,13 +3272,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -3400,9 +3306,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3419,13 +3323,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -3442,9 +3346,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "1" ], @@ -3461,13 +3363,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -3490,7 +3392,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[True]": { - "recorded-date": "04-08-2023, 16:05:41", + "recorded-date": "06-11-2025, 12:34:01", "recorded-content": { "get_execution_history": { "events": [ @@ -3569,9 +3471,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "4" ], @@ -3588,13 +3488,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -3626,9 +3526,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "4" ], @@ -3645,13 +3543,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -3680,9 +3578,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "4" ], @@ -3699,13 +3595,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -3736,9 +3632,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "4" ], @@ -3755,13 +3649,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -3778,9 +3672,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "4" ], @@ -3797,13 +3689,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -3831,9 +3723,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "4" ], @@ -3850,13 +3740,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -3873,9 +3763,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "4" ], @@ -3892,13 +3780,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -3921,7 +3809,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value6]": { - "recorded-date": "04-08-2023, 16:06:16", + "recorded-date": "06-11-2025, 12:34:40", "recorded-content": { "get_execution_history": { "events": [ @@ -4000,9 +3888,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4019,13 +3905,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -4057,9 +3943,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4076,13 +3960,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -4111,9 +3995,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4130,13 +4012,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -4167,9 +4049,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4186,13 +4066,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -4209,9 +4089,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4228,13 +4106,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -4262,9 +4140,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4281,13 +4157,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -4304,9 +4180,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4323,13 +4197,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -4783,7 +4657,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value5]": { - "recorded-date": "04-08-2023, 16:05:59", + "recorded-date": "06-11-2025, 12:34:21", "recorded-content": { "get_execution_history": { "events": [ @@ -4862,9 +4736,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4881,13 +4753,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -4919,9 +4791,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4938,13 +4808,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -4973,9 +4843,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -4992,13 +4860,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -5029,9 +4897,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -5048,13 +4914,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -5071,9 +4937,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -5090,13 +4954,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -5124,9 +4988,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -5143,13 +5005,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200, "final": { @@ -5166,9 +5028,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -5185,13 +5045,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } diff --git a/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.validation.json index b35e29ce0760f..0574ae9f68a30 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.validation.json @@ -1,33 +1,93 @@ { "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke": { - "last_validated_date": "2023-06-22T11:39:30+00:00" + "last_validated_date": "2025-11-06T12:31:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.88, + "teardown": 2.88, + "total": 19.76 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_bytes_payload": { - "last_validated_date": "2023-08-04T08:48:27+00:00" + "last_validated_date": "2025-11-06T12:32:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.23, + "teardown": 2.73, + "total": 18.96 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0.0]": { - "last_validated_date": "2023-08-04T14:04:46+00:00" + "last_validated_date": "2025-11-06T12:33:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.33, + "teardown": 2.8, + "total": 19.13 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_0]": { - "last_validated_date": "2023-08-04T14:05:04+00:00" + "last_validated_date": "2025-11-06T12:33:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.29, + "teardown": 2.79, + "total": 19.08 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_1]": { - "last_validated_date": "2023-08-04T14:05:23+00:00" + "last_validated_date": "2025-11-06T12:33:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.7, + "teardown": 2.84, + "total": 19.54 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[HelloWorld]": { - "last_validated_date": "2023-08-04T14:04:28+00:00" + "last_validated_date": "2025-11-06T12:32:47+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 16.35, + "teardown": 2.87, + "total": 19.23 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[True]": { - "last_validated_date": "2023-08-04T14:05:41+00:00" + "last_validated_date": "2025-11-06T12:34:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.45, + "teardown": 3.0, + "total": 19.45 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value5]": { - "last_validated_date": "2023-08-04T14:05:59+00:00" + "last_validated_date": "2025-11-06T12:34:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.33, + "teardown": 2.85, + "total": 19.18 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value6]": { - "last_validated_date": "2023-08-04T14:06:16+00:00" + "last_validated_date": "2025-11-06T12:34:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.16, + "teardown": 2.83, + "total": 18.99 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_unsupported_param": { - "last_validated_date": "2023-06-22T12:15:28+00:00" + "last_validated_date": "2025-11-06T12:32:28+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.45, + "teardown": 2.34, + "total": 18.79 + } }, "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_list_functions": { "last_validated_date": "2023-05-11T09:29:56+00:00" diff --git a/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.snapshot.json index 6bdef2214ab02..7f81bba580e9c 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution": { - "recorded-date": "28-06-2023, 11:07:54", + "recorded-date": "06-11-2025, 12:35:23", "recorded-content": { "get_execution_history": { "events": [ @@ -72,14 +72,12 @@ "ExecutionArn": "arn::states::111111111111:execution::TestStartTarget", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], "Content-Length": [ - "160" + "164" ], "Date": "date", "Content-Type": [ @@ -88,15 +86,15 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "160", + "Content-Length": "164", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StartDate": "start-date" }, @@ -118,14 +116,12 @@ "ExecutionArn": "arn::states::111111111111:execution::TestStartTarget", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], "Content-Length": [ - "160" + "164" ], "Date": "date", "Content-Type": [ @@ -134,15 +130,15 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "160", + "Content-Length": "164", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StartDate": "start-date" }, @@ -159,14 +155,12 @@ "ExecutionArn": "arn::states::111111111111:execution::TestStartTarget", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], "Content-Length": [ - "160" + "164" ], "Date": "date", "Content-Type": [ @@ -175,15 +169,15 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "160", + "Content-Length": "164", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StartDate": "start-date" }, @@ -205,7 +199,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution_input_json": { - "recorded-date": "28-06-2023, 11:12:13", + "recorded-date": "06-11-2025, 12:35:53", "recorded-content": { "get_execution_history": { "events": [ @@ -283,14 +277,12 @@ "ExecutionArn": "arn::states::111111111111:execution::TestStartTarget", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], "Content-Length": [ - "161" + "163" ], "Date": "date", "Content-Type": [ @@ -299,15 +291,15 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "161", + "Content-Length": "163", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StartDate": "start-date" }, @@ -329,14 +321,12 @@ "ExecutionArn": "arn::states::111111111111:execution::TestStartTarget", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], "Content-Length": [ - "161" + "163" ], "Date": "date", "Content-Type": [ @@ -345,15 +335,15 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "161", + "Content-Length": "163", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StartDate": "start-date" }, @@ -370,14 +360,12 @@ "ExecutionArn": "arn::states::111111111111:execution::TestStartTarget", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], "Content-Length": [ - "161" + "163" ], "Date": "date", "Content-Type": [ @@ -386,15 +374,15 @@ }, "HttpHeaders": { "connection": "keep-alive", - "Content-Length": "161", + "Content-Length": "163", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StartDate": "start-date" }, diff --git a/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.validation.json index 523e1dd819bc6..e9e1d958d873f 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.validation.json @@ -1,8 +1,20 @@ { "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution": { - "last_validated_date": "2023-06-28T09:07:54+00:00" + "last_validated_date": "2025-11-06T12:35:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 26.43, + "teardown": 3.49, + "total": 29.92 + } }, "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution_input_json": { - "last_validated_date": "2023-06-28T09:12:13+00:00" + "last_validated_date": "2025-11-06T12:35:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 26.93, + "teardown": 3.38, + "total": 30.31 + } } } diff --git a/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.snapshot.json index 37813b499bd00..2eb900d45cb18 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[HelloWorld]": { - "recorded-date": "03-09-2023, 13:33:23", + "recorded-date": "06-11-2025, 12:36:44", "recorded-content": { "get_execution_history": { "events": [ @@ -75,8 +75,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -87,15 +88,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -116,8 +118,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -128,15 +131,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -152,8 +156,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -164,15 +169,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -193,7 +199,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[message1]": { - "recorded-date": "03-09-2023, 13:33:39", + "recorded-date": "06-11-2025, 12:37:01", "recorded-content": { "get_execution_history": { "events": [ @@ -268,8 +274,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -280,15 +287,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -309,8 +317,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -321,15 +330,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -345,8 +355,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -357,15 +368,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -386,7 +398,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[1]": { - "recorded-date": "03-09-2023, 13:33:55", + "recorded-date": "06-11-2025, 12:37:17", "recorded-content": { "get_execution_history": { "events": [ @@ -461,8 +473,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -473,15 +486,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -502,8 +516,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -514,15 +529,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -538,8 +554,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -550,15 +567,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -579,7 +597,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[True]": { - "recorded-date": "03-09-2023, 13:34:10", + "recorded-date": "06-11-2025, 12:37:34", "recorded-content": { "get_execution_history": { "events": [ @@ -654,8 +672,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -666,15 +685,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -695,8 +715,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -707,15 +728,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -731,8 +753,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -743,15 +766,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -772,7 +796,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[None]": { - "recorded-date": "03-09-2023, 13:34:25", + "recorded-date": "06-11-2025, 12:37:50", "recorded-content": { "get_execution_history": { "events": [ @@ -847,8 +871,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -859,15 +884,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -888,8 +914,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -900,15 +927,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -924,8 +952,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -936,15 +965,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -965,7 +995,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[]": { - "recorded-date": "03-09-2023, 13:34:40", + "recorded-date": "06-11-2025, 12:38:06", "recorded-content": { "get_execution_history": { "events": [ @@ -1040,8 +1070,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -1052,15 +1083,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1081,8 +1113,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -1093,15 +1126,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1117,8 +1151,9 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" + "x-amzn-RequestId": "x-amzn-RequestId", + "connection": [ + "keep-alive" ], "Content-Length": [ "294" @@ -1129,15 +1164,16 @@ ] }, "HttpHeaders": { + "connection": "keep-alive", "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1359,7 +1395,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base_error_topic_arn": { - "recorded-date": "03-09-2023, 13:35:10", + "recorded-date": "06-11-2025, 12:39:31", "recorded-content": { "get_execution_history": { "events": [ @@ -1457,7 +1493,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[HelloWorld]": { - "recorded-date": "27-01-2025, 07:07:04", + "recorded-date": "06-11-2025, 12:38:22", "recorded-content": { "get_execution_history": { "events": [ @@ -1544,9 +1580,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -1563,12 +1597,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1589,9 +1623,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -1608,12 +1640,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1629,9 +1661,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -1648,12 +1678,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1701,7 +1731,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[\"HelloWorld\"]": { - "recorded-date": "27-01-2025, 07:07:20", + "recorded-date": "06-11-2025, 12:38:40", "recorded-content": { "get_execution_history": { "events": [ @@ -1788,9 +1818,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -1807,12 +1835,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1833,9 +1861,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -1852,12 +1878,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1873,9 +1899,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -1892,12 +1916,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -1945,7 +1969,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[{}]": { - "recorded-date": "27-01-2025, 07:07:36", + "recorded-date": "06-11-2025, 12:38:58", "recorded-content": { "get_execution_history": { "events": [ @@ -2032,9 +2056,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2051,12 +2073,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -2077,9 +2099,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2096,12 +2116,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -2117,9 +2137,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2136,12 +2154,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -2189,7 +2207,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[message_value3]": { - "recorded-date": "27-01-2025, 07:07:54", + "recorded-date": "06-11-2025, 12:39:15", "recorded-content": { "get_execution_history": { "events": [ @@ -2276,9 +2294,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2295,12 +2311,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -2321,9 +2337,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2340,12 +2354,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -2361,9 +2375,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2380,12 +2392,12 @@ "Content-Length": "294", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -2433,7 +2445,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params0-True]": { - "recorded-date": "08-02-2024, 10:06:30", + "recorded-date": "06-11-2025, 12:36:11", "recorded-content": { "get_execution_history": { "events": [ @@ -2531,7 +2543,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params1-False]": { - "recorded-date": "08-02-2024, 10:06:49", + "recorded-date": "06-11-2025, 12:36:27", "recorded-content": { "get_execution_history": { "events": [ @@ -2612,9 +2624,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2631,12 +2641,12 @@ "Content-Length": "352", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "SequenceNumber": "" }, @@ -2658,9 +2668,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2677,12 +2685,12 @@ "Content-Length": "352", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "SequenceNumber": "" }, @@ -2699,9 +2707,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -2718,12 +2724,12 @@ "Content-Length": "352", "Content-Type": "text/xml", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "SequenceNumber": "" }, diff --git a/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.validation.json index a53bb3fba34a7..b09febda41f1d 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_sns_task_service.validation.json @@ -1,44 +1,122 @@ { "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params0-True]": { - "last_validated_date": "2024-02-08T10:06:30+00:00" + "last_validated_date": "2025-11-06T12:36:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.22, + "teardown": 2.48, + "total": 16.7 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params1-False]": { - "last_validated_date": "2024-02-08T10:06:49+00:00" + "last_validated_date": "2025-11-06T12:36:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.29, + "teardown": 2.44, + "total": 16.73 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[1]": { - "last_validated_date": "2023-09-03T11:33:55+00:00" + "last_validated_date": "2025-11-06T12:37:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.59, + "teardown": 2.46, + "total": 17.05 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[HelloWorld]": { - "last_validated_date": "2023-09-03T11:33:23+00:00" + "last_validated_date": "2025-11-06T12:36:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.28, + "teardown": 2.35, + "total": 16.63 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[None]": { - "last_validated_date": "2023-09-03T11:34:25+00:00" + "last_validated_date": "2025-11-06T12:37:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.16, + "teardown": 2.37, + "total": 15.53 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[True]": { - "last_validated_date": "2023-09-03T11:34:10+00:00" + "last_validated_date": "2025-11-06T12:37:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.4, + "teardown": 2.47, + "total": 16.87 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[]": { - "last_validated_date": "2023-09-03T11:34:40+00:00" + "last_validated_date": "2025-11-06T12:38:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.17, + "teardown": 2.33, + "total": 15.5 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[message1]": { - "last_validated_date": "2023-09-03T11:33:39+00:00" + "last_validated_date": "2025-11-06T12:37:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.23, + "teardown": 2.13, + "total": 16.36 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base_error_topic_arn": { - "last_validated_date": "2023-09-03T11:35:10+00:00" + "last_validated_date": "2025-11-06T12:39:34+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.84, + "teardown": 2.38, + "total": 16.22 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes": { "last_validated_date": "2023-09-03T11:34:55+00:00" }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[\"HelloWorld\"]": { - "last_validated_date": "2025-01-27T07:07:20+00:00" + "last_validated_date": "2025-11-06T12:38:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.15, + "teardown": 2.7, + "total": 17.85 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[HelloWorld]": { - "last_validated_date": "2025-01-27T07:07:04+00:00" + "last_validated_date": "2025-11-06T12:38:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.49, + "teardown": 2.56, + "total": 17.05 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[message_value3]": { - "last_validated_date": "2025-01-27T07:07:54+00:00" + "last_validated_date": "2025-11-06T12:39:17+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.07, + "teardown": 2.67, + "total": 16.74 + } }, "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[{}]": { - "last_validated_date": "2025-01-27T07:07:36+00:00" + "last_validated_date": "2025-11-06T12:39:01+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.14, + "teardown": 2.61, + "total": 17.75 + } } } diff --git a/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.snapshot.json b/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.snapshot.json index 6fb95be414ef4..d1cd8af6569ec 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message": { - "recorded-date": "18-04-2024, 06:37:09", + "recorded-date": "06-11-2025, 12:39:48", "recorded-content": { "get_execution_history": { "events": [ @@ -70,9 +70,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -89,12 +87,12 @@ "Content-Length": "106", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -116,9 +114,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -135,12 +131,12 @@ "Content-Length": "106", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -157,9 +153,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -176,12 +170,12 @@ "Content-Length": "106", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -202,7 +196,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_unsupported_parameters": { - "recorded-date": "18-04-2024, 06:37:24", + "recorded-date": "06-11-2025, 12:40:04", "recorded-content": { "get_execution_history": { "events": [ @@ -278,9 +272,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -297,12 +289,12 @@ "Content-Length": "106", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -324,9 +316,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -343,12 +333,12 @@ "Content-Length": "106", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -365,9 +355,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -384,12 +372,12 @@ "Content-Length": "106", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -410,7 +398,7 @@ } }, "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_attributes": { - "recorded-date": "22-08-2024, 10:04:56", + "recorded-date": "06-11-2025, 12:40:20", "recorded-content": { "get_execution_history": { "events": [ @@ -495,9 +483,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -514,12 +500,12 @@ "Content-Length": "166", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -542,9 +528,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -561,12 +545,12 @@ "Content-Length": "166", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { @@ -584,9 +568,7 @@ "MessageId": "", "SdkHttpMetadata": { "AllHttpHeaders": { - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "connection": [ "keep-alive" ], @@ -603,12 +585,12 @@ "Content-Length": "166", "Content-Type": "application/x-amz-json-1.0", "Date": "date", - "x-amzn-RequestId": "" + "x-amzn-RequestId": "x-amzn-RequestId" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" } }, "outputDetails": { diff --git a/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.validation.json b/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.validation.json index 24c2546956eb3..1dee24dc90729 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.validation.json +++ b/tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.validation.json @@ -1,11 +1,29 @@ { "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message": { - "last_validated_date": "2024-08-21T15:47:00+00:00" + "last_validated_date": "2025-11-06T12:39:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.54, + "teardown": 1.92, + "total": 16.46 + } }, "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_attributes": { - "last_validated_date": "2024-08-22T10:04:56+00:00" + "last_validated_date": "2025-11-06T12:40:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.51, + "teardown": 2.57, + "total": 17.08 + } }, "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_unsupported_parameters": { - "last_validated_date": "2024-04-18T06:37:24+00:00" + "last_validated_date": "2025-11-06T12:40:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.29, + "teardown": 1.62, + "total": 15.91 + } } } diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api.py b/tests/aws/services/stepfunctions/v2/test_sfn_api.py index b46ec48bd3361..96f7300258a35 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api.py @@ -316,7 +316,7 @@ def test_list_sms( f"statemachine_2_{short_uid()}", f"statemachine_3_{short_uid()}", ] - state_machine_arns = list() + state_machine_arns = [] for i, sm_name in enumerate(sm_names): creation_resp = create_state_machine( @@ -371,7 +371,7 @@ def test_list_sms_pagination( definition_str = json.dumps(definition) sm_names = [f"statemachine_{i}_{short_uid()}" for i in range(13)] - state_machine_arns = list() + state_machine_arns = [] for i, sm_name in enumerate(sm_names): creation_resp = create_state_machine( @@ -646,9 +646,9 @@ def test_list_executions_pagination( state_machine_arn=state_machine_arn, ) - execution_arns = list() + execution_arns = [] for i in range(13): - input_data = json.dumps(dict()) + input_data = json.dumps({}) exec_resp = aws_client.stepfunctions.start_execution( stateMachineArn=state_machine_arn, input=input_data @@ -759,9 +759,9 @@ def test_list_executions_versions_pagination( state_machine_version_arn=state_machine_version_arn, ) - execution_arns = list() + execution_arns = [] for i in range(13): - input_data = json.dumps(dict()) + input_data = json.dumps({}) exec_resp = aws_client.stepfunctions.start_execution( stateMachineArn=state_machine_version_arn, input=input_data diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py index b1c1b100a9316..20ddd61043ce4 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py @@ -303,7 +303,7 @@ def test_error_create_alias_invalid_router_configs( ) state_machine_arn = create_state_machine_response["stateMachineArn"] - state_machine_version_arns: list[Arn] = list() + state_machine_version_arns: list[Arn] = [] state_machine_version_arns.append(create_state_machine_response["stateMachineVersionArn"]) for version_number in range(2): definition["Comment"] = f"Definition for version {version_number}" @@ -531,7 +531,7 @@ def test_base_lifecycle_create_delete_list( sfn_snapshot.add_transformer( RegexTransformer(state_machine_alias_base_name, "state_machine_alias_base_name") ) - state_machine_alias_arns: list[str] = list() + state_machine_alias_arns: list[str] = [] for num in range(3): state_machine_alias_name = f"{state_machine_alias_base_name}-{num}" create_state_machine_alias_response = create_state_machine_alias( @@ -788,7 +788,7 @@ def test_base_lifecycle_create_update_describe( ) state_machine_arn = create_state_machine_response["stateMachineArn"] - state_machine_version_arns: list[Arn] = list() + state_machine_version_arns: list[Arn] = [] state_machine_version_arns.append(create_state_machine_response["stateMachineVersionArn"]) for version_number in range(2): definition["Comment"] = f"Definition for version {version_number}" diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py index c193ee4432cb7..fc594475787d9 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py @@ -77,7 +77,7 @@ def test_start_async_describe_history_execution( ): definition = ServicesTemplates.load_sfn_template(BaseTemplate.BASE_PASS_RESULT) definition_str = json.dumps(definition) - execution_input = json.dumps(dict()) + execution_input = json.dumps({}) state_machine_arn, execution_arn = create_and_record_express_async_execution( aws_client, create_state_machine_iam_role, diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py index 75d6af6532d87..f349be85afd5d 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py @@ -33,7 +33,7 @@ ] _TEST_INCOMPLETE_LOGGING_CONFIGURATIONS = [ LoggingConfiguration(), - LoggingConfiguration(destinations=list()), + LoggingConfiguration(destinations=[]), ] diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py index 01bbd45143709..c13bafeea3988 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py @@ -34,8 +34,18 @@ def test_validate_state_machine_definition_not_a_definition( @pytest.mark.parametrize( "validation_template", - [ValidationTemplate.VALID_BASE_PASS, ValidationTemplate.INVALID_BASE_NO_STARTAT], - ids=["VALID_BASE_PASS", "INVALID_BASE_NO_STARTAT"], + [ + ValidationTemplate.VALID_BASE_PASS, + ValidationTemplate.INVALID_DOWNGRADE_QUERY_LANGUAGE, + ValidationTemplate.VALID_QUERY_LANGUAGE_PASS, + ValidationTemplate.INVALID_BASE_NO_STARTAT, + ], + ids=[ + "VALID_BASE_PASS", + "INVALID_DOWNGRADE_QUERY_LANGUAGE", + "VALID_QUERY_LANGUAGE_PASS", + "INVALID_BASE_NO_STARTAT", + ], ) @markers.aws.validated def test_validate_state_machine_definition_type_standard( @@ -53,9 +63,17 @@ def test_validate_state_machine_definition_type_standard( [ ValidationTemplate.VALID_BASE_PASS, ValidationTemplate.INVALID_BASE_NO_STARTAT, + ValidationTemplate.INVALID_DOWNGRADE_QUERY_LANGUAGE, + ValidationTemplate.VALID_QUERY_LANGUAGE_PASS, CallbackTemplates.SQS_WAIT_FOR_TASK_TOKEN, ], - ids=["VALID_BASE_PASS", "INVALID_BASE_NO_STARTAT", "ILLEGAL_WFTT"], + ids=[ + "VALID_BASE_PASS", + "INVALID_BASE_NO_STARTAT", + "INVALID_DOWNGRADE_QUERY_LANGUAGE", + "VALID_QUERY_LANGUAGE_PASS", + "ILLEGAL_WFTT", + ], ) @markers.aws.validated def test_validate_state_machine_definition_type_express( diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.snapshot.json b/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.snapshot.json index 1a66526efd53b..4c5acae6bb169 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_STRING]": { - "recorded-date": "09-10-2024, 08:51:57", + "recorded-date": "19-10-2025, 02:38:10", "recorded-content": { "validation_response": { "diagnostics": [ @@ -20,7 +20,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[NOT_A_DEF]": { - "recorded-date": "09-10-2024, 08:51:58", + "recorded-date": "19-10-2025, 02:38:10", "recorded-content": { "validation_response": { "diagnostics": [ @@ -41,7 +41,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_DICT]": { - "recorded-date": "09-10-2024, 08:51:58", + "recorded-date": "19-10-2025, 02:38:10", "recorded-content": { "validation_response": { "diagnostics": [ @@ -62,7 +62,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_BASE_PASS]": { - "recorded-date": "09-10-2024, 08:51:58", + "recorded-date": "19-10-2025, 02:38:11", "recorded-content": { "validation_response": { "diagnostics": [], @@ -76,7 +76,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_BASE_NO_STARTAT]": { - "recorded-date": "09-10-2024, 08:51:58", + "recorded-date": "19-10-2025, 02:38:11", "recorded-content": { "validation_response": { "diagnostics": [ @@ -97,7 +97,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_BASE_PASS]": { - "recorded-date": "09-10-2024, 08:51:58", + "recorded-date": "19-10-2025, 02:38:11", "recorded-content": { "validation_response": { "diagnostics": [], @@ -111,7 +111,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_BASE_NO_STARTAT]": { - "recorded-date": "09-10-2024, 08:51:58", + "recorded-date": "19-10-2025, 02:38:11", "recorded-content": { "validation_response": { "diagnostics": [ @@ -132,7 +132,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[ILLEGAL_WFTT]": { - "recorded-date": "09-10-2024, 08:51:58", + "recorded-date": "19-10-2025, 02:38:12", "recorded-content": { "validation_response": { "diagnostics": [ @@ -151,5 +151,75 @@ } } } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_QUERY_LANGUAGE_PASS]": { + "recorded-date": "19-10-2025, 02:38:11", + "recorded-content": { + "validation_response": { + "diagnostics": [], + "result": "OK", + "truncated": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_DOWNGRADE_QUERY_LANGUAGE]": { + "recorded-date": "19-10-2025, 02:38:11", + "recorded-content": { + "validation_response": { + "diagnostics": [ + { + "code": "SCHEMA_VALIDATION_FAILED", + "location": "/States/Pass", + "message": "'QueryLanguage' can not be 'JSONPath' if set to 'JSONata' for whole state machine", + "severity": "ERROR" + } + ], + "result": "FAIL", + "truncated": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_DOWNGRADE_QUERY_LANGUAGE]": { + "recorded-date": "19-10-2025, 02:38:11", + "recorded-content": { + "validation_response": { + "diagnostics": [ + { + "code": "SCHEMA_VALIDATION_FAILED", + "location": "/States/Pass", + "message": "'QueryLanguage' can not be 'JSONPath' if set to 'JSONata' for whole state machine", + "severity": "ERROR" + } + ], + "result": "FAIL", + "truncated": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_QUERY_LANGUAGE_PASS]": { + "recorded-date": "19-10-2025, 02:38:11", + "recorded-content": { + "validation_response": { + "diagnostics": [], + "result": "OK", + "truncated": false, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.validation.json b/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.validation.json index 5e40a3ce68487..5142bb49a8533 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.validation.json +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_validation.validation.json @@ -1,26 +1,110 @@ { "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_DICT]": { - "last_validated_date": "2024-10-09T08:51:58+00:00" + "last_validated_date": "2025-10-19T02:38:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_STRING]": { - "last_validated_date": "2024-10-09T08:51:57+00:00" + "last_validated_date": "2025-10-19T02:38:10+00:00", + "durations_in_seconds": { + "setup": 1.14, + "call": 0.58, + "teardown": 0.0, + "total": 1.72 + } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[NOT_A_DEF]": { - "last_validated_date": "2024-10-09T08:51:58+00:00" + "last_validated_date": "2025-10-19T02:38:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.0, + "total": 0.13 + } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[ILLEGAL_WFTT]": { - "last_validated_date": "2024-10-09T08:51:58+00:00" + "last_validated_date": "2025-10-19T02:38:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_BASE_NO_STARTAT]": { - "last_validated_date": "2024-10-09T08:51:58+00:00" + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.13, + "teardown": 0.0, + "total": 0.13 + } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_DOWNGRADE_QUERY_LANGUAGE]": { + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_BASE_PASS]": { - "last_validated_date": "2024-10-09T08:51:58+00:00" + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_QUERY_LANGUAGE_PASS]": { + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_BASE_NO_STARTAT]": { - "last_validated_date": "2024-10-09T08:51:58+00:00" + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.12, + "teardown": 0.0, + "total": 0.12 + } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_DOWNGRADE_QUERY_LANGUAGE]": { + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.1, + "teardown": 0.0, + "total": 0.1 + } }, "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_BASE_PASS]": { - "last_validated_date": "2024-10-09T08:51:58+00:00" + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } + }, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_QUERY_LANGUAGE_PASS]": { + "last_validated_date": "2025-10-19T02:38:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.11, + "teardown": 0.0, + "total": 0.11 + } } } diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py index 2a9e7dd020e8e..1ffc74f4f3c84 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py @@ -192,7 +192,7 @@ def test_list_state_machine_versions_pagination( sfn_snapshot.match("creation_resp_1", creation_resp_1) state_machine_arn = creation_resp_1["stateMachineArn"] - state_machine_version_arns = list() + state_machine_version_arns = [] for revision_no in range(1, 14): definition["Comment"] = f"{definition['Comment']}-R{revision_no}" definition_raw_str = json.dumps(definition) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py new file mode 100644 index 0000000000000..d01ce521dbc2a --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py @@ -0,0 +1,355 @@ +import json + +import pytest + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.aws.util import is_aws_cloud +from localstack.testing.pytest import markers +from localstack.utils.aws import arns +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateTemplate as TST, +) + + +class TestFieldValidationMode: + """ + + TODO test cases and edge cases found out in the wild that are good candidates for tests + - lambda response payload is not a string but a JSON - results in a validation error against the API spec + - somehow in SQS SendMessage response the following mock {"MD5OfMessageBody": {"unexpected": ["object"]}} while the API spec requires {"MD5OfMessageBody": "string"}. + At the same time, MessageId is validated to be a string. Both fields are in the API spec and both are strings there. + """ + + EVENTBRIDGE_VALIDATION_PASS_FIELDS_NOT_IN_SPEC = [ + pytest.param({"random": "json"}, id="field_not_part_of_api_spec"), + pytest.param({}, id="empty_json"), + # pytest.param("Hello World", id="simple_string"), TODO validate these general failure modes in a validation parity test + # pytest.param(42, id="simple_number"), + # pytest.param(["a", "b", "c"], id="simple_list"), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", EVENTBRIDGE_VALIDATION_PASS_FIELDS_NOT_IN_SPEC) + def test_strict_mode_mock_result_field_not_in_api_spec( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + """ + Only fields from the API spec are validated + """ + template = TST.load_sfn_template(TST.BASE_EVENTS_PUT_EVENTS_TASK_STATE) + definition = json.dumps(template) + + entries = [ + { + "Detail": "detail", + "DetailType": "detail_type", + "Source": "source", + }, + ] + exec_input = json.dumps({"Entries": entries}) + + mock = {"result": json.dumps(result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + EVENTBRIDGE_VAlIDATION_ERRORS = [ + pytest.param( + {"Entries": "Entries value should have been a list of entry objects"}, + id="wrong_type_top_level_field", + ), + pytest.param( + { + "Entries": [ + {"EventId": ["EventId value should have been a string not a list of strings"]} + ] + }, + id="wrong_type_nested_field", + ), + pytest.param( + { + "Entries": [ + {"EventId": "First eventId has correct type: string"}, + {"EventId": ["Second eventId has incorrect type: array"]}, + ] + }, + id="wrong_type_2nd_array_element", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", EVENTBRIDGE_VAlIDATION_ERRORS) + def test_strict_mode_eventbridge_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_EVENTS_PUT_EVENTS_TASK_STATE) + definition = json.dumps(template) + + entries = [ + { + "Detail": "detail", + "DetailType": "detail_type", + "Source": "source", + }, + ] + exec_input = json.dumps({"Entries": entries}) + + mock = {"result": json.dumps(result)} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + SFN_VALIDATION_ERRORS = [ + pytest.param( + {}, + id="missing_required_fields", + ), + pytest.param( + {"executionArn": "stringValueExecutionArn", "startDate": "stringValueStartDate"}, + id="field_name_not_in_sfn_case", + ), + pytest.param( + {"ExecutionArn": "stringValueExecutionArn", "StartDate": True}, + id="wrong_timestamp_type_bool", + ), + pytest.param( + {"ExecutionArn": "stringValueExecutionArn", "StartDate": 1764103483}, + id="wrong_timestamp_type_number", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", SFN_VALIDATION_ERRORS) + def test_strict_mode_sfn_task( + self, + aws_client, + aws_client_no_sync_prefix, + account_id, + region_name, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_SFN_START_EXECUTION_TASK_STATE) + definition = json.dumps(template) + + target_state_machine_arn = arns.stepfunctions_state_machine_arn( + name="TargetStateMachine", account_id=account_id, region_name=region_name + ) + + target_execution_name = "TestStartTarget" + + exec_input = json.dumps( + { + "stateMachineArn": target_state_machine_arn, + "targetInput": None, + "name": target_execution_name, + } + ) + + mock = {"result": json.dumps(result)} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + LAMBDA_VALIDATION_ERRORS = [ + pytest.param( + {"StatusCode": "200"}, + id="wrong_integer_type_string", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", LAMBDA_VALIDATION_ERRORS) + def test_strict_mode_lambda_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_LAMBDA_SERVICE_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"FunctionName": "function_name", "Payload": None}) + + mock = {"result": json.dumps(result)} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + DYNAMODB_VALIDATION_ERRORS = [ + pytest.param( + {"ConsumedCapacity": {"CapacityUnits": "123.45"}}, + id="wrong_float_type_string", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", DYNAMODB_VALIDATION_ERRORS) + def test_strict_mode_dynamodb_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_DYNAMODB_SERVICE_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps( + { + "TableName": "table_name", + "Item": {"data": {"S": "HelloWorld"}, "id": {"S": "id1"}}, + } + ) + + mock = {"result": json.dumps(result)} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + AWS_SDK_S3_VALIDATION_ERRORS = [ + pytest.param( + {"ContentLength": "9000"}, + id="wrong_long_type_string", + ), + pytest.param( + {"StorageClass": "Papyrus"}, + id="non_existent_enum_value", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", AWS_SDK_S3_VALIDATION_ERRORS) + def test_strict_mode_aws_sdk_s3_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_AWS_SDK_S3_GET_OBJECT_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"Bucket": "bucket_name", "Key": "file_key"}) + + mock = {"result": json.dumps(result)} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + AWS_SDK_KMS_VALIDATION_ERRORS = [ + pytest.param( + {"CiphertextBlob": 123}, + id="wrong_blob_type_number", + ), + pytest.param( + {"CiphertextBlob": ""}, + id="wrong_blob_length_less_than_minimum", + marks=pytest.mark.skipif( + condition=not is_aws_cloud(), + reason="string value length validation is not implemented yet", + ), # TODO implement value length validation + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", AWS_SDK_KMS_VALIDATION_ERRORS) + def test_strict_mode_aws_sdk_kms_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_AWS_SDK_KMS_ENCRYPT_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"KeyId": "key_id", "Plaintext": "plain_text"}) + + mock = {"result": json.dumps(result)} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + AWS_SDK_LAMBDA_GET_FUNCTION_VALIDATION_ERRORS = [ + pytest.param( + {"Configuration": {"MemorySize": 127}}, + id="integer_less_than_minimum_allowed", + ), + pytest.param( + {"Configuration": {"MemorySize": 10241}}, + id="integer_more_than_maximum_allowed", + ), + ] + + @pytest.mark.skipif( + condition=not is_aws_cloud(), reason="number value range validation is not implemented yet" + ) # TODO implement number value range validation + @markers.aws.validated + @pytest.mark.parametrize("result", AWS_SDK_LAMBDA_GET_FUNCTION_VALIDATION_ERRORS) + def test_strict_mode_aws_sdk_lambda_get_function_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_AWS_SDK_LAMBDA_GET_FUNCTION) + definition = json.dumps(template) + exec_input = json.dumps({"FunctionName": "function_name"}) + + mock = {"result": json.dumps(result)} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.snapshot.json new file mode 100644 index 0000000000000..9cfb658b6091c --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.snapshot.json @@ -0,0 +1,270 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_mock_result_field_not_in_api_spec[field_not_part_of_api_spec]": { + "recorded-date": "03-12-2025, 01:10:10", + "recorded-content": { + "test_case_response": { + "output": { + "random": "json" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_mock_result_field_not_in_api_spec[empty_json]": { + "recorded-date": "03-12-2025, 01:10:10", + "recorded-content": { + "test_case_response": { + "output": {}, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_eventbridge_task[wrong_type_top_level_field]": { + "recorded-date": "03-12-2025, 01:10:10", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'Entries' must be an array" + }, + "message": "Mock result schema validation error: Field 'Entries' must be an array", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_eventbridge_task[wrong_type_nested_field]": { + "recorded-date": "03-12-2025, 01:10:10", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'EventId' must be a string" + }, + "message": "Mock result schema validation error: Field 'EventId' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_eventbridge_task[wrong_type_2nd_array_element]": { + "recorded-date": "03-12-2025, 01:10:10", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'EventId' must be a string" + }, + "message": "Mock result schema validation error: Field 'EventId' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[missing_required_fields]": { + "recorded-date": "03-12-2025, 01:10:11", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Required field 'ExecutionArn' is missing" + }, + "message": "Mock result schema validation error: Required field 'ExecutionArn' is missing", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[field_name_not_in_sfn_case]": { + "recorded-date": "03-12-2025, 01:10:11", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Required field 'ExecutionArn' is missing" + }, + "message": "Mock result schema validation error: Required field 'ExecutionArn' is missing", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[wrong_timestamp_type_bool]": { + "recorded-date": "03-12-2025, 01:10:11", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'StartDate' must be a string" + }, + "message": "Mock result schema validation error: Field 'StartDate' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[wrong_timestamp_type_number]": { + "recorded-date": "03-12-2025, 01:10:11", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'StartDate' must be a string" + }, + "message": "Mock result schema validation error: Field 'StartDate' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_lambda_task[wrong_integer_type_string]": { + "recorded-date": "03-12-2025, 01:10:11", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'StatusCode' must be a number" + }, + "message": "Mock result schema validation error: Field 'StatusCode' must be a number", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_dynamodb_task[wrong_float_type_string]": { + "recorded-date": "03-12-2025, 01:10:12", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'CapacityUnits' must be a number" + }, + "message": "Mock result schema validation error: Field 'CapacityUnits' must be a number", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_s3_task[wrong_long_type_string]": { + "recorded-date": "03-12-2025, 01:10:12", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'ContentLength' must be a number" + }, + "message": "Mock result schema validation error: Field 'ContentLength' must be a number", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_s3_task[non_existent_enum_value]": { + "recorded-date": "03-12-2025, 01:10:12", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'StorageClass' is not an expected value" + }, + "message": "Mock result schema validation error: Field 'StorageClass' is not an expected value", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_kms_task[wrong_blob_type_number]": { + "recorded-date": "03-12-2025, 01:10:12", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'CiphertextBlob' must be a string" + }, + "message": "Mock result schema validation error: Field 'CiphertextBlob' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_kms_task[wrong_blob_length_less_than_minimum]": { + "recorded-date": "03-12-2025, 01:10:13", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'CiphertextBlob' is smaller than the minimum length" + }, + "message": "Mock result schema validation error: Field 'CiphertextBlob' is smaller than the minimum length", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_lambda_get_function_task[integer_less_than_minimum_allowed]": { + "recorded-date": "03-12-2025, 01:10:13", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'MemorySize' is less than the minimum value" + }, + "message": "Mock result schema validation error: Field 'MemorySize' is less than the minimum value", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_lambda_get_function_task[integer_more_than_maximum_allowed]": { + "recorded-date": "03-12-2025, 01:10:13", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'MemorySize' exceeds the maximum value" + }, + "message": "Mock result schema validation error: Field 'MemorySize' exceeds the maximum value", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.validation.json new file mode 100644 index 0000000000000..8175803528190 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.validation.json @@ -0,0 +1,155 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_kms_task[wrong_blob_length_less_than_minimum]": { + "last_validated_date": "2025-12-03T01:10:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.24, + "teardown": 0.0, + "total": 0.24 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_kms_task[wrong_blob_type_number]": { + "last_validated_date": "2025-12-03T01:10:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_lambda_get_function_task[integer_less_than_minimum_allowed]": { + "last_validated_date": "2025-12-03T01:10:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_lambda_get_function_task[integer_more_than_maximum_allowed]": { + "last_validated_date": "2025-12-03T01:10:13+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_s3_task[non_existent_enum_value]": { + "last_validated_date": "2025-12-03T01:10:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.28, + "teardown": 0.0, + "total": 0.28 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_aws_sdk_s3_task[wrong_long_type_string]": { + "last_validated_date": "2025-12-03T01:10:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_dynamodb_task[wrong_float_type_string]": { + "last_validated_date": "2025-12-03T01:10:12+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_eventbridge_task[wrong_type_2nd_array_element]": { + "last_validated_date": "2025-12-03T01:10:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_eventbridge_task[wrong_type_nested_field]": { + "last_validated_date": "2025-12-03T01:10:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_eventbridge_task[wrong_type_top_level_field]": { + "last_validated_date": "2025-12-03T01:10:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_lambda_task[wrong_integer_type_string]": { + "last_validated_date": "2025-12-03T01:10:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_mock_result_field_not_in_api_spec[empty_json]": { + "last_validated_date": "2025-12-03T01:10:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_mock_result_field_not_in_api_spec[field_not_part_of_api_spec]": { + "last_validated_date": "2025-12-03T01:10:10+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.65, + "teardown": 0.0, + "total": 1.17 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[field_name_not_in_sfn_case]": { + "last_validated_date": "2025-12-03T01:10:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[missing_required_fields]": { + "last_validated_date": "2025-12-03T01:10:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.26, + "teardown": 0.0, + "total": 0.26 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[wrong_timestamp_type_bool]": { + "last_validated_date": "2025-12-03T01:10:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode.py::TestFieldValidationMode::test_strict_mode_sfn_task[wrong_timestamp_type_number]": { + "last_validated_date": "2025-12-03T01:10:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.py b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.py new file mode 100644 index 0000000000000..f6e7e9fec3976 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.py @@ -0,0 +1,60 @@ +import json + +import pytest + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.pytest import markers +from localstack.utils.aws import arns +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateTemplate as TST, +) + + +class TestFieldValidationModeNone: + SFN_VALIDATION_ERRORS = [ + pytest.param( + {}, + id="missing_required_fields", + ), + pytest.param( + {"ExecutionArn": "stringValueExecutionArn", "StartDate": True}, + id="wrong_timestamp_type_bool", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", SFN_VALIDATION_ERRORS) + def test_none_mode_sfn_task( + self, + aws_client_no_sync_prefix, + account_id, + region_name, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_SFN_START_EXECUTION_TASK_STATE) + definition = json.dumps(template) + + target_state_machine_arn = arns.stepfunctions_state_machine_arn( + name="TargetStateMachine", account_id=account_id, region_name=region_name + ) + + target_execution_name = "TestStartTarget" + + exec_input = json.dumps( + { + "stateMachineArn": target_state_machine_arn, + "targetInput": None, + "name": target_execution_name, + } + ) + + mock = {"result": json.dumps(result), "fieldValidationMode": "NONE"} + + test_state_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_state_response", test_state_response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.snapshot.json new file mode 100644 index 0000000000000..1814ca1c64915 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.snapshot.json @@ -0,0 +1,35 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.py::TestFieldValidationModeNone::test_none_mode_sfn_task[missing_required_fields]": { + "recorded-date": "03-12-2025, 01:12:35", + "recorded-content": { + "test_state_response": { + "output": { + "targetExecutionResult": {} + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.py::TestFieldValidationModeNone::test_none_mode_sfn_task[wrong_timestamp_type_bool]": { + "recorded-date": "03-12-2025, 01:12:35", + "recorded-content": { + "test_state_response": { + "output": { + "targetExecutionResult": { + "ExecutionArn": "stringValueExecutionArn", + "StartDate": true + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.validation.json new file mode 100644 index 0000000000000..4610684b4aa39 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.py::TestFieldValidationModeNone::test_none_mode_sfn_task[missing_required_fields]": { + "last_validated_date": "2025-12-03T01:12:35+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.62, + "teardown": 0.0, + "total": 1.11 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_none.py::TestFieldValidationModeNone::test_none_mode_sfn_task[wrong_timestamp_type_bool]": { + "last_validated_date": "2025-12-03T01:12:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py new file mode 100644 index 0000000000000..f29dabbbfde1f --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py @@ -0,0 +1,398 @@ +import json + +import pytest + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.aws.util import is_aws_cloud +from localstack.testing.pytest import markers +from localstack.utils.aws import arns +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateTemplate as TST, +) + + +class TestFieldValidationModePresent: + EVENTBRIDGE_VALIDATION_PASS_FIELDS_NOT_IN_SPEC = [ + pytest.param({"random": "json"}, id="field_not_part_of_api_spec"), + pytest.param({}, id="empty_json"), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", EVENTBRIDGE_VALIDATION_PASS_FIELDS_NOT_IN_SPEC) + def test_present_mode_mock_result_field_not_in_api_spec( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + """ + Only fields from the API spec are validated + """ + template = TST.load_sfn_template(TST.BASE_EVENTS_PUT_EVENTS_TASK_STATE) + definition = json.dumps(template) + + entries = [ + { + "Detail": "detail", + "DetailType": "detail_type", + "Source": "source", + }, + ] + exec_input = json.dumps({"Entries": entries}) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + EVENTBRIDGE_VAlIDATION_ERRORS = [ + pytest.param( + {"Entries": "Entries value should have been a list of entry objects"}, + id="wrong_type_top_level_field", + ), + pytest.param( + { + "Entries": [ + {"EventId": ["EventId value should have been a string not a list of strings"]} + ] + }, + id="wrong_type_nested_field", + ), + pytest.param( + { + "Entries": [ + {"EventId": "First eventId has correct type: string"}, + {"EventId": ["Second eventId has incorrect type: array"]}, + ] + }, + id="wrong_type_2nd_array_element", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", EVENTBRIDGE_VAlIDATION_ERRORS) + def test_present_mode_eventbridge_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_EVENTS_PUT_EVENTS_TASK_STATE) + definition = json.dumps(template) + + entries = [ + { + "Detail": "detail", + "DetailType": "detail_type", + "Source": "source", + }, + ] + exec_input = json.dumps({"Entries": entries}) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + SFN_MALFORMED_RESULTS_ALLOWED_IN_PRESENT_MODE = [ + pytest.param( + {}, + id="missing_required_fields", # not validated in PRESENT mode + ), + pytest.param( + {"executionArn": "stringValueExecutionArn", "startDate": "stringValueStartDate"}, + id="field_name_not_in_sfn_case", # should be treated as unknown field + marks=pytest.mark.skipif( + condition=not is_aws_cloud(), + reason="in LocalStack mock field names are normalized whereas in AWS they are not", + # TODO analyse if this normalization can be reasonable fixed - mock is only applied to StateTaskService._eval_service_task, all before_ and after_ methods are still executed, maybe it is not needed + ), + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", SFN_MALFORMED_RESULTS_ALLOWED_IN_PRESENT_MODE) + def test_present_mode_sfn_task_validation_pass( + self, + aws_client_no_sync_prefix, + account_id, + region_name, + sfn_snapshot, + result, + ): + """ + This test is not throwing validation error but rather checks that the edge case where validation passes in PRESENT mode. + """ + template = TST.load_sfn_template(TST.BASE_SFN_START_EXECUTION_TASK_STATE) + definition = json.dumps(template) + + target_state_machine_arn = arns.stepfunctions_state_machine_arn( + name="TargetStateMachine", account_id=account_id, region_name=region_name + ) + + target_execution_name = "TestStartTarget" + + exec_input = json.dumps( + { + "stateMachineArn": target_state_machine_arn, + "targetInput": None, + "name": target_execution_name, + } + ) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + test_state_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_state_response", test_state_response) + + SFN_MALFORMED_RESULTS_NOT_ALLOWED_IN_PRESENT_MODE = [ + pytest.param( + { + "StartDate": True + }, # ExecutionArn required field is not present but that won't be an error in PRESENT mode, the format of the present field will be though + id="wrong_timestamp_type_bool", + ), + pytest.param( + { + "ExecutionArn": 1764103483 + }, # StartDate required field is not present but that won't be an error in PRESENT mode, the format of the present field will be though + id="wrong_string_type_number", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", SFN_MALFORMED_RESULTS_NOT_ALLOWED_IN_PRESENT_MODE) + def test_present_mode_sfn_task_validation_fail( + self, + aws_client, + aws_client_no_sync_prefix, + account_id, + region_name, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_SFN_START_EXECUTION_TASK_STATE) + definition = json.dumps(template) + + target_state_machine_arn = arns.stepfunctions_state_machine_arn( + name="TargetStateMachine", account_id=account_id, region_name=region_name + ) + + target_execution_name = "TestStartTarget" + + exec_input = json.dumps( + { + "stateMachineArn": target_state_machine_arn, + "targetInput": None, + "name": target_execution_name, + } + ) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + LAMBDA_VALIDATION_ERRORS = [ + pytest.param( + { + "StatusCode": "200" + }, # StatusCode is not a required field but is validated because it is present + id="wrong_integer_type_string", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", LAMBDA_VALIDATION_ERRORS) + def test_present_mode_lambda_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_LAMBDA_SERVICE_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"FunctionName": "function_name", "Payload": None}) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + DYNAMODB_VALIDATION_ERRORS = [ + pytest.param( + {"ConsumedCapacity": {"CapacityUnits": "123.45"}}, + id="wrong_float_type_string", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", DYNAMODB_VALIDATION_ERRORS) + def test_present_mode_dynamodb_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_DYNAMODB_SERVICE_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps( + { + "TableName": "table_name", + "Item": {"data": {"S": "HelloWorld"}, "id": {"S": "id1"}}, + } + ) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + AWS_SDK_S3_VALIDATION_ERRORS = [ + pytest.param( + {"ContentLength": "9000"}, + id="wrong_long_type_string", + ), + pytest.param( + {"StorageClass": "Papyrus"}, + id="non_existent_enum_value", + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", AWS_SDK_S3_VALIDATION_ERRORS) + def test_present_mode_aws_sdk_s3_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_AWS_SDK_S3_GET_OBJECT_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"Bucket": "bucket_name", "Key": "file_key"}) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + AWS_SDK_KMS_VALIDATION_ERRORS = [ + pytest.param( + {"CiphertextBlob": 123}, + id="wrong_blob_type_number", + ), + pytest.param( + {"CiphertextBlob": ""}, + id="wrong_blob_length_less_than_minimum", + marks=pytest.mark.skipif( + condition=not is_aws_cloud(), + reason="string value length validation is not implemented yet", + ), + ), + ] + + @markers.aws.validated + @pytest.mark.parametrize("result", AWS_SDK_KMS_VALIDATION_ERRORS) + def test_present_mode_aws_sdk_kms_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_AWS_SDK_KMS_ENCRYPT_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"KeyId": "key_id", "Plaintext": "plain_text"}) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + AWS_SDK_LAMBDA_GET_FUNCTION_VALIDATION_ERRORS = [ + pytest.param( + {"Configuration": {"MemorySize": 127}}, + id="integer_less_than_minimum_allowed", + ), + pytest.param( + {"Configuration": {"MemorySize": 10241}}, + id="integer_more_than_maximum_allowed", + ), + ] + + @pytest.mark.skipif( + condition=not is_aws_cloud(), reason="number value range validation is not implemented yet" + ) # TODO implement number value range validation + @markers.aws.validated + @pytest.mark.parametrize("result", AWS_SDK_LAMBDA_GET_FUNCTION_VALIDATION_ERRORS) + def test_present_mode_aws_sdk_lambda_get_function_task( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + result, + ): + template = TST.load_sfn_template(TST.BASE_AWS_SDK_LAMBDA_GET_FUNCTION) + definition = json.dumps(template) + exec_input = json.dumps({"FunctionName": "function_name"}) + + mock = {"result": json.dumps(result), "fieldValidationMode": "PRESENT"} + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.snapshot.json new file mode 100644 index 0000000000000..2fd232e07d1e7 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.snapshot.json @@ -0,0 +1,271 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_mock_result_field_not_in_api_spec[field_not_part_of_api_spec]": { + "recorded-date": "03-12-2025, 01:16:43", + "recorded-content": { + "test_case_response": { + "output": { + "random": "json" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_mock_result_field_not_in_api_spec[empty_json]": { + "recorded-date": "03-12-2025, 01:16:43", + "recorded-content": { + "test_case_response": { + "output": {}, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_eventbridge_task[wrong_type_top_level_field]": { + "recorded-date": "03-12-2025, 01:16:44", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'Entries' must be an array" + }, + "message": "Mock result schema validation error: Field 'Entries' must be an array", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_eventbridge_task[wrong_type_nested_field]": { + "recorded-date": "03-12-2025, 01:16:44", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'EventId' must be a string" + }, + "message": "Mock result schema validation error: Field 'EventId' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_eventbridge_task[wrong_type_2nd_array_element]": { + "recorded-date": "03-12-2025, 01:16:44", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'EventId' must be a string" + }, + "message": "Mock result schema validation error: Field 'EventId' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_pass[missing_required_fields]": { + "recorded-date": "03-12-2025, 01:16:44", + "recorded-content": { + "test_state_response": { + "output": { + "targetExecutionResult": {} + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_pass[field_name_not_in_sfn_case]": { + "recorded-date": "03-12-2025, 01:16:44", + "recorded-content": { + "test_state_response": { + "output": { + "targetExecutionResult": { + "executionArn": "stringValueExecutionArn", + "startDate": "stringValueStartDate" + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_fail[wrong_timestamp_type_bool]": { + "recorded-date": "03-12-2025, 01:16:45", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'StartDate' must be a string" + }, + "message": "Mock result schema validation error: Field 'StartDate' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_fail[wrong_string_type_number]": { + "recorded-date": "03-12-2025, 01:16:45", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'ExecutionArn' must be a string" + }, + "message": "Mock result schema validation error: Field 'ExecutionArn' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_lambda_task[wrong_integer_type_string]": { + "recorded-date": "03-12-2025, 01:16:45", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'StatusCode' must be a number" + }, + "message": "Mock result schema validation error: Field 'StatusCode' must be a number", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_dynamodb_task[wrong_float_type_string]": { + "recorded-date": "03-12-2025, 01:16:45", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'CapacityUnits' must be a number" + }, + "message": "Mock result schema validation error: Field 'CapacityUnits' must be a number", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_s3_task[wrong_long_type_string]": { + "recorded-date": "03-12-2025, 01:16:46", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'ContentLength' must be a number" + }, + "message": "Mock result schema validation error: Field 'ContentLength' must be a number", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_s3_task[non_existent_enum_value]": { + "recorded-date": "03-12-2025, 01:16:46", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'StorageClass' is not an expected value" + }, + "message": "Mock result schema validation error: Field 'StorageClass' is not an expected value", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_kms_task[wrong_blob_type_number]": { + "recorded-date": "03-12-2025, 01:16:46", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'CiphertextBlob' must be a string" + }, + "message": "Mock result schema validation error: Field 'CiphertextBlob' must be a string", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_kms_task[wrong_blob_length_less_than_minimum]": { + "recorded-date": "03-12-2025, 01:16:46", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'CiphertextBlob' is smaller than the minimum length" + }, + "message": "Mock result schema validation error: Field 'CiphertextBlob' is smaller than the minimum length", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_lambda_get_function_task[integer_less_than_minimum_allowed]": { + "recorded-date": "03-12-2025, 01:16:46", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'MemorySize' is less than the minimum value" + }, + "message": "Mock result schema validation error: Field 'MemorySize' is less than the minimum value", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_lambda_get_function_task[integer_more_than_maximum_allowed]": { + "recorded-date": "03-12-2025, 01:16:47", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mock result schema validation error: Field 'MemorySize' exceeds the maximum value" + }, + "message": "Mock result schema validation error: Field 'MemorySize' exceeds the maximum value", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.validation.json new file mode 100644 index 0000000000000..869bb7efbd54e --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.validation.json @@ -0,0 +1,155 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_kms_task[wrong_blob_length_less_than_minimum]": { + "last_validated_date": "2025-12-03T01:16:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_kms_task[wrong_blob_type_number]": { + "last_validated_date": "2025-12-03T01:16:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_lambda_get_function_task[integer_less_than_minimum_allowed]": { + "last_validated_date": "2025-12-03T01:16:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_lambda_get_function_task[integer_more_than_maximum_allowed]": { + "last_validated_date": "2025-12-03T01:16:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_s3_task[non_existent_enum_value]": { + "last_validated_date": "2025-12-03T01:16:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.26, + "teardown": 0.0, + "total": 0.26 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_aws_sdk_s3_task[wrong_long_type_string]": { + "last_validated_date": "2025-12-03T01:16:46+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_dynamodb_task[wrong_float_type_string]": { + "last_validated_date": "2025-12-03T01:16:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.18, + "teardown": 0.0, + "total": 0.18 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_eventbridge_task[wrong_type_2nd_array_element]": { + "last_validated_date": "2025-12-03T01:16:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_eventbridge_task[wrong_type_nested_field]": { + "last_validated_date": "2025-12-03T01:16:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_eventbridge_task[wrong_type_top_level_field]": { + "last_validated_date": "2025-12-03T01:16:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_lambda_task[wrong_integer_type_string]": { + "last_validated_date": "2025-12-03T01:16:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_mock_result_field_not_in_api_spec[empty_json]": { + "last_validated_date": "2025-12-03T01:16:43+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_mock_result_field_not_in_api_spec[field_not_part_of_api_spec]": { + "last_validated_date": "2025-12-03T01:16:43+00:00", + "durations_in_seconds": { + "setup": 0.51, + "call": 0.68, + "teardown": 0.0, + "total": 1.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_fail[wrong_string_type_number]": { + "last_validated_date": "2025-12-03T01:16:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_fail[wrong_timestamp_type_bool]": { + "last_validated_date": "2025-12-03T01:16:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_pass[field_name_not_in_sfn_case]": { + "last_validated_date": "2025-12-03T01:16:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_field_validation_mode_present.py::TestFieldValidationModePresent::test_present_mode_sfn_task_validation_pass[missing_required_fields]": { + "last_validated_date": "2025-12-03T01:16:44+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py b/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py new file mode 100644 index 0000000000000..aea596a208161 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py @@ -0,0 +1,322 @@ +import json +from typing import Final + +import pytest + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.pytest import markers +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateMachineTemplate as TSMT, +) +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateTemplate as TST, +) + +TEST_STATE_NAME: Final[str] = "TestState" + +DEFAULT_INPUT = json.dumps({"Values": ["first", "second", "third"]}) + +BASE_TEMPLATE_BINDINGS: list[str] = [ + TST.BASE_MAP_STATE, + TST.BASE_MAP_STATE_CATCH, + TST.BASE_MAP_STATE_RETRY, + TST.MAP_TASK_STATE, +] + +IDS_BASE_TEMPLATE_BINDINGS: list[str] = [ + "BASE_MAP_STATE", + "BASE_MAP_STATE_CATCH", + "BASE_MAP_STATE_RETRY", + "MAP_TASK_STATE", +] + + +class TestStateConfiguration: + @markers.aws.validated + @pytest.mark.parametrize("tct_template", BASE_TEMPLATE_BINDINGS, ids=IDS_BASE_TEMPLATE_BINDINGS) + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_map_state_failure_count( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + tct_template, + ): + sfn_snapshot.add_transformer(sfn_snapshot.transform.resource_name()) + + sfn_role_arn = create_state_machine_iam_role(aws_client) + template = TST.load_sfn_template(tct_template) + definition = json.dumps(template) + + below_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={"mapIterationFailureCount": 0}, + inspectionLevel=inspection_level, + mock={"result": json.dumps([1, 1, 1])}, + ) + sfn_snapshot.match("below_tolerated_failure", below_tolerated_failure) + + exceeds_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={"mapIterationFailureCount": 2}, + inspectionLevel=inspection_level, + mock={"result": json.dumps([0, 0, 1])}, + ) + sfn_snapshot.match("exceeds_tolerated_failure", exceeds_tolerated_failure) + + @markers.aws.validated + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_map_state_error_caused_by_state( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + ): + test_input = [1, 1, 1] + + sfn_role_arn = create_state_machine_iam_role(aws_client) + template = TST.load_sfn_template(TST.BASE_MAP_STATE) + definition = json.dumps(template) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=json.dumps({"Values": test_input}), + stateConfiguration={ + "errorCausedByState": TEST_STATE_NAME, + }, + inspectionLevel=inspection_level, + mock={ + "errorOutput": {"error": "MockException", "cause": "Mock the cause of the error."} + }, + ) + + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize("inspection_level", [InspectionLevel.DEBUG, InspectionLevel.TRACE]) + def test_state_configuration_retrier_retry_count( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + ): + sfn_role_arn = create_state_machine_iam_role(aws_client) + template = TST.load_sfn_template(TST.BASE_TASK_STATE_RETRY) + definition = json.dumps(template) + + below_tolerated_retry_count_with_backoff = ( + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={ + "retrierRetryCount": 4, + }, + inspectionLevel=inspection_level, + mock={ + "errorOutput": { + "error": "MockException", + "cause": "Mock the cause of the error.", + } + }, + ) + ) + + sfn_snapshot.match( + "below_tolerated_retry_count_with_backoff", below_tolerated_retry_count_with_backoff + ) + + exceeds_tolerated_retry_count = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={ + "retrierRetryCount": 5, + }, + inspectionLevel=inspection_level, + mock={ + "errorOutput": {"error": "MockException", "cause": "Mock the cause of the error."} + }, + ) + + sfn_snapshot.match("exceeds_tolerated_retry_count", exceeds_tolerated_retry_count) + + @markers.aws.validated + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_state_configuration_map_iteration_failure_count_with_state_error( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + ): + sfn_role_arn = create_state_machine_iam_role(aws_client) + + template = TST.load_sfn_template(TST.BASE_MAP_STATE) + definition = json.dumps(template) + + below_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={ + "mapIterationFailureCount": 0, + "errorCausedByState": "TestState", + }, + inspectionLevel=inspection_level, + mock={"errorOutput": {"error": "MockException", "cause": "Mock the error cause."}}, + ) + sfn_snapshot.match("below_tolerated_failure", below_tolerated_failure) + + exceeds_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={ + "mapIterationFailureCount": 2, + "errorCausedByState": "TestState", + }, + inspectionLevel=inspection_level, + mock={"errorOutput": {"error": "MockException", "cause": "Mock the error cause."}}, + ) + sfn_snapshot.match("exceeds_tolerated_failure", exceeds_tolerated_failure) + + @markers.aws.validated + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_state_machine_with_state_error( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + ): + sfn_role_arn = create_state_machine_iam_role(aws_client) + + template = TSMT.load_sfn_template(TSMT.BASE_MAP_STATE_MACHINE) + definition = json.dumps(template) + + below_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + stateName="State0", + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={ + "mapIterationFailureCount": 0, + "errorCausedByState": "HandleItem", + }, + inspectionLevel=inspection_level, + mock={"errorOutput": {"error": "MockException", "cause": "Mock the error cause."}}, + ) + sfn_snapshot.match("below_tolerated_failure", below_tolerated_failure) + + exceeds_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + stateName="State0", + definition=definition, + roleArn=sfn_role_arn, + input=DEFAULT_INPUT, + stateConfiguration={ + "mapIterationFailureCount": 2, + "errorCausedByState": "HandleItem", + }, + inspectionLevel=inspection_level, + mock={"errorOutput": {"error": "MockException", "cause": "Mock the error cause."}}, + ) + sfn_snapshot.match("exceeds_tolerated_failure", exceeds_tolerated_failure) + + @markers.aws.validated + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_state_machine_configuration_map_iteration_item_reader( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + ): + sfn_role_arn = create_state_machine_iam_role(aws_client) + exec_input = json.dumps({"Bucket": "test-bucket", "Key": "test-key"}) + + template = TSMT.load_sfn_template(TSMT.MAP_ITEM_READER_STATE_MACHINE) + definition = json.dumps(template) + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + stateName="State0", + definition=definition, + roleArn=sfn_role_arn, + input=exec_input, + stateConfiguration={ + "mapItemReaderData": json.dumps([1, 2, 3]), + }, + inspectionLevel=inspection_level, + mock={"result": json.dumps([1, 4, 9])}, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_state_machine_configuration_item_reader_with_error( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + ): + sfn_role_arn = create_state_machine_iam_role(aws_client) + exec_input = json.dumps({"Bucket": "test-bucket", "Key": "test-key"}) + + template = TSMT.load_sfn_template(TSMT.MAP_ITEM_READER_STATE_MACHINE) + definition = json.dumps(template) + below_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + stateName="State0", + definition=definition, + roleArn=sfn_role_arn, + input=exec_input, + stateConfiguration={ + "mapIterationFailureCount": 0, + "errorCausedByState": "HandleItem", + "mapItemReaderData": json.dumps([1, 2, 3]), + }, + inspectionLevel=inspection_level, + mock={"errorOutput": {"error": "MockException", "cause": "Mock the error cause."}}, + ) + sfn_snapshot.match("below_tolerated_failure", below_tolerated_failure) + + exceeds_tolerated_failure = aws_client_no_sync_prefix.stepfunctions.test_state( + stateName="State0", + definition=definition, + roleArn=sfn_role_arn, + input=exec_input, + stateConfiguration={ + "mapIterationFailureCount": 2, + "errorCausedByState": "HandleItem", + "mapItemReaderData": json.dumps([1, 2, 3]), + }, + inspectionLevel=inspection_level, + mock={"errorOutput": {"error": "MockException", "cause": "Mock the error cause."}}, + ) + sfn_snapshot.match("exceeds_tolerated_failure", exceeds_tolerated_failure) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.snapshot.json new file mode 100644 index 0000000000000..e671c9d9b73cb --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.snapshot.json @@ -0,0 +1,1395 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_error_caused_by_state[INFO]": { + "recorded-date": "13-11-2025, 17:07:52", + "recorded-content": { + "test_case_response": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_error_caused_by_state[DEBUG]": { + "recorded-date": "13-11-2025, 17:08:07", + "recorded-content": { + "test_case_response": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "input": { + "Values": [ + 1, + 1, + 1 + ] + }, + "maxConcurrency": 0 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_error_caused_by_state[TRACE]": { + "recorded-date": "13-11-2025, 17:08:21", + "recorded-content": { + "test_case_response": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "input": { + "Values": [ + 1, + 1, + 1 + ] + }, + "maxConcurrency": 0 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_configuration_map_iteration_failure_count_with_state_error[INFO]": { + "recorded-date": "13-11-2025, 17:08:37", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_configuration_map_iteration_failure_count_with_state_error[DEBUG]": { + "recorded-date": "13-11-2025, 17:08:51", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_configuration_map_iteration_failure_count_with_state_error[TRACE]": { + "recorded-date": "13-11-2025, 17:09:07", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_with_state_error[INFO]": { + "recorded-date": "13-11-2025, 17:09:27", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_with_state_error[DEBUG]": { + "recorded-date": "13-11-2025, 17:09:43", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "afterInputPath": { + "Values": [ + "first", + "second", + "third" + ] + }, + "afterItemsPath": "[\"first\", \"second\", \"third\"]", + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 2 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "afterInputPath": { + "Values": [ + "first", + "second", + "third" + ] + }, + "afterItemsPath": "[\"first\", \"second\", \"third\"]", + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 2 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_with_state_error[TRACE]": { + "recorded-date": "13-11-2025, 17:09:57", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "afterInputPath": { + "Values": [ + "first", + "second", + "third" + ] + }, + "afterItemsPath": "[\"first\", \"second\", \"third\"]", + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 2 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "afterInputPath": { + "Values": [ + "first", + "second", + "third" + ] + }, + "afterItemsPath": "[\"first\", \"second\", \"third\"]", + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 2 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_map_iteration_item_reader[INFO]": { + "recorded-date": "13-11-2025, 17:59:50", + "recorded-content": { + "test_case_response": { + "output": "[1, 4, 9]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_map_iteration_item_reader[DEBUG]": { + "recorded-date": "13-11-2025, 18:00:05", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Bucket": "test-bucket", + "Key": "test-key" + }, + "maxConcurrency": 1 + }, + "output": "[1, 4, 9]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_map_iteration_item_reader[TRACE]": { + "recorded-date": "13-11-2025, 18:00:20", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Bucket": "test-bucket", + "Key": "test-key" + }, + "maxConcurrency": 1 + }, + "output": "[1, 4, 9]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-BASE_MAP_STATE]": { + "recorded-date": "13-11-2025, 18:01:00", + "recorded-content": { + "below_tolerated_failure": { + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 0, + 0, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-BASE_MAP_STATE_CATCH]": { + "recorded-date": "13-11-2025, 18:01:16", + "recorded-content": { + "below_tolerated_failure": { + "output": "[1, 1, 1]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "nextState": "HandleThreshold", + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "States.ExceedToleratedFailureThreshold", + "Cause": "The specified tolerated failure threshold was exceeded" + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-BASE_MAP_STATE_RETRY]": { + "recorded-date": "13-11-2025, 18:01:31", + "recorded-content": { + "below_tolerated_failure": { + "output": "[1, 1, 1]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-MAP_TASK_STATE]": { + "recorded-date": "13-11-2025, 18:07:46", + "recorded-content": { + "below_tolerated_failure": { + "output": { + "result": [ + "pass", + "pass", + "pass" + ], + "successCount": 3, + "failedCount": 0 + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-BASE_MAP_STATE]": { + "recorded-date": "18-11-2025, 11:30:45", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 0, + 0, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-BASE_MAP_STATE_CATCH]": { + "recorded-date": "13-11-2025, 18:02:22", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": "[1, 1, 1]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "inspectionData": { + "errorDetails": { + "catchIndex": 1 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "nextState": "HandleThreshold", + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "States.ExceedToleratedFailureThreshold", + "Cause": "The specified tolerated failure threshold was exceeded" + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-BASE_MAP_STATE_RETRY]": { + "recorded-date": "13-11-2025, 18:02:38", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": "[1, 1, 1]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "inspectionData": { + "errorDetails": { + "retryBackoffIntervalSeconds": 1, + "retryIndex": 1 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-MAP_TASK_STATE]": { + "recorded-date": "13-11-2025, 18:07:18", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": { + "result": [ + "pass", + "pass", + "pass" + ], + "successCount": 3, + "failedCount": 0 + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "inspectionData": { + "errorDetails": { + "retryBackoffIntervalSeconds": 1, + "retryIndex": 1 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-BASE_MAP_STATE]": { + "recorded-date": "13-11-2025, 18:03:08", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0 + }, + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 0, + 0, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-BASE_MAP_STATE_CATCH]": { + "recorded-date": "13-11-2025, 18:03:23", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": "[1, 1, 1]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "inspectionData": { + "errorDetails": { + "catchIndex": 1 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "nextState": "HandleThreshold", + "output": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "States.ExceedToleratedFailureThreshold", + "Cause": "The specified tolerated failure threshold was exceeded" + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-BASE_MAP_STATE_RETRY]": { + "recorded-date": "13-11-2025, 18:03:38", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": "[1, 1, 1]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "inspectionData": { + "errorDetails": { + "retryBackoffIntervalSeconds": 1, + "retryIndex": 1 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-MAP_TASK_STATE]": { + "recorded-date": "13-11-2025, 18:06:47", + "recorded-content": { + "below_tolerated_failure": { + "inspectionData": { + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": { + "result": [ + "pass", + "pass", + "pass" + ], + "successCount": 3, + "failedCount": 0 + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "The specified tolerated failure threshold was exceeded", + "error": "States.ExceedToleratedFailureThreshold", + "inspectionData": { + "errorDetails": { + "retryBackoffIntervalSeconds": 1, + "retryIndex": 1 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_item_reader_with_error[INFO]": { + "recorded-date": "14-11-2025, 11:13:46", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_item_reader_with_error[DEBUG]": { + "recorded-date": "14-11-2025, 11:14:02", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Bucket": "test-bucket", + "Key": "test-key" + }, + "maxConcurrency": 1 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Bucket": "test-bucket", + "Key": "test-key" + }, + "maxConcurrency": 1 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_item_reader_with_error[TRACE]": { + "recorded-date": "14-11-2025, 11:14:18", + "recorded-content": { + "below_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Bucket": "test-bucket", + "Key": "test-key" + }, + "maxConcurrency": 1 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_failure": { + "cause": "Mock the error cause.", + "error": "MockException", + "inspectionData": { + "input": { + "Bucket": "test-bucket", + "Key": "test-key" + }, + "maxConcurrency": 1 + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_configuration_retrier_retry_count[TRACE]": { + "recorded-date": "22-11-2025, 17:13:28", + "recorded-content": { + "below_tolerated_retry_count_with_backoff": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "retryBackoffIntervalSeconds": 16, + "retryIndex": 0 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + } + }, + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_retry_count": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "retryIndex": 0 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + } + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_configuration_retrier_retry_count[DEBUG]": { + "recorded-date": "22-11-2025, 17:13:15", + "recorded-content": { + "below_tolerated_retry_count_with_backoff": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "retryBackoffIntervalSeconds": 16, + "retryIndex": 0 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + } + }, + "status": "RETRIABLE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "exceeds_tolerated_retry_count": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "retryIndex": 0 + }, + "input": { + "Values": [ + "first", + "second", + "third" + ] + } + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.validation.json new file mode 100644 index 0000000000000..98b079ae98aa6 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.validation.json @@ -0,0 +1,236 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_error_caused_by_state[DEBUG]": { + "last_validated_date": "2025-11-13T17:08:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.93, + "teardown": 1.67, + "total": 14.6 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_error_caused_by_state[INFO]": { + "last_validated_date": "2025-11-13T17:07:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.3, + "teardown": 1.91, + "total": 15.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_error_caused_by_state[TRACE]": { + "last_validated_date": "2025-11-13T17:08:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.77, + "teardown": 1.96, + "total": 14.73 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-BASE_MAP_STATE]": { + "last_validated_date": "2025-11-18T11:40:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.05, + "teardown": 1.82, + "total": 14.87 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-BASE_MAP_STATE_CATCH]": { + "last_validated_date": "2025-11-18T11:41:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.67, + "teardown": 1.7, + "total": 15.37 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-BASE_MAP_STATE_RETRY]": { + "last_validated_date": "2025-11-18T11:41:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.8, + "teardown": 1.78, + "total": 14.58 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG-MAP_TASK_STATE]": { + "last_validated_date": "2025-11-18T11:41:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.48, + "teardown": 1.66, + "total": 15.14 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[DEBUG]": { + "last_validated_date": "2025-11-13T17:07:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.18, + "teardown": 1.72, + "total": 14.9 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-BASE_MAP_STATE]": { + "last_validated_date": "2025-11-18T11:39:50+00:00", + "durations_in_seconds": { + "setup": 1.44, + "call": 16.26, + "teardown": 1.95, + "total": 19.65 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-BASE_MAP_STATE_CATCH]": { + "last_validated_date": "2025-11-18T11:40:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.81, + "teardown": 2.06, + "total": 14.87 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-BASE_MAP_STATE_RETRY]": { + "last_validated_date": "2025-11-18T11:40:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.96, + "teardown": 1.66, + "total": 15.62 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO-MAP_TASK_STATE]": { + "last_validated_date": "2025-11-18T11:40:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.16, + "teardown": 1.65, + "total": 19.81 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[INFO]": { + "last_validated_date": "2025-11-13T17:07:09+00:00", + "durations_in_seconds": { + "setup": 1.08, + "call": 15.54, + "teardown": 1.76, + "total": 18.38 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-BASE_MAP_STATE]": { + "last_validated_date": "2025-11-18T11:41:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.27, + "teardown": 1.77, + "total": 15.04 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-BASE_MAP_STATE_CATCH]": { + "last_validated_date": "2025-11-18T11:42:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.39, + "teardown": 1.78, + "total": 15.17 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-BASE_MAP_STATE_RETRY]": { + "last_validated_date": "2025-11-18T11:42:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.37, + "teardown": 1.61, + "total": 14.98 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE-MAP_TASK_STATE]": { + "last_validated_date": "2025-11-18T11:42:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.14, + "teardown": 2.11, + "total": 16.25 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_map_state_failure_count[TRACE]": { + "last_validated_date": "2025-11-13T17:07:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.2, + "teardown": 2.1, + "total": 15.3 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_configuration_retrier_retry_count[DEBUG]": { + "last_validated_date": "2025-11-22T17:13:16+00:00", + "durations_in_seconds": { + "setup": 0.59, + "call": 13.49, + "teardown": 1.03, + "total": 15.11 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_configuration_retrier_retry_count[TRACE]": { + "last_validated_date": "2025-11-22T17:13:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 11.95, + "teardown": 1.09, + "total": 13.04 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_item_reader_with_error[DEBUG]": { + "last_validated_date": "2025-11-14T11:14:04+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.13, + "teardown": 1.75, + "total": 15.88 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_item_reader_with_error[INFO]": { + "last_validated_date": "2025-11-14T11:13:48+00:00", + "durations_in_seconds": { + "setup": 1.16, + "call": 16.23, + "teardown": 1.76, + "total": 19.15 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_item_reader_with_error[TRACE]": { + "last_validated_date": "2025-11-14T11:14:20+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.42, + "teardown": 1.86, + "total": 16.28 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_map_iteration_item_reader[DEBUG]": { + "last_validated_date": "2025-11-13T18:00:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.76, + "teardown": 1.7, + "total": 15.46 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_map_iteration_item_reader[INFO]": { + "last_validated_date": "2025-11-13T17:59:52+00:00", + "durations_in_seconds": { + "setup": 1.15, + "call": 21.21, + "teardown": 1.77, + "total": 24.13 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_configuration.py::TestStateConfiguration::test_state_machine_configuration_map_iteration_item_reader[TRACE]": { + "last_validated_date": "2025-11-13T18:00:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.78, + "teardown": 1.64, + "total": 14.42 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py b/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py new file mode 100644 index 0000000000000..8498ccaea94dc --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py @@ -0,0 +1,299 @@ +import json +from datetime import datetime +from typing import Final + +import pytest +from botocore.exceptions import ClientError + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.aws.util import is_aws_cloud +from localstack.testing.pytest import markers +from tests.aws.services.stepfunctions.templates.context_object.context_object_templates import ( + ContextObjectTemplates, +) +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateTemplate as TST, +) + +TEST_STATE_NAME: Final[str] = "TestState" + +_CONTEXT_OBJECT_FULL: Final[dict] = { + "Execution": { + "Id": "arn:aws:states:::execution:MyStateMachine:execution-name-12345", + "Input": { + "input-value": 0, + "message": "test data", + "values": ["charizard", "pikachu", "bulbasaur"], + }, + "Name": "execution-name-12345", + "RoleArn": "arn:aws:iam:::role/StepFunctionsRole", + "StartTime": "2025-01-15T10:30:00.000Z", + }, + "State": { + "EnteredTime": "2025-01-15T10:30:01.500Z", + "Name": "MyTaskState", + "RetryCount": 0, + }, + "StateMachine": { + "Id": "arn:aws:states:::stateMachine:MyStateMachine", + "Name": "MyStateMachine", + }, + # Context object contains 'Task' when state is not a Task state with a '.sync' or '.waitForTaskToken' service integration pattern + # "Task": { + # "Token": "AAAAKgAAAAIAAAAAAAAAAZGexGPEB9DwXS+Bz/bGmUq4hN8v7X3LKnKj0p1qJ7qL4d5kF2gH3iJ4jK5lM6nN7oO8pP9qQ0rR1sS2tT3uU4vV5wW6xX7yY8zZ9aA0bB1cC2dD3eE4fF5gG6hH7iI8jJ9kK0lL1mM2nN3oO4pP5qQ6rR7sS8tT9uU0vV1wW2xX3yY4zZ5" + # }, + # Invalid Context object provided: 'Map' field is not supported when mocking a Context object + # "Map": { + # "Item": {"Index": 2, "Value": {"id": "item-3", "name": "Third Item", "quantity": 15}} + # }, +} + +CONTEXT_OBJECT_FULL: Final[str] = json.dumps(_CONTEXT_OBJECT_FULL) + +TASK_CONTEXT_OBJECT_FULL: Final[str] = json.dumps( + _CONTEXT_OBJECT_FULL | {"Task": {"Token": "abcd123"}} +) + +BASE_CONTEXT_OBJECT_BINDINGS: list[str] = ["$$", "$$.Execution.Input", "$$.Execution.Input.values"] + +IDS_BASE_CONTEXT_OBJECT_BINDINGS: list[str] = ["ALL", "EXECUTION_INPUT", "EXECUTION_INPUT_VALUES"] + + +class TestStateContextObject: + @markers.aws.validated + @pytest.mark.parametrize( + "context_object_literal", BASE_CONTEXT_OBJECT_BINDINGS, ids=IDS_BASE_CONTEXT_OBJECT_BINDINGS + ) + def test_state_task_context_object( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + context_object_literal, + ): + state_machine_template = ContextObjectTemplates.load_sfn_template( + ContextObjectTemplates.CONTEXT_OBJECT_RESULT_PATH + ) + + state_template = state_machine_template["States"][TEST_STATE_NAME] + + definition = json.dumps(state_template) + definition = definition.replace( + ContextObjectTemplates.CONTEXT_OBJECT_LITERAL_PLACEHOLDER, context_object_literal + ) + + exec_input = json.dumps( + {"FunctionName": "lambda_function_name", "Payload": {"input-value": 0}} + ) + mocked_result = json.dumps({"pokemon": ["charizard", "pikachu", "bulbasaur"]}) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.TRACE, + context=CONTEXT_OBJECT_FULL, + mock={"result": mocked_result}, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + def test_state_wait_task_context_object( + self, + aws_client_no_sync_prefix, + account_id, + region_name, + sfn_snapshot, + ): + state_template = TST.load_sfn_template( + TST.IO_SQS_SERVICE_TASK_WAIT, + ) + + sqs_queue_url = f"https://sqs.{region_name}.amazonaws.com/{account_id}/test-queue" + state_template["Parameters"]["QueueUrl"] = sqs_queue_url + + definition = json.dumps(state_template) + mocked_result = json.dumps({"pokemon": ["charizard", "pikachu", "bulbasaur"]}) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + context=TASK_CONTEXT_OBJECT_FULL, + mock={"result": mocked_result}, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize( + "context_object_literal", + BASE_CONTEXT_OBJECT_BINDINGS, + ids=IDS_BASE_CONTEXT_OBJECT_BINDINGS, + ) + @pytest.mark.skipif(not is_aws_cloud(), reason="Error messages are different") + def test_state_map_context_object( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + context_object_literal, + ): + state_machine_template = ContextObjectTemplates.load_sfn_template( + ContextObjectTemplates.CONTEXT_OBJECT_ITEMS_PATH + ) + + state_template = state_machine_template["States"][TEST_STATE_NAME] + + definition = json.dumps(state_template) + definition = definition.replace( + ContextObjectTemplates.CONTEXT_OBJECT_LITERAL_PLACEHOLDER, context_object_literal + ) + + exec_input = json.dumps(["fire", "electricity", "grass"]) + mocked_result = json.dumps(["CHARIZARD", "PIKACHU", "BULBASAUR"]) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.TRACE, + context=CONTEXT_OBJECT_FULL, + mock={"result": mocked_result}, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize( + "state_template,context_template", + [ + (ContextObjectTemplates.CONTEXT_OBJECT_RESULT_PATH, TASK_CONTEXT_OBJECT_FULL), + (ContextObjectTemplates.CONTEXT_OBJECT_INPUT_PATH, CONTEXT_OBJECT_FULL), + ], + ids=["TOKEN_TASK_NOT_SYNC", "INVALID_STATE_PASS"], + ) + @pytest.mark.skipif(condition=not is_aws_cloud(), reason="Failure cases not yet handled.") + def test_state_context_object_invalid_states( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + state_template, + context_template, + ): + state_machine_template = ContextObjectTemplates.load_sfn_template(state_template) + + state_template = state_machine_template["States"][TEST_STATE_NAME] + + definition = json.dumps(state_template) + definition = definition.replace( + ContextObjectTemplates.CONTEXT_OBJECT_LITERAL_PLACEHOLDER, "$$" + ) + + exec_input = json.dumps(["fire", "electricity", "grass"]) + mocked_result = json.dumps({"pokemon": ["CHARIZARD", "PIKACHU", "BULBASAUR"]}) + + with pytest.raises(ClientError) as exc: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.TRACE, + context=context_template, + mock={"result": mocked_result}, + ) + sfn_snapshot.match( + "exception", {"exception_typename": exc.typename, "exception_value": exc.value} + ) + + @markers.aws.validated + @pytest.mark.parametrize( + "context_template", + [ + {"Execution": {"Id": 1}}, + {"Map": {}}, + {"Execution": {"pokemon": "squirtle"}}, + {"Pokedex": {}}, + ], + ids=[ + "INVALID_EXECUTION_ID_TYPE", + "INVALID_MAP", + "INVALID_EXECUTION_FIELD", + "INVALID_FIELD", + ], + ) + def test_state_context_object_validation_failures( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + context_template, + ): + context_object = json.dumps(context_template) + + state_template = TST.load_sfn_template( + TST.BASE_SFN_START_EXECUTION_TASK_STATE, + ) + definition = json.dumps(state_template) + + exec_input = json.dumps( + {"stateMachineArn": "arn", "targetInput": None, "name": "exec_name"} + ) + + result = {"ExecutionArn": "exec_arn", "StartDate": datetime.now().isoformat()} + mocked_result = json.dumps(result) + + with pytest.raises(ClientError) as exc: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.TRACE, + context=context_object, + mock={"result": mocked_result}, + ) + + sfn_snapshot.match( + "exception", {"exception_typename": exc.typename, "exception_value": exc.value} + ) + + @markers.aws.validated + @pytest.mark.parametrize( + "context_template", + [ + {"Execution": {}, "State": {}, "StateMachine": {}}, + pytest.param( + {}, + marks=pytest.mark.skipif( + not is_aws_cloud(), reason="Empty context case is not properly accounted for." + ), + ), + ], + ids=[ + "EMPTY_OBJECTS", + "EMPTY", + ], + ) + def test_state_context_object_edge_cases( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + context_template, + ): + context_object = json.dumps(context_template) + + state_machine_template = ContextObjectTemplates.load_sfn_template( + ContextObjectTemplates.CONTEXT_OBJECT_RESULT_PATH + ) + + state_template = state_machine_template["States"][TEST_STATE_NAME] + + definition = json.dumps(state_template) + definition = definition.replace( + ContextObjectTemplates.CONTEXT_OBJECT_LITERAL_PLACEHOLDER, "$$" + ) + + exec_input = json.dumps( + {"FunctionName": "lambda_function_name", "Payload": {"input-value": 0}} + ) + + mocked_result = json.dumps({"pokemon": "pikachu"}) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.TRACE, + context=context_object, + mock={"result": mocked_result}, + ) + sfn_snapshot.match("test_case_response", test_case_response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.snapshot.json new file mode 100644 index 0000000000000..4c77205196992 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.snapshot.json @@ -0,0 +1,630 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_task_context_object[ALL]": { + "recorded-date": "03-12-2025, 18:51:48", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "afterParameters": { + "Payload": { + "Input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + } + }, + "FunctionName": "lambda_function_name" + }, + "afterResultPath": { + "ContextObjectValue": { + "Execution": { + "Id": "arn::states:::execution:MyStateMachine:execution-name-12345", + "Input": { + "input-value": 0, + "message": "test data", + "values": [ + "charizard", + "pikachu", + "bulbasaur" + ] + }, + "Name": "execution-name-12345", + "RoleArn": "arn::iam:::role/StepFunctionsRole", + "StartTime": "date" + }, + "State": { + "EnteredTime": "date", + "Name": "MyTaskState", + "RetryCount": 0 + }, + "StateMachine": { + "Id": "arn::states:::stateMachine:MyStateMachine", + "Name": "MyStateMachine" + } + }, + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "afterResultSelector": { + "ContextObjectValue": { + "Execution": { + "Id": "arn::states:::execution:MyStateMachine:execution-name-12345", + "Input": { + "input-value": 0, + "message": "test data", + "values": [ + "charizard", + "pikachu", + "bulbasaur" + ] + }, + "Name": "execution-name-12345", + "RoleArn": "arn::iam:::role/StepFunctionsRole", + "StartTime": "date" + }, + "State": { + "EnteredTime": "date", + "Name": "MyTaskState", + "RetryCount": 0 + }, + "StateMachine": { + "Id": "arn::states:::stateMachine:MyStateMachine", + "Name": "MyStateMachine" + } + }, + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "result": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "output": { + "ContextObjectValue": { + "Execution": { + "Id": "arn::states:::execution:MyStateMachine:execution-name-12345", + "Input": { + "input-value": 0, + "message": "test data", + "values": [ + "charizard", + "pikachu", + "bulbasaur" + ] + }, + "Name": "execution-name-12345", + "RoleArn": "arn::iam:::role/StepFunctionsRole", + "StartTime": "date" + }, + "State": { + "EnteredTime": "date", + "Name": "MyTaskState", + "RetryCount": 0 + }, + "StateMachine": { + "Id": "arn::states:::stateMachine:MyStateMachine", + "Name": "MyStateMachine" + } + }, + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_task_context_object[EXECUTION_INPUT]": { + "recorded-date": "03-12-2025, 18:51:48", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "afterParameters": { + "Payload": { + "Input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + } + }, + "FunctionName": "lambda_function_name" + }, + "afterResultPath": { + "ContextObjectValue": { + "input-value": 0, + "message": "test data", + "values": [ + "charizard", + "pikachu", + "bulbasaur" + ] + }, + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "afterResultSelector": { + "ContextObjectValue": { + "input-value": 0, + "message": "test data", + "values": [ + "charizard", + "pikachu", + "bulbasaur" + ] + }, + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "result": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "output": { + "ContextObjectValue": { + "input-value": 0, + "message": "test data", + "values": [ + "charizard", + "pikachu", + "bulbasaur" + ] + }, + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_task_context_object[EXECUTION_INPUT_VALUES]": { + "recorded-date": "03-12-2025, 18:51:49", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "afterParameters": { + "Payload": { + "Input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + } + }, + "FunctionName": "lambda_function_name" + }, + "afterResultPath": { + "ContextObjectValue": [ + "charizard", + "pikachu", + "bulbasaur" + ], + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "afterResultSelector": { + "ContextObjectValue": [ + "charizard", + "pikachu", + "bulbasaur" + ], + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "result": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "output": { + "ContextObjectValue": [ + "charizard", + "pikachu", + "bulbasaur" + ], + "LambdaOutput": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_wait_task_context_object": { + "recorded-date": "09-12-2025, 11:42:20", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": {}, + "afterParameters": { + "QueueUrl": "https://sqs..amazonaws.com/111111111111/test-queue", + "MessageBody": { + "Message": "Hello from Step Functions!", + "TaskToken": "abcd123" + } + }, + "afterResultPath": { + "SQS": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "afterResultSelector": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + }, + "input": {}, + "result": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "nextState": "NEXT_STATE", + "output": { + "SQS": { + "pokemon": [ + "charizard", + "pikachu", + "bulbasaur" + ] + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_map_context_object[ALL]": { + "recorded-date": "03-12-2025, 18:51:49", + "recorded-content": { + "test_case_response": { + "cause": "Reference path \"$\" must point to array.", + "error": "States.Runtime", + "inspectionData": { + "afterInputPath": "[\"fire\", \"electricity\", \"grass\"]", + "input": "[\"fire\", \"electricity\", \"grass\"]" + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_map_context_object[EXECUTION_INPUT]": { + "recorded-date": "03-12-2025, 18:51:49", + "recorded-content": { + "test_case_response": { + "cause": "Reference path \"$.Execution.Input\" must point to array.", + "error": "States.Runtime", + "inspectionData": { + "afterInputPath": "[\"fire\", \"electricity\", \"grass\"]", + "input": "[\"fire\", \"electricity\", \"grass\"]" + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_map_context_object[EXECUTION_INPUT_VALUES]": { + "recorded-date": "03-12-2025, 18:51:49", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": "[\"fire\", \"electricity\", \"grass\"]", + "afterItemsPath": "[\"charizard\", \"pikachu\", \"bulbasaur\"]", + "afterResultPath": "[\"CHARIZARD\", \"PIKACHU\", \"BULBASAUR\"]", + "afterResultSelector": "[\"CHARIZARD\", \"PIKACHU\", \"BULBASAUR\"]", + "input": "[\"fire\", \"electricity\", \"grass\"]", + "maxConcurrency": 1 + }, + "output": "[\"CHARIZARD\", \"PIKACHU\", \"BULBASAUR\"]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_invalid_states[TOKEN_TASK_NOT_SYNC]": { + "recorded-date": "03-12-2025, 18:51:50", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: Invalid Context object provided: Context object contains 'Task' when state is not a Task state with a '.sync' or '.waitForTaskToken' service integration pattern" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_invalid_states[INVALID_STATE_PASS]": { + "recorded-date": "03-12-2025, 18:51:50", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: State type 'Pass' is not supported when a mock is specified" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_EXECUTION_ID_TYPE]": { + "recorded-date": "03-12-2025, 18:51:50", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: Invalid Context object provided: Execution.Id must be a string" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_MAP]": { + "recorded-date": "03-12-2025, 18:51:50", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: Invalid Context object provided: 'Map' field is not supported when mocking a Context object" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_EXECUTION_FIELD]": { + "recorded-date": "03-12-2025, 18:51:51", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: Invalid Context object provided: Field 'pokemon' is not allowed" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_FIELD]": { + "recorded-date": "03-12-2025, 18:51:51", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: Invalid Context object provided: Field 'Pokedex' is not allowed" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_edge_cases[EMPTY_OBJECTS]": { + "recorded-date": "03-12-2025, 18:51:51", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "afterParameters": { + "Payload": { + "Input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + } + }, + "FunctionName": "lambda_function_name" + }, + "afterResultPath": { + "ContextObjectValue": { + "Execution": {}, + "State": {}, + "StateMachine": {} + }, + "LambdaOutput": { + "pokemon": "pikachu" + } + }, + "afterResultSelector": { + "ContextObjectValue": { + "Execution": {}, + "State": {}, + "StateMachine": {} + }, + "LambdaOutput": { + "pokemon": "pikachu" + } + }, + "input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "result": { + "pokemon": "pikachu" + } + }, + "output": { + "ContextObjectValue": { + "Execution": {}, + "State": {}, + "StateMachine": {} + }, + "LambdaOutput": { + "pokemon": "pikachu" + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_edge_cases[EMPTY]": { + "recorded-date": "03-12-2025, 18:51:51", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "afterParameters": { + "Payload": { + "Input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + } + }, + "FunctionName": "lambda_function_name" + }, + "afterResultPath": { + "ContextObjectValue": {}, + "LambdaOutput": { + "pokemon": "pikachu" + } + }, + "afterResultSelector": { + "ContextObjectValue": {}, + "LambdaOutput": { + "pokemon": "pikachu" + } + }, + "input": { + "FunctionName": "lambda_function_name", + "Payload": { + "input-value": 0 + } + }, + "result": { + "pokemon": "pikachu" + } + }, + "output": { + "ContextObjectValue": {}, + "LambdaOutput": { + "pokemon": "pikachu" + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.validation.json new file mode 100644 index 0000000000000..d3aea0013582c --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.validation.json @@ -0,0 +1,137 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_edge_cases[EMPTY]": { + "last_validated_date": "2025-12-03T18:51:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_edge_cases[EMPTY_OBJECTS]": { + "last_validated_date": "2025-12-03T18:51:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_invalid_states[INVALID_STATE_PASS]": { + "last_validated_date": "2025-12-03T18:51:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_invalid_states[TOKEN_TASK_NOT_SYNC]": { + "last_validated_date": "2025-12-03T18:51:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_EXECUTION_FIELD]": { + "last_validated_date": "2025-12-03T18:51:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_EXECUTION_ID_TYPE]": { + "last_validated_date": "2025-12-03T18:51:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_FIELD]": { + "last_validated_date": "2025-12-03T18:51:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.24, + "teardown": 0.0, + "total": 0.24 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_context_object_validation_failures[INVALID_MAP]": { + "last_validated_date": "2025-12-03T18:51:50+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_map_context_object[ALL]": { + "last_validated_date": "2025-12-03T18:51:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.23, + "teardown": 0.0, + "total": 0.23 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_map_context_object[EXECUTION_INPUT]": { + "last_validated_date": "2025-12-03T18:51:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_map_context_object[EXECUTION_INPUT_VALUES]": { + "last_validated_date": "2025-12-03T18:51:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_task_context_object[ALL]": { + "last_validated_date": "2025-12-03T18:51:48+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.67, + "teardown": 0.0, + "total": 1.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_task_context_object[EXECUTION_INPUT]": { + "last_validated_date": "2025-12-03T18:51:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_task_context_object[EXECUTION_INPUT_VALUES]": { + "last_validated_date": "2025-12-03T18:51:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_context_object.py::TestStateContextObject::test_state_wait_task_context_object": { + "last_validated_date": "2025-12-09T11:42:20+00:00", + "durations_in_seconds": { + "setup": 0.48, + "call": 0.62, + "teardown": 0.0, + "total": 1.1 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py b/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py new file mode 100644 index 0000000000000..88d6741579695 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py @@ -0,0 +1,241 @@ +import json + +import pytest + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.pytest import markers +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateTemplate as TST, +) + + +class TestStateMockValidation: + STATES_NOT_ACCEPTING_MOCKS = [ + pytest.param(TST.BASE_PASS_STATE, id="PassState"), + pytest.param(TST.BASE_FAIL_STATE, id="FailState"), + pytest.param(TST.BASE_SUCCEED_STATE, id="SucceedState"), + # TODO choice + ] + MOCK_VARIANTS = [ + pytest.param({"result": json.dumps({"mock": "the unmockable"})}, id="mock_result"), + pytest.param({"errorOutput": {"error": "Error", "cause": "Cause"}}, id="mock_error_output"), + ] + + @markers.aws.validated + @pytest.mark.parametrize("definition_template", STATES_NOT_ACCEPTING_MOCKS) + @pytest.mark.parametrize("mock", MOCK_VARIANTS) + def test_state_type_does_not_accept_mock( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + definition_template, + mock, + ): + template = TST.load_sfn_template(definition_template) + definition = json.dumps(template) + + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + STATES_REQUIRING_MOCKS = [ + pytest.param(TST.BASE_MAP_STATE, id="MapState"), + pytest.param(TST.MAP_TASK_STATE, id="MapTaskState"), + pytest.param(TST.BASE_PARALLEL_STATE, id="ParallelState"), + ] + + @markers.aws.validated + @pytest.mark.parametrize("definition_template", STATES_REQUIRING_MOCKS) + def test_state_type_requires_mock( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + definition_template, + ): + template = TST.load_sfn_template(definition_template) + definition = json.dumps(template) + + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_both_mock_result_and_mock_error_output_are_set( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_LAMBDA_SERVICE_TASK_STATE) + definition = json.dumps(template) + mock = { + "result": json.dumps({"mock": "response"}), + "errorOutput": {"error": "Error", "cause": "Cause"}, + } + + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_reveal_secrets_is_true_and_mock_result_is_set( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_LAMBDA_SERVICE_TASK_STATE) + definition = json.dumps(template) + mock = {"result": json.dumps({"mock": "response"})} + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + revealSecrets=True, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_mock_result_is_not_array_on_map_state_without_result_writer( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_MAP_STATE) + definition = json.dumps(template) + mock = {"result": json.dumps({"mock": "array is expected but object is provided instead"})} + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_mock_result_is_not_object_on_map_state_with_result_writer( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_MAP_STATE_WITH_RESULT_WRITER) + definition = json.dumps(template) + mock = {"result": json.dumps(["object is expected but array is provided instead"])} + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_mock_result_is_not_array_on_parallel_state( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_PARALLEL_STATE) + definition = json.dumps(template) + mock = {"result": json.dumps({"mock": "array is expected but object is provided instead"})} + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_mock_result_array_size_mismatch_on_parallel_state( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_PARALLEL_STATE) + definition = json.dumps(template) + mock = {"result": json.dumps([{"branch1": "result"}])} + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) + # ANTLR parser message has different wording but the same meaning as AWS response. Not investing time now to convert to the exact same wording - relying on error code for test. + # TODO match wording and hide implementation details (ANTLR) + @markers.aws.validated + def test_parallel_state_empty_branches( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + definition = json.dumps( + { + "Type": "Parallel", + "Branches": [], + "End": True, + } + ) + mock = {"result": json.dumps([])} + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + inspectionLevel=InspectionLevel.TRACE, + mock=mock, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_map_iteration_failure_count_without_mock( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + ): + sfn_role_arn = create_state_machine_iam_role(aws_client) + template = TST.load_sfn_template(TST.BASE_MAP_STATE) + definition = json.dumps(template) + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + stateConfiguration={"mapIterationFailureCount": 0}, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + def test_map_iteration_failure_count_exceeds_mock_result_items( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + ): + sfn_role_arn = create_state_machine_iam_role(aws_client) + template = TST.load_sfn_template(TST.BASE_MAP_STATE) + definition = json.dumps(template) + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=json.dumps({"Values": [1, 1, 1]}), + stateConfiguration={"mapIterationFailureCount": 4}, + mock={"result": json.dumps([1, 1, 1])}, + ) + sfn_snapshot.match("validation_exception", e.value.response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.snapshot.json new file mode 100644 index 0000000000000..a27d32bbcf7b8 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.snapshot.json @@ -0,0 +1,290 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_result-PassState]": { + "recorded-date": "10-12-2025, 00:28:08", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "State type 'Pass' is not supported when a mock is specified" + }, + "message": "State type 'Pass' is not supported when a mock is specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_result-FailState]": { + "recorded-date": "10-12-2025, 00:28:09", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "State type 'Fail' is not supported when a mock is specified" + }, + "message": "State type 'Fail' is not supported when a mock is specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_result-SucceedState]": { + "recorded-date": "10-12-2025, 00:28:09", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "State type 'Succeed' is not supported when a mock is specified" + }, + "message": "State type 'Succeed' is not supported when a mock is specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_error_output-PassState]": { + "recorded-date": "10-12-2025, 00:28:09", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "State type 'Pass' is not supported when a mock is specified" + }, + "message": "State type 'Pass' is not supported when a mock is specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_error_output-FailState]": { + "recorded-date": "10-12-2025, 00:28:09", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "State type 'Fail' is not supported when a mock is specified" + }, + "message": "State type 'Fail' is not supported when a mock is specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_error_output-SucceedState]": { + "recorded-date": "10-12-2025, 00:28:09", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "State type 'Succeed' is not supported when a mock is specified" + }, + "message": "State type 'Succeed' is not supported when a mock is specified", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_requires_mock[MapState]": { + "recorded-date": "10-12-2025, 00:28:10", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "InvalidDefinition", + "Message": "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]" + }, + "message": "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_requires_mock[MapTaskState]": { + "recorded-date": "10-12-2025, 00:28:10", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "InvalidDefinition", + "Message": "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]" + }, + "message": "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_both_mock_result_and_mock_error_output_are_set": { + "recorded-date": "10-12-2025, 00:28:10", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "A test mock should have only one of the following fields: [result, errorOutput]." + }, + "message": "A test mock should have only one of the following fields: [result, errorOutput].", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_reveal_secrets_is_true_and_mock_result_is_set": { + "recorded-date": "10-12-2025, 00:28:10", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "TestState does not support RevealSecrets when a mock is specified." + }, + "message": "TestState does not support RevealSecrets when a mock is specified.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_is_not_array_on_map_state_without_result_writer": { + "recorded-date": "10-12-2025, 00:28:10", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mocked result must be an array." + }, + "message": "Mocked result must be an array.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_is_not_object_on_map_state_with_result_writer": { + "recorded-date": "10-12-2025, 00:28:11", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mocked result must be a JSON object." + }, + "message": "Mocked result must be a JSON object.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_requires_mock[ParallelState]": { + "recorded-date": "24-02-2026, 22:49:05", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "InvalidDefinition", + "Message": "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]" + }, + "message": "TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_is_not_array_on_parallel_state": { + "recorded-date": "24-02-2026, 22:49:05", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mocked result must be an array." + }, + "message": "Mocked result must be an array.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_array_size_mismatch_on_parallel_state": { + "recorded-date": "24-02-2026, 22:49:05", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mocked result must contain the same number of items as number of Parallel branches." + }, + "message": "Mocked result must contain the same number of items as number of Parallel branches.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_parallel_state_empty_branches": { + "recorded-date": "24-02-2026, 22:49:05", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "InvalidDefinition", + "Message": "Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: Value cannot be empty at /States/StateName/Branches'" + }, + "message": "Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: Value cannot be empty at /States/StateName/Branches'", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_map_iteration_failure_count_without_mock": { + "recorded-date": "01-03-2026, 22:59:26", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "TestState does not support MapIterationFailureCount when a mock is not specified." + }, + "message": "TestState does not support MapIterationFailureCount when a mock is not specified.", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_map_iteration_failure_count_exceeds_mock_result_items": { + "recorded-date": "02-03-2026, 00:17:09", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "Map iteration failure count must be less than or equal to the number of Map iterations" + }, + "message": "Map iteration failure count must be less than or equal to the number of Map iterations", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.validation.json new file mode 100644 index 0000000000000..97414144be834 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.validation.json @@ -0,0 +1,164 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_both_mock_result_and_mock_error_output_are_set": { + "last_validated_date": "2026-02-27T10:02:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_map_iteration_failure_count_exceeds_mock_result_items": { + "last_validated_date": "2026-03-02T00:17:10+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 13.04, + "teardown": 0.97, + "total": 14.51 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_map_iteration_failure_count_without_mock": { + "last_validated_date": "2026-03-01T22:59:27+00:00", + "durations_in_seconds": { + "setup": 0.57, + "call": 14.18, + "teardown": 1.06, + "total": 15.81 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_array_size_mismatch_on_parallel_state": { + "last_validated_date": "2026-02-27T10:02:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_is_not_array_on_map_state_without_result_writer": { + "last_validated_date": "2026-02-27T10:02:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_is_not_array_on_parallel_state": { + "last_validated_date": "2026-02-27T10:02:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_mock_result_is_not_object_on_map_state_with_result_writer": { + "last_validated_date": "2026-02-27T10:02:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.27, + "teardown": 0.0, + "total": 0.27 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_parallel_state_empty_branches": { + "last_validated_date": "2026-02-27T10:02:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_reveal_secrets_is_true_and_mock_result_is_set": { + "last_validated_date": "2026-02-27T10:02:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_error_output-FailState]": { + "last_validated_date": "2026-02-27T10:02:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_error_output-PassState]": { + "last_validated_date": "2026-02-27T10:02:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_error_output-SucceedState]": { + "last_validated_date": "2026-02-27T10:02:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_result-FailState]": { + "last_validated_date": "2026-02-27T10:02:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_result-PassState]": { + "last_validated_date": "2026-02-27T10:02:35+00:00", + "durations_in_seconds": { + "setup": 0.66, + "call": 0.71, + "teardown": 0.0, + "total": 1.37 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_does_not_accept_mock[mock_result-SucceedState]": { + "last_validated_date": "2026-02-27T10:02:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_requires_mock[MapState]": { + "last_validated_date": "2026-02-27T10:02:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_requires_mock[MapTaskState]": { + "last_validated_date": "2026-02-27T10:02:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_state_mock_validation.py::TestStateMockValidation::test_state_type_requires_mock[ParallelState]": { + "last_validated_date": "2026-02-27T10:02:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py new file mode 100644 index 0000000000000..305fbf7821939 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py @@ -0,0 +1,132 @@ +import json +from typing import Final + +import pytest + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.pytest import markers +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateMachineTemplate as TSMT, +) + +TEST_STATE_NAME: Final[str] = "State0" + +HELLO_WORLD_INPUT = json.dumps({"Value": "HelloWorld"}) + +BASE_TEMPLATE_INPUT_BINDINGS: list[tuple[str, str, str]] = [ + (TSMT.BASE_PASS_STATE_MACHINE, "State0", HELLO_WORLD_INPUT), + (TSMT.BASE_FAIL_STATE_MACHINE, "State0", HELLO_WORLD_INPUT), + (TSMT.BASE_MAP_STATE_MACHINE, "HandleItem", HELLO_WORLD_INPUT), +] + +IDS_BASE_TEMPLATE_INPUT_BINDINGS: list[str] = [ + "BASE_PASS_STATE_MACHINE", + "BASE_FAIL_STATE_MACHINE", + "BASE_MAP_STATE_MACHINE", +] + + +class TestStateMachineScenarios: + @markers.aws.validated + @pytest.mark.parametrize( + "tct_template,state_name,execution_input", + BASE_TEMPLATE_INPUT_BINDINGS, + ids=IDS_BASE_TEMPLATE_INPUT_BINDINGS, + ) + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_base_state_name( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + tct_template, + state_name, + execution_input, + inspection_level, + ): + template = TSMT.load_sfn_template(tct_template) + definition = json.dumps(template) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + stateName=state_name, + definition=definition, + input=execution_input, + inspectionLevel=inspection_level, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize("state_name", ["State0", "State1"]) + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_base_state_name_multi_state( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + state_name, + inspection_level, + ): + template = TSMT.load_sfn_template(TSMT.BASE_MULTI_STATE_MACHINE) + definition = json.dumps(template) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + stateName=state_name, + definition=definition, + input=HELLO_WORLD_INPUT, + inspectionLevel=inspection_level, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize( + "state_name", + ["dne", '"invalid"', ""], + ids=["dne", "invalid", "empty"], + ) + def test_base_state_name_validation_failures( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + state_name, + ): + template = TSMT.load_sfn_template(TSMT.BASE_PASS_STATE_MACHINE) + definition = json.dumps(template) + + with pytest.raises(Exception) as ex: + aws_client_no_sync_prefix.stepfunctions.test_state( + stateName=state_name, + definition=definition, + input=HELLO_WORLD_INPUT, + inspectionLevel=InspectionLevel.INFO, + ) + + sfn_snapshot.match( + "exception", {"exception_typename": ex.typename, "exception_value": ex.value} + ) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) + # ANTLR parser message has different wording but the same meaning as AWS response. Not investing time now to convert to the exact same wording - relying on error code for test. + # TODO match wording and hide implementation details (ANTLR) + # expected: + # /Error/Message "Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: The field 'Type' should have one of these values: [Task, Wait, Pass, Succeed, Fail, Choice, Parallel, Map] at /States/ExistingButInvalidState/Type'" + # actual: + # 'ASLParserException line 1:170, at "TypeThatDoesNotExist", mismatched input \'"TypeThatDoesNotExist"\' expecting {\'"Task"\', \'"Choice"\', \'"Fail"\', \'"Succeed"\', \'"Pass"\', \'"Wait"\', \'"Parallel"\', \'"Map"\'}' + def test_state_name_invalid_state_definition( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TSMT.load_sfn_template(TSMT.BASE_INVALID_STATE_DEFINITION) + definition = json.dumps(template) + + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + stateName="ExistingButInvalidState", + definition=definition, + input=HELLO_WORLD_INPUT, + inspectionLevel=InspectionLevel.INFO, + ) + sfn_snapshot.match("validation_exception", e.value.response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.snapshot.json new file mode 100644 index 0000000000000..ab77a405eefc6 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.snapshot.json @@ -0,0 +1,365 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[INFO-BASE_PASS_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:38", + "recorded-content": { + "test_case_response": { + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[INFO-BASE_FAIL_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:38", + "recorded-content": { + "test_case_response": { + "cause": "This state machines raises a 'SomeFailure' failure.", + "error": "SomeFailure", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[DEBUG-BASE_PASS_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:39", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "Value": "HelloWorld" + }, + "afterParameters": { + "Value": "HelloWorld" + }, + "afterResultPath": { + "Value": "HelloWorld" + }, + "afterResultSelector": { + "Value": "HelloWorld" + }, + "input": { + "Value": "HelloWorld" + } + }, + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[DEBUG-BASE_FAIL_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:39", + "recorded-content": { + "test_case_response": { + "cause": "This state machines raises a 'SomeFailure' failure.", + "error": "SomeFailure", + "inspectionData": { + "input": { + "Value": "HelloWorld" + } + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[TRACE-BASE_PASS_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:39", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "Value": "HelloWorld" + }, + "afterParameters": { + "Value": "HelloWorld" + }, + "afterResultPath": { + "Value": "HelloWorld" + }, + "afterResultSelector": { + "Value": "HelloWorld" + }, + "input": { + "Value": "HelloWorld" + } + }, + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[TRACE-BASE_FAIL_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:40", + "recorded-content": { + "test_case_response": { + "cause": "This state machines raises a 'SomeFailure' failure.", + "error": "SomeFailure", + "inspectionData": { + "input": { + "Value": "HelloWorld" + } + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[INFO-State0]": { + "recorded-date": "03-12-2025, 00:48:40", + "recorded-content": { + "test_case_response": { + "nextState": "State1", + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[INFO-State1]": { + "recorded-date": "03-12-2025, 00:48:40", + "recorded-content": { + "test_case_response": { + "cause": "This state machines raises a 'SomeFailure' failure.", + "error": "SomeFailure", + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[DEBUG-State0]": { + "recorded-date": "03-12-2025, 00:48:40", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Value": "HelloWorld" + } + }, + "nextState": "State1", + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[DEBUG-State1]": { + "recorded-date": "03-12-2025, 00:48:41", + "recorded-content": { + "test_case_response": { + "cause": "This state machines raises a 'SomeFailure' failure.", + "error": "SomeFailure", + "inspectionData": { + "input": { + "Value": "HelloWorld" + } + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[TRACE-State0]": { + "recorded-date": "03-12-2025, 00:48:41", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Value": "HelloWorld" + } + }, + "nextState": "State1", + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[TRACE-State1]": { + "recorded-date": "03-12-2025, 00:48:41", + "recorded-content": { + "test_case_response": { + "cause": "This state machines raises a 'SomeFailure' failure.", + "error": "SomeFailure", + "inspectionData": { + "input": { + "Value": "HelloWorld" + } + }, + "status": "FAILED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_validation_failures[dne]": { + "recorded-date": "03-12-2025, 00:48:41", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: State not found in definition" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_validation_failures[invalid]": { + "recorded-date": "03-12-2025, 00:48:42", + "recorded-content": { + "exception": { + "exception_typename": "ValidationException", + "exception_value": "An error occurred (ValidationException) when calling the TestState operation: State not found in definition" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_validation_failures[empty]": { + "recorded-date": "03-12-2025, 00:48:42", + "recorded-content": { + "exception": { + "exception_typename": "ParamValidationError", + "exception_value": "Parameter validation failed:\nInvalid length for parameter stateName, value: 0, valid min length: 1" + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[INFO-BASE_MAP_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:39", + "recorded-content": { + "test_case_response": { + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[DEBUG-BASE_MAP_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:39", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "Value": "HelloWorld" + }, + "afterParameters": { + "Value": "HelloWorld" + }, + "afterResultPath": { + "Value": "HelloWorld" + }, + "afterResultSelector": { + "Value": "HelloWorld" + }, + "input": { + "Value": "HelloWorld" + } + }, + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[TRACE-BASE_MAP_STATE_MACHINE]": { + "recorded-date": "03-12-2025, 00:48:40", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterInputPath": { + "Value": "HelloWorld" + }, + "afterParameters": { + "Value": "HelloWorld" + }, + "afterResultPath": { + "Value": "HelloWorld" + }, + "afterResultSelector": { + "Value": "HelloWorld" + }, + "input": { + "Value": "HelloWorld" + } + }, + "output": { + "Value": "HelloWorld" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_state_name_invalid_state_definition": { + "recorded-date": "04-12-2025, 12:16:39", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "InvalidDefinition", + "Message": "Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: The field 'Type' should have one of these values: [Task, Wait, Pass, Succeed, Fail, Choice, Parallel, Map] at /States/ExistingButInvalidState/Type'" + }, + "message": "Invalid State Machine Definition: 'SCHEMA_VALIDATION_FAILED: The field 'Type' should have one of these values: [Task, Wait, Pass, Succeed, Fail, Choice, Parallel, Map] at /States/ExistingButInvalidState/Type'", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.validation.json new file mode 100644 index 0000000000000..122acaa70d2a6 --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.validation.json @@ -0,0 +1,173 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[DEBUG-BASE_FAIL_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[DEBUG-BASE_MAP_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.18, + "teardown": 0.0, + "total": 0.18 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[DEBUG-BASE_PASS_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[INFO-BASE_FAIL_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[INFO-BASE_MAP_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[INFO-BASE_PASS_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:38+00:00", + "durations_in_seconds": { + "setup": 0.5, + "call": 0.6, + "teardown": 0.0, + "total": 1.1 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[TRACE-BASE_FAIL_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[TRACE-BASE_MAP_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name[TRACE-BASE_PASS_STATE_MACHINE]": { + "last_validated_date": "2025-12-03T00:48:39+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.18, + "teardown": 0.0, + "total": 0.18 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[DEBUG-State0]": { + "last_validated_date": "2025-12-03T00:48:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[DEBUG-State1]": { + "last_validated_date": "2025-12-03T00:48:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.27, + "teardown": 0.0, + "total": 0.27 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[INFO-State0]": { + "last_validated_date": "2025-12-03T00:48:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[INFO-State1]": { + "last_validated_date": "2025-12-03T00:48:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[TRACE-State0]": { + "last_validated_date": "2025-12-03T00:48:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_multi_state[TRACE-State1]": { + "last_validated_date": "2025-12-03T00:48:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.18, + "teardown": 0.0, + "total": 0.18 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_validation_failures[dne]": { + "last_validated_date": "2025-12-03T00:48:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_validation_failures[empty]": { + "last_validated_date": "2025-12-03T00:48:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.06, + "teardown": 0.0, + "total": 0.06 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_base_state_name_validation_failures[invalid]": { + "last_validated_date": "2025-12-03T00:48:42+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.59, + "teardown": 0.0, + "total": 0.59 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_machine_scenarios.py::TestStateMachineScenarios::test_state_name_invalid_state_definition": { + "last_validated_date": "2025-12-04T12:16:39+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 0.67, + "teardown": 0.0, + "total": 1.22 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py new file mode 100644 index 0000000000000..2bde8b14988aa --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py @@ -0,0 +1,280 @@ +import json +from datetime import datetime + +import pytest +from localstack_snapshot.snapshots.transformer import JsonpathTransformer, RegexTransformer + +from localstack.aws.api.stepfunctions import InspectionLevel +from localstack.testing.pytest import markers +from localstack.utils.aws import arns +from localstack.utils.strings import long_uid, md5, short_uid +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateTemplate as TST, +) + + +class TestStateMockScenarios: + @markers.aws.validated + def test_base_lambda_service_task_mock_is_not_json_string( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + function_name = f"lambda_func_{short_uid()}" + sfn_snapshot.add_transformer(RegexTransformer(function_name, "")) + + template = TST.load_sfn_template(TST.BASE_LAMBDA_SERVICE_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"FunctionName": function_name, "Payload": None}) + mock = { + "result": "not JSON string", + "fieldValidationMode": "NONE", # the result must be a valid JSON string even if field validation mode is NONE + } + + with pytest.raises(aws_client.stepfunctions.exceptions.ValidationException) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("validation-exception", e.value.response) + + @markers.aws.validated + def test_base_lambda_service_task_mock_success( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + function_name = f"lambda_func_{short_uid()}" + sfn_snapshot.add_transformer(RegexTransformer(function_name, "")) + + template = TST.load_sfn_template(TST.BASE_LAMBDA_SERVICE_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"FunctionName": function_name, "Payload": None}) + result = { + # Lambda API spec requires response payload to be a string. + # However, when not mocked, optimized lambda task output is a JSON. + # TODO Clarify whether such transformation is supposed to happen in TestState call as well or there is a caveat. + "Payload": "function output", + "SdkHttpMetadata": {"HttpStatusCode": 200}, + } + mock = {"result": json.dumps(result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + DYNAMODB_TEMPLATES = [ + pytest.param(TST.BASE_DYNAMODB_SERVICE_TASK_STATE, id="base"), + pytest.param(TST.IO_DYNAMODB_SERVICE_TASK_STATE, id="io"), + pytest.param(TST.IO_OUTPUT_PATH_DYNAMODB_SERVICE_TASK_STATE, id="io_output_path"), + ] + + @markers.aws.validated + @pytest.mark.parametrize("template_path", DYNAMODB_TEMPLATES) + def test_io_dynamodb_service_task_mock_success( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + template_path, + ): + table_name = f"sfn_test_table_{short_uid()}" + sfn_snapshot.add_transformer(RegexTransformer(table_name, "")) + + template = TST.load_sfn_template(template_path) + definition = json.dumps(template) + exec_input = json.dumps( + { + "TableName": table_name, + "Item": {"data": {"S": "HelloWorld"}, "id": {"S": "id1"}}, + } + ) + result = {"SdkHttpMetadata": {"HttpStatusCode": 200}} + mock = {"result": json.dumps(result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + def test_put_events_mock_success( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_EVENTS_PUT_EVENTS_TASK_STATE) + definition = json.dumps(template) + + entries = [ + { + "Detail": "detail", + "DetailType": "detail_type", + "Source": "source", + }, + ] + exec_input = json.dumps({"Entries": entries}) + + result = {"Entries": [{"EventId": long_uid()}]} + mock = {"result": json.dumps(result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + def test_send_sqs_message_success( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + template = TST.load_sfn_template(TST.BASE_SQS_SEND_MESSAGE_TASK_STATE) + definition = json.dumps(template) + + message_body = "message_body" + md5_of_message_body = md5(message_body) + sfn_snapshot.add_transformer(RegexTransformer(md5_of_message_body, "")) + + exec_input = json.dumps({"QueueUrl": "queue_url", "MessageBody": message_body}) + result = {"MD5OfMessageBody": md5_of_message_body, "MessageId": long_uid()} + mock = {"result": json.dumps(result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + INSPECTION_LEVELS = [ + pytest.param(InspectionLevel.INFO, id="INFO"), + pytest.param(InspectionLevel.DEBUG, id="DEBUG"), + pytest.param(InspectionLevel.TRACE, id="TRACE"), + ] + + @markers.aws.validated + @pytest.mark.parametrize("inspection_level", INSPECTION_LEVELS) + def test_base_parallel_state_mock_success( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + inspection_level, + ): + template = TST.load_sfn_template(TST.BASE_PARALLEL_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"key": "value"}) + mock_result = [{"branch1": "result"}, {"branch2": "result"}] + mock = {"result": json.dumps(mock_result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=inspection_level, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize("inspection_level", INSPECTION_LEVELS) + def test_io_parallel_state_mock_success( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + inspection_level, + ): + template = TST.load_sfn_template(TST.IO_PARALLEL_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"parallelInput": {"data": "input_value"}}) + mock_result = [{"branch1": "result"}, {"branch2": "result"}] + mock = {"result": json.dumps(mock_result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=inspection_level, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize("inspection_level", INSPECTION_LEVELS) + def test_io_jsonata_parallel_state_mock_success( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + inspection_level, + ): + template = TST.load_sfn_template(TST.IO_JSONATA_PARALLEL_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"parallelInput": {"data": "input_value"}}) + mock_result = [{"branch1": "result"}, {"branch2": "result"}] + mock = {"result": json.dumps(mock_result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=inspection_level, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + def test_sfn_start_execution_success( + self, + aws_client_no_sync_prefix, + account_id, + region_name, + sfn_snapshot, + ): + sfn_snapshot.add_transformer( + JsonpathTransformer( + jsonpath="$..output..StartDate", + replacement="start-date", + replace_reference=False, + ) + ) + + template = TST.load_sfn_template(TST.BASE_SFN_START_EXECUTION_TASK_STATE) + definition = json.dumps(template) + + target_state_machine_arn = arns.stepfunctions_state_machine_arn( + name="TargetStateMachine", account_id=account_id, region_name=region_name + ) + + target_execution_name = "TestStartTarget" + target_execution_arn = arns.stepfunctions_standard_execution_arn( + target_state_machine_arn, target_execution_name + ) + + exec_input = json.dumps( + { + "stateMachineArn": target_state_machine_arn, + "targetInput": None, + "name": target_execution_name, + } + ) + + result = {"ExecutionArn": target_execution_arn, "StartDate": datetime.now().isoformat()} + mock = {"result": json.dumps(result)} + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.INFO, + mock=mock, + ) + sfn_snapshot.match("test_case_response", test_case_response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.snapshot.json new file mode 100644 index 0000000000000..64904254aa59c --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.snapshot.json @@ -0,0 +1,410 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_lambda_service_task_mock_success": { + "recorded-date": "03-12-2025, 00:34:13", + "recorded-content": { + "test_case_response": { + "output": { + "Payload": "function output", + "SdkHttpMetadata": { + "HttpStatusCode": 200 + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_dynamodb_service_task_mock_success[base]": { + "recorded-date": "03-12-2025, 00:34:14", + "recorded-content": { + "test_case_response": { + "output": { + "SdkHttpMetadata": { + "HttpStatusCode": 200 + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_dynamodb_service_task_mock_success[io]": { + "recorded-date": "03-12-2025, 00:34:14", + "recorded-content": { + "test_case_response": { + "output": { + "TableName": "", + "Item": { + "data": { + "S": "HelloWorld" + }, + "id": { + "S": "id1" + } + }, + "putItemOutput": { + "SdkHttpMetadata": { + "HttpStatusCode": 200 + } + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_dynamodb_service_task_mock_success[io_output_path]": { + "recorded-date": "03-12-2025, 00:34:14", + "recorded-content": { + "test_case_response": { + "output": { + "SdkHttpMetadata": { + "HttpStatusCode": 200 + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_put_events_mock_success": { + "recorded-date": "03-12-2025, 00:34:14", + "recorded-content": { + "test_case_response": { + "output": { + "Entries": [ + { + "EventId": "" + } + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_send_sqs_message_success": { + "recorded-date": "03-12-2025, 00:34:14", + "recorded-content": { + "test_case_response": { + "output": { + "MD5OfMessageBody": "", + "MessageId": "" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_sfn_start_execution_success": { + "recorded-date": "03-12-2025, 00:34:15", + "recorded-content": { + "test_case_response": { + "output": { + "targetExecutionResult": { + "ExecutionArn": "arn::states::111111111111:execution:TargetStateMachine:TestStartTarget", + "StartDate": "start-date" + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_lambda_service_task_mock_is_not_json_string": { + "recorded-date": "03-12-2025, 00:34:13", + "recorded-content": { + "validation-exception": { + "Error": { + "Code": "ValidationException", + "Message": "Mocked result must be valid JSON" + }, + "message": "Mocked result must be valid JSON", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_parallel_state_mock_success[INFO]": { + "recorded-date": "24-02-2026, 22:49:12", + "recorded-content": { + "test_case_response": { + "output": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_parallel_state_mock_success[DEBUG]": { + "recorded-date": "24-02-2026, 22:49:12", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterResultPath": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "afterResultSelector": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "input": { + "key": "value" + } + }, + "output": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_parallel_state_mock_success[TRACE]": { + "recorded-date": "24-02-2026, 22:49:12", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterResultPath": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "afterResultSelector": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "input": { + "key": "value" + } + }, + "output": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_parallel_state_mock_success[INFO]": { + "recorded-date": "24-02-2026, 22:49:12", + "recorded-content": { + "test_case_response": { + "output": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_parallel_state_mock_success[DEBUG]": { + "recorded-date": "24-02-2026, 22:49:13", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterResultPath": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "afterResultSelector": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "input": { + "parallelInput": { + "data": "input_value" + } + } + }, + "output": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_parallel_state_mock_success[TRACE]": { + "recorded-date": "24-02-2026, 22:49:13", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "afterResultPath": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "afterResultSelector": "[{\"branch1\": \"result\"}, {\"branch2\": \"result\"}]", + "input": { + "parallelInput": { + "data": "input_value" + } + } + }, + "output": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_jsonata_parallel_state_mock_success[INFO]": { + "recorded-date": "27-02-2026, 09:29:56", + "recorded-content": { + "test_case_response": { + "output": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_jsonata_parallel_state_mock_success[DEBUG]": { + "recorded-date": "27-02-2026, 09:29:57", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "parallelInput": { + "data": "input_value" + } + } + }, + "output": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_jsonata_parallel_state_mock_success[TRACE]": { + "recorded-date": "27-02-2026, 09:29:57", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "parallelInput": { + "data": "input_value" + } + } + }, + "output": { + "parallelInput": { + "data": "input_value" + }, + "parallelResult": [ + { + "branch1": "result" + }, + { + "branch2": "result" + } + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.validation.json new file mode 100644 index 0000000000000..507740201862c --- /dev/null +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.validation.json @@ -0,0 +1,155 @@ +{ + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_lambda_service_task_mock_is_not_json_string": { + "last_validated_date": "2026-02-27T10:03:22+00:00", + "durations_in_seconds": { + "setup": 0.52, + "call": 0.62, + "teardown": 0.0, + "total": 1.14 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_lambda_service_task_mock_success": { + "last_validated_date": "2026-02-27T10:03:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_parallel_state_mock_success[DEBUG]": { + "last_validated_date": "2026-02-27T10:03:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_parallel_state_mock_success[INFO]": { + "last_validated_date": "2026-02-27T10:03:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_base_parallel_state_mock_success[TRACE]": { + "last_validated_date": "2026-02-27T10:03:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.35, + "teardown": 0.0, + "total": 0.35 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_dynamodb_service_task_mock_success[base]": { + "last_validated_date": "2026-02-27T10:03:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_dynamodb_service_task_mock_success[io]": { + "last_validated_date": "2026-02-27T10:03:22+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_dynamodb_service_task_mock_success[io_output_path]": { + "last_validated_date": "2026-02-27T10:03:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_jsonata_parallel_state_mock_success[DEBUG]": { + "last_validated_date": "2026-02-27T10:03:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_jsonata_parallel_state_mock_success[INFO]": { + "last_validated_date": "2026-02-27T10:03:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_jsonata_parallel_state_mock_success[TRACE]": { + "last_validated_date": "2026-02-27T10:03:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_parallel_state_mock_success[DEBUG]": { + "last_validated_date": "2026-02-27T10:03:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.25, + "teardown": 0.0, + "total": 0.25 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_parallel_state_mock_success[INFO]": { + "last_validated_date": "2026-02-27T10:03:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_io_parallel_state_mock_success[TRACE]": { + "last_validated_date": "2026-02-27T10:03:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.2, + "teardown": 0.0, + "total": 0.2 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_put_events_mock_success": { + "last_validated_date": "2026-02-27T10:03:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.19, + "teardown": 0.0, + "total": 0.19 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_send_sqs_message_success": { + "last_validated_date": "2026-02-27T10:03:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_mock_scenarios.py::TestStateMockScenarios::test_sfn_start_execution_success": { + "last_validated_date": "2026-02-27T10:03:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.21, + "teardown": 0.0, + "total": 0.21 + } + } +} diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py index facf99bd57c7a..bfd51262ebeed 100644 --- a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py @@ -7,9 +7,18 @@ from localstack.aws.api.stepfunctions import InspectionLevel from localstack.testing.pytest import markers from localstack.utils.strings import short_uid +from tests.aws.services.stepfunctions.templates.evaluatejsonata.evaluate_jsonata_templates import ( + EvaluateJsonataTemplate as EJT, +) +from tests.aws.services.stepfunctions.templates.outputdecl.output_templates import ( + OutputTemplates as OT, +) from tests.aws.services.stepfunctions.templates.services.services_templates import ( ServicesTemplates as ST, ) +from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( + TestStateMachineTemplate as TSMT, +) from tests.aws.services.stepfunctions.templates.test_state.test_state_templates import ( TestStateTemplate as TST, ) @@ -22,6 +31,7 @@ } ) BASE_CHOICE_STATE_INPUT = json.dumps({"type": "Private", "value": 22}) +BASE_MAP_STATE_INPUT = json.dumps({"Values": [1, 2, 3]}) BASE_TEMPLATE_INPUT_BINDINGS: list[tuple[str, str]] = [ (TST.BASE_PASS_STATE, HELLO_WORLD_INPUT), @@ -83,17 +93,6 @@ def test_base_inspection_level_info( sfn_snapshot.match("test_case_response", test_case_response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Unknown generalisable behaviour by AWS leads to the outputting of undeclared and - # unsupported state modifiers. Such as ResultSelector, which is neither defined in - # this Pass state, nor supported by Pass states. - "$..inspectionData.afterInputPath", - "$..inspectionData.afterParameters", - "$..inspectionData.afterResultPath", - "$..inspectionData.afterResultSelector", - ] - ) @pytest.mark.parametrize( "tct_template,execution_input", BASE_TEMPLATE_INPUT_BINDINGS, @@ -123,17 +122,6 @@ def test_base_inspection_level_debug( sfn_snapshot.match("test_case_response", test_case_response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Unknown generalisable behaviour by AWS leads to the outputting of undeclared and - # unsupported state modifiers. Such as ResultSelector, which is neither defined in - # this Pass state, nor supported by Pass states. - "$..inspectionData.afterInputPath", - "$..inspectionData.afterParameters", - "$..inspectionData.afterResultPath", - "$..inspectionData.afterResultSelector", - ] - ) @pytest.mark.parametrize( "tct_template,execution_input", BASE_TEMPLATE_INPUT_BINDINGS, @@ -163,16 +151,6 @@ def test_base_inspection_level_trace( sfn_snapshot.match("test_case_response", test_case_response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Unknown generalisable behaviour by AWS leads to the outputting of undeclared and - # unsupported state modifiers. - "$..inspectionData.afterInputPath", - "$..inspectionData.afterParameters", - "$..inspectionData.afterResultPath", - "$..inspectionData.afterResultSelector", - ] - ) @pytest.mark.parametrize( "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] ) @@ -209,14 +187,6 @@ def test_base_lambda_task_state( sfn_snapshot.match("test_case_response", test_case_response) @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Unknown generalisable behaviour by AWS leads to the outputting of undeclared state modifiers. - "$..inspectionData.afterInputPath", - "$..inspectionData.afterResultPath", - "$..inspectionData.afterResultSelector", - ] - ) @pytest.mark.parametrize( "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] ) @@ -250,3 +220,248 @@ def test_base_lambda_service_task_state( inspectionLevel=inspection_level, ) sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + def test_base_lambda_service_task_state_no_role_arn_validation( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + ): + function_name = f"lambda_func_{short_uid()}" + sfn_snapshot.add_transformer(RegexTransformer(function_name, "")) + + template = TST.load_sfn_template(TST.BASE_LAMBDA_SERVICE_TASK_STATE) + definition = json.dumps(template) + exec_input = json.dumps({"FunctionName": function_name, "Payload": None}) + + with pytest.raises(Exception) as e: + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=exec_input, + inspectionLevel=InspectionLevel.TRACE, + ) + sfn_snapshot.match("validation_exception", e.value.response) + + @markers.aws.validated + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + @pytest.mark.parametrize( + "expression_dict", + [ + {"MaxConcurrency": EJT.JSONATA_NUMBER_EXPRESSION}, + {"ToleratedFailurePercentage": EJT.JSONATA_NUMBER_EXPRESSION}, + {"ToleratedFailureCount": EJT.JSONATA_NUMBER_EXPRESSION}, + ], + ids=[ + "MAX_CONCURRENCY", + "TOLERATED_FAILURE_PERCENTAGE", + "TOLERATED_FAILURE_COUNT", + ], + ) + def test_base_map_state_inspect( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + expression_dict, + inspection_level, + ): + sfn_snapshot.add_transformer(sfn_snapshot.transform.resource_name()) + + sfn_role_arn = create_state_machine_iam_role(aws_client) + template = TST.load_sfn_template(TST.BASE_MAP_STATE) + template.update(expression_dict) + + definition = json.dumps(template) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=BASE_MAP_STATE_INPUT, + inspectionLevel=inspection_level, + mock={"result": json.dumps([1, 1, 1])}, + ) + sfn_snapshot.match("test_case_response", test_case_response) + + @markers.aws.validated + @pytest.mark.parametrize( + "inspection_level", [InspectionLevel.INFO, InspectionLevel.DEBUG, InspectionLevel.TRACE] + ) + def test_state_task_catch_error( + self, + aws_client, + aws_client_no_sync_prefix, + create_state_machine_iam_role, + sfn_snapshot, + inspection_level, + ): + sfn_snapshot.add_transformer(sfn_snapshot.transform.resource_name()) + + sfn_role_arn = create_state_machine_iam_role(aws_client) + template = TST.load_sfn_template(TST.BASE_TASK_STATE_CATCH) + definition = json.dumps(template) + + catch_mock_exception_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=HELLO_WORLD_INPUT, + inspectionLevel=inspection_level, + mock={ + "errorOutput": {"error": "MockException", "cause": "Mock the cause of the error."} + }, + ) + + sfn_snapshot.match("test_catch_mock_exception_response", catch_mock_exception_response) + + catch_task_failed_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + roleArn=sfn_role_arn, + input=HELLO_WORLD_INPUT, + inspectionLevel=inspection_level, + mock={"errorOutput": {"error": "States.TaskFailed", "cause": "The task failed."}}, + ) + + sfn_snapshot.match("catch_task_failed_response", catch_task_failed_response) + + @markers.aws.validated + def test_localstack_blogpost_scenario( + self, + aws_client, + aws_client_no_sync_prefix, + sfn_snapshot, + region_name, + ): + template = TSMT.load_sfn_template(TSMT.LOCALSTACK_BLOGPOST_SCENARIO_STATE_MACHINE) + template["States"]["Ask for Approval"]["Arguments"]["ApiEndpoint"] = ( + f"example.execute-api.{region_name}.amazonaws.com" + ) + definition = json.dumps(template) + + # Step 1 - Testing the Approval Required state + # 1.1 Approval Required state correctly approves small purchases + + small_purchase_input = json.dumps({"cost": 9}) + + small_purchase_approval_required_response = ( + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=small_purchase_input, + stateName="Approval Required", + inspectionLevel=InspectionLevel.TRACE, + ) + ) + sfn_snapshot.match( + "small_purchase_approval_required_response", small_purchase_approval_required_response + ) + + # 1.2 Approval Required state correctly sends large purchases to the approval ask process + + large_purchase_input = json.dumps({"cost": 10}) + + large_purchase_approval_required_response = ( + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=large_purchase_input, + stateName="Approval Required", + inspectionLevel=InspectionLevel.TRACE, + ) + ) + sfn_snapshot.match( + "large_purchase_approval_required_response", large_purchase_approval_required_response + ) + + # Step 2 - Testing the Approval Ask state + # Approval Ask state correctly approves large purchases + + large_purchase_input = json.dumps({"cost": 10}) + + large_purchase_ask_for_approval_response = ( + aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=large_purchase_input, + stateName="Ask for Approval", + mock={"result": '{"approval": true }'}, + inspectionLevel=InspectionLevel.TRACE, + ) + ) + sfn_snapshot.match( + "large_purchase_ask_for_approval_response", large_purchase_ask_for_approval_response + ) + + # Step 3 - Testing the Check Approval state + # 3.1 Approval granted + + check_approval_granted_input = json.dumps( + {"approval": True, "approval_code": "2387462", "approved_by": "Mary"} + ) + + check_approval_granted_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=check_approval_granted_input, + stateName="Check Approval", + inspectionLevel=InspectionLevel.TRACE, + ) + sfn_snapshot.match("check_approval_granted_response", check_approval_granted_response) + + # 3.2 Approval denied + + check_approval_denied_input = json.dumps({"approval": False}) + + check_approval_denied_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + input=check_approval_denied_input, + stateName="Check Approval", + inspectionLevel=InspectionLevel.TRACE, + ) + sfn_snapshot.match("check_approval_denied_response", check_approval_denied_response) + + TEMPLATES_VARIABLES = [ + pytest.param( + ( + OT.BASE_EXPR, + { + "var_input_value": "test_value", + "var_constant_1": 10, + }, + ), + id="base_expressions", + ), + pytest.param((OT.BASE_LITERALS, {}), id="empty_variables"), + ] + + @pytest.mark.parametrize("templates_variables", TEMPLATES_VARIABLES) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..RoleArn", # needed until optional roleArn is introduced for test state, see comment in provider implementation. + "$..output.ja_states_context.State.Name", # currently, test state returns "StateName" as a state name in AWS. We return actual state name. Adding to skip as not worth a special condition in the code + ] + ) + def test_state_with_variables( + self, + aws_client_no_sync_prefix, + sfn_snapshot, + templates_variables, + ): + sfn_snapshot.add_transformer( + sfn_snapshot.transform.key_value("RoleArn", "role-arn", reference_replacement=False) + ) # roleArn is null in AWS but is not null in LocalStack, see comment in provider implementation. If this transformer is not added then roleArn is picked up as another resource name in LocalStack, causing resource placeholders mismatch, e.g. where is expected. + sfn_snapshot.add_transformer(sfn_snapshot.transform.resource_name()) + template_path, variables_raw = templates_variables + template = OT.load_sfn_template(template_path) + + definition = json.dumps(template) + + exec_input = json.dumps({"input_values": [1, 2, 3]}) + variables = json.dumps(variables_raw) + + test_case_response = aws_client_no_sync_prefix.stepfunctions.test_state( + definition=definition, + stateName="State0", + input=exec_input, + variables=variables, + inspectionLevel=InspectionLevel.TRACE, + ) + sfn_snapshot.match("test_case_response", test_case_response) diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.snapshot.json b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.snapshot.json index 1e11cdcc339f2..a70b54e455d41 100644 --- a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.snapshot.json +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:45:35", + "recorded-date": "14-11-2025, 16:38:49", "recorded-content": { "test_case_response": { "output": { @@ -15,7 +15,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_RESULT_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:45:49", + "recorded-date": "14-11-2025, 16:39:04", "recorded-content": { "test_case_response": { "output": { @@ -30,7 +30,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:46:03", + "recorded-date": "14-11-2025, 16:39:19", "recorded-content": { "test_case_response": { "output": { @@ -55,7 +55,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_RESULT_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:46:15", + "recorded-date": "14-11-2025, 16:39:34", "recorded-content": { "test_case_response": { "output": { @@ -79,7 +79,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_FAIL_STATE]": { - "recorded-date": "12-04-2024, 20:46:28", + "recorded-date": "14-11-2025, 16:39:49", "recorded-content": { "test_case_response": { "cause": "This state machines raises a 'SomeFailure' failure.", @@ -93,7 +93,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_SUCCEED_STATE]": { - "recorded-date": "12-04-2024, 20:46:41", + "recorded-date": "14-11-2025, 16:40:09", "recorded-content": { "test_case_response": { "output": { @@ -108,7 +108,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_CHOICE_STATE]": { - "recorded-date": "12-04-2024, 20:46:53", + "recorded-date": "14-11-2025, 16:40:25", "recorded-content": { "test_case_response": { "nextState": "ValueInTwenties", @@ -125,7 +125,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:47:06", + "recorded-date": "14-11-2025, 16:40:40", "recorded-content": { "test_case_response": { "inspectionData": { @@ -157,7 +157,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_RESULT_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:47:19", + "recorded-date": "14-11-2025, 16:40:54", "recorded-content": { "test_case_response": { "inspectionData": { @@ -186,7 +186,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:47:31", + "recorded-date": "14-11-2025, 16:41:09", "recorded-content": { "test_case_response": { "inspectionData": { @@ -247,7 +247,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_RESULT_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:47:44", + "recorded-date": "14-11-2025, 16:41:23", "recorded-content": { "test_case_response": { "inspectionData": { @@ -300,7 +300,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_FAIL_STATE]": { - "recorded-date": "12-04-2024, 20:47:56", + "recorded-date": "14-11-2025, 16:41:38", "recorded-content": { "test_case_response": { "cause": "This state machines raises a 'SomeFailure' failure.", @@ -322,7 +322,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_SUCCEED_STATE]": { - "recorded-date": "12-04-2024, 20:48:10", + "recorded-date": "14-11-2025, 16:41:59", "recorded-content": { "test_case_response": { "inspectionData": { @@ -345,7 +345,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_CHOICE_STATE]": { - "recorded-date": "12-04-2024, 20:48:24", + "recorded-date": "14-11-2025, 16:42:14", "recorded-content": { "test_case_response": { "inspectionData": { @@ -372,7 +372,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:48:37", + "recorded-date": "14-11-2025, 16:42:30", "recorded-content": { "test_case_response": { "inspectionData": { @@ -404,7 +404,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_RESULT_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:48:50", + "recorded-date": "14-11-2025, 16:42:45", "recorded-content": { "test_case_response": { "inspectionData": { @@ -433,7 +433,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:49:03", + "recorded-date": "14-11-2025, 16:43:00", "recorded-content": { "test_case_response": { "inspectionData": { @@ -494,7 +494,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_RESULT_PASS_STATE]": { - "recorded-date": "12-04-2024, 20:49:22", + "recorded-date": "14-11-2025, 16:43:16", "recorded-content": { "test_case_response": { "inspectionData": { @@ -547,7 +547,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_FAIL_STATE]": { - "recorded-date": "12-04-2024, 20:49:31", + "recorded-date": "14-11-2025, 16:43:31", "recorded-content": { "test_case_response": { "cause": "This state machines raises a 'SomeFailure' failure.", @@ -569,7 +569,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_SUCCEED_STATE]": { - "recorded-date": "12-04-2024, 20:49:44", + "recorded-date": "14-11-2025, 16:43:45", "recorded-content": { "test_case_response": { "inspectionData": { @@ -592,7 +592,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_CHOICE_STATE]": { - "recorded-date": "12-04-2024, 20:49:57", + "recorded-date": "14-11-2025, 16:44:00", "recorded-content": { "test_case_response": { "inspectionData": { @@ -619,7 +619,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[INFO]": { - "recorded-date": "12-04-2024, 20:50:22", + "recorded-date": "14-11-2025, 16:44:29", "recorded-content": { "test_case_response": { "output": "\"HelloWorld!\"", @@ -632,7 +632,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[DEBUG]": { - "recorded-date": "12-04-2024, 20:50:37", + "recorded-date": "14-11-2025, 16:44:48", "recorded-content": { "test_case_response": { "inspectionData": { @@ -659,7 +659,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[TRACE]": { - "recorded-date": "12-04-2024, 20:50:52", + "recorded-date": "14-11-2025, 16:45:08", "recorded-content": { "test_case_response": { "inspectionData": { @@ -686,7 +686,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[INFO]": { - "recorded-date": "12-04-2024, 20:51:07", + "recorded-date": "14-11-2025, 16:45:27", "recorded-content": { "test_case_response": { "output": { @@ -703,9 +703,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -722,13 +720,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -741,7 +739,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[DEBUG]": { - "recorded-date": "12-04-2024, 20:51:22", + "recorded-date": "14-11-2025, 16:45:46", "recorded-content": { "test_case_response": { "inspectionData": { @@ -767,9 +765,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -786,13 +782,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -810,9 +806,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -829,13 +823,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -857,9 +851,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -876,13 +868,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -901,9 +893,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -920,13 +910,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -939,7 +929,7 @@ } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[TRACE]": { - "recorded-date": "12-04-2024, 20:51:37", + "recorded-date": "14-11-2025, 16:46:06", "recorded-content": { "test_case_response": { "inspectionData": { @@ -965,9 +955,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -984,13 +972,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1008,9 +996,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -1027,13 +1013,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1055,9 +1041,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -1074,13 +1058,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 } @@ -1099,9 +1083,7 @@ "Connection": [ "keep-alive" ], - "x-amzn-RequestId": [ - "" - ], + "x-amzn-RequestId": "x-amzn-RequestId", "Content-Length": [ "2" ], @@ -1118,13 +1100,13 @@ "Date": "date", "X-Amz-Executed-Version": "$LATEST", "x-amzn-Remapped-Content-Length": "0", - "x-amzn-RequestId": "", + "x-amzn-RequestId": "x-amzn-RequestId", "X-Amzn-Trace-Id": "X-Amzn-Trace-Id" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { - "RequestId": "" + "RequestId": "RequestId" }, "StatusCode": 200 }, @@ -1135,5 +1117,1258 @@ } } } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect_trace[MAX_CONCURRENCY]": { + "recorded-date": "12-11-2025, 15:25:18", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 1 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect_trace[TOLERATED_FAILURE_PERCENTAGE]": { + "recorded-date": "12-11-2025, 14:19:49", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 0, + "toleratedFailurePercentage": 1.0 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect_trace[TOLERATED_FAILURE_COUNT]": { + "recorded-date": "12-11-2025, 14:20:04", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_MAP_STATE]": { + "recorded-date": "13-11-2025, 11:02:39", + "recorded-content": {} + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[MAX_CONCURRENCY-INFO]": { + "recorded-date": "14-11-2025, 16:46:24", + "recorded-content": { + "test_case_response": { + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[MAX_CONCURRENCY-DEBUG]": { + "recorded-date": "14-11-2025, 16:46:39", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 1 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[MAX_CONCURRENCY-TRACE]": { + "recorded-date": "14-11-2025, 16:46:54", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 1 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_PERCENTAGE-INFO]": { + "recorded-date": "14-11-2025, 16:47:08", + "recorded-content": { + "test_case_response": { + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_PERCENTAGE-DEBUG]": { + "recorded-date": "14-11-2025, 16:47:23", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 0, + "toleratedFailurePercentage": 1.0 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_PERCENTAGE-TRACE]": { + "recorded-date": "14-11-2025, 16:47:38", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 0, + "toleratedFailurePercentage": 1.0 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_COUNT-INFO]": { + "recorded-date": "14-11-2025, 16:47:54", + "recorded-content": { + "test_case_response": { + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_COUNT-DEBUG]": { + "recorded-date": "14-11-2025, 16:48:09", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_COUNT-TRACE]": { + "recorded-date": "14-11-2025, 16:48:24", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "maxConcurrency": 0, + "toleratedFailureCount": 1 + }, + "output": { + "input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "result": [ + 1, + 1, + 1 + ] + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_task_catch_error[INFO]": { + "recorded-date": "21-11-2025, 13:59:35", + "recorded-content": { + "test_catch_mock_exception_response": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "nextState": "HandleMockError", + "output": { + "input": { + "Value": "HelloWorld" + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Value": "HelloWorld" + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "MockException", + "Cause": "Mock the cause of the error." + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "catch_task_failed_response": { + "cause": "The task failed.", + "error": "States.TaskFailed", + "nextState": "HandleThreshold", + "output": { + "input": { + "Value": "HelloWorld" + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Value": "HelloWorld" + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "States.TaskFailed", + "Cause": "The task failed." + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_task_catch_error[DEBUG]": { + "recorded-date": "21-11-2025, 13:59:50", + "recorded-content": { + "test_catch_mock_exception_response": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "catchIndex": 0 + }, + "input": { + "Value": "HelloWorld" + } + }, + "nextState": "HandleMockError", + "output": { + "input": { + "Value": "HelloWorld" + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Value": "HelloWorld" + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "MockException", + "Cause": "Mock the cause of the error." + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "catch_task_failed_response": { + "cause": "The task failed.", + "error": "States.TaskFailed", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "catchIndex": 1 + }, + "input": { + "Value": "HelloWorld" + } + }, + "nextState": "HandleThreshold", + "output": { + "input": { + "Value": "HelloWorld" + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Value": "HelloWorld" + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "States.TaskFailed", + "Cause": "The task failed." + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_task_catch_error[TRACE]": { + "recorded-date": "21-11-2025, 14:00:05", + "recorded-content": { + "test_catch_mock_exception_response": { + "cause": "Mock the cause of the error.", + "error": "MockException", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "catchIndex": 0 + }, + "input": { + "Value": "HelloWorld" + } + }, + "nextState": "HandleMockError", + "output": { + "input": { + "Value": "HelloWorld" + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Value": "HelloWorld" + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "MockException", + "Cause": "Mock the cause of the error." + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "catch_task_failed_response": { + "cause": "The task failed.", + "error": "States.TaskFailed", + "inspectionData": { + "afterArguments": { + "FunctionName": "foo", + "Payload": "bar" + }, + "errorDetails": { + "catchIndex": 1 + }, + "input": { + "Value": "HelloWorld" + } + }, + "nextState": "HandleThreshold", + "output": { + "input": { + "Value": "HelloWorld" + }, + "context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "Value": "HelloWorld" + }, + "Name": "", + "RoleArn": "arn::iam::111111111111:role/", + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date", + "RetryCount": 0 + } + }, + "errorOutput": { + "Error": "States.TaskFailed", + "Cause": "The task failed." + } + }, + "status": "CAUGHT_ERROR", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_localstack_blogpost_scenario": { + "recorded-date": "02-12-2025, 23:11:05", + "recorded-content": { + "small_purchase_approval_required_response": { + "inspectionData": { + "input": { + "cost": 9 + } + }, + "nextState": "Purchase Approved", + "output": { + "cost": 9 + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "large_purchase_approval_required_response": { + "inspectionData": { + "input": { + "cost": 10 + } + }, + "nextState": "Ask for Approval", + "output": { + "cost": 10 + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "large_purchase_ask_for_approval_response": { + "inspectionData": { + "afterArguments": { + "Method": "POST", + "ApiEndpoint": "example.execute-api..amazonaws.com", + "Path": "/approval", + "RequestBody": { + "cost": 10, + "dept_id": "12345678" + }, + "AuthType": "NO_AUTH" + }, + "input": { + "cost": 10 + }, + "result": { + "approval": true + } + }, + "nextState": "Check Approval", + "output": { + "approval": true, + "approval_code": "2387462", + "approved_by": "Mary" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "check_approval_granted_response": { + "inspectionData": { + "input": { + "approval": true, + "approval_code": "2387462", + "approved_by": "Mary" + } + }, + "nextState": "Purchase Approved", + "output": { + "approval": true, + "approval_code": "2387462", + "approved_by": "Mary" + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "check_approval_denied_response": { + "inspectionData": { + "input": { + "approval": false + } + }, + "nextState": "Not Approved", + "output": { + "approval": false + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state_no_role_arn_validation": { + "recorded-date": "03-12-2025, 00:38:08", + "recorded-content": { + "validation_exception": { + "Error": { + "Code": "ValidationException", + "Message": "RoleArn must be specified when testing a Task state" + }, + "message": "RoleArn must be specified when testing a Task state", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_with_variables[base_expressions]": { + "recorded-date": "24-02-2026, 11:15:19", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "input_values": [ + 1, + 2, + 3 + ] + }, + "variables": { + "var_input_value": "test_value", + "var_constant_1": 10 + } + }, + "output": { + "ja_states_context": { + "Execution": { + "Id": "arn::states::111111111111:express::", + "Input": { + "input_values": [ + 1, + 2, + 3 + ] + }, + "Name": "", + "RoleArn": null, + "StartTime": "date" + }, + "StateMachine": { + "Id": "arn::states::111111111111:stateMachine:", + "Name": "" + }, + "State": { + "Name": "StateName", + "EnteredTime": "date" + } + }, + "ja_states_input": { + "input_values": [ + 1, + 2, + 3 + ] + }, + "ja_var_access": "test_value", + "ja_expr": 16 + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_with_variables[empty_variables]": { + "recorded-date": "24-02-2026, 11:15:19", + "recorded-content": { + "test_case_response": { + "inspectionData": { + "input": { + "input_values": [ + 1, + 2, + 3 + ] + } + }, + "output": { + "constant_null": null, + "constant_int": 0, + "constant_float": 0.1, + "constant_bool": true, + "constant_str": "constant string", + "constant_not_jsonata": " {% states.input %} ", + "constant_varpath_states": "$states.input", + "constant_varpath": "$no.such.var.path", + "constant_jp_input": "$", + "constant_jp_input.$": "$", + "constant_jp_input_path": "$.input_value", + "constant_jp_context": "$$", + "constant_if": "States.Format('Format:{}', 101)", + "constant_lst_empty": [], + "constant_lst": [ + null, + 0, + 0.1, + true, + [], + { + "constant": 0 + }, + " {% states.input %} ", + "$states.input", + "$no.such.var.path" + ], + "constant_obj_empty": {}, + "constant_obj": { + "in_obj_constant_null": null, + "in_obj_constant_int": 0, + "in_obj_constant_float": 0.1, + "in_obj_constant_bool": true, + "in_obj_constant_str": "constant string", + "in_obj_constant_not_jsonata": " {% states.input %} ", + "in_obj_constant_lst_empty": [], + "in_obj_constant_lst": [ + null, + 0, + 0.1, + true, + [], + { + "constant": 0 + }, + " {% states.input %} ", + "$states.input", + "$no.such.var.path" + ], + "in_obj_constant_obj_empty": {}, + "in_obj_constant_obj": { + "constant": 0 + } + } + }, + "status": "SUCCEEDED", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.validation.json b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.validation.json index 6ee1aeac5b542..682e0e35d2d66 100644 --- a/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.validation.json +++ b/tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.validation.json @@ -1,83 +1,416 @@ { "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_CHOICE_STATE]": { - "last_validated_date": "2024-04-12T20:48:24+00:00" + "last_validated_date": "2025-11-14T16:42:16+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.57, + "teardown": 1.66, + "total": 15.23 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_FAIL_STATE]": { - "last_validated_date": "2024-04-12T20:47:56+00:00" + "last_validated_date": "2025-11-14T16:41:40+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.65, + "teardown": 1.75, + "total": 15.4 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:47:06+00:00" + "last_validated_date": "2025-11-14T16:40:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.21, + "teardown": 1.79, + "total": 15.0 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_RESULT_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:47:19+00:00" + "last_validated_date": "2025-11-14T16:40:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.84, + "teardown": 1.78, + "total": 14.62 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_SUCCEED_STATE]": { - "last_validated_date": "2024-04-12T20:48:10+00:00" + "last_validated_date": "2025-11-14T16:42:00+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.47, + "teardown": 1.74, + "total": 20.21 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:47:31+00:00" + "last_validated_date": "2025-11-14T16:41:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.56, + "teardown": 1.65, + "total": 14.21 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_RESULT_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:47:44+00:00" + "last_validated_date": "2025-11-14T16:41:25+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.87, + "teardown": 1.64, + "total": 14.51 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_CHOICE_STATE]": { - "last_validated_date": "2024-04-12T20:46:53+00:00" + "last_validated_date": "2025-11-14T16:40:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.61, + "teardown": 1.77, + "total": 15.38 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_FAIL_STATE]": { - "last_validated_date": "2024-04-12T20:46:28+00:00" + "last_validated_date": "2025-11-14T16:39:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.73, + "teardown": 1.81, + "total": 14.54 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:45:35+00:00" + "last_validated_date": "2025-11-14T16:38:51+00:00", + "durations_in_seconds": { + "setup": 1.16, + "call": 15.38, + "teardown": 1.95, + "total": 18.49 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_RESULT_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:45:49+00:00" + "last_validated_date": "2025-11-14T16:39:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.83, + "teardown": 1.75, + "total": 14.58 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_SUCCEED_STATE]": { - "last_validated_date": "2024-04-12T20:46:41+00:00" + "last_validated_date": "2025-11-14T16:40:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 18.15, + "teardown": 2.27, + "total": 20.42 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:46:03+00:00" + "last_validated_date": "2025-11-14T16:39:21+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.93, + "teardown": 1.85, + "total": 14.78 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_RESULT_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:46:15+00:00" + "last_validated_date": "2025-11-14T16:39:36+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.77, + "teardown": 1.7, + "total": 15.47 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_CHOICE_STATE]": { - "last_validated_date": "2024-04-12T20:49:57+00:00" + "last_validated_date": "2025-11-14T16:44:02+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.71, + "teardown": 1.82, + "total": 14.53 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_FAIL_STATE]": { - "last_validated_date": "2024-04-12T20:49:31+00:00" + "last_validated_date": "2025-11-14T16:43:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.87, + "teardown": 1.78, + "total": 14.65 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:48:37+00:00" + "last_validated_date": "2025-11-14T16:42:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.77, + "teardown": 1.86, + "total": 16.63 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_RESULT_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:48:50+00:00" + "last_validated_date": "2025-11-14T16:42:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.98, + "teardown": 1.84, + "total": 14.82 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_SUCCEED_STATE]": { - "last_validated_date": "2024-04-12T20:49:44+00:00" + "last_validated_date": "2025-11-14T16:43:47+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.54, + "teardown": 1.69, + "total": 14.23 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:49:03+00:00" + "last_validated_date": "2025-11-14T16:43:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.84, + "teardown": 2.72, + "total": 15.56 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_RESULT_PASS_STATE]": { - "last_validated_date": "2024-04-12T20:49:22+00:00" + "last_validated_date": "2025-11-14T16:43:18+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.82, + "teardown": 1.76, + "total": 15.58 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[DEBUG]": { - "last_validated_date": "2024-04-12T20:51:22+00:00" + "last_validated_date": "2025-11-14T16:45:49+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.84, + "teardown": 3.3, + "total": 19.14 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[INFO]": { - "last_validated_date": "2024-04-12T20:51:07+00:00" + "last_validated_date": "2025-11-14T16:45:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 15.59, + "teardown": 3.28, + "total": 18.87 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[TRACE]": { - "last_validated_date": "2024-04-12T20:51:37+00:00" + "last_validated_date": "2025-11-14T16:46:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.65, + "teardown": 3.49, + "total": 20.14 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state_no_role_arn_validation": { + "last_validated_date": "2025-12-03T00:38:08+00:00", + "durations_in_seconds": { + "setup": 0.53, + "call": 0.67, + "teardown": 0.0, + "total": 1.2 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[DEBUG]": { - "last_validated_date": "2024-04-12T20:50:37+00:00" + "last_validated_date": "2025-11-14T16:44:51+00:00", + "durations_in_seconds": { + "setup": 0.01, + "call": 15.42, + "teardown": 3.14, + "total": 18.57 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[INFO]": { - "last_validated_date": "2024-04-12T20:50:22+00:00" + "last_validated_date": "2025-11-14T16:44:33+00:00", + "durations_in_seconds": { + "setup": 11.62, + "call": 16.2, + "teardown": 3.41, + "total": 31.23 + } }, "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[TRACE]": { - "last_validated_date": "2024-04-12T20:50:52+00:00" + "last_validated_date": "2025-11-14T16:45:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.93, + "teardown": 3.07, + "total": 20.0 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[MAX_CONCURRENCY-DEBUG]": { + "last_validated_date": "2025-11-14T16:46:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.75, + "teardown": 1.87, + "total": 14.62 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[MAX_CONCURRENCY-INFO]": { + "last_validated_date": "2025-11-14T16:46:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 14.44, + "teardown": 1.86, + "total": 16.3 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[MAX_CONCURRENCY-TRACE]": { + "last_validated_date": "2025-11-14T16:46:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.99, + "teardown": 1.9, + "total": 14.89 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_COUNT-DEBUG]": { + "last_validated_date": "2025-11-14T16:48:11+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.69, + "teardown": 1.65, + "total": 15.34 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_COUNT-INFO]": { + "last_validated_date": "2025-11-14T16:47:56+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.98, + "teardown": 2.01, + "total": 14.99 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_COUNT-TRACE]": { + "last_validated_date": "2025-11-14T16:48:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.64, + "teardown": 2.6, + "total": 15.24 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_PERCENTAGE-DEBUG]": { + "last_validated_date": "2025-11-14T16:47:24+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.76, + "teardown": 1.79, + "total": 14.55 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_PERCENTAGE-INFO]": { + "last_validated_date": "2025-11-14T16:47:10+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.72, + "teardown": 1.77, + "total": 14.49 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect[TOLERATED_FAILURE_PERCENTAGE-TRACE]": { + "last_validated_date": "2025-11-14T16:47:41+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.78, + "teardown": 2.43, + "total": 16.21 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect_trace[MAX_CONCURRENCY]": { + "last_validated_date": "2025-11-12T15:25:20+00:00", + "durations_in_seconds": { + "setup": 1.65, + "call": 15.24, + "teardown": 1.84, + "total": 18.73 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect_trace[TOLERATED_FAILURE_COUNT]": { + "last_validated_date": "2025-11-12T14:20:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.61, + "teardown": 1.86, + "total": 14.47 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_map_state_inspect_trace[TOLERATED_FAILURE_PERCENTAGE]": { + "last_validated_date": "2025-11-12T14:19:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.68, + "teardown": 1.81, + "total": 14.49 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_localstack_blogpost_scenario": { + "last_validated_date": "2025-12-02T23:11:05+00:00", + "durations_in_seconds": { + "setup": 0.55, + "call": 1.26, + "teardown": 0.0, + "total": 1.81 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_task_catch_error[DEBUG]": { + "last_validated_date": "2025-11-21T13:59:51+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 12.82, + "teardown": 1.67, + "total": 14.49 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_task_catch_error[INFO]": { + "last_validated_date": "2025-11-21T13:59:37+00:00", + "durations_in_seconds": { + "setup": 1.32, + "call": 16.93, + "teardown": 1.76, + "total": 20.01 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_task_catch_error[TRACE]": { + "last_validated_date": "2025-11-21T14:00:06+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 13.14, + "teardown": 1.77, + "total": 14.91 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_with_variables[base_expressions]": { + "last_validated_date": "2026-02-24T11:15:19+00:00", + "durations_in_seconds": { + "setup": 0.49, + "call": 0.63, + "teardown": 0.0, + "total": 1.12 + } + }, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_state_with_variables[empty_variables]": { + "last_validated_date": "2026-02-24T11:15:19+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 0.22, + "teardown": 0.0, + "total": 0.22 + } } } diff --git a/tests/aws/services/sts/test_sts.py b/tests/aws/services/sts/test_sts.py index 9e430bbe8b489..e5163627ce914 100644 --- a/tests/aws/services/sts/test_sts.py +++ b/tests/aws/services/sts/test_sts.py @@ -3,6 +3,7 @@ import pytest import requests +from botocore.config import Config from botocore.exceptions import ClientError from localstack import config @@ -187,12 +188,8 @@ def test_assume_role_with_saml(self, aws_client): fed_name=fed_name, ).replace("\n", "") - role_arn = "arn:aws:iam::{account_id}:role/{role_name}".format( - account_id=account_id, role_name=role_name - ) - principal_arn = "arn:aws:iam:{account_id}:saml-provider/{provider_name}".format( - account_id=account_id, provider_name=provider_name - ) + role_arn = f"arn:aws:iam::{account_id}:role/{role_name}" + principal_arn = f"arn:aws:iam:{account_id}:saml-provider/{provider_name}" base64_saml_assertion = b64encode(saml_assertion.encode("utf-8")).decode("utf-8") response = aws_client.sts.assume_role_with_saml( RoleArn=role_arn, @@ -326,6 +323,45 @@ def test_get_caller_identity_role_access_key( assert fake_account_id == response["Account"] assert assume_role_response_other_account["AssumedRoleUser"]["Arn"] == response["Arn"] + @markers.aws.validated + def test_sts_invalid_parameters( + self, + aws_client_factory, + snapshot, + ): + aws_client = aws_client_factory(config=Config(parameter_validation=False)) + with pytest.raises(ClientError) as e: + aws_client.sts.assume_role(RoleArn="nothing-valid-in-here", RoleSessionName="Session1") + snapshot.match("malformed-arn", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.sts.assume_role( + RoleArn="arn::b:::something/test-role", RoleSessionName="Session1" + ) + snapshot.match("no-partition", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.sts.assume_role( + RoleArn="arn:a::::something/test-role", RoleSessionName="Session1" + ) + snapshot.match("no-service", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.sts.assume_role( + RoleArn="arn:a:::something/test-role", RoleSessionName="Session1" + ) + snapshot.match("not-enough-colons", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.sts.assume_role(RoleArn="arn:a:a::aaaaaaaaaa:", RoleSessionName="Session1") + snapshot.match("no-resource", e.value.response) + + with pytest.raises(ClientError) as e: + aws_client.sts.assume_role( + RoleArn="arn:a:b:::something/test-role", RoleSessionName="Session1:2" + ) + snapshot.match("invalid-session-name", e.value.response) + class TestSTSAssumeRoleTagging: @markers.aws.validated diff --git a/tests/aws/services/sts/test_sts.snapshot.json b/tests/aws/services/sts/test_sts.snapshot.json index b9c07c65bc9d5..c49aca93c2301 100644 --- a/tests/aws/services/sts/test_sts.snapshot.json +++ b/tests/aws/services/sts/test_sts.snapshot.json @@ -207,5 +207,76 @@ } } } + }, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_sts_invalid_parameters": { + "recorded-date": "21-07-2025, 19:25:22", + "recorded-content": { + "malformed-arn": { + "Error": { + "Code": "ValidationError", + "Message": "nothing-valid-in-here is invalid", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "no-partition": { + "Error": { + "Code": "ValidationError", + "Message": "arn::b:::something/test-role is invalid", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "no-service": { + "Error": { + "Code": "ValidationError", + "Message": "arn:a::::something/test-role is invalid", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "not-enough-colons": { + "Error": { + "Code": "ValidationError", + "Message": "arn:a:::something/test-role is invalid", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "no-resource": { + "Error": { + "Code": "ValidationError", + "Message": "arn:a:a::aaaaaaaaaa: is invalid", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + }, + "invalid-session-name": { + "Error": { + "Code": "ValidationError", + "Message": "1 validation error detected: Value 'Session1:2' at 'roleSessionName' failed to satisfy constraint: Member must satisfy regular expression pattern: [\\w+=,.@-]*", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/sts/test_sts.validation.json b/tests/aws/services/sts/test_sts.validation.json index e651d68a58e60..dde34ba006cd9 100644 --- a/tests/aws/services/sts/test_sts.validation.json +++ b/tests/aws/services/sts/test_sts.validation.json @@ -5,6 +5,15 @@ "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_iam_role_chaining_override_transitive_tags": { "last_validated_date": "2025-04-10T08:53:00+00:00" }, + "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_sts_invalid_parameters": { + "last_validated_date": "2025-07-21T18:33:53+00:00", + "durations_in_seconds": { + "setup": 1.21, + "call": 0.67, + "teardown": 0.0, + "total": 1.88 + } + }, "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role": { "last_validated_date": "2024-06-05T17:23:49+00:00" }, @@ -19,5 +28,14 @@ }, "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_iam_role_chaining_override_transitive_tags": { "last_validated_date": "2025-04-10T08:08:37+00:00" + }, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_sts_invalid_parameters": { + "last_validated_date": "2025-07-21T19:25:22+00:00", + "durations_in_seconds": { + "setup": 1.19, + "call": 3.28, + "teardown": 0.0, + "total": 4.47 + } } } diff --git a/tests/aws/services/swf/test_swf.py b/tests/aws/services/swf/test_swf.py index ba42346656a4e..3871fccd14b66 100644 --- a/tests/aws/services/swf/test_swf.py +++ b/tests/aws/services/swf/test_swf.py @@ -15,9 +15,9 @@ def test_run_workflow(self, aws_client): swf_client = aws_client.swf swf_unique_id = short_uid() - workflow_domain_name = "test-swf-domain-{}".format(swf_unique_id) - workflow_type_name = "test-swf-workflow-{}".format(swf_unique_id) - workflow_activity_name = "test-swf-activity-{}".format(swf_unique_id) + workflow_domain_name = f"test-swf-domain-{swf_unique_id}" + workflow_type_name = f"test-swf-workflow-{swf_unique_id}" + workflow_activity_name = f"test-swf-activity-{swf_unique_id}" swf_client.register_domain( name=workflow_domain_name, workflowExecutionRetentionPeriodInDays="1" diff --git a/tests/aws/services/transcribe/test_transcribe.py b/tests/aws/services/transcribe/test_transcribe.py index e3235ade6ce8b..689b4ad7fe011 100644 --- a/tests/aws/services/transcribe/test_transcribe.py +++ b/tests/aws/services/transcribe/test_transcribe.py @@ -14,6 +14,7 @@ from localstack.packages.ffmpeg import ffmpeg_package from localstack.services.transcribe.packages import vosk_package from localstack.services.transcribe.provider import LANGUAGE_MODELS, TranscribeProvider +from localstack.testing import config as test_config from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.files import new_tmp_file @@ -97,6 +98,10 @@ def transcribe_snapshot_transformer(snapshot): class TestTranscribe: @pytest.fixture(scope="class", autouse=True) def pre_install_dependencies(self): + if is_aws_cloud() or test_config.TEST_SKIP_LOCALSTACK_START: + # we don't install the dependencies if LocalStack is not running in process + return + if not ffmpeg_installed.is_set() or not vosk_installed.is_set(): install_async() @@ -111,7 +116,6 @@ def pre_install_dependencies(self): LOG.info("Spent %s seconds downloading transcribe dependencies", int(time.time() - start)) assert not installation_errored.is_set(), "installation of transcribe dependencies failed" - yield @staticmethod def _wait_transcription_job( @@ -442,6 +446,7 @@ def test_transcribe_error_speaker_labels(self, transcribe_create_job, aws_client transcribe_create_job(audio_file=file_path, params=settings) snapshot.match("err_speaker_labels_diarization", e.value) + @markers.requires_in_process # test relies on the installation of ffmpeg @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ @@ -455,7 +460,7 @@ def test_transcribe_error_invalid_length(self, transcribe_create_job, aws_client media_file = os.path.join(tempfile.gettempdir(), "audio_4h.mp3") run( - f"{ffmpeg_bin} -f lavfi -i anullsrc=r=44100:cl=mono -t 14400 -q:a 9 -acodec libmp3lame {media_file}" + f"{ffmpeg_bin} -f lavfi -i anullsrc=r=44100:cl=mono -t 14401 -q:a 9 -acodec libmp3lame {media_file}" ) job_name = transcribe_create_job(audio_file=media_file) diff --git a/tests/aws/templates/apigateway-mock-cors-deployment.json b/tests/aws/templates/apigateway-mock-cors-deployment.json new file mode 100644 index 0000000000000..a3b19161143d3 --- /dev/null +++ b/tests/aws/templates/apigateway-mock-cors-deployment.json @@ -0,0 +1,86 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "Api": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "cors-test-api" + } + }, + "MethodGet": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "RestApiId": { "Ref": "Api" }, + "ResourceId": { "Fn::GetAtt": ["Api", "RootResourceId"] }, + "HttpMethod": "GET", + "AuthorizationType": "NONE", + "Integration": { + "Type": "MOCK", + "RequestTemplates": { + "application/json": "{\"statusCode\":200}" + }, + "IntegrationResponses": [ + { + "StatusCode": "200", + "ResponseTemplates": { + "application/json": "{\"msg\":\"{{ response_msg }}\"}" + } + } + ] + }, + "MethodResponses": [{ "StatusCode": "200" }] + } + }, + "MethodOptions": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "RestApiId": { "Ref": "Api" }, + "ResourceId": { "Fn::GetAtt": ["Api", "RootResourceId"] }, + "HttpMethod": "OPTIONS", + "AuthorizationType": "NONE", + "Integration": { + "Type": "MOCK", + "RequestTemplates": { + "application/json": "{\"statusCode\":200}" + }, + "IntegrationResponses": [ + { + "StatusCode": "200", + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type'", + "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + }, + "ResponseTemplates": { + "application/json": "{\"statusCode\":200}" + } + } + ] + }, + "MethodResponses": [ + { + "StatusCode": "200", + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": false, + "method.response.header.Access-Control-Allow-Methods": false, + "method.response.header.Access-Control-Allow-Origin": false + } + } + ] + } + }, + "{{ deployment_logical_id }}": { + "Type": "AWS::ApiGateway::Deployment", + "DependsOn": ["MethodGet", "MethodOptions"], + "Properties": { + "RestApiId": { "Ref": "Api" }, + "StageName": "local" + } + } + }, + "Outputs": { + "ApiId": { + "Value": { "Ref": "Api" } + } + } +} diff --git a/tests/aws/templates/apigateway_basepath_mapping.yaml b/tests/aws/templates/apigateway_basepath_mapping.yaml new file mode 100644 index 0000000000000..38027b982b404 --- /dev/null +++ b/tests/aws/templates/apigateway_basepath_mapping.yaml @@ -0,0 +1,57 @@ +Parameters: + CertificateArn: + Type: String + + DomainName: + Type: String +Resources: + MyRestApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: test-api + MyMethod: + Type: AWS::ApiGateway::Method + Properties: + RestApiId: + Ref: MyRestApi + ResourceId: + Fn::GetAtt: + - MyRestApi + - RootResourceId + HttpMethod: GET + AuthorizationType: NONE + Integration: + Type: MOCK + RequestTemplates: + application/json: '{"statusCode": 200}' + MethodResponses: + - StatusCode: 200 + MyDeployment: + Type: AWS::ApiGateway::Deployment + Properties: + RestApiId: + Ref: MyRestApi + DependsOn: + - MyMethod + MyStage: + Type: AWS::ApiGateway::Stage + Properties: + RestApiId: + Ref: MyRestApi + DeploymentId: + Ref: MyDeployment + StageName: prod + MyDomainName: + Type: AWS::ApiGateway::DomainName + Properties: + DomainName: !Ref DomainName + CertificateArn: !Ref CertificateArn + MyBasePathMapping: + Type: AWS::ApiGateway::BasePathMapping + Properties: + DomainName: + Ref: MyDomainName + RestApiId: + Ref: MyRestApi + Stage: + Ref: MyStage diff --git a/tests/aws/templates/apigateway_canary_deployment.yml b/tests/aws/templates/apigateway_canary_deployment.yml new file mode 100644 index 0000000000000..4cf7ea8dd7792 --- /dev/null +++ b/tests/aws/templates/apigateway_canary_deployment.yml @@ -0,0 +1,59 @@ +Parameters: + RestApiName: + Type: String + +Resources: + RestApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: !Ref RestApiName + Stage: + Type: AWS::ApiGateway::Stage + Properties: + RestApiId: + Ref: RestApi + DeploymentId: + Ref: ApiDeployment + StageName: prod + Variables: + lambdaAlias: Prod + + MockMethod: + Type: 'AWS::ApiGateway::Method' + Properties: + RestApiId: !Ref RestApi + ResourceId: !GetAtt + - RestApi + - RootResourceId + HttpMethod: POST + AuthorizationType: NONE + Integration: + Type: MOCK + + ApiDeployment: + Type: AWS::ApiGateway::Deployment + Properties: + RestApiId: + Ref: RestApi + Description: "basic deployment" + DependsOn: + - MockMethod + + ApiCanaryDeployment: + Type: AWS::ApiGateway::Deployment + Properties: + RestApiId: + Ref: RestApi + Description: "canary description" + DeploymentCanarySettings: + PercentTraffic: 50 + StageVariableOverrides: + lambdaAlias: Dev + StageName: prod + DependsOn: + - MockMethod + - Stage + +Outputs: + RestApiId: + Value: !GetAtt RestApi.RestApiId diff --git a/tests/aws/templates/apigateway_stage_access_log_settings.yaml b/tests/aws/templates/apigateway_stage_access_log_settings.yaml new file mode 100644 index 0000000000000..19d99f3134b69 --- /dev/null +++ b/tests/aws/templates/apigateway_stage_access_log_settings.yaml @@ -0,0 +1,52 @@ +Parameters: + RestApiName: + Type: String + +Resources: + RestApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: !Ref RestApiName + + MockMethod: + Type: 'AWS::ApiGateway::Method' + Properties: + RestApiId: !Ref RestApi + ResourceId: !GetAtt + - RestApi + - RootResourceId + HttpMethod: POST + AuthorizationType: NONE + Integration: + Type: MOCK + + ApiDeployment: + Type: AWS::ApiGateway::Deployment + Properties: + RestApiId: + Ref: RestApi + Description: "basic deployment" + DependsOn: + - MockMethod + + TestStage: + Type: AWS::ApiGateway::Stage + Properties: + StageName: test + RestApiId: !Ref RestApi + DeploymentId: !Ref ApiDeployment + Description: "test stage description" + AccessLogSetting: + DestinationArn: !GetAtt MyLogGroup.Arn + Format: $context.extendedRequestId $context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.responseLength $context.requestId + MyLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Join + - '-' + - - !Ref RestApi + - access-logs + +Outputs: + RestApiId: + Value: !GetAtt RestApi.RestApiId diff --git a/tests/aws/templates/aws_novalue.yml b/tests/aws/templates/aws_novalue.yml new file mode 100644 index 0000000000000..42d99e4f9ae2a --- /dev/null +++ b/tests/aws/templates/aws_novalue.yml @@ -0,0 +1,22 @@ +Parameters: + SetBucketName: + Type: String + FallbackBucketName: + Type: String +Conditions: + ShouldSetBucketName: + Fn::Equals: + - "yes" + - !Ref SetBucketName +Resources: + MyBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: + Fn::If: + - ShouldSetBucketName + - !Ref FallbackBucketName + - !Ref AWS::NoValue +Outputs: + BucketName: + Value: !Ref MyBucket diff --git a/tests/aws/templates/cdk-lambda-redeploy.json b/tests/aws/templates/cdk-lambda-redeploy.json new file mode 100644 index 0000000000000..dc013eedf6d26 --- /dev/null +++ b/tests/aws/templates/cdk-lambda-redeploy.json @@ -0,0 +1,124 @@ +{ + "Resources": { + "ReproPaymentsHandlerServiceRole7453EE1B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + }, + "Metadata": { + "aws:cdk:path": "CdkStack/Repro/PaymentsHandler/ServiceRole/Resource" + } + }, + "ReproPaymentsHandlerFA388BE4": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "DeployBucket" + }, + "S3Key": { + "Ref": "DeployKey" + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "ReproPaymentsHandlerServiceRole7453EE1B", + "Arn" + ] + }, + "Runtime": "nodejs22.x" + }, + "DependsOn": [ + "ReproPaymentsHandlerServiceRole7453EE1B" + ], + "Metadata": { + "aws:cdk:path": "CdkStack/Repro/PaymentsHandler/Resource", + "aws:asset:path": "asset.83bd6ee15f56848d793729be08e79f53eed8a43e4d0857f3ad9a83280cb4784b", + "aws:asset:is-bundled": true, + "aws:asset:property": "Code" + } + }, + "CDKMetadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Analytics": "v2:deflate64:H4sIAAAAAAAA/zXNywqDMBCF4WdxP05thEKXVeiyBX0AGeMo8RKLk9SF+O4lSlff6vxHoUrueI1olVg3QzyaGrfSkR6AVqm2kaa6ocrODfeCr4Ont9qZ2YKhCbdiHhny1gZ3kLQiEXaCjwBIipnXA7uMhOGsYd7af2OHgmX2i2Y4BqWjztguBN/efbzbIXxjL5evUni9YRL1Yky8eOvMxFic/gBWkHMZyAAAAA==" + }, + "Metadata": { + "aws:cdk:path": "CdkStack/CDKMetadata/Default" + } + } + }, + "Outputs": { + "ReproLambdaName070199FC": { + "Value": { + "Ref": "ReproPaymentsHandlerFA388BE4" + } + } + }, + "Parameters": { + "DeployBucket": { + "Type": "String" + }, + "DeployKey": { + "Type": "String" + }, + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} diff --git a/tests/aws/templates/cdk_bootstrap_v28.yaml b/tests/aws/templates/cdk_bootstrap_v28.yaml new file mode 100644 index 0000000000000..7083afbc023f5 --- /dev/null +++ b/tests/aws/templates/cdk_bootstrap_v28.yaml @@ -0,0 +1,671 @@ +Description: This stack includes resources needed to deploy AWS CDK apps into this environment +Parameters: + TrustedAccounts: + Description: List of AWS accounts that are trusted to publish assets and deploy stacks to this environment + Default: "" + Type: CommaDelimitedList + TrustedAccountsForLookup: + Description: List of AWS accounts that are trusted to look up values in this environment + Default: "" + Type: CommaDelimitedList + CloudFormationExecutionPolicies: + Description: List of the ManagedPolicy ARN(s) to attach to the CloudFormation deployment role + Default: "" + Type: CommaDelimitedList + FileAssetsBucketName: + Description: The name of the S3 bucket used for file assets + Default: "" + Type: String + FileAssetsBucketKmsKeyId: + Description: Empty to create a new key (default), 'AWS_MANAGED_KEY' to use a managed S3 key, or the ID/ARN of an existing key. + Default: "" + Type: String + ContainerAssetsRepositoryName: + Description: A user-provided custom name to use for the container assets ECR repository + Default: "" + Type: String + Qualifier: + Description: An identifier to distinguish multiple bootstrap stacks in the same environment + Default: hnb659fds + Type: String + AllowedPattern: "[A-Za-z0-9_-]{1,10}" + ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters + PublicAccessBlockConfiguration: + Description: Whether or not to enable S3 Staging Bucket Public Access Block Configuration + Default: "true" + Type: String + AllowedValues: + - "true" + - "false" + InputPermissionsBoundary: + Description: Whether or not to use either the CDK supplied or custom permissions boundary + Default: "" + Type: String + UseExamplePermissionsBoundary: + Default: "false" + AllowedValues: + - "true" + - "false" + Type: String + BootstrapVariant: + Type: String + Default: "AWS CDK: Default Resources" + Description: Describe the provenance of the resources in this bootstrap stack. Change this when you customize the template. To prevent accidents, the CDK CLI will not overwrite bootstrap stacks with a different variant. +Conditions: + HasTrustedAccounts: + Fn::Not: + - Fn::Equals: + - "" + - Fn::Join: + - "" + - Ref: TrustedAccounts + HasTrustedAccountsForLookup: + Fn::Not: + - Fn::Equals: + - "" + - Fn::Join: + - "" + - Ref: TrustedAccountsForLookup + HasCloudFormationExecutionPolicies: + Fn::Not: + - Fn::Equals: + - "" + - Fn::Join: + - "" + - Ref: CloudFormationExecutionPolicies + HasCustomFileAssetsBucketName: + Fn::Not: + - Fn::Equals: + - "" + - Ref: FileAssetsBucketName + CreateNewKey: + Fn::Equals: + - "" + - Ref: FileAssetsBucketKmsKeyId + UseAwsManagedKey: + Fn::Equals: + - AWS_MANAGED_KEY + - Ref: FileAssetsBucketKmsKeyId + ShouldCreatePermissionsBoundary: + Fn::Equals: + - "true" + - Ref: UseExamplePermissionsBoundary + PermissionsBoundarySet: + Fn::Not: + - Fn::Equals: + - "" + - Ref: InputPermissionsBoundary + HasCustomContainerAssetsRepositoryName: + Fn::Not: + - Fn::Equals: + - "" + - Ref: ContainerAssetsRepositoryName + UsePublicAccessBlockConfiguration: + Fn::Equals: + - "true" + - Ref: PublicAccessBlockConfiguration +Resources: + FileAssetsBucketEncryptionKey: + Type: AWS::KMS::Key + Properties: + KeyPolicy: + Statement: + - Action: + - kms:Create* + - kms:Describe* + - kms:Enable* + - kms:List* + - kms:Put* + - kms:Update* + - kms:Revoke* + - kms:Disable* + - kms:Get* + - kms:Delete* + - kms:ScheduleKeyDeletion + - kms:CancelKeyDeletion + - kms:GenerateDataKey + - kms:TagResource + - kms:UntagResource + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + Resource: "*" + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + AWS: "*" + Resource: "*" + Condition: + StringEquals: + kms:CallerAccount: + Ref: AWS::AccountId + kms:ViaService: + - Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + AWS: + Fn::Sub: ${FilePublishingRole.Arn} + Resource: "*" + Condition: CreateNewKey + UpdateReplacePolicy: Delete + DeletionPolicy: Delete + FileAssetsBucketEncryptionKeyAlias: + Condition: CreateNewKey + Type: AWS::KMS::Alias + Properties: + AliasName: + Fn::Sub: alias/cdk-${Qualifier}-assets-key + TargetKeyId: + Ref: FileAssetsBucketEncryptionKey + StagingBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: + Fn::If: + - HasCustomFileAssetsBucketName + - Fn::Sub: ${FileAssetsBucketName} + - Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region} + AccessControl: Private + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: aws:kms + KMSMasterKeyID: + Fn::If: + - CreateNewKey + - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} + - Fn::If: + - UseAwsManagedKey + - Ref: AWS::NoValue + - Fn::Sub: ${FileAssetsBucketKmsKeyId} + PublicAccessBlockConfiguration: + Fn::If: + - UsePublicAccessBlockConfiguration + - BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + - Ref: AWS::NoValue + VersioningConfiguration: + Status: Enabled + LifecycleConfiguration: + Rules: + - Id: CleanupOldVersions + Status: Enabled + NoncurrentVersionExpiration: + NoncurrentDays: 30 + - Id: AbortIncompleteMultipartUploads + Status: Enabled + AbortIncompleteMultipartUpload: + DaysAfterInitiation: 1 + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + StagingBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Ref: StagingBucket + PolicyDocument: + Id: AccessControl + Version: "2012-10-17" + Statement: + - Sid: AllowSSLRequestsOnly + Action: s3:* + Effect: Deny + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + Condition: + Bool: + aws:SecureTransport: "false" + Principal: "*" + ContainerAssetsRepository: + Type: AWS::ECR::Repository + Properties: + ImageTagMutability: IMMUTABLE + LifecyclePolicy: + LifecyclePolicyText: | + { + "rules": [ + { + "rulePriority": 1, + "description": "Untagged images should not exist, but expire any older than one year", + "selection": { + "tagStatus": "untagged", + "countType": "sinceImagePushed", + "countUnit": "days", + "countNumber": 365 + }, + "action": { "type": "expire" } + } + ] + } + RepositoryName: + Fn::If: + - HasCustomContainerAssetsRepositoryName + - Fn::Sub: ${ContainerAssetsRepositoryName} + - Fn::Sub: cdk-${Qualifier}-container-assets-${AWS::AccountId}-${AWS::Region} + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + - Sid: LambdaECRImageRetrievalPolicy + Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Condition: + StringLike: + aws:sourceArn: + Fn::Sub: arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:* + - Sid: EmrServerlessImageRetrievalPolicy + Effect: Allow + Principal: + Service: emr-serverless.amazonaws.com + Action: + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + - ecr:DescribeImages + Condition: + StringLike: + aws:sourceArn: + Fn::Sub: arn:${AWS::Partition}:emr-serverless:${AWS::Region}:${AWS::AccountId}:/applications/* + FilePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: file-publishing + ImagePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: image-publishing + LookupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccountsForLookup + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccountsForLookup + - Ref: AWS::NoValue + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region} + ManagedPolicyArns: + - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess + Policies: + - PolicyDocument: + Statement: + - Sid: DontReadSecrets + Effect: Deny + Action: + - kms:Decrypt + Resource: "*" + Version: "2012-10-17" + PolicyName: LookupRolePolicy + Tags: + - Key: aws-cdk:bootstrap-role + Value: lookup + FilePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - s3:GetObject* + - s3:GetBucket* + - s3:GetEncryptionConfiguration + - s3:List* + - s3:DeleteObject* + - s3:PutObject* + - s3:Abort* + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + Condition: + StringEquals: + aws:ResourceAccount: + - Fn::Sub: ${AWS::AccountId} + Effect: Allow + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Resource: + Fn::If: + - CreateNewKey + - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} + - Fn::Sub: arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId} + Version: "2012-10-17" + Roles: + - Ref: FilePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + ImagePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - ecr:PutImage + - ecr:InitiateLayerUpload + - ecr:UploadLayerPart + - ecr:CompleteLayerUpload + - ecr:BatchCheckLayerAvailability + - ecr:DescribeRepositories + - ecr:DescribeImages + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Resource: + Fn::Sub: ${ContainerAssetsRepository.Arn} + Effect: Allow + - Action: + - ecr:GetAuthorizationToken + Resource: "*" + Effect: Allow + Version: "2012-10-17" + Roles: + - Ref: ImagePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + DeploymentActionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + Policies: + - PolicyDocument: + Statement: + - Sid: CloudFormationPermissions + Effect: Allow + Action: + - cloudformation:CreateChangeSet + - cloudformation:DeleteChangeSet + - cloudformation:DescribeChangeSet + - cloudformation:DescribeStacks + - cloudformation:ExecuteChangeSet + - cloudformation:CreateStack + - cloudformation:UpdateStack + - cloudformation:RollbackStack + - cloudformation:ContinueUpdateRollback + Resource: "*" + - Sid: PipelineCrossAccountArtifactsBucket + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + - s3:Abort* + - s3:DeleteObject* + - s3:PutObject* + Resource: "*" + Condition: + StringNotEquals: + s3:ResourceAccount: + Ref: AWS::AccountId + - Sid: PipelineCrossAccountArtifactsKey + Effect: Allow + Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Resource: "*" + Condition: + StringEquals: + kms:ViaService: + Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: iam:PassRole + Resource: + Fn::Sub: ${CloudFormationExecutionRole.Arn} + Effect: Allow + - Sid: CliPermissions + Action: + - cloudformation:DescribeStackEvents + - cloudformation:GetTemplate + - cloudformation:DeleteStack + - cloudformation:UpdateTerminationProtection + - sts:GetCallerIdentity + - cloudformation:GetTemplateSummary + Resource: "*" + Effect: Allow + - Sid: CliStagingBucket + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + - Sid: ReadVersion + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + Resource: + - Fn::Sub: arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion} + - Sid: Refactor + Effect: Allow + Action: + - cloudformation:CreateStackRefactor + - cloudformation:DescribeStackRefactor + - cloudformation:ExecuteStackRefactor + - cloudformation:ListStackRefactorActions + - cloudformation:ListStackRefactors + - cloudformation:ListStacks + Resource: "*" + Version: "2012-10-17" + PolicyName: default + RoleName: + Fn::Sub: cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: deploy + CloudFormationExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: cloudformation.amazonaws.com + Version: "2012-10-17" + ManagedPolicyArns: + Fn::If: + - HasCloudFormationExecutionPolicies + - Ref: CloudFormationExecutionPolicies + - Fn::If: + - HasTrustedAccounts + - Ref: AWS::NoValue + - - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess + RoleName: + Fn::Sub: cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region} + PermissionsBoundary: + Fn::If: + - PermissionsBoundarySet + - Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary} + - Ref: AWS::NoValue + CdkBoostrapPermissionsBoundaryPolicy: + Condition: ShouldCreatePermissionsBoundary + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Statement: + - Sid: ExplicitAllowAll + Action: + - "*" + Effect: Allow + Resource: "*" + - Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied + Action: + - iam:CreateUser + - iam:CreateRole + - iam:PutRolePermissionsBoundary + - iam:PutUserPermissionsBoundary + Condition: + StringNotEquals: + iam:PermissionsBoundary: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Effect: Deny + Resource: "*" + - Sid: DenyPermBoundaryIAMPolicyAlteration + Action: + - iam:CreatePolicyVersion + - iam:DeletePolicy + - iam:DeletePolicyVersion + - iam:SetDefaultPolicyVersion + Effect: Deny + Resource: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + - Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole + Action: + - iam:DeleteUserPermissionsBoundary + - iam:DeleteRolePermissionsBoundary + Effect: Deny + Resource: "*" + Version: "2012-10-17" + Description: Bootstrap Permission Boundary + ManagedPolicyName: + Fn::Sub: cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Path: / + CdkBootstrapVersion: + Type: AWS::SSM::Parameter + Properties: + Type: String + Name: + Fn::Sub: /cdk-bootstrap/${Qualifier}/version + Value: "28" +Outputs: + BucketName: + Description: The name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: ${StagingBucket} + BucketDomainName: + Description: The domain name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: ${StagingBucket.RegionalDomainName} + FileAssetKeyArn: + Description: The ARN of the KMS key used to encrypt the asset bucket (deprecated) + Value: + Fn::If: + - CreateNewKey + - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} + - Fn::Sub: ${FileAssetsBucketKmsKeyId} + Export: + Name: + Fn::Sub: CdkBootstrap-${Qualifier}-FileAssetKeyArn + ImageRepositoryName: + Description: The name of the ECR repository which hosts docker image assets + Value: + Fn::Sub: ${ContainerAssetsRepository} + BootstrapVersion: + Description: The version of the bootstrap resources that are currently mastered in this stack + Value: + Fn::GetAtt: + - CdkBootstrapVersion + - Value diff --git a/tests/aws/templates/cfn_apigw_with_include_fn.yml b/tests/aws/templates/cfn_apigw_with_include_fn.yml new file mode 100644 index 0000000000000..eccd1ec40219f --- /dev/null +++ b/tests/aws/templates/cfn_apigw_with_include_fn.yml @@ -0,0 +1,18 @@ + Parameters: + ApiName: + Type: String + BucketName: + Type: String + Resources: + RestApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: !Ref ApiName + Body: + 'Fn::Transform': + Name: 'AWS::Include' + Parameters: + Location: !Sub "s3://${BucketName}/api.yaml" + Outputs: + RestApiId: + Value: !Ref RestApi diff --git a/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml b/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml index 21161124d0ed0..c49fa319b5557 100644 --- a/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml +++ b/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml @@ -1,3 +1,6 @@ +Parameters: + CustomURL: + Type: String Resources: SimpleFnServiceRole6574647D: Type: AWS::IAM::Role @@ -35,6 +38,7 @@ Resources: API_URL_2: https://storage.execute-api.us-east-2.amazonaws.com/test-resource API_URL_3: https://reporting.execute-api.us-east-1.amazonaws.com/test-resource API_URL_4: https://blockchain.execute-api.us-west-1.amazonaws.com/test-resource + API_URL_CUSTOM: !Ref CustomURL DependsOn: - SimpleFnServiceRole6574647D Outputs: diff --git a/tests/aws/templates/cfn_parameter_list_subnet_id_type.yaml b/tests/aws/templates/cfn_parameter_list_subnet_id_type.yaml new file mode 100644 index 0000000000000..eebeb6b947c96 --- /dev/null +++ b/tests/aws/templates/cfn_parameter_list_subnet_id_type.yaml @@ -0,0 +1,12 @@ +Parameters: + ParamsList: + Type: List +Resources: + MyParam: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: !Join ["|", !Ref ParamsList] +Outputs: + ParamValue: + Value: !GetAtt MyParam.Value diff --git a/tests/aws/templates/conditions/conditional-resource-getatt-delete.yaml b/tests/aws/templates/conditions/conditional-resource-getatt-delete.yaml new file mode 100644 index 0000000000000..7d8081ba6fef8 --- /dev/null +++ b/tests/aws/templates/conditions/conditional-resource-getatt-delete.yaml @@ -0,0 +1,43 @@ +AWSTemplateFormatVersion: '2010-09-09' + +Parameters: + EnableQueue: + Type: String + Default: 'false' + AllowedValues: ['true', 'false'] + +Conditions: + CreateQueue: !Equals [!Ref EnableQueue, 'true'] + +Resources: + MyTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: !Sub '${AWS::StackName}-table' + BillingMode: PAY_PER_REQUEST + AttributeDefinitions: + - AttributeName: PK + AttributeType: S + KeySchema: + - AttributeName: PK + KeyType: HASH + + MyQueue: + Type: AWS::SQS::Queue + Condition: CreateQueue + + MyAlarm: + Type: AWS::CloudWatch::Alarm + Condition: CreateQueue + Properties: + AlarmName: !Sub '${AWS::StackName}-alarm' + MetricName: ApproximateNumberOfMessagesVisible + Namespace: AWS/SQS + Statistic: Sum + Period: 300 + EvaluationPeriods: 1 + Threshold: 100 + ComparisonOperator: GreaterThanThreshold + Dimensions: + - Name: QueueName + Value: !GetAtt MyQueue.QueueName diff --git a/tests/aws/templates/conditions/get-att-condition.yml b/tests/aws/templates/conditions/get-att-condition.yml new file mode 100644 index 0000000000000..cd17bb4fb6bef --- /dev/null +++ b/tests/aws/templates/conditions/get-att-condition.yml @@ -0,0 +1,21 @@ +Parameters: + OptionParameter: + Type: String + AllowedValues: + - option-a + - option-b +Resources: + MyTopic: + Type: AWS::SNS::Topic + Condition: ShouldCreateTopic + + MySsmParam: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: !GetAtt MyTopic.TopicName + +Conditions: + ShouldCreateTopic: !Equals + - !Ref OptionParameter + - option-a \ No newline at end of file diff --git a/tests/aws/templates/deletion_policy.yaml b/tests/aws/templates/deletion_policy.yaml new file mode 100644 index 0000000000000..6de133bcd5fdc --- /dev/null +++ b/tests/aws/templates/deletion_policy.yaml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Minimal DynamoDB Conditional DeletionPolicy Example + +Parameters: + ParameterValue: + Type: String + EnvType: + Type: String + Default: dev + AllowedValues: [dev, prod] + Description: Environment name. Use 'prod' to trigger Retain policy. + +Conditions: + IsPermanentEnvironment: !Equals [!Ref EnvType, prod] + +Resources: + MyParameter: + Type: AWS::SSM::Parameter + DeletionPolicy: !If [IsPermanentEnvironment, Retain, Delete] + Properties: + Type: String + Value: !Ref ParameterValue + +Outputs: + ParameterName: + Value: !Ref MyParameter \ No newline at end of file diff --git a/tests/aws/templates/engine/fn_select_fn_mapp.yml b/tests/aws/templates/engine/fn_select_fn_mapp.yml new file mode 100644 index 0000000000000..d7bdb845f5c14 --- /dev/null +++ b/tests/aws/templates/engine/fn_select_fn_mapp.yml @@ -0,0 +1,30 @@ +Mappings: + RegionMap: + us-east-1: + AZs: + - "empty" + +Resources: + Parameter: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: + Fn::If: [UseFnGetAZs, "true", "false"] + Name: commands + + +Conditions: + UseFnGetAZs: + Fn::Equals: + - "empty" + - Fn::Select: + - 0 + - Fn::FindInMap: + - "RegionMap" + - "us-east-1" + - "AZs" + +Outputs: + ParameterValue: + Value: !GetAtt Parameter.Value \ No newline at end of file diff --git a/tests/aws/templates/getatt_validation.yml b/tests/aws/templates/getatt_validation.yml new file mode 100644 index 0000000000000..fb82b2ad47b7e --- /dev/null +++ b/tests/aws/templates/getatt_validation.yml @@ -0,0 +1,10 @@ +Parameters: + MyParameterValue: + Type: String + +Resources: + MyParameter: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: !Select [ 0, !Split [ "-", !GetAtt [ !Ref MyParameterValue ] ] ] diff --git a/tests/aws/templates/getatt_validation2.yml b/tests/aws/templates/getatt_validation2.yml new file mode 100644 index 0000000000000..e1a8b61a22d07 --- /dev/null +++ b/tests/aws/templates/getatt_validation2.yml @@ -0,0 +1,18 @@ +Parameters: + MyParameterValue: + Type: String + +Resources: + MyParameter1: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: foo + + MyParameter2: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: + Fn::GetAtt: + - MyParameter1 diff --git a/tests/aws/templates/logs_group.yml b/tests/aws/templates/logs_group.yml index b0aaa510dcfae..0a0f5c1f5ac07 100644 --- a/tests/aws/templates/logs_group.yml +++ b/tests/aws/templates/logs_group.yml @@ -1,11 +1,16 @@ +Parameters: + LogGroupName: + Type: String + Default: AWS::NoValue + Resources: logGroup68A52FBE: Type: AWS::Logs::LogGroup Properties: + LogGroupName: !Ref LogGroupName RetentionInDays: 731 Outputs: LogGroupNameOutput: Value: Ref: logGroup68A52FBE - diff --git a/tests/aws/templates/macros/add_standard_tags.py b/tests/aws/templates/macros/add_standard_tags.py new file mode 100644 index 0000000000000..dffe4ffb631e7 --- /dev/null +++ b/tests/aws/templates/macros/add_standard_tags.py @@ -0,0 +1,10 @@ +def handler(event, context): + fragment = add_standard_attributes(event["fragment"]) + + return {"requestId": event["requestId"], "status": "success", "fragment": fragment} + + +def add_standard_attributes(fragment): + fragment["Tags"] = {"MacroAdded": "True"} + + return fragment diff --git a/tests/aws/templates/macros/format_template.py b/tests/aws/templates/macros/format_template.py index 8809fa228f456..2315928921984 100644 --- a/tests/aws/templates/macros/format_template.py +++ b/tests/aws/templates/macros/format_template.py @@ -1,3 +1,34 @@ +""" +This macro takes the incoming fragment (i.e. the sibling nodes of the `Fn::Transform` call, +and for every string it calls .format on the string with the arguments taken from the parameters. + +For example: + +Transform: ThisMacroName +Properties: + Value: + Type: String +Resources: + MyResource: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: "my value is {Value}" + +deployed with ParameterKey=Value,ParameterValue=test will result in the final template: + +Properties: + Value: + Type: String +Resources: + MyResource: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: "my value is test" +""" + + def handler(event, context): parameters = event["templateParameterValues"] fragment = walk(event["fragment"], parameters) diff --git a/tests/aws/templates/references_to_conditions.yml b/tests/aws/templates/references_to_conditions.yml new file mode 100644 index 0000000000000..01bed33232515 --- /dev/null +++ b/tests/aws/templates/references_to_conditions.yml @@ -0,0 +1,39 @@ +Parameters: + Toggle: + Type: String + + ParameterValue: + Type: String + +Conditions: + ShouldDeploy: + Fn::Equals: + - !Ref Toggle + - "yes" + +Resources: + BaseParameter: + # We need a single resource to deploy + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: !Ref ParameterValue + + ConditionalParameter: + Type: AWS::SSM::Parameter + Condition: ShouldDeploy + Properties: + Type: String + Value: !Ref ParameterValue + + AnotherResource: + Type: AWS::SSM::Parameter + Condition: ShouldDeploy + Properties: + Type: String + Value: !GetAtt ConditionalParameter.Value + +Outputs: + ConditionalParameterValue: + Condition: ShouldDeploy + Value: !GetAtt ConditionalParameter.Value diff --git a/tests/aws/templates/resolve_secretsmanager_with_backslashes.yaml b/tests/aws/templates/resolve_secretsmanager_with_backslashes.yaml new file mode 100644 index 0000000000000..78ceb01dcc29e --- /dev/null +++ b/tests/aws/templates/resolve_secretsmanager_with_backslashes.yaml @@ -0,0 +1,24 @@ +Parameters: + DynamicParameter: + Type: String + +Resources: + MyParameter: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: + Fn::Join: + - "" + - - "{{resolve:secretsmanager:arn:" + - Ref: AWS::Partition + - ":secretsmanager:" + - Ref: AWS::Region + - ":" + - Ref: AWS::AccountId + - ":secret:" + - Ref: DynamicParameter + - ":SecretString:::}}" +Outputs: + ParameterValue: + Value: !GetAtt MyParameter.Value diff --git a/tests/aws/templates/resolve_ssm.yaml b/tests/aws/templates/resolve_ssm.yaml index d67220bb00a3d..692d2c881688e 100644 --- a/tests/aws/templates/resolve_ssm.yaml +++ b/tests/aws/templates/resolve_ssm.yaml @@ -2,14 +2,25 @@ Parameters: DynamicParameter: Type: String + ParameterName: + Type: String + Resources: topic69831491: Type: AWS::SNS::Topic Properties: - TopicName: !Join [ "", [ "{{resolve:ssm:", !Ref DynamicParameter, "}}" ] ] + TopicName: !Join [ "", [ "{{resolve:ssm:", !Ref DynamicParameter, "}}" ] ] + MyParameter: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: !Sub "abc:{{resolve:ssm:${ParameterName}}}" Outputs: TopicName: Value: Fn::GetAtt: - topic69831491 - TopicName + + ParameterValue: + Value: !GetAtt MyParameter.Value diff --git a/tests/aws/templates/route53_hostedzoneid_weighted_template.yaml b/tests/aws/templates/route53_hostedzoneid_weighted_template.yaml new file mode 100644 index 0000000000000..03f6e1a7c5b3e --- /dev/null +++ b/tests/aws/templates/route53_hostedzoneid_weighted_template.yaml @@ -0,0 +1,60 @@ +Parameters: + Name: + Type: String + HostedZoneId: + Type: String + BucketRegionOneHost: + Type: String + BucketRegionOneHostedZoneId: + Type: String + BucketRegionTwoHost: + Type: String + BucketRegionTwoHostedZoneId: + Type: String + WeightBucketOne: + Type: Number + Default: 255 + WeightBucketTwo: + Type: Number + Default: 0 + BucketOneSetIdentifier: + Type: String + Default: region-1 + BucketTwoSetIdentifier: + Type: String + Default: region-2 + +Resources: + ApiWeightedRecordSetOne: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: !Ref HostedZoneId + Name: !Ref Name + Type: A + SetIdentifier: !Ref BucketOneSetIdentifier + Weight: !Ref WeightBucketOne + AliasTarget: + DNSName: !Ref BucketRegionOneHost + HostedZoneId: !Ref BucketRegionOneHostedZoneId + EvaluateTargetHealth: false + + ApiWeightedRecordSetTwo: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: !Ref HostedZoneId + Name: !Ref Name + Type: A + SetIdentifier: !Ref BucketTwoSetIdentifier + Weight: !Ref WeightBucketTwo + AliasTarget: + DNSName: !Ref BucketRegionTwoHost + HostedZoneId: !Ref BucketRegionTwoHostedZoneId + EvaluateTargetHealth: false + +Outputs: + RecordSetOneId: + Value: + Ref: ApiWeightedRecordSetOne + RecordSetTwoId: + Value: + Ref: ApiWeightedRecordSetTwo diff --git a/tests/aws/templates/sqs_fifo_autogenerate_name.yaml b/tests/aws/templates/sqs_fifo_autogenerate_name.yaml deleted file mode 100644 index 62e85cf2aa551..0000000000000 --- a/tests/aws/templates/sqs_fifo_autogenerate_name.yaml +++ /dev/null @@ -1,22 +0,0 @@ -Parameters: - IsFifo: - Type: String - -Conditions: - IsFifo: !Equals [ !Ref IsFifo, "true"] - -Resources: - FooQueueA2A23E59: - Type: AWS::SQS::Queue - Properties: - ContentBasedDeduplication: !If [ IsFifo, "true", !Ref AWS::NoValue ] - FifoQueue: !If [ IsFifo, "true", !Ref AWS::NoValue ] - VisibilityTimeout: 300 - UpdateReplacePolicy: Delete - DeletionPolicy: Delete -Outputs: - FooQueueName: - Value: - Fn::GetAtt: - - FooQueueA2A23E59 - - QueueName diff --git a/tests/aws/templates/sqs_fifo_queue.yml b/tests/aws/templates/sqs_fifo_queue.yml deleted file mode 100644 index 6d1077a421c4c..0000000000000 --- a/tests/aws/templates/sqs_fifo_queue.yml +++ /dev/null @@ -1,18 +0,0 @@ -AWSTemplateFormatVersion: 2010-09-09 -Parameters: - QueueName: - Type: String - Default: "test-queue" - Description: "Name of the SQS queue" -Resources: - FifoQueue: - Type: 'AWS::SQS::Queue' - Properties: - QueueName: !Sub "${QueueName}.fifo" - ContentBasedDeduplication: "false" - FifoQueue: "true" - -Outputs: - QueueURL: - Value: !GetAtt FifoQueue.QueueName - Description: "URL of the SQS queue" diff --git a/tests/aws/templates/sqs_queue_update_1.yaml b/tests/aws/templates/sqs_queue_update_1.yaml new file mode 100644 index 0000000000000..63c8f35db036a --- /dev/null +++ b/tests/aws/templates/sqs_queue_update_1.yaml @@ -0,0 +1,11 @@ +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn \ No newline at end of file diff --git a/tests/aws/templates/sqs_queue_update_2.yaml b/tests/aws/templates/sqs_queue_update_2.yaml new file mode 100644 index 0000000000000..be3c3b69929f4 --- /dev/null +++ b/tests/aws/templates/sqs_queue_update_2.yaml @@ -0,0 +1,16 @@ +Resources: + StandardQueue: + Type: 'AWS::SQS::Queue' + Properties: + VisibilityTimeout: 30 + MaximumMessageSize: 4321 + MessageRetentionPeriod: 17539 + ReceiveMessageWaitTimeSeconds: 17 + +Outputs: + QueueUrl: + Description: 'URL of the SQS Standard Queue' + Value: !Ref StandardQueue + QueueArn: + Description: 'ARN of the SQS Standard Queue' + Value: !GetAtt StandardQueue.Arn \ No newline at end of file diff --git a/tests/aws/templates/sqs_queue_update_no_change.yml b/tests/aws/templates/sqs_queue_update_no_change.yml deleted file mode 100644 index dad49efefd6fb..0000000000000 --- a/tests/aws/templates/sqs_queue_update_no_change.yml +++ /dev/null @@ -1,29 +0,0 @@ -Parameters: - AddBucket: - Type: String - AllowedValues: - - "true" - - "false" - - BucketName: - Type: String - -Conditions: - ShouldDeployBucket: !Equals ["true", !Ref AddBucket] - -Resources: - Queue: - Type: AWS::SQS::Queue - - Bucket: - Type: AWS::S3::Bucket - Condition: ShouldDeployBucket - Properties: - BucketName: !Ref BucketName - -Outputs: - QueueUrl: - Value: !Ref Queue - - QueueArn: - Value: !GetAtt Queue.Arn diff --git a/tests/aws/templates/sqs_with_queuepolicy_updated.yaml b/tests/aws/templates/sqs_with_queuepolicy_updated.yaml deleted file mode 100644 index 17818bddc3fd2..0000000000000 --- a/tests/aws/templates/sqs_with_queuepolicy_updated.yaml +++ /dev/null @@ -1,25 +0,0 @@ -Resources: - Queue4A7E3555: - Type: AWS::SQS::Queue - QueuePolicy25439813: - Type: AWS::SQS::QueuePolicy - Properties: - PolicyDocument: - Statement: - - Action: - - sqs:SendMessage - - sqs:GetQueueAttributes - - sqs:GetQueueUrl - Effect: Deny - Principal: "*" - Resource: - Fn::GetAtt: - - Queue4A7E3555 - - Arn - Version: "2012-10-17" - Queues: - - Ref: Queue4A7E3555 -Outputs: - QueueUrlOutput: - Value: - Ref: Queue4A7E3555 diff --git a/tests/aws/templates/stack-id-validation.yaml b/tests/aws/templates/stack-id-validation.yaml new file mode 100644 index 0000000000000..b504527d00be5 --- /dev/null +++ b/tests/aws/templates/stack-id-validation.yaml @@ -0,0 +1,22 @@ +Resources: + MyParameter: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: !Join + - "-" + - - !Ref AWS::StackName + - "s3logs" + - Fn::Select: + - 4 + - Fn::Split: + - '-' + - Fn::Select: + - 2 + - Fn::Split: + - / + - Ref: AWS::StackId + +Outputs: + ParameterValue: + Value: !GetAtt MyParameter.Value diff --git a/tests/aws/templates/update_retain_policy.yaml b/tests/aws/templates/update_retain_policy.yaml new file mode 100644 index 0000000000000..ffd7d60bbf040 --- /dev/null +++ b/tests/aws/templates/update_retain_policy.yaml @@ -0,0 +1,18 @@ +Parameters: + ParameterValue: + Type: String + + ParameterName: + Type: String + + PolicyType: + Type: String + +Resources: + MyParameter: + Type: AWS::SSM::Parameter + Properties: + Name: !Ref ParameterName + Type: String + Value: !Ref ParameterValue + UpdateReplacePolicy: !Ref PolicyType \ No newline at end of file diff --git a/tests/aws/templates/vpc_gateway_attachment.yml b/tests/aws/templates/vpc_gateway_attachment.yml new file mode 100644 index 0000000000000..ecd5dfadf5752 --- /dev/null +++ b/tests/aws/templates/vpc_gateway_attachment.yml @@ -0,0 +1,34 @@ +Resources: + Vpc: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.0.0.0/16 + + InternetGateway: + Type: AWS::EC2::InternetGateway + +# TODO: not supported by LocalStack yet +# VpnGateway: +# Type: AWS::EC2::VPNGateway +# Properties: +# Type: ipsec.1 + + GatewayAttachment1: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref Vpc + InternetGatewayId: !Ref InternetGateway + +# GatewayAttachment2: +# Type: AWS::EC2::VPCGatewayAttachment +# Properties: +# VpcId: !Ref Vpc +# VpnGatewayId: !Ref VpnGateway + +Outputs: + VpcId: + Value: !Ref Vpc + GatewayAttachment1Ref: + Value: !Ref GatewayAttachment1 +# GatewayAttachment2Ref: +# Value: !Ref GatewayAttachment2 diff --git a/tests/aws/test_error_injection.py b/tests/aws/test_error_injection.py index c01fde6f76187..9edd72cd1449f 100644 --- a/tests/aws/test_error_injection.py +++ b/tests/aws/test_error_injection.py @@ -11,6 +11,7 @@ class TestErrorInjection: + @markers.requires_in_process @markers.aws.only_localstack def test_kinesis_error_injection( self, monkeypatch, wait_for_stream_ready, aws_client, aws_client_factory @@ -34,6 +35,7 @@ def test_kinesis_error_injection( finally: aws_client.kinesis.delete_stream(StreamName=stream_name) + @markers.requires_in_process @markers.aws.only_localstack def test_dynamodb_error_injection(self, monkeypatch, aws_client, dynamodb_create_table): table_name = dynamodb_create_table()["TableDescription"]["TableName"] @@ -51,6 +53,7 @@ def test_dynamodb_error_injection(self, monkeypatch, aws_client, dynamodb_create ) exc.match("ProvisionedThroughputExceededException") + @markers.requires_in_process @markers.aws.only_localstack def test_dynamodb_read_error_injection(self, monkeypatch, aws_client, dynamodb_create_table): table_name = dynamodb_create_table()["TableDescription"]["TableName"] @@ -68,6 +71,7 @@ def test_dynamodb_read_error_injection(self, monkeypatch, aws_client, dynamodb_c ) exc.match("ProvisionedThroughputExceededException") + @markers.requires_in_process @markers.aws.only_localstack def test_dynamodb_write_error_injection(self, monkeypatch, aws_client, dynamodb_create_table): table_name = dynamodb_create_table()["TableDescription"]["TableName"] diff --git a/tests/aws/test_integration.py b/tests/aws/test_integration.py index f81a8383c4f13..171183df4d9fa 100644 --- a/tests/aws/test_integration.py +++ b/tests/aws/test_integration.py @@ -86,7 +86,7 @@ def test_firehose_s3( bucket_name = s3_create_bucket() s3_prefix = "/testdata" - test_data = '{"test": "firehose_data_%s"}' % short_uid() + test_data = f'{{"test": "firehose_data_{short_uid()}"}}' # create Firehose stream stream = firehose_create_delivery_stream( DeliveryStreamName=stream_name, @@ -122,7 +122,7 @@ def test_firehose_extended_s3( bucket_name = s3_create_bucket() s3_prefix = "/testdata2" - test_data = '{"test": "firehose_data_%s"}' % short_uid() + test_data = f'{{"test": "firehose_data_{short_uid()}"}}' # create Firehose stream stream = firehose_create_delivery_stream( DeliveryStreamName=stream_name, @@ -160,7 +160,7 @@ def test_firehose_kinesis_to_s3( kinesis_stream_name = kinesis_create_stream() s3_prefix = "/testdata" - test_data = '{"test": "firehose_data_%s"}' % short_uid() + test_data = f'{{"test": "firehose_data_{short_uid()}"}}' # create Firehose stream stream = aws_client.firehose.create_delivery_stream( @@ -208,6 +208,7 @@ def _assert_objects_created(): # clean up aws_client.firehose.delete_delivery_stream(DeliveryStreamName=stream_name) + @pytest.mark.skip(reason="flaky") @markers.aws.unknown def test_lambda_streams_batch_and_transactions( self, @@ -462,10 +463,7 @@ def process_records(records): def check_events(): if len(events) != num_events: - msg = "DynamoDB updates retrieved (actual/expected): %s/%s" % ( - len(events), - num_events, - ) + msg = f"DynamoDB updates retrieved (actual/expected): {len(events)}/{num_events}" LOGGER.warning(msg) assert len(events) == num_events event_items = [json.loads(base64.b64decode(e["data"])) for e in events] @@ -481,7 +479,7 @@ def check_events(): for i, event in enumerate(inserts): assert "old_image" not in event - item_id = "testId%d" % i + item_id = f"testId{i:d}" matching = [i for i in inserts if i["new_image"]["id"] == item_id][0] assert matching["new_image"] == {"id": item_id, "data": "foobar123"} @@ -529,7 +527,7 @@ def found(update): for i, event in enumerate(removes): assert "new_image" not in event - item_id = "testId%d" % i + item_id = f"testId{i:d}" matching = [i for i in removes if i["old_image"]["id"] == item_id][0] assert matching["old_image"] == {"id": item_id, "data": "foobar123"} @@ -584,9 +582,9 @@ def test_kinesis_lambda_forward_chain( ) # publish test record - test_data = {"test_data": "forward_chain_data_%s with 'quotes\\\"" % short_uid()} + test_data = {"test_data": f"forward_chain_data_{short_uid()} with 'quotes\\\""} data = clone(test_data) - data[lambda_integration.MSG_BODY_MESSAGE_TARGET] = "kinesis:%s" % stream2_name + data[lambda_integration.MSG_BODY_MESSAGE_TARGET] = f"kinesis:{stream2_name}" LOGGER.debug("put record") aws_client.kinesis.put_record( Data=to_bytes(json.dumps(data)), diff --git a/tests/aws/test_moto.py b/tests/aws/test_moto.py index e28b4fd71ebc9..c1de30c2aba97 100644 --- a/tests/aws/test_moto.py +++ b/tests/aws/test_moto.py @@ -1,5 +1,4 @@ from io import BytesIO -from typing import Optional import pytest from moto.core import DEFAULT_ACCOUNT_ID as DEFAULT_MOTO_ACCOUNT_ID @@ -21,7 +20,7 @@ def test_call_with_sqs_creates_state_correctly(): qname = f"queue-{short_uid()}" response = moto.call_moto( - moto.create_aws_request_context("sqs", "CreateQueue", {"QueueName": qname}), + moto.create_aws_request_context("sqs", "CreateQueue", "json", {"QueueName": qname}), include_response_metadata=True, ) url = response["QueueUrl"] @@ -30,12 +29,14 @@ def test_call_with_sqs_creates_state_correctly(): assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 assert response["QueueUrl"].endswith(f"/{qname}") - response = moto.call_moto(moto.create_aws_request_context("sqs", "ListQueues")) + response = moto.call_moto(moto.create_aws_request_context("sqs", "ListQueues", "json")) assert url in response["QueueUrls"] finally: - moto.call_moto(moto.create_aws_request_context("sqs", "DeleteQueue", {"QueueUrl": url})) + moto.call_moto( + moto.create_aws_request_context("sqs", "DeleteQueue", "json", {"QueueUrl": url}) + ) - response = moto.call_moto(moto.create_aws_request_context("sqs", "ListQueues")) + response = moto.call_moto(moto.create_aws_request_context("sqs", "ListQueues", "json")) assert url not in response.get("QueueUrls", []) @@ -46,6 +47,7 @@ def test_call_sqs_invalid_call_raises_http_exception(): moto.create_aws_request_context( "sqs", "DeleteQueue", + "json", { "QueueUrl": "http://0.0.0.0/nonexistingqueue", }, @@ -59,7 +61,7 @@ def test_call_non_implemented_operation(): with pytest.raises(NotImplementedError): # we'll need to keep finding methods that moto doesn't implement ;-) moto.call_moto( - moto.create_aws_request_context("athena", "DeleteDataCatalog", {"Name": "foo"}) + moto.create_aws_request_context("athena", "DeleteDataCatalog", "json", {"Name": "foo"}) ) @@ -71,11 +73,11 @@ def test_call_with_sqs_modifies_state_in_moto_backend(): qname = f"queue-{short_uid()}" response = moto.call_moto( - moto.create_aws_request_context("sqs", "CreateQueue", {"QueueName": qname}) + moto.create_aws_request_context("sqs", "CreateQueue", "json", {"QueueName": qname}) ) url = response["QueueUrl"] assert qname in sqs_backends[DEFAULT_MOTO_ACCOUNT_ID][AWS_REGION_US_EAST_1].queues - moto.call_moto(moto.create_aws_request_context("sqs", "DeleteQueue", {"QueueUrl": url})) + moto.call_moto(moto.create_aws_request_context("sqs", "DeleteQueue", "json", {"QueueUrl": url})) assert qname not in sqs_backends[DEFAULT_MOTO_ACCOUNT_ID][AWS_REGION_US_EAST_1].queues @@ -94,17 +96,21 @@ def test_call_s3_with_streaming_trait(payload, monkeypatch): key_name = f"key-{short_uid()}" # create the bucket - moto.call_moto(moto.create_aws_request_context("s3", "CreateBucket", {"Bucket": bucket_name})) + moto.call_moto( + moto.create_aws_request_context("s3", "CreateBucket", "rest-xml", {"Bucket": bucket_name}) + ) moto.call_moto( moto.create_aws_request_context( - "s3", "PutObject", {"Bucket": bucket_name, "Key": key_name, "Body": payload} + "s3", "PutObject", "rest-xml", {"Bucket": bucket_name, "Key": key_name, "Body": payload} ) ) # check whether it was created/received correctly response = moto.call_moto( - moto.create_aws_request_context("s3", "GetObject", {"Bucket": bucket_name, "Key": key_name}) + moto.create_aws_request_context( + "s3", "GetObject", "rest-xml", {"Bucket": bucket_name, "Key": key_name} + ) ) assert hasattr(response["Body"], "read"), ( f"expected Body to be readable, was {type(response['Body'])}" @@ -114,15 +120,17 @@ def test_call_s3_with_streaming_trait(payload, monkeypatch): # cleanup moto.call_moto( moto.create_aws_request_context( - "s3", "DeleteObject", {"Bucket": bucket_name, "Key": key_name} + "s3", "DeleteObject", "rest-xml", {"Bucket": bucket_name, "Key": key_name} ) ) - moto.call_moto(moto.create_aws_request_context("s3", "DeleteBucket", {"Bucket": bucket_name})) + moto.call_moto( + moto.create_aws_request_context("s3", "DeleteBucket", "rest-xml", {"Bucket": bucket_name}) + ) @markers.aws.only_localstack def test_call_include_response_metadata(): - ctx = moto.create_aws_request_context("sqs", "ListQueues") + ctx = moto.create_aws_request_context("sqs", "ListQueues", "json") response = moto.call_moto(ctx) assert "ResponseMetadata" not in response @@ -138,14 +146,14 @@ def test_call_with_modified_request(): qname1 = f"queue-{short_uid()}" qname2 = f"queue-{short_uid()}" - context = moto.create_aws_request_context("sqs", "CreateQueue", {"QueueName": qname1}) + context = moto.create_aws_request_context("sqs", "CreateQueue", "json", {"QueueName": qname1}) response = moto.call_moto_with_request(context, {"QueueName": qname2}) # overwrite old request url = response["QueueUrl"] assert qname2 in sqs_backends[DEFAULT_MOTO_ACCOUNT_ID][AWS_REGION_US_EAST_1].queues assert qname1 not in sqs_backends[DEFAULT_MOTO_ACCOUNT_ID][AWS_REGION_US_EAST_1].queues - moto.call_moto(moto.create_aws_request_context("sqs", "DeleteQueue", {"QueueUrl": url})) + moto.call_moto(moto.create_aws_request_context("sqs", "DeleteQueue", "json", {"QueueUrl": url})) @markers.aws.only_localstack @@ -155,6 +163,7 @@ def test_call_with_es_creates_state_correctly(): moto.create_aws_request_context( "es", "CreateElasticsearchDomain", + "rest-json", { "DomainName": domain_name, "ElasticsearchVersion": "7.10", @@ -170,7 +179,7 @@ def test_call_with_es_creates_state_correctly(): finally: response = moto.call_moto( moto.create_aws_request_context( - "es", "DeleteElasticsearchDomain", {"DomainName": domain_name} + "es", "DeleteElasticsearchDomain", "rest-json", {"DomainName": domain_name} ), include_response_metadata=True, ) @@ -186,12 +195,12 @@ def test_call_multi_region_backends(): moto.call_moto( moto.create_aws_request_context( - "sqs", "CreateQueue", {"QueueName": qname_us}, region="us-east-1" + "sqs", "CreateQueue", "json", {"QueueName": qname_us}, region="us-east-1" ) ) moto.call_moto( moto.create_aws_request_context( - "sqs", "CreateQueue", {"QueueName": qname_eu}, region="eu-central-1" + "sqs", "CreateQueue", "json", {"QueueName": qname_eu}, region="eu-central-1" ) ) @@ -212,6 +221,7 @@ def test_call_with_sqs_invalid_call_raises_exception(): moto.create_aws_request_context( "sqs", "DeleteQueue", + "json", { "QueueUrl": "http://0.0.0.0/nonexistingqueue", }, @@ -224,7 +234,7 @@ def test_call_with_sqs_returns_service_response(): qname = f"queue-{short_uid()}" create_queue_response = moto.call_moto( - moto.create_aws_request_context("sqs", "CreateQueue", {"QueueName": qname}) + moto.create_aws_request_context("sqs", "CreateQueue", "json", {"QueueName": qname}) ) assert "QueueUrl" in create_queue_response @@ -248,6 +258,7 @@ def test_call_with_sns_with_full_uri(): sns_service = load_service("sns") context = RequestContext(sns_request) context.account = "test" + context.protocol = "query" context.region = "us-west-1" context.service = sns_service context.operation = sns_service.operation_model("CreateTopic") @@ -287,7 +298,7 @@ def test_moto_fallback_dispatcher(): assert "CreateQueue" in dispatcher def _dispatch(action, params): - context = moto.create_aws_request_context("sqs", action, params) + context = moto.create_aws_request_context("sqs", action, "json", params) return dispatcher[action](context, params) qname = f"queue-{short_uid()}" @@ -306,7 +317,7 @@ class FakeNoSuchBucket(ServiceException): code: str = "NoSuchBucket" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[str] + BucketName: str | None def __init__(self) -> None: super().__init__() @@ -347,7 +358,7 @@ def test_moto_fallback_dispatcher_error_handling(monkeypatch): dispatcher = MotoFallbackDispatcher(provider) def _dispatch(action, params): - context = moto.create_aws_request_context("s3", action, params) + context = moto.create_aws_request_context("s3", action, "rest-xml", params) return dispatcher[action](context, params) bucket_name = f"bucket-{short_uid()}" @@ -376,7 +387,7 @@ def test_request_with_response_header_location_fields(): # CreateHostedZoneResponse has a member "Location" that's located in the headers zone_name = f"zone-{short_uid()}.com" request = moto.create_aws_request_context( - "route53", "CreateHostedZone", {"Name": zone_name, "CallerReference": "test"} + "route53", "CreateHostedZone", "rest-xml", {"Name": zone_name, "CallerReference": "test"} ) response = moto.call_moto(request, include_response_metadata=True) # assert response["Location"] # FIXME: this is required according to the spec, but not returned by moto @@ -385,6 +396,14 @@ def test_request_with_response_header_location_fields(): # clean up moto.call_moto( moto.create_aws_request_context( - "route53", "DeleteHostedZone", {"Id": response["HostedZone"]["Id"]} + "route53", "DeleteHostedZone", "rest-xml", {"Id": response["HostedZone"]["Id"]} ) ) + + +@markers.aws.only_localstack +@pytest.mark.skip(reason="json protocol for CloudWatch is not yet supported by Moto") +def test_request_with_multi_protocol_non_default_protocol(): + request = moto.create_aws_request_context("cloudwatch", "DescribeAlarms", "json") + response = moto.call_moto(request) + assert "CompositeAlarms" in response diff --git a/tests/aws/test_multiregion.py b/tests/aws/test_multiregion.py index 73dd75db6aa0c..2dab153598529 100644 --- a/tests/aws/test_multiregion.py +++ b/tests/aws/test_multiregion.py @@ -24,7 +24,7 @@ def test_multi_region_sns(self, aws_client_factory): len_1 = len(sns_1.list_topics()["Topics"]) len_2 = len(sns_2.list_topics()["Topics"]) - topic_name1 = "t-%s" % short_uid() + topic_name1 = f"t-{short_uid()}" sns_1.create_topic(Name=topic_name1) result1 = sns_1.list_topics()["Topics"] result2 = sns_2.list_topics()["Topics"] @@ -32,7 +32,7 @@ def test_multi_region_sns(self, aws_client_factory): assert len(result2) == len_2 assert REGION1 in result1[0]["TopicArn"] - topic_name2 = "t-%s" % short_uid() + topic_name2 = f"t-{short_uid()}" sns_2.create_topic(Name=topic_name2) result2 = sns_2.list_topics()["Topics"] assert len(result2) == len_2 + 1 @@ -48,20 +48,20 @@ def test_multi_region_api_gateway(self, aws_client_factory, account_id): len_1 = len(gw_1.get_rest_apis()["items"]) len_2 = len(gw_2.get_rest_apis()["items"]) - api_name1 = "a-%s" % short_uid() + api_name1 = f"a-{short_uid()}" gw_1.create_rest_api(name=api_name1) result1 = gw_1.get_rest_apis()["items"] assert len(result1) == len_1 + 1 assert len(gw_2.get_rest_apis()["items"]) == len_2 - api_name2 = "a-%s" % short_uid() + api_name2 = f"a-{short_uid()}" gw_2.create_rest_api(name=api_name2) result2 = gw_2.get_rest_apis()["items"] assert len(gw_1.get_rest_apis()["items"]) == len_1 + 1 assert len(result2) == len_2 + 1 - api_name3 = "a-%s" % short_uid() - queue_name1 = "q-%s" % short_uid() + api_name3 = f"a-{short_uid()}" + queue_name1 = f"q-{short_uid()}" sqs_1.create_queue(QueueName=queue_name1) queue_arn = arns.sqs_queue_arn(queue_name1, region_name=REGION3, account_id=account_id) @@ -88,8 +88,5 @@ def test_multi_region_api_gateway(self, aws_client_factory, account_id): assert json.loads(to_str(base64.b64decode(to_str(messages[0]["Body"])))) == test_data def _gateway_request_url(self, api_id, stage_name, path): - pattern = "%s/restapis/{api_id}/{stage_name}/%s{path}" % ( - config.internal_service_url(), - PATH_USER_REQUEST, - ) + pattern = f"{config.internal_service_url()}/restapis/{{api_id}}/{{stage_name}}/{PATH_USER_REQUEST}{{path}}" return pattern.format(api_id=api_id, stage_name=stage_name, path=path) diff --git a/tests/aws/test_network_configuration.py b/tests/aws/test_network_configuration.py index de6a7dcdbdd3b..71d4bf2706915 100644 --- a/tests/aws/test_network_configuration.py +++ b/tests/aws/test_network_configuration.py @@ -27,6 +27,7 @@ class TestOpenSearch: OpenSearch does not respect any customisations and just returns a domain with localhost.localstack.cloud in. """ + @markers.requires_in_process @markers.aws.only_localstack def test_default_strategy( self, opensearch_create_domain, assert_host_customisation, aws_client @@ -60,6 +61,7 @@ def test_port_strategy( assert_host_customisation(endpoint) + @markers.requires_in_process @markers.aws.only_localstack def test_path_strategy( self, @@ -81,6 +83,7 @@ def test_path_strategy( class TestS3: @markers.aws.only_localstack + @markers.requires_in_process def test_non_us_east_1_location( self, s3_empty_bucket, cleanups, assert_host_customisation, aws_client_factory ): @@ -101,6 +104,7 @@ def cleanup(): assert_host_customisation(res["Location"]) + @markers.requires_in_process @markers.aws.only_localstack def test_multipart_upload(self, s3_bucket, assert_host_customisation, aws_client): key_name = f"key-{short_uid()}" @@ -119,6 +123,7 @@ def test_multipart_upload(self, s3_bucket, assert_host_customisation, aws_client assert_host_customisation(res["Location"]) + @markers.requires_in_process @markers.aws.only_localstack def test_201_response(self, s3_bucket, assert_host_customisation, aws_client): key_name = f"key-{short_uid()}" @@ -150,6 +155,7 @@ class TestSQS: * LOCALSTACK_HOST """ + @markers.requires_in_process @markers.aws.only_localstack def test_off_strategy_without_external_port( self, monkeypatch, sqs_create_queue, assert_host_customisation @@ -162,6 +168,7 @@ def test_off_strategy_without_external_port( assert_host_customisation(queue_url) assert queue_name in queue_url + @markers.requires_in_process @markers.aws.only_localstack def test_off_strategy_with_external_port( self, monkeypatch, sqs_create_queue, assert_host_customisation @@ -181,6 +188,7 @@ def test_off_strategy_with_external_port( assert queue_name in queue_url assert f":{external_port}" in queue_url + @markers.requires_in_process @markers.aws.only_localstack @pytest.mark.parametrize("strategy", ["standard", "domain"]) def test_domain_based_strategies( @@ -194,6 +202,7 @@ def test_domain_based_strategies( assert_host_customisation(queue_url) assert queue_name in queue_url + @markers.requires_in_process @markers.aws.only_localstack def test_path_strategy(self, monkeypatch, sqs_create_queue, assert_host_customisation): monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", "path") @@ -206,6 +215,7 @@ def test_path_strategy(self, monkeypatch, sqs_create_queue, assert_host_customis class TestLambda: + @markers.requires_in_process @markers.aws.only_localstack def test_function_url(self, assert_host_customisation, create_lambda_function, aws_client): function_name = f"function-{short_uid()}" @@ -226,6 +236,7 @@ def test_function_url(self, assert_host_customisation, create_lambda_function, a assert_host_customisation(function_url) + @markers.requires_in_process @pytest.mark.skip(reason="Not implemented for new provider (was tested for old provider)") @markers.aws.only_localstack def test_http_api_for_function_url( diff --git a/tests/aws/test_terraform.py b/tests/aws/test_terraform.py deleted file mode 100644 index e6f8656addd3a..0000000000000 --- a/tests/aws/test_terraform.py +++ /dev/null @@ -1,246 +0,0 @@ -import os -import re -import threading - -import pytest - -from localstack.packages.terraform import terraform_package -from localstack.testing.config import ( - TEST_AWS_ACCESS_KEY_ID, - TEST_AWS_REGION_NAME, - TEST_AWS_SECRET_ACCESS_KEY, -) -from localstack.testing.pytest import markers -from localstack.utils.common import is_command_available, rm_rf, run, start_worker_thread - -# TODO: remove all of these - -BUCKET_NAME = "tf-bucket" -QUEUE_NAME = "tf-queue" -QUEUE_ARN = "arn:aws:sqs:us-east-1:{account_id}:tf-queue" - -# lambda Testing Variables -LAMBDA_NAME = "tf-lambda" -LAMBDA_ARN = "arn:aws:lambda:us-east-1:{account_id}:function:{lambda_name}" -LAMBDA_HANDLER = "index.handler" -LAMBDA_RUNTIME = "python3.8" -LAMBDA_ROLE = "arn:aws:iam::{account_id}:role/iam_for_lambda" - -INIT_LOCK = threading.RLock() - -# set after calling install() -TERRAFORM_BIN = None - - -def check_terraform_version(): - if not is_command_available(TERRAFORM_BIN): - return False, None - - ver_string = run([TERRAFORM_BIN, "-version"]) - ver_string = re.search(r"v(\d+\.\d+\.\d+)", ver_string).group(1) - if ver_string is None: - return False, None - return True, ver_string - - -@pytest.fixture(scope="module", autouse=True) -def setup_test(account_id, region_name): - with INIT_LOCK: - available, version = check_terraform_version() - - if not available: - msg = "could not find a compatible version of terraform" - if version: - msg += f" (version = {version})" - else: - msg += " (command not found)" - - return pytest.skip(msg) - - env_vars = { - "AWS_ACCESS_KEY_ID": account_id, - "AWS_SECRET_ACCESS_KEY": account_id, - "AWS_REGION": region_name, - } - - run( - "cd %s; %s apply -input=false tfplan" % (get_base_dir(), TERRAFORM_BIN), - env_vars=env_vars, - ) - - yield - - # clean up - run("cd %s; %s destroy -auto-approve" % (get_base_dir(), TERRAFORM_BIN), env_vars=env_vars) - - -def get_base_dir(): - return os.path.join(os.path.dirname(__file__), "terraform") - - -# TODO: replace "clouddrove/api-gateway/aws" with normal apigateway module and update terraform -# TODO: rework this setup for multiple (potentially parallel) terraform tests by providing variables (see .auto.tfvars) -# TODO: fetch generated ARNs from terraform instead of static/building ARNs -@pytest.mark.skip(reason="disabled until further notice due to flakiness and lacking quality") -class TestTerraform: - @classmethod - def init_async(cls): - def _run(*args): - with INIT_LOCK: - terraform_package.install() - global TERRAFORM_BIN - TERRAFORM_BIN = terraform_package.get_installer().get_executable_path() - base_dir = get_base_dir() - env_vars = { - "AWS_ACCESS_KEY_ID": TEST_AWS_ACCESS_KEY_ID, - "AWS_SECRET_ACCESS_KEY": TEST_AWS_SECRET_ACCESS_KEY, - "AWS_REGION": TEST_AWS_REGION_NAME, - } - if not os.path.exists(os.path.join(base_dir, ".terraform", "plugins")): - run(f"cd {base_dir}; {TERRAFORM_BIN} init -input=false", env_vars=env_vars) - # remove any cache files from previous runs - for tf_file in [ - "tfplan", - "terraform.tfstate", - "terraform.tfstate.backup", - ]: - rm_rf(os.path.join(base_dir, tf_file)) - # create TF plan - run( - f"cd {base_dir}; {TERRAFORM_BIN} plan -out=tfplan -input=false", - env_vars=env_vars, - ) - - start_worker_thread(_run) - - @markers.skip_offline - @markers.aws.needs_fixing - def test_bucket_exists(self, aws_client): - response = aws_client.s3.head_bucket(Bucket=BUCKET_NAME) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - - cors = { - "AllowedHeaders": ["*"], - "AllowedMethods": ["GET", "PUT", "POST"], - "AllowedOrigins": ["*"], - "ExposeHeaders": ["ETag", "x-amz-version-id"], - "MaxAgeSeconds": 3000, - } - - response = aws_client.s3.get_bucket_cors(Bucket=BUCKET_NAME) - assert response["CORSRules"][0] == cors - - response = aws_client.s3.get_bucket_versioning(Bucket=BUCKET_NAME) - assert response["Status"] == "Enabled" - - @markers.skip_offline - @markers.aws.needs_fixing - def test_sqs(self, aws_client): - queue_url = aws_client.sqs.get_queue_url(QueueName=QUEUE_NAME)["QueueUrl"] - response = aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["All"]) - - assert response["Attributes"]["DelaySeconds"] == "90" - assert response["Attributes"]["MaximumMessageSize"] == "2048" - assert response["Attributes"]["MessageRetentionPeriod"] == "86400" - assert response["Attributes"]["ReceiveMessageWaitTimeSeconds"] == "10" - - @markers.skip_offline - @markers.aws.needs_fixing - def test_lambda(self, aws_client, account_id): - response = aws_client.lambda_.get_function(FunctionName=LAMBDA_NAME) - assert response["Configuration"]["FunctionName"] == LAMBDA_NAME - assert response["Configuration"]["Handler"] == LAMBDA_HANDLER - assert response["Configuration"]["Runtime"] == LAMBDA_RUNTIME - assert response["Configuration"]["Role"] == LAMBDA_ROLE.format(account_id=account_id) - - @markers.skip_offline - @markers.aws.needs_fixing - def test_event_source_mapping(self, aws_client, account_id): - queue_arn = QUEUE_ARN.format(account_id=account_id) - lambda_arn = LAMBDA_ARN.format(account_id=account_id, lambda_name=LAMBDA_NAME) - all_mappings = aws_client.lambda_.list_event_source_mappings( - EventSourceArn=queue_arn, FunctionName=LAMBDA_NAME - ) - function_mapping = all_mappings.get("EventSourceMappings")[0] - assert function_mapping["FunctionArn"] == lambda_arn - assert function_mapping["EventSourceArn"] == queue_arn - - @markers.skip_offline - @pytest.mark.skip(reason="flaky") - @markers.aws.needs_fixing - def test_apigateway(self, aws_client): - rest_apis = aws_client.apigateway.get_rest_apis() - - rest_id = None - for rest_api in rest_apis["items"]: - if rest_api["name"] == "test-tf-apigateway": - rest_id = rest_api["id"] - break - - assert rest_id - resources = aws_client.apigateway.get_resources(restApiId=rest_id)["items"] - - # We always have 1 default root resource (with path "/") - assert len(resources) == 3 - - res1 = [r for r in resources if r.get("pathPart") == "mytestresource"] - assert res1 - assert res1[0]["path"] == "/mytestresource" - assert len(res1[0]["resourceMethods"]) == 2 - assert res1[0]["resourceMethods"]["GET"]["methodIntegration"]["type"] == "MOCK" - - res2 = [r for r in resources if r.get("pathPart") == "mytestresource1"] - assert res2 - assert res2[0]["path"] == "/mytestresource1" - assert len(res2[0]["resourceMethods"]) == 2 - assert res2[0]["resourceMethods"]["GET"]["methodIntegration"]["type"] == "AWS_PROXY" - assert res2[0]["resourceMethods"]["GET"]["methodIntegration"]["uri"] - - @markers.skip_offline - @markers.aws.needs_fixing - def test_route53(self, aws_client): - response = aws_client.route53.create_hosted_zone(Name="zone123", CallerReference="ref123") - assert response["ResponseMetadata"]["HTTPStatusCode"] == 201 - change_id = response.get("ChangeInfo", {}).get("Id", "change123") - - response = aws_client.route53.get_change(Id=change_id) - assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 - - @markers.skip_offline - @markers.aws.needs_fixing - def test_acm(self, aws_client): - certs = aws_client.acm.list_certificates()["CertificateSummaryList"] - certs = [c for c in certs if c.get("DomainName") == "example.com"] - assert len(certs) == 1 - - @markers.skip_offline - @pytest.mark.skip(reason="flaky") - @markers.aws.needs_fixing - def test_apigateway_escaped_policy(self, aws_client): - rest_apis = aws_client.apigateway.get_rest_apis() - - service_apis = [] - - for rest_api in rest_apis["items"]: - if rest_api["name"] == "service_api": - service_apis.append(rest_api) - - assert len(service_apis) == 1 - - @markers.skip_offline - @markers.aws.needs_fixing - def test_dynamodb(self, aws_client): - def _table_exists(tablename, dynamotables): - return any(name for name in dynamotables["TableNames"] if name == tablename) - - tables = aws_client.dynamodb.list_tables() - assert _table_exists("tf_dynamotable1", tables) - assert _table_exists("tf_dynamotable2", tables) - assert _table_exists("tf_dynamotable3", tables) - - @markers.skip_offline - @markers.aws.needs_fixing - def test_security_groups(self, aws_client): - rules = aws_client.ec2.describe_security_groups(MaxResults=100)["SecurityGroups"] - matching = [r for r in rules if r["Description"] == "TF SG with ingress / egress rules"] - assert matching diff --git a/tests/bin/test.docker-helper.bats b/tests/bin/test.docker-helper.bats index d05277c98e5d5..2d91df6525466 100755 --- a/tests/bin/test.docker-helper.bats +++ b/tests/bin/test.docker-helper.bats @@ -13,6 +13,9 @@ setup_file() { "branch") echo "main" ;; + "describe") + echo "$TEST_TAG" + ;; "remote") echo "origin git@github.com:localstack/localstack.git (push)" ;; @@ -26,8 +29,13 @@ setup_file() { function python3() { case $2 in "setuptools_scm") - # setuptools_scm returns out test version - echo "$TEST_SPECIFIC_VERSION" + # setuptools_scm returns our test version; prefer TEST_TAG if set + if [ -n "${TEST_TAG-}" ]; then + # strip leading 'v' if present (e.g., v1.2.3 -> 1.2.3) + echo "${TEST_TAG#v}" + else + echo "$TEST_SPECIFIC_VERSION" + fi ;; "pip") # pip exits with $TEST_PIP_EXIT_CODE @@ -62,6 +70,7 @@ setup_file() { # build @test "build creates image from custom Dockerfile" { + export PLATFORM="amd64" export IMAGE_NAME="localstack/test" export DOCKERFILE="tests/bin/files/Dockerfile" export TEST_SPECIFIC_VERSION="3.6.1.dev45" @@ -117,12 +126,42 @@ setup_file() { @test "push fails on non-default branch" { export MAIN_BRANCH="non-existing-branch" export IMAGE_NAME="localstack/test" + export DOCKER_USERNAME=test + export DOCKER_PASSWORD=test export PLATFORM=amd64 run bin/docker-helper.sh push [ "$status" -ne 0 ] [[ "$output" =~ "is not non-existing-branch" ]] } +@test "push succeeds on tag without main branch" { + export MAIN_BRANCH="non-existing-branch" + export IMAGE_NAME="localstack/test" + export DOCKER_USERNAME=test + export DOCKER_PASSWORD=test + export PLATFORM=amd64 + export TEST_TAG=v1.0.0 + run bin/docker-helper.sh push + [ "$status" -eq 0 ] + [[ "$output" =~ "docker push $IMAGE_NAME:1-$PLATFORM" ]] + [[ "$output" =~ "docker push $IMAGE_NAME:1.0-$PLATFORM" ]] + [[ "$output" =~ "docker push $IMAGE_NAME:1.0.0-$PLATFORM" ]] + [[ "$output" =~ "docker push $IMAGE_NAME:stable-$PLATFORM" ]] +} + +@test "push fails on malformed tag" { + export MAIN_BRANCH="non-existing-branch" + export IMAGE_NAME="localstack/test" + export DOCKER_USERNAME=test + export DOCKER_PASSWORD=test + export PLATFORM=amd64 + export TEST_TAG=not-a-version + run bin/docker-helper.sh push + [ "$status" -ne 0 ] + [[ "$output" =~ "tag '$TEST_TAG' is not a version tag" ]] +} + + @test "push fails without PLATFORM" { export IMAGE_NAME="localstack/test" export MAIN_BRANCH="main" @@ -259,6 +298,7 @@ setup_file() { @test "cmd-build throws error when setuptools-scm is not installed" { + export PLATFORM="amd64" export TEST_PIP_FAIL=1 export IMAGE_NAME="localstack/test" @@ -266,3 +306,36 @@ setup_file() { [ "$status" -eq 1 ] [[ "$output" =~ "ERROR" ]] } + +# get-release-version + +@test "get-release-version returns version for a release commit" { + export TEST_SPECIFIC_VERSION="4.0.0" + run bin/docker-helper.sh get-release-version + [ "$status" -eq 0 ] + [[ "$output" == "4.0.0" ]] +} + +@test "get-release-version throws error for non-release commits" { + export TEST_SPECIFIC_VERSION="4.0.0.dev123" + run bin/docker-helper.sh get-release-version + [ "$status" -eq 1 ] + [[ "$output" == "Not a release commit." ]] +} + +@test "build fails without PLATFORM" { + export IMAGE_NAME="localstack/test" + export TEST_SPECIFIC_VERSION="3.6.1.dev45" + run bin/docker-helper.sh build + [ "$status" -ne 0 ] + [[ "$output" =~ "PLATFORM is missing" ]] +} + +@test "build succeeds with PLATFORM" { + export IMAGE_NAME="localstack/test" + export PLATFORM="amd64" + export TEST_SPECIFIC_VERSION="3.6.1.dev45" + run bin/docker-helper.sh build + [ "$status" -eq 0 ] + [[ "$output" =~ "docker buildx build --platform linux/amd64" ]] +} diff --git a/tests/bootstrap/cdk_templates/LocalStackHostBootstrap/ClusterStack.json b/tests/bootstrap/cdk_templates/LocalStackHostBootstrap/ClusterStack.json index b3d13d04488aa..34917a1af679b 100644 --- a/tests/bootstrap/cdk_templates/LocalStackHostBootstrap/ClusterStack.json +++ b/tests/bootstrap/cdk_templates/LocalStackHostBootstrap/ClusterStack.json @@ -27,7 +27,7 @@ "EncryptionAtRestOptions": { "Enabled": false }, - "EngineVersion": "OpenSearch_2.3", + "EngineVersion": "OpenSearch_2.19", "LogPublishingOptions": {}, "NodeToNodeEncryptionOptions": { "Enabled": false diff --git a/tests/bootstrap/conftest.py b/tests/bootstrap/conftest.py index 9886d22853fa9..9219be34abdb6 100644 --- a/tests/bootstrap/conftest.py +++ b/tests/bootstrap/conftest.py @@ -1,5 +1,4 @@ import os -from typing import Optional import pytest @@ -20,7 +19,7 @@ def cdk_template_path(): @pytest.fixture(scope="session") def infrastructure_setup(cdk_template_path, aws_client_factory): def _infrastructure_setup( - namespace: str, force_synth: Optional[bool] = False, port: int = constants.DEFAULT_PORT_EDGE + namespace: str, force_synth: bool | None = False, port: int = constants.DEFAULT_PORT_EDGE ) -> InfraProvisioner: """ :param namespace: repo-unique identifier for this CDK app. diff --git a/tests/bootstrap/test_cosmetic_configuration.py b/tests/bootstrap/test_cosmetic_configuration.py index 37610216c3b25..aab3afd9006e8 100644 --- a/tests/bootstrap/test_cosmetic_configuration.py +++ b/tests/bootstrap/test_cosmetic_configuration.py @@ -1,6 +1,6 @@ import io import os -from typing import Generator, Type +from collections.abc import Generator from urllib.parse import urlparse import aws_cdk as cdk @@ -46,14 +46,14 @@ def chosen_localstack_host() -> str: # these fixtures have been copied from the pre-existing fixtures @pytest.fixture(scope="class") -def class_container_factory() -> Generator[ContainerFactory, None, None]: +def class_container_factory() -> Generator[ContainerFactory]: factory = ContainerFactory() yield factory factory.remove_all_containers() @pytest.fixture(scope="class") -def class_stream_container_logs() -> Generator[LogStreamFactory, None, None]: +def class_stream_container_logs() -> Generator[LogStreamFactory]: factory = LogStreamFactory() yield factory factory.close() @@ -87,7 +87,7 @@ def container( def raise_exception_with_cloudwatch_logs( - aws_client: ServiceLevelClientFactory, exc_class: Type[Exception] = AssertionError + aws_client: ServiceLevelClientFactory, exc_class: type[Exception] = AssertionError ): out = io.StringIO() @@ -155,7 +155,7 @@ def infrastructure( stack, "Domain", domain_name=domain_name, - version=cdk.aws_opensearchservice.EngineVersion.OPENSEARCH_2_3, + version=cdk.aws_opensearchservice.EngineVersion.OPENSEARCH_2_19, ) cdk.CfnOutput(stack, "DomainEndpoint", value=domain.domain_endpoint) diff --git a/tests/bootstrap/test_localstack_container_server.py b/tests/bootstrap/test_localstack_container_server.py index 29dea53c7b9cf..37c8eddb47800 100644 --- a/tests/bootstrap/test_localstack_container_server.py +++ b/tests/bootstrap/test_localstack_container_server.py @@ -19,16 +19,13 @@ def test_lifecycle(self): assert server.wait_is_up(60) health_response = requests.get("http://localhost:4566/_localstack/health") - assert health_response.ok, ( - "expected health check to return OK: %s" % health_response.text - ) + assert health_response.ok, f"expected health check to return OK: {health_response.text}" restart_response = requests.post( "http://localhost:4566/_localstack/health", json={"action": "restart"} ) assert restart_response.ok, ( - "expected restart command via health endpoint to return OK: %s" - % restart_response.text + f"expected restart command via health endpoint to return OK: {restart_response.text}" ) def check_restart_successful(): diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py deleted file mode 100644 index 4c3a166dbd7a6..0000000000000 --- a/tests/cli/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Bootstrapping is the process of starting localstack. This is not always testable in the regular integration test -environment. This is what this module is for.""" diff --git a/tests/cli/conftest.py b/tests/cli/conftest.py deleted file mode 100644 index 0579c8a9122cb..0000000000000 --- a/tests/cli/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ -import pytest - -from localstack import config - - -@pytest.fixture(autouse=True) -def _setup_cli_environment(monkeypatch): - # normally we are setting LOCALSTACK_CLI in localstack/cli/main.py, which is not actually run in the tests - monkeypatch.setenv("LOCALSTACK_CLI", "1") - monkeypatch.setattr(config, "dirs", config.Directories.for_cli()) diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py deleted file mode 100644 index aef3f08abd50d..0000000000000 --- a/tests/cli/test_cli.py +++ /dev/null @@ -1,306 +0,0 @@ -import json -import logging -import os.path - -import pytest -import requests -from click.testing import CliRunner - -import localstack.utils.container_utils.docker_cmd_client -from localstack import config, constants -from localstack.cli.localstack import localstack as cli -from localstack.config import Directories, in_docker -from localstack.constants import MODULE_MAIN_PATH -from localstack.utils import bootstrap -from localstack.utils.bootstrap import in_ci -from localstack.utils.common import poll_condition -from localstack.utils.container_utils.container_client import ContainerClient, NoSuchImage -from localstack.utils.files import mkdir -from localstack.utils.net import get_free_udp_port -from localstack.utils.run import run, to_str - -LOG = logging.getLogger(__name__) - - -@pytest.fixture -def runner(): - return CliRunner() - - -def container_exists(client: ContainerClient, container_name: str) -> bool: - try: - container_id = client.get_container_id(container_name) - return True if container_id else False - except Exception: - return False - - -@pytest.fixture(autouse=True) -def container_client(): - client = localstack.utils.container_utils.docker_cmd_client.CmdDockerClient() - - yield client - - try: - client.stop_container(config.MAIN_CONTAINER_NAME, timeout=5) - except Exception: - pass - - # wait until container has been removed - assert poll_condition( - lambda: not container_exists(client, config.MAIN_CONTAINER_NAME), timeout=20 - ) - - -@pytest.fixture -def backup_and_remove_image(monkeypatch, container_client: ContainerClient): - """ - To test whether the image is pulled correctly, we must remove the image. - However we do not want to do this and remove the current image, so "back it - up" - i.e. tag it with another tag, and restore it afterwards. - """ - - source_image_name = f"{constants.DOCKER_IMAGE_NAME}:latest" - tagged_image_name = f"{constants.DOCKER_IMAGE_NAME}:backup" - container_client.tag_image(source_image_name, tagged_image_name) - container_client.remove_image(source_image_name, force=True) - monkeypatch.setenv("IMAGE_NAME", source_image_name) - - yield - - container_client.tag_image(tagged_image_name, source_image_name) - - -@pytest.mark.skipif(condition=in_docker(), reason="cannot run CLI tests in docker") -class TestCliContainerLifecycle: - def test_start_wait_stop(self, runner, container_client): - result = runner.invoke(cli, ["start", "-d"]) - assert result.exit_code == 0 - assert "starting LocalStack" in result.output - - result = runner.invoke(cli, ["wait", "-t", "60"]) - assert result.exit_code == 0 - - assert container_client.is_container_running(config.MAIN_CONTAINER_NAME), ( - "container name was not running after wait" - ) - - # Note: if `LOCALSTACK_HOST` is set to a domain that does not resolve to `127.0.0.1` then - # this test will fail - health = requests.get(config.external_service_url() + "/_localstack/health") - assert health.ok, "health request did not return OK: %s" % health.text - - result = runner.invoke(cli, ["stop"]) - assert result.exit_code == 0 - - with pytest.raises(requests.ConnectionError): - requests.get(config.external_service_url() + "/_localstack/health") - - @pytest.mark.usefixtures("backup_and_remove_image") - def test_pulling_image_message(self, runner, container_client: ContainerClient): - image_name_and_tag = f"{constants.DOCKER_IMAGE_NAME}:latest" - with pytest.raises(NoSuchImage): - container_client.inspect_image(image_name_and_tag, pull=False) - - result = runner.invoke(cli, ["start", "-d"]) - - assert result.exit_code == 0, result.output - - # we cannot check for "Pulling container image" which would be more accurate - # since it is printed in a temporary status line and may not be present in - # the output if the docker pull is fast enough, but we can check for the - # presence of another message which is present when pulling the image. - assert "download complete" in result.output - - def test_start_already_running(self, runner, container_client): - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "180"]) - result = runner.invoke(cli, ["start"]) - assert container_exists(container_client, config.MAIN_CONTAINER_NAME) - assert result.exit_code == 1 - assert "Error" in result.output - assert "is already running" in result.output - - def test_wait_timeout_raises_exception(self, runner, container_client): - # assume a wait without start fails - result = runner.invoke(cli, ["wait", "-t", "0.5"]) - assert result.exit_code != 0 - - def test_logs(self, runner, container_client): - result = runner.invoke(cli, ["logs"]) - assert result.exit_code != 0 - - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - result = runner.invoke(cli, ["logs", "--tail", "20"]) - assert constants.READY_MARKER_OUTPUT in result.output.splitlines() - - def test_restart(self, runner, container_client): - result = runner.invoke(cli, ["restart"]) - assert result.exit_code != 0 - - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - result = runner.invoke(cli, ["restart"]) - assert result.exit_code == 0 - assert "restarted" in result.output - - def test_status_services(self, runner): - result = runner.invoke(cli, ["status", "services"]) - assert result.exit_code != 0 - assert "could not connect to LocalStack health endpoint" in result.output - - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - result = runner.invoke(cli, ["status", "services"]) - - # just a smoke test - assert "dynamodb" in result.output - for line in result.output.splitlines(): - if "dynamodb" in line: - assert "available" in line - - def test_custom_docker_flags(self, runner, tmp_path, monkeypatch, container_client): - volume = tmp_path / "volume" - volume.mkdir() - - monkeypatch.setattr(config, "DOCKER_FLAGS", f"-p 42069 -v {volume}:{volume}") - - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - inspect = container_client.inspect_container(config.MAIN_CONTAINER_NAME) - assert "42069/tcp" in inspect["HostConfig"]["PortBindings"] - assert f"{volume}:{volume}" in inspect["HostConfig"]["Binds"] - - def test_volume_dir_mounted_correctly(self, runner, tmp_path, monkeypatch, container_client): - volume_dir = tmp_path / "volume" - - # set different directories and make sure they are mounted correctly - monkeypatch.setenv("LOCALSTACK_VOLUME_DIR", str(volume_dir)) - monkeypatch.setattr(config, "VOLUME_DIR", str(volume_dir)) - - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - # check that mounts were created correctly - inspect = container_client.inspect_container(config.MAIN_CONTAINER_NAME) - binds = inspect["HostConfig"]["Binds"] - assert f"{volume_dir}:{constants.DEFAULT_VOLUME_DIR}" in binds - - def test_container_starts_non_root(self, runner, monkeypatch, container_client): - user = "localstack" - monkeypatch.setattr(config, "DOCKER_FLAGS", f"--user={user}") - - if in_ci() and os.path.exists("/home/runner"): - volume_dir = "/home/runner/.cache/localstack/volume/" - mkdir(volume_dir) - run(["sudo", "chmod", "-R", "777", volume_dir]) - - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - cmd = ["awslocal", "stepfunctions", "list-state-machines"] - output = container_client.exec_in_container(config.MAIN_CONTAINER_NAME, cmd) - result = json.loads(output[0]) - assert "stateMachines" in result - - output = container_client.exec_in_container(config.MAIN_CONTAINER_NAME, ["ps", "-fu", user]) - assert "localstack-supervisor" in to_str(output[0]) - - def test_start_cli_within_container(self, runner, container_client, tmp_path): - output = container_client.run_container( - # CAVEAT: Updates to the Docker image are not immediately reflected when using the latest image from - # DockerHub in the CI. Re-build the Docker image locally through - # `IMAGE_NAME="localstack/localstack" ./bin/docker-helper.sh build` for local testing. - "localstack/localstack", - remove=True, - entrypoint="", - command=["bin/localstack", "start", "-d"], - volumes=[ - ("/var/run/docker.sock", "/var/run/docker.sock"), - (MODULE_MAIN_PATH, "/opt/code/localstack/localstack"), - ], - env_vars={"LOCALSTACK_VOLUME_DIR": f"{tmp_path}/ls-volume"}, - ) - stdout = to_str(output[0]) - assert "starting LocalStack" in stdout - assert "detaching" in stdout - - # assert that container is running - runner.invoke(cli, ["wait", "-t", "60"]) - - -@pytest.mark.skipif(condition=in_docker(), reason="cannot run CLI tests in docker") -class TestDNSServer: - def test_dns_port_published_with_flag(self, runner, container_client, monkeypatch): - port = get_free_udp_port() - monkeypatch.setenv("DEBUG", "1") - monkeypatch.setenv("DNS_PORT", str(port)) - monkeypatch.setattr(config, "DNS_PORT", port) - - runner.invoke(cli, ["start", "-d", "--host-dns"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - inspect = container_client.inspect_container(config.MAIN_CONTAINER_NAME) - assert f"{port}/udp" in inspect["HostConfig"]["PortBindings"] - - def test_dns_port_not_published_by_default(self, runner, container_client, monkeypatch): - monkeypatch.setenv("DEBUG", "1") - - runner.invoke(cli, ["start", "-d"]) - runner.invoke(cli, ["wait", "-t", "60"]) - - inspect = container_client.inspect_container(config.MAIN_CONTAINER_NAME) - assert "53/udp" not in inspect["HostConfig"]["PortBindings"] - - -class TestHooks: - def test_prepare_host_hook_called_with_correct_dirs(self, runner, monkeypatch): - """ - Assert that the prepare_host(..) hook is called with the appropriate dirs layout (e.g., cache - dir writeable). Required, for example, for API key activation and local key caching. - """ - - # simulate that we're running in Docker - monkeypatch.setattr(config, "is_in_docker", True) - - result_configs = [] - - def _prepare_host(*args, **kwargs): - # store the configs that will be passed to prepare_host hooks (Docker status, infra process, dirs layout) - result_configs.append((config.is_in_docker, None, config.dirs)) - - # patch the prepare_host function which calls the hooks - monkeypatch.setattr(bootstrap, "prepare_host", _prepare_host) - - def noop(*args, **kwargs): - pass - - # patch start_infra_in_docker to be a no-op (we don't actually want to start the container for this test) - assert bootstrap.start_infra_in_docker - monkeypatch.setattr(bootstrap, "start_infra_in_docker", noop) - - # run the 'start' command, which should call the prepare_host hooks - runner.invoke(cli, ["start"]) - - # assert that result configs are as expected - assert len(result_configs) == 1 - dirs: Directories - in_docker, is_infra_process, dirs = result_configs[0] - assert in_docker is False - # cache dir should exist and be writeable - assert os.path.exists(dirs.cache) - assert os.access(dirs.cache, os.W_OK) - - -class TestImports: - """Simple tests to assert that certain code paths can be imported from the CLI""" - - def test_import_venv(self): - from localstack.utils.venv import VirtualEnvironment - - assert VirtualEnvironment diff --git a/tests/conftest.py b/tests/conftest.py index 2a23489c537bc..38f80266897fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,9 @@ import os import pytest +from _pytest.monkeypatch import MonkeyPatch + +from localstack import config os.environ["LOCALSTACK_INTERNAL_TEST_RUN"] = "1" @@ -87,3 +90,16 @@ def secondary_aws_client(secondary_aws_client_factory): from localstack.testing.aws.util import base_testing_aws_client return base_testing_aws_client(secondary_aws_client_factory) + + +@pytest.fixture(scope="session", autouse=True) +def enable_stack_trace_for_tests(): + """ + Ensure stack traces are enabled in HTTP responses during test sessions. + + This is useful for debugging purposes. + """ + mpatch = MonkeyPatch() + mpatch.setattr(config, "INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE", True) + yield mpatch + mpatch.undo() diff --git a/tests/integration/aws/test_app.py b/tests/integration/aws/test_app.py index 59341e5e9c5a5..2aa77351795d0 100644 --- a/tests/integration/aws/test_app.py +++ b/tests/integration/aws/test_app.py @@ -105,8 +105,7 @@ def test_chunked_response_streaming(self, cleanups): chunks = [bytes(f"{n:2}", "utf-8") for n in range(0, 100)] def chunk_generator(): - for chunk in chunks: - yield chunk + yield from chunks def stream_response_handler(_request) -> Response: return Response(response=chunk_generator()) @@ -133,8 +132,7 @@ def handler(request: Request) -> Response: cleanups.append(lambda: ROUTER.remove(rule)) def chunk_generator(): - for chunk in chunks: - yield chunk + yield from chunks response = requests.post( config.internal_service_url() + "/_test/test_chunked_request", data=chunk_generator() diff --git a/tests/unit/test_dns_server.py b/tests/integration/dns/test_dns_server.py similarity index 92% rename from tests/unit/test_dns_server.py rename to tests/integration/dns/test_dns_server.py index 96ffd172b1f7a..479d4aefe5f09 100644 --- a/tests/unit/test_dns_server.py +++ b/tests/integration/dns/test_dns_server.py @@ -20,6 +20,14 @@ class TestDNSServer: + """ + End to end tests of our DNS server implementation. + These tests use the `example.org` and `example.com` domains for testing, and also tests the fallback to the public DNS + Changes in the upstream name resolution of those domains might lead to test failures. + + TODO we should investigate creating our own test domain with a fixed behavior to avoid potential test failures + """ + @pytest.fixture def dns_server(self): dns_port = get_free_udp_port() @@ -137,6 +145,24 @@ def test_dns_server_add_multiple_hosts(self, dns_server, query_dns): assert answer.answer assert "123.123.123.123" in answer.to_text() + def test_dns_server_resolves_alias_wildcards(self, dns_server, query_dns): + """Check if server resolves aliases with wildcards""" + dns_server.add_host("example.org", TargetRecord("1.1.1.1", RecordType.A)) + answer = query_dns("subdomain1.example.org", "A") + assert len(answer.answer) == 0 + + dns_server.add_alias( + source_name="*.example.org", + record_type=RecordType.A, + target=AliasTarget(target="example.org"), + ) + answer = query_dns("subdomain1.example.org", "A") + assert answer.answer + assert "1.1.1.1" in answer.to_text() + answer = query_dns("subdomain2.example.org", "A") + assert answer.answer + assert "1.1.1.1" in answer.to_text() + def test_overriding_with_dns_resolve_ip(self, dns_server, query_dns, monkeypatch): monkeypatch.setattr(config, "DNS_RESOLVE_IP", "2.2.2.2") @@ -158,11 +184,12 @@ def test_dns_server_soa_record_suffix_matching(self, dns_server, query_dns): assert "something.org." in answer.to_text() assert "noc.something.org." in answer.to_text() + @pytest.mark.skip(reason="failing as of 2025-12-17") def test_dns_server_subdomain_of_route(self, dns_server, query_dns): """Test querying a subdomain of a record entry without a wildcard""" # add ipv4 host - dns_server.add_host("example.org", TargetRecord("127.0.0.1", RecordType.A)) - answer = query_dns("nonexistent.example.org", "A") + dns_server.add_host("example.com", TargetRecord("127.0.0.1", RecordType.A)) + answer = query_dns("nonexistent.example.com", "A") assert not answer.answer # should still have authority section # TODO uncomment once it is clear why in CI the authority section is missing @@ -283,19 +310,19 @@ def test_dns_server_alias_lifecycle(self, dns_server, query_dns): """Test adding and deleting aliases""" dns_server.add_host("example.org", TargetRecord("122.122.122.122", RecordType.A)) dns_server.add_alias( - source_name="foo.something.org", + source_name="foo.example.org", record_type=RecordType.A, target=AliasTarget(target="example.org"), ) - answer = query_dns("foo.something.org", "A") + answer = query_dns("foo.example.org", "A") assert answer.answer assert "122.122.122.122" in answer.to_text() # delete alias and try again dns_server.delete_alias( - source_name="foo.something.org", record_type=RecordType.A, target=AliasTarget(target="") + source_name="foo.example.org", record_type=RecordType.A, target=AliasTarget(target="") ) - answer = query_dns("foo.something.org", "A") + answer = query_dns("foo.example.org", "A") assert not answer.answer # check if add_host is still available @@ -402,7 +429,7 @@ def test_delete_operations_of_nonexistent_entries(self, dns_server): with pytest.raises(ValueError): dns_server.delete_alias( - source_name="foo.something.org", + source_name="foo.example.org", record_type=RecordType.A, target=AliasTarget(target=""), ) diff --git a/tests/integration/docker_utils/conftest.py b/tests/integration/docker_utils/conftest.py index 39860824f8a58..a247666b56214 100644 --- a/tests/integration/docker_utils/conftest.py +++ b/tests/integration/docker_utils/conftest.py @@ -1,5 +1,4 @@ import os -from typing import Type import pytest @@ -22,7 +21,7 @@ def _check_skip(client: ContainerClient): ids=["CmdDockerClient", "SdkDockerClient"], scope="class", ) -def docker_client_class(request) -> Type[ContainerClient]: +def docker_client_class(request) -> type[ContainerClient]: return request.param diff --git a/tests/integration/docker_utils/test_docker.py b/tests/integration/docker_utils/test_docker.py index 4f62f0076bfce..45a754a87e682 100644 --- a/tests/integration/docker_utils/test_docker.py +++ b/tests/integration/docker_utils/test_docker.py @@ -6,7 +6,8 @@ import re import textwrap import time -from typing import Callable, NamedTuple, Type +from collections.abc import Callable +from typing import NamedTuple import pytest from docker.models.containers import Container @@ -19,6 +20,7 @@ from localstack.utils.container_utils.container_client import ( AccessDenied, ContainerClient, + ContainerConfiguration, ContainerException, DockerContainerStats, DockerContainerStatus, @@ -42,19 +44,22 @@ reserve_available_container_port, reserve_container_port, ) -from localstack.utils.net import Port, PortNotAvailableException, get_free_tcp_port +from localstack.utils.net import ( + Port, + PortNotAvailableException, + get_addressable_container_host, + get_free_tcp_port, +) from localstack.utils.strings import to_bytes from localstack.utils.sync import retry from localstack.utils.threads import FuncThread from tests.integration.docker_utils.conftest import is_podman_test, skip_for_podman -ContainerInfo = NamedTuple( - "ContainerInfo", - [ - ("container_id", str), - ("container_name", str), - ], -) + +class ContainerInfo(NamedTuple): + container_id: str + container_name: str + LOG = logging.getLogger(__name__) @@ -129,6 +134,84 @@ def _create_network(network_name: str): LOG.debug("Error while cleaning up network %s: %s", network, e) +@pytest.fixture +def authenticated_registry(docker_client: ContainerClient, create_container): + """ + Creates and starts an authenticated Docker registry for testing. + Returns a dict with registry details: port, username, password, auth_config, and cleanup function. + """ + registry_port = get_free_tcp_port() + registry_name = _random_container_name() + + username = "testuser" + password = "testpass" + # precomputed_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) + precomputed_hash = b"$2b$12$aR8RRkqWtpy.lZetR15tT.PjDmTsRX7p3fHNbOe0bPDV4xArNTVZG" + htpasswd_content = f"{username}:".encode() + precomputed_hash + b"\n" + + # Create and start registry container + ports = PortMappings() + ports.add(registry_port, 5000) + + htpasswd_content_str = htpasswd_content.decode("utf-8").strip() + registry_container = create_container( + "registry:2", + name=registry_name, + ports=ports, + env_vars={ + "REGISTRY_AUTH": "htpasswd", + "REGISTRY_AUTH_HTPASSWD_PATH": "/auth/htpasswd", + "REGISTRY_AUTH_HTPASSWD_REALM": "Registry Realm", + "REGISTRY_HTTP_SECRET": "localstack-registry-secret", + }, + command=[ + "sh", + "-c", + f"mkdir -p /auth && echo '{htpasswd_content_str}' > /auth/htpasswd && exec /entrypoint.sh /etc/docker/registry/config.yml", + ], + ) + docker_client.start_container(registry_container.container_id) + + registry_host = get_addressable_container_host() + + def _check_registry_auth(): + import requests + + s = requests.Session() + s.trust_env = False + url = f"http://{registry_host}:{registry_port}/v2/" + response = s.get( + url, + auth=(username, password), + timeout=2, + ) + if response.status_code == 200: + LOG.info("Registry authentication ready at %s", url) + return + raise Exception(f"Registry auth check returned status {response.status_code}") + + retry(_check_registry_auth, retries=20, sleep=0.5) + + yield { + "port": registry_port, + "host": registry_host, # For health checks from test container + "docker_host": "localhost", # For Docker daemon to push/pull (always localhost, Docker treats it as insecure) + "name": registry_name, + "container_id": registry_container.container_id, + "auth_config": { + "username": username, + "password": password, + }, + } + + # Cleanup + try: + docker_client.stop_container(registry_name) + docker_client.remove_container(registry_name) + except Exception: + LOG.warning("Failed to clean up registry container: %s", registry_name, exc_info=True) + + class TestDockerClient: def test_get_system_info(self, docker_client: ContainerClient): info = docker_client.get_system_info() @@ -365,7 +448,7 @@ def test_start_non_existing_container(self, docker_client: ContainerClient): with pytest.raises(NoSuchContainer): docker_client.start_container("this_container_does_not_exist") - def test_docker_not_available(self, docker_client_class: Type[ContainerClient], monkeypatch): + def test_docker_not_available(self, docker_client_class: type[ContainerClient], monkeypatch): monkeypatch.setattr(config, "DOCKER_CMD", "non-existing-binary") monkeypatch.setenv("DOCKER_HOST", "/var/run/docker.sock1") # initialize the client after mocking the environment @@ -827,6 +910,83 @@ def test_list_containers_filter_illegal_filter(self, docker_client: ContainerCli with pytest.raises(ContainerException): docker_client.list_containers(filter="illegalfilter=foobar") + def test_get_running_container_names_should_ignore_stopped_containers( + self, docker_client: ContainerClient, create_container + ): + cn1 = _random_container_name() + cn2 = _random_container_name() + cn3 = _random_container_name() + + create_container("alpine", name=cn1, command=["sleep", "30"]) + create_container("alpine", name=cn2, command=["sleep", "30"]) + create_container("alpine", name=cn3, command=["sleep", "30"]) + + docker_client.start_container(cn1) + docker_client.start_container(cn2) + + container_names = docker_client.get_running_container_names() + # We dont do equal comparison here to avoid brittle tests since there could be other containers. + assert 2 <= len(container_names) + assert all(x in container_names for x in [cn1, cn2]) + assert cn3 not in container_names + + def test_get_all_container_names_should_include_even_stopped_containers( + self, docker_client: ContainerClient, create_container + ): + cn1 = _random_container_name() + cn2 = _random_container_name() + cn3 = _random_container_name() + + create_container("alpine", name=cn1, command=["sleep", "30"]) + create_container("alpine", name=cn2, command=["sleep", "30"]) + create_container("alpine", name=cn3, command=["sleep", "30"]) + + docker_client.start_container(cn1) + docker_client.start_container(cn2) + + container_names = docker_client.get_all_container_names() + # We dont do equal comparison here to avoid brittle tests since there could be other containers. + assert 3 <= len(container_names) + assert all(x in container_names for x in [cn1, cn2, cn3]) + + def test_remove_container_should_work_when_container_is_running_and_checking_container_existence( + self, docker_client: ContainerClient, create_container + ): + cn1 = _random_container_name() + create_container("alpine", name=cn1, command=["sleep", "30"]) + + docker_client.start_container(cn1) + + docker_client.remove_container(cn1, check_existence=True) + + container_names = docker_client.get_all_container_names() + assert cn1 not in container_names + + def test_remove_container_should_work_when_container_is_stopped_and_checking_container_existence( + self, docker_client: ContainerClient, create_container + ): + cn1 = _random_container_name() + create_container("alpine", name=cn1, command=["sleep", "30"]) + docker_client.remove_container(cn1, check_existence=True) + + container_names = docker_client.get_all_container_names() + assert cn1 not in container_names + + def test_remove_container_should_throw_exception_when_container_doesnt_exist_and_not_forcing( + self, docker_client: ContainerClient + ): + with pytest.raises(NoSuchContainer): + docker_client.remove_container( + "mygiganonexistingcontainer", check_existence=False, force=False + ) + + def test_remove_container_should_work_even_when_container_doesnt_exist_because_of_forcing( + self, docker_client: ContainerClient + ): + docker_client.remove_container( + "mygiganonexistingcontainer", check_existence=False, force=True + ) + def test_list_containers_filter(self, docker_client: ContainerClient, create_container): name_prefix = "filter_tests_" cn1 = name_prefix + _random_container_name() @@ -1199,6 +1359,136 @@ def test_push_invalid_registry(self, docker_client: ContainerClient): finally: docker_client.remove_image(image_name) + @markers.skip_offline + def test_authenticated_pull_with_invalid_credentials( + self, docker_client: ContainerClient, authenticated_registry + ): + docker_host = authenticated_registry["docker_host"] + port = authenticated_registry["port"] + test_image = f"{docker_host}:{port}/test-image-{short_uid()}:latest" + + # Try to pull without credentials + with pytest.raises((NoSuchImage, ContainerException)): + docker_client.pull_image(test_image) + + wrong_auth = { + "username": authenticated_registry["auth_config"]["username"], + "password": "wrongpassword", + } + with pytest.raises(ContainerException): + docker_client.pull_image(test_image, auth_config=wrong_auth) + + @markers.skip_offline + def test_authenticated_push_and_pull( + self, docker_client: ContainerClient, authenticated_registry + ): + registry_port = authenticated_registry["port"] + docker_host = authenticated_registry["docker_host"] + auth_config = authenticated_registry["auth_config"] + test_image = f"{docker_host}:{registry_port}/test-hello-{short_uid()}:latest" + + try: + _pull_image_if_not_exists(docker_client, "hello-world") + docker_client.tag_image("hello-world", test_image) + + docker_client.push_image(test_image, auth_config=auth_config) + docker_client.remove_image(test_image) + + docker_client.pull_image(test_image, auth_config=auth_config) + + # Verify exists + image_info = docker_client.inspect_image(test_image, pull=False) + assert test_image in image_info.get("RepoTags", []) + + finally: + try: + docker_client.remove_image(test_image) + except Exception: + pass + + @markers.skip_offline + def test_authenticated_push_without_credentials_raises_error( + self, docker_client: ContainerClient, authenticated_registry + ): + registry_port = authenticated_registry["port"] + docker_host = authenticated_registry["docker_host"] + test_image = f"{docker_host}:{registry_port}/test-hello-{short_uid()}:latest" + + _pull_image_if_not_exists(docker_client, "hello-world") + docker_client.tag_image("hello-world", test_image) + + try: + with pytest.raises(AccessDenied): + docker_client.push_image(test_image) + + finally: + try: + docker_client.remove_image(test_image) + except Exception: + pass + + @markers.skip_offline + @pytest.mark.parametrize( + "method,use_config", + [ + ("create", False), + ("run", False), + ("create", True), + ("run", True), + ], + ids=[ + "create_container", + "run_container", + "create_container_from_config", + "run_container_from_config", + ], + ) + def test_container_operations_with_auth_config( + self, docker_client: ContainerClient, authenticated_registry, method: str, use_config: bool + ): + registry_port = authenticated_registry["port"] + docker_host = authenticated_registry["docker_host"] + auth_config = authenticated_registry["auth_config"] + test_image = f"{docker_host}:{registry_port}/test-hello-{short_uid()}:latest" + + try: + _pull_image_if_not_exists(docker_client, "hello-world") + docker_client.tag_image("hello-world", test_image) + docker_client.push_image(test_image, auth_config=auth_config) + docker_client.remove_image(test_image) + + # Execute the appropriate method + if use_config: + container_config = ContainerConfiguration( + image_name=test_image, + auth_config=auth_config, + ) + if method == "create": + container = docker_client.create_container_from_config(container_config) + output, _ = docker_client.start_container(container, attach=True) + else: # method == "run" + output, _ = docker_client.run_container_from_config(container_config) + else: + if method == "create": + container = docker_client.create_container( + image_name=test_image, + auth_config=auth_config, + ) + output, _ = docker_client.start_container(container, attach=True) + else: # method == "run" + output, _ = docker_client.run_container( + image_name=test_image, + auth_config=auth_config, + ) + + assert "hello" in output.decode(config.DEFAULT_ENCODING) + + finally: + try: + docker_client.remove_image(test_image) + except Exception: + pass + @markers.skip_offline def test_tag_image(self, docker_client: ContainerClient): if _is_podman_test() and isinstance(docker_client, SdkDockerClient): @@ -1276,6 +1566,57 @@ def test_build_image( assert "foo=bar" in result["Config"]["Env"] assert "45329/tcp" in result["Config"]["ExposedPorts"] + @markers.skip_offline + def test_remove_anonymous_volumes( + self, docker_client: ContainerClient, create_container, tmp_path, cleanups + ): + dockerfile_dir = tmp_path / "dockerfile" + tmp_file = short_uid() + ctx_dir = dockerfile_dir + dockerfile_path = os.path.join(dockerfile_dir, "Dockerfile") + + # Create a custom Dockerfile that creates an anonymous volume + dockerfile = f""" + FROM alpine + ADD {tmp_file} . + VOLUME [ "/tmp" ] + ENV foo=bar + EXPOSE 45329 + """ + save_file(dockerfile_path, dockerfile) + save_file(os.path.join(ctx_dir, tmp_file), "test content 123") + + dockerfile_ref = str(dockerfile_dir) + + # Using the SDK directly, as our abstraction doesn't directly expose volumes yet + docker_sdk = SdkDockerClient().docker_client + + image_name = f"img-{short_uid()}" + docker_client.build_image(dockerfile_path=dockerfile_ref, image_name=image_name) + cleanups.append(lambda: docker_client.remove_image(image_name, force=True)) + + container = create_container( + image_name, + command=["sh", "-c", "while true; do sleep 1; done"], + ) + + volumes = docker_client.inspect_container_volumes(container.container_id) + assert len(volumes) == 1, "We should have a single (anonymous) volume in our custom image" + anon_volume_name = [v.name for v in volumes][0] + + # Verify that the anonymous volume exists + current_volumes = docker_sdk.volumes.list() + current_volume_names = [v.name for v in current_volumes] + assert anon_volume_name in current_volume_names + + # Remove the container, and explicilty remove anonymous volumes + docker_client.remove_container(container_name=container.container_name, volumes=True) + + # Verify that the anonymous volume has been removed + current_volumes = docker_sdk.volumes.list() + current_volume_names = [v.name for v in current_volumes] + assert anon_volume_name not in current_volume_names + @markers.skip_offline def test_run_container_non_existent_image(self, docker_client: ContainerClient): try: @@ -2060,6 +2401,35 @@ def test_get_container_stats(self, docker_client, create_container): assert 0.0 <= stats["MemPerc"] <= 100.0 +class TestDockerCgroupLimits: + def test_run_container_with_memory_limit(self, docker_client, create_container): + container = create_container( + "alpine", + command=[ + "sh", + "-c", + "if [ -e /sys/fs/cgroup/cgroup.controllers ]; then cat /sys/fs/cgroup/memory.max; else cat /sys/fs/cgroup/memory/memory.limit_in_bytes; fi", + ], + mem_limit="128m", + ) + stdout, _ = docker_client.start_container(container.container_id, attach=True) + assert "134217728" in stdout.decode("utf-8") + + def test_run_container_with_cpu_shares(self, docker_client, create_container): + container = create_container( + "alpine", + command=[ + "sh", + "-c", + "if [ -e /sys/fs/cgroup/cgroup.controllers ]; then cat /sys/fs/cgroup/cpu.weight; else cat /sys/fs/cgroup/cpu,cpuacct/cpu.shares; fi", + ], + cpu_shares=256, + ) + stdout, _ = docker_client.start_container(container.container_id, attach=True) + stdout = stdout.decode("utf-8") + assert "35" in stdout or "256" in stdout + + def _pull_image_if_not_exists(docker_client: ContainerClient, image_name: str): if image_name not in docker_client.get_docker_image_names(): docker_client.pull_image(image_name) diff --git a/tests/integration/services/test_internal.py b/tests/integration/services/test_internal.py index c32d4d8676b3e..00f65aa429a76 100644 --- a/tests/integration/services/test_internal.py +++ b/tests/integration/services/test_internal.py @@ -60,4 +60,4 @@ def test_get(self): assert doc["session_id"] assert doc["machine_id"] assert doc["system"] - assert type(doc["is_license_activated"]) == bool + assert isinstance(doc["is_license_activated"], bool) diff --git a/tests/integration/test_catalog.py b/tests/integration/test_catalog.py new file mode 100644 index 0000000000000..4717dfc04647c --- /dev/null +++ b/tests/integration/test_catalog.py @@ -0,0 +1,212 @@ +import json + +import pytest +from botocore.exceptions import WaiterError + +from localstack import config +from localstack.services.cloudformation.engine.v2 import ( + change_set_resource_support_checker as support_checker_module, +) +from localstack.services.cloudformation.engine.v2.change_set_resource_support_checker import ( + ChangeSetResourceSupportChecker, +) +from localstack.testing.pytest import markers +from localstack.utils.catalog.catalog import ( + AwsServicesSupportStatus, + CatalogPlugin, + CfnResourceSupportStatus, +) +from localstack.utils.catalog.common import ( + AwsServicesSupportInLatest, + AwsServiceSupportAtRuntime, + CloudFormationResourcesSupportAtRuntime, + CloudFormationResourcesSupportInLatest, +) +from localstack.utils.strings import short_uid + +UNSUPPORTED_RESOURCE_CASES = [ + ( + "AWS::TestService::UnsupportedLatest", + CloudFormationResourcesSupportInLatest.NOT_SUPPORTED, + "testservice", + ), + ( + "AWS::RuntimeService::NotImplemented", + CloudFormationResourcesSupportAtRuntime.NOT_IMPLEMENTED, + "runtimeservice", + ), + ( + "AWS::LicenseRuntime::RequiresUpgrade", + AwsServiceSupportAtRuntime.AVAILABLE_WITH_LICENSE_UPGRADE, + "licenseruntime", + ), + ( + "AWS::RuntimeService::Missing", + AwsServiceSupportAtRuntime.NOT_IMPLEMENTED, + "runtimeservice", + ), + ( + "AWS::LatestService::RequiresUpgrade", + AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE, + "latestservice", + ), + ( + "AWS::LatestService::NotSupported", + AwsServicesSupportInLatest.NOT_SUPPORTED, + "latestservice", + ), +] + + +class _TestingCatalogPlugin(CatalogPlugin): + name = "testing-catalog" + + def __init__(self): + self._unsupported_resources = { + resource: status for resource, status, _ in UNSUPPORTED_RESOURCE_CASES + } + + def get_aws_service_status( + self, service_name: str, operation_name: str | None = None + ) -> AwsServicesSupportStatus | None: + return AwsServicesSupportInLatest.SUPPORTED + + def get_cloudformation_resource_status( + self, resource_name: str, service_name: str, is_pro_resource: bool = False + ) -> CfnResourceSupportStatus | AwsServicesSupportInLatest | None: + return self._unsupported_resources.get( + resource_name, CloudFormationResourcesSupportAtRuntime.AVAILABLE + ) + + +@pytest.fixture +def testing_catalog(monkeypatch): + plugin = _TestingCatalogPlugin() + monkeypatch.setattr(support_checker_module, "get_aws_catalog", lambda: plugin) + return plugin + + +@markers.aws.only_localstack +def test_ignore_unsupported_resources_toggle(testing_catalog, aws_client, monkeypatch, cleanups): + unsupported_resource = "AWS::LatestService::NotSupported" + + # template with one supported and one unsupported resource + bucket_name = f"cfn-toggle-{short_uid()}" + template_body = json.dumps( + { + "Resources": { + "SupportedBucket": { + "Type": "AWS::S3::Bucket", + "Properties": {"BucketName": bucket_name}, + }, + "Unsupported": {"Type": unsupported_resource}, + }, + } + ) + + # 1) ignore lists empty -> change set should fail + monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES", False) + monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_TYPE_CREATE", []) + stack_name_fail = f"stack-fail-{short_uid()}" + change_set_name_fail = f"cs-{short_uid()}" + response = aws_client.cloudformation.create_change_set( + StackName=stack_name_fail, + ChangeSetName=change_set_name_fail, + TemplateBody=template_body, + ChangeSetType="CREATE", + ) + cs_id_fail, stack_id_fail = response["Id"], response["StackId"] + + waiter = aws_client.cloudformation.get_waiter("change_set_create_complete") + with pytest.raises(WaiterError) as exc_info: + waiter.wait( + ChangeSetName=cs_id_fail, + ) + + assert exc_info.value.last_response["Status"] == "FAILED" + status_reason = exc_info.value.last_response["StatusReason"] + assert ChangeSetResourceSupportChecker.TITLE_MESSAGE in status_reason + assert unsupported_resource in status_reason + cleanups.append(lambda: aws_client.cloudformation.delete_change_set(ChangeSetName=cs_id_fail)) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id_fail)) + + # 2) add unsupported resource to create ignore list -> deployment succeeds and bucket is present + monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_TYPE_CREATE", [unsupported_resource]) + stack_name_ok = f"stack-ok-{short_uid()}" + change_set_name_ok = f"cs-{short_uid()}" + response = aws_client.cloudformation.create_change_set( + StackName=stack_name_ok, + ChangeSetName=change_set_name_ok, + TemplateBody=template_body, + ChangeSetType="CREATE", + ) + cs_id_ok, stack_id_ok = response["Id"], response["StackId"] + + waiter.wait( + ChangeSetName=cs_id_ok, + ) + aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id_ok) + aws_client.cloudformation.get_waiter("stack_create_complete").wait( + StackName=stack_name_ok, + ) + + buckets = aws_client.s3.list_buckets()["Buckets"] + assert any(b["Name"] == bucket_name for b in buckets) + + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id_ok)) + + +@markers.aws.only_localstack +@pytest.mark.parametrize( + "unsupported_resource, expected_service", + [(resource, expected_service) for resource, _, expected_service in UNSUPPORTED_RESOURCE_CASES], +) +def test_catalog_reports_unsupported_resources_in_stack_status( + testing_catalog, aws_client, unsupported_resource, expected_service, monkeypatch, cleanups +): + monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES", False) + template_body = json.dumps( + { + "Resources": {"Unsupported": {"Type": unsupported_resource}}, + } + ) + + stack_name = f"stack-{short_uid()}" + change_set_name = f"change-set-{short_uid()}" + + response = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + TemplateBody=template_body, + Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], + ChangeSetType="CREATE", + ) + + change_set_id = response["Id"] + stack_id = response["StackId"] + + waiter = aws_client.cloudformation.get_waiter("change_set_create_complete") + with pytest.raises(WaiterError) as exc_info: + waiter.wait( + ChangeSetName=change_set_id, + ) + assert exc_info.value.last_response["Status"] == "FAILED" + status_reason = exc_info.value.last_response["StatusReason"] + assert ChangeSetResourceSupportChecker.TITLE_MESSAGE in status_reason + assert unsupported_resource in status_reason + + with pytest.raises(WaiterError) as exc_info: + aws_client.cloudformation.get_waiter("stack_create_complete").wait( + StackName=stack_id, + ) + + stack_description = exc_info.value.last_response["Stacks"][0] + stack_status_reason = stack_description.get("StackStatusReason", "") + assert ChangeSetResourceSupportChecker.TITLE_MESSAGE in stack_status_reason + assert unsupported_resource in stack_status_reason + assert expected_service in stack_status_reason.lower() + + cleanups.append( + lambda: aws_client.cloudformation.delete_change_set(ChangeSetName=change_set_id) + ) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) diff --git a/tests/integration/test_config_service.py b/tests/integration/test_config_service.py index 03dad8d204de5..35792ac52eda9 100644 --- a/tests/integration/test_config_service.py +++ b/tests/integration/test_config_service.py @@ -33,7 +33,7 @@ def _create_config_recorder(iam_role_arn: str): def test_put_configuration_recorder( self, aws_client, create_role, create_configuration_recorder ): - iam_role_name = "role-{}".format(short_uid()) + iam_role_name = f"role-{short_uid()}" iam_role_arn = create_role( RoleName=iam_role_name, AssumeRolePolicyDocument=json.dumps(ASSUME_POLICY_DOCUMENT) )["Role"]["Arn"] @@ -57,7 +57,7 @@ def test_put_configuration_recorder( def test_put_delivery_channel( self, aws_client, s3_create_bucket, create_role, create_configuration_recorder ): - iam_role_name = "role-{}".format(short_uid()) + iam_role_name = f"role-{short_uid()}" iam_role_arn = create_role( RoleName=iam_role_name, AssumeRolePolicyDocument=json.dumps(ASSUME_POLICY_DOCUMENT) )["Role"]["Arn"] diff --git a/tests/performance/test_dynamodb_performance.py b/tests/performance/test_dynamodb_performance.py index a100ae20ec59b..d16fc53d48894 100644 --- a/tests/performance/test_dynamodb_performance.py +++ b/tests/performance/test_dynamodb_performance.py @@ -6,7 +6,7 @@ def connect(): - return boto3.client("dynamodb", endpoint_url="http://localhost:%s" % PORT_DYNAMODB) + return boto3.client("dynamodb", endpoint_url=f"http://localhost:{PORT_DYNAMODB}") def create(): @@ -25,7 +25,7 @@ def insert(count): for i in range(count): if i > 0 and i % 100 == 0: delta = time.time() - start - print("%s sec for %s items = %s req/sec" % (delta, i, i / delta)) + print(f"{delta} sec for {i} items = {i / delta} req/sec") client.put_item( TableName="customers", Item={ diff --git a/tests/performance/test_sqs_performance.py b/tests/performance/test_sqs_performance.py index 329b6ced480e1..05cc649161227 100644 --- a/tests/performance/test_sqs_performance.py +++ b/tests/performance/test_sqs_performance.py @@ -18,7 +18,7 @@ def print_duration(start, num_msgs, action): duration = datetime.now() - start duration = duration.total_seconds() req_sec = num_msgs / duration - print("%s %s messages in %s seconds (%s req/sec)" % (action, num_msgs, duration, req_sec)) + print(f"{action} {num_msgs} messages in {duration} seconds ({req_sec} req/sec)") def send_messages(): @@ -29,7 +29,7 @@ def send_messages(): ).sqs queue_url = sqs.create_queue(QueueName=QUEUE_NAME)["QueueUrl"] - print("Starting to send %s messages" % NUM_MESSAGES) + print(f"Starting to send {NUM_MESSAGES} messages") start = datetime.now() for i in range(1, NUM_MESSAGES + 1): sqs.send_message(QueueUrl=queue_url, MessageBody="test123") @@ -50,7 +50,7 @@ def receive_messages(): result = sqs.receive_message(QueueUrl=queue_url) messages.extend(result.get("Messages") or []) print_duration(start, len(messages), action="Received") - print("All %s messages received" % len(messages)) + print(f"All {len(messages)} messages received") def main(): diff --git a/tests/unit/aws/handlers/service.py b/tests/unit/aws/handlers/service.py deleted file mode 100644 index c6f039a29e5cd..0000000000000 --- a/tests/unit/aws/handlers/service.py +++ /dev/null @@ -1,145 +0,0 @@ -import pytest - -from localstack.aws.api import CommonServiceException, RequestContext -from localstack.aws.chain import HandlerChain -from localstack.aws.forwarder import create_aws_request_context -from localstack.aws.handlers.service import ServiceExceptionSerializer, ServiceResponseParser -from localstack.aws.protocol.serializer import create_serializer -from localstack.http import Request, Response - - -@pytest.fixture -def service_response_handler_chain() -> HandlerChain: - """Returns a dummy chain for testing.""" - return HandlerChain(response_handlers=[ServiceResponseParser()]) - - -class TestServiceResponseHandler: - def test_use_set_response(self, service_response_handler_chain): - context = create_aws_request_context("opensearch", "CreateDomain", {"DomainName": "foobar"}) - context.service_response = {"sure": "why not"} - - service_response_handler_chain.handle(context, Response(status=200)) - assert context.service_response == {"sure": "why not"} - - def test_parse_response(self, service_response_handler_chain): - context = create_aws_request_context("sqs", "CreateQueue", {"QueueName": "foobar"}) - backend_response = {"QueueUrl": "http://localhost:4566/000000000000/foobar"} - http_response = create_serializer(context.service).serialize_to_response( - backend_response, context.operation, context.request.headers, context.request_id - ) - - service_response_handler_chain.handle(context, http_response) - assert context.service_response == backend_response - - def test_parse_response_with_streaming_response(self, service_response_handler_chain): - context = create_aws_request_context("s3", "GetObject", {"Bucket": "foo", "Key": "bar.bin"}) - backend_response = {"Body": b"\x00\x01foo", "ContentType": "application/octet-stream"} - http_response = create_serializer(context.service).serialize_to_response( - backend_response, context.operation, context.request.headers, context.request_id - ) - - service_response_handler_chain.handle(context, http_response) - assert context.service_response["ContentLength"] == 5 - assert context.service_response["ContentType"] == "application/octet-stream" - assert context.service_response["Body"].read() == b"\x00\x01foo" - - def test_common_service_exception(self, service_response_handler_chain): - context = create_aws_request_context("opensearch", "CreateDomain", {"DomainName": "foobar"}) - context.service_exception = CommonServiceException( - "MyCommonException", "oh noes", status_code=409, sender_fault=True - ) - - service_response_handler_chain.handle(context, Response(status=409)) - assert context.service_exception.message == "oh noes" - assert context.service_exception.code == "MyCommonException" - assert context.service_exception.sender_fault - assert context.service_exception.status_code == 409 - - def test_service_exception(self, service_response_handler_chain): - from localstack.aws.api.opensearch import ResourceAlreadyExistsException - - context = create_aws_request_context("opensearch", "CreateDomain", {"DomainName": "foobar"}) - context.service_exception = ResourceAlreadyExistsException("oh noes") - - response = create_serializer(context.service).serialize_error_to_response( - context.service_exception, context.operation, context.request.headers - ) - - service_response_handler_chain.handle(context, response) - assert context.service_exception.message == "oh noes" - assert context.service_exception.code == "ResourceAlreadyExistsException" - assert not context.service_exception.sender_fault - assert context.service_exception.status_code == 409 - - def test_service_exception_with_code_from_spec(self, service_response_handler_chain): - from localstack.aws.api.sqs import QueueDoesNotExist - - context = create_aws_request_context( - "sqs", - "SendMessage", - {"QueueUrl": "http://localhost:4566/000000000000/foobared", "MessageBody": "foo"}, - ) - context.service_exception = QueueDoesNotExist() - - response = create_serializer(context.service).serialize_error_to_response( - context.service_exception, context.operation, context.request.headers - ) - - service_response_handler_chain.handle(context, response) - - assert context.service_exception.message == "" - assert context.service_exception.code == "AWS.SimpleQueueService.NonExistentQueue" - assert context.service_exception.sender_fault - assert context.service_exception.status_code == 400 - - def test_sets_exception_from_error_response(self, service_response_handler_chain): - context = create_aws_request_context( - "opensearch", "DescribeDomain", {"DomainName": "foobar"} - ) - response = Response( - b'{"__type": "ResourceNotFoundException", "message": "Domain not found: foobar"}', - 409, - ) - service_response_handler_chain.handle(context, response) - - assert context.service_exception.message == "Domain not found: foobar" - assert context.service_exception.code == "ResourceNotFoundException" - assert not context.service_exception.sender_fault - assert context.service_exception.status_code == 409 - - assert context.service_response is None - - def test_nothing_set_does_nothing(self, service_response_handler_chain): - context = RequestContext(request=Request("GET", "/_localstack/health")) - - service_response_handler_chain.handle(context, Response("ok", 200)) - - assert context.service_exception is None - assert context.service_response is None - - def test_invalid_exception_does_nothing(self, service_response_handler_chain): - context = create_aws_request_context( - "opensearch", "DescribeDomain", {"DomainName": "foobar"} - ) - context.service_exception = ValueError() - service_response_handler_chain.handle(context, Response(status=500)) - - assert context.service_response is None - assert isinstance(context.service_exception, ValueError) - - @pytest.mark.parametrize( - "message, output", [("", "not yet implemented or pro feature"), ("Ups!", "Ups!")] - ) - def test_not_implemented_error(self, service_response_handler_chain, message, output): - context = create_aws_request_context( - "opensearch", "DescribeDomain", {"DomainName": "foobar"} - ) - not_implemented_exception = NotImplementedError(message) - - ServiceExceptionSerializer().create_exception_response(not_implemented_exception, context) - - assert output in context.service_exception.message - assert context.service_exception.code == "InternalFailure" - assert not context.service_exception.sender_fault - assert context.service_exception.status_code == 501 diff --git a/tests/unit/aws/handlers/analytics.py b/tests/unit/aws/handlers/test_analytics.py similarity index 87% rename from tests/unit/aws/handlers/analytics.py rename to tests/unit/aws/handlers/test_analytics.py index 26e52c02a26dc..aefa0691e319a 100644 --- a/tests/unit/aws/handlers/analytics.py +++ b/tests/unit/aws/handlers/test_analytics.py @@ -23,13 +23,13 @@ def test_starts_aggregator_after_first_call(self): counter = ServiceRequestCounter(service_request_aggregator=aggregator) aggregator.start.assert_not_called() - context = create_aws_request_context("s3", "ListBuckets") + context = create_aws_request_context("s3", "ListBuckets", "rest-xml") chain = HandlerChain([counter]) chain.handle(context, Response()) aggregator.start.assert_called_once() - context = create_aws_request_context("s3", "ListBuckets") + context = create_aws_request_context("s3", "ListBuckets", "rest-xml") chain = HandlerChain([counter]) chain.handle(context, Response()) @@ -53,7 +53,7 @@ def test_ignores_requests_when_analytics_is_disabled(self, monkeypatch): chain = HandlerChain([counter]) chain.handle( - create_aws_request_context("s3", "ListBuckets"), + create_aws_request_context("s3", "ListBuckets", "rest-xml"), Response(), ) @@ -66,12 +66,12 @@ def test_calls_aggregator(self): chain = HandlerChain([counter]) chain.handle( - create_aws_request_context("s3", "ListBuckets"), + create_aws_request_context("s3", "ListBuckets", "rest-xml"), Response(), ) counter( chain, - create_aws_request_context("s3", "HeadBucket", {"Bucket": "foobar"}), + create_aws_request_context("s3", "HeadBucket", "rest-xml", {"Bucket": "foobar"}), Response(), ) @@ -88,7 +88,9 @@ def test_parses_error_correctly(self): chain = HandlerChain([counter]) chain.handle( - create_aws_request_context("opensearch", "DescribeDomain", {"DomainName": "foobar"}), + create_aws_request_context( + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} + ), Response( b'{"__type": "ResourceNotFoundException", "message": "Domain not found: foobar"}', 404, @@ -111,7 +113,9 @@ def test_invalid_error_behaves_like_botocore(self): chain = HandlerChain([counter]) chain.handle( - create_aws_request_context("opensearch", "DescribeDomain", {"DomainName": "foobar"}), + create_aws_request_context( + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} + ), Response(b'{"__type": "ResourceN}', 404), ) diff --git a/tests/unit/aws/handlers/openapi.py b/tests/unit/aws/handlers/test_openapi.py similarity index 100% rename from tests/unit/aws/handlers/openapi.py rename to tests/unit/aws/handlers/test_openapi.py diff --git a/tests/unit/aws/handlers/response_enrichment.py b/tests/unit/aws/handlers/test_response_enrichment.py similarity index 92% rename from tests/unit/aws/handlers/response_enrichment.py rename to tests/unit/aws/handlers/test_response_enrichment.py index 4f5f3d7e8ab6d..f130d4d3b18bb 100644 --- a/tests/unit/aws/handlers/response_enrichment.py +++ b/tests/unit/aws/handlers/test_response_enrichment.py @@ -14,7 +14,7 @@ def response_handler_chain() -> HandlerChain: class TestResponseMetadataEnricher: def test_adds_header_to_successful_response(self, response_handler_chain): - context = create_aws_request_context("s3", "ListBuckets") + context = create_aws_request_context("s3", "ListBuckets", "rest-xml") response = Response("success", 200) response_handler_chain.handle(context, response) @@ -23,7 +23,7 @@ def test_adds_header_to_successful_response(self, response_handler_chain): def test_adds_header_to_error_response(self, response_handler_chain): context = create_aws_request_context( - "opensearch", "DescribeDomain", {"DomainName": "foobar"} + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} ) response = Response(b'{"__type": "ResourceNotFoundException"}', 409) diff --git a/tests/unit/aws/handlers/test_service.py b/tests/unit/aws/handlers/test_service.py new file mode 100644 index 0000000000000..22dd95e9f94eb --- /dev/null +++ b/tests/unit/aws/handlers/test_service.py @@ -0,0 +1,295 @@ +from collections.abc import Iterable + +import pytest +from moto.core.exceptions import RESTError, ServiceException +from moto.ec2.exceptions import EC2_ERROR_RESPONSE + +from localstack.aws.api import CommonServiceException, RequestContext +from localstack.aws.chain import HandlerChain +from localstack.aws.forwarder import create_aws_request_context +from localstack.aws.handlers.service import ServiceExceptionSerializer, ServiceResponseParser +from localstack.aws.protocol.serializer import create_serializer +from localstack.http import Request, Response +from localstack.utils.catalog.common import AwsServiceSupportAtRuntime + + +@pytest.fixture +def service_response_handler_chain() -> HandlerChain: + """Returns a dummy chain for testing.""" + return HandlerChain(response_handlers=[ServiceResponseParser()]) + + +class TestServiceResponseHandler: + def test_use_set_response(self, service_response_handler_chain): + context = create_aws_request_context( + "opensearch", "CreateDomain", "rest-json", {"DomainName": "foobar"} + ) + context.service_response = {"sure": "why not"} + + service_response_handler_chain.handle(context, Response(status=200)) + assert context.service_response == {"sure": "why not"} + + def test_parse_response(self, service_response_handler_chain): + context = create_aws_request_context("sqs", "CreateQueue", "json", {"QueueName": "foobar"}) + backend_response = {"QueueUrl": "http://localhost:4566/000000000000/foobar"} + http_response = create_serializer(context.service).serialize_to_response( + backend_response, context.operation, context.request.headers, context.request_id + ) + + service_response_handler_chain.handle(context, http_response) + assert context.service_response == backend_response + + def test_parse_response_with_streaming_response(self, service_response_handler_chain): + context = create_aws_request_context( + "s3", "GetObject", "rest-xml", {"Bucket": "foo", "Key": "bar.bin"} + ) + backend_response = {"Body": b"\x00\x01foo", "ContentType": "application/octet-stream"} + http_response = create_serializer(context.service).serialize_to_response( + backend_response, context.operation, context.request.headers, context.request_id + ) + + service_response_handler_chain.handle(context, http_response) + assert context.service_response["ContentLength"] == 5 + assert context.service_response["ContentType"] == "application/octet-stream" + assert context.service_response["Body"].read() == b"\x00\x01foo" + + def test_common_service_exception(self, service_response_handler_chain): + context = create_aws_request_context( + "opensearch", "CreateDomain", "rest-json", {"DomainName": "foobar"} + ) + context.service_exception = CommonServiceException( + "MyCommonException", "oh noes", status_code=409, sender_fault=True + ) + + service_response_handler_chain.handle(context, Response(status=409)) + assert context.service_exception.message == "oh noes" + assert context.service_exception.code == "MyCommonException" + assert context.service_exception.sender_fault + assert context.service_exception.status_code == 409 + + def test_service_exception(self, service_response_handler_chain): + from localstack.aws.api.opensearch import ResourceAlreadyExistsException + + context = create_aws_request_context( + "opensearch", "CreateDomain", "rest-json", {"DomainName": "foobar"} + ) + context.service_exception = ResourceAlreadyExistsException("oh noes") + + response = create_serializer(context.service).serialize_error_to_response( + context.service_exception, + context.operation, + context.request.headers, + context.request_id, + ) + + service_response_handler_chain.handle(context, response) + assert context.service_exception.message == "oh noes" + assert context.service_exception.code == "ResourceAlreadyExistsException" + assert not context.service_exception.sender_fault + assert context.service_exception.status_code == 409 + + def test_service_exception_with_code_from_spec(self, service_response_handler_chain): + from localstack.aws.api.sqs import QueueDoesNotExist + + context = create_aws_request_context( + "sqs", + "SendMessage", + "json", + {"QueueUrl": "http://localhost:4566/000000000000/foobared", "MessageBody": "foo"}, + ) + context.service_exception = QueueDoesNotExist() + + response = create_serializer(context.service).serialize_error_to_response( + context.service_exception, + context.operation, + context.request.headers, + context.request_id, + ) + + service_response_handler_chain.handle(context, response) + + assert context.service_exception.message == "" + assert context.service_exception.code == "QueueDoesNotExist" + assert not context.service_exception.sender_fault + assert context.service_exception.status_code == 400 + + def test_sets_exception_from_error_response(self, service_response_handler_chain): + context = create_aws_request_context( + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} + ) + response = Response( + b'{"__type": "ResourceNotFoundException", "message": "Domain not found: foobar"}', + 409, + ) + service_response_handler_chain.handle(context, response) + + assert context.service_exception.message == "Domain not found: foobar" + assert context.service_exception.code == "ResourceNotFoundException" + assert not context.service_exception.sender_fault + assert context.service_exception.status_code == 409 + + assert context.service_response is None + + def test_nothing_set_does_nothing(self, service_response_handler_chain): + context = RequestContext(request=Request("GET", "/_localstack/health")) + + service_response_handler_chain.handle(context, Response("ok", 200)) + + assert context.service_exception is None + assert context.service_response is None + + def test_invalid_exception_does_nothing(self, service_response_handler_chain): + context = create_aws_request_context( + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} + ) + context.service_exception = ValueError() + service_response_handler_chain.handle(context, Response(status=500)) + + assert context.service_response is None + assert isinstance(context.service_exception, ValueError) + + +class TestServiceExceptionSerializer: + @pytest.mark.parametrize( + "catalog_status, expected_message", + [ + ( + AwsServiceSupportAtRuntime.AVAILABLE_WITH_LICENSE_UPGRADE, + "is not included within your LocalStack license", + ), + ( + None, + "The API for service opensearch is either not included in your current license plan or has not yet been emulated by LocalStack.", + ), + ], + ) + def test_not_implemented_error_uses_catalog_when_message_is_empty( + self, catalog_status, expected_message, aws_catalog_mock + ): + catalog = aws_catalog_mock("localstack.aws.handlers.service.get_aws_catalog") + catalog.get_aws_service_status.return_value = catalog_status + context = create_aws_request_context( + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} + ) + not_implemented_exception = NotImplementedError("") + + ServiceExceptionSerializer().create_exception_response(not_implemented_exception, context) + + assert expected_message in context.service_exception.message + assert context.service_exception.code == "InternalFailure" + assert not context.service_exception.sender_fault + assert context.service_exception.status_code == 501 + + def test_not_implemented_error_with_custom_message(self): + context = create_aws_request_context( + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} + ) + custom_message = "Ups!" + not_implemented_exception = NotImplementedError(custom_message) + + ServiceExceptionSerializer().create_exception_response(not_implemented_exception, context) + + assert context.service_exception.message == custom_message + assert context.service_exception.code == "InternalFailure" + assert not context.service_exception.sender_fault + assert context.service_exception.status_code == 501 + + def test_internal_error_propagate_traceback(self, service_response_handler_chain): + raised_exception: Exception | None = None + + def raise_internal_error_handler(*args, **kwargs): + raise ValueError("error") + + def capture_original_exception_handler( + chain: HandlerChain, + exception: Exception, + context: RequestContext, + response: Response, + ): + nonlocal raised_exception + raised_exception = exception + return + + err_chain = HandlerChain( + request_handlers=[raise_internal_error_handler], + exception_handlers=[ + capture_original_exception_handler, + ServiceExceptionSerializer(), + ], + ) + + err_context = create_aws_request_context( + "opensearch", "DescribeDomain", "rest-json", {"DomainName": "foobar"} + ) + err_chain.handle(err_context, Response()) + + assert err_context.service_exception.code == "InternalError" + assert err_context.service_exception.__traceback__ + assert err_context.service_exception.__traceback__ == raised_exception.__traceback__ + assert err_context.service_exception.status_code == 500 + + def test_moto_service_exception_is_translated(self, service_response_handler_chain): + # Redefine exception here but use the right base exc. This is to improve tolerance against Moto refactors. + class MessageRejectedError(ServiceException): + code = "MessageRejected" + + # Ensure ServiceExceptions are translated + context = create_aws_request_context( + "ses", + "SendRawEmail", + "query", + { + "Destinations": ["invalid@example.com"], + "RawMessage": { + "Data": b"From: origin@example.com\nTo: destination@example.com\nSubject: sub\n\nbody\n\n" + }, + }, + ) + msg = "Did not have authority to send email" + moto_exception = MessageRejectedError(msg) + + ServiceExceptionSerializer().create_exception_response(moto_exception, context) + + assert msg in context.service_exception.message + assert context.service_exception.code == "MessageRejected" + assert not context.service_exception.sender_fault + assert context.service_exception.status_code == 400 + + def test_moto_rest_error_is_translated(self, service_response_handler_chain): + # Redefine exception here but use the right base exc. This is to improve tolerance against Moto refactors. + class InvalidKeyPairNameError(RESTError): + code = 400 + request_id_tag_name = "RequestID" + extended_templates = {"custom_response": EC2_ERROR_RESPONSE} + env = RESTError.extended_environment(extended_templates) + + def __init__(self, key: Iterable[str]): + super().__init__( + "InvalidKeyPair.NotFound", + f"The keypair '{key}' does not exist.", + template="custom_response", + ) + + # Ensure RESTErrors are translated + context = create_aws_request_context( + "ec2", + "RunInstances", + "ec2", + { + "ImageId": "ami-deadc0de", + "InstanceType": "t3.nano", + "KeyName": "some-key-pair", + "MaxCount": 1, + "MinCount": 1, + }, + ) + moto_exception = InvalidKeyPairNameError({"some-key-pair"}) + + ServiceExceptionSerializer().create_exception_response(moto_exception, context) + + assert ( + "The keypair '{'some-key-pair'}' does not exist." in context.service_exception.message + ) + assert context.service_exception.code == "InvalidKeyPair.NotFound" + assert not context.service_exception.sender_fault + assert context.service_exception.status_code == 400 diff --git a/tests/unit/aws/protocol/test_parser.py b/tests/unit/aws/protocol/test_parser.py index f647daed9d4be..4b686dc636e8c 100644 --- a/tests/unit/aws/protocol/test_parser.py +++ b/tests/unit/aws/protocol/test_parser.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from io import BytesIO from urllib.parse import unquote, urlencode, urlsplit @@ -7,6 +7,7 @@ from botocore.serialize import create_serializer from localstack.aws.protocol.parser import ( + CBORRequestParser, OperationNotFoundParserError, ProtocolParserError, QueryRequestParser, @@ -339,12 +340,19 @@ def test_query_parser_pass_str_as_int_raises_error(): def _botocore_parser_integration_test( - service: str, action: str, headers: dict = None, expected: dict = None, **kwargs + *, + service: str, + action: str, + protocol: str = None, + headers: dict = None, + expected: dict = None, + **kwargs, ): # Load the appropriate service service = load_service(service) + service_protocol = protocol or service.protocol # Use the serializer from botocore to serialize the request params - serializer = create_serializer(service.protocol) + serializer = create_serializer(service_protocol) operation_model = service.operation_model(action) serialized_request = serializer.serialize_to_request(kwargs, operation_model) @@ -365,12 +373,12 @@ def _botocore_parser_integration_test( # use custom headers (if provided), or headers from serialized request as default headers = serialized_request.get("headers") if headers is None else headers - if service.protocol in ["query", "ec2"]: + if service_protocol in ["query", "ec2"]: # Serialize the body as query parameter body = urlencode(serialized_request["body"]) # Use our parser to parse the serialized body - parser = create_parser(service) + parser = create_parser(service, service_protocol) parsed_operation_model, parsed_request = parser.parse( HttpRequest( method=serialized_request.get("method") or "GET", @@ -450,8 +458,8 @@ def test_query_parser_no_input_shape_autoscaling_with_botocore(): def test_query_parser_iot_with_botocore(): """Test if timestamp for 'rest-json' is parsed correctly""" - start = datetime(2023, 1, 10, tzinfo=timezone.utc) - end = datetime(2023, 1, 11, tzinfo=timezone.utc) + start = datetime(2023, 1, 10, tzinfo=UTC) + end = datetime(2023, 1, 11, tzinfo=UTC) _botocore_parser_integration_test( service="iot", action="ListAuditMitigationActionsTasks", @@ -626,7 +634,10 @@ def test_json_parser_cognito_with_botocore(): ) -def test_json_cbor_blob_parsing(): +# TODO: once Kinesis supports multi protocols (json/cbor), update this test to select the protocol instead when +# creating the parser +@pytest.mark.parametrize("parser_factory", [CBORRequestParser, create_parser]) +def test_json_cbor_blob_parsing(parser_factory): serialized_request = { "url_path": "/", "query_string": "", @@ -655,7 +666,7 @@ def test_json_cbor_blob_parsing(): # Load the appropriate service service = load_service("kinesis") operation_model = service.operation_model("PutRecord") - parser = create_parser(service) + parser = parser_factory(service) parsed_operation_model, parsed_request = parser.parse( HttpRequest( method=serialized_request.get("method") or "GET", @@ -678,7 +689,10 @@ def test_json_cbor_blob_parsing(): assert parsed_request["PartitionKey"] == "partitionkey" -def test_json_cbor_blob_parsing_w_timestamp(snapshot): +# TODO: once Kinesis supports multi protocols (json/cbor), update this test to select the protocol instead when +# creating the parser +@pytest.mark.parametrize("parser_factory", [CBORRequestParser, create_parser]) +def test_json_cbor_blob_parsing_w_timestamp(snapshot, parser_factory): serialized_request = { "url_path": "/", "query_string": "", @@ -707,7 +721,7 @@ def test_json_cbor_blob_parsing_w_timestamp(snapshot): # Load the appropriate service service = load_service("kinesis") operation_model = service.operation_model("SubscribeToShard") - parser = create_parser(service) + parser = parser_factory(service) parsed_operation_model, parsed_request = parser.parse( HttpRequest( method=serialized_request.get("method"), @@ -721,6 +735,7 @@ def test_json_cbor_blob_parsing_w_timestamp(snapshot): # Check if the determined operation_model is correct assert parsed_operation_model == operation_model + assert isinstance(parsed_request["StartingPosition"]["Timestamp"], datetime) snapshot.match("parsed_request", parsed_request) @@ -730,7 +745,7 @@ def test_restjson_parser_xray_with_botocore(): action="PutTelemetryRecords", TelemetryRecords=[ { - "Timestamp": datetime(2015, 1, 1), + "Timestamp": datetime(2015, 1, 1, tzinfo=UTC), "SegmentsReceivedCount": 123, "SegmentsSentCount": 123, "SegmentsSpilloverCount": 123, @@ -838,7 +853,7 @@ def test_restjson_opensearch_with_botocore(): "RollbackOnDisable": "DEFAULT_ROLLBACK", "MaintenanceSchedules": [ { - "StartAt": datetime(2015, 1, 1), + "StartAt": datetime(2015, 1, 1, tzinfo=UTC), "Duration": {"Value": 123, "Unit": "HOURS"}, "CronExpressionForRecurrence": "string", }, @@ -895,9 +910,9 @@ def test_ec2_parser_ec2_with_botocore(): def test_restjson_parser_path_params_with_slashes(): _botocore_parser_integration_test( - service="qldb", + service="appconfig", action="ListTagsForResource", - ResourceArn="arn:aws:qldb:eu-central-1:000000000000:ledger/c-c67c827a", + ResourceArn="arn:aws:appconfig:us-east-1:000000000000:application/application-1/environment/env-1", ) @@ -1213,6 +1228,29 @@ def test_restxml_header_list_parsing(): ) +def test_restxml_header_list_parsing_with_multiple_header_values(): + """ + Tests that list attributes that are encoded into headers are parsed correctly. + However, our serializer will by default encode the header list by concatenating it in a comma-separated string + Some different serializers, like the Java SDK or Go, will add a header entry for each value. + See https://github.com/aws/aws-sdk-go-v2/issues/1620 for example + It will send: + X-Amz-Object-Attributes: Checksum + X-Amz-Object-Attributes: ObjectParts + Instead of: + X-Amz-Object-Attributes: Checksum,ObjectParts + """ + _botocore_parser_integration_test( + service="s3", + action="GetObjectAttributes", + Bucket="test-bucket", + Key="/test-key", + ObjectAttributes=["ObjectSize", "StorageClass"], + # override serialized headers with a manual list + headers={"X-Amz-Object-Attributes": ["ObjectSize", "StorageClass"]}, + ) + + def test_restxml_header_optional_list_parsing(): """Tests that non-existing header list attributes are working correctly.""" # OptionalObjectAttributes (the "x-amz-optional-object-attributes") in ListObjectsV2Request is optional @@ -1229,7 +1267,7 @@ def test_restxml_header_date_parsing(): ContentLength=3, Body=BytesIO(b"foo"), Metadata={}, - Expires=datetime(2015, 1, 1, 0, 0, tzinfo=timezone.utc), + Expires=datetime(2015, 1, 1, 0, 0, tzinfo=UTC), ) @@ -1409,3 +1447,128 @@ def test_restxml_ignores_get_body(): assert "Bucket" in parsed_request assert parsed_request["Bucket"] == "test-bucket" assert parsed_request["Key"] == "foo" + + +def test_smithy_rpc_v2_cbor(): + # we are using a service that LocalStack does not implement yet because it implements `smithy-rpc-v2-cbor` + # we can replace this service by CloudWatch once it has support in Botocore + # example taken from: + # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/arc-region-switch/client/create_plan.html + + _botocore_parser_integration_test( + service="arc-region-switch", + protocol="smithy-rpc-v2-cbor", + action="CreatePlan", + description="string", + workflows=[ + { + "steps": [ + { + "name": "string", + "description": "string", + "executionBlockConfiguration": { + "customActionLambdaConfig": { + "timeoutMinutes": 123, + "lambdas": [ + { + "crossAccountRole": "string", + "externalId": "string", + "arn": "string", + }, + ], + "retryIntervalMinutes": 10.0, + "regionToRun": "activatingRegion", + "ungraceful": {"behavior": "skip"}, + }, + }, + "executionBlockType": "CustomActionLambda", + }, + ], + "workflowTargetAction": "activate", + "workflowTargetRegion": "string", + "workflowDescription": "string", + }, + ], + executionRole="string", + recoveryTimeObjectiveMinutes=123, + associatedAlarms={ + "string": { + "crossAccountRole": "string", + "externalId": "string", + "resourceIdentifier": "string", + "alarmType": "applicationHealth", + } + }, + triggers=[ + { + "description": "string", + "targetRegion": "string", + "action": "activate", + "conditions": [ + { + "associatedAlarmName": "string", + "condition": "red", + }, + ], + "minDelayMinutesBetweenExecutions": 123, + }, + ], + name="string", + regions=[ + "region1", + "region2", + ], + recoveryApproach="activeActive", + primaryRegion="string", + tags={"string": "string"}, + ) + + +def test_rpc_v2_cbor_timestamp_parsing(): + # This is a real request from the Java SDK v2 + # It does not encode Timestamps like Botocore: it encodes them as Double of Length 8 (botocore uses Integer) + request = HttpRequest( + method="POST", + path="/v1/service/GraniteServiceVersion20100801/operation/PutMetricData", + body=b"\xbfiNamespacelSITE/TRAFFICjMetricData\x81\xbfjMetricNamemPAGES_VISITEDjDimensions\x81\xbfdNamelUNIQUE_PAGESeValuedURLS\xffiTimestamp\xc1\xfbA\xdaP\xaf+\x88\xa3\xd7eValue\xfb?\xf3\xbfg\xf4\xdb\xdf\x8fdUnitdNone\xff\xff", + ) + parser = create_parser(load_service("cloudwatch"), protocol="smithy-rpc-v2-cbor") + parsed_operation_model, parsed_request = parser.parse(request) + timestamp = parsed_request["MetricData"][0]["Timestamp"] + assert isinstance(timestamp, datetime) + assert timestamp.microsecond == 135000 + assert timestamp.second == 38 + assert timestamp.minute == 22 + assert timestamp.tzinfo == UTC + + +@pytest.mark.parametrize("protocol", ("json", "smithy-rpc-v2-cbor")) +def test_protocol_selection(protocol): + # we are using a service that LocalStack does not implement yet because it implements `smithy-rpc-v2-cbor` + # we can replace this service by CloudWatch once it has support in Botocore + + _botocore_parser_integration_test( + service="arc-region-switch", + protocol=protocol, + action="TagResource", + arn="string", + tags={"string": "string"}, + ) + + +def test_rpcv2_operation_detection_with_prefix(): + """ + Every request for the rpcv2Cbor protocol MUST be sent to a URL with the following form: + {prefix?}/service/{serviceName}/operation/{operationName} + The Smithy RPCv2 CBOR protocol will only use the last four segments of the URL when routing requests. + For example, a service could use a v1 prefix in the URL path, which would not affect the operation a request + is routed to: `v1/service/FooService/operation/BarOperation` + """ + request = HttpRequest( + method="POST", + path="/v1/service/ArcRegionSwitch/operation/TagResource", + body=b"\xa2carnfstringdtags\xa1fstringfstring", + ) + parser = create_parser(load_service("arc-region-switch")) + parsed_operation_model, parsed_request = parser.parse(request) + assert parsed_operation_model.name == "TagResource" diff --git a/tests/unit/aws/protocol/test_parser.snapshot.json b/tests/unit/aws/protocol/test_parser.snapshot.json index bf3fab6bc744b..404ffca79e03f 100644 --- a/tests/unit/aws/protocol/test_parser.snapshot.json +++ b/tests/unit/aws/protocol/test_parser.snapshot.json @@ -1,12 +1,25 @@ { - "tests/unit/aws/protocol/test_parser.py::test_json_cbor_blob_parsing_w_timestamp": { - "recorded-date": "21-06-2024, 13:58:29", + "tests/unit/aws/protocol/test_parser.py::test_json_cbor_blob_parsing_w_timestamp[CBORRequestParser]": { + "recorded-date": "17-09-2025, 21:46:34", "recorded-content": { "parsed_request": { "ConsumerARN": "", "ShardId": "", "StartingPosition": { - "Timestamp": "2024-06-21 08:54:08.123000", + "Timestamp": "2024-06-21 08:54:08.123000+00:00", + "Type": "AT_TIMESTAMP" + } + } + } + }, + "tests/unit/aws/protocol/test_parser.py::test_json_cbor_blob_parsing_w_timestamp[create_parser]": { + "recorded-date": "17-09-2025, 21:46:34", + "recorded-content": { + "parsed_request": { + "ConsumerARN": "", + "ShardId": "", + "StartingPosition": { + "Timestamp": "2024-06-21 08:54:08.123000+00:00", "Type": "AT_TIMESTAMP" } } diff --git a/tests/unit/aws/protocol/test_parser.validation.json b/tests/unit/aws/protocol/test_parser.validation.json deleted file mode 100644 index d667ef29c28c0..0000000000000 --- a/tests/unit/aws/protocol/test_parser.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/unit/aws/protocol/test_parser.py::test_json_cbor_blob_parsing_w_timestamp": { - "last_validated_date": "2024-06-21T13:58:29+00:00" - } -} diff --git a/tests/unit/aws/protocol/test_serializer.py b/tests/unit/aws/protocol/test_serializer.py index 156abc589644e..6821796927b25 100644 --- a/tests/unit/aws/protocol/test_serializer.py +++ b/tests/unit/aws/protocol/test_serializer.py @@ -2,9 +2,10 @@ import io import json import re +from collections.abc import Iterator from datetime import datetime from io import BytesIO -from typing import Any, Dict, Iterator, List, Optional +from typing import Any from xml.etree import ElementTree import pytest @@ -37,6 +38,7 @@ ) from localstack.aws.api.sts import Credentials, GetSessionTokenResponse from localstack.aws.protocol.serializer import ( + CBORResponseSerializer, ProtocolSerializerError, QueryResponseSerializer, UnknownSerializerError, @@ -44,7 +46,7 @@ create_serializer, ) from localstack.aws.spec import load_service -from localstack.constants import APPLICATION_AMZ_CBOR_1_1 +from localstack.constants import APPLICATION_AMZ_CBOR_1_1, APPLICATION_CBOR from localstack.http import Request, Response from localstack.utils.common import to_str from localstack.utils.strings import long_uid @@ -53,17 +55,19 @@ def _botocore_serializer_integration_test( + *, service: str, action: str, response: dict, - status_code=200, - expected_response_content: dict = None, + status_code: int = 200, + expected_response_content: dict | None = None, + protocol: str | None = None, ) -> dict: """ Performs an integration test for the serializer using botocore as parser. It executes the following steps: - Load the given service (f.e. "sqs") - - Serialize the response with the appropriate serializer from the AWS Serivce Framework + - Serialize the response with the appropriate serializer from the AWS Service Framework - Parse the serialized response using the botocore parser - Checks if the metadata is correct (status code, requestID,...) - Checks if the parsed response content is equal to the input to the serializer @@ -74,14 +78,17 @@ def _botocore_serializer_integration_test( :param status_code: Optional - expected status code of the response - defaults to 200 :param expected_response_content: Optional - if the input data ("response") differs from the actually expected data (because f.e. it contains None values) + :param: protocol: Optional: to specify which protocol to use for the service. If not provided, + fallback to the service's default protocol :return: boto-parsed serialized response """ # Load the appropriate service service = load_service(service) + service_protocol = protocol or service.protocol # Use our serializer to serialize the response - response_serializer = create_serializer(service) + response_serializer = create_serializer(service, protocol=service_protocol) # The serializer changes the incoming dict, therefore copy it before passing it to the serializer response_to_parse = copy.deepcopy(response) serialized_response = response_serializer.serialize_to_response( @@ -89,9 +96,12 @@ def _botocore_serializer_integration_test( ) # Use the parser from botocore to parse the serialized response - response_parser = create_parser(service.protocol) + response_parser = create_parser(service_protocol) + # Properly use HeadersDict from botocore to properly parse headers + response_dict = serialized_response.to_readonly_response_dict() + response_dict["headers"] = HeadersDict(response_dict.get("headers", {})) parsed_response = response_parser.parse( - serialized_response.to_readonly_response_dict(), + response_dict, service.operation_model(action).output_shape, ) @@ -119,9 +129,10 @@ def _botocore_error_serializer_integration_test( exception: ServiceException, code: str, status_code: int, - message: Optional[str], + message: str | None, is_sender_fault: bool = False, - **additional_error_fields: Dict[str, Any], + protocol: str | None = None, + **additional_error_fields: Any, ) -> dict: """ Performs an integration test for the error serialization using botocore as parser. @@ -146,11 +157,12 @@ def _botocore_error_serializer_integration_test( # Load the appropriate service service = load_service(service_model_name) + service_protocol = protocol or service.protocol # Use our serializer to serialize the response - response_serializer = create_serializer(service) + response_serializer = create_serializer(service, service_protocol) serialized_response = response_serializer.serialize_error_to_response( - exception, service.operation_model(action), None, long_uid() + exception, service.operation_model(action), {}, long_uid() ) # Use the parser from botocore to parse the serialized response @@ -160,7 +172,7 @@ def _botocore_error_serializer_integration_test( # f.e. needed for x-amzn-errortype response_dict["headers"] = HeadersDict(response_dict["headers"]) - response_parser: ResponseParser = create_parser(service.protocol) + response_parser: ResponseParser = create_parser(service_protocol) parsed_response = response_parser.parse( response_dict, service.operation_model(action).output_shape, @@ -185,6 +197,8 @@ def _botocore_error_serializer_integration_test( type = parsed_response["Error"].get("Type") if is_sender_fault: assert type == "Sender" + elif service_protocol == "smithy-rpc-v2-cbor" and service.is_query_compatible: + assert type == "Receiver" else: assert type is None if additional_error_fields: @@ -195,7 +209,7 @@ def _botocore_error_serializer_integration_test( def _botocore_event_streaming_test( - service: str, action: str, response: dict, response_root_tag: str, expected_events: List[dict] + service: str, action: str, response: dict, response_root_tag: str, expected_events: list[dict] ): """ Tests the serialization of event streaming responses using botocore. @@ -247,6 +261,10 @@ def _botocore_event_streaming_test( assert actual_events == expected_events +def _cbor_serializer_factory(service): + return CBORResponseSerializer() + + def test_rest_xml_serializer_cloudfront_with_botocore(): parameters = { "TestResult": { @@ -271,7 +289,9 @@ def test_rest_xml_serializer_cloudfront_with_botocore(): "FunctionOutput": "string", } } - _botocore_serializer_integration_test("cloudfront", "TestFunction", parameters) + _botocore_serializer_integration_test( + service="cloudfront", action="TestFunction", response=parameters + ) def test_rest_xml_serializer_route53_with_botocore(): @@ -284,7 +304,9 @@ def test_rest_xml_serializer_route53_with_botocore(): }, "DelegationSet": {"NameServers": ["dns.localhost.localstack.cloud"]}, } - _botocore_serializer_integration_test("route53", "CreateHostedZone", parameters, 201) + _botocore_serializer_integration_test( + service="route53", action="CreateHostedZone", response=parameters, status_code=201 + ) def test_rest_xml_serializer_s3_with_botocore(): @@ -316,7 +338,9 @@ def test_rest_xml_serializer_s3_with_botocore(): }, } } - _botocore_serializer_integration_test("s3", "GetBucketAnalyticsConfiguration", parameters) + _botocore_serializer_integration_test( + service="s3", action="GetBucketAnalyticsConfiguration", response=parameters + ) def test_rest_xml_serializer_s3_2_with_botocore(): @@ -356,7 +380,7 @@ def test_rest_xml_serializer_s3_2_with_botocore(): "ObjectLockLegalHoldStatus": "ON", "StatusCode": 200, } - _botocore_serializer_integration_test("s3", "GetObject", parameters) + _botocore_serializer_integration_test(service="s3", action="GetObject", response=parameters) def test_query_serializer_cloudformation_with_botocore(): @@ -386,7 +410,9 @@ def test_query_serializer_cloudformation_with_botocore(): "Timestamp": datetime(2015, 1, 1, 23, 59, 59, 6000, tzinfo=tzutc()), } } - _botocore_serializer_integration_test("cloudformation", "DetectStackResourceDrift", parameters) + _botocore_serializer_integration_test( + service="cloudformation", action="DetectStackResourceDrift", response=parameters + ) def test_query_serializer_redshift_with_botocore(): @@ -411,11 +437,13 @@ def test_query_serializer_redshift_with_botocore(): }, ], } - _botocore_serializer_integration_test("redshift", "DescribeClusterDbRevisions", parameters) + _botocore_serializer_integration_test( + service="redshift", action="DescribeClusterDbRevisions", response=parameters + ) def test_query_serializer_sqs_empty_return_shape_with_botocore(): - _botocore_serializer_integration_test("sqs", "SetQueueAttributes", {}) + _botocore_serializer_integration_test(service="sqs", action="SetQueueAttributes", response={}) def test_query_serializer_sqs_flattened_list_with_botocore(): @@ -425,7 +453,7 @@ def test_query_serializer_sqs_flattened_list_with_botocore(): "http://localhost:4566/000000000000/myqueue2", ] } - _botocore_serializer_integration_test("sqs", "ListQueues", response) + _botocore_serializer_integration_test(service="sqs", action="ListQueues", response=response) def test_query_serializer_sqs_flattened_map_with_botocore(): @@ -435,7 +463,9 @@ def test_query_serializer_sqs_flattened_map_with_botocore(): "DelaySeconds": "0", } } - _botocore_serializer_integration_test("sqs", "GetQueueAttributes", response) + _botocore_serializer_integration_test( + service="sqs", action="GetQueueAttributes", response=response + ) def test_query_serializer_sqs_flattened_list_map_with_botocore(): @@ -455,7 +485,7 @@ def test_query_serializer_sqs_flattened_list_map_with_botocore(): } ] } - _botocore_serializer_integration_test("sqs", "ReceiveMessage", response) + _botocore_serializer_integration_test(service="sqs", action="ReceiveMessage", response=response) def test_query_serializer_sqs_none_value_in_map(): @@ -472,7 +502,13 @@ def test_query_serializer_sqs_none_value_in_map(): } expected_response = copy.deepcopy(response) del expected_response["Messages"][0]["Attributes"] - _botocore_serializer_integration_test("sqs", "ReceiveMessage", response, 200, expected_response) + _botocore_serializer_integration_test( + service="sqs", + action="ReceiveMessage", + response=response, + status_code=200, + expected_response_content=expected_response, + ) def test_query_protocol_error_serialization(): @@ -627,8 +663,8 @@ class NoSuchKey(ServiceException): code: str = "NoSuchKey" sender_fault: bool = False status_code: int = 404 - DeleteMarker: Optional[bool] - VersionId: Optional[str] + DeleteMarker: bool | None + VersionId: str | None exception = NoSuchKey( "You shall not access this API! Sincerely, your friendly neighbourhood firefighter.", @@ -731,6 +767,96 @@ def test_json_protocol_error_serialization_with_shaped_default_members_on_root() assert "message" not in parsed_body +def test_json_protocol_error_serialization_empty_message(): + # if the exception message is not passed when instantiating the exception, the message attribute will be set to + # an empty string "". This is not serialized if the message field is not a required member + exception = TransactionCanceledException() + + response = _botocore_error_serializer_integration_test( + "dynamodb", + "ExecuteTransaction", + exception, + "TransactionCanceledException", + 400, + "", + ) + assert "message" not in response + + +@pytest.mark.parametrize("empty_value", ("", None)) +def test_json_protocol_error_serialization_with_empty_non_required_members(empty_value): + class _ResourceNotFoundException(ServiceException): + code: str = "ResourceNotFoundException" + sender_fault: bool = True + status_code: int = 404 + ResourceType: str | None + ResourceId: str | None + + exception = _ResourceNotFoundException("Not Found", ResourceType="") + + response = _botocore_error_serializer_integration_test( + "arc-region-switch", + "ApprovePlanExecutionStep", + exception, + "ResourceNotFoundException", + 404, + "Not Found", + protocol="json", + ) + assert "ResourceType" not in response + + +def test_json_protocol_error_serialization_falsy_non_required_members(): + # if the exception message is not passed, an empty message will be passed as an empty string `""` + exception = TransactionCanceledException("Exception message!", CancellationReasons=[]) + + response = _botocore_error_serializer_integration_test( + "dynamodb", + "ExecuteTransaction", + exception, + "TransactionCanceledException", + 400, + "Exception message!", + Message="Exception message!", + ) + assert "CancellationReasons" not in response + + +@pytest.mark.parametrize("empty_value", ("", None)) +def test_json_protocol_error_serialization_with_empty_required_members(empty_value): + class _ResourceNotFoundException(ServiceException): + code: str = "ResourceNotFoundException" + sender_fault: bool = False + status_code: int = 404 + resourceId: str + resourceType: str + + resource_type = "test" + exception = _ResourceNotFoundException( + "Exception message!", + resourceType=resource_type, + resourceId=empty_value, + ) + expected_exception_values = { + "resourceType": resource_type, + } + # if the value is None, it is not serialized, even if required + # but if it is an empty string, it will be + if empty_value is not None: + expected_exception_values["resourceId"] = "" + + response = _botocore_error_serializer_integration_test( + "verifiedpermissions", + "IsAuthorizedWithToken", + exception, + "ResourceNotFoundException", + 404, + "Exception message!", + **expected_exception_values, + ) + assert "" not in response + + def test_rest_json_protocol_error_serialization_with_additional_members(): class NotFoundException(ServiceException): code: str = "NotFoundException" @@ -842,6 +968,102 @@ class InvalidObjectState(ServiceException): ) +def test_rpc_v2_cbor_protocol_error_serialization(): + class _ResourceNotFoundException(ServiceException): + code: str = "ResourceNotFoundException" + sender_fault: bool = True + status_code: int = 404 + + exception = _ResourceNotFoundException("Not Found!") + + _botocore_error_serializer_integration_test( + "arc-region-switch", + "ListPlans", + exception, + "ResourceNotFoundException", + 404, + "Not Found!", + ) + + +def test_rpc_v2_cbor_protocol_custom_error_serialization(): + # CBOR needs a shape for the error, and we have to implement a custom way to serialize user defined exception + exception = CommonServiceException( + "UserDefinedException", "Parameter x was invalid!", sender_fault=True + ) + _botocore_error_serializer_integration_test( + "cloudwatch", + "SetAlarmState", + exception, + "UserDefinedException", + 400, + "Parameter x was invalid!", + protocol="smithy-rpc-v2-cbor", + is_sender_fault=True, + ) + + +def test_rpc_v2_cbor_protocol_error_serialization_default_headers(): + class _ResourceNotFoundException(ServiceException): + code: str = "ResourceNotFoundException" + sender_fault: bool = True + status_code: int = 404 + + exception = _ResourceNotFoundException("Not Found!") + service = load_service("arc-region-switch") + response_serializer = create_serializer(service) + serialized_response = response_serializer.serialize_error_to_response( + exception, + service.operation_model("ListPlans"), + {}, + long_uid(), + ) + assert serialized_response.headers["Content-Type"] == APPLICATION_CBOR + assert serialized_response.headers["Smithy-Protocol"] == "rpc-v2-cbor" + + +@pytest.mark.parametrize("empty_value", ("", None)) +def test_rpc_v2_cbor_protocol_error_serialization_with_empty_required_members(empty_value): + class AccessDeniedException(ServiceException): + code: str = "AccessDeniedException" + sender_fault: bool = True + status_code: int = 403 + + exception = AccessDeniedException("") + + _botocore_error_serializer_integration_test( + "arc-region-switch", + "ApprovePlanExecutionStep", + exception, + "AccessDeniedException", + 403, + "", + ) + + +@pytest.mark.parametrize("empty_value", ("", None)) +def test_rpc_v2_cbor_protocol_error_serialization_with_empty_non_required_members(empty_value): + class _ResourceNotFoundException(ServiceException): + code: str = "ResourceNotFoundException" + sender_fault: bool = True + status_code: int = 404 + ResourceType: str | None + ResourceId: str | None + + exception = _ResourceNotFoundException("Not Found", ResourceType="") + + response = _botocore_error_serializer_integration_test( + "arc-region-switch", + "ApprovePlanExecutionStep", + exception, + "ResourceNotFoundException", + 404, + "Not Found", + protocol="smithy-rpc-v2-cbor", + ) + assert "ResourceType" not in response + + def test_json_protocol_content_type_1_0(): """AppRunner defines the jsonVersion 1.0, therefore the Content-Type needs to be application/x-amz-json-1.0.""" service = load_service("apprunner") @@ -969,7 +1191,9 @@ def test_json_serializer_cognito_with_botocore(): }, } } - _botocore_serializer_integration_test("cognito-idp", "DescribeUserPool", parameters) + _botocore_serializer_integration_test( + service="cognito-idp", action="DescribeUserPool", response=parameters + ) def test_json_serializer_date_serialization_with_botocore(): @@ -978,7 +1202,9 @@ def test_json_serializer_date_serialization_with_botocore(): "LastModifiedDate": datetime(2022, 2, 8, 9, 17, 40, 122939, tzinfo=tzlocal()), } } - _botocore_serializer_integration_test("cognito-idp", "DescribeUserPool", parameters) + _botocore_serializer_integration_test( + service="cognito-idp", action="DescribeUserPool", response=parameters + ) def test_restjson_protocol_error_serialization(): @@ -1051,7 +1277,9 @@ def test_restjson_serializer_xray_with_botocore(): } } - _botocore_serializer_integration_test("xray", "UpdateSamplingRule", parameters) + _botocore_serializer_integration_test( + service="xray", action="UpdateSamplingRule", response=parameters + ) def test_restjson_header_target_serialization(): @@ -1087,9 +1315,9 @@ def test_restjson_header_target_serialization(): } result = _botocore_serializer_integration_test( - "glacier", - "InitiateJob", - response, + service="glacier", + action="InitiateJob", + response=response, status_code=202, ) @@ -1118,7 +1346,10 @@ def test_restjson_headers_target_serialization(): # skipping assert here, because the response will contain all HTTP headers (given the nature of "ResponseHeaders" # attribute). result = _botocore_serializer_integration_test( - "dataexchange", "SendApiAsset", response, expected_response_content=_skip_assert + service="dataexchange", + action="SendApiAsset", + response=response, + expected_response_content=_skip_assert, ) assert result["Body"] == "hello" @@ -1134,9 +1365,9 @@ def test_restjson_headers_target_serialization(): def test_restjson_statuscode_target_serialization(): _botocore_serializer_integration_test( - "lambda", - "Invoke", - { + service="lambda", + action="Invoke", + response={ "StatusCode": 203, "LogResult": "Log Message!", "ExecutedVersion": "Latest", @@ -1178,9 +1409,9 @@ def test_restjson_payload_serialization(): } result = _botocore_serializer_integration_test( - "appconfig", - "GetConfiguration", - response, + service="appconfig", + action="GetConfiguration", + response=response, status_code=200, ) headers = result["ResponseMetadata"]["HTTPHeaders"] @@ -1202,7 +1433,11 @@ def test_restjson_none_serialization(): "DeadLetterConfig": {}, } _botocore_serializer_integration_test( - "lambda", "CreateFunction", parameters, status_code=201, expected_response_content=expected + service="lambda", + action="CreateFunction", + response=parameters, + status_code=201, + expected_response_content=expected, ) exception = CommonServiceException("CodeVerificationFailedException", None) _botocore_error_serializer_integration_test( @@ -1218,18 +1453,27 @@ def test_restjson_none_serialization(): def test_restxml_none_serialization(): # Structure = None _botocore_serializer_integration_test( - "route53", "ListHostedZonesByName", {}, expected_response_content={} + service="route53", + action="ListHostedZonesByName", + response={}, + expected_response_content={}, ) # Structure Value = None parameters = {"HostedZones": None} _botocore_serializer_integration_test( - "route53", "ListHostedZonesByName", parameters, expected_response_content={} + service="route53", + action="ListHostedZonesByName", + response=parameters, + expected_response_content={}, ) # List Value = None parameters = {"HostedZones": [None]} expected = {"HostedZones": []} _botocore_serializer_integration_test( - "route53", "ListHostedZonesByName", parameters, expected_response_content=expected + service="route53", + action="ListHostedZonesByName", + response=parameters, + expected_response_content=expected, ) # Exception without a message exception = CommonServiceException("NoSuchKeySigningKey", None) @@ -1250,7 +1494,9 @@ def test_restjson_int_header_serialization(): "NextPollConfigurationToken": "abcdefg", "NextPollIntervalInSeconds": 42, } - _botocore_serializer_integration_test("appconfigdata", "GetLatestConfiguration", response) + _botocore_serializer_integration_test( + service="appconfigdata", action="GetLatestConfiguration", response=response + ) def test_ec2_serializer_ec2_with_botocore(): @@ -1285,11 +1531,13 @@ def test_ec2_serializer_ec2_with_botocore(): } } - _botocore_serializer_integration_test("ec2", "CreateInstanceEventWindow", parameters) + _botocore_serializer_integration_test( + service="ec2", action="CreateInstanceEventWindow", response=parameters + ) def test_ec2_serializer_ec2_with_empty_response(): - _botocore_serializer_integration_test("ec2", "CreateTags", {}) + _botocore_serializer_integration_test(service="ec2", action="CreateTags", response={}) def test_ec2_protocol_custom_error_serialization(): @@ -1338,15 +1586,17 @@ def test_restxml_s3_errors_have_error_root_element(): def test_restxml_without_output_shape(): - _botocore_serializer_integration_test("cloudfront", "DeleteDistribution", {}, status_code=204) + _botocore_serializer_integration_test( + service="cloudfront", action="DeleteDistribution", response={}, status_code=204 + ) def test_restxml_header_location(): """Tests fields with the location trait "header" for rest-xml.""" _botocore_serializer_integration_test( - "cloudfront", - "CreateCloudFrontOriginAccessIdentity", - { + service="cloudfront", + action="CreateCloudFrontOriginAccessIdentity", + response={ "Location": "location-header-field", "ETag": "location-etag-field", "CloudFrontOriginAccessIdentity": {}, @@ -1362,15 +1612,15 @@ def test_restxml_header_location(): "Metadata": {"string": "string"}, "StatusCode": 200, } - _botocore_serializer_integration_test("s3", "GetObject", parameters) + _botocore_serializer_integration_test(service="s3", action="GetObject", response=parameters) def test_restxml_headers_location(): """Tests fields with the location trait "headers" for rest-xml.""" _botocore_serializer_integration_test( - "s3", - "HeadObject", - { + service="s3", + action="HeadObject", + response={ "DeleteMarker": False, "Metadata": {"headers_key1": "headers_value1", "headers_key2": "headers_value2"}, "ContentType": "application/octet-stream", @@ -1384,16 +1634,18 @@ def test_restxml_headers_location(): def test_restjson_header_location(): """Tests fields with the location trait "header" for rest-xml.""" _botocore_serializer_integration_test( - "ebs", "GetSnapshotBlock", {"BlockData": "binary-data", "DataLength": 15} + service="ebs", + action="GetSnapshotBlock", + response={"BlockData": "binary-data", "DataLength": 15}, ) def test_restjson_headers_location(): """Tests fields with the location trait "headers" for rest-json.""" response = _botocore_serializer_integration_test( - "dataexchange", - "SendApiAsset", - { + service="dataexchange", + action="SendApiAsset", + response={ "ResponseHeaders": {"headers_key1": "headers_value1", "headers_key2": "headers_value2"}, }, expected_response_content=_skip_assert, @@ -1408,6 +1660,125 @@ def test_restjson_headers_location(): assert "headers_value2" == response["ResponseHeaders"]["headers_key2"] +def test_rpc_v2_cbor_serializer_arc_region_switch_with_botocore(): + # we are using a service that LocalStack does not implement yet because it implements `smithy-rpc-v2-cbor` + # we can replace this service by CloudWatch once it has support in Botocore + # example taken from: + # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/arc-region-switch/client/list_plans.html + parameters = { + "plans": [ + { + "arn": "string", + "owner": "string", + "name": "string", + "regions": [ + "string", + ], + "recoveryApproach": "activeActive", + "primaryRegion": "string", + "version": "string", + # validated with AWS, it returns millisecond precision + "updatedAt": datetime(2015, 1, 1, 23, 59, 59, microsecond=262000, tzinfo=tzlocal()), + "description": "string", + "executionRole": "string", + "activePlanExecution": "string", + "recoveryTimeObjectiveMinutes": 123, + }, + ], + "nextToken": "string", + } + + _botocore_serializer_integration_test( + service="arc-region-switch", + action="ListPlans", + response=parameters, + protocol="smithy-rpc-v2-cbor", + ) + + +def test_rpc_v2_undefined_map_and_list_serialization(): + # they are two types of map/list in CBOR, indefinite length types and "defined" ones: + # You'd use \xbf to indicate a map with indefinite length, then \xff to indicate the end of the map. + # You can also use \xa4 to indicate a map with exactly 4 things in it, so \xff is not required at the end. + # The Smithy specs are asking to follow the CBOR specs and use defined length structures, but AWS is returning + # undefined one with `\xbf` and `\xff` + service = load_service("arc-region-switch") + response_serializer = create_serializer(service, protocol="smithy-rpc-v2-cbor") + response_data = { + "plans": [ + { + "arn": "arn:aws:arc-region-switch::671107678412:plan/TestPlan:a1b61f", + "owner": "671107678412", + "name": "TestPlan", + "regions": ["us-east-1", "us-east-2"], + "recoveryApproach": "activePassive", + "primaryRegion": "us-east-1", + "version": "1", + "executionRole": "arn:aws:iam::671107678412:role/testswitch", + } + ] + } + + result: Response = response_serializer.serialize_to_response( + response_data, service.operation_model("ListPlans"), {}, long_uid() + ) + assert result is not None + assert result.content_type == "application/cbor" + raw_response_body = result.data + # this has been validated with AWS, AWS uses undefined length CBOR map and list + assert raw_response_body[:10] == b"\xbfeplans\x9f\xbfc" + assert raw_response_body[-3:] == b"\xff\xff\xff" + + +def test_rpc_v2_timestamp_serialization(): + service = load_service("arc-region-switch") + response_serializer = create_serializer(service, protocol="smithy-rpc-v2-cbor") + response_data = { + "plans": [ + { + "arn": "arn:aws:arc-region-switch::671107678412:plan/TestPlan:a1b61f", + "owner": "671107678412", + "name": "TestPlan", + "regions": ["us-east-1", "us-east-2"], + "recoveryApproach": "activePassive", + "primaryRegion": "us-east-1", + "version": "1", + "updatedAt": datetime(2025, 9, 16, 14, 54, 5, 262000, tzinfo=tzutc()), + "executionRole": "arn:aws:iam::671107678412:role/testswitch", + } + ] + } + + result: Response = response_serializer.serialize_to_response( + response_data, service.operation_model("ListPlans"), {}, long_uid() + ) + raw_response_body = result.data + # we also validate its timestamp serialization (with double) + timestamp_key_index = raw_response_body.find(b"updatedAt") + timestamp_value_index = timestamp_key_index + len(b"updatedAt") + # this has been validated with AWS as well, it encodes the timestamp as a double of length 8 + assert ( + raw_response_body[timestamp_value_index : timestamp_value_index + 8] + == b"\xc1\xfbA\xda2^\x83P" + ) + + +@pytest.mark.parametrize("protocol", ("json", "smithy-rpc-v2-cbor")) +def test_protocol_selection(protocol): + # we are using a service that LocalStack does not implement yet because it implements `smithy-rpc-v2-cbor` + # we can replace this service by CloudWatch once it has support in Botocore + parameters = { + "resourceTags": {"string": "string"}, + } + + _botocore_serializer_integration_test( + service="arc-region-switch", + protocol=protocol, + action="ListTagsForResource", + response=parameters, + ) + + def _iterable_to_stream(iterable, buffer_size=io.DEFAULT_BUFFER_SIZE): """ Concerts a given iterable (generator) to a stream for testing purposes. @@ -1501,9 +1872,9 @@ def test_all_non_existing_key(): """Tests the different protocols to allow non-existing keys in structures / dicts.""" # query _botocore_serializer_integration_test( - "cloudformation", - "DetectStackResourceDrift", - { + service="cloudformation", + action="DetectStackResourceDrift", + response={ "StackResourceDrift": { "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/MyStack/d0a825a0-e4cd-xmpl-b9fb-061c69e99204", "unknown": {"foo": "bar"}, @@ -1517,9 +1888,9 @@ def test_all_non_existing_key(): ) # ec2 _botocore_serializer_integration_test( - "ec2", - "CreateInstanceEventWindow", - { + service="ec2", + action="CreateInstanceEventWindow", + response={ "InstanceEventWindow": { "InstanceEventWindowId": "string", "unknown": {"foo": "bar"}, @@ -1534,9 +1905,9 @@ def test_all_non_existing_key(): ) # json _botocore_serializer_integration_test( - "cognito-idp", - "DescribeUserPool", - { + service="cognito-idp", + action="DescribeUserPool", + response={ "UserPool": { "Id": "string", "Unknown": "Ignored", @@ -1550,9 +1921,9 @@ def test_all_non_existing_key(): ) # rest-json _botocore_serializer_integration_test( - "xray", - "UpdateSamplingRule", - { + service="xray", + action="UpdateSamplingRule", + response={ "SamplingRuleRecord": { "SamplingRule": { "ResourceARN": "123456789001234567890", @@ -1570,9 +1941,9 @@ def test_all_non_existing_key(): ) # rest-xml _botocore_serializer_integration_test( - "cloudfront", - "TestFunction", - { + service="cloudfront", + action="TestFunction", + response={ "TestResult": { "FunctionErrorMessage": "string", }, @@ -1661,7 +2032,7 @@ def __eq__(self, other): if isinstance(other, ResponseStream): return other.response.data == self.read() - return super(ComparableBytesIO, self).__eq__(other) + return super().__eq__(other) class ComparableBytesList(list): @@ -1673,11 +2044,11 @@ def __eq__(self, other): if isinstance(other, str): return b"".join(self) == other.encode("utf-8") - return super(ComparableBytesList, self).__eq__(other) + return super().__eq__(other) class ComparableBytesIterator(Iterator[bytes]): - def __init__(self, bytes_list: List[bytes]): + def __init__(self, bytes_list: list[bytes]): self.gen = iter(bytes_list) self.value = b"".join(bytes_list) @@ -1694,7 +2065,7 @@ def __eq__(self, other): if isinstance(other, ResponseStream): return other.response.data == self.value - return super(ComparableBytesIterator, self).__eq__(other) + return super().__eq__(other) @pytest.mark.parametrize( @@ -1720,7 +2091,7 @@ def test_restxml_streaming_payload(payload): "Metadata": {}, "StatusCode": 200, } - _botocore_serializer_integration_test("s3", "GetObject", parameters) + _botocore_serializer_integration_test(service="s3", action="GetObject", response=parameters) @pytest.mark.parametrize( @@ -1736,9 +2107,9 @@ def test_restxml_streaming_payload(payload): def test_restjson_streaming_payload(payload): """See docs for ``test_restxml_streaming_payload``.""" _botocore_serializer_integration_test( - "lambda", - "Invoke", - { + service="lambda", + action="Invoke", + response={ "StatusCode": 200, "Payload": payload, }, @@ -1788,8 +2159,8 @@ def test_restjson_streaming_payload(payload): ) def test_accept_header_detection( service: str, - accept_header: Optional[str], - content_type_header: Optional[str], + accept_header: str | None, + content_type_header: str | None, expected_mime_type: str, ): service_model = load_service(service) @@ -1843,9 +2214,11 @@ def test_query_protocol_json_serialization(headers_dict): "headers_dict", [{"Content-Type": "application/cbor"}, {"Accept": "application/cbor"}], ) -def test_json_protocol_cbor_serialization(headers_dict): +@pytest.mark.parametrize("serializer_factory", [create_serializer, _cbor_serializer_factory]) +def test_json_protocol_cbor_serialization(headers_dict, serializer_factory): + # TODO: test datetime serialization format for Kinesis manually service = load_service("kinesis") - response_serializer = create_serializer(service) + response_serializer = serializer_factory(service) headers = Headers(headers_dict) response_data = GetRecordsOutput( Records=[ diff --git a/tests/unit/aws/test_connect.py b/tests/unit/aws/test_connect.py index 85962c4b41964..447f37be471d1 100644 --- a/tests/unit/aws/test_connect.py +++ b/tests/unit/aws/test_connect.py @@ -198,22 +198,18 @@ def test_external_aws_client_credentials_loaded_from_env_if_set_to_none( "iam", "iot", "iot_data", - "iotanalytics", "iotwireless", "kafka", "kinesis", "kms", "lakeformation", "logs", - "mediastore", "mq", "mwaa", "neptune", "opensearch", "organizations", "pi", - "qldb", - "qldb_session", "rds", "rds_data", "redshift", diff --git a/tests/unit/aws/test_mocking.py b/tests/unit/aws/test_mocking.py index 87db62b2967c6..4fa97cbdc717c 100644 --- a/tests/unit/aws/test_mocking.py +++ b/tests/unit/aws/test_mocking.py @@ -55,7 +55,7 @@ def test_get_mocking_skeleton(): skeleton = get_mocking_skeleton("sqs") request = {"QueueName": "my-queue-name"} - context = create_aws_request_context("sqs", "CreateQueue", request) + context = create_aws_request_context("sqs", "CreateQueue", "json", request) response = skeleton.invoke(context) # just a smoke test assert b"QueueUrl" in response.data diff --git a/tests/unit/aws/test_service_router.py b/tests/unit/aws/test_service_router.py index cba7fd1f6e95a..5dece947a4782 100644 --- a/tests/unit/aws/test_service_router.py +++ b/tests/unit/aws/test_service_router.py @@ -1,105 +1,142 @@ from datetime import datetime -from typing import Any, Dict, Tuple +from typing import Any from urllib.parse import urlsplit import pytest from botocore.awsrequest import AWSRequest, create_request_object from botocore.config import Config from botocore.model import OperationModel, ServiceModel, Shape, StructureShape +from botocore.serialize import create_serializer -from localstack.aws.protocol.service_router import determine_aws_service_model +from localstack.aws.protocol.service_router import ( + ProtocolError, + determine_aws_protocol, + determine_aws_service_model, + match_available_protocols, +) from localstack.aws.spec import get_service_catalog from localstack.http import Request from localstack.utils.run import to_str -def _collect_operations() -> Tuple[ServiceModel, OperationModel]: +@pytest.fixture +def get_aws_client_for_protocol(aws_client_factory, monkeypatch): + def _create_client(service_name: str, protocol: str): + client = aws_client_factory.get_client( + service_name, + config=Config( + connect_timeout=1_000, + read_timeout=1_000, + retries={"total_max_attempts": 1}, + parameter_validation=False, + user_agent="aws-cli/1.33.7", + ), + ) + # TODO: we should come up with a better way to create a protocol-aware client + protocol_serializer = create_serializer(protocol_name=protocol, include_validation=False) + monkeypatch.setattr(client, "_serializer", protocol_serializer) + return client + + yield _create_client + + +def _collect_operations() -> tuple[ServiceModel, OperationModel]: """ Collects all service<>operation combinations to test. """ service_catalog = get_service_catalog() for service_name in service_catalog.service_names: service = service_catalog.get(service_name) - for operation_name in service.operation_names: - # FIXME try to support more and more services, get these exclusions down! - # Exclude all operations for the following, currently _not_ supported services - if service.service_name in [ - "bedrock-agent", - "bedrock-agent-runtime", - "bedrock-data-automation", - "bedrock-data-automation-runtime", - "chime", - "chime-sdk-identity", - "chime-sdk-media-pipelines", - "chime-sdk-meetings", - "chime-sdk-messaging", - "chime-sdk-voice", - "codecatalyst", - "connect", - "connect-contact-lens", - "connectcampaigns", - "connectcampaignsv2", - "greengrassv2", - "iot1click", - "iot1click-devices", - "iot1click-projects", - "ivs", - "ivs-realtime", - "kinesis-video-archived", - "kinesis-video-archived-media", - "kinesis-video-media", - "kinesis-video-signaling", - "kinesis-video-webrtc-storage", - "kinesisvideo", - "lex-models", - "lex-runtime", - "lexv2-models", - "lexv2-runtime", - "mailmanager", - "marketplace-catalog", - "marketplace-deployment", - "marketplace-reporting", - "personalize", - "personalize-events", - "personalize-runtime", - "pinpoint-sms-voice", - "qconnect", - "sagemaker-edge", - "sagemaker-featurestore-runtime", - "sagemaker-metrics", - "sms-voice", - "sso", - "sso-oidc", - "wisdom", - "workdocs", - ]: - yield pytest.param( - service, - service.protocol, - service.operation_model(operation_name), - marks=pytest.mark.skip( - reason=f"{service.service_name} is currently not supported by the service router" - ), - ) - # Exclude services / operations which have ambiguities and where the service routing needs to resolve those - elif ( - service.service_name in ["docdb", "neptune"] # maps to rds - or service.service_name in "timestream-write" # maps to timestream-query - or ( - service.service_name == "sesv2" - and operation_name == "PutEmailIdentityDkimSigningAttributes" - ) - ): - yield pytest.param( - service, - service.protocol, - service.operation_model(operation_name), - marks=pytest.mark.skip( - reason=f"{service.service_name} may differ due to ambiguities in the service specs" - ), - ) - else: - yield service, service.protocol, service.operation_model(operation_name) + service_protocols = {service.protocol} + if protocols := service.metadata.get("protocols"): + service_protocols.update(protocols) + + for service_protocol in sorted(service_protocols): + for operation_name in service.operation_names: + # FIXME try to support more and more services, get these exclusions down! + # Exclude all operations for the following, currently _not_ supported services + if service.service_name in [ + "bedrock-agent", + "bedrock-agentcore", + "bedrock-agentcore-control", + "bedrock-agent-runtime", + "bedrock-data-automation", + "bedrock-data-automation-runtime", + "chime", + "chime-sdk-identity", + "chime-sdk-media-pipelines", + "chime-sdk-meetings", + "chime-sdk-messaging", + "chime-sdk-voice", + "codecatalyst", + "connect", + "connect-contact-lens", + "connectcampaigns", + "connectcampaignsv2", + "greengrassv2", + "iot1click", + "iot1click-devices", + "iot1click-projects", + "ivs", + "ivs-realtime", + "kinesis-video-archived", + "kinesis-video-archived-media", + "kinesis-video-media", + "kinesis-video-signaling", + "kinesis-video-webrtc-storage", + "kinesisvideo", + "lex-models", + "lex-runtime", + "lexv2-models", + "lexv2-runtime", + "mailmanager", + "marketplace-catalog", + "marketplace-deployment", + "marketplace-reporting", + "personalize", + "personalize-events", + "personalize-runtime", + "pinpoint-sms-voice", + "qconnect", + "sagemaker-edge", + "sagemaker-featurestore-runtime", + "sagemaker-metrics", + "signin", + "signer", # `signer` has conflicts with `signer-data` `GetRevocationStatus` + "signer-data", + "sms-voice", + "sso", + "sso-oidc", + "wisdom", + "workdocs", + ]: + yield pytest.param( + service, + service_protocol, + service.operation_model(operation_name), + marks=pytest.mark.skip( + reason=f"{service.service_name} is currently not supported by the service router" + ), + ) + # Exclude services / operations which have ambiguities and where the service routing needs to resolve those + elif ( + service.service_name in ["docdb", "neptune"] # maps to rds + or service.service_name in "timestream-write" # maps to timestream-query + or ( + service.service_name == "sesv2" + and operation_name == "PutEmailIdentityDkimSigningAttributes" + ) + ): + yield pytest.param( + service, + service_protocol, + service.operation_model(operation_name), + marks=pytest.mark.skip( + reason=f"{service.service_name} may differ due to ambiguities in the service specs" + ), + ) + else: + yield service, service_protocol, service.operation_model(operation_name) def _botocore_request_to_localstack_request(request_object: AWSRequest) -> Request: @@ -130,7 +167,7 @@ def _botocore_request_to_localstack_request(request_object: AWSRequest) -> Reque } -def _create_dummy_request_args(operation_model: OperationModel) -> Dict: +def _create_dummy_request_args(operation_model: OperationModel) -> dict: """Creates a dummy request param dict for the given operation.""" input_shape: StructureShape = operation_model.input_shape if not input_shape: @@ -159,7 +196,11 @@ def _generate_test_name(param: Any): ids=_generate_test_name, ) def test_service_router_works_for_every_service( - service: ServiceModel, protocol: str, operation: OperationModel, caplog, aws_client_factory + service: ServiceModel, + protocol: str, + operation: OperationModel, + caplog, + get_aws_client_for_protocol, ): caplog.set_level("CRITICAL", "botocore") @@ -172,16 +213,7 @@ def test_service_router_works_for_every_service( ) # Create a dummy request for the service router - client = aws_client_factory.get_client( - service_name, - config=Config( - connect_timeout=1_000, - read_timeout=1_000, - retries={"total_max_attempts": 1}, - parameter_validation=False, - user_agent="aws-cli/1.33.7", - ), - ) + client = get_aws_client_for_protocol(service_name, protocol) request_context = { "client_region": client.meta.region_name, @@ -203,10 +235,11 @@ def test_service_router_works_for_every_service( # Execute the service router detected_service_model = determine_aws_service_model(request) + detected_service_protocol = determine_aws_protocol(request, detected_service_model) - # Make sure the detected service is the same as the one we generated the request for + # Make sure the detected service and protocol are the same as the one we generated the request for assert detected_service_model.service_name == service.service_name - assert detected_service_model.protocol == service.protocol + assert detected_service_protocol == protocol def test_endpoint_prefix_based_routing(): @@ -263,3 +296,78 @@ def test_endpoint_prefix_based_routing_for_sqs(): ) assert detected_service_model.service_name == "sqs" assert detected_service_model.protocol == "json" + + +def test_multi_protocols_detection(): + rpc_v2_request = Request( + method="POST", + path="/service/ArcRegionSwitch/operation/ListPlans", + headers={ + "Host": "localhost.localstack.cloud", + "Smithy-Protocol": "rpc-v2-cbor", + "Content-Type": "application/cbor", + }, + ) + + detected_service_model = determine_aws_service_model(rpc_v2_request) + assert detected_service_model.service_name == "arc-region-switch" + assert detected_service_model.protocol == "smithy-rpc-v2-cbor" + + detected_protocol = determine_aws_protocol(rpc_v2_request, detected_service_model) + assert detected_protocol == "smithy-rpc-v2-cbor" + + # test explicitly with JSON + json_request = Request( + method="POST", + path="/", + headers={ + "Host": "localhost.localstack.cloud", + "X-Amz-Target": "ArcRegionSwitch.ListPlans", + "Content-Type": "application/x-amz-json-1.0", + }, + ) + + detected_service_model = determine_aws_service_model(json_request) + assert detected_service_model.service_name == "arc-region-switch" + # the default service model protocol is still RPC v2 CBOR + assert detected_service_model.protocol == "smithy-rpc-v2-cbor" + + detected_protocol = determine_aws_protocol(json_request, detected_service_model) + assert detected_protocol == "json" + + +def test_multi_protocols_detection_error(): + bad_request = Request( + method="POST", + path="/", + headers={ + "Host": "localhost.localstack.cloud", + "X-Amz-Target": "ArcRegionSwitch.ListPlans", + }, + ) + + detected_service_model = determine_aws_service_model(bad_request) + assert detected_service_model.service_name == "arc-region-switch" + # the default service model protocol is still RPC v2 CBOR + assert detected_service_model.protocol == "smithy-rpc-v2-cbor" + + with pytest.raises(ProtocolError): + determine_aws_protocol(bad_request, detected_service_model) + + +def test_query_protocol_detection_with_empty_body(): + sns_request = Request( + method="POST", + path="/", + headers={ + "Host": "localhost.localstack.cloud", + }, + query_string="Action=ConfirmSubscription&TopicArn=bad_arn&Token=token", + ) + + detected_service_model = determine_aws_service_model(sns_request) + assert detected_service_model.service_name == "sns" + assert detected_service_model.protocol == "query" + + # we set wrong protocol here just to verify we can match `query` even if we don't have the Content-Type + assert match_available_protocols(sns_request, ["query", "json"]) diff --git a/tests/unit/aws/test_skeleton.py b/tests/unit/aws/test_skeleton.py index 846d41340cd18..8c4ebba8b793a 100644 --- a/tests/unit/aws/test_skeleton.py +++ b/tests/unit/aws/test_skeleton.py @@ -1,4 +1,4 @@ -from typing import Dict, List, TypedDict +from typing import TypedDict import pytest from botocore.parsers import create_parser @@ -14,14 +14,15 @@ from localstack.aws.skeleton import DispatchTable, ServiceRequestDispatcher, Skeleton from localstack.aws.spec import load_service from localstack.http import Request +from localstack.utils.catalog.common import AwsServiceSupportAtRuntime """ Stripped down version of the SQS API generated by the Scaffold. """ String = str -StringList = List[String] +StringList = list[String] Binary = bytes -BinaryList = List[Binary] +BinaryList = list[Binary] Integer = int @@ -64,9 +65,9 @@ class MessageSystemAttributeValue(TypedDict): DataType: String -MessageBodyAttributeMap = Dict[String, MessageAttributeValue] -MessageSystemAttributeMap = Dict[MessageSystemAttributeName, String] -MessageBodySystemAttributeMap = Dict[ +MessageBodyAttributeMap = dict[String, MessageAttributeValue] +MessageSystemAttributeMap = dict[MessageSystemAttributeName, String] +MessageBodySystemAttributeMap = dict[ MessageSystemAttributeNameForSends, MessageSystemAttributeValue ] @@ -196,23 +197,28 @@ def test_skeleton_e2e_sqs_send_message(): @pytest.mark.parametrize( - "api_class, oracle_message", + "api_class, service_support_status, oracle_message", [ ( TestSqsApiNotImplemented(), + AwsServiceSupportAtRuntime.NOT_IMPLEMENTED, ( - "The API action 'SendMessage' for service 'sqs' is either not available " - "in your current license plan or has not yet been emulated by LocalStack. " - "Please refer to https://docs.localstack.cloud/references/coverage/coverage_sqs for more information." + "Sorry, the SendMessage operation on the sqs service is not currently supported by LocalStack." ), ), ( TestSqsApiNotImplementedWithMessage(), + AwsServiceSupportAtRuntime.NOT_IMPLEMENTED, "We will implement it soon, that's a promise!", ), ], ) -def test_skeleton_e2e_sqs_send_message_not_implemented(api_class, oracle_message): +def test_skeleton_e2e_sqs_send_message_not_implemented( + api_class, service_support_status, oracle_message, aws_catalog_mock +): + catalog = aws_catalog_mock("localstack.aws.skeleton.get_aws_catalog") + catalog.get_aws_service_status.return_value = service_support_status + sqs_service = load_service("sqs-query") skeleton = Skeleton(sqs_service, api_class) request = Request( @@ -287,7 +293,9 @@ def delete_queue(_context: RequestContext, _request: ServiceRequest): } -def test_dispatch_missing_method_returns_internal_failure(): +def test_dispatch_missing_method_returns_internal_failure(aws_catalog_mock): + catalog = aws_catalog_mock("localstack.aws.skeleton.get_aws_catalog") + catalog.get_aws_service_status.return_value = AwsServiceSupportAtRuntime.NOT_IMPLEMENTED table: DispatchTable = {} sqs_service = load_service("sqs-query") @@ -316,9 +324,7 @@ def test_dispatch_missing_method_returns_internal_failure(): assert parsed_response["Error"] == { "Code": "InternalFailure", "Message": ( - "The API action 'DeleteQueue' for service 'sqs' is either not available in your " - "current license plan or has not yet been emulated by LocalStack. " - "Please refer to https://docs.localstack.cloud/references/coverage/coverage_sqs for more information." + "Sorry, the DeleteQueue operation on the sqs service is not currently supported by LocalStack." ), } @@ -330,7 +336,7 @@ class SomeAction(ServiceRequest): ArgTwo: int def fn(context, arg_one, arg_two): - assert type(context) == RequestContext + assert isinstance(context, RequestContext) assert arg_one == "foo" assert arg_two == 69 @@ -340,7 +346,7 @@ def fn(context, arg_one, arg_two): def test_without_context_without_expand(self): def fn(*args): assert len(args) == 1 - assert type(args[0]) == dict + assert isinstance(args[0], dict) dispatcher = ServiceRequestDispatcher( fn, "SomeAction", pass_context=False, expand_parameters=False @@ -350,8 +356,8 @@ def fn(*args): def test_without_expand(self): def fn(*args): assert len(args) == 2 - assert type(args[0]) == RequestContext - assert type(args[1]) == dict + assert isinstance(args[0], RequestContext) + assert isinstance(args[1], dict) dispatcher = ServiceRequestDispatcher( fn, "SomeAction", pass_context=True, expand_parameters=False @@ -360,7 +366,7 @@ def fn(*args): def test_dispatch_without_args(self): def fn(context): - assert type(context) == RequestContext + assert isinstance(context, RequestContext) dispatcher = ServiceRequestDispatcher(fn, "SomeAction") dispatcher(RequestContext(None), ServiceRequest()) diff --git a/tests/unit/aws/test_spec.py b/tests/unit/aws/test_spec.py index b20b57ad76087..d99f6ffbb5724 100644 --- a/tests/unit/aws/test_spec.py +++ b/tests/unit/aws/test_spec.py @@ -1,5 +1,3 @@ -from typing import Type - import pytest from botocore.exceptions import UnknownServiceError from botocore.model import ServiceModel, StringShape @@ -121,7 +119,7 @@ def test_protocol_specific_loading( ], ) def test_invalid_service_loading( - service_name: ServiceName, protocol: ProtocolName, expected_exception: Type[Exception] + service_name: ServiceName, protocol: ProtocolName, expected_exception: type[Exception] ): with pytest.raises(expected_exception): load_service(service=service_name, protocol=protocol) diff --git a/tests/unit/cli/test_cli.py b/tests/unit/cli/test_cli.py deleted file mode 100644 index 0313ded90f218..0000000000000 --- a/tests/unit/cli/test_cli.py +++ /dev/null @@ -1,336 +0,0 @@ -import json -import logging -import sys -import threading -from queue import Queue - -import click -import pytest -from click.testing import CliRunner - -import localstack.constants -import localstack.utils.analytics.cli -from localstack import config -from localstack.cli.localstack import create_with_plugins, is_frozen_bundle -from localstack.cli.localstack import localstack as cli -from localstack.config import HostAndPort -from localstack.constants import VERSION -from localstack.http import Request -from localstack.utils.common import is_command_available -from localstack.utils.container_utils.container_client import ContainerException, DockerNotAvailable - -cli: click.Group - - -@pytest.fixture -def runner(): - return CliRunner() - - -@pytest.mark.parametrize( - "exception,expected_message", - [ - (KeyboardInterrupt(), "Aborted!"), - (DockerNotAvailable(), "Docker could not be found on the system"), - (ContainerException("example message"), "example message"), - (click.ClickException("example message"), "example message"), - (click.exceptions.Exit(code=1), ""), - ], -) -def test_error_handling(runner: CliRunner, monkeypatch, exception, expected_message): - """Test different globally handled exceptions, their status code, and error message.""" - - def mock_call(*args, **kwargs): - raise exception - - from localstack.utils import bootstrap - - monkeypatch.setattr(bootstrap, "start_infra_locally", mock_call) - result = runner.invoke(cli, ["start", "--host"]) - assert result.exit_code == 1 - assert expected_message in result.output - - -def test_error_handling_help(runner): - """Make sure the help command is not interpreted as an error (Exit exception is raised).""" - result = runner.invoke(cli, ["-h"]) - assert result.exit_code == 0 - assert "Usage: localstack" in result.output - - -def test_create_with_plugins(runner): - localstack_cli = create_with_plugins() - result = runner.invoke(localstack_cli.group, ["--version"]) - assert result.exit_code == 0 - assert result.output.strip() == f"LocalStack CLI {VERSION}" - - -def test_version(runner): - result = runner.invoke(cli, ["--version"]) - assert result.exit_code == 0 - assert result.output.strip() == f"LocalStack CLI {VERSION}" - - -def test_status_services_error(runner): - result = runner.invoke(cli, ["status", "services"]) - assert result.exit_code == 1 - assert "Error" in result.output - - -@pytest.mark.parametrize("command", ["ssh", "stop"]) -def test_container_not_runnin_error(runner, command): - result = runner.invoke(cli, [command]) - assert result.exit_code == 1 - assert "Error" in result.output - assert "Expected a running LocalStack container" in result.output - - -def test_start_docker_is_default(runner, monkeypatch): - from localstack.utils import bootstrap - - called = threading.Event() - - def mock_call(*args, **kwargs): - called.set() - - monkeypatch.setattr(bootstrap, "start_infra_in_docker", mock_call) - runner.invoke(cli, ["start"]) - assert called.is_set() - - -def test_start_host(runner, monkeypatch): - from localstack.utils import bootstrap - - called = threading.Event() - - def mock_call(*args, **kwargs): - called.set() - - monkeypatch.setattr(bootstrap, "start_infra_locally", mock_call) - runner.invoke(cli, ["start", "--host"]) - assert called.is_set() - - -def test_status_services(runner, httpserver, monkeypatch): - # configure LOCALSTACK_HOST because the services endpoint makes a request against the - # external URL of LocalStack, which may be different to the edge port - monkeypatch.setattr( - config, - "LOCALSTACK_HOST", - HostAndPort( - host="localhost.localstack.cloud", - port=httpserver.port, - ), - ) - - services = {"dynamodb": "starting", "s3": "running"} - httpserver.expect_request("/_localstack/health", method="GET").respond_with_json( - {"services": services} - ) - - result = runner.invoke(cli, ["status", "services"]) - - assert result.exit_code == 0, result - - assert "dynamodb" in result.output - assert "s3" in result.output - - for line in result.output.splitlines(): - if "dynamodb" in line: - assert "starting" in line - assert "running" not in line - if "s3" in line: - assert "running" in line - assert "starting" not in line - - -def test_validate_config(runner, monkeypatch, tmp_path): - if not is_command_available("docker-compose"): - pytest.skip("config validation needs the docker-compose command") - - file = tmp_path / "docker-compose.yml" - file.touch() - - file.write_text( - """version: "3.3" -services: - localstack: - container_name: "${LOCALSTACK_DOCKER_NAME-localstack-main}" - image: localstack/localstack - network_mode: bridge - ports: - - "127.0.0.1:53:53" - - "127.0.0.1:53:53/udp" - - "127.0.0.1:443:443" - - "127.0.0.1:4566:4566" - - "127.0.0.1:4571:4571" - environment: - - SERVICES=${SERVICES- } - - DEBUG=${DEBUG- } - - DATA_DIR=${DATA_DIR- } - - LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN- } - - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- } - - DOCKER_HOST=unix:///var/run/docker.sock - volumes: - - "${TMPDIR:-/tmp/localstack}:/tmp/localstack" - - "/var/run/docker.sock:/var/run/docker.sock" -""" - ) - - result = runner.invoke(cli, ["config", "validate", "--file", str(file)]) - - assert result.exit_code == 0 - assert "config valid" in result.output - - -def test_validate_config_syntax_error(runner, monkeypatch, tmp_path): - if not is_command_available("docker-compose"): - pytest.skip("config validation needs the docker-compose command") - - file = tmp_path / "docker-compose.yml" - file.touch() - - file.write_text("foobar.---\n") - - result = runner.invoke(cli, ["config", "validate", "--file", str(file)]) - - assert result.exit_code == 1 - assert "Error" in result.output - - -@pytest.mark.parametrize( - "cli_input,expected_cmd,expected_params", - [ - ("stop", "localstack stop", []), - ("config show", "localstack config show", ["format_"]), - ("--debug config show --format plain", "localstack config show", ["format_"]), - ], -) -def test_publish_analytics_event_on_command_invocation( - cli_input, expected_cmd, expected_params, runner, monkeypatch, caplog, httpserver -): - # must suppress pytest logging due to weird issue with click https://github.com/pytest-dev/pytest/issues/3344 - caplog.set_level(logging.CRITICAL) - monkeypatch.setattr(localstack.utils.analytics.cli, "ANALYTICS_API_RESPONSE_TIMEOUT_SECS", 3) - request_data = Queue() - input = cli_input.split(" ") - - def _handler(_request: Request): - request_data.put(_request.data) - - httpserver.expect_request("").respond_with_handler(_handler) - monkeypatch.setattr(localstack.constants, "ANALYTICS_API", httpserver.url_for("/")) - runner.invoke(cli, input) - request_payload = request_data.get(timeout=5) - - assert request_data.qsize() == 0 - payload = json.loads(request_payload) - events = payload["events"] - assert len(events) == 1 - event = events[0] - metadata = event["metadata"] - assert "client_time" in metadata - assert "session_id" in metadata - assert event["name"] == "cli_cmd" - assert event["payload"]["cmd"] == expected_cmd - assert event["payload"]["params"] == expected_params - - -@pytest.mark.parametrize( - "cli_input", - [ - "invalid", - "status services", - "config show --format invalid", - ], -) -def test_do_not_publish_analytics_event_on_invalid_command_invocation( - cli_input, runner, monkeypatch, caplog, httpserver -): - # must suppress pytest logging due to weird issue with click https://github.com/pytest-dev/pytest/issues/3344 - caplog.set_level(logging.CRITICAL) - monkeypatch.setattr(localstack.utils.analytics.cli, "ANALYTICS_API_RESPONSE_TIMEOUT_SECS", 3) - request_data = [] - input = cli_input.split(" ") - - def _handler(_request: Request): - request_data.append(_request.data) - - httpserver.expect_request("").respond_with_handler(_handler) - monkeypatch.setenv("ANALYTICS_API", httpserver.url_for("/")) - runner.invoke(cli, input) - assert len(request_data) == 0, ( - "analytics API should not be invoked when an invalid command is supplied" - ) - - -def test_disable_publish_analytics_event_on_command_invocation( - runner, monkeypatch, caplog, httpserver -): - # must suppress pytest logging due to weird issue with click https://github.com/pytest-dev/pytest/issues/3344 - caplog.set_level(logging.CRITICAL) - monkeypatch.setattr(localstack.utils.analytics.cli, "ANALYTICS_API_RESPONSE_TIMEOUT_SECS", 3) - monkeypatch.setattr(localstack.config, "DISABLE_EVENTS", True) - request_data = [] - - def _handler(_request: Request): - request_data.append(_request.data) - - httpserver.expect_request("").respond_with_handler(_handler) - monkeypatch.setenv("ANALYTICS_API", httpserver.url_for("/")) - runner.invoke(cli, ["config", "show"]) - assert len(request_data) == 0, "analytics API should not be invoked when DISABLE_EVENTS is set" - - -def test_timeout_publishing_command_invocation(runner, monkeypatch, caplog, httpserver): - # must suppress pytest logging due to weird issue with click https://github.com/pytest-dev/pytest/issues/3344 - caplog.set_level(logging.CRITICAL) - monkeypatch.setattr( - # simulate slow API call by turning timeout way down - localstack.utils.analytics.cli, - "ANALYTICS_API_RESPONSE_TIMEOUT_SECS", - 0.001, - ) - request_data = [] - - def _handler(_request: Request): - request_data.append(_request.data) - - httpserver.expect_request("").respond_with_handler(_handler) - monkeypatch.setenv("ANALYTICS_API", httpserver.url_for("/")) - runner.invoke(cli, ["config", "show"]) - assert len(request_data) == 0, ( - "analytics event publisher process should time out if request is taking too long" - ) - - -def test_is_frozen(monkeypatch): - # mimic a frozen pyinstaller binary according to https://pyinstaller.org/en/stable/runtime-information.html - monkeypatch.setattr(sys, "frozen", True, raising=False) - monkeypatch.setattr(sys, "_MEIPASS", "/absolute/path/to/bundle/folder", raising=False) - assert is_frozen_bundle() - - -def test_not_is_frozen(monkeypatch): - # mimic running from source - monkeypatch.delattr(sys, "frozen", raising=False) - assert not is_frozen_bundle() - monkeypatch.setattr(sys, "frozen", True, raising=False) - monkeypatch.delattr(sys, "_MEIPASS", raising=False) - assert not is_frozen_bundle() - - -@pytest.mark.parametrize("shell", ["bash", "zsh", "fish"]) -def test_completion(monkeypatch, runner, shell: str): - test_binary_name = "testbinaryname" - monkeypatch.setattr(localstack.config, "DISABLE_EVENTS", True) - monkeypatch.setattr(sys, "argv", [test_binary_name]) - result = runner.invoke(cli, ["completion", shell]) - assert result.exit_code == 0 - assert f"_{test_binary_name.upper()}_COMPLETE={shell}_complete" in result.output - - -def test_completion_unknown_shell(monkeypatch, runner): - monkeypatch.setattr(localstack.config, "DISABLE_EVENTS", True) - result = runner.invoke(cli, ["completion", "unknown_shell"]) - assert result.exit_code != 0 diff --git a/tests/unit/cli/test_lpm.py b/tests/unit/cli/test_lpm.py index 605aac7ef00ad..4bf7c75564dcb 100644 --- a/tests/unit/cli/test_lpm.py +++ b/tests/unit/cli/test_lpm.py @@ -1,5 +1,4 @@ import os.path -from typing import List import pytest from click.testing import CliRunner @@ -48,7 +47,7 @@ class FailingPackage(Package): def __init__(self): super().__init__("Failing Installer", "latest") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["latest"] def _get_installer(self, version: str) -> PackageInstaller: @@ -69,7 +68,7 @@ class SuccessfulPackage(Package): def __init__(self): super().__init__("Successful Installer", "latest") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["latest"] def _get_installer(self, version: str) -> PackageInstaller: @@ -86,7 +85,7 @@ def _get_install_marker_path(self, install_dir: str) -> str: def _install(self, target: InstallTarget) -> None: pass - def patched_get_packages(*_) -> List[Package]: + def patched_get_packages(*_) -> list[Package]: return [FailingPackage(), SuccessfulPackage()] with Patch.function(target=PackagesPluginManager.get_packages, fn=patched_get_packages): diff --git a/tests/unit/cli/test_profiles.py b/tests/unit/cli/test_profiles.py deleted file mode 100644 index c48fd4b9e739d..0000000000000 --- a/tests/unit/cli/test_profiles.py +++ /dev/null @@ -1,148 +0,0 @@ -import os -import sys - -from localstack.cli.profiles import set_and_remove_profile_from_sys_argv - - -def profile_test(monkeypatch, input_args, expected_profile, expected_argv): - monkeypatch.setattr(sys, "argv", input_args) - monkeypatch.setenv("CONFIG_PROFILE", "") - set_and_remove_profile_from_sys_argv() - assert os.environ["CONFIG_PROFILE"] == expected_profile - assert sys.argv == expected_argv - - -def test_profiles_equals_notation(monkeypatch): - profile_test( - monkeypatch, - input_args=["--profile=non-existing-test-profile"], - expected_profile="non-existing-test-profile", - expected_argv=[], - ) - - -def test_profiles_separate_args_notation(monkeypatch): - profile_test( - monkeypatch, - input_args=["--profile", "non-existing-test-profile"], - expected_profile="non-existing-test-profile", - expected_argv=[], - ) - - -def test_p_equals_notation(monkeypatch): - profile_test( - monkeypatch, - input_args=["-p=non-existing-test-profile"], - expected_profile="non-existing-test-profile", - expected_argv=["-p=non-existing-test-profile"], - ) - - -def test_p_separate_args_notation(monkeypatch): - profile_test( - monkeypatch, - input_args=["-p", "non-existing-test-profile"], - expected_profile="non-existing-test-profile", - expected_argv=["-p", "non-existing-test-profile"], - ) - - -def test_profiles_args_before_and_after(monkeypatch): - profile_test( - monkeypatch, - input_args=["cli", "-D", "--profile=non-existing-test-profile", "start"], - expected_profile="non-existing-test-profile", - expected_argv=["cli", "-D", "start"], - ) - - -def test_profiles_args_before_and_after_separate(monkeypatch): - profile_test( - monkeypatch, - input_args=["cli", "-D", "--profile", "non-existing-test-profile", "start"], - expected_profile="non-existing-test-profile", - expected_argv=["cli", "-D", "start"], - ) - - -def test_p_args_before_and_after_separate(monkeypatch): - profile_test( - monkeypatch, - input_args=["cli", "-D", "-p", "non-existing-test-profile", "start"], - expected_profile="non-existing-test-profile", - expected_argv=["cli", "-D", "-p", "non-existing-test-profile", "start"], - ) - - -def test_profiles_args_multiple(monkeypatch): - profile_test( - monkeypatch, - input_args=[ - "cli", - "--profile", - "non-existing-test-profile", - "start", - "--profile", - "another-profile", - ], - expected_profile="another-profile", - expected_argv=["cli", "start"], - ) - - -def test_p_args_multiple(monkeypatch): - profile_test( - monkeypatch, - input_args=[ - "cli", - "-p", - "non-existing-test-profile", - "start", - "-p", - "another-profile", - ], - expected_profile="non-existing-test-profile", - expected_argv=[ - "cli", - "-p", - "non-existing-test-profile", - "start", - "-p", - "another-profile", - ], - ) - - -def test_p_and_profile_args(monkeypatch): - profile_test( - monkeypatch, - input_args=[ - "cli", - "-p", - "non-existing-test-profile", - "start", - "--profile", - "the_profile", - "-p", - "another-profile", - ], - expected_profile="the_profile", - expected_argv=[ - "cli", - "-p", - "non-existing-test-profile", - "start", - "-p", - "another-profile", - ], - ) - - -def test_trailing_p_argument(monkeypatch): - profile_test( - monkeypatch, - input_args=["cli", "start", "-p"], - expected_profile="", - expected_argv=["cli", "start", "-p"], - ) diff --git a/tests/unit/http_/test_asgi.py b/tests/unit/http_/test_asgi.py index dbed33d8be862..85ce71dc837f2 100644 --- a/tests/unit/http_/test_asgi.py +++ b/tests/unit/http_/test_asgi.py @@ -5,7 +5,6 @@ from concurrent.futures import ThreadPoolExecutor from queue import Queue from threading import Thread -from typing import List import pytest import requests @@ -17,7 +16,7 @@ def test_serve_asgi_adapter(serve_asgi_adapter): - request_list: List[Request] = [] + request_list: list[Request] = [] @Request.application def app(request: Request) -> Response: @@ -136,7 +135,7 @@ def _gen(): def test_chunked_transfer_encoding_request(serve_asgi_adapter): - request_list: List[Request] = [] + request_list: list[Request] = [] @Request.application def app(request: Request) -> Response: @@ -176,8 +175,7 @@ def __init__(self, data: list[bytes]): self.closed = False def __iter__(self): - for packet in self.data: - yield packet + yield from self.data def close(self): # should be called through the werkzeug layers diff --git a/tests/unit/http_/test_dispatcher.py b/tests/unit/http_/test_dispatcher.py index 1a4b2421b5e26..41af4071410f4 100644 --- a/tests/unit/http_/test_dispatcher.py +++ b/tests/unit/http_/test_dispatcher.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any import pytest from werkzeug.exceptions import NotFound @@ -14,7 +14,7 @@ def test_handler_dispatcher(self): def handler_foo(_request: Request) -> Response: return Response("ok") - def handler_bar(_request: Request, bar, baz) -> Dict[str, any]: + def handler_bar(_request: Request, bar, baz) -> dict[str, any]: response = Response() response.set_json({"bar": bar, "baz": baz}) return response @@ -42,7 +42,7 @@ def handler(_request: Request, arg1) -> Response: # invalid signature def test_handler_dispatcher_with_dict_return(self): router = Router(dispatcher=handler_dispatcher()) - def handler(_request: Request, arg1) -> Dict[str, Any]: + def handler(_request: Request, arg1) -> dict[str, Any]: return {"arg1": arg1, "hello": "there"} router.add("/foo/", handler) diff --git a/tests/unit/http_/test_hypercorn.py b/tests/unit/http_/test_hypercorn.py index 85693337c9228..f2b2aec01ac56 100644 --- a/tests/unit/http_/test_hypercorn.py +++ b/tests/unit/http_/test_hypercorn.py @@ -1,6 +1,5 @@ import re from contextlib import contextmanager -from typing import Optional import requests from werkzeug.datastructures import Headers @@ -17,7 +16,7 @@ @contextmanager -def server_context(server: Server, timeout: Optional[float] = 10): +def server_context(server: Server, timeout: float | None = 10): server.start() server.wait_is_up(timeout) try: @@ -108,8 +107,7 @@ def handler(request: WerkzeugRequest) -> Response: proxy_server = ProxyServer(httpserver.url_for("/"), gateway_listen, use_ssl=True) def chunk_generator(): - for chunk in chunks: - yield chunk + yield from chunks with server_context(proxy_server): response = requests.get( @@ -122,8 +120,7 @@ def test_proxy_server_with_streamed_response(httpserver): chunks = [bytes(f"{n:2}", "utf-8") for n in range(0, 100)] def chunk_generator(): - for chunk in chunks: - yield chunk + yield from chunks def stream_response_handler(_: WerkzeugRequest) -> Response: return Response(response=chunk_generator()) diff --git a/tests/unit/http_/test_proxy.py b/tests/unit/http_/test_proxy.py index e7b6318bbbd85..28faeb49c96fa 100644 --- a/tests/unit/http_/test_proxy.py +++ b/tests/unit/http_/test_proxy.py @@ -1,5 +1,4 @@ import json -from typing import Tuple import pytest import requests @@ -14,7 +13,7 @@ @pytest.fixture -def router_server(serve_asgi_adapter) -> Tuple[Router, HypercornServer]: +def router_server(serve_asgi_adapter) -> tuple[Router, HypercornServer]: """Creates a new Router with a handler dispatcher, serves it through a newly created ASGI server, and returns both the router and the server. """ diff --git a/tests/unit/http_/test_router.py b/tests/unit/http_/test_router.py index 149cdba1ff005..3d03a1add6772 100644 --- a/tests/unit/http_/test_router.py +++ b/tests/unit/http_/test_router.py @@ -1,5 +1,4 @@ import threading -from typing import List, Tuple import pytest import requests @@ -34,7 +33,7 @@ def echo_params_json(request: Request, params: dict[str, str]): class RequestCollector: """Test dispatcher that collects requests into a list""" - requests: List[Tuple[Request, E, RequestArguments]] + requests: list[tuple[Request, E, RequestArguments]] def __init__(self) -> None: super().__init__() diff --git a/tests/unit/lambda_debug_mode/__init__.py b/tests/unit/lambda_debug_mode/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/unit/lambda_debug_mode/test_config_parsing.py b/tests/unit/lambda_debug_mode/test_config_parsing.py deleted file mode 100644 index 5dd4ad45468c5..0000000000000 --- a/tests/unit/lambda_debug_mode/test_config_parsing.py +++ /dev/null @@ -1,134 +0,0 @@ -import pytest - -from localstack.utils.lambda_debug_mode.lambda_debug_mode_config import ( - load_lambda_debug_mode_config, -) - -DEBUG_CONFIG_EMPTY = "" - -DEBUG_CONFIG_NULL_FUNCTIONS = """ -functions: - null -""" - -DEBUG_CONFIG_NULL_FUNCTION_CONFIG = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname:$LATEST: - null -""" - -DEBUG_CONFIG_NULL_DEBUG_PORT = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname: - debug-port: null -""" - -DEBUG_CONFIG_NULL_ENFORCE_TIMEOUTS = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname: - debug-port: null - enforce-timeouts: null -""" - -DEBUG_CONFIG_DUPLICATE_DEBUG_PORT = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname1: - debug-port: 19891 - arn:aws:lambda:eu-central-1:000000000000:function:functionname2: - debug-port: 19891 -""" - -DEBUG_CONFIG_DUPLICATE_ARN = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname: - debug-port: 19891 - arn:aws:lambda:eu-central-1:000000000000:function:functionname: - debug-port: 19892 -""" - -DEBUG_CONFIG_INVALID_MISSING_QUALIFIER_ARN = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname:: - debug-port: 19891 -""" - -DEBUG_CONFIG_INVALID_ARN_STRUCTURE = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function: - debug-port: 19891 -""" - -DEBUG_CONFIG_DUPLICATE_IMPLICIT_ARN = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname: - debug-port: 19891 - arn:aws:lambda:eu-central-1:000000000000:function:functionname:$LATEST: - debug-port: 19892 -""" - -DEBUG_CONFIG_BASE = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname:$LATEST: - debug-port: 19891 -""" - -DEBUG_CONFIG_BASE_UNQUALIFIED = """ -functions: - arn:aws:lambda:eu-central-1:000000000000:function:functionname: - debug-port: 19891 -""" - - -@pytest.mark.parametrize( - "yaml_config", - [ - DEBUG_CONFIG_EMPTY, - DEBUG_CONFIG_NULL_FUNCTIONS, - DEBUG_CONFIG_NULL_FUNCTION_CONFIG, - DEBUG_CONFIG_DUPLICATE_DEBUG_PORT, - DEBUG_CONFIG_DUPLICATE_ARN, - DEBUG_CONFIG_DUPLICATE_IMPLICIT_ARN, - DEBUG_CONFIG_NULL_ENFORCE_TIMEOUTS, - DEBUG_CONFIG_INVALID_ARN_STRUCTURE, - DEBUG_CONFIG_INVALID_MISSING_QUALIFIER_ARN, - ], - ids=[ - "empty", - "null_functions", - "null_function_config", - "duplicate_debug_port", - "deplicate_arn", - "duplicate_implicit_arn", - "null_enforce_timeouts", - "invalid_arn_structure", - "invalid_missing_qualifier_arn", - ], -) -def test_debug_config_invalid(yaml_config: str): - assert load_lambda_debug_mode_config(yaml_config) is None - - -def test_debug_config_null_debug_port(): - config = load_lambda_debug_mode_config(DEBUG_CONFIG_NULL_DEBUG_PORT) - assert list(config.functions.values())[0].debug_port is None - - -@pytest.mark.parametrize( - "yaml_config", - [ - DEBUG_CONFIG_BASE, - DEBUG_CONFIG_BASE_UNQUALIFIED, - ], - ids=[ - "base", - "base_unqualified", - ], -) -def test_debug_config_base(yaml_config): - config = load_lambda_debug_mode_config(yaml_config) - assert len(config.functions) == 1 - assert ( - "arn:aws:lambda:eu-central-1:000000000000:function:functionname:$LATEST" in config.functions - ) - assert list(config.functions.values())[0].debug_port == 19891 - assert list(config.functions.values())[0].enforce_timeouts is False diff --git a/tests/unit/logging_/test_format.py b/tests/unit/logging_/test_format.py index fc4ab72adc2c0..77aa301398043 100644 --- a/tests/unit/logging_/test_format.py +++ b/tests/unit/logging_/test_format.py @@ -37,7 +37,7 @@ class CustomMaskSensitiveInputFilter(MaskSensitiveInputFilter): sensitive_keys = ["sensitive_key"] def __init__(self): - super(CustomMaskSensitiveInputFilter, self).__init__(self.sensitive_keys) + super().__init__(self.sensitive_keys) @pytest.fixture diff --git a/tests/unit/packages/test_api.py b/tests/unit/packages/test_api.py index 608970f25d7c1..789ee2e7d3bca 100644 --- a/tests/unit/packages/test_api.py +++ b/tests/unit/packages/test_api.py @@ -2,7 +2,7 @@ from pathlib import Path from queue import Queue from threading import Event, RLock -from typing import List, Optional +from typing import Optional import pytest @@ -15,7 +15,7 @@ class TestPackage(Package): def __init__(self): super().__init__("Test Package", "test-version") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["test-version"] def _get_installer(self, version: str) -> PackageInstaller: @@ -23,7 +23,7 @@ def _get_installer(self, version: str) -> PackageInstaller: class TestPackageInstaller(PackageInstaller): - def __init__(self, version: str, install_lock: Optional[RLock] = None): + def __init__(self, version: str, install_lock: Optional[RLock] = None): # noqa UP045 super().__init__("test-installer", version, install_lock) def _get_install_marker_path(self, install_dir: str) -> str: @@ -65,7 +65,7 @@ class LockingTestPackageInstaller(PackageInstaller): Package installer class used for testing the locking behavior. """ - def __init__(self, queue: Queue = None, install_lock: Optional[RLock] = None): + def __init__(self, queue: Queue = None, install_lock: Optional[RLock] = None): # noqa UP045 super().__init__("lock-test-installer", "test", install_lock) self.queue = queue or Queue() self.about_to_wait = Event() diff --git a/tests/unit/runtime/test_init.py b/tests/unit/runtime/test_init.py index 81784a531d979..09f5c96b93e99 100644 --- a/tests/unit/runtime/test_init.py +++ b/tests/unit/runtime/test_init.py @@ -135,7 +135,7 @@ def test_run_stage_executes_scripts_correctly(self, manager, tmp_path): script_01.touch(mode=0o777) script_02.touch(mode=0o777) - script_01.write_text("#!/bin/bash\necho 'hello 1' >> %s/script_01.out" % tmp_path) + script_01.write_text(f"#!/bin/bash\necho 'hello 1' >> {tmp_path}/script_01.out") script_02.write_text("#!/bin/bash\nexit 1") script_03.write_text( "import pathlib; pathlib.Path('%s').write_text('hello 3')" @@ -261,10 +261,10 @@ def test_recursion(self, manager, tmp_path): script_02.touch(mode=0o777) script_03.touch(mode=0o777) - script_00.write_text("#!/bin/bash\necho 'hello 0' >> %s/script_00.out" % tmp_path) - script_01.write_text("#!/bin/bash\necho 'hello 1' >> %s/script_01.out" % tmp_path) - script_02.write_text("#!/bin/bash\necho 'hello 2' >> %s/script_02.out" % tmp_path) - script_03.write_text("#!/bin/bash\necho 'hello 3' >> %s/script_03.out" % tmp_path) + script_00.write_text(f"#!/bin/bash\necho 'hello 0' >> {tmp_path}/script_00.out") + script_01.write_text(f"#!/bin/bash\necho 'hello 1' >> {tmp_path}/script_01.out") + script_02.write_text(f"#!/bin/bash\necho 'hello 2' >> {tmp_path}/script_02.out") + script_03.write_text(f"#!/bin/bash\necho 'hello 3' >> {tmp_path}/script_03.out") result = manager.run_stage(Stage.READY) diff --git a/tests/unit/services/apigateway/test_apigateway_common.py b/tests/unit/services/apigateway/test_apigateway_common.py index a3df3f21ffa9c..93dddf6ef7b65 100644 --- a/tests/unit/services/apigateway/test_apigateway_common.py +++ b/tests/unit/services/apigateway/test_apigateway_common.py @@ -2,7 +2,7 @@ import unittest import xml from json import JSONDecodeError -from typing import Any, Dict +from typing import Any from unittest.mock import MagicMock, Mock import boto3 @@ -952,7 +952,7 @@ def test_construct_invocation_event(self): class TestRequestParameterResolver: def test_resolve_request_parameters(self): - integration: Dict[str, Any] = { + integration: dict[str, Any] = { "requestParameters": { "integration.request.path.pathParam": "method.request.path.id", "integration.request.querystring.baz": "method.request.querystring.baz", diff --git a/tests/unit/services/apigateway/test_handler_integration_request.py b/tests/unit/services/apigateway/test_handler_integration_request.py index 72b021e4b2d63..7bf8b46a06f14 100644 --- a/tests/unit/services/apigateway/test_handler_integration_request.py +++ b/tests/unit/services/apigateway/test_handler_integration_request.py @@ -10,6 +10,7 @@ RestApiInvocationContext, ) from localstack.services.apigateway.next_gen.execute_api.gateway_response import ( + InternalServerError, UnsupportedMediaTypeError, ) from localstack.services.apigateway.next_gen.execute_api.handlers import ( @@ -328,7 +329,7 @@ def test_convert_binary( outcome = possible_values.get(expected, input_data) if outcome is None: - with pytest.raises(Exception): + with pytest.raises(InternalServerError): convert(context=default_context) else: converted_body = convert(context=default_context) diff --git a/tests/unit/services/apigateway/test_handler_integration_response.py b/tests/unit/services/apigateway/test_handler_integration_response.py index 122af7c5bbc13..d35cd6a3f15a1 100644 --- a/tests/unit/services/apigateway/test_handler_integration_response.py +++ b/tests/unit/services/apigateway/test_handler_integration_response.py @@ -11,6 +11,7 @@ ) from localstack.services.apigateway.next_gen.execute_api.gateway_response import ( ApiConfigurationError, + InternalServerError, ) from localstack.services.apigateway.next_gen.execute_api.handlers import ( IntegrationResponseHandler, @@ -332,7 +333,7 @@ def test_convert_binary( outcome = possible_values.get(expected, input_data) if outcome is None: - with pytest.raises(Exception): + with pytest.raises(InternalServerError): convert(body=input_data, context=ctx, content_handling=content_handling) else: converted_body = convert( diff --git a/tests/unit/services/cloudformation/test_cloudformation.py b/tests/unit/services/cloudformation/test_cloudformation.py index 98644ff410e6c..62caa4d958983 100644 --- a/tests/unit/services/cloudformation/test_cloudformation.py +++ b/tests/unit/services/cloudformation/test_cloudformation.py @@ -1,9 +1,12 @@ +import pytest + from localstack.services.cloudformation.api_utils import is_local_service_url from localstack.services.cloudformation.deployment_utils import ( PLACEHOLDER_AWS_NO_VALUE, remove_none_values, ) from localstack.services.cloudformation.engine.template_deployer import order_resources +from localstack.services.cloudformation.engine.v2.resolving import REGEX_DYNAMIC_REF def test_is_local_service_url(): @@ -60,3 +63,28 @@ def test_order_resources(): ) assert list(sorted_resources.keys()) == ["A", "B"] + + +class TestDynamicResolving: + @pytest.mark.parametrize( + "value,matches", + [ + pytest.param("{{resolve:ssm:abc123}}", True, id="ssm-basic"), + pytest.param("abc:{{resolve:ssm:abc123}}:foo", True, id="ssm-with-surrounding"), + pytest.param("{{resolve:ssm:${ParameterName}}}", False, id="ssm-in-sub"), + pytest.param("{{resolve:secretsmanager:foo}}", True, id="secrets-basic"), + pytest.param( + "{{resolve:secretsmanager:arn:aws:secretsmanager:us-east-1:000000000000:secret:foo:SecretString:}}", + True, + id="secrets-partial", + ), + pytest.param( + "{{resolve:secretsmanager:arn:aws:secretsmanager:us-east-1:000000000000:secret:foo:SecretString:::}}", + True, + id="secrets-full", + ), + pytest.param("{{resolve:secretsmanager:${SecretName}}}", False, id="secrets-in-sub"), + ], + ) + def test_dynamic_ref_regex_matches(self, value, matches): + assert bool(REGEX_DYNAMIC_REF.search(value)) == matches diff --git a/tests/unit/services/cloudformation/test_deploy_ui.py b/tests/unit/services/cloudformation/test_deploy_ui.py deleted file mode 100644 index d5db5387bc579..0000000000000 --- a/tests/unit/services/cloudformation/test_deploy_ui.py +++ /dev/null @@ -1,12 +0,0 @@ -from rolo import Request - -from localstack.services.cloudformation.deploy_ui import CloudFormationUi - - -class TestCloudFormationUiResource: - def test_get(self): - resource = CloudFormationUi() - response = resource.on_get(Request("GET", "/", body=b"None")) - assert response.status == "200 OK" - assert "" in response.get_data(as_text=True), "deploy UI did not render HTML" - assert "text/html" in response.headers.get("content-type", "") diff --git a/tests/unit/services/cloudformation/test_deployment_utils.py b/tests/unit/services/cloudformation/test_deployment_utils.py index d1df32f079edd..f2c5e55be0211 100644 --- a/tests/unit/services/cloudformation/test_deployment_utils.py +++ b/tests/unit/services/cloudformation/test_deployment_utils.py @@ -12,7 +12,7 @@ def test_nested_parameters_are_fixed(self): fixed_params = fix_boto_parameters_based_on_report(params, message) value = fixed_params["LaunchTemplate"]["Version"] assert value == "1" - assert type(value) == str + assert isinstance(value, str) def test_top_level_parameters_are_converted(self): params = {"Version": 1} @@ -21,4 +21,4 @@ def test_top_level_parameters_are_converted(self): fixed_params = fix_boto_parameters_based_on_report(params, message) value = fixed_params["Version"] assert value == "1" - assert type(value) == str + assert isinstance(value, str) diff --git a/tests/unit/services/cloudformation/test_provider_utils.py b/tests/unit/services/cloudformation/test_provider_utils.py index 5fec01d31d662..dad72ee3534de 100644 --- a/tests/unit/services/cloudformation/test_provider_utils.py +++ b/tests/unit/services/cloudformation/test_provider_utils.py @@ -135,3 +135,82 @@ def test_lower_camelcase_to_pascalcase(self): } ], } + + def test_lower_camelcase_to_pascalcase_skip_keys(self): + original_dict = { + "Stages": [ + { + "Actions": [ + { + "Actiontypeid": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1", + }, + "Configuration": { + "S3bucket": "localstack-codepipeline-source-86a13a88", + "S3objectkey": "source-key", + "Subconfig": {"Subconfig1": "Foo", "Subconfig2": "bar"}, + }, + "Inputartifacts": [], + "Name": "S3Source", + "Namespace": "S3SourceVariables", + "Outputartifacts": [{"Name": "Artifact_Source_S3Source"}], + "Rolearn": "arn:aws:iam::096845016391:role/EcrPipelineStack-MyPipelineSourceS3SourceCodePipeli-YOoRQUZQe6WU", + "Runorder": 1, + } + ], + "Name": "Source", + } + ] + } + target_dict = { + "stages": [ + { + "actions": [ + { + "actiontypeid": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + # The excluded key itself is transformed + # Its values are not + # Recursion stops, items at lower levels are not transformed as well + "configuration": { + "S3bucket": "localstack-codepipeline-source-86a13a88", + "S3objectkey": "source-key", + "Subconfig": {"Subconfig1": "Foo", "Subconfig2": "bar"}, + }, + "inputartifacts": [], + "name": "S3Source", + "namespace": "S3SourceVariables", + "outputartifacts": [{"name": "Artifact_Source_S3Source"}], + "rolearn": "arn:aws:iam::096845016391:role/EcrPipelineStack-MyPipelineSourceS3SourceCodePipeli-YOoRQUZQe6WU", + "runorder": 1, + } + ], + "name": "Source", + } + ] + } + converted_dict = utils.keys_pascalcase_to_lower_camelcase( + original_dict, skip_keys={"Configuration"} + ) + assert converted_dict == target_dict + + def test_resource_tags_to_remove_or_update(self): + previous = [ + {"Key": "k1", "Value": "v1"}, + {"Key": "k2", "Value": "v2"}, + {"Key": "k3", "Value": "v3"}, + {"Key": "k4", "Value": "v4"}, + ] + desired = [{"Key": "k2", "Value": "v2-updated"}, {"Key": "k3", "Value": "v3"}] + + to_remove, to_update = utils.resource_tags_to_remove_or_update(previous, desired) + + assert sorted(to_remove) == ["k1", "k4"] + assert to_update == {"k2": "v2-updated", "k3": "v3"} diff --git a/tests/unit/services/kms/test_kms.py b/tests/unit/services/kms/test_kms.py index ffdec68c06b58..f1ca464e8ea7f 100644 --- a/tests/unit/services/kms/test_kms.py +++ b/tests/unit/services/kms/test_kms.py @@ -15,7 +15,7 @@ def test_alias_name_validator(): - with pytest.raises(Exception): + with pytest.raises(ValidationException): validate_alias_name("test-alias") diff --git a/tests/unit/services/s3/test_s3.py b/tests/unit/services/s3/test_s3.py index a01fe9d58f8c3..adac05fd12298 100644 --- a/tests/unit/services/s3/test_s3.py +++ b/tests/unit/services/s3/test_s3.py @@ -9,7 +9,7 @@ import pytest from localstack.aws.api import RequestContext -from localstack.aws.api.s3 import InvalidArgument +from localstack.aws.api.s3 import AccessDenied, AuthorizationQueryParametersError, InvalidArgument from localstack.config import S3_VIRTUAL_HOSTNAME from localstack.constants import LOCALHOST from localstack.http import Request @@ -18,6 +18,11 @@ from localstack.services.s3.codec import AwsChunkedDecoder from localstack.services.s3.constants import S3_CHUNK_SIZE from localstack.services.s3.exceptions import MalformedXML +from localstack.services.s3.headers import ( + decode_header_rfc2047, + encode_header_rfc2047, + replace_non_iso_8859_1_characters, +) from localstack.services.s3.models import S3Multipart, S3Object, S3Part from localstack.services.s3.storage.ephemeral import EphemeralS3ObjectStore from localstack.services.s3.validation import validate_canned_acl @@ -369,6 +374,31 @@ def test_parse_post_object_tagging_xml_exception(self): def test_get_bucket_and_key_from_s3_uri(self, s3_uri, bucket, object_key): assert s3_utils.get_bucket_and_key_from_s3_uri(s3_uri) == (bucket, object_key) + @pytest.mark.parametrize( + "lower_case, header_name", + [ + ("test-header", "Test-Header"), + ("test-response-header", "Test-Response-Header"), + ("testHeader", "Testheader"), + ], + ) + def test_capitalized_snake_case(self, lower_case, header_name): + assert s3_utils.capitalize_header_name_from_snake_case(lower_case) == header_name + + @pytest.mark.parametrize( + "param_name, header_name", + [ + ("ResponseContentType", "response-content-type"), + ("ResponseContentLanguage", "response-content-language"), + ("ResponseExpires", "response-expires"), + ("ResponseCacheControl", "response-cache-control"), + ("ResponseContentDisposition", "response-content-disposition"), + ("ResponseContentEncoding", "response-content-encoding"), + ], + ) + def test_header_name_from_pascal_case(self, param_name, header_name): + assert s3_utils.header_name_from_capitalized_param(param_name) == header_name + class TestS3PresignedUrl: """ @@ -454,7 +484,7 @@ def test_is_valid_presigned_url_v2(self): if not will_raise: assert presigned_url.is_valid_sig_v2(query_args) == is_sig_v2 else: - with pytest.raises(Exception): + with pytest.raises(AccessDenied): presigned_url.is_valid_sig_v2(query_args) def test_is_valid_presigned_url_v4(self): @@ -512,7 +542,7 @@ def test_is_valid_presigned_url_v4(self): if not will_raise: assert presigned_url.is_valid_sig_v4(query_args) == is_sig_v4 else: - with pytest.raises(Exception): + with pytest.raises(AuthorizationQueryParametersError): presigned_url.is_valid_sig_v4(query_args) @@ -726,3 +756,98 @@ def test_version_id_ordering(self): for index, version_id in enumerate(version_ids[1:]): previous_version = version_ids[index] assert s3_utils.is_version_older_than_other(previous_version, version_id) + + +class TestS3HeaderEncoding: + @pytest.mark.parametrize( + "header, expected", + [ + ("ÄMÄZÕÑ S3", "=?UTF-8?Q?=C3=84M=C3=84Z=C3=95=C3=91_S3?="), + ( + "test_—_file%E2%80%94_é_2.pdf", + "=?UTF-8?Q?test=5F=E2=80=94=5Ffile%E2%80%94=5F=C3=A9=5F2.pdf?=", + ), + ( + "test_—_file%E2%80%94_é_2👑.pdf", + "=?UTF-8?Q?test=5F=E2=80=94=5Ffile%E2%80%94=5F=C3=A9=5F2=F0=9F=91=91.pdf?=", + ), + ( + "! \"#$%&'()*+,-./0123456789:;<>'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t", + "! \"#$%&'()*+,-./0123456789:;<>'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t", + ), + ("\x00\x01\x02\x03\x04", "=?UTF-8?B?AAECAwQ=?="), + ], + ) + def test_encode_header_rfc_2047(self, header, expected): + assert encode_header_rfc2047(header) == expected + + @pytest.mark.parametrize( + "header, expected", + [ + ( + "=?UTF-8?Q?=C3=84M=C3=84Z=C3=95=C3=91_S3?=", + "ÄMÄZÕÑ S3", + ), + ( + "=?UTF-8?Q?test=5F=E2=80=94=5Ffile%E2%80%94=5F=C3=A9=5F2.pdf?=", + "test_—_file%E2%80%94_é_2.pdf", + ), + ( + "=?UTF-8?Q?test=5F=E2=80=94=5Ffile%E2%80%94=5F=C3=A9=5F2=F0=9F=91=91.pdf?=", + "test_—_file%E2%80%94_é_2👑.pdf", + ), + ( + "! \"#$%&'()*+,-./0123456789:;<>'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t", + "! \"#$%&'()*+,-./0123456789:;<>'?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t", + ), + ( + "=?UTF-8?B?AAECAwQ=?=", + "\x00\x01\x02\x03\x04", + ), + # broken B64 + ( + "=?UTF-8?B?=GGG?=", + "���", + ), + ], + ) + def test_decode_header_rfc2047(self, header, expected): + assert decode_header_rfc2047(header) == expected + + @pytest.mark.parametrize( + "header, expected", + [ + ("non-ascii-%E2%80%94_—_é_", "non-ascii-%E2%80%94_ _é_"), + ('filename="test_—_file%E2%80%94_é_2.pdf"', 'filename="test_ _file%E2%80%94_é_2.pdf"'), + ( + 'filename="test_—_file%E2%80%94_é_2.pdf"', + 'filename="test_ _file%E2%80%94_é_2.pdf"', + ), + ("", ""), + ], + ) + def test_sanitize_for_latin_1_header(self, header, expected): + assert replace_non_iso_8859_1_characters(header) == expected + + +class TestS3ContinuationToken: + @pytest.mark.parametrize( + "key", + [ + "file%2Fname", + "test@key/", + "test%123", + "test%percent", + "test key/", + "test key//", + "test%123/", + "a/%F0%9F%98%80/", + "date=2026-01-01/", + "date=2026-02-01/", + "date=2026-03-01/", + "date=2026-05-01/", + ], + ) + def test_continuation_token_is_consistent(self, key): + encoded_key = s3_utils.encode_continuation_token(key) + assert s3_utils.decode_continuation_token(encoded_key) == key diff --git a/tests/unit/services/sns/test_sns.py b/tests/unit/services/sns/test_sns.py index 545ea6d39c1ed..e842065a4b52e 100644 --- a/tests/unit/services/sns/test_sns.py +++ b/tests/unit/services/sns/test_sns.py @@ -13,11 +13,11 @@ from localstack.services.sns.provider import ( encode_subscription_token_with_region, get_region_from_subscription_token, - is_raw_message_delivery, ) from localstack.services.sns.publisher import ( compute_canonical_string, create_sns_message_body, + is_raw_message_delivery, ) from localstack.utils.time import timestamp_millis diff --git a/tests/unit/services/sqs/test_sqs.py b/tests/unit/services/sqs/test_sqs.py index 47232c8cf29e1..1515d6d8507df 100644 --- a/tests/unit/services/sqs/test_sqs.py +++ b/tests/unit/services/sqs/test_sqs.py @@ -4,8 +4,8 @@ import localstack.services.sqs.models from localstack.services.sqs import provider from localstack.services.sqs.constants import DEFAULT_MAXIMUM_MESSAGE_SIZE -from localstack.services.sqs.provider import _create_message_attribute_hash from localstack.services.sqs.utils import ( + create_message_attribute_hash, guess_endpoint_strategy_and_host, is_sqs_queue_url, parse_queue_url, @@ -20,18 +20,18 @@ def test_sqs_message_attrs_md5(): "DataType": "Number", } } - md5 = _create_message_attribute_hash(msg_attrs) + md5 = create_message_attribute_hash(msg_attrs) assert md5 == "235c5c510d26fb653d073faed50ae77c" def test_convert_non_printable_chars(): - string = "invalid characters - %s %s %s" % (chr(8), chr(11), chr(12)) + string = f"invalid characters - {chr(8)} {chr(11)} {chr(12)}" result = convert_to_printable_chars(string) assert result == "invalid characters - " result = convert_to_printable_chars({"foo": [string]}) assert result == {"foo": ["invalid characters - "]} - string = "valid characters - %s %s %s %s" % (chr(9), chr(10), chr(13), chr(32)) + string = f"valid characters - {chr(9)} {chr(10)} {chr(13)} {chr(32)}" result = convert_to_printable_chars(string) assert result == string diff --git a/tests/unit/services/stepfunctions/test_jsonata_variable_references.py b/tests/unit/services/stepfunctions/test_jsonata_variable_references.py new file mode 100644 index 0000000000000..e991443853800 --- /dev/null +++ b/tests/unit/services/stepfunctions/test_jsonata_variable_references.py @@ -0,0 +1,84 @@ +"""Unit tests for JSONata variable reference extraction. + +Regression tests for https://github.com/localstack/localstack/issues/13579 +""" + +import pytest + +from localstack.services.stepfunctions.asl.jsonata.jsonata import ( + IllegalJSONataVariableReference, + extract_jsonata_variable_references, +) + + +class TestExtractJsonataVariableReferences: + def test_simple_variable(self): + refs = extract_jsonata_variable_references("$states.input.foo") + assert refs == {"$states.input.foo"} + + def test_multiple_variables(self): + refs = extract_jsonata_variable_references("$states.input.part1 + $states.input.part2") + assert refs == {"$states.input.part1", "$states.input.part2"} + + def test_variables_inside_array_literal(self): + """Regression test for #13579: $merge with dynamic args returns {}.""" + refs = extract_jsonata_variable_references( + "$merge([$states.input.part1, $states.input.part2])" + ) + assert refs == {"$merge", "$states.input.part1", "$states.input.part2"} + + def test_variables_inside_nested_brackets(self): + refs = extract_jsonata_variable_references("$append([$a, $b], [$c])") + assert refs == {"$append", "$a", "$b", "$c"} + + def test_variable_with_bracket_field_access(self): + """String literals inside brackets should be skipped by branch 2.""" + refs = extract_jsonata_variable_references('$states.input["field-name"]') + assert refs == {"$states.input"} + + def test_variable_inside_string_literal_ignored(self): + refs = extract_jsonata_variable_references('"$notavar"') + assert refs == set() + + def test_variable_inside_single_quote_string_ignored(self): + refs = extract_jsonata_variable_references("'$notavar'") + assert refs == set() + + def test_variable_inside_regex_literal_ignored(self): + refs = extract_jsonata_variable_references("/\\$pattern/i") + assert refs == set() + + def test_empty_expression(self): + refs = extract_jsonata_variable_references("") + assert refs == set() + + def test_no_variables(self): + refs = extract_jsonata_variable_references("1 + 2") + assert refs == set() + + def test_lone_dollar_ignored(self): + """Bare $ is the JSONata context variable (e.g. in filter predicates + like [$ = 1]) — it should not be extracted as a variable reference.""" + refs = extract_jsonata_variable_references("$") + assert refs == set() + + def test_double_dollar_raises(self): + """$$ is the JSONata root input reference — it is captured by the regex + but rejected as an illegal variable reference.""" + with pytest.raises(IllegalJSONataVariableReference): + extract_jsonata_variable_references("$$") + + def test_filter_expression_with_variable(self): + refs = extract_jsonata_variable_references("$data[$type]") + assert refs == {"$data", "$type"} + + def test_filter_predicate_with_context_variable(self): + """Filter predicates using bare $ (context variable) should not cause + errors or be extracted. Regression test for MAP_TASK_STATE expressions + like $count($states.result[$ = 1]).""" + refs = extract_jsonata_variable_references("$count($states.result[$ = 1])") + assert refs == {"$count", "$states.result"} + + def test_filter_predicate_bare_dollar_equals_zero(self): + refs = extract_jsonata_variable_references("$states.result[$ = 0]") + assert refs == {"$states.result"} diff --git a/tests/unit/state/test_pickle.py b/tests/unit/state/test_pickle.py index 42167e936a13d..2c406c368d1bb 100644 --- a/tests/unit/state/test_pickle.py +++ b/tests/unit/state/test_pickle.py @@ -26,8 +26,7 @@ def __init__(self, n: int): self.gen = self._count() def _count(self): - for i in range(self.n): - yield i + yield from range(self.n) class SubclassWithGenerator(ClassWithGenerator): diff --git a/tests/unit/test_common.py b/tests/unit/test_common.py index 2988ecaff64b8..6ed89d8cc4e60 100644 --- a/tests/unit/test_common.py +++ b/tests/unit/test_common.py @@ -2,10 +2,9 @@ import io import itertools import os -import threading import time import zipfile -from datetime import date, datetime, timezone +from datetime import UTC, date, datetime, timedelta from zoneinfo import ZoneInfo import pytest @@ -46,6 +45,23 @@ def test_isoformat_milliseconds(self): env = common.isoformat_milliseconds(datetime(2010, 3, 20, 7, 24, 00, 0)) assert env == "2010-03-20T07:24:00.000" + @pytest.mark.parametrize( + "str_format", + [ + "2025-09-18T14:07:30", + "2025-09-18T14:07:30Z", + "2025-09-18T14:07:30.70300Z", + "18/Sep/2025:14:07:30 +0000", + ], + ) + def test_parse_timestamp_timezone_aware(self, str_format): + datetime_obj = common.parse_timestamp(str_format) + # we cannot assert that tzinfo is `datetime.UTC` because it is only supported starting Python 3.11 + # so we assert manually that the returned `tzinfo` is UTC + # we are using ZoneInfo("UTC") in the `parse_timestamp` utility as it is in the import path of the CLI + assert datetime_obj.tzinfo is not None + assert datetime_obj.utcoffset() == timedelta(0) + def test_base64_to_hex(self): env = common.base64_to_hex("Zm9vIGJhcg ==") assert env == b"666f6f20626172" @@ -75,11 +91,23 @@ def test_now(self): def test_now_utc(self): env = common.now_utc() - test = datetime.now(timezone.utc).timestamp() + test = datetime.now(UTC).timestamp() assert test == pytest.approx(env, 1) - def test_is_number(self): - assert common.is_number(5) + @pytest.mark.parametrize( + "value,is_number", + [ + (5, True), + (-12.1, True), + (2e15, True), + ("test", False), + (False, False), + (True, False), + (None, False), + ], + ) + def test_is_number(self, value, is_number): + assert common.is_number(value) == is_number def test_is_ip_address(self): assert common.is_ip_address("10.0.0.1") @@ -94,7 +122,7 @@ def test_mktime(self): def test_mktime_with_tz(self): # see https://en.wikipedia.org/wiki/File:1000000000seconds.jpg - dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=timezone.utc) + dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=UTC) assert int(common.mktime(dt)) == 1000000000 dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=ZoneInfo("EST")) @@ -102,7 +130,7 @@ def test_mktime_with_tz(self): def test_mktime_millis_with_tz(self): # see https://en.wikipedia.org/wiki/File:1000000000 - dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=timezone.utc) + dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=UTC) assert int(common.mktime(dt, millis=True) / 1000) == 1000000000 dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=ZoneInfo("EST")) @@ -190,7 +218,7 @@ def test_retry(self): def fn(): i = next(count) - e = RuntimeError("exception %d" % i) + e = RuntimeError(f"exception {i:d}") exceptions.append(e) if i == 2: @@ -208,7 +236,7 @@ def test_retry_raises_last_exception(self): def fn(): i = next(count) - e = RuntimeError("exception %d" % i) + e = RuntimeError(f"exception {i:d}") exceptions.append(e) raise e @@ -334,24 +362,6 @@ def test_format_number(self): assert fn(-12.521, decimals=4) == "-12.521" assert fn(-1.2234354123e3, decimals=4) == "-1223.4354" - def test_cleanup_threads_and_processes_calls_shutdown_hooks(self): - # TODO: move all run/concurrency related tests into separate class - - started = threading.Event() - done = threading.Event() - - def run_method(*args, **kwargs): - started.set() - func_thread = kwargs["_thread"] - # thread waits until it is stopped - func_thread._stop_event.wait() - done.set() - - common.start_thread(run_method) - assert started.wait(timeout=2) - common.cleanup_threads_and_processes() - assert done.wait(timeout=2) - def test_proxy_map(self): old_http_proxy = config.OUTBOUND_HTTP_PROXY old_https_proxy = config.OUTBOUND_HTTPS_PROXY @@ -507,9 +517,9 @@ def test_unzip_bad_crc(self): def test_save_load_file(tmp_path): - file_name = tmp_path / ("normal_permissions_%s" % short_uid()) - content = "some_content_%s" % short_uid() - more_content = "some_more_content_%s" % short_uid() + file_name = tmp_path / (f"normal_permissions_{short_uid()}") + content = f"some_content_{short_uid()}" + more_content = f"some_more_content_{short_uid()}" save_file(file_name, content) assert content == load_file(file_name) @@ -517,10 +527,19 @@ def test_save_load_file(tmp_path): assert content + more_content == load_file(file_name) +def test_load_file_strict(tmp_path): + file_name = tmp_path / short_uid() + assert not os.path.isfile(file_name) + + assert load_file(file_name) is None + with pytest.raises(FileNotFoundError): + load_file(file_name, strict=True) + + def test_save_load_file_with_permissions(tmp_path): - file_name = tmp_path / ("special_permissions_%s" % short_uid()) - content = "some_content_%s" % short_uid() - more_content = "some_more_content_%s" % short_uid() + file_name = tmp_path / (f"special_permissions_{short_uid()}") + content = f"some_content_{short_uid()}" + more_content = f"some_more_content_{short_uid()}" permissions = 0o600 save_file(file_name, content, permissions=permissions) @@ -532,9 +551,9 @@ def test_save_load_file_with_permissions(tmp_path): def test_save_load_file_with_changing_permissions(tmp_path): - file_name = tmp_path / ("changing_permissions_%s" % short_uid()) - content = "some_content_%s" % short_uid() - more_content = "some_more_content_%s" % short_uid() + file_name = tmp_path / (f"changing_permissions_{short_uid()}") + content = f"some_content_{short_uid()}" + more_content = f"some_more_content_{short_uid()}" permissions = 0o600 save_file(file_name, content) diff --git a/tests/unit/test_dockerclient.py b/tests/unit/test_dockerclient.py index 03f9e5cdc63a8..7e18bcd1af1c0 100644 --- a/tests/unit/test_dockerclient.py +++ b/tests/unit/test_dockerclient.py @@ -1,7 +1,6 @@ import json import logging import textwrap -from typing import List from unittest.mock import patch import pytest @@ -14,6 +13,7 @@ PortMappings, Ulimit, Util, + get_registry_from_image_name, ) from localstack.utils.container_utils.docker_cmd_client import CmdDockerClient @@ -21,7 +21,7 @@ class TestDockerClient: - def _docker_cmd(self) -> List[str]: + def _docker_cmd(self) -> list[str]: """Return the string to be used for running Docker commands.""" return config.DOCKER_CMD.split() @@ -307,7 +307,7 @@ def test_compose_env_files(self, tmp_path): def list_in(a, b): - return len(a) <= len(b) and any((b[x : x + len(a)] == a for x in range(len(b) - len(a) + 1))) + return len(a) <= len(b) and any(b[x : x + len(a)] == a for x in range(len(b) - len(a) + 1)) class TestPortMappings: @@ -389,3 +389,64 @@ def test_adjacent_port_to_many_to_one(self): } result = port_mappings.to_dict() assert result == expected_result + + +class TestGetRegistryFromImageName: + """Tests for the get_registry_from_image_name utility function.""" + + @pytest.mark.parametrize( + "image_name,expected", + [ + # Docker Hub images (no explicit registry) + ("nginx", "docker.io"), + ("nginx:latest", "docker.io"), + ("localstack/localstack", "docker.io"), + ("localstack/localstack:latest", "docker.io"), + # Images with explicit registry + ("private-registry.com/localstack/localstack", "private-registry.com"), + ("private-registry.com/localstack", "private-registry.com"), + ("registry.example.com/my-image:v1.0", "registry.example.com"), + # Localhost registries + ("localhost:5000/myimage", "localhost:5000"), + ("localhost/myimage", "localhost"), + # Docker Hub with explicit domain + ("docker.io/nginx", "docker.io"), + ("docker.io/nginx:latest", "docker.io"), + # AWS ECR + ( + "123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo", + "123456789.dkr.ecr.us-east-1.amazonaws.com", + ), + ( + "123456789.dkr.ecr.us-east-1.amazonaws.com/my-repo:tag", + "123456789.dkr.ecr.us-east-1.amazonaws.com", + ), + # Google Container Registry + ("gcr.io/my-project/my-image", "gcr.io"), + ("gcr.io/my-project/my-image:latest", "gcr.io"), + # Registry with port + ("registry.example.com:5000/image", "registry.example.com:5000"), + ("registry.example.com:443/image:tag", "registry.example.com:443"), + # LocalStack ECR + ( + "000000000000.dkr.ecr.us-east-1.localhost.localstack.cloud:4566/repo", + "000000000000.dkr.ecr.us-east-1.localhost.localstack.cloud:4566", + ), + ], + ) + def test_extract_registry_from_various_image_formats(self, image_name, expected): + """Test extracting registry from various Docker image name formats.""" + assert get_registry_from_image_name(image_name) == expected + + @pytest.mark.parametrize( + "prefix,image_name,expected", + [ + ("my-mirror.example.com", "nginx:latest", "my-mirror.example.com"), + ("my-mirror.example.com", "docker.io/nginx:latest", "my-mirror.example.com"), + ("my-mirror.example.com", "registry.example.com/image", "my-mirror.example.com"), + ("harbor.example.com", "localhost:5000/myimage", "harbor.example.com"), + ], + ) + def test_with_docker_global_image_prefix(self, image_name, expected, prefix, monkeypatch): + monkeypatch.setattr(config, "DOCKER_GLOBAL_IMAGE_PREFIX", prefix) + assert get_registry_from_image_name(image_name) == expected diff --git a/tests/unit/test_edge.py b/tests/unit/test_edge.py index 09eb91c580e46..14b2b07e18c4f 100644 --- a/tests/unit/test_edge.py +++ b/tests/unit/test_edge.py @@ -1,5 +1,3 @@ -from typing import List - import pytest import requests from pytest_httpserver.httpserver import HTTPServer @@ -9,7 +7,7 @@ from localstack.utils.net import get_free_tcp_port -def gateway_listen_value(httpserver: HTTPServer) -> List[HostAndPort]: +def gateway_listen_value(httpserver: HTTPServer) -> list[HostAndPort]: return [HostAndPort(host=httpserver.host, port=httpserver.port)] diff --git a/tests/unit/test_stores.py b/tests/unit/test_stores.py index 307c4f3b70f52..b94b13439cc89 100644 --- a/tests/unit/test_stores.py +++ b/tests/unit/test_stores.py @@ -86,10 +86,12 @@ def test_store_namespacing(self, sample_stores): backend1_eu = sample_stores[account1][eu_region] assert backend1_eu._account_id == account1 assert backend1_eu._region_name == eu_region + assert backend1_eu._service_name == "zzz" backend1_ap = sample_stores[account1][ap_region] assert backend1_ap._account_id == account1 assert backend1_ap._region_name == ap_region + assert backend1_ap._service_name == "zzz" # Ensure region-specific data isolation backend1_eu.region_specific_attr.extend([1, 2, 3]) diff --git a/tests/unit/test_tagging.py b/tests/unit/test_tagging.py index 876fa15753485..fc0870cbd84f9 100644 --- a/tests/unit/test_tagging.py +++ b/tests/unit/test_tagging.py @@ -1,6 +1,7 @@ import pytest -from localstack.utils.tagging import TaggingService +from localstack.utils.strings import short_uid +from localstack.utils.tagging import TaggingService, Tags class TestTaggingService: @@ -45,3 +46,62 @@ def test_field_name_override(self, tagging_service): assert svc.list_tags_for_resource("arn") == { "Tags": [{"keY": "my", "valuE": "congratulations"}] } + + +@pytest.fixture +def mock_arn(): + return f"arn-{short_uid()}" + + +@pytest.fixture +def tags_collection(): + return Tags() + + +class TestTagsCollection: + def test_update_tags(self, tags_collection, mock_arn): + # Ensure the tags which existed / didn't exist before are updated accordingly. + tags_collection.update_tags(mock_arn, {"Environment": "Production", "Foo": "Bar"}) + tags = tags_collection.get_tags(mock_arn) + assert "Foo" in tags + assert tags["Foo"] == "Bar" + assert tags["Environment"] == "Production" + + def test_get_tags(self, tags_collection, mock_arn): + non_existent_resource_tags = tags_collection.get_tags("bad-arn") + assert len(non_existent_resource_tags) == 0 + + def test_delete_tags(self, tags_collection, mock_arn): + tags_collection.update_tags(mock_arn, {"Foo": "Bar"}) + + # Test deleting the same key twice even when it's not in the tag mapping. This should not raise. + for _ in range(2): + tags_collection.delete_tags(mock_arn, ["Environment"]) + tags = tags_collection.get_tags(mock_arn) + assert "Foo" in tags + assert len(tags) == 1 + + tags_collection.delete_tags(mock_arn, ["Foo"]) + tags = tags_collection.get_tags(mock_arn) + assert len(tags) == 0 + + # This operation shouldn't raise if the ARN is not in the tagging store. + non_existent_arn = f"non-existent-{short_uid()}" + tags_collection.delete_tags(non_existent_arn, ["Foo"]) + tags = tags_collection.get_tags(non_existent_arn) + assert len(tags) == 0 + + def test_delete_all_tags(self, tags_collection, mock_arn): + tags_collection.update_tags(mock_arn, {"Foo": "Bar", "Environment": "Testing"}) + tags = tags_collection.get_tags(mock_arn) + assert len(tags) == 2 + + tags_collection.delete_all_tags(mock_arn) + updated_tags = tags_collection.get_tags(mock_arn) + assert len(updated_tags) == 0 + + # Delete all tags should not raise if the ARN is not in the store + non_existent_arn = f"non-existent-{short_uid()}" + tags_collection.delete_all_tags(non_existent_arn) + tags = tags_collection.get_tags(non_existent_arn) + assert len(tags) == 0 diff --git a/tests/unit/utils/analytics/test_metrics.py b/tests/unit/utils/analytics/test_metrics.py index 1695aeea340d7..781a2083d8270 100644 --- a/tests/unit/utils/analytics/test_metrics.py +++ b/tests/unit/utils/analytics/test_metrics.py @@ -31,7 +31,7 @@ def test_counter_reset(): counter.increment(value=5) counter.reset() collected = counter.collect() - assert collected == list(), f"Unexpected counter value: expected 0, got {collected}" + assert collected == [], f"Unexpected counter value: expected 0, got {collected}" def test_labeled_counter_increment(): diff --git a/tests/unit/utils/analytics/test_publisher.py b/tests/unit/utils/analytics/test_publisher.py index a74096b3f256d..d7f3bde593c9f 100644 --- a/tests/unit/utils/analytics/test_publisher.py +++ b/tests/unit/utils/analytics/test_publisher.py @@ -1,7 +1,4 @@ import datetime -import threading -from queue import Queue -from typing import List import pytest @@ -9,7 +6,6 @@ from localstack.utils.analytics.client import AnalyticsClient from localstack.utils.analytics.events import Event, EventMetadata from localstack.utils.analytics.metadata import get_session_id -from localstack.utils.analytics.publisher import Publisher, PublisherBuffer from localstack.utils.sync import retry @@ -21,76 +17,6 @@ def new_event(payload=None) -> Event: ) -class TestPublisherBuffer: - def test_basic(self): - calls = Queue() - - class QueuePublisher(Publisher): - def publish(self, _events: List[Event]): - calls.put(_events) - - buffer = PublisherBuffer(QueuePublisher(), flush_size=2, flush_interval=1000) - - t = threading.Thread(target=buffer.run) - t.start() - - try: - e1 = new_event() - e2 = new_event() - e3 = new_event() - - buffer.handle(e1) - buffer.handle(e2) - - c1 = calls.get(timeout=2) - assert len(c1) == 2 - - buffer.handle(e3) # should flush after close despite flush_size = 2 - finally: - buffer.close() - - c2 = calls.get(timeout=2) - assert len(c2) == 1 - - assert c1[0] == e1 - assert c1[1] == e2 - assert c2[0] == e3 - - t.join(10) - - def test_interval(self): - calls = Queue() - - class QueuePublisher(Publisher): - def publish(self, _events: List[Event]): - calls.put(_events) - - buffer = PublisherBuffer(QueuePublisher(), flush_size=10, flush_interval=1) - - t = threading.Thread(target=buffer.run) - t.start() - - try: - e1 = new_event() - e2 = new_event() - e3 = new_event() - e4 = new_event() - - buffer.handle(e1) - buffer.handle(e2) - c1 = calls.get(timeout=2) - - buffer.handle(e3) - buffer.handle(e4) - c2 = calls.get(timeout=2) - finally: - buffer.close() - - assert len(c1) == 2 - assert len(c2) == 2 - t.join(10) - - class TestGlobalAnalyticsBus: def test(self, httpserver): httpserver.expect_request("/v0/session").respond_with_json({"track_events": True}) diff --git a/tests/unit/utils/analytics/test_service_call_aggregator.py b/tests/unit/utils/analytics/test_service_call_aggregator.py index 765570a19a2f9..6fcb9c2d79164 100644 --- a/tests/unit/utils/analytics/test_service_call_aggregator.py +++ b/tests/unit/utils/analytics/test_service_call_aggregator.py @@ -1,6 +1,5 @@ import time from queue import Queue -from typing import List import dateutil.parser import pytest @@ -71,7 +70,7 @@ def mock_emit_payload(_payload): def test_integration(monkeypatch): - events: List[Event] = [] + events: list[Event] = [] def _handle(_event: Event): events.append(_event) diff --git a/tests/unit/utils/aws/test_arns.py b/tests/unit/utils/aws/test_arns.py new file mode 100644 index 0000000000000..17c0c4409e718 --- /dev/null +++ b/tests/unit/utils/aws/test_arns.py @@ -0,0 +1,19 @@ +from localstack.utils.aws import arns + + +def test_arn_creation_with_colon_names(): + region_name = "us-east-1" + account_id = "123456789012" + name = "noColons" + name_colon = "col:on" + pattern = "arn:%s::%s:%s:thing/%s" + + assert ( + arns._resource_arn(name, pattern, account_id, region_name) + == f"arn:aws::{region_name}:{account_id}:thing/{name}" + ) + assert arns._resource_arn(name_colon, pattern, account_id, region_name) == name_colon + assert ( + arns._resource_arn(name_colon, pattern, account_id, region_name, True) + == f"arn:aws::{region_name:}:{account_id}:thing/{name_colon}" + ) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/__init__.py b/tests/unit/utils/catalog/__init__.py similarity index 100% rename from tests/aws/services/cloudformation/v2/ported_from_v1/resources/__init__.py rename to tests/unit/utils/catalog/__init__.py diff --git a/tests/unit/utils/catalog/conftest.py b/tests/unit/utils/catalog/conftest.py new file mode 100644 index 0000000000000..738e9b1214211 --- /dev/null +++ b/tests/unit/utils/catalog/conftest.py @@ -0,0 +1,38 @@ +from localstack.utils.catalog.common import AwsRemoteCatalog, LocalStackMetadata + +CATALOG = AwsRemoteCatalog( + schema_version="v1", + localstack=LocalStackMetadata(version="4.7"), + services={ + "athena": { + "pro": { + "provider": "athena:pro", + "operations": ["StartQueryExecution", "GetQueryExecution"], + "plans": ["ultimate", "enterprise"], + } + }, + "s3": { + "community": { + "provider": "s3:default", + "operations": ["CreateBucket"], + "plans": ["free", "base", "ultimate", "enterprise"], + }, + "pro": { + "provider": "s3:pro", + "operations": ["SelectObjectContent"], + "plans": ["base", "ultimate", "enterprise"], + }, + }, + "kms": { + "community": { + "provider": "kms:default", + "operations": ["ListKeys"], + "plans": ["free", "base", "ultimate", "enterprise"], + } + }, + }, + cloudformation_resources={ + "community": {"AWS::S3::Bucket": {"methods": ["Create", "Delete"]}}, + "pro": {"AWS::Athena::CapacitiesReservation": {"methods": ["Create", "Update", "Delete"]}}, + }, +) diff --git a/tests/unit/utils/catalog/test_catalog.py b/tests/unit/utils/catalog/test_catalog.py new file mode 100644 index 0000000000000..44fdc59410d6a --- /dev/null +++ b/tests/unit/utils/catalog/test_catalog.py @@ -0,0 +1,155 @@ +import pytest + +from localstack.utils.catalog.catalog import AwsCatalogRemoteStatePlugin +from localstack.utils.catalog.catalog_loader import RemoteCatalogLoader +from localstack.utils.catalog.common import ( + AwsRemoteCatalog, + AwsServiceCatalog, + AwsServiceOperationsSupportInLatest, + AwsServicesSupportInLatest, + CloudFormationResource, + CloudFormationResourcesSupportAtRuntime, + CloudFormationResourcesSupportInLatest, + LocalstackEmulatorType, + LocalStackMetadata, +) + + +class FakeCatalogLoader(RemoteCatalogLoader): + def __init__(self, catalog: AwsRemoteCatalog): + self.catalog = catalog + + def get_remote_catalog(self) -> AwsRemoteCatalog: + return self.catalog + + +CATALOG = AwsRemoteCatalog( + schema_version="1.0", + localstack=LocalStackMetadata(version="4.7"), + services={ + "athena": { + "pro": AwsServiceCatalog( + provider="athena:pro", + operations=["StartQueryExecution", "GetQueryExecution"], + plans=["ultimate", "enterprise"], + ) + }, + "s3": { + "community": AwsServiceCatalog( + provider="s3:default", + operations=["CreateBucket"], + plans=["free", "base", "ultimate", "enterprise"], + ), + "pro": AwsServiceCatalog( + provider="s3:pro", + operations=["SelectObjectContent"], + plans=["base", "ultimate", "enterprise"], + ), + }, + "kms": { + "community": AwsServiceCatalog( + provider="kms:default", + operations=["ListKeys"], + plans=["free", "base", "ultimate", "enterprise"], + ) + }, + }, + cloudformation_resources={ + "community": { + "AWS::S3::Bucket": CloudFormationResource(methods=["Create", "Delete"]), + "AWS::KMS::Key": CloudFormationResource(methods=["Create", "Delete"]), + }, + "pro": { + "AWS::Athena::CapacitiesReservation": CloudFormationResource( + methods=["Create", "Update", "Delete"] + ), + }, + }, +) + + +@pytest.fixture(scope="class", autouse=True) +def aws_catalog_with_static_data(): + return AwsCatalogRemoteStatePlugin(FakeCatalogLoader(CATALOG)) + + +class TestAwsCatalog: + @pytest.mark.parametrize( + "service_name,expected_status", + [ + ("s3", AwsServicesSupportInLatest.SUPPORTED), + ("athena", AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE), + ("nonexistent", AwsServicesSupportInLatest.NOT_SUPPORTED), + ], + ) + def test_get_service_status(self, aws_catalog_with_static_data, service_name, expected_status): + result = aws_catalog_with_static_data.get_aws_service_status(service_name) + assert result == expected_status + + @pytest.mark.parametrize( + "service_name,operation_name,expected_status", + [ + ("kms", "ListKeys", AwsServiceOperationsSupportInLatest.SUPPORTED), + ( + "s3", + "SelectObjectContent", + AwsServiceOperationsSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE, + ), + ("s3", "UnsupportedOp", AwsServiceOperationsSupportInLatest.NOT_SUPPORTED), + ], + ) + def test_get_service_status_with_operation( + self, aws_catalog_with_static_data, service_name, operation_name, expected_status + ): + result = aws_catalog_with_static_data.get_aws_service_status(service_name, operation_name) + assert result == expected_status + + def test_get_service_status_with_only_one_emulator_type(self, aws_catalog_with_static_data): + result = aws_catalog_with_static_data.get_aws_service_status("athena") + assert result == AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE + + def test_get_service_status_with_empty_operation(self, aws_catalog_with_static_data): + assert ( + aws_catalog_with_static_data.get_aws_service_status("s3", None) + == AwsServicesSupportInLatest.SUPPORTED + ) + assert ( + aws_catalog_with_static_data.get_aws_service_status("s3", "") + == AwsServiceOperationsSupportInLatest.SUPPORTED + ) + + @pytest.mark.parametrize( + "resource_name,service_name,expected_status", + [ + ("AWS::S3::Bucket", "s3", CloudFormationResourcesSupportAtRuntime.AVAILABLE), + ("AWS::S3::NonExistent", "s3", CloudFormationResourcesSupportInLatest.NOT_SUPPORTED), + ( + "AWS::Athena::CapacitiesReservation", + "athena", + AwsServicesSupportInLatest.SUPPORTED_WITH_LICENSE_UPGRADE, + ), + ( + "AWS::NonExistentService::NonExistent", + "nonexistentservice", + AwsServicesSupportInLatest.NOT_SUPPORTED, + ), + ], + ) + def test_get_cfn_resource_status( + self, aws_catalog_with_static_data, resource_name, service_name, expected_status + ): + result = aws_catalog_with_static_data.get_cloudformation_resource_status( + resource_name, service_name + ) + assert result == expected_status + + def test_build_cfn_catalog_resources(self, aws_catalog_with_static_data): + community_resources = aws_catalog_with_static_data.cfn_resources_in_latest[ + LocalstackEmulatorType.COMMUNITY + ] + assert set(community_resources) == {"AWS::S3::Bucket", "AWS::KMS::Key"} + + pro_resources = aws_catalog_with_static_data.cfn_resources_in_latest[ + LocalstackEmulatorType.PRO + ] + assert set(pro_resources) == {"AWS::Athena::CapacitiesReservation"} diff --git a/tests/unit/utils/catalog/test_catalog_loader.py b/tests/unit/utils/catalog/test_catalog_loader.py new file mode 100644 index 0000000000000..4648a5e7b0df3 --- /dev/null +++ b/tests/unit/utils/catalog/test_catalog_loader.py @@ -0,0 +1,42 @@ +import json + +import pydantic +import pytest + +from localstack.utils.catalog.catalog_loader import AwsCatalogLoaderException, RemoteCatalogLoader +from localstack.utils.catalog.common import AwsRemoteCatalog +from localstack.utils.json import FileMappedDocument +from tests.unit.utils.catalog.conftest import CATALOG + + +class TestCatalogLoader: + def test_parse_valid_catalog(self): + catalog_loader = RemoteCatalogLoader() + assert catalog_loader._parse_catalog(CATALOG.json().encode()) == CATALOG + + def test_parse_catalog_with_missing_key(self): + catalog_loader = RemoteCatalogLoader() + catalog = CATALOG.dict() + catalog.pop("localstack") + with pytest.raises(pydantic.ValidationError): + catalog_loader._parse_catalog(json.dumps(catalog).encode()) + + def test_parse_catalog_with_invalid_json(self): + with pytest.raises(AwsCatalogLoaderException, match="Could not de-serialize json catalog"): + RemoteCatalogLoader()._parse_catalog(b'{"invalid": json content') + + def test_save_catalog_to_cache(self, tmp_path): + path = tmp_path / "test_catalog.json" + catalog_doc = FileMappedDocument(path) + catalog_loader = RemoteCatalogLoader() + catalog_doc.update( + CATALOG.model_dump() | {"localstack": {"version": "v1.1"}, "key1": "value1"} + ) + + assert "key1" in catalog_doc + assert catalog_doc["localstack"]["version"] == "v1.1" + + catalog_loader._save_catalog_to_cache(catalog_doc, CATALOG) + + catalog_doc.load() + assert AwsRemoteCatalog(**catalog_doc) == CATALOG diff --git a/tests/unit/utils/test_batch_policy.py b/tests/unit/utils/test_batching.py similarity index 77% rename from tests/unit/utils/test_batch_policy.py rename to tests/unit/utils/test_batching.py index e93fd594ded1d..5b3fc1ddfdcaa 100644 --- a/tests/unit/utils/test_batch_policy.py +++ b/tests/unit/utils/test_batching.py @@ -1,8 +1,10 @@ +import threading import time +from queue import Queue import pytest -from localstack.utils.batch_policy import Batcher +from localstack.utils.batching import AsyncBatcher, Batcher class SimpleItem: @@ -25,7 +27,7 @@ def test_add_single_item(self): assert result == ["item1", "item2"] assert batcher.get_current_size() == 0 - def test_add_multple_items(self): + def test_add_multiple_items(self): batcher = Batcher(max_count=3) assert not batcher.add(["item1", "item2"]) @@ -188,3 +190,71 @@ def test_deep_copy(self): batch = batcher.flush() assert batch[0]["key"] == "value" + + +class TestAsyncBatcher: + def test_basic(self): + calls = Queue() + + def collect(_batch: list): + calls.put(_batch) + + buffer = AsyncBatcher(collect, max_batch_size=2, max_flush_interval=1000) + + t = threading.Thread(target=buffer.run) + t.start() + + try: + e1 = "e1" + e2 = "e2" + e3 = "e3" + + buffer.add(e1) + buffer.add(e2) + + c1 = calls.get(timeout=2) + assert len(c1) == 2 + + buffer.add(e3) # should flush after close despite flush_size = 2 + finally: + buffer.close() + + c2 = calls.get(timeout=2) + assert len(c2) == 1 + + assert c1[0] == e1 + assert c1[1] == e2 + assert c2[0] == e3 + + t.join(10) + + def test_interval(self): + calls = Queue() + + def collect(_batch: list): + calls.put(_batch) + + buffer = AsyncBatcher(collect, max_batch_size=10, max_flush_interval=1) + + t = threading.Thread(target=buffer.run) + t.start() + + try: + e1 = "e1" + e2 = "e2" + e3 = "e3" + e4 = "e4" + + buffer.add(e1) + buffer.add(e2) + c1 = calls.get(timeout=2) + + buffer.add(e3) + buffer.add(e4) + c2 = calls.get(timeout=2) + finally: + buffer.close() + + assert len(c1) == 2 + assert len(c2) == 2 + t.join(10) diff --git a/tests/unit/utils/test_bootstrap.py b/tests/unit/utils/test_bootstrap.py index 9ff4d2e5fc8d8..b5f5a94ac07d0 100644 --- a/tests/unit/utils/test_bootstrap.py +++ b/tests/unit/utils/test_bootstrap.py @@ -1,6 +1,6 @@ import os from contextlib import contextmanager -from typing import Any, Dict +from typing import Any import pytest @@ -16,7 +16,7 @@ @contextmanager -def temporary_env(env: Dict[str, Any]): +def temporary_env(env: dict[str, Any]): old = os.environ.copy() try: os.environ.update(env) diff --git a/tests/unit/utils/test_collections.py b/tests/unit/utils/test_collections.py index adb2581e77460..46ef48873455f 100644 --- a/tests/unit/utils/test_collections.py +++ b/tests/unit/utils/test_collections.py @@ -9,13 +9,14 @@ ImmutableList, convert_to_typed_dict, is_comma_delimited_list, + optional_list, select_from_typed_dict, ) class MyTypeDict(TypedDict): key_one: str - key_optional: Optional[str] + key_optional: str | None def test_select_from_typed_dict(): @@ -42,7 +43,7 @@ def test_immutable_dict(): d1 = ImmutableDict({"a": ["b"], "c": 1}) assert dict(d1) == {"a": ["b"], "c": 1} - assert {k for k in d1} == {"a", "c"} + assert set(d1) == {"a", "c"} assert d1["a"] == ["b"] assert d1["c"] == 1 assert len(d1) == 2 @@ -129,7 +130,7 @@ class TestTypedDict(TypedDict): def test_convert_to_typed_dict_with_union(): class TestTypedDict(TypedDict): - union_member: Union[str, int] + union_member: Union[str, int] # noqa test_dict = {"union_member": 1} @@ -140,7 +141,7 @@ class TestTypedDict(TypedDict): def test_convert_to_typed_dict_with_optional(): class TestTypedDict(TypedDict): - optional_member: Optional[str] + optional_member: Optional[str] # noqa test_dict = {"optional_member": 1} @@ -193,3 +194,16 @@ def test_is_comma_limited_list(): assert not is_comma_delimited_list("foo, bar baz") assert not is_comma_delimited_list("foo,") assert not is_comma_delimited_list("") + + +@pytest.mark.parametrize( + "condition,input,expected", + [ + (True, [1, 2, 3], [1, 2, 3]), + (False, [1, 2, 3], []), + (True, [], []), + (False, [], []), + ], +) +def test_optional_list(condition, input, expected): + assert optional_list(condition, input) == expected diff --git a/tests/unit/utils/test_coverage_docs.py b/tests/unit/utils/test_coverage_docs.py deleted file mode 100644 index b21442736295a..0000000000000 --- a/tests/unit/utils/test_coverage_docs.py +++ /dev/null @@ -1,19 +0,0 @@ -from localstack.utils.coverage_docs import get_coverage_link_for_service - - -def test_coverage_link_for_existing_service(): - coverage_link = get_coverage_link_for_service("s3", "random_action") - assert coverage_link == ( - "The API action 'random_action' for service 's3' is either not available in your current " - "license plan or has not yet been emulated by LocalStack. " - "Please refer to https://docs.localstack.cloud/references/coverage/coverage_s3 for more information." - ) - - -def test_coverage_link_for_non_existing_service(): - coverage_link = get_coverage_link_for_service("dummy_service", "random_action") - assert coverage_link == ( - "The API for service 'dummy_service' is either not included in your current license plan or " - "has not yet been emulated by LocalStack. " - "Please refer to https://docs.localstack.cloud/references/coverage for more details." - ) diff --git a/tests/unit/utils/test_event_matcher.py b/tests/unit/utils/test_event_matcher.py index 436aa1b7c15be..88a2a13759206 100644 --- a/tests/unit/utils/test_event_matcher.py +++ b/tests/unit/utils/test_event_matcher.py @@ -2,7 +2,6 @@ import pytest -from localstack import config from localstack.aws.api.events import InvalidEventPatternException from localstack.utils.event_matcher import matches_event @@ -19,46 +18,16 @@ EVENT_STR = json.dumps(EVENT_DICT) -@pytest.fixture -def event_rule_engine(monkeypatch): - """Fixture to control EVENT_RULE_ENGINE config""" - - def _set_engine(engine: str): - monkeypatch.setattr(config, "EVENT_RULE_ENGINE", engine) - - return _set_engine - - -@pytest.mark.skip(reason="jpype conflict") -def test_matches_event_with_java_engine_strings(event_rule_engine): - """Test Java engine with string inputs (EventBridge case)""" - event_rule_engine("java") +def test_matches_event_strings(): assert matches_event(EVENT_PATTERN_STR, EVENT_STR) -@pytest.mark.skip(reason="jpype conflict") -def test_matches_event_with_java_engine_dicts(event_rule_engine): - """Test Java engine with dict inputs (ESM/Pipes case)""" - event_rule_engine("java") - assert matches_event(EVENT_PATTERN_DICT, EVENT_DICT) - - -def test_matches_event_with_python_engine_strings(event_rule_engine): - """Test Python engine with string inputs""" - event_rule_engine("python") - assert matches_event(EVENT_PATTERN_STR, EVENT_STR) - - -def test_matches_event_with_python_engine_dicts(event_rule_engine): - """Test Python engine with dict inputs""" - event_rule_engine("python") +def test_matches_event_dicts(): assert matches_event(EVENT_PATTERN_DICT, EVENT_STR) -@pytest.mark.skip(reason="jpype conflict") -def test_matches_event_mixed_inputs(event_rule_engine): +def test_matches_event_mixed_inputs(): """Test with mixed string/dict inputs""" - event_rule_engine("java") assert matches_event(EVENT_PATTERN_STR, EVENT_DICT) assert matches_event(EVENT_PATTERN_DICT, EVENT_STR) @@ -69,20 +38,9 @@ def test_matches_event_non_matching_pattern(): assert not matches_event(non_matching_pattern, EVENT_DICT) -@pytest.mark.parametrize("engine", ("python", "java")) -def test_matches_event_invalid_json(event_rule_engine, engine): +def test_matches_event_invalid_json(): """Test with invalid JSON strings""" - - if engine == "java": - # this lets the exception bubble up to the provider, when AWS returns a proper exception, it should be fixed - exception_type = json.JSONDecodeError - # do not re-enable this test, enabling jpype here will break StepFunctions - pytest.skip("jpype conflict") - else: - exception_type = InvalidEventPatternException - - event_rule_engine(engine) - with pytest.raises(exception_type): + with pytest.raises(InvalidEventPatternException): matches_event("{invalid-json}", EVENT_STR) diff --git a/tests/unit/utils/test_id_generator.py b/tests/unit/utils/test_id_generator.py index 74024d4bf570e..8cdcb9bbbfd00 100644 --- a/tests/unit/utils/test_id_generator.py +++ b/tests/unit/utils/test_id_generator.py @@ -134,7 +134,7 @@ def test_custom_id_context_manager(default_resource_identifier): def test_custom_id_context_manager_exception_handling(default_resource_identifier): custom_id = "set_id" - with pytest.raises(Exception): + with pytest.raises(Exception): # noqa: B017 with localstack_id_manager.custom_id(default_resource_identifier, custom_id): assert default_resource_identifier.generate() == custom_id raise Exception() diff --git a/tests/unit/utils/test_json.py b/tests/unit/utils/test_json.py index 7680ab7793b61..4e2ed844e086c 100644 --- a/tests/unit/utils/test_json.py +++ b/tests/unit/utils/test_json.py @@ -1,9 +1,27 @@ import json -from localstack.utils.json import BytesEncoder +from localstack.utils.json import BytesEncoder, assign_to_path def test_json_encoder(): payload = {"foo": b"foobar"} result = json.dumps(payload, cls=BytesEncoder) assert result == '{"foo": "Zm9vYmFy"}' + + +def test_assign_to_path_single_path(): + target = {} + assign_to_path(target, "a", "bar") + assert target == {"a": "bar"} + + +def test_assign_multi_nested_path(): + target = {} + assign_to_path(target, "a.b.foo", "bar") + assert target == {"a": {"b": {"foo": "bar"}}} + + +def test_assign_to_path_mixed_delimiters(): + target = {} + assign_to_path(target, "a.b/c", "d", delimiter="/") + assert target == {"a.b": {"c": "d"}} diff --git a/tests/unit/utils/test_net_utils.py b/tests/unit/utils/test_net_utils.py index 60b86eca3ae45..a38ef200e860f 100644 --- a/tests/unit/utils/test_net_utils.py +++ b/tests/unit/utils/test_net_utils.py @@ -23,6 +23,10 @@ ) +class TestPortRange(PortRange): + pass + + @markers.skip_offline def test_resolve_hostname(): assert "127." in resolve_hostname(LOCALHOST) @@ -117,6 +121,14 @@ def test_subrange(): assert r.is_port_reserved(50005) +def test_subrange_from_subclass(): + r = TestPortRange(1000, 5000) + sr = r.subrange(1000, 2000) + + assert isinstance(sr, TestPortRange) + assert sr.as_range() == range(1000, 2001) + + def test_get_free_tcp_port_range_fails_if_reserved(monkeypatch): mock = MagicMock() mock.return_value = True diff --git a/tests/unit/utils/test_objects.py b/tests/unit/utils/test_objects.py index d981b99b59279..3057c6be7c2e2 100644 --- a/tests/unit/utils/test_objects.py +++ b/tests/unit/utils/test_objects.py @@ -22,7 +22,7 @@ def foo(self): assert instance1 assert BaseClass.get("c1") == instance1 assert instance1.foo() == "bar" - with pytest.raises(Exception): + with pytest.raises(NotImplementedError): assert BaseClass.get("c2") class C2(BaseClass): diff --git a/tests/unit/utils/test_patch.py b/tests/unit/utils/test_patch.py index 3b6d2685e4a17..60c65e4105cd6 100644 --- a/tests/unit/utils/test_patch.py +++ b/tests/unit/utils/test_patch.py @@ -228,3 +228,67 @@ def do_echo(self, arg): @patch(MyEchoer.new_echo) def new_echo(self, *args): pass + + +def test_idempotency(): + def monkey(self, *args): + return f"monkey: {args[-1]}" + + with Patch.function(MyEchoer.do_echo, monkey) as patch_1: + patch_1.apply() + + assert len([p for p in Patch.applied_patches if p is patch_1]) == 1 + + +def test_multiple_patches_same_target_sequentially(): + def _new_echo(fn, *args): + return fn(*args) + " new" + + def _newer_echo(fn, *args): + return fn(*args) + " newer" + + with Patch.function(echo, _new_echo): + with Patch.function(echo, _newer_echo): + assert echo("foo") == "echo: foo new newer" + + assert echo("foo") == "echo: foo" + + +@pytest.mark.xfail(reason="Not yet working: it removes all patches") +def test_multiple_patches_same_target_sequentially_undone(): + def _new_echo(fn, *args): + return fn(*args) + " new" + + def _newer_echo(fn, *args): + return fn(*args) + " newer" + + patch_1 = Patch.function(echo, _new_echo) + patch_1.apply() + assert echo("foo") == "echo: foo new" + + patch_2 = Patch.function(echo, _newer_echo) + patch_2.apply() + assert echo("foo") == "echo: foo new newer" + + patch_1.undo() + assert echo("foo") == "echo: foo newer" + + +@pytest.mark.xfail(reason="Not yet working: it only applies latest") +def test_multiple_patches_same_target_apply_concurrently(): + def test_echo(arg): + return f"echo: {arg}" + + def _new_echo(fn, *args): + return fn(*args) + " new" + + def _newer_echo(fn, *args): + return fn(*args) + " newer" + + patch1 = Patch.function(test_echo, _new_echo) + patch2 = Patch.function(test_echo, _newer_echo) + + patch1.apply() + patch2.apply() + + assert test_echo("foo") == "echo: foo new newer" diff --git a/tests/unit/utils/test_scheduler.py b/tests/unit/utils/test_scheduler.py index 82bd0f8ffe5a6..7c58225e9afd0 100644 --- a/tests/unit/utils/test_scheduler.py +++ b/tests/unit/utils/test_scheduler.py @@ -1,7 +1,6 @@ import threading import time from concurrent.futures.thread import ThreadPoolExecutor -from typing import Tuple import pytest @@ -13,8 +12,8 @@ class DummyTask: def __init__(self, fn=None) -> None: super().__init__() self.i = 0 - self.invocations = list() - self.completions = list() + self.invocations = [] + self.completions = [] self.fn = fn def __call__(self, *args, **kwargs): @@ -40,7 +39,7 @@ def dispatcher(): class TestScheduler: @staticmethod - def create_and_start(dispatcher) -> Tuple[Scheduler, threading.Thread]: + def create_and_start(dispatcher) -> tuple[Scheduler, threading.Thread]: scheduler = Scheduler(executor=dispatcher) thread = threading.Thread(target=scheduler.run) thread.start() diff --git a/tests/unit/utils/test_sync.py b/tests/unit/utils/test_sync.py index a1cda2ed38d1d..0eda2486f7784 100644 --- a/tests/unit/utils/test_sync.py +++ b/tests/unit/utils/test_sync.py @@ -1,6 +1,8 @@ import threading -from localstack.utils.sync import SynchronizedDefaultDict +import pytest + +from localstack.utils.sync import Once, SynchronizedDefaultDict, once_func def test_synchronized_defaultdict(): @@ -17,3 +19,166 @@ def test_synchronized_defaultdict(): with d["a"]: assert isinstance(d["a"], type(threading.RLock())) + + +class TestOnce: + def test_executes_only_once(self): + once = Once() + + res = [] + + def fn(): + res.append(1) + + assert once.do(fn) is None + assert once.do(fn) is None + assert len(res) == 1 + + def test_exception_propagates(self): + once = Once() + + res = [] + + def error_fn(): + res.append(1) + raise ValueError("oops") + + # Only the first call raises an exception + with pytest.raises(ValueError, match="oops"): + once.do(error_fn) + + once.do(error_fn) # No exception raised + + assert len(res) == 1 + + def test_different_functions(self): + once = Once() + + res = [] + fn_1 = lambda: res.append(1) # noqa + fn_2 = lambda: res.append(2) # noqa + + # Only the first call to Once is run + once.do(fn_1) + once.do(fn_2) + + assert len(res) == 1 + assert res[0] == 1 + + +class TestOnceDecorator: + def test_executes_only_once(self): + counter = [] + + @once_func + def increment(): + counter.append(1) + return sum(counter) + + result1 = increment() + result2 = increment() + result3 = increment() + + assert len(counter) == 1 + assert result1 == 1 + assert result2 == 1 + assert result3 == 1 + + def test_with_arguments(self): + calls = [] + + @once_func + def add(a, b): + calls.append((a, b)) + return a + b + + result1 = add(2, 3) + result2 = add(5, 7) + result3 = add(1, 1) + + assert len(calls) == 1 + assert calls[0] == (2, 3) + assert result1 == 5 + assert result2 == 5 + assert result3 == 5 + + def test_exception_reraises(self): + call_count = [] + + @once_func + def failing_function(): + call_count.append(1) + raise ValueError("Something went wrong") + + with pytest.raises(ValueError, match="Something went wrong"): + failing_function() + + with pytest.raises(ValueError, match="Something went wrong"): + failing_function() + + with pytest.raises(ValueError, match="Something went wrong"): + failing_function() + + assert len(call_count) == 1 + + def test_none_return_value(self): + calls = [] + + @once_func + def returns_none(): + calls.append(1) + return None + + result1 = returns_none() + result2 = returns_none() + + assert len(calls) == 1 + assert result1 is None + assert result2 is None + + def test_preserves_function_metadata(self): + @once_func + def documented_function(): + """This is a docstring.""" + return 42 + + assert documented_function.__name__ == "documented_function" + assert documented_function.__doc__ == "This is a docstring." + + def test_multiple_decorated_functions(self): + counter1 = [] + counter2 = [] + + @once_func + def function1(): + counter1.append(1) + return "func1" + + @once_func + def function2(): + counter2.append(1) + return "func2" + + assert function1() == "func1" + assert function1() == "func1" + assert function2() == "func2" + assert function2() == "func2" + + assert len(counter1) == 1 + assert len(counter2) == 1 + + def test_with_kwargs(self): + calls = [] + + @once_func + def with_kwargs(a, b=10, **kwargs): + calls.append((a, b, kwargs)) + return "result" + + result1 = with_kwargs(1, b=20, extra="data") + result2 = with_kwargs(5) + + assert len(calls) == 1 + assert calls[0] == (1, 20, {"extra": "data"}) + assert result1 == "result" + assert result2 == "result" diff --git a/tests/unit/utils/test_tags.py b/tests/unit/utils/test_tags.py new file mode 100644 index 0000000000000..dcde947e97258 --- /dev/null +++ b/tests/unit/utils/test_tags.py @@ -0,0 +1,18 @@ +from localstack.utils import tagging + + +def test_convert_tag_list_to_dictionary(): + tags_list = [{"Key": "key", "Value": "value"}] + assert tagging.tag_list_to_map(tags_list) == {"key": "value"} + tags_list = [{"key": "key", "value": "value"}] + assert tagging.tag_list_to_map(tags_list, key_field="key", value_field="value") == { + "key": "value" + } + + +def test_convert_tag_dictionary_to_list(): + tags_dict = {"key": "value"} + assert tagging.tag_map_to_list(tags_dict) == [{"Key": "key", "Value": "value"}] + assert tagging.tag_map_to_list(tags_dict, key_field="key", value_field="value") == [ + {"key": "key", "value": "value"} + ] diff --git a/tests/unit/utils/test_threads.py b/tests/unit/utils/test_threads.py new file mode 100644 index 0000000000000..5ea39264ff771 --- /dev/null +++ b/tests/unit/utils/test_threads.py @@ -0,0 +1,62 @@ +import threading + +from localstack.utils.threads import ( + TMP_THREADS, + FuncThread, + cleanup_threads_and_processes, + start_thread, + start_worker_thread, +) + + +class TestThreads: + class TestStartThread: + def test_start_thread_returns_a_func_thread(self): + def examplefunc(*args): + pass + + thread = start_thread(examplefunc) + + assert isinstance(thread, FuncThread) + assert thread.name.startswith("examplefunc-") + assert thread in TMP_THREADS + + def test_start_thread_with_custom_name(self): + thread = start_thread(lambda: None, name="somefunc") + + assert thread.name.startswith("somefunc-") + + class TestStartWorkerThread: + def test_start_worker_thread_returns_a_func_thread(self): + thread = start_worker_thread(lambda: None) + + assert isinstance(thread, FuncThread) + assert thread.name.startswith("start_worker_thread-") + assert thread not in TMP_THREADS + + def test_start_worker_thread_with_custom_name(self): + thread = start_worker_thread(lambda: None, name="somefunc") + assert thread.name.startswith("somefunc-") + + def test_cleanup_threads_and_processes_calls_shutdown_hooks(self): + started = threading.Event() + done = threading.Event() + + # Note: we're extending FuncThread here to make sure we have access to `_stop_event` + # Regular users would use `start_thread` instead + class ThreadTest(FuncThread): + def __init__(self) -> None: + super().__init__(self.run_method) + + def run_method(self, *args): + started.set() + # thread waits until it is stopped + self._stop_event.wait() + done.set() + + test_thread = ThreadTest() + TMP_THREADS.append(test_thread) + test_thread.start() + assert started.wait(timeout=2) + cleanup_threads_and_processes() + assert done.wait(timeout=2)